diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 986b3cb51e..46121bb732 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,35 +30,37 @@ jobs: fail-fast: false matrix: include: - - name: test-unit-asyncify (1/15) + - name: test-unit-asyncify (1/16) target: test - - name: test-unit-asyncify (2/15) + - name: test-unit-asyncify (2/16) target: test-php - - name: test-unit-asyncify (3/15) + - name: test-unit-asyncify (3/16) + target: test-php-networking + - name: test-unit-asyncify (4/16) target: test-php-request-handler-files - - name: test-unit-asyncify (4/15) + - name: test-unit-asyncify (5/16) target: test-php-request-handler-requests - - name: test-unit-asyncify (5/15) + - name: test-unit-asyncify (6/16) target: test-php-asyncify-file-get-contents-http - - name: test-unit-asyncify (6/15) + - name: test-unit-asyncify (7/16) target: test-php-asyncify-file-get-contents-https - - name: test-unit-asyncify (7/15) + - name: test-unit-asyncify (8/16) target: test-php-asyncify-fopen-http - - name: test-unit-asyncify (8/15) + - name: test-unit-asyncify (9/16) target: test-php-asyncify-fopen-https - - name: test-unit-asyncify (9/15) + - name: test-unit-asyncify (10/16) target: test-php-asyncify-fsockopen-http - - name: test-unit-asyncify (10/15) + - name: test-unit-asyncify (11/16) target: test-php-asyncify-fsockopen-https - - name: test-unit-asyncify (11/15) + - name: test-unit-asyncify (12/16) target: test-php-asyncify-gethostbyname-http - - name: test-unit-asyncify (12/15) + - name: test-unit-asyncify (13/16) target: test-php-asyncify-gethostbyname-https - - name: test-unit-asyncify (13/15) + - name: test-unit-asyncify (14/16) target: test-php-asyncify-mysqli-http - - name: test-unit-asyncify (14/15) + - name: test-unit-asyncify (15/16) target: test-php-asyncify-mysqli-https - - name: test-unit-sqlite3 (15/15) + - name: test-unit-asyncify (16/16) target: test-php-asyncify-sqlite3 name: ${{ matrix.name }} services: @@ -88,6 +90,24 @@ jobs: MYSQL_DATABASE: test_db MYSQL_USER: user MYSQL_PASSWORD: password + test-unit-jspi: + runs-on: ubuntu-latest + needs: [lint-and-typecheck] + strategy: + fail-fast: false + matrix: + include: + - name: test-unit-jspi (1/1) + target: test-php-dynamic-loading-jspi + name: ${{ matrix.name }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/actions/prepare-playground + with: + node-version: 23 + - run: node --expose-gc node_modules/nx/bin/nx affected --target=${{ matrix.target }} # Most of these tests pass locally but the process is crashing # on the CI runner. # diff --git a/packages/php-wasm/compile/Makefile b/packages/php-wasm/compile/Makefile index 09a01acf05..905c94aaca 100644 --- a/packages/php-wasm/compile/Makefile +++ b/packages/php-wasm/compile/Makefile @@ -264,7 +264,6 @@ libintl/asyncify/dist/root/lib/lib/libintl.a: base-image docker cp $$(docker create playground-php-wasm:libintl):/root/lib/include ./libintl/asyncify/dist/root/lib docker cp $$(docker create playground-php-wasm:libintl):/root/lib/data/. ./libintl/ - libintl_jspi: libintl/jspi/dist/root/lib/lib/libintl.a libintl/jspi/dist/root/lib/lib/libintl.a: base-image mkdir -p ./libintl/jspi/dist/root/lib diff --git a/packages/php-wasm/compile/base-image/Dockerfile b/packages/php-wasm/compile/base-image/Dockerfile index 038ab80e6c..30a542a42e 100644 --- a/packages/php-wasm/compile/base-image/Dockerfile +++ b/packages/php-wasm/compile/base-image/Dockerfile @@ -67,6 +67,8 @@ RUN chmod a+x /root/replace.sh COPY ./replace-across-lines.sh /root/replace-across-lines.sh RUN chmod a+x /root/replace-across-lines.sh +COPY ./emcc-for-php-wasm.sh /root/emcc-for-php-wasm.sh + # Patch emcc to allow skipping flags and passing additional flags using environment variables. # # We're compiling libraries statically using emscripten's -sSIDE_MODULE. It differs from the usual unix @@ -85,17 +87,10 @@ RUN chmod a+x /root/replace-across-lines.sh # │ skip those flags when │ │ add these flags when │ # │ calling emcc │ │ calling emcc │ # └────────────────────────┘ └────────────────────────┘ -RUN cp /root/emsdk/upstream/emscripten/emcc /root/emsdk/upstream/emscripten/emcc2 && \ - cp /root/emsdk/upstream/emscripten/emcc.py /root/emsdk/upstream/emscripten/emcc2.py && \ - echo $'#!/bin/bash\n\ -for arg do shift\n\ - [[ " ${EMCC_SKIP[*]} " =~ " ${arg} " ]] && continue \n\ - set -- "$@" "$arg" \n\ -done\n\ -# Passing extra flags breaks the version check \n\ -if [[ "$@" == "-v" ]]; then\n\ - export EMCC_FLAGS=""\n\ -fi\n\ -/root/emsdk/upstream/emscripten/emcc2 "$@" $EMCC_FLAGS \n' > /root/emsdk/upstream/emscripten/emcc && \ +RUN <= 0; i--)); do + # Skip empty args because array may be sparse + [[ -z "${args[$i]:-}" ]] && continue + + arg=${args[i]} + if ( + [[ "$arg" =~ ^-l([a-z]|[A-Z]|[0-9]|[\-_])+$ ]] || + [[ "$arg" =~ (^|/)lib([a-z]|[A-Z]|[0-9]|[\-_])+\.a$ ]] + ); then + if [[ -v seen_libs["$arg"] ]]; then + unset 'args[i]' + else + seen_libs["$arg"]=1 + fi + fi +done + +/root/emsdk/upstream/emscripten/emcc2 "${args[@]}" ${EMCC_FLAGS:-} diff --git a/packages/php-wasm/compile/libintl/Dockerfile b/packages/php-wasm/compile/libintl/Dockerfile index e0799201f8..80b2837f2e 100644 --- a/packages/php-wasm/compile/libintl/Dockerfile +++ b/packages/php-wasm/compile/libintl/Dockerfile @@ -31,6 +31,8 @@ RUN set -euxo pipefail && \ cd /root/icu/source && \ mkdir -p /root/lib && \ source /root/emsdk/emsdk_env.sh && \ + export CFLAGS="-fPIC" && \ + export CXXFLAGS="-fPIC" && \ emconfigure ./configure \ --build=i386-pc-linux-gnu \ --target=wasm32-unknown-emscripten \ @@ -41,8 +43,8 @@ RUN set -euxo pipefail && \ --disable-shared \ --enable-static && \ export JSPI_FLAGS=$(if [ "$JSPI" = "1" ]; then echo "-sSUPPORT_LONGJMP=wasm -fwasm-exceptions"; else echo ""; fi) && \ - EMCC_FLAGS=" -D__x86_64__ -sSIDE_MODULE $JSPI_FLAGS " emmake make -j"$(nproc)" && \ - EMCC_FLAGS=" -D__x86_64__ -sSIDE_MODULE $JSPI_FLAGS " emmake make install -i; + EMCC_FLAGS=" -D__x86_64__ -fPIC -sSIDE_MODULE $JSPI_FLAGS " emmake make -j"$(nproc)" && \ + EMCC_FLAGS=" -D__x86_64__ -fPIC -sSIDE_MODULE $JSPI_FLAGS " emmake make install; RUN set -euxo pipefail && \ diff --git a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/icu/74.2/pkgdata.inc b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/icu/74.2/pkgdata.inc index b1ea2fe2e1..d6259d9f83 100644 --- a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/icu/74.2/pkgdata.inc +++ b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/icu/74.2/pkgdata.inc @@ -4,9 +4,9 @@ SOBJ=so A=a LIBPREFIX=lib LIB_EXT_ORDER=.74.2 -COMPILE=/root/emsdk/upstream/emscripten/emcc -ffunction-sections -fdata-sections -D_REENTRANT -DU_HAVE_ELF_H=1 -DU_HAVE_STRTOD_L=1 -DU_HAVE_XLOCALE_H=1 -DU_HAVE_STRING_VIEW=1 -DU_ATTRIBUTE_DEPRECATED= -O2 -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -c +COMPILE=/root/emsdk/upstream/emscripten/emcc -ffunction-sections -fdata-sections -D_REENTRANT -DU_HAVE_ELF_H=1 -DU_HAVE_STRTOD_L=1 -DU_HAVE_XLOCALE_H=1 -DU_HAVE_STRING_VIEW=1 -DU_ATTRIBUTE_DEPRECATED= -fPIC -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -c LIBFLAGS=-I/root/lib/include -DPIC -fPIC -GENLIB=/root/emsdk/upstream/emscripten/emcc -O2 -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -Wl,--gc-sections -shared -Wl,-Bsymbolic +GENLIB=/root/emsdk/upstream/emscripten/emcc -fPIC -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -Wl,--gc-sections -shared -Wl,-Bsymbolic LDICUDTFLAGS=-nodefaultlibs -nostdlib LD_SONAME=-Wl,-soname -Wl, RPATH_FLAGS= diff --git a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicui18n.a b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicui18n.a index 82cf85c081..d1644f88f5 100755 Binary files a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicui18n.a and b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicui18n.a differ diff --git a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuio.a b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuio.a index 697dd3e1df..c050fc7a09 100755 Binary files a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuio.a and b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuio.a differ diff --git a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutest.a b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutest.a index e243fce159..04e869bdbb 100755 Binary files a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutest.a and b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutest.a differ diff --git a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutu.a b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutu.a index 30f085c937..eb448ac8c9 100755 Binary files a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutu.a and b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicutu.a differ diff --git a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuuc.a b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuuc.a index 52289c9bbf..192f0d4b41 100755 Binary files a/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuuc.a and b/packages/php-wasm/compile/libintl/asyncify/dist/root/lib/lib/libicuuc.a differ diff --git a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/icu/74.2/pkgdata.inc b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/icu/74.2/pkgdata.inc index b1ea2fe2e1..d6259d9f83 100644 --- a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/icu/74.2/pkgdata.inc +++ b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/icu/74.2/pkgdata.inc @@ -4,9 +4,9 @@ SOBJ=so A=a LIBPREFIX=lib LIB_EXT_ORDER=.74.2 -COMPILE=/root/emsdk/upstream/emscripten/emcc -ffunction-sections -fdata-sections -D_REENTRANT -DU_HAVE_ELF_H=1 -DU_HAVE_STRTOD_L=1 -DU_HAVE_XLOCALE_H=1 -DU_HAVE_STRING_VIEW=1 -DU_ATTRIBUTE_DEPRECATED= -O2 -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -c +COMPILE=/root/emsdk/upstream/emscripten/emcc -ffunction-sections -fdata-sections -D_REENTRANT -DU_HAVE_ELF_H=1 -DU_HAVE_STRTOD_L=1 -DU_HAVE_XLOCALE_H=1 -DU_HAVE_STRING_VIEW=1 -DU_ATTRIBUTE_DEPRECATED= -fPIC -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -c LIBFLAGS=-I/root/lib/include -DPIC -fPIC -GENLIB=/root/emsdk/upstream/emscripten/emcc -O2 -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -Wl,--gc-sections -shared -Wl,-Bsymbolic +GENLIB=/root/emsdk/upstream/emscripten/emcc -fPIC -std=c11 -Wall -pedantic -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -Wl,--gc-sections -shared -Wl,-Bsymbolic LDICUDTFLAGS=-nodefaultlibs -nostdlib LD_SONAME=-Wl,-soname -Wl, RPATH_FLAGS= diff --git a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicui18n.a b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicui18n.a index 82cf85c081..d1644f88f5 100755 Binary files a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicui18n.a and b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicui18n.a differ diff --git a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuio.a b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuio.a index 697dd3e1df..c050fc7a09 100755 Binary files a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuio.a and b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuio.a differ diff --git a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutest.a b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutest.a index 6f00f0adb9..317e316942 100755 Binary files a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutest.a and b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutest.a differ diff --git a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutu.a b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutu.a index 30f085c937..eb448ac8c9 100755 Binary files a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutu.a and b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicutu.a differ diff --git a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuuc.a b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuuc.a index 52289c9bbf..192f0d4b41 100755 Binary files a/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuuc.a and b/packages/php-wasm/compile/libintl/jspi/dist/root/lib/lib/libicuuc.a differ diff --git a/packages/php-wasm/compile/libsqlite3/<7.4/pdo_sqlite_config.m4 b/packages/php-wasm/compile/libsqlite3/<7.4/pdo_sqlite_config.m4 new file mode 100644 index 0000000000..b68c06109d --- /dev/null +++ b/packages/php-wasm/compile/libsqlite3/<7.4/pdo_sqlite_config.m4 @@ -0,0 +1,86 @@ +dnl config.m4 for extension pdo_sqlite +dnl vim:et:sw=2:ts=2: + +PHP_ARG_WITH(pdo-sqlite, for sqlite 3 support for PDO, +[ --without-pdo-sqlite[=DIR] + PDO: sqlite 3 support. DIR is the sqlite base + install directory [BUNDLED]], $PHP_PDO) + +if test "$PHP_PDO_SQLITE" != "no"; then + + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + ifdef([PHP_CHECK_PDO_INCLUDES], + [ + PHP_CHECK_PDO_INCLUDES + ],[ + AC_MSG_CHECKING([for PDO includes]) + if test -f $abs_srcdir/include/php/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $abs_srcdir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $phpincludedir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$phpincludedir/ext + else + AC_MSG_ERROR([Cannot find php_pdo_driver.h.]) + fi + AC_MSG_RESULT($pdo_cv_inc_path) + ]) + + php_pdo_sqlite_sources_core="pdo_sqlite.c sqlite_driver.c sqlite_statement.c" + + SEARCH_PATH="$PHP_PDO_SQLITE /root/lib" # you might want to change this + SEARCH_FOR="/include/sqlite3.h" # you most likely want to change this + if test -r $PHP_PDO_SQLITE/$SEARCH_FOR; then # path given as parameter + PDO_SQLITE_DIR=$PHP_PDO_SQLITE + else # search default path list + AC_MSG_CHECKING([for sqlite3 files in default path]) + for i in $SEARCH_PATH ; do + if test -r $i/$SEARCH_FOR; then + PDO_SQLITE_DIR=$i + AC_MSG_RESULT(found in $i) + fi + done + fi + if test -z "$PDO_SQLITE_DIR"; then + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([Please reinstall the sqlite3 distribution]) + fi + + PHP_ADD_INCLUDE($PDO_SQLITE_DIR/include) + + LIBNAME=sqlite3 + LIBSYMBOL=sqlite3_open + + PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + [ + PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PDO_SQLITE_DIR/$PHP_LIBDIR, PDO_SQLITE_SHARED_LIBADD) + AC_DEFINE(HAVE_PDO_SQLITELIB,1,[ ]) + ],[ + AC_MSG_ERROR([wrong sqlite lib version or lib not found]) + ],[ + -L$PDO_SQLITE_DIR/$PHP_LIBDIR -lm + ]) + PHP_CHECK_LIBRARY(sqlite3,sqlite3_key,[ + AC_DEFINE(HAVE_SQLITE3_KEY,1, [have commercial sqlite3 with crypto support]) + ]) + PHP_CHECK_LIBRARY(sqlite3,sqlite3_close_v2,[ + AC_DEFINE(HAVE_SQLITE3_CLOSE_V2, 1, [have sqlite3_close_v2]) + ]) + PHP_CHECK_LIBRARY(sqlite3,sqlite3_column_table_name,[ + AC_DEFINE(HAVE_SQLITE3_COLUMN_TABLE_NAME, 1, [have sqlite3_column_table_name]) + ]) + + PHP_SUBST(PDO_SQLITE_SHARED_LIBADD) + PHP_NEW_EXTENSION(pdo_sqlite, $php_pdo_sqlite_sources_core, $ext_shared,,-I$pdo_cv_inc_path) + + dnl Solaris fix + PHP_CHECK_LIBRARY(rt, fdatasync, [PHP_ADD_LIBRARY(rt,, PDO_SQLITE_SHARED_LIBADD)]) + + ifdef([PHP_ADD_EXTENSION_DEP], + [ + PHP_ADD_EXTENSION_DEP(pdo_sqlite, pdo) + ]) +fi diff --git a/packages/php-wasm/compile/libsqlite3/<7.4/sqlite_config0.m4 b/packages/php-wasm/compile/libsqlite3/<7.4/sqlite_config0.m4 new file mode 100644 index 0000000000..c48ad543b4 --- /dev/null +++ b/packages/php-wasm/compile/libsqlite3/<7.4/sqlite_config0.m4 @@ -0,0 +1,70 @@ +dnl config.m4 for extension sqlite3 +dnl vim:et:ts=2:sw=2 + +PHP_ARG_WITH(sqlite3, whether to enable the SQLite3 extension, +[ --without-sqlite3[=DIR] Do not include SQLite3 support. DIR is the prefix to + SQLite3 installation directory.], yes) + +if test $PHP_SQLITE3 != "no"; then + sqlite3_extra_sources="" + PHP_SQLITE3_CFLAGS=" -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 " + + dnl when running phpize enable_maintainer_zts is not available + if test -z "$enable_maintainer_zts"; then + if test -f "$phpincludedir/main/php_config.h"; then + ZTS=`grep '#define ZTS' $phpincludedir/main/php_config.h|$SED 's/#define ZTS//'` + if test "$ZTS" -eq "1"; then + enable_maintainer_zts="yes" + fi + fi + fi + + AC_MSG_CHECKING([for sqlite3 files in default path]) + for i in $PHP_SQLITE3 /root/lib; do + if test -r $i/include/sqlite3.h; then + SQLITE3_DIR=$i + AC_MSG_RESULT(found in $i) + break + fi + done + + if test -z "$SQLITE3_DIR"; then + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([Please reinstall the sqlite distribution from http://www.sqlite.org]) + fi + + AC_MSG_CHECKING([for SQLite 3.3.9+]) + PHP_CHECK_LIBRARY(sqlite3, sqlite3_prepare_v2, [ + AC_MSG_RESULT(found) + PHP_ADD_LIBRARY_WITH_PATH(sqlite3, $SQLITE3_DIR/$PHP_LIBDIR, SQLITE3_SHARED_LIBADD) + PHP_ADD_INCLUDE($SQLITE3_DIR/include) + ],[ + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([Please install SQLite 3.3.9 first or check libsqlite3 is present]) + ],[ + -L$SQLITE3_DIR/$PHP_LIBDIR -lm + ]) + + PHP_CHECK_LIBRARY(sqlite3,sqlite3_key,[ + AC_DEFINE(HAVE_SQLITE3_KEY, 1, [have commercial sqlite3 with crypto support]) + ]) + PHP_CHECK_LIBRARY(sqlite3,sqlite3_column_table_name,[ + AC_DEFINE(SQLITE_ENABLE_COLUMN_METADATA, 1, [have sqlite3 with column metadata enabled]) + ]) + PHP_CHECK_LIBRARY(sqlite3,sqlite3_errstr,[ + AC_DEFINE(HAVE_SQLITE3_ERRSTR, 1, [have sqlite3_errstr function]) + ]) + + PHP_CHECK_LIBRARY(sqlite3,sqlite3_load_extension, + [], + [AC_DEFINE(SQLITE_OMIT_LOAD_EXTENSION, 1, [have sqlite3 with extension support]) + ]) + + AC_DEFINE(HAVE_SQLITE3,1,[ ]) + + sqlite3_sources="sqlite3.c $sqlite3_extra_sources" + + PHP_NEW_EXTENSION(sqlite3, $sqlite3_sources, $ext_shared,,$PHP_SQLITE3_CFLAGS) + PHP_ADD_BUILD_DIR([$ext_builddir/libsqlite]) + PHP_SUBST(SQLITE3_SHARED_LIBADD) +fi diff --git a/packages/php-wasm/compile/php/Dockerfile b/packages/php-wasm/compile/php/Dockerfile index 1e90b61f7b..c05ae6a74e 100644 --- a/packages/php-wasm/compile/php/Dockerfile +++ b/packages/php-wasm/compile/php/Dockerfile @@ -12,8 +12,8 @@ ARG PHP_VERSION # Clone PHP RUN git clone https://github.com/php/php-src.git php-src \ - --branch PHP-$PHP_VERSION \ - --single-branch \ + --branch PHP-$PHP_VERSION \ + --single-branch \ --depth 1; # Work around memory leak due to PHP using Emscripten's incomplete mmap/munmap support @@ -25,13 +25,6 @@ COPY ./php-wasm-dns-polyfill/ /root/php-src/ext/dns_polyfill # Polyfill for post_message_to_js functions COPY ./php-post-message-to-js/ /root/php-src/ext/post_message_to_js -# Work around to add ICU FLAGS to intl below 7.3 to inject -fPIC. -RUN if [[ "${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -eq "2" ]]; then \ - sed -i "s/\$ICU_INCS/\$ICU_INCS \$ICU_CFLAGS/" /root/php-src/ext/intl/config.m4; \ - fi; - -RUN cd php-src && ./buildconf --force - # Bring in the libraries RUN mkdir -p /root/lib/include /root/lib/lib /libs COPY ./bison2.7/dist/ /libs/bison2.7 @@ -71,6 +64,25 @@ else \ cp -r /root/builds-libopenssl/asyncify/dist/root/lib /libs/libopenssl; \ fi + +# Workaround to add ICU FLAGS to intl below 7.3 to inject -fPIC. +RUN if [[ "${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -eq "2" ]]; then \ + sed -i "s/\$ICU_INCS/\$ICU_INCS \$ICU_CFLAGS/" /root/php-src/ext/intl/config.m4; \ + fi; + +# Workaround to unbundle sqlite from PHP <7.4. +# Prior to 7.4, PHP bundled sqlite internally, +# leading compilation to fail with : wasm-ld: error: duplicate symbol: [sqlite3_xxx] +# Unbundling sqlite manually by copying files from 7.4 solves the issue +# Related discussion: https://github.com/WordPress/wordpress-playground/pull/2248#issuecomment-2988044885 +# And 7.4 commit reference: https://github.com/php/php-src/commit/6083a38 +RUN if [[ "${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -le "3" ]]; then \ + cp /root/builds/libsqlite3/\<7\.4/pdo_sqlite_config.m4 /root/php-src/ext/pdo_sqlite/config.m4; \ + cp /root/builds/libsqlite3/\<7\.4/sqlite_config0.m4 /root/php-src/ext/sqlite3/config0.m4; \ + fi; + +RUN cd php-src && ./buildconf --force + # Build PHP # The PHP extensions to build: @@ -100,7 +112,6 @@ ARG EMSCRIPTEN_ENVIRONMENT=web RUN touch /root/.configure-flags && \ touch /root/.emcc-php-wasm-flags && \ touch /root/.emcc-php-wasm-sources && \ - touch /root/.emcc-php-wasm-flags && \ touch /root/.EXPORTED_FUNCTIONS # Grab Bison 2.7 patch in case we need it @@ -138,8 +149,7 @@ RUN if [ "$WITH_LIBZIP" = "yes" ]; then \ cp -r /libs/libzip/1.2.0/* /root/lib; \ # https://php-legacy-docs.zend.com/manual/php5/en/zlib.installation echo -n ' --with-zlib --with-zlib-dir=/root/lib --enable-zip --with-libzip=/root/lib ' >> /root/.php-configure-flags; \ - echo -n ' -I /root/lib -I /root/lib ' >> /root/.emcc-php-wasm-flags; \ - echo -n ' /root/lib/lib/libzip.a /root/lib/lib/libz.a' >> /root/.emcc-php-wasm-sources; \ + echo -n ' /root/lib/lib/libzip.a /root/lib/lib/libz.a ' >> /root/.emcc-php-wasm-sources; \ cp /root/lib/lib/libzip/include/zipconf.h /root/lib/lib; \ cp /root/lib/lib/libzip/include/zipconf.h /root/lib/include; \ echo '#define LIBZIP_VERSION "1.2.0"' >> /root/lib/include/zipconf.h; \ @@ -147,9 +157,8 @@ RUN if [ "$WITH_LIBZIP" = "yes" ]; then \ else \ cp -r /libs/libzip/1.9.2/* /root/lib; \ # https://www.php.net/manual/en/zlib.installation.php - echo -n ' --with-zlib --with-zlib-dir=/root/lib --with-zip' >> /root/.php-configure-flags; \ - echo -n ' -I /root/lib -I /root/lib' >> /root/.emcc-php-wasm-flags; \ - echo -n ' /root/lib/lib/libzip.a /root/lib/lib/libz.a' >> /root/.emcc-php-wasm-sources; \ + echo -n ' --with-zlib --with-zlib-dir=/root/lib --with-zip ' >> /root/.php-configure-flags; \ + echo -n ' /root/lib/lib/libzip.a /root/lib/lib/libz.a ' >> /root/.emcc-php-wasm-sources; \ fi && \ /root/replace.sh 's/pharcmd=pharcmd/pharcmd=/g' /root/php-src/configure && \ /root/replace.sh 's/pharcmd_install=install-pharcmd/pharcmd_install=/g' /root/php-src/configure; \ @@ -169,6 +178,8 @@ RUN if [ "$WITH_CLI_SAPI" = "yes" ]; \ echo -n ', "_run_cli", "_wasm_add_cli_arg"' >> /root/.EXPORTED_FUNCTIONS && \ echo -n ' -DWITH_CLI_SAPI=1 ' >> /root/.emcc-php-wasm-flags && \ /root/replace.sh 's/GET_SHELL_CB\(cb\);/(cb) = php_cli_get_shell_callbacks();/g' /root/php-src/ext/readline/readline_cli.c; \ + # Avoid a symbol conflict between php_cli.c and php_embed.c + /root/replace.sh 's/\bHARDCODED_INI\b/HARDCODED_EMBED_INI/g' /root/php-src/sapi/embed/php_embed.c \ else \ echo -n ' --disable-cli ' >> /root/.php-configure-flags; \ fi; @@ -177,9 +188,8 @@ RUN if [ "$WITH_CLI_SAPI" = "yes" ]; \ RUN if [ "$WITH_LIBXML" = "yes" ]; \ then \ set -euxo pipefail;\ - echo -n ' --enable-libxml --with-libxml --with-libxml-dir=/root/lib --enable-dom --enable-xml --enable-simplexml --enable-xmlreader --enable-xmlwriter' >> /root/.php-configure-flags && \ - echo -n ' -I /root/lib -I /root/lib/include/libxml2 -lxml2' >> /root/.emcc-php-wasm-flags && \ - echo -n ' /root/lib/lib/libxml2.a' >> /root/.emcc-php-wasm-sources && \ + echo -n ' --enable-libxml --with-libxml --with-libxml-dir=/root/lib --enable-dom --enable-xml --enable-simplexml --enable-xmlreader --enable-xmlwriter ' >> /root/.php-configure-flags && \ + echo -n ' /root/lib/lib/libxml2.a ' >> /root/.emcc-php-wasm-sources && \ # Libxml check is wrong in PHP < 7.4.0. # In the regular cc it's just a warning, but in the emscripten's emcc that's an error: perl -pi.bak -e 's/char xmlInitParser/void xmlInitParser/g' /root/php-src/configure; \ @@ -194,10 +204,11 @@ RUN if [ "$WITH_LIBXML" = "yes" ]; \ # Add sqlite3 if needed RUN if [ "$WITH_SQLITE" = "yes" ]; \ then \ - echo -n ' --with-sqlite3 --enable-pdo --with-pdo-sqlite=/root/lib ' >> /root/.php-configure-flags && \ - echo -n ' -I ext/pdo_sqlite ' >> /root/.emcc-php-wasm-flags; \ - echo -n ' -lsqlite3 ' >> /root/.emcc-php-wasm-flags; \ - fi; + echo -n ' --with-sqlite3 --enable-pdo --with-pdo-sqlite=/root/lib ' >> /root/.php-configure-flags; \ + echo -n ' /root/lib/lib/libsqlite3.a ' >> /root/.emcc-php-wasm-sources; \ + else \ + echo -n ' --without-sqlite3 --disable-pdo ' >> /root/.php-configure-flags; \ + fi # Add GD if needed RUN if [ "$WITH_GD" = "yes" ]; \ @@ -208,7 +219,7 @@ RUN if [ "$WITH_GD" = "yes" ]; \ else \ echo -n ' --with-png-dir=/root/lib --with-jpeg --with-webp --with-gd --enable-gd ' >> /root/.php-configure-flags; \ fi; \ - echo -n ' -I /root/lib -I /root/install -lz -lpng16 -lwebp -lsharpyuv -ljpeg ' >> /root/.emcc-php-wasm-flags; \ + echo -n ' /root/lib/lib/libjpeg.a /root/lib/lib/libpng16.a /root/lib/lib/libwebp.a /root/lib/lib/libsharpyuv.a ' >> /root/.emcc-php-wasm-sources; \ fi; # Add openssl if needed @@ -216,7 +227,7 @@ RUN if [ "$WITH_OPENSSL" = "yes" ]; \ then \ cp -r /libs/libopenssl/* /root/lib; \ echo -n ' --with-openssl --with-openssl-dir=/root/lib ' >> /root/.php-configure-flags; \ - echo -n ' -lssl -lcrypto ' >> /root/.emcc-php-wasm-flags; \ + echo -n ' /root/lib/lib/libcrypto.a /root/lib/lib/libssl.a ' >> /root/.emcc-php-wasm-sources; \ fi; # Remove fileinfo if needed @@ -284,7 +295,7 @@ RUN if [ "$WITH_INTL" = "yes" ]; \ then \ if [ "${PHP_VERSION:0:1}" -eq "8" ] || [[ "${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -ge "2" ]]; then \ echo -n ' --enable-intl ' >> /root/.php-configure-flags; \ - echo -n ' -licuuc -licudata -licui18n -licuio ' >> /root/.emcc-php-wasm-flags; \ + echo -n ' /root/lib/lib/libicudata.a /root/lib/lib/libicui18n.a /root/lib/lib/libicuio.a /root/lib/lib/libicutest.a /root/lib/lib/libicutu.a /root/lib/lib/libicuuc.a ' >> /root/.emcc-php-wasm-sources; \ if [[ "${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -le "3" ]]; then \ /root/replace.sh 's/UBool operator/bool operator/' /root/php-src/ext/intl/breakiterator/codepointiterator_internal.h; \ @@ -297,19 +308,13 @@ RUN if [ "$WITH_INTL" = "yes" ]; \ for file in "${files[@]}"; do /root/replace.sh 's/\bFALSE\b/false/g; s/\bTRUE\b/true/g' "$file"; done; \ fi; \ fi; \ - fi; + fi # Add mbregex if needed -# PHP == 7.0 likely needs an older version of oniguruma and isn't supported for now. RUN if [ "$WITH_MBREGEX" = "yes" ] && [ "${PHP_VERSION:0:3}" != "7.0" ]; \ then \ - echo -n ' --enable-mbregex ' >> /root/.php-configure-flags; \ - # PHP >= 8.0 and 7.4 use the system oniguruma library. - # PHP < 7.4 ship their own oniguruma library. - if [[ "${PHP_VERSION:0:1}" -ge "8" ]] || [ "${PHP_VERSION:0:3}" = "7.4" ]; then \ - echo -n ' --with-onig=/root/lib ' >> /root/.php-configure-flags; \ - echo -n ' /root/lib/lib/libonig.a ' >> /root/.emcc-php-wasm-sources; \ - fi; \ + echo -n ' --enable-mbregex --with-onig=/root/lib ' >> /root/.php-configure-flags; \ + echo -n ' /root/lib/lib/libonig.a ' >> /root/.emcc-php-wasm-sources; \ else \ echo -n ' --disable-mbregex ' >> /root/.php-configure-flags; \ fi; @@ -361,8 +366,8 @@ CURL_LIBS="-I/root/lib/lib -L/root/lib/lib" \ --disable-opcache \ --disable-posix \ --enable-hash \ - --enable-static=yes \ - --enable-shared=no \ + --enable-static \ + --enable-shared \ --enable-session \ --enable-filter \ --enable-calendar \ @@ -486,7 +491,6 @@ RUN source /root/emsdk/emsdk_env.sh && \ -fdebug-compilation-dir=${DEBUG_DWARF_COMPILATION_DIR}/ \ -fdebug-prefix-map=/root/php-src/=${OUTPUT_DIR_ON_HOST}/${PHP_VERSION_ESCAPED}/php-src/ " \ # ...which means we must skip all the libraries - they will be provided in the final linking step. - EMCC_SKIP="-lz -ledit -ldl -lncurses -lzip -lpng16 -lssl -lcrypto -licuuc -licudata -licui18n -licuio -lxml2 -lc -lm -lsqlite3 /root/lib/lib/libxml2.a /root/lib/lib/libsqlite3.so /root/lib/lib/libsqlite3.a /root/lib/lib/libsqlite3.a /root/lib/lib/libpng16.so /root/lib/lib/libwebp.a /root/lib/lib/libsharpyuv.a /root/lib/lib/libjpeg.a" \ emmake make -j1 RUN cp -v /root/php-src/.libs/libphp*.la /root/lib/libphp.la @@ -538,17 +542,6 @@ RUN set -euxo pipefail; \ fi; \ fi; -# PHP < 8.0 errors out with "null function or function signature mismatch" -# unless EMULATE_FUNCTION_POINTER_CASTS is enabled. The error originates in -# the rc_dtor_func which traces back to calling the zend_list_free function. -# The signatures are the same on the face value, but the wasm runtime is not -# happy somehow. This can probably be patched in PHP, but for now we just -# enable the flag and pay the price of the additional overhead. -# https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html -RUN if [ "${PHP_VERSION:0:1}" -lt "8" ]; then \ - echo -n ' -s EMULATE_FUNCTION_POINTER_CASTS=1' >> /root/.emcc-php-wasm-flags; \ - fi - # Add ws networking proxy support if needed RUN if [ "$WITH_WS_NETWORKING_PROXY" = "yes" ]; \ then \ @@ -578,7 +571,7 @@ RUN if [ "$WITH_WS_NETWORKING_PROXY" = "yes" ]; \ # due to an async call – it means the same PHP files are loaded before # the previous request, where they're already loaded, is concluded. # TODO: Consider reverting asyncify additions before merging this PR if file locking doesn't support asyncify. -RUN export ASYNCIFY_IMPORTS=$'["_dlopen_js",\n\ +RUN export ASYNCIFY_IMPORTS=$'[\n\ "invoke_i",\n\ "invoke_ii",\n\ "invoke_iii",\n\ @@ -645,6 +638,7 @@ RUN export ASYNCIFY_IMPORTS=$'["_dlopen_js",\n\ "__fwritex",\ "__netlink_enumerate",\ "__overflow",\ +"__synccall",\ "__toread",\ "__uflow",\ "__vfprintf_internal",\ @@ -992,6 +986,8 @@ RUN export ASYNCIFY_IMPORTS=$'["_dlopen_js",\n\ "iter_wrapper_get_gc",\ "lex_scan",\ "list_entry_destructor",\ +"zend_hash_index_del",\ +"zend_list_free",\ "lockBtree",\ "lockTrace",\ "main",\ @@ -1585,6 +1581,7 @@ RUN export ASYNCIFY_IMPORTS=$'["_dlopen_js",\n\ "zend_call_known_instance_method_with_2_params",\ "zend_call_method_if_exists",\ "zend_call_method",\ +"zend_close_rsrc",\ "zend_close_rsrc_list",\ "ZEND_COUNT_SPEC_CV_UNUSED_HANDLER",\ "zend_deactivate",\ @@ -2010,20 +2007,23 @@ RUN export ASYNCIFY_IMPORTS=$'["_dlopen_js",\n\ else \ export ASYNCIFY_ONLY="$ASYNCIFY_ONLY_UNPREFIXED"; \ fi; \ + echo -n ' -s ASYNCIFY_ONLY=['$ASYNCIFY_ONLY'] '| tr -d "\n" >> /root/.emcc-php-asyncify-flags; # Build the final .wasm file RUN mkdir /root/output COPY ./php/phpwasm-emscripten-library.js /root/phpwasm-emscripten-library.js -COPY ./php/phpwasm-emscripten-library-known-undefined-functions.js /root/phpwasm-emscripten-library-known-undefined-functions.js +COPY ./php/phpwasm-emscripten-library-dynamic-linking.js /root/phpwasm-emscripten-library-dynamic-linking.js COPY ./php/phpwasm-emscripten-library-file-locking-for-node.js /root/phpwasm-emscripten-library-file-locking-for-node.js +COPY ./php/phpwasm-emscripten-library-known-undefined-functions.js /root/phpwasm-emscripten-library-known-undefined-functions.js ARG WITH_SOURCEMAPS ARG WITH_DEBUG RUN set -euxo pipefail; \ mkdir -p /build/output; \ source /root/emsdk/emsdk_env.sh; \ if [ "$WITH_JSPI" = "yes" ]; then \ - export ASYNCIFY_FLAGS=" -s ASYNCIFY=2 -sSUPPORT_LONGJMP=wasm -fwasm-exceptions -sJSPI_IMPORTS=js_open_process,js_waitpid,js_process_status,js_create_input_device,wasm_setsockopt,wasm_shutdown,wasm_close -sJSPI_EXPORTS=wasm_sleep,wasm_read,emscripten_sleep,wasm_sapi_handle_request,wasm_sapi_request_shutdown,wasm_poll_socket,wrap_select,__wrap_select,select,php_pollfd_for,fflush,wasm_popen,wasm_read,wasm_php_exec,run_cli -s EXTRA_EXPORTED_RUNTIME_METHODS=ccall,PROXYFS,wasmExports,_malloc "; \ + # Both imports and exports are required for inter-module communication with wrapped methods, e.g., wasm_recv. + export ASYNCIFY_FLAGS=" -s ASYNCIFY=2 -sSUPPORT_LONGJMP=wasm -fwasm-exceptions -sJSPI_IMPORTS=js_open_process,js_waitpid,js_process_status,js_create_input_device,wasm_setsockopt,wasm_shutdown,wasm_close,wasm_recv -sJSPI_EXPORTS=wasm_sleep,wasm_read,emscripten_sleep,wasm_sapi_handle_request,wasm_sapi_request_shutdown,wasm_poll_socket,wrap_select,__wrap_select,select,php_pollfd_for,fflush,wasm_popen,wasm_read,wasm_php_exec,run_cli,wasm_recv -s EXTRA_EXPORTED_RUNTIME_METHODS=ccall,PROXYFS,wasmExports,_malloc "; \ echo '#define PLAYGROUND_JSPI 1' > /root/php_wasm_asyncify.h; \ else \ export ASYNCIFY_FLAGS=" -s ASYNCIFY=1 -s ASYNCIFY_IGNORE_INDIRECT=1 -s EXPORTED_RUNTIME_METHODS=ccall,PROXYFS,wasmExports $(cat /root/.emcc-php-asyncify-flags) "; \ @@ -2064,7 +2064,8 @@ RUN set -euxo pipefail; \ PLATFORM_SPECIFIC_ARGS=''; \ if [ "$EMSCRIPTEN_ENVIRONMENT" = "node" ] && [ "$WITH_JSPI" = "yes" ]; then \ PLATFORM_SPECIFIC_ARGS="$PLATFORM_SPECIFIC_ARGS -DPHP_WASM_FILE_LOCKING_SUPPORT=1"; \ - PLATFORM_SPECIFIC_ARGS="$PLATFORM_SPECIFIC_ARGS --js-library /root/phpwasm-emscripten-library-file-locking-for-node.js"; \ + PLATFORM_SPECIFIC_ARGS="$PLATFORM_SPECIFIC_ARGS --js-library /root/phpwasm-emscripten-library-dynamic-linking.js"; \ + PLATFORM_SPECIFIC_ARGS="$PLATFORM_SPECIFIC_ARGS --js-library /root/phpwasm-emscripten-library-file-locking-for-node.js -Wl,--wrap=getpid"; \ fi; \ emcc $OPTIMIZATION_FLAGS \ --js-library /root/phpwasm-emscripten-library.js \ @@ -2076,12 +2077,13 @@ RUN set -euxo pipefail; \ -I Zend \ -I main \ -I TSRM/ \ - -I /root/lib/include \ + -I/root/lib -I/root/lib/include \ -L/root/lib -L/root/lib/lib/ \ -D__x86_64__\ -lproxyfs.js \ $ASYNCIFY_FLAGS \ $(cat /root/.emcc-php-wasm-flags) \ + -s MAIN_MODULE \ -s EXPORTED_FUNCTIONS="$EXPORTED_FUNCTIONS" \ -s WASM_BIGINT=1 \ -s MIN_NODE_VERSION=200900 \ diff --git a/packages/php-wasm/compile/php/php7.3.patch b/packages/php-wasm/compile/php/php7.3.patch index b033fceaba..67a00e6c9b 100644 --- a/packages/php-wasm/compile/php/php7.3.patch +++ b/packages/php-wasm/compile/php/php7.3.patch @@ -4,7 +4,7 @@ diff --git a/php-src/ext/pdo_sqlite/sqlite_statement.c b/php-src/ext/pdo_sqlite/ @@ -299,7 +299,7 @@ static int pdo_sqlite_stmt_describe(pdo_stmt_t *stmt, int colno) return 1; } - + -static int pdo_sqlite_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong *len, int *caller_frees) +static int pdo_sqlite_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, size_t *len, int *caller_frees) { @@ -19,7 +19,7 @@ diff --git a/php-src/ext/standard/file.c b/php-src/ext/standard/file.c php_stream *srcstream = NULL, *deststream = NULL; int ret = FAILURE; php_stream_statbuf src_s, dest_s; - + switch (php_stream_stat_path_ex(src, 0, &src_s, ctx)) { case -1: /* non-statable stream */ @@ -48,6 +48,8 @@ diff --git a/php-src/ext/standard/file.c b/php-src/ext/standard/file.c php_error_docref(NULL, E_WARNING, "The first argument to copy() function cannot be a directory"); return FAILURE; } + + diff --git a/php-src/main/streams/cast.c b/php-src/main/streams/cast.c --- a/php-src/main/streams/cast.c +++ b/php-src/main/streams/cast.c @@ -58,9 +60,39 @@ diff --git a/php-src/main/streams/cast.c b/php-src/main/streams/cast.c -static int stream_cookie_seeker(void *cookie, zend_off_t position, int whence) +static int stream_cookie_seeker(void *cookie, long long *position, int whence) { - + - return php_stream_seek((php_stream *)cookie, position, whence); + return php_stream_seek((php_stream *)cookie, *position, whence); } # endif - + + +diff --git a/php-src/Zend/zend_variables.c b/php-src/Zend/zend_variables.c +index c2594274dd4..42fbacf8c6e 100644 +--- a/php-src/Zend/zend_variables.c ++++ b/php-src/Zend/zend_variables.c +@@ -44,6 +44,13 @@ static void ZEND_FASTCALL zend_ast_ref_destroy_wrapper(zend_ast_ref *ast); + + typedef void (ZEND_FASTCALL *zend_rc_dtor_func_t)(zend_refcounted *p); + ++static void zend_list_free_wrapper(zend_refcounted *p) { ++ zend_resource *res = (zend_resource*)p; ++ zval zv; ++ ZVAL_RES(&zv, res); ++ zend_list_free(&zv); ++} ++ + static const zend_rc_dtor_func_t zend_rc_dtor_func[] = { + /* IS_UNDEF */ (zend_rc_dtor_func_t)zend_empty_destroy, + /* IS_NULL */ (zend_rc_dtor_func_t)zend_empty_destroy, +@@ -54,7 +61,9 @@ static const zend_rc_dtor_func_t zend_rc_dtor_func[] = { + /* IS_STRING */ (zend_rc_dtor_func_t)zend_string_destroy, + /* IS_ARRAY */ (zend_rc_dtor_func_t)zend_array_destroy_wrapper, + /* IS_OBJECT */ (zend_rc_dtor_func_t)zend_object_destroy_wrapper, +- /* IS_RESOURCE */ (zend_rc_dtor_func_t)zend_resource_destroy_wrapper, ++ // Info: zend_resource_destroy_wrapper is an alias for zend_list_free ++ // https://github.com/php/php-src/blob/PHP-7.3.33/Zend/zend_variables.c#L41 ++ /* IS_RESOURCE */ zend_list_free_wrapper, + /* IS_REFERENCE */ (zend_rc_dtor_func_t)zend_reference_destroy, + /* IS_CONSTANT_AST */ (zend_rc_dtor_func_t)zend_ast_ref_destroy_wrapper + }; diff --git a/packages/php-wasm/compile/php/php7.4.patch b/packages/php-wasm/compile/php/php7.4.patch index c92f940cfe..c6afaa96f7 100644 --- a/packages/php-wasm/compile/php/php7.4.patch +++ b/packages/php-wasm/compile/php/php7.4.patch @@ -15,9 +15,11 @@ index 4d10e688b5..ca50261b8a 100644 + php_pollfd_for(data->fd, POLLIN, NULL); + ret = read(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); + } - + if (ret == (size_t)-1 && errno == EINTR) { /* Read was interrupted, retry once, + + diff --git a/php-src/ext/standard/file.c b/php-src/ext/standard/file.c --- a/php-src/ext/standard/file.c +++ b/php-src/ext/standard/file.c @@ -56,3 +58,32 @@ diff --git a/php-src/ext/standard/file.c b/php-src/ext/standard/file.c } switch (php_stream_stat_path_ex(dest, PHP_STREAM_URL_STAT_QUIET | PHP_STREAM_URL_STAT_NOCACHE, &dest_s, ctx)) { + + +diff --git a/php-src/Zend/zend_variables.c b/php-src/Zend/zend_variables.c +index 810866a1be2..fdd1c80fea5 100644 +--- a/php-src/Zend/zend_variables.c ++++ b/php-src/Zend/zend_variables.c +@@ -36,6 +36,13 @@ static void ZEND_FASTCALL zend_empty_destroy(zend_reference *ref); + + typedef void (ZEND_FASTCALL *zend_rc_dtor_func_t)(zend_refcounted *p); + ++static void zend_list_free_wrapper(zend_refcounted *p) { ++ zend_resource *res = (zend_resource*)p; ++ zval zv; ++ ZVAL_RES(&zv, res); ++ zend_list_free(&zv); ++} ++ + static const zend_rc_dtor_func_t zend_rc_dtor_func[] = { + /* IS_UNDEF */ (zend_rc_dtor_func_t)zend_empty_destroy, + /* IS_NULL */ (zend_rc_dtor_func_t)zend_empty_destroy, +@@ -46,7 +53,7 @@ static const zend_rc_dtor_func_t zend_rc_dtor_func[] = { + /* IS_STRING */ (zend_rc_dtor_func_t)zend_string_destroy, + /* IS_ARRAY */ (zend_rc_dtor_func_t)zend_array_destroy, + /* IS_OBJECT */ (zend_rc_dtor_func_t)zend_objects_store_del, +- /* IS_RESOURCE */ (zend_rc_dtor_func_t)zend_list_free, ++ /* IS_RESOURCE */ zend_list_free_wrapper, + /* IS_REFERENCE */ (zend_rc_dtor_func_t)zend_reference_destroy, + /* IS_CONSTANT_AST */ (zend_rc_dtor_func_t)zend_ast_ref_destroy + }; diff --git a/packages/php-wasm/compile/php/php_wasm.c b/packages/php-wasm/compile/php/php_wasm.c index 59f1789193..f2701922fe 100644 --- a/packages/php-wasm/compile/php/php_wasm.c +++ b/packages/php-wasm/compile/php/php_wasm.c @@ -1908,7 +1908,7 @@ extern pid_t js_getpid(); * We provide our own getpid() implementation to return a distinct ID * for the purpose of file locking. */ -EMSCRIPTEN_KEEPALIVE pid_t getpid() { +EMSCRIPTEN_KEEPALIVE pid_t __wrap_getpid() { // The process ID is provided in JS as a PHPLoader option, // so we ask JS for it here. return js_getpid(); diff --git a/packages/php-wasm/compile/php/phpwasm-emscripten-library-dynamic-linking.js b/packages/php-wasm/compile/php/phpwasm-emscripten-library-dynamic-linking.js new file mode 100644 index 0000000000..5c997b6794 --- /dev/null +++ b/packages/php-wasm/compile/php/phpwasm-emscripten-library-dynamic-linking.js @@ -0,0 +1,19 @@ +/** + * This file is an Emscripten "library" file. It is included in the + * build "php__.js" files and implements JavaScript functions + * that can be called from C code. + * + * @see https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#implement-a-c-api-in-javascript + */ +'use strict'; + +var LibraryForCustomDynamicLinking = { + _dlopen_js: function __dlopen_js(handle) { + var jsflags = { loadAsync: false } + return dlopenInternal(handle, jsflags); + }, + _dlopen_js__deps: ['$dlopenInternal'], + _dlopen_js__async: false +}; + +mergeInto(LibraryManager.library, LibraryForCustomDynamicLinking); diff --git a/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js b/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js index ecbcba7103..8fabdebc09 100644 --- a/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js +++ b/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js @@ -683,14 +683,17 @@ const LibraryForFileLocking = { js_release_file_locks: async function js_release_file_locks() { _js_wasm_trace('js_release_file_locks()'); const pid = PHPLoader.processId; - return await PHPLoader.fileLockManager - .releaseLocksForProcess(pid) - .then(() => { - _js_wasm_trace('js_release_file_locks succeeded'); - }) - .catch((e) => { - _js_wasm_trace('js_release_file_locks error %s', e); - }); + if(pid && PHPLoader.fileLockManager) + { + return await PHPLoader.fileLockManager + .releaseLocksForProcess(pid) + .then(() => { + _js_wasm_trace('js_release_file_locks succeeded'); + }) + .catch((e) => { + _js_wasm_trace('js_release_file_locks error %s', e); + }); + } }, }; diff --git a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js index 0e92ab283f..2e13322145 100644 --- a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js +++ b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js @@ -352,7 +352,7 @@ const LibraryExample = { }, /** - * Shims unix shutdown(2) functionallity for asynchronous sockets: + * Shims unix shutdown(2) functionality for asynchronous sockets: * https://man7.org/linux/man-pages/man2/shutdown.2.html * * Does not support SHUT_RD or SHUT_WR. @@ -752,7 +752,7 @@ const LibraryExample = { }, /** - * Shims unix shutdown(2) functionallity for asynchronous: + * Shims unix shutdown(2) functionality for asynchronous: * https://man7.org/linux/man-pages/man2/shutdown.2.html * * Does not support SHUT_RD or SHUT_WR. @@ -766,7 +766,7 @@ const LibraryExample = { }, /** - * Shims unix close(2) functionallity for asynchronous: + * Shims unix close(2) functionality for asynchronous: * https://man7.org/linux/man-pages/man2/close.2.html * * @param {int} socketd @@ -777,7 +777,38 @@ const LibraryExample = { }, /** - * Shims setsockopt(2) functionallity for asynchronous websockets: + * Shims recv(2) functionality for asynchronous websockets: + * https://man7.org/linux/man-pages/man2/recv.2.html + * + * @param {int} sockfd Socket descriptor + * @param {int} buffer Pointer to the stored message buffer + * @param {int} size The maximum bytes to receive + * @param {int} flags Flags to modify the behavior to recv call + * @returns {Promise} Resolved with the number of bytes recieved + */ + wasm_recv : function ( + sockfd, + buffer, + size, + flags + ) { + return Asyncify.handleSleep((wakeUp) => { + const poll = function() { + let newl = ___syscall_recvfrom(sockfd, buffer, size, flags, null, null); + if(newl > 0) { + wakeUp(newl); + } else if ( newl === -6 ) { + setTimeout(poll, 20); + } else { + wakeUp(0); + } + }; + poll(); + }); + }, + + /** + * Shims setsockopt(2) functionality for asynchronous websockets: * https://man7.org/linux/man-pages/man2/setsockopt.2.html * The only supported options are SO_KEEPALIVE and TCP_NODELAY. * diff --git a/packages/php-wasm/compile/project.json b/packages/php-wasm/compile/project.json index 996c9826f0..9fddf9b53a 100644 --- a/packages/php-wasm/compile/project.json +++ b/packages/php-wasm/compile/project.json @@ -8,6 +8,31 @@ "commands": ["node packages/php-wasm/compile/build.js"], "parallel": false } + }, + "xdebug": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "node packages/php-wasm/compile/xdebug/build.js --output-dir=packages/php-wasm/node/jspi" + ], + "parallel": false + } + }, + "xdebug:all": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run php-wasm-compile:xdebug --PHP_VERSION=8.4", + "nx run php-wasm-compile:xdebug --PHP_VERSION=8.3", + "nx run php-wasm-compile:xdebug --PHP_VERSION=8.2", + "nx run php-wasm-compile:xdebug --PHP_VERSION=8.1", + "nx run php-wasm-compile:xdebug --PHP_VERSION=8.0", + "nx run php-wasm-compile:xdebug --PHP_VERSION=7.4", + "nx run php-wasm-compile:xdebug --PHP_VERSION=7.3", + "nx run php-wasm-compile:xdebug --PHP_VERSION=7.2" + ], + "parallel": false + } } }, "tags": [] diff --git a/packages/php-wasm/compile/xdebug/Dockerfile b/packages/php-wasm/compile/xdebug/Dockerfile new file mode 100644 index 0000000000..fc0c04846f --- /dev/null +++ b/packages/php-wasm/compile/xdebug/Dockerfile @@ -0,0 +1,122 @@ +FROM playground-php-wasm:base + +# Each version of PHP requires its own version of Xdebug +# because each version of PHP has a specific Zend Engine API version. +# Consequently, each version of Xdebug is designed to be +# compatible with a particular Zend Engine API version. +ARG PHP_VERSION + +# Install Bison, required to build PHP +RUN mkdir -p /libs +COPY ./bison2.7/dist/ /libs/bison2.7 +COPY ./bison2.7/bison27.patch /root/bison27.patch + +RUN if [[ "${PHP_VERSION:0:1}" -le "7" && "${PHP_VERSION:2:1}" -le "3" ]]; then \ + if /libs/bison2.7/usr/local/bison/bin/bison -h >/dev/null; then \ + mv /libs/bison2.7/usr/local/bison /usr/local/bison && \ + ln -s /usr/local/bison/bin/bison /usr/bin/bison && \ + ln -s /usr/local/bison/bin/yacc /usr/bin/yacc; \ + else \ + wget https://ftp.gnu.org/gnu/bison/bison-2.7.tar.gz && \ + tar -xvf bison-2.7.tar.gz && \ + rm bison-2.7.tar.gz && \ + cd bison-2.7 && \ + git apply --no-index /root/bison27.patch && \ + ./configure --prefix=/usr/local && \ + make && \ + make install; \ + if [[ $? -ne 0 ]]; then \ + echo 'Failed to build Bison 2.7 dependency.'; \ + exit -1; \ + fi; \ + fi; \ + else \ + apt install -y bison; \ + fi; + +# Build PHP +RUN git clone https://github.com/php/php-src.git php-src \ + --branch PHP-$PHP_VERSION \ + --single-branch \ + --depth 1; + +RUN cd php-src && ./buildconf --force + +# PHP <= 7.3 Get and patch PHP +COPY ./php/php*.patch /root/ +RUN cd /root && git apply --no-index /root/php${PHP_VERSION:0:3}*.patch -v + +RUN source /root/emsdk/emsdk_env.sh && \ + cd php-src && \ + emconfigure ./configure \ + --disable-fiber-asm \ + --enable-embed \ + --disable-cgi \ + --disable-opcache \ + --disable-phpdbg \ + --without-pcre-jit \ + --disable-cli \ + --disable-libxml \ + --without-libxml \ + --disable-dom \ + --disable-xml \ + --disable-simplexml \ + --disable-xmlreader \ + --disable-xmlwriter \ + --without-sqlite3 \ + --without-pdo-sqlite \ + # PHP 7.4 + --without-iconv + +# Disable asm arithmetic. +RUN if [[ ("${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -ge "4") || "${PHP_VERSION:0:1}" -ge "8" ]]; then \ + /root/replace.sh 's/ZEND_USE_ASM_ARITHMETIC 1/ZEND_USE_ASM_ARITHMETIC 0/g' /root/php-src/Zend/zend_operators.h; \ + elif [[ "${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -eq "3" ]]; then \ + /root/replace.sh 's/defined\(HAVE_ASM_GOTO\)/0/g' /root/php-src/Zend/zend_operators.h; \ + fi; +RUN /root/replace.sh 's/defined\(__GNUC__\)/0/g' /root/php-src/Zend/zend_multiply.h +RUN /root/replace.sh 's/defined\(__GNUC__\)/0/g' /root/php-src/Zend/zend_cpuinfo.c +RUN /root/replace.sh 's/defined\(__clang__\)/0/g' /root/php-src/Zend/zend_cpuinfo.c + +# PHP <= 7.3 is not very good at detecting the presence of the POSIX readdir_r function +# so we need to force it to be enabled. +RUN if [[ "${PHP_VERSION:0:1}" -le "7" && "${PHP_VERSION:2:1}" -le "3" ]]; then \ + echo '#define HAVE_POSIX_READDIR_R 1' >> /root/php-src/main/php_config.h; \ + fi; + +RUN source /root/emsdk/emsdk_env.sh && \ + cd /root/php-src && \ + emmake make install + +# Build Xdebug +RUN set -euxo pipefail; \ + + if [[ "${PHP_VERSION:0:1}" -ge 8 ]]; then \ + export XDEBUG_BRANCH="xdebug_3_4"; \ + else \ + export XDEBUG_BRANCH="xdebug_3_1"; \ + fi; \ + + git clone https://github.com/xdebug/xdebug.git xdebug \ + --branch "${XDEBUG_BRANCH}" \ + --single-branch \ + --depth 1; \ + + cd xdebug; \ + + phpize . ; \ + + source /root/emsdk/emsdk_env.sh; \ + # Use non-linux host type or configure will try to require linux kernel + # features that are unsupported by emscripten (e.g., rtnetlink.h). + emconfigure ./configure --host="i386-unknown-freebsd" --disable-static --enable-shared; \ + # XDebug linking fails to produce a shared lib because libm is missing. + # Let's explicitly skip libm in the understanding that it is provided by Emscripten + # with the main module. + /root/replace.sh 's/^(XDEBUG_SHARED_LIBADD\s*=.*\s*)-lm\b/$1/' Makefile; \ + + export EMCC_FLAGS="-sSIDE_MODULE -D__x86_64__ -sWASM_BIGINT -Dsetsockopt=wasm_setsockopt -Drecv=wasm_recv -O3"; \ + + export EMCC_FLAGS="${EMCC_FLAGS} -sJSPI -sJSPI_IMPORTS=wasm_recv -sJSPI_EXPORTS=wasm_recv"; \ + + emmake make -j1 diff --git a/packages/php-wasm/compile/xdebug/build.js b/packages/php-wasm/compile/xdebug/build.js new file mode 100644 index 0000000000..cb26a32af7 --- /dev/null +++ b/packages/php-wasm/compile/xdebug/build.js @@ -0,0 +1,114 @@ +import path from 'path'; +import { spawn } from 'child_process'; +import { phpVersions } from '../../supported-php-versions.mjs'; + +// yargs parse +import yargs from 'yargs'; +const argParser = yargs(process.argv.slice(2)) + .usage('Usage: $0 [options]') + .options({ + PHP_VERSION: { + type: 'string', + description: 'The PHP version to build', + required: true, + }, + ['output-dir']: { + type: 'string', + description: 'The output directory', + required: true, + }, + }); + +const args = argParser.argv; + +const platformDefaults = { + all: { + PHP_VERSION: '8.0.24', + }, +}; + +const getArg = (name) => { + let value = + name in args + ? args[name] + : name in platformDefaults.all + ? platformDefaults.all[name] + : 'no'; + if (name === 'PHP_VERSION') { + value = fullyQualifiedPHPVersion(value); + } + return `${name}=${value}`; +}; + +const requestedVersion = getArg('PHP_VERSION'); +if (!requestedVersion || requestedVersion === 'undefined') { + process.stdout.write(`PHP version ${requestedVersion} is not supported\n`); + process.stdout.write(await argParser.getHelp()); + process.exit(1); +} + +const sourceDir = path.dirname(new URL(import.meta.url).pathname); +const outputDir = path.resolve(process.cwd(), args.outputDir); + +// Build the base image +await asyncSpawn('make', ['base-image'], { + cwd: path.dirname(sourceDir), + stdio: 'inherit', +}); + +// Build the xdebug.so extension +await asyncSpawn( + 'docker', + [ + 'build', + '-f', + 'xdebug/Dockerfile', + '.', + '--tag=playground-php-wasm:xdebug', + '--progress=plain', + '--build-arg', + getArg('PHP_VERSION'), + ], + { cwd: path.dirname(sourceDir), stdio: 'inherit' } +); + +const version = args['PHP_VERSION'].replace('.', '_'); + +await asyncSpawn( + 'docker', + [ + 'run', + '--name', + 'playground-php-wasm-tmp', + '--rm', + '-v', + `${outputDir}:/output`, + 'playground-php-wasm:xdebug', + // Use sh -c because wildcards are a shell feature and + // they don't work without running cp through shell. + 'sh', + '-c', + `mkdir -p /output/extensions/xdebug/${version} && cp -rf /root/xdebug/modules/* /output/extensions/xdebug/${version}`, + ], + { cwd: path.dirname(sourceDir), stdio: 'inherit' } +); + +function asyncSpawn(...args) { + console.log('Running', args[0], args[1].join(' '), '...'); + return new Promise((resolve, reject) => { + const child = spawn(...args); + child.on('close', (code) => { + if (code === 0) resolve(code); + else reject(new Error(`Process exited with code ${code}`)); + }); + }); +} + +function fullyQualifiedPHPVersion(requestedVersion) { + for (const { version, lastRelease } of phpVersions) { + if (requestedVersion === version) { + return lastRelease; + } + } + return requestedVersion; +} diff --git a/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm b/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm index b227e09a80..397ebac710 100755 Binary files a/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm and b/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm differ diff --git a/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm b/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm index 4933fa9ec9..4fb768649b 100755 Binary files a/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm and b/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm differ diff --git a/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm b/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm index b21e6f0644..d30a9be67d 100755 Binary files a/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm and b/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm b/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm index dde7a70091..16eea1b03f 100755 Binary files a/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm and b/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm b/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm index 2578a73bda..faf01a6dcb 100755 Binary files a/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm and b/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm b/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm index e17997be79..e5e065ca52 100755 Binary files a/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm and b/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm b/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm index 9a2d901e44..40dce2b700 100755 Binary files a/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm and b/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm b/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm index bd01bb0a89..59c6eca519 100755 Binary files a/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm and b/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm differ diff --git a/packages/php-wasm/node/asyncify/php_7_2.js b/packages/php-wasm/node/asyncify/php_7_2.js index 821e8c0870..bdc1ce68bb 100644 --- a/packages/php-wasm/node/asyncify/php_7_2.js +++ b/packages/php-wasm/node/asyncify/php_7_2.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '7_2_34', 'php_7_2.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 18432541; +export const dependenciesTotalSize = 29001508; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -27,35 +27,35 @@ export function init(RuntimeName, PHPLoader) { // can continue to use Module afterwards as well. var Module = typeof PHPLoader != 'undefined' ? PHPLoader : {}; - var ENVIRONMENT_IS_WORKER = RuntimeName === 'WORKER'; + // Determine the runtime environment we are in. You can customize this by + // setting the ENVIRONMENT setting at compile time (see settings.js). + var ENVIRONMENT_IS_WEB = RuntimeName === 'WEB'; + var ENVIRONMENT_IS_WORKER = RuntimeName === 'WORKER'; var ENVIRONMENT_IS_NODE = RuntimeName === 'NODE'; + var ENVIRONMENT_IS_SHELL = RuntimeName === 'SHELL'; if (ENVIRONMENT_IS_NODE) { } // --pre-jses are emitted after the Module integration code, so that they can // refer to Module (if they choose; they can also define Module) + // Sometimes an existing Module object exists with properties // meant to overwrite the default module functionality. Here // we collect those properties and reapply _after_ we configure // the current environment's defaults to avoid having to be so // defensive during initialization. - var moduleOverrides = { - ...Module, - }; + var moduleOverrides = { ...Module }; var arguments_ = []; - var thisProgram = './this.program'; - var quit_ = (status, toThrow) => { throw toThrow; }; // `/` should be present at the end if `scriptDirectory` is not empty var scriptDirectory = ''; - function locateFile(path) { if (Module['locateFile']) { return Module['locateFile'](path, scriptDirectory); @@ -71,7 +71,9 @@ export function init(RuntimeName, PHPLoader) { // the complexity of lazy-loading. var fs = require('fs'); var nodePath = require('path'); + scriptDirectory = __dirname + '/'; + // include: node_shell_read.js readBinary = (filename) => { // We need to re-wrap `file://` strings to URLs. @@ -79,6 +81,7 @@ export function init(RuntimeName, PHPLoader) { var ret = fs.readFileSync(filename); return ret; }; + readAsync = async (filename, binary = true) => { // See the comment in the `readBinary` function. filename = isFileURI(filename) ? new URL(filename) : filename; @@ -89,27 +92,30 @@ export function init(RuntimeName, PHPLoader) { if (!Module['thisProgram'] && process.argv.length > 1) { thisProgram = process.argv[1].replace(/\\/g, '/'); } + arguments_ = process.argv.slice(2); + if (typeof module != 'undefined') { module['exports'] = Module; } + quit_ = (status, toThrow) => { process.exitCode = status; throw toThrow; }; - } // Note that this includes Node.js workers when relevant (pthreads is enabled). + } + + // Note that this includes Node.js workers when relevant (pthreads is enabled). // Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and // ENVIRONMENT_IS_NODE. else { } var out = Module['print'] || console.log.bind(console); - var err = Module['printErr'] || console.error.bind(console); // Merge back in the overrides Object.assign(Module, moduleOverrides); - // Free the object hierarchy contained in the overrides, this lets the GC // reclaim data used. moduleOverrides = null; @@ -118,6 +124,7 @@ export function init(RuntimeName, PHPLoader) { // to the proper local x. This has two benefits: first, we only emit it if it is // expected to arrive, and second, by using a local everywhere else that can be // minified. + if (Module['arguments']) arguments_ = Module['arguments']; if (Module['thisProgram']) thisProgram = Module['thisProgram']; @@ -125,8 +132,10 @@ export function init(RuntimeName, PHPLoader) { // perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message // end include: shell.js + // include: preamble.js // === Preamble library stuff === + // Documentation for the public APIs defined in this file must be updated in: // site/source/docs/api_reference/preamble.js.rst // A prebuilt local version of the documentation is available at: @@ -134,14 +143,19 @@ export function init(RuntimeName, PHPLoader) { // You can also build docs locally as HTML or other formats in site/ // An online HTML version (which may be of a different version of Emscripten) // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + + var dynamicLibraries = Module['dynamicLibraries'] || []; + var wasmBinary = Module['wasmBinary']; // Wasm globals + var wasmMemory; //======================================== // Runtime essentials //======================================== + // whether we are quitting the application. no code should run after this. // set in exit() and abort() var ABORT = false; @@ -155,7 +169,8 @@ export function init(RuntimeName, PHPLoader) { // don't define it at all in release modes. This matches the behaviour of // MINIMAL_RUNTIME. // TODO(sbc): Make this the default even without STRICT enabled. - /** @type {function(*, string=)} */ function assert(condition, text) { + /** @type {function(*, string=)} */ + function assert(condition, text) { if (!condition) { // This build was created without ASSERTIONS defined. `assert()` should not // ever be called in this configuration but in case there are callers in @@ -165,18 +180,30 @@ export function init(RuntimeName, PHPLoader) { } // Memory management - var /** @type {!Int8Array} */ HEAP8, - /** @type {!Uint8Array} */ HEAPU8, - /** @type {!Int16Array} */ HEAP16, - /** @type {!Uint16Array} */ HEAPU16, - /** @type {!Int32Array} */ HEAP32, - /** @type {!Uint32Array} */ HEAPU32, - /** @type {!Float32Array} */ HEAPF32, + + var HEAP, + /** @type {!Int8Array} */ + HEAP8, + /** @type {!Uint8Array} */ + HEAPU8, + /** @type {!Int16Array} */ + HEAP16, + /** @type {!Uint16Array} */ + HEAPU16, + /** @type {!Int32Array} */ + HEAP32, + /** @type {!Uint32Array} */ + HEAPU32, + /** @type {!Float32Array} */ + HEAPF32, /* BigInt64Array type is not correctly defined in closure -/** not-@type {!BigInt64Array} */ HEAP64, +/** not-@type {!BigInt64Array} */ + HEAP64, /* BigUint64Array type is not correctly defined in closure -/** not-t@type {!BigUint64Array} */ HEAPU64, - /** @type {!Float64Array} */ HEAPF64; +/** not-t@type {!BigUint64Array} */ + HEAPU64, + /** @type {!Float64Array} */ + HEAPF64; var runtimeInitialized = false; @@ -185,7 +212,8 @@ export function init(RuntimeName, PHPLoader) { /** * Indicates whether filename is delivered via file protocol (as opposed to http/https) * @noinline - */ var isFileURI = (filename) => filename.startsWith('file://'); + */ + var isFileURI = (filename) => filename.startsWith('file://'); // include: runtime_shared.js // include: runtime_stack_check.js @@ -196,6 +224,7 @@ export function init(RuntimeName, PHPLoader) { // end include: runtime_debug.js // include: memoryprofiler.js // end include: memoryprofiler.js + function updateMemoryViews() { var b = wasmMemory.buffer; Module['HEAP8'] = HEAP8 = new Int8Array(b); @@ -211,6 +240,35 @@ export function init(RuntimeName, PHPLoader) { } // end include: runtime_shared.js + // In non-standalone/normal mode, we create the memory here. + // include: runtime_init_memory.js + // Create the wasm memory. (Note: this only applies if IMPORTED_MEMORY is defined) + + // check for full engine support (use string 'subarray' to avoid closure compiler confusion) + + if (Module['wasmMemory']) { + wasmMemory = Module['wasmMemory']; + } else { + var INITIAL_MEMORY = Module['INITIAL_MEMORY'] || 1073741824; + + /** @suppress {checkTypes} */ + wasmMemory = new WebAssembly.Memory({ + initial: INITIAL_MEMORY / 65536, + // In theory we should not need to emit the maximum if we want "unlimited" + // or 4GB of memory, but VMs error on that atm, see + // https://github.com/emscripten-core/emscripten/issues/14130 + // And in the pthreads case we definitely need to emit a maximum. So + // always emit one. + maximum: 32768, + }); + } + + updateMemoryViews(); + + // end include: runtime_init_memory.js + + var __RELOC_FUNCS__ = []; + function preRun() { if (Module['preRun']) { if (typeof Module['preRun'] == 'function') @@ -224,17 +282,28 @@ export function init(RuntimeName, PHPLoader) { function initRuntime() { runtimeInitialized = true; - SOCKFS.root = FS.mount(SOCKFS, {}, null); + + callRuntimeCallbacks(__RELOC_FUNCS__); + + callRuntimeCallbacks(onInits); if (!Module['noFSInit'] && !FS.initialized) FS.init(); TTY.init(); + SOCKFS.root = FS.mount(SOCKFS, {}, null); PIPEFS.root = FS.mount(PIPEFS, {}, null); - wasmExports['db'](); + + wasmExports['__wasm_call_ctors'](); + + callRuntimeCallbacks(onPostCtors); FS.ignorePermissions = false; } + function preMain() { + callRuntimeCallbacks(onMains); + } + function exitRuntime() { - ___funcs_on_exit(); - // Native atexit() functions + ___funcs_on_exit(); // Native atexit() functions + callRuntimeCallbacks(onExits); FS.quit(); TTY.shutdown(); runtimeExited = true; @@ -248,6 +317,7 @@ export function init(RuntimeName, PHPLoader) { addOnPostRun(Module['postRun'].shift()); } } + callRuntimeCallbacks(onPostRuns); } @@ -259,39 +329,45 @@ export function init(RuntimeName, PHPLoader) { // it happens right before run - run will be postponed until // the dependencies are met. var runDependencies = 0; + var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled - var dependenciesFulfilled = null; - - // overridden to take different actions when all run dependencies are fulfilled function getUniqueRunDependency(id) { return id; } function addRunDependency(id) { runDependencies++; + Module['monitorRunDependencies']?.(runDependencies); } function removeRunDependency(id) { runDependencies--; + Module['monitorRunDependencies']?.(runDependencies); + if (runDependencies == 0) { if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; - callback(); + callback(); // can add another dependenciesFulfilled } } } - /** @param {string|number=} what */ function abort(what) { + /** @param {string|number=} what */ + function abort(what) { Module['onAbort']?.(what); + what = 'Aborted(' + what + ')'; // TODO(sbc): Should we remove printing and leave it up to whoever // catches the exception? err(what); + ABORT = true; + what += '. Build with -sASSERTIONS for more info.'; + // Use a wasm runtime error, because a JS error might be seen as a foreign // exception, which means we'd run destructors on it. We need the error to // simply make the program stop. @@ -300,13 +376,14 @@ export function init(RuntimeName, PHPLoader) { // a trap or not based on a hidden field within the object. So at the moment // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that // allows this in the wasm spec. + // Suppress closure compiler warning here. Closure compiler's builtin extern // definition for WebAssembly.RuntimeError claims it takes no arguments even // though it can. // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. - /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError( - what - ); + /** @suppress {checkTypes} */ + var e = new WebAssembly.RuntimeError(what); + // Throw the error whether or not MODULARIZE is set because abort is used // in code paths apart from instantiation where an exception is expected // to be thrown when abort is called. @@ -336,8 +413,11 @@ export function init(RuntimeName, PHPLoader) { try { var response = await readAsync(binaryFile); return new Uint8Array(response); - } catch {} + } catch { + // Fall back to getBinarySync below; + } } + // Otherwise, getBinarySync should be able to get it synchronously return getBinarySync(binaryFile); } @@ -349,6 +429,7 @@ export function init(RuntimeName, PHPLoader) { return instance; } catch (reason) { err(`failed to asynchronously prepare wasm: ${reason}`); + abort(reason); } } @@ -357,6 +438,12 @@ export function init(RuntimeName, PHPLoader) { if ( !binary && typeof WebAssembly.instantiateStreaming == 'function' && + // Avoid instantiateStreaming() on Node.js environment for now, as while + // Node.js v18.1.0 implements it, it does not have a full fetch() + // implementation yet. + // + // Reference: + // https://github.com/emscripten-core/emscripten/pull/16917 !ENVIRONMENT_IS_NODE ) { try { @@ -371,6 +458,7 @@ export function init(RuntimeName, PHPLoader) { // in which case falling back to ArrayBuffer instantiation should work. err(`wasm streaming compile failed: ${reason}`); err('falling back to ArrayBuffer instantiation'); + // fall back of instantiateArrayBuffer below } } return instantiateArrayBuffer(binaryFile, imports); @@ -379,7 +467,10 @@ export function init(RuntimeName, PHPLoader) { function getWasmImports() { // prepare imports return { - a: wasmImports, + env: wasmImports, + wasi_snapshot_preview1: wasmImports, + 'GOT.mem': new Proxy(wasmImports, GOTHandler), + 'GOT.func': new Proxy(wasmImports, GOTHandler), }; } @@ -389,30 +480,42 @@ export function init(RuntimeName, PHPLoader) { // Load the wasm module and create an instance of using native support in the JS engine. // handle a generated wasm instance, receiving its exports and // performing other necessary setup - /** @param {WebAssembly.Module=} module*/ function receiveInstance( - instance, - module - ) { + /** @param {WebAssembly.Module=} module*/ + function receiveInstance(instance, module) { wasmExports = instance.exports; + + wasmExports = relocateExports(wasmExports, 1024); + wasmExports = Asyncify.instrumentWasmExports(wasmExports); + + var metadata = getDylinkMetadata(module); + if (metadata.neededDynlibs) { + dynamicLibraries = + metadata.neededDynlibs.concat(dynamicLibraries); + } + mergeLibSymbols(wasmExports, 'main'); + LDSO.init(); + loadDylibs(); + Module['wasmExports'] = wasmExports; - wasmMemory = wasmExports['cb']; - updateMemoryViews(); - wasmTable = wasmExports['eb']; + + __RELOC_FUNCS__.push(wasmExports['__wasm_apply_data_relocs']); + removeRunDependency('wasm-instantiate'); return wasmExports; } // wait for the pthread pool (if any) addRunDependency('wasm-instantiate'); + // Prefer streaming instantiation if available. function receiveInstantiationResult(result) { // 'result' is a ResultObject object which has both the module and instance. // receiveInstance() will swap in the exports (to Module.asm) so they can be called - // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. - // When the regression is fixed, can restore the above PTHREADS-enabled path. - return receiveInstance(result['instance']); + return receiveInstance(result['instance'], result['module']); } + var info = getWasmImports(); + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback // to manually instantiate the Wasm module themselves. This allows pages to // run the instantiation parallel to any other async startup actions they are @@ -427,14 +530,23 @@ export function init(RuntimeName, PHPLoader) { }); }); } + wasmBinaryFile ??= findWasmBinary(); var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); var exports = receiveInstantiationResult(result); return exports; } + // With MAIN_MODULE + ASYNCIFY the normal method of placing stub functions in + // wasmImports for as-yet-undefined symbols doesn't work since ASYNCIFY then + // wraps these stub functions and we can't then replace them directly. Instead + // the stub functions call into `asyncifyStubs` which gets populated by the + // dynamic linker as symbols are loaded. + var asyncifyStubs = {}; // end include: preamble.js + // Begin JS library code + class ExitStatus { name = 'ExitStatus'; constructor(status) { @@ -451,27 +563,40 @@ export function init(RuntimeName, PHPLoader) { } }; + var GOT = {}; + + var currentModuleWeakSymbols = new Set([]); + var GOTHandler = { + get(obj, symName) { + var rtn = GOT[symName]; + if (!rtn) { + rtn = GOT[symName] = new WebAssembly.Global({ + value: 'i32', + mutable: true, + }); + } + if (!currentModuleWeakSymbols.has(symName)) { + // Any non-weak reference to a symbol marks it as `required`, which + // enabled `reportUndefinedSymbols` to report undefined symbol errors + // correctly. + rtn.required = true; + } + return rtn; + }, + }; + var callRuntimeCallbacks = (callbacks) => { while (callbacks.length > 0) { // Pass the module as the first argument. callbacks.shift()(Module); } }; - var onPostRuns = []; - var addOnPostRun = (cb) => onPostRuns.unshift(cb); var onPreRuns = []; - var addOnPreRun = (cb) => onPreRuns.unshift(cb); - var noExitRuntime = Module['noExitRuntime'] || false; - - var stackRestore = (val) => __emscripten_stack_restore(val); - - var stackSave = () => _emscripten_stack_get_current(); - var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; @@ -483,11 +608,8 @@ export function init(RuntimeName, PHPLoader) { * @param {number=} idx * @param {number=} maxBytesToRead * @return {string} - */ var UTF8ArrayToString = ( - heapOrArray, - idx = 0, - maxBytesToRead = NaN - ) => { + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { var endIdx = idx + maxBytesToRead; var endPtr = idx; // TextDecoder needs to know the byte length in advance, it doesn't stop on @@ -496,6 +618,7 @@ export function init(RuntimeName, PHPLoader) { // (As a tiny code save trick, compare endPtr against endIdx using a negation, // so that undefined/NaN means Infinity) while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); } @@ -508,17 +631,17 @@ export function init(RuntimeName, PHPLoader) { // https://www.ietf.org/rfc/rfc2279.txt // https://tools.ietf.org/html/rfc3629 var u0 = heapOrArray[idx++]; - if (!(u0 & 128)) { + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } var u1 = heapOrArray[idx++] & 63; - if ((u0 & 224) == 192) { + if ((u0 & 0xe0) == 0xc0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } var u2 = heapOrArray[idx++] & 63; - if ((u0 & 240) == 224) { + if ((u0 & 0xf0) == 0xe0) { u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; } else { u0 = @@ -527,737 +650,1853 @@ export function init(RuntimeName, PHPLoader) { (u2 << 6) | (heapOrArray[idx++] & 63); } - if (u0 < 65536) { + + if (u0 < 0x10000) { str += String.fromCharCode(u0); } else { - var ch = u0 - 65536; + var ch = u0 - 0x10000; str += String.fromCharCode( - 55296 | (ch >> 10), - 56320 | (ch & 1023) + 0xd800 | (ch >> 10), + 0xdc00 | (ch & 0x3ff) ); } } return str; }; + var getDylinkMetadata = (binary) => { + var offset = 0; + var end = 0; + + function getU8() { + return binary[offset++]; + } + + function getLEB() { + var ret = 0; + var mul = 1; + while (1) { + var byte = binary[offset++]; + ret += (byte & 0x7f) * mul; + mul *= 0x80; + if (!(byte & 0x80)) break; + } + return ret; + } + + function getString() { + var len = getLEB(); + offset += len; + return UTF8ArrayToString(binary, offset - len, len); + } + + function getStringList() { + var count = getLEB(); + var rtn = []; + while (count--) rtn.push(getString()); + return rtn; + } + + /** @param {string=} message */ + function failIf(condition, message) { + if (condition) throw new Error(message); + } + + if (binary instanceof WebAssembly.Module) { + var dylinkSection = WebAssembly.Module.customSections( + binary, + 'dylink.0' + ); + failIf(dylinkSection.length === 0, 'need dylink section'); + binary = new Uint8Array(dylinkSection[0]); + end = binary.length; + } else { + var int32View = new Uint32Array( + new Uint8Array(binary.subarray(0, 24)).buffer + ); + var magicNumberFound = int32View[0] == 0x6d736100; + failIf(!magicNumberFound, 'need to see wasm magic number'); // \0asm + // we should see the dylink custom section right after the magic number and wasm version + failIf(binary[8] !== 0, 'need the dylink section to be first'); + offset = 9; + var section_size = getLEB(); //section size + end = offset + section_size; + var name = getString(); + failIf(name !== 'dylink.0'); + } + + var customSection = { + neededDynlibs: [], + tlsExports: new Set(), + weakImports: new Set(), + runtimePaths: [], + }; + var WASM_DYLINK_MEM_INFO = 0x1; + var WASM_DYLINK_NEEDED = 0x2; + var WASM_DYLINK_EXPORT_INFO = 0x3; + var WASM_DYLINK_IMPORT_INFO = 0x4; + var WASM_DYLINK_RUNTIME_PATH = 0x5; + var WASM_SYMBOL_TLS = 0x100; + var WASM_SYMBOL_BINDING_MASK = 0x3; + var WASM_SYMBOL_BINDING_WEAK = 0x1; + while (offset < end) { + var subsectionType = getU8(); + var subsectionSize = getLEB(); + if (subsectionType === WASM_DYLINK_MEM_INFO) { + customSection.memorySize = getLEB(); + customSection.memoryAlign = getLEB(); + customSection.tableSize = getLEB(); + customSection.tableAlign = getLEB(); + } else if (subsectionType === WASM_DYLINK_NEEDED) { + customSection.neededDynlibs = getStringList(); + } else if (subsectionType === WASM_DYLINK_EXPORT_INFO) { + var count = getLEB(); + while (count--) { + var symname = getString(); + var flags = getLEB(); + if (flags & WASM_SYMBOL_TLS) { + customSection.tlsExports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_IMPORT_INFO) { + var count = getLEB(); + while (count--) { + var modname = getString(); + var symname = getString(); + var flags = getLEB(); + if ( + (flags & WASM_SYMBOL_BINDING_MASK) == + WASM_SYMBOL_BINDING_WEAK + ) { + customSection.weakImports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_RUNTIME_PATH) { + customSection.runtimePaths = getStringList(); + } else { + // unknown subsection + offset += subsectionSize; + } + } + + return customSection; + }; /** - * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the - * emscripten HEAP, returns a copy of that string as a Javascript String object. - * * @param {number} ptr - * @param {number=} maxBytesToRead - An optional length that specifies the - * maximum number of bytes to read. You can omit this parameter to scan the - * string until the first 0 byte. If maxBytesToRead is passed, and the string - * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the - * string will cut short at that byte index (i.e. maxBytesToRead will not - * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing - * frequent uses of UTF8ToString() with and without maxBytesToRead may throw - * JS JIT optimizations off, so it is worth to consider consistently using one - * @return {string} - */ var UTF8ToString = (ptr, maxBytesToRead) => - ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; + * @param {string} type + */ + function getValue(ptr, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': + return HEAP8[ptr]; + case 'i8': + return HEAP8[ptr]; + case 'i16': + return HEAP16[ptr >> 1]; + case 'i32': + return HEAP32[ptr >> 2]; + case 'i64': + return HEAP64[ptr >> 3]; + case 'float': + return HEAPF32[ptr >> 2]; + case 'double': + return HEAPF64[ptr >> 3]; + case '*': + return HEAPU32[ptr >> 2]; + default: + abort(`invalid type for getValue: ${type}`); + } + } - Module['UTF8ToString'] = UTF8ToString; + var newDSO = (name, handle, syms) => { + var dso = { + refcount: Infinity, + name, + exports: syms, + global: true, + }; + LDSO.loadedLibsByName[name] = dso; + if (handle != undefined) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return dso; + }; + var LDSO = { + loadedLibsByName: {}, + loadedLibsByHandle: {}, + init() { + newDSO('__main__', 0, wasmImports); + }, + }; - var ___assert_fail = (condition, filename, line, func) => - abort( - `Assertion failed: ${UTF8ToString(condition)}, at: ` + - [ - filename ? UTF8ToString(filename) : 'unknown filename', - line, - func ? UTF8ToString(func) : 'unknown function', - ] - ); + var ___heap_base = 11378176; - var ___call_sighandler = (fp, sig) => ((a1) => dynCall_vi(fp, a1))(sig); + var alignMemory = (size, alignment) => { + return Math.ceil(size / alignment) * alignment; + }; - class ExceptionInfo { - // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. - constructor(excPtr) { - this.excPtr = excPtr; - this.ptr = excPtr - 24; + var getMemory = (size) => { + // After the runtime is initialized, we must only use sbrk() normally. + if (runtimeInitialized) { + // Currently we don't support freeing of static data when modules are + // unloaded via dlclose. This function is tagged as `noleakcheck` to + // avoid having this reported as leak. + return _calloc(size, 1); } - set_type(type) { - HEAPU32[(this.ptr + 4) >> 2] = type; + var ret = ___heap_base; + // Keep __heap_base stack aligned. + var end = ret + alignMemory(size, 16); + ___heap_base = end; + GOT['__heap_base'].value = end; + return ret; + }; + + var isInternalSym = (symName) => { + // TODO: find a way to mark these in the binary or avoid exporting them. + return ( + [ + '__cpp_exception', + '__c_longjmp', + '__wasm_apply_data_relocs', + '__dso_handle', + '__tls_size', + '__tls_align', + '__set_stack_limits', + '_emscripten_tls_init', + '__wasm_init_tls', + '__wasm_call_ctors', + '__start_em_asm', + '__stop_em_asm', + '__start_em_js', + '__stop_em_js', + ].includes(symName) || symName.startsWith('__em_js__') + ); + }; + + var uleb128Encode = (n, target) => { + if (n < 128) { + target.push(n); + } else { + target.push(n % 128 | 128, n >> 7); } - get_type() { - return HEAPU32[(this.ptr + 4) >> 2]; + }; + + var sigToWasmTypes = (sig) => { + var typeNames = { + i: 'i32', + j: 'i64', + f: 'f32', + d: 'f64', + e: 'externref', + p: 'i32', + }; + var type = { + parameters: [], + results: sig[0] == 'v' ? [] : [typeNames[sig[0]]], + }; + for (var i = 1; i < sig.length; ++i) { + type.parameters.push(typeNames[sig[i]]); } - set_destructor(destructor) { - HEAPU32[(this.ptr + 8) >> 2] = destructor; + return type; + }; + + var generateFuncType = (sig, target) => { + var sigRet = sig.slice(0, 1); + var sigParam = sig.slice(1); + var typeCodes = { + i: 0x7f, // i32 + p: 0x7f, // i32 + j: 0x7e, // i64 + f: 0x7d, // f32 + d: 0x7c, // f64 + e: 0x6f, // externref + }; + + // Parameters, length + signatures + target.push(0x60 /* form: func */); + uleb128Encode(sigParam.length, target); + for (var paramType of sigParam) { + target.push(typeCodes[paramType]); } - get_destructor() { - return HEAPU32[(this.ptr + 8) >> 2]; + + // Return values, length + signatures + // With no multi-return in MVP, either 0 (void) or 1 (anything else) + if (sigRet == 'v') { + target.push(0x00); + } else { + target.push(0x01, typeCodes[sigRet]); } - set_caught(caught) { - caught = caught ? 1 : 0; - HEAP8[this.ptr + 12] = caught; + }; + var convertJsFunctionToWasm = (func, sig) => { + // If the type reflection proposal is available, use the new + // "WebAssembly.Function" constructor. + // Otherwise, construct a minimal wasm module importing the JS function and + // re-exporting it. + if (typeof WebAssembly.Function == 'function') { + return new WebAssembly.Function(sigToWasmTypes(sig), func); } - get_caught() { - return HEAP8[this.ptr + 12] != 0; + + // The module is static, with the exception of the type section, which is + // generated based on the signature passed in. + var typeSectionBody = [ + 0x01, // count: 1 + ]; + generateFuncType(sig, typeSectionBody); + + // Rest of the module is static + var bytes = [ + 0x00, + 0x61, + 0x73, + 0x6d, // magic ("\0asm") + 0x01, + 0x00, + 0x00, + 0x00, // version: 1 + 0x01, // Type section code + ]; + // Write the overall length of the type section followed by the body + uleb128Encode(typeSectionBody.length, bytes); + bytes.push(...typeSectionBody); + + // The rest of the module is static + bytes.push( + 0x02, + 0x07, // import section + // (import "e" "f" (func 0 (type 0))) + 0x01, + 0x01, + 0x65, + 0x01, + 0x66, + 0x00, + 0x00, + 0x07, + 0x05, // export section + // (export "f" (func 0 (type 0))) + 0x01, + 0x01, + 0x66, + 0x00, + 0x00 + ); + + // We can compile this wasm module synchronously because it is very small. + // This accepts an import (at "e.f"), that it reroutes to an export (at "f") + var module = new WebAssembly.Module(new Uint8Array(bytes)); + var instance = new WebAssembly.Instance(module, { e: { f: func } }); + var wrappedFunc = instance.exports['f']; + return wrappedFunc; + }; + + var wasmTableMirror = []; + + /** @type {WebAssembly.Table} */ + var wasmTable = new WebAssembly.Table({ + initial: 15750, + element: 'anyfunc', + }); + var getWasmTableEntry = (funcPtr) => { + var func = wasmTableMirror[funcPtr]; + if (!func) { + /** @suppress {checkTypes} */ + wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); } - set_rethrown(rethrown) { - rethrown = rethrown ? 1 : 0; - HEAP8[this.ptr + 13] = rethrown; + return func; + }; + + var updateTableMap = (offset, count) => { + if (functionsInTableMap) { + for (var i = offset; i < offset + count; i++) { + var item = getWasmTableEntry(i); + // Ignore null values. + if (item) { + functionsInTableMap.set(item, i); + } + } } - get_rethrown() { - return HEAP8[this.ptr + 13] != 0; + }; + + var functionsInTableMap; + + var getFunctionAddress = (func) => { + // First, create the map if this is the first use. + if (!functionsInTableMap) { + functionsInTableMap = new WeakMap(); + updateTableMap(0, wasmTable.length); } - // Initialize native structure fields. Should be called once after allocated. - init(type, destructor) { - this.set_adjusted_ptr(0); - this.set_type(type); - this.set_destructor(destructor); + return functionsInTableMap.get(func) || 0; + }; + + var freeTableIndexes = []; + + var getEmptyTableSlot = () => { + // Reuse a free index if there is one, otherwise grow. + if (freeTableIndexes.length) { + return freeTableIndexes.pop(); } - set_adjusted_ptr(adjustedPtr) { - HEAPU32[(this.ptr + 16) >> 2] = adjustedPtr; + // Grow the table + try { + /** @suppress {checkTypes} */ + wasmTable.grow(1); + } catch (err) { + if (!(err instanceof RangeError)) { + throw err; + } + throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.'; } - get_adjusted_ptr() { - return HEAPU32[(this.ptr + 16) >> 2]; + return wasmTable.length - 1; + }; + + var setWasmTableEntry = (idx, func) => { + /** @suppress {checkTypes} */ + wasmTable.set(idx, func); + // With ABORT_ON_WASM_EXCEPTIONS wasmTable.get is overridden to return wrapped + // functions so we need to call it here to retrieve the potential wrapper correctly + // instead of just storing 'func' directly into wasmTableMirror + /** @suppress {checkTypes} */ + wasmTableMirror[idx] = wasmTable.get(idx); + }; + + /** @param {string=} sig */ + var addFunction = (func, sig) => { + // Check if the function is already in the table, to ensure each function + // gets a unique index. + var rtn = getFunctionAddress(func); + if (rtn) { + return rtn; } - } - var exceptionLast = 0; + // It's not in the table, add it now. - var uncaughtExceptionCount = 0; + var ret = getEmptyTableSlot(); - var ___cxa_throw = (ptr, type, destructor) => { - var info = new ExceptionInfo(ptr); - // Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception. - info.init(type, destructor); - exceptionLast = ptr; - uncaughtExceptionCount++; - throw exceptionLast; - }; + // Set the new value. + try { + // Attempting to call this with JS function will cause of table.set() to fail + setWasmTableEntry(ret, func); + } catch (err) { + if (!(err instanceof TypeError)) { + throw err; + } + var wrapped = convertJsFunctionToWasm(func, sig); + setWasmTableEntry(ret, wrapped); + } - var initRandomFill = () => (view) => crypto.getRandomValues(view); + functionsInTableMap.set(func, ret); - var randomFill = (view) => { - // Lazily init on the first invocation. - (randomFill = initRandomFill())(view); + return ret; }; - var PATH = { - isAbs: (path) => path.charAt(0) === '/', - splitPath: (filename) => { - var splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; - return splitPathRe.exec(filename).slice(1); - }, - normalizeArray: (parts, allowAboveRoot) => { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length - 1; i >= 0; i--) { - var last = parts[i]; - if (last === '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } + var updateGOT = (exports, replace) => { + for (var symName in exports) { + if (isInternalSym(symName)) { + continue; } - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up; up--) { - parts.unshift('..'); + + var value = exports[symName]; + + GOT[symName] ||= new WebAssembly.Global({ + value: 'i32', + mutable: true, + }); + if (replace || GOT[symName].value == 0) { + if (typeof value == 'function') { + GOT[symName].value = addFunction(value); + } else if (typeof value == 'number') { + GOT[symName].value = value; + } else { + err( + `unhandled export type for '${symName}': ${typeof value}` + ); } } - return parts; - }, - normalize: (path) => { - var isAbsolute = PATH.isAbs(path), - trailingSlash = path.slice(-1) === '/'; - // Normalize the path - path = PATH.normalizeArray( - path.split('/').filter((p) => !!p), - !isAbsolute - ).join('/'); - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; + } + }; + /** @param {boolean=} replace */ + var relocateExports = (exports, memoryBase, replace) => { + var relocated = {}; + + for (var e in exports) { + var value = exports[e]; + if (typeof value == 'object') { + // a breaking change in the wasm spec, globals are now objects + // https://github.com/WebAssembly/mutable-global/issues/1 + value = value.value; } - return (isAbsolute ? '/' : '') + path; - }, - dirname: (path) => { - var result = PATH.splitPath(path), - root = result[0], - dir = result[1]; - if (!root && !dir) { - // No dirname whatsoever - return '.'; + if (typeof value == 'number') { + value += memoryBase; } - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.slice(0, -1); + relocated[e] = value; + } + updateGOT(relocated, replace); + return relocated; + }; + + var isSymbolDefined = (symName) => { + // Ignore 'stub' symbols that are auto-generated as part of the original + // `wasmImports` used to instantiate the main module. + var existing = wasmImports[symName]; + if (!existing || existing.stub) { + return false; + } + // Even if a symbol exists in wasmImports, and is not itself a stub, it + // could be an ASYNCIFY wrapper function that wraps a stub function. + if (symName in asyncifyStubs && !asyncifyStubs[symName]) { + return false; + } + return true; + }; + + var dynCallLegacy = (sig, ptr, args) => { + sig = sig.replace(/p/g, 'i'); + var f = Module['dynCall_' + sig]; + return f(ptr, ...args); + }; + + var dynCall = (sig, ptr, args = []) => { + var rtn = dynCallLegacy(sig, ptr, args); + return rtn; + }; + + var stackSave = () => _emscripten_stack_get_current(); + + var stackRestore = (val) => __emscripten_stack_restore(val); + var createInvokeFunction = + (sig) => + (ptr, ...args) => { + var sp = stackSave(); + try { + return dynCall(sig, ptr, args); + } catch (e) { + stackRestore(sp); + // Create a try-catch guard that rethrows the Emscripten EH exception. + // Exceptions thrown from C++ will be a pointer (number) and longjmp + // will throw the number Infinity. Use the compact and fast "e !== e+0" + // test to check if e was not a Number. + if (e !== e + 0) throw e; + _setThrew(1, 0); + // In theory this if statement could be done on + // creating the function, but I just added this to + // save wasting code space as it only happens on exception. + if (sig[0] == 'j') return 0n; } - return root + dir; - }, - basename: (path) => path && path.match(/([^\/]+|\/)\/*$/)[1], - join: (...paths) => PATH.normalize(paths.join('/')), - join2: (l, r) => PATH.normalize(l + '/' + r), + }; + var resolveGlobalSymbol = (symName, direct = false) => { + var sym; + if (isSymbolDefined(symName)) { + sym = wasmImports[symName]; + } + // Asm.js-style exception handling: invoke wrapper generation + else if (symName.startsWith('invoke_')) { + // Create (and cache) new invoke_ functions on demand. + sym = wasmImports[symName] = createInvokeFunction( + symName.split('_')[1] + ); + } + return { sym, name: symName }; }; - var PATH_FS = { - resolve: (...args) => { - var resolvedPath = '', - resolvedAbsolute = false; - for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = i >= 0 ? args[i] : FS.cwd(); - // Skip empty and invalid entries - if (typeof path != 'string') { - throw new TypeError( - 'Arguments to path.resolve must be strings' - ); - } else if (!path) { - return ''; + var onPostCtors = []; + var addOnPostCtor = (cb) => onPostCtors.unshift(cb); + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + var UTF8ToString = (ptr, maxBytesToRead) => { + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; + }; + Module['UTF8ToString'] = UTF8ToString; + + /** + * @param {string=} libName + * @param {Object=} localScope + * @param {number=} handle + */ + var loadWebAssemblyModule = ( + binary, + flags, + libName, + localScope, + handle + ) => { + var metadata = getDylinkMetadata(binary); + currentModuleWeakSymbols = metadata.weakImports; + + // loadModule loads the wasm module after all its dependencies have been loaded. + // can be called both sync/async. + function loadModule() { + // alignments are powers of 2 + var memAlign = Math.pow(2, metadata.memoryAlign); + // prepare memory + var memoryBase = metadata.memorySize + ? alignMemory( + getMemory(metadata.memorySize + memAlign), + memAlign + ) + : 0; // TODO: add to cleanups + var tableBase = metadata.tableSize ? wasmTable.length : 0; + if (handle) { + HEAP8[handle + 8] = 1; + HEAPU32[(handle + 12) >> 2] = memoryBase; + HEAP32[(handle + 16) >> 2] = metadata.memorySize; + HEAPU32[(handle + 20) >> 2] = tableBase; + HEAP32[(handle + 24) >> 2] = metadata.tableSize; + } + + if (metadata.tableSize) { + wasmTable.grow(metadata.tableSize); + } + + // This is the export map that we ultimately return. We declare it here + // so it can be used within resolveSymbol. We resolve symbols against + // this local symbol map in the case there they are not present on the + // global Module object. We need this fallback because Modules sometime + // need to import their own symbols + var moduleExports; + + function resolveSymbol(sym) { + var resolved = resolveGlobalSymbol(sym).sym; + if (!resolved && localScope) { + resolved = localScope[sym]; } - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = PATH.isAbs(path); - } - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - resolvedPath = PATH.normalizeArray( - resolvedPath.split('/').filter((p) => !!p), - !resolvedAbsolute - ).join('/'); - return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'; - }, - relative: (from, to) => { - from = PATH_FS.resolve(from).slice(1); - to = PATH_FS.resolve(to).slice(1); - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; + if (!resolved) { + resolved = moduleExports[sym]; } - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; + return resolved; + } + + // TODO kill ↓↓↓ (except "symbols local to this module", it will likely be + // not needed if we require that if A wants symbols from B it has to link + // to B explicitly: similarly to -Wl,--no-undefined) + // + // wasm dynamic libraries are pure wasm, so they cannot assist in + // their own loading. When side module A wants to import something + // provided by a side module B that is loaded later, we need to + // add a layer of indirection, but worse, we can't even tell what + // to add the indirection for, without inspecting what A's imports + // are. To do that here, we use a JS proxy (another option would + // be to inspect the binary directly). + var proxyHandler = { + get(stubs, prop) { + // symbols that should be local to this module + switch (prop) { + case '__memory_base': + return memoryBase; + case '__table_base': + return tableBase; + } + if (prop in wasmImports && !wasmImports[prop].stub) { + // No stub needed, symbol already exists in symbol table + var res = wasmImports[prop]; + // Asyncify wraps exports, and we need to look through those wrappers. + if (res.orig) { + res = res.orig; + } + return res; + } + // Return a stub function that will resolve the symbol + // when first called. + if (!(prop in stubs)) { + var resolved; + stubs[prop] = (...args) => { + resolved ||= resolveSymbol(prop); + return resolved(...args); + }; + } + return stubs[prop]; + }, + }; + var proxy = new Proxy({}, proxyHandler); + var info = { + 'GOT.mem': new Proxy({}, GOTHandler), + 'GOT.func': new Proxy({}, GOTHandler), + env: proxy, + wasi_snapshot_preview1: proxy, + }; + + function postInstantiation(module, instance) { + // add new entries to functionsInTableMap + updateTableMap(tableBase, metadata.tableSize); + moduleExports = relocateExports(instance.exports, memoryBase); + moduleExports = Asyncify.instrumentWasmExports(moduleExports); + if (!flags.allowUndefined) { + reportUndefinedSymbols(); } - if (start > end) return []; - return arr.slice(start, end - start + 1); - } - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; + + function addEmAsm(addr, body) { + var args = []; + var arity = 0; + for (; arity < 16; arity++) { + if (body.indexOf('$' + arity) != -1) { + args.push('$' + arity); + } else { + break; + } + } + args = args.join(','); + var func = `(${args}) => { ${body} };`; + ASM_CONSTS[start] = eval(func); + } + + // Add any EM_ASM function that exist in the side module + if ('__start_em_asm' in moduleExports) { + var start = moduleExports['__start_em_asm']; + var stop = moduleExports['__stop_em_asm']; + + while (start < stop) { + var jsString = UTF8ToString(start); + addEmAsm(start, jsString); + start = HEAPU8.indexOf(0, start) + 1; + } + } + + function addEmJs(name, cSig, body) { + // The signature here is a C signature (e.g. "(int foo, char* bar)"). + // See `create_em_js` in emcc.py` for the build-time version of this + // code. + var jsArgs = []; + cSig = cSig.slice(1, -1); + if (cSig != 'void') { + cSig = cSig.split(','); + for (var i in cSig) { + var jsArg = cSig[i].split(' ').pop(); + jsArgs.push(jsArg.replace('*', '')); + } + } + var func = `(${jsArgs}) => ${body};`; + moduleExports[name] = eval(func); + } + + for (var name in moduleExports) { + if (name.startsWith('__em_js__')) { + var start = moduleExports[name]; + var jsString = UTF8ToString(start); + // EM_JS strings are stored in the data section in the form + // SIG<::>BODY. + var parts = jsString.split('<::>'); + addEmJs( + name.replace('__em_js__', ''), + parts[0], + parts[1] + ); + delete moduleExports[name]; + } + } + + // initialize the module + var applyRelocs = moduleExports['__wasm_apply_data_relocs']; + if (applyRelocs) { + if (runtimeInitialized) { + applyRelocs(); + } else { + __RELOC_FUNCS__.push(applyRelocs); + } + } + var init = moduleExports['__wasm_call_ctors']; + if (init) { + if (runtimeInitialized) { + init(); + } else { + // we aren't ready to run compiled code yet + addOnPostCtor(init); + } } + return moduleExports; } - var outputParts = []; - for (var i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); + + if (flags.loadAsync) { + if (binary instanceof WebAssembly.Module) { + var instance = new WebAssembly.Instance(binary, info); + return Promise.resolve(postInstantiation(binary, instance)); + } + return WebAssembly.instantiate(binary, info).then((result) => + postInstantiation(result.module, result.instance) + ); } - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - return outputParts.join('/'); - }, - }; - var FS_stdin_getChar_buffer = []; + var module = + binary instanceof WebAssembly.Module + ? binary + : new WebAssembly.Module(binary); + var instance = new WebAssembly.Instance(module, info); + return postInstantiation(module, instance); + } - var lengthBytesUTF8 = (str) => { - var len = 0; - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - var c = str.charCodeAt(i); - // possibly a lead surrogate - if (c <= 127) { - len++; - } else if (c <= 2047) { - len += 2; - } else if (c >= 55296 && c <= 57343) { - len += 4; - ++i; - } else { - len += 3; - } + // now load needed libraries and the module itself. + if (flags.loadAsync) { + return metadata.neededDynlibs + .reduce( + (chain, dynNeeded) => + chain.then(() => + loadDynamicLibrary(dynNeeded, flags, localScope) + ), + Promise.resolve() + ) + .then(loadModule); } - return len; + + metadata.neededDynlibs.forEach((needed) => + loadDynamicLibrary(needed, flags, localScope) + ); + return loadModule(); }; - Module['lengthBytesUTF8'] = lengthBytesUTF8; + var mergeLibSymbols = (exports, libName) => { + registerDynCallSymbols(exports); + // add symbols into global namespace TODO: weak linking etc. + for (var [sym, exp] of Object.entries(exports)) { + // When RTLD_GLOBAL is enabled, the symbols defined by this shared object + // will be made available for symbol resolution of subsequently loaded + // shared objects. + // + // We should copy the symbols (which include methods and variables) from + // SIDE_MODULE to MAIN_MODULE. + const setImport = (target) => { + if (target in asyncifyStubs) { + asyncifyStubs[target] = exp; + } + if (!isSymbolDefined(target)) { + wasmImports[target] = exp; + } + }; + setImport(sym); - var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { - // Parameter maxBytesToWrite is not optional. Negative values, 0, null, - // undefined and false each don't write out any bytes. - if (!(maxBytesToWrite > 0)) return 0; - var startIdx = outIdx; - var endIdx = outIdx + maxBytesToWrite - 1; - // -1 for string null terminator. - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description - // and https://www.ietf.org/rfc/rfc2279.txt - // and https://tools.ietf.org/html/rfc3629 - var u = str.charCodeAt(i); - // possibly a lead surrogate - if (u >= 55296 && u <= 57343) { - var u1 = str.charCodeAt(++i); - u = (65536 + ((u & 1023) << 10)) | (u1 & 1023); + // Special case for handling of main symbol: If a side module exports + // `main` that also acts a definition for `__main_argc_argv` and vice + // versa. + const main_alias = '__main_argc_argv'; + if (sym == 'main') { + setImport(main_alias); } - if (u <= 127) { - if (outIdx >= endIdx) break; - heap[outIdx++] = u; - } else if (u <= 2047) { - if (outIdx + 1 >= endIdx) break; - heap[outIdx++] = 192 | (u >> 6); - heap[outIdx++] = 128 | (u & 63); - } else if (u <= 65535) { - if (outIdx + 2 >= endIdx) break; - heap[outIdx++] = 224 | (u >> 12); - heap[outIdx++] = 128 | ((u >> 6) & 63); - heap[outIdx++] = 128 | (u & 63); - } else { - if (outIdx + 3 >= endIdx) break; - heap[outIdx++] = 240 | (u >> 18); - heap[outIdx++] = 128 | ((u >> 12) & 63); - heap[outIdx++] = 128 | ((u >> 6) & 63); - heap[outIdx++] = 128 | (u & 63); + if (sym == main_alias) { + setImport('main'); } } - // Null-terminate the pointer to the buffer. - heap[outIdx] = 0; - return outIdx - startIdx; }; - /** @type {function(string, boolean=, number=)} */ var intArrayFromString = - (stringy, dontAddNull, length) => { - var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; - var u8array = new Array(len); - var numBytesWritten = stringToUTF8Array( - stringy, - u8array, - 0, - u8array.length - ); - if (dontAddNull) u8array.length = numBytesWritten; - return u8array; + var asyncLoad = async (url) => { + var arrayBuffer = await readAsync(url); + return new Uint8Array(arrayBuffer); + }; + asyncLoad.isAsync = true; + + var preloadPlugins = Module['preloadPlugins'] || []; + var registerWasmPlugin = () => { + // Use string keys here to avoid minification since the plugin consumer + // also uses string keys. + var wasmPlugin = { + promiseChainEnd: Promise.resolve(), + canHandle: (name) => { + return !Module['noWasmDecoding'] && name.endsWith('.so'); + }, + handle: (byteArray, name, onload, onerror) => { + // loadWebAssemblyModule can not load modules out-of-order, so rather + // than just running the promises in parallel, this makes a chain of + // promises to run in series. + wasmPlugin['promiseChainEnd'] = wasmPlugin['promiseChainEnd'] + .then(() => + loadWebAssemblyModule( + byteArray, + { loadAsync: true, nodelete: true }, + name, + {} + ) + ) + .then( + (exports) => { + preloadedWasm[name] = exports; + onload(byteArray); + }, + (error) => { + err( + `failed to instantiate wasm: ${name}: ${error}` + ); + onerror(); + } + ); + }, }; + preloadPlugins.push(wasmPlugin); + }; + var preloadedWasm = {}; - var FS_stdin_getChar = () => { - if (!FS_stdin_getChar_buffer.length) { - var result = null; - if (ENVIRONMENT_IS_NODE) { - // we will read data by chunks of BUFSIZE - var BUFSIZE = 256; - var buf = Buffer.alloc(BUFSIZE); - var bytesRead = 0; - // For some reason we must suppress a closure warning here, even though - // fd definitely exists on process.stdin, and is even the proper way to - // get the fd of stdin, - // https://github.com/nodejs/help/issues/2136#issuecomment-523649904 - // This started to happen after moving this logic out of library_tty.js, - // so it is related to the surrounding code in some unclear manner. - /** @suppress {missingProperties} */ var fd = process.stdin.fd; - try { - bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); - } catch (e) { - // Cross-platform differences: on Windows, reading EOF throws an - // exception, but on other OSes, reading EOF returns 0. Uniformize - // behavior by treating the EOF exception to return 0. - if (e.toString().includes('EOF')) bytesRead = 0; - else throw e; - } - if (bytesRead > 0) { - result = buf.slice(0, bytesRead).toString('utf-8'); - } - } else { - } - if (!result) { - return null; + var registerDynCallSymbols = (exports) => { + for (var [sym, exp] of Object.entries(exports)) { + if (sym.startsWith('dynCall_') && !Module.hasOwnProperty(sym)) { + Module[sym] = exp; } - FS_stdin_getChar_buffer = intArrayFromString(result, true); } - return FS_stdin_getChar_buffer.shift(); }; - var TTY = { - ttys: [], - init() {}, - shutdown() {}, - register(dev, ops) { - TTY.ttys[dev] = { - input: [], - output: [], - ops, - }; - FS.registerDevice(dev, TTY.stream_ops); - }, - stream_ops: { - open(stream) { - var tty = TTY.ttys[stream.node.rdev]; - if (!tty) { - throw new FS.ErrnoError(43); - } - stream.tty = tty; - stream.seekable = false; - }, - close(stream) { - // flush any pending line data - stream.tty.ops.fsync(stream.tty); - }, - fsync(stream) { - stream.tty.ops.fsync(stream.tty); - }, - read(stream, buffer, offset, length, pos) { - if (!stream.tty || !stream.tty.ops.get_char) { - throw new FS.ErrnoError(60); - } - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = stream.tty.ops.get_char(stream.tty); - } catch (e) { - throw new FS.ErrnoError(29); - } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(6); - } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset + i] = result; - } - if (bytesRead) { - stream.node.atime = Date.now(); - } - return bytesRead; - }, - write(stream, buffer, offset, length, pos) { - if (!stream.tty || !stream.tty.ops.put_char) { - throw new FS.ErrnoError(60); - } - try { - for (var i = 0; i < length; i++) { - stream.tty.ops.put_char(stream.tty, buffer[offset + i]); - } - } catch (e) { - throw new FS.ErrnoError(29); - } - if (length) { - stream.node.mtime = stream.node.ctime = Date.now(); + /** + * @param {number=} handle + * @param {Object=} localScope + */ + function loadDynamicLibrary( + libName, + flags = { global: true, nodelete: true }, + localScope, + handle + ) { + // when loadDynamicLibrary did not have flags, libraries were loaded + // globally & permanently + + var dso = LDSO.loadedLibsByName[libName]; + if (dso) { + // the library is being loaded or has been loaded already. + if (!flags.global) { + if (localScope) { + Object.assign(localScope, dso.exports); } - return i; - }, - }, - default_tty_ops: { - get_char(tty) { - return FS_stdin_getChar(); - }, - put_char(tty, val) { - if (val === null || val === 10) { - out(UTF8ArrayToString(tty.output)); - tty.output = []; - } else { - if (val != 0) tty.output.push(val); + registerDynCallSymbols(dso.exports); + } else if (!dso.global) { + // The library was previously loaded only locally but not + // we have a request with global=true. + dso.global = true; + mergeLibSymbols(dso.exports, libName); + } + // same for "nodelete" + if (flags.nodelete && dso.refcount !== Infinity) { + dso.refcount = Infinity; + } + dso.refcount++; + if (handle) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return flags.loadAsync ? Promise.resolve(true) : true; + } + + // allocate new DSO + dso = newDSO(libName, handle, 'loading'); + dso.refcount = flags.nodelete ? Infinity : 1; + dso.global = flags.global; + + // libName -> libData + function loadLibData() { + // for wasm, we can use fetch for async, but for fs mode we can only imitate it + if (handle) { + var data = HEAPU32[(handle + 28) >> 2]; + var dataSize = HEAPU32[(handle + 32) >> 2]; + if (data && dataSize) { + var libData = HEAP8.slice(data, data + dataSize); + return flags.loadAsync ? Promise.resolve(libData) : libData; } - }, - fsync(tty) { - if (tty.output?.length > 0) { - out(UTF8ArrayToString(tty.output)); - tty.output = []; + } + + var libFile = locateFile(libName); + if (flags.loadAsync) { + return asyncLoad(libFile); + } + + // load the binary synchronously + if (!readBinary) { + throw new Error( + `${libFile}: file not found, and synchronous loading of external files is not available` + ); + } + return readBinary(libFile); + } + + // libName -> exports + function getExports() { + // lookup preloaded cache first + var preloaded = preloadedWasm[libName]; + if (preloaded) { + return flags.loadAsync ? Promise.resolve(preloaded) : preloaded; + } + + // module not preloaded - load lib data and create new module from it + if (flags.loadAsync) { + return loadLibData().then((libData) => + loadWebAssemblyModule( + libData, + flags, + libName, + localScope, + handle + ) + ); + } + + return loadWebAssemblyModule( + loadLibData(), + flags, + libName, + localScope, + handle + ); + } + + // module for lib is loaded - update the dso & global namespace + function moduleLoaded(exports) { + if (dso.global) { + mergeLibSymbols(exports, libName); + } else if (localScope) { + Object.assign(localScope, exports); + registerDynCallSymbols(exports); + } + dso.exports = exports; + } + + if (flags.loadAsync) { + return getExports().then((exports) => { + moduleLoaded(exports); + return true; + }); + } + + moduleLoaded(getExports()); + return true; + } + + var reportUndefinedSymbols = () => { + for (var [symName, entry] of Object.entries(GOT)) { + if (entry.value == 0) { + var value = resolveGlobalSymbol(symName, true).sym; + if (!value && !entry.required) { + // Ignore undefined symbols that are imported as weak. + continue; } - }, - ioctl_tcgets(tty) { - // typical setting - return { - c_iflag: 25856, - c_oflag: 5, - c_cflag: 191, - c_lflag: 35387, - c_cc: [ - 3, 28, 127, 21, 4, 0, 1, 0, 17, 19, 26, 0, 18, 15, 23, - 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - }; - }, - ioctl_tcsets(tty, optional_actions, data) { - // currently just ignore - return 0; - }, - ioctl_tiocgwinsz(tty) { - return [24, 80]; - }, - }, - default_tty1_ops: { - put_char(tty, val) { - if (val === null || val === 10) { - err(UTF8ArrayToString(tty.output)); - tty.output = []; + if (typeof value == 'function') { + /** @suppress {checkTypes} */ + entry.value = addFunction(value, value.sig); + } else if (typeof value == 'number') { + entry.value = value; } else { - if (val != 0) tty.output.push(val); - } - }, - fsync(tty) { - if (tty.output?.length > 0) { - err(UTF8ArrayToString(tty.output)); - tty.output = []; + throw new Error( + `bad export type for '${symName}': ${typeof value}` + ); } - }, - }, + } + } }; + var loadDylibs = () => { + if (!dynamicLibraries.length) { + reportUndefinedSymbols(); + return; + } - var zeroMemory = (ptr, size) => HEAPU8.fill(0, ptr, ptr + size); + // Load binaries asynchronously + addRunDependency('loadDylibs'); + dynamicLibraries + .reduce( + (chain, lib) => + chain.then(() => + loadDynamicLibrary(lib, { + loadAsync: true, + global: true, + nodelete: true, + allowUndefined: true, + }) + ), + Promise.resolve() + ) + .then(() => { + // we got them all, wonderful + reportUndefinedSymbols(); + removeRunDependency('loadDylibs'); + }); + }; - var alignMemory = (size, alignment) => - Math.ceil(size / alignment) * alignment; + var noExitRuntime = Module['noExitRuntime'] || false; - var mmapAlloc = (size) => { - size = alignMemory(size, 65536); - var ptr = _emscripten_builtin_memalign(65536, size); - if (ptr) zeroMemory(ptr, size); - return ptr; - }; + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */ + function setValue(ptr, value, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': + HEAP8[ptr] = value; + break; + case 'i8': + HEAP8[ptr] = value; + break; + case 'i16': + HEAP16[ptr >> 1] = value; + break; + case 'i32': + HEAP32[ptr >> 2] = value; + break; + case 'i64': + HEAP64[ptr >> 3] = BigInt(value); + break; + case 'float': + HEAPF32[ptr >> 2] = value; + break; + case 'double': + HEAPF64[ptr >> 3] = value; + break; + case '*': + HEAPU32[ptr >> 2] = value; + break; + default: + abort(`invalid type for setValue: ${type}`); + } + } - var MEMFS = { - ops_table: null, - mount(mount) { - return MEMFS.createNode(null, '/', 16895, 0); - }, - createNode(parent, name, mode, dev) { - if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { - // no supported - throw new FS.ErrnoError(63); - } - MEMFS.ops_table ||= { - dir: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - lookup: MEMFS.node_ops.lookup, - mknod: MEMFS.node_ops.mknod, - rename: MEMFS.node_ops.rename, - unlink: MEMFS.node_ops.unlink, - rmdir: MEMFS.node_ops.rmdir, - readdir: MEMFS.node_ops.readdir, - symlink: MEMFS.node_ops.symlink, - }, - stream: { - llseek: MEMFS.stream_ops.llseek, - }, - }, - file: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - }, - stream: { - llseek: MEMFS.stream_ops.llseek, - read: MEMFS.stream_ops.read, - write: MEMFS.stream_ops.write, - mmap: MEMFS.stream_ops.mmap, - msync: MEMFS.stream_ops.msync, - }, - }, - link: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - readlink: MEMFS.node_ops.readlink, - }, - stream: {}, - }, - chrdev: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - }, - stream: FS.chrdev_stream_ops, - }, - }; - var node = FS.createNode(parent, name, mode, dev); - if (FS.isDir(node.mode)) { - node.node_ops = MEMFS.ops_table.dir.node; - node.stream_ops = MEMFS.ops_table.dir.stream; - node.contents = {}; - } else if (FS.isFile(node.mode)) { - node.node_ops = MEMFS.ops_table.file.node; - node.stream_ops = MEMFS.ops_table.file.stream; - node.usedBytes = 0; - // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity. - // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred - // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size - // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. - node.contents = null; - } else if (FS.isLink(node.mode)) { - node.node_ops = MEMFS.ops_table.link.node; - node.stream_ops = MEMFS.ops_table.link.stream; - } else if (FS.isChrdev(node.mode)) { - node.node_ops = MEMFS.ops_table.chrdev.node; - node.stream_ops = MEMFS.ops_table.chrdev.stream; + var ___assert_fail = (condition, filename, line, func) => + abort( + `Assertion failed: ${UTF8ToString(condition)}, at: ` + + [ + filename ? UTF8ToString(filename) : 'unknown filename', + line, + func ? UTF8ToString(func) : 'unknown function', + ] + ); + ___assert_fail.sig = 'vppip'; + + var ___asyncify_data = new WebAssembly.Global( + { value: 'i32', mutable: true }, + 0 + ); + + var ___asyncify_state = new WebAssembly.Global( + { value: 'i32', mutable: true }, + 0 + ); + + var ___call_sighandler = (fp, sig) => + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + sig + ); + ___call_sighandler.sig = 'vpi'; + + class ExceptionInfo { + // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. + constructor(excPtr) { + this.excPtr = excPtr; + this.ptr = excPtr - 24; + } + + set_type(type) { + HEAPU32[(this.ptr + 4) >> 2] = type; + } + + get_type() { + return HEAPU32[(this.ptr + 4) >> 2]; + } + + set_destructor(destructor) { + HEAPU32[(this.ptr + 8) >> 2] = destructor; + } + + get_destructor() { + return HEAPU32[(this.ptr + 8) >> 2]; + } + + set_caught(caught) { + caught = caught ? 1 : 0; + HEAP8[this.ptr + 12] = caught; + } + + get_caught() { + return HEAP8[this.ptr + 12] != 0; + } + + set_rethrown(rethrown) { + rethrown = rethrown ? 1 : 0; + HEAP8[this.ptr + 13] = rethrown; + } + + get_rethrown() { + return HEAP8[this.ptr + 13] != 0; + } + + // Initialize native structure fields. Should be called once after allocated. + init(type, destructor) { + this.set_adjusted_ptr(0); + this.set_type(type); + this.set_destructor(destructor); + } + + set_adjusted_ptr(adjustedPtr) { + HEAPU32[(this.ptr + 16) >> 2] = adjustedPtr; + } + + get_adjusted_ptr() { + return HEAPU32[(this.ptr + 16) >> 2]; + } + } + + var exceptionLast = 0; + + var uncaughtExceptionCount = 0; + var ___cxa_throw = (ptr, type, destructor) => { + var info = new ExceptionInfo(ptr); + // Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception. + info.init(type, destructor); + exceptionLast = ptr; + uncaughtExceptionCount++; + throw exceptionLast; + }; + ___cxa_throw.sig = 'vppp'; + + var ___memory_base = new WebAssembly.Global( + { value: 'i32', mutable: false }, + 1024 + ); + + var ___stack_high = 11378176; + + var ___stack_low = 11312640; + + var ___stack_pointer = new WebAssembly.Global( + { value: 'i32', mutable: true }, + 11378176 + ); + + var PATH = { + isAbs: (path) => path.charAt(0) === '/', + splitPath: (filename) => { + var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + return splitPathRe.exec(filename).slice(1); + }, + normalizeArray: (parts, allowAboveRoot) => { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } } - node.atime = node.mtime = node.ctime = Date.now(); - // add the new node to the parent - if (parent) { - parent.contents[name] = node; - parent.atime = parent.mtime = parent.ctime = node.atime; + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up; up--) { + parts.unshift('..'); + } } - return node; + return parts; }, - getFileDataAsTypedArray(node) { - if (!node.contents) return new Uint8Array(0); - if (node.contents.subarray) - return node.contents.subarray(0, node.usedBytes); - // Make sure to not return excess unused bytes. - return new Uint8Array(node.contents); + normalize: (path) => { + var isAbsolute = PATH.isAbs(path), + trailingSlash = path.slice(-1) === '/'; + // Normalize the path + path = PATH.normalizeArray( + path.split('/').filter((p) => !!p), + !isAbsolute + ).join('/'); + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + return (isAbsolute ? '/' : '') + path; }, - expandFileStorage(node, newCapacity) { - var prevCapacity = node.contents ? node.contents.length : 0; - if (prevCapacity >= newCapacity) return; - // No need to expand, the storage was already large enough. - // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. - // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to - // avoid overshooting the allocation cap by a very large margin. - var CAPACITY_DOUBLING_MAX = 1024 * 1024; - newCapacity = Math.max( - newCapacity, - (prevCapacity * - (prevCapacity < CAPACITY_DOUBLING_MAX ? 2 : 1.125)) >>> - 0 - ); - if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); - // At minimum allocate 256b for each file when expanding. - var oldContents = node.contents; - node.contents = new Uint8Array(newCapacity); - // Allocate new storage. - if (node.usedBytes > 0) - node.contents.set(oldContents.subarray(0, node.usedBytes), 0); + dirname: (path) => { + var result = PATH.splitPath(path), + root = result[0], + dir = result[1]; + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.slice(0, -1); + } + return root + dir; }, - resizeFileStorage(node, newSize) { - if (node.usedBytes == newSize) return; - if (newSize == 0) { - node.contents = null; - // Fully decommit when requesting a resize to zero. - node.usedBytes = 0; - } else { - var oldContents = node.contents; - node.contents = new Uint8Array(newSize); - // Allocate new storage. - if (oldContents) { - node.contents.set( - oldContents.subarray( - 0, - Math.min(newSize, node.usedBytes) - ) + basename: (path) => path && path.match(/([^\/]+|\/)\/*$/)[1], + join: (...paths) => PATH.normalize(paths.join('/')), + join2: (l, r) => PATH.normalize(l + '/' + r), + }; + + var initRandomFill = () => { + return (view) => crypto.getRandomValues(view); + }; + var randomFill = (view) => { + // Lazily init on the first invocation. + (randomFill = initRandomFill())(view); + }; + + var PATH_FS = { + resolve: (...args) => { + var resolvedPath = '', + resolvedAbsolute = false; + for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = i >= 0 ? args[i] : FS.cwd(); + // Skip empty and invalid entries + if (typeof path != 'string') { + throw new TypeError( + 'Arguments to path.resolve must be strings' ); + } else if (!path) { + return ''; // an invalid portion invalidates the whole thing } - node.usedBytes = newSize; + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = PATH.isAbs(path); } + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + resolvedPath = PATH.normalizeArray( + resolvedPath.split('/').filter((p) => !!p), + !resolvedAbsolute + ).join('/'); + return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'; }, - node_ops: { - getattr(node) { - var attr = {}; - // device numbers reuse inode numbers. - attr.dev = FS.isChrdev(node.mode) ? node.id : 1; - attr.ino = node.id; - attr.mode = node.mode; - attr.nlink = 1; - attr.uid = 0; - attr.gid = 0; - attr.rdev = node.rdev; - if (FS.isDir(node.mode)) { - attr.size = 4096; - } else if (FS.isFile(node.mode)) { - attr.size = node.usedBytes; - } else if (FS.isLink(node.mode)) { - attr.size = node.link.length; - } else { - attr.size = 0; + relative: (from, to) => { + from = PATH_FS.resolve(from).slice(1); + to = PATH_FS.resolve(to).slice(1); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; } - attr.atime = new Date(node.atime); - attr.mtime = new Date(node.mtime); - attr.ctime = new Date(node.ctime); - // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), - // but this is not required by the standard. - attr.blksize = 4096; - attr.blocks = Math.ceil(attr.size / attr.blksize); - return attr; - }, - setattr(node, attr) { - for (const key of ['mode', 'atime', 'mtime', 'ctime']) { - if (attr[key] != null) { - node[key] = attr[key]; - } + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; } - if (attr.size !== undefined) { - MEMFS.resizeFileStorage(node, attr.size); + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + return outputParts.join('/'); + }, + }; + + var FS_stdin_getChar_buffer = []; + + var lengthBytesUTF8 = (str) => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var c = str.charCodeAt(i); // possibly a lead surrogate + if (c <= 0x7f) { + len++; + } else if (c <= 0x7ff) { + len += 2; + } else if (c >= 0xd800 && c <= 0xdfff) { + len += 4; + ++i; + } else { + len += 3; + } + } + return len; + }; + Module['lengthBytesUTF8'] = lengthBytesUTF8; + + var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + // Parameter maxBytesToWrite is not optional. Negative values, 0, null, + // undefined and false each don't write out any bytes. + if (!(maxBytesToWrite > 0)) return 0; + + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator. + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description + // and https://www.ietf.org/rfc/rfc2279.txt + // and https://tools.ietf.org/html/rfc3629 + var u = str.charCodeAt(i); // possibly a lead surrogate + if (u >= 0xd800 && u <= 0xdfff) { + var u1 = str.charCodeAt(++i); + u = (0x10000 + ((u & 0x3ff) << 10)) | (u1 & 0x3ff); + } + if (u <= 0x7f) { + if (outIdx >= endIdx) break; + heap[outIdx++] = u; + } else if (u <= 0x7ff) { + if (outIdx + 1 >= endIdx) break; + heap[outIdx++] = 0xc0 | (u >> 6); + heap[outIdx++] = 0x80 | (u & 63); + } else if (u <= 0xffff) { + if (outIdx + 2 >= endIdx) break; + heap[outIdx++] = 0xe0 | (u >> 12); + heap[outIdx++] = 0x80 | ((u >> 6) & 63); + heap[outIdx++] = 0x80 | (u & 63); + } else { + if (outIdx + 3 >= endIdx) break; + heap[outIdx++] = 0xf0 | (u >> 18); + heap[outIdx++] = 0x80 | ((u >> 12) & 63); + heap[outIdx++] = 0x80 | ((u >> 6) & 63); + heap[outIdx++] = 0x80 | (u & 63); + } + } + // Null-terminate the pointer to the buffer. + heap[outIdx] = 0; + return outIdx - startIdx; + }; + /** @type {function(string, boolean=, number=)} */ + var intArrayFromString = (stringy, dontAddNull, length) => { + var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; + var u8array = new Array(len); + var numBytesWritten = stringToUTF8Array( + stringy, + u8array, + 0, + u8array.length + ); + if (dontAddNull) u8array.length = numBytesWritten; + return u8array; + }; + var FS_stdin_getChar = () => { + if (!FS_stdin_getChar_buffer.length) { + var result = null; + if (ENVIRONMENT_IS_NODE) { + // we will read data by chunks of BUFSIZE + var BUFSIZE = 256; + var buf = Buffer.alloc(BUFSIZE); + var bytesRead = 0; + + // For some reason we must suppress a closure warning here, even though + // fd definitely exists on process.stdin, and is even the proper way to + // get the fd of stdin, + // https://github.com/nodejs/help/issues/2136#issuecomment-523649904 + // This started to happen after moving this logic out of library_tty.js, + // so it is related to the surrounding code in some unclear manner. + /** @suppress {missingProperties} */ + var fd = process.stdin.fd; + + try { + bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); + } catch (e) { + // Cross-platform differences: on Windows, reading EOF throws an + // exception, but on other OSes, reading EOF returns 0. Uniformize + // behavior by treating the EOF exception to return 0. + if (e.toString().includes('EOF')) bytesRead = 0; + else throw e; + } + + if (bytesRead > 0) { + result = buf.slice(0, bytesRead).toString('utf-8'); + } + } else { + } + if (!result) { + return null; + } + FS_stdin_getChar_buffer = intArrayFromString(result, true); + } + return FS_stdin_getChar_buffer.shift(); + }; + var TTY = { + ttys: [], + init() { + // https://github.com/emscripten-core/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // currently, FS.init does not distinguish if process.stdin is a file or TTY + // // device, it always assumes it's a TTY device. because of this, we're forcing + // // process.stdin to UTF8 encoding to at least make stdin reading compatible + // // with text files until FS.init can be refactored. + // process.stdin.setEncoding('utf8'); + // } + }, + shutdown() { + // https://github.com/emscripten-core/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? + // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation + // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? + // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle + // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call + // process.stdin.pause(); + // } + }, + register(dev, ops) { + TTY.ttys[dev] = { input: [], output: [], ops: ops }; + FS.registerDevice(dev, TTY.stream_ops); + }, + stream_ops: { + open(stream) { + var tty = TTY.ttys[stream.node.rdev]; + if (!tty) { + throw new FS.ErrnoError(43); } + stream.tty = tty; + stream.seekable = false; }, - lookup(parent, name) { - throw MEMFS.doesNotExistError; + close(stream) { + // flush any pending line data + stream.tty.ops.fsync(stream.tty); }, - mknod(parent, name, mode, dev) { - return MEMFS.createNode(parent, name, mode, dev); + fsync(stream) { + stream.tty.ops.fsync(stream.tty); }, - rename(old_node, new_dir, new_name) { - var new_node; + read(stream, buffer, offset, length, pos /* ignored */) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new FS.ErrnoError(60); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new FS.ErrnoError(29); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(6); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.atime = Date.now(); + } + return bytesRead; + }, + write(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new FS.ErrnoError(60); + } try { - new_node = FS.lookupNode(new_dir, new_name); - } catch (e) {} - if (new_node) { - if (FS.isDir(old_node.mode)) { - // if we're overwriting a directory at new_name, make sure it's empty. - for (var i in new_node.contents) { - throw new FS.ErrnoError(55); - } + for (var i = 0; i < length; i++) { + stream.tty.ops.put_char(stream.tty, buffer[offset + i]); } - FS.hashRemoveNode(new_node); + } catch (e) { + throw new FS.ErrnoError(29); } - // do the internal rewiring - delete old_node.parent.contents[old_node.name]; - new_dir.contents[new_name] = old_node; - old_node.name = new_name; - new_dir.ctime = - new_dir.mtime = - old_node.parent.ctime = - old_node.parent.mtime = - Date.now(); + if (length) { + stream.node.mtime = stream.node.ctime = Date.now(); + } + return i; }, - unlink(parent, name) { - delete parent.contents[name]; - parent.ctime = parent.mtime = Date.now(); + }, + default_tty_ops: { + get_char(tty) { + return FS_stdin_getChar(); }, - rmdir(parent, name) { - var node = FS.lookupNode(parent, name); - for (var i in node.contents) { - throw new FS.ErrnoError(55); + put_char(tty, val) { + if (val === null || val === 10) { + out(UTF8ArrayToString(tty.output)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); // val == 0 would cut text output off in the middle. } - delete parent.contents[name]; - parent.ctime = parent.mtime = Date.now(); }, - readdir(node) { - return ['.', '..', ...Object.keys(node.contents)]; + fsync(tty) { + if (tty.output?.length > 0) { + out(UTF8ArrayToString(tty.output)); + tty.output = []; + } }, - symlink(parent, newname, oldpath) { - var node = MEMFS.createNode(parent, newname, 511 | 40960, 0); - node.link = oldpath; - return node; + ioctl_tcgets(tty) { + // typical setting + return { + c_iflag: 25856, + c_oflag: 5, + c_cflag: 191, + c_lflag: 35387, + c_cc: [ + 0x03, 0x1c, 0x7f, 0x15, 0x04, 0x00, 0x01, 0x00, 0x11, + 0x13, 0x1a, 0x00, 0x12, 0x0f, 0x17, 0x16, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + ], + }; }, - readlink(node) { - if (!FS.isLink(node.mode)) { - throw new FS.ErrnoError(28); - } - return node.link; + ioctl_tcsets(tty, optional_actions, data) { + // currently just ignore + return 0; + }, + ioctl_tiocgwinsz(tty) { + return [24, 80]; }, }, - stream_ops: { - read(stream, buffer, offset, length, position) { - var contents = stream.node.contents; - if (position >= stream.node.usedBytes) return 0; + default_tty1_ops: { + put_char(tty, val) { + if (val === null || val === 10) { + err(UTF8ArrayToString(tty.output)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + fsync(tty) { + if (tty.output?.length > 0) { + err(UTF8ArrayToString(tty.output)); + tty.output = []; + } + }, + }, + }; + + var zeroMemory = (ptr, size) => HEAPU8.fill(0, ptr, ptr + size); + + var mmapAlloc = (size) => { + size = alignMemory(size, 65536); + var ptr = _emscripten_builtin_memalign(65536, size); + if (ptr) zeroMemory(ptr, size); + return ptr; + }; + var MEMFS = { + ops_table: null, + mount(mount) { + return MEMFS.createNode(null, '/', 16895, 0); + }, + createNode(parent, name, mode, dev) { + if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { + // no supported + throw new FS.ErrnoError(63); + } + MEMFS.ops_table ||= { + dir: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + lookup: MEMFS.node_ops.lookup, + mknod: MEMFS.node_ops.mknod, + rename: MEMFS.node_ops.rename, + unlink: MEMFS.node_ops.unlink, + rmdir: MEMFS.node_ops.rmdir, + readdir: MEMFS.node_ops.readdir, + symlink: MEMFS.node_ops.symlink, + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + }, + }, + file: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + read: MEMFS.stream_ops.read, + write: MEMFS.stream_ops.write, + mmap: MEMFS.stream_ops.mmap, + msync: MEMFS.stream_ops.msync, + }, + }, + link: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + readlink: MEMFS.node_ops.readlink, + }, + stream: {}, + }, + chrdev: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: FS.chrdev_stream_ops, + }, + }; + var node = FS.createNode(parent, name, mode, dev); + if (FS.isDir(node.mode)) { + node.node_ops = MEMFS.ops_table.dir.node; + node.stream_ops = MEMFS.ops_table.dir.stream; + node.contents = {}; + } else if (FS.isFile(node.mode)) { + node.node_ops = MEMFS.ops_table.file.node; + node.stream_ops = MEMFS.ops_table.file.stream; + node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity. + // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred + // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size + // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. + node.contents = null; + } else if (FS.isLink(node.mode)) { + node.node_ops = MEMFS.ops_table.link.node; + node.stream_ops = MEMFS.ops_table.link.stream; + } else if (FS.isChrdev(node.mode)) { + node.node_ops = MEMFS.ops_table.chrdev.node; + node.stream_ops = MEMFS.ops_table.chrdev.stream; + } + node.atime = node.mtime = node.ctime = Date.now(); + // add the new node to the parent + if (parent) { + parent.contents[name] = node; + parent.atime = parent.mtime = parent.ctime = node.atime; + } + return node; + }, + getFileDataAsTypedArray(node) { + if (!node.contents) return new Uint8Array(0); + if (node.contents.subarray) + return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. + return new Uint8Array(node.contents); + }, + expandFileStorage(node, newCapacity) { + var prevCapacity = node.contents ? node.contents.length : 0; + if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to + // avoid overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max( + newCapacity, + (prevCapacity * + (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>> + 0 + ); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); // Allocate new storage. + if (node.usedBytes > 0) + node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. + }, + resizeFileStorage(node, newSize) { + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; // Fully decommit when requesting a resize to zero. + node.usedBytes = 0; + } else { + var oldContents = node.contents; + node.contents = new Uint8Array(newSize); // Allocate new storage. + if (oldContents) { + node.contents.set( + oldContents.subarray( + 0, + Math.min(newSize, node.usedBytes) + ) + ); // Copy old data over to the new storage. + } + node.usedBytes = newSize; + } + }, + node_ops: { + getattr(node) { + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = FS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (FS.isDir(node.mode)) { + attr.size = 4096; + } else if (FS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (FS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.atime); + attr.mtime = new Date(node.mtime); + attr.ctime = new Date(node.ctime); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr(node, attr) { + for (const key of ['mode', 'atime', 'mtime', 'ctime']) { + if (attr[key] != null) { + node[key] = attr[key]; + } + } + if (attr.size !== undefined) { + MEMFS.resizeFileStorage(node, attr.size); + } + }, + lookup(parent, name) { + throw MEMFS.doesNotExistError; + }, + mknod(parent, name, mode, dev) { + return MEMFS.createNode(parent, name, mode, dev); + }, + rename(old_node, new_dir, new_name) { + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (new_node) { + if (FS.isDir(old_node.mode)) { + // if we're overwriting a directory at new_name, make sure it's empty. + for (var i in new_node.contents) { + throw new FS.ErrnoError(55); + } + } + FS.hashRemoveNode(new_node); + } + // do the internal rewiring + delete old_node.parent.contents[old_node.name]; + new_dir.contents[new_name] = old_node; + old_node.name = new_name; + new_dir.ctime = + new_dir.mtime = + old_node.parent.ctime = + old_node.parent.mtime = + Date.now(); + }, + unlink(parent, name) { + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + rmdir(parent, name) { + var node = FS.lookupNode(parent, name); + for (var i in node.contents) { + throw new FS.ErrnoError(55); + } + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + readdir(node) { + return ['.', '..', ...Object.keys(node.contents)]; + }, + symlink(parent, newname, oldpath) { + var node = MEMFS.createNode(parent, newname, 0o777 | 40960, 0); + node.link = oldpath; + return node; + }, + readlink(node) { + if (!FS.isLink(node.mode)) { + throw new FS.ErrnoError(28); + } + return node.link; + }, + }, + stream_ops: { + read(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; var size = Math.min(stream.node.usedBytes - position, length); if (size > 8 && contents.subarray) { // non-trivial, and typed array @@ -1279,9 +2518,11 @@ export function init(RuntimeName, PHPLoader) { if (buffer.buffer === HEAP8.buffer) { canOwn = false; } + if (!length) return 0; var node = stream.node; node.mtime = node.ctime = Date.now(); + if ( buffer.subarray && (!node.contents || node.contents.subarray) @@ -1308,6 +2549,7 @@ export function init(RuntimeName, PHPLoader) { return length; } } + // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. MEMFS.expandFileStorage(node, position + length); if (node.contents.subarray && buffer.subarray) { @@ -1318,7 +2560,7 @@ export function init(RuntimeName, PHPLoader) { ); } else { for (var i = 0; i < length; i++) { - node.contents[position + i] = buffer[offset + i]; + node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. } } node.usedBytes = Math.max(node.usedBytes, position + length); @@ -1383,10 +2625,7 @@ export function init(RuntimeName, PHPLoader) { HEAP8.set(contents, ptr); } } - return { - ptr, - allocated, - }; + return { ptr, allocated }; }, msync(stream, buffer, offset, length, mmapFlags) { MEMFS.stream_ops.write( @@ -1403,13 +2642,6 @@ export function init(RuntimeName, PHPLoader) { }, }; - var asyncLoad = async (url) => { - var arrayBuffer = await readAsync(url); - return new Uint8Array(arrayBuffer); - }; - - asyncLoad.isAsync = true; - var FS_createDataFile = ( parent, name, @@ -1421,11 +2653,10 @@ export function init(RuntimeName, PHPLoader) { FS.createDataFile(parent, name, fileData, canRead, canWrite, canOwn); }; - var preloadPlugins = Module['preloadPlugins'] || []; - var FS_handledByPreloadPlugin = (byteArray, fullname, finish, onerror) => { // Ensure plugins are ready. if (typeof Browser != 'undefined') Browser.init(); + var handled = false; preloadPlugins.forEach((plugin) => { if (handled) return; @@ -1436,7 +2667,6 @@ export function init(RuntimeName, PHPLoader) { }); return handled; }; - var FS_createPreloadedFile = ( parent, name, @@ -1454,8 +2684,7 @@ export function init(RuntimeName, PHPLoader) { var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; - var dep = getUniqueRunDependency(`cp ${fullname}`); - // might have several active requests for the same fullname + var dep = getUniqueRunDependency(`cp ${fullname}`); // might have several active requests for the same fullname function processData(byteArray) { function finish(byteArray) { preFinish?.(); @@ -1709,16 +2938,11 @@ export function init(RuntimeName, PHPLoader) { return PATH.join(...parts); }, flagsForNode(flags) { - flags &= ~2097152; - // Ignore this flag from musl, otherwise node.js fails to open the file. - flags &= ~2048; - // Ignore this flag from musl, otherwise node.js fails to open the file. - flags &= ~32768; - // Ignore this flag from musl, otherwise node.js fails to open the file. - flags &= ~524288; - // Some applications may pass it; it makes no sense for a single process. - flags &= ~65536; - // Node.js doesn't need this passed in, it errors. + flags &= ~2097152; // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~2048; // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~32768; // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~524288; // Some applications may pass it; it makes no sense for a single process. + flags &= ~65536; // Node.js doesn't need this passed in, it errors. var newFlags = 0; for (var k in NODEFS.flagsForNodeMap) { if (flags & k) { @@ -1825,9 +3049,7 @@ export function init(RuntimeName, PHPLoader) { if (FS.isDir(node.mode)) { fs.mkdirSync(path, node.mode); } else { - fs.writeFileSync(path, '', { - mode: node.mode, - }); + fs.writeFileSync(path, '', { mode: node.mode }); } }); return node; @@ -1941,21 +3163,22 @@ export function init(RuntimeName, PHPLoader) { }); } } + if (position < 0) { throw new FS.ErrnoError(28); } + return position; }, mmap(stream, length, position, prot, flags) { if (!FS.isFile(stream.node.mode)) { throw new FS.ErrnoError(43); } + var ptr = mmapAlloc(length); + NODEFS.stream_ops.read(stream, HEAP8, ptr, length, position); - return { - ptr, - allocated: true, - }; + return { ptr, allocated: true }; }, msync(stream, buffer, offset, length, mmapFlags) { NODEFS.stream_ops.write( @@ -2198,14 +3421,15 @@ export function init(RuntimeName, PHPLoader) { } } } + if (position < 0) { throw new FS.ErrnoError(ERRNO_CODES.EINVAL); } + return position; }, }, }; - var FS = { root: null, mounts: [], @@ -2269,7 +3493,7 @@ export function init(RuntimeName, PHPLoader) { mounted = null; constructor(parent, name, mode, rdev) { if (!parent) { - parent = this; + parent = this; // root node sets parent to itself } this.parent = parent; this.mount = parent.mount; @@ -2307,30 +3531,37 @@ export function init(RuntimeName, PHPLoader) { throw new FS.ErrnoError(44); } opts.follow_mount ??= true; + if (!PATH.isAbs(path)) { path = FS.cwd() + '/' + path; } + // limit max consecutive symlinks to 40 (SYMLOOP_MAX). linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) { // split the absolute path var parts = path.split('/').filter((p) => !!p); + // start at the root var current = FS.root; var current_path = '/'; + for (var i = 0; i < parts.length; i++) { var islast = i === parts.length - 1; if (islast && opts.parent) { // stop resolving break; } + if (parts[i] === '.') { continue; } + if (parts[i] === '..') { current_path = PATH.dirname(current_path); current = current.parent; continue; } + current_path = PATH.join2(current_path, parts[i]); try { current = FS.lookupNode(current, parts[i]); @@ -2339,12 +3570,11 @@ export function init(RuntimeName, PHPLoader) { // and return an object with an undefined node. This is needed for // resolving symlinks in the path when creating a file. if (e?.errno === 44 && islast && opts.noent_okay) { - return { - path: current_path, - }; + return { path: current_path }; } throw e; } + // jump to the mount's root node if this is a mountpoint if ( FS.isMountpoint(current) && @@ -2352,6 +3582,7 @@ export function init(RuntimeName, PHPLoader) { ) { current = current.mounted.root; } + // by default, lookupPath will not follow a symlink if it is the final path component. // setting opts.follow = true will override this behavior. if (FS.isLink(current.mode) && (!islast || opts.follow)) { @@ -2366,10 +3597,7 @@ export function init(RuntimeName, PHPLoader) { continue linkloop; } } - return { - path: current_path, - node: current, - }; + return { path: current_path, node: current }; } throw new FS.ErrnoError(32); }, @@ -2389,6 +3617,7 @@ export function init(RuntimeName, PHPLoader) { }, hashName(parentid, name) { var hash = 0; + for (var i = 0; i < name.length; i++) { hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; } @@ -2431,7 +3660,9 @@ export function init(RuntimeName, PHPLoader) { }, createNode(parent, name, mode, rdev) { var node = new FS.FSNode(parent, name, mode, rdev); + FS.hashAddNode(node); + return node; }, destroyNode(node) { @@ -2535,7 +3766,7 @@ export function init(RuntimeName, PHPLoader) { return 32; } else if (FS.isDir(node.mode)) { if ( - FS.flagsToPermissionString(flags) !== 'r' || + FS.flagsToPermissionString(flags) !== 'r' || // opening for write flags & (512 | 64) ) { // TODO: check for O_SEARCH? (== search for dir only) @@ -2605,22 +3836,24 @@ export function init(RuntimeName, PHPLoader) { }, }, major: (dev) => dev >> 8, - minor: (dev) => dev & 255, + minor: (dev) => dev & 0xff, makedev: (ma, mi) => (ma << 8) | mi, registerDevice(dev, ops) { - FS.devices[dev] = { - stream_ops: ops, - }; + FS.devices[dev] = { stream_ops: ops }; }, getDevice: (dev) => FS.devices[dev], getMounts(mount) { var mounts = []; var check = [mount]; + while (check.length) { var m = check.pop(); + mounts.push(m); + check.push(...m.mounts); } + return mounts; }, syncfs(populate, callback) { @@ -2628,18 +3861,23 @@ export function init(RuntimeName, PHPLoader) { callback = populate; populate = false; } + FS.syncFSRequests++; + if (FS.syncFSRequests > 1) { err( `warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work` ); } + var mounts = FS.getMounts(FS.root.mount); var completed = 0; + function doCallback(errCode) { FS.syncFSRequests--; return callback(errCode); } + function done(errCode) { if (errCode) { if (!done.errored) { @@ -2652,6 +3890,7 @@ export function init(RuntimeName, PHPLoader) { doCallback(null); } } + // sync all mounts mounts.forEach((mount) => { if (!mount.type.syncfs) { @@ -2664,67 +3903,79 @@ export function init(RuntimeName, PHPLoader) { var root = mountpoint === '/'; var pseudo = !mountpoint; var node; + if (root && FS.root) { throw new FS.ErrnoError(10); } else if (!root && !pseudo) { - var lookup = FS.lookupPath(mountpoint, { - follow_mount: false, - }); - mountpoint = lookup.path; - // use the absolute path + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + mountpoint = lookup.path; // use the absolute path node = lookup.node; + if (FS.isMountpoint(node)) { throw new FS.ErrnoError(10); } + if (!FS.isDir(node.mode)) { throw new FS.ErrnoError(54); } } + var mount = { type, opts, mountpoint, mounts: [], }; + // create a root node for the fs var mountRoot = type.mount(mount); mountRoot.mount = mount; mount.root = mountRoot; + if (root) { FS.root = mountRoot; } else if (node) { // set as a mountpoint node.mounted = mount; + // add the new mount to the current mount's children if (node.mount) { node.mount.mounts.push(mount); } } + return mountRoot; }, unmount(mountpoint) { - var lookup = FS.lookupPath(mountpoint, { - follow_mount: false, - }); + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + if (!FS.isMountpoint(lookup.node)) { throw new FS.ErrnoError(28); } + // destroy the nodes for this mount, and all its child mounts var node = lookup.node; var mount = node.mounted; var mounts = FS.getMounts(mount); + Object.keys(FS.nameTable).forEach((hash) => { var current = FS.nameTable[hash]; + while (current) { var next = current.name_next; + if (mounts.includes(current.mount)) { FS.destroyNode(current); } + current = next; } }); + // no longer a mountpoint node.mounted = null; + // remove this mount from the child mounts var idx = node.mount.mounts.indexOf(mount); node.mount.mounts.splice(idx, 1); @@ -2733,9 +3984,7 @@ export function init(RuntimeName, PHPLoader) { return parent.node_ops.lookup(parent, name); }, mknod(path, mode, dev) { - var lookup = FS.lookupPath(path, { - parent: true, - }); + var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); if (!name) { @@ -2754,11 +4003,7 @@ export function init(RuntimeName, PHPLoader) { return parent.node_ops.mknod(parent, name, mode, dev); }, statfs(path) { - return FS.statfsNode( - FS.lookupPath(path, { - follow: true, - }).node - ); + return FS.statfsNode(FS.lookupPath(path, { follow: true }).node); }, statfsStream(stream) { // We keep a separate statfsStream function because noderawfs overrides @@ -2782,17 +4027,18 @@ export function init(RuntimeName, PHPLoader) { flags: 2, namelen: 255, }; + if (node.node_ops.statfs) { Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root)); } return rtn; }, - create(path, mode = 438) { + create(path, mode = 0o666) { mode &= 4095; mode |= 32768; return FS.mknod(path, mode, 0); }, - mkdir(path, mode = 511) { + mkdir(path, mode = 0o777) { mode &= 511 | 512; mode |= 16384; return FS.mknod(path, mode, 0); @@ -2813,7 +4059,7 @@ export function init(RuntimeName, PHPLoader) { mkdev(path, mode, dev) { if (typeof dev == 'undefined') { dev = mode; - mode = 438; + mode = 0o666; } mode |= 8192; return FS.mknod(path, mode, dev); @@ -2822,9 +4068,7 @@ export function init(RuntimeName, PHPLoader) { if (!PATH_FS.resolve(oldpath)) { throw new FS.ErrnoError(44); } - var lookup = FS.lookupPath(newpath, { - parent: true, - }); + var lookup = FS.lookupPath(newpath, { parent: true }); var parent = lookup.node; if (!parent) { throw new FS.ErrnoError(44); @@ -2846,15 +4090,13 @@ export function init(RuntimeName, PHPLoader) { var new_name = PATH.basename(new_path); // parents must exist var lookup, old_dir, new_dir; + // let the errors from non existent directories percolate up - lookup = FS.lookupPath(old_path, { - parent: true, - }); + lookup = FS.lookupPath(old_path, { parent: true }); old_dir = lookup.node; - lookup = FS.lookupPath(new_path, { - parent: true, - }); + lookup = FS.lookupPath(new_path, { parent: true }); new_dir = lookup.node; + if (!old_dir || !new_dir) throw new FS.ErrnoError(44); // need to be part of the same mount if (old_dir.mount !== new_dir.mount) { @@ -2876,7 +4118,9 @@ export function init(RuntimeName, PHPLoader) { var new_node; try { new_node = FS.lookupNode(new_dir, new_name); - } catch (e) {} + } catch (e) { + // not fatal + } // early out if nothing needs to change if (old_node === new_node) { return; @@ -2928,9 +4172,7 @@ export function init(RuntimeName, PHPLoader) { } }, rmdir(path) { - var lookup = FS.lookupPath(path, { - parent: true, - }); + var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); var node = FS.lookupNode(parent, name); @@ -2948,17 +4190,13 @@ export function init(RuntimeName, PHPLoader) { FS.destroyNode(node); }, readdir(path) { - var lookup = FS.lookupPath(path, { - follow: true, - }); + var lookup = FS.lookupPath(path, { follow: true }); var node = lookup.node; var readdir = FS.checkOpExists(node.node_ops.readdir, 54); return readdir(node); }, unlink(path) { - var lookup = FS.lookupPath(path, { - parent: true, - }); + var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; if (!parent) { throw new FS.ErrnoError(44); @@ -2993,9 +4231,7 @@ export function init(RuntimeName, PHPLoader) { return link.node_ops.readlink(link); }, stat(path, dontFollow) { - var lookup = FS.lookupPath(path, { - follow: !dontFollow, - }); + var lookup = FS.lookupPath(path, { follow: !dontFollow }); var node = lookup.node; var getattr = FS.checkOpExists(node.node_ops.getattr, 63); return getattr(node); @@ -3022,9 +4258,7 @@ export function init(RuntimeName, PHPLoader) { chmod(path, mode, dontFollow) { var node; if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { - follow: !dontFollow, - }); + var lookup = FS.lookupPath(path, { follow: !dontFollow }); node = lookup.node; } else { node = path; @@ -3042,14 +4276,13 @@ export function init(RuntimeName, PHPLoader) { FS.doSetAttr(stream, node, { timestamp: Date.now(), dontFollow, + // we ignore the uid / gid for now }); }, chown(path, uid, gid, dontFollow) { var node; if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { - follow: !dontFollow, - }); + var lookup = FS.lookupPath(path, { follow: !dontFollow }); node = lookup.node; } else { node = path; @@ -3085,9 +4318,7 @@ export function init(RuntimeName, PHPLoader) { } var node; if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { - follow: true, - }); + var lookup = FS.lookupPath(path, { follow: true }); node = lookup.node; } else { node = path; @@ -3102,17 +4333,15 @@ export function init(RuntimeName, PHPLoader) { FS.doTruncate(stream, stream.node, len); }, utime(path, atime, mtime) { - var lookup = FS.lookupPath(path, { - follow: true, - }); + var lookup = FS.lookupPath(path, { follow: true }); var node = lookup.node; var setattr = FS.checkOpExists(node.node_ops.setattr, 63); setattr(node, { - atime, - mtime, + atime: atime, + mtime: mtime, }); }, - open(path, flags, mode = 438) { + open(path, flags, mode = 0o666) { if (path === '') { throw new FS.ErrnoError(44); } @@ -3154,7 +4383,7 @@ export function init(RuntimeName, PHPLoader) { // Ignore the permission bits here to ensure we can `open` this new // file below. We use chmod below the apply the permissions once the // file is open. - node = FS.mknod(path, mode | 511, 0); + node = FS.mknod(path, mode | 0o777, 0); created = true; } } @@ -3184,11 +4413,11 @@ export function init(RuntimeName, PHPLoader) { } // we've already handled these, don't pass down to the underlying vfs flags &= ~(128 | 512 | 131072); + // register the stream with the filesystem var stream = FS.createStream({ node, - path: FS.getPath(node), - // we want the absolute path to the node + path: FS.getPath(node), // we want the absolute path to the node flags, seekable: true, position: 0, @@ -3202,7 +4431,7 @@ export function init(RuntimeName, PHPLoader) { stream.stream_ops.open(stream); } if (created) { - FS.chmod(node, mode & 511); + FS.chmod(node, mode & 0o777); } if (Module['logReadFiles'] && !(flags & 1)) { if (!(path in FS.readFiles)) { @@ -3215,8 +4444,7 @@ export function init(RuntimeName, PHPLoader) { if (FS.isClosed(stream)) { throw new FS.ErrnoError(8); } - if (stream.getdents) stream.getdents = null; - // free readdir state + if (stream.getdents) stream.getdents = null; // free readdir state try { if (stream.stream_ops.close) { stream.stream_ops.close(stream); @@ -3418,9 +4646,7 @@ export function init(RuntimeName, PHPLoader) { }, cwd: () => FS.currentPath, chdir(path) { - var lookup = FS.lookupPath(path, { - follow: true, - }); + var lookup = FS.lookupPath(path, { follow: true }); if (lookup.node === null) { throw new FS.ErrnoError(44); } @@ -3492,16 +4718,11 @@ export function init(RuntimeName, PHPLoader) { var stream = FS.getStreamChecked(fd); var ret = { parent: null, - mount: { - mountpoint: 'fake', - }, - node_ops: { - readlink: () => stream.path, - }, + mount: { mountpoint: 'fake' }, + node_ops: { readlink: () => stream.path }, id: fd + 1, }; - ret.parent = ret; - // make it look like a simple root node + ret.parent = ret; // make it look like a simple root node return ret; }, readdir() { @@ -3521,6 +4742,7 @@ export function init(RuntimeName, PHPLoader) { // TODO deprecate the old functionality of a single // input / output callback and that utilizes FS.createDevice // and instead require a unique set of stream ops + // by default, we symlink the standard streams to the // default tty devices. however, if the standard streams // have been overwritten we create a unique device for @@ -3540,6 +4762,7 @@ export function init(RuntimeName, PHPLoader) { } else { FS.symlink('/dev/tty1', '/dev/stderr'); } + // open default streams for the stdin, stdout and stderr devices var stdin = FS.open('/dev/stdin', 0); var stdout = FS.open('/dev/stdout', 1); @@ -3547,10 +4770,13 @@ export function init(RuntimeName, PHPLoader) { }, staticInit() { FS.nameTable = new Array(4096); + FS.mount(MEMFS, {}, '/'); + FS.createDefaultDirectories(); FS.createDefaultDevices(); FS.createSpecialDirectories(); + FS.filesystems = { MEMFS: MEMFS, NODEFS: NODEFS, @@ -3559,10 +4785,12 @@ export function init(RuntimeName, PHPLoader) { }, init(input, output, error) { FS.initialized = true; + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here input ??= Module['stdin']; output ??= Module['stdout']; error ??= Module['stderr']; + FS.createStandardStreams(input, output, error); }, quit() { @@ -3603,16 +4831,12 @@ export function init(RuntimeName, PHPLoader) { parentObject: null, }; try { - var lookup = FS.lookupPath(path, { - parent: true, - }); + var lookup = FS.lookupPath(path, { parent: true }); ret.parentExists = true; ret.parentPath = lookup.path; ret.parentObject = lookup.node; ret.name = PATH.basename(path); - lookup = FS.lookupPath(path, { - follow: !dontResolveLastLink, - }); + lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); ret.exists = true; ret.path = lookup.path; ret.object = lookup.node; @@ -3691,7 +4915,7 @@ export function init(RuntimeName, PHPLoader) { output(10); } }, - read(stream, buffer, offset, length, pos) { + read(stream, buffer, offset, length, pos /* ignored */) { var bytesRead = 0; for (var i = 0; i < length; i++) { var result; @@ -3750,8 +4974,7 @@ export function init(RuntimeName, PHPLoader) { // Actual getting is abstracted away for eventual reuse. class LazyUint8Array { lengthKnown = false; - chunks = []; - // Loaded chunks. Index is the chunk number + chunks = []; // Loaded chunks. Index is the chunk number get(idx) { if (idx > this.length - 1 || idx < 0) { return undefined; @@ -3787,9 +5010,11 @@ export function init(RuntimeName, PHPLoader) { var usesGzip = (header = xhr.getResponseHeader('Content-Encoding')) && header === 'gzip'; - var chunkSize = 1024 * 1024; - // Chunk size in bytes + + var chunkSize = 1024 * 1024; // Chunk size in bytes + if (!hasByteServing) chunkSize = datalength; + // Function to get a range from the remote URL. var doXHR = (from, to) => { if (from > to) @@ -3806,6 +5031,7 @@ export function init(RuntimeName, PHPLoader) { datalength + ' bytes available! programmer error!' ); + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); @@ -3814,6 +5040,7 @@ export function init(RuntimeName, PHPLoader) { 'Range', 'bytes=' + from + '-' + to ); + // Some hints to the browser that we want binary data. xhr.responseType = 'arraybuffer'; if (xhr.overrideMimeType) { @@ -3821,6 +5048,7 @@ export function init(RuntimeName, PHPLoader) { 'text/plain; charset=x-user-defined' ); } + xhr.send(null); if ( !( @@ -3844,10 +5072,8 @@ export function init(RuntimeName, PHPLoader) { var lazyArray = this; lazyArray.setDataGetter((chunkNum) => { var start = chunkNum * chunkSize; - var end = (chunkNum + 1) * chunkSize - 1; - // including this byte - end = Math.min(end, datalength - 1); - // if datalength-1 is selected, this is the last block + var end = (chunkNum + 1) * chunkSize - 1; // including this byte + end = Math.min(end, datalength - 1); // if datalength-1 is selected, this is the last block if (typeof lazyArray.chunks[chunkNum] == 'undefined') { lazyArray.chunks[chunkNum] = doXHR(start, end); } @@ -3855,16 +5081,17 @@ export function init(RuntimeName, PHPLoader) { throw new Error('doXHR failed!'); return lazyArray.chunks[chunkNum]; }); + if (usesGzip || !datalength) { // if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length - chunkSize = datalength = 1; - // this will force getter(0)/doXHR do download the whole file + chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file datalength = this.getter(0).length; chunkSize = datalength; out( 'LazyFiles on gzip forces download of the whole file when length is accessed' ); } + this._length = datalength; this._chunkSize = chunkSize; this.lengthKnown = true; @@ -3882,20 +5109,16 @@ export function init(RuntimeName, PHPLoader) { return this._chunkSize; } } + if (typeof XMLHttpRequest != 'undefined') { if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; var lazyArray = new LazyUint8Array(); - var properties = { - isDevice: false, - contents: lazyArray, - }; + var properties = { isDevice: false, contents: lazyArray }; } else { - var properties = { - isDevice: false, - url, - }; + var properties = { isDevice: false, url: url }; } + var node = FS.createFile( parent, name, @@ -3960,18 +5183,199 @@ export function init(RuntimeName, PHPLoader) { throw new FS.ErrnoError(48); } writeChunks(stream, HEAP8, ptr, length, position); - return { - ptr, - allocated: true, - }; + return { ptr, allocated: true }; }; node.stream_ops = stream_ops; return node; }, }; - Module['FS'] = FS; + var SYSCALLS = { + DEFAULT_POLLMASK: 5, + calculateAt(dirfd, path, allowEmpty) { + if (PATH.isAbs(path)) { + return path; + } + // relative path + var dir; + if (dirfd === -100) { + dir = FS.cwd(); + } else { + var dirstream = SYSCALLS.getStreamFromFD(dirfd); + dir = dirstream.path; + } + if (path.length == 0) { + if (!allowEmpty) { + throw new FS.ErrnoError(44); + } + return dir; + } + return dir + '/' + path; + }, + writeStat(buf, stat) { + HEAP32[buf >> 2] = stat.dev; + HEAP32[(buf + 4) >> 2] = stat.mode; + HEAPU32[(buf + 8) >> 2] = stat.nlink; + HEAP32[(buf + 12) >> 2] = stat.uid; + HEAP32[(buf + 16) >> 2] = stat.gid; + HEAP32[(buf + 20) >> 2] = stat.rdev; + HEAP64[(buf + 24) >> 3] = BigInt(stat.size); + HEAP32[(buf + 32) >> 2] = 4096; + HEAP32[(buf + 36) >> 2] = stat.blocks; + var atime = stat.atime.getTime(); + var mtime = stat.mtime.getTime(); + var ctime = stat.ctime.getTime(); + HEAP64[(buf + 40) >> 3] = BigInt(Math.floor(atime / 1000)); + HEAPU32[(buf + 48) >> 2] = (atime % 1000) * 1000 * 1000; + HEAP64[(buf + 56) >> 3] = BigInt(Math.floor(mtime / 1000)); + HEAPU32[(buf + 64) >> 2] = (mtime % 1000) * 1000 * 1000; + HEAP64[(buf + 72) >> 3] = BigInt(Math.floor(ctime / 1000)); + HEAPU32[(buf + 80) >> 2] = (ctime % 1000) * 1000 * 1000; + HEAP64[(buf + 88) >> 3] = BigInt(stat.ino); + return 0; + }, + writeStatFs(buf, stats) { + HEAP32[(buf + 4) >> 2] = stats.bsize; + HEAP32[(buf + 40) >> 2] = stats.bsize; + HEAP32[(buf + 8) >> 2] = stats.blocks; + HEAP32[(buf + 12) >> 2] = stats.bfree; + HEAP32[(buf + 16) >> 2] = stats.bavail; + HEAP32[(buf + 20) >> 2] = stats.files; + HEAP32[(buf + 24) >> 2] = stats.ffree; + HEAP32[(buf + 28) >> 2] = stats.fsid; + HEAP32[(buf + 44) >> 2] = stats.flags; // ST_NOSUID + HEAP32[(buf + 36) >> 2] = stats.namelen; + }, + doMsync(addr, stream, len, flags, offset) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(43); + } + if (flags & 2) { + // MAP_PRIVATE calls need not to be synced back to underlying fs + return 0; + } + var buffer = HEAPU8.slice(addr, addr + len); + FS.msync(stream, buffer, offset, len, flags); + }, + getStreamFromFD(fd) { + var stream = FS.getStreamChecked(fd); + return stream; + }, + varargs: undefined, + getStr(ptr) { + var ret = UTF8ToString(ptr); + return ret; + }, + }; + var ___syscall__newselect = function ( + nfds, + readfds, + writefds, + exceptfds, + timeout + ) { + try { + // readfds are supported, + // writefds checks socket open status + // exceptfds are supported, although on web, such exceptional conditions never arise in web sockets + // and so the exceptfds list will always return empty. + // timeout is supported, although on SOCKFS and PIPEFS these are ignored and always treated as 0 - fully async + + var total = 0; + + var srcReadLow = readfds ? HEAP32[readfds >> 2] : 0, + srcReadHigh = readfds ? HEAP32[(readfds + 4) >> 2] : 0; + var srcWriteLow = writefds ? HEAP32[writefds >> 2] : 0, + srcWriteHigh = writefds ? HEAP32[(writefds + 4) >> 2] : 0; + var srcExceptLow = exceptfds ? HEAP32[exceptfds >> 2] : 0, + srcExceptHigh = exceptfds ? HEAP32[(exceptfds + 4) >> 2] : 0; + + var dstReadLow = 0, + dstReadHigh = 0; + var dstWriteLow = 0, + dstWriteHigh = 0; + var dstExceptLow = 0, + dstExceptHigh = 0; + + var allLow = + (readfds ? HEAP32[readfds >> 2] : 0) | + (writefds ? HEAP32[writefds >> 2] : 0) | + (exceptfds ? HEAP32[exceptfds >> 2] : 0); + var allHigh = + (readfds ? HEAP32[(readfds + 4) >> 2] : 0) | + (writefds ? HEAP32[(writefds + 4) >> 2] : 0) | + (exceptfds ? HEAP32[(exceptfds + 4) >> 2] : 0); + + var check = (fd, low, high, val) => + fd < 32 ? low & val : high & val; + + for (var fd = 0; fd < nfds; fd++) { + var mask = 1 << fd % 32; + if (!check(fd, allLow, allHigh, mask)) { + continue; // index isn't in the set + } + + var stream = SYSCALLS.getStreamFromFD(fd); + + var flags = SYSCALLS.DEFAULT_POLLMASK; + + if (stream.stream_ops?.poll) { + var timeoutInMillis = -1; + if (timeout) { + // select(2) is declared to accept "struct timeval { time_t tv_sec; suseconds_t tv_usec; }". + // However, musl passes the two values to the syscall as an array of long values. + // Note that sizeof(time_t) != sizeof(long) in wasm32. The former is 8, while the latter is 4. + // This means using "C_STRUCTS.timeval.tv_usec" leads to a wrong offset. + // So, instead, we use POINTER_SIZE. + var tv_sec = readfds ? HEAP32[timeout >> 2] : 0, + tv_usec = readfds ? HEAP32[(timeout + 4) >> 2] : 0; + timeoutInMillis = (tv_sec + tv_usec / 1000000) * 1000; + } + flags = stream.stream_ops.poll(stream, timeoutInMillis); + } + + if (flags & 1 && check(fd, srcReadLow, srcReadHigh, mask)) { + fd < 32 + ? (dstReadLow = dstReadLow | mask) + : (dstReadHigh = dstReadHigh | mask); + total++; + } + if (flags & 4 && check(fd, srcWriteLow, srcWriteHigh, mask)) { + fd < 32 + ? (dstWriteLow = dstWriteLow | mask) + : (dstWriteHigh = dstWriteHigh | mask); + total++; + } + if (flags & 2 && check(fd, srcExceptLow, srcExceptHigh, mask)) { + fd < 32 + ? (dstExceptLow = dstExceptLow | mask) + : (dstExceptHigh = dstExceptHigh | mask); + total++; + } + } + + if (readfds) { + HEAP32[readfds >> 2] = dstReadLow; + HEAP32[(readfds + 4) >> 2] = dstReadHigh; + } + if (writefds) { + HEAP32[writefds >> 2] = dstWriteLow; + HEAP32[(writefds + 4) >> 2] = dstWriteHigh; + } + if (exceptfds) { + HEAP32[exceptfds >> 2] = dstExceptLow; + HEAP32[(exceptfds + 4) >> 2] = dstExceptHigh; + } + + return total; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + }; + ___syscall__newselect.sig = 'iipppp'; + var SOCKFS = { websocketArgs: {}, callbacks: {}, @@ -3989,32 +5393,34 @@ export function init(RuntimeName, PHPLoader) { // object so we can register network callbacks from native JavaScript too. // For more documentation see system/include/emscripten/emscripten.h (Module['websocket'] ??= {})['on'] = SOCKFS.on; + return FS.createNode(null, '/', 16895, 0); }, createSocket(family, type, protocol) { - type &= ~526336; - // Some applications may pass it; it makes no sense for a single process. + type &= ~526336; // Some applications may pass it; it makes no sense for a single process. var streaming = type == 1; if (streaming && protocol && protocol != 6) { - throw new FS.ErrnoError(66); + throw new FS.ErrnoError(66); // if SOCK_STREAM, must be tcp or 0. } + // create our internal socket structure var sock = { family, type, protocol, server: null, - error: null, - // Used in getsockopt for SOL_SOCKET/SO_ERROR test + error: null, // Used in getsockopt for SOL_SOCKET/SO_ERROR test peers: {}, pending: [], recv_queue: [], sock_ops: SOCKFS.websocket_sock_ops, }; + // create the filesystem node to store the socket structure var name = SOCKFS.nextname(); var node = FS.createNode(SOCKFS.root, name, 49152, 0); node.sock = sock; + // and the wrapping stream that enables library functions such // as read and write to indirectly interact with the socket var stream = FS.createStream({ @@ -4024,9 +5430,11 @@ export function init(RuntimeName, PHPLoader) { seekable: false, stream_ops: SOCKFS.stream_ops, }); + // map the new stream to the socket structure (sockets have a 1:1 // relationship with a stream) sock.stream = stream; + return sock; }, getSocket(fd) { @@ -4045,7 +5453,7 @@ export function init(RuntimeName, PHPLoader) { var sock = stream.node.sock; return sock.sock_ops.ioctl(sock, request, varargs); }, - read(stream, buffer, offset, length, position) { + read(stream, buffer, offset, length, position /* ignored */) { var sock = stream.node.sock; var msg = sock.sock_ops.recvmsg(sock, length); if (!msg) { @@ -4055,7 +5463,7 @@ export function init(RuntimeName, PHPLoader) { buffer.set(msg.buffer, offset); return msg.buffer.length; }, - write(stream, buffer, offset, length, position) { + write(stream, buffer, offset, length, position /* ignored */) { var sock = stream.node.sock; return sock.sock_ops.sendmsg(sock, buffer, offset, length); }, @@ -4073,18 +5481,23 @@ export function init(RuntimeName, PHPLoader) { websocket_sock_ops: { createPeer(sock, addr, port) { var ws; + if (typeof addr == 'object') { ws = addr; addr = null; port = null; } + if (ws) { // for sockets that've already connected (e.g. we're the server) // we can inspect the _socket property for the address if (ws._socket) { addr = ws._socket.remoteAddress; port = ws._socket.remotePort; - } else { + } + // if we're just now initializing a connection to the remote, + // inspect the url property + else { var result = /ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url); if (!result) { throw new Error( @@ -4101,10 +5514,10 @@ export function init(RuntimeName, PHPLoader) { // comments without checking context, so we'd end up with ws:#, the replace swaps the '#' for '//' again. var url = 'ws://'.replace('#', '//'); // Make the WebSocket subprotocol (Sec-WebSocket-Protocol) default to binary if no configuration is set. - var subProtocols = 'binary'; - // The default value is 'binary' + var subProtocols = 'binary'; // The default value is 'binary' // The default WebSocket options var opts = undefined; + // Fetch runtime WebSocket URL config. if ('function' === typeof SOCKFS.websocketArgs['url']) { url = SOCKFS.websocketArgs['url'](...arguments); @@ -4121,6 +5534,7 @@ export function init(RuntimeName, PHPLoader) { ) { subProtocols = 'null'; } + if (url === 'ws://' || url === 'wss://') { // Is the supplied URL config just a prefix, if so complete it. var parts = addr.split('/'); @@ -4132,14 +5546,17 @@ export function init(RuntimeName, PHPLoader) { '/' + parts.slice(1).join('/'); } + if (subProtocols !== 'null') { // The regex trims the string (removes spaces at the beginning and end, then splits the string by // , into an Array. Whitespace removal is important for Websockify and ws. subProtocols = subProtocols .replace(/^ +| +$/g, '') .split(/ *, */); + opts = subProtocols; } + // If node we use the ws library. var WebSocketConstructor; if (ENVIRONMENT_IS_NODE) { @@ -4162,14 +5579,17 @@ export function init(RuntimeName, PHPLoader) { throw new FS.ErrnoError(23); } } + var peer = { addr, port, socket: ws, msg_send_queue: [], }; + SOCKFS.websocket_sock_ops.addPeer(sock, peer); SOCKFS.websocket_sock_ops.handlePeerEvents(sock, peer); + // if this is a bound dgram socket, send the port number first to allow // us to override the ephemeral port reported to us by remotePort on the // remote end. @@ -4184,11 +5604,12 @@ export function init(RuntimeName, PHPLoader) { 'o'.charCodeAt(0), 'r'.charCodeAt(0), 't'.charCodeAt(0), - (sock.sport & 65280) >> 8, - sock.sport & 255, + (sock.sport & 0xff00) >> 8, + sock.sport & 0xff, ]) ); } + return peer; }, getPeer(sock, addr, port) { @@ -4202,9 +5623,11 @@ export function init(RuntimeName, PHPLoader) { }, handlePeerEvents(sock, peer) { var first = true; + var handleOpen = function () { sock.connecting = false; SOCKFS.emit('open', sock.stream.fd); + try { var queued = peer.msg_send_queue.shift(); while (queued) { @@ -4217,22 +5640,22 @@ export function init(RuntimeName, PHPLoader) { peer.socket.close(); } }; + function handleMessage(data) { if (typeof data == 'string') { - var encoder = new TextEncoder(); - // should be utf-8 - data = encoder.encode(data); + var encoder = new TextEncoder(); // should be utf-8 + data = encoder.encode(data); // make a typed array from the string } else { - assert(data.byteLength !== undefined); - // must receive an ArrayBuffer + assert(data.byteLength !== undefined); // must receive an ArrayBuffer if (data.byteLength == 0) { // An empty ArrayBuffer will emit a pseudo disconnect event // as recv/recvmsg will return zero which indicates that a socket // has performed a shutdown although the connection has not been disconnected yet. return; } - data = new Uint8Array(data); + data = new Uint8Array(data); // make a typed array view on the array buffer } + // if this is the port message, override the peer's port with it var wasfirst = first; first = false; @@ -4255,20 +5678,22 @@ export function init(RuntimeName, PHPLoader) { SOCKFS.websocket_sock_ops.addPeer(sock, peer); return; } + sock.recv_queue.push({ addr: peer.addr, port: peer.port, - data, + data: data, }); SOCKFS.emit('message', sock.stream.fd); } + if (ENVIRONMENT_IS_NODE) { peer.socket.on('open', handleOpen); peer.socket.on('message', function (data, isBinary) { if (!isBinary) { return; } - handleMessage(new Uint8Array(data).buffer); + handleMessage(new Uint8Array(data).buffer); // copy from node Buffer -> ArrayBuffer }); peer.socket.on('close', function () { SOCKFS.emit('close', sock.stream.fd); @@ -4278,13 +5703,13 @@ export function init(RuntimeName, PHPLoader) { // ECONNREFUSED they are not necessarily the expected error code e.g. // ENOTFOUND on getaddrinfo seems to be node.js specific, so using ECONNREFUSED // is still probably the most useful thing to do. - sock.error = 14; - // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + sock.error = 14; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. SOCKFS.emit('error', [ sock.stream.fd, sock.error, 'ECONNREFUSED: Connection refused', ]); + // don't throw }); } else { peer.socket.onopen = handleOpen; @@ -4299,8 +5724,7 @@ export function init(RuntimeName, PHPLoader) { peer.socket.onerror = function (error) { // The WebSocket spec only allows a 'simple event' to be thrown on error, // so we only really know as much as ECONNREFUSED. - sock.error = 14; - // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + sock.error = 14; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. SOCKFS.emit('error', [ sock.stream.fd, sock.error, @@ -4315,6 +5739,7 @@ export function init(RuntimeName, PHPLoader) { // if there are pending clients. return sock.pending.length ? 64 | 1 : 0; } + var mask = 0; var dest = sock.type === 1 // we only care about the socket state for connection-based sockets @@ -4324,6 +5749,7 @@ export function init(RuntimeName, PHPLoader) { sock.dport ) : null; + if ( sock.recv_queue.length || !dest || // connection-less sockets are always ready to read @@ -4333,12 +5759,14 @@ export function init(RuntimeName, PHPLoader) { // let recv return 0 once closed mask |= 64 | 1; } + if ( !dest || // connection-less sockets are always ready to write (dest && dest.socket.readyState === dest.socket.OPEN) ) { mask |= 4; } + if ( (dest && dest.socket.readyState === dest.socket.CLOSING) || (dest && dest.socket.readyState === dest.socket.CLOSED) @@ -4353,6 +5781,7 @@ export function init(RuntimeName, PHPLoader) { mask |= 16; } } + return mask; }, ioctl(sock, request, arg) { @@ -4364,7 +5793,6 @@ export function init(RuntimeName, PHPLoader) { } HEAP32[arg >> 2] = bytes; return 0; - default: return 28; } @@ -4391,7 +5819,7 @@ export function init(RuntimeName, PHPLoader) { typeof sock.saddr != 'undefined' || typeof sock.sport != 'undefined' ) { - throw new FS.ErrnoError(28); + throw new FS.ErrnoError(28); // already bound } sock.saddr = addr; sock.sport = port; @@ -4418,9 +5846,11 @@ export function init(RuntimeName, PHPLoader) { if (sock.server) { throw new FS.ErrnoError(138); } + // TODO autobind // if (!sock.addr && sock.type == 2) { // } + // early out if we're already connected / in the middle of connecting if ( typeof sock.daddr != 'undefined' && @@ -4439,6 +5869,7 @@ export function init(RuntimeName, PHPLoader) { } } } + // add the socket to our peer list and set our // destination address / port to match var peer = SOCKFS.websocket_sock_ops.createPeer( @@ -4448,6 +5879,7 @@ export function init(RuntimeName, PHPLoader) { ); sock.daddr = peer.addr; sock.dport = peer.port; + // because we cannot synchronously block to wait for the WebSocket // connection to complete, we return here pretending that the connection // was a success. @@ -4458,7 +5890,7 @@ export function init(RuntimeName, PHPLoader) { throw new FS.ErrnoError(138); } if (sock.server) { - throw new FS.ErrnoError(28); + throw new FS.ErrnoError(28); // already listening } var WebSocketServer = require('ws').Server; var host = sock.saddr; @@ -4469,9 +5901,10 @@ export function init(RuntimeName, PHPLoader) { sock.server = new WebSocketServer({ host, port: sock.sport, + // TODO support backlog }); - SOCKFS.emit('listen', sock.stream.fd); - // Send Event with listen fd. + SOCKFS.emit('listen', sock.stream.fd); // Send Event with listen fd. + sock.server.on('connection', function (ws) { if (sock.type === 1) { var newsock = SOCKFS.createSocket( @@ -4479,6 +5912,7 @@ export function init(RuntimeName, PHPLoader) { sock.type, sock.protocol ); + // create a peer on the new socket var peer = SOCKFS.websocket_sock_ops.createPeer( newsock, @@ -4486,6 +5920,7 @@ export function init(RuntimeName, PHPLoader) { ); newsock.daddr = peer.addr; newsock.dport = peer.port; + // push to queue for accept to pick up sock.pending.push(newsock); SOCKFS.emit('connection', newsock.stream.fd); @@ -4508,13 +5943,13 @@ export function init(RuntimeName, PHPLoader) { // is still probably the most useful thing to do. This error shouldn't // occur in a well written app as errors should get trapped in the compiled // app's own getaddrinfo call. - sock.error = 23; - // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + sock.error = 23; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. SOCKFS.emit('error', [ sock.stream.fd, sock.error, 'EHOSTUNREACH: Host is unreachable', ]); + // don't throw }); }, accept(listensock) { @@ -4539,10 +5974,7 @@ export function init(RuntimeName, PHPLoader) { addr = sock.saddr || 0; port = sock.sport || 0; } - return { - addr, - port, - }; + return { addr, port }; }, sendmsg(sock, buffer, offset, length, addr, port) { if (sock.type === 2) { @@ -4561,8 +5993,10 @@ export function init(RuntimeName, PHPLoader) { addr = sock.daddr; port = sock.dport; } + // find the peer for the destination address var dest = SOCKFS.websocket_sock_ops.getPeer(sock, addr, port); + // early out if not connected with a connection-based socket if (sock.type === 1) { if ( @@ -4573,6 +6007,7 @@ export function init(RuntimeName, PHPLoader) { throw new FS.ErrnoError(53); } } + // create a copy of the incoming data to send, as the WebSocket API // doesn't work entirely with an ArrayBufferView, it'll just send // the entire underlying buffer @@ -4580,7 +6015,9 @@ export function init(RuntimeName, PHPLoader) { offset += buffer.byteOffset; buffer = buffer.buffer; } + var data = buffer.slice(offset, offset + length); + // if we don't have a cached connectionless UDP datagram connection, or // the TCP socket is still connecting, queue the message to be sent upon // connect, and lie, saying the data was sent now. @@ -4602,6 +6039,7 @@ export function init(RuntimeName, PHPLoader) { dest.msg_send_queue.push(data); return length; } + try { // send the actual data dest.socket.send(data); @@ -4616,6 +6054,7 @@ export function init(RuntimeName, PHPLoader) { // tcp servers should not be recv()'ing on the listen socket throw new FS.ErrnoError(53); } + var queued = sock.recv_queue.shift(); if (!queued) { if (sock.type === 1) { @@ -4624,6 +6063,7 @@ export function init(RuntimeName, PHPLoader) { sock.daddr, sock.dport ); + if (!dest) { // if we have a destination address but are not connected, error out throw new FS.ErrnoError(53); @@ -4640,6 +6080,7 @@ export function init(RuntimeName, PHPLoader) { } throw new FS.ErrnoError(6); } + // queued.data will be an ArrayBuffer if it's unadulterated, but if it's // requeued TCP data it'll be an ArrayBufferView var queuedLength = queued.data.byteLength || queued.data.length; @@ -4655,6 +6096,7 @@ export function init(RuntimeName, PHPLoader) { addr: queued.addr, port: queued.port, }; + // push back any unread data for TCP connections if (flags & 2) { bytesRead = 0; @@ -4668,6 +6110,7 @@ export function init(RuntimeName, PHPLoader) { ); sock.recv_queue.unshift(queued); } + return res; }, }, @@ -4679,6 +6122,24 @@ export function init(RuntimeName, PHPLoader) { return socket; }; + var Sockets = { + BUFFER_SIZE: 10240, + MAX_BUFFER_SIZE: 10485760, + nextFd: 1, + fds: {}, + nextport: 1, + maxport: 65535, + peer: null, + connections: {}, + portmap: {}, + localAddr: 4261412874, + addrPool: [ + 33554442, 50331658, 67108874, 83886090, 100663306, 117440522, + 134217738, 150994954, 167772170, 184549386, 201326602, 218103818, + 234881034, + ], + }; + var inetPton4 = (str) => { var b = str.split('.'); for (var i = 0; i < 4; i++) { @@ -4691,8 +6152,9 @@ export function init(RuntimeName, PHPLoader) { var inetPton6 = (str) => { var words; - var w, offset, z; - /* http://home.deds.nl/~aeron/regex/ */ var valid6regx = + var w, offset, z, i; + /* http://home.deds.nl/~aeron/regex/ */ + var valid6regx = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i; var parts = []; if (!valid6regx.test(str)) { @@ -4703,10 +6165,11 @@ export function init(RuntimeName, PHPLoader) { } // Z placeholder to keep track of zeros when splitting the string on ":" if (str.startsWith('::')) { - str = str.replace('::', 'Z:'); + str = str.replace('::', 'Z:'); // leading zeros case } else { str = str.replace('::', ':Z:'); } + if (str.indexOf('.') > 0) { // parse IPv4 embedded stress str = str.replace(new RegExp('[.]', 'g'), ':'); @@ -4721,6 +6184,7 @@ export function init(RuntimeName, PHPLoader) { } else { words = str.split(':'); } + offset = 0; z = 0; for (w = 0; w < words.length; w++) { @@ -4748,13 +6212,8 @@ export function init(RuntimeName, PHPLoader) { ]; }; - /** @param {number=} addrlen */ var writeSockaddr = ( - sa, - family, - addr, - port, - addrlen - ) => { + /** @param {number=} addrlen */ + var writeSockaddr = (sa, family, addr, port, addrlen) => { switch (family) { case 2: addr = inetPton4(addr); @@ -4766,7 +6225,6 @@ export function init(RuntimeName, PHPLoader) { HEAP32[(sa + 4) >> 2] = addr; HEAP16[(sa + 2) >> 1] = _htons(port); break; - case 10: addr = inetPton6(addr); zeroMemory(sa, 28); @@ -4780,7 +6238,6 @@ export function init(RuntimeName, PHPLoader) { HEAP32[(sa + 20) >> 2] = addr[3]; HEAP16[(sa + 2) >> 1] = _htons(port); break; - default: return 5; } @@ -4803,27 +6260,32 @@ export function init(RuntimeName, PHPLoader) { if (res !== null) { return name; } + // See if this name is already mapped. var addr; + if (DNS.address_map.addrs[name]) { addr = DNS.address_map.addrs[name]; } else { var id = DNS.address_map.id++; assert(id < 65535, 'exceeded max address mappings of 65535'); - addr = '172.29.' + (id & 255) + '.' + (id & 65280); + + addr = '172.29.' + (id & 0xff) + '.' + (id & 0xff00); + DNS.address_map.names[addr] = name; DNS.address_map.addrs[name] = addr; } + return addr; }, lookup_addr(addr) { if (DNS.address_map.names[addr]) { return DNS.address_map.names[addr]; } + return null; }, }; - function ___syscall_accept4(fd, addr, addrlen, flags, d1, d2) { try { var sock = getSocketFromFD(fd); @@ -4843,15 +6305,16 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_accept4.sig = 'iippiii'; var inetNtop4 = (addr) => - (addr & 255) + + (addr & 0xff) + '.' + - ((addr >> 8) & 255) + + ((addr >> 8) & 0xff) + '.' + - ((addr >> 16) & 255) + + ((addr >> 16) & 0xff) + '.' + - ((addr >> 24) & 255); + ((addr >> 24) & 0xff); var inetNtop6 = (ints) => { // ref: http://www.ietf.org/rfc/rfc2373.txt - section 2.5.4 @@ -4876,16 +6339,18 @@ export function init(RuntimeName, PHPLoader) { var len = 0; var i = 0; var parts = [ - ints[0] & 65535, + ints[0] & 0xffff, ints[0] >> 16, - ints[1] & 65535, + ints[1] & 0xffff, ints[1] >> 16, - ints[2] & 65535, + ints[2] & 0xffff, ints[2] >> 16, - ints[3] & 65535, + ints[3] & 0xffff, ints[3] >> 16, ]; + // Handle IPv4-compatible, IPv4-mapped, loopback and any/unspecified addresses + var hasipv4 = true; var v4part = ''; // check if the 10 high-order bytes are all zeros (first 5 words) @@ -4895,6 +6360,7 @@ export function init(RuntimeName, PHPLoader) { break; } } + if (hasipv4) { // low-order 32-bits store an IPv4 address (bytes 13 to 16) (last 2 words) v4part = inetNtop4(parts[6] | (parts[7] << 16)); @@ -4908,15 +6374,15 @@ export function init(RuntimeName, PHPLoader) { if (parts[5] === 0) { str = '::'; //special case IPv6 addresses - if (v4part === '0.0.0.0') v4part = ''; - // any/unspecified address - if (v4part === '0.0.0.1') v4part = '1'; - // loopback address + if (v4part === '0.0.0.0') v4part = ''; // any/unspecified address + if (v4part === '0.0.0.1') v4part = '1'; // loopback address str += v4part; return str; } } + // Handle all other IPv6 addresses + // first run to find the longest contiguous zero words for (word = 0; word < 8; word++) { if (parts[word] === 0) { @@ -4931,6 +6397,7 @@ export function init(RuntimeName, PHPLoader) { zstart = word - longest + 1; } } + for (word = 0; word < 8; word++) { if (longest > 1) { // compress contiguous zeros - to produce "::" @@ -4941,13 +6408,13 @@ export function init(RuntimeName, PHPLoader) { ) { if (word === zstart) { str += ':'; - if (zstart === 0) str += ':'; + if (zstart === 0) str += ':'; //leading zeros case } continue; } } // converts 16-bit words from big-endian to little-endian before converting to hex string - str += Number(_ntohs(parts[word] & 65535)).toString(16); + str += Number(_ntohs(parts[word] & 0xffff)).toString(16); str += word < 7 ? ':' : ''; } return str; @@ -4958,22 +6425,18 @@ export function init(RuntimeName, PHPLoader) { var family = HEAP16[sa >> 1]; var port = _ntohs(HEAPU16[(sa + 2) >> 1]); var addr; + switch (family) { case 2: if (salen !== 16) { - return { - errno: 28, - }; + return { errno: 28 }; } addr = HEAP32[(sa + 4) >> 2]; addr = inetNtop4(addr); break; - case 10: if (salen !== 28) { - return { - errno: 28, - }; + return { errno: 28 }; } addr = [ HEAP32[(sa + 8) >> 2], @@ -4983,17 +6446,11 @@ export function init(RuntimeName, PHPLoader) { ]; addr = inetNtop6(addr); break; - default: - return { - errno: 5, - }; + return { errno: 5 }; } - return { - family, - addr, - port, - }; + + return { family: family, addr: addr, port: port }; }; var getSocketAddress = (addrp, addrlen) => { @@ -5002,7 +6459,6 @@ export function init(RuntimeName, PHPLoader) { info.addr = DNS.lookup_addr(info.addr) || info.addr; return info; }; - function ___syscall_bind(fd, addr, addrlen, d1, d2, d3) { try { var sock = getSocketFromFD(fd); @@ -5014,85 +6470,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } - - var SYSCALLS = { - DEFAULT_POLLMASK: 5, - calculateAt(dirfd, path, allowEmpty) { - if (PATH.isAbs(path)) { - return path; - } - // relative path - var dir; - if (dirfd === -100) { - dir = FS.cwd(); - } else { - var dirstream = SYSCALLS.getStreamFromFD(dirfd); - dir = dirstream.path; - } - if (path.length == 0) { - if (!allowEmpty) { - throw new FS.ErrnoError(44); - } - return dir; - } - return dir + '/' + path; - }, - writeStat(buf, stat) { - HEAP32[buf >> 2] = stat.dev; - HEAP32[(buf + 4) >> 2] = stat.mode; - HEAPU32[(buf + 8) >> 2] = stat.nlink; - HEAP32[(buf + 12) >> 2] = stat.uid; - HEAP32[(buf + 16) >> 2] = stat.gid; - HEAP32[(buf + 20) >> 2] = stat.rdev; - HEAP64[(buf + 24) >> 3] = BigInt(stat.size); - HEAP32[(buf + 32) >> 2] = 4096; - HEAP32[(buf + 36) >> 2] = stat.blocks; - var atime = stat.atime.getTime(); - var mtime = stat.mtime.getTime(); - var ctime = stat.ctime.getTime(); - HEAP64[(buf + 40) >> 3] = BigInt(Math.floor(atime / 1e3)); - HEAPU32[(buf + 48) >> 2] = (atime % 1e3) * 1e3 * 1e3; - HEAP64[(buf + 56) >> 3] = BigInt(Math.floor(mtime / 1e3)); - HEAPU32[(buf + 64) >> 2] = (mtime % 1e3) * 1e3 * 1e3; - HEAP64[(buf + 72) >> 3] = BigInt(Math.floor(ctime / 1e3)); - HEAPU32[(buf + 80) >> 2] = (ctime % 1e3) * 1e3 * 1e3; - HEAP64[(buf + 88) >> 3] = BigInt(stat.ino); - return 0; - }, - writeStatFs(buf, stats) { - HEAP32[(buf + 4) >> 2] = stats.bsize; - HEAP32[(buf + 40) >> 2] = stats.bsize; - HEAP32[(buf + 8) >> 2] = stats.blocks; - HEAP32[(buf + 12) >> 2] = stats.bfree; - HEAP32[(buf + 16) >> 2] = stats.bavail; - HEAP32[(buf + 20) >> 2] = stats.files; - HEAP32[(buf + 24) >> 2] = stats.ffree; - HEAP32[(buf + 28) >> 2] = stats.fsid; - HEAP32[(buf + 44) >> 2] = stats.flags; - // ST_NOSUID - HEAP32[(buf + 36) >> 2] = stats.namelen; - }, - doMsync(addr, stream, len, flags, offset) { - if (!FS.isFile(stream.node.mode)) { - throw new FS.ErrnoError(43); - } - if (flags & 2) { - // MAP_PRIVATE calls need not to be synced back to underlying fs - return 0; - } - var buffer = HEAPU8.slice(addr, addr + len); - FS.msync(stream, buffer, offset, len, flags); - }, - getStreamFromFD(fd) { - var stream = FS.getStreamChecked(fd); - return stream; - }, - varargs: undefined, - getStr(ptr) { - var ret = UTF8ToString(ptr); - return ret; - }, - }; + ___syscall_bind.sig = 'iippiii'; function ___syscall_chdir(path) { try { @@ -5104,6 +6482,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_chdir.sig = 'ip'; function ___syscall_chmod(path, mode) { try { @@ -5115,6 +6494,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_chmod.sig = 'ipi'; function ___syscall_connect(fd, addr, addrlen, d1, d2, d3) { try { @@ -5127,6 +6507,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_connect.sig = 'iippiii'; function ___syscall_dup(fd) { try { @@ -5137,6 +6518,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_dup.sig = 'ii'; function ___syscall_dup3(fd, newfd, flags) { try { @@ -5152,6 +6534,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_dup3.sig = 'iiii'; function ___syscall_faccessat(dirfd, path, amode, flags) { try { @@ -5161,9 +6544,7 @@ export function init(RuntimeName, PHPLoader) { // need a valid mode return -28; } - var lookup = FS.lookupPath(path, { - follow: true, - }); + var lookup = FS.lookupPath(path, { follow: true }); var node = lookup.node; if (!node) { return -44; @@ -5172,7 +6553,10 @@ export function init(RuntimeName, PHPLoader) { if (amode & 4) perms += 'r'; if (amode & 2) perms += 'w'; if (amode & 1) perms += 'x'; - if (perms && FS.nodePermissions(node, perms)) { + if ( + perms /* otherwise, they've just passed F_OK */ && + FS.nodePermissions(node, perms) + ) { return -2; } return 0; @@ -5181,17 +6565,20 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_faccessat.sig = 'iipii'; + + var ___syscall_fadvise64 = (fd, offset, len, advice) => 0; + ___syscall_fadvise64.sig = 'iijji'; var INT53_MAX = 9007199254740992; var INT53_MIN = -9007199254740992; - var bigintToI53Checked = (num) => num < INT53_MIN || num > INT53_MAX ? NaN : Number(num); - function ___syscall_fallocate(fd, mode, offset, len) { offset = bigintToI53Checked(offset); len = bigintToI53Checked(len); + try { if (isNaN(offset)) return 61; if (mode != 0) { @@ -5213,6 +6600,19 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_fallocate.sig = 'iiijj'; + + function ___syscall_fchdir(fd) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + FS.chdir(stream.path); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fchdir.sig = 'ii'; function ___syscall_fchmod(fd, mode) { try { @@ -5223,6 +6623,21 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_fchmod.sig = 'iii'; + + function ___syscall_fchmodat2(dirfd, path, mode, flags) { + try { + var nofollow = flags & 256; + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + FS.chmod(path, mode, nofollow); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fchmodat2.sig = 'iipii'; function ___syscall_fchown32(fd, owner, group) { try { @@ -5233,6 +6648,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_fchown32.sig = 'iiii'; function ___syscall_fchownat(dirfd, path, owner, group, flags) { try { @@ -5247,14 +6663,15 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_fchownat.sig = 'iipiii'; - /** @suppress {duplicate } */ var syscallGetVarargI = () => { + /** @suppress {duplicate } */ + var syscallGetVarargI = () => { // the `+` prepended here is necessary to convince the JSCompiler that varargs is indeed a number. var ret = HEAP32[+SYSCALLS.varargs >> 2]; SYSCALLS.varargs += 4; return ret; }; - var syscallGetVarargP = syscallGetVarargI; function ___syscall_fcntl64(fd, cmd, varargs) { @@ -5274,21 +6691,16 @@ export function init(RuntimeName, PHPLoader) { newStream = FS.dupStream(stream, arg); return newStream.fd; } - case 1: case 2: - return 0; - - // FD_CLOEXEC makes no sense for a single process. + return 0; // FD_CLOEXEC makes no sense for a single process. case 3: return stream.flags; - case 4: { var arg = syscallGetVarargI(); stream.flags |= arg; return 0; } - case 12: { var arg = syscallGetVarargP(); var offset = 0; @@ -5296,7 +6708,6 @@ export function init(RuntimeName, PHPLoader) { HEAP16[(arg + offset) >> 1] = 2; return 0; } - case 13: case 14: // Pretend that the locking is successful. These are process-level locks, @@ -5311,16 +6722,18 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_fcntl64.sig = 'iiip'; function ___syscall_fdatasync(fd) { try { var stream = SYSCALLS.getStreamFromFD(fd); - return 0; + return 0; // we can't do anything synchronously; the in-memory FS is already synced to } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; return -e.errno; } } + ___syscall_fdatasync.sig = 'ii'; function ___syscall_fstat64(fd, buf) { try { @@ -5330,9 +6743,34 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_fstat64.sig = 'iip'; + + function ___syscall_statfs64(path, size, buf) { + try { + SYSCALLS.writeStatFs(buf, FS.statfs(SYSCALLS.getStr(path))); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_statfs64.sig = 'ippp'; + + function ___syscall_fstatfs64(fd, size, buf) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + SYSCALLS.writeStatFs(buf, FS.statfsStream(stream)); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fstatfs64.sig = 'iipp'; function ___syscall_ftruncate64(fd, length) { length = bigintToI53Checked(length); + try { if (isNaN(length)) return 61; FS.ftruncate(fd, length); @@ -5342,12 +6780,12 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_ftruncate64.sig = 'iij'; - var stringToUTF8 = (str, outPtr, maxBytesToWrite) => - stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); - + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { + return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + }; Module['stringToUTF8'] = stringToUTF8; - function ___syscall_getcwd(buf, size) { try { if (size === 0) return -28; @@ -5361,14 +6799,17 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_getcwd.sig = 'ipp'; function ___syscall_getdents64(fd, dirp, count) { try { var stream = SYSCALLS.getStreamFromFD(fd); stream.getdents ||= FS.readdir(stream.path); + var struct_size = 280; var pos = 0; var off = FS.llseek(stream, 0, 1); + var startIdx = Math.floor(off / struct_size); var endIdx = Math.min( stream.getdents.length, @@ -5380,13 +6821,11 @@ export function init(RuntimeName, PHPLoader) { var name = stream.getdents[idx]; if (name === '.') { id = stream.node.id; - type = 4; + type = 4; // DT_DIR } else if (name === '..') { - var lookup = FS.lookupPath(stream.path, { - parent: true, - }); + var lookup = FS.lookupPath(stream.path, { parent: true }); id = lookup.node.id; - type = 4; + type = 4; // DT_DIR } else { var child; try { @@ -5406,7 +6845,7 @@ export function init(RuntimeName, PHPLoader) { ? 4 // DT_DIR, directory. : FS.isLink(child.mode) ? 10 // DT_LNK, symbolic link. - : 8; + : 8; // DT_REG, regular file. } HEAP64[(dirp + pos) >> 3] = BigInt(id); HEAP64[(dirp + pos + 8) >> 3] = BigInt((idx + 1) * struct_size); @@ -5422,12 +6861,13 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_getdents64.sig = 'iipp'; function ___syscall_getpeername(fd, addr, addrlen, d1, d2, d3) { try { var sock = getSocketFromFD(fd); if (!sock.daddr) { - return -53; + return -53; // The socket is not connected. } var errno = writeSockaddr( addr, @@ -5442,6 +6882,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_getpeername.sig = 'iippiii'; function ___syscall_getsockname(fd, addr, addrlen, d1, d2, d3) { try { @@ -5460,6 +6901,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_getsockname.sig = 'iippiii'; function ___syscall_getsockopt(fd, level, optname, optval, optlen, d1) { try { @@ -5470,17 +6912,17 @@ export function init(RuntimeName, PHPLoader) { if (optname === 4) { HEAP32[optval >> 2] = sock.error; HEAP32[optlen >> 2] = 4; - sock.error = null; - // Clear the error (The SO_ERROR option obtains and then clears this field). + sock.error = null; // Clear the error (The SO_ERROR option obtains and then clears this field). return 0; } } - return -50; + return -50; // The option is unknown at the level indicated. } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; return -e.errno; } } + ___syscall_getsockopt.sig = 'iiiippi'; function ___syscall_ioctl(fd, op, varargs) { SYSCALLS.varargs = varargs; @@ -5491,7 +6933,6 @@ export function init(RuntimeName, PHPLoader) { if (!stream.tty) return -59; return 0; } - case 21505: { if (!stream.tty) return -59; if (stream.tty.ops.ioctl_tcgets) { @@ -5508,14 +6949,12 @@ export function init(RuntimeName, PHPLoader) { } return 0; } - case 21510: case 21511: case 21512: { if (!stream.tty) return -59; - return 0; + return 0; // no-op, not actually adjusting terminal settings } - case 21506: case 21507: case 21508: { @@ -5538,26 +6977,22 @@ export function init(RuntimeName, PHPLoader) { c_cc, }); } - return 0; + return 0; // no-op, not actually adjusting terminal settings } - case 21519: { if (!stream.tty) return -59; var argp = syscallGetVarargP(); HEAP32[argp >> 2] = 0; return 0; } - case 21520: { if (!stream.tty) return -59; - return -28; + return -28; // not supported } - case 21531: { var argp = syscallGetVarargP(); return FS.ioctl(stream, op, argp); } - case 21523: { // TODO: in theory we should write to the winsize struct that gets // passed in, but for now musl doesn't read anything on it @@ -5572,7 +7007,6 @@ export function init(RuntimeName, PHPLoader) { } return 0; } - case 21524: { // TODO: technically, this ioctl call should change the window size. // but, since emscripten doesn't have any concept of a terminal window @@ -5580,20 +7014,19 @@ export function init(RuntimeName, PHPLoader) { if (!stream.tty) return -59; return 0; } - case 21515: { if (!stream.tty) return -59; return 0; } - default: - return -28; + return -28; // not supported } } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; return -e.errno; } } + ___syscall_ioctl.sig = 'iiip'; function ___syscall_listen(fd, backlog) { try { @@ -5605,6 +7038,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_listen.sig = 'iiiiiii'; function ___syscall_lstat64(path, buf) { try { @@ -5615,6 +7049,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_lstat64.sig = 'ipp'; function ___syscall_mkdirat(dirfd, path, mode) { try { @@ -5627,6 +7062,31 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_mkdirat.sig = 'iipi'; + + function ___syscall_mknodat(dirfd, path, mode, dev) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + // we don't want this in the JS API as it uses mknod to create all nodes. + switch (mode & 61440) { + case 32768: + case 8192: + case 24576: + case 4096: + case 49152: + break; + default: + return -28; + } + FS.mknod(path, mode, dev); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_mknodat.sig = 'iipii'; function ___syscall_newfstatat(dirfd, path, buf, flags) { try { @@ -5644,6 +7104,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_newfstatat.sig = 'iippi'; function ___syscall_openat(dirfd, path, flags, varargs) { SYSCALLS.varargs = varargs; @@ -5657,13 +7118,14 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_openat.sig = 'iipip'; var PIPEFS = { BUCKET_BUFFER_SIZE: 8192, mount(mount) { // Do not pollute the real root directory or its child nodes with pipes // Looks like it is OK to create another pseudo-root node not linked to the FS.root hierarchy this way - return FS.createNode(null, '/', 16384 | 511, 0); + return FS.createNode(null, '/', 16384 | 0o777, 0); }, createPipe() { var pipe = { @@ -5673,17 +7135,21 @@ export function init(RuntimeName, PHPLoader) { refcnt: 2, timestamp: new Date(), }; + pipe.buckets.push({ buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), offset: 0, roffset: 0, }); + var rName = PIPEFS.nextname(); var wName = PIPEFS.nextname(); var rNode = FS.createNode(PIPEFS.root, rName, 4096, 0); var wNode = FS.createNode(PIPEFS.root, wName, 4096, 0); + rNode.pipe = pipe; wNode.pipe = pipe; + var readableStream = FS.createStream({ path: rName, node: rNode, @@ -5692,6 +7158,7 @@ export function init(RuntimeName, PHPLoader) { stream_ops: PIPEFS.stream_ops, }); rNode.stream = readableStream; + var writableStream = FS.createStream({ path: wName, node: wNode, @@ -5700,6 +7167,7 @@ export function init(RuntimeName, PHPLoader) { stream_ops: PIPEFS.stream_ops, }); wNode.stream = writableStream; + return { readable_fd: readableStream.fd, writable_fd: writableStream.fd, @@ -5712,7 +7180,7 @@ export function init(RuntimeName, PHPLoader) { return { dev: 14, ino: node.id, - mode: 4480, + mode: 0o10600, nlink: 1, uid: 0, gid: 0, @@ -5727,6 +7195,7 @@ export function init(RuntimeName, PHPLoader) { }, poll(stream) { var pipe = stream.node.pipe; + if ((stream.flags & 2097155) === 1) { return 256 | 4; } @@ -5735,6 +7204,7 @@ export function init(RuntimeName, PHPLoader) { return 64 | 1; } } + return 0; }, dup(stream) { @@ -5746,13 +7216,16 @@ export function init(RuntimeName, PHPLoader) { fsync(stream) { return 28; }, - read(stream, buffer, offset, length, position) { + read(stream, buffer, offset, length, position /* ignored */) { var pipe = stream.node.pipe; var currentLength = 0; + for (var bucket of pipe.buckets) { currentLength += bucket.offset - bucket.roffset; } + var data = buffer.subarray(offset, offset + length); + if (length <= 0) { return 0; } @@ -5761,10 +7234,13 @@ export function init(RuntimeName, PHPLoader) { throw new FS.ErrnoError(6); } var toRead = Math.min(currentLength, length); + var totalRead = toRead; var toRemove = 0; + for (var bucket of pipe.buckets) { var bucketSize = bucket.offset - bucket.roffset; + if (toRead <= bucketSize) { var tmpSlice = bucket.buffer.subarray( bucket.roffset, @@ -5789,6 +7265,7 @@ export function init(RuntimeName, PHPLoader) { toRemove++; } } + if (toRemove && toRemove == pipe.buckets.length) { // Do not generate excessive garbage in use cases such as // write several bytes, read everything, write several bytes, read everything... @@ -5796,17 +7273,23 @@ export function init(RuntimeName, PHPLoader) { pipe.buckets[toRemove].offset = 0; pipe.buckets[toRemove].roffset = 0; } + pipe.buckets.splice(0, toRemove); + return totalRead; }, - write(stream, buffer, offset, length, position) { + write(stream, buffer, offset, length, position /* ignored */) { var pipe = stream.node.pipe; + var data = buffer.subarray(offset, offset + length); + var dataLen = data.byteLength; if (dataLen <= 0) { return 0; } + var currBucket = null; + if (pipe.buckets.length == 0) { currBucket = { buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), @@ -5817,7 +7300,9 @@ export function init(RuntimeName, PHPLoader) { } else { currBucket = pipe.buckets[pipe.buckets.length - 1]; } + assert(currBucket.offset <= PIPEFS.BUCKET_BUFFER_SIZE); + var freeBytesInCurrBuffer = PIPEFS.BUCKET_BUFFER_SIZE - currBucket.offset; if (freeBytesInCurrBuffer >= dataLen) { @@ -5835,9 +7320,11 @@ export function init(RuntimeName, PHPLoader) { data.byteLength ); } + var numBuckets = (data.byteLength / PIPEFS.BUCKET_BUFFER_SIZE) | 0; var remElements = data.byteLength % PIPEFS.BUCKET_BUFFER_SIZE; + for (var i = 0; i < numBuckets; i++) { var newBucket = { buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), @@ -5853,6 +7340,7 @@ export function init(RuntimeName, PHPLoader) { data.byteLength ); } + if (remElements > 0) { var newBucket = { buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), @@ -5862,6 +7350,7 @@ export function init(RuntimeName, PHPLoader) { pipe.buckets.push(newBucket); newBucket.buffer.set(data); } + return dataLen; }, close(stream) { @@ -5879,21 +7368,24 @@ export function init(RuntimeName, PHPLoader) { return 'pipe[' + PIPEFS.nextname.current++ + ']'; }, }; - function ___syscall_pipe(fdPtr) { try { if (fdPtr == 0) { throw new FS.ErrnoError(21); } + var res = PIPEFS.createPipe(); + HEAP32[fdPtr >> 2] = res.readable_fd; HEAP32[(fdPtr + 4) >> 2] = res.writable_fd; + return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; return -e.errno; } } + ___syscall_pipe.sig = 'ip'; function ___syscall_poll(fds, nfds, timeout) { try { @@ -5920,6 +7412,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_poll.sig = 'ipii'; function ___syscall_readlinkat(dirfd, path, buf, bufsize) { try { @@ -5927,6 +7420,7 @@ export function init(RuntimeName, PHPLoader) { path = SYSCALLS.calculateAt(dirfd, path); if (bufsize <= 0) return -28; var ret = FS.readlink(path); + var len = Math.min(bufsize, lengthBytesUTF8(ret)); var endChar = HEAP8[buf + len]; stringToUTF8(ret, buf, bufsize + 1); @@ -5939,6 +7433,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_readlinkat.sig = 'iippp'; function ___syscall_recvfrom(fd, buf, len, flags, addr, addrlen) { try { @@ -5948,8 +7443,7 @@ export function init(RuntimeName, PHPLoader) { len, typeof flags !== 'undefined' ? flags : 0 ); - if (!msg) return 0; - // socket is closed + if (!msg) return 0; // socket is closed if (addr) { var errno = writeSockaddr( addr, @@ -5966,6 +7460,72 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_recvfrom.sig = 'iippipp'; + + function ___syscall_recvmsg(fd, message, flags, d1, d2, d3) { + try { + var sock = getSocketFromFD(fd); + var iov = HEAPU32[(message + 8) >> 2]; + var num = HEAP32[(message + 12) >> 2]; + // get the total amount of data we can read across all arrays + var total = 0; + for (var i = 0; i < num; i++) { + total += HEAP32[(iov + (8 * i + 4)) >> 2]; + } + // try to read total data + var msg = sock.sock_ops.recvmsg(sock, total); + if (!msg) return 0; // socket is closed + + // TODO honor flags: + // MSG_OOB + // Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific. + // MSG_PEEK + // Peeks at the incoming message. + // MSG_WAITALL + // Requests that the function block until the full amount of data requested can be returned. The function may return a smaller amount of data if a signal is caught, if the connection is terminated, if MSG_PEEK was specified, or if an error is pending for the socket. + + // write the source address out + var name = HEAPU32[message >> 2]; + if (name) { + var errno = writeSockaddr( + name, + sock.family, + DNS.lookup_name(msg.addr), + msg.port + ); + } + // write the buffer out to the scatter-gather arrays + var bytesRead = 0; + var bytesRemaining = msg.buffer.byteLength; + for (var i = 0; bytesRemaining > 0 && i < num; i++) { + var iovbase = HEAPU32[(iov + (8 * i + 0)) >> 2]; + var iovlen = HEAP32[(iov + (8 * i + 4)) >> 2]; + if (!iovlen) { + continue; + } + var length = Math.min(iovlen, bytesRemaining); + var buf = msg.buffer.subarray(bytesRead, bytesRead + length); + HEAPU8.set(buf, iovbase + bytesRead); + bytesRead += length; + bytesRemaining -= length; + } + + // TODO set msghdr.msg_flags + // MSG_EOR + // End of record was received (if supported by the protocol). + // MSG_OOB + // Out-of-band data was received. + // MSG_TRUNC + // Normal data was truncated. + // MSG_CTRUNC + + return bytesRead; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_recvmsg.sig = 'iipiiii'; function ___syscall_renameat(olddirfd, oldpath, newdirfd, newpath) { try { @@ -5980,6 +7540,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_renameat.sig = 'iipip'; function ___syscall_rmdir(path) { try { @@ -5991,18 +7552,56 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_rmdir.sig = 'ip'; - function ___syscall_sendto(fd, message, length, flags, addr, addr_len) { + function ___syscall_sendmsg(fd, message, flags, d1, d2, d3) { try { var sock = getSocketFromFD(fd); - if (!addr) { - // send, no address provided - return FS.write(sock.stream, HEAP8, message, length); + var iov = HEAPU32[(message + 8) >> 2]; + var num = HEAP32[(message + 12) >> 2]; + // read the address and port to send to + var addr, port; + var name = HEAPU32[message >> 2]; + var namelen = HEAP32[(message + 4) >> 2]; + if (name) { + var info = getSocketAddress(name, namelen); + port = info.port; + addr = info.addr; + } + // concatenate scatter-gather arrays into one message buffer + var total = 0; + for (var i = 0; i < num; i++) { + total += HEAP32[(iov + (8 * i + 4)) >> 2]; + } + var view = new Uint8Array(total); + var offset = 0; + for (var i = 0; i < num; i++) { + var iovbase = HEAPU32[(iov + (8 * i + 0)) >> 2]; + var iovlen = HEAP32[(iov + (8 * i + 4)) >> 2]; + for (var j = 0; j < iovlen; j++) { + view[offset++] = HEAP8[iovbase + j]; + } } - var dest = getSocketAddress(addr, addr_len); - // sendto an address - return sock.sock_ops.sendmsg( - sock, + // write the buffer + return sock.sock_ops.sendmsg(sock, view, 0, total, addr, port); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_sendmsg.sig = 'iipippi'; + + function ___syscall_sendto(fd, message, length, flags, addr, addr_len) { + try { + var sock = getSocketFromFD(fd); + if (!addr) { + // send, no address provided + return FS.write(sock.stream, HEAP8, message, length); + } + var dest = getSocketAddress(addr, addr_len); + // sendto an address + return sock.sock_ops.sendmsg( + sock, HEAP8, message, length, @@ -6014,6 +7613,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_sendto.sig = 'iippipp'; function ___syscall_socket(domain, type, protocol) { try { @@ -6024,6 +7624,7 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_socket.sig = 'iiiiiii'; function ___syscall_stat64(path, buf) { try { @@ -6034,29 +7635,36 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_stat64.sig = 'ipp'; - function ___syscall_statfs64(path, size, buf) { + function ___syscall_symlinkat(target, dirfd, linkpath) { try { - SYSCALLS.writeStatFs(buf, FS.statfs(SYSCALLS.getStr(path))); + target = SYSCALLS.getStr(target); + linkpath = SYSCALLS.getStr(linkpath); + linkpath = SYSCALLS.calculateAt(dirfd, linkpath); + FS.symlink(target, linkpath); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; return -e.errno; } } + ___syscall_symlinkat.sig = 'ipip'; + + function ___syscall_truncate64(path, length) { + length = bigintToI53Checked(length); - function ___syscall_symlinkat(target, dirfd, linkpath) { try { - target = SYSCALLS.getStr(target); - linkpath = SYSCALLS.getStr(linkpath); - linkpath = SYSCALLS.calculateAt(dirfd, linkpath); - FS.symlink(target, linkpath); + if (isNaN(length)) return 61; + path = SYSCALLS.getStr(path); + FS.truncate(path, length); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; return -e.errno; } } + ___syscall_truncate64.sig = 'ipj'; function ___syscall_unlinkat(dirfd, path, flags) { try { @@ -6075,9 +7683,11 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_unlinkat.sig = 'iipi'; - var readI53FromI64 = (ptr) => - HEAPU32[ptr >> 2] + HEAP32[(ptr + 4) >> 2] * 4294967296; + var readI53FromI64 = (ptr) => { + return HEAPU32[ptr >> 2] + HEAP32[(ptr + 4) >> 2] * 4294967296; + }; function ___syscall_utimensat(dirfd, path, times, flags) { try { @@ -6097,7 +7707,7 @@ export function init(RuntimeName, PHPLoader) { } else if (nanoseconds == 1073741822) { atime = null; } else { - atime = seconds * 1e3 + nanoseconds / (1e3 * 1e3); + atime = seconds * 1000 + nanoseconds / (1000 * 1000); } times += 16; seconds = readI53FromI64(times); @@ -6107,7 +7717,7 @@ export function init(RuntimeName, PHPLoader) { } else if (nanoseconds == 1073741822) { mtime = null; } else { - mtime = seconds * 1e3 + nanoseconds / (1e3 * 1e3); + mtime = seconds * 1000 + nanoseconds / (1000 * 1000); } } // null here means UTIME_OMIT was passed. If both were set to UTIME_OMIT then @@ -6121,29 +7731,310 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + ___syscall_utimensat.sig = 'iippi'; + + var ___table_base = new WebAssembly.Global( + { value: 'i32', mutable: false }, + 1 + ); var __abort_js = () => abort(''); + __abort_js.sig = 'v'; + + var ENV = PHPLoader.ENV || {}; + + var stackAlloc = (sz) => __emscripten_stack_alloc(sz); + var stringToUTF8OnStack = (str) => { + var size = lengthBytesUTF8(str) + 1; + var ret = stackAlloc(size); + stringToUTF8(str, ret, size); + return ret; + }; + + var dlSetError = (msg) => { + var sp = stackSave(); + var cmsg = stringToUTF8OnStack(msg); + ___dl_seterr(cmsg, 0); + stackRestore(sp); + }; + + var dlopenInternal = (handle, jsflags) => { + // void *dlopen(const char *file, int mode); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html + var filename = UTF8ToString(handle + 36); + var flags = HEAP32[(handle + 4) >> 2]; + filename = PATH.normalize(filename); + var searchpaths = []; + + var global = Boolean(flags & 256); + var localScope = global ? null : {}; + + // We don't care about RTLD_NOW and RTLD_LAZY. + var combinedFlags = { + global, + nodelete: Boolean(flags & 4096), + loadAsync: jsflags.loadAsync, + }; + + if (jsflags.loadAsync) { + return loadDynamicLibrary( + filename, + combinedFlags, + localScope, + handle + ); + } + + try { + return loadDynamicLibrary( + filename, + combinedFlags, + localScope, + handle + ); + } catch (e) { + dlSetError(`Could not load dynamic lib: ${filename}\n${e}`); + return 0; + } + }; + var __dlopen_js = (handle) => { + return Asyncify.handleSleep((wakeUp) => { + dlopenInternal(handle, { loadAsync: true }) + .then(wakeUp) + .catch(() => wakeUp(0)); + }); + }; + __dlopen_js.sig = 'pp'; + __dlopen_js.isAsync = true; + + var __dlsym_js = (handle, symbol, symbolIndex) => { + // void *dlsym(void *restrict handle, const char *restrict name); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html + symbol = UTF8ToString(symbol); + var result; + var newSymIndex; + + var lib = LDSO.loadedLibsByHandle[handle]; + if (!lib.exports.hasOwnProperty(symbol) || lib.exports[symbol].stub) { + dlSetError( + `Tried to lookup unknown symbol "${symbol}" in dynamic lib: ${lib.name}` + ); + return 0; + } + newSymIndex = Object.keys(lib.exports).indexOf(symbol); + result = lib.exports[symbol]; + + if (typeof result == 'function') { + // Asyncify wraps exports, and we need to look through those wrappers. + if (result.orig) { + result = result.orig; + } + var addr = getFunctionAddress(result); + if (addr) { + result = addr; + } else { + // Insert the function into the wasm table. If its a direct wasm + // function the second argument will not be needed. If its a JS + // function we rely on the `sig` attribute being set based on the + // `__sig` specified in library JS file. + result = addFunction(result, result.sig); + HEAPU32[symbolIndex >> 2] = newSymIndex; + } + } + return result; + }; + __dlsym_js.sig = 'pppp'; + + var handleException = (e) => { + // Certain exception types we do not treat as errors since they are used for + // internal control flow. + // 1. ExitStatus, which is thrown by exit() + // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others + // that wish to return to JS event loop. + if (e instanceof ExitStatus || e == 'unwind') { + return EXITSTATUS; + } + quit_(1, e); + }; + + var runtimeKeepaliveCounter = 0; + var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; + var _proc_exit = (code) => { + EXITSTATUS = code; + if (!keepRuntimeAlive()) { + Module['onExit']?.(code); + ABORT = true; + } + quit_(code, new ExitStatus(code)); + }; + _proc_exit.sig = 'vi'; + + /** @suppress {duplicate } */ + /** @param {boolean|number=} implicit */ + var exitJS = (status, implicit) => { + EXITSTATUS = status; + + if (!keepRuntimeAlive()) { + exitRuntime(); + } + + _proc_exit(status); + }; + var _exit = exitJS; + Module['_exit'] = _exit; + _exit.sig = 'vi'; + + var maybeExit = () => { + if (runtimeExited) { + return; + } + if (!keepRuntimeAlive()) { + try { + _exit(EXITSTATUS); + } catch (e) { + handleException(e); + } + } + }; + var callUserCallback = (func) => { + if (runtimeExited || ABORT) { + return; + } + try { + func(); + maybeExit(); + } catch (e) { + handleException(e); + } + }; + + var runtimeKeepalivePush = () => { + runtimeKeepaliveCounter += 1; + }; + runtimeKeepalivePush.sig = 'v'; + + var runtimeKeepalivePop = () => { + runtimeKeepaliveCounter -= 1; + }; + runtimeKeepalivePop.sig = 'v'; + + var __emscripten_dlopen_js = (handle, onsuccess, onerror, user_data) => { + /** @param {Object=} e */ + function errorCallback(e) { + var filename = UTF8ToString(handle + 36); + dlSetError(`'Could not load dynamic lib: ${filename}\n${e}`); + runtimeKeepalivePop(); + callUserCallback(() => + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + user_data + ) + ); + } + function successCallback() { + runtimeKeepalivePop(); + callUserCallback(() => + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + user_data + ) + ); + } + + runtimeKeepalivePush(); + var promise = dlopenInternal(handle, { loadAsync: true }); + if (promise) { + promise.then(successCallback, errorCallback); + } else { + errorCallback(); + } + }; + __emscripten_dlopen_js.sig = 'vpppp'; + + var getExecutableName = () => thisProgram || './this.program'; + + var __emscripten_get_progname = (str, len) => + stringToUTF8(getExecutableName(), str, len); + __emscripten_get_progname.sig = 'vpi'; var __emscripten_lookup_name = (name) => { // uint32_t _emscripten_lookup_name(const char *name); var nameString = UTF8ToString(name); return inetPton4(DNS.lookup_name(nameString)); }; - - var runtimeKeepaliveCounter = 0; + __emscripten_lookup_name.sig = 'ip'; var __emscripten_runtime_keepalive_clear = () => { noExitRuntime = false; runtimeKeepaliveCounter = 0; }; + __emscripten_runtime_keepalive_clear.sig = 'v'; + + var __emscripten_system = (command) => { + if (ENVIRONMENT_IS_NODE) { + if (!command) return 1; // shell is available + + var cmdstr = UTF8ToString(command); + if (!cmdstr.length) return 0; // this is what glibc seems to do (shell works test?) + + var cp = require('child_process'); + var ret = cp.spawnSync(cmdstr, [], { + shell: true, + stdio: 'inherit', + }); + + var _W_EXITCODE = (ret, sig) => (ret << 8) | sig; + + // this really only can happen if process is killed by signal + if (ret.status === null) { + // sadly node doesn't expose such function + var signalToNumber = (sig) => { + // implement only the most common ones, and fallback to SIGINT + switch (sig) { + case 'SIGHUP': + return 1; + case 'SIGQUIT': + return 3; + case 'SIGFPE': + return 8; + case 'SIGKILL': + return 9; + case 'SIGALRM': + return 14; + case 'SIGTERM': + return 15; + default: + return 2; + } + }; + return _W_EXITCODE(0, signalToNumber(ret.signal)); + } + + return _W_EXITCODE(ret.status, 0); + } + // int system(const char *command); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html + // Can't call external programs. + if (!command) return 0; // no shell available + return -52; + }; + __emscripten_system.sig = 'ip'; var __emscripten_throw_longjmp = () => { throw Infinity; }; + __emscripten_throw_longjmp.sig = 'v'; function __gmtime_js(time, tmPtr) { time = bigintToI53Checked(time); - var date = new Date(time * 1e3); + + var date = new Date(time * 1000); HEAP32[tmPtr >> 2] = date.getUTCSeconds(); HEAP32[(tmPtr + 4) >> 2] = date.getUTCMinutes(); HEAP32[(tmPtr + 8) >> 2] = date.getUTCHours(); @@ -6152,9 +8043,10 @@ export function init(RuntimeName, PHPLoader) { HEAP32[(tmPtr + 20) >> 2] = date.getUTCFullYear() - 1900; HEAP32[(tmPtr + 24) >> 2] = date.getUTCDay(); var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0); - var yday = ((date.getTime() - start) / (1e3 * 60 * 60 * 24)) | 0; + var yday = ((date.getTime() - start) / (1000 * 60 * 60 * 24)) | 0; HEAP32[(tmPtr + 28) >> 2] = yday; } + __gmtime_js.sig = 'vjp'; var isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); @@ -6166,20 +8058,20 @@ export function init(RuntimeName, PHPLoader) { var MONTH_DAYS_REGULAR_CUMULATIVE = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, ]; - var ydayFromDate = (date) => { var leap = isLeapYear(date.getFullYear()); var monthDaysCumulative = leap ? MONTH_DAYS_LEAP_CUMULATIVE : MONTH_DAYS_REGULAR_CUMULATIVE; - var yday = monthDaysCumulative[date.getMonth()] + date.getDate() - 1; - // -1 since it's days since Jan 1 + var yday = monthDaysCumulative[date.getMonth()] + date.getDate() - 1; // -1 since it's days since Jan 1 + return yday; }; function __localtime_js(time, tmPtr) { time = bigintToI53Checked(time); - var date = new Date(time * 1e3); + + var date = new Date(time * 1000); HEAP32[tmPtr >> 2] = date.getSeconds(); HEAP32[(tmPtr + 4) >> 2] = date.getMinutes(); HEAP32[(tmPtr + 8) >> 2] = date.getHours(); @@ -6187,9 +8079,11 @@ export function init(RuntimeName, PHPLoader) { HEAP32[(tmPtr + 16) >> 2] = date.getMonth(); HEAP32[(tmPtr + 20) >> 2] = date.getFullYear() - 1900; HEAP32[(tmPtr + 24) >> 2] = date.getDay(); + var yday = ydayFromDate(date) | 0; HEAP32[(tmPtr + 28) >> 2] = yday; HEAP32[(tmPtr + 36) >> 2] = -(date.getTimezoneOffset() * 60); + // Attention: DST is in December in South, and some regions don't have DST at all. var start = new Date(date.getFullYear(), 0, 1); var summerOffset = new Date( @@ -6204,6 +8098,7 @@ export function init(RuntimeName, PHPLoader) { Math.min(winterOffset, summerOffset)) | 0; HEAP32[(tmPtr + 32) >> 2] = dst; } + __localtime_js.sig = 'vjp'; var __mktime_js = function (tmPtr) { var ret = (() => { @@ -6216,6 +8111,7 @@ export function init(RuntimeName, PHPLoader) { HEAP32[tmPtr >> 2], 0 ); + // There's an ambiguous hour when the time goes back; the tm_isdst field is // used to disambiguate it. Date() basically guesses, so we fix it up if it // guessed wrong, or fill in tm_isdst with the guess if it's -1. @@ -6228,8 +8124,7 @@ export function init(RuntimeName, PHPLoader) { 1 ).getTimezoneOffset(); var winterOffset = start.getTimezoneOffset(); - var dstOffset = Math.min(winterOffset, summerOffset); - // DST is in December in South + var dstOffset = Math.min(winterOffset, summerOffset); // DST is in December in South if (dst < 0) { // Attention: some regions don't have DST at all. HEAP32[(tmPtr + 32) >> 2] = Number( @@ -6240,9 +8135,10 @@ export function init(RuntimeName, PHPLoader) { var trueOffset = dst > 0 ? dstOffset : nonDstOffset; // Don't try setMinutes(date.getMinutes() + ...) -- it's messed up. date.setTime( - date.getTime() + (trueOffset - guessedOffset) * 6e4 + date.getTime() + (trueOffset - guessedOffset) * 60000 ); } + HEAP32[(tmPtr + 24) >> 2] = date.getDay(); var yday = ydayFromDate(date) | 0; HEAP32[(tmPtr + 28) >> 2] = yday; @@ -6253,18 +8149,21 @@ export function init(RuntimeName, PHPLoader) { HEAP32[(tmPtr + 12) >> 2] = date.getDate(); HEAP32[(tmPtr + 16) >> 2] = date.getMonth(); HEAP32[(tmPtr + 20) >> 2] = date.getYear(); + var timeMs = date.getTime(); if (isNaN(timeMs)) { return -1; } // Return time in microseconds - return timeMs / 1e3; + return timeMs / 1000; })(); return BigInt(ret); }; + __mktime_js.sig = 'jp'; function __mmap_js(len, prot, flags, fd, offset, allocated, addr) { offset = bigintToI53Checked(offset); + try { if (isNaN(offset)) return 61; var stream = SYSCALLS.getStreamFromFD(fd); @@ -6278,9 +8177,31 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + __mmap_js.sig = 'ipiiijpp'; + + function __msync_js(addr, len, prot, flags, fd, offset) { + offset = bigintToI53Checked(offset); + + try { + if (isNaN(offset)) return 61; + SYSCALLS.doMsync( + addr, + SYSCALLS.getStreamFromFD(fd), + len, + flags, + offset + ); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + __msync_js.sig = 'ippiiij'; function __munmap_js(addr, len, prot, flags, fd, offset) { offset = bigintToI53Checked(offset); + try { var stream = SYSCALLS.getStreamFromFD(fd); if (prot & 2) { @@ -6291,101 +8212,66 @@ export function init(RuntimeName, PHPLoader) { return -e.errno; } } + __munmap_js.sig = 'ippiiij'; var timers = {}; - var handleException = (e) => { - // Certain exception types we do not treat as errors since they are used for - // internal control flow. - // 1. ExitStatus, which is thrown by exit() - // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others - // that wish to return to JS event loop. - if (e instanceof ExitStatus || e == 'unwind') { - return EXITSTATUS; - } - quit_(1, e); - }; - - var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; - - var _proc_exit = (code) => { - EXITSTATUS = code; - if (!keepRuntimeAlive()) { - Module['onExit']?.(code); - ABORT = true; - } - quit_(code, new ExitStatus(code)); - }; - - /** @suppress {duplicate } */ /** @param {boolean|number=} implicit */ var exitJS = - (status, implicit) => { - EXITSTATUS = status; - if (!keepRuntimeAlive()) { - exitRuntime(); - } - _proc_exit(status); - }; - - var _exit = exitJS; - - Module['_exit'] = _exit; - - var maybeExit = () => { - if (runtimeExited) { - return; - } - if (!keepRuntimeAlive()) { - try { - _exit(EXITSTATUS); - } catch (e) { - handleException(e); - } - } - }; - - var callUserCallback = (func) => { - if (runtimeExited || ABORT) { - return; - } - try { - func(); - maybeExit(); - } catch (e) { - handleException(e); - } - }; - var _emscripten_get_now = () => performance.now(); - + _emscripten_get_now.sig = 'd'; var __setitimer_js = (which, timeout_ms) => { // First, clear any existing timer. if (timers[which]) { clearTimeout(timers[which].id); delete timers[which]; } + // A timeout of zero simply cancels the current timeout so we have nothing // more to do. if (!timeout_ms) return 0; + var id = setTimeout(() => { delete timers[which]; callUserCallback(() => __emscripten_timeout(which, _emscripten_get_now()) ); }, timeout_ms); - timers[which] = { - id, - timeout_ms, - }; + timers[which] = { id, timeout_ms }; return 0; }; + __setitimer_js.sig = 'iid'; - var __tzset_js = (timezone, daylight, std_name, dst_name) => { - // TODO: Use (malleable) environment variables instead of system settings. - var currentYear = new Date().getFullYear(); + var __timegm_js = function (tmPtr) { + var ret = (() => { + var time = Date.UTC( + HEAP32[(tmPtr + 20) >> 2] + 1900, + HEAP32[(tmPtr + 16) >> 2], + HEAP32[(tmPtr + 12) >> 2], + HEAP32[(tmPtr + 8) >> 2], + HEAP32[(tmPtr + 4) >> 2], + HEAP32[tmPtr >> 2], + 0 + ); + var date = new Date(time); + + HEAP32[(tmPtr + 24) >> 2] = date.getUTCDay(); + var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0); + var yday = ((date.getTime() - start) / (1000 * 60 * 60 * 24)) | 0; + HEAP32[(tmPtr + 28) >> 2] = yday; + + return date.getTime() / 1000; + })(); + return BigInt(ret); + }; + __timegm_js.sig = 'jp'; + + var __tzset_js = (timezone, daylight, std_name, dst_name) => { + // TODO: Use (malleable) environment variables instead of system settings. + var currentYear = new Date().getFullYear(); var winter = new Date(currentYear, 0, 1); var summer = new Date(currentYear, 6, 1); var winterOffset = winter.getTimezoneOffset(); var summerOffset = summer.getTimezoneOffset(); + // Local standard timezone offset. Local standard time is not adjusted for // daylight savings. This code uses the fact that getTimezoneOffset returns // a greater value during Standard Time versus Daylight Saving Time (DST). @@ -6393,22 +8279,28 @@ export function init(RuntimeName, PHPLoader) { // compares whether the output of the given date the same (Standard) or less // (DST). var stdTimezoneOffset = Math.max(winterOffset, summerOffset); + // timezone is specified as seconds west of UTC ("The external variable // `timezone` shall be set to the difference, in seconds, between // Coordinated Universal Time (UTC) and local standard time."), the same // as returned by stdTimezoneOffset. // See http://pubs.opengroup.org/onlinepubs/009695399/functions/tzset.html HEAPU32[timezone >> 2] = stdTimezoneOffset * 60; + HEAP32[daylight >> 2] = Number(winterOffset != summerOffset); + var extractZone = (timezoneOffset) => { // Why inverse sign? // Read here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset var sign = timezoneOffset >= 0 ? '-' : '+'; + var absOffset = Math.abs(timezoneOffset); var hours = String(Math.floor(absOffset / 60)).padStart(2, '0'); var minutes = String(absOffset % 60).padStart(2, '0'); + return `UTC${sign}${hours}${minutes}`; }; + var winterName = extractZone(winterOffset); var summerName = extractZone(summerOffset); if (summerOffset < winterOffset) { @@ -6420,1459 +8312,9169 @@ export function init(RuntimeName, PHPLoader) { stringToUTF8(summerName, std_name, 17); } }; + __tzset_js.sig = 'vpppp'; - var _emscripten_date_now = () => Date.now(); - - var nowIsMonotonic = 1; + var _emscripten_set_main_loop_timing = (mode, value) => { + MainLoop.timingMode = mode; + MainLoop.timingValue = value; - var checkWasiClock = (clock_id) => clock_id >= 0 && clock_id <= 3; + if (!MainLoop.func) { + return 1; // Return non-zero on failure, can't set timing mode when there is no main loop. + } - function _clock_time_get(clk_id, ignored_precision, ptime) { - ignored_precision = bigintToI53Checked(ignored_precision); - if (!checkWasiClock(clk_id)) { - return 28; + if (!MainLoop.running) { + runtimeKeepalivePush(); + MainLoop.running = true; } - var now; - // all wasi clocks but realtime are monotonic - if (clk_id === 0) { - now = _emscripten_date_now(); - } else if (nowIsMonotonic) { - now = _emscripten_get_now(); - } else { - return 52; + if (mode == 0) { + MainLoop.scheduler = function MainLoop_scheduler_setTimeout() { + var timeUntilNextTick = + Math.max( + 0, + MainLoop.tickStartTime + value - _emscripten_get_now() + ) | 0; + setTimeout(MainLoop.runner, timeUntilNextTick); // doing this each time means that on exception, we stop + }; + MainLoop.method = 'timeout'; + } else if (mode == 1) { + MainLoop.scheduler = function MainLoop_scheduler_rAF() { + MainLoop.requestAnimationFrame(MainLoop.runner); + }; + MainLoop.method = 'rAF'; + } else if (mode == 2) { + if (typeof MainLoop.setImmediate == 'undefined') { + if (typeof setImmediate == 'undefined') { + // Emulate setImmediate. (note: not a complete polyfill, we don't emulate clearImmediate() to keep code size to minimum, since not needed) + var setImmediates = []; + var emscriptenMainLoopMessageId = 'setimmediate'; + /** @param {Event} event */ + var MainLoop_setImmediate_messageHandler = (event) => { + // When called in current thread or Worker, the main loop ID is structured slightly different to accommodate for --proxy-to-worker runtime listening to Worker events, + // so check for both cases. + if ( + event.data === emscriptenMainLoopMessageId || + event.data.target === emscriptenMainLoopMessageId + ) { + event.stopPropagation(); + setImmediates.shift()(); + } + }; + addEventListener( + 'message', + MainLoop_setImmediate_messageHandler, + true + ); + MainLoop.setImmediate = + /** @type{function(function(): ?, ...?): number} */ ( + (func) => { + setImmediates.push(func); + if (ENVIRONMENT_IS_WORKER) { + Module['setImmediates'] ??= []; + Module['setImmediates'].push(func); + postMessage({ + target: emscriptenMainLoopMessageId, + }); // In --proxy-to-worker, route the message via proxyClient.js + } else + postMessage( + emscriptenMainLoopMessageId, + '*' + ); // On the main thread, can just send the message to itself. + } + ); + } else { + MainLoop.setImmediate = setImmediate; + } + } + MainLoop.scheduler = function MainLoop_scheduler_setImmediate() { + MainLoop.setImmediate(MainLoop.runner); + }; + MainLoop.method = 'immediate'; } - // "now" is in ms, and wasi times are in ns. - var nsec = Math.round(now * 1e3 * 1e3); - HEAP64[ptime >> 3] = BigInt(nsec); return 0; - } - - var getHeapMax = () => - // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate - // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side - // for any code that deals with heap sizes, which would require special - // casing all heap size related code to treat 0 specially. - 2147483648; - - var _emscripten_get_heap_max = () => getHeapMax(); - - var growMemory = (size) => { - var b = wasmMemory.buffer; - var pages = ((size - b.byteLength + 65535) / 65536) | 0; - try { - // round size grow request up to wasm page size (fixed 64KB per spec) - wasmMemory.grow(pages); - // .grow() takes a delta compared to the previous size - updateMemoryViews(); - return 1; - } catch (e) {} }; + _emscripten_set_main_loop_timing.sig = 'iii'; - var _emscripten_resize_heap = (requestedSize) => { - var oldSize = HEAPU8.length; - // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. - requestedSize >>>= 0; - // With multithreaded builds, races can happen (another thread might increase the size - // in between), so return a failure, and let the caller retry. - // Memory resize rules: - // 1. Always increase heap size to at least the requested size, rounded up - // to next page multiple. - // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap - // geometrically: increase the heap size according to - // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most - // overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). - // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap - // linearly: increase the heap size by at least - // MEMORY_GROWTH_LINEAR_STEP bytes. - // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by - // MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest - // 4. If we were unable to allocate as much memory, it may be due to - // over-eager decision to excessively reserve due to (3) above. - // Hence if an allocation fails, cut down on the amount of excess - // growth, in an attempt to succeed to perform a smaller allocation. - // A limit is set for how much we can grow. We should not exceed that - // (the wasm binary specifies it, so if we tried, we'd fail anyhow). - var maxHeapSize = getHeapMax(); - if (requestedSize > maxHeapSize) { - return false; - } - // Loop through potential heap size increases. If we attempt a too eager - // reservation that fails, cut down on the attempted size and reserve a - // smaller bump instead. (max 3 times, chosen somewhat arbitrarily) - for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { - var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); - // ensure geometric growth - // but limit overreserving (default to capping at +96MB overgrowth at most) - overGrownHeapSize = Math.min( - overGrownHeapSize, - requestedSize + 100663296 - ); - var newSize = Math.min( - maxHeapSize, - alignMemory(Math.max(requestedSize, overGrownHeapSize), 65536) - ); - var replacement = growMemory(newSize); - if (replacement) { - return true; + /** + * @param {number=} arg + * @param {boolean=} noSetTiming + */ + var setMainLoop = ( + iterFunc, + fps, + simulateInfiniteLoop, + arg, + noSetTiming + ) => { + MainLoop.func = iterFunc; + MainLoop.arg = arg; + + var thisMainLoopId = MainLoop.currentlyRunningMainloop; + function checkIsRunning() { + if (thisMainLoopId < MainLoop.currentlyRunningMainloop) { + runtimeKeepalivePop(); + maybeExit(); + return false; } + return true; } - return false; - }; - var runtimeKeepalivePush = () => { - runtimeKeepaliveCounter += 1; - }; + // We create the loop runner here but it is not actually running until + // _emscripten_set_main_loop_timing is called (which might happen a + // later time). This member signifies that the current runner has not + // yet been started so that we can call runtimeKeepalivePush when it + // gets it timing set for the first time. + MainLoop.running = false; + MainLoop.runner = function MainLoop_runner() { + if (ABORT) return; + if (MainLoop.queue.length > 0) { + var start = Date.now(); + var blocker = MainLoop.queue.shift(); + blocker.func(blocker.arg); + if (MainLoop.remainingBlockers) { + var remaining = MainLoop.remainingBlockers; + var next = + remaining % 1 == 0 + ? remaining - 1 + : Math.floor(remaining); + if (blocker.counted) { + MainLoop.remainingBlockers = next; + } else { + // not counted, but move the progress along a tiny bit + next = next + 0.5; // do not steal all the next one's progress + MainLoop.remainingBlockers = (8 * remaining + next) / 9; + } + } + MainLoop.updateStatus(); - var runtimeKeepalivePop = () => { - runtimeKeepaliveCounter -= 1; - }; + // catches pause/resume main loop from blocker execution + if (!checkIsRunning()) return; - /** @param {number=} timeout */ var safeSetTimeout = (func, timeout) => { - runtimeKeepalivePush(); - return setTimeout(() => { - runtimeKeepalivePop(); - callUserCallback(func); - }, timeout); - }; + setTimeout(MainLoop.runner, 0); + return; + } - var _emscripten_sleep = (ms) => - Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)); + // catch pauses from non-main loop sources + if (!checkIsRunning()) return; - Module['_emscripten_sleep'] = _emscripten_sleep; + // Implement very basic swap interval control + MainLoop.currentFrameNumber = (MainLoop.currentFrameNumber + 1) | 0; + if ( + MainLoop.timingMode == 1 && + MainLoop.timingValue > 1 && + MainLoop.currentFrameNumber % MainLoop.timingValue != 0 + ) { + // Not the scheduled time to render this frame - skip. + MainLoop.scheduler(); + return; + } else if (MainLoop.timingMode == 0) { + MainLoop.tickStartTime = _emscripten_get_now(); + } - _emscripten_sleep.isAsync = true; + MainLoop.runIter(iterFunc); - var ENV = PHPLoader.ENV || {}; + // catch pauses from the main loop itself + if (!checkIsRunning()) return; - var getExecutableName = () => thisProgram || './this.program'; + MainLoop.scheduler(); + }; - var getEnvStrings = () => { - if (!getEnvStrings.strings) { - // Default values. - // Browser language detection #8751 - var lang = - ( - (typeof navigator == 'object' && - navigator.languages && - navigator.languages[0]) || - 'C' - ).replace('-', '_') + '.UTF-8'; - var env = { - USER: 'web_user', - LOGNAME: 'web_user', - PATH: '/', - PWD: '/', - HOME: '/home/web_user', - LANG: lang, - _: getExecutableName(), - }; - // Apply the user-provided values, if any. - for (var x in ENV) { - // x is a key in ENV; if ENV[x] is undefined, that means it was - // explicitly set to be so. We allow user code to do that to - // force variables with default values to remain unset. - if (ENV[x] === undefined) delete env[x]; - else env[x] = ENV[x]; - } - var strings = []; - for (var x in env) { - strings.push(`${x}=${env[x]}`); + if (!noSetTiming) { + if (fps > 0) { + _emscripten_set_main_loop_timing(0, 1000.0 / fps); + } else { + // Do rAF by rendering each frame (no decimating) + _emscripten_set_main_loop_timing(1, 1); } - getEnvStrings.strings = strings; - } - return getEnvStrings.strings; - }; - var stringToAscii = (str, buffer) => { - for (var i = 0; i < str.length; ++i) { - HEAP8[buffer++] = str.charCodeAt(i); + MainLoop.scheduler(); } - // Null-terminate the string - HEAP8[buffer] = 0; - }; - var _environ_get = (__environ, environ_buf) => { - var bufSize = 0; - getEnvStrings().forEach((string, i) => { - var ptr = environ_buf + bufSize; - HEAPU32[(__environ + i * 4) >> 2] = ptr; - stringToAscii(string, ptr); - bufSize += string.length + 1; - }); - return 0; + if (simulateInfiniteLoop) { + throw 'unwind'; + } }; - var _environ_sizes_get = (penviron_count, penviron_buf_size) => { - var strings = getEnvStrings(); - HEAPU32[penviron_count >> 2] = strings.length; - var bufSize = 0; - strings.forEach((string) => (bufSize += string.length + 1)); - HEAPU32[penviron_buf_size >> 2] = bufSize; - return 0; + var MainLoop = { + running: false, + scheduler: null, + method: '', + currentlyRunningMainloop: 0, + func: null, + arg: 0, + timingMode: 0, + timingValue: 0, + currentFrameNumber: 0, + queue: [], + preMainLoop: [], + postMainLoop: [], + pause() { + MainLoop.scheduler = null; + // Incrementing this signals the previous main loop that it's now become old, and it must return. + MainLoop.currentlyRunningMainloop++; + }, + resume() { + MainLoop.currentlyRunningMainloop++; + var timingMode = MainLoop.timingMode; + var timingValue = MainLoop.timingValue; + var func = MainLoop.func; + MainLoop.func = null; + // do not set timing and call scheduler, we will do it on the next lines + setMainLoop(func, 0, false, MainLoop.arg, true); + _emscripten_set_main_loop_timing(timingMode, timingValue); + MainLoop.scheduler(); + }, + updateStatus() { + if (Module['setStatus']) { + var message = Module['statusMessage'] || 'Please wait...'; + var remaining = MainLoop.remainingBlockers ?? 0; + var expected = MainLoop.expectedBlockers ?? 0; + if (remaining) { + if (remaining < expected) { + Module['setStatus']( + `{message} ({expected - remaining}/{expected})` + ); + } else { + Module['setStatus'](message); + } + } else { + Module['setStatus'](''); + } + } + }, + init() { + Module['preMainLoop'] && + MainLoop.preMainLoop.push(Module['preMainLoop']); + Module['postMainLoop'] && + MainLoop.postMainLoop.push(Module['postMainLoop']); + }, + runIter(func) { + if (ABORT) return; + for (var pre of MainLoop.preMainLoop) { + if (pre() === false) { + return; // |return false| skips a frame + } + } + callUserCallback(func); + for (var post of MainLoop.postMainLoop) { + post(); + } + }, + nextRAF: 0, + fakeRequestAnimationFrame(func) { + // try to keep 60fps between calls to here + var now = Date.now(); + if (MainLoop.nextRAF === 0) { + MainLoop.nextRAF = now + 1000 / 60; + } else { + while (now + 2 >= MainLoop.nextRAF) { + // fudge a little, to avoid timer jitter causing us to do lots of delay:0 + MainLoop.nextRAF += 1000 / 60; + } + } + var delay = Math.max(MainLoop.nextRAF - now, 0); + setTimeout(func, delay); + }, + requestAnimationFrame(func) { + if (typeof requestAnimationFrame == 'function') { + requestAnimationFrame(func); + return; + } + var RAF = MainLoop.fakeRequestAnimationFrame; + RAF(func); + }, }; - function _fd_close(fd) { - try { - var stream = SYSCALLS.getStreamFromFD(fd); - FS.close(stream); - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } + var AL = { + QUEUE_INTERVAL: 25, + QUEUE_LOOKAHEAD: 0.1, + DEVICE_NAME: 'Emscripten OpenAL', + CAPTURE_DEVICE_NAME: 'Emscripten OpenAL capture', + ALC_EXTENSIONS: { + ALC_SOFT_pause_device: true, + ALC_SOFT_HRTF: true, + }, + AL_EXTENSIONS: { + AL_EXT_float32: true, + AL_SOFT_loop_points: true, + AL_SOFT_source_length: true, + AL_EXT_source_distance_model: true, + AL_SOFT_source_spatialize: true, + }, + _alcErr: 0, + alcErr: 0, + deviceRefCounts: {}, + alcStringCache: {}, + paused: false, + stringCache: {}, + contexts: {}, + currentCtx: null, + buffers: { + 0: { + id: 0, + refCount: 0, + audioBuf: null, + frequency: 0, + bytesPerSample: 2, + channels: 1, + length: 0, + }, + }, + paramArray: [], + _nextId: 1, + newId: () => (AL.freeIds.length > 0 ? AL.freeIds.pop() : AL._nextId++), + freeIds: [], + scheduleContextAudio: (ctx) => { + // If we are animating using the requestAnimationFrame method, then the main loop does not run when in the background. + // To give a perfect glitch-free audio stop when switching from foreground to background, we need to avoid updating + // audio altogether when in the background, so detect that case and kill audio buffer streaming if so. + if ( + MainLoop.timingMode === 1 && + document['visibilityState'] != 'visible' + ) { + return; + } - function _fd_fdstat_get(fd, pbuf) { - try { - var rightsBase = 0; - var rightsInheriting = 0; - var flags = 0; - { - var stream = SYSCALLS.getStreamFromFD(fd); - // All character devices are terminals (other things a Linux system would - // assume is a character device, like the mouse, we have special APIs for). - var type = stream.tty - ? 2 - : FS.isDir(stream.mode) - ? 3 - : FS.isLink(stream.mode) - ? 7 - : 4; + for (var i in ctx.sources) { + AL.scheduleSourceAudio(ctx.sources[i]); + } + }, + scheduleSourceAudio: (src, lookahead) => { + // See comment on scheduleContextAudio above. + if ( + MainLoop.timingMode === 1 && + document['visibilityState'] != 'visible' + ) { + return; + } + if (src.state !== 4114) { + return; } - HEAP8[pbuf] = type; - HEAP16[(pbuf + 2) >> 1] = flags; - HEAP64[(pbuf + 8) >> 3] = BigInt(rightsBase); - HEAP64[(pbuf + 16) >> 3] = BigInt(rightsInheriting); - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } - /** @param {number=} offset */ var doReadv = ( - stream, - iov, - iovcnt, - offset - ) => { - var ret = 0; - for (var i = 0; i < iovcnt; i++) { - var ptr = HEAPU32[iov >> 2]; - var len = HEAPU32[(iov + 4) >> 2]; - iov += 8; - var curr = FS.read(stream, HEAP8, ptr, len, offset); - if (curr < 0) return -1; - ret += curr; - if (curr < len) break; - // nothing more to read - if (typeof offset != 'undefined') { - offset += curr; + var currentTime = AL.updateSourceTime(src); + + var startTime = src.bufStartTime; + var startOffset = src.bufOffset; + var bufCursor = src.bufsProcessed; + + // Advance past any audio that is already scheduled + for (var i = 0; i < src.audioQueue.length; i++) { + var audioSrc = src.audioQueue[i]; + startTime = audioSrc._startTime + audioSrc._duration; + startOffset = 0.0; + bufCursor += audioSrc._skipCount + 1; } - } - return ret; - }; - function _fd_read(fd, iov, iovcnt, pnum) { - try { - var stream = SYSCALLS.getStreamFromFD(fd); - var num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } + if (!lookahead) { + lookahead = AL.QUEUE_LOOKAHEAD; + } + var lookaheadTime = currentTime + lookahead; + var skipCount = 0; + while (startTime < lookaheadTime) { + if (bufCursor >= src.bufQueue.length) { + if (src.looping) { + bufCursor %= src.bufQueue.length; + } else { + break; + } + } - function _fd_seek(fd, offset, whence, newOffset) { - offset = bigintToI53Checked(offset); - try { - if (isNaN(offset)) return 61; - var stream = SYSCALLS.getStreamFromFD(fd); - FS.llseek(stream, offset, whence); - HEAP64[newOffset >> 3] = BigInt(stream.position); - if (stream.getdents && offset === 0 && whence === 0) - stream.getdents = null; - // reset readdir state - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } + var buf = src.bufQueue[bufCursor % src.bufQueue.length]; + // If the buffer contains no data, skip it + if (buf.length === 0) { + skipCount++; + // If we've gone through the whole queue and everything is 0 length, just give up + if (skipCount === src.bufQueue.length) { + break; + } + } else { + var audioSrc = src.context.audioCtx.createBufferSource(); + audioSrc.buffer = buf.audioBuf; + audioSrc.playbackRate.value = src.playbackRate; + if (buf.audioBuf._loopStart || buf.audioBuf._loopEnd) { + audioSrc.loopStart = buf.audioBuf._loopStart; + audioSrc.loopEnd = buf.audioBuf._loopEnd; + } - /** @param {number=} offset */ var doWritev = ( - stream, - iov, - iovcnt, - offset - ) => { - var ret = 0; - for (var i = 0; i < iovcnt; i++) { - var ptr = HEAPU32[iov >> 2]; - var len = HEAPU32[(iov + 4) >> 2]; - iov += 8; - var curr = FS.write(stream, HEAP8, ptr, len, offset); - if (curr < 0) return -1; - ret += curr; - if (curr < len) { - // No more space to write. - break; + var duration = 0.0; + // If the source is a looping static buffer, use native looping for gapless playback + if (src.type === 4136 && src.looping) { + duration = Number.POSITIVE_INFINITY; + audioSrc.loop = true; + if (buf.audioBuf._loopStart) { + audioSrc.loopStart = buf.audioBuf._loopStart; + } + if (buf.audioBuf._loopEnd) { + audioSrc.loopEnd = buf.audioBuf._loopEnd; + } + } else { + duration = + (buf.audioBuf.duration - startOffset) / + src.playbackRate; + } + + audioSrc._startOffset = startOffset; + audioSrc._duration = duration; + audioSrc._skipCount = skipCount; + skipCount = 0; + + audioSrc.connect(src.gain); + + if (typeof audioSrc.start != 'undefined') { + // Sample the current time as late as possible to mitigate drift + startTime = Math.max( + startTime, + src.context.audioCtx.currentTime + ); + audioSrc.start(startTime, startOffset); + } else if (typeof audioSrc.noteOn != 'undefined') { + startTime = Math.max( + startTime, + src.context.audioCtx.currentTime + ); + audioSrc.noteOn(startTime); + } + audioSrc._startTime = startTime; + src.audioQueue.push(audioSrc); + + startTime += duration; + } + + startOffset = 0.0; + bufCursor++; } - if (typeof offset != 'undefined') { - offset += curr; + }, + updateSourceTime: (src) => { + var currentTime = src.context.audioCtx.currentTime; + if (src.state !== 4114) { + return currentTime; } - } - return ret; - }; - function _fd_write(fd, iov, iovcnt, pnum) { - try { - var stream = SYSCALLS.getStreamFromFD(fd); - var num = doWritev(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } + // if the start time is unset, determine it based on the current offset. + // This will be the case when a source is resumed after being paused, and + // allows us to pretend that the source actually started playing some time + // in the past such that it would just now have reached the stored offset. + if (!isFinite(src.bufStartTime)) { + src.bufStartTime = + currentTime - src.bufOffset / src.playbackRate; + src.bufOffset = 0.0; + } - var _getaddrinfo = (node, service, hint, out) => { - var addr = 0; - var port = 0; - var flags = 0; - var family = 0; - var type = 0; - var proto = 0; - var ai; - function allocaddrinfo(family, type, proto, canon, addr, port) { - var sa, salen, ai; - var errno; - salen = family === 10 ? 28 : 16; - addr = family === 10 ? inetNtop6(addr) : inetNtop4(addr); - sa = _malloc(salen); - errno = writeSockaddr(sa, family, addr, port); - assert(!errno); - ai = _malloc(32); - HEAP32[(ai + 4) >> 2] = family; - HEAP32[(ai + 8) >> 2] = type; - HEAP32[(ai + 12) >> 2] = proto; - HEAPU32[(ai + 24) >> 2] = canon; - HEAPU32[(ai + 20) >> 2] = sa; - if (family === 10) { - HEAP32[(ai + 16) >> 2] = 28; - } else { - HEAP32[(ai + 16) >> 2] = 16; + var nextStartTime = 0.0; + while (src.audioQueue.length) { + var audioSrc = src.audioQueue[0]; + src.bufsProcessed += audioSrc._skipCount; + nextStartTime = audioSrc._startTime + audioSrc._duration; // n.b. audioSrc._duration already factors in playbackRate, so no divide by src.playbackRate on it. + + if (currentTime < nextStartTime) { + break; + } + + src.audioQueue.shift(); + src.bufStartTime = nextStartTime; + src.bufOffset = 0.0; + src.bufsProcessed++; } - HEAP32[(ai + 28) >> 2] = 0; - return ai; - } - if (hint) { - flags = HEAP32[hint >> 2]; - family = HEAP32[(hint + 4) >> 2]; - type = HEAP32[(hint + 8) >> 2]; - proto = HEAP32[(hint + 12) >> 2]; - } - if (type && !proto) { - proto = type === 2 ? 17 : 6; - } - if (!type && proto) { - type = proto === 17 ? 2 : 1; - } - // If type or proto are set to zero in hints we should really be returning multiple addrinfo values, but for - // now default to a TCP STREAM socket so we can at least return a sensible addrinfo given NULL hints. - if (proto === 0) { - proto = 6; - } - if (type === 0) { - type = 1; - } - if (!node && !service) { - return -2; - } - if (flags & ~(1 | 2 | 4 | 1024 | 8 | 16 | 32)) { - return -1; - } - if (hint !== 0 && HEAP32[hint >> 2] & 2 && !node) { - return -1; - } - if (flags & 32) { - // TODO - return -2; - } - if (type !== 0 && type !== 1 && type !== 2) { - return -7; - } - if (family !== 0 && family !== 2 && family !== 10) { - return -6; - } - if (service) { - service = UTF8ToString(service); - port = parseInt(service, 10); - if (isNaN(port)) { - if (flags & 1024) { - return -2; + + if (src.bufsProcessed >= src.bufQueue.length && !src.looping) { + // The source has played its entire queue and is non-looping, so just mark it as stopped. + AL.setSourceState(src, 4116); + } else if (src.type === 4136 && src.looping) { + // If the source is a looping static buffer, determine the buffer offset based on the loop points + var buf = src.bufQueue[0]; + if (buf.length === 0) { + src.bufOffset = 0.0; + } else { + var delta = + (currentTime - src.bufStartTime) * src.playbackRate; + var loopStart = buf.audioBuf._loopStart || 0.0; + var loopEnd = + buf.audioBuf._loopEnd || buf.audioBuf.duration; + if (loopEnd <= loopStart) { + loopEnd = buf.audioBuf.duration; + } + + if (delta < loopEnd) { + src.bufOffset = delta; + } else { + src.bufOffset = + loopStart + + ((delta - loopStart) % (loopEnd - loopStart)); + } + } + } else if (src.audioQueue[0]) { + // The source is still actively playing, so we just need to calculate where we are in the current buffer + // so it can be remembered if the source gets paused. + src.bufOffset = + (currentTime - src.audioQueue[0]._startTime) * + src.playbackRate; + } else { + // The source hasn't finished yet, but there is no scheduled audio left for it. This can be because + // the source has just been started/resumed, or due to an underrun caused by a long blocking operation. + // We need to determine what state we would be in by this point in time so that when we next schedule + // audio playback, it will be just as if no underrun occurred. + + if (src.type !== 4136 && src.looping) { + // if the source is a looping buffer queue, let's first calculate the queue duration, so we can + // quickly fast forward past any full loops of the queue and only worry about the remainder. + var srcDuration = AL.sourceDuration(src) / src.playbackRate; + if (srcDuration > 0.0) { + src.bufStartTime += + Math.floor( + (currentTime - src.bufStartTime) / srcDuration + ) * srcDuration; + } + } + + // Since we've already skipped any full-queue loops if there were any, we just need to find + // out where in the queue the remaining time puts us, which won't require stepping through the + // entire queue more than once. + for (var i = 0; i < src.bufQueue.length; i++) { + if (src.bufsProcessed >= src.bufQueue.length) { + if (src.looping) { + src.bufsProcessed %= src.bufQueue.length; + } else { + AL.setSourceState(src, 4116); + break; + } + } + + var buf = src.bufQueue[src.bufsProcessed]; + if (buf.length > 0) { + nextStartTime = + src.bufStartTime + + buf.audioBuf.duration / src.playbackRate; + + if (currentTime < nextStartTime) { + src.bufOffset = + (currentTime - src.bufStartTime) * + src.playbackRate; + break; + } + + src.bufStartTime = nextStartTime; + } + + src.bufOffset = 0.0; + src.bufsProcessed++; } - // TODO support resolving well-known service names from: - // http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt - return -8; } - } - if (!node) { - if (family === 0) { - family = 2; + + return currentTime; + }, + cancelPendingSourceAudio: (src) => { + AL.updateSourceTime(src); + + for (var i = 1; i < src.audioQueue.length; i++) { + var audioSrc = src.audioQueue[i]; + audioSrc.stop(); } - if ((flags & 1) === 0) { - if (family === 2) { - addr = _htonl(2130706433); + + if (src.audioQueue.length > 1) { + src.audioQueue.length = 1; + } + }, + stopSourceAudio: (src) => { + for (var i = 0; i < src.audioQueue.length; i++) { + src.audioQueue[i].stop(); + } + src.audioQueue.length = 0; + }, + setSourceState: (src, state) => { + if (state === 4114) { + if (src.state === 4114 || src.state == 4116) { + src.bufsProcessed = 0; + src.bufOffset = 0.0; } else { - addr = [0, 0, 0, _htonl(1)]; + } + + AL.stopSourceAudio(src); + + src.state = 4114; + src.bufStartTime = Number.NEGATIVE_INFINITY; + AL.scheduleSourceAudio(src); + } else if (state === 4115) { + if (src.state === 4114) { + // Store off the current offset to restore with on resume. + AL.updateSourceTime(src); + AL.stopSourceAudio(src); + + src.state = 4115; + } + } else if (state === 4116) { + if (src.state !== 4113) { + src.state = 4116; + src.bufsProcessed = src.bufQueue.length; + src.bufStartTime = Number.NEGATIVE_INFINITY; + src.bufOffset = 0.0; + AL.stopSourceAudio(src); + } + } else if (state === 4113) { + if (src.state !== 4113) { + src.state = 4113; + src.bufsProcessed = 0; + src.bufStartTime = Number.NEGATIVE_INFINITY; + src.bufOffset = 0.0; + AL.stopSourceAudio(src); } } - ai = allocaddrinfo(family, type, proto, null, addr, port); - HEAPU32[out >> 2] = ai; - return 0; - } - // try as a numeric address - node = UTF8ToString(node); - addr = inetPton4(node); - if (addr !== null) { - // incoming node is a valid ipv4 address - if (family === 0 || family === 2) { - family = 2; - } else if (family === 10 && flags & 8) { - addr = [0, 0, _htonl(65535), addr]; - family = 10; - } else { - return -2; + }, + initSourcePanner: (src) => { + if (src.type === 0x1030 /* AL_UNDETERMINED */) { + return; } - } else { - addr = inetPton6(node); - if (addr !== null) { - // incoming node is a valid ipv6 address - if (family === 0 || family === 10) { - family = 10; - } else { - return -2; + + // Find the first non-zero buffer in the queue to determine the proper format + var templateBuf = AL.buffers[0]; + for (var i = 0; i < src.bufQueue.length; i++) { + if (src.bufQueue[i].id !== 0) { + templateBuf = src.bufQueue[i]; + break; } } - } - if (addr != null) { - ai = allocaddrinfo(family, type, proto, node, addr, port); - HEAPU32[out >> 2] = ai; - return 0; - } - if (flags & 4) { - return -2; - } - // try as a hostname - // resolve the hostname to a temporary fake address - node = DNS.lookup_name(node); - addr = inetPton4(node); - if (family === 0) { - family = 2; - } else if (family === 10) { - addr = [0, 0, _htonl(65535), addr]; - } - ai = allocaddrinfo(family, type, proto, null, addr, port); - HEAPU32[out >> 2] = ai; - return 0; - }; + // Create a panner if AL_SOURCE_SPATIALIZE_SOFT is set to true, or alternatively if it's set to auto and the source is mono + if ( + src.spatialize === 1 || + (src.spatialize === 2 /* AL_AUTO_SOFT */ && + templateBuf.channels === 1) + ) { + if (src.panner) { + return; + } + src.panner = src.context.audioCtx.createPanner(); - var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => { - var info = readSockaddr(sa, salen); - if (info.errno) { - return -6; - } - var port = info.port; - var addr = info.addr; - var overflowed = false; - if (node && nodelen) { - var lookup; - if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) { - if (flags & 8) { - return -2; + AL.updateSourceGlobal(src); + AL.updateSourceSpace(src); + + src.panner.connect(src.context.gain); + src.gain.disconnect(); + src.gain.connect(src.panner); + } else { + if (!src.panner) { + return; } + + src.panner.disconnect(); + src.gain.disconnect(); + src.gain.connect(src.context.gain); + src.panner = null; + } + }, + updateContextGlobal: (ctx) => { + for (var i in ctx.sources) { + AL.updateSourceGlobal(ctx.sources[i]); + } + }, + updateSourceGlobal: (src) => { + var panner = src.panner; + if (!panner) { + return; + } + + panner.refDistance = src.refDistance; + panner.maxDistance = src.maxDistance; + panner.rolloffFactor = src.rolloffFactor; + + panner.panningModel = src.context.hrtf ? 'HRTF' : 'equalpower'; + + // Use the source's distance model if AL_SOURCE_DISTANCE_MODEL is enabled + var distanceModel = src.context.sourceDistanceModel + ? src.distanceModel + : src.context.distanceModel; + switch (distanceModel) { + case 0: + panner.distanceModel = 'inverse'; + panner.refDistance = 3.40282e38 /* FLT_MAX */; + break; + case 0xd001 /* AL_INVERSE_DISTANCE */: + case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: + panner.distanceModel = 'inverse'; + break; + case 0xd003 /* AL_LINEAR_DISTANCE */: + case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: + panner.distanceModel = 'linear'; + break; + case 0xd005 /* AL_EXPONENT_DISTANCE */: + case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: + panner.distanceModel = 'exponential'; + break; + } + }, + updateListenerSpace: (ctx) => { + var listener = ctx.audioCtx.listener; + if (listener.positionX) { + listener.positionX.value = ctx.listener.position[0]; + listener.positionY.value = ctx.listener.position[1]; + listener.positionZ.value = ctx.listener.position[2]; } else { - addr = lookup; + listener.setPosition( + ctx.listener.position[0], + ctx.listener.position[1], + ctx.listener.position[2] + ); } - var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen); - if (numBytesWrittenExclNull + 1 >= nodelen) { - overflowed = true; + if (listener.forwardX) { + listener.forwardX.value = ctx.listener.direction[0]; + listener.forwardY.value = ctx.listener.direction[1]; + listener.forwardZ.value = ctx.listener.direction[2]; + listener.upX.value = ctx.listener.up[0]; + listener.upY.value = ctx.listener.up[1]; + listener.upZ.value = ctx.listener.up[2]; + } else { + listener.setOrientation( + ctx.listener.direction[0], + ctx.listener.direction[1], + ctx.listener.direction[2], + ctx.listener.up[0], + ctx.listener.up[1], + ctx.listener.up[2] + ); } - } - if (serv && servlen) { - port = '' + port; - var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen); - if (numBytesWrittenExclNull + 1 >= servlen) { - overflowed = true; + + // Update sources that are relative to the listener + for (var i in ctx.sources) { + AL.updateSourceSpace(ctx.sources[i]); } - } - if (overflowed) { - // Note: even when we overflow, getnameinfo() is specced to write out the truncated results. - return -12; - } - return 0; - }; + }, + updateSourceSpace: (src) => { + if (!src.panner) { + return; + } + var panner = src.panner; + + var posX = src.position[0]; + var posY = src.position[1]; + var posZ = src.position[2]; + var dirX = src.direction[0]; + var dirY = src.direction[1]; + var dirZ = src.direction[2]; + + var listener = src.context.listener; + var lPosX = listener.position[0]; + var lPosY = listener.position[1]; + var lPosZ = listener.position[2]; + + // WebAudio does spatialization in world-space coordinates, meaning both the buffer sources and + // the listener position are in the same absolute coordinate system relative to a fixed origin. + // By default, OpenAL works this way as well, but it also provides a "listener relative" mode, where + // a buffer source's coordinate are interpreted not in absolute world space, but as being relative + // to the listener object itself, so as the listener moves the source appears to move with it + // with no update required. Since web audio does not support this mode, we must transform the source + // coordinates from listener-relative space to absolute world space. + // + // We do this via affine transformation matrices applied to the source position and source direction. + // A change-of-basis converts from listener-space displacements to world-space displacements, + // which must be done for both the source position and direction. Lastly, the source position must be + // added to the listener position to get the final source position, since the source position represents + // a displacement from the listener. + if (src.relative) { + // Negate the listener direction since forward is -Z. + var lBackX = -listener.direction[0]; + var lBackY = -listener.direction[1]; + var lBackZ = -listener.direction[2]; + var lUpX = listener.up[0]; + var lUpY = listener.up[1]; + var lUpZ = listener.up[2]; + + var inverseMagnitude = (x, y, z) => { + var length = Math.sqrt(x * x + y * y + z * z); + + if (length < Number.EPSILON) { + return 0.0; + } - var Protocols = { - list: [], - map: {}, - }; + return 1.0 / length; + }; - var _setprotoent = (stayopen) => { - // void setprotoent(int stayopen); - // Allocate and populate a protoent structure given a name, protocol number and array of aliases - function allocprotoent(name, proto, aliases) { - // write name into buffer - var nameBuf = _malloc(name.length + 1); - stringToAscii(name, nameBuf); - // write aliases into buffer - var j = 0; - var length = aliases.length; - var aliasListBuf = _malloc((length + 1) * 4); - // Use length + 1 so we have space for the terminating NULL ptr. - for (var i = 0; i < length; i++, j += 4) { - var alias = aliases[i]; - var aliasBuf = _malloc(alias.length + 1); - stringToAscii(alias, aliasBuf); - HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf; + // Normalize the Back vector + var invMag = inverseMagnitude(lBackX, lBackY, lBackZ); + lBackX *= invMag; + lBackY *= invMag; + lBackZ *= invMag; + + // ...and the Up vector + invMag = inverseMagnitude(lUpX, lUpY, lUpZ); + lUpX *= invMag; + lUpY *= invMag; + lUpZ *= invMag; + + // Calculate the Right vector as the cross product of the Up and Back vectors + var lRightX = lUpY * lBackZ - lUpZ * lBackY; + var lRightY = lUpZ * lBackX - lUpX * lBackZ; + var lRightZ = lUpX * lBackY - lUpY * lBackX; + + // Back and Up might not be exactly perpendicular, so the cross product also needs normalization + invMag = inverseMagnitude(lRightX, lRightY, lRightZ); + lRightX *= invMag; + lRightY *= invMag; + lRightZ *= invMag; + + // Recompute Up from the now orthonormal Right and Back vectors so we have a fully orthonormal basis + lUpX = lBackY * lRightZ - lBackZ * lRightY; + lUpY = lBackZ * lRightX - lBackX * lRightZ; + lUpZ = lBackX * lRightY - lBackY * lRightX; + + var oldX = dirX; + var oldY = dirY; + var oldZ = dirZ; + + // Use our 3 vectors to apply a change-of-basis matrix to the source direction + dirX = oldX * lRightX + oldY * lUpX + oldZ * lBackX; + dirY = oldX * lRightY + oldY * lUpY + oldZ * lBackY; + dirZ = oldX * lRightZ + oldY * lUpZ + oldZ * lBackZ; + + oldX = posX; + oldY = posY; + oldZ = posZ; + + // ...and to the source position + posX = oldX * lRightX + oldY * lUpX + oldZ * lBackX; + posY = oldX * lRightY + oldY * lUpY + oldZ * lBackY; + posZ = oldX * lRightZ + oldY * lUpZ + oldZ * lBackZ; + + // The change-of-basis corrects the orientation, but the origin is still the listener. + // Translate the source position by the listener position to finish. + posX += lPosX; + posY += lPosY; + posZ += lPosZ; + } + + if (panner.positionX) { + // Assigning to panner.positionX/Y/Z unnecessarily seems to cause performance issues + // See https://github.com/emscripten-core/emscripten/issues/15847 + + if (posX != panner.positionX.value) + panner.positionX.value = posX; + if (posY != panner.positionY.value) + panner.positionY.value = posY; + if (posZ != panner.positionZ.value) + panner.positionZ.value = posZ; + } else { + panner.setPosition(posX, posY, posZ); + } + if (panner.orientationX) { + // Assigning to panner.orientation/Y/Z unnecessarily seems to cause performance issues + // See https://github.com/emscripten-core/emscripten/issues/15847 + + if (dirX != panner.orientationX.value) + panner.orientationX.value = dirX; + if (dirY != panner.orientationY.value) + panner.orientationY.value = dirY; + if (dirZ != panner.orientationZ.value) + panner.orientationZ.value = dirZ; + } else { + panner.setOrientation(dirX, dirY, dirZ); } - HEAPU32[(aliasListBuf + j) >> 2] = 0; - // Terminating NULL pointer. - // generate protoent - var pe = _malloc(12); - HEAPU32[pe >> 2] = nameBuf; - HEAPU32[(pe + 4) >> 2] = aliasListBuf; - HEAP32[(pe + 8) >> 2] = proto; - return pe; - } - // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial - // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful. - var list = Protocols.list; - var map = Protocols.map; - if (list.length === 0) { - var entry = allocprotoent('tcp', 6, ['TCP']); - list.push(entry); - map['tcp'] = map['6'] = entry; - entry = allocprotoent('udp', 17, ['UDP']); - list.push(entry); - map['udp'] = map['17'] = entry; - } - _setprotoent.index = 0; - }; - var _getprotobyname = (name) => { - // struct protoent *getprotobyname(const char *); - name = UTF8ToString(name); - _setprotoent(true); - var result = Protocols.map[name]; - return result; - }; + var oldShift = src.dopplerShift; + var velX = src.velocity[0]; + var velY = src.velocity[1]; + var velZ = src.velocity[2]; + var lVelX = listener.velocity[0]; + var lVelY = listener.velocity[1]; + var lVelZ = listener.velocity[2]; + if ( + (posX === lPosX && posY === lPosY && posZ === lPosZ) || + (velX === lVelX && velY === lVelY && velZ === lVelZ) + ) { + src.dopplerShift = 1.0; + } else { + // Doppler algorithm from 1.1 spec + var speedOfSound = src.context.speedOfSound; + var dopplerFactor = src.context.dopplerFactor; - var _getprotobynumber = (number) => { - // struct protoent *getprotobynumber(int proto); - _setprotoent(true); - var result = Protocols.map[number]; - return result; - }; + var slX = lPosX - posX; + var slY = lPosY - posY; + var slZ = lPosZ - posZ; - var stackAlloc = (sz) => __emscripten_stack_alloc(sz); + var magSl = Math.sqrt(slX * slX + slY * slY + slZ * slZ); + var vls = (slX * lVelX + slY * lVelY + slZ * lVelZ) / magSl; + var vss = (slX * velX + slY * velY + slZ * velZ) / magSl; - /** @suppress {duplicate } */ var stringToUTF8OnStack = (str) => { - var size = lengthBytesUTF8(str) + 1; - var ret = stackAlloc(size); - stringToUTF8(str, ret, size); - return ret; - }; + vls = Math.min(vls, speedOfSound / dopplerFactor); + vss = Math.min(vss, speedOfSound / dopplerFactor); - var allocateUTF8OnStack = stringToUTF8OnStack; + src.dopplerShift = + (speedOfSound - dopplerFactor * vls) / + (speedOfSound - dopplerFactor * vss); + } + if (src.dopplerShift !== oldShift) { + AL.updateSourceRate(src); + } + }, + updateSourceRate: (src) => { + if (src.state === 4114) { + // clear scheduled buffers + AL.cancelPendingSourceAudio(src); + + var audioSrc = src.audioQueue[0]; + if (!audioSrc) { + return; // It is possible that AL.scheduleContextAudio() has not yet fed the next buffer, if so, skip. + } - function _js_getpid() { - return PHPLoader.processId ?? 42; - } + var duration; + if (src.type === 4136 && src.looping) { + duration = Number.POSITIVE_INFINITY; + } else { + // audioSrc._duration is expressed after factoring in playbackRate, so when changing playback rate, need + // to recompute/rescale the rate to the new playback speed. + duration = + (audioSrc.buffer.duration - audioSrc._startOffset) / + src.playbackRate; + } - function _js_wasm_trace(format, ...args) { - if (PHPLoader.trace instanceof Function) { - PHPLoader.trace(_js_getpid(), format, ...args); - } - } + audioSrc._duration = duration; + audioSrc.playbackRate.value = src.playbackRate; - var PHPWASM = { - init: function () { - Module['ENV'] = Module['ENV'] || {}; - // Ensure a platform-level bin directory for a fallback `php` binary. - Module['ENV']['PATH'] = [ - Module['ENV']['PATH'], - '/internal/shared/bin', - ] - .filter(Boolean) - .join(':'); + // reschedule buffers with the new playbackRate + AL.scheduleSourceAudio(src); + } + }, + sourceDuration: (src) => { + var length = 0.0; + for (var i = 0; i < src.bufQueue.length; i++) { + var audioBuf = src.bufQueue[i].audioBuf; + length += audioBuf ? audioBuf.duration : 0.0; + } + return length; + }, + sourceTell: (src) => { + AL.updateSourceTime(src); - // The /internal directory is required by the C module. It's where the - // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. - FS.mkdir('/internal'); - // The files from the shared directory are shared between all the - // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); - // The files from the preload directory are preloaded using the - // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); - // Platform-level bin directory for a fallback `php` binary. Without it, - // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); - const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; - Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( - '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') - ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); - originalOnRuntimeInitialized(); - }; - // Create stdout and stderr devices. We can't just use Emscripten's - // default stdout and stderr devices because they stop processing data - // on the first null byte. However, when dealing with binary data, - // null bytes are valid and common. - FS.registerDevice(FS.makedev(64, 0), { - open: () => {}, - close: () => {}, - read: () => 0, - write: (stream, buffer, offset, length, pos) => { - const chunk = buffer.subarray(offset, offset + length); - PHPWASM.onStdout(chunk); - return length; - }, - }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); - FS.registerDevice(FS.makedev(63, 0), { - open: () => {}, - close: () => {}, - read: () => 0, - write: (stream, buffer, offset, length, pos) => { - const chunk = buffer.subarray(offset, offset + length); - PHPWASM.onStderr(chunk); - return length; - }, - }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); - FS.registerDevice(FS.makedev(62, 0), { - open: () => {}, - close: () => {}, - read: () => 0, - write: (stream, buffer, offset, length, pos) => { - const chunk = buffer.subarray(offset, offset + length); - PHPWASM.onHeaders(chunk); - return length; - }, - }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); - // Handle events. - PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE - ? require('events').EventEmitter - : class EventEmitter { - constructor() { - this.listeners = {}; - } - emit(eventName, data) { - if (this.listeners[eventName]) { - this.listeners[eventName].forEach( - (callback) => { - callback(data); - } - ); - } - } - once(eventName, callback) { - const self = this; - function removedCallback() { - callback(...arguments); - self.removeListener(eventName, removedCallback); - } - this.on(eventName, removedCallback); - } - removeAllListeners(eventName) { - if (eventName) { - delete this.listeners[eventName]; - } else { - this.listeners = {}; - } - } - removeListener(eventName, callback) { - if (this.listeners[eventName]) { - const idx = - this.listeners[eventName].indexOf(callback); - if (idx !== -1) { - this.listeners[eventName].splice(idx, 1); - } - } - } - }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; - PHPWASM.child_proc_by_pid = {}; - PHPWASM.input_devices = {}; - const originalWrite = TTY.stream_ops.write; - TTY.stream_ops.write = function (stream, ...rest) { - const retval = originalWrite(stream, ...rest); - // Implicit flush since PHP's fflush() doesn't seem to trigger the fsync event - // @TODO: Fix this at the wasm level - stream.tty.ops.fsync(stream.tty); - return retval; - }; - const originalPutChar = TTY.stream_ops.put_char; - TTY.stream_ops.put_char = function (tty, val) { - /** - * Buffer newlines that Emscripten normally ignores. - * - * Emscripten doesn't do it by default because its default - * print function is console.log that implicitly adds a newline. We are overwriting - * it with an environment-specific function that outputs exaclty what it was given, - * e.g. in Node.js it's process.stdout.write(). Therefore, we need to mak sure - * all the newlines make it to the output buffer. - */ if (val === 10) tty.output.push(val); - return originalPutChar(tty, val); - }; + var offset = 0.0; + for (var i = 0; i < src.bufsProcessed; i++) { + if (src.bufQueue[i].audioBuf) { + offset += src.bufQueue[i].audioBuf.duration; + } + } + offset += src.bufOffset; + + return offset; }, - onHeaders: function (chunk) { - if (Module['onHeaders']) { - Module['onHeaders'](chunk); - return; + sourceSeek: (src, offset) => { + var playing = src.state == 4114; + if (playing) { + AL.setSourceState(src, 4113); + } + + if (src.bufQueue[src.bufsProcessed].audioBuf !== null) { + src.bufsProcessed = 0; + while ( + offset > src.bufQueue[src.bufsProcessed].audioBuf.duration + ) { + offset -= src.bufQueue[src.bufsProcessed].audioBuf.duration; + src.bufsProcessed++; + } + + src.bufOffset = offset; + } + + if (playing) { + AL.setSourceState(src, 4114); } - console.log('headers', { - chunk, - }); }, - onStdout: function (chunk) { - if (Module['onStdout']) { - Module['onStdout'](chunk); - return; + getGlobalParam: (funcname, param) => { + if (!AL.currentCtx) { + return null; } - if (ENVIRONMENT_IS_NODE) { - process.stdout.write(chunk); - } else { - console.log('stdout', { - chunk, - }); + + switch (param) { + case 49152: + return AL.currentCtx.dopplerFactor; + case 49155: + return AL.currentCtx.speedOfSound; + case 53248: + return AL.currentCtx.distanceModel; + default: + AL.currentCtx.err = 40962; + return null; } }, - onStderr: function (chunk) { - if (Module['onStderr']) { - Module['onStderr'](chunk); + setGlobalParam: (funcname, param, value) => { + if (!AL.currentCtx) { return; } - if (ENVIRONMENT_IS_NODE) { - process.stderr.write(chunk); - } else { - console.warn('stderr', { - chunk, - }); + + switch (param) { + case 49152: + if (!Number.isFinite(value) || value < 0.0) { + // Strictly negative values are disallowed + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.dopplerFactor = value; + AL.updateListenerSpace(AL.currentCtx); + break; + case 49155: + if (!Number.isFinite(value) || value <= 0.0) { + // Negative or zero values are disallowed + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.speedOfSound = value; + AL.updateListenerSpace(AL.currentCtx); + break; + case 53248: + switch (value) { + case 0: + case 0xd001 /* AL_INVERSE_DISTANCE */: + case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: + case 0xd003 /* AL_LINEAR_DISTANCE */: + case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: + case 0xd005 /* AL_EXPONENT_DISTANCE */: + case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: + AL.currentCtx.distanceModel = value; + AL.updateContextGlobal(AL.currentCtx); + break; + default: + AL.currentCtx.err = 40963; + return; + } + break; + default: + AL.currentCtx.err = 40962; + return; } }, - getAllWebSockets: function (sock) { - const webSockets = new Set(); - if (sock.server) { - sock.server.clients.forEach((ws) => { - webSockets.add(ws); - }); + getListenerParam: (funcname, param) => { + if (!AL.currentCtx) { + return null; } - for (const peer of PHPWASM.getAllPeers(sock)) { - webSockets.add(peer.socket); + + switch (param) { + case 4100: + return AL.currentCtx.listener.position; + case 4102: + return AL.currentCtx.listener.velocity; + case 4111: + return AL.currentCtx.listener.direction.concat( + AL.currentCtx.listener.up + ); + case 4106: + return AL.currentCtx.gain.gain.value; + default: + AL.currentCtx.err = 40962; + return null; } - return Array.from(webSockets); }, - getAllPeers: function (sock) { - const peers = new Set(); - if (sock.server) { - sock.pending - .filter((pending) => pending.peers) - .forEach((pending) => { - for (const peer of Object.values(pending.peers)) { - peers.add(peer); - } - }); + setListenerParam: (funcname, param, value) => { + if (!AL.currentCtx) { + return; } - if (sock.peers) { - for (const peer of Object.values(sock.peers)) { - peers.add(peer); - } + if (value === null) { + AL.currentCtx.err = 40962; + return; } - return Array.from(peers); - }, - awaitData: function (ws) { - return PHPWASM.awaitEvent(ws, 'message'); - }, - awaitConnection: function (ws) { - if (ws.OPEN === ws.readyState) { - return [Promise.resolve(), PHPWASM.noop]; + + var listener = AL.currentCtx.listener; + switch (param) { + case 4100: + if ( + !Number.isFinite(value[0]) || + !Number.isFinite(value[1]) || + !Number.isFinite(value[2]) + ) { + AL.currentCtx.err = 40963; + return; + } + + listener.position[0] = value[0]; + listener.position[1] = value[1]; + listener.position[2] = value[2]; + AL.updateListenerSpace(AL.currentCtx); + break; + case 4102: + if ( + !Number.isFinite(value[0]) || + !Number.isFinite(value[1]) || + !Number.isFinite(value[2]) + ) { + AL.currentCtx.err = 40963; + return; + } + + listener.velocity[0] = value[0]; + listener.velocity[1] = value[1]; + listener.velocity[2] = value[2]; + AL.updateListenerSpace(AL.currentCtx); + break; + case 4106: + if (!Number.isFinite(value) || value < 0.0) { + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.gain.gain.value = value; + break; + case 4111: + if ( + !Number.isFinite(value[0]) || + !Number.isFinite(value[1]) || + !Number.isFinite(value[2]) || + !Number.isFinite(value[3]) || + !Number.isFinite(value[4]) || + !Number.isFinite(value[5]) + ) { + AL.currentCtx.err = 40963; + return; + } + + listener.direction[0] = value[0]; + listener.direction[1] = value[1]; + listener.direction[2] = value[2]; + listener.up[0] = value[3]; + listener.up[1] = value[4]; + listener.up[2] = value[5]; + AL.updateListenerSpace(AL.currentCtx); + break; + default: + AL.currentCtx.err = 40962; + return; } - return PHPWASM.awaitEvent(ws, 'open'); }, - awaitClose: function (ws) { - if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { - return [Promise.resolve(), PHPWASM.noop]; + getBufferParam: (funcname, bufferId, param) => { + if (!AL.currentCtx) { + return; } - return PHPWASM.awaitEvent(ws, 'close'); - }, - awaitError: function (ws) { - if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { - return [Promise.resolve(), PHPWASM.noop]; + var buf = AL.buffers[bufferId]; + if (!buf || bufferId === 0) { + AL.currentCtx.err = 40961; + return; } - return PHPWASM.awaitEvent(ws, 'error'); - }, - awaitEvent: function (ws, event) { - let resolve; - const listener = () => { - resolve(); - }; - const promise = new Promise(function (_resolve) { - resolve = _resolve; - ws.once(event, listener); - }); - const cancel = () => { - ws.removeListener(event, listener); - // Rejecting the promises bubbles up and kills the entire - // node process. Let's resolve them on the next tick instead - // to give the caller some space to unbind any handlers. - setTimeout(resolve); - }; - return [promise, cancel]; - }, - noop: function () {}, - spawnProcess: function (command, args, options) { - if (Module['spawnProcess']) { - const spawnedPromise = Module['spawnProcess']( - command, - args, - options - ); - return Promise.resolve(spawnedPromise).then(function (spawned) { - if (!spawned || !spawned.on) { - throw new Error( - 'spawnProcess() must return an EventEmitter but returned a different type.' - ); + + switch (param) { + case 0x2001 /* AL_FREQUENCY */: + return buf.frequency; + case 0x2002 /* AL_BITS */: + return buf.bytesPerSample * 8; + case 0x2003 /* AL_CHANNELS */: + return buf.channels; + case 0x2004 /* AL_SIZE */: + return buf.length * buf.bytesPerSample * buf.channels; + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + if (buf.length === 0) { + return [0, 0]; } - return spawned; - }); - } - if (ENVIRONMENT_IS_NODE) { - return require('child_process').spawn(command, args, { - ...options, - shell: true, - stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, - }); + return [ + (buf.audioBuf._loopStart || 0.0) * buf.frequency, + (buf.audioBuf._loopEnd || buf.length) * buf.frequency, + ]; + default: + AL.currentCtx.err = 40962; + return null; } - const e = new Error( - 'popen(), proc_open() etc. are unsupported in the browser. Call php.setSpawnHandler() ' + - 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' + - 'and similar functions via php.ini.' - ); - e.code = 'SPAWN_UNSUPPORTED'; - throw e; }, - shutdownSocket: function (socketd, how) { - // This implementation only supports websockets at the moment - const sock = getSocketFromFD(socketd); - const peer = Object.values(sock.peers)[0]; - if (!peer) { - return -1; + setBufferParam: (funcname, bufferId, param, value) => { + if (!AL.currentCtx) { + return; } - try { - peer.socket.close(); - SOCKFS.websocket_sock_ops.removePeer(sock, peer); - return 0; - } catch (e) { - console.log('Socket shutdown error', e); - return -1; + var buf = AL.buffers[bufferId]; + if (!buf || bufferId === 0) { + AL.currentCtx.err = 40961; + return; + } + if (value === null) { + AL.currentCtx.err = 40962; + return; } - }, - }; - function _js_create_input_device(deviceId) { - let dataBuffer = []; - let dataCallback; - const filename = 'proc_id_' + deviceId; - const device = FS.createDevice( - '/dev', - filename, - function () {}, - function (byte) { - try { - dataBuffer.push(byte); - if (dataCallback) { - dataCallback(new Uint8Array(dataBuffer)); - dataBuffer = []; + switch (param) { + case 0x2004 /* AL_SIZE */: + if (value !== 0) { + AL.currentCtx.err = 40963; + return; } - } catch (e) { - console.error(e); - throw e; - } - } - ); - const devicePath = '/dev/' + filename; - PHPWASM.input_devices[deviceId] = { - devicePath, - onData: function (cb) { - dataCallback = cb; - dataBuffer.forEach(function (data) { - cb(data); - }); - dataBuffer.length = 0; - }, - }; - return allocateUTF8OnStack(devicePath); - } - function _js_open_process( - command, - argsPtr, - argsLength, - descriptorsPtr, - descriptorsLength, - cwdPtr, - cwdLength, - envPtr, - envLength - ) { - if (!command) { - return 1; - } - const cmdstr = UTF8ToString(command); - if (!cmdstr.length) { - return 0; - } - let argsArray = []; - if (argsLength) { - for (var i = 0; i < argsLength; i++) { - const charPointer = argsPtr + i * 4; - argsArray.push(UTF8ToString(HEAPU32[charPointer >> 2])); + // Per the spec, setting AL_SIZE to 0 is a legal NOP. + break; + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + if ( + value[0] < 0 || + value[0] > buf.length || + value[1] < 0 || + value[1] > buf.Length || + value[0] >= value[1] + ) { + AL.currentCtx.err = 40963; + return; + } + if (buf.refCount > 0) { + AL.currentCtx.err = 40964; + return; + } + + if (buf.audioBuf) { + buf.audioBuf._loopStart = value[0] / buf.frequency; + buf.audioBuf._loopEnd = value[1] / buf.frequency; + } + break; + default: + AL.currentCtx.err = 40962; + return; } - } - const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : FS.cwd(); - let envObject = null; - if (envLength) { - envObject = {}; - for (var i = 0; i < envLength; i++) { - const envPointer = envPtr + i * 4; - const envEntry = UTF8ToString(HEAPU32[envPointer >> 2]); - const splitAt = envEntry.indexOf('='); - if (splitAt === -1) { - continue; - } - const key = envEntry.substring(0, splitAt); - const value = envEntry.substring(splitAt + 1); - envObject[key] = value; + }, + getSourceParam: (funcname, sourceId, param) => { + if (!AL.currentCtx) { + return null; } - } - var std = {}; - // Extracts an array of available descriptors that should be dispatched to streams. - // On the C side, the descriptors are expressed as `**int` so we must go read - // each of the `descriptorsLength` `*int` pointers and convert the associated data into - // a JavaScript object { descriptor : { child : fd, parent : fd } }. - for (var i = 0; i < descriptorsLength; i++) { - const descriptorPtr = HEAPU32[(descriptorsPtr + i * 4) >> 2]; - std[HEAPU32[descriptorPtr >> 2]] = { - child: HEAPU32[(descriptorPtr + 4) >> 2], - parent: HEAPU32[(descriptorPtr + 8) >> 2], - }; - } - return Asyncify.handleSleep(async (wakeUp) => { - let cp; - try { - const options = {}; - if (cwdstr !== null) { - options.cwd = cwdstr; - } - if (envObject !== null) { - options.env = envObject; - } - cp = PHPWASM.spawnProcess(cmdstr, argsArray, options); - if (cp instanceof Promise) { - cp = await cp; - } - } catch (e) { - if (e.code === 'SPAWN_UNSUPPORTED') { - wakeUp(1); - return; - } - console.error(e); - wakeUp(1); - throw e; + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; + return null; } - const ProcInfo = { - pid: cp.pid, - exited: false, - stdinFd: std[0]?.child, - stdinIsDevice: std[0]?.child in PHPWASM.input_devices, - stdoutChildFd: std[1]?.child, - stdoutParentFd: std[1]?.parent, - stderrChildFd: std[2]?.child, - stderrParentFd: std[2]?.parent, - stdout: new PHPWASM.EventEmitter(), - stderr: new PHPWASM.EventEmitter(), - }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; - PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; - cp.on('exit', function (code) { - for (const fd of [ - // The child process exited. Let's clean up its output streams: - ProcInfo.stdoutChildFd, - ProcInfo.stderrChildFd, - ]) { - if (FS.streams[fd] && !FS.isClosed(FS.streams[fd])) { - FS.close(FS.streams[fd]); + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + return src.relative; + case 0x1001 /* AL_CONE_INNER_ANGLE */: + return src.coneInnerAngle; + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + return src.coneOuterAngle; + case 0x1003 /* AL_PITCH */: + return src.pitch; + case 4100: + return src.position; + case 4101: + return src.direction; + case 4102: + return src.velocity; + case 0x1007 /* AL_LOOPING */: + return src.looping; + case 0x1009 /* AL_BUFFER */: + if (src.type === 4136) { + return src.bufQueue[0].id; } - } - ProcInfo.exitCode = code; - ProcInfo.exited = true; - // Emit events for the wasm_poll_socket function. - ProcInfo.stdout.emit('data'); - ProcInfo.stderr.emit('data'); - }); - // Pass data from child process's stdout to PHP's end of the stdout pipe. - if (ProcInfo.stdoutChildFd) { - const stdoutStream = SYSCALLS.getStreamFromFD( - ProcInfo.stdoutChildFd - ); - let stdoutAt = 0; - cp.stdout.on('data', function (data) { - ProcInfo.stdout.emit('data', data); - stdoutStream.stream_ops.write( - stdoutStream, - data, - 0, - data.length, - stdoutAt - ); - stdoutAt += data.length; - }); + return 0; + case 4106: + return src.gain.gain.value; + case 0x100d /* AL_MIN_GAIN */: + return src.minGain; + case 0x100e /* AL_MAX_GAIN */: + return src.maxGain; + case 0x1010 /* AL_SOURCE_STATE */: + return src.state; + case 0x1015 /* AL_BUFFERS_QUEUED */: + if (src.bufQueue.length === 1 && src.bufQueue[0].id === 0) { + return 0; + } + return src.bufQueue.length; + case 0x1016 /* AL_BUFFERS_PROCESSED */: + if ( + (src.bufQueue.length === 1 && + src.bufQueue[0].id === 0) || + src.looping + ) { + return 0; + } + return src.bufsProcessed; + case 0x1020 /* AL_REFERENCE_DISTANCE */: + return src.refDistance; + case 0x1021 /* AL_ROLLOFF_FACTOR */: + return src.rolloffFactor; + case 0x1022 /* AL_CONE_OUTER_GAIN */: + return src.coneOuterGain; + case 0x1023 /* AL_MAX_DISTANCE */: + return src.maxDistance; + case 0x1024 /* AL_SEC_OFFSET */: + return AL.sourceTell(src); + case 0x1025 /* AL_SAMPLE_OFFSET */: + var offset = AL.sourceTell(src); + if (offset > 0.0) { + offset *= src.bufQueue[0].frequency; + } + return offset; + case 0x1026 /* AL_BYTE_OFFSET */: + var offset = AL.sourceTell(src); + if (offset > 0.0) { + offset *= + src.bufQueue[0].frequency * + src.bufQueue[0].bytesPerSample; + } + return offset; + case 0x1027 /* AL_SOURCE_TYPE */: + return src.type; + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + return src.spatialize; + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + var length = 0; + var bytesPerFrame = 0; + for (var i = 0; i < src.bufQueue.length; i++) { + length += src.bufQueue[i].length; + if (src.bufQueue[i].id !== 0) { + bytesPerFrame = + src.bufQueue[i].bytesPerSample * + src.bufQueue[i].channels; + } + } + return length * bytesPerFrame; + case 0x200a /* AL_SAMPLE_LENGTH_SOFT */: + var length = 0; + for (var i = 0; i < src.bufQueue.length; i++) { + length += src.bufQueue[i].length; + } + return length; + case 0x200b /* AL_SEC_LENGTH_SOFT */: + return AL.sourceDuration(src); + case 53248: + return src.distanceModel; + default: + AL.currentCtx.err = 40962; + return null; } - // Pass data from child process's stderr to PHP's end of the stdout pipe. - if (ProcInfo.stderrChildFd) { - const stderrStream = SYSCALLS.getStreamFromFD( - ProcInfo.stderrChildFd - ); - let stderrAt = 0; - cp.stderr.on('data', function (data) { - ProcInfo.stderr.emit('data', data); - stderrStream.stream_ops.write( - stderrStream, - data, - 0, - data.length, - stderrAt - ); - stderrAt += data.length; - }); + }, + setSourceParam: (funcname, sourceId, param, value) => { + if (!AL.currentCtx) { + return; } - /** - * Wait until the child process has been spawned. - * Unfortunately there is no Node.js API to check whether - * the process has already been spawned. We can only listen - * to the 'spawn' event and if it has already been spawned, - * listen to the 'exit' event. - */ try { - await new Promise((resolve, reject) => { - /** - * There was no `await` between the `spawnProcess` call - * and the `await` below so the process haven't had a chance - * to run any of the exit-related callbacks yet. - * - * Good. - * - * Let's listen to all the lifecycle events and resolve - * the promise when the process starts or immediately crashes. - */ let resolved = false; - cp.on('spawn', () => { - if (resolved) return; - resolved = true; - resolve(); - }); - cp.on('error', (e) => { - if (resolved) return; - resolved = true; - reject(e); - }); - cp.on('exit', function (code) { - if (resolved) return; - resolved = true; - if (code === 0) { - resolve(); - } else { - reject( - new Error(`Process exited with code ${code}`) - ); - } - }); - /** - * If the process haven't even started after 5 seconds, something - * is wrong. Perhaps we're missing an event listener, or perhaps - * the `spawnProcess` implementation failed to dispatch the relevant - * event. Either way, let's crash to avoid blocking the proc_open() - * call indefinitely. - */ setTimeout(() => { - if (resolved) return; - resolved = true; - reject(new Error('Process timed out')); - }, 5e3); - }); - } catch (e) { - console.error(e); - wakeUp(ProcInfo.pid); + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; return; } - // Now we want to pass data from the STDIN source supplied by PHP - // to the child process. - // PHP will write STDIN data to a device. - if (ProcInfo.stdinIsDevice) { - // We use Emscripten devices as pipes. This is a bit of a hack - // but it works as we get a callback when the device is written to. - // Let's listen to anything it outputs and pass it to the child process. - PHPWASM.input_devices[ProcInfo.stdinFd].onData(function (data) { - if (!data) return; - if (typeof data === 'number') { - data = new Uint8Array([data]); - } - const dataStr = new TextDecoder('utf-8').decode(data); - cp.stdin.write(dataStr); - }); - wakeUp(ProcInfo.pid); + if (value === null) { + AL.currentCtx.err = 40962; return; } - if (ProcInfo.stdinFd) { - // PHP will write STDIN data to a file descriptor. - const stdinStream = SYSCALLS.getStreamFromFD(ProcInfo.stdinFd); - if (stdinStream.node) { - // Pipe the entire stdinStream to cp.stdin - const CHUNK_SIZE = 1024; - const buffer = new Uint8Array(CHUNK_SIZE); - let offset = 0; - while (true) { - const bytesRead = stdinStream.stream_ops.read( - stdinStream, - buffer, - 0, - CHUNK_SIZE, - offset - ); - if (bytesRead === null || bytesRead === 0) { - break; + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + if (value === 1) { + src.relative = true; + AL.updateSourceSpace(src); + } else if (value === 0) { + src.relative = false; + AL.updateSourceSpace(src); + } else { + AL.currentCtx.err = 40963; + return; + } + break; + case 0x1001 /* AL_CONE_INNER_ANGLE */: + if (!Number.isFinite(value)) { + AL.currentCtx.err = 40963; + return; + } + + src.coneInnerAngle = value; + if (src.panner) { + src.panner.coneInnerAngle = value % 360.0; + } + break; + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + if (!Number.isFinite(value)) { + AL.currentCtx.err = 40963; + return; + } + + src.coneOuterAngle = value; + if (src.panner) { + src.panner.coneOuterAngle = value % 360.0; + } + break; + case 0x1003 /* AL_PITCH */: + if (!Number.isFinite(value) || value <= 0.0) { + AL.currentCtx.err = 40963; + return; + } + + if (src.pitch === value) { + break; + } + + src.pitch = value; + AL.updateSourceRate(src); + break; + case 4100: + if ( + !Number.isFinite(value[0]) || + !Number.isFinite(value[1]) || + !Number.isFinite(value[2]) + ) { + AL.currentCtx.err = 40963; + return; + } + + src.position[0] = value[0]; + src.position[1] = value[1]; + src.position[2] = value[2]; + AL.updateSourceSpace(src); + break; + case 4101: + if ( + !Number.isFinite(value[0]) || + !Number.isFinite(value[1]) || + !Number.isFinite(value[2]) + ) { + AL.currentCtx.err = 40963; + return; + } + + src.direction[0] = value[0]; + src.direction[1] = value[1]; + src.direction[2] = value[2]; + AL.updateSourceSpace(src); + break; + case 4102: + if ( + !Number.isFinite(value[0]) || + !Number.isFinite(value[1]) || + !Number.isFinite(value[2]) + ) { + AL.currentCtx.err = 40963; + return; + } + + src.velocity[0] = value[0]; + src.velocity[1] = value[1]; + src.velocity[2] = value[2]; + AL.updateSourceSpace(src); + break; + case 0x1007 /* AL_LOOPING */: + if (value === 1) { + src.looping = true; + AL.updateSourceTime(src); + if (src.type === 4136 && src.audioQueue.length > 0) { + var audioSrc = src.audioQueue[0]; + audioSrc.loop = true; + audioSrc._duration = Number.POSITIVE_INFINITY; } - try { - cp.stdin.write(buffer.subarray(0, bytesRead)); - } catch (e) { - console.error(e); - return 1; + } else if (value === 0) { + src.looping = false; + var currentTime = AL.updateSourceTime(src); + if (src.type === 4136 && src.audioQueue.length > 0) { + var audioSrc = src.audioQueue[0]; + audioSrc.loop = false; + audioSrc._duration = + src.bufQueue[0].audioBuf.duration / + src.playbackRate; + audioSrc._startTime = + currentTime - src.bufOffset / src.playbackRate; } - if (bytesRead < CHUNK_SIZE) { - break; + } else { + AL.currentCtx.err = 40963; + return; + } + break; + case 0x1009 /* AL_BUFFER */: + if (src.state === 4114 || src.state === 4115) { + AL.currentCtx.err = 40964; + return; + } + + if (value === 0) { + for (var i in src.bufQueue) { + src.bufQueue[i].refCount--; } - offset += bytesRead; + src.bufQueue.length = 1; + src.bufQueue[0] = AL.buffers[0]; + + src.bufsProcessed = 0; + src.type = 0x1030 /* AL_UNDETERMINED */; + } else { + var buf = AL.buffers[value]; + if (!buf) { + AL.currentCtx.err = 40963; + return; + } + + for (var i in src.bufQueue) { + src.bufQueue[i].refCount--; + } + src.bufQueue.length = 0; + + buf.refCount++; + src.bufQueue = [buf]; + src.bufsProcessed = 0; + src.type = 4136; } - wakeUp(ProcInfo.pid); + + AL.initSourcePanner(src); + AL.scheduleSourceAudio(src); + break; + case 4106: + if (!Number.isFinite(value) || value < 0.0) { + AL.currentCtx.err = 40963; + return; + } + src.gain.gain.value = value; + break; + case 0x100d /* AL_MIN_GAIN */: + if ( + !Number.isFinite(value) || + value < 0.0 || + value > Math.min(src.maxGain, 1.0) + ) { + AL.currentCtx.err = 40963; + return; + } + src.minGain = value; + break; + case 0x100e /* AL_MAX_GAIN */: + if ( + !Number.isFinite(value) || + value < Math.max(0.0, src.minGain) || + value > 1.0 + ) { + AL.currentCtx.err = 40963; + return; + } + src.maxGain = value; + break; + case 0x1020 /* AL_REFERENCE_DISTANCE */: + if (!Number.isFinite(value) || value < 0.0) { + AL.currentCtx.err = 40963; + return; + } + src.refDistance = value; + if (src.panner) { + src.panner.refDistance = value; + } + break; + case 0x1021 /* AL_ROLLOFF_FACTOR */: + if (!Number.isFinite(value) || value < 0.0) { + AL.currentCtx.err = 40963; + return; + } + src.rolloffFactor = value; + if (src.panner) { + src.panner.rolloffFactor = value; + } + break; + case 0x1022 /* AL_CONE_OUTER_GAIN */: + if (!Number.isFinite(value) || value < 0.0 || value > 1.0) { + AL.currentCtx.err = 40963; + return; + } + src.coneOuterGain = value; + if (src.panner) { + src.panner.coneOuterGain = value; + } + break; + case 0x1023 /* AL_MAX_DISTANCE */: + if (!Number.isFinite(value) || value < 0.0) { + AL.currentCtx.err = 40963; + return; + } + src.maxDistance = value; + if (src.panner) { + src.panner.maxDistance = value; + } + break; + case 0x1024 /* AL_SEC_OFFSET */: + if (value < 0.0 || value > AL.sourceDuration(src)) { + AL.currentCtx.err = 40963; + return; + } + + AL.sourceSeek(src, value); + break; + case 0x1025 /* AL_SAMPLE_OFFSET */: + var srcLen = AL.sourceDuration(src); + if (srcLen > 0.0) { + var frequency; + for (var bufId in src.bufQueue) { + if (bufId) { + frequency = src.bufQueue[bufId].frequency; + break; + } + } + value /= frequency; + } + if (value < 0.0 || value > srcLen) { + AL.currentCtx.err = 40963; + return; + } + + AL.sourceSeek(src, value); + break; + case 0x1026 /* AL_BYTE_OFFSET */: + var srcLen = AL.sourceDuration(src); + if (srcLen > 0.0) { + var bytesPerSec; + for (var bufId in src.bufQueue) { + if (bufId) { + var buf = src.bufQueue[bufId]; + bytesPerSec = + buf.frequency * + buf.bytesPerSample * + buf.channels; + break; + } + } + value /= bytesPerSec; + } + if (value < 0.0 || value > srcLen) { + AL.currentCtx.err = 40963; + return; + } + + AL.sourceSeek(src, value); + break; + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + if ( + value !== 0 && + value !== 1 && + value !== 2 /* AL_AUTO_SOFT */ + ) { + AL.currentCtx.err = 40963; + return; + } + + src.spatialize = value; + AL.initSourcePanner(src); + break; + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200a /* AL_SAMPLE_LENGTH_SOFT */: + case 0x200b /* AL_SEC_LENGTH_SOFT */: + AL.currentCtx.err = 40964; + break; + case 53248: + switch (value) { + case 0: + case 0xd001 /* AL_INVERSE_DISTANCE */: + case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: + case 0xd003 /* AL_LINEAR_DISTANCE */: + case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: + case 0xd005 /* AL_EXPONENT_DISTANCE */: + case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: + src.distanceModel = value; + if (AL.currentCtx.sourceDistanceModel) { + AL.updateContextGlobal(AL.currentCtx); + } + break; + default: + AL.currentCtx.err = 40963; + return; + } + break; + default: + AL.currentCtx.err = 40962; return; - } } - wakeUp(ProcInfo.pid); - }); - } + }, + captures: {}, + sharedCaptureAudioCtx: null, + requireValidCaptureDevice: (deviceId, funcname) => { + if (deviceId === 0) { + AL.alcErr = 40961; + return null; + } + var c = AL.captures[deviceId]; + if (!c) { + AL.alcErr = 40961; + return null; + } + var err = c.mediaStreamError; + if (err) { + AL.alcErr = 40961; + return null; + } + return c; + }, + }; + var _alBuffer3f = (bufferId, param, value0, value1, value2) => { + AL.setBufferParam('alBuffer3f', bufferId, param, null); + }; + _alBuffer3f.sig = 'viifff'; - function _js_process_status(pid, exitCodePtr) { - if (!PHPWASM.child_proc_by_pid[pid]) { - return -1; + var _alBuffer3i = (bufferId, param, value0, value1, value2) => { + AL.setBufferParam('alBuffer3i', bufferId, param, null); + }; + _alBuffer3i.sig = 'viiiii'; + + var _alBufferData = (bufferId, format, pData, size, freq) => { + if (!AL.currentCtx) { + return; } - if (PHPWASM.child_proc_by_pid[pid].exited) { - HEAPU32[exitCodePtr >> 2] = PHPWASM.child_proc_by_pid[pid].exitCode; - return 1; + var buf = AL.buffers[bufferId]; + if (!buf) { + AL.currentCtx.err = 40963; + return; + } + if (freq <= 0) { + AL.currentCtx.err = 40963; + return; } - return 0; - } - function _js_waitpid(pid, exitCodePtr) { - if (!PHPWASM.child_proc_by_pid[pid]) { - return -1; + var audioBuf = null; + try { + switch (format) { + case 0x1100 /* AL_FORMAT_MONO8 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer( + 1, + size, + freq + ); + var channel0 = audioBuf.getChannelData(0); + for (var i = 0; i < size; ++i) { + channel0[i] = + HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; + } + } + buf.bytesPerSample = 1; + buf.channels = 1; + buf.length = size; + break; + case 0x1101 /* AL_FORMAT_MONO16 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer( + 1, + size >> 1, + freq + ); + var channel0 = audioBuf.getChannelData(0); + pData >>= 1; + for (var i = 0; i < size >> 1; ++i) { + channel0[i] = + HEAP16[pData++] * + 0.000030517578125 /* 1/32768 */; + } + } + buf.bytesPerSample = 2; + buf.channels = 1; + buf.length = size >> 1; + break; + case 0x1102 /* AL_FORMAT_STEREO8 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer( + 2, + size >> 1, + freq + ); + var channel0 = audioBuf.getChannelData(0); + var channel1 = audioBuf.getChannelData(1); + for (var i = 0; i < size >> 1; ++i) { + channel0[i] = + HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; + channel1[i] = + HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; + } + } + buf.bytesPerSample = 1; + buf.channels = 2; + buf.length = size >> 1; + break; + case 0x1103 /* AL_FORMAT_STEREO16 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer( + 2, + size >> 2, + freq + ); + var channel0 = audioBuf.getChannelData(0); + var channel1 = audioBuf.getChannelData(1); + pData >>= 1; + for (var i = 0; i < size >> 2; ++i) { + channel0[i] = + HEAP16[pData++] * + 0.000030517578125 /* 1/32768 */; + channel1[i] = + HEAP16[pData++] * + 0.000030517578125 /* 1/32768 */; + } + } + buf.bytesPerSample = 2; + buf.channels = 2; + buf.length = size >> 2; + break; + case 0x10010 /* AL_FORMAT_MONO_FLOAT32 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer( + 1, + size >> 2, + freq + ); + var channel0 = audioBuf.getChannelData(0); + pData >>= 2; + for (var i = 0; i < size >> 2; ++i) { + channel0[i] = HEAPF32[pData++]; + } + } + buf.bytesPerSample = 4; + buf.channels = 1; + buf.length = size >> 2; + break; + case 0x10011 /* AL_FORMAT_STEREO_FLOAT32 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer( + 2, + size >> 3, + freq + ); + var channel0 = audioBuf.getChannelData(0); + var channel1 = audioBuf.getChannelData(1); + pData >>= 2; + for (var i = 0; i < size >> 3; ++i) { + channel0[i] = HEAPF32[pData++]; + channel1[i] = HEAPF32[pData++]; + } + } + buf.bytesPerSample = 4; + buf.channels = 2; + buf.length = size >> 3; + break; + default: + AL.currentCtx.err = 40963; + return; + } + buf.frequency = freq; + buf.audioBuf = audioBuf; + } catch (e) { + AL.currentCtx.err = 40963; + return; } - return Asyncify.handleSleep((wakeUp) => { - const poll = function () { - if (PHPWASM.child_proc_by_pid[pid]?.exited) { - HEAPU32[exitCodePtr >> 2] = - PHPWASM.child_proc_by_pid[pid].exitCode; - wakeUp(pid); - } else { - setTimeout(poll, 50); - } - }; - poll(); - }); - } + }; + _alBufferData.sig = 'viipii'; - var arraySum = (array, index) => { - var sum = 0; - for (var i = 0; i <= index; sum += array[i++]) {} - return sum; + var _alBufferf = (bufferId, param, value) => { + AL.setBufferParam('alBufferf', bufferId, param, null); }; + _alBufferf.sig = 'viif'; - var MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var _alBufferfv = (bufferId, param, pValues) => { + if (!AL.currentCtx) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } - var MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + AL.setBufferParam('alBufferfv', bufferId, param, null); + }; + _alBufferfv.sig = 'viip'; - var addDays = (date, days) => { - var newDate = new Date(date.getTime()); - while (days > 0) { - var leap = isLeapYear(newDate.getFullYear()); - var currentMonth = newDate.getMonth(); - var daysInCurrentMonth = ( - leap ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR - )[currentMonth]; - if (days > daysInCurrentMonth - newDate.getDate()) { - // we spill over to next month - days -= daysInCurrentMonth - newDate.getDate() + 1; - newDate.setDate(1); - if (currentMonth < 11) { - newDate.setMonth(currentMonth + 1); - } else { - newDate.setMonth(0); - newDate.setFullYear(newDate.getFullYear() + 1); - } - } else { - // we stay in current month - newDate.setDate(newDate.getDate() + days); - return newDate; - } + var _alBufferi = (bufferId, param, value) => { + AL.setBufferParam('alBufferi', bufferId, param, null); + }; + _alBufferi.sig = 'viii'; + + var _alBufferiv = (bufferId, param, pValues) => { + if (!AL.currentCtx) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + AL.paramArray[0] = HEAP32[pValues >> 2]; + AL.paramArray[1] = HEAP32[(pValues + 4) >> 2]; + AL.setBufferParam('alBufferiv', bufferId, param, AL.paramArray); + break; + default: + AL.setBufferParam('alBufferiv', bufferId, param, null); + break; } - return newDate; }; + _alBufferiv.sig = 'viip'; - var _strptime = (buf, format, tm) => { - // char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tm); - // http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html - var pattern = UTF8ToString(format); - // escape special characters - // TODO: not sure we really need to escape all of these in JS regexps - var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.'; - for (var i = 0, ii = SPECIAL_CHARS.length; i < ii; ++i) { - pattern = pattern.replace( - new RegExp('\\' + SPECIAL_CHARS[i], 'g'), - '\\' + SPECIAL_CHARS[i] - ); + var _alDeleteBuffers = (count, pBufferIds) => { + if (!AL.currentCtx) { + return; } - // reduce number of matchers - var EQUIVALENT_MATCHERS = { - A: '%a', - B: '%b', - c: '%a %b %d %H:%M:%S %Y', - D: '%m\\/%d\\/%y', - e: '%d', - F: '%Y-%m-%d', - h: '%b', - R: '%H\\:%M', - r: '%I\\:%M\\:%S\\s%p', - T: '%H\\:%M\\:%S', - x: '%m\\/%d\\/(?:%y|%Y)', - X: '%H\\:%M\\:%S', - }; - // TODO: take care of locale - var DATE_PATTERNS = { - /* weekday name */ a: '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)', - /* month name */ b: '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)', - /* century */ C: '\\d\\d', - /* day of month */ d: '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31', - /* hour (24hr) */ H: '\\d(?!\\d)|[0,1]\\d|20|21|22|23', - /* hour (12hr) */ I: '\\d(?!\\d)|0\\d|10|11|12', - /* day of year */ j: '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d', - /* month */ m: '0[1-9]|[1-9](?!\\d)|10|11|12', - /* minutes */ M: '0\\d|\\d(?!\\d)|[1-5]\\d', - /* whitespace */ n: ' ', - /* AM/PM */ p: 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.', - /* seconds */ S: '0\\d|\\d(?!\\d)|[1-5]\\d|60', - /* week number */ U: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', - /* week number */ W: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', - /* weekday number */ w: '[0-6]', - /* 2-digit year */ y: '\\d\\d', - /* 4-digit year */ Y: '\\d\\d\\d\\d', - /* whitespace */ t: ' ', - /* time zone */ z: 'Z|(?:[\\+\\-]\\d\\d:?(?:\\d\\d)?)', - }; - var MONTH_NUMBERS = { - JAN: 0, - FEB: 1, - MAR: 2, - APR: 3, - MAY: 4, - JUN: 5, - JUL: 6, - AUG: 7, - SEP: 8, - OCT: 9, - NOV: 10, - DEC: 11, - }; - var DAY_NUMBERS_SUN_FIRST = { - SUN: 0, - MON: 1, - TUE: 2, - WED: 3, - THU: 4, - FRI: 5, - SAT: 6, - }; - var DAY_NUMBERS_MON_FIRST = { - MON: 0, - TUE: 1, - WED: 2, - THU: 3, - FRI: 4, - SAT: 5, - SUN: 6, - }; - var capture = []; - var pattern_out = pattern - .replace(/%(.)/g, (m, c) => EQUIVALENT_MATCHERS[c] || m) - .replace(/%(.)/g, (_, c) => { - let pat = DATE_PATTERNS[c]; - if (pat) { - capture.push(c); - return `(${pat})`; - } else { - return c; - } - }) - .replace( - // any number of space or tab characters match zero or more spaces - /\s+/g, - '\\s*' - ); - var matches = new RegExp('^' + pattern_out, 'i').exec( - UTF8ToString(buf) - ); - function initDate() { - function fixup(value, min, max) { - return typeof value != 'number' || isNaN(value) - ? min - : value >= min - ? value <= max - ? value - : max - : min; + + for (var i = 0; i < count; ++i) { + var bufId = HEAP32[(pBufferIds + i * 4) >> 2]; + /// Deleting the zero buffer is a legal NOP, so ignore it + if (bufId === 0) { + continue; } - return { - year: fixup(HEAP32[(tm + 20) >> 2] + 1900, 1970, 9999), - month: fixup(HEAP32[(tm + 16) >> 2], 0, 11), - day: fixup(HEAP32[(tm + 12) >> 2], 1, 31), - hour: fixup(HEAP32[(tm + 8) >> 2], 0, 23), - min: fixup(HEAP32[(tm + 4) >> 2], 0, 59), - sec: fixup(HEAP32[tm >> 2], 0, 59), - gmtoff: 0, - }; - } - if (matches) { - var date = initDate(); - var value; - var getMatch = (symbol) => { - var pos = capture.indexOf(symbol); - // check if symbol appears in regexp - if (pos >= 0) { - // return matched value or null (falsy!) for non-matches - return matches[pos + 1]; - } + + // Make sure the buffer index is valid. + if (!AL.buffers[bufId]) { + AL.currentCtx.err = 40961; return; - }; - // seconds - if ((value = getMatch('S'))) { - date.sec = Number(value); } - // minutes - if ((value = getMatch('M'))) { - date.min = Number(value); - } - // hours - if ((value = getMatch('H'))) { - // 24h clock - date.hour = Number(value); - } else if ((value = getMatch('I'))) { - // AM/PM clock - var hour = Number(value); - if ((value = getMatch('p'))) { - hour += value.toUpperCase()[0] === 'P' ? 12 : 0; - } - date.hour = hour; + + // Make sure the buffer is no longer in use. + if (AL.buffers[bufId].refCount) { + AL.currentCtx.err = 40964; + return; } - // year - if ((value = getMatch('Y'))) { - // parse from four-digit year - date.year = Number(value); - } else if ((value = getMatch('y'))) { - // parse from two-digit year... - var year = Number(value); - if ((value = getMatch('C'))) { - // ...and century - year += Number(value) * 100; - } else { - // ...and rule-of-thumb - year += year < 69 ? 2e3 : 1900; - } - date.year = year; + } + + for (var i = 0; i < count; ++i) { + var bufId = HEAP32[(pBufferIds + i * 4) >> 2]; + if (bufId === 0) { + continue; } - // month - if ((value = getMatch('m'))) { - // parse from month number - date.month = Number(value) - 1; - } else if ((value = getMatch('b'))) { - // parse from month name - date.month = - MONTH_NUMBERS[value.substring(0, 3).toUpperCase()] || 0; + + AL.deviceRefCounts[AL.buffers[bufId].deviceId]--; + delete AL.buffers[bufId]; + AL.freeIds.push(bufId); + } + }; + _alDeleteBuffers.sig = 'vip'; + + var _alSourcei = (sourceId, param, value) => { + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200a /* AL_SAMPLE_LENGTH_SOFT */: + case 53248: + AL.setSourceParam('alSourcei', sourceId, param, value); + break; + default: + AL.setSourceParam('alSourcei', sourceId, param, null); + break; + } + }; + _alSourcei.sig = 'viii'; + + var _alDeleteSources = (count, pSourceIds) => { + if (!AL.currentCtx) { + return; + } + + for (var i = 0; i < count; ++i) { + var srcId = HEAP32[(pSourceIds + i * 4) >> 2]; + if (!AL.currentCtx.sources[srcId]) { + AL.currentCtx.err = 40961; + return; } - // day - if ((value = getMatch('d'))) { - // get day of month directly - date.day = Number(value); - } else if ((value = getMatch('j'))) { - // get day of month from day of year ... - var day = Number(value); - var leapYear = isLeapYear(date.year); - for (var month = 0; month < 12; ++month) { - var daysUntilMonth = arraySum( - leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, - month - 1 - ); - if ( - day <= - daysUntilMonth + - (leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[ - month - ] - ) { - date.day = day - daysUntilMonth; - } - } - } else if ((value = getMatch('a'))) { - // get day of month from weekday ... - var weekDay = value.substring(0, 3).toUpperCase(); - if ((value = getMatch('U'))) { - // ... and week number (Sunday being first day of week) - // Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. - // All days in a new year preceding the first Sunday are considered to be in week 0. - var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay]; - var weekNumber = Number(value); - // January 1st + } + + for (var i = 0; i < count; ++i) { + var srcId = HEAP32[(pSourceIds + i * 4) >> 2]; + AL.setSourceState(AL.currentCtx.sources[srcId], 4116); + _alSourcei(srcId, 0x1009 /* AL_BUFFER */, 0); + delete AL.currentCtx.sources[srcId]; + AL.freeIds.push(srcId); + } + }; + _alDeleteSources.sig = 'vip'; + + var _alDisable = (param) => { + if (!AL.currentCtx) { + return; + } + switch (param) { + case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: + AL.currentCtx.sourceDistanceModel = false; + AL.updateContextGlobal(AL.currentCtx); + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alDisable.sig = 'vi'; + + var _alDistanceModel = (model) => { + AL.setGlobalParam('alDistanceModel', 53248, model); + }; + _alDistanceModel.sig = 'vi'; + + var _alDopplerFactor = (value) => { + AL.setGlobalParam('alDopplerFactor', 49152, value); + }; + _alDopplerFactor.sig = 'vf'; + + var _alDopplerVelocity = (value) => { + warnOnce( + 'alDopplerVelocity() is deprecated, and only kept for compatibility with OpenAL 1.0. Use alSpeedOfSound() instead.' + ); + if (!AL.currentCtx) { + return; + } + if (value <= 0) { + // Negative or zero values are disallowed + AL.currentCtx.err = 40963; + return; + } + }; + _alDopplerVelocity.sig = 'vf'; + + var _alEnable = (param) => { + if (!AL.currentCtx) { + return; + } + switch (param) { + case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: + AL.currentCtx.sourceDistanceModel = true; + AL.updateContextGlobal(AL.currentCtx); + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alEnable.sig = 'vi'; + + var _alGenBuffers = (count, pBufferIds) => { + if (!AL.currentCtx) { + return; + } + + for (var i = 0; i < count; ++i) { + var buf = { + deviceId: AL.currentCtx.deviceId, + id: AL.newId(), + refCount: 0, + audioBuf: null, + frequency: 0, + bytesPerSample: 2, + channels: 1, + length: 0, + }; + AL.deviceRefCounts[buf.deviceId]++; + AL.buffers[buf.id] = buf; + HEAP32[(pBufferIds + i * 4) >> 2] = buf.id; + } + }; + _alGenBuffers.sig = 'vip'; + + var _alGenSources = (count, pSourceIds) => { + if (!AL.currentCtx) { + return; + } + for (var i = 0; i < count; ++i) { + var gain = AL.currentCtx.audioCtx.createGain(); + gain.connect(AL.currentCtx.gain); + var src = { + context: AL.currentCtx, + id: AL.newId(), + type: 0x1030 /* AL_UNDETERMINED */, + state: 4113, + bufQueue: [AL.buffers[0]], + audioQueue: [], + looping: false, + pitch: 1.0, + dopplerShift: 1.0, + gain, + minGain: 0.0, + maxGain: 1.0, + panner: null, + bufsProcessed: 0, + bufStartTime: Number.NEGATIVE_INFINITY, + bufOffset: 0.0, + relative: false, + refDistance: 1.0, + maxDistance: 3.40282e38 /* FLT_MAX */, + rolloffFactor: 1.0, + position: [0.0, 0.0, 0.0], + velocity: [0.0, 0.0, 0.0], + direction: [0.0, 0.0, 0.0], + coneOuterGain: 0.0, + coneInnerAngle: 360.0, + coneOuterAngle: 360.0, + distanceModel: 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */, + spatialize: 2 /* AL_AUTO_SOFT */, + + get playbackRate() { + return this.pitch * this.dopplerShift; + }, + }; + AL.currentCtx.sources[src.id] = src; + HEAP32[(pSourceIds + i * 4) >> 2] = src.id; + } + }; + _alGenSources.sig = 'vip'; + + var _alGetBoolean = (param) => { + var val = AL.getGlobalParam('alGetBoolean', param); + if (val === null) { + return 0; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + return val !== 0 ? 1 : 0; + default: + AL.currentCtx.err = 40962; + return 0; + } + }; + _alGetBoolean.sig = 'ii'; + + var _alGetBooleanv = (param, pValues) => { + var val = AL.getGlobalParam('alGetBooleanv', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + HEAP8[pValues] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetBooleanv.sig = 'vip'; + + var _alGetBuffer3f = (bufferId, param, pValue0, pValue1, pValue2) => { + var val = AL.getBufferParam('alGetBuffer3f', bufferId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.err = 40962; + }; + _alGetBuffer3f.sig = 'viippp'; + + var _alGetBuffer3i = (bufferId, param, pValue0, pValue1, pValue2) => { + var val = AL.getBufferParam('alGetBuffer3i', bufferId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.err = 40962; + }; + _alGetBuffer3i.sig = 'viippp'; + + var _alGetBufferf = (bufferId, param, pValue) => { + var val = AL.getBufferParam('alGetBufferf', bufferId, param); + if (val === null) { + return; + } + if (!pValue) { + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.err = 40962; + }; + _alGetBufferf.sig = 'viip'; + + var _alGetBufferfv = (bufferId, param, pValues) => { + var val = AL.getBufferParam('alGetBufferfv', bufferId, param); + if (val === null) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.err = 40962; + }; + _alGetBufferfv.sig = 'viip'; + + var _alGetBufferi = (bufferId, param, pValue) => { + var val = AL.getBufferParam('alGetBufferi', bufferId, param); + if (val === null) { + return; + } + if (!pValue) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x2001 /* AL_FREQUENCY */: + case 0x2002 /* AL_BITS */: + case 0x2003 /* AL_CHANNELS */: + case 0x2004 /* AL_SIZE */: + HEAP32[pValue >> 2] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetBufferi.sig = 'viip'; + + var _alGetBufferiv = (bufferId, param, pValues) => { + var val = AL.getBufferParam('alGetBufferiv', bufferId, param); + if (val === null) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x2001 /* AL_FREQUENCY */: + case 0x2002 /* AL_BITS */: + case 0x2003 /* AL_CHANNELS */: + case 0x2004 /* AL_SIZE */: + HEAP32[pValues >> 2] = val; + break; + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + HEAP32[pValues >> 2] = val[0]; + HEAP32[(pValues + 4) >> 2] = val[1]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetBufferiv.sig = 'viip'; + + var _alGetDouble = (param) => { + var val = AL.getGlobalParam('alGetDouble', param); + if (val === null) { + return 0.0; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + return val; + default: + AL.currentCtx.err = 40962; + return 0.0; + } + }; + _alGetDouble.sig = 'di'; + + var _alGetDoublev = (param, pValues) => { + var val = AL.getGlobalParam('alGetDoublev', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + HEAPF64[pValues >> 3] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetDoublev.sig = 'vip'; + + var _alGetEnumValue = (pEnumName) => { + if (!AL.currentCtx) { + return 0; + } + + if (!pEnumName) { + AL.currentCtx.err = 40963; + return 0; + } + var name = UTF8ToString(pEnumName); + + switch (name) { + // Spec doesn't clearly state that alGetEnumValue() is required to + // support _only_ extension tokens. + // We should probably follow OpenAL-Soft's example and support all + // of the names we know. + // See http://repo.or.cz/openal-soft.git/blob/HEAD:/Alc/ALc.c + case 'AL_BITS': + return 0x2002; + case 'AL_BUFFER': + return 0x1009; + case 'AL_BUFFERS_PROCESSED': + return 0x1016; + case 'AL_BUFFERS_QUEUED': + return 0x1015; + case 'AL_BYTE_OFFSET': + return 0x1026; + case 'AL_CHANNELS': + return 0x2003; + case 'AL_CONE_INNER_ANGLE': + return 0x1001; + case 'AL_CONE_OUTER_ANGLE': + return 0x1002; + case 'AL_CONE_OUTER_GAIN': + return 0x1022; + case 'AL_DIRECTION': + return 0x1005; + case 'AL_DISTANCE_MODEL': + return 0xd000; + case 'AL_DOPPLER_FACTOR': + return 0xc000; + case 'AL_DOPPLER_VELOCITY': + return 0xc001; + case 'AL_EXPONENT_DISTANCE': + return 0xd005; + case 'AL_EXPONENT_DISTANCE_CLAMPED': + return 0xd006; + case 'AL_EXTENSIONS': + return 0xb004; + case 'AL_FORMAT_MONO16': + return 0x1101; + case 'AL_FORMAT_MONO8': + return 0x1100; + case 'AL_FORMAT_STEREO16': + return 0x1103; + case 'AL_FORMAT_STEREO8': + return 0x1102; + case 'AL_FREQUENCY': + return 0x2001; + case 'AL_GAIN': + return 0x100a; + case 'AL_INITIAL': + return 0x1011; + case 'AL_INVALID': + return -1; + case 'AL_ILLEGAL_ENUM': // fallthrough + case 'AL_INVALID_ENUM': + return 0xa002; + case 'AL_INVALID_NAME': + return 0xa001; + case 'AL_ILLEGAL_COMMAND': // fallthrough + case 'AL_INVALID_OPERATION': + return 0xa004; + case 'AL_INVALID_VALUE': + return 0xa003; + case 'AL_INVERSE_DISTANCE': + return 0xd001; + case 'AL_INVERSE_DISTANCE_CLAMPED': + return 0xd002; + case 'AL_LINEAR_DISTANCE': + return 0xd003; + case 'AL_LINEAR_DISTANCE_CLAMPED': + return 0xd004; + case 'AL_LOOPING': + return 0x1007; + case 'AL_MAX_DISTANCE': + return 0x1023; + case 'AL_MAX_GAIN': + return 0x100e; + case 'AL_MIN_GAIN': + return 0x100d; + case 'AL_NONE': + return 0; + case 'AL_NO_ERROR': + return 0; + case 'AL_ORIENTATION': + return 0x100f; + case 'AL_OUT_OF_MEMORY': + return 0xa005; + case 'AL_PAUSED': + return 0x1013; + case 'AL_PENDING': + return 0x2011; + case 'AL_PITCH': + return 0x1003; + case 'AL_PLAYING': + return 0x1012; + case 'AL_POSITION': + return 0x1004; + case 'AL_PROCESSED': + return 0x2012; + case 'AL_REFERENCE_DISTANCE': + return 0x1020; + case 'AL_RENDERER': + return 0xb003; + case 'AL_ROLLOFF_FACTOR': + return 0x1021; + case 'AL_SAMPLE_OFFSET': + return 0x1025; + case 'AL_SEC_OFFSET': + return 0x1024; + case 'AL_SIZE': + return 0x2004; + case 'AL_SOURCE_RELATIVE': + return 0x202; + case 'AL_SOURCE_STATE': + return 0x1010; + case 'AL_SOURCE_TYPE': + return 0x1027; + case 'AL_SPEED_OF_SOUND': + return 0xc003; + case 'AL_STATIC': + return 0x1028; + case 'AL_STOPPED': + return 0x1014; + case 'AL_STREAMING': + return 0x1029; + case 'AL_UNDETERMINED': + return 0x1030; + case 'AL_UNUSED': + return 0x2010; + case 'AL_VELOCITY': + return 0x1006; + case 'AL_VENDOR': + return 0xb001; + case 'AL_VERSION': + return 0xb002; + + /* Extensions */ + case 'AL_AUTO_SOFT': + return 0x0002; + case 'AL_SOURCE_DISTANCE_MODEL': + return 0x200; + case 'AL_SOURCE_SPATIALIZE_SOFT': + return 0x1214; + case 'AL_LOOP_POINTS_SOFT': + return 0x2015; + case 'AL_BYTE_LENGTH_SOFT': + return 0x2009; + case 'AL_SAMPLE_LENGTH_SOFT': + return 0x200a; + case 'AL_SEC_LENGTH_SOFT': + return 0x200b; + case 'AL_FORMAT_MONO_FLOAT32': + return 0x10010; + case 'AL_FORMAT_STEREO_FLOAT32': + return 0x10011; + + default: + AL.currentCtx.err = 40963; + return 0; + } + }; + _alGetEnumValue.sig = 'ip'; + + var _alGetError = () => { + if (!AL.currentCtx) { + return 40964; + } + // Reset error on get. + var err = AL.currentCtx.err; + AL.currentCtx.err = 0; + return err; + }; + _alGetError.sig = 'i'; + + var _alGetFloat = (param) => { + var val = AL.getGlobalParam('alGetFloat', param); + if (val === null) { + return 0.0; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + return val; + default: + return 0.0; + } + }; + _alGetFloat.sig = 'fi'; + + var _alGetFloatv = (param, pValues) => { + var val = AL.getGlobalParam('alGetFloatv', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + HEAPF32[pValues >> 2] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetFloatv.sig = 'vip'; + + var _alGetInteger = (param) => { + var val = AL.getGlobalParam('alGetInteger', param); + if (val === null) { + return 0; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + return val; + default: + AL.currentCtx.err = 40962; + return 0; + } + }; + _alGetInteger.sig = 'ii'; + + var _alGetIntegerv = (param, pValues) => { + var val = AL.getGlobalParam('alGetIntegerv', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case 49152: + case 49155: + case 53248: + HEAP32[pValues >> 2] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetIntegerv.sig = 'vip'; + + var _alGetListener3f = (param, pValue0, pValue1, pValue2) => { + var val = AL.getListenerParam('alGetListener3f', param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4102: + HEAPF32[pValue0 >> 2] = val[0]; + HEAPF32[pValue1 >> 2] = val[1]; + HEAPF32[pValue2 >> 2] = val[2]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetListener3f.sig = 'vippp'; + + var _alGetListener3i = (param, pValue0, pValue1, pValue2) => { + var val = AL.getListenerParam('alGetListener3i', param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4102: + HEAP32[pValue0 >> 2] = val[0]; + HEAP32[pValue1 >> 2] = val[1]; + HEAP32[pValue2 >> 2] = val[2]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetListener3i.sig = 'vippp'; + + var _alGetListenerf = (param, pValue) => { + var val = AL.getListenerParam('alGetListenerf', param); + if (val === null) { + return; + } + if (!pValue) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4106: + HEAPF32[pValue >> 2] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetListenerf.sig = 'vip'; + + var _alGetListenerfv = (param, pValues) => { + var val = AL.getListenerParam('alGetListenerfv', param); + if (val === null) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4102: + HEAPF32[pValues >> 2] = val[0]; + HEAPF32[(pValues + 4) >> 2] = val[1]; + HEAPF32[(pValues + 8) >> 2] = val[2]; + break; + case 4111: + HEAPF32[pValues >> 2] = val[0]; + HEAPF32[(pValues + 4) >> 2] = val[1]; + HEAPF32[(pValues + 8) >> 2] = val[2]; + HEAPF32[(pValues + 12) >> 2] = val[3]; + HEAPF32[(pValues + 16) >> 2] = val[4]; + HEAPF32[(pValues + 20) >> 2] = val[5]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetListenerfv.sig = 'vip'; + + var _alGetListeneri = (param, pValue) => { + var val = AL.getListenerParam('alGetListeneri', param); + if (val === null) { + return; + } + if (!pValue) { + AL.currentCtx.err = 40963; + return; + } + + AL.currentCtx.err = 40962; + }; + _alGetListeneri.sig = 'vip'; + + var _alGetListeneriv = (param, pValues) => { + var val = AL.getListenerParam('alGetListeneriv', param); + if (val === null) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4102: + HEAP32[pValues >> 2] = val[0]; + HEAP32[(pValues + 4) >> 2] = val[1]; + HEAP32[(pValues + 8) >> 2] = val[2]; + break; + case 4111: + HEAP32[pValues >> 2] = val[0]; + HEAP32[(pValues + 4) >> 2] = val[1]; + HEAP32[(pValues + 8) >> 2] = val[2]; + HEAP32[(pValues + 12) >> 2] = val[3]; + HEAP32[(pValues + 16) >> 2] = val[4]; + HEAP32[(pValues + 20) >> 2] = val[5]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetListeneriv.sig = 'vip'; + + var _alGetSource3f = (sourceId, param, pValue0, pValue1, pValue2) => { + var val = AL.getSourceParam('alGetSource3f', sourceId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4101: + case 4102: + HEAPF32[pValue0 >> 2] = val[0]; + HEAPF32[pValue1 >> 2] = val[1]; + HEAPF32[pValue2 >> 2] = val[2]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetSource3f.sig = 'viippp'; + + var _alGetSource3i = (sourceId, param, pValue0, pValue1, pValue2) => { + var val = AL.getSourceParam('alGetSource3i', sourceId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4101: + case 4102: + HEAP32[pValue0 >> 2] = val[0]; + HEAP32[pValue1 >> 2] = val[1]; + HEAP32[pValue2 >> 2] = val[2]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetSource3i.sig = 'viippp'; + + var _alGetSourcef = (sourceId, param, pValue) => { + var val = AL.getSourceParam('alGetSourcef', sourceId, param); + if (val === null) { + return; + } + if (!pValue) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case 4106: + case 0x100d /* AL_MIN_GAIN */: + case 0x100e /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200b /* AL_SEC_LENGTH_SOFT */: + HEAPF32[pValue >> 2] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetSourcef.sig = 'viip'; + + var _alGetSourcefv = (sourceId, param, pValues) => { + var val = AL.getSourceParam('alGetSourcefv', sourceId, param); + if (val === null) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case 4106: + case 0x100d /* AL_MIN_GAIN */: + case 0x100e /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200b /* AL_SEC_LENGTH_SOFT */: + HEAPF32[pValues >> 2] = val[0]; + break; + case 4100: + case 4101: + case 4102: + HEAPF32[pValues >> 2] = val[0]; + HEAPF32[(pValues + 4) >> 2] = val[1]; + HEAPF32[(pValues + 8) >> 2] = val[2]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetSourcefv.sig = 'viip'; + + var _alGetSourcei = (sourceId, param, pValue) => { + var val = AL.getSourceParam('alGetSourcei', sourceId, param); + if (val === null) { + return; + } + if (!pValue) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1010 /* AL_SOURCE_STATE */: + case 0x1015 /* AL_BUFFERS_QUEUED */: + case 0x1016 /* AL_BUFFERS_PROCESSED */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1027 /* AL_SOURCE_TYPE */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200a /* AL_SAMPLE_LENGTH_SOFT */: + case 53248: + HEAP32[pValue >> 2] = val; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetSourcei.sig = 'viip'; + + var _alGetSourceiv = (sourceId, param, pValues) => { + var val = AL.getSourceParam('alGetSourceiv', sourceId, param); + if (val === null) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1010 /* AL_SOURCE_STATE */: + case 0x1015 /* AL_BUFFERS_QUEUED */: + case 0x1016 /* AL_BUFFERS_PROCESSED */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1027 /* AL_SOURCE_TYPE */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200a /* AL_SAMPLE_LENGTH_SOFT */: + case 53248: + HEAP32[pValues >> 2] = val; + break; + case 4100: + case 4101: + case 4102: + HEAP32[pValues >> 2] = val[0]; + HEAP32[(pValues + 4) >> 2] = val[1]; + HEAP32[(pValues + 8) >> 2] = val[2]; + break; + default: + AL.currentCtx.err = 40962; + return; + } + }; + _alGetSourceiv.sig = 'viip'; + + var stringToNewUTF8 = (str) => { + var size = lengthBytesUTF8(str) + 1; + var ret = _malloc(size); + if (ret) stringToUTF8(str, ret, size); + return ret; + }; + + var _alGetString = (param) => { + if (AL.stringCache[param]) { + return AL.stringCache[param]; + } + + var ret; + switch (param) { + case 0: + ret = 'No Error'; + break; + case 40961: + ret = 'Invalid Name'; + break; + case 40962: + ret = 'Invalid Enum'; + break; + case 40963: + ret = 'Invalid Value'; + break; + case 40964: + ret = 'Invalid Operation'; + break; + case 0xa005 /* AL_OUT_OF_MEMORY */: + ret = 'Out of Memory'; + break; + case 0xb001 /* AL_VENDOR */: + ret = 'Emscripten'; + break; + case 0xb002 /* AL_VERSION */: + ret = '1.1'; + break; + case 0xb003 /* AL_RENDERER */: + ret = 'WebAudio'; + break; + case 0xb004 /* AL_EXTENSIONS */: + ret = Object.keys(AL.AL_EXTENSIONS).join(' '); + break; + default: + if (AL.currentCtx) { + AL.currentCtx.err = 40962; + } else { + } + return 0; + } + + ret = stringToNewUTF8(ret); + AL.stringCache[param] = ret; + return ret; + }; + _alGetString.sig = 'pi'; + + var _alIsBuffer = (bufferId) => { + if (!AL.currentCtx) { + return false; + } + if (bufferId > AL.buffers.length) { + return false; + } + + if (!AL.buffers[bufferId]) { + return false; + } + return true; + }; + _alIsBuffer.sig = 'ii'; + + var _alIsEnabled = (param) => { + if (!AL.currentCtx) { + return 0; + } + switch (param) { + case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: + return AL.currentCtx.sourceDistanceModel ? 0 : 1; + default: + AL.currentCtx.err = 40962; + return 0; + } + }; + _alIsEnabled.sig = 'ii'; + + var _alIsExtensionPresent = (pExtName) => { + var name = UTF8ToString(pExtName); + + return AL.AL_EXTENSIONS[name] ? 1 : 0; + }; + _alIsExtensionPresent.sig = 'ip'; + + var _alIsSource = (sourceId) => { + if (!AL.currentCtx) { + return false; + } + + if (!AL.currentCtx.sources[sourceId]) { + return false; + } + return true; + }; + _alIsSource.sig = 'ii'; + + var _alListener3f = (param, value0, value1, value2) => { + switch (param) { + case 4100: + case 4102: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setListenerParam('alListener3f', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListener3f', param, null); + break; + } + }; + _alListener3f.sig = 'vifff'; + + var _alListener3i = (param, value0, value1, value2) => { + switch (param) { + case 4100: + case 4102: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setListenerParam('alListener3i', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListener3i', param, null); + break; + } + }; + _alListener3i.sig = 'viiii'; + + var _alListenerf = (param, value) => { + switch (param) { + case 4106: + AL.setListenerParam('alListenerf', param, value); + break; + default: + AL.setListenerParam('alListenerf', param, null); + break; + } + }; + _alListenerf.sig = 'vif'; + + var _alListenerfv = (param, pValues) => { + if (!AL.currentCtx) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4102: + AL.paramArray[0] = HEAPF32[pValues >> 2]; + AL.paramArray[1] = HEAPF32[(pValues + 4) >> 2]; + AL.paramArray[2] = HEAPF32[(pValues + 8) >> 2]; + AL.setListenerParam('alListenerfv', param, AL.paramArray); + break; + case 4111: + AL.paramArray[0] = HEAPF32[pValues >> 2]; + AL.paramArray[1] = HEAPF32[(pValues + 4) >> 2]; + AL.paramArray[2] = HEAPF32[(pValues + 8) >> 2]; + AL.paramArray[3] = HEAPF32[(pValues + 12) >> 2]; + AL.paramArray[4] = HEAPF32[(pValues + 16) >> 2]; + AL.paramArray[5] = HEAPF32[(pValues + 20) >> 2]; + AL.setListenerParam('alListenerfv', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListenerfv', param, null); + break; + } + }; + _alListenerfv.sig = 'vip'; + + var _alListeneri = (param, value) => { + AL.setListenerParam('alListeneri', param, null); + }; + _alListeneri.sig = 'vii'; + + var _alListeneriv = (param, pValues) => { + if (!AL.currentCtx) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 4100: + case 4102: + AL.paramArray[0] = HEAP32[pValues >> 2]; + AL.paramArray[1] = HEAP32[(pValues + 4) >> 2]; + AL.paramArray[2] = HEAP32[(pValues + 8) >> 2]; + AL.setListenerParam('alListeneriv', param, AL.paramArray); + break; + case 4111: + AL.paramArray[0] = HEAP32[pValues >> 2]; + AL.paramArray[1] = HEAP32[(pValues + 4) >> 2]; + AL.paramArray[2] = HEAP32[(pValues + 8) >> 2]; + AL.paramArray[3] = HEAP32[(pValues + 12) >> 2]; + AL.paramArray[4] = HEAP32[(pValues + 16) >> 2]; + AL.paramArray[5] = HEAP32[(pValues + 20) >> 2]; + AL.setListenerParam('alListeneriv', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListeneriv', param, null); + break; + } + }; + _alListeneriv.sig = 'vip'; + + var _alSource3f = (sourceId, param, value0, value1, value2) => { + switch (param) { + case 4100: + case 4101: + case 4102: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setSourceParam('alSource3f', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSource3f', sourceId, param, null); + break; + } + }; + _alSource3f.sig = 'viifff'; + + var _alSource3i = (sourceId, param, value0, value1, value2) => { + switch (param) { + case 4100: + case 4101: + case 4102: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setSourceParam('alSource3i', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSource3i', sourceId, param, null); + break; + } + }; + _alSource3i.sig = 'viiiii'; + + var _alSourcePause = (sourceId) => { + if (!AL.currentCtx) { + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; + return; + } + AL.setSourceState(src, 4115); + }; + _alSourcePause.sig = 'vi'; + + var _alSourcePausev = (count, pSourceIds) => { + if (!AL.currentCtx) { + return; + } + if (!pSourceIds) { + AL.currentCtx.err = 40963; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[HEAP32[(pSourceIds + i * 4) >> 2]]) { + AL.currentCtx.err = 40961; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = HEAP32[(pSourceIds + i * 4) >> 2]; + AL.setSourceState(AL.currentCtx.sources[srcId], 4115); + } + }; + _alSourcePausev.sig = 'vip'; + + var _alSourcePlay = (sourceId) => { + if (!AL.currentCtx) { + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; + return; + } + AL.setSourceState(src, 4114); + }; + _alSourcePlay.sig = 'vi'; + + var _alSourcePlayv = (count, pSourceIds) => { + if (!AL.currentCtx) { + return; + } + if (!pSourceIds) { + AL.currentCtx.err = 40963; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[HEAP32[(pSourceIds + i * 4) >> 2]]) { + AL.currentCtx.err = 40961; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = HEAP32[(pSourceIds + i * 4) >> 2]; + AL.setSourceState(AL.currentCtx.sources[srcId], 4114); + } + }; + _alSourcePlayv.sig = 'vip'; + + var _alSourceQueueBuffers = (sourceId, count, pBufferIds) => { + if (!AL.currentCtx) { + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; + return; + } + if (src.type === 4136) { + AL.currentCtx.err = 40964; + return; + } + + if (count === 0) { + return; + } + + // Find the first non-zero buffer in the queue to determine the proper format + var templateBuf = AL.buffers[0]; + for (var buf of src.bufQueue.length) { + if (buf.id !== 0) { + templateBuf = buf; + break; + } + } + + for (var i = 0; i < count; ++i) { + var bufId = HEAP32[(pBufferIds + i * 4) >> 2]; + var buf = AL.buffers[bufId]; + if (!buf) { + AL.currentCtx.err = 40961; + return; + } + + // Check that the added buffer has the correct format. If the template is the zero buffer, any format is valid. + if ( + templateBuf.id !== 0 && + (buf.frequency !== templateBuf.frequency || + buf.bytesPerSample !== templateBuf.bytesPerSample || + buf.channels !== templateBuf.channels) + ) { + AL.currentCtx.err = 40964; + } + } + + // If the only buffer in the queue is the zero buffer, clear the queue before we add anything. + if (src.bufQueue.length === 1 && src.bufQueue[0].id === 0) { + src.bufQueue.length = 0; + } + + src.type = 0x1029 /* AL_STREAMING */; + for (var i = 0; i < count; ++i) { + var bufId = HEAP32[(pBufferIds + i * 4) >> 2]; + var buf = AL.buffers[bufId]; + buf.refCount++; + src.bufQueue.push(buf); + } + + // if the source is looping, cancel the schedule so we can reschedule the loop order + if (src.looping) { + AL.cancelPendingSourceAudio(src); + } + + AL.initSourcePanner(src); + AL.scheduleSourceAudio(src); + }; + _alSourceQueueBuffers.sig = 'viip'; + + var _alSourceRewind = (sourceId) => { + if (!AL.currentCtx) { + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; + return; + } + // Stop the source first to clear the source queue + AL.setSourceState(src, 4116); + // Now set the state of AL_INITIAL according to the specification + AL.setSourceState(src, 4113); + }; + _alSourceRewind.sig = 'vi'; + + var _alSourceRewindv = (count, pSourceIds) => { + if (!AL.currentCtx) { + return; + } + if (!pSourceIds) { + AL.currentCtx.err = 40963; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[HEAP32[(pSourceIds + i * 4) >> 2]]) { + AL.currentCtx.err = 40961; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = HEAP32[(pSourceIds + i * 4) >> 2]; + AL.setSourceState(AL.currentCtx.sources[srcId], 4113); + } + }; + _alSourceRewindv.sig = 'vip'; + + var _alSourceStop = (sourceId) => { + if (!AL.currentCtx) { + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; + return; + } + AL.setSourceState(src, 4116); + }; + _alSourceStop.sig = 'vi'; + + var _alSourceStopv = (count, pSourceIds) => { + if (!AL.currentCtx) { + return; + } + if (!pSourceIds) { + AL.currentCtx.err = 40963; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[HEAP32[(pSourceIds + i * 4) >> 2]]) { + AL.currentCtx.err = 40961; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = HEAP32[(pSourceIds + i * 4) >> 2]; + AL.setSourceState(AL.currentCtx.sources[srcId], 4116); + } + }; + _alSourceStopv.sig = 'vip'; + + var _alSourceUnqueueBuffers = (sourceId, count, pBufferIds) => { + if (!AL.currentCtx) { + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { + AL.currentCtx.err = 40961; + return; + } + if ( + count > + (src.bufQueue.length === 1 && src.bufQueue[0].id === 0 + ? 0 + : src.bufsProcessed) + ) { + AL.currentCtx.err = 40963; + return; + } + + if (count === 0) { + return; + } + + for (var i = 0; i < count; i++) { + var buf = src.bufQueue.shift(); + buf.refCount--; + // Write the buffers index out to the return list. + HEAP32[(pBufferIds + i * 4) >> 2] = buf.id; + src.bufsProcessed--; + } + + /// If the queue is empty, put the zero buffer back in + if (src.bufQueue.length === 0) { + src.bufQueue.push(AL.buffers[0]); + } + + AL.initSourcePanner(src); + AL.scheduleSourceAudio(src); + }; + _alSourceUnqueueBuffers.sig = 'viip'; + + var _alSourcef = (sourceId, param, value) => { + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case 4106: + case 0x100d /* AL_MIN_GAIN */: + case 0x100e /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200b /* AL_SEC_LENGTH_SOFT */: + AL.setSourceParam('alSourcef', sourceId, param, value); + break; + default: + AL.setSourceParam('alSourcef', sourceId, param, null); + break; + } + }; + _alSourcef.sig = 'viif'; + + var _alSourcefv = (sourceId, param, pValues) => { + if (!AL.currentCtx) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case 4106: + case 0x100d /* AL_MIN_GAIN */: + case 0x100e /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200b /* AL_SEC_LENGTH_SOFT */: + var val = HEAPF32[pValues >> 2]; + AL.setSourceParam('alSourcefv', sourceId, param, val); + break; + case 4100: + case 4101: + case 4102: + AL.paramArray[0] = HEAPF32[pValues >> 2]; + AL.paramArray[1] = HEAPF32[(pValues + 4) >> 2]; + AL.paramArray[2] = HEAPF32[(pValues + 8) >> 2]; + AL.setSourceParam('alSourcefv', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSourcefv', sourceId, param, null); + break; + } + }; + _alSourcefv.sig = 'viip'; + + var _alSourceiv = (sourceId, param, pValues) => { + if (!AL.currentCtx) { + return; + } + if (!pValues) { + AL.currentCtx.err = 40963; + return; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200a /* AL_SAMPLE_LENGTH_SOFT */: + case 53248: + var val = HEAP32[pValues >> 2]; + AL.setSourceParam('alSourceiv', sourceId, param, val); + break; + case 4100: + case 4101: + case 4102: + AL.paramArray[0] = HEAP32[pValues >> 2]; + AL.paramArray[1] = HEAP32[(pValues + 4) >> 2]; + AL.paramArray[2] = HEAP32[(pValues + 8) >> 2]; + AL.setSourceParam('alSourceiv', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSourceiv', sourceId, param, null); + break; + } + }; + _alSourceiv.sig = 'viip'; + + var _alSpeedOfSound = (value) => { + AL.setGlobalParam('alSpeedOfSound', 49155, value); + }; + _alSpeedOfSound.sig = 'vf'; + + var _alcCaptureCloseDevice = (deviceId) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureCloseDevice'); + if (!c) return false; + + delete AL.captures[deviceId]; + AL.freeIds.push(deviceId); + + // This clean-up might be unnecessary (paranoid) ? + + // May happen if user hasn't decided to grant or deny input + c.mediaStreamSourceNode?.disconnect(); + c.mergerNode?.disconnect(); + c.splitterNode?.disconnect(); + // May happen if user hasn't decided to grant or deny input + c.scriptProcessorNode?.disconnect(); + if (c.mediaStream) { + // Disabling the microphone of the browser. + // Without this operation, the red dot on the browser tab page will remain. + c.mediaStream.getTracks().forEach((track) => track.stop()); + } + + delete c.buffers; + + c.capturedFrameCount = 0; + c.isCapturing = false; + + return true; + }; + _alcCaptureCloseDevice.sig = 'ip'; + + var listenOnce = (object, event, func) => + object.addEventListener(event, func, { once: true }); + /** @param {Object=} elements */ + var autoResumeAudioContext = (ctx, elements) => { + if (!elements) { + elements = [document, document.getElementById('canvas')]; + } + ['keydown', 'mousedown', 'touchstart'].forEach((event) => { + elements.forEach((element) => { + if (element) { + listenOnce(element, event, () => { + if (ctx.state === 'suspended') ctx.resume(); + }); + } + }); + }); + }; + + var _alcCaptureOpenDevice = ( + pDeviceName, + requestedSampleRate, + format, + bufferFrameCapacity + ) => { + var resolvedDeviceName = AL.CAPTURE_DEVICE_NAME; + + // NULL is a valid device name here (resolves to default); + if (pDeviceName !== 0) { + resolvedDeviceName = UTF8ToString(pDeviceName); + if (resolvedDeviceName !== AL.CAPTURE_DEVICE_NAME) { + // ALC_OUT_OF_MEMORY + // From the programmer's guide, ALC_OUT_OF_MEMORY's meaning is + // overloaded here, to mean: + // 'The specified device is invalid, or can not capture audio.' + // This may be misleading to API users, but well... + AL.alcErr = 0xa005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + } + + // Otherwise it's probably okay (though useless) for bufferFrameCapacity to be zero. + if (bufferFrameCapacity < 0) { + // ALCsizei is signed int + AL.alcErr = 40964; + return 0; + } + + navigator.getUserMedia = + navigator.getUserMedia || + navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + navigator.msGetUserMedia; + var has_getUserMedia = + navigator.getUserMedia || + (navigator.mediaDevices && navigator.mediaDevices.getUserMedia); + + if (!has_getUserMedia) { + // See previously mentioned rationale for ALC_OUT_OF_MEMORY + AL.alcErr = 0xa005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + + var AudioContext = window.AudioContext || window.webkitAudioContext; + + if (!AL.sharedCaptureAudioCtx) { + try { + AL.sharedCaptureAudioCtx = new AudioContext(); + } catch (e) { + // See previously mentioned rationale for ALC_OUT_OF_MEMORY + AL.alcErr = 0xa005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + } + + autoResumeAudioContext(AL.sharedCaptureAudioCtx); + + var outputChannelCount; + + switch (format) { + case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ + case 0x1101: /* AL_FORMAT_MONO16 */ + case 0x1100 /* AL_FORMAT_MONO8 */: + outputChannelCount = 1; + break; + case 0x10011: /* AL_FORMAT_STEREO_FLOAT32 */ + case 0x1103: /* AL_FORMAT_STEREO16 */ + case 0x1102 /* AL_FORMAT_STEREO8 */: + outputChannelCount = 2; + break; + default: + AL.alcErr = 40964; + return 0; + } + + function newF32Array(cap) { + return new Float32Array(cap); + } + function newI16Array(cap) { + return new Int16Array(cap); + } + function newU8Array(cap) { + return new Uint8Array(cap); + } + + var requestedSampleType; + var newSampleArray; + + switch (format) { + case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ + case 0x10011 /* AL_FORMAT_STEREO_FLOAT32 */: + requestedSampleType = 'f32'; + newSampleArray = newF32Array; + break; + case 0x1101: /* AL_FORMAT_MONO16 */ + case 0x1103 /* AL_FORMAT_STEREO16 */: + requestedSampleType = 'i16'; + newSampleArray = newI16Array; + break; + case 0x1100: /* AL_FORMAT_MONO8 */ + case 0x1102 /* AL_FORMAT_STEREO8 */: + requestedSampleType = 'u8'; + newSampleArray = newU8Array; + break; + } + + var buffers = []; + try { + for (var chan = 0; chan < outputChannelCount; ++chan) { + buffers[chan] = newSampleArray(bufferFrameCapacity); + } + } catch (e) { + AL.alcErr = 0xa005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + + // What we'll place into the `AL.captures` array in the end, + // declared here for closures to access it + var newCapture = { + audioCtx: AL.sharedCaptureAudioCtx, + deviceName: resolvedDeviceName, + requestedSampleRate, + requestedSampleType, + outputChannelCount, + inputChannelCount: null, // Not known until the getUserMedia() promise resolves + mediaStreamError: null, // Used by other functions to return early and report an error. + mediaStreamSourceNode: null, + mediaStream: null, + // Either one, or none of the below two, is active. + mergerNode: null, + splitterNode: null, + scriptProcessorNode: null, + isCapturing: false, + buffers, + get bufferFrameCapacity() { + return buffers[0].length; + }, + capturePlayhead: 0, // current write position, in sample frames + captureReadhead: 0, + capturedFrameCount: 0, + }; + + // Preparing for getUserMedia() + + var onError = (mediaStreamError) => { + newCapture.mediaStreamError = mediaStreamError; + }; + var onSuccess = (mediaStream) => { + newCapture.mediaStreamSourceNode = + newCapture.audioCtx.createMediaStreamSource(mediaStream); + newCapture.mediaStream = mediaStream; + + var inputChannelCount = 1; + switch (newCapture.mediaStreamSourceNode.channelCountMode) { + case 'max': + inputChannelCount = outputChannelCount; + break; + case 'clamped-max': + inputChannelCount = Math.min( + outputChannelCount, + newCapture.mediaStreamSourceNode.channelCount + ); + break; + case 'explicit': + inputChannelCount = + newCapture.mediaStreamSourceNode.channelCount; + break; + } + + newCapture.inputChannelCount = inputChannelCount; + + // Have to pick a size from 256, 512, 1024, 2048, 4096, 8192, 16384. + // One can also set it to zero, which leaves the decision up to the impl. + // An extension could allow specifying this value. + var processorFrameCount = 512; + + newCapture.scriptProcessorNode = + newCapture.audioCtx.createScriptProcessor( + processorFrameCount, + inputChannelCount, + outputChannelCount + ); + + if (inputChannelCount > outputChannelCount) { + newCapture.mergerNode = + newCapture.audioCtx.createChannelMerger(inputChannelCount); + newCapture.mediaStreamSourceNode.connect(newCapture.mergerNode); + newCapture.mergerNode.connect(newCapture.scriptProcessorNode); + } else if (inputChannelCount < outputChannelCount) { + newCapture.splitterNode = + newCapture.audioCtx.createChannelSplitter( + outputChannelCount + ); + newCapture.mediaStreamSourceNode.connect( + newCapture.splitterNode + ); + newCapture.splitterNode.connect(newCapture.scriptProcessorNode); + } else { + newCapture.mediaStreamSourceNode.connect( + newCapture.scriptProcessorNode + ); + } + + newCapture.scriptProcessorNode.connect( + newCapture.audioCtx.destination + ); + + newCapture.scriptProcessorNode.onaudioprocess = ( + audioProcessingEvent + ) => { + if (!newCapture.isCapturing) { + return; + } + + var c = newCapture; + var srcBuf = audioProcessingEvent.inputBuffer; + + // Actually just copy srcBuf's channel data into + // c.buffers, optimizing for each case. + switch (format) { + case 0x10010 /* AL_FORMAT_MONO_FLOAT32 */: + var channel0 = srcBuf.getChannelData(0); + for (var i = 0; i < srcBuf.length; ++i) { + var wi = + (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i]; + } + break; + case 0x10011 /* AL_FORMAT_STEREO_FLOAT32 */: + var channel0 = srcBuf.getChannelData(0); + var channel1 = srcBuf.getChannelData(1); + for (var i = 0; i < srcBuf.length; ++i) { + var wi = + (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i]; + c.buffers[1][wi] = channel1[i]; + } + break; + case 0x1101 /* AL_FORMAT_MONO16 */: + var channel0 = srcBuf.getChannelData(0); + for (var i = 0; i < srcBuf.length; ++i) { + var wi = + (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i] * 32767; + } + break; + case 0x1103 /* AL_FORMAT_STEREO16 */: + var channel0 = srcBuf.getChannelData(0); + var channel1 = srcBuf.getChannelData(1); + for (var i = 0; i < srcBuf.length; ++i) { + var wi = + (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i] * 32767; + c.buffers[1][wi] = channel1[i] * 32767; + } + break; + case 0x1100 /* AL_FORMAT_MONO8 */: + var channel0 = srcBuf.getChannelData(0); + for (var i = 0; i < srcBuf.length; ++i) { + var wi = + (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = (channel0[i] + 1.0) * 127; + } + break; + case 0x1102 /* AL_FORMAT_STEREO8 */: + var channel0 = srcBuf.getChannelData(0); + var channel1 = srcBuf.getChannelData(1); + for (var i = 0; i < srcBuf.length; ++i) { + var wi = + (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = (channel0[i] + 1.0) * 127; + c.buffers[1][wi] = (channel1[i] + 1.0) * 127; + } + break; + } + + c.capturePlayhead += srcBuf.length; + c.capturePlayhead %= c.bufferFrameCapacity; + c.capturedFrameCount += srcBuf.length; + c.capturedFrameCount = Math.min( + c.capturedFrameCount, + c.bufferFrameCapacity + ); + }; + }; + + // The latest way to call getUserMedia() + if (navigator.mediaDevices?.getUserMedia) { + navigator.mediaDevices + .getUserMedia({ audio: true }) + .then(onSuccess) + .catch(onError); + } else { + // The usual (now deprecated) way + navigator.getUserMedia({ audio: true }, onSuccess, onError); + } + + var id = AL.newId(); + AL.captures[id] = newCapture; + return id; + }; + _alcCaptureOpenDevice.sig = 'ppiii'; + + var _alcCaptureSamples = (deviceId, pFrames, requestedFrameCount) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureSamples'); + if (!c) return; + + // ALCsizei is actually 32-bit signed int, so could be negative + // Also, spec says : + // Requesting more sample frames than are currently available is + // an error. + + var dstfreq = c.requestedSampleRate; + var srcfreq = c.audioCtx.sampleRate; + + var fratio = srcfreq / dstfreq; + + if ( + requestedFrameCount < 0 || + requestedFrameCount > c.capturedFrameCount / fratio + ) { + AL.alcErr = 40964; + return; + } + + function setF32Sample(i, sample) { + HEAPF32[(pFrames + 4 * i) >> 2] = sample; + } + function setI16Sample(i, sample) { + HEAP16[(pFrames + 2 * i) >> 1] = sample; + } + function setU8Sample(i, sample) { + HEAP8[pFrames + i] = sample; + } + + var setSample; + + switch (c.requestedSampleType) { + case 'f32': + setSample = setF32Sample; + break; + case 'i16': + setSample = setI16Sample; + break; + case 'u8': + setSample = setU8Sample; + break; + default: + return; + } + + // If fratio is an integer we don't need linear resampling, just skip samples + if (Math.floor(fratio) == fratio) { + for ( + var i = 0, frame_i = 0; + frame_i < requestedFrameCount; + ++frame_i + ) { + for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { + setSample(i, c.buffers[chan][c.captureReadhead]); + } + c.captureReadhead = + (fratio + c.captureReadhead) % c.bufferFrameCapacity; + } + } else { + // Perform linear resampling. + + // There is room for improvement - right now we're fine with linear resampling. + // We don't use OfflineAudioContexts for this: See the discussion at + // https://github.com/jpernst/emscripten/issues/2#issuecomment-312729735 + // if you're curious about why. + for ( + var i = 0, frame_i = 0; + frame_i < requestedFrameCount; + ++frame_i + ) { + var lefti = Math.floor(c.captureReadhead); + var righti = Math.ceil(c.captureReadhead); + var d = c.captureReadhead - lefti; + for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { + var lefts = c.buffers[chan][lefti]; + var rights = c.buffers[chan][righti]; + setSample(i, (1 - d) * lefts + d * rights); + } + c.captureReadhead = + (c.captureReadhead + fratio) % c.bufferFrameCapacity; + } + } + + // Spec doesn't say if alcCaptureSamples() must zero the number + // of available captured sample-frames, but not only would it + // be insane not to do, OpenAL-Soft happens to do that as well. + c.capturedFrameCount = 0; + }; + _alcCaptureSamples.sig = 'vppi'; + + var _alcCaptureStart = (deviceId) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStart'); + if (!c) return; + + if (c.isCapturing) { + // NOTE: Spec says (emphasis mine): + // The amount of audio samples available after **restarting** a + // stopped capture device is reset to zero. + // So redundant calls to alcCaptureStart() must have no effect. + return; + } + c.isCapturing = true; + c.capturedFrameCount = 0; + c.capturePlayhead = 0; + }; + _alcCaptureStart.sig = 'vp'; + + var _alcCaptureStop = (deviceId) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStop'); + if (!c) return; + + c.isCapturing = false; + }; + _alcCaptureStop.sig = 'vp'; + + var _alcCloseDevice = (deviceId) => { + if ( + !(deviceId in AL.deviceRefCounts) || + AL.deviceRefCounts[deviceId] > 0 + ) { + return 0; + } + + delete AL.deviceRefCounts[deviceId]; + AL.freeIds.push(deviceId); + return 1; + }; + _alcCloseDevice.sig = 'ip'; + + var _alcCreateContext = (deviceId, pAttrList) => { + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 0xa001; /* ALC_INVALID_DEVICE */ + return 0; + } + + var options = null; + var attrs = []; + var hrtf = null; + pAttrList >>= 2; + if (pAttrList) { + var attr = 0; + var val = 0; + while (true) { + attr = HEAP32[pAttrList++]; + attrs.push(attr); + if (attr === 0) { + break; + } + val = HEAP32[pAttrList++]; + attrs.push(val); + + switch (attr) { + case 0x1007 /* ALC_FREQUENCY */: + if (!options) { + options = {}; + } + + options.sampleRate = val; + break; + case 0x1010 /* ALC_MONO_SOURCES */: // fallthrough + case 0x1011 /* ALC_STEREO_SOURCES */: + // Do nothing; these hints are satisfied by default + break; + case 0x1992 /* ALC_HRTF_SOFT */: + switch (val) { + case 0: + hrtf = false; + break; + case 1: + hrtf = true; + break; + case 2 /* ALC_DONT_CARE_SOFT */: + break; + default: + AL.alcErr = 40964; + return 0; + } + break; + case 0x1996 /* ALC_HRTF_ID_SOFT */: + if (val !== 0) { + AL.alcErr = 40964; + return 0; + } + break; + default: + AL.alcErr = 0xa004; /* ALC_INVALID_VALUE */ + return 0; + } + } + } + + var AudioContext = window.AudioContext || window.webkitAudioContext; + var ac = null; + try { + // Only try to pass options if there are any, for compat with browsers that don't support this + if (options) { + ac = new AudioContext(options); + } else { + ac = new AudioContext(); + } + } catch (e) { + if (e.name === 'NotSupportedError') { + AL.alcErr = 0xa004; /* ALC_INVALID_VALUE */ + } else { + AL.alcErr = 0xa001; /* ALC_INVALID_DEVICE */ + } + + return 0; + } + + autoResumeAudioContext(ac); + + // Old Web Audio API (e.g. Safari 6.0.5) had an inconsistently named createGainNode function. + if (typeof ac.createGain == 'undefined') { + ac.createGain = ac.createGainNode; + } + + var gain = ac.createGain(); + gain.connect(ac.destination); + var ctx = { + deviceId, + id: AL.newId(), + attrs, + audioCtx: ac, + listener: { + position: [0.0, 0.0, 0.0], + velocity: [0.0, 0.0, 0.0], + direction: [0.0, 0.0, 0.0], + up: [0.0, 0.0, 0.0], + }, + sources: [], + interval: setInterval( + () => AL.scheduleContextAudio(ctx), + AL.QUEUE_INTERVAL + ), + gain, + distanceModel: 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */, + speedOfSound: 343.3, + dopplerFactor: 1.0, + sourceDistanceModel: false, + hrtf: hrtf || false, + + _err: 0, + get err() { + return this._err; + }, + set err(val) { + // Errors should not be overwritten by later errors until they are cleared by a query. + if (this._err === 0 || val === 0) { + this._err = val; + } + }, + }; + AL.deviceRefCounts[deviceId]++; + AL.contexts[ctx.id] = ctx; + + if (hrtf !== null) { + // Apply hrtf attrib to all contexts for this device + for (var ctxId in AL.contexts) { + var c = AL.contexts[ctxId]; + if (c.deviceId === deviceId) { + c.hrtf = hrtf; + AL.updateContextGlobal(c); + } + } + } + + return ctx.id; + }; + _alcCreateContext.sig = 'ppp'; + + var _alcDestroyContext = (contextId) => { + var ctx = AL.contexts[contextId]; + if (AL.currentCtx === ctx) { + AL.alcErr = 0xa002 /* ALC_INVALID_CONTEXT */; + return; + } + + // Stop playback, etc + if (AL.contexts[contextId].interval) { + clearInterval(AL.contexts[contextId].interval); + } + AL.deviceRefCounts[ctx.deviceId]--; + delete AL.contexts[contextId]; + AL.freeIds.push(contextId); + }; + _alcDestroyContext.sig = 'vp'; + + var _alcGetContextsDevice = (contextId) => { + if (contextId in AL.contexts) { + return AL.contexts[contextId].deviceId; + } + return 0; + }; + _alcGetContextsDevice.sig = 'pp'; + + var _alcGetCurrentContext = () => { + if (AL.currentCtx !== null) { + return AL.currentCtx.id; + } + return 0; + }; + _alcGetCurrentContext.sig = 'p'; + + var _alcGetEnumValue = (deviceId, pEnumName) => { + // Spec says : + // Using a NULL handle is legal, but only the + // tokens defined by the AL core are guaranteed. + if (deviceId !== 0 && !(deviceId in AL.deviceRefCounts)) { + // ALC_INVALID_DEVICE is not listed as a possible error state for + // this function, sadly. + return 0; + } else if (!pEnumName) { + AL.alcErr = 40964; + return 0; + } + var name = UTF8ToString(pEnumName); + // See alGetEnumValue(), but basically behave the same as OpenAL-Soft + switch (name) { + case 'ALC_NO_ERROR': + return 0; + case 'ALC_INVALID_DEVICE': + return 0xa001; + case 'ALC_INVALID_CONTEXT': + return 0xa002; + case 'ALC_INVALID_ENUM': + return 0xa003; + case 'ALC_INVALID_VALUE': + return 0xa004; + case 'ALC_OUT_OF_MEMORY': + return 0xa005; + case 'ALC_MAJOR_VERSION': + return 0x1000; + case 'ALC_MINOR_VERSION': + return 0x1001; + case 'ALC_ATTRIBUTES_SIZE': + return 0x1002; + case 'ALC_ALL_ATTRIBUTES': + return 0x1003; + case 'ALC_DEFAULT_DEVICE_SPECIFIER': + return 0x1004; + case 'ALC_DEVICE_SPECIFIER': + return 0x1005; + case 'ALC_EXTENSIONS': + return 0x1006; + case 'ALC_FREQUENCY': + return 0x1007; + case 'ALC_REFRESH': + return 0x1008; + case 'ALC_SYNC': + return 0x1009; + case 'ALC_MONO_SOURCES': + return 0x1010; + case 'ALC_STEREO_SOURCES': + return 0x1011; + case 'ALC_CAPTURE_DEVICE_SPECIFIER': + return 0x310; + case 'ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER': + return 0x311; + case 'ALC_CAPTURE_SAMPLES': + return 0x312; + + /* Extensions */ + case 'ALC_HRTF_SOFT': + return 0x1992; + case 'ALC_HRTF_ID_SOFT': + return 0x1996; + case 'ALC_DONT_CARE_SOFT': + return 0x0002; + case 'ALC_HRTF_STATUS_SOFT': + return 0x1993; + case 'ALC_NUM_HRTF_SPECIFIERS_SOFT': + return 0x1994; + case 'ALC_HRTF_SPECIFIER_SOFT': + return 0x1995; + case 'ALC_HRTF_DISABLED_SOFT': + return 0x0000; + case 'ALC_HRTF_ENABLED_SOFT': + return 0x0001; + case 'ALC_HRTF_DENIED_SOFT': + return 0x0002; + case 'ALC_HRTF_REQUIRED_SOFT': + return 0x0003; + case 'ALC_HRTF_HEADPHONES_DETECTED_SOFT': + return 0x0004; + case 'ALC_HRTF_UNSUPPORTED_FORMAT_SOFT': + return 0x0005; + + default: + AL.alcErr = 40964; + return 0; + } + }; + _alcGetEnumValue.sig = 'ipp'; + + var _alcGetError = (deviceId) => { + var err = AL.alcErr; + AL.alcErr = 0; + return err; + }; + _alcGetError.sig = 'ip'; + + var _alcGetIntegerv = (deviceId, param, size, pValues) => { + if (size === 0 || !pValues) { + // Ignore the query, per the spec + return; + } + + switch (param) { + case 0x1000 /* ALC_MAJOR_VERSION */: + HEAP32[pValues >> 2] = 1; + break; + case 0x1001 /* ALC_MINOR_VERSION */: + HEAP32[pValues >> 2] = 1; + break; + case 0x1002 /* ALC_ATTRIBUTES_SIZE */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xa002 /* ALC_INVALID_CONTEXT */; + return; + } + + HEAP32[pValues >> 2] = AL.currentCtx.attrs.length; + break; + case 0x1003 /* ALC_ALL_ATTRIBUTES */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xa002 /* ALC_INVALID_CONTEXT */; + return; + } + + for (var i = 0; i < AL.currentCtx.attrs.length; i++) { + HEAP32[(pValues + i * 4) >> 2] = AL.currentCtx.attrs[i]; + } + break; + case 0x1007 /* ALC_FREQUENCY */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xa002 /* ALC_INVALID_CONTEXT */; + return; + } + + HEAP32[pValues >> 2] = AL.currentCtx.audioCtx.sampleRate; + break; + case 0x1010 /* ALC_MONO_SOURCES */: + case 0x1011 /* ALC_STEREO_SOURCES */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xa002 /* ALC_INVALID_CONTEXT */; + return; + } + + HEAP32[pValues >> 2] = 0x7fffffff; + break; + case 0x1992 /* ALC_HRTF_SOFT */: + case 0x1993 /* ALC_HRTF_STATUS_SOFT */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + + var hrtfStatus = 0; /* ALC_HRTF_DISABLED_SOFT */ + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId === deviceId) { + hrtfStatus = ctx.hrtf + ? 1 /* ALC_HRTF_ENABLED_SOFT */ + : 0 /* ALC_HRTF_DISABLED_SOFT */; + } + } + HEAP32[pValues >> 2] = hrtfStatus; + break; + case 0x1994 /* ALC_NUM_HRTF_SPECIFIERS_SOFT */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + HEAP32[pValues >> 2] = 1; + break; + case 0x20003 /* ALC_MAX_AUXILIARY_SENDS */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xa002 /* ALC_INVALID_CONTEXT */; + return; + } + + HEAP32[pValues >> 2] = 1; + case 0x312 /* ALC_CAPTURE_SAMPLES */: + var c = AL.requireValidCaptureDevice( + deviceId, + 'alcGetIntegerv' + ); + if (!c) { + return; + } + var n = c.capturedFrameCount; + var dstfreq = c.requestedSampleRate; + var srcfreq = c.audioCtx.sampleRate; + var nsamples = Math.floor(n * (dstfreq / srcfreq)); + HEAP32[pValues >> 2] = nsamples; + break; + default: + AL.alcErr = 40963; + return; + } + }; + _alcGetIntegerv.sig = 'vpiip'; + + var _alcGetString = (deviceId, param) => { + if (AL.alcStringCache[param]) { + return AL.alcStringCache[param]; + } + + var ret; + switch (param) { + case 0: + ret = 'No Error'; + break; + case 40961: + ret = 'Invalid Device'; + break; + case 0xa002 /* ALC_INVALID_CONTEXT */: + ret = 'Invalid Context'; + break; + case 40963: + ret = 'Invalid Enum'; + break; + case 40964: + ret = 'Invalid Value'; + break; + case 0xa005 /* ALC_OUT_OF_MEMORY */: + ret = 'Out of Memory'; + break; + case 0x1004 /* ALC_DEFAULT_DEVICE_SPECIFIER */: + if ( + typeof AudioContext != 'undefined' || + typeof webkitAudioContext != 'undefined' + ) { + ret = AL.DEVICE_NAME; + } else { + return 0; + } + break; + case 0x1005 /* ALC_DEVICE_SPECIFIER */: + if ( + typeof AudioContext != 'undefined' || + typeof webkitAudioContext != 'undefined' + ) { + ret = AL.DEVICE_NAME + '\0'; + } else { + ret = '\0'; + } + break; + case 0x311 /* ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER */: + ret = AL.CAPTURE_DEVICE_NAME; + break; + case 0x310 /* ALC_CAPTURE_DEVICE_SPECIFIER */: + if (deviceId === 0) { + ret = AL.CAPTURE_DEVICE_NAME + '\0'; + } else { + var c = AL.requireValidCaptureDevice( + deviceId, + 'alcGetString' + ); + if (!c) { + return 0; + } + ret = c.deviceName; + } + break; + case 0x1006 /* ALC_EXTENSIONS */: + if (!deviceId) { + AL.alcErr = 40961; + return 0; + } + + ret = Object.keys(AL.ALC_EXTENSIONS).join(' '); + break; + default: + AL.alcErr = 40963; + return 0; + } + + ret = stringToNewUTF8(ret); + AL.alcStringCache[param] = ret; + return ret; + }; + _alcGetString.sig = 'ppi'; + + var _alcIsExtensionPresent = (deviceId, pExtName) => { + var name = UTF8ToString(pExtName); + + return AL.ALC_EXTENSIONS[name] ? 1 : 0; + }; + _alcIsExtensionPresent.sig = 'ipp'; + + var _alcMakeContextCurrent = (contextId) => { + if (contextId === 0) { + AL.currentCtx = null; + } else { + AL.currentCtx = AL.contexts[contextId]; + } + return 1; + }; + _alcMakeContextCurrent.sig = 'ip'; + + var _alcOpenDevice = (pDeviceName) => { + if (pDeviceName) { + var name = UTF8ToString(pDeviceName); + if (name !== AL.DEVICE_NAME) { + return 0; + } + } + + if ( + typeof AudioContext != 'undefined' || + typeof webkitAudioContext != 'undefined' + ) { + var deviceId = AL.newId(); + AL.deviceRefCounts[deviceId] = 0; + return deviceId; + } + return 0; + }; + _alcOpenDevice.sig = 'pp'; + + var _alcProcessContext = (contextId) => {}; + _alcProcessContext.sig = 'vp'; + + var _alcSuspendContext = (contextId) => {}; + _alcSuspendContext.sig = 'vp'; + + var _emscripten_get_now_res = () => { + // return resolution of get_now, in nanoseconds + if (ENVIRONMENT_IS_NODE) { + return 1; // nanoseconds + } + // Modern environment where performance.now() is supported: + return 1000; // microseconds (1/1000 of a millisecond) + }; + _emscripten_get_now_res.sig = 'd'; + + var nowIsMonotonic = 1; + + var checkWasiClock = (clock_id) => clock_id >= 0 && clock_id <= 3; + var _clock_res_get = (clk_id, pres) => { + if (!checkWasiClock(clk_id)) { + return 28; + } + var nsec; + // all wasi clocks but realtime are monotonic + if (clk_id === 0) { + nsec = 1000 * 1000; // educated guess that it's milliseconds + } else if (nowIsMonotonic) { + nsec = _emscripten_get_now_res(); + } else { + return 52; + } + HEAP64[pres >> 3] = BigInt(nsec); + return 0; + }; + _clock_res_get.sig = 'iip'; + + var _emscripten_date_now = () => Date.now(); + _emscripten_date_now.sig = 'd'; + + function _clock_time_get(clk_id, ignored_precision, ptime) { + ignored_precision = bigintToI53Checked(ignored_precision); + + if (!checkWasiClock(clk_id)) { + return 28; + } + var now; + // all wasi clocks but realtime are monotonic + if (clk_id === 0) { + now = _emscripten_date_now(); + } else if (nowIsMonotonic) { + now = _emscripten_get_now(); + } else { + return 52; + } + // "now" is in ms, and wasi times are in ns. + var nsec = Math.round(now * 1000 * 1000); + HEAP64[ptime >> 3] = BigInt(nsec); + return 0; + } + _clock_time_get.sig = 'iijp'; + + var _emscripten_alcDevicePauseSOFT = (deviceId) => { + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + + if (AL.paused) { + return; + } + AL.paused = true; + + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId !== deviceId) { + continue; + } + + ctx.audioCtx.suspend(); + clearInterval(ctx.interval); + ctx.interval = null; + } + }; + _emscripten_alcDevicePauseSOFT.sig = 'vi'; + + var _emscripten_alcDeviceResumeSOFT = (deviceId) => { + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return; + } + + if (!AL.paused) { + return; + } + AL.paused = false; + + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId !== deviceId) { + continue; + } + + ctx.interval = setInterval( + () => AL.scheduleContextAudio(ctx), + AL.QUEUE_INTERVAL + ); + ctx.audioCtx.resume(); + } + }; + _emscripten_alcDeviceResumeSOFT.sig = 'vi'; + + var _emscripten_alcGetStringiSOFT = (deviceId, param, index) => { + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return 0; + } + + if (AL.alcStringCache[param]) { + return AL.alcStringCache[param]; + } + + var ret; + switch (param) { + case 0x1995 /* ALC_HRTF_SPECIFIER_SOFT */: + if (index === 0) { + ret = 'Web Audio HRTF'; + } else { + AL.alcErr = 40964; + return 0; + } + break; + default: + if (index !== 0) { + AL.alcErr = 40963; + return 0; + } + return _alcGetString(deviceId, param); + } + + ret = stringToNewUTF8(ret); + AL.alcStringCache[param] = ret; + return ret; + }; + _emscripten_alcGetStringiSOFT.sig = 'iiii'; + + var _emscripten_alcResetDeviceSOFT = (deviceId, pAttrList) => { + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = 40961; + return 0; + } + + var hrtf = null; + pAttrList >>= 2; + if (pAttrList) { + var attr = 0; + var val = 0; + while (true) { + attr = HEAP32[pAttrList++]; + if (attr === 0) { + break; + } + val = HEAP32[pAttrList++]; + + switch (attr) { + case 0x1992 /* ALC_HRTF_SOFT */: + if (val === 1) { + hrtf = true; + } else if (val === 0) { + hrtf = false; + } + break; + } + } + } + + if (hrtf !== null) { + // Apply hrtf attrib to all contexts for this device + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId === deviceId) { + ctx.hrtf = hrtf; + AL.updateContextGlobal(ctx); + } + } + } + + return 1; + }; + _emscripten_alcResetDeviceSOFT.sig = 'iii'; + + var readEmAsmArgsArray = []; + var readEmAsmArgs = (sigPtr, buf) => { + readEmAsmArgsArray.length = 0; + var ch; + // Most arguments are i32s, so shift the buffer pointer so it is a plain + // index into HEAP32. + while ((ch = HEAPU8[sigPtr++])) { + // Floats are always passed as doubles, so all types except for 'i' + // are 8 bytes and require alignment. + var wide = ch != 105; + wide &= ch != 112; + buf += wide && buf % 8 ? 4 : 0; + readEmAsmArgsArray.push( + // Special case for pointers under wasm64 or CAN_ADDRESS_2GB mode. + ch == 112 + ? HEAPU32[buf >> 2] + : ch == 106 + ? HEAP64[buf >> 3] + : ch == 105 + ? HEAP32[buf >> 2] + : HEAPF64[buf >> 3] + ); + buf += wide ? 8 : 4; + } + return readEmAsmArgsArray; + }; + var runEmAsmFunction = (code, sigPtr, argbuf) => { + var args = readEmAsmArgs(sigPtr, argbuf); + return ASM_CONSTS[code](...args); + }; + var _emscripten_asm_const_int = (code, sigPtr, argbuf) => { + return runEmAsmFunction(code, sigPtr, argbuf); + }; + _emscripten_asm_const_int.sig = 'ippp'; + + var _emscripten_console_error = (str) => { + console.error(UTF8ToString(str)); + }; + _emscripten_console_error.sig = 'vp'; + + var _emscripten_console_log = (str) => { + console.log(UTF8ToString(str)); + }; + _emscripten_console_log.sig = 'vp'; + + var _emscripten_console_trace = (str) => { + console.trace(UTF8ToString(str)); + }; + _emscripten_console_trace.sig = 'vp'; + + var _emscripten_console_warn = (str) => { + console.warn(UTF8ToString(str)); + }; + _emscripten_console_warn.sig = 'vp'; + + var _emscripten_err = (str) => err(UTF8ToString(str)); + _emscripten_err.sig = 'vp'; + + var getHeapMax = () => + // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate + // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side + // for any code that deals with heap sizes, which would require special + // casing all heap size related code to treat 0 specially. + 2147483648; + var _emscripten_get_heap_max = () => getHeapMax(); + _emscripten_get_heap_max.sig = 'p'; + + var GLctx; + + var webgl_enable_ANGLE_instanced_arrays = (ctx) => { + // Extension available in WebGL 1 from Firefox 26 and Google Chrome 30 onwards. Core feature in WebGL 2. + var ext = ctx.getExtension('ANGLE_instanced_arrays'); + // Because this extension is a core function in WebGL 2, assign the extension entry points in place of + // where the core functions will reside in WebGL 2. This way the calling code can call these without + // having to dynamically branch depending if running against WebGL 1 or WebGL 2. + if (ext) { + ctx['vertexAttribDivisor'] = (index, divisor) => + ext['vertexAttribDivisorANGLE'](index, divisor); + ctx['drawArraysInstanced'] = (mode, first, count, primcount) => + ext['drawArraysInstancedANGLE'](mode, first, count, primcount); + ctx['drawElementsInstanced'] = ( + mode, + count, + type, + indices, + primcount + ) => + ext['drawElementsInstancedANGLE']( + mode, + count, + type, + indices, + primcount + ); + return 1; + } + }; + + var webgl_enable_OES_vertex_array_object = (ctx) => { + // Extension available in WebGL 1 from Firefox 25 and WebKit 536.28/desktop Safari 6.0.3 onwards. Core feature in WebGL 2. + var ext = ctx.getExtension('OES_vertex_array_object'); + if (ext) { + ctx['createVertexArray'] = () => ext['createVertexArrayOES'](); + ctx['deleteVertexArray'] = (vao) => + ext['deleteVertexArrayOES'](vao); + ctx['bindVertexArray'] = (vao) => ext['bindVertexArrayOES'](vao); + ctx['isVertexArray'] = (vao) => ext['isVertexArrayOES'](vao); + return 1; + } + }; + + var webgl_enable_WEBGL_draw_buffers = (ctx) => { + // Extension available in WebGL 1 from Firefox 28 onwards. Core feature in WebGL 2. + var ext = ctx.getExtension('WEBGL_draw_buffers'); + if (ext) { + ctx['drawBuffers'] = (n, bufs) => ext['drawBuffersWEBGL'](n, bufs); + return 1; + } + }; + + var webgl_enable_EXT_polygon_offset_clamp = (ctx) => + !!(ctx.extPolygonOffsetClamp = ctx.getExtension( + 'EXT_polygon_offset_clamp' + )); + + var webgl_enable_EXT_clip_control = (ctx) => + !!(ctx.extClipControl = ctx.getExtension('EXT_clip_control')); + + var webgl_enable_WEBGL_polygon_mode = (ctx) => + !!(ctx.webglPolygonMode = ctx.getExtension('WEBGL_polygon_mode')); + + var webgl_enable_WEBGL_multi_draw = (ctx) => + // Closure is expected to be allowed to minify the '.multiDrawWebgl' property, so not accessing it quoted. + !!(ctx.multiDrawWebgl = ctx.getExtension('WEBGL_multi_draw')); + + var getEmscriptenSupportedExtensions = (ctx) => { + // Restrict the list of advertised extensions to those that we actually + // support. + var supportedExtensions = [ + // WebGL 1 extensions + 'ANGLE_instanced_arrays', + 'EXT_blend_minmax', + 'EXT_disjoint_timer_query', + 'EXT_frag_depth', + 'EXT_shader_texture_lod', + 'EXT_sRGB', + 'OES_element_index_uint', + 'OES_fbo_render_mipmap', + 'OES_standard_derivatives', + 'OES_texture_float', + 'OES_texture_half_float', + 'OES_texture_half_float_linear', + 'OES_vertex_array_object', + 'WEBGL_color_buffer_float', + 'WEBGL_depth_texture', + 'WEBGL_draw_buffers', + // WebGL 1 and WebGL 2 extensions + 'EXT_clip_control', + 'EXT_color_buffer_half_float', + 'EXT_depth_clamp', + 'EXT_float_blend', + 'EXT_polygon_offset_clamp', + 'EXT_texture_compression_bptc', + 'EXT_texture_compression_rgtc', + 'EXT_texture_filter_anisotropic', + 'KHR_parallel_shader_compile', + 'OES_texture_float_linear', + 'WEBGL_blend_func_extended', + 'WEBGL_compressed_texture_astc', + 'WEBGL_compressed_texture_etc', + 'WEBGL_compressed_texture_etc1', + 'WEBGL_compressed_texture_s3tc', + 'WEBGL_compressed_texture_s3tc_srgb', + 'WEBGL_debug_renderer_info', + 'WEBGL_debug_shaders', + 'WEBGL_lose_context', + 'WEBGL_multi_draw', + 'WEBGL_polygon_mode', + ]; + // .getSupportedExtensions() can return null if context is lost, so coerce to empty array. + return (ctx.getSupportedExtensions() || []).filter((ext) => + supportedExtensions.includes(ext) + ); + }; + + var GL = { + counter: 1, + buffers: [], + programs: [], + framebuffers: [], + renderbuffers: [], + textures: [], + shaders: [], + vaos: [], + contexts: [], + offscreenCanvases: {}, + queries: [], + stringCache: {}, + unpackAlignment: 4, + unpackRowLength: 0, + recordError: (errorCode) => { + if (!GL.lastError) { + GL.lastError = errorCode; + } + }, + getNewId: (table) => { + var ret = GL.counter++; + for (var i = table.length; i < ret; i++) { + table[i] = null; + } + return ret; + }, + genObject: (n, buffers, createFunction, objectTable) => { + for (var i = 0; i < n; i++) { + var buffer = GLctx[createFunction](); + var id = buffer && GL.getNewId(objectTable); + if (buffer) { + buffer.name = id; + objectTable[id] = buffer; + } else { + GL.recordError(0x502 /* GL_INVALID_OPERATION */); + } + HEAP32[(buffers + i * 4) >> 2] = id; + } + }, + getSource: (shader, count, string, length) => { + var source = ''; + for (var i = 0; i < count; ++i) { + var len = length ? HEAPU32[(length + i * 4) >> 2] : undefined; + source += UTF8ToString(HEAPU32[(string + i * 4) >> 2], len); + } + return source; + }, + createContext: ( + /** @type {HTMLCanvasElement} */ canvas, + webGLContextAttributes + ) => { + // BUG: Workaround Safari WebGL issue: After successfully acquiring WebGL + // context on a canvas, calling .getContext() will always return that + // context independent of which 'webgl' or 'webgl2' + // context version was passed. See: + // https://bugs.webkit.org/show_bug.cgi?id=222758 + // and: + // https://github.com/emscripten-core/emscripten/issues/13295. + // TODO: Once the bug is fixed and shipped in Safari, adjust the Safari + // version field in above check. + if (!canvas.getContextSafariWebGL2Fixed) { + canvas.getContextSafariWebGL2Fixed = canvas.getContext; + /** @type {function(this:HTMLCanvasElement, string, (Object|null)=): (Object|null)} */ + function fixedGetContext(ver, attrs) { + var gl = canvas.getContextSafariWebGL2Fixed(ver, attrs); + return (ver == 'webgl') == + gl instanceof WebGLRenderingContext + ? gl + : null; + } + canvas.getContext = fixedGetContext; + } + + var ctx = canvas.getContext('webgl', webGLContextAttributes); + + if (!ctx) return 0; + + var handle = GL.registerContext(ctx, webGLContextAttributes); + + return handle; + }, + registerContext: (ctx, webGLContextAttributes) => { + // without pthreads a context is just an integer ID + var handle = GL.getNewId(GL.contexts); + + var context = { + handle, + attributes: webGLContextAttributes, + version: webGLContextAttributes.majorVersion, + GLctx: ctx, + }; + + // Store the created context object so that we can access the context + // given a canvas without having to pass the parameters again. + if (ctx.canvas) ctx.canvas.GLctxObject = context; + GL.contexts[handle] = context; + if ( + typeof webGLContextAttributes.enableExtensionsByDefault == + 'undefined' || + webGLContextAttributes.enableExtensionsByDefault + ) { + GL.initExtensions(context); + } + + return handle; + }, + makeContextCurrent: (contextHandle) => { + // Active Emscripten GL layer context object. + GL.currentContext = GL.contexts[contextHandle]; + // Active WebGL context object. + Module['ctx'] = GLctx = GL.currentContext?.GLctx; + return !(contextHandle && !GLctx); + }, + getContext: (contextHandle) => { + return GL.contexts[contextHandle]; + }, + deleteContext: (contextHandle) => { + if (GL.currentContext === GL.contexts[contextHandle]) { + GL.currentContext = null; + } + if (typeof JSEvents == 'object') { + // Release all JS event handlers on the DOM element that the GL context is + // associated with since the context is now deleted. + JSEvents.removeAllHandlersOnTarget( + GL.contexts[contextHandle].GLctx.canvas + ); + } + // Make sure the canvas object no longer refers to the context object so + // there are no GC surprises. + if (GL.contexts[contextHandle]?.GLctx.canvas) { + GL.contexts[contextHandle].GLctx.canvas.GLctxObject = undefined; + } + GL.contexts[contextHandle] = null; + }, + initExtensions: (context) => { + // If this function is called without a specific context object, init the + // extensions of the currently active context. + context ||= GL.currentContext; + + if (context.initExtensionsDone) return; + context.initExtensionsDone = true; + + var GLctx = context.GLctx; + + // Detect the presence of a few extensions manually, ction GL interop + // layer itself will need to know if they exist. + + // Extensions that are available in both WebGL 1 and WebGL 2 + webgl_enable_WEBGL_multi_draw(GLctx); + webgl_enable_EXT_polygon_offset_clamp(GLctx); + webgl_enable_EXT_clip_control(GLctx); + webgl_enable_WEBGL_polygon_mode(GLctx); + // Extensions that are only available in WebGL 1 (the calls will be no-ops + // if called on a WebGL 2 context active) + webgl_enable_ANGLE_instanced_arrays(GLctx); + webgl_enable_OES_vertex_array_object(GLctx); + webgl_enable_WEBGL_draw_buffers(GLctx); + { + GLctx.disjointTimerQueryExt = GLctx.getExtension( + 'EXT_disjoint_timer_query' + ); + } + + getEmscriptenSupportedExtensions(GLctx).forEach((ext) => { + // WEBGL_lose_context, WEBGL_debug_renderer_info and WEBGL_debug_shaders + // are not enabled by default. + if (!ext.includes('lose_context') && !ext.includes('debug')) { + // Call .getExtension() to enable that extension permanently. + GLctx.getExtension(ext); + } + }); + }, + }; + /** @suppress {duplicate } */ + var _glActiveTexture = (x0) => GLctx.activeTexture(x0); + _glActiveTexture.sig = 'vi'; + var _emscripten_glActiveTexture = _glActiveTexture; + _emscripten_glActiveTexture.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glAttachShader = (program, shader) => { + GLctx.attachShader(GL.programs[program], GL.shaders[shader]); + }; + _glAttachShader.sig = 'vii'; + var _emscripten_glAttachShader = _glAttachShader; + _emscripten_glAttachShader.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glBeginQueryEXT = (target, id) => { + GLctx.disjointTimerQueryExt['beginQueryEXT'](target, GL.queries[id]); + }; + _glBeginQueryEXT.sig = 'vii'; + var _emscripten_glBeginQueryEXT = _glBeginQueryEXT; + + /** @suppress {duplicate } */ + var _glBindAttribLocation = (program, index, name) => { + GLctx.bindAttribLocation( + GL.programs[program], + index, + UTF8ToString(name) + ); + }; + _glBindAttribLocation.sig = 'viip'; + var _emscripten_glBindAttribLocation = _glBindAttribLocation; + _emscripten_glBindAttribLocation.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glBindBuffer = (target, buffer) => { + GLctx.bindBuffer(target, GL.buffers[buffer]); + }; + _glBindBuffer.sig = 'vii'; + var _emscripten_glBindBuffer = _glBindBuffer; + _emscripten_glBindBuffer.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glBindFramebuffer = (target, framebuffer) => { + GLctx.bindFramebuffer(target, GL.framebuffers[framebuffer]); + }; + _glBindFramebuffer.sig = 'vii'; + var _emscripten_glBindFramebuffer = _glBindFramebuffer; + _emscripten_glBindFramebuffer.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glBindRenderbuffer = (target, renderbuffer) => { + GLctx.bindRenderbuffer(target, GL.renderbuffers[renderbuffer]); + }; + _glBindRenderbuffer.sig = 'vii'; + var _emscripten_glBindRenderbuffer = _glBindRenderbuffer; + _emscripten_glBindRenderbuffer.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glBindTexture = (target, texture) => { + GLctx.bindTexture(target, GL.textures[texture]); + }; + _glBindTexture.sig = 'vii'; + var _emscripten_glBindTexture = _glBindTexture; + _emscripten_glBindTexture.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glBindVertexArray = (vao) => { + GLctx.bindVertexArray(GL.vaos[vao]); + }; + _glBindVertexArray.sig = 'vi'; + /** @suppress {duplicate } */ + var _glBindVertexArrayOES = _glBindVertexArray; + _glBindVertexArrayOES.sig = 'vi'; + var _emscripten_glBindVertexArrayOES = _glBindVertexArrayOES; + _emscripten_glBindVertexArrayOES.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glBlendColor = (x0, x1, x2, x3) => GLctx.blendColor(x0, x1, x2, x3); + _glBlendColor.sig = 'vffff'; + var _emscripten_glBlendColor = _glBlendColor; + _emscripten_glBlendColor.sig = 'vffff'; + + /** @suppress {duplicate } */ + var _glBlendEquation = (x0) => GLctx.blendEquation(x0); + _glBlendEquation.sig = 'vi'; + var _emscripten_glBlendEquation = _glBlendEquation; + _emscripten_glBlendEquation.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glBlendEquationSeparate = (x0, x1) => + GLctx.blendEquationSeparate(x0, x1); + _glBlendEquationSeparate.sig = 'vii'; + var _emscripten_glBlendEquationSeparate = _glBlendEquationSeparate; + _emscripten_glBlendEquationSeparate.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glBlendFunc = (x0, x1) => GLctx.blendFunc(x0, x1); + _glBlendFunc.sig = 'vii'; + var _emscripten_glBlendFunc = _glBlendFunc; + _emscripten_glBlendFunc.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glBlendFuncSeparate = (x0, x1, x2, x3) => + GLctx.blendFuncSeparate(x0, x1, x2, x3); + _glBlendFuncSeparate.sig = 'viiii'; + var _emscripten_glBlendFuncSeparate = _glBlendFuncSeparate; + _emscripten_glBlendFuncSeparate.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glBufferData = (target, size, data, usage) => { + // N.b. here first form specifies a heap subarray, second form an integer + // size, so the ?: code here is polymorphic. It is advised to avoid + // randomly mixing both uses in calling code, to avoid any potential JS + // engine JIT issues. + GLctx.bufferData( + target, + data ? HEAPU8.subarray(data, data + size) : size, + usage + ); + }; + _glBufferData.sig = 'vippi'; + var _emscripten_glBufferData = _glBufferData; + _emscripten_glBufferData.sig = 'vippi'; + + /** @suppress {duplicate } */ + var _glBufferSubData = (target, offset, size, data) => { + GLctx.bufferSubData(target, offset, HEAPU8.subarray(data, data + size)); + }; + _glBufferSubData.sig = 'vippp'; + var _emscripten_glBufferSubData = _glBufferSubData; + _emscripten_glBufferSubData.sig = 'vippp'; + + /** @suppress {duplicate } */ + var _glCheckFramebufferStatus = (x0) => GLctx.checkFramebufferStatus(x0); + _glCheckFramebufferStatus.sig = 'ii'; + var _emscripten_glCheckFramebufferStatus = _glCheckFramebufferStatus; + _emscripten_glCheckFramebufferStatus.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glClear = (x0) => GLctx.clear(x0); + _glClear.sig = 'vi'; + var _emscripten_glClear = _glClear; + _emscripten_glClear.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glClearColor = (x0, x1, x2, x3) => GLctx.clearColor(x0, x1, x2, x3); + _glClearColor.sig = 'vffff'; + var _emscripten_glClearColor = _glClearColor; + _emscripten_glClearColor.sig = 'vffff'; + + /** @suppress {duplicate } */ + var _glClearDepthf = (x0) => GLctx.clearDepth(x0); + _glClearDepthf.sig = 'vf'; + var _emscripten_glClearDepthf = _glClearDepthf; + _emscripten_glClearDepthf.sig = 'vf'; + + /** @suppress {duplicate } */ + var _glClearStencil = (x0) => GLctx.clearStencil(x0); + _glClearStencil.sig = 'vi'; + var _emscripten_glClearStencil = _glClearStencil; + _emscripten_glClearStencil.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glClipControlEXT = (origin, depth) => { + GLctx.extClipControl['clipControlEXT'](origin, depth); + }; + _glClipControlEXT.sig = 'vii'; + var _emscripten_glClipControlEXT = _glClipControlEXT; + + /** @suppress {duplicate } */ + var _glColorMask = (red, green, blue, alpha) => { + GLctx.colorMask(!!red, !!green, !!blue, !!alpha); + }; + _glColorMask.sig = 'viiii'; + var _emscripten_glColorMask = _glColorMask; + _emscripten_glColorMask.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glCompileShader = (shader) => { + GLctx.compileShader(GL.shaders[shader]); + }; + _glCompileShader.sig = 'vi'; + var _emscripten_glCompileShader = _glCompileShader; + _emscripten_glCompileShader.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glCompressedTexImage2D = ( + target, + level, + internalFormat, + width, + height, + border, + imageSize, + data + ) => { + // `data` may be null here, which means "allocate uniniitalized space but + // don't upload" in GLES parlance, but `compressedTexImage2D` requires the + // final data parameter, so we simply pass a heap view starting at zero + // effectively uploading whatever happens to be near address zero. See + // https://github.com/emscripten-core/emscripten/issues/19300. + GLctx.compressedTexImage2D( + target, + level, + internalFormat, + width, + height, + border, + HEAPU8.subarray(data, data + imageSize) + ); + }; + _glCompressedTexImage2D.sig = 'viiiiiiip'; + var _emscripten_glCompressedTexImage2D = _glCompressedTexImage2D; + _emscripten_glCompressedTexImage2D.sig = 'viiiiiiip'; + + /** @suppress {duplicate } */ + var _glCompressedTexSubImage2D = ( + target, + level, + xoffset, + yoffset, + width, + height, + format, + imageSize, + data + ) => { + GLctx.compressedTexSubImage2D( + target, + level, + xoffset, + yoffset, + width, + height, + format, + HEAPU8.subarray(data, data + imageSize) + ); + }; + _glCompressedTexSubImage2D.sig = 'viiiiiiiip'; + var _emscripten_glCompressedTexSubImage2D = _glCompressedTexSubImage2D; + _emscripten_glCompressedTexSubImage2D.sig = 'viiiiiiiip'; + + /** @suppress {duplicate } */ + var _glCopyTexImage2D = (x0, x1, x2, x3, x4, x5, x6, x7) => + GLctx.copyTexImage2D(x0, x1, x2, x3, x4, x5, x6, x7); + _glCopyTexImage2D.sig = 'viiiiiiii'; + var _emscripten_glCopyTexImage2D = _glCopyTexImage2D; + _emscripten_glCopyTexImage2D.sig = 'viiiiiiii'; + + /** @suppress {duplicate } */ + var _glCopyTexSubImage2D = (x0, x1, x2, x3, x4, x5, x6, x7) => + GLctx.copyTexSubImage2D(x0, x1, x2, x3, x4, x5, x6, x7); + _glCopyTexSubImage2D.sig = 'viiiiiiii'; + var _emscripten_glCopyTexSubImage2D = _glCopyTexSubImage2D; + _emscripten_glCopyTexSubImage2D.sig = 'viiiiiiii'; + + /** @suppress {duplicate } */ + var _glCreateProgram = () => { + var id = GL.getNewId(GL.programs); + var program = GLctx.createProgram(); + // Store additional information needed for each shader program: + program.name = id; + // Lazy cache results of + // glGetProgramiv(GL_ACTIVE_UNIFORM_MAX_LENGTH/GL_ACTIVE_ATTRIBUTE_MAX_LENGTH/GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH) + program.maxUniformLength = + program.maxAttributeLength = + program.maxUniformBlockNameLength = + 0; + program.uniformIdCounter = 1; + GL.programs[id] = program; + return id; + }; + _glCreateProgram.sig = 'i'; + var _emscripten_glCreateProgram = _glCreateProgram; + _emscripten_glCreateProgram.sig = 'i'; + + /** @suppress {duplicate } */ + var _glCreateShader = (shaderType) => { + var id = GL.getNewId(GL.shaders); + GL.shaders[id] = GLctx.createShader(shaderType); + + return id; + }; + _glCreateShader.sig = 'ii'; + var _emscripten_glCreateShader = _glCreateShader; + _emscripten_glCreateShader.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glCullFace = (x0) => GLctx.cullFace(x0); + _glCullFace.sig = 'vi'; + var _emscripten_glCullFace = _glCullFace; + _emscripten_glCullFace.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glDeleteBuffers = (n, buffers) => { + for (var i = 0; i < n; i++) { + var id = HEAP32[(buffers + i * 4) >> 2]; + var buffer = GL.buffers[id]; + + // From spec: "glDeleteBuffers silently ignores 0's and names that do not + // correspond to existing buffer objects." + if (!buffer) continue; + + GLctx.deleteBuffer(buffer); + buffer.name = 0; + GL.buffers[id] = null; + } + }; + _glDeleteBuffers.sig = 'vip'; + var _emscripten_glDeleteBuffers = _glDeleteBuffers; + _emscripten_glDeleteBuffers.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glDeleteFramebuffers = (n, framebuffers) => { + for (var i = 0; i < n; ++i) { + var id = HEAP32[(framebuffers + i * 4) >> 2]; + var framebuffer = GL.framebuffers[id]; + if (!framebuffer) continue; // GL spec: "glDeleteFramebuffers silently ignores 0s and names that do not correspond to existing framebuffer objects". + GLctx.deleteFramebuffer(framebuffer); + framebuffer.name = 0; + GL.framebuffers[id] = null; + } + }; + _glDeleteFramebuffers.sig = 'vip'; + var _emscripten_glDeleteFramebuffers = _glDeleteFramebuffers; + _emscripten_glDeleteFramebuffers.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glDeleteProgram = (id) => { + if (!id) return; + var program = GL.programs[id]; + if (!program) { + // glDeleteProgram actually signals an error when deleting a nonexisting + // object, unlike some other GL delete functions. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + GLctx.deleteProgram(program); + program.name = 0; + GL.programs[id] = null; + }; + _glDeleteProgram.sig = 'vi'; + var _emscripten_glDeleteProgram = _glDeleteProgram; + _emscripten_glDeleteProgram.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glDeleteQueriesEXT = (n, ids) => { + for (var i = 0; i < n; i++) { + var id = HEAP32[(ids + i * 4) >> 2]; + var query = GL.queries[id]; + if (!query) continue; // GL spec: "unused names in ids are ignored, as is the name zero." + GLctx.disjointTimerQueryExt['deleteQueryEXT'](query); + GL.queries[id] = null; + } + }; + _glDeleteQueriesEXT.sig = 'vip'; + var _emscripten_glDeleteQueriesEXT = _glDeleteQueriesEXT; + + /** @suppress {duplicate } */ + var _glDeleteRenderbuffers = (n, renderbuffers) => { + for (var i = 0; i < n; i++) { + var id = HEAP32[(renderbuffers + i * 4) >> 2]; + var renderbuffer = GL.renderbuffers[id]; + if (!renderbuffer) continue; // GL spec: "glDeleteRenderbuffers silently ignores 0s and names that do not correspond to existing renderbuffer objects". + GLctx.deleteRenderbuffer(renderbuffer); + renderbuffer.name = 0; + GL.renderbuffers[id] = null; + } + }; + _glDeleteRenderbuffers.sig = 'vip'; + var _emscripten_glDeleteRenderbuffers = _glDeleteRenderbuffers; + _emscripten_glDeleteRenderbuffers.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glDeleteShader = (id) => { + if (!id) return; + var shader = GL.shaders[id]; + if (!shader) { + // glDeleteShader actually signals an error when deleting a nonexisting + // object, unlike some other GL delete functions. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + GLctx.deleteShader(shader); + GL.shaders[id] = null; + }; + _glDeleteShader.sig = 'vi'; + var _emscripten_glDeleteShader = _glDeleteShader; + _emscripten_glDeleteShader.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glDeleteTextures = (n, textures) => { + for (var i = 0; i < n; i++) { + var id = HEAP32[(textures + i * 4) >> 2]; + var texture = GL.textures[id]; + // GL spec: "glDeleteTextures silently ignores 0s and names that do not + // correspond to existing textures". + if (!texture) continue; + GLctx.deleteTexture(texture); + texture.name = 0; + GL.textures[id] = null; + } + }; + _glDeleteTextures.sig = 'vip'; + var _emscripten_glDeleteTextures = _glDeleteTextures; + _emscripten_glDeleteTextures.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glDeleteVertexArrays = (n, vaos) => { + for (var i = 0; i < n; i++) { + var id = HEAP32[(vaos + i * 4) >> 2]; + GLctx.deleteVertexArray(GL.vaos[id]); + GL.vaos[id] = null; + } + }; + _glDeleteVertexArrays.sig = 'vip'; + /** @suppress {duplicate } */ + var _glDeleteVertexArraysOES = _glDeleteVertexArrays; + _glDeleteVertexArraysOES.sig = 'vip'; + var _emscripten_glDeleteVertexArraysOES = _glDeleteVertexArraysOES; + _emscripten_glDeleteVertexArraysOES.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glDepthFunc = (x0) => GLctx.depthFunc(x0); + _glDepthFunc.sig = 'vi'; + var _emscripten_glDepthFunc = _glDepthFunc; + _emscripten_glDepthFunc.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glDepthMask = (flag) => { + GLctx.depthMask(!!flag); + }; + _glDepthMask.sig = 'vi'; + var _emscripten_glDepthMask = _glDepthMask; + _emscripten_glDepthMask.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glDepthRangef = (x0, x1) => GLctx.depthRange(x0, x1); + _glDepthRangef.sig = 'vff'; + var _emscripten_glDepthRangef = _glDepthRangef; + _emscripten_glDepthRangef.sig = 'vff'; + + /** @suppress {duplicate } */ + var _glDetachShader = (program, shader) => { + GLctx.detachShader(GL.programs[program], GL.shaders[shader]); + }; + _glDetachShader.sig = 'vii'; + var _emscripten_glDetachShader = _glDetachShader; + _emscripten_glDetachShader.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glDisable = (x0) => GLctx.disable(x0); + _glDisable.sig = 'vi'; + var _emscripten_glDisable = _glDisable; + _emscripten_glDisable.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glDisableVertexAttribArray = (index) => { + GLctx.disableVertexAttribArray(index); + }; + _glDisableVertexAttribArray.sig = 'vi'; + var _emscripten_glDisableVertexAttribArray = _glDisableVertexAttribArray; + _emscripten_glDisableVertexAttribArray.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glDrawArrays = (mode, first, count) => { + GLctx.drawArrays(mode, first, count); + }; + _glDrawArrays.sig = 'viii'; + var _emscripten_glDrawArrays = _glDrawArrays; + _emscripten_glDrawArrays.sig = 'viii'; + + /** @suppress {duplicate } */ + var _glDrawArraysInstanced = (mode, first, count, primcount) => { + GLctx.drawArraysInstanced(mode, first, count, primcount); + }; + _glDrawArraysInstanced.sig = 'viiii'; + /** @suppress {duplicate } */ + var _glDrawArraysInstancedANGLE = _glDrawArraysInstanced; + var _emscripten_glDrawArraysInstancedANGLE = _glDrawArraysInstancedANGLE; + + var tempFixedLengthArray = []; + + /** @suppress {duplicate } */ + var _glDrawBuffers = (n, bufs) => { + var bufArray = tempFixedLengthArray[n]; + for (var i = 0; i < n; i++) { + bufArray[i] = HEAP32[(bufs + i * 4) >> 2]; + } + + GLctx.drawBuffers(bufArray); + }; + _glDrawBuffers.sig = 'vip'; + /** @suppress {duplicate } */ + var _glDrawBuffersWEBGL = _glDrawBuffers; + var _emscripten_glDrawBuffersWEBGL = _glDrawBuffersWEBGL; + + /** @suppress {duplicate } */ + var _glDrawElements = (mode, count, type, indices) => { + GLctx.drawElements(mode, count, type, indices); + }; + _glDrawElements.sig = 'viiip'; + var _emscripten_glDrawElements = _glDrawElements; + _emscripten_glDrawElements.sig = 'viiip'; + + /** @suppress {duplicate } */ + var _glDrawElementsInstanced = (mode, count, type, indices, primcount) => { + GLctx.drawElementsInstanced(mode, count, type, indices, primcount); + }; + _glDrawElementsInstanced.sig = 'viiipi'; + /** @suppress {duplicate } */ + var _glDrawElementsInstancedANGLE = _glDrawElementsInstanced; + var _emscripten_glDrawElementsInstancedANGLE = + _glDrawElementsInstancedANGLE; + + /** @suppress {duplicate } */ + var _glEnable = (x0) => GLctx.enable(x0); + _glEnable.sig = 'vi'; + var _emscripten_glEnable = _glEnable; + _emscripten_glEnable.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glEnableVertexAttribArray = (index) => { + GLctx.enableVertexAttribArray(index); + }; + _glEnableVertexAttribArray.sig = 'vi'; + var _emscripten_glEnableVertexAttribArray = _glEnableVertexAttribArray; + _emscripten_glEnableVertexAttribArray.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glEndQueryEXT = (target) => { + GLctx.disjointTimerQueryExt['endQueryEXT'](target); + }; + _glEndQueryEXT.sig = 'vi'; + var _emscripten_glEndQueryEXT = _glEndQueryEXT; + + /** @suppress {duplicate } */ + var _glFinish = () => GLctx.finish(); + _glFinish.sig = 'v'; + var _emscripten_glFinish = _glFinish; + _emscripten_glFinish.sig = 'v'; + + /** @suppress {duplicate } */ + var _glFlush = () => GLctx.flush(); + _glFlush.sig = 'v'; + var _emscripten_glFlush = _glFlush; + _emscripten_glFlush.sig = 'v'; + + /** @suppress {duplicate } */ + var _glFramebufferRenderbuffer = ( + target, + attachment, + renderbuffertarget, + renderbuffer + ) => { + GLctx.framebufferRenderbuffer( + target, + attachment, + renderbuffertarget, + GL.renderbuffers[renderbuffer] + ); + }; + _glFramebufferRenderbuffer.sig = 'viiii'; + var _emscripten_glFramebufferRenderbuffer = _glFramebufferRenderbuffer; + _emscripten_glFramebufferRenderbuffer.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glFramebufferTexture2D = ( + target, + attachment, + textarget, + texture, + level + ) => { + GLctx.framebufferTexture2D( + target, + attachment, + textarget, + GL.textures[texture], + level + ); + }; + _glFramebufferTexture2D.sig = 'viiiii'; + var _emscripten_glFramebufferTexture2D = _glFramebufferTexture2D; + _emscripten_glFramebufferTexture2D.sig = 'viiiii'; + + /** @suppress {duplicate } */ + var _glFrontFace = (x0) => GLctx.frontFace(x0); + _glFrontFace.sig = 'vi'; + var _emscripten_glFrontFace = _glFrontFace; + _emscripten_glFrontFace.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glGenBuffers = (n, buffers) => { + GL.genObject(n, buffers, 'createBuffer', GL.buffers); + }; + _glGenBuffers.sig = 'vip'; + var _emscripten_glGenBuffers = _glGenBuffers; + _emscripten_glGenBuffers.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGenFramebuffers = (n, ids) => { + GL.genObject(n, ids, 'createFramebuffer', GL.framebuffers); + }; + _glGenFramebuffers.sig = 'vip'; + var _emscripten_glGenFramebuffers = _glGenFramebuffers; + _emscripten_glGenFramebuffers.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGenQueriesEXT = (n, ids) => { + for (var i = 0; i < n; i++) { + var query = GLctx.disjointTimerQueryExt['createQueryEXT'](); + if (!query) { + GL.recordError(0x502 /* GL_INVALID_OPERATION */); + while (i < n) HEAP32[(ids + i++ * 4) >> 2] = 0; + return; + } + var id = GL.getNewId(GL.queries); + query.name = id; + GL.queries[id] = query; + HEAP32[(ids + i * 4) >> 2] = id; + } + }; + _glGenQueriesEXT.sig = 'vip'; + var _emscripten_glGenQueriesEXT = _glGenQueriesEXT; + + /** @suppress {duplicate } */ + var _glGenRenderbuffers = (n, renderbuffers) => { + GL.genObject(n, renderbuffers, 'createRenderbuffer', GL.renderbuffers); + }; + _glGenRenderbuffers.sig = 'vip'; + var _emscripten_glGenRenderbuffers = _glGenRenderbuffers; + _emscripten_glGenRenderbuffers.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGenTextures = (n, textures) => { + GL.genObject(n, textures, 'createTexture', GL.textures); + }; + _glGenTextures.sig = 'vip'; + var _emscripten_glGenTextures = _glGenTextures; + _emscripten_glGenTextures.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGenVertexArrays = (n, arrays) => { + GL.genObject(n, arrays, 'createVertexArray', GL.vaos); + }; + _glGenVertexArrays.sig = 'vip'; + /** @suppress {duplicate } */ + var _glGenVertexArraysOES = _glGenVertexArrays; + _glGenVertexArraysOES.sig = 'vip'; + var _emscripten_glGenVertexArraysOES = _glGenVertexArraysOES; + _emscripten_glGenVertexArraysOES.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGenerateMipmap = (x0) => GLctx.generateMipmap(x0); + _glGenerateMipmap.sig = 'vi'; + var _emscripten_glGenerateMipmap = _glGenerateMipmap; + _emscripten_glGenerateMipmap.sig = 'vi'; + + var __glGetActiveAttribOrUniform = ( + funcName, + program, + index, + bufSize, + length, + size, + type, + name + ) => { + program = GL.programs[program]; + var info = GLctx[funcName](program, index); + if (info) { + // If an error occurs, nothing will be written to length, size and type and name. + var numBytesWrittenExclNull = + name && stringToUTF8(info.name, name, bufSize); + if (length) HEAP32[length >> 2] = numBytesWrittenExclNull; + if (size) HEAP32[size >> 2] = info.size; + if (type) HEAP32[type >> 2] = info.type; + } + }; + + /** @suppress {duplicate } */ + var _glGetActiveAttrib = ( + program, + index, + bufSize, + length, + size, + type, + name + ) => + __glGetActiveAttribOrUniform( + 'getActiveAttrib', + program, + index, + bufSize, + length, + size, + type, + name + ); + _glGetActiveAttrib.sig = 'viiipppp'; + var _emscripten_glGetActiveAttrib = _glGetActiveAttrib; + _emscripten_glGetActiveAttrib.sig = 'viiipppp'; + + /** @suppress {duplicate } */ + var _glGetActiveUniform = ( + program, + index, + bufSize, + length, + size, + type, + name + ) => + __glGetActiveAttribOrUniform( + 'getActiveUniform', + program, + index, + bufSize, + length, + size, + type, + name + ); + _glGetActiveUniform.sig = 'viiipppp'; + var _emscripten_glGetActiveUniform = _glGetActiveUniform; + _emscripten_glGetActiveUniform.sig = 'viiipppp'; + + /** @suppress {duplicate } */ + var _glGetAttachedShaders = (program, maxCount, count, shaders) => { + var result = GLctx.getAttachedShaders(GL.programs[program]); + var len = result.length; + if (len > maxCount) { + len = maxCount; + } + HEAP32[count >> 2] = len; + for (var i = 0; i < len; ++i) { + var id = GL.shaders.indexOf(result[i]); + HEAP32[(shaders + i * 4) >> 2] = id; + } + }; + _glGetAttachedShaders.sig = 'viipp'; + var _emscripten_glGetAttachedShaders = _glGetAttachedShaders; + _emscripten_glGetAttachedShaders.sig = 'viipp'; + + /** @suppress {duplicate } */ + var _glGetAttribLocation = (program, name) => + GLctx.getAttribLocation(GL.programs[program], UTF8ToString(name)); + _glGetAttribLocation.sig = 'iip'; + var _emscripten_glGetAttribLocation = _glGetAttribLocation; + _emscripten_glGetAttribLocation.sig = 'iip'; + + var writeI53ToI64 = (ptr, num) => { + HEAPU32[ptr >> 2] = num; + var lower = HEAPU32[ptr >> 2]; + HEAPU32[(ptr + 4) >> 2] = (num - lower) / 4294967296; + }; + + var emscriptenWebGLGet = (name_, p, type) => { + // Guard against user passing a null pointer. + // Note that GLES2 spec does not say anything about how passing a null + // pointer should be treated. Testing on desktop core GL 3, the application + // crashes on glGetIntegerv to a null pointer, but better to report an error + // instead of doing anything random. + if (!p) { + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + var ret = undefined; + switch ( + name_ // Handle a few trivial GLES values + ) { + case 0x8dfa: // GL_SHADER_COMPILER + ret = 1; + break; + case 0x8df8: // GL_SHADER_BINARY_FORMATS + if (type != 0 && type != 1) { + GL.recordError(0x500); // GL_INVALID_ENUM + } + // Do not write anything to the out pointer, since no binary formats are + // supported. + return; + case 0x8df9: // GL_NUM_SHADER_BINARY_FORMATS + ret = 0; + break; + case 0x86a2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS + // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete + // since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be + // queried for length), so implement it ourselves to allow C++ GLES2 + // code get the length. + var formats = GLctx.getParameter( + 0x86a3 /*GL_COMPRESSED_TEXTURE_FORMATS*/ + ); + ret = formats ? formats.length : 0; + break; + } + + if (ret === undefined) { + var result = GLctx.getParameter(name_); + switch (typeof result) { + case 'number': + ret = result; + break; + case 'boolean': + ret = result ? 1 : 0; + break; + case 'string': + GL.recordError(0x500); // GL_INVALID_ENUM + return; + case 'object': + if (result === null) { + // null is a valid result for some (e.g., which buffer is bound - + // perhaps nothing is bound), but otherwise can mean an invalid + // name_, which we need to report as an error + switch (name_) { + case 0x8894: // ARRAY_BUFFER_BINDING + case 0x8b8d: // CURRENT_PROGRAM + case 0x8895: // ELEMENT_ARRAY_BUFFER_BINDING + case 0x8ca6: // FRAMEBUFFER_BINDING or DRAW_FRAMEBUFFER_BINDING + case 0x8ca7: // RENDERBUFFER_BINDING + case 0x8069: // TEXTURE_BINDING_2D + case 0x85b5: // WebGL 2 GL_VERTEX_ARRAY_BINDING, or WebGL 1 extension OES_vertex_array_object GL_VERTEX_ARRAY_BINDING_OES + case 0x8514: { + // TEXTURE_BINDING_CUBE_MAP + ret = 0; + break; + } + default: { + GL.recordError(0x500); // GL_INVALID_ENUM + return; + } + } + } else if ( + result instanceof Float32Array || + result instanceof Uint32Array || + result instanceof Int32Array || + result instanceof Array + ) { + for (var i = 0; i < result.length; ++i) { + switch (type) { + case 0: + HEAP32[(p + i * 4) >> 2] = result[i]; + break; + case 2: + HEAPF32[(p + i * 4) >> 2] = result[i]; + break; + case 4: + HEAP8[p + i] = result[i] ? 1 : 0; + break; + } + } + return; + } else { + try { + ret = result.name | 0; + } catch (e) { + GL.recordError(0x500); // GL_INVALID_ENUM + err( + `GL_INVALID_ENUM in glGet${type}v: Unknown object returned from WebGL getParameter(${name_})! (error: ${e})` + ); + return; + } + } + break; + default: + GL.recordError(0x500); // GL_INVALID_ENUM + err( + `GL_INVALID_ENUM in glGet${type}v: Native code calling glGet${type}v(${name_}) and it returns ${result} of type ${typeof result}!` + ); + return; + } + } + + switch (type) { + case 1: + writeI53ToI64(p, ret); + break; + case 0: + HEAP32[p >> 2] = ret; + break; + case 2: + HEAPF32[p >> 2] = ret; + break; + case 4: + HEAP8[p] = ret ? 1 : 0; + break; + } + }; + + /** @suppress {duplicate } */ + var _glGetBooleanv = (name_, p) => emscriptenWebGLGet(name_, p, 4); + _glGetBooleanv.sig = 'vip'; + var _emscripten_glGetBooleanv = _glGetBooleanv; + _emscripten_glGetBooleanv.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGetBufferParameteriv = (target, value, data) => { + if (!data) { + // GLES2 specification does not specify how to behave if data is a null + // pointer. Since calling this function does not make sense if data == + // null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + HEAP32[data >> 2] = GLctx.getBufferParameter(target, value); + }; + _glGetBufferParameteriv.sig = 'viip'; + var _emscripten_glGetBufferParameteriv = _glGetBufferParameteriv; + _emscripten_glGetBufferParameteriv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glGetError = () => { + var error = GLctx.getError() || GL.lastError; + GL.lastError = 0 /*GL_NO_ERROR*/; + return error; + }; + _glGetError.sig = 'i'; + var _emscripten_glGetError = _glGetError; + _emscripten_glGetError.sig = 'i'; + + /** @suppress {duplicate } */ + var _glGetFloatv = (name_, p) => emscriptenWebGLGet(name_, p, 2); + _glGetFloatv.sig = 'vip'; + var _emscripten_glGetFloatv = _glGetFloatv; + _emscripten_glGetFloatv.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGetFramebufferAttachmentParameteriv = ( + target, + attachment, + pname, + params + ) => { + var result = GLctx.getFramebufferAttachmentParameter( + target, + attachment, + pname + ); + if ( + result instanceof WebGLRenderbuffer || + result instanceof WebGLTexture + ) { + result = result.name | 0; + } + HEAP32[params >> 2] = result; + }; + _glGetFramebufferAttachmentParameteriv.sig = 'viiip'; + var _emscripten_glGetFramebufferAttachmentParameteriv = + _glGetFramebufferAttachmentParameteriv; + _emscripten_glGetFramebufferAttachmentParameteriv.sig = 'viiip'; + + /** @suppress {duplicate } */ + var _glGetIntegerv = (name_, p) => emscriptenWebGLGet(name_, p, 0); + _glGetIntegerv.sig = 'vip'; + var _emscripten_glGetIntegerv = _glGetIntegerv; + _emscripten_glGetIntegerv.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glGetProgramInfoLog = (program, maxLength, length, infoLog) => { + var log = GLctx.getProgramInfoLog(GL.programs[program]); + if (log === null) log = '(unknown error)'; + var numBytesWrittenExclNull = + maxLength > 0 && infoLog + ? stringToUTF8(log, infoLog, maxLength) + : 0; + if (length) HEAP32[length >> 2] = numBytesWrittenExclNull; + }; + _glGetProgramInfoLog.sig = 'viipp'; + var _emscripten_glGetProgramInfoLog = _glGetProgramInfoLog; + _emscripten_glGetProgramInfoLog.sig = 'viipp'; + + /** @suppress {duplicate } */ + var _glGetProgramiv = (program, pname, p) => { + if (!p) { + // GLES2 specification does not specify how to behave if p is a null + // pointer. Since calling this function does not make sense if p == null, + // issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + + if (program >= GL.counter) { + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + + program = GL.programs[program]; + + if (pname == 0x8b84) { + // GL_INFO_LOG_LENGTH + var log = GLctx.getProgramInfoLog(program); + if (log === null) log = '(unknown error)'; + HEAP32[p >> 2] = log.length + 1; + } else if (pname == 0x8b87 /* GL_ACTIVE_UNIFORM_MAX_LENGTH */) { + if (!program.maxUniformLength) { + var numActiveUniforms = GLctx.getProgramParameter( + program, + 0x8b86 /*GL_ACTIVE_UNIFORMS*/ + ); + for (var i = 0; i < numActiveUniforms; ++i) { + program.maxUniformLength = Math.max( + program.maxUniformLength, + GLctx.getActiveUniform(program, i).name.length + 1 + ); + } + } + HEAP32[p >> 2] = program.maxUniformLength; + } else if (pname == 0x8b8a /* GL_ACTIVE_ATTRIBUTE_MAX_LENGTH */) { + if (!program.maxAttributeLength) { + var numActiveAttributes = GLctx.getProgramParameter( + program, + 0x8b89 /*GL_ACTIVE_ATTRIBUTES*/ + ); + for (var i = 0; i < numActiveAttributes; ++i) { + program.maxAttributeLength = Math.max( + program.maxAttributeLength, + GLctx.getActiveAttrib(program, i).name.length + 1 + ); + } + } + HEAP32[p >> 2] = program.maxAttributeLength; + } else if ( + pname == 0x8a35 /* GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH */ + ) { + if (!program.maxUniformBlockNameLength) { + var numActiveUniformBlocks = GLctx.getProgramParameter( + program, + 0x8a36 /*GL_ACTIVE_UNIFORM_BLOCKS*/ + ); + for (var i = 0; i < numActiveUniformBlocks; ++i) { + program.maxUniformBlockNameLength = Math.max( + program.maxUniformBlockNameLength, + GLctx.getActiveUniformBlockName(program, i).length + 1 + ); + } + } + HEAP32[p >> 2] = program.maxUniformBlockNameLength; + } else { + HEAP32[p >> 2] = GLctx.getProgramParameter(program, pname); + } + }; + _glGetProgramiv.sig = 'viip'; + var _emscripten_glGetProgramiv = _glGetProgramiv; + _emscripten_glGetProgramiv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glGetQueryObjecti64vEXT = (id, pname, params) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense + // if p == null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + var query = GL.queries[id]; + var param; + { + param = GLctx.disjointTimerQueryExt['getQueryObjectEXT']( + query, + pname + ); + } + var ret; + if (typeof param == 'boolean') { + ret = param ? 1 : 0; + } else { + ret = param; + } + writeI53ToI64(params, ret); + }; + _glGetQueryObjecti64vEXT.sig = 'viip'; + var _emscripten_glGetQueryObjecti64vEXT = _glGetQueryObjecti64vEXT; + + /** @suppress {duplicate } */ + var _glGetQueryObjectivEXT = (id, pname, params) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense + // if p == null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + var query = GL.queries[id]; + var param = GLctx.disjointTimerQueryExt['getQueryObjectEXT']( + query, + pname + ); + var ret; + if (typeof param == 'boolean') { + ret = param ? 1 : 0; + } else { + ret = param; + } + HEAP32[params >> 2] = ret; + }; + _glGetQueryObjectivEXT.sig = 'viip'; + var _emscripten_glGetQueryObjectivEXT = _glGetQueryObjectivEXT; + + /** @suppress {duplicate } */ + var _glGetQueryObjectui64vEXT = _glGetQueryObjecti64vEXT; + var _emscripten_glGetQueryObjectui64vEXT = _glGetQueryObjectui64vEXT; + + /** @suppress {duplicate } */ + var _glGetQueryObjectuivEXT = _glGetQueryObjectivEXT; + var _emscripten_glGetQueryObjectuivEXT = _glGetQueryObjectuivEXT; + + /** @suppress {duplicate } */ + var _glGetQueryivEXT = (target, pname, params) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense + // if p == null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + HEAP32[params >> 2] = GLctx.disjointTimerQueryExt['getQueryEXT']( + target, + pname + ); + }; + _glGetQueryivEXT.sig = 'viip'; + var _emscripten_glGetQueryivEXT = _glGetQueryivEXT; + + /** @suppress {duplicate } */ + var _glGetRenderbufferParameteriv = (target, pname, params) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense + // if params == null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + HEAP32[params >> 2] = GLctx.getRenderbufferParameter(target, pname); + }; + _glGetRenderbufferParameteriv.sig = 'viip'; + var _emscripten_glGetRenderbufferParameteriv = + _glGetRenderbufferParameteriv; + _emscripten_glGetRenderbufferParameteriv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glGetShaderInfoLog = (shader, maxLength, length, infoLog) => { + var log = GLctx.getShaderInfoLog(GL.shaders[shader]); + if (log === null) log = '(unknown error)'; + var numBytesWrittenExclNull = + maxLength > 0 && infoLog + ? stringToUTF8(log, infoLog, maxLength) + : 0; + if (length) HEAP32[length >> 2] = numBytesWrittenExclNull; + }; + _glGetShaderInfoLog.sig = 'viipp'; + var _emscripten_glGetShaderInfoLog = _glGetShaderInfoLog; + _emscripten_glGetShaderInfoLog.sig = 'viipp'; + + /** @suppress {duplicate } */ + var _glGetShaderPrecisionFormat = ( + shaderType, + precisionType, + range, + precision + ) => { + var result = GLctx.getShaderPrecisionFormat(shaderType, precisionType); + HEAP32[range >> 2] = result.rangeMin; + HEAP32[(range + 4) >> 2] = result.rangeMax; + HEAP32[precision >> 2] = result.precision; + }; + _glGetShaderPrecisionFormat.sig = 'viipp'; + var _emscripten_glGetShaderPrecisionFormat = _glGetShaderPrecisionFormat; + _emscripten_glGetShaderPrecisionFormat.sig = 'viipp'; + + /** @suppress {duplicate } */ + var _glGetShaderSource = (shader, bufSize, length, source) => { + var result = GLctx.getShaderSource(GL.shaders[shader]); + if (!result) return; // If an error occurs, nothing will be written to length or source. + var numBytesWrittenExclNull = + bufSize > 0 && source ? stringToUTF8(result, source, bufSize) : 0; + if (length) HEAP32[length >> 2] = numBytesWrittenExclNull; + }; + _glGetShaderSource.sig = 'viipp'; + var _emscripten_glGetShaderSource = _glGetShaderSource; + _emscripten_glGetShaderSource.sig = 'viipp'; + + /** @suppress {duplicate } */ + var _glGetShaderiv = (shader, pname, p) => { + if (!p) { + // GLES2 specification does not specify how to behave if p is a null + // pointer. Since calling this function does not make sense if p == null, + // issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + if (pname == 0x8b84) { + // GL_INFO_LOG_LENGTH + var log = GLctx.getShaderInfoLog(GL.shaders[shader]); + if (log === null) log = '(unknown error)'; + // The GLES2 specification says that if the shader has an empty info log, + // a value of 0 is returned. Otherwise the log has a null char appended. + // (An empty string is falsey, so we can just check that instead of + // looking at log.length.) + var logLength = log ? log.length + 1 : 0; + HEAP32[p >> 2] = logLength; + } else if (pname == 0x8b88) { + // GL_SHADER_SOURCE_LENGTH + var source = GLctx.getShaderSource(GL.shaders[shader]); + // source may be a null, or the empty string, both of which are falsey + // values that we report a 0 length for. + var sourceLength = source ? source.length + 1 : 0; + HEAP32[p >> 2] = sourceLength; + } else { + HEAP32[p >> 2] = GLctx.getShaderParameter( + GL.shaders[shader], + pname + ); + } + }; + _glGetShaderiv.sig = 'viip'; + var _emscripten_glGetShaderiv = _glGetShaderiv; + _emscripten_glGetShaderiv.sig = 'viip'; + + var webglGetExtensions = () => { + var exts = getEmscriptenSupportedExtensions(GLctx); + exts = exts.concat(exts.map((e) => 'GL_' + e)); + return exts; + }; + + /** @suppress {duplicate } */ + var _glGetString = (name_) => { + var ret = GL.stringCache[name_]; + if (!ret) { + switch (name_) { + case 0x1f03 /* GL_EXTENSIONS */: + ret = stringToNewUTF8(webglGetExtensions().join(' ')); + break; + case 0x1f00 /* GL_VENDOR */: + case 0x1f01 /* GL_RENDERER */: + case 0x9245 /* UNMASKED_VENDOR_WEBGL */: + case 0x9246 /* UNMASKED_RENDERER_WEBGL */: + var s = GLctx.getParameter(name_); + if (!s) { + GL.recordError(0x500 /*GL_INVALID_ENUM*/); + } + ret = s ? stringToNewUTF8(s) : 0; + break; + + case 0x1f02 /* GL_VERSION */: + var webGLVersion = GLctx.getParameter( + 0x1f02 /*GL_VERSION*/ + ); + // return GLES version string corresponding to the version of the WebGL context + var glVersion = `OpenGL ES 2.0 (${webGLVersion})`; + ret = stringToNewUTF8(glVersion); + break; + case 0x8b8c /* GL_SHADING_LANGUAGE_VERSION */: + var glslVersion = GLctx.getParameter( + 0x8b8c /*GL_SHADING_LANGUAGE_VERSION*/ + ); + // extract the version number 'N.M' from the string 'WebGL GLSL ES N.M ...' + var ver_re = /^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/; + var ver_num = glslVersion.match(ver_re); + if (ver_num !== null) { + if (ver_num[1].length == 3) + ver_num[1] = ver_num[1] + '0'; // ensure minor version has 2 digits + glslVersion = `OpenGL ES GLSL ES ${ver_num[1]} (${glslVersion})`; + } + ret = stringToNewUTF8(glslVersion); + break; + default: + GL.recordError(0x500 /*GL_INVALID_ENUM*/); + // fall through + } + GL.stringCache[name_] = ret; + } + return ret; + }; + _glGetString.sig = 'pi'; + var _emscripten_glGetString = _glGetString; + _emscripten_glGetString.sig = 'pi'; + + /** @suppress {duplicate } */ + var _glGetTexParameterfv = (target, pname, params) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null + // pointer. Since calling this function does not make sense if p == null, + // issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + HEAPF32[params >> 2] = GLctx.getTexParameter(target, pname); + }; + _glGetTexParameterfv.sig = 'viip'; + var _emscripten_glGetTexParameterfv = _glGetTexParameterfv; + _emscripten_glGetTexParameterfv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glGetTexParameteriv = (target, pname, params) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null + // pointer. Since calling this function does not make sense if p == null, + // issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + HEAP32[params >> 2] = GLctx.getTexParameter(target, pname); + }; + _glGetTexParameteriv.sig = 'viip'; + var _emscripten_glGetTexParameteriv = _glGetTexParameteriv; + _emscripten_glGetTexParameteriv.sig = 'viip'; + + /** @suppress {checkTypes} */ + var jstoi_q = (str) => parseInt(str); + + /** @noinline */ + var webglGetLeftBracePos = (name) => + name.slice(-1) == ']' && name.lastIndexOf('['); + + var webglPrepareUniformLocationsBeforeFirstUse = (program) => { + var uniformLocsById = program.uniformLocsById, // Maps GLuint -> WebGLUniformLocation + uniformSizeAndIdsByName = program.uniformSizeAndIdsByName, // Maps name -> [uniform array length, GLuint] + i, + j; + + // On the first time invocation of glGetUniformLocation on this shader program: + // initialize cache data structures and discover which uniforms are arrays. + if (!uniformLocsById) { + // maps GLint integer locations to WebGLUniformLocations + program.uniformLocsById = uniformLocsById = {}; + // maps integer locations back to uniform name strings, so that we can lazily fetch uniform array locations + program.uniformArrayNamesById = {}; + + var numActiveUniforms = GLctx.getProgramParameter( + program, + 0x8b86 /*GL_ACTIVE_UNIFORMS*/ + ); + for (i = 0; i < numActiveUniforms; ++i) { + var u = GLctx.getActiveUniform(program, i); + var nm = u.name; + var sz = u.size; + var lb = webglGetLeftBracePos(nm); + var arrayName = lb > 0 ? nm.slice(0, lb) : nm; + + // Assign a new location. + var id = program.uniformIdCounter; + program.uniformIdCounter += sz; + // Eagerly get the location of the uniformArray[0] base element. + // The remaining indices >0 will be left for lazy evaluation to + // improve performance. Those may never be needed to fetch, if the + // application fills arrays always in full starting from the first + // element of the array. + uniformSizeAndIdsByName[arrayName] = [sz, id]; + + // Store placeholder integers in place that highlight that these + // >0 index locations are array indices pending population. + for (j = 0; j < sz; ++j) { + uniformLocsById[id] = j; + program.uniformArrayNamesById[id++] = arrayName; + } + } + } + }; + + /** @suppress {duplicate } */ + var _glGetUniformLocation = (program, name) => { + name = UTF8ToString(name); + + if ((program = GL.programs[program])) { + webglPrepareUniformLocationsBeforeFirstUse(program); + var uniformLocsById = program.uniformLocsById; // Maps GLuint -> WebGLUniformLocation + var arrayIndex = 0; + var uniformBaseName = name; + + // Invariant: when populating integer IDs for uniform locations, we must + // maintain the precondition that arrays reside in contiguous addresses, + // i.e. for a 'vec4 colors[10];', colors[4] must be at location + // colors[0]+4. However, user might call glGetUniformLocation(program, + // "colors") for an array, so we cannot discover based on the user input + // arguments whether the uniform we are dealing with is an array. The only + // way to discover which uniforms are arrays is to enumerate over all the + // active uniforms in the program. + var leftBrace = webglGetLeftBracePos(name); + + // If user passed an array accessor "[index]", parse the array index off the accessor. + if (leftBrace > 0) { + arrayIndex = jstoi_q(name.slice(leftBrace + 1)) >>> 0; // "index]", coerce parseInt(']') with >>>0 to treat "foo[]" as "foo[0]" and foo[-1] as unsigned out-of-bounds. + uniformBaseName = name.slice(0, leftBrace); + } + + // Have we cached the location of this uniform before? + // A pair [array length, GLint of the uniform location] + var sizeAndId = program.uniformSizeAndIdsByName[uniformBaseName]; + + // If an uniform with this name exists, and if its index is within the + // array limits (if it's even an array), query the WebGLlocation, or + // return an existing cached location. + if (sizeAndId && arrayIndex < sizeAndId[0]) { + arrayIndex += sizeAndId[1]; // Add the base location of the uniform to the array index offset. + if ( + (uniformLocsById[arrayIndex] = + uniformLocsById[arrayIndex] || + GLctx.getUniformLocation(program, name)) + ) { + return arrayIndex; + } + } + } else { + // N.b. we are currently unable to distinguish between GL program IDs that + // never existed vs GL program IDs that have been deleted, so report + // GL_INVALID_VALUE in both cases. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + } + return -1; + }; + _glGetUniformLocation.sig = 'iip'; + var _emscripten_glGetUniformLocation = _glGetUniformLocation; + _emscripten_glGetUniformLocation.sig = 'iip'; + + var webglGetUniformLocation = (location) => { + var p = GLctx.currentProgram; + + if (p) { + var webglLoc = p.uniformLocsById[location]; + // p.uniformLocsById[location] stores either an integer, or a + // WebGLUniformLocation. + // If an integer, we have not yet bound the location, so do it now. The + // integer value specifies the array index we should bind to. + if (typeof webglLoc == 'number') { + p.uniformLocsById[location] = webglLoc = + GLctx.getUniformLocation( + p, + p.uniformArrayNamesById[location] + + (webglLoc > 0 ? `[${webglLoc}]` : '') + ); + } + // Else an already cached WebGLUniformLocation, return it. + return webglLoc; + } else { + GL.recordError(0x502 /*GL_INVALID_OPERATION*/); + } + }; + + /** @suppress{checkTypes} */ + var emscriptenWebGLGetUniform = (program, location, params, type) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null + // pointer. Since calling this function does not make sense if params == + // null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + program = GL.programs[program]; + webglPrepareUniformLocationsBeforeFirstUse(program); + var data = GLctx.getUniform(program, webglGetUniformLocation(location)); + if (typeof data == 'number' || typeof data == 'boolean') { + switch (type) { + case 0: + HEAP32[params >> 2] = data; + break; + case 2: + HEAPF32[params >> 2] = data; + break; + } + } else { + for (var i = 0; i < data.length; i++) { + switch (type) { + case 0: + HEAP32[(params + i * 4) >> 2] = data[i]; + break; + case 2: + HEAPF32[(params + i * 4) >> 2] = data[i]; + break; + } + } + } + }; + + /** @suppress {duplicate } */ + var _glGetUniformfv = (program, location, params) => { + emscriptenWebGLGetUniform(program, location, params, 2); + }; + _glGetUniformfv.sig = 'viip'; + var _emscripten_glGetUniformfv = _glGetUniformfv; + _emscripten_glGetUniformfv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glGetUniformiv = (program, location, params) => { + emscriptenWebGLGetUniform(program, location, params, 0); + }; + _glGetUniformiv.sig = 'viip'; + var _emscripten_glGetUniformiv = _glGetUniformiv; + _emscripten_glGetUniformiv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glGetVertexAttribPointerv = (index, pname, pointer) => { + if (!pointer) { + // GLES2 specification does not specify how to behave if pointer is a null + // pointer. Since calling this function does not make sense if pointer == + // null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + HEAP32[pointer >> 2] = GLctx.getVertexAttribOffset(index, pname); + }; + _glGetVertexAttribPointerv.sig = 'viip'; + var _emscripten_glGetVertexAttribPointerv = _glGetVertexAttribPointerv; + _emscripten_glGetVertexAttribPointerv.sig = 'viip'; + + /** @suppress{checkTypes} */ + var emscriptenWebGLGetVertexAttrib = (index, pname, params, type) => { + if (!params) { + // GLES2 specification does not specify how to behave if params is a null + // pointer. Since calling this function does not make sense if params == + // null, issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + var data = GLctx.getVertexAttrib(index, pname); + if (pname == 0x889f /*VERTEX_ATTRIB_ARRAY_BUFFER_BINDING*/) { + HEAP32[params >> 2] = data && data['name']; + } else if (typeof data == 'number' || typeof data == 'boolean') { + switch (type) { + case 0: + HEAP32[params >> 2] = data; + break; + case 2: + HEAPF32[params >> 2] = data; + break; + case 5: + HEAP32[params >> 2] = Math.fround(data); + break; + } + } else { + for (var i = 0; i < data.length; i++) { + switch (type) { + case 0: + HEAP32[(params + i * 4) >> 2] = data[i]; + break; + case 2: + HEAPF32[(params + i * 4) >> 2] = data[i]; + break; + case 5: + HEAP32[(params + i * 4) >> 2] = Math.fround(data[i]); + break; + } + } + } + }; + + /** @suppress {duplicate } */ + var _glGetVertexAttribfv = (index, pname, params) => { + // N.B. This function may only be called if the vertex attribute was + // specified using the function glVertexAttrib*f(), otherwise the results + // are undefined. (GLES3 spec 6.1.12) + emscriptenWebGLGetVertexAttrib(index, pname, params, 2); + }; + _glGetVertexAttribfv.sig = 'viip'; + var _emscripten_glGetVertexAttribfv = _glGetVertexAttribfv; + _emscripten_glGetVertexAttribfv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glGetVertexAttribiv = (index, pname, params) => { + // N.B. This function may only be called if the vertex attribute was + // specified using the function glVertexAttrib*f(), otherwise the results + // are undefined. (GLES3 spec 6.1.12) + emscriptenWebGLGetVertexAttrib(index, pname, params, 5); + }; + _glGetVertexAttribiv.sig = 'viip'; + var _emscripten_glGetVertexAttribiv = _glGetVertexAttribiv; + _emscripten_glGetVertexAttribiv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glHint = (x0, x1) => GLctx.hint(x0, x1); + _glHint.sig = 'vii'; + var _emscripten_glHint = _glHint; + _emscripten_glHint.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glIsBuffer = (buffer) => { + var b = GL.buffers[buffer]; + if (!b) return 0; + return GLctx.isBuffer(b); + }; + _glIsBuffer.sig = 'ii'; + var _emscripten_glIsBuffer = _glIsBuffer; + _emscripten_glIsBuffer.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glIsEnabled = (x0) => GLctx.isEnabled(x0); + _glIsEnabled.sig = 'ii'; + var _emscripten_glIsEnabled = _glIsEnabled; + _emscripten_glIsEnabled.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glIsFramebuffer = (framebuffer) => { + var fb = GL.framebuffers[framebuffer]; + if (!fb) return 0; + return GLctx.isFramebuffer(fb); + }; + _glIsFramebuffer.sig = 'ii'; + var _emscripten_glIsFramebuffer = _glIsFramebuffer; + _emscripten_glIsFramebuffer.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glIsProgram = (program) => { + program = GL.programs[program]; + if (!program) return 0; + return GLctx.isProgram(program); + }; + _glIsProgram.sig = 'ii'; + var _emscripten_glIsProgram = _glIsProgram; + _emscripten_glIsProgram.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glIsQueryEXT = (id) => { + var query = GL.queries[id]; + if (!query) return 0; + return GLctx.disjointTimerQueryExt['isQueryEXT'](query); + }; + _glIsQueryEXT.sig = 'ii'; + var _emscripten_glIsQueryEXT = _glIsQueryEXT; + + /** @suppress {duplicate } */ + var _glIsRenderbuffer = (renderbuffer) => { + var rb = GL.renderbuffers[renderbuffer]; + if (!rb) return 0; + return GLctx.isRenderbuffer(rb); + }; + _glIsRenderbuffer.sig = 'ii'; + var _emscripten_glIsRenderbuffer = _glIsRenderbuffer; + _emscripten_glIsRenderbuffer.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glIsShader = (shader) => { + var s = GL.shaders[shader]; + if (!s) return 0; + return GLctx.isShader(s); + }; + _glIsShader.sig = 'ii'; + var _emscripten_glIsShader = _glIsShader; + _emscripten_glIsShader.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glIsTexture = (id) => { + var texture = GL.textures[id]; + if (!texture) return 0; + return GLctx.isTexture(texture); + }; + _glIsTexture.sig = 'ii'; + var _emscripten_glIsTexture = _glIsTexture; + _emscripten_glIsTexture.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glIsVertexArray = (array) => { + var vao = GL.vaos[array]; + if (!vao) return 0; + return GLctx.isVertexArray(vao); + }; + _glIsVertexArray.sig = 'ii'; + /** @suppress {duplicate } */ + var _glIsVertexArrayOES = _glIsVertexArray; + _glIsVertexArrayOES.sig = 'ii'; + var _emscripten_glIsVertexArrayOES = _glIsVertexArrayOES; + _emscripten_glIsVertexArrayOES.sig = 'ii'; + + /** @suppress {duplicate } */ + var _glLineWidth = (x0) => GLctx.lineWidth(x0); + _glLineWidth.sig = 'vf'; + var _emscripten_glLineWidth = _glLineWidth; + _emscripten_glLineWidth.sig = 'vf'; + + /** @suppress {duplicate } */ + var _glLinkProgram = (program) => { + program = GL.programs[program]; + GLctx.linkProgram(program); + // Invalidate earlier computed uniform->ID mappings, those have now become stale + program.uniformLocsById = 0; // Mark as null-like so that glGetUniformLocation() knows to populate this again. + program.uniformSizeAndIdsByName = {}; + }; + _glLinkProgram.sig = 'vi'; + var _emscripten_glLinkProgram = _glLinkProgram; + _emscripten_glLinkProgram.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glPixelStorei = (pname, param) => { + if (pname == 3317) { + GL.unpackAlignment = param; + } else if (pname == 3314) { + GL.unpackRowLength = param; + } + GLctx.pixelStorei(pname, param); + }; + _glPixelStorei.sig = 'vii'; + var _emscripten_glPixelStorei = _glPixelStorei; + _emscripten_glPixelStorei.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glPolygonModeWEBGL = (face, mode) => { + GLctx.webglPolygonMode['polygonModeWEBGL'](face, mode); + }; + _glPolygonModeWEBGL.sig = 'vii'; + var _emscripten_glPolygonModeWEBGL = _glPolygonModeWEBGL; + + /** @suppress {duplicate } */ + var _glPolygonOffset = (x0, x1) => GLctx.polygonOffset(x0, x1); + _glPolygonOffset.sig = 'vff'; + var _emscripten_glPolygonOffset = _glPolygonOffset; + _emscripten_glPolygonOffset.sig = 'vff'; + + /** @suppress {duplicate } */ + var _glPolygonOffsetClampEXT = (factor, units, clamp) => { + GLctx.extPolygonOffsetClamp['polygonOffsetClampEXT']( + factor, + units, + clamp + ); + }; + _glPolygonOffsetClampEXT.sig = 'vfff'; + var _emscripten_glPolygonOffsetClampEXT = _glPolygonOffsetClampEXT; + + /** @suppress {duplicate } */ + var _glQueryCounterEXT = (id, target) => { + GLctx.disjointTimerQueryExt['queryCounterEXT'](GL.queries[id], target); + }; + _glQueryCounterEXT.sig = 'vii'; + var _emscripten_glQueryCounterEXT = _glQueryCounterEXT; + + var computeUnpackAlignedImageSize = (width, height, sizePerPixel) => { + function roundedToNextMultipleOf(x, y) { + return (x + y - 1) & -y; + } + var plainRowSize = (GL.unpackRowLength || width) * sizePerPixel; + var alignedRowSize = roundedToNextMultipleOf( + plainRowSize, + GL.unpackAlignment + ); + return height * alignedRowSize; + }; + + var colorChannelsInGlTextureFormat = (format) => { + // Micro-optimizations for size: map format to size by subtracting smallest + // enum value (0x1902) from all values first. Also omit the most common + // size value (1) from the list, which is assumed by formats not on the + // list. + var colorChannels = { + // 0x1902 /* GL_DEPTH_COMPONENT */ - 0x1902: 1, + // 0x1906 /* GL_ALPHA */ - 0x1902: 1, + 5: 3, + 6: 4, + // 0x1909 /* GL_LUMINANCE */ - 0x1902: 1, + 8: 2, + 29502: 3, + 29504: 4, + }; + return colorChannels[format - 0x1902] || 1; + }; + + var heapObjectForWebGLType = (type) => { + // Micro-optimization for size: Subtract lowest GL enum number (0x1400/* GL_BYTE */) from type to compare + // smaller values for the heap, for shorter generated code size. + // Also the type HEAPU16 is not tested for explicitly, but any unrecognized type will return out HEAPU16. + // (since most types are HEAPU16) + type -= 0x1400; + + if (type == 1) return HEAPU8; + + if (type == 4) return HEAP32; + + if (type == 6) return HEAPF32; + + if (type == 5 || type == 28922) return HEAPU32; + + return HEAPU16; + }; + + var toTypedArrayIndex = (pointer, heap) => + pointer >>> (31 - Math.clz32(heap.BYTES_PER_ELEMENT)); + + var emscriptenWebGLGetTexPixelData = ( + type, + format, + width, + height, + pixels, + internalFormat + ) => { + var heap = heapObjectForWebGLType(type); + var sizePerPixel = + colorChannelsInGlTextureFormat(format) * heap.BYTES_PER_ELEMENT; + var bytes = computeUnpackAlignedImageSize(width, height, sizePerPixel); + return heap.subarray( + toTypedArrayIndex(pixels, heap), + toTypedArrayIndex(pixels + bytes, heap) + ); + }; + + /** @suppress {duplicate } */ + var _glReadPixels = (x, y, width, height, format, type, pixels) => { + var pixelData = emscriptenWebGLGetTexPixelData( + type, + format, + width, + height, + pixels, + format + ); + if (!pixelData) { + GL.recordError(0x500 /*GL_INVALID_ENUM*/); + return; + } + GLctx.readPixels(x, y, width, height, format, type, pixelData); + }; + _glReadPixels.sig = 'viiiiiip'; + var _emscripten_glReadPixels = _glReadPixels; + _emscripten_glReadPixels.sig = 'viiiiiip'; + + /** @suppress {duplicate } */ + var _glReleaseShaderCompiler = () => { + // NOP (as allowed by GLES 2.0 spec) + }; + _glReleaseShaderCompiler.sig = 'v'; + var _emscripten_glReleaseShaderCompiler = _glReleaseShaderCompiler; + _emscripten_glReleaseShaderCompiler.sig = 'v'; + + /** @suppress {duplicate } */ + var _glRenderbufferStorage = (x0, x1, x2, x3) => + GLctx.renderbufferStorage(x0, x1, x2, x3); + _glRenderbufferStorage.sig = 'viiii'; + var _emscripten_glRenderbufferStorage = _glRenderbufferStorage; + _emscripten_glRenderbufferStorage.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glSampleCoverage = (value, invert) => { + GLctx.sampleCoverage(value, !!invert); + }; + _glSampleCoverage.sig = 'vfi'; + var _emscripten_glSampleCoverage = _glSampleCoverage; + _emscripten_glSampleCoverage.sig = 'vfi'; + + /** @suppress {duplicate } */ + var _glScissor = (x0, x1, x2, x3) => GLctx.scissor(x0, x1, x2, x3); + _glScissor.sig = 'viiii'; + var _emscripten_glScissor = _glScissor; + _emscripten_glScissor.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glShaderBinary = (count, shaders, binaryformat, binary, length) => { + GL.recordError(0x500 /*GL_INVALID_ENUM*/); + }; + _glShaderBinary.sig = 'vipipi'; + var _emscripten_glShaderBinary = _glShaderBinary; + _emscripten_glShaderBinary.sig = 'vipipi'; + + /** @suppress {duplicate } */ + var _glShaderSource = (shader, count, string, length) => { + var source = GL.getSource(shader, count, string, length); + + GLctx.shaderSource(GL.shaders[shader], source); + }; + _glShaderSource.sig = 'viipp'; + var _emscripten_glShaderSource = _glShaderSource; + _emscripten_glShaderSource.sig = 'viipp'; + + /** @suppress {duplicate } */ + var _glStencilFunc = (x0, x1, x2) => GLctx.stencilFunc(x0, x1, x2); + _glStencilFunc.sig = 'viii'; + var _emscripten_glStencilFunc = _glStencilFunc; + _emscripten_glStencilFunc.sig = 'viii'; + + /** @suppress {duplicate } */ + var _glStencilFuncSeparate = (x0, x1, x2, x3) => + GLctx.stencilFuncSeparate(x0, x1, x2, x3); + _glStencilFuncSeparate.sig = 'viiii'; + var _emscripten_glStencilFuncSeparate = _glStencilFuncSeparate; + _emscripten_glStencilFuncSeparate.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glStencilMask = (x0) => GLctx.stencilMask(x0); + _glStencilMask.sig = 'vi'; + var _emscripten_glStencilMask = _glStencilMask; + _emscripten_glStencilMask.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glStencilMaskSeparate = (x0, x1) => GLctx.stencilMaskSeparate(x0, x1); + _glStencilMaskSeparate.sig = 'vii'; + var _emscripten_glStencilMaskSeparate = _glStencilMaskSeparate; + _emscripten_glStencilMaskSeparate.sig = 'vii'; + + /** @suppress {duplicate } */ + var _glStencilOp = (x0, x1, x2) => GLctx.stencilOp(x0, x1, x2); + _glStencilOp.sig = 'viii'; + var _emscripten_glStencilOp = _glStencilOp; + _emscripten_glStencilOp.sig = 'viii'; + + /** @suppress {duplicate } */ + var _glStencilOpSeparate = (x0, x1, x2, x3) => + GLctx.stencilOpSeparate(x0, x1, x2, x3); + _glStencilOpSeparate.sig = 'viiii'; + var _emscripten_glStencilOpSeparate = _glStencilOpSeparate; + _emscripten_glStencilOpSeparate.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glTexImage2D = ( + target, + level, + internalFormat, + width, + height, + border, + format, + type, + pixels + ) => { + var pixelData = pixels + ? emscriptenWebGLGetTexPixelData( + type, + format, + width, + height, + pixels, + internalFormat + ) + : null; + GLctx.texImage2D( + target, + level, + internalFormat, + width, + height, + border, + format, + type, + pixelData + ); + }; + _glTexImage2D.sig = 'viiiiiiiip'; + var _emscripten_glTexImage2D = _glTexImage2D; + _emscripten_glTexImage2D.sig = 'viiiiiiiip'; + + /** @suppress {duplicate } */ + var _glTexParameterf = (x0, x1, x2) => GLctx.texParameterf(x0, x1, x2); + _glTexParameterf.sig = 'viif'; + var _emscripten_glTexParameterf = _glTexParameterf; + _emscripten_glTexParameterf.sig = 'viif'; + + /** @suppress {duplicate } */ + var _glTexParameterfv = (target, pname, params) => { + var param = HEAPF32[params >> 2]; + GLctx.texParameterf(target, pname, param); + }; + _glTexParameterfv.sig = 'viip'; + var _emscripten_glTexParameterfv = _glTexParameterfv; + _emscripten_glTexParameterfv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glTexParameteri = (x0, x1, x2) => GLctx.texParameteri(x0, x1, x2); + _glTexParameteri.sig = 'viii'; + var _emscripten_glTexParameteri = _glTexParameteri; + _emscripten_glTexParameteri.sig = 'viii'; + + /** @suppress {duplicate } */ + var _glTexParameteriv = (target, pname, params) => { + var param = HEAP32[params >> 2]; + GLctx.texParameteri(target, pname, param); + }; + _glTexParameteriv.sig = 'viip'; + var _emscripten_glTexParameteriv = _glTexParameteriv; + _emscripten_glTexParameteriv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glTexSubImage2D = ( + target, + level, + xoffset, + yoffset, + width, + height, + format, + type, + pixels + ) => { + var pixelData = pixels + ? emscriptenWebGLGetTexPixelData( + type, + format, + width, + height, + pixels, + 0 + ) + : null; + GLctx.texSubImage2D( + target, + level, + xoffset, + yoffset, + width, + height, + format, + type, + pixelData + ); + }; + _glTexSubImage2D.sig = 'viiiiiiiip'; + var _emscripten_glTexSubImage2D = _glTexSubImage2D; + _emscripten_glTexSubImage2D.sig = 'viiiiiiiip'; + + /** @suppress {duplicate } */ + var _glUniform1f = (location, v0) => { + GLctx.uniform1f(webglGetUniformLocation(location), v0); + }; + _glUniform1f.sig = 'vif'; + var _emscripten_glUniform1f = _glUniform1f; + _emscripten_glUniform1f.sig = 'vif'; + + var miniTempWebGLFloatBuffers = []; + + /** @suppress {duplicate } */ + var _glUniform1fv = (location, count, value) => { + if (count <= 288) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; ++i) { + view[i] = HEAPF32[(value + 4 * i) >> 2]; + } + } else { + var view = HEAPF32.subarray(value >> 2, (value + count * 4) >> 2); + } + GLctx.uniform1fv(webglGetUniformLocation(location), view); + }; + _glUniform1fv.sig = 'viip'; + var _emscripten_glUniform1fv = _glUniform1fv; + _emscripten_glUniform1fv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniform1i = (location, v0) => { + GLctx.uniform1i(webglGetUniformLocation(location), v0); + }; + _glUniform1i.sig = 'vii'; + var _emscripten_glUniform1i = _glUniform1i; + _emscripten_glUniform1i.sig = 'vii'; + + var miniTempWebGLIntBuffers = []; + + /** @suppress {duplicate } */ + var _glUniform1iv = (location, count, value) => { + if (count <= 288) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; ++i) { + view[i] = HEAP32[(value + 4 * i) >> 2]; + } + } else { + var view = HEAP32.subarray(value >> 2, (value + count * 4) >> 2); + } + GLctx.uniform1iv(webglGetUniformLocation(location), view); + }; + _glUniform1iv.sig = 'viip'; + var _emscripten_glUniform1iv = _glUniform1iv; + _emscripten_glUniform1iv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniform2f = (location, v0, v1) => { + GLctx.uniform2f(webglGetUniformLocation(location), v0, v1); + }; + _glUniform2f.sig = 'viff'; + var _emscripten_glUniform2f = _glUniform2f; + _emscripten_glUniform2f.sig = 'viff'; + + /** @suppress {duplicate } */ + var _glUniform2fv = (location, count, value) => { + if (count <= 144) { + // avoid allocation when uploading few enough uniforms + count *= 2; + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; i += 2) { + view[i] = HEAPF32[(value + 4 * i) >> 2]; + view[i + 1] = HEAPF32[(value + (4 * i + 4)) >> 2]; + } + } else { + var view = HEAPF32.subarray(value >> 2, (value + count * 8) >> 2); + } + GLctx.uniform2fv(webglGetUniformLocation(location), view); + }; + _glUniform2fv.sig = 'viip'; + var _emscripten_glUniform2fv = _glUniform2fv; + _emscripten_glUniform2fv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniform2i = (location, v0, v1) => { + GLctx.uniform2i(webglGetUniformLocation(location), v0, v1); + }; + _glUniform2i.sig = 'viii'; + var _emscripten_glUniform2i = _glUniform2i; + _emscripten_glUniform2i.sig = 'viii'; + + /** @suppress {duplicate } */ + var _glUniform2iv = (location, count, value) => { + if (count <= 144) { + // avoid allocation when uploading few enough uniforms + count *= 2; + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; i += 2) { + view[i] = HEAP32[(value + 4 * i) >> 2]; + view[i + 1] = HEAP32[(value + (4 * i + 4)) >> 2]; + } + } else { + var view = HEAP32.subarray(value >> 2, (value + count * 8) >> 2); + } + GLctx.uniform2iv(webglGetUniformLocation(location), view); + }; + _glUniform2iv.sig = 'viip'; + var _emscripten_glUniform2iv = _glUniform2iv; + _emscripten_glUniform2iv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniform3f = (location, v0, v1, v2) => { + GLctx.uniform3f(webglGetUniformLocation(location), v0, v1, v2); + }; + _glUniform3f.sig = 'vifff'; + var _emscripten_glUniform3f = _glUniform3f; + _emscripten_glUniform3f.sig = 'vifff'; + + /** @suppress {duplicate } */ + var _glUniform3fv = (location, count, value) => { + if (count <= 96) { + // avoid allocation when uploading few enough uniforms + count *= 3; + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; i += 3) { + view[i] = HEAPF32[(value + 4 * i) >> 2]; + view[i + 1] = HEAPF32[(value + (4 * i + 4)) >> 2]; + view[i + 2] = HEAPF32[(value + (4 * i + 8)) >> 2]; + } + } else { + var view = HEAPF32.subarray(value >> 2, (value + count * 12) >> 2); + } + GLctx.uniform3fv(webglGetUniformLocation(location), view); + }; + _glUniform3fv.sig = 'viip'; + var _emscripten_glUniform3fv = _glUniform3fv; + _emscripten_glUniform3fv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniform3i = (location, v0, v1, v2) => { + GLctx.uniform3i(webglGetUniformLocation(location), v0, v1, v2); + }; + _glUniform3i.sig = 'viiii'; + var _emscripten_glUniform3i = _glUniform3i; + _emscripten_glUniform3i.sig = 'viiii'; + + /** @suppress {duplicate } */ + var _glUniform3iv = (location, count, value) => { + if (count <= 96) { + // avoid allocation when uploading few enough uniforms + count *= 3; + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; i += 3) { + view[i] = HEAP32[(value + 4 * i) >> 2]; + view[i + 1] = HEAP32[(value + (4 * i + 4)) >> 2]; + view[i + 2] = HEAP32[(value + (4 * i + 8)) >> 2]; + } + } else { + var view = HEAP32.subarray(value >> 2, (value + count * 12) >> 2); + } + GLctx.uniform3iv(webglGetUniformLocation(location), view); + }; + _glUniform3iv.sig = 'viip'; + var _emscripten_glUniform3iv = _glUniform3iv; + _emscripten_glUniform3iv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniform4f = (location, v0, v1, v2, v3) => { + GLctx.uniform4f(webglGetUniformLocation(location), v0, v1, v2, v3); + }; + _glUniform4f.sig = 'viffff'; + var _emscripten_glUniform4f = _glUniform4f; + _emscripten_glUniform4f.sig = 'viffff'; + + /** @suppress {duplicate } */ + var _glUniform4fv = (location, count, value) => { + if (count <= 72) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLFloatBuffers[4 * count]; + // hoist the heap out of the loop for size and for pthreads+growth. + var heap = HEAPF32; + value = value >> 2; + count *= 4; + for (var i = 0; i < count; i += 4) { + var dst = value + i; + view[i] = heap[dst]; + view[i + 1] = heap[dst + 1]; + view[i + 2] = heap[dst + 2]; + view[i + 3] = heap[dst + 3]; + } + } else { + var view = HEAPF32.subarray(value >> 2, (value + count * 16) >> 2); + } + GLctx.uniform4fv(webglGetUniformLocation(location), view); + }; + _glUniform4fv.sig = 'viip'; + var _emscripten_glUniform4fv = _glUniform4fv; + _emscripten_glUniform4fv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniform4i = (location, v0, v1, v2, v3) => { + GLctx.uniform4i(webglGetUniformLocation(location), v0, v1, v2, v3); + }; + _glUniform4i.sig = 'viiiii'; + var _emscripten_glUniform4i = _glUniform4i; + _emscripten_glUniform4i.sig = 'viiiii'; + + /** @suppress {duplicate } */ + var _glUniform4iv = (location, count, value) => { + if (count <= 72) { + // avoid allocation when uploading few enough uniforms + count *= 4; + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; i += 4) { + view[i] = HEAP32[(value + 4 * i) >> 2]; + view[i + 1] = HEAP32[(value + (4 * i + 4)) >> 2]; + view[i + 2] = HEAP32[(value + (4 * i + 8)) >> 2]; + view[i + 3] = HEAP32[(value + (4 * i + 12)) >> 2]; + } + } else { + var view = HEAP32.subarray(value >> 2, (value + count * 16) >> 2); + } + GLctx.uniform4iv(webglGetUniformLocation(location), view); + }; + _glUniform4iv.sig = 'viip'; + var _emscripten_glUniform4iv = _glUniform4iv; + _emscripten_glUniform4iv.sig = 'viip'; + + /** @suppress {duplicate } */ + var _glUniformMatrix2fv = (location, count, transpose, value) => { + if (count <= 72) { + // avoid allocation when uploading few enough uniforms + count *= 4; + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; i += 4) { + view[i] = HEAPF32[(value + 4 * i) >> 2]; + view[i + 1] = HEAPF32[(value + (4 * i + 4)) >> 2]; + view[i + 2] = HEAPF32[(value + (4 * i + 8)) >> 2]; + view[i + 3] = HEAPF32[(value + (4 * i + 12)) >> 2]; + } + } else { + var view = HEAPF32.subarray(value >> 2, (value + count * 16) >> 2); + } + GLctx.uniformMatrix2fv( + webglGetUniformLocation(location), + !!transpose, + view + ); + }; + _glUniformMatrix2fv.sig = 'viiip'; + var _emscripten_glUniformMatrix2fv = _glUniformMatrix2fv; + _emscripten_glUniformMatrix2fv.sig = 'viiip'; + + /** @suppress {duplicate } */ + var _glUniformMatrix3fv = (location, count, transpose, value) => { + if (count <= 32) { + // avoid allocation when uploading few enough uniforms + count *= 9; + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; i += 9) { + view[i] = HEAPF32[(value + 4 * i) >> 2]; + view[i + 1] = HEAPF32[(value + (4 * i + 4)) >> 2]; + view[i + 2] = HEAPF32[(value + (4 * i + 8)) >> 2]; + view[i + 3] = HEAPF32[(value + (4 * i + 12)) >> 2]; + view[i + 4] = HEAPF32[(value + (4 * i + 16)) >> 2]; + view[i + 5] = HEAPF32[(value + (4 * i + 20)) >> 2]; + view[i + 6] = HEAPF32[(value + (4 * i + 24)) >> 2]; + view[i + 7] = HEAPF32[(value + (4 * i + 28)) >> 2]; + view[i + 8] = HEAPF32[(value + (4 * i + 32)) >> 2]; + } + } else { + var view = HEAPF32.subarray(value >> 2, (value + count * 36) >> 2); + } + GLctx.uniformMatrix3fv( + webglGetUniformLocation(location), + !!transpose, + view + ); + }; + _glUniformMatrix3fv.sig = 'viiip'; + var _emscripten_glUniformMatrix3fv = _glUniformMatrix3fv; + _emscripten_glUniformMatrix3fv.sig = 'viiip'; + + /** @suppress {duplicate } */ + var _glUniformMatrix4fv = (location, count, transpose, value) => { + if (count <= 18) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLFloatBuffers[16 * count]; + // hoist the heap out of the loop for size and for pthreads+growth. + var heap = HEAPF32; + value = value >> 2; + count *= 16; + for (var i = 0; i < count; i += 16) { + var dst = value + i; + view[i] = heap[dst]; + view[i + 1] = heap[dst + 1]; + view[i + 2] = heap[dst + 2]; + view[i + 3] = heap[dst + 3]; + view[i + 4] = heap[dst + 4]; + view[i + 5] = heap[dst + 5]; + view[i + 6] = heap[dst + 6]; + view[i + 7] = heap[dst + 7]; + view[i + 8] = heap[dst + 8]; + view[i + 9] = heap[dst + 9]; + view[i + 10] = heap[dst + 10]; + view[i + 11] = heap[dst + 11]; + view[i + 12] = heap[dst + 12]; + view[i + 13] = heap[dst + 13]; + view[i + 14] = heap[dst + 14]; + view[i + 15] = heap[dst + 15]; + } + } else { + var view = HEAPF32.subarray(value >> 2, (value + count * 64) >> 2); + } + GLctx.uniformMatrix4fv( + webglGetUniformLocation(location), + !!transpose, + view + ); + }; + _glUniformMatrix4fv.sig = 'viiip'; + var _emscripten_glUniformMatrix4fv = _glUniformMatrix4fv; + _emscripten_glUniformMatrix4fv.sig = 'viiip'; + + /** @suppress {duplicate } */ + var _glUseProgram = (program) => { + program = GL.programs[program]; + GLctx.useProgram(program); + // Record the currently active program so that we can access the uniform + // mapping table of that program. + GLctx.currentProgram = program; + }; + _glUseProgram.sig = 'vi'; + var _emscripten_glUseProgram = _glUseProgram; + _emscripten_glUseProgram.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glValidateProgram = (program) => { + GLctx.validateProgram(GL.programs[program]); + }; + _glValidateProgram.sig = 'vi'; + var _emscripten_glValidateProgram = _glValidateProgram; + _emscripten_glValidateProgram.sig = 'vi'; + + /** @suppress {duplicate } */ + var _glVertexAttrib1f = (x0, x1) => GLctx.vertexAttrib1f(x0, x1); + _glVertexAttrib1f.sig = 'vif'; + var _emscripten_glVertexAttrib1f = _glVertexAttrib1f; + _emscripten_glVertexAttrib1f.sig = 'vif'; + + /** @suppress {duplicate } */ + var _glVertexAttrib1fv = (index, v) => { + GLctx.vertexAttrib1f(index, HEAPF32[v >> 2]); + }; + _glVertexAttrib1fv.sig = 'vip'; + var _emscripten_glVertexAttrib1fv = _glVertexAttrib1fv; + _emscripten_glVertexAttrib1fv.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glVertexAttrib2f = (x0, x1, x2) => GLctx.vertexAttrib2f(x0, x1, x2); + _glVertexAttrib2f.sig = 'viff'; + var _emscripten_glVertexAttrib2f = _glVertexAttrib2f; + _emscripten_glVertexAttrib2f.sig = 'viff'; + + /** @suppress {duplicate } */ + var _glVertexAttrib2fv = (index, v) => { + GLctx.vertexAttrib2f(index, HEAPF32[v >> 2], HEAPF32[(v + 4) >> 2]); + }; + _glVertexAttrib2fv.sig = 'vip'; + var _emscripten_glVertexAttrib2fv = _glVertexAttrib2fv; + _emscripten_glVertexAttrib2fv.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glVertexAttrib3f = (x0, x1, x2, x3) => + GLctx.vertexAttrib3f(x0, x1, x2, x3); + _glVertexAttrib3f.sig = 'vifff'; + var _emscripten_glVertexAttrib3f = _glVertexAttrib3f; + _emscripten_glVertexAttrib3f.sig = 'vifff'; + + /** @suppress {duplicate } */ + var _glVertexAttrib3fv = (index, v) => { + GLctx.vertexAttrib3f( + index, + HEAPF32[v >> 2], + HEAPF32[(v + 4) >> 2], + HEAPF32[(v + 8) >> 2] + ); + }; + _glVertexAttrib3fv.sig = 'vip'; + var _emscripten_glVertexAttrib3fv = _glVertexAttrib3fv; + _emscripten_glVertexAttrib3fv.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glVertexAttrib4f = (x0, x1, x2, x3, x4) => + GLctx.vertexAttrib4f(x0, x1, x2, x3, x4); + _glVertexAttrib4f.sig = 'viffff'; + var _emscripten_glVertexAttrib4f = _glVertexAttrib4f; + _emscripten_glVertexAttrib4f.sig = 'viffff'; + + /** @suppress {duplicate } */ + var _glVertexAttrib4fv = (index, v) => { + GLctx.vertexAttrib4f( + index, + HEAPF32[v >> 2], + HEAPF32[(v + 4) >> 2], + HEAPF32[(v + 8) >> 2], + HEAPF32[(v + 12) >> 2] + ); + }; + _glVertexAttrib4fv.sig = 'vip'; + var _emscripten_glVertexAttrib4fv = _glVertexAttrib4fv; + _emscripten_glVertexAttrib4fv.sig = 'vip'; + + /** @suppress {duplicate } */ + var _glVertexAttribDivisor = (index, divisor) => { + GLctx.vertexAttribDivisor(index, divisor); + }; + _glVertexAttribDivisor.sig = 'vii'; + /** @suppress {duplicate } */ + var _glVertexAttribDivisorANGLE = _glVertexAttribDivisor; + var _emscripten_glVertexAttribDivisorANGLE = _glVertexAttribDivisorANGLE; + + /** @suppress {duplicate } */ + var _glVertexAttribPointer = ( + index, + size, + type, + normalized, + stride, + ptr + ) => { + GLctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); + }; + _glVertexAttribPointer.sig = 'viiiiip'; + var _emscripten_glVertexAttribPointer = _glVertexAttribPointer; + _emscripten_glVertexAttribPointer.sig = 'viiiiip'; + + /** @suppress {duplicate } */ + var _glViewport = (x0, x1, x2, x3) => GLctx.viewport(x0, x1, x2, x3); + _glViewport.sig = 'viiii'; + var _emscripten_glViewport = _glViewport; + _emscripten_glViewport.sig = 'viiii'; + + var _emscripten_out = (str) => out(UTF8ToString(str)); + _emscripten_out.sig = 'vp'; + + class HandleAllocator { + allocated = [undefined]; + freelist = []; + get(id) { + return this.allocated[id]; + } + has(id) { + return this.allocated[id] !== undefined; + } + allocate(handle) { + var id = this.freelist.pop() || this.allocated.length; + this.allocated[id] = handle; + return id; + } + free(id) { + // Set the slot to `undefined` rather than using `delete` here since + // apparently arrays with holes in them can be less efficient. + this.allocated[id] = undefined; + this.freelist.push(id); + } + } + var promiseMap = new HandleAllocator(); + var makePromise = () => { + var promiseInfo = {}; + promiseInfo.promise = new Promise((resolve, reject) => { + promiseInfo.reject = reject; + promiseInfo.resolve = resolve; + }); + promiseInfo.id = promiseMap.allocate(promiseInfo); + return promiseInfo; + }; + var _emscripten_promise_create = () => makePromise().id; + _emscripten_promise_create.sig = 'p'; + + var _emscripten_promise_destroy = (id) => { + promiseMap.free(id); + }; + _emscripten_promise_destroy.sig = 'vp'; + + var getPromise = (id) => promiseMap.get(id).promise; + + var _emscripten_promise_resolve = (id, result, value) => { + var info = promiseMap.get(id); + switch (result) { + case 0: + info.resolve(value); + return; + case 1: + info.resolve(getPromise(value)); + return; + case 2: + info.resolve(getPromise(value)); + _emscripten_promise_destroy(value); + return; + case 3: + info.reject(value); + return; + } + }; + _emscripten_promise_resolve.sig = 'vpip'; + + var growMemory = (size) => { + var b = wasmMemory.buffer; + var pages = ((size - b.byteLength + 65535) / 65536) | 0; + try { + // round size grow request up to wasm page size (fixed 64KB per spec) + wasmMemory.grow(pages); // .grow() takes a delta compared to the previous size + updateMemoryViews(); + return 1 /*success*/; + } catch (e) {} + // implicit 0 return to save code size (caller will cast "undefined" into 0 + // anyhow) + }; + var _emscripten_resize_heap = (requestedSize) => { + var oldSize = HEAPU8.length; + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + requestedSize >>>= 0; + // With multithreaded builds, races can happen (another thread might increase the size + // in between), so return a failure, and let the caller retry. + + // Memory resize rules: + // 1. Always increase heap size to at least the requested size, rounded up + // to next page multiple. + // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap + // geometrically: increase the heap size according to + // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most + // overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). + // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap + // linearly: increase the heap size by at least + // MEMORY_GROWTH_LINEAR_STEP bytes. + // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by + // MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest + // 4. If we were unable to allocate as much memory, it may be due to + // over-eager decision to excessively reserve due to (3) above. + // Hence if an allocation fails, cut down on the amount of excess + // growth, in an attempt to succeed to perform a smaller allocation. + + // A limit is set for how much we can grow. We should not exceed that + // (the wasm binary specifies it, so if we tried, we'd fail anyhow). + var maxHeapSize = getHeapMax(); + if (requestedSize > maxHeapSize) { + return false; + } + + // Loop through potential heap size increases. If we attempt a too eager + // reservation that fails, cut down on the attempted size and reserve a + // smaller bump instead. (max 3 times, chosen somewhat arbitrarily) + for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { + var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); // ensure geometric growth + // but limit overreserving (default to capping at +96MB overgrowth at most) + overGrownHeapSize = Math.min( + overGrownHeapSize, + requestedSize + 100663296 + ); + + var newSize = Math.min( + maxHeapSize, + alignMemory(Math.max(requestedSize, overGrownHeapSize), 65536) + ); + + var replacement = growMemory(newSize); + if (replacement) { + return true; + } + } + return false; + }; + _emscripten_resize_heap.sig = 'ip'; + + var maybeCStringToJsString = (cString) => { + // "cString > 2" checks if the input is a number, and isn't of the special + // values we accept here, EMSCRIPTEN_EVENT_TARGET_* (which map to 0, 1, 2). + // In other words, if cString > 2 then it's a pointer to a valid place in + // memory, and points to a C string. + return cString > 2 ? UTF8ToString(cString) : cString; + }; + + /** @type {Object} */ + var specialHTMLTargets = [ + 0, + typeof document != 'undefined' ? document : 0, + typeof window != 'undefined' ? window : 0, + ]; + /** @suppress {duplicate } */ + var findEventTarget = (target) => { + target = maybeCStringToJsString(target); + var domElement = + specialHTMLTargets[target] || + (typeof document != 'undefined' + ? document.querySelector(target) + : null); + return domElement; + }; + var findCanvasEventTarget = findEventTarget; + var _emscripten_set_canvas_element_size = (target, width, height) => { + var canvas = findCanvasEventTarget(target); + if (!canvas) return -4; + canvas.width = width; + canvas.height = height; + return 0; + }; + _emscripten_set_canvas_element_size.sig = 'ipii'; + + /** @param {number=} timeout */ + var safeSetTimeout = (func, timeout) => { + runtimeKeepalivePush(); + return setTimeout(() => { + runtimeKeepalivePop(); + callUserCallback(func); + }, timeout); + }; + var _emscripten_sleep = (ms) => { + // emscripten_sleep() does not return a value, but we still need a |return| + // here for stack switching support (ASYNCIFY=2). In that mode this function + // returns a Promise instead of nothing, and that Promise is what tells the + // wasm VM to pause the stack. + return Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)); + }; + Module['_emscripten_sleep'] = _emscripten_sleep; + _emscripten_sleep.sig = 'vi'; + _emscripten_sleep.isAsync = true; + + var _emscripten_wget_data = (url, pbuffer, pnum, perror) => { + return Asyncify.handleSleep((wakeUp) => { + /* no need for run dependency, this is async but will not do any prepare etc. step */ + asyncLoad(UTF8ToString(url)).then( + (byteArray) => { + // can only allocate the buffer after the wakeUp, not during an asyncing + var buffer = _malloc(byteArray.length); // must be freed by caller! + HEAPU8.set(byteArray, buffer); + HEAPU32[pbuffer >> 2] = buffer; + HEAP32[pnum >> 2] = byteArray.length; + HEAP32[perror >> 2] = 0; + wakeUp(); + }, + () => { + HEAP32[perror >> 2] = 1; + wakeUp(); + } + ); + }); + }; + _emscripten_wget_data.sig = 'vpppp'; + _emscripten_wget_data.isAsync = true; + + var getEnvStrings = () => { + if (!getEnvStrings.strings) { + // Default values. + // Browser language detection #8751 + var lang = + ( + (typeof navigator == 'object' && + navigator.languages && + navigator.languages[0]) || + 'C' + ).replace('-', '_') + '.UTF-8'; + var env = { + USER: 'web_user', + LOGNAME: 'web_user', + PATH: '/', + PWD: '/', + HOME: '/home/web_user', + LANG: lang, + _: getExecutableName(), + }; + // Apply the user-provided values, if any. + for (var x in ENV) { + // x is a key in ENV; if ENV[x] is undefined, that means it was + // explicitly set to be so. We allow user code to do that to + // force variables with default values to remain unset. + if (ENV[x] === undefined) delete env[x]; + else env[x] = ENV[x]; + } + var strings = []; + for (var x in env) { + strings.push(`${x}=${env[x]}`); + } + getEnvStrings.strings = strings; + } + return getEnvStrings.strings; + }; + + var stringToAscii = (str, buffer) => { + for (var i = 0; i < str.length; ++i) { + HEAP8[buffer++] = str.charCodeAt(i); + } + // Null-terminate the string + HEAP8[buffer] = 0; + }; + var _environ_get = (__environ, environ_buf) => { + var bufSize = 0; + getEnvStrings().forEach((string, i) => { + var ptr = environ_buf + bufSize; + HEAPU32[(__environ + i * 4) >> 2] = ptr; + stringToAscii(string, ptr); + bufSize += string.length + 1; + }); + return 0; + }; + _environ_get.sig = 'ipp'; + + var _environ_sizes_get = (penviron_count, penviron_buf_size) => { + var strings = getEnvStrings(); + HEAPU32[penviron_count >> 2] = strings.length; + var bufSize = 0; + strings.forEach((string) => (bufSize += string.length + 1)); + HEAPU32[penviron_buf_size >> 2] = bufSize; + return 0; + }; + _environ_sizes_get.sig = 'ipp'; + + function _fd_close(fd) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + FS.close(stream); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_close.sig = 'ii'; + + function _fd_fdstat_get(fd, pbuf) { + try { + var rightsBase = 0; + var rightsInheriting = 0; + var flags = 0; + { + var stream = SYSCALLS.getStreamFromFD(fd); + // All character devices are terminals (other things a Linux system would + // assume is a character device, like the mouse, we have special APIs for). + var type = stream.tty + ? 2 + : FS.isDir(stream.mode) + ? 3 + : FS.isLink(stream.mode) + ? 7 + : 4; + } + HEAP8[pbuf] = type; + HEAP16[(pbuf + 2) >> 1] = flags; + HEAP64[(pbuf + 8) >> 3] = BigInt(rightsBase); + HEAP64[(pbuf + 16) >> 3] = BigInt(rightsInheriting); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_fdstat_get.sig = 'iip'; + + /** @param {number=} offset */ + var doReadv = (stream, iov, iovcnt, offset) => { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[iov >> 2]; + var len = HEAPU32[(iov + 4) >> 2]; + iov += 8; + var curr = FS.read(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) break; // nothing more to read + if (typeof offset != 'undefined') { + offset += curr; + } + } + return ret; + }; + + function _fd_pread(fd, iov, iovcnt, offset, pnum) { + offset = bigintToI53Checked(offset); + + try { + if (isNaN(offset)) return 61; + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doReadv(stream, iov, iovcnt, offset); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_pread.sig = 'iippjp'; + + /** @param {number=} offset */ + var doWritev = (stream, iov, iovcnt, offset) => { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[iov >> 2]; + var len = HEAPU32[(iov + 4) >> 2]; + iov += 8; + var curr = FS.write(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) { + // No more space to write. + break; + } + if (typeof offset != 'undefined') { + offset += curr; + } + } + return ret; + }; + + function _fd_pwrite(fd, iov, iovcnt, offset, pnum) { + offset = bigintToI53Checked(offset); + + try { + if (isNaN(offset)) return 61; + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doWritev(stream, iov, iovcnt, offset); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_pwrite.sig = 'iippjp'; + + function _fd_read(fd, iov, iovcnt, pnum) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doReadv(stream, iov, iovcnt); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_read.sig = 'iippp'; + + function _fd_seek(fd, offset, whence, newOffset) { + offset = bigintToI53Checked(offset); + + try { + if (isNaN(offset)) return 61; + var stream = SYSCALLS.getStreamFromFD(fd); + FS.llseek(stream, offset, whence); + HEAP64[newOffset >> 3] = BigInt(stream.position); + if (stream.getdents && offset === 0 && whence === 0) + stream.getdents = null; // reset readdir state + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_seek.sig = 'iijip'; + + var _fd_sync = function (fd) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + return Asyncify.handleSleep((wakeUp) => { + var mount = stream.node.mount; + if (!mount.type.syncfs) { + // We write directly to the file system, so there's nothing to do here. + wakeUp(0); + return; + } + mount.type.syncfs(mount, false, (err) => { + if (err) { + wakeUp(29); + return; + } + wakeUp(0); + }); + }); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + }; + _fd_sync.sig = 'ii'; + _fd_sync.isAsync = true; + + function _fd_write(fd, iov, iovcnt, pnum) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doWritev(stream, iov, iovcnt); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_write.sig = 'iippp'; + + var _getaddrinfo = (node, service, hint, out) => { + // Note getaddrinfo currently only returns a single addrinfo with ai_next defaulting to NULL. When NULL + // hints are specified or ai_family set to AF_UNSPEC or ai_socktype or ai_protocol set to 0 then we + // really should provide a linked list of suitable addrinfo values. + var addrs = []; + var canon = null; + var addr = 0; + var port = 0; + var flags = 0; + var family = 0; + var type = 0; + var proto = 0; + var ai, last; + + function allocaddrinfo(family, type, proto, canon, addr, port) { + var sa, salen, ai; + var errno; + + salen = family === 10 ? 28 : 16; + addr = family === 10 ? inetNtop6(addr) : inetNtop4(addr); + sa = _malloc(salen); + errno = writeSockaddr(sa, family, addr, port); + assert(!errno); + + ai = _malloc(32); + HEAP32[(ai + 4) >> 2] = family; + HEAP32[(ai + 8) >> 2] = type; + HEAP32[(ai + 12) >> 2] = proto; + HEAPU32[(ai + 24) >> 2] = canon; + HEAPU32[(ai + 20) >> 2] = sa; + if (family === 10) { + HEAP32[(ai + 16) >> 2] = 28; + } else { + HEAP32[(ai + 16) >> 2] = 16; + } + HEAP32[(ai + 28) >> 2] = 0; + + return ai; + } + + if (hint) { + flags = HEAP32[hint >> 2]; + family = HEAP32[(hint + 4) >> 2]; + type = HEAP32[(hint + 8) >> 2]; + proto = HEAP32[(hint + 12) >> 2]; + } + if (type && !proto) { + proto = type === 2 ? 17 : 6; + } + if (!type && proto) { + type = proto === 17 ? 2 : 1; + } + + // If type or proto are set to zero in hints we should really be returning multiple addrinfo values, but for + // now default to a TCP STREAM socket so we can at least return a sensible addrinfo given NULL hints. + if (proto === 0) { + proto = 6; + } + if (type === 0) { + type = 1; + } + + if (!node && !service) { + return -2; + } + if (flags & ~(1 | 2 | 4 | 1024 | 8 | 16 | 32)) { + return -1; + } + if (hint !== 0 && HEAP32[hint >> 2] & 2 && !node) { + return -1; + } + if (flags & 32) { + // TODO + return -2; + } + if (type !== 0 && type !== 1 && type !== 2) { + return -7; + } + if (family !== 0 && family !== 2 && family !== 10) { + return -6; + } + + if (service) { + service = UTF8ToString(service); + port = parseInt(service, 10); + + if (isNaN(port)) { + if (flags & 1024) { + return -2; + } + // TODO support resolving well-known service names from: + // http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt + return -8; + } + } + + if (!node) { + if (family === 0) { + family = 2; + } + if ((flags & 1) === 0) { + if (family === 2) { + addr = _htonl(2130706433); + } else { + addr = [0, 0, 0, _htonl(1)]; + } + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + } + + // + // try as a numeric address + // + node = UTF8ToString(node); + addr = inetPton4(node); + if (addr !== null) { + // incoming node is a valid ipv4 address + if (family === 0 || family === 2) { + family = 2; + } else if (family === 10 && flags & 8) { + addr = [0, 0, _htonl(0xffff), addr]; + family = 10; + } else { + return -2; + } + } else { + addr = inetPton6(node); + if (addr !== null) { + // incoming node is a valid ipv6 address + if (family === 0 || family === 10) { + family = 10; + } else { + return -2; + } + } + } + if (addr != null) { + ai = allocaddrinfo(family, type, proto, node, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + } + if (flags & 4) { + return -2; + } + + // + // try as a hostname + // + // resolve the hostname to a temporary fake address + node = DNS.lookup_name(node); + addr = inetPton4(node); + if (family === 0) { + family = 2; + } else if (family === 10) { + addr = [0, 0, _htonl(0xffff), addr]; + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + }; + _getaddrinfo.sig = 'ipppp'; + + var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => { + var info = readSockaddr(sa, salen); + if (info.errno) { + return -6; + } + var port = info.port; + var addr = info.addr; + + var overflowed = false; + + if (node && nodelen) { + var lookup; + if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) { + if (flags & 8) { + return -2; + } + } else { + addr = lookup; + } + var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen); + + if (numBytesWrittenExclNull + 1 >= nodelen) { + overflowed = true; + } + } + + if (serv && servlen) { + port = '' + port; + var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen); + + if (numBytesWrittenExclNull + 1 >= servlen) { + overflowed = true; + } + } + + if (overflowed) { + // Note: even when we overflow, getnameinfo() is specced to write out the truncated results. + return -12; + } + + return 0; + }; + _getnameinfo.sig = 'ipipipii'; + + var Protocols = { + list: [], + map: {}, + }; + + var _setprotoent = (stayopen) => { + // void setprotoent(int stayopen); + + // Allocate and populate a protoent structure given a name, protocol number and array of aliases + function allocprotoent(name, proto, aliases) { + // write name into buffer + var nameBuf = _malloc(name.length + 1); + stringToAscii(name, nameBuf); + + // write aliases into buffer + var j = 0; + var length = aliases.length; + var aliasListBuf = _malloc((length + 1) * 4); // Use length + 1 so we have space for the terminating NULL ptr. + + for (var i = 0; i < length; i++, j += 4) { + var alias = aliases[i]; + var aliasBuf = _malloc(alias.length + 1); + stringToAscii(alias, aliasBuf); + HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf; + } + HEAPU32[(aliasListBuf + j) >> 2] = 0; // Terminating NULL pointer. + + // generate protoent + var pe = _malloc(12); + HEAPU32[pe >> 2] = nameBuf; + HEAPU32[(pe + 4) >> 2] = aliasListBuf; + HEAP32[(pe + 8) >> 2] = proto; + return pe; + } + + // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial + // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful. + var list = Protocols.list; + var map = Protocols.map; + if (list.length === 0) { + var entry = allocprotoent('tcp', 6, ['TCP']); + list.push(entry); + map['tcp'] = map['6'] = entry; + entry = allocprotoent('udp', 17, ['UDP']); + list.push(entry); + map['udp'] = map['17'] = entry; + } + + _setprotoent.index = 0; + }; + _setprotoent.sig = 'vi'; + + var _getprotobyname = (name) => { + // struct protoent *getprotobyname(const char *); + name = UTF8ToString(name); + _setprotoent(true); + var result = Protocols.map[name]; + return result; + }; + _getprotobyname.sig = 'pp'; + + var _getprotobynumber = (number) => { + // struct protoent *getprotobynumber(int proto); + _setprotoent(true); + var result = Protocols.map[number]; + return result; + }; + _getprotobynumber.sig = 'pi'; + + var allocateUTF8OnStack = stringToUTF8OnStack; + + function _js_getpid() { + return PHPLoader.processId ?? 42; + } + + function _js_wasm_trace(format, ...args) { + if (PHPLoader.trace instanceof Function) { + PHPLoader.trace(_js_getpid(), format, ...args); + } + } + var PHPWASM = { + init: function () { + Module['ENV'] = Module['ENV'] || {}; + // Ensure a platform-level bin directory for a fallback `php` binary. + Module['ENV']['PATH'] = [ + Module['ENV']['PATH'], + '/internal/shared/bin', + ] + .filter(Boolean) + .join(':'); + + // The /internal directory is required by the C module. It's where the + // stdout, stderr, and headers information are written for the JavaScript + // code to read later on. + FS.mkdir('/internal'); + // The files from the shared directory are shared between all the + // PHP processes managed by PHPProcessManager. + FS.mkdir('/internal/shared'); + // The files from the preload directory are preloaded using the + // auto_prepend_file php.ini directive. + FS.mkdir('/internal/shared/preload'); + // Platform-level bin directory for a fallback `php` binary. Without it, + // PHP may not populate the PHP_BINARY constant. + FS.mkdir('/internal/shared/bin'); + const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; + Module['onRuntimeInitialized'] = () => { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + originalOnRuntimeInitialized(); + }; + + // Create stdout and stderr devices. We can't just use Emscripten's + // default stdout and stderr devices because they stop processing data + // on the first null byte. However, when dealing with binary data, + // null bytes are valid and common. + FS.registerDevice(FS.makedev(64, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onStdout(chunk); + return length; + }, + }); + FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + + FS.registerDevice(FS.makedev(63, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onStderr(chunk); + return length; + }, + }); + FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + + FS.registerDevice(FS.makedev(62, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onHeaders(chunk); + return length; + }, + }); + FS.mkdev('/internal/headers', FS.makedev(62, 0)); + + // Handle events. + PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE + ? require('events').EventEmitter + : class EventEmitter { + constructor() { + this.listeners = {}; + } + emit(eventName, data) { + if (this.listeners[eventName]) { + this.listeners[eventName].forEach( + (callback) => { + callback(data); + } + ); + } + } + once(eventName, callback) { + const self = this; + function removedCallback() { + callback(...arguments); + self.removeListener(eventName, removedCallback); + } + this.on(eventName, removedCallback); + } + removeAllListeners(eventName) { + if (eventName) { + delete this.listeners[eventName]; + } else { + this.listeners = {}; + } + } + removeListener(eventName, callback) { + if (this.listeners[eventName]) { + const idx = + this.listeners[eventName].indexOf(callback); + if (idx !== -1) { + this.listeners[eventName].splice(idx, 1); + } + } + } + }; + + // Clean up the fd -> childProcess mapping when the fd is closed: + const originalClose = FS.close; + FS.close = function (stream) { + originalClose(stream); + delete PHPWASM.child_proc_by_fd[stream.fd]; + }; + + PHPWASM.child_proc_by_fd = {}; + PHPWASM.child_proc_by_pid = {}; + + PHPWASM.input_devices = {}; + const originalWrite = TTY.stream_ops.write; + TTY.stream_ops.write = function (stream, ...rest) { + const retval = originalWrite(stream, ...rest); + // Implicit flush since PHP's fflush() doesn't seem to trigger the fsync event + // @TODO: Fix this at the wasm level + stream.tty.ops.fsync(stream.tty); + return retval; + }; + const originalPutChar = TTY.stream_ops.put_char; + TTY.stream_ops.put_char = function (tty, val) { + /** + * Buffer newlines that Emscripten normally ignores. + * + * Emscripten doesn't do it by default because its default + * print function is console.log that implicitly adds a newline. We are overwriting + * it with an environment-specific function that outputs exaclty what it was given, + * e.g. in Node.js it's process.stdout.write(). Therefore, we need to mak sure + * all the newlines make it to the output buffer. + */ + if (val === 10) tty.output.push(val); + return originalPutChar(tty, val); + }; + }, + onHeaders: function (chunk) { + if (Module['onHeaders']) { + Module['onHeaders'](chunk); + return; + } + console.log('headers', { chunk }); + }, + onStdout: function (chunk) { + if (Module['onStdout']) { + Module['onStdout'](chunk); + return; + } + if (ENVIRONMENT_IS_NODE) { + process.stdout.write(chunk); + } else { + console.log('stdout', { chunk }); + } + }, + onStderr: function (chunk) { + if (Module['onStderr']) { + Module['onStderr'](chunk); + return; + } + if (ENVIRONMENT_IS_NODE) { + process.stderr.write(chunk); + } else { + console.warn('stderr', { chunk }); + } + }, + getAllWebSockets: function (sock) { + const webSockets = /* @__PURE__ */ new Set(); + if (sock.server) { + sock.server.clients.forEach((ws) => { + webSockets.add(ws); + }); + } + for (const peer of PHPWASM.getAllPeers(sock)) { + webSockets.add(peer.socket); + } + return Array.from(webSockets); + }, + getAllPeers: function (sock) { + const peers = new Set(); + if (sock.server) { + sock.pending + .filter((pending) => pending.peers) + .forEach((pending) => { + for (const peer of Object.values(pending.peers)) { + peers.add(peer); + } + }); + } + if (sock.peers) { + for (const peer of Object.values(sock.peers)) { + peers.add(peer); + } + } + return Array.from(peers); + }, + awaitData: function (ws) { + return PHPWASM.awaitEvent(ws, 'message'); + }, + awaitConnection: function (ws) { + if (ws.OPEN === ws.readyState) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'open'); + }, + awaitClose: function (ws) { + if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'close'); + }, + awaitError: function (ws) { + if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'error'); + }, + awaitEvent: function (ws, event) { + let resolve; + const listener = () => { + resolve(); + }; + const promise = new Promise(function (_resolve) { + resolve = _resolve; + ws.once(event, listener); + }); + const cancel = () => { + ws.removeListener(event, listener); + // Rejecting the promises bubbles up and kills the entire + // node process. Let's resolve them on the next tick instead + // to give the caller some space to unbind any handlers. + setTimeout(resolve); + }; + return [promise, cancel]; + }, + noop: function () {}, + spawnProcess: function (command, args, options) { + if (Module['spawnProcess']) { + const spawnedPromise = Module['spawnProcess']( + command, + args, + options + ); + return Promise.resolve(spawnedPromise).then(function (spawned) { + if (!spawned || !spawned.on) { + throw new Error( + 'spawnProcess() must return an EventEmitter but returned a different type.' + ); + } + return spawned; + }); + } + + if (ENVIRONMENT_IS_NODE) { + return require('child_process').spawn(command, args, { + ...options, + shell: true, + stdio: ['pipe', 'pipe', 'pipe'], + timeout: 100, + }); + } + const e = new Error( + 'popen(), proc_open() etc. are unsupported in the browser. Call php.setSpawnHandler() ' + + 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' + + 'and similar functions via php.ini.' + ); + e.code = 'SPAWN_UNSUPPORTED'; + throw e; + }, + shutdownSocket: function (socketd, how) { + // This implementation only supports websockets at the moment + const sock = getSocketFromFD(socketd); + const peer = Object.values(sock.peers)[0]; + + if (!peer) { + return -1; + } + + try { + peer.socket.close(); + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + return 0; + } catch (e) { + console.log('Socket shutdown error', e); + return -1; + } + }, + }; + + function _js_create_input_device(deviceId) { + let dataBuffer = []; + let dataCallback; + const filename = 'proc_id_' + deviceId; + const device = FS.createDevice( + '/dev', + filename, + function () {}, + function (byte) { + try { + dataBuffer.push(byte); + if (dataCallback) { + dataCallback(new Uint8Array(dataBuffer)); + dataBuffer = []; + } + } catch (e) { + console.error(e); + throw e; + } + } + ); + + const devicePath = '/dev/' + filename; + PHPWASM.input_devices[deviceId] = { + devicePath: devicePath, + onData: function (cb) { + dataCallback = cb; + dataBuffer.forEach(function (data) { + cb(data); + }); + dataBuffer.length = 0; + }, + }; + return allocateUTF8OnStack(devicePath); + } + + function _js_open_process( + command, + argsPtr, + argsLength, + descriptorsPtr, + descriptorsLength, + cwdPtr, + cwdLength, + envPtr, + envLength + ) { + if (!command) { + return 1; + } + + const cmdstr = UTF8ToString(command); + if (!cmdstr.length) { + return 0; + } + + let argsArray = []; + if (argsLength) { + for (var i = 0; i < argsLength; i++) { + const charPointer = argsPtr + i * 4; + argsArray.push(UTF8ToString(HEAPU32[charPointer >> 2])); + } + } + + const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : FS.cwd(); + let envObject = null; + + if (envLength) { + envObject = {}; + for (var i = 0; i < envLength; i++) { + const envPointer = envPtr + i * 4; + const envEntry = UTF8ToString(HEAPU32[envPointer >> 2]); + const splitAt = envEntry.indexOf('='); + if (splitAt === -1) { + continue; + } + const key = envEntry.substring(0, splitAt); + const value = envEntry.substring(splitAt + 1); + envObject[key] = value; + } + } + + var std = {}; + // Extracts an array of available descriptors that should be dispatched to streams. + // On the C side, the descriptors are expressed as `**int` so we must go read + // each of the `descriptorsLength` `*int` pointers and convert the associated data into + // a JavaScript object { descriptor : { child : fd, parent : fd } }. + for (var i = 0; i < descriptorsLength; i++) { + const descriptorPtr = HEAPU32[(descriptorsPtr + i * 4) >> 2]; + std[HEAPU32[descriptorPtr >> 2]] = { + child: HEAPU32[(descriptorPtr + 4) >> 2], + parent: HEAPU32[(descriptorPtr + 8) >> 2], + }; + } + + return Asyncify.handleSleep(async (wakeUp) => { + let cp; + try { + const options = {}; + if (cwdstr !== null) { + options.cwd = cwdstr; + } + if (envObject !== null) { + options.env = envObject; + } + cp = PHPWASM.spawnProcess(cmdstr, argsArray, options); + if (cp instanceof Promise) { + cp = await cp; + } + } catch (e) { + if (e.code === 'SPAWN_UNSUPPORTED') { + wakeUp(1); + return; + } + console.error(e); + wakeUp(1); + throw e; + } + + const ProcInfo = { + pid: cp.pid, + exited: false, + stdinFd: std[0]?.child, + stdinIsDevice: std[0]?.child in PHPWASM.input_devices, + stdoutChildFd: std[1]?.child, + stdoutParentFd: std[1]?.parent, + stderrChildFd: std[2]?.child, + stderrParentFd: std[2]?.parent, + stdout: new PHPWASM.EventEmitter(), + stderr: new PHPWASM.EventEmitter(), + }; + if (ProcInfo.stdoutChildFd) + PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; + if (ProcInfo.stderrChildFd) + PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; + if (ProcInfo.stdoutParentFd) + PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; + if (ProcInfo.stderrParentFd) + PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; + PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; + + cp.on('exit', function (code) { + for (const fd of [ + // The child process exited. Let's clean up its output streams: + ProcInfo.stdoutChildFd, + ProcInfo.stderrChildFd, + // Note we're not closing stdinFd as the parent still might be holding on to it. + + // We won't close these because the parent process is responsible for that: + // ProcInfo.stdoutParentFd, + // ProcInfo.stderrParentFd, + ]) { + if (FS.streams[fd] && !FS.isClosed(FS.streams[fd])) { + FS.close(FS.streams[fd]); + } + } + + ProcInfo.exitCode = code; + ProcInfo.exited = true; + // Emit events for the wasm_poll_socket function. + ProcInfo.stdout.emit('data'); + ProcInfo.stderr.emit('data'); + }); + + // Pass data from child process's stdout to PHP's end of the stdout pipe. + if (ProcInfo.stdoutChildFd) { + const stdoutStream = SYSCALLS.getStreamFromFD( + ProcInfo.stdoutChildFd + ); + let stdoutAt = 0; + cp.stdout.on('data', function (data) { + ProcInfo.stdout.emit('data', data); + stdoutStream.stream_ops.write( + stdoutStream, + data, + 0, + data.length, + stdoutAt + ); + stdoutAt += data.length; + }); + } + + // Pass data from child process's stderr to PHP's end of the stdout pipe. + if (ProcInfo.stderrChildFd) { + const stderrStream = SYSCALLS.getStreamFromFD( + ProcInfo.stderrChildFd + ); + let stderrAt = 0; + cp.stderr.on('data', function (data) { + ProcInfo.stderr.emit('data', data); + stderrStream.stream_ops.write( + stderrStream, + data, + 0, + data.length, + stderrAt + ); + stderrAt += data.length; + }); + } + + /** + * Wait until the child process has been spawned. + * Unfortunately there is no Node.js API to check whether + * the process has already been spawned. We can only listen + * to the 'spawn' event and if it has already been spawned, + * listen to the 'exit' event. + */ + try { + await new Promise((resolve, reject) => { + /** + * There was no `await` between the `spawnProcess` call + * and the `await` below so the process haven't had a chance + * to run any of the exit-related callbacks yet. + * + * Good. + * + * Let's listen to all the lifecycle events and resolve + * the promise when the process starts or immediately crashes. + */ + let resolved = false; + cp.on('spawn', () => { + if (resolved) return; + resolved = true; + resolve(); + }); + cp.on('error', (e) => { + if (resolved) return; + resolved = true; + reject(e); + }); + cp.on('exit', function (code) { + if (resolved) return; + resolved = true; + if (code === 0) { + resolve(); + } else { + reject( + new Error(`Process exited with code ${code}`) + ); + } + }); + /** + * If the process haven't even started after 5 seconds, something + * is wrong. Perhaps we're missing an event listener, or perhaps + * the `spawnProcess` implementation failed to dispatch the relevant + * event. Either way, let's crash to avoid blocking the proc_open() + * call indefinitely. + */ + setTimeout(() => { + if (resolved) return; + resolved = true; + reject(new Error('Process timed out')); + }, 5000); + }); + } catch (e) { + console.error(e); + wakeUp(ProcInfo.pid); + return; + } + + // Now we want to pass data from the STDIN source supplied by PHP + // to the child process. + + // PHP will write STDIN data to a device. + if (ProcInfo.stdinIsDevice) { + // We use Emscripten devices as pipes. This is a bit of a hack + // but it works as we get a callback when the device is written to. + // Let's listen to anything it outputs and pass it to the child process. + PHPWASM.input_devices[ProcInfo.stdinFd].onData(function (data) { + if (!data) return; + if (typeof data === 'number') { + data = new Uint8Array([data]); + } + const dataStr = new TextDecoder('utf-8').decode(data); + cp.stdin.write(dataStr); + }); + wakeUp(ProcInfo.pid); + return; + } + + if (ProcInfo.stdinFd) { + // PHP will write STDIN data to a file descriptor. + const stdinStream = SYSCALLS.getStreamFromFD(ProcInfo.stdinFd); + if (stdinStream.node) { + // Pipe the entire stdinStream to cp.stdin + const CHUNK_SIZE = 1024; + const buffer = new Uint8Array(CHUNK_SIZE); + let offset = 0; + + while (true) { + const bytesRead = stdinStream.stream_ops.read( + stdinStream, + buffer, + 0, + CHUNK_SIZE, + offset + ); + if (bytesRead === null || bytesRead === 0) { + break; + } + try { + cp.stdin.write(buffer.subarray(0, bytesRead)); + } catch (e) { + console.error(e); + return 1; + } + if (bytesRead < CHUNK_SIZE) { + break; + } + offset += bytesRead; + } + + wakeUp(ProcInfo.pid); + return; + } + } + + wakeUp(ProcInfo.pid); + }); + } + + function _js_process_status(pid, exitCodePtr) { + if (!PHPWASM.child_proc_by_pid[pid]) { + return -1; + } + if (PHPWASM.child_proc_by_pid[pid].exited) { + HEAPU32[exitCodePtr >> 2] = PHPWASM.child_proc_by_pid[pid].exitCode; + return 1; + } + return 0; + } + + function _js_waitpid(pid, exitCodePtr) { + if (!PHPWASM.child_proc_by_pid[pid]) { + return -1; + } + return Asyncify.handleSleep((wakeUp) => { + const poll = function () { + if (PHPWASM.child_proc_by_pid[pid]?.exited) { + HEAPU32[exitCodePtr >> 2] = + PHPWASM.child_proc_by_pid[pid].exitCode; + wakeUp(pid); + } else { + setTimeout(poll, 50); + } + }; + poll(); + }); + } + + function _random_get(buffer, size) { + try { + randomFill(HEAPU8.subarray(buffer, buffer + size)); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _random_get.sig = 'ipp'; + + var arraySum = (array, index) => { + var sum = 0; + for (var i = 0; i <= index; sum += array[i++]) { + // no-op + } + return sum; + }; + + var MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + var MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var addDays = (date, days) => { + var newDate = new Date(date.getTime()); + while (days > 0) { + var leap = isLeapYear(newDate.getFullYear()); + var currentMonth = newDate.getMonth(); + var daysInCurrentMonth = ( + leap ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR + )[currentMonth]; + + if (days > daysInCurrentMonth - newDate.getDate()) { + // we spill over to next month + days -= daysInCurrentMonth - newDate.getDate() + 1; + newDate.setDate(1); + if (currentMonth < 11) { + newDate.setMonth(currentMonth + 1); + } else { + newDate.setMonth(0); + newDate.setFullYear(newDate.getFullYear() + 1); + } + } else { + // we stay in current month + newDate.setDate(newDate.getDate() + days); + return newDate; + } + } + + return newDate; + }; + + var _strptime = (buf, format, tm) => { + // char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tm); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html + var pattern = UTF8ToString(format); + + // escape special characters + // TODO: not sure we really need to escape all of these in JS regexps + var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.'; + for (var i = 0, ii = SPECIAL_CHARS.length; i < ii; ++i) { + pattern = pattern.replace( + new RegExp('\\' + SPECIAL_CHARS[i], 'g'), + '\\' + SPECIAL_CHARS[i] + ); + } + + // reduce number of matchers + var EQUIVALENT_MATCHERS = { + A: '%a', + B: '%b', + c: '%a %b %d %H:%M:%S %Y', + D: '%m\\/%d\\/%y', + e: '%d', + F: '%Y-%m-%d', + h: '%b', + R: '%H\\:%M', + r: '%I\\:%M\\:%S\\s%p', + T: '%H\\:%M\\:%S', + x: '%m\\/%d\\/(?:%y|%Y)', + X: '%H\\:%M\\:%S', + }; + // TODO: take care of locale + + var DATE_PATTERNS = { + /* weekday name */ a: '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)', + /* month name */ b: '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)', + /* century */ C: '\\d\\d', + /* day of month */ d: '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31', + /* hour (24hr) */ H: '\\d(?!\\d)|[0,1]\\d|20|21|22|23', + /* hour (12hr) */ I: '\\d(?!\\d)|0\\d|10|11|12', + /* day of year */ j: '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d', + /* month */ m: '0[1-9]|[1-9](?!\\d)|10|11|12', + /* minutes */ M: '0\\d|\\d(?!\\d)|[1-5]\\d', + /* whitespace */ n: ' ', + /* AM/PM */ p: 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.', + /* seconds */ S: '0\\d|\\d(?!\\d)|[1-5]\\d|60', + /* week number */ U: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* week number */ W: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* weekday number */ w: '[0-6]', + /* 2-digit year */ y: '\\d\\d', + /* 4-digit year */ Y: '\\d\\d\\d\\d', + /* whitespace */ t: ' ', + /* time zone */ z: 'Z|(?:[\\+\\-]\\d\\d:?(?:\\d\\d)?)', + }; + + var MONTH_NUMBERS = { + JAN: 0, + FEB: 1, + MAR: 2, + APR: 3, + MAY: 4, + JUN: 5, + JUL: 6, + AUG: 7, + SEP: 8, + OCT: 9, + NOV: 10, + DEC: 11, + }; + var DAY_NUMBERS_SUN_FIRST = { + SUN: 0, + MON: 1, + TUE: 2, + WED: 3, + THU: 4, + FRI: 5, + SAT: 6, + }; + var DAY_NUMBERS_MON_FIRST = { + MON: 0, + TUE: 1, + WED: 2, + THU: 3, + FRI: 4, + SAT: 5, + SUN: 6, + }; + + var capture = []; + var pattern_out = pattern + .replace(/%(.)/g, (m, c) => EQUIVALENT_MATCHERS[c] || m) + .replace(/%(.)/g, (_, c) => { + let pat = DATE_PATTERNS[c]; + if (pat) { + capture.push(c); + return `(${pat})`; + } else { + return c; + } + }) + .replace( + // any number of space or tab characters match zero or more spaces + /\s+/g, + '\\s*' + ); + + var matches = new RegExp('^' + pattern_out, 'i').exec( + UTF8ToString(buf) + ); + + function initDate() { + function fixup(value, min, max) { + return typeof value != 'number' || isNaN(value) + ? min + : value >= min + ? value <= max + ? value + : max + : min; + } + return { + year: fixup(HEAP32[(tm + 20) >> 2] + 1900, 1970, 9999), + month: fixup(HEAP32[(tm + 16) >> 2], 0, 11), + day: fixup(HEAP32[(tm + 12) >> 2], 1, 31), + hour: fixup(HEAP32[(tm + 8) >> 2], 0, 23), + min: fixup(HEAP32[(tm + 4) >> 2], 0, 59), + sec: fixup(HEAP32[tm >> 2], 0, 59), + gmtoff: 0, + }; + } + + if (matches) { + var date = initDate(); + var value; + + var getMatch = (symbol) => { + var pos = capture.indexOf(symbol); + // check if symbol appears in regexp + if (pos >= 0) { + // return matched value or null (falsy!) for non-matches + return matches[pos + 1]; + } + return; + }; + + // seconds + if ((value = getMatch('S'))) { + date.sec = Number(value); + } + + // minutes + if ((value = getMatch('M'))) { + date.min = Number(value); + } + + // hours + if ((value = getMatch('H'))) { + // 24h clock + date.hour = Number(value); + } else if ((value = getMatch('I'))) { + // AM/PM clock + var hour = Number(value); + if ((value = getMatch('p'))) { + hour += value.toUpperCase()[0] === 'P' ? 12 : 0; + } + date.hour = hour; + } + + // year + if ((value = getMatch('Y'))) { + // parse from four-digit year + date.year = Number(value); + } else if ((value = getMatch('y'))) { + // parse from two-digit year... + var year = Number(value); + if ((value = getMatch('C'))) { + // ...and century + year += Number(value) * 100; + } else { + // ...and rule-of-thumb + year += year < 69 ? 2000 : 1900; + } + date.year = year; + } + + // month + if ((value = getMatch('m'))) { + // parse from month number + date.month = Number(value) - 1; + } else if ((value = getMatch('b'))) { + // parse from month name + date.month = + MONTH_NUMBERS[value.substring(0, 3).toUpperCase()] || 0; + // TODO: derive month from day in year+year, week number+day of week+year + } + + // day + if ((value = getMatch('d'))) { + // get day of month directly + date.day = Number(value); + } else if ((value = getMatch('j'))) { + // get day of month from day of year ... + var day = Number(value); + var leapYear = isLeapYear(date.year); + for (var month = 0; month < 12; ++month) { + var daysUntilMonth = arraySum( + leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, + month - 1 + ); + if ( + day <= + daysUntilMonth + + (leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[ + month + ] + ) { + date.day = day - daysUntilMonth; + } + } + } else if ((value = getMatch('a'))) { + // get day of month from weekday ... + var weekDay = value.substring(0, 3).toUpperCase(); + if ((value = getMatch('U'))) { + // ... and week number (Sunday being first day of week) + // Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Sunday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay]; + var weekNumber = Number(value); + + // January 1st var janFirst = new Date(date.year, 0, 1); var endDate; if (janFirst.getDay() === 0) { @@ -7882,490 +17484,13949 @@ export function init(RuntimeName, PHPLoader) { weekDayNumber + 7 * (weekNumber - 1) ); } else { - // Jan 1st is not a Sunday, and, hence still in the 0th CW - endDate = addDays( - janFirst, - 7 - - janFirst.getDay() + - weekDayNumber + - 7 * (weekNumber - 1) - ); + // Jan 1st is not a Sunday, and, hence still in the 0th CW + endDate = addDays( + janFirst, + 7 - + janFirst.getDay() + + weekDayNumber + + 7 * (weekNumber - 1) + ); + } + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } else if ((value = getMatch('W'))) { + // ... and week number (Monday being first day of week) + // Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Monday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_MON_FIRST[weekDay]; + var weekNumber = Number(value); + + // January 1st + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay() === 1) { + // Jan 1st is a Monday, and, hence in the 1st CW + endDate = addDays( + janFirst, + weekDayNumber + 7 * (weekNumber - 1) + ); + } else { + // Jan 1st is not a Monday, and, hence still in the 0th CW + endDate = addDays( + janFirst, + 7 - + janFirst.getDay() + + 1 + + weekDayNumber + + 7 * (weekNumber - 1) + ); + } + + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } + } + + // time zone + if ((value = getMatch('z'))) { + // GMT offset as either 'Z' or +-HH:MM or +-HH or +-HHMM + if (value.toLowerCase() === 'z') { + date.gmtoff = 0; + } else { + var match = value.match(/^((?:\-|\+)\d\d):?(\d\d)?/); + date.gmtoff = match[1] * 3600; + if (match[2]) { + date.gmtoff += + date.gmtoff > 0 ? match[2] * 60 : -match[2] * 60; + } + } + } + + /* + tm_sec int seconds after the minute 0-61* + tm_min int minutes after the hour 0-59 + tm_hour int hours since midnight 0-23 + tm_mday int day of the month 1-31 + tm_mon int months since January 0-11 + tm_year int years since 1900 + tm_wday int days since Sunday 0-6 + tm_yday int days since January 1 0-365 + tm_isdst int Daylight Saving Time flag + tm_gmtoff long offset from GMT (seconds) + */ + + var fullDate = new Date( + date.year, + date.month, + date.day, + date.hour, + date.min, + date.sec, + 0 + ); + HEAP32[tm >> 2] = fullDate.getSeconds(); + HEAP32[(tm + 4) >> 2] = fullDate.getMinutes(); + HEAP32[(tm + 8) >> 2] = fullDate.getHours(); + HEAP32[(tm + 12) >> 2] = fullDate.getDate(); + HEAP32[(tm + 16) >> 2] = fullDate.getMonth(); + HEAP32[(tm + 20) >> 2] = fullDate.getFullYear() - 1900; + HEAP32[(tm + 24) >> 2] = fullDate.getDay(); + HEAP32[(tm + 28) >> 2] = + arraySum( + isLeapYear(fullDate.getFullYear()) + ? MONTH_DAYS_LEAP + : MONTH_DAYS_REGULAR, + fullDate.getMonth() - 1 + ) + + fullDate.getDate() - + 1; + HEAP32[(tm + 32) >> 2] = 0; + HEAP32[(tm + 36) >> 2] = date.gmtoff; + + // we need to convert the matched sequence into an integer array to take care of UTF-8 characters > 0x7F + // TODO: not sure that intArrayFromString handles all unicode characters correctly + return buf + intArrayFromString(matches[0]).length - 1; + } + + return 0; + }; + _strptime.sig = 'pppp'; + + function _wasm_close(socketd) { + return PHPWASM.shutdownSocket(socketd, 2); + } + + function _wasm_setsockopt( + socketd, + level, + optionName, + optionValuePtr, + optionLen + ) { + const optionValue = HEAPU8[optionValuePtr]; + const SOL_SOCKET = 1; + const SO_KEEPALIVE = 9; + const IPPROTO_TCP = 6; + const TCP_NODELAY = 1; + const isSupported = + (level === SOL_SOCKET && optionName === SO_KEEPALIVE) || + (level === IPPROTO_TCP && optionName === TCP_NODELAY); + if (!isSupported) { + console.warn( + `Unsupported socket option: ${level}, ${optionName}, ${optionValue}` + ); + return -1; + } + const ws = PHPWASM.getAllWebSockets(socketd)[0]; + if (!ws) { + return -1; + } + ws.setSocketOpt(level, optionName, optionValuePtr); + return 0; + } + + var runAndAbortIfError = (func) => { + try { + return func(); + } catch (e) { + abort(e); + } + }; + + var Asyncify = { + instrumentWasmImports(imports) { + var importPattern = + /^(invoke_i|invoke_ii|invoke_iii|invoke_iiii|invoke_iiiii|invoke_iiiiii|invoke_iiiiiii|invoke_iiiiiiii|invoke_iiiiiiiiii|invoke_v|invoke_vi|invoke_vii|invoke_viidii|invoke_viii|invoke_viiii|invoke_viiiii|invoke_viiiiii|invoke_viiiiiii|invoke_viiiiiiiii|invoke_i|invoke_ii|invoke_iii|invoke_iiii|invoke_iiiii|invoke_iiiiii|invoke_iiiiiii|invoke_iiiiiiii|invoke_iiiiiiiiii|invoke_iij|invoke_iiji|invoke_iijii|invoke_iijiji|invoke_jii|invoke_jiii|invoke_viijii|invoke_vji|js_open_process|_js_open_process|_asyncjs__js_open_process|js_popen_to_file|_js_popen_to_file|_asyncjs__js_popen_to_file|__syscall_fcntl64|js_release_file_locks|js_flock|js_fd_read|_js_fd_read|_fd_close|js_module_onMessage|_js_module_onMessage|_asyncjs__js_module_onMessage|js_waitpid|_js_waitpid|_asyncjs__js_waitpid|wasm_poll_socket|_wasm_poll_socket|_asyncjs__wasm_poll_socket|_wasm_shutdown|_asyncjs__wasm_shutdown|__asyncjs__.*)$/; + + for (let [x, original] of Object.entries(imports)) { + if (typeof original == 'function') { + let isAsyncifyImport = + original.isAsync || importPattern.test(x); + } + } + }, + instrumentWasmExports(exports) { + var ret = {}; + for (let [x, original] of Object.entries(exports)) { + if (typeof original == 'function') { + ret[x] = (...args) => { + Asyncify.exportCallStack.push(x); + try { + return original(...args); + } finally { + if (!ABORT) { + var y = Asyncify.exportCallStack.pop(); + Asyncify.maybeStopUnwind(); + } + } + }; + ret[x].orig = original; + } else { + ret[x] = original; + } + } + return ret; + }, + State: { + Normal: 0, + Unwinding: 1, + Rewinding: 2, + Disabled: 3, + }, + state: 0, + StackSize: 4096, + currData: null, + handleSleepReturnValue: 0, + exportCallStack: [], + callStackNameToId: {}, + callStackIdToName: {}, + callStackId: 0, + asyncPromiseHandlers: null, + sleepCallbacks: [], + getCallStackId(funcName) { + var id = Asyncify.callStackNameToId[funcName]; + if (id === undefined) { + id = Asyncify.callStackId++; + Asyncify.callStackNameToId[funcName] = id; + Asyncify.callStackIdToName[id] = funcName; + } + return id; + }, + maybeStopUnwind() { + if ( + Asyncify.currData && + Asyncify.state === Asyncify.State.Unwinding && + Asyncify.exportCallStack.length === 0 + ) { + // We just finished unwinding. + // Be sure to set the state before calling any other functions to avoid + // possible infinite recursion here (For example in debug pthread builds + // the dbg() function itself can call back into WebAssembly to get the + // current pthread_self() pointer). + Asyncify.state = Asyncify.State.Normal; + runtimeKeepalivePush(); + // Keep the runtime alive so that a re-wind can be done later. + runAndAbortIfError(_asyncify_stop_unwind); + if (typeof Fibers != 'undefined') { + Fibers.trampoline(); + } + } + }, + whenDone() { + return new Promise((resolve, reject) => { + Asyncify.asyncPromiseHandlers = { resolve, reject }; + }); + }, + allocateData() { + // An asyncify data structure has three fields: + // 0 current stack pos + // 4 max stack pos + // 8 id of function at bottom of the call stack (callStackIdToName[id] == name of js function) + // + // The Asyncify ABI only interprets the first two fields, the rest is for the runtime. + // We also embed a stack in the same memory region here, right next to the structure. + // This struct is also defined as asyncify_data_t in emscripten/fiber.h + var ptr = _malloc(12 + Asyncify.StackSize); + Asyncify.setDataHeader(ptr, ptr + 12, Asyncify.StackSize); + Asyncify.setDataRewindFunc(ptr); + return ptr; + }, + setDataHeader(ptr, stack, stackSize) { + HEAPU32[ptr >> 2] = stack; + HEAPU32[(ptr + 4) >> 2] = stack + stackSize; + }, + setDataRewindFunc(ptr) { + var bottomOfCallStack = Asyncify.exportCallStack[0]; + var rewindId = Asyncify.getCallStackId(bottomOfCallStack); + HEAP32[(ptr + 8) >> 2] = rewindId; + }, + getDataRewindFuncName(ptr) { + var id = HEAP32[(ptr + 8) >> 2]; + var name = Asyncify.callStackIdToName[id]; + return name; + }, + getDataRewindFunc__deps: ['$resolveGlobalSymbol'], + getDataRewindFunc(name) { + var func = wasmExports[name]; + // Exported functions in side modules are not listed in `wasmExports`, + // So we should use `resolveGlobalSymbol` helper function, which is defined in `library_dylink.js`. + if (!func) { + func = resolveGlobalSymbol(name, false).sym; + } + return func; + }, + doRewind(ptr) { + var name = Asyncify.getDataRewindFuncName(ptr); + var func = Asyncify.getDataRewindFunc(name); + // Once we have rewound and the stack we no longer need to artificially + // keep the runtime alive. + runtimeKeepalivePop(); + return func(); + }, + handleSleep(startAsync) { + if (ABORT) return; + if (Asyncify.state === Asyncify.State.Normal) { + // Prepare to sleep. Call startAsync, and see what happens: + // if the code decided to call our callback synchronously, + // then no async operation was in fact begun, and we don't + // need to do anything. + var reachedCallback = false; + var reachedAfterCallback = false; + startAsync((handleSleepReturnValue = 0) => { + if (ABORT) return; + Asyncify.handleSleepReturnValue = handleSleepReturnValue; + reachedCallback = true; + if (!reachedAfterCallback) { + // We are happening synchronously, so no need for async. + return; + } + Asyncify.state = Asyncify.State.Rewinding; + runAndAbortIfError(() => + _asyncify_start_rewind(Asyncify.currData) + ); + if (typeof MainLoop != 'undefined' && MainLoop.func) { + MainLoop.resume(); + } + var asyncWasmReturnValue, + isError = false; + try { + asyncWasmReturnValue = Asyncify.doRewind( + Asyncify.currData + ); + } catch (err) { + asyncWasmReturnValue = err; + isError = true; + } + // Track whether the return value was handled by any promise handlers. + var handled = false; + if (!Asyncify.currData) { + // All asynchronous execution has finished. + // `asyncWasmReturnValue` now contains the final + // return value of the exported async WASM function. + // + // Note: `asyncWasmReturnValue` is distinct from + // `Asyncify.handleSleepReturnValue`. + // `Asyncify.handleSleepReturnValue` contains the return + // value of the last C function to have executed + // `Asyncify.handleSleep()`, where as `asyncWasmReturnValue` + // contains the return value of the exported WASM function + // that may have called C functions that + // call `Asyncify.handleSleep()`. + var asyncPromiseHandlers = + Asyncify.asyncPromiseHandlers; + if (asyncPromiseHandlers) { + Asyncify.asyncPromiseHandlers = null; + (isError + ? asyncPromiseHandlers.reject + : asyncPromiseHandlers.resolve)( + asyncWasmReturnValue + ); + handled = true; + } + } + if (isError && !handled) { + // If there was an error and it was not handled by now, we have no choice but to + // rethrow that error into the global scope where it can be caught only by + // `onerror` or `onunhandledpromiserejection`. + throw asyncWasmReturnValue; + } + }); + reachedAfterCallback = true; + if (!reachedCallback) { + // A true async operation was begun; start a sleep. + Asyncify.state = Asyncify.State.Unwinding; + // TODO: reuse, don't alloc/free every sleep + Asyncify.currData = Asyncify.allocateData(); + if (typeof MainLoop != 'undefined' && MainLoop.func) { + MainLoop.pause(); + } + runAndAbortIfError(() => + _asyncify_start_unwind(Asyncify.currData) + ); + } + } else if (Asyncify.state === Asyncify.State.Rewinding) { + // Stop a resume. + Asyncify.state = Asyncify.State.Normal; + runAndAbortIfError(_asyncify_stop_rewind); + _free(Asyncify.currData); + Asyncify.currData = null; + // Call all sleep callbacks now that the sleep-resume is all done. + Asyncify.sleepCallbacks.forEach(callUserCallback); + } else { + abort(`invalid state: ${Asyncify.state}`); + } + return Asyncify.handleSleepReturnValue; + }, + handleAsync(startAsync) { + return Asyncify.handleSleep((wakeUp) => { + // TODO: add error handling as a second param when handleSleep implements it. + startAsync().then(wakeUp); + }); + }, + }; + + var getCFunc = (ident) => { + var func = Module['_' + ident]; // closure exported function + return func; + }; + + var writeArrayToMemory = (array, buffer) => { + HEAP8.set(array, buffer); + }; + + /** + * @param {string|null=} returnType + * @param {Array=} argTypes + * @param {Arguments|Array=} args + * @param {Object=} opts + */ + var ccall = (ident, returnType, argTypes, args, opts) => { + // For fast lookup of conversion functions + var toC = { + string: (str) => { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { + // null string + ret = stringToUTF8OnStack(str); + } + return ret; + }, + array: (arr) => { + var ret = stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + }; + + function convertReturnValue(ret) { + if (returnType === 'string') { + return UTF8ToString(ret); + } + if (returnType === 'boolean') return Boolean(ret); + return ret; + } + + var func = getCFunc(ident); + var cArgs = []; + var stack = 0; + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + // Data for a previous async operation that was in flight before us. + var previousAsync = Asyncify.currData; + var ret = func(...cArgs); + function onDone(ret) { + runtimeKeepalivePop(); + if (stack !== 0) stackRestore(stack); + return convertReturnValue(ret); + } + var asyncMode = opts?.async; + + // Keep the runtime alive through all calls. Note that this call might not be + // async, but for simplicity we push and pop in all calls. + runtimeKeepalivePush(); + if (Asyncify.currData != previousAsync) { + // This is a new async operation. The wasm is paused and has unwound its stack. + // We need to return a Promise that resolves the return value + // once the stack is rewound and execution finishes. + return Asyncify.whenDone().then(onDone); + } + + ret = onDone(ret); + // If this is an async ccall, ensure we return a promise + if (asyncMode) return Promise.resolve(ret); + return ret; + }; + + var FS_createPath = FS.createPath; + + var FS_unlink = (path) => FS.unlink(path); + + var FS_createLazyFile = FS.createLazyFile; + + var FS_createDevice = FS.createDevice; + + var writeI53ToI64Clamped = (ptr, num) => { + if (num > 0x7fffffffffffffff) { + HEAPU32[ptr >> 2] = 4294967295; + HEAPU32[(ptr + 4) >> 2] = 2147483647; + } else if (num < -0x8000000000000000) { + HEAPU32[ptr >> 2] = 0; + HEAPU32[(ptr + 4) >> 2] = 2147483648; + } else { + writeI53ToI64(ptr, num); + } + }; + + var writeI53ToI64Signaling = (ptr, num) => { + if (num > 0x7fffffffffffffff || num < -0x8000000000000000) { + throw `RangeError: ${num}`; + } + writeI53ToI64(ptr, num); + }; + + var writeI53ToU64Clamped = (ptr, num) => { + if (num > 0xffffffffffffffff) { + HEAPU32[ptr >> 2] = 4294967295; + HEAPU32[(ptr + 4) >> 2] = 4294967295; + } else if (num < 0) { + HEAPU32[ptr >> 2] = 0; + HEAPU32[(ptr + 4) >> 2] = 0; + } else { + writeI53ToI64(ptr, num); + } + }; + + var writeI53ToU64Signaling = (ptr, num) => { + if (num < 0 || num > 0xffffffffffffffff) { + throw `RangeError: ${num}`; + } + writeI53ToI64(ptr, num); + }; + + var readI53FromU64 = (ptr) => { + return HEAPU32[ptr >> 2] + HEAPU32[(ptr + 4) >> 2] * 4294967296; + }; + + var convertI32PairToI53 = (lo, hi) => { + return (lo >>> 0) + hi * 4294967296; + }; + + var convertI32PairToI53Checked = (lo, hi) => { + return (hi + 0x200000) >>> 0 < 0x400001 - !!lo + ? (lo >>> 0) + hi * 4294967296 + : NaN; + }; + + var convertU32PairToI53 = (lo, hi) => { + return (lo >>> 0) + (hi >>> 0) * 4294967296; + }; + + var getTempRet0 = (val) => __emscripten_tempret_get(); + + var setTempRet0 = (val) => __emscripten_tempret_set(val); + + var _stackAlloc = stackAlloc; + + var _stackSave = stackSave; + + var _stackRestore = stackSave; + + var _setTempRet0 = setTempRet0; + Module['_setTempRet0'] = _setTempRet0; + + var _getTempRet0 = getTempRet0; + Module['_getTempRet0'] = _getTempRet0; + + var ptrToString = (ptr) => { + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + ptr >>>= 0; + return '0x' + ptr.toString(16).padStart(8, '0'); + }; + + var _emscripten_notify_memory_growth = (memoryIndex) => { + updateMemoryViews(); + }; + _emscripten_notify_memory_growth.sig = 'vp'; + + var withStackSave = (f) => { + var stack = stackSave(); + var ret = f(); + stackRestore(stack); + return ret; + }; + + var strError = (errno) => UTF8ToString(_strerror(errno)); + + var _endprotoent = () => { + // void endprotoent(void); + // We're not using a real protocol database so we don't do a real close. + }; + _endprotoent.sig = 'v'; + + var _getprotoent = (number) => { + // struct protoent *getprotoent(void); + // reads the next entry from the protocols 'database' or return NULL if 'eof' + if (_setprotoent.index === Protocols.list.length) { + return 0; + } + var result = Protocols.list[_setprotoent.index++]; + return result; + }; + _getprotoent.sig = 'p'; + + var _emscripten_run_script = (ptr) => { + eval(UTF8ToString(ptr)); + }; + _emscripten_run_script.sig = 'vp'; + + /** @suppress{checkTypes} */ + var _emscripten_run_script_int = (ptr) => { + return eval(UTF8ToString(ptr)) | 0; + }; + _emscripten_run_script_int.sig = 'ip'; + + var _emscripten_run_script_string = (ptr) => { + var s = eval(UTF8ToString(ptr)); + if (s == null) { + return 0; + } + s += ''; + var me = _emscripten_run_script_string; + var len = lengthBytesUTF8(s); + if (!me.bufferSize || me.bufferSize < len + 1) { + if (me.bufferSize) _free(me.buffer); + me.bufferSize = len + 1; + me.buffer = _malloc(me.bufferSize); + } + stringToUTF8(s, me.buffer, me.bufferSize); + return me.buffer; + }; + _emscripten_run_script_string.sig = 'pp'; + + var _emscripten_random = () => Math.random(); + _emscripten_random.sig = 'f'; + + var _emscripten_performance_now = () => performance.now(); + _emscripten_performance_now.sig = 'd'; + + var __emscripten_get_now_is_monotonic = () => nowIsMonotonic; + __emscripten_get_now_is_monotonic.sig = 'i'; + + var warnOnce = (text) => { + warnOnce.shown ||= {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; + err(text); + } + }; + + var jsStackTrace = () => new Error().stack.toString(); + + /** @param {number=} flags */ + var getCallstack = (flags) => { + var callstack = jsStackTrace(); + + // Process all lines: + var lines = callstack.split('\n'); + callstack = ''; + // Extract components of form: + // ' Object._main@http://server.com:4324:12' + var firefoxRe = new RegExp('\\s*(.*?)@(.*?):([0-9]+):([0-9]+)'); + // Extract components of form: + // ' at Object._main (http://server.com/file.html:4324:12)' + var chromeRe = new RegExp('\\s*at (.*?) \\((.*):(.*):(.*)\\)'); + + for (var line of lines) { + var symbolName = ''; + var file = ''; + var lineno = 0; + var column = 0; + + var parts = chromeRe.exec(line); + if (parts?.length == 5) { + symbolName = parts[1]; + file = parts[2]; + lineno = parts[3]; + column = parts[4]; + } else { + parts = firefoxRe.exec(line); + if (parts?.length >= 4) { + symbolName = parts[1]; + file = parts[2]; + lineno = parts[3]; + // Old Firefox doesn't carry column information, but in new FF30, it + // is present. See https://bugzilla.mozilla.org/show_bug.cgi?id=762556 + column = parts[4] | 0; + } else { + // Was not able to extract this line for demangling/sourcemapping + // purposes. Output it as-is. + callstack += line + '\n'; + continue; + } + } + + // Find the symbols in the callstack that corresponds to the functions that + // report callstack information, and remove everything up to these from the + // output. + if ( + symbolName == '_emscripten_log' || + symbolName == '_emscripten_get_callstack' + ) { + callstack = ''; + continue; + } + + if (flags & 24) { + if (flags & 64) { + file = file.substring( + file.replace(/\\/g, '/').lastIndexOf('/') + 1 + ); + } + callstack += ` at ${symbolName} (${file}:${lineno}:${column})\n`; + } + } + // Trim extra whitespace at the end of the output. + callstack = callstack.replace(/\s+$/, ''); + return callstack; + }; + var emscriptenLog = (flags, str) => { + if (flags & 24) { + str = str.replace(/\s+$/, ''); // Ensure the message and the callstack are joined cleanly with exactly one newline. + str += (str.length > 0 ? '\n' : '') + getCallstack(flags); + } + + if (flags & 1) { + if (flags & 4) { + console.error(str); + } else if (flags & 2) { + console.warn(str); + } else if (flags & 512) { + console.info(str); + } else if (flags & 256) { + console.debug(str); + } else { + console.log(str); + } + } else if (flags & 6) { + err(str); + } else { + out(str); + } + }; + + var reallyNegative = (x) => x < 0 || (x === 0 && 1 / x === -Infinity); + + var reSign = (value, bits) => { + if (value <= 0) { + return value; + } + var half = + bits <= 32 + ? Math.abs(1 << (bits - 1)) // abs is needed if bits == 32 + : Math.pow(2, bits - 1); + // for huge values, we can hit the precision limit and always get true here. + // so don't do that but, in general there is no perfect solution here. With + // 64-bit ints, we get rounding and errors + // TODO: In i64 mode 1, resign the two parts separately and safely + if (value >= half && (bits <= 32 || value > half)) { + // Cannot bitshift half, as it may be at the limit of the bits JS uses in + // bitshifts + value = -2 * half + value; + } + return value; + }; + + var unSign = (value, bits) => { + if (value >= 0) { + return value; + } + // Need some trickery, since if bits == 32, we are right at the limit of the + // bits JS uses in bitshifts + return bits <= 32 + ? 2 * Math.abs(1 << (bits - 1)) + value + : Math.pow(2, bits) + value; + }; + + var strLen = (ptr) => { + var end = ptr; + while (HEAPU8[end]) ++end; + return end - ptr; + }; + + var formatString = (format, varargs) => { + var textIndex = format; + var argIndex = varargs; + // This must be called before reading a double or i64 vararg. It will bump the pointer properly. + // It also does an assert on i32 values, so it's nice to call it before all varargs calls. + function prepVararg(ptr, type) { + if (type === 'double' || type === 'i64') { + // move so the load is aligned + if (ptr & 7) { + ptr += 4; + } + } else { + } + return ptr; + } + function getNextArg(type) { + // NOTE: Explicitly ignoring type safety. Otherwise this fails: + // int x = 4; printf("%c\n", (char)x); + var ret; + argIndex = prepVararg(argIndex, type); + if (type === 'double') { + ret = HEAPF64[argIndex >> 3]; + argIndex += 8; + } else if (type == 'i64') { + ret = [HEAP32[argIndex >> 2], HEAP32[(argIndex + 4) >> 2]]; + argIndex += 8; + } else { + type = 'i32'; // varargs are always i32, i64, or double + ret = HEAP32[argIndex >> 2]; + argIndex += 4; + } + return ret; + } + + var ret = []; + var curr, next, currArg; + while (1) { + var startTextIndex = textIndex; + curr = HEAP8[textIndex]; + if (curr === 0) break; + next = HEAP8[textIndex + 1]; + if (curr == 37) { + // Handle flags. + var flagAlwaysSigned = false; + var flagLeftAlign = false; + var flagAlternative = false; + var flagZeroPad = false; + var flagPadSign = false; + flagsLoop: while (1) { + switch (next) { + case 43: + flagAlwaysSigned = true; + break; + case 45: + flagLeftAlign = true; + break; + case 35: + flagAlternative = true; + break; + case 48: + if (flagZeroPad) { + break flagsLoop; + } else { + flagZeroPad = true; + break; + } + case 32: + flagPadSign = true; + break; + default: + break flagsLoop; + } + textIndex++; + next = HEAP8[textIndex + 1]; + } + + // Handle width. + var width = 0; + if (next == 42) { + width = getNextArg('i32'); + textIndex++; + next = HEAP8[textIndex + 1]; + } else { + while (next >= 48 && next <= 57) { + width = width * 10 + (next - 48); + textIndex++; + next = HEAP8[textIndex + 1]; + } + } + + // Handle precision. + var precisionSet = false, + precision = -1; + if (next == 46) { + precision = 0; + precisionSet = true; + textIndex++; + next = HEAP8[textIndex + 1]; + if (next == 42) { + precision = getNextArg('i32'); + textIndex++; + } else { + while (1) { + var precisionChr = HEAP8[textIndex + 1]; + if (precisionChr < 48 || precisionChr > 57) break; + precision = precision * 10 + (precisionChr - 48); + textIndex++; + } + } + next = HEAP8[textIndex + 1]; + } + if (precision < 0) { + precision = 6; // Standard default. + precisionSet = false; + } + + // Handle integer sizes. WARNING: These assume a 32-bit architecture! + var argSize; + switch (String.fromCharCode(next)) { + case 'h': + var nextNext = HEAP8[textIndex + 2]; + if (nextNext == 104) { + textIndex++; + argSize = 1; // char (actually i32 in varargs) + } else { + argSize = 2; // short (actually i32 in varargs) + } + break; + case 'l': + var nextNext = HEAP8[textIndex + 2]; + if (nextNext == 108) { + textIndex++; + argSize = 8; // long long + } else { + argSize = 4; // long + } + break; + case 'L': // long long + case 'q': // int64_t + case 'j': // intmax_t + argSize = 8; + break; + case 'z': // size_t + case 't': // ptrdiff_t + case 'I': // signed ptrdiff_t or unsigned size_t + argSize = 4; + break; + default: + argSize = null; + } + if (argSize) textIndex++; + next = HEAP8[textIndex + 1]; + + // Handle type specifier. + switch (String.fromCharCode(next)) { + case 'd': + case 'i': + case 'u': + case 'o': + case 'x': + case 'X': + case 'p': { + // Integer. + var signed = next == 100 || next == 105; + argSize = argSize || 4; + currArg = getNextArg('i' + argSize * 8); + var argText; + // Flatten i64-1 [low, high] into a (slightly rounded) double + if (argSize == 8) { + currArg = + next == 117 + ? convertU32PairToI53( + currArg[0], + currArg[1] + ) + : convertI32PairToI53( + currArg[0], + currArg[1] + ); + } + // Truncate to requested size. + if (argSize <= 4) { + var limit = Math.pow(256, argSize) - 1; + currArg = (signed ? reSign : unSign)( + currArg & limit, + argSize * 8 + ); + } + // Format the number. + var currAbsArg = Math.abs(currArg); + var prefix = ''; + if (next == 100 || next == 105) { + argText = reSign(currArg, 8 * argSize).toString(10); + } else if (next == 117) { + argText = unSign(currArg, 8 * argSize).toString(10); + currArg = Math.abs(currArg); + } else if (next == 111) { + argText = + (flagAlternative ? '0' : '') + + currAbsArg.toString(8); + } else if (next == 120 || next == 88) { + prefix = + flagAlternative && currArg != 0 ? '0x' : ''; + if (currArg < 0) { + // Represent negative numbers in hex as 2's complement. + currArg = -currArg; + argText = (currAbsArg - 1).toString(16); + var buffer = []; + for (var i = 0; i < argText.length; i++) { + buffer.push( + ( + 0xf - parseInt(argText[i], 16) + ).toString(16) + ); + } + argText = buffer.join(''); + while (argText.length < argSize * 2) + argText = 'f' + argText; + } else { + argText = currAbsArg.toString(16); + } + if (next == 88) { + prefix = prefix.toUpperCase(); + argText = argText.toUpperCase(); + } + } else if (next == 112) { + if (currAbsArg === 0) { + argText = '(nil)'; + } else { + prefix = '0x'; + argText = currAbsArg.toString(16); + } + } + if (precisionSet) { + while (argText.length < precision) { + argText = '0' + argText; + } + } + + // Add sign if needed + if (currArg >= 0) { + if (flagAlwaysSigned) { + prefix = '+' + prefix; + } else if (flagPadSign) { + prefix = ' ' + prefix; + } + } + + // Move sign to prefix so we zero-pad after the sign + if (argText.charAt(0) == '-') { + prefix = '-' + prefix; + argText = argText.slice(1); + } + + // Add padding. + while (prefix.length + argText.length < width) { + if (flagLeftAlign) { + argText += ' '; + } else { + if (flagZeroPad) { + argText = '0' + argText; + } else { + prefix = ' ' + prefix; + } + } + } + + // Insert the result into the buffer. + argText = prefix + argText; + argText + .split('') + .forEach((chr) => ret.push(chr.charCodeAt(0))); + break; + } + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': { + // Float. + currArg = getNextArg('double'); + var argText; + if (isNaN(currArg)) { + argText = 'nan'; + flagZeroPad = false; + } else if (!isFinite(currArg)) { + argText = (currArg < 0 ? '-' : '') + 'inf'; + flagZeroPad = false; + } else { + var isGeneral = false; + var effectivePrecision = Math.min(precision, 20); + + // Convert g/G to f/F or e/E, as per: + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html + if (next == 103 || next == 71) { + isGeneral = true; + precision = precision || 1; + var exponent = parseInt( + currArg + .toExponential(effectivePrecision) + .split('e')[1], + 10 + ); + if (precision > exponent && exponent >= -4) { + next = (next == 103 ? 'f' : 'F').charCodeAt( + 0 + ); + precision -= exponent + 1; + } else { + next = (next == 103 ? 'e' : 'E').charCodeAt( + 0 + ); + precision--; + } + effectivePrecision = Math.min(precision, 20); + } + + if (next == 101 || next == 69) { + argText = + currArg.toExponential(effectivePrecision); + // Make sure the exponent has at least 2 digits. + if (/[eE][-+]\d$/.test(argText)) { + argText = + argText.slice(0, -1) + + '0' + + argText.slice(-1); + } + } else if (next == 102 || next == 70) { + argText = currArg.toFixed(effectivePrecision); + if (currArg === 0 && reallyNegative(currArg)) { + argText = '-' + argText; + } + } + + var parts = argText.split('e'); + if (isGeneral && !flagAlternative) { + // Discard trailing zeros and periods. + while ( + parts[0].length > 1 && + parts[0].includes('.') && + (parts[0].slice(-1) == '0' || + parts[0].slice(-1) == '.') + ) { + parts[0] = parts[0].slice(0, -1); + } + } else { + // Make sure we have a period in alternative mode. + if ( + flagAlternative && + argText.indexOf('.') == -1 + ) + parts[0] += '.'; + // Zero pad until required precision. + while (precision > effectivePrecision++) + parts[0] += '0'; + } + argText = + parts[0] + + (parts.length > 1 ? 'e' + parts[1] : ''); + + // Capitalize 'E' if needed. + if (next == 69) argText = argText.toUpperCase(); + + // Add sign. + if (currArg >= 0) { + if (flagAlwaysSigned) { + argText = '+' + argText; + } else if (flagPadSign) { + argText = ' ' + argText; + } + } + } + + // Add padding. + while (argText.length < width) { + if (flagLeftAlign) { + argText += ' '; + } else { + if ( + flagZeroPad && + (argText[0] == '-' || argText[0] == '+') + ) { + argText = + argText[0] + '0' + argText.slice(1); + } else { + argText = + (flagZeroPad ? '0' : ' ') + argText; + } + } + } + + // Adjust case. + if (next < 97) argText = argText.toUpperCase(); + + // Insert the result into the buffer. + argText + .split('') + .forEach((chr) => ret.push(chr.charCodeAt(0))); + break; + } + case 's': { + // String. + var arg = getNextArg('i8*'); + var argLength = arg ? strLen(arg) : '(null)'.length; + if (precisionSet) + argLength = Math.min(argLength, precision); + if (!flagLeftAlign) { + while (argLength < width--) { + ret.push(32); + } + } + if (arg) { + for (var i = 0; i < argLength; i++) { + ret.push(HEAPU8[arg++]); + } + } else { + ret = ret.concat( + intArrayFromString( + '(null)'.slice(0, argLength), + true + ) + ); + } + if (flagLeftAlign) { + while (argLength < width--) { + ret.push(32); + } + } + break; + } + case 'c': { + // Character. + if (flagLeftAlign) ret.push(getNextArg('i8')); + while (--width > 0) { + ret.push(32); + } + if (!flagLeftAlign) ret.push(getNextArg('i8')); + break; + } + case 'n': { + // Write the length written so far to the next parameter. + var ptr = getNextArg('i32*'); + HEAP32[ptr >> 2] = ret.length; + break; + } + case '%': { + // Literal percent sign. + ret.push(curr); + break; + } + default: { + // Unknown specifiers remain untouched. + for (var i = startTextIndex; i < textIndex + 2; i++) { + ret.push(HEAP8[i]); + } + } + } + textIndex += 2; + // TODO: Support a/A (hex float) and m (last error) specifiers. + // TODO: Support %1${specifier} for arg selection. + } else { + ret.push(curr); + textIndex += 1; + } + } + return ret; + }; + + var _emscripten_log = (flags, format, varargs) => { + var result = formatString(format, varargs); + var str = UTF8ArrayToString(result); + emscriptenLog(flags, str); + }; + _emscripten_log.sig = 'vipp'; + + var _emscripten_get_compiler_setting = (name) => { + throw 'You must build with -sRETAIN_COMPILER_SETTINGS for getCompilerSetting or emscripten_get_compiler_setting to work'; + }; + _emscripten_get_compiler_setting.sig = 'pp'; + + var _emscripten_has_asyncify = () => 1; + _emscripten_has_asyncify.sig = 'i'; + + var _emscripten_debugger = () => { + debugger; + }; + _emscripten_debugger.sig = 'v'; + + var _emscripten_print_double = (x, to, max) => { + var str = x + ''; + if (to) return stringToUTF8(str, to, max); + else return lengthBytesUTF8(str); + }; + _emscripten_print_double.sig = 'idpi'; + + var _emscripten_asm_const_double = (code, sigPtr, argbuf) => { + return runEmAsmFunction(code, sigPtr, argbuf); + }; + _emscripten_asm_const_double.sig = 'dppp'; + + var _emscripten_asm_const_ptr = (code, sigPtr, argbuf) => { + return runEmAsmFunction(code, sigPtr, argbuf); + }; + _emscripten_asm_const_ptr.sig = 'pppp'; + + var runMainThreadEmAsm = (emAsmAddr, sigPtr, argbuf, sync) => { + var args = readEmAsmArgs(sigPtr, argbuf); + return ASM_CONSTS[emAsmAddr](...args); + }; + + var _emscripten_asm_const_int_sync_on_main_thread = ( + emAsmAddr, + sigPtr, + argbuf + ) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1); + _emscripten_asm_const_int_sync_on_main_thread.sig = 'ippp'; + + var _emscripten_asm_const_ptr_sync_on_main_thread = ( + emAsmAddr, + sigPtr, + argbuf + ) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1); + _emscripten_asm_const_ptr_sync_on_main_thread.sig = 'pppp'; + + var _emscripten_asm_const_double_sync_on_main_thread = + _emscripten_asm_const_int_sync_on_main_thread; + _emscripten_asm_const_double_sync_on_main_thread.sig = 'dppp'; + + var _emscripten_asm_const_async_on_main_thread = ( + emAsmAddr, + sigPtr, + argbuf + ) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 0); + _emscripten_asm_const_async_on_main_thread.sig = 'vppp'; + + var jstoi_s = Number; + + var __Unwind_Backtrace = (func, arg) => { + var trace = getCallstack(); + var parts = trace.split('\n'); + for (var i = 0; i < parts.length; i++) { + var ret = (( + a1, + a2 + ) => {}) /* a dynamic function call to signature iii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + 0, + arg + ); + if (ret !== 0) return; + } + }; + __Unwind_Backtrace.sig = 'ipp'; + + var __Unwind_GetIPInfo = (context, ipBefore) => abort('Unwind_GetIPInfo'); + __Unwind_GetIPInfo.sig = 'ppp'; + + var __Unwind_FindEnclosingFunction = (ip) => 0; + __Unwind_FindEnclosingFunction.sig = 'pp'; + + var __Unwind_RaiseException = (ex) => { + err('Warning: _Unwind_RaiseException is not correctly implemented'); + return ___cxa_throw(ex, 0, 0); + }; + __Unwind_RaiseException.sig = 'ip'; + + var __Unwind_DeleteException = (ex) => err('TODO: Unwind_DeleteException'); + __Unwind_DeleteException.sig = 'vp'; + + var getDynCaller = (sig, ptr) => { + return (...args) => dynCall(sig, ptr, args); + }; + + var _emscripten_exit_with_live_runtime = () => { + runtimeKeepalivePush(); + throw 'unwind'; + }; + _emscripten_exit_with_live_runtime.sig = 'v'; + + var _emscripten_force_exit = (status) => { + __emscripten_runtime_keepalive_clear(); + _exit(status); + }; + _emscripten_force_exit.sig = 'vi'; + + var _emscripten_outn = (str, len) => out(UTF8ToString(str, len)); + _emscripten_outn.sig = 'vpp'; + + var _emscripten_errn = (str, len) => err(UTF8ToString(str, len)); + _emscripten_errn.sig = 'vpp'; + + var _emscripten_throw_number = (number) => { + throw number; + }; + _emscripten_throw_number.sig = 'vd'; + + var _emscripten_throw_string = (str) => { + throw UTF8ToString(str); + }; + _emscripten_throw_string.sig = 'vp'; + + var _emscripten_runtime_keepalive_push = runtimeKeepalivePush; + _emscripten_runtime_keepalive_push.sig = 'v'; + + var _emscripten_runtime_keepalive_pop = runtimeKeepalivePop; + _emscripten_runtime_keepalive_pop.sig = 'v'; + + var _emscripten_runtime_keepalive_check = keepRuntimeAlive; + _emscripten_runtime_keepalive_check.sig = 'i'; + + var asmjsMangle = (x) => { + if (x == '__main_argc_argv') { + x = 'main'; + } + return x.startsWith('dynCall_') ? x : '_' + x; + }; + + var ___global_base = 1024; + + var __emscripten_fs_load_embedded_files = (ptr) => { + do { + var name_addr = HEAPU32[ptr >> 2]; + ptr += 4; + var len = HEAPU32[ptr >> 2]; + ptr += 4; + var content = HEAPU32[ptr >> 2]; + ptr += 4; + var name = UTF8ToString(name_addr); + FS.createPath('/', PATH.dirname(name), true, true); + // canOwn this data in the filesystem, it is a slice of wasm memory that will never change + FS.createDataFile( + name, + null, + HEAP8.subarray(content, content + len), + true, + true, + true + ); + } while (HEAPU32[ptr >> 2]); + }; + __emscripten_fs_load_embedded_files.sig = 'vp'; + + var POINTER_SIZE = 4; + function getNativeTypeSize(type) { + // prettier-ignore + switch (type) { + case 'i1': case 'i8': case 'u8': return 1; + case 'i16': case 'u16': return 2; + case 'i32': case 'u32': return 4; + case 'i64': case 'u64': return 8; + case 'float': return 4; + case 'double': return 8; + default: { + if (type[type.length - 1] === '*') { + return POINTER_SIZE; + } + if (type[0] === 'i') { + const bits = Number(type.slice(1)); + assert(bits % 8 === 0, `getNativeTypeSize invalid bits ${bits}, ${type} type`); + return bits / 8; + } + return 0; + } + } + } + + var onInits = []; + + var addOnInit = (cb) => onInits.unshift(cb); + + var onMains = []; + + var addOnPreMain = (cb) => onMains.unshift(cb); + + var onExits = []; + + var addOnExit = (cb) => onExits.unshift(cb); + + var STACK_SIZE = 65536; + + var STACK_ALIGN = 16; + + var ASSERTIONS = 0; + + /** + * @param {string=} returnType + * @param {Array=} argTypes + * @param {Object=} opts + */ + var cwrap = (ident, returnType, argTypes, opts) => { + // When the function takes numbers and returns a number, we can just return + // the original function + var numericArgs = + !argTypes || + argTypes.every((type) => type === 'number' || type === 'boolean'); + var numericRet = returnType !== 'string'; + if (numericRet && numericArgs && !opts) { + return getCFunc(ident); + } + return (...args) => ccall(ident, returnType, argTypes, args, opts); + }; + + var removeFunction = (index) => { + functionsInTableMap.delete(getWasmTableEntry(index)); + setWasmTableEntry(index, null); + freeTableIndexes.push(index); + }; + + var _emscripten_math_cbrt = Math.cbrt; + _emscripten_math_cbrt.sig = 'dd'; + + var _emscripten_math_pow = Math.pow; + _emscripten_math_pow.sig = 'ddd'; + + var _emscripten_math_random = Math.random; + _emscripten_math_random.sig = 'd'; + + var _emscripten_math_sign = Math.sign; + _emscripten_math_sign.sig = 'dd'; + + var _emscripten_math_sqrt = Math.sqrt; + _emscripten_math_sqrt.sig = 'dd'; + + var _emscripten_math_exp = Math.exp; + _emscripten_math_exp.sig = 'dd'; + + var _emscripten_math_expm1 = Math.expm1; + _emscripten_math_expm1.sig = 'dd'; + + var _emscripten_math_fmod = (x, y) => x % y; + _emscripten_math_fmod.sig = 'ddd'; + + var _emscripten_math_log = Math.log; + _emscripten_math_log.sig = 'dd'; + + var _emscripten_math_log1p = Math.log1p; + _emscripten_math_log1p.sig = 'dd'; + + var _emscripten_math_log10 = Math.log10; + _emscripten_math_log10.sig = 'dd'; + + var _emscripten_math_log2 = Math.log2; + _emscripten_math_log2.sig = 'dd'; + + var _emscripten_math_round = Math.round; + _emscripten_math_round.sig = 'dd'; + + var _emscripten_math_acos = Math.acos; + _emscripten_math_acos.sig = 'dd'; + + var _emscripten_math_acosh = Math.acosh; + _emscripten_math_acosh.sig = 'dd'; + + var _emscripten_math_asin = Math.asin; + _emscripten_math_asin.sig = 'dd'; + + var _emscripten_math_asinh = Math.asinh; + _emscripten_math_asinh.sig = 'dd'; + + var _emscripten_math_atan = Math.atan; + _emscripten_math_atan.sig = 'dd'; + + var _emscripten_math_atanh = Math.atanh; + _emscripten_math_atanh.sig = 'dd'; + + var _emscripten_math_atan2 = Math.atan2; + _emscripten_math_atan2.sig = 'ddd'; + + var _emscripten_math_cos = Math.cos; + _emscripten_math_cos.sig = 'dd'; + + var _emscripten_math_cosh = Math.cosh; + _emscripten_math_cosh.sig = 'dd'; + + var _emscripten_math_hypot = (count, varargs) => { + var args = []; + for (var i = 0; i < count; ++i) { + args.push(HEAPF64[(varargs + i * 8) >> 3]); + } + return Math.hypot(...args); + }; + _emscripten_math_hypot.sig = 'dip'; + + var _emscripten_math_sin = Math.sin; + _emscripten_math_sin.sig = 'dd'; + + var _emscripten_math_sinh = Math.sinh; + _emscripten_math_sinh.sig = 'dd'; + + var _emscripten_math_tan = Math.tan; + _emscripten_math_tan.sig = 'dd'; + + var _emscripten_math_tanh = Math.tanh; + _emscripten_math_tanh.sig = 'dd'; + + var intArrayToString = (array) => { + var ret = []; + for (var i = 0; i < array.length; i++) { + var chr = array[i]; + if (chr > 0xff) { + chr &= 0xff; + } + ret.push(String.fromCharCode(chr)); + } + return ret.join(''); + }; + + var AsciiToString = (ptr) => { + var str = ''; + while (1) { + var ch = HEAPU8[ptr++]; + if (!ch) return str; + str += String.fromCharCode(ch); + } + }; + + var UTF16Decoder = + typeof TextDecoder != 'undefined' + ? new TextDecoder('utf-16le') + : undefined; + + var UTF16ToString = (ptr, maxBytesToRead) => { + var endPtr = ptr; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. + // Also, use the length info to avoid running tiny strings through + // TextDecoder, since .subarray() allocates garbage. + var idx = endPtr >> 1; + var maxIdx = idx + maxBytesToRead / 2; + // If maxBytesToRead is not passed explicitly, it will be undefined, and this + // will always evaluate to true. This saves on code size. + while (!(idx >= maxIdx) && HEAPU16[idx]) ++idx; + endPtr = idx << 1; + + if (endPtr - ptr > 32 && UTF16Decoder) + return UTF16Decoder.decode(HEAPU8.subarray(ptr, endPtr)); + + // Fallback: decode without UTF16Decoder + var str = ''; + + // If maxBytesToRead is not passed explicitly, it will be undefined, and the + // for-loop's condition will always evaluate to true. The loop is then + // terminated on the first null char. + for (var i = 0; !(i >= maxBytesToRead / 2); ++i) { + var codeUnit = HEAP16[(ptr + i * 2) >> 1]; + if (codeUnit == 0) break; + // fromCharCode constructs a character from a UTF-16 code unit, so we can + // pass the UTF16 string right through. + str += String.fromCharCode(codeUnit); + } + + return str; + }; + + var stringToUTF16 = (str, outPtr, maxBytesToWrite) => { + // Backwards compatibility: if max bytes is not specified, assume unsafe unbounded write is allowed. + maxBytesToWrite ??= 0x7fffffff; + if (maxBytesToWrite < 2) return 0; + maxBytesToWrite -= 2; // Null terminator. + var startPtr = outPtr; + var numCharsToWrite = + maxBytesToWrite < str.length * 2 ? maxBytesToWrite / 2 : str.length; + for (var i = 0; i < numCharsToWrite; ++i) { + // charCodeAt returns a UTF-16 encoded code unit, so it can be directly written to the HEAP. + var codeUnit = str.charCodeAt(i); // possibly a lead surrogate + HEAP16[outPtr >> 1] = codeUnit; + outPtr += 2; + } + // Null-terminate the pointer to the HEAP. + HEAP16[outPtr >> 1] = 0; + return outPtr - startPtr; + }; + + var lengthBytesUTF16 = (str) => str.length * 2; + + var UTF32ToString = (ptr, maxBytesToRead) => { + var i = 0; + + var str = ''; + // If maxBytesToRead is not passed explicitly, it will be undefined, and this + // will always evaluate to true. This saves on code size. + while (!(i >= maxBytesToRead / 4)) { + var utf32 = HEAP32[(ptr + i * 4) >> 2]; + if (utf32 == 0) break; + ++i; + // Gotcha: fromCharCode constructs a character from a UTF-16 encoded code (pair), not from a Unicode code point! So encode the code point to UTF-16 for constructing. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + if (utf32 >= 0x10000) { + var ch = utf32 - 0x10000; + str += String.fromCharCode( + 0xd800 | (ch >> 10), + 0xdc00 | (ch & 0x3ff) + ); + } else { + str += String.fromCharCode(utf32); + } + } + return str; + }; + + var stringToUTF32 = (str, outPtr, maxBytesToWrite) => { + // Backwards compatibility: if max bytes is not specified, assume unsafe unbounded write is allowed. + maxBytesToWrite ??= 0x7fffffff; + if (maxBytesToWrite < 4) return 0; + var startPtr = outPtr; + var endPtr = startPtr + maxBytesToWrite - 4; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! We must decode the string to UTF-32 to the heap. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var codeUnit = str.charCodeAt(i); // possibly a lead surrogate + if (codeUnit >= 0xd800 && codeUnit <= 0xdfff) { + var trailSurrogate = str.charCodeAt(++i); + codeUnit = + (0x10000 + ((codeUnit & 0x3ff) << 10)) | + (trailSurrogate & 0x3ff); + } + HEAP32[outPtr >> 2] = codeUnit; + outPtr += 4; + if (outPtr + 4 > endPtr) break; + } + // Null-terminate the pointer to the HEAP. + HEAP32[outPtr >> 2] = 0; + return outPtr - startPtr; + }; + + var lengthBytesUTF32 = (str) => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! We must decode the string to UTF-32 to the heap. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var codeUnit = str.charCodeAt(i); + if (codeUnit >= 0xd800 && codeUnit <= 0xdfff) ++i; // possibly a lead surrogate, so skip over the tail surrogate. + len += 4; + } + + return len; + }; + + var JSEvents = { + memcpy(target, src, size) { + HEAP8.set(HEAP8.subarray(src, src + size), target); + }, + removeAllEventListeners() { + while (JSEvents.eventHandlers.length) { + JSEvents._removeHandler(JSEvents.eventHandlers.length - 1); + } + JSEvents.deferredCalls = []; + }, + registerRemoveEventListeners() { + if (!JSEvents.removeEventListenersRegistered) { + addOnExit(JSEvents.removeAllEventListeners); + JSEvents.removeEventListenersRegistered = true; + } + }, + inEventHandler: 0, + deferredCalls: [], + deferCall(targetFunction, precedence, argsList) { + function arraysHaveEqualContent(arrA, arrB) { + if (arrA.length != arrB.length) return false; + + for (var i in arrA) { + if (arrA[i] != arrB[i]) return false; + } + return true; + } + // Test if the given call was already queued, and if so, don't add it again. + for (var call of JSEvents.deferredCalls) { + if ( + call.targetFunction == targetFunction && + arraysHaveEqualContent(call.argsList, argsList) + ) { + return; + } + } + JSEvents.deferredCalls.push({ + targetFunction, + precedence, + argsList, + }); + + JSEvents.deferredCalls.sort((x, y) => x.precedence < y.precedence); + }, + removeDeferredCalls(targetFunction) { + JSEvents.deferredCalls = JSEvents.deferredCalls.filter( + (call) => call.targetFunction != targetFunction + ); + }, + canPerformEventHandlerRequests() { + if (navigator.userActivation) { + // Verify against transient activation status from UserActivation API + // whether it is possible to perform a request here without needing to defer. See + // https://developer.mozilla.org/en-US/docs/Web/Security/User_activation#transient_activation + // and https://caniuse.com/mdn-api_useractivation + // At the time of writing, Firefox does not support this API: https://bugzilla.mozilla.org/show_bug.cgi?id=1791079 + return navigator.userActivation.isActive; + } + + return ( + JSEvents.inEventHandler && + JSEvents.currentEventHandler.allowsDeferredCalls + ); + }, + runDeferredCalls() { + if (!JSEvents.canPerformEventHandlerRequests()) { + return; + } + var deferredCalls = JSEvents.deferredCalls; + JSEvents.deferredCalls = []; + for (var call of deferredCalls) { + call.targetFunction(...call.argsList); + } + }, + eventHandlers: [], + removeAllHandlersOnTarget: (target, eventTypeString) => { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if ( + JSEvents.eventHandlers[i].target == target && + (!eventTypeString || + eventTypeString == + JSEvents.eventHandlers[i].eventTypeString) + ) { + JSEvents._removeHandler(i--); + } + } + }, + _removeHandler(i) { + var h = JSEvents.eventHandlers[i]; + h.target.removeEventListener( + h.eventTypeString, + h.eventListenerFunc, + h.useCapture + ); + JSEvents.eventHandlers.splice(i, 1); + }, + registerOrRemoveHandler(eventHandler) { + if (!eventHandler.target) { + return -4; + } + if (eventHandler.callbackfunc) { + eventHandler.eventListenerFunc = function (event) { + // Increment nesting count for the event handler. + ++JSEvents.inEventHandler; + JSEvents.currentEventHandler = eventHandler; + // Process any old deferred calls the user has placed. + JSEvents.runDeferredCalls(); + // Process the actual event, calls back to user C code handler. + eventHandler.handlerFunc(event); + // Process any new deferred calls that were placed right now from this event handler. + JSEvents.runDeferredCalls(); + // Out of event handler - restore nesting count. + --JSEvents.inEventHandler; + }; + + eventHandler.target.addEventListener( + eventHandler.eventTypeString, + eventHandler.eventListenerFunc, + eventHandler.useCapture + ); + JSEvents.eventHandlers.push(eventHandler); + JSEvents.registerRemoveEventListeners(); + } else { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if ( + JSEvents.eventHandlers[i].target == + eventHandler.target && + JSEvents.eventHandlers[i].eventTypeString == + eventHandler.eventTypeString + ) { + JSEvents._removeHandler(i--); + } + } + } + return 0; + }, + getNodeNameForTarget(target) { + if (!target) return ''; + if (target == window) return '#window'; + if (target == screen) return '#screen'; + return target?.nodeName || ''; + }, + fullscreenEnabled() { + return ( + document.fullscreenEnabled || + // Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitFullscreenEnabled. + // TODO: If Safari at some point ships with unprefixed version, update the version check above. + document.webkitFullscreenEnabled + ); + }, + }; + + var registerKeyEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.keyEvent ||= _malloc(160); + + var keyEventHandlerFunc = (e) => { + var keyEventData = JSEvents.keyEvent; + HEAPF64[keyEventData >> 3] = e.timeStamp; + + var idx = keyEventData >> 2; + + HEAP32[idx + 2] = e.location; + HEAP8[keyEventData + 12] = e.ctrlKey; + HEAP8[keyEventData + 13] = e.shiftKey; + HEAP8[keyEventData + 14] = e.altKey; + HEAP8[keyEventData + 15] = e.metaKey; + HEAP8[keyEventData + 16] = e.repeat; + HEAP32[idx + 5] = e.charCode; + HEAP32[idx + 6] = e.keyCode; + HEAP32[idx + 7] = e.which; + stringToUTF8(e.key || '', keyEventData + 32, 32); + stringToUTF8(e.code || '', keyEventData + 64, 32); + stringToUTF8(e.char || '', keyEventData + 96, 32); + stringToUTF8(e.locale || '', keyEventData + 128, 32); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + keyEventData, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: keyEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_keypress_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerKeyEventCallback( + target, + userData, + useCapture, + callbackfunc, + 1, + 'keypress', + targetThread + ); + _emscripten_set_keypress_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_keydown_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerKeyEventCallback( + target, + userData, + useCapture, + callbackfunc, + 2, + 'keydown', + targetThread + ); + _emscripten_set_keydown_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_keyup_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerKeyEventCallback( + target, + userData, + useCapture, + callbackfunc, + 3, + 'keyup', + targetThread + ); + _emscripten_set_keyup_callback_on_thread.sig = 'ippipp'; + + var getBoundingClientRect = (e) => + specialHTMLTargets.indexOf(e) < 0 + ? e.getBoundingClientRect() + : { left: 0, top: 0 }; + + var fillMouseEventData = (eventStruct, e, target) => { + HEAPF64[eventStruct >> 3] = e.timeStamp; + var idx = eventStruct >> 2; + HEAP32[idx + 2] = e.screenX; + HEAP32[idx + 3] = e.screenY; + HEAP32[idx + 4] = e.clientX; + HEAP32[idx + 5] = e.clientY; + HEAP8[eventStruct + 24] = e.ctrlKey; + HEAP8[eventStruct + 25] = e.shiftKey; + HEAP8[eventStruct + 26] = e.altKey; + HEAP8[eventStruct + 27] = e.metaKey; + HEAP16[idx * 2 + 14] = e.button; + HEAP16[idx * 2 + 15] = e.buttons; + + HEAP32[idx + 8] = e['movementX']; + + HEAP32[idx + 9] = e['movementY']; + + // Note: rect contains doubles (truncated to placate SAFE_HEAP, which is the same behaviour when writing to HEAP32 anyway) + var rect = getBoundingClientRect(target); + HEAP32[idx + 10] = e.clientX - (rect.left | 0); + HEAP32[idx + 11] = e.clientY - (rect.top | 0); + }; + + var registerMouseEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.mouseEvent ||= _malloc(64); + target = findEventTarget(target); + + var mouseEventHandlerFunc = (e = event) => { + // TODO: Make this access thread safe, or this could update live while app is reading it. + fillMouseEventData(JSEvents.mouseEvent, e, target); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + JSEvents.mouseEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + allowsDeferredCalls: + eventTypeString != 'mousemove' && + eventTypeString != 'mouseenter' && + eventTypeString != 'mouseleave', // Mouse move events do not allow fullscreen/pointer lock requests to be handled in them! + eventTypeString, + callbackfunc, + handlerFunc: mouseEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_click_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 4, + 'click', + targetThread + ); + _emscripten_set_click_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_mousedown_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 5, + 'mousedown', + targetThread + ); + _emscripten_set_mousedown_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_mouseup_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 6, + 'mouseup', + targetThread + ); + _emscripten_set_mouseup_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_dblclick_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 7, + 'dblclick', + targetThread + ); + _emscripten_set_dblclick_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_mousemove_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 8, + 'mousemove', + targetThread + ); + _emscripten_set_mousemove_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_mouseenter_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 33, + 'mouseenter', + targetThread + ); + _emscripten_set_mouseenter_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_mouseleave_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 34, + 'mouseleave', + targetThread + ); + _emscripten_set_mouseleave_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_mouseover_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 35, + 'mouseover', + targetThread + ); + _emscripten_set_mouseover_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_mouseout_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerMouseEventCallback( + target, + userData, + useCapture, + callbackfunc, + 36, + 'mouseout', + targetThread + ); + _emscripten_set_mouseout_callback_on_thread.sig = 'ippipp'; + + var _emscripten_get_mouse_status = (mouseState) => { + if (!JSEvents.mouseEvent) return -7; + // HTML5 does not really have a polling API for mouse events, so implement one manually by + // returning the data from the most recently received event. This requires that user has registered + // at least some no-op function as an event handler to any of the mouse function. + JSEvents.memcpy(mouseState, JSEvents.mouseEvent, 64); + return 0; + }; + _emscripten_get_mouse_status.sig = 'ip'; + + var registerWheelEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.wheelEvent ||= _malloc(96); + + // The DOM Level 3 events spec event 'wheel' + var wheelHandlerFunc = (e = event) => { + var wheelEvent = JSEvents.wheelEvent; + fillMouseEventData(wheelEvent, e, target); + HEAPF64[(wheelEvent + 64) >> 3] = e['deltaX']; + HEAPF64[(wheelEvent + 72) >> 3] = e['deltaY']; + HEAPF64[(wheelEvent + 80) >> 3] = e['deltaZ']; + HEAP32[(wheelEvent + 88) >> 2] = e['deltaMode']; + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + wheelEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + allowsDeferredCalls: true, + eventTypeString, + callbackfunc, + handlerFunc: wheelHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_wheel_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => { + target = findEventTarget(target); + if (!target) return -4; + if (typeof target.onwheel != 'undefined') { + return registerWheelEventCallback( + target, + userData, + useCapture, + callbackfunc, + 9, + 'wheel', + targetThread + ); + } else { + return -1; + } + }; + _emscripten_set_wheel_callback_on_thread.sig = 'ippipp'; + + var registerUiEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.uiEvent ||= _malloc(36); + + target = findEventTarget(target); + + var uiEventHandlerFunc = (e = event) => { + if (e.target != target) { + // Never take ui events such as scroll via a 'bubbled' route, but always from the direct element that + // was targeted. Otherwise e.g. if app logs a message in response to a page scroll, the Emscripten log + // message box could cause to scroll, generating a new (bubbled) scroll message, causing a new log print, + // causing a new scroll, etc.. + return; + } + var b = document.body; // Take document.body to a variable, Closure compiler does not outline access to it on its own. + if (!b) { + // During a page unload 'body' can be null, with "Cannot read property 'clientWidth' of null" being thrown + return; + } + var uiEvent = JSEvents.uiEvent; + HEAP32[uiEvent >> 2] = 0; // always zero for resize and scroll + HEAP32[(uiEvent + 4) >> 2] = b.clientWidth; + HEAP32[(uiEvent + 8) >> 2] = b.clientHeight; + HEAP32[(uiEvent + 12) >> 2] = innerWidth; + HEAP32[(uiEvent + 16) >> 2] = innerHeight; + HEAP32[(uiEvent + 20) >> 2] = outerWidth; + HEAP32[(uiEvent + 24) >> 2] = outerHeight; + HEAP32[(uiEvent + 28) >> 2] = pageXOffset | 0; // scroll offsets are float + HEAP32[(uiEvent + 32) >> 2] = pageYOffset | 0; + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + uiEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: uiEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_resize_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerUiEventCallback( + target, + userData, + useCapture, + callbackfunc, + 10, + 'resize', + targetThread + ); + _emscripten_set_resize_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_scroll_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerUiEventCallback( + target, + userData, + useCapture, + callbackfunc, + 11, + 'scroll', + targetThread + ); + _emscripten_set_scroll_callback_on_thread.sig = 'ippipp'; + + var registerFocusEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.focusEvent ||= _malloc(256); + + var focusEventHandlerFunc = (e = event) => { + var nodeName = JSEvents.getNodeNameForTarget(e.target); + var id = e.target.id ? e.target.id : ''; + + var focusEvent = JSEvents.focusEvent; + stringToUTF8(nodeName, focusEvent + 0, 128); + stringToUTF8(id, focusEvent + 128, 128); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + focusEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: focusEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_blur_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerFocusEventCallback( + target, + userData, + useCapture, + callbackfunc, + 12, + 'blur', + targetThread + ); + _emscripten_set_blur_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_focus_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerFocusEventCallback( + target, + userData, + useCapture, + callbackfunc, + 13, + 'focus', + targetThread + ); + _emscripten_set_focus_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_focusin_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerFocusEventCallback( + target, + userData, + useCapture, + callbackfunc, + 14, + 'focusin', + targetThread + ); + _emscripten_set_focusin_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_focusout_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerFocusEventCallback( + target, + userData, + useCapture, + callbackfunc, + 15, + 'focusout', + targetThread + ); + _emscripten_set_focusout_callback_on_thread.sig = 'ippipp'; + + var fillDeviceOrientationEventData = (eventStruct, e, target) => { + HEAPF64[eventStruct >> 3] = e.alpha; + HEAPF64[(eventStruct + 8) >> 3] = e.beta; + HEAPF64[(eventStruct + 16) >> 3] = e.gamma; + HEAP8[eventStruct + 24] = e.absolute; + }; + + var registerDeviceOrientationEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.deviceOrientationEvent ||= _malloc(32); + + var deviceOrientationEventHandlerFunc = (e = event) => { + fillDeviceOrientationEventData( + JSEvents.deviceOrientationEvent, + e, + target + ); // TODO: Thread-safety with respect to emscripten_get_deviceorientation_status() + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + JSEvents.deviceOrientationEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: deviceOrientationEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_deviceorientation_callback_on_thread = ( + userData, + useCapture, + callbackfunc, + targetThread + ) => { + return registerDeviceOrientationEventCallback( + 2, + userData, + useCapture, + callbackfunc, + 16, + 'deviceorientation', + targetThread + ); + }; + _emscripten_set_deviceorientation_callback_on_thread.sig = 'ipipp'; + + var _emscripten_get_deviceorientation_status = (orientationState) => { + if (!JSEvents.deviceOrientationEvent) return -7; + // HTML5 does not really have a polling API for device orientation events, so implement one manually by + // returning the data from the most recently received event. This requires that user has registered + // at least some no-op function as an event handler. + JSEvents.memcpy(orientationState, JSEvents.deviceOrientationEvent, 32); + return 0; + }; + _emscripten_get_deviceorientation_status.sig = 'ip'; + + var fillDeviceMotionEventData = (eventStruct, e, target) => { + var supportedFields = 0; + var a = e['acceleration']; + supportedFields |= a && 1; + var ag = e['accelerationIncludingGravity']; + supportedFields |= ag && 2; + var rr = e['rotationRate']; + supportedFields |= rr && 4; + a = a || {}; + ag = ag || {}; + rr = rr || {}; + HEAPF64[eventStruct >> 3] = a['x']; + HEAPF64[(eventStruct + 8) >> 3] = a['y']; + HEAPF64[(eventStruct + 16) >> 3] = a['z']; + HEAPF64[(eventStruct + 24) >> 3] = ag['x']; + HEAPF64[(eventStruct + 32) >> 3] = ag['y']; + HEAPF64[(eventStruct + 40) >> 3] = ag['z']; + HEAPF64[(eventStruct + 48) >> 3] = rr['alpha']; + HEAPF64[(eventStruct + 56) >> 3] = rr['beta']; + HEAPF64[(eventStruct + 64) >> 3] = rr['gamma']; + }; + + var registerDeviceMotionEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.deviceMotionEvent ||= _malloc(80); + + var deviceMotionEventHandlerFunc = (e = event) => { + fillDeviceMotionEventData(JSEvents.deviceMotionEvent, e, target); // TODO: Thread-safety with respect to emscripten_get_devicemotion_status() + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + JSEvents.deviceMotionEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: deviceMotionEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_devicemotion_callback_on_thread = ( + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerDeviceMotionEventCallback( + 2, + userData, + useCapture, + callbackfunc, + 17, + 'devicemotion', + targetThread + ); + _emscripten_set_devicemotion_callback_on_thread.sig = 'ipipp'; + + var _emscripten_get_devicemotion_status = (motionState) => { + if (!JSEvents.deviceMotionEvent) return -7; + // HTML5 does not really have a polling API for device motion events, so implement one manually by + // returning the data from the most recently received event. This requires that user has registered + // at least some no-op function as an event handler. + JSEvents.memcpy(motionState, JSEvents.deviceMotionEvent, 80); + return 0; + }; + _emscripten_get_devicemotion_status.sig = 'ip'; + + var screenOrientation = () => { + if (!window.screen) return undefined; + return ( + screen.orientation || + screen['mozOrientation'] || + screen['webkitOrientation'] + ); + }; + + var fillOrientationChangeEventData = (eventStruct) => { + // OrientationType enum + var orientationsType1 = [ + 'portrait-primary', + 'portrait-secondary', + 'landscape-primary', + 'landscape-secondary', + ]; + // alternative selection from OrientationLockType enum + var orientationsType2 = [ + 'portrait', + 'portrait', + 'landscape', + 'landscape', + ]; + + var orientationIndex = 0; + var orientationAngle = 0; + var screenOrientObj = screenOrientation(); + if (typeof screenOrientObj === 'object') { + orientationIndex = orientationsType1.indexOf(screenOrientObj.type); + if (orientationIndex < 0) { + orientationIndex = orientationsType2.indexOf( + screenOrientObj.type + ); + } + if (orientationIndex >= 0) { + orientationIndex = 1 << orientationIndex; + } + orientationAngle = screenOrientObj.angle; + } else { + // fallback for Safari earlier than 16.4 (March 2023) + orientationAngle = window.orientation; + } + + HEAP32[eventStruct >> 2] = orientationIndex; + HEAP32[(eventStruct + 4) >> 2] = orientationAngle; + }; + + var registerOrientationChangeEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.orientationChangeEvent ||= _malloc(8); + + var orientationChangeEventHandlerFunc = (e = event) => { + var orientationChangeEvent = JSEvents.orientationChangeEvent; + + fillOrientationChangeEventData(orientationChangeEvent); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + orientationChangeEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: orientationChangeEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_orientationchange_callback_on_thread = ( + userData, + useCapture, + callbackfunc, + targetThread + ) => { + if (!window.screen || !screen.orientation) return -1; + return registerOrientationChangeEventCallback( + screen.orientation, + userData, + useCapture, + callbackfunc, + 18, + 'change', + targetThread + ); + }; + _emscripten_set_orientationchange_callback_on_thread.sig = 'ipipp'; + + var _emscripten_get_orientation_status = (orientationChangeEvent) => { + // screenOrientation() resolving standard, window.orientation being the deprecated mobile-only + if (!screenOrientation() && typeof orientation == 'undefined') + return -1; + fillOrientationChangeEventData(orientationChangeEvent); + return 0; + }; + _emscripten_get_orientation_status.sig = 'ip'; + + var _emscripten_lock_orientation = (allowedOrientations) => { + var orientations = []; + if (allowedOrientations & 1) orientations.push('portrait-primary'); + if (allowedOrientations & 2) orientations.push('portrait-secondary'); + if (allowedOrientations & 4) orientations.push('landscape-primary'); + if (allowedOrientations & 8) orientations.push('landscape-secondary'); + var succeeded; + if (screen.lockOrientation) { + succeeded = screen.lockOrientation(orientations); + } else if (screen.mozLockOrientation) { + succeeded = screen.mozLockOrientation(orientations); + } else if (screen.webkitLockOrientation) { + succeeded = screen.webkitLockOrientation(orientations); + } else { + return -1; + } + if (succeeded) { + return 0; + } + return -6; + }; + _emscripten_lock_orientation.sig = 'ii'; + + var _emscripten_unlock_orientation = () => { + if (screen.unlockOrientation) { + screen.unlockOrientation(); + } else if (screen.mozUnlockOrientation) { + screen.mozUnlockOrientation(); + } else if (screen.webkitUnlockOrientation) { + screen.webkitUnlockOrientation(); + } else { + return -1; + } + return 0; + }; + _emscripten_unlock_orientation.sig = 'i'; + + var fillFullscreenChangeEventData = (eventStruct) => { + var fullscreenElement = + document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement; + var isFullscreen = !!fullscreenElement; + // Assigning a boolean to HEAP32 with expected type coercion. + /** @suppress{checkTypes} */ + HEAP8[eventStruct] = isFullscreen; + HEAP8[eventStruct + 1] = JSEvents.fullscreenEnabled(); + // If transitioning to fullscreen, report info about the element that is now fullscreen. + // If transitioning to windowed mode, report info about the element that just was fullscreen. + var reportedElement = isFullscreen + ? fullscreenElement + : JSEvents.previousFullscreenElement; + var nodeName = JSEvents.getNodeNameForTarget(reportedElement); + var id = reportedElement?.id || ''; + stringToUTF8(nodeName, eventStruct + 2, 128); + stringToUTF8(id, eventStruct + 130, 128); + HEAP32[(eventStruct + 260) >> 2] = reportedElement + ? reportedElement.clientWidth + : 0; + HEAP32[(eventStruct + 264) >> 2] = reportedElement + ? reportedElement.clientHeight + : 0; + HEAP32[(eventStruct + 268) >> 2] = screen.width; + HEAP32[(eventStruct + 272) >> 2] = screen.height; + if (isFullscreen) { + JSEvents.previousFullscreenElement = fullscreenElement; + } + }; + + var registerFullscreenChangeEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.fullscreenChangeEvent ||= _malloc(276); + + var fullscreenChangeEventhandlerFunc = (e = event) => { + var fullscreenChangeEvent = JSEvents.fullscreenChangeEvent; + + fillFullscreenChangeEventData(fullscreenChangeEvent); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + fullscreenChangeEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: fullscreenChangeEventhandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_fullscreenchange_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => { + if (!JSEvents.fullscreenEnabled()) return -1; + target = findEventTarget(target); + if (!target) return -4; + + // Unprefixed Fullscreen API shipped in Chromium 71 (https://bugs.chromium.org/p/chromium/issues/detail?id=383813) + // As of Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitfullscreenchange. TODO: revisit this check once Safari ships unprefixed version. + registerFullscreenChangeEventCallback( + target, + userData, + useCapture, + callbackfunc, + 19, + 'webkitfullscreenchange', + targetThread + ); + + return registerFullscreenChangeEventCallback( + target, + userData, + useCapture, + callbackfunc, + 19, + 'fullscreenchange', + targetThread + ); + }; + _emscripten_set_fullscreenchange_callback_on_thread.sig = 'ippipp'; + + var _emscripten_get_fullscreen_status = (fullscreenStatus) => { + if (!JSEvents.fullscreenEnabled()) return -1; + fillFullscreenChangeEventData(fullscreenStatus); + return 0; + }; + _emscripten_get_fullscreen_status.sig = 'ip'; + + var _emscripten_get_canvas_element_size = (target, width, height) => { + var canvas = findCanvasEventTarget(target); + if (!canvas) return -4; + HEAP32[width >> 2] = canvas.width; + HEAP32[height >> 2] = canvas.height; + }; + _emscripten_get_canvas_element_size.sig = 'ippp'; + + var getCanvasElementSize = (target) => { + var sp = stackSave(); + var w = stackAlloc(8); + var h = w + 4; + + var targetInt = stringToUTF8OnStack(target.id); + var ret = _emscripten_get_canvas_element_size(targetInt, w, h); + var size = [HEAP32[w >> 2], HEAP32[h >> 2]]; + stackRestore(sp); + return size; + }; + + var setCanvasElementSize = (target, width, height) => { + if (!target.controlTransferredOffscreen) { + target.width = width; + target.height = height; + } else { + // This function is being called from high-level JavaScript code instead of asm.js/Wasm, + // and it needs to synchronously proxy over to another thread, so marshal the string onto the heap to do the call. + var sp = stackSave(); + var targetInt = stringToUTF8OnStack(target.id); + _emscripten_set_canvas_element_size(targetInt, width, height); + stackRestore(sp); + } + }; + var registerRestoreOldStyle = (canvas) => { + var canvasSize = getCanvasElementSize(canvas); + var oldWidth = canvasSize[0]; + var oldHeight = canvasSize[1]; + var oldCssWidth = canvas.style.width; + var oldCssHeight = canvas.style.height; + var oldBackgroundColor = canvas.style.backgroundColor; // Chrome reads color from here. + var oldDocumentBackgroundColor = document.body.style.backgroundColor; // IE11 reads color from here. + // Firefox always has black background color. + var oldPaddingLeft = canvas.style.paddingLeft; // Chrome, FF, Safari + var oldPaddingRight = canvas.style.paddingRight; + var oldPaddingTop = canvas.style.paddingTop; + var oldPaddingBottom = canvas.style.paddingBottom; + var oldMarginLeft = canvas.style.marginLeft; // IE11 + var oldMarginRight = canvas.style.marginRight; + var oldMarginTop = canvas.style.marginTop; + var oldMarginBottom = canvas.style.marginBottom; + var oldDocumentBodyMargin = document.body.style.margin; + var oldDocumentOverflow = document.documentElement.style.overflow; // Chrome, Firefox + var oldDocumentScroll = document.body.scroll; // IE + var oldImageRendering = canvas.style.imageRendering; + + function restoreOldStyle() { + var fullscreenElement = + document.fullscreenElement || document.webkitFullscreenElement; + if (!fullscreenElement) { + document.removeEventListener( + 'fullscreenchange', + restoreOldStyle + ); + + // Unprefixed Fullscreen API shipped in Chromium 71 (https://bugs.chromium.org/p/chromium/issues/detail?id=383813) + // As of Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitfullscreenchange. TODO: revisit this check once Safari ships unprefixed version. + document.removeEventListener( + 'webkitfullscreenchange', + restoreOldStyle + ); + + setCanvasElementSize(canvas, oldWidth, oldHeight); + + canvas.style.width = oldCssWidth; + canvas.style.height = oldCssHeight; + canvas.style.backgroundColor = oldBackgroundColor; // Chrome + // IE11 hack: assigning 'undefined' or an empty string to document.body.style.backgroundColor has no effect, so first assign back the default color + // before setting the undefined value. Setting undefined value is also important, or otherwise we would later treat that as something that the user + // had explicitly set so subsequent fullscreen transitions would not set background color properly. + if (!oldDocumentBackgroundColor) + document.body.style.backgroundColor = 'white'; + document.body.style.backgroundColor = + oldDocumentBackgroundColor; // IE11 + canvas.style.paddingLeft = oldPaddingLeft; // Chrome, FF, Safari + canvas.style.paddingRight = oldPaddingRight; + canvas.style.paddingTop = oldPaddingTop; + canvas.style.paddingBottom = oldPaddingBottom; + canvas.style.marginLeft = oldMarginLeft; // IE11 + canvas.style.marginRight = oldMarginRight; + canvas.style.marginTop = oldMarginTop; + canvas.style.marginBottom = oldMarginBottom; + document.body.style.margin = oldDocumentBodyMargin; + document.documentElement.style.overflow = oldDocumentOverflow; // Chrome, Firefox + document.body.scroll = oldDocumentScroll; // IE + canvas.style.imageRendering = oldImageRendering; + if (canvas.GLctxObject) + canvas.GLctxObject.GLctx.viewport( + 0, + 0, + oldWidth, + oldHeight + ); + + if (currentFullscreenStrategy.canvasResizedCallback) { + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + 37, + 0, + currentFullscreenStrategy.canvasResizedCallbackUserData + ); + } + } + } + document.addEventListener('fullscreenchange', restoreOldStyle); + // Unprefixed Fullscreen API shipped in Chromium 71 (https://bugs.chromium.org/p/chromium/issues/detail?id=383813) + // As of Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitfullscreenchange. TODO: revisit this check once Safari ships unprefixed version. + document.addEventListener('webkitfullscreenchange', restoreOldStyle); + return restoreOldStyle; + }; + + var setLetterbox = (element, topBottom, leftRight) => { + // Cannot use margin to specify letterboxes in FF or Chrome, since those ignore margins in fullscreen mode. + element.style.paddingLeft = element.style.paddingRight = + leftRight + 'px'; + element.style.paddingTop = element.style.paddingBottom = + topBottom + 'px'; + }; + + var JSEvents_resizeCanvasForFullscreen = (target, strategy) => { + var restoreOldStyle = registerRestoreOldStyle(target); + var cssWidth = strategy.softFullscreen ? innerWidth : screen.width; + var cssHeight = strategy.softFullscreen ? innerHeight : screen.height; + var rect = getBoundingClientRect(target); + var windowedCssWidth = rect.width; + var windowedCssHeight = rect.height; + var canvasSize = getCanvasElementSize(target); + var windowedRttWidth = canvasSize[0]; + var windowedRttHeight = canvasSize[1]; + + if (strategy.scaleMode == 3) { + setLetterbox( + target, + (cssHeight - windowedCssHeight) / 2, + (cssWidth - windowedCssWidth) / 2 + ); + cssWidth = windowedCssWidth; + cssHeight = windowedCssHeight; + } else if (strategy.scaleMode == 2) { + if (cssWidth * windowedRttHeight < windowedRttWidth * cssHeight) { + var desiredCssHeight = + (windowedRttHeight * cssWidth) / windowedRttWidth; + setLetterbox(target, (cssHeight - desiredCssHeight) / 2, 0); + cssHeight = desiredCssHeight; + } else { + var desiredCssWidth = + (windowedRttWidth * cssHeight) / windowedRttHeight; + setLetterbox(target, 0, (cssWidth - desiredCssWidth) / 2); + cssWidth = desiredCssWidth; + } + } + + // If we are adding padding, must choose a background color or otherwise Chrome will give the + // padding a default white color. Do it only if user has not customized their own background color. + target.style.backgroundColor ||= 'black'; + // IE11 does the same, but requires the color to be set in the document body. + document.body.style.backgroundColor ||= 'black'; // IE11 + // Firefox always shows black letterboxes independent of style color. + + target.style.width = cssWidth + 'px'; + target.style.height = cssHeight + 'px'; + + if (strategy.filteringMode == 1) { + target.style.imageRendering = 'optimizeSpeed'; + target.style.imageRendering = '-moz-crisp-edges'; + target.style.imageRendering = '-o-crisp-edges'; + target.style.imageRendering = '-webkit-optimize-contrast'; + target.style.imageRendering = 'optimize-contrast'; + target.style.imageRendering = 'crisp-edges'; + target.style.imageRendering = 'pixelated'; + } + + var dpiScale = + strategy.canvasResolutionScaleMode == 2 ? devicePixelRatio : 1; + if (strategy.canvasResolutionScaleMode != 0) { + var newWidth = (cssWidth * dpiScale) | 0; + var newHeight = (cssHeight * dpiScale) | 0; + setCanvasElementSize(target, newWidth, newHeight); + if (target.GLctxObject) + target.GLctxObject.GLctx.viewport(0, 0, newWidth, newHeight); + } + return restoreOldStyle; + }; + var JSEvents_requestFullscreen = (target, strategy) => { + // EMSCRIPTEN_FULLSCREEN_SCALE_DEFAULT + EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE is a mode where no extra logic is performed to the DOM elements. + if ( + strategy.scaleMode != 0 || + strategy.canvasResolutionScaleMode != 0 + ) { + JSEvents_resizeCanvasForFullscreen(target, strategy); + } + + if (target.requestFullscreen) { + target.requestFullscreen(); + } else if (target.webkitRequestFullscreen) { + target.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else { + return JSEvents.fullscreenEnabled() ? -3 : -1; + } + + currentFullscreenStrategy = strategy; + + if (strategy.canvasResizedCallback) { + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + 37, + 0, + strategy.canvasResizedCallbackUserData + ); + } + + return 0; + }; + + var hideEverythingExceptGivenElement = (onlyVisibleElement) => { + var child = onlyVisibleElement; + var parent = child.parentNode; + var hiddenElements = []; + while (child != document.body) { + var children = parent.children; + for (var currChild of children) { + if (currChild != child) { + hiddenElements.push({ + node: currChild, + displayState: currChild.style.display, + }); + currChild.style.display = 'none'; + } + } + child = parent; + parent = parent.parentNode; + } + return hiddenElements; + }; + + var restoreHiddenElements = (hiddenElements) => { + for (var elem of hiddenElements) { + elem.node.style.display = elem.displayState; + } + }; + + var currentFullscreenStrategy = {}; + + var restoreOldWindowedStyle = null; + + var softFullscreenResizeWebGLRenderTarget = () => { + var dpr = devicePixelRatio; + var inHiDPIFullscreenMode = + currentFullscreenStrategy.canvasResolutionScaleMode == 2; + var inAspectRatioFixedFullscreenMode = + currentFullscreenStrategy.scaleMode == 2; + var inPixelPerfectFullscreenMode = + currentFullscreenStrategy.canvasResolutionScaleMode != 0; + var inCenteredWithoutScalingFullscreenMode = + currentFullscreenStrategy.scaleMode == 3; + var screenWidth = inHiDPIFullscreenMode + ? Math.round(innerWidth * dpr) + : innerWidth; + var screenHeight = inHiDPIFullscreenMode + ? Math.round(innerHeight * dpr) + : innerHeight; + var w = screenWidth; + var h = screenHeight; + var canvas = currentFullscreenStrategy.target; + var canvasSize = getCanvasElementSize(canvas); + var x = canvasSize[0]; + var y = canvasSize[1]; + var topMargin; + + if (inAspectRatioFixedFullscreenMode) { + if (w * y < x * h) h = ((w * y) / x) | 0; + else if (w * y > x * h) w = ((h * x) / y) | 0; + topMargin = ((screenHeight - h) / 2) | 0; + } + + if (inPixelPerfectFullscreenMode) { + setCanvasElementSize(canvas, w, h); + if (canvas.GLctxObject) + canvas.GLctxObject.GLctx.viewport(0, 0, w, h); + } + + // Back to CSS pixels. + if (inHiDPIFullscreenMode) { + topMargin /= dpr; + w /= dpr; + h /= dpr; + // Round to nearest 4 digits of precision. + w = Math.round(w * 1e4) / 1e4; + h = Math.round(h * 1e4) / 1e4; + topMargin = Math.round(topMargin * 1e4) / 1e4; + } + + if (inCenteredWithoutScalingFullscreenMode) { + var t = (innerHeight - jstoi_q(canvas.style.height)) / 2; + var b = (innerWidth - jstoi_q(canvas.style.width)) / 2; + setLetterbox(canvas, t, b); + } else { + canvas.style.width = w + 'px'; + canvas.style.height = h + 'px'; + var b = (innerWidth - w) / 2; + setLetterbox(canvas, topMargin, b); + } + + if ( + !inCenteredWithoutScalingFullscreenMode && + currentFullscreenStrategy.canvasResizedCallback + ) { + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + 37, + 0, + currentFullscreenStrategy.canvasResizedCallbackUserData + ); + } + }; + + var doRequestFullscreen = (target, strategy) => { + if (!JSEvents.fullscreenEnabled()) return -1; + target = findEventTarget(target); + if (!target) return -4; + + if (!target.requestFullscreen && !target.webkitRequestFullscreen) { + return -3; + } + + // Queue this function call if we're not currently in an event handler and + // the user saw it appropriate to do so. + if (!JSEvents.canPerformEventHandlerRequests()) { + if (strategy.deferUntilInEventHandler) { + JSEvents.deferCall( + JSEvents_requestFullscreen, + 1 /* priority over pointer lock */, + [target, strategy] + ); + return 1; + } + return -2; + } + + return JSEvents_requestFullscreen(target, strategy); + }; + + var _emscripten_request_fullscreen = (target, deferUntilInEventHandler) => { + var strategy = { + // These options perform no added logic, but just bare request fullscreen. + scaleMode: 0, + canvasResolutionScaleMode: 0, + filteringMode: 0, + deferUntilInEventHandler, + canvasResizedCallbackTargetThread: 2, + }; + return doRequestFullscreen(target, strategy); + }; + _emscripten_request_fullscreen.sig = 'ipi'; + + var _emscripten_request_fullscreen_strategy = ( + target, + deferUntilInEventHandler, + fullscreenStrategy + ) => { + var strategy = { + scaleMode: HEAP32[fullscreenStrategy >> 2], + canvasResolutionScaleMode: HEAP32[(fullscreenStrategy + 4) >> 2], + filteringMode: HEAP32[(fullscreenStrategy + 8) >> 2], + deferUntilInEventHandler, + canvasResizedCallback: HEAP32[(fullscreenStrategy + 12) >> 2], + canvasResizedCallbackUserData: + HEAP32[(fullscreenStrategy + 16) >> 2], + }; + + return doRequestFullscreen(target, strategy); + }; + _emscripten_request_fullscreen_strategy.sig = 'ipip'; + + var _emscripten_enter_soft_fullscreen = (target, fullscreenStrategy) => { + target = findEventTarget(target); + if (!target) return -4; + + var strategy = { + scaleMode: HEAP32[fullscreenStrategy >> 2], + canvasResolutionScaleMode: HEAP32[(fullscreenStrategy + 4) >> 2], + filteringMode: HEAP32[(fullscreenStrategy + 8) >> 2], + canvasResizedCallback: HEAP32[(fullscreenStrategy + 12) >> 2], + canvasResizedCallbackUserData: + HEAP32[(fullscreenStrategy + 16) >> 2], + target, + softFullscreen: true, + }; + + var restoreOldStyle = JSEvents_resizeCanvasForFullscreen( + target, + strategy + ); + + document.documentElement.style.overflow = 'hidden'; // Firefox, Chrome + document.body.scroll = 'no'; // IE11 + document.body.style.margin = '0px'; // Override default document margin area on all browsers. + + var hiddenElements = hideEverythingExceptGivenElement(target); + + function restoreWindowedState() { + restoreOldStyle(); + restoreHiddenElements(hiddenElements); + removeEventListener( + 'resize', + softFullscreenResizeWebGLRenderTarget + ); + if (strategy.canvasResizedCallback) { + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + 37, + 0, + strategy.canvasResizedCallbackUserData + ); + } + currentFullscreenStrategy = 0; + } + restoreOldWindowedStyle = restoreWindowedState; + currentFullscreenStrategy = strategy; + addEventListener('resize', softFullscreenResizeWebGLRenderTarget); + + // Inform the caller that the canvas size has changed. + if (strategy.canvasResizedCallback) { + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + 37, + 0, + strategy.canvasResizedCallbackUserData + ); + } + + return 0; + }; + _emscripten_enter_soft_fullscreen.sig = 'ipp'; + + var _emscripten_exit_soft_fullscreen = () => { + restoreOldWindowedStyle?.(); + restoreOldWindowedStyle = null; + + return 0; + }; + _emscripten_exit_soft_fullscreen.sig = 'i'; + + var _emscripten_exit_fullscreen = () => { + if (!JSEvents.fullscreenEnabled()) return -1; + // Make sure no queued up calls will fire after this. + JSEvents.removeDeferredCalls(JSEvents_requestFullscreen); + + var d = specialHTMLTargets[1]; + if (d.exitFullscreen) { + d.fullscreenElement && d.exitFullscreen(); + } else if (d.webkitExitFullscreen) { + d.webkitFullscreenElement && d.webkitExitFullscreen(); + } else { + return -1; + } + + return 0; + }; + _emscripten_exit_fullscreen.sig = 'i'; + + var fillPointerlockChangeEventData = (eventStruct) => { + var pointerLockElement = + document.pointerLockElement || + document.mozPointerLockElement || + document.webkitPointerLockElement || + document.msPointerLockElement; + var isPointerlocked = !!pointerLockElement; + // Assigning a boolean to HEAP32 with expected type coercion. + /** @suppress{checkTypes} */ + HEAP8[eventStruct] = isPointerlocked; + var nodeName = JSEvents.getNodeNameForTarget(pointerLockElement); + var id = pointerLockElement?.id || ''; + stringToUTF8(nodeName, eventStruct + 1, 128); + stringToUTF8(id, eventStruct + 129, 128); + }; + + var registerPointerlockChangeEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.pointerlockChangeEvent ||= _malloc(257); + + var pointerlockChangeEventHandlerFunc = (e = event) => { + var pointerlockChangeEvent = JSEvents.pointerlockChangeEvent; + fillPointerlockChangeEventData(pointerlockChangeEvent); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + pointerlockChangeEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: pointerlockChangeEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + /** @suppress {missingProperties} */ + var _emscripten_set_pointerlockchange_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => { + // TODO: Currently not supported in pthreads or in --proxy-to-worker mode. (In pthreads mode, document object is not defined) + if ( + !document || + !document.body || + (!document.body.requestPointerLock && + !document.body.mozRequestPointerLock && + !document.body.webkitRequestPointerLock && + !document.body.msRequestPointerLock) + ) { + return -1; + } + + target = findEventTarget(target); + if (!target) return -4; + registerPointerlockChangeEventCallback( + target, + userData, + useCapture, + callbackfunc, + 20, + 'mozpointerlockchange', + targetThread + ); + registerPointerlockChangeEventCallback( + target, + userData, + useCapture, + callbackfunc, + 20, + 'webkitpointerlockchange', + targetThread + ); + registerPointerlockChangeEventCallback( + target, + userData, + useCapture, + callbackfunc, + 20, + 'mspointerlockchange', + targetThread + ); + return registerPointerlockChangeEventCallback( + target, + userData, + useCapture, + callbackfunc, + 20, + 'pointerlockchange', + targetThread + ); + }; + _emscripten_set_pointerlockchange_callback_on_thread.sig = 'ippipp'; + + var registerPointerlockErrorEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + var pointerlockErrorEventHandlerFunc = (e = event) => { + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + 0, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: pointerlockErrorEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + /** @suppress {missingProperties} */ + var _emscripten_set_pointerlockerror_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => { + // TODO: Currently not supported in pthreads or in --proxy-to-worker mode. (In pthreads mode, document object is not defined) + if ( + !document || + (!document.body.requestPointerLock && + !document.body.mozRequestPointerLock && + !document.body.webkitRequestPointerLock && + !document.body.msRequestPointerLock) + ) { + return -1; + } + + target = findEventTarget(target); + + if (!target) return -4; + registerPointerlockErrorEventCallback( + target, + userData, + useCapture, + callbackfunc, + 38, + 'mozpointerlockerror', + targetThread + ); + registerPointerlockErrorEventCallback( + target, + userData, + useCapture, + callbackfunc, + 38, + 'webkitpointerlockerror', + targetThread + ); + registerPointerlockErrorEventCallback( + target, + userData, + useCapture, + callbackfunc, + 38, + 'mspointerlockerror', + targetThread + ); + return registerPointerlockErrorEventCallback( + target, + userData, + useCapture, + callbackfunc, + 38, + 'pointerlockerror', + targetThread + ); + }; + _emscripten_set_pointerlockerror_callback_on_thread.sig = 'ippipp'; + + /** @suppress {missingProperties} */ + var _emscripten_get_pointerlock_status = (pointerlockStatus) => { + if (pointerlockStatus) + fillPointerlockChangeEventData(pointerlockStatus); + if ( + !document.body || + (!document.body.requestPointerLock && + !document.body.mozRequestPointerLock && + !document.body.webkitRequestPointerLock && + !document.body.msRequestPointerLock) + ) { + return -1; + } + return 0; + }; + _emscripten_get_pointerlock_status.sig = 'ip'; + + var requestPointerLock = (target) => { + if (target.requestPointerLock) { + target.requestPointerLock(); + } else { + // document.body is known to accept pointer lock, so use that to differentiate if the user passed a bad element, + // or if the whole browser just doesn't support the feature. + if (document.body.requestPointerLock) { + return -3; + } + return -1; + } + return 0; + }; + + var _emscripten_request_pointerlock = ( + target, + deferUntilInEventHandler + ) => { + target = findEventTarget(target); + if (!target) return -4; + if (!target.requestPointerLock) { + return -1; + } + + // Queue this function call if we're not currently in an event handler and + // the user saw it appropriate to do so. + if (!JSEvents.canPerformEventHandlerRequests()) { + if (deferUntilInEventHandler) { + JSEvents.deferCall( + requestPointerLock, + 2 /* priority below fullscreen */, + [target] + ); + return 1; + } + return -2; + } + + return requestPointerLock(target); + }; + _emscripten_request_pointerlock.sig = 'ipi'; + + var _emscripten_exit_pointerlock = () => { + // Make sure no queued up calls will fire after this. + JSEvents.removeDeferredCalls(requestPointerLock); + + if (document.exitPointerLock) { + document.exitPointerLock(); + } else { + return -1; + } + return 0; + }; + _emscripten_exit_pointerlock.sig = 'i'; + + var _emscripten_vibrate = (msecs) => { + if (!navigator.vibrate) return -1; + navigator.vibrate(msecs); + return 0; + }; + _emscripten_vibrate.sig = 'ii'; + + var _emscripten_vibrate_pattern = (msecsArray, numEntries) => { + if (!navigator.vibrate) return -1; + + var vibrateList = []; + for (var i = 0; i < numEntries; ++i) { + var msecs = HEAP32[(msecsArray + i * 4) >> 2]; + vibrateList.push(msecs); + } + navigator.vibrate(vibrateList); + return 0; + }; + _emscripten_vibrate_pattern.sig = 'ipi'; + + var fillVisibilityChangeEventData = (eventStruct) => { + var visibilityStates = ['hidden', 'visible', 'prerender', 'unloaded']; + var visibilityState = visibilityStates.indexOf( + document.visibilityState + ); + + // Assigning a boolean to HEAP32 with expected type coercion. + /** @suppress{checkTypes} */ + HEAP8[eventStruct] = document.hidden; + HEAP32[(eventStruct + 4) >> 2] = visibilityState; + }; + + var registerVisibilityChangeEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.visibilityChangeEvent ||= _malloc(8); + + var visibilityChangeEventHandlerFunc = (e = event) => { + var visibilityChangeEvent = JSEvents.visibilityChangeEvent; + + fillVisibilityChangeEventData(visibilityChangeEvent); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + visibilityChangeEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: visibilityChangeEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_visibilitychange_callback_on_thread = ( + userData, + useCapture, + callbackfunc, + targetThread + ) => { + if (!specialHTMLTargets[1]) { + return -4; + } + return registerVisibilityChangeEventCallback( + specialHTMLTargets[1], + userData, + useCapture, + callbackfunc, + 21, + 'visibilitychange', + targetThread + ); + }; + _emscripten_set_visibilitychange_callback_on_thread.sig = 'ipipp'; + + var _emscripten_get_visibility_status = (visibilityStatus) => { + if ( + typeof document.visibilityState == 'undefined' && + typeof document.hidden == 'undefined' + ) { + return -1; + } + fillVisibilityChangeEventData(visibilityStatus); + return 0; + }; + _emscripten_get_visibility_status.sig = 'ip'; + + var registerTouchEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.touchEvent ||= _malloc(1552); + + target = findEventTarget(target); + + var touchEventHandlerFunc = (e) => { + var t, + touches = {}, + et = e.touches; + // To ease marshalling different kinds of touches that browser reports (all touches are listed in e.touches, + // only changed touches in e.changedTouches, and touches on target at a.targetTouches), mark a boolean in + // each Touch object so that we can later loop only once over all touches we see to marshall over to Wasm. + + for (let t of et) { + // Browser might recycle the generated Touch objects between each frame (Firefox on Android), so reset any + // changed/target states we may have set from previous frame. + t.isChanged = t.onTarget = 0; + touches[t.identifier] = t; + } + // Mark which touches are part of the changedTouches list. + for (let t of e.changedTouches) { + t.isChanged = 1; + touches[t.identifier] = t; + } + // Mark which touches are part of the targetTouches list. + for (let t of e.targetTouches) { + touches[t.identifier].onTarget = 1; + } + + var touchEvent = JSEvents.touchEvent; + HEAPF64[touchEvent >> 3] = e.timeStamp; + HEAP8[touchEvent + 12] = e.ctrlKey; + HEAP8[touchEvent + 13] = e.shiftKey; + HEAP8[touchEvent + 14] = e.altKey; + HEAP8[touchEvent + 15] = e.metaKey; + var idx = touchEvent + 16; + var targetRect = getBoundingClientRect(target); + var numTouches = 0; + for (let t of Object.values(touches)) { + var idx32 = idx >> 2; // Pre-shift the ptr to index to HEAP32 to save code size + HEAP32[idx32 + 0] = t.identifier; + HEAP32[idx32 + 1] = t.screenX; + HEAP32[idx32 + 2] = t.screenY; + HEAP32[idx32 + 3] = t.clientX; + HEAP32[idx32 + 4] = t.clientY; + HEAP32[idx32 + 5] = t.pageX; + HEAP32[idx32 + 6] = t.pageY; + HEAP8[idx + 28] = t.isChanged; + HEAP8[idx + 29] = t.onTarget; + HEAP32[idx32 + 8] = t.clientX - (targetRect.left | 0); + HEAP32[idx32 + 9] = t.clientY - (targetRect.top | 0); + + idx += 48; + + if (++numTouches > 31) { + break; + } + } + HEAP32[(touchEvent + 8) >> 2] = numTouches; + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + touchEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target, + allowsDeferredCalls: + eventTypeString == 'touchstart' || + eventTypeString == 'touchend', + eventTypeString, + callbackfunc, + handlerFunc: touchEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_touchstart_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerTouchEventCallback( + target, + userData, + useCapture, + callbackfunc, + 22, + 'touchstart', + targetThread + ); + _emscripten_set_touchstart_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_touchend_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerTouchEventCallback( + target, + userData, + useCapture, + callbackfunc, + 23, + 'touchend', + targetThread + ); + _emscripten_set_touchend_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_touchmove_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerTouchEventCallback( + target, + userData, + useCapture, + callbackfunc, + 24, + 'touchmove', + targetThread + ); + _emscripten_set_touchmove_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_touchcancel_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => + registerTouchEventCallback( + target, + userData, + useCapture, + callbackfunc, + 25, + 'touchcancel', + targetThread + ); + _emscripten_set_touchcancel_callback_on_thread.sig = 'ippipp'; + + var fillGamepadEventData = (eventStruct, e) => { + HEAPF64[eventStruct >> 3] = e.timestamp; + for (var i = 0; i < e.axes.length; ++i) { + HEAPF64[(eventStruct + i * 8 + 16) >> 3] = e.axes[i]; + } + for (var i = 0; i < e.buttons.length; ++i) { + if (typeof e.buttons[i] == 'object') { + HEAPF64[(eventStruct + i * 8 + 528) >> 3] = e.buttons[i].value; + } else { + HEAPF64[(eventStruct + i * 8 + 528) >> 3] = e.buttons[i]; + } + } + for (var i = 0; i < e.buttons.length; ++i) { + if (typeof e.buttons[i] == 'object') { + HEAP8[eventStruct + i + 1040] = e.buttons[i].pressed; + } else { + // Assigning a boolean to HEAP32, that's ok, but Closure would like to warn about it: + /** @suppress {checkTypes} */ + HEAP8[eventStruct + i + 1040] = e.buttons[i] == 1; + } + } + HEAP8[eventStruct + 1104] = e.connected; + HEAP32[(eventStruct + 1108) >> 2] = e.index; + HEAP32[(eventStruct + 8) >> 2] = e.axes.length; + HEAP32[(eventStruct + 12) >> 2] = e.buttons.length; + stringToUTF8(e.id, eventStruct + 1112, 64); + stringToUTF8(e.mapping, eventStruct + 1176, 64); + }; + + var registerGamepadEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.gamepadEvent ||= _malloc(1240); + + var gamepadEventHandlerFunc = (e = event) => { + var gamepadEvent = JSEvents.gamepadEvent; + fillGamepadEventData(gamepadEvent, e['gamepad']); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + gamepadEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + allowsDeferredCalls: true, + eventTypeString, + callbackfunc, + handlerFunc: gamepadEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + /** @suppress {checkTypes} */ + var _emscripten_sample_gamepad_data = () => { + try { + if (navigator.getGamepads) + return (JSEvents.lastGamepadState = navigator.getGamepads()) + ? 0 + : -1; + } catch (e) { + navigator.getGamepads = null; // Disable getGamepads() so that it won't be attempted to be used again. + } + return -1; + }; + _emscripten_sample_gamepad_data.sig = 'i'; + var _emscripten_set_gamepadconnected_callback_on_thread = ( + userData, + useCapture, + callbackfunc, + targetThread + ) => { + if (_emscripten_sample_gamepad_data()) return -1; + return registerGamepadEventCallback( + 2, + userData, + useCapture, + callbackfunc, + 26, + 'gamepadconnected', + targetThread + ); + }; + _emscripten_set_gamepadconnected_callback_on_thread.sig = 'ipipp'; + + var _emscripten_set_gamepaddisconnected_callback_on_thread = ( + userData, + useCapture, + callbackfunc, + targetThread + ) => { + if (_emscripten_sample_gamepad_data()) return -1; + return registerGamepadEventCallback( + 2, + userData, + useCapture, + callbackfunc, + 27, + 'gamepaddisconnected', + targetThread + ); + }; + _emscripten_set_gamepaddisconnected_callback_on_thread.sig = 'ipipp'; + + var _emscripten_get_num_gamepads = () => { + // N.B. Do not call emscripten_get_num_gamepads() unless having first called emscripten_sample_gamepad_data(), and that has returned EMSCRIPTEN_RESULT_SUCCESS. + // Otherwise the following line will throw an exception. + return JSEvents.lastGamepadState.length; + }; + _emscripten_get_num_gamepads.sig = 'i'; + + var _emscripten_get_gamepad_status = (index, gamepadState) => { + // INVALID_PARAM is returned on a Gamepad index that never was there. + if (index < 0 || index >= JSEvents.lastGamepadState.length) return -5; + + // NO_DATA is returned on a Gamepad index that was removed. + // For previously disconnected gamepads there should be an empty slot (null/undefined/false) at the index. + // This is because gamepads must keep their original position in the array. + // For example, removing the first of two gamepads produces [null/undefined/false, gamepad]. + if (!JSEvents.lastGamepadState[index]) return -7; + + fillGamepadEventData(gamepadState, JSEvents.lastGamepadState[index]); + return 0; + }; + _emscripten_get_gamepad_status.sig = 'iip'; + + var registerBeforeUnloadEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString + ) => { + var beforeUnloadEventHandlerFunc = (e = event) => { + // Note: This is always called on the main browser thread, since it needs synchronously return a value! + var confirmationMessage = (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + 0, + userData + ); + + if (confirmationMessage) { + confirmationMessage = UTF8ToString(confirmationMessage); + } + if (confirmationMessage) { + e.preventDefault(); + e.returnValue = confirmationMessage; + return confirmationMessage; + } + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: beforeUnloadEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_beforeunload_callback_on_thread = ( + userData, + callbackfunc, + targetThread + ) => { + if (typeof onbeforeunload == 'undefined') return -1; + // beforeunload callback can only be registered on the main browser thread, because the page will go away immediately after returning from the handler, + // and there is no time to start proxying it anywhere. + if (targetThread !== 1) return -5; + return registerBeforeUnloadEventCallback( + 2, + userData, + true, + callbackfunc, + 28, + 'beforeunload' + ); + }; + _emscripten_set_beforeunload_callback_on_thread.sig = 'ippp'; + + var fillBatteryEventData = (eventStruct, e) => { + HEAPF64[eventStruct >> 3] = e.chargingTime; + HEAPF64[(eventStruct + 8) >> 3] = e.dischargingTime; + HEAPF64[(eventStruct + 16) >> 3] = e.level; + HEAP8[eventStruct + 24] = e.charging; + }; + + var battery = () => + navigator.battery || navigator.mozBattery || navigator.webkitBattery; + + var registerBatteryEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + JSEvents.batteryEvent ||= _malloc(32); + + var batteryEventHandlerFunc = (e = event) => { + var batteryEvent = JSEvents.batteryEvent; + fillBatteryEventData(batteryEvent, battery()); + + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + batteryEvent, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: batteryEventHandlerFunc, + useCapture, + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_batterychargingchange_callback_on_thread = ( + userData, + callbackfunc, + targetThread + ) => { + if (!battery()) return -1; + return registerBatteryEventCallback( + battery(), + userData, + true, + callbackfunc, + 29, + 'chargingchange', + targetThread + ); + }; + _emscripten_set_batterychargingchange_callback_on_thread.sig = 'ippp'; + + var _emscripten_set_batterylevelchange_callback_on_thread = ( + userData, + callbackfunc, + targetThread + ) => { + if (!battery()) return -1; + return registerBatteryEventCallback( + battery(), + userData, + true, + callbackfunc, + 30, + 'levelchange', + targetThread + ); + }; + _emscripten_set_batterylevelchange_callback_on_thread.sig = 'ippp'; + + var _emscripten_get_battery_status = (batteryState) => { + if (!battery()) return -1; + fillBatteryEventData(batteryState, battery()); + return 0; + }; + _emscripten_get_battery_status.sig = 'ip'; + + var _emscripten_set_element_css_size = (target, width, height) => { + target = findEventTarget(target); + if (!target) return -4; + + target.style.width = width + 'px'; + target.style.height = height + 'px'; + + return 0; + }; + _emscripten_set_element_css_size.sig = 'ipdd'; + + var _emscripten_get_element_css_size = (target, width, height) => { + target = findEventTarget(target); + if (!target) return -4; + + var rect = getBoundingClientRect(target); + HEAPF64[width >> 3] = rect.width; + HEAPF64[height >> 3] = rect.height; + + return 0; + }; + _emscripten_get_element_css_size.sig = 'ippp'; + + var _emscripten_html5_remove_all_event_listeners = () => + JSEvents.removeAllEventListeners(); + _emscripten_html5_remove_all_event_listeners.sig = 'v'; + + var _emscripten_request_animation_frame = (cb, userData) => + requestAnimationFrame((timeStamp) => + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature idi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + timeStamp, + userData + ) + ); + _emscripten_request_animation_frame.sig = 'ipp'; + + var _emscripten_cancel_animation_frame = (id) => cancelAnimationFrame(id); + _emscripten_cancel_animation_frame.sig = 'vi'; + + var _emscripten_request_animation_frame_loop = (cb, userData) => { + function tick(timeStamp) { + if ( + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature idi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + timeStamp, + userData + ) + ) { + requestAnimationFrame(tick); + } + } + return requestAnimationFrame(tick); + }; + _emscripten_request_animation_frame_loop.sig = 'vpp'; + + var _emscripten_get_device_pixel_ratio = () => { + return (typeof devicePixelRatio == 'number' && devicePixelRatio) || 1.0; + }; + _emscripten_get_device_pixel_ratio.sig = 'd'; + + var _emscripten_get_callstack = (flags, str, maxbytes) => { + var callstack = getCallstack(flags); + // User can query the required amount of bytes to hold the callstack. + if (!str || maxbytes <= 0) { + return lengthBytesUTF8(callstack) + 1; + } + // Output callstack string as C string to HEAP. + var bytesWrittenExcludingNull = stringToUTF8(callstack, str, maxbytes); + + // Return number of bytes written, including null. + return bytesWrittenExcludingNull + 1; + }; + _emscripten_get_callstack.sig = 'iipi'; + + /** @returns {number} */ + var convertFrameToPC = (frame) => { + abort( + 'Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER' + ); + // return 0 if we can't find any + return 0; + }; + + var _emscripten_return_address = (level) => { + var callstack = jsStackTrace().split('\n'); + if (callstack[0] == 'Error') { + callstack.shift(); + } + // skip this function and the caller to get caller's return address + var caller = callstack[level + 3]; + return convertFrameToPC(caller); + }; + _emscripten_return_address.sig = 'pi'; + + var UNWIND_CACHE = {}; + + var saveInUnwindCache = (callstack) => { + callstack.forEach((frame) => { + var pc = convertFrameToPC(frame); + if (pc) { + UNWIND_CACHE[pc] = frame; + } + }); + }; + + var _emscripten_stack_snapshot = () => { + var callstack = jsStackTrace().split('\n'); + if (callstack[0] == 'Error') { + callstack.shift(); + } + saveInUnwindCache(callstack); + + // Caches the stack snapshot so that emscripten_stack_unwind_buffer() can + // unwind from this spot. + UNWIND_CACHE.last_addr = convertFrameToPC(callstack[3]); + UNWIND_CACHE.last_stack = callstack; + return UNWIND_CACHE.last_addr; + }; + _emscripten_stack_snapshot.sig = 'p'; + + var _emscripten_stack_unwind_buffer = (addr, buffer, count) => { + var stack; + if (UNWIND_CACHE.last_addr == addr) { + stack = UNWIND_CACHE.last_stack; + } else { + stack = jsStackTrace().split('\n'); + if (stack[0] == 'Error') { + stack.shift(); + } + saveInUnwindCache(stack); + } + + var offset = 3; + while (stack[offset] && convertFrameToPC(stack[offset]) != addr) { + ++offset; + } + + for (var i = 0; i < count && stack[i + offset]; ++i) { + HEAP32[(buffer + i * 4) >> 2] = convertFrameToPC(stack[i + offset]); + } + return i; + }; + _emscripten_stack_unwind_buffer.sig = 'ippi'; + + var _emscripten_pc_get_function = (pc) => { + abort( + 'Cannot use emscripten_pc_get_function without -sUSE_OFFSET_CONVERTER' + ); + return 0; + }; + _emscripten_pc_get_function.sig = 'pp'; + + var convertPCtoSourceLocation = (pc) => { + if (UNWIND_CACHE.last_get_source_pc == pc) + return UNWIND_CACHE.last_source; + + var match; + var source; + + if (!source) { + var frame = UNWIND_CACHE[pc]; + if (!frame) return null; + // Example: at callMain (a.out.js:6335:22) + if ((match = /\((.*):(\d+):(\d+)\)$/.exec(frame))) { + source = { file: match[1], line: match[2], column: match[3] }; + // Example: main@a.out.js:1337:42 + } else if ((match = /@(.*):(\d+):(\d+)/.exec(frame))) { + source = { file: match[1], line: match[2], column: match[3] }; + } + } + UNWIND_CACHE.last_get_source_pc = pc; + UNWIND_CACHE.last_source = source; + return source; + }; + + var _emscripten_pc_get_file = (pc) => { + var result = convertPCtoSourceLocation(pc); + if (!result) return 0; + + if (_emscripten_pc_get_file.ret) _free(_emscripten_pc_get_file.ret); + _emscripten_pc_get_file.ret = stringToNewUTF8(result.file); + return _emscripten_pc_get_file.ret; + }; + _emscripten_pc_get_file.sig = 'pp'; + + var _emscripten_pc_get_line = (pc) => { + var result = convertPCtoSourceLocation(pc); + return result ? result.line : 0; + }; + _emscripten_pc_get_line.sig = 'ip'; + + var _emscripten_pc_get_column = (pc) => { + var result = convertPCtoSourceLocation(pc); + return result ? result.column || 0 : 0; + }; + _emscripten_pc_get_column.sig = 'ip'; + + var _sched_yield = () => 0; + Module['_sched_yield'] = _sched_yield; + _sched_yield.sig = 'i'; + + var wasiRightsToMuslOFlags = (rights) => { + if (rights & 2 && rights & 64) { + return 2; + } + if (rights & 2) { + return 0; + } + if (rights & 64) { + return 1; + } + throw new FS.ErrnoError(28); + }; + + var wasiOFlagsToMuslOFlags = (oflags) => { + var musl_oflags = 0; + if (oflags & 1) { + musl_oflags |= 64; + } + if (oflags & 8) { + musl_oflags |= 512; + } + if (oflags & 2) { + musl_oflags |= 65536; + } + if (oflags & 4) { + musl_oflags |= 128; + } + return musl_oflags; + }; + + var _emscripten_unwind_to_js_event_loop = () => { + throw 'unwind'; + }; + _emscripten_unwind_to_js_event_loop.sig = 'v'; + + var setImmediateWrapped = (func) => { + setImmediateWrapped.mapping ||= []; + var id = setImmediateWrapped.mapping.length; + setImmediateWrapped.mapping[id] = setImmediate(() => { + setImmediateWrapped.mapping[id] = undefined; + func(); + }); + return id; + }; + + var safeRequestAnimationFrame = (func) => { + runtimeKeepalivePush(); + return MainLoop.requestAnimationFrame(() => { + runtimeKeepalivePop(); + callUserCallback(func); + }); + }; + + var clearImmediateWrapped = (id) => { + clearImmediate(setImmediateWrapped.mapping[id]); + setImmediateWrapped.mapping[id] = undefined; + }; + + var emClearImmediate; + var emSetImmediate; + + var emClearImmediate_deps = ['$emSetImmediate']; + + var _emscripten_set_immediate = (cb, userData) => { + runtimeKeepalivePush(); + return emSetImmediate(() => { + runtimeKeepalivePop(); + callUserCallback(() => + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + userData + ) + ); + }); + }; + _emscripten_set_immediate.sig = 'ipp'; + + var _emscripten_clear_immediate = (id) => { + runtimeKeepalivePop(); + emClearImmediate(id); + }; + _emscripten_clear_immediate.sig = 'vi'; + + var _emscripten_set_immediate_loop = (cb, userData) => { + function tick() { + callUserCallback(() => { + if ( + (( + a1 + ) => {}) /* a dynamic function call to signature ii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + userData + ) + ) { + emSetImmediate(tick); + } else { + runtimeKeepalivePop(); + } + }); + } + runtimeKeepalivePush(); + emSetImmediate(tick); + }; + _emscripten_set_immediate_loop.sig = 'vpp'; + + var _emscripten_set_timeout = (cb, msecs, userData) => + safeSetTimeout( + () => + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + userData + ), + msecs + ); + _emscripten_set_timeout.sig = 'ipdp'; + + var _emscripten_clear_timeout = clearTimeout; + _emscripten_clear_timeout.sig = 'vi'; + + var _emscripten_set_timeout_loop = (cb, msecs, userData) => { + function tick() { + var t = _emscripten_get_now(); + var n = t + msecs; + runtimeKeepalivePop(); + callUserCallback(() => { + if ( + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature idi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + t, + userData + ) + ) { + runtimeKeepalivePush(); + // Save a little bit of code space: modern browsers should treat + // negative setTimeout as timeout of 0 + // (https://stackoverflow.com/questions/8430966/is-calling-settimeout-with-a-negative-delay-ok) + var remaining = n - _emscripten_get_now(); + // Recent revsions of node, however, give TimeoutNegativeWarning + remaining = Math.max(0, remaining); + setTimeout(tick, remaining); + } + }); + } + runtimeKeepalivePush(); + return setTimeout(tick, 0); + }; + _emscripten_set_timeout_loop.sig = 'vpdp'; + + var _emscripten_set_interval = (cb, msecs, userData) => { + runtimeKeepalivePush(); + return setInterval(() => { + callUserCallback(() => + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + userData + ) + ); + }, msecs); + }; + _emscripten_set_interval.sig = 'ipdp'; + + var _emscripten_clear_interval = (id) => { + runtimeKeepalivePop(); + clearInterval(id); + }; + _emscripten_clear_interval.sig = 'vi'; + + var _emscripten_async_call = (func, arg, millis) => { + var wrapper = () => + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + + if ( + millis >= 0 || + // node does not support requestAnimationFrame + ENVIRONMENT_IS_NODE + ) { + safeSetTimeout(wrapper, millis); + } else { + safeRequestAnimationFrame(wrapper); + } + }; + _emscripten_async_call.sig = 'vppi'; + + var registerPostMainLoop = (f) => { + // Does nothing unless $MainLoop is included/used. + typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f); + }; + + var registerPreMainLoop = (f) => { + // Does nothing unless $MainLoop is included/used. + typeof MainLoop != 'undefined' && MainLoop.preMainLoop.push(f); + }; + + var _emscripten_get_main_loop_timing = (mode, value) => { + if (mode) HEAP32[mode >> 2] = MainLoop.timingMode; + if (value) HEAP32[value >> 2] = MainLoop.timingValue; + }; + _emscripten_get_main_loop_timing.sig = 'vpp'; + + var _emscripten_set_main_loop = (func, fps, simulateInfiniteLoop) => { + var iterFunc = + () => {} /* a dynamic function call to signature v, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */; + setMainLoop(iterFunc, fps, simulateInfiniteLoop); + }; + _emscripten_set_main_loop.sig = 'vpii'; + + var _emscripten_set_main_loop_arg = ( + func, + arg, + fps, + simulateInfiniteLoop + ) => { + var iterFunc = () => + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + setMainLoop(iterFunc, fps, simulateInfiniteLoop, arg); + }; + _emscripten_set_main_loop_arg.sig = 'vppii'; + + var _emscripten_cancel_main_loop = () => { + MainLoop.pause(); + MainLoop.func = null; + }; + _emscripten_cancel_main_loop.sig = 'v'; + + var _emscripten_pause_main_loop = () => MainLoop.pause(); + _emscripten_pause_main_loop.sig = 'v'; + + var _emscripten_resume_main_loop = () => MainLoop.resume(); + _emscripten_resume_main_loop.sig = 'v'; + + var __emscripten_push_main_loop_blocker = (func, arg, name) => { + MainLoop.queue.push({ + func: () => { + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + }, + name: UTF8ToString(name), + counted: true, + }); + MainLoop.updateStatus(); + }; + __emscripten_push_main_loop_blocker.sig = 'vppp'; + + var __emscripten_push_uncounted_main_loop_blocker = (func, arg, name) => { + MainLoop.queue.push({ + func: () => { + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + }, + name: UTF8ToString(name), + counted: false, + }); + MainLoop.updateStatus(); + }; + __emscripten_push_uncounted_main_loop_blocker.sig = 'vppp'; + + var _emscripten_set_main_loop_expected_blockers = (num) => { + MainLoop.expectedBlockers = num; + MainLoop.remainingBlockers = num; + MainLoop.updateStatus(); + }; + _emscripten_set_main_loop_expected_blockers.sig = 'vi'; + + var idsToPromises = (idBuf, size) => { + var promises = []; + for (var i = 0; i < size; i++) { + var id = HEAP32[(idBuf + i * 4) >> 2]; + promises[i] = getPromise(id); + } + return promises; + }; + + var makePromiseCallback = (callback, userData) => { + return (value) => { + runtimeKeepalivePop(); + var stack = stackSave(); + // Allocate space for the result value and initialize it to NULL. + var resultPtr = stackAlloc(POINTER_SIZE); + HEAPU32[resultPtr >> 2] = 0; + try { + var result = (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + resultPtr, + userData, + value + ); + var resultVal = HEAPU32[resultPtr >> 2]; + } catch (e) { + // If the thrown value is potentially a valid pointer, use it as the + // rejection reason. Otherwise use a null pointer as the reason. If we + // allow arbitrary objects to be thrown here, we will get a TypeError in + // MEMORY64 mode when they are later converted to void* rejection + // values. + if (typeof e != 'number') { + throw 0; + } + throw e; + } finally { + // Thrown errors will reject the promise, but at least we will restore + // the stack first. + stackRestore(stack); + } + switch (result) { + case 0: + return resultVal; + case 1: + return getPromise(resultVal); + case 2: + var ret = getPromise(resultVal); + _emscripten_promise_destroy(resultVal); + return ret; + case 3: + throw resultVal; + } + }; + }; + + var _emscripten_promise_then = (id, onFulfilled, onRejected, userData) => { + runtimeKeepalivePush(); + var promise = getPromise(id); + var newId = promiseMap.allocate({ + promise: promise.then( + makePromiseCallback(onFulfilled, userData), + makePromiseCallback(onRejected, userData) + ), + }); + return newId; + }; + _emscripten_promise_then.sig = 'ppppp'; + + var _emscripten_promise_all = (idBuf, resultBuf, size) => { + var promises = idsToPromises(idBuf, size); + var id = promiseMap.allocate({ + promise: Promise.all(promises).then((results) => { + if (resultBuf) { + for (var i = 0; i < size; i++) { + var result = results[i]; + HEAPU32[(resultBuf + i * 4) >> 2] = result; + } + } + return resultBuf; + }), + }); + return id; + }; + _emscripten_promise_all.sig = 'pppp'; + + var setPromiseResult = (ptr, fulfill, value) => { + var result = fulfill ? 0 : 3; + HEAP32[ptr >> 2] = result; + HEAPU32[(ptr + 4) >> 2] = value; + }; + + var _emscripten_promise_all_settled = (idBuf, resultBuf, size) => { + var promises = idsToPromises(idBuf, size); + var id = promiseMap.allocate({ + promise: Promise.allSettled(promises).then((results) => { + if (resultBuf) { + var offset = resultBuf; + for (var i = 0; i < size; i++, offset += 8) { + if (results[i].status === 'fulfilled') { + setPromiseResult(offset, true, results[i].value); + } else { + setPromiseResult(offset, false, results[i].reason); + } + } + } + return resultBuf; + }), + }); + return id; + }; + _emscripten_promise_all_settled.sig = 'pppp'; + + var _emscripten_promise_any = (idBuf, errorBuf, size) => { + var promises = idsToPromises(idBuf, size); + var id = promiseMap.allocate({ + promise: Promise.any(promises).catch((err) => { + if (errorBuf) { + for (var i = 0; i < size; i++) { + HEAPU32[(errorBuf + i * 4) >> 2] = err.errors[i]; + } + } + throw errorBuf; + }), + }); + return id; + }; + _emscripten_promise_any.sig = 'pppp'; + + var _emscripten_promise_race = (idBuf, size) => { + var promises = idsToPromises(idBuf, size); + var id = promiseMap.allocate({ + promise: Promise.race(promises), + }); + return id; + }; + _emscripten_promise_race.sig = 'ppp'; + + var _emscripten_promise_await = (returnValuePtr, id) => { + return Asyncify.handleSleep((wakeUp) => { + getPromise(id).then( + (value) => { + setPromiseResult(returnValuePtr, true, value); + wakeUp(); + }, + (value) => { + setPromiseResult(returnValuePtr, false, value); + wakeUp(); + } + ); + }); + }; + _emscripten_promise_await.sig = 'vpp'; + _emscripten_promise_await.isAsync = true; + + var ___resumeException = (ptr) => { + if (!exceptionLast) { + exceptionLast = ptr; + } + throw exceptionLast; + }; + ___resumeException.sig = 'vp'; + + var findMatchingCatch = (args) => { + var thrown = exceptionLast; + if (!thrown) { + // just pass through the null ptr + setTempRet0(0); + return 0; + } + var info = new ExceptionInfo(thrown); + info.set_adjusted_ptr(thrown); + var thrownType = info.get_type(); + if (!thrownType) { + // just pass through the thrown ptr + setTempRet0(0); + return thrown; + } + + // can_catch receives a **, add indirection + // The different catch blocks are denoted by different types. + // Due to inheritance, those types may not precisely match the + // type of the thrown object. Find one which matches, and + // return the type of the catch block which should be called. + for (var caughtType of args) { + if (caughtType === 0 || caughtType === thrownType) { + // Catch all clause matched or exactly the same type is caught + break; + } + var adjusted_ptr_addr = info.ptr + 16; + if (___cxa_can_catch(caughtType, thrownType, adjusted_ptr_addr)) { + setTempRet0(caughtType); + return thrown; + } + } + setTempRet0(thrownType); + return thrown; + }; + var ___cxa_find_matching_catch_2 = () => findMatchingCatch([]); + ___cxa_find_matching_catch_2.sig = 'p'; + + var ___cxa_find_matching_catch_3 = (arg0) => findMatchingCatch([arg0]); + ___cxa_find_matching_catch_3.sig = 'pp'; + + var ___cxa_find_matching_catch_4 = (arg0, arg1) => + findMatchingCatch([arg0, arg1]); + ___cxa_find_matching_catch_4.sig = 'ppp'; + + var exceptionCaught = []; + + var ___cxa_rethrow = () => { + var info = exceptionCaught.pop(); + if (!info) { + abort('no exception to throw'); + } + var ptr = info.excPtr; + if (!info.get_rethrown()) { + // Only pop if the corresponding push was through rethrow_primary_exception + exceptionCaught.push(info); + info.set_rethrown(true); + info.set_caught(false); + uncaughtExceptionCount++; + } + exceptionLast = ptr; + throw exceptionLast; + }; + ___cxa_rethrow.sig = 'v'; + + var _llvm_eh_typeid_for = (type) => type; + _llvm_eh_typeid_for.sig = 'vp'; + + var ___cxa_begin_catch = (ptr) => { + var info = new ExceptionInfo(ptr); + if (!info.get_caught()) { + info.set_caught(true); + uncaughtExceptionCount--; + } + info.set_rethrown(false); + exceptionCaught.push(info); + ___cxa_increment_exception_refcount(ptr); + return ___cxa_get_exception_ptr(ptr); + }; + ___cxa_begin_catch.sig = 'pp'; + + var ___cxa_end_catch = () => { + // Clear state flag. + _setThrew(0, 0); + // Call destructor if one is registered then clear it. + var info = exceptionCaught.pop(); + + ___cxa_decrement_exception_refcount(info.excPtr); + exceptionLast = 0; // XXX in decRef? + }; + ___cxa_end_catch.sig = 'v'; + + var ___cxa_uncaught_exceptions = () => uncaughtExceptionCount; + Module['___cxa_uncaught_exceptions'] = ___cxa_uncaught_exceptions; + ___cxa_uncaught_exceptions.sig = 'i'; + + var ___cxa_call_unexpected = (exception) => + abort( + 'Unexpected exception thrown, this is not properly supported - aborting' + ); + ___cxa_call_unexpected.sig = 'vp'; + + var ___cxa_current_primary_exception = () => { + if (!exceptionCaught.length) { + return 0; + } + var info = exceptionCaught[exceptionCaught.length - 1]; + ___cxa_increment_exception_refcount(info.excPtr); + return info.excPtr; + }; + Module['___cxa_current_primary_exception'] = + ___cxa_current_primary_exception; + ___cxa_current_primary_exception.sig = 'p'; + + function ___cxa_current_exception_type() { + if (!exceptionCaught.length) { + return 0; + } + var info = exceptionCaught[exceptionCaught.length - 1]; + return info.get_type(); + } + ___cxa_current_exception_type.sig = 'p'; + + var ___cxa_rethrow_primary_exception = (ptr) => { + if (!ptr) return; + var info = new ExceptionInfo(ptr); + exceptionCaught.push(info); + info.set_rethrown(true); + ___cxa_rethrow(); + }; + Module['___cxa_rethrow_primary_exception'] = + ___cxa_rethrow_primary_exception; + ___cxa_rethrow_primary_exception.sig = 'vp'; + + var Browser = { + useWebGL: false, + isFullscreen: false, + pointerLock: false, + moduleContextCreatedCallbacks: [], + workers: [], + preloadedImages: {}, + preloadedAudios: {}, + getCanvas: () => Module['canvas'], + init() { + if (Browser.initted) return; + Browser.initted = true; + + // Support for plugins that can process preloaded files. You can add more of these to + // your app by creating and appending to preloadPlugins. + // + // Each plugin is asked if it can handle a file based on the file's name. If it can, + // it is given the file's raw data. When it is done, it calls a callback with the file's + // (possibly modified) data. For example, a plugin might decompress a file, or it + // might create some side data structure for use later (like an Image element, etc.). + + var imagePlugin = {}; + imagePlugin['canHandle'] = function imagePlugin_canHandle(name) { + return ( + !Module['noImageDecoding'] && + /\.(jpg|jpeg|png|bmp|webp)$/i.test(name) + ); + }; + imagePlugin['handle'] = function imagePlugin_handle( + byteArray, + name, + onload, + onerror + ) { + var b = new Blob([byteArray], { + type: Browser.getMimetype(name), + }); + if (b.size !== byteArray.length) { + // Safari bug #118630 + // Safari's Blob can only take an ArrayBuffer + b = new Blob([new Uint8Array(byteArray).buffer], { + type: Browser.getMimetype(name), + }); + } + var url = URL.createObjectURL(b); + var img = new Image(); + img.onload = () => { + var canvas = /** @type {!HTMLCanvasElement} */ ( + document.createElement('canvas') + ); + canvas.width = img.width; + canvas.height = img.height; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + Browser.preloadedImages[name] = canvas; + URL.revokeObjectURL(url); + onload?.(byteArray); + }; + img.onerror = (event) => { + err(`Image ${url} could not be decoded`); + onerror?.(); + }; + img.src = url; + }; + preloadPlugins.push(imagePlugin); + + var audioPlugin = {}; + audioPlugin['canHandle'] = function audioPlugin_canHandle(name) { + return ( + !Module['noAudioDecoding'] && + name.slice(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 } + ); + }; + audioPlugin['handle'] = function audioPlugin_handle( + byteArray, + name, + onload, + onerror + ) { + var done = false; + function finish(audio) { + if (done) return; + done = true; + Browser.preloadedAudios[name] = audio; + onload?.(byteArray); + } + function fail() { + if (done) return; + done = true; + Browser.preloadedAudios[name] = new Audio(); // empty shim + onerror?.(); + } + var b = new Blob([byteArray], { + type: Browser.getMimetype(name), + }); + var url = URL.createObjectURL(b); // XXX we never revoke this! + var audio = new Audio(); + audio.addEventListener( + 'canplaythrough', + () => finish(audio), + false + ); // use addEventListener due to chromium bug 124926 + audio.onerror = function audio_onerror(event) { + if (done) return; + err( + `warning: browser could not fully decode audio ${name}, trying slower base64 approach` + ); + function encode64(data) { + var BASE = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + var PAD = '='; + var ret = ''; + var leftchar = 0; + var leftbits = 0; + for (var i = 0; i < data.length; i++) { + leftchar = (leftchar << 8) | data[i]; + leftbits += 8; + while (leftbits >= 6) { + var curr = (leftchar >> (leftbits - 6)) & 0x3f; + leftbits -= 6; + ret += BASE[curr]; + } + } + if (leftbits == 2) { + ret += BASE[(leftchar & 3) << 4]; + ret += PAD + PAD; + } else if (leftbits == 4) { + ret += BASE[(leftchar & 0xf) << 2]; + ret += PAD; + } + return ret; + } + audio.src = + 'data:audio/x-' + + name.slice(-3) + + ';base64,' + + encode64(byteArray); + finish(audio); // we don't wait for confirmation this worked - but it's worth trying + }; + audio.src = url; + // workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror + safeSetTimeout(() => { + finish(audio); // try to use it even though it is not necessarily ready to play + }, 10000); + }; + preloadPlugins.push(audioPlugin); + + // Canvas event setup + + function pointerLockChange() { + var canvas = Browser.getCanvas(); + Browser.pointerLock = + document['pointerLockElement'] === canvas || + document['mozPointerLockElement'] === canvas || + document['webkitPointerLockElement'] === canvas || + document['msPointerLockElement'] === canvas; + } + var canvas = Browser.getCanvas(); + if (canvas) { + // forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module + // Module['forcedAspectRatio'] = 4 / 3; + + canvas.requestPointerLock = + canvas['requestPointerLock'] || + canvas['mozRequestPointerLock'] || + canvas['webkitRequestPointerLock'] || + canvas['msRequestPointerLock'] || + (() => {}); + canvas.exitPointerLock = + document['exitPointerLock'] || + document['mozExitPointerLock'] || + document['webkitExitPointerLock'] || + document['msExitPointerLock'] || + (() => {}); // no-op if function does not exist + canvas.exitPointerLock = canvas.exitPointerLock.bind(document); + + document.addEventListener( + 'pointerlockchange', + pointerLockChange, + false + ); + document.addEventListener( + 'mozpointerlockchange', + pointerLockChange, + false + ); + document.addEventListener( + 'webkitpointerlockchange', + pointerLockChange, + false + ); + document.addEventListener( + 'mspointerlockchange', + pointerLockChange, + false + ); + + if (Module['elementPointerLock']) { + canvas.addEventListener( + 'click', + (ev) => { + if ( + !Browser.pointerLock && + Browser.getCanvas().requestPointerLock + ) { + Browser.getCanvas().requestPointerLock(); + ev.preventDefault(); + } + }, + false + ); + } + } + }, + createContext( + /** @type {HTMLCanvasElement} */ canvas, + useWebGL, + setInModule, + webGLContextAttributes + ) { + if (useWebGL && Module['ctx'] && canvas == Browser.getCanvas()) + return Module['ctx']; // no need to recreate GL context if it's already been created for this canvas. + + var ctx; + var contextHandle; + if (useWebGL) { + // For GLES2/desktop GL compatibility, adjust a few defaults to be different to WebGL defaults, so that they align better with the desktop defaults. + var contextAttributes = { + antialias: false, + alpha: false, + majorVersion: 1, + }; + + if (webGLContextAttributes) { + for (var attribute in webGLContextAttributes) { + contextAttributes[attribute] = + webGLContextAttributes[attribute]; + } + } + + // This check of existence of GL is here to satisfy Closure compiler, which yells if variable GL is referenced below but GL object is not + // actually compiled in because application is not doing any GL operations. TODO: Ideally if GL is not being used, this function + // Browser.createContext() should not even be emitted. + if (typeof GL != 'undefined') { + contextHandle = GL.createContext(canvas, contextAttributes); + if (contextHandle) { + ctx = GL.getContext(contextHandle).GLctx; + } + } + } else { + ctx = canvas.getContext('2d'); + } + + if (!ctx) return null; + + if (setInModule) { + Module['ctx'] = ctx; + if (useWebGL) GL.makeContextCurrent(contextHandle); + Browser.useWebGL = useWebGL; + Browser.moduleContextCreatedCallbacks.forEach((callback) => + callback() + ); + Browser.init(); + } + return ctx; + }, + fullscreenHandlersInstalled: false, + lockPointer: undefined, + resizeCanvas: undefined, + requestFullscreen(lockPointer, resizeCanvas) { + Browser.lockPointer = lockPointer; + Browser.resizeCanvas = resizeCanvas; + if (typeof Browser.lockPointer == 'undefined') + Browser.lockPointer = true; + if (typeof Browser.resizeCanvas == 'undefined') + Browser.resizeCanvas = false; + + var canvas = Browser.getCanvas(); + function fullscreenChange() { + Browser.isFullscreen = false; + var canvasContainer = canvas.parentNode; + if ( + (document['fullscreenElement'] || + document['mozFullScreenElement'] || + document['msFullscreenElement'] || + document['webkitFullscreenElement'] || + document['webkitCurrentFullScreenElement']) === + canvasContainer + ) { + canvas.exitFullscreen = Browser.exitFullscreen; + if (Browser.lockPointer) canvas.requestPointerLock(); + Browser.isFullscreen = true; + if (Browser.resizeCanvas) { + Browser.setFullscreenCanvasSize(); + } else { + Browser.updateCanvasDimensions(canvas); + } + } else { + // remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen + canvasContainer.parentNode.insertBefore( + canvas, + canvasContainer + ); + canvasContainer.parentNode.removeChild(canvasContainer); + + if (Browser.resizeCanvas) { + Browser.setWindowedCanvasSize(); + } else { + Browser.updateCanvasDimensions(canvas); + } + } + Module['onFullScreen']?.(Browser.isFullscreen); + Module['onFullscreen']?.(Browser.isFullscreen); + } + + if (!Browser.fullscreenHandlersInstalled) { + Browser.fullscreenHandlersInstalled = true; + document.addEventListener( + 'fullscreenchange', + fullscreenChange, + false + ); + document.addEventListener( + 'mozfullscreenchange', + fullscreenChange, + false + ); + document.addEventListener( + 'webkitfullscreenchange', + fullscreenChange, + false + ); + document.addEventListener( + 'MSFullscreenChange', + fullscreenChange, + false + ); + } + + // create a new parent to ensure the canvas has no siblings. this allows browsers to optimize full screen performance when its parent is the full screen root + var canvasContainer = document.createElement('div'); + canvas.parentNode.insertBefore(canvasContainer, canvas); + canvasContainer.appendChild(canvas); + + // use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size) + canvasContainer.requestFullscreen = + canvasContainer['requestFullscreen'] || + canvasContainer['mozRequestFullScreen'] || + canvasContainer['msRequestFullscreen'] || + (canvasContainer['webkitRequestFullscreen'] + ? () => + canvasContainer['webkitRequestFullscreen']( + Element['ALLOW_KEYBOARD_INPUT'] + ) + : null) || + (canvasContainer['webkitRequestFullScreen'] + ? () => + canvasContainer['webkitRequestFullScreen']( + Element['ALLOW_KEYBOARD_INPUT'] + ) + : null); + + canvasContainer.requestFullscreen(); + }, + exitFullscreen() { + // This is workaround for chrome. Trying to exit from fullscreen + // not in fullscreen state will cause "TypeError: Document not active" + // in chrome. See https://github.com/emscripten-core/emscripten/pull/8236 + if (!Browser.isFullscreen) { + return false; + } + + var CFS = + document['exitFullscreen'] || + document['cancelFullScreen'] || + document['mozCancelFullScreen'] || + document['msExitFullscreen'] || + document['webkitCancelFullScreen'] || + (() => {}); + CFS.apply(document, []); + return true; + }, + safeSetTimeout(func, timeout) { + // Legacy function, this is used by the SDL2 port so we need to keep it + // around at least until that is updated. + // See https://github.com/libsdl-org/SDL/pull/6304 + return safeSetTimeout(func, timeout); + }, + getMimetype(name) { + return { + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + bmp: 'image/bmp', + ogg: 'audio/ogg', + wav: 'audio/wav', + mp3: 'audio/mpeg', + }[name.slice(name.lastIndexOf('.') + 1)]; + }, + getUserMedia(func) { + window.getUserMedia ||= + navigator['getUserMedia'] || navigator['mozGetUserMedia']; + window.getUserMedia(func); + }, + getMovementX(event) { + return ( + event['movementX'] || + event['mozMovementX'] || + event['webkitMovementX'] || + 0 + ); + }, + getMovementY(event) { + return ( + event['movementY'] || + event['mozMovementY'] || + event['webkitMovementY'] || + 0 + ); + }, + getMouseWheelDelta(event) { + var delta = 0; + switch (event.type) { + case 'DOMMouseScroll': + // 3 lines make up a step + delta = event.detail / 3; + break; + case 'mousewheel': + // 120 units make up a step + delta = event.wheelDelta / 120; + break; + case 'wheel': + delta = event.deltaY; + switch (event.deltaMode) { + case 0: + // DOM_DELTA_PIXEL: 100 pixels make up a step + delta /= 100; + break; + case 1: + // DOM_DELTA_LINE: 3 lines make up a step + delta /= 3; + break; + case 2: + // DOM_DELTA_PAGE: A page makes up 80 steps + delta *= 80; + break; + default: + throw ( + 'unrecognized mouse wheel delta mode: ' + + event.deltaMode + ); + } + break; + default: + throw 'unrecognized mouse wheel event: ' + event.type; + } + return delta; + }, + mouseX: 0, + mouseY: 0, + mouseMovementX: 0, + mouseMovementY: 0, + touches: {}, + lastTouches: {}, + calculateMouseCoords(pageX, pageY) { + // Calculate the movement based on the changes + // in the coordinates. + var canvas = Browser.getCanvas(); + var rect = canvas.getBoundingClientRect(); + + // Neither .scrollX or .pageXOffset are defined in a spec, but + // we prefer .scrollX because it is currently in a spec draft. + // (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/) + var scrollX = + typeof window.scrollX != 'undefined' + ? window.scrollX + : window.pageXOffset; + var scrollY = + typeof window.scrollY != 'undefined' + ? window.scrollY + : window.pageYOffset; + var adjustedX = pageX - (scrollX + rect.left); + var adjustedY = pageY - (scrollY + rect.top); + + // the canvas might be CSS-scaled compared to its backbuffer; + // SDL-using content will want mouse coordinates in terms + // of backbuffer units. + adjustedX = adjustedX * (canvas.width / rect.width); + adjustedY = adjustedY * (canvas.height / rect.height); + + return { x: adjustedX, y: adjustedY }; + }, + setMouseCoords(pageX, pageY) { + const { x, y } = Browser.calculateMouseCoords(pageX, pageY); + Browser.mouseMovementX = x - Browser.mouseX; + Browser.mouseMovementY = y - Browser.mouseY; + Browser.mouseX = x; + Browser.mouseY = y; + }, + calculateMouseEvent(event) { + // event should be mousemove, mousedown or mouseup + if (Browser.pointerLock) { + // When the pointer is locked, calculate the coordinates + // based on the movement of the mouse. + // Workaround for Firefox bug 764498 + if (event.type != 'mousemove' && 'mozMovementX' in event) { + Browser.mouseMovementX = Browser.mouseMovementY = 0; + } else { + Browser.mouseMovementX = Browser.getMovementX(event); + Browser.mouseMovementY = Browser.getMovementY(event); + } + + // add the mouse delta to the current absolute mouse position + Browser.mouseX += Browser.mouseMovementX; + Browser.mouseY += Browser.mouseMovementY; + } else { + if ( + event.type === 'touchstart' || + event.type === 'touchend' || + event.type === 'touchmove' + ) { + var touch = event.touch; + if (touch === undefined) { + return; // the "touch" property is only defined in SDL + } + var coords = Browser.calculateMouseCoords( + touch.pageX, + touch.pageY + ); + + if (event.type === 'touchstart') { + Browser.lastTouches[touch.identifier] = coords; + Browser.touches[touch.identifier] = coords; + } else if ( + event.type === 'touchend' || + event.type === 'touchmove' + ) { + var last = Browser.touches[touch.identifier]; + last ||= coords; + Browser.lastTouches[touch.identifier] = last; + Browser.touches[touch.identifier] = coords; + } + return; + } + + Browser.setMouseCoords(event.pageX, event.pageY); + } + }, + resizeListeners: [], + updateResizeListeners() { + var canvas = Browser.getCanvas(); + Browser.resizeListeners.forEach((listener) => + listener(canvas.width, canvas.height) + ); + }, + setCanvasSize(width, height, noUpdates) { + var canvas = Browser.getCanvas(); + Browser.updateCanvasDimensions(canvas, width, height); + if (!noUpdates) Browser.updateResizeListeners(); + }, + windowedWidth: 0, + windowedHeight: 0, + setFullscreenCanvasSize() { + // check if SDL is available + if (typeof SDL != 'undefined') { + var flags = HEAPU32[SDL.screen >> 2]; + flags = flags | 0x00800000; // set SDL_FULLSCREEN flag + HEAP32[SDL.screen >> 2] = flags; + } + Browser.updateCanvasDimensions(Browser.getCanvas()); + Browser.updateResizeListeners(); + }, + setWindowedCanvasSize() { + // check if SDL is available + if (typeof SDL != 'undefined') { + var flags = HEAPU32[SDL.screen >> 2]; + flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag + HEAP32[SDL.screen >> 2] = flags; + } + Browser.updateCanvasDimensions(Browser.getCanvas()); + Browser.updateResizeListeners(); + }, + updateCanvasDimensions(canvas, wNative, hNative) { + if (wNative && hNative) { + canvas.widthNative = wNative; + canvas.heightNative = hNative; + } else { + wNative = canvas.widthNative; + hNative = canvas.heightNative; + } + var w = wNative; + var h = hNative; + if (Module['forcedAspectRatio'] > 0) { + if (w / h < Module['forcedAspectRatio']) { + w = Math.round(h * Module['forcedAspectRatio']); + } else { + h = Math.round(w / Module['forcedAspectRatio']); + } + } + if ( + (document['fullscreenElement'] || + document['mozFullScreenElement'] || + document['msFullscreenElement'] || + document['webkitFullscreenElement'] || + document['webkitCurrentFullScreenElement']) === + canvas.parentNode && + typeof screen != 'undefined' + ) { + var factor = Math.min(screen.width / w, screen.height / h); + w = Math.round(w * factor); + h = Math.round(h * factor); + } + if (Browser.resizeCanvas) { + if (canvas.width != w) canvas.width = w; + if (canvas.height != h) canvas.height = h; + if (typeof canvas.style != 'undefined') { + canvas.style.removeProperty('width'); + canvas.style.removeProperty('height'); + } + } else { + if (canvas.width != wNative) canvas.width = wNative; + if (canvas.height != hNative) canvas.height = hNative; + if (typeof canvas.style != 'undefined') { + if (w != wNative || h != hNative) { + canvas.style.setProperty( + 'width', + w + 'px', + 'important' + ); + canvas.style.setProperty( + 'height', + h + 'px', + 'important' + ); + } else { + canvas.style.removeProperty('width'); + canvas.style.removeProperty('height'); + } + } + } + }, + }; + + var _emscripten_run_preload_plugins = (file, onload, onerror) => { + runtimeKeepalivePush(); + + var _file = UTF8ToString(file); + var data = FS.analyzePath(_file); + if (!data.exists) return -1; + FS.createPreloadedFile( + PATH.dirname(_file), + PATH.basename(_file), + // TODO: This copy is not needed if the contents are already a Uint8Array, + // which they often are (and always are in WasmFS). + new Uint8Array(data.object.contents), + true, + true, + () => { + runtimeKeepalivePop(); + if (onload) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + file + ); + }, + () => { + runtimeKeepalivePop(); + if (onerror) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + file + ); + }, + true // don'tCreateFile - it's already there + ); + return 0; + }; + _emscripten_run_preload_plugins.sig = 'ippp'; + + var Browser_asyncPrepareDataCounter = 0; + + var _emscripten_run_preload_plugins_data = ( + data, + size, + suffix, + arg, + onload, + onerror + ) => { + runtimeKeepalivePush(); + + var _suffix = UTF8ToString(suffix); + var name = + 'prepare_data_' + Browser_asyncPrepareDataCounter++ + '.' + _suffix; + var cname = stringToNewUTF8(name); + FS.createPreloadedFile( + '/', + name, + HEAPU8.subarray(data, data + size), + true, + true, + () => { + runtimeKeepalivePop(); + if (onload) + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg, + cname + ); + }, + () => { + runtimeKeepalivePop(); + if (onerror) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + }, + true // don'tCreateFile - it's already there + ); + }; + _emscripten_run_preload_plugins_data.sig = 'vpipppp'; + + var _emscripten_async_run_script = (script, millis) => { + // TODO: cache these to avoid generating garbage + safeSetTimeout(() => _emscripten_run_script(script), millis); + }; + _emscripten_async_run_script.sig = 'vpi'; + + var _emscripten_async_load_script = async (url, onload, onerror) => { + url = UTF8ToString(url); + runtimeKeepalivePush(); + + var loadDone = () => { + runtimeKeepalivePop(); + if (onload) { + var onloadCallback = () => + callUserCallback( + () => {} /* a dynamic function call to signature v, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */ + ); + if (runDependencies > 0) { + dependenciesFulfilled = onloadCallback; + } else { + onloadCallback(); + } + } + }; + + var loadError = () => { + runtimeKeepalivePop(); + if (onerror) { + callUserCallback( + () => {} /* a dynamic function call to signature v, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */ + ); + } + }; + + if (ENVIRONMENT_IS_NODE) { + try { + var data = await readAsync(url, false); + eval(data); + loadDone(); + } catch { + loadError(); + } + return; + } + + var script = document.createElement('script'); + script.onload = loadDone; + script.onerror = loadError; + script.src = url; + document.body.appendChild(script); + }; + _emscripten_async_load_script.sig = 'vppp'; + _emscripten_async_load_script.isAsync = true; + + var _emscripten_get_window_title = () => { + var buflen = 256; + + if (!_emscripten_get_window_title.buffer) { + _emscripten_get_window_title.buffer = _malloc(buflen); + } + + stringToUTF8( + document.title, + _emscripten_get_window_title.buffer, + buflen + ); + + return _emscripten_get_window_title.buffer; + }; + _emscripten_get_window_title.sig = 'p'; + + var _emscripten_set_window_title = (title) => + (document.title = UTF8ToString(title)); + _emscripten_set_window_title.sig = 'vp'; + + var _emscripten_get_screen_size = (width, height) => { + HEAP32[width >> 2] = screen.width; + HEAP32[height >> 2] = screen.height; + }; + _emscripten_get_screen_size.sig = 'vpp'; + + var _emscripten_hide_mouse = () => { + var styleSheet = document.styleSheets[0]; + var rules = styleSheet.cssRules; + for (var i = 0; i < rules.length; i++) { + if (rules[i].cssText.startsWith('canvas')) { + styleSheet.deleteRule(i); + i--; + } + } + styleSheet.insertRule( + 'canvas.emscripten { border: 1px solid black; cursor: none; }', + 0 + ); + }; + _emscripten_hide_mouse.sig = 'v'; + + var _emscripten_set_canvas_size = (width, height) => + Browser.setCanvasSize(width, height); + _emscripten_set_canvas_size.sig = 'vii'; + + var _emscripten_get_canvas_size = (width, height, isFullscreen) => { + var canvas = Browser.getCanvas(); + HEAP32[width >> 2] = canvas.width; + HEAP32[height >> 2] = canvas.height; + HEAP32[isFullscreen >> 2] = Browser.isFullscreen ? 1 : 0; + }; + _emscripten_get_canvas_size.sig = 'vppp'; + + var _emscripten_create_worker = (url) => { + url = UTF8ToString(url); + var id = Browser.workers.length; + var info = { + worker: new Worker(url), + callbacks: [], + awaited: 0, + buffer: 0, + bufferSize: 0, + }; + info.worker.onmessage = function info_worker_onmessage(msg) { + if (ABORT) return; + var info = Browser.workers[id]; + if (!info) return; // worker was destroyed meanwhile + var callbackId = msg.data['callbackId']; + var callbackInfo = info.callbacks[callbackId]; + if (!callbackInfo) return; // no callback or callback removed meanwhile + // Don't trash our callback state if we expect additional calls. + if (msg.data['finalResponse']) { + info.awaited--; + info.callbacks[callbackId] = null; // TODO: reuse callbackIds, compress this + runtimeKeepalivePop(); + } + var data = msg.data['data']; + if (data) { + if (!data.byteLength) data = new Uint8Array(data); + if (!info.buffer || info.bufferSize < data.length) { + if (info.buffer) _free(info.buffer); + info.bufferSize = data.length; + info.buffer = _malloc(data.length); + } + HEAPU8.set(data, info.buffer); + callbackInfo.func(info.buffer, data.length, callbackInfo.arg); + } else { + callbackInfo.func(0, 0, callbackInfo.arg); + } + }; + Browser.workers.push(info); + return id; + }; + _emscripten_create_worker.sig = 'ip'; + + var _emscripten_destroy_worker = (id) => { + var info = Browser.workers[id]; + info.worker.terminate(); + if (info.buffer) _free(info.buffer); + Browser.workers[id] = null; + }; + _emscripten_destroy_worker.sig = 'vi'; + + var _emscripten_call_worker = (id, funcName, data, size, callback, arg) => { + funcName = UTF8ToString(funcName); + var info = Browser.workers[id]; + var callbackId = -1; + if (callback) { + // If we are waiting for a response from the worker we need to keep + // the runtime alive at least long enough to receive it. + // The corresponding runtimeKeepalivePop is in the `finalResponse` + // handler above. + runtimeKeepalivePush(); + callbackId = info.callbacks.length; + info.callbacks.push({ + func: ( + a1, + a2, + a3 + ) => {} /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */, + arg, + }); + info.awaited++; + } + var transferObject = { + funcName: funcName, + callbackId: callbackId, + data: data ? new Uint8Array(HEAPU8.subarray(data, data + size)) : 0, + }; + if (data) { + info.worker.postMessage(transferObject, [ + transferObject.data.buffer, + ]); + } else { + info.worker.postMessage(transferObject); + } + }; + _emscripten_call_worker.sig = 'vippipp'; + + var _emscripten_get_worker_queue_size = (id) => { + var info = Browser.workers[id]; + if (!info) return -1; + return info.awaited; + }; + _emscripten_get_worker_queue_size.sig = 'ii'; + + var getPreloadedImageData = (path, w, h) => { + path = PATH_FS.resolve(path); + + var canvas = /** @type {HTMLCanvasElement} */ ( + Browser.preloadedImages[path] + ); + if (!canvas) return 0; + + var ctx = canvas.getContext('2d'); + var image = ctx.getImageData(0, 0, canvas.width, canvas.height); + var buf = _malloc(canvas.width * canvas.height * 4); + + HEAPU8.set(image.data, buf); + + HEAP32[w >> 2] = canvas.width; + HEAP32[h >> 2] = canvas.height; + return buf; + }; + + var _emscripten_get_preloaded_image_data = (path, w, h) => + getPreloadedImageData(UTF8ToString(path), w, h); + _emscripten_get_preloaded_image_data.sig = 'pppp'; + + var getPreloadedImageData__data = ['$PATH_FS', 'malloc']; + + var _emscripten_get_preloaded_image_data_from_FILE = (file, w, h) => { + var fd = _fileno(file); + var stream = FS.getStream(fd); + if (stream) { + return getPreloadedImageData(stream.path, w, h); + } + + return 0; + }; + _emscripten_get_preloaded_image_data_from_FILE.sig = 'pppp'; + + var wget = { + wgetRequests: {}, + nextWgetRequestHandle: 0, + getNextWgetRequestHandle() { + var handle = wget.nextWgetRequestHandle; + wget.nextWgetRequestHandle++; + return handle; + }, + }; + + /** + * @param {number=} mode Optionally, the mode to create in. Uses mkdir's + * default if not set. + */ + var FS_mkdirTree = (path, mode) => FS.mkdirTree(path, mode); + + var _emscripten_async_wget = (url, file, onload, onerror) => { + runtimeKeepalivePush(); + + var _url = UTF8ToString(url); + var _file = UTF8ToString(file); + _file = PATH_FS.resolve(_file); + function doCallback(callback) { + if (callback) { + runtimeKeepalivePop(); + callUserCallback(() => { + var sp = stackSave(); + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + stringToUTF8OnStack(_file) + ); + stackRestore(sp); + }); + } + } + var destinationDirectory = PATH.dirname(_file); + FS_createPreloadedFile( + destinationDirectory, + PATH.basename(_file), + _url, + true, + true, + () => doCallback(onload), + () => doCallback(onerror), + false, // dontCreateFile + false, // canOwn + () => { + // preFinish + // if a file exists there, we overwrite it + try { + FS_unlink(_file); + } catch (e) {} + // if the destination directory does not yet exist, create it + FS_mkdirTree(destinationDirectory); + } + ); + }; + _emscripten_async_wget.sig = 'vpppp'; + + var _emscripten_async_wget_data = async ( + url, + userdata, + onload, + onerror + ) => { + runtimeKeepalivePush(); + /* no need for run dependency, this is async but will not do any prepare etc. step */ + try { + var byteArray = await asyncLoad(UTF8ToString(url)); + runtimeKeepalivePop(); + callUserCallback(() => { + var buffer = _malloc(byteArray.length); + HEAPU8.set(byteArray, buffer); + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + userdata, + buffer, + byteArray.length + ); + _free(buffer); + }); + } catch (e) { + if (onerror) { + runtimeKeepalivePop(); + callUserCallback(() => { + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + userdata + ); + }); + } + } + }; + _emscripten_async_wget_data.sig = 'vpppp'; + _emscripten_async_wget_data.isAsync = true; + + var _emscripten_async_wget2 = ( + url, + file, + request, + param, + userdata, + onload, + onerror, + onprogress + ) => { + runtimeKeepalivePush(); + + var _url = UTF8ToString(url); + var _file = UTF8ToString(file); + _file = PATH_FS.resolve(_file); + var _request = UTF8ToString(request); + var _param = UTF8ToString(param); + var index = _file.lastIndexOf('/'); + + var http = new XMLHttpRequest(); + http.open(_request, _url, true); + http.responseType = 'arraybuffer'; + + var handle = wget.getNextWgetRequestHandle(); + + var destinationDirectory = PATH.dirname(_file); + + // LOAD + http.onload = (e) => { + runtimeKeepalivePop(); + if (http.status >= 200 && http.status < 300) { + // if a file exists there, we overwrite it + try { + FS.unlink(_file); + } catch (e) {} + // if the destination directory does not yet exist, create it + FS.mkdirTree(destinationDirectory); + + FS.createDataFile( + _file.slice(0, index), + _file.slice(index + 1), + new Uint8Array(/** @type{ArrayBuffer}*/ (http.response)), + true, + true, + false + ); + if (onload) { + var sp = stackSave(); + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + userdata, + stringToUTF8OnStack(_file) + ); + stackRestore(sp); + } + } else { + if (onerror) + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + userdata, + http.status + ); + } + + delete wget.wgetRequests[handle]; + }; + + // ERROR + http.onerror = (e) => { + runtimeKeepalivePop(); + if (onerror) + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + userdata, + http.status + ); + delete wget.wgetRequests[handle]; + }; + + // PROGRESS + http.onprogress = (e) => { + if ( + e.lengthComputable || + (e.lengthComputable === undefined && e.total != 0) + ) { + var percentComplete = (e.loaded / e.total) * 100; + if (onprogress) + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + userdata, + percentComplete + ); + } + }; + + // ABORT + http.onabort = (e) => { + runtimeKeepalivePop(); + delete wget.wgetRequests[handle]; + }; + + if (_request == 'POST') { + //Send the proper header information along with the request + http.setRequestHeader( + 'Content-type', + 'application/x-www-form-urlencoded' + ); + http.send(_param); + } else { + http.send(null); + } + + wget.wgetRequests[handle] = http; + + return handle; + }; + _emscripten_async_wget2.sig = 'ipppppppp'; + + var _emscripten_async_wget2_data = ( + url, + request, + param, + userdata, + free, + onload, + onerror, + onprogress + ) => { + var _url = UTF8ToString(url); + var _request = UTF8ToString(request); + var _param = UTF8ToString(param); + + var http = new XMLHttpRequest(); + http.open(_request, _url, true); + http.responseType = 'arraybuffer'; + + var handle = wget.getNextWgetRequestHandle(); + + function onerrorjs() { + if (onerror) { + var sp = stackSave(); + var statusText = 0; + if (http.statusText) { + statusText = stringToUTF8OnStack(http.statusText); + } + (( + a1, + a2, + a3, + a4 + ) => {}) /* a dynamic function call to signature viiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + userdata, + http.status, + statusText + ); + stackRestore(sp); + } + } + + // LOAD + http.onload = (e) => { + if ( + (http.status >= 200 && http.status < 300) || + (http.status === 0 && _url.slice(0, 4).toLowerCase() != 'http') + ) { + var byteArray = new Uint8Array( + /** @type{ArrayBuffer} */ (http.response) + ); + var buffer = _malloc(byteArray.length); + HEAPU8.set(byteArray, buffer); + if (onload) + (( + a1, + a2, + a3, + a4 + ) => {}) /* a dynamic function call to signature viiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + userdata, + buffer, + byteArray.length + ); + if (free) _free(buffer); + } else { + onerrorjs(); + } + delete wget.wgetRequests[handle]; + }; + + // ERROR + http.onerror = (e) => { + onerrorjs(); + delete wget.wgetRequests[handle]; + }; + + // PROGRESS + http.onprogress = (e) => { + if (onprogress) + (( + a1, + a2, + a3, + a4 + ) => {}) /* a dynamic function call to signature viiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + handle, + userdata, + e.loaded, + e.lengthComputable || e.lengthComputable === undefined + ? e.total + : 0 + ); + }; + + // ABORT + http.onabort = (e) => { + delete wget.wgetRequests[handle]; + }; + + if (_request == 'POST') { + //Send the proper header information along with the request + http.setRequestHeader( + 'Content-type', + 'application/x-www-form-urlencoded' + ); + http.send(_param); + } else { + http.send(null); + } + + wget.wgetRequests[handle] = http; + + return handle; + }; + _emscripten_async_wget2_data.sig = 'ippppippp'; + + var _emscripten_async_wget2_abort = (handle) => { + var http = wget.wgetRequests[handle]; + http?.abort(); + }; + _emscripten_async_wget2_abort.sig = 'vi'; + + var ___asctime_r = (tmPtr, buf) => { + var date = { + tm_sec: HEAP32[tmPtr >> 2], + tm_min: HEAP32[(tmPtr + 4) >> 2], + tm_hour: HEAP32[(tmPtr + 8) >> 2], + tm_mday: HEAP32[(tmPtr + 12) >> 2], + tm_mon: HEAP32[(tmPtr + 16) >> 2], + tm_year: HEAP32[(tmPtr + 20) >> 2], + tm_wday: HEAP32[(tmPtr + 24) >> 2], + }; + var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + var months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + var s = + days[date.tm_wday] + + ' ' + + months[date.tm_mon] + + (date.tm_mday < 10 ? ' ' : ' ') + + date.tm_mday + + (date.tm_hour < 10 ? ' 0' : ' ') + + date.tm_hour + + (date.tm_min < 10 ? ':0' : ':') + + date.tm_min + + (date.tm_sec < 10 ? ':0' : ':') + + date.tm_sec + + ' ' + + (1900 + date.tm_year) + + '\n'; + + // asctime_r is specced to behave in an undefined manner if the algorithm would attempt + // to write out more than 26 bytes (including the null terminator). + // See http://pubs.opengroup.org/onlinepubs/9699919799/functions/asctime.html + // Our undefined behavior is to truncate the write to at most 26 bytes, including null terminator. + stringToUTF8(s, buf, 26); + return buf; + }; + ___asctime_r.sig = 'ppp'; + + var _strptime_l = (buf, format, tm, locale) => _strptime(buf, format, tm); + _strptime_l.sig = 'ppppp'; + + function ___syscall_shutdown(fd, how) { + try { + getSocketFromFD(fd); + return -52; // unsupported feature + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + Module['___syscall_shutdown'] = ___syscall_shutdown; + ___syscall_shutdown.sig = 'iiiiiii'; + + var __dlsym_catchup_js = (handle, symbolIndex) => { + var lib = LDSO.loadedLibsByHandle[handle]; + var symDict = lib.exports; + var symName = Object.keys(symDict)[symbolIndex]; + var sym = symDict[symName]; + var result = addFunction(sym, sym.sig); + return result; + }; + __dlsym_catchup_js.sig = 'ppi'; + + var FS_readFile = FS.readFile; + + var _setNetworkCallback = (event, userData, callback) => { + function _callback(data) { + callUserCallback(() => { + if (event === 'error') { + withStackSave(() => { + var msg = stringToUTF8OnStack(data[2]); + (( + a1, + a2, + a3, + a4 + ) => {}) /* a dynamic function call to signature viiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + data[0], + data[1], + msg, + userData + ); + }); + } else { + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + data, + userData + ); + } + }); + } + + // FIXME(sbc): This has no corresponding Pop so will currently keep the + // runtime alive indefinitely. + runtimeKeepalivePush(); + SOCKFS.on(event, callback ? _callback : null); + }; + + var _emscripten_set_socket_error_callback = (userData, callback) => + _setNetworkCallback('error', userData, callback); + _emscripten_set_socket_error_callback.sig = 'vpp'; + + var _emscripten_set_socket_open_callback = (userData, callback) => + _setNetworkCallback('open', userData, callback); + _emscripten_set_socket_open_callback.sig = 'vpp'; + + var _emscripten_set_socket_listen_callback = (userData, callback) => + _setNetworkCallback('listen', userData, callback); + _emscripten_set_socket_listen_callback.sig = 'vpp'; + + var _emscripten_set_socket_connection_callback = (userData, callback) => + _setNetworkCallback('connection', userData, callback); + _emscripten_set_socket_connection_callback.sig = 'vpp'; + + var _emscripten_set_socket_message_callback = (userData, callback) => + _setNetworkCallback('message', userData, callback); + _emscripten_set_socket_message_callback.sig = 'vpp'; + + var _emscripten_set_socket_close_callback = (userData, callback) => + _setNetworkCallback('close', userData, callback); + _emscripten_set_socket_close_callback.sig = 'vpp'; + + var _emscripten_webgl_enable_ANGLE_instanced_arrays = (ctx) => + webgl_enable_ANGLE_instanced_arrays(GL.contexts[ctx].GLctx); + _emscripten_webgl_enable_ANGLE_instanced_arrays.sig = 'ip'; + + var _emscripten_webgl_enable_OES_vertex_array_object = (ctx) => + webgl_enable_OES_vertex_array_object(GL.contexts[ctx].GLctx); + _emscripten_webgl_enable_OES_vertex_array_object.sig = 'ip'; + + var _emscripten_webgl_enable_WEBGL_draw_buffers = (ctx) => + webgl_enable_WEBGL_draw_buffers(GL.contexts[ctx].GLctx); + _emscripten_webgl_enable_WEBGL_draw_buffers.sig = 'ip'; + + var _emscripten_webgl_enable_WEBGL_multi_draw = (ctx) => + webgl_enable_WEBGL_multi_draw(GL.contexts[ctx].GLctx); + _emscripten_webgl_enable_WEBGL_multi_draw.sig = 'ip'; + + var _emscripten_webgl_enable_EXT_polygon_offset_clamp = (ctx) => + webgl_enable_EXT_polygon_offset_clamp(GL.contexts[ctx].GLctx); + _emscripten_webgl_enable_EXT_polygon_offset_clamp.sig = 'ip'; + + var _emscripten_webgl_enable_EXT_clip_control = (ctx) => + webgl_enable_EXT_clip_control(GL.contexts[ctx].GLctx); + _emscripten_webgl_enable_EXT_clip_control.sig = 'ip'; + + var _emscripten_webgl_enable_WEBGL_polygon_mode = (ctx) => + webgl_enable_WEBGL_polygon_mode(GL.contexts[ctx].GLctx); + _emscripten_webgl_enable_WEBGL_polygon_mode.sig = 'ip'; + + var _glVertexPointer = (size, type, stride, ptr) => { + throw 'Legacy GL function (glVertexPointer) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation.'; + }; + _glVertexPointer.sig = 'viiip'; + + var _glMatrixMode = () => { + throw 'Legacy GL function (glMatrixMode) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation.'; + }; + _glMatrixMode.sig = 'vi'; + + var _glBegin = () => { + throw 'Legacy GL function (glBegin) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation.'; + }; + _glBegin.sig = 'vi'; + + var _glLoadIdentity = () => { + throw 'Legacy GL function (glLoadIdentity) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation.'; + }; + _glLoadIdentity.sig = 'v'; + + var _glVertexAttribDivisorNV = _glVertexAttribDivisor; + + var _glDrawArraysInstancedNV = _glDrawArraysInstanced; + + var _glDrawElementsInstancedNV = _glDrawElementsInstanced; + + var _glVertexAttribDivisorEXT = _glVertexAttribDivisor; + + var _glDrawArraysInstancedEXT = _glDrawArraysInstanced; + + var _glDrawElementsInstancedEXT = _glDrawElementsInstanced; + + var _glVertexAttribDivisorARB = _glVertexAttribDivisor; + + var _glDrawArraysInstancedARB = _glDrawArraysInstanced; + + var _glDrawElementsInstancedARB = _glDrawElementsInstanced; + + var _glDrawBuffersEXT = _glDrawBuffers; + + /** @suppress {duplicate } */ + var _glMultiDrawArraysWEBGL = (mode, firsts, counts, drawcount) => { + GLctx.multiDrawWebgl['multiDrawArraysWEBGL']( + mode, + HEAP32, + firsts >> 2, + HEAP32, + counts >> 2, + drawcount + ); + }; + _glMultiDrawArraysWEBGL.sig = 'vippi'; + var _glMultiDrawArrays = _glMultiDrawArraysWEBGL; + _glMultiDrawArrays.sig = 'vippi'; + + var _glMultiDrawArraysANGLE = _glMultiDrawArraysWEBGL; + + /** @suppress {duplicate } */ + var _glMultiDrawArraysInstancedWEBGL = ( + mode, + firsts, + counts, + instanceCounts, + drawcount + ) => { + GLctx.multiDrawWebgl['multiDrawArraysInstancedWEBGL']( + mode, + HEAP32, + firsts >> 2, + HEAP32, + counts >> 2, + HEAP32, + instanceCounts >> 2, + drawcount + ); + }; + _glMultiDrawArraysInstancedWEBGL.sig = 'vipppi'; + var _glMultiDrawArraysInstancedANGLE = _glMultiDrawArraysInstancedWEBGL; + + /** @suppress {duplicate } */ + var _glMultiDrawElementsWEBGL = ( + mode, + counts, + type, + offsets, + drawcount + ) => { + GLctx.multiDrawWebgl['multiDrawElementsWEBGL']( + mode, + HEAP32, + counts >> 2, + type, + HEAP32, + offsets >> 2, + drawcount + ); + }; + _glMultiDrawElementsWEBGL.sig = 'vipipi'; + var _glMultiDrawElements = _glMultiDrawElementsWEBGL; + _glMultiDrawElements.sig = 'vipipi'; + + var _glMultiDrawElementsANGLE = _glMultiDrawElementsWEBGL; + + /** @suppress {duplicate } */ + var _glMultiDrawElementsInstancedWEBGL = ( + mode, + counts, + type, + offsets, + instanceCounts, + drawcount + ) => { + GLctx.multiDrawWebgl['multiDrawElementsInstancedWEBGL']( + mode, + HEAP32, + counts >> 2, + type, + HEAP32, + offsets >> 2, + HEAP32, + instanceCounts >> 2, + drawcount + ); + }; + _glMultiDrawElementsInstancedWEBGL.sig = 'vipippi'; + var _glMultiDrawElementsInstancedANGLE = _glMultiDrawElementsInstancedWEBGL; + + var _glClearDepth = (x0) => GLctx.clearDepth(x0); + _glClearDepth.sig = 'vd'; + + var _glDepthRange = (x0, x1) => GLctx.depthRange(x0, x1); + _glDepthRange.sig = 'vdd'; + + var _emscripten_glGenVertexArrays = _glGenVertexArrays; + _emscripten_glGenVertexArrays.sig = 'vip'; + + var _emscripten_glDeleteVertexArrays = _glDeleteVertexArrays; + _emscripten_glDeleteVertexArrays.sig = 'vip'; + + var _emscripten_glBindVertexArray = _glBindVertexArray; + _emscripten_glBindVertexArray.sig = 'vi'; + + var _emscripten_glIsVertexArray = _glIsVertexArray; + _emscripten_glIsVertexArray.sig = 'ii'; + + var _emscripten_glVertexPointer = _glVertexPointer; + _emscripten_glVertexPointer.sig = 'viiip'; + + var _emscripten_glMatrixMode = _glMatrixMode; + _emscripten_glMatrixMode.sig = 'vi'; + + var _emscripten_glBegin = _glBegin; + _emscripten_glBegin.sig = 'vi'; + + var _emscripten_glLoadIdentity = _glLoadIdentity; + _emscripten_glLoadIdentity.sig = 'v'; + + var _emscripten_glVertexAttribDivisor = _glVertexAttribDivisor; + _emscripten_glVertexAttribDivisor.sig = 'vii'; + + var _emscripten_glDrawArraysInstanced = _glDrawArraysInstanced; + _emscripten_glDrawArraysInstanced.sig = 'viiii'; + + var _emscripten_glDrawElementsInstanced = _glDrawElementsInstanced; + _emscripten_glDrawElementsInstanced.sig = 'viiipi'; + + var _emscripten_glVertexAttribDivisorNV = _glVertexAttribDivisorNV; + + var _emscripten_glDrawArraysInstancedNV = _glDrawArraysInstancedNV; + + var _emscripten_glDrawElementsInstancedNV = _glDrawElementsInstancedNV; + + var _emscripten_glVertexAttribDivisorEXT = _glVertexAttribDivisorEXT; + + var _emscripten_glDrawArraysInstancedEXT = _glDrawArraysInstancedEXT; + + var _emscripten_glDrawElementsInstancedEXT = _glDrawElementsInstancedEXT; + + var _emscripten_glVertexAttribDivisorARB = _glVertexAttribDivisorARB; + + var _emscripten_glDrawArraysInstancedARB = _glDrawArraysInstancedARB; + + var _emscripten_glDrawElementsInstancedARB = _glDrawElementsInstancedARB; + + var _emscripten_glDrawBuffers = _glDrawBuffers; + _emscripten_glDrawBuffers.sig = 'vip'; + + var _emscripten_glDrawBuffersEXT = _glDrawBuffersEXT; + + var _emscripten_glMultiDrawArrays = _glMultiDrawArrays; + _emscripten_glMultiDrawArrays.sig = 'vippi'; + + var _emscripten_glMultiDrawArraysANGLE = _glMultiDrawArraysANGLE; + + var _emscripten_glMultiDrawArraysWEBGL = _glMultiDrawArraysWEBGL; + + var _emscripten_glMultiDrawArraysInstancedANGLE = + _glMultiDrawArraysInstancedANGLE; + + var _emscripten_glMultiDrawArraysInstancedWEBGL = + _glMultiDrawArraysInstancedWEBGL; + + var _emscripten_glMultiDrawElements = _glMultiDrawElements; + _emscripten_glMultiDrawElements.sig = 'vipipi'; + + var _emscripten_glMultiDrawElementsANGLE = _glMultiDrawElementsANGLE; + + var _emscripten_glMultiDrawElementsWEBGL = _glMultiDrawElementsWEBGL; + + var _emscripten_glMultiDrawElementsInstancedANGLE = + _glMultiDrawElementsInstancedANGLE; + + var _emscripten_glMultiDrawElementsInstancedWEBGL = + _glMultiDrawElementsInstancedWEBGL; + + var _emscripten_glClearDepth = _glClearDepth; + _emscripten_glClearDepth.sig = 'vd'; + + var _emscripten_glDepthRange = _glDepthRange; + _emscripten_glDepthRange.sig = 'vdd'; + + var writeGLArray = (arr, dst, dstLength, heapType) => { + var len = arr.length; + var writeLength = dstLength < len ? dstLength : len; + var heap = heapType ? HEAPF32 : HEAP32; + // Works because HEAPF32 and HEAP32 have the same bytes-per-element + dst = dst >> 2; + for (var i = 0; i < writeLength; ++i) { + heap[dst + i] = arr[i]; + } + return len; + }; + + var webglPowerPreferences = ['default', 'low-power', 'high-performance']; + + /** @suppress {duplicate } */ + var _emscripten_webgl_do_create_context = (target, attributes) => { + var attr32 = attributes >> 2; + var powerPreference = HEAP32[attr32 + (8 >> 2)]; + var contextAttributes = { + alpha: !!HEAP8[attributes + 0], + depth: !!HEAP8[attributes + 1], + stencil: !!HEAP8[attributes + 2], + antialias: !!HEAP8[attributes + 3], + premultipliedAlpha: !!HEAP8[attributes + 4], + preserveDrawingBuffer: !!HEAP8[attributes + 5], + powerPreference: webglPowerPreferences[powerPreference], + failIfMajorPerformanceCaveat: !!HEAP8[attributes + 12], + // The following are not predefined WebGL context attributes in the WebGL specification, so the property names can be minified by Closure. + majorVersion: HEAP32[attr32 + (16 >> 2)], + minorVersion: HEAP32[attr32 + (20 >> 2)], + enableExtensionsByDefault: HEAP8[attributes + 24], + explicitSwapControl: HEAP8[attributes + 25], + proxyContextToMainThread: HEAP32[attr32 + (28 >> 2)], + renderViaOffscreenBackBuffer: HEAP8[attributes + 32], + }; + + var canvas = findCanvasEventTarget(target); + + if (!canvas) { + return 0; + } + + if (contextAttributes.explicitSwapControl) { + return 0; + } + + var contextHandle = GL.createContext(canvas, contextAttributes); + return contextHandle; + }; + _emscripten_webgl_do_create_context.sig = 'ppp'; + var _emscripten_webgl_create_context = _emscripten_webgl_do_create_context; + _emscripten_webgl_create_context.sig = 'ppp'; + + /** @suppress {duplicate } */ + var _emscripten_webgl_do_get_current_context = () => + GL.currentContext ? GL.currentContext.handle : 0; + _emscripten_webgl_do_get_current_context.sig = 'p'; + var _emscripten_webgl_get_current_context = + _emscripten_webgl_do_get_current_context; + _emscripten_webgl_get_current_context.sig = 'p'; + + /** @suppress {duplicate } */ + var _emscripten_webgl_do_commit_frame = () => { + if (!GL.currentContext || !GL.currentContext.GLctx) { + return -3; + } + + if (!GL.currentContext.attributes.explicitSwapControl) { + return -3; + } + // We would do GL.currentContext.GLctx.commit(); here, but the current implementation + // in browsers has removed it - swap is implicit, so this function is a no-op for now + // (until/unless the spec changes). + return 0; + }; + _emscripten_webgl_do_commit_frame.sig = 'i'; + var _emscripten_webgl_commit_frame = _emscripten_webgl_do_commit_frame; + _emscripten_webgl_commit_frame.sig = 'i'; + + var _emscripten_webgl_make_context_current = (contextHandle) => { + var success = GL.makeContextCurrent(contextHandle); + return success ? 0 : -5; + }; + _emscripten_webgl_make_context_current.sig = 'ip'; + + var _emscripten_webgl_get_drawing_buffer_size = ( + contextHandle, + width, + height + ) => { + var GLContext = GL.getContext(contextHandle); + + if (!GLContext || !GLContext.GLctx || !width || !height) { + return -5; + } + HEAP32[width >> 2] = GLContext.GLctx.drawingBufferWidth; + HEAP32[height >> 2] = GLContext.GLctx.drawingBufferHeight; + return 0; + }; + _emscripten_webgl_get_drawing_buffer_size.sig = 'ippp'; + + var _emscripten_webgl_get_context_attributes = (c, a) => { + if (!a) return -5; + c = GL.contexts[c]; + if (!c) return -3; + var t = c.GLctx; + if (!t) return -3; + t = t.getContextAttributes(); + + HEAP8[a] = t.alpha; + HEAP8[a + 1] = t.depth; + HEAP8[a + 2] = t.stencil; + HEAP8[a + 3] = t.antialias; + HEAP8[a + 4] = t.premultipliedAlpha; + HEAP8[a + 5] = t.preserveDrawingBuffer; + var power = + t['powerPreference'] && + webglPowerPreferences.indexOf(t['powerPreference']); + HEAP32[(a + 8) >> 2] = power; + HEAP8[a + 12] = t.failIfMajorPerformanceCaveat; + HEAP32[(a + 16) >> 2] = c.version; + HEAP32[(a + 20) >> 2] = 0; + HEAP8[a + 24] = c.attributes.enableExtensionsByDefault; + return 0; + }; + _emscripten_webgl_get_context_attributes.sig = 'ipp'; + + var _emscripten_webgl_destroy_context = (contextHandle) => { + if (GL.currentContext == contextHandle) GL.currentContext = 0; + GL.deleteContext(contextHandle); + }; + _emscripten_webgl_destroy_context.sig = 'ip'; + + var _emscripten_webgl_enable_extension = (contextHandle, extension) => { + var context = GL.getContext(contextHandle); + var extString = UTF8ToString(extension); + if (extString.startsWith('GL_')) extString = extString.slice(3); // Allow enabling extensions both with "GL_" prefix and without. + + // Switch-board that pulls in code for all GL extensions, even if those are not used :/ + // Build with -sGL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS=0 to avoid this. + + // Obtain function entry points to WebGL 1 extension related functions. + if (extString == 'ANGLE_instanced_arrays') + webgl_enable_ANGLE_instanced_arrays(GLctx); + if (extString == 'OES_vertex_array_object') + webgl_enable_OES_vertex_array_object(GLctx); + if (extString == 'WEBGL_draw_buffers') + webgl_enable_WEBGL_draw_buffers(GLctx); + + if (extString == 'WEBGL_multi_draw') + webgl_enable_WEBGL_multi_draw(GLctx); + if (extString == 'EXT_polygon_offset_clamp') + webgl_enable_EXT_polygon_offset_clamp(GLctx); + if (extString == 'EXT_clip_control') + webgl_enable_EXT_clip_control(GLctx); + if (extString == 'WEBGL_polygon_mode') + webgl_enable_WEBGL_polygon_mode(GLctx); + + var ext = context.GLctx.getExtension(extString); + return !!ext; + }; + _emscripten_webgl_enable_extension.sig = 'ipp'; + + var _emscripten_supports_offscreencanvas = () => + // TODO: Add a new build mode, e.g. OFFSCREENCANVAS_SUPPORT=2, which + // necessitates OffscreenCanvas support at build time, and "return 1;" here in that build mode. + 0; + _emscripten_supports_offscreencanvas.sig = 'i'; + + var registerWebGlEventCallback = ( + target, + userData, + useCapture, + callbackfunc, + eventTypeId, + eventTypeString, + targetThread + ) => { + var webGlEventHandlerFunc = (e = event) => { + if ( + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature iiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + eventTypeId, + 0, + userData + ) + ) + e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: webGlEventHandlerFunc, + useCapture, + }; + JSEvents.registerOrRemoveHandler(eventHandler); + }; + + var _emscripten_set_webglcontextlost_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => { + registerWebGlEventCallback( + target, + userData, + useCapture, + callbackfunc, + 31, + 'webglcontextlost', + targetThread + ); + return 0; + }; + _emscripten_set_webglcontextlost_callback_on_thread.sig = 'ippipp'; + + var _emscripten_set_webglcontextrestored_callback_on_thread = ( + target, + userData, + useCapture, + callbackfunc, + targetThread + ) => { + registerWebGlEventCallback( + target, + userData, + useCapture, + callbackfunc, + 32, + 'webglcontextrestored', + targetThread + ); + return 0; + }; + _emscripten_set_webglcontextrestored_callback_on_thread.sig = 'ippipp'; + + var _emscripten_is_webgl_context_lost = (contextHandle) => + !GL.contexts[contextHandle] || + GL.contexts[contextHandle].GLctx.isContextLost(); + _emscripten_is_webgl_context_lost.sig = 'ip'; + + var _emscripten_webgl_get_supported_extensions = () => + stringToNewUTF8(GLctx.getSupportedExtensions().join(' ')); + _emscripten_webgl_get_supported_extensions.sig = 'p'; + + var _emscripten_webgl_get_program_parameter_d = (program, param) => + GLctx.getProgramParameter(GL.programs[program], param); + _emscripten_webgl_get_program_parameter_d.sig = 'dii'; + + var _emscripten_webgl_get_program_info_log_utf8 = (program) => + stringToNewUTF8(GLctx.getProgramInfoLog(GL.programs[program])); + _emscripten_webgl_get_program_info_log_utf8.sig = 'pi'; + + var _emscripten_webgl_get_shader_parameter_d = (shader, param) => + GLctx.getShaderParameter(GL.shaders[shader], param); + _emscripten_webgl_get_shader_parameter_d.sig = 'dii'; + + var _emscripten_webgl_get_shader_info_log_utf8 = (shader) => + stringToNewUTF8(GLctx.getShaderInfoLog(GL.shaders[shader])); + _emscripten_webgl_get_shader_info_log_utf8.sig = 'pi'; + + var _emscripten_webgl_get_shader_source_utf8 = (shader) => + stringToNewUTF8(GLctx.getShaderSource(GL.shaders[shader])); + _emscripten_webgl_get_shader_source_utf8.sig = 'pi'; + + var _emscripten_webgl_get_vertex_attrib_d = (index, param) => + GLctx.getVertexAttrib(index, param); + _emscripten_webgl_get_vertex_attrib_d.sig = 'dii'; + + var _emscripten_webgl_get_vertex_attrib_o = (index, param) => { + var obj = GLctx.getVertexAttrib(index, param); + return obj?.name; + }; + _emscripten_webgl_get_vertex_attrib_o.sig = 'iii'; + + var _emscripten_webgl_get_vertex_attrib_v = ( + index, + param, + dst, + dstLength, + dstType + ) => + writeGLArray( + GLctx.getVertexAttrib(index, param), + dst, + dstLength, + dstType + ); + _emscripten_webgl_get_vertex_attrib_v.sig = 'iiipii'; + + var _emscripten_webgl_get_uniform_d = (program, location) => + GLctx.getUniform( + GL.programs[program], + webglGetUniformLocation(location) + ); + _emscripten_webgl_get_uniform_d.sig = 'dii'; + + var _emscripten_webgl_get_uniform_v = ( + program, + location, + dst, + dstLength, + dstType + ) => + writeGLArray( + GLctx.getUniform( + GL.programs[program], + webglGetUniformLocation(location) + ), + dst, + dstLength, + dstType + ); + _emscripten_webgl_get_uniform_v.sig = 'iiipii'; + + var _emscripten_webgl_get_parameter_v = (param, dst, dstLength, dstType) => + writeGLArray(GLctx.getParameter(param), dst, dstLength, dstType); + _emscripten_webgl_get_parameter_v.sig = 'iipii'; + + var _emscripten_webgl_get_parameter_d = (param) => + GLctx.getParameter(param); + _emscripten_webgl_get_parameter_d.sig = 'di'; + + var _emscripten_webgl_get_parameter_o = (param) => { + var obj = GLctx.getParameter(param); + return obj?.name; + }; + _emscripten_webgl_get_parameter_o.sig = 'ii'; + + var _emscripten_webgl_get_parameter_utf8 = (param) => + stringToNewUTF8(GLctx.getParameter(param)); + _emscripten_webgl_get_parameter_utf8.sig = 'pi'; + + var _emscripten_webgl_get_parameter_i64v = (param, dst) => + writeI53ToI64(dst, GLctx.getParameter(param)); + _emscripten_webgl_get_parameter_i64v.sig = 'vip'; + + var _glutPostRedisplay = () => { + if (GLUT.displayFunc && !GLUT.requestedAnimationFrame) { + GLUT.requestedAnimationFrame = true; + MainLoop.requestAnimationFrame(() => { + GLUT.requestedAnimationFrame = false; + MainLoop.runIter(() => + (() => {})(/* a dynamic function call to signature v, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */) + ); + }); + } + }; + _glutPostRedisplay.sig = 'v'; + + var GLUT = { + initTime: null, + idleFunc: null, + displayFunc: null, + keyboardFunc: null, + keyboardUpFunc: null, + specialFunc: null, + specialUpFunc: null, + reshapeFunc: null, + motionFunc: null, + passiveMotionFunc: null, + mouseFunc: null, + buttons: 0, + modifiers: 0, + initWindowWidth: 256, + initWindowHeight: 256, + initDisplayMode: 18, + windowX: 0, + windowY: 0, + windowWidth: 0, + windowHeight: 0, + requestedAnimationFrame: false, + saveModifiers: (event) => { + GLUT.modifiers = 0; + if (event['shiftKey']) GLUT.modifiers += 1; /* GLUT_ACTIVE_SHIFT */ + if (event['ctrlKey']) GLUT.modifiers += 2; /* GLUT_ACTIVE_CTRL */ + if (event['altKey']) GLUT.modifiers += 4; /* GLUT_ACTIVE_ALT */ + }, + onMousemove: (event) => { + /* Send motion event only if the motion changed, prevents + * spamming our app with uncessary callback call. It does happen in + * Chrome on Windows. + */ + var lastX = Browser.mouseX; + var lastY = Browser.mouseY; + Browser.calculateMouseEvent(event); + var newX = Browser.mouseX; + var newY = Browser.mouseY; + if (newX == lastX && newY == lastY) return; + + if ( + GLUT.buttons == 0 && + event.target == Browser.getCanvas() && + GLUT.passiveMotionFunc + ) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + lastX, + lastY + ); + } else if (GLUT.buttons != 0 && GLUT.motionFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + lastX, + lastY + ); + } + }, + getSpecialKey: (keycode) => { + var key = null; + switch (keycode) { + case 8: + key = 120 /* backspace */; + break; + case 46: + key = 111 /* delete */; + break; + + case 0x70 /*DOM_VK_F1*/: + key = 1 /* GLUT_KEY_F1 */; + break; + case 0x71 /*DOM_VK_F2*/: + key = 2 /* GLUT_KEY_F2 */; + break; + case 0x72 /*DOM_VK_F3*/: + key = 3 /* GLUT_KEY_F3 */; + break; + case 0x73 /*DOM_VK_F4*/: + key = 4 /* GLUT_KEY_F4 */; + break; + case 0x74 /*DOM_VK_F5*/: + key = 5 /* GLUT_KEY_F5 */; + break; + case 0x75 /*DOM_VK_F6*/: + key = 6 /* GLUT_KEY_F6 */; + break; + case 0x76 /*DOM_VK_F7*/: + key = 7 /* GLUT_KEY_F7 */; + break; + case 0x77 /*DOM_VK_F8*/: + key = 8 /* GLUT_KEY_F8 */; + break; + case 0x78 /*DOM_VK_F9*/: + key = 9 /* GLUT_KEY_F9 */; + break; + case 0x79 /*DOM_VK_F10*/: + key = 10 /* GLUT_KEY_F10 */; + break; + case 0x7a /*DOM_VK_F11*/: + key = 11 /* GLUT_KEY_F11 */; + break; + case 0x7b /*DOM_VK_F12*/: + key = 12 /* GLUT_KEY_F12 */; + break; + case 0x25 /*DOM_VK_LEFT*/: + key = 100 /* GLUT_KEY_LEFT */; + break; + case 0x26 /*DOM_VK_UP*/: + key = 101 /* GLUT_KEY_UP */; + break; + case 0x27 /*DOM_VK_RIGHT*/: + key = 102 /* GLUT_KEY_RIGHT */; + break; + case 0x28 /*DOM_VK_DOWN*/: + key = 103 /* GLUT_KEY_DOWN */; + break; + case 0x21 /*DOM_VK_PAGE_UP*/: + key = 104 /* GLUT_KEY_PAGE_UP */; + break; + case 0x22 /*DOM_VK_PAGE_DOWN*/: + key = 105 /* GLUT_KEY_PAGE_DOWN */; + break; + case 0x24 /*DOM_VK_HOME*/: + key = 106 /* GLUT_KEY_HOME */; + break; + case 0x23 /*DOM_VK_END*/: + key = 107 /* GLUT_KEY_END */; + break; + case 0x2d /*DOM_VK_INSERT*/: + key = 108 /* GLUT_KEY_INSERT */; + break; + + case 16 /*DOM_VK_SHIFT*/: + case 0x05 /*DOM_VK_LEFT_SHIFT*/: + key = 112 /* GLUT_KEY_SHIFT_L */; + break; + case 0x06 /*DOM_VK_RIGHT_SHIFT*/: + key = 113 /* GLUT_KEY_SHIFT_R */; + break; + + case 17 /*DOM_VK_CONTROL*/: + case 0x03 /*DOM_VK_LEFT_CONTROL*/: + key = 114 /* GLUT_KEY_CONTROL_L */; + break; + case 0x04 /*DOM_VK_RIGHT_CONTROL*/: + key = 115 /* GLUT_KEY_CONTROL_R */; + break; + + case 18 /*DOM_VK_ALT*/: + case 0x02 /*DOM_VK_LEFT_ALT*/: + key = 116 /* GLUT_KEY_ALT_L */; + break; + case 0x01 /*DOM_VK_RIGHT_ALT*/: + key = 117 /* GLUT_KEY_ALT_R */; + break; + } + return key; + }, + getASCIIKey: (event) => { + if (event['ctrlKey'] || event['altKey'] || event['metaKey']) + return null; + + var keycode = event['keyCode']; + + /* The exact list is soooo hard to find in a canonical place! */ + + if (48 <= keycode && keycode <= 57) return keycode; // numeric TODO handle shift? + if (65 <= keycode && keycode <= 90) + return event['shiftKey'] ? keycode : keycode + 32; + if (96 <= keycode && keycode <= 105) return keycode - 48; // numpad numbers + if (106 <= keycode && keycode <= 111) return keycode - 106 + 42; // *,+-./ TODO handle shift? + + switch (keycode) { + case 9: // tab key + case 13: // return key + case 27: // escape + case 32: // space + case 61: // equal + return keycode; + } + + var s = event['shiftKey']; + switch (keycode) { + case 186: + return s ? 58 : 59; // colon / semi-colon + case 187: + return s ? 43 : 61; // add / equal (these two may be wrong) + case 188: + return s ? 60 : 44; // less-than / comma + case 189: + return s ? 95 : 45; // dash + case 190: + return s ? 62 : 46; // greater-than / period + case 191: + return s ? 63 : 47; // forward slash + case 219: + return s ? 123 : 91; // open bracket + case 220: + return s ? 124 : 47; // back slash + case 221: + return s ? 125 : 93; // close bracket + case 222: + return s ? 34 : 39; // single quote + } + + return null; + }, + onKeydown: (event) => { + if (GLUT.specialFunc || GLUT.keyboardFunc) { + var key = GLUT.getSpecialKey(event['keyCode']); + if (key !== null) { + if (GLUT.specialFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + key, + Browser.mouseX, + Browser.mouseY + ); + } + } else { + key = GLUT.getASCIIKey(event); + if (key !== null && GLUT.keyboardFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + key, + Browser.mouseX, + Browser.mouseY + ); + } + } + } + }, + onKeyup: (event) => { + if (GLUT.specialUpFunc || GLUT.keyboardUpFunc) { + var key = GLUT.getSpecialKey(event['keyCode']); + if (key !== null) { + if (GLUT.specialUpFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + key, + Browser.mouseX, + Browser.mouseY + ); + } + } else { + key = GLUT.getASCIIKey(event); + if (key !== null && GLUT.keyboardUpFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + key, + Browser.mouseX, + Browser.mouseY + ); + } + } + } + }, + touchHandler: (event) => { + if (event.target != Browser.getCanvas()) { + return; + } + + var touches = event.changedTouches, + main = touches[0], + type = ''; + + switch (event.type) { + case 'touchstart': + type = 'mousedown'; + break; + case 'touchmove': + type = 'mousemove'; + break; + case 'touchend': + type = 'mouseup'; + break; + default: + return; + } + + var simulatedEvent = document.createEvent('MouseEvent'); + simulatedEvent.initMouseEvent( + type, + true, + true, + window, + 1, + main.screenX, + main.screenY, + main.clientX, + main.clientY, + false, + false, + false, + false, + 0 /*main*/, + null + ); + + main.target.dispatchEvent(simulatedEvent); + event.preventDefault(); + }, + onMouseButtonDown: (event) => { + Browser.calculateMouseEvent(event); + + GLUT.buttons |= 1 << event['button']; + + if (event.target == Browser.getCanvas() && GLUT.mouseFunc) { + try { + event.target.setCapture(); + } catch (e) {} + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2, + a3, + a4 + ) => {}) /* a dynamic function call to signature viiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + event['button'], + 0 /*GLUT_DOWN*/, + Browser.mouseX, + Browser.mouseY + ); + } + }, + onMouseButtonUp: (event) => { + Browser.calculateMouseEvent(event); + + GLUT.buttons &= ~(1 << event['button']); + + if (GLUT.mouseFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2, + a3, + a4 + ) => {}) /* a dynamic function call to signature viiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + event['button'], + 1 /*GLUT_UP*/, + Browser.mouseX, + Browser.mouseY + ); + } + }, + onMouseWheel: (event) => { + Browser.calculateMouseEvent(event); + + // cross-browser wheel delta + var e = window.event || event; // old IE support + // Note the minus sign that flips browser wheel direction (positive direction scrolls page down) to native wheel direction (positive direction is mouse wheel up) + var delta = -Browser.getMouseWheelDelta(event); + delta = + delta == 0 + ? 0 + : delta > 0 + ? Math.max(delta, 1) + : Math.min(delta, -1); // Quantize to integer so that minimum scroll is at least +/- 1. + + var button = 3; // wheel up + if (delta < 0) { + button = 4; // wheel down + } + + if (GLUT.mouseFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + (( + a1, + a2, + a3, + a4 + ) => {}) /* a dynamic function call to signature viiii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + button, + 0 /*GLUT_DOWN*/, + Browser.mouseX, + Browser.mouseY + ); + } + }, + onFullscreenEventChange: (event) => { + var width; + var height; + if ( + document['fullscreen'] || + document['fullScreen'] || + document['mozFullScreen'] || + document['webkitIsFullScreen'] + ) { + width = screen['width']; + height = screen['height']; + } else { + width = GLUT.windowWidth; + height = GLUT.windowHeight; + // TODO set position + document.removeEventListener( + 'fullscreenchange', + GLUT.onFullscreenEventChange, + true + ); + document.removeEventListener( + 'mozfullscreenchange', + GLUT.onFullscreenEventChange, + true + ); + document.removeEventListener( + 'webkitfullscreenchange', + GLUT.onFullscreenEventChange, + true + ); + } + Browser.setCanvasSize(width, height, true); // N.B. GLUT.reshapeFunc is also registered as a canvas resize callback. + // Just call it once here. + /* Can't call _glutReshapeWindow as that requests cancelling fullscreen. */ + if (GLUT.reshapeFunc) { + // out("GLUT.reshapeFunc (from FS): " + width + ", " + height); + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + width, + height + ); + } + _glutPostRedisplay(); + }, + }; + + var _glutGetModifiers = () => GLUT.modifiers; + _glutGetModifiers.sig = 'i'; + + var _glutInit = (argcp, argv) => { + // Ignore arguments + GLUT.initTime = Date.now(); + + var isTouchDevice = 'ontouchstart' in document.documentElement; + if (isTouchDevice) { + // onMouseButtonDown, onMouseButtonUp and onMousemove handlers + // depend on Browser.mouseX / Browser.mouseY fields. Those fields + // don't get updated by touch events. So register a touchHandler + // function that translates the touch events to mouse events. + + // GLUT doesn't support touch, mouse only, so from touch events we + // are only looking at single finger touches to emulate left click, + // so we can use workaround and convert all touch events in mouse + // events. See touchHandler. + window.addEventListener('touchmove', GLUT.touchHandler, true); + window.addEventListener('touchstart', GLUT.touchHandler, true); + window.addEventListener('touchend', GLUT.touchHandler, true); + } + + window.addEventListener('keydown', GLUT.onKeydown, true); + window.addEventListener('keyup', GLUT.onKeyup, true); + window.addEventListener('mousemove', GLUT.onMousemove, true); + window.addEventListener('mousedown', GLUT.onMouseButtonDown, true); + window.addEventListener('mouseup', GLUT.onMouseButtonUp, true); + // IE9, Chrome, Safari, Opera + window.addEventListener('mousewheel', GLUT.onMouseWheel, true); + // Firefox + window.addEventListener('DOMMouseScroll', GLUT.onMouseWheel, true); + + Browser.resizeListeners.push((width, height) => { + if (GLUT.reshapeFunc) { + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + width, + height + ); + } + }); + + addOnExit(() => { + if (isTouchDevice) { + window.removeEventListener( + 'touchmove', + GLUT.touchHandler, + true + ); + window.removeEventListener( + 'touchstart', + GLUT.touchHandler, + true + ); + window.removeEventListener('touchend', GLUT.touchHandler, true); + } + + window.removeEventListener('keydown', GLUT.onKeydown, true); + window.removeEventListener('keyup', GLUT.onKeyup, true); + window.removeEventListener('mousemove', GLUT.onMousemove, true); + window.removeEventListener( + 'mousedown', + GLUT.onMouseButtonDown, + true + ); + window.removeEventListener('mouseup', GLUT.onMouseButtonUp, true); + // IE9, Chrome, Safari, Opera + window.removeEventListener('mousewheel', GLUT.onMouseWheel, true); + // Firefox + window.removeEventListener( + 'DOMMouseScroll', + GLUT.onMouseWheel, + true + ); + + var canvas = Browser.getCanvas(); + canvas.width = canvas.height = 1; + }); + }; + _glutInit.sig = 'vpp'; + + var _glutInitWindowSize = (width, height) => { + Browser.setCanvasSize( + (GLUT.initWindowWidth = width), + (GLUT.initWindowHeight = height) + ); + }; + _glutInitWindowSize.sig = 'vii'; + + var _glutInitWindowPosition = (x, y) => {}; + _glutInitWindowPosition.sig = 'vii'; + + var _glutGet = (type) => { + switch (type) { + case 100 /* GLUT_WINDOW_X */: + return 0; /* TODO */ + case 101 /* GLUT_WINDOW_Y */: + return 0; /* TODO */ + case 102 /* GLUT_WINDOW_WIDTH */: + return Browser.getCanvas().width; + case 103 /* GLUT_WINDOW_HEIGHT */: + return Browser.getCanvas().height; + case 200 /* GLUT_SCREEN_WIDTH */: + return Browser.getCanvas().width; + case 201 /* GLUT_SCREEN_HEIGHT */: + return Browser.getCanvas().height; + case 500 /* GLUT_INIT_WINDOW_X */: + return 0; /* TODO */ + case 501 /* GLUT_INIT_WINDOW_Y */: + return 0; /* TODO */ + case 502 /* GLUT_INIT_WINDOW_WIDTH */: + return GLUT.initWindowWidth; + case 503 /* GLUT_INIT_WINDOW_HEIGHT */: + return GLUT.initWindowHeight; + case 700 /* GLUT_ELAPSED_TIME */: + var now = Date.now(); + return now - GLUT.initTime; + case 0x0069 /* GLUT_WINDOW_STENCIL_SIZE */: + return GLctx.getContextAttributes().stencil ? 8 : 0; + case 0x006a /* GLUT_WINDOW_DEPTH_SIZE */: + return GLctx.getContextAttributes().depth ? 8 : 0; + case 0x006e /* GLUT_WINDOW_ALPHA_SIZE */: + return GLctx.getContextAttributes().alpha ? 8 : 0; + case 0x0078 /* GLUT_WINDOW_NUM_SAMPLES */: + return GLctx.getContextAttributes().antialias ? 1 : 0; + + default: + throw 'glutGet(' + type + ') not implemented yet'; + } + }; + _glutGet.sig = 'ii'; + + var _glutIdleFunc = (func) => { + function callback() { + if (GLUT.idleFunc) { + (() => {})(/* a dynamic function call to signature v, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */); + safeSetTimeout(callback, 4); // HTML spec specifies a 4ms minimum delay on the main thread; workers might get more, but we standardize here + } + } + if (!GLUT.idleFunc) { + safeSetTimeout(callback, 0); + } + GLUT.idleFunc = func; + }; + _glutIdleFunc.sig = 'vp'; + + var _glutTimerFunc = (msec, func, value) => + safeSetTimeout( + () => + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + value + ), + msec + ); + _glutTimerFunc.sig = 'vipi'; + + var _glutDisplayFunc = (func) => { + GLUT.displayFunc = func; + }; + _glutDisplayFunc.sig = 'vp'; + + var _glutKeyboardFunc = (func) => { + GLUT.keyboardFunc = func; + }; + _glutKeyboardFunc.sig = 'vp'; + + var _glutKeyboardUpFunc = (func) => { + GLUT.keyboardUpFunc = func; + }; + _glutKeyboardUpFunc.sig = 'vp'; + + var _glutSpecialFunc = (func) => { + GLUT.specialFunc = func; + }; + _glutSpecialFunc.sig = 'vp'; + + var _glutSpecialUpFunc = (func) => { + GLUT.specialUpFunc = func; + }; + _glutSpecialUpFunc.sig = 'vp'; + + var _glutReshapeFunc = (func) => { + GLUT.reshapeFunc = func; + }; + _glutReshapeFunc.sig = 'vp'; + + var _glutMotionFunc = (func) => { + GLUT.motionFunc = func; + }; + _glutMotionFunc.sig = 'vp'; + + var _glutPassiveMotionFunc = (func) => { + GLUT.passiveMotionFunc = func; + }; + _glutPassiveMotionFunc.sig = 'vp'; + + var _glutMouseFunc = (func) => { + GLUT.mouseFunc = func; + }; + _glutMouseFunc.sig = 'vp'; + + var _glutSetCursor = (cursor) => { + var cursorStyle = 'auto'; + switch (cursor) { + case 0x0000 /* GLUT_CURSOR_RIGHT_ARROW */: + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0001 /* GLUT_CURSOR_LEFT_ARROW */: + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0002 /* GLUT_CURSOR_INFO */: + cursorStyle = 'pointer'; + break; + case 0x0003 /* GLUT_CURSOR_DESTROY */: + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0004 /* GLUT_CURSOR_HELP */: + cursorStyle = 'help'; + break; + case 0x0005 /* GLUT_CURSOR_CYCLE */: + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0006 /* GLUT_CURSOR_SPRAY */: + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0007 /* GLUT_CURSOR_WAIT */: + cursorStyle = 'wait'; + break; + case 0x0008 /* GLUT_CURSOR_TEXT */: + cursorStyle = 'text'; + break; + case 0x0009: /* GLUT_CURSOR_CROSSHAIR */ + case 0x0066 /* GLUT_CURSOR_FULL_CROSSHAIR */: + cursorStyle = 'crosshair'; + break; + case 0x000a /* GLUT_CURSOR_UP_DOWN */: + cursorStyle = 'ns-resize'; + break; + case 0x000b /* GLUT_CURSOR_LEFT_RIGHT */: + cursorStyle = 'ew-resize'; + break; + case 0x000c /* GLUT_CURSOR_TOP_SIDE */: + cursorStyle = 'n-resize'; + break; + case 0x000d /* GLUT_CURSOR_BOTTOM_SIDE */: + cursorStyle = 's-resize'; + break; + case 0x000e /* GLUT_CURSOR_LEFT_SIDE */: + cursorStyle = 'w-resize'; + break; + case 0x000f /* GLUT_CURSOR_RIGHT_SIDE */: + cursorStyle = 'e-resize'; + break; + case 0x0010 /* GLUT_CURSOR_TOP_LEFT_CORNER */: + cursorStyle = 'nw-resize'; + break; + case 0x0011 /* GLUT_CURSOR_TOP_RIGHT_CORNER */: + cursorStyle = 'ne-resize'; + break; + case 0x0012 /* GLUT_CURSOR_BOTTOM_RIGHT_CORNER */: + cursorStyle = 'se-resize'; + break; + case 0x0013 /* GLUT_CURSOR_BOTTOM_LEFT_CORNER */: + cursorStyle = 'sw-resize'; + break; + case 0x0064 /* GLUT_CURSOR_INHERIT */: + break; + case 0x0065 /* GLUT_CURSOR_NONE */: + cursorStyle = 'none'; + break; + default: + throw 'glutSetCursor: Unknown cursor type: ' + cursor; + } + Browser.getCanvas().style.cursor = cursorStyle; + }; + _glutSetCursor.sig = 'vi'; + + var _glutCreateWindow = (name) => { + var contextAttributes = { + antialias: + (GLUT.initDisplayMode & 0x0080) /*GLUT_MULTISAMPLE*/ != 0, + depth: (GLUT.initDisplayMode & 0x0010) /*GLUT_DEPTH*/ != 0, + stencil: (GLUT.initDisplayMode & 0x0020) /*GLUT_STENCIL*/ != 0, + alpha: (GLUT.initDisplayMode & 0x0008) /*GLUT_ALPHA*/ != 0, + }; + if ( + !Browser.createContext( + Browser.getCanvas(), + /*useWebGL=*/ true, + /*setInModule=*/ true, + contextAttributes + ) + ) { + return 0; // failure + } + return 1; // a new GLUT window ID for the created context + }; + _glutCreateWindow.sig = 'ip'; + + var _glutDestroyWindow = (name) => { + delete Module['ctx']; + return 1; + }; + _glutDestroyWindow.sig = 'vi'; + + var _glutReshapeWindow = (width, height) => { + Browser.exitFullscreen(); + Browser.setCanvasSize(width, height, true); // N.B. GLUT.reshapeFunc is also registered as a canvas resize callback. + // Just call it once here. + if (GLUT.reshapeFunc) { + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + width, + height + ); + } + _glutPostRedisplay(); + }; + _glutReshapeWindow.sig = 'vii'; + + var _glutPositionWindow = (x, y) => { + Browser.exitFullscreen(); + /* TODO */ + _glutPostRedisplay(); + }; + _glutPositionWindow.sig = 'vii'; + + var _glutFullScreen = () => { + GLUT.windowX = 0; // TODO + GLUT.windowY = 0; // TODO + var canvas = Browser.getCanvas(); + GLUT.windowWidth = canvas.width; + GLUT.windowHeight = canvas.height; + document.addEventListener( + 'fullscreenchange', + GLUT.onFullscreenEventChange, + true + ); + document.addEventListener( + 'mozfullscreenchange', + GLUT.onFullscreenEventChange, + true + ); + document.addEventListener( + 'webkitfullscreenchange', + GLUT.onFullscreenEventChange, + true + ); + Browser.requestFullscreen( + /*lockPointer=*/ false, + /*resizeCanvas=*/ false + ); + }; + _glutFullScreen.sig = 'v'; + + var _glutInitDisplayMode = (mode) => (GLUT.initDisplayMode = mode); + _glutInitDisplayMode.sig = 'vi'; + + var _glutSwapBuffers = () => {}; + _glutSwapBuffers.sig = 'v'; + + var _glutMainLoop = () => { + var canvas = Browser.getCanvas(); + _glutReshapeWindow(canvas.width, canvas.height); + _glutPostRedisplay(); + throw 'unwind'; + }; + _glutMainLoop.sig = 'v'; + + var _XOpenDisplay = (name) => 1; + _XOpenDisplay.sig = 'pp'; + + var _XCreateWindow = ( + display, + parent, + x, + y, + width, + height, + border_width, + depth, + class_, + visual, + valuemask, + attributes + ) => { + // All we can do is set the width and height + Browser.setCanvasSize(width, height); + return 2; + }; + _XCreateWindow.sig = 'pppiiiiiiippp'; + + var _XChangeWindowAttributes = ( + display, + window, + valuemask, + attributes + ) => {}; + _XChangeWindowAttributes.sig = 'ipppp'; + + var _XSetWMHints = (display, win, hints) => {}; + _XSetWMHints.sig = 'ippp'; + + var _XMapWindow = (display, win) => {}; + _XMapWindow.sig = 'ipp'; + + var _XStoreName = (display, win, name) => {}; + _XStoreName.sig = 'ippp'; + + var _XInternAtom = (display, name_, hmm) => 0; + _XInternAtom.sig = 'pppi'; + + var _XSendEvent = (display, win, propagate, event_mask, even_send) => {}; + _XSendEvent.sig = 'ippipp'; + + var _XPending = (display) => 0; + _XPending.sig = 'ip'; + + var EGL = { + errorCode: 12288, + defaultDisplayInitialized: false, + currentContext: 0, + currentReadSurface: 0, + currentDrawSurface: 0, + contextAttributes: { + alpha: false, + depth: false, + stencil: false, + antialias: false, + }, + stringCache: {}, + setErrorCode(code) { + EGL.errorCode = code; + }, + chooseConfig(display, attribList, config, config_size, numConfigs) { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + + if (attribList) { + // read attribList if it is non-null + for (;;) { + var param = HEAP32[attribList >> 2]; + if (param == 0x3021 /*EGL_ALPHA_SIZE*/) { + var alphaSize = HEAP32[(attribList + 4) >> 2]; + EGL.contextAttributes.alpha = alphaSize > 0; + } else if (param == 0x3025 /*EGL_DEPTH_SIZE*/) { + var depthSize = HEAP32[(attribList + 4) >> 2]; + EGL.contextAttributes.depth = depthSize > 0; + } else if (param == 0x3026 /*EGL_STENCIL_SIZE*/) { + var stencilSize = HEAP32[(attribList + 4) >> 2]; + EGL.contextAttributes.stencil = stencilSize > 0; + } else if (param == 0x3031 /*EGL_SAMPLES*/) { + var samples = HEAP32[(attribList + 4) >> 2]; + EGL.contextAttributes.antialias = samples > 0; + } else if (param == 0x3032 /*EGL_SAMPLE_BUFFERS*/) { + var samples = HEAP32[(attribList + 4) >> 2]; + EGL.contextAttributes.antialias = samples == 1; + } else if ( + param == 0x3100 /*EGL_CONTEXT_PRIORITY_LEVEL_IMG*/ + ) { + var requestedPriority = HEAP32[(attribList + 4) >> 2]; + EGL.contextAttributes.lowLatency = + requestedPriority != + 0x3103 /*EGL_CONTEXT_PRIORITY_LOW_IMG*/; + } else if (param == 0x3038 /*EGL_NONE*/) { + break; + } + attribList += 8; + } + } + + if ((!config || !config_size) && !numConfigs) { + EGL.setErrorCode(0x300c /* EGL_BAD_PARAMETER */); + return 0; + } + if (numConfigs) { + HEAP32[numConfigs >> 2] = 1; // Total number of supported configs: 1. + } + if (config && config_size > 0) { + HEAPU32[config >> 2] = 62002; + } + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + }; + + var _eglGetDisplay = (nativeDisplayType) => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + // Emscripten EGL implementation "emulates" X11, and eglGetDisplay is + // expected to accept/receive a pointer to an X11 Display object (or + // EGL_DEFAULT_DISPLAY). + if ( + nativeDisplayType != 0 /* EGL_DEFAULT_DISPLAY */ && + nativeDisplayType != 1 /* see library_xlib.js */ + ) { + return 0; // EGL_NO_DISPLAY + } + return 62000; + }; + _eglGetDisplay.sig = 'pp'; + + var _eglInitialize = (display, majorVersion, minorVersion) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (majorVersion) { + HEAP32[majorVersion >> 2] = 1; // Advertise EGL Major version: '1' + } + if (minorVersion) { + HEAP32[minorVersion >> 2] = 4; // Advertise EGL Minor version: '4' + } + EGL.defaultDisplayInitialized = true; + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }; + _eglInitialize.sig = 'ippp'; + + var _eglTerminate = (display) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + EGL.currentContext = 0; + EGL.currentReadSurface = 0; + EGL.currentDrawSurface = 0; + EGL.defaultDisplayInitialized = false; + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }; + _eglTerminate.sig = 'ip'; + + var _eglGetConfigs = (display, configs, config_size, numConfigs) => + EGL.chooseConfig(display, 0, configs, config_size, numConfigs); + _eglGetConfigs.sig = 'ippip'; + + var _eglChooseConfig = ( + display, + attrib_list, + configs, + config_size, + numConfigs + ) => + EGL.chooseConfig( + display, + attrib_list, + configs, + config_size, + numConfigs + ); + _eglChooseConfig.sig = 'ipppip'; + + var _eglGetConfigAttrib = (display, config, attribute, value) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (config != 62002) { + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300c /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch (attribute) { + case 0x3020: // EGL_BUFFER_SIZE + HEAP32[value >> 2] = EGL.contextAttributes.alpha ? 32 : 24; + return 1; + case 0x3021: // EGL_ALPHA_SIZE + HEAP32[value >> 2] = EGL.contextAttributes.alpha ? 8 : 0; + return 1; + case 0x3022: // EGL_BLUE_SIZE + HEAP32[value >> 2] = 8; + return 1; + case 0x3023: // EGL_GREEN_SIZE + HEAP32[value >> 2] = 8; + return 1; + case 0x3024: // EGL_RED_SIZE + HEAP32[value >> 2] = 8; + return 1; + case 0x3025: // EGL_DEPTH_SIZE + HEAP32[value >> 2] = EGL.contextAttributes.depth ? 24 : 0; + return 1; + case 0x3026: // EGL_STENCIL_SIZE + HEAP32[value >> 2] = EGL.contextAttributes.stencil ? 8 : 0; + return 1; + case 0x3027: // EGL_CONFIG_CAVEAT + // We can return here one of EGL_NONE (0x3038), EGL_SLOW_CONFIG (0x3050) or EGL_NON_CONFORMANT_CONFIG (0x3051). + HEAP32[value >> 2] = 0x3038; + return 1; + case 0x3028: // EGL_CONFIG_ID + HEAP32[value >> 2] = 62002; + return 1; + case 0x3029: // EGL_LEVEL + HEAP32[value >> 2] = 0; + return 1; + case 0x302a: // EGL_MAX_PBUFFER_HEIGHT + HEAP32[value >> 2] = 4096; + return 1; + case 0x302b: // EGL_MAX_PBUFFER_PIXELS + HEAP32[value >> 2] = 16777216; + return 1; + case 0x302c: // EGL_MAX_PBUFFER_WIDTH + HEAP32[value >> 2] = 4096; + return 1; + case 0x302d: // EGL_NATIVE_RENDERABLE + HEAP32[value >> 2] = 0; + return 1; + case 0x302e: // EGL_NATIVE_VISUAL_ID + HEAP32[value >> 2] = 0; + return 1; + case 0x302f: // EGL_NATIVE_VISUAL_TYPE + HEAP32[value >> 2] = 0x3038; + return 1; + case 0x3031: // EGL_SAMPLES + HEAP32[value >> 2] = EGL.contextAttributes.antialias ? 4 : 0; + return 1; + case 0x3032: // EGL_SAMPLE_BUFFERS + HEAP32[value >> 2] = EGL.contextAttributes.antialias ? 1 : 0; + return 1; + case 0x3033: // EGL_SURFACE_TYPE + HEAP32[value >> 2] = 0x4; + return 1; + case 0x3034: // EGL_TRANSPARENT_TYPE + // If this returns EGL_TRANSPARENT_RGB (0x3052), transparency is used through color-keying. No such thing applies to Emscripten canvas. + HEAP32[value >> 2] = 0x3038; + return 1; + case 0x3035: // EGL_TRANSPARENT_BLUE_VALUE + case 0x3036: // EGL_TRANSPARENT_GREEN_VALUE + case 0x3037: // EGL_TRANSPARENT_RED_VALUE + // "If EGL_TRANSPARENT_TYPE is EGL_NONE, then the values for EGL_TRANSPARENT_RED_VALUE, EGL_TRANSPARENT_GREEN_VALUE, and EGL_TRANSPARENT_BLUE_VALUE are undefined." + HEAP32[value >> 2] = -1; + return 1; + case 0x3039: // EGL_BIND_TO_TEXTURE_RGB + case 0x303a: // EGL_BIND_TO_TEXTURE_RGBA + HEAP32[value >> 2] = 0; + return 1; + case 0x303b: // EGL_MIN_SWAP_INTERVAL + HEAP32[value >> 2] = 0; + return 1; + case 0x303c: // EGL_MAX_SWAP_INTERVAL + HEAP32[value >> 2] = 1; + return 1; + case 0x303d: // EGL_LUMINANCE_SIZE + case 0x303e: // EGL_ALPHA_MASK_SIZE + HEAP32[value >> 2] = 0; + return 1; + case 0x303f: // EGL_COLOR_BUFFER_TYPE + // EGL has two types of buffers: EGL_RGB_BUFFER and EGL_LUMINANCE_BUFFER. + HEAP32[value >> 2] = 0x308e; + return 1; + case 0x3040: // EGL_RENDERABLE_TYPE + // A bit combination of EGL_OPENGL_ES_BIT,EGL_OPENVG_BIT,EGL_OPENGL_ES2_BIT and EGL_OPENGL_BIT. + HEAP32[value >> 2] = 0x4; + return 1; + case 0x3042: // EGL_CONFORMANT + // "EGL_CONFORMANT is a mask indicating if a client API context created with respect to the corresponding EGLConfig will pass the required conformance tests for that API." + HEAP32[value >> 2] = 0; + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }; + _eglGetConfigAttrib.sig = 'ippip'; + + var _eglCreateWindowSurface = (display, config, win, attrib_list) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (config != 62002) { + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; + } + // TODO: Examine attrib_list! Parameters that can be present there are: + // - EGL_RENDER_BUFFER (must be EGL_BACK_BUFFER) + // - EGL_VG_COLORSPACE (can't be set) + // - EGL_VG_ALPHA_FORMAT (can't be set) + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 62006; /* Magic ID for Emscripten 'default surface' */ + }; + _eglCreateWindowSurface.sig = 'pppip'; + + var _eglDestroySurface = (display, surface) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if ( + surface != + 62006 /* Magic ID for the only EGLSurface supported by Emscripten */ + ) { + EGL.setErrorCode(0x300d /* EGL_BAD_SURFACE */); + return 1; + } + if (EGL.currentReadSurface == surface) { + EGL.currentReadSurface = 0; + } + if (EGL.currentDrawSurface == surface) { + EGL.currentDrawSurface = 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; /* Magic ID for Emscripten 'default surface' */ + }; + _eglDestroySurface.sig = 'ipp'; + + var _eglCreateContext = (display, config, hmm, contextAttribs) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + + // EGL 1.4 spec says default EGL_CONTEXT_CLIENT_VERSION is GLES1, but this is not supported by Emscripten. + // So user must pass EGL_CONTEXT_CLIENT_VERSION == 2 to initialize EGL. + var glesContextVersion = 1; + for (;;) { + var param = HEAP32[contextAttribs >> 2]; + if (param == 0x3098 /*EGL_CONTEXT_CLIENT_VERSION*/) { + glesContextVersion = HEAP32[(contextAttribs + 4) >> 2]; + } else if (param == 0x3038 /*EGL_NONE*/) { + break; + } else { + /* EGL1.4 specifies only EGL_CONTEXT_CLIENT_VERSION as supported attribute */ + EGL.setErrorCode(0x3004 /*EGL_BAD_ATTRIBUTE*/); + return 0; + } + contextAttribs += 8; + } + if (glesContextVersion != 2) { + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; /* EGL_NO_CONTEXT */ + } + + EGL.contextAttributes.majorVersion = glesContextVersion - 1; // WebGL 1 is GLES 2, WebGL2 is GLES3 + EGL.contextAttributes.minorVersion = 0; + + EGL.context = GL.createContext( + Browser.getCanvas(), + EGL.contextAttributes + ); + + if (EGL.context != 0) { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + + // Run callbacks so that GL emulation works + GL.makeContextCurrent(EGL.context); + Browser.useWebGL = true; + Browser.moduleContextCreatedCallbacks.forEach((callback) => + callback() + ); + + // Note: This function only creates a context, but it shall not make it active. + GL.makeContextCurrent(null); + return 62004; + } else { + EGL.setErrorCode(0x3009 /* EGL_BAD_MATCH */); // By the EGL 1.4 spec, an implementation that does not support GLES2 (WebGL in this case), this error code is set. + return 0; /* EGL_NO_CONTEXT */ + } + }; + _eglCreateContext.sig = 'ppppp'; + + var _eglDestroyContext = (display, context) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (context != 62004) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + + GL.deleteContext(EGL.context); + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + if (EGL.currentContext == context) { + EGL.currentContext = 0; + } + return 1 /* EGL_TRUE */; + }; + _eglDestroyContext.sig = 'ipp'; + + var _eglQuerySurface = (display, surface, attribute, value) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (surface != 62006 /* Magic ID for Emscripten 'default surface' */) { + EGL.setErrorCode(0x300d /* EGL_BAD_SURFACE */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300c /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch (attribute) { + case 0x3028: // EGL_CONFIG_ID + HEAP32[value >> 2] = 62002; + return 1; + case 0x3058: // EGL_LARGEST_PBUFFER + // Odd EGL API: If surface is not a pbuffer surface, 'value' should not be written to. It's not specified as an error, so true should(?) be returned. + // Existing Android implementation seems to do so at least. + return 1; + case 0x3057: // EGL_WIDTH + HEAP32[value >> 2] = Browser.getCanvas().width; + return 1; + case 0x3056: // EGL_HEIGHT + HEAP32[value >> 2] = Browser.getCanvas().height; + return 1; + case 0x3090: // EGL_HORIZONTAL_RESOLUTION + HEAP32[value >> 2] = -1; + return 1; + case 0x3091: // EGL_VERTICAL_RESOLUTION + HEAP32[value >> 2] = -1; + return 1; + case 0x3092: // EGL_PIXEL_ASPECT_RATIO + HEAP32[value >> 2] = -1; + return 1; + case 0x3086: // EGL_RENDER_BUFFER + // The main surface is bound to the visible canvas window - it's always backbuffered. + // Alternative to EGL_BACK_BUFFER would be EGL_SINGLE_BUFFER. + HEAP32[value >> 2] = 0x3084; + return 1; + case 0x3099: // EGL_MULTISAMPLE_RESOLVE + HEAP32[value >> 2] = 0x309a; + return 1; + case 0x3093: // EGL_SWAP_BEHAVIOR + // The two possibilities are EGL_BUFFER_PRESERVED and EGL_BUFFER_DESTROYED. Slightly unsure which is the + // case for browser environment, but advertise the 'weaker' behavior to be sure. + HEAP32[value >> 2] = 0x3095; + return 1; + case 0x3080: // EGL_TEXTURE_FORMAT + case 0x3081: // EGL_TEXTURE_TARGET + case 0x3082: // EGL_MIPMAP_TEXTURE + case 0x3083: // EGL_MIPMAP_LEVEL + // This is a window surface, not a pbuffer surface. Spec: + // "Querying EGL_TEXTURE_FORMAT, EGL_TEXTURE_TARGET, EGL_MIPMAP_TEXTURE, or EGL_MIPMAP_LEVEL for a non-pbuffer surface is not an error, but value is not modified." + // So pass-through. + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }; + _eglQuerySurface.sig = 'ippip'; + + var _eglQueryContext = (display, context, attribute, value) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + if (context != 62004) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300c /* EGL_BAD_PARAMETER */); + return 0; + } + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch (attribute) { + case 0x3028: // EGL_CONFIG_ID + HEAP32[value >> 2] = 62002; + return 1; + case 0x3097: // EGL_CONTEXT_CLIENT_TYPE + HEAP32[value >> 2] = 0x30a0; + return 1; + case 0x3098: // EGL_CONTEXT_CLIENT_VERSION + HEAP32[value >> 2] = EGL.contextAttributes.majorVersion + 1; + return 1; + case 0x3086: // EGL_RENDER_BUFFER + // The context is bound to the visible canvas window - it's always backbuffered. + // Alternative to EGL_BACK_BUFFER would be EGL_SINGLE_BUFFER. + HEAP32[value >> 2] = 0x3084; + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }; + _eglQueryContext.sig = 'ippip'; + + var _eglGetError = () => EGL.errorCode; + _eglGetError.sig = 'i'; + + var _eglQueryString = (display, name) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + if (EGL.stringCache[name]) return EGL.stringCache[name]; + var ret; + switch (name) { + case 0x3053 /* EGL_VENDOR */: + ret = stringToNewUTF8('Emscripten'); + break; + case 0x3054 /* EGL_VERSION */: + ret = stringToNewUTF8('1.4 Emscripten EGL'); + break; + case 0x3055 /* EGL_EXTENSIONS */: + ret = stringToNewUTF8(''); + break; // Currently not supporting any EGL extensions. + case 0x308d /* EGL_CLIENT_APIS */: + ret = stringToNewUTF8('OpenGL_ES'); + break; + default: + EGL.setErrorCode(0x300c /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.stringCache[name] = ret; + return ret; + }; + _eglQueryString.sig = 'ppi'; + + var _eglBindAPI = (api) => { + if (api == 0x30a0 /* EGL_OPENGL_ES_API */) { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + } + // if (api == 0x30A1 /* EGL_OPENVG_API */ || api == 0x30A2 /* EGL_OPENGL_API */) { + EGL.setErrorCode(0x300c /* EGL_BAD_PARAMETER */); + return 0; + }; + _eglBindAPI.sig = 'ii'; + + var _eglQueryAPI = () => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 0x30a0; // EGL_OPENGL_ES_API + }; + _eglQueryAPI.sig = 'i'; + + var _eglWaitClient = () => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }; + _eglWaitClient.sig = 'i'; + + var _eglWaitNative = (nativeEngineId) => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }; + _eglWaitNative.sig = 'ii'; + + var _eglWaitGL = _eglWaitClient; + _eglWaitGL.sig = 'i'; + + var _eglSwapInterval = (display, interval) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (interval == 0) _emscripten_set_main_loop_timing(0, 0); + else _emscripten_set_main_loop_timing(1, interval); + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }; + _eglSwapInterval.sig = 'ipi'; + + var _eglMakeCurrent = (display, draw, read, context) => { + if (display != 62000) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0 /* EGL_FALSE */; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + if (context != 0 && context != 62004) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + if ( + (read != 0 && read != 62006) || + (draw != 0 && + draw != 62006) /* Magic ID for Emscripten 'default surface' */ + ) { + EGL.setErrorCode(0x300d /* EGL_BAD_SURFACE */); + return 0; + } + + GL.makeContextCurrent(context ? EGL.context : null); + + EGL.currentContext = context; + EGL.currentDrawSurface = draw; + EGL.currentReadSurface = read; + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1 /* EGL_TRUE */; + }; + _eglMakeCurrent.sig = 'ipppp'; + + var _eglGetCurrentContext = () => EGL.currentContext; + _eglGetCurrentContext.sig = 'p'; + + var _eglGetCurrentSurface = (readdraw) => { + if (readdraw == 0x305a /* EGL_READ */) { + return EGL.currentReadSurface; + } else if (readdraw == 0x3059 /* EGL_DRAW */) { + return EGL.currentDrawSurface; + } else { + EGL.setErrorCode(0x300c /* EGL_BAD_PARAMETER */); + return 0 /* EGL_NO_SURFACE */; + } + }; + _eglGetCurrentSurface.sig = 'pi'; + + var _eglGetCurrentDisplay = () => (EGL.currentContext ? 62000 : 0); + _eglGetCurrentDisplay.sig = 'p'; + + var _eglSwapBuffers = (dpy, surface) => { + if (!EGL.defaultDisplayInitialized) { + EGL.setErrorCode(0x3001 /* EGL_NOT_INITIALIZED */); + } else if (!GLctx) { + EGL.setErrorCode(0x3002 /* EGL_BAD_ACCESS */); + } else if (GLctx.isContextLost()) { + EGL.setErrorCode(0x300e /* EGL_CONTEXT_LOST */); + } else { + // According to documentation this does an implicit flush. + // Due to discussion at https://github.com/emscripten-core/emscripten/pull/1871 + // the flush was removed since this _may_ result in slowing code down. + //_glFlush(); + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1 /* EGL_TRUE */; + } + return 0 /* EGL_FALSE */; + }; + _eglSwapBuffers.sig = 'ipp'; + + var _eglReleaseThread = () => { + // Equivalent to eglMakeCurrent with EGL_NO_CONTEXT and EGL_NO_SURFACE. + EGL.currentContext = 0; + EGL.currentReadSurface = 0; + EGL.currentDrawSurface = 0; + // EGL spec v1.4 p.55: + // "calling eglGetError immediately following a successful call to eglReleaseThread should not be done. + // Such a call will return EGL_SUCCESS - but will also result in reallocating per-thread state." + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1 /* EGL_TRUE */; + }; + _eglReleaseThread.sig = 'i'; + + var _uuid_clear = (uu) => zeroMemory(uu, 16); + _uuid_clear.sig = 'vp'; + + var _uuid_compare = (uu1, uu2) => _memcmp(uu1, uu2, 16); + _uuid_compare.sig = 'ipp'; + + var _uuid_copy = (dst, src) => _memcpy(dst, src, 16); + _uuid_copy.sig = 'vpp'; + + var _uuid_generate = (out) => { + // void uuid_generate(uuid_t out); + var uuid = new Uint8Array(16); + randomFill(uuid); + + // Makes uuid compliant to RFC-4122 + uuid[6] = (uuid[6] & 0x0f) | 0x40; // uuid version + uuid[8] = (uuid[8] & 0x3f) | 0x80; // uuid variant + writeArrayToMemory(uuid, out); + }; + _uuid_generate.sig = 'vp'; + + var _uuid_is_null = (uu) => { + // int uuid_is_null(const uuid_t uu); + for (var i = 0; i < 4; i++, uu = (uu + 4) | 0) { + var val = HEAP32[uu >> 2]; + if (val) { + return 0; + } + } + return 1; + }; + _uuid_is_null.sig = 'ip'; + + var _uuid_parse = (inp, uu) => { + // int uuid_parse(const char *in, uuid_t uu); + inp = UTF8ToString(inp); + if (inp.length === 36) { + var i = 0; + var uuid = new Array(16); + inp.toLowerCase().replace(/[0-9a-f]{2}/g, function (byte) { + if (i < 16) { + uuid[i++] = parseInt(byte, 16); + } + }); + + if (i < 16) { + return -1; + } + writeArrayToMemory(uuid, uu); + return 0; + } + return -1; + }; + _uuid_parse.sig = 'ipp'; + + /** @param {number|boolean=} upper */ + var _uuid_unparse = (uu, out, upper) => { + // void uuid_unparse(const uuid_t uu, char *out); + var i = 0; + var uuid = 'xxxx-xx-xx-xx-xxxxxx'.replace(/[x]/g, function (c) { + var r = upper + ? HEAPU8[uu + i].toString(16).toUpperCase() + : HEAPU8[uu + i].toString(16); + r = r.length === 1 ? '0' + r : r; // Zero pad single digit hex values + i++; + return r; + }); + stringToUTF8(uuid, out, 37); // Always fixed 36 bytes of ASCII characters and a trailing \0. + }; + _uuid_unparse.sig = 'vpp'; + + var _uuid_unparse_lower = (uu, out) => { + // void uuid_unparse_lower(const uuid_t uu, char *out); + _uuid_unparse(uu, out); + }; + _uuid_unparse_lower.sig = 'vpp'; + + var _uuid_unparse_upper = (uu, out) => { + // void uuid_unparse_upper(const uuid_t uu, char *out); + _uuid_unparse(uu, out, true); + }; + _uuid_unparse_upper.sig = 'vpp'; + + var _uuid_type = (uu) => 4; + _uuid_type.sig = 'ip'; + + var _uuid_variant = (uu) => 1; + _uuid_variant.sig = 'ip'; + + var GLEW = { + isLinaroFork: 1, + extensions: null, + error: { + 0: null, + 1: null, + 2: null, + 3: null, + 4: null, + 5: null, + 6: null, + 7: null, + 8: null, + }, + version: { + 1: null, + 2: null, + 3: null, + 4: null, + }, + errorStringConstantFromCode(error) { + if (GLEW.isLinaroFork) { + switch (error) { + case 4: + return 'OpenGL ES lib expected, found OpenGL lib'; // GLEW_ERROR_NOT_GLES_VERSION + case 5: + return 'OpenGL lib expected, found OpenGL ES lib'; // GLEW_ERROR_GLES_VERSION + case 6: + return 'Missing EGL version'; // GLEW_ERROR_NO_EGL_VERSION + case 7: + return 'EGL 1.1 and up are supported'; // GLEW_ERROR_EGL_VERSION_10_ONLY + default: + break; + } + } + + switch (error) { + case 0: + return 'No error'; // GLEW_OK || GLEW_NO_ERROR + case 1: + return 'Missing GL version'; // GLEW_ERROR_NO_GL_VERSION + case 2: + return 'GL 1.1 and up are supported'; // GLEW_ERROR_GL_VERSION_10_ONLY + case 3: + return 'GLX 1.2 and up are supported'; // GLEW_ERROR_GLX_VERSION_11_ONLY + default: + return null; + } + }, + errorString(error) { + if (!GLEW.error[error]) { + var string = GLEW.errorStringConstantFromCode(error); + if (!string) { + string = 'Unknown error'; + error = 8; // prevent array from growing more than this + } + GLEW.error[error] = stringToNewUTF8(string); + } + return GLEW.error[error]; + }, + versionStringConstantFromCode(name) { + switch (name) { + case 1: + return '1.10.0'; // GLEW_VERSION + case 2: + return '1'; // GLEW_VERSION_MAJOR + case 3: + return '10'; // GLEW_VERSION_MINOR + case 4: + return '0'; // GLEW_VERSION_MICRO + default: + return null; + } + }, + versionString(name) { + if (!GLEW.version[name]) { + var string = GLEW.versionStringConstantFromCode(name); + if (!string) return 0; + GLEW.version[name] = stringToNewUTF8(string); + } + return GLEW.version[name]; + }, + extensionIsSupported(name) { + GLEW.extensions ||= webglGetExtensions(); + + if (GLEW.extensions.includes(name)) return 1; + + // extensions from GLEmulations do not come unprefixed + // so, try with prefix + return GLEW.extensions.includes('GL_' + name); + }, + }; + + var _glewInit = () => 0; + _glewInit.sig = 'i'; + + var _glewIsSupported = (name) => { + var exts = UTF8ToString(name).split(' '); + for (var ext of exts) { + if (!GLEW.extensionIsSupported(ext)) return 0; + } + return 1; + }; + _glewIsSupported.sig = 'ip'; + + var _glewGetExtension = (name) => + GLEW.extensionIsSupported(UTF8ToString(name)); + _glewGetExtension.sig = 'ip'; + + var _glewGetErrorString = (error) => GLEW.errorString(error); + _glewGetErrorString.sig = 'pi'; + + var _glewGetString = (name) => GLEW.versionString(name); + _glewGetString.sig = 'pi'; + + var IDBStore = { + indexedDB() { + if (typeof indexedDB != 'undefined') return indexedDB; + var ret = null; + if (typeof window == 'object') + ret = + window.indexedDB || + window.mozIndexedDB || + window.webkitIndexedDB || + window.msIndexedDB; + return ret; + }, + DB_VERSION: 22, + DB_STORE_NAME: 'FILE_DATA', + dbs: {}, + blobs: [0], + getDB(name, callback) { + // check the cache first + var db = IDBStore.dbs[name]; + if (db) { + return callback(null, db); + } + var req; + try { + req = IDBStore.indexedDB().open(name, IDBStore.DB_VERSION); + } catch (e) { + return callback(e); + } + req.onupgradeneeded = (e) => { + var db = /** @type {IDBDatabase} */ (e.target.result); + var transaction = e.target.transaction; + var fileStore; + if (db.objectStoreNames.contains(IDBStore.DB_STORE_NAME)) { + fileStore = transaction.objectStore(IDBStore.DB_STORE_NAME); + } else { + fileStore = db.createObjectStore(IDBStore.DB_STORE_NAME); + } + }; + req.onsuccess = () => { + db = /** @type {IDBDatabase} */ (req.result); + // add to the cache + IDBStore.dbs[name] = db; + callback(null, db); + }; + req.onerror = function (event) { + callback(event.target.error || 'unknown error'); + event.preventDefault(); + }; + }, + getStore(dbName, type, callback) { + IDBStore.getDB(dbName, (error, db) => { + if (error) return callback(error); + var transaction = db.transaction( + [IDBStore.DB_STORE_NAME], + type + ); + transaction.onerror = (event) => { + callback(event.target.error || 'unknown error'); + event.preventDefault(); + }; + var store = transaction.objectStore(IDBStore.DB_STORE_NAME); + callback(null, store); + }); + }, + getFile(dbName, id, callback) { + IDBStore.getStore(dbName, 'readonly', (err, store) => { + if (err) return callback(err); + var req = store.get(id); + req.onsuccess = (event) => { + var result = event.target.result; + if (!result) { + return callback(`file ${id} not found`); + } + return callback(null, result); + }; + req.onerror = callback; + }); + }, + setFile(dbName, id, data, callback) { + IDBStore.getStore(dbName, 'readwrite', (err, store) => { + if (err) return callback(err); + var req = store.put(data, id); + req.onsuccess = (event) => callback(); + req.onerror = callback; + }); + }, + deleteFile(dbName, id, callback) { + IDBStore.getStore(dbName, 'readwrite', (err, store) => { + if (err) return callback(err); + var req = store.delete(id); + req.onsuccess = (event) => callback(); + req.onerror = callback; + }); + }, + existsFile(dbName, id, callback) { + IDBStore.getStore(dbName, 'readonly', (err, store) => { + if (err) return callback(err); + var req = store.count(id); + req.onsuccess = (event) => + callback(null, event.target.result > 0); + req.onerror = callback; + }); + }, + clearStore(dbName, callback) { + IDBStore.getStore(dbName, 'readwrite', (err, store) => { + if (err) return callback(err); + var req = store.clear(); + req.onsuccess = (event) => callback(); + req.onerror = callback; + }); + }, + }; + + var _emscripten_idb_async_load = (db, id, arg, onload, onerror) => { + runtimeKeepalivePush(); + IDBStore.getFile( + UTF8ToString(db), + UTF8ToString(id), + (error, byteArray) => { + runtimeKeepalivePop(); + callUserCallback(() => { + if (error) { + if (onerror) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + return; + } + var buffer = _malloc(byteArray.length); + HEAPU8.set(byteArray, buffer); + (( + a1, + a2, + a3 + ) => {}) /* a dynamic function call to signature viii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg, + buffer, + byteArray.length + ); + _free(buffer); + }); + } + ); + }; + _emscripten_idb_async_load.sig = 'vppppp'; + + var _emscripten_idb_async_store = ( + db, + id, + ptr, + num, + arg, + onstore, + onerror + ) => { + // note that we copy the data here, as these are async operatins - changes + // to HEAPU8 meanwhile should not affect us! + runtimeKeepalivePush(); + IDBStore.setFile( + UTF8ToString(db), + UTF8ToString(id), + new Uint8Array(HEAPU8.subarray(ptr, ptr + num)), + (error) => { + runtimeKeepalivePop(); + callUserCallback(() => { + if (error) { + if (onerror) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + return; + } + if (onstore) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + }); + } + ); + }; + _emscripten_idb_async_store.sig = 'vpppippp'; + + var _emscripten_idb_async_delete = (db, id, arg, ondelete, onerror) => { + runtimeKeepalivePush(); + IDBStore.deleteFile(UTF8ToString(db), UTF8ToString(id), (error) => { + runtimeKeepalivePop(); + callUserCallback(() => { + if (error) { + if (onerror) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + return; + } + if (ondelete) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + }); + }); + }; + _emscripten_idb_async_delete.sig = 'vppppp'; + + var _emscripten_idb_async_exists = (db, id, arg, oncheck, onerror) => { + runtimeKeepalivePush(); + IDBStore.existsFile( + UTF8ToString(db), + UTF8ToString(id), + (error, exists) => { + runtimeKeepalivePop(); + callUserCallback(() => { + if (error) { + if (onerror) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + return; + } + if (oncheck) + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg, + exists + ); + }); + } + ); + }; + _emscripten_idb_async_exists.sig = 'vppppp'; + + var _emscripten_idb_async_clear = (db, arg, onclear, onerror) => { + runtimeKeepalivePush(); + IDBStore.clearStore(UTF8ToString(db), (error) => { + runtimeKeepalivePop(); + callUserCallback(() => { + if (error) { + if (onerror) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + return; + } + if (onclear) + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + arg + ); + }); + }); + }; + _emscripten_idb_async_clear.sig = 'vpppp'; + + var _emscripten_idb_load = (db, id, pbuffer, pnum, perror) => + Asyncify.handleSleep((wakeUp) => { + IDBStore.getFile( + UTF8ToString(db), + UTF8ToString(id), + (error, byteArray) => { + if (error) { + HEAP32[perror >> 2] = 1; + wakeUp(); + return; + } + var buffer = _malloc(byteArray.length); // must be freed by the caller! + HEAPU8.set(byteArray, buffer); + HEAPU32[pbuffer >> 2] = buffer; + HEAP32[pnum >> 2] = byteArray.length; + HEAP32[perror >> 2] = 0; + wakeUp(); + } + ); + }); + _emscripten_idb_load.sig = 'vppppp'; + _emscripten_idb_load.isAsync = true; + + var _emscripten_idb_store = (db, id, ptr, num, perror) => + Asyncify.handleSleep((wakeUp) => { + IDBStore.setFile( + UTF8ToString(db), + UTF8ToString(id), + new Uint8Array(HEAPU8.subarray(ptr, ptr + num)), + (error) => { + // Closure warns about storing booleans in TypedArrays. + /** @suppress{checkTypes} */ + HEAP32[perror >> 2] = !!error; + wakeUp(); + } + ); + }); + _emscripten_idb_store.sig = 'vpppip'; + _emscripten_idb_store.isAsync = true; + + var _emscripten_idb_delete = (db, id, perror) => + Asyncify.handleSleep((wakeUp) => { + IDBStore.deleteFile(UTF8ToString(db), UTF8ToString(id), (error) => { + /** @suppress{checkTypes} */ + HEAP32[perror >> 2] = !!error; + wakeUp(); + }); + }); + _emscripten_idb_delete.sig = 'vppp'; + _emscripten_idb_delete.isAsync = true; + + var _emscripten_idb_exists = (db, id, pexists, perror) => + Asyncify.handleSleep((wakeUp) => { + IDBStore.existsFile( + UTF8ToString(db), + UTF8ToString(id), + (error, exists) => { + /** @suppress{checkTypes} */ + HEAP32[pexists >> 2] = !!exists; + /** @suppress{checkTypes} */ + HEAP32[perror >> 2] = !!error; + wakeUp(); + } + ); + }); + _emscripten_idb_exists.sig = 'vpppp'; + _emscripten_idb_exists.isAsync = true; + + var _emscripten_idb_clear = (db, perror) => + Asyncify.handleSleep((wakeUp) => { + IDBStore.clearStore(UTF8ToString(db), (error) => { + /** @suppress{checkTypes} */ + HEAP32[perror >> 2] = !!error; + wakeUp(); + }); + }); + _emscripten_idb_clear.sig = 'vpp'; + _emscripten_idb_clear.isAsync = true; + + var _emscripten_idb_load_blob = (db, id, pblob, perror) => + Asyncify.handleSleep((wakeUp) => { + IDBStore.pending = (msg) => { + IDBStore.pending = null; + var blob = msg.blob; + if (!blob) { + HEAP32[perror >> 2] = 1; + wakeUp(); + return; + } + var blobId = IDBStore.blobs.length; + IDBStore.blobs.push(blob); + HEAP32[pblob >> 2] = blobId; + wakeUp(); + }; + postMessage({ + target: 'IDBStore', + method: 'loadBlob', + db: UTF8ToString(db), + id: UTF8ToString(id), + }); + }); + _emscripten_idb_load_blob.sig = 'vpppp'; + _emscripten_idb_load_blob.isAsync = true; + + var _emscripten_idb_store_blob = (db, id, ptr, num, perror) => + Asyncify.handleSleep((wakeUp) => { + IDBStore.pending = (msg) => { + IDBStore.pending = null; + HEAP32[perror >> 2] = !!msg.error; + wakeUp(); + }; + postMessage({ + target: 'IDBStore', + method: 'storeBlob', + db: UTF8ToString(db), + id: UTF8ToString(id), + blob: new Blob([ + new Uint8Array(HEAPU8.subarray(ptr, ptr + num)), + ]), + }); + }); + _emscripten_idb_store_blob.sig = 'vpppip'; + _emscripten_idb_store_blob.isAsync = true; + + var _emscripten_idb_read_from_blob = (blobId, start, num, buffer) => { + var blob = IDBStore.blobs[blobId]; + if (!blob) return 1; + if (start + num > blob.size) return 2; + var byteArray = new FileReaderSync().readAsArrayBuffer( + blob.slice(start, start + num) + ); + HEAPU8.set(new Uint8Array(byteArray), buffer); + return 0; + }; + _emscripten_idb_read_from_blob.sig = 'viiip'; + + var _emscripten_idb_free_blob = (blobId) => { + IDBStore.blobs[blobId] = null; + }; + _emscripten_idb_free_blob.sig = 'vi'; + + var _emscripten_scan_registers = (func) => { + return Asyncify.handleSleep((wakeUp) => { + // We must first unwind, so things are spilled to the stack. Then while + // we are pausing we do the actual scan. After that we can resume. Note + // how using a timeout here avoids unbounded call stack growth, which + // could happen if we tried to scan the stack immediately after unwinding. + safeSetTimeout(() => { + var stackBegin = Asyncify.currData + 12; + var stackEnd = HEAPU32[Asyncify.currData >> 2]; + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature vii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + stackBegin, + stackEnd + ); + wakeUp(); + }, 0); + }); + }; + _emscripten_scan_registers.sig = 'vp'; + _emscripten_scan_registers.isAsync = true; + + var _emscripten_lazy_load_code = () => + Asyncify.handleSleep((wakeUp) => { + // Update the expected wasm binary file to be the lazy one. + wasmBinaryFile += '.lazy.wasm'; + // Add a callback for when all run dependencies are fulfilled, which happens when async wasm loading is done. + dependenciesFulfilled = wakeUp; + // Load the new wasm. + createWasm(); + }); + _emscripten_lazy_load_code.sig = 'v'; + _emscripten_lazy_load_code.isAsync = true; + + async function __load_secondary_module() { + // Mark the module as loading for the wasm module (so it doesn't try to load it again). + wasmExports['load_secondary_module_status'].value = 1; + var imports = { primary: wasmExports }; + // Replace '.wasm' suffix with '.deferred.wasm'. + var deferred = wasmBinaryFile.slice(0, -5) + '.deferred.wasm'; + await instantiateAsync(null, deferred, imports); + } + __load_secondary_module.sig = 'v'; + __load_secondary_module.isAsync = true; + + var Fibers = { + nextFiber: 0, + trampolineRunning: false, + trampoline() { + if (!Fibers.trampolineRunning && Fibers.nextFiber) { + Fibers.trampolineRunning = true; + do { + var fiber = Fibers.nextFiber; + Fibers.nextFiber = 0; + Fibers.finishContextSwitch(fiber); + } while (Fibers.nextFiber); + Fibers.trampolineRunning = false; + } + }, + finishContextSwitch(newFiber) { + var stack_base = HEAPU32[newFiber >> 2]; + var stack_max = HEAPU32[(newFiber + 4) >> 2]; + _emscripten_stack_set_limits(stack_base, stack_max); + + stackRestore(HEAPU32[(newFiber + 8) >> 2]); + + var entryPoint = HEAPU32[(newFiber + 12) >> 2]; + + if (entryPoint !== 0) { + Asyncify.currData = null; + HEAPU32[(newFiber + 12) >> 2] = 0; + + var userData = HEAPU32[(newFiber + 16) >> 2]; + (( + a1 + ) => {}) /* a dynamic function call to signature vi, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + userData + ); + } else { + var asyncifyData = newFiber + 20; + Asyncify.currData = asyncifyData; + + Asyncify.state = Asyncify.State.Rewinding; + _asyncify_start_rewind(asyncifyData); + Asyncify.doRewind(asyncifyData); + } + }, + }; + + var _emscripten_fiber_swap = (oldFiber, newFiber) => { + if (ABORT) return; + if (Asyncify.state === Asyncify.State.Normal) { + Asyncify.state = Asyncify.State.Unwinding; + + var asyncifyData = oldFiber + 20; + Asyncify.setDataRewindFunc(asyncifyData); + Asyncify.currData = asyncifyData; + + _asyncify_start_unwind(asyncifyData); + + var stackTop = stackSave(); + HEAPU32[(oldFiber + 8) >> 2] = stackTop; + + Fibers.nextFiber = newFiber; + } else { + Asyncify.state = Asyncify.State.Normal; + _asyncify_stop_rewind(); + Asyncify.currData = null; + } + }; + _emscripten_fiber_swap.sig = 'vpp'; + _emscripten_fiber_swap.isAsync = true; + + var _SDL_GetTicks = () => (Date.now() - SDL.startTime) | 0; + _SDL_GetTicks.sig = 'i'; + + var _SDL_LockSurface = (surf) => { + var surfData = SDL.surfaces[surf]; + + surfData.locked++; + if (surfData.locked > 1) return 0; + + if (!surfData.buffer) { + surfData.buffer = _malloc(surfData.width * surfData.height * 4); + HEAPU32[(surf + 20) >> 2] = surfData.buffer; + } + + // Mark in C/C++-accessible SDL structure + // SDL_Surface has the following fields: Uint32 flags, SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; ... + // So we have fields all of the same size, and 5 of them before us. + // TODO: Use macros like in library.js + HEAPU32[(surf + 20) >> 2] = surfData.buffer; + + if (surf == SDL.screen && Module.screenIsReadOnly && surfData.image) + return 0; + + if (SDL.defaults.discardOnLock) { + if (!surfData.image) { + surfData.image = surfData.ctx.createImageData( + surfData.width, + surfData.height + ); + } + if (!SDL.defaults.opaqueFrontBuffer) return; + } else { + surfData.image = surfData.ctx.getImageData( + 0, + 0, + surfData.width, + surfData.height + ); + } + + // Emulate desktop behavior and kill alpha values on the locked surface. (very costly!) Set SDL.defaults.opaqueFrontBuffer = false + // if you don't want this. + if (surf == SDL.screen && SDL.defaults.opaqueFrontBuffer) { + var data = surfData.image.data; + var num = data.length; + for (var i = 0; i < num / 4; i++) { + data[i * 4 + 3] = 255; // opacity, as canvases blend alpha + } + } + + if (SDL.defaults.copyOnLock && !SDL.defaults.discardOnLock) { + // Copy pixel data to somewhere accessible to 'C/C++' + if (surfData.isFlagSet(2097152)) { + // If this is needed then + // we should compact the data from 32bpp to 8bpp index. + // I think best way to implement this is use + // additional colorMap hash (color->index). + // Something like this: + // + // var size = surfData.width * surfData.height; + // var data = ''; + // for (var i = 0; i> 2], + y: HEAP32[(rect + 4) >> 2], + w: HEAP32[(rect + 8) >> 2], + h: HEAP32[(rect + 12) >> 2], + }; + }, + updateRect(rect, r) { + HEAP32[rect >> 2] = r.x; + HEAP32[(rect + 4) >> 2] = r.y; + HEAP32[(rect + 8) >> 2] = r.w; + HEAP32[(rect + 12) >> 2] = r.h; + }, + intersectionOfRects(first, second) { + var leftX = Math.max(first.x, second.x); + var leftY = Math.max(first.y, second.y); + var rightX = Math.min(first.x + first.w, second.x + second.w); + var rightY = Math.min(first.y + first.h, second.y + second.h); + + return { + x: leftX, + y: leftY, + w: Math.max(leftX, rightX) - leftX, + h: Math.max(leftY, rightY) - leftY, + }; + }, + checkPixelFormat(fmt) {}, + loadColorToCSSRGB(color) { + var rgba = HEAP32[color >> 2]; + return ( + 'rgb(' + + (rgba & 255) + + ',' + + ((rgba >> 8) & 255) + + ',' + + ((rgba >> 16) & 255) + + ')' + ); + }, + loadColorToCSSRGBA(color) { + var rgba = HEAP32[color >> 2]; + return ( + 'rgba(' + + (rgba & 255) + + ',' + + ((rgba >> 8) & 255) + + ',' + + ((rgba >> 16) & 255) + + ',' + + ((rgba >> 24) & 255) / 255 + + ')' + ); + }, + translateColorToCSSRGBA: (rgba) => + 'rgba(' + + (rgba & 0xff) + + ',' + + ((rgba >> 8) & 0xff) + + ',' + + ((rgba >> 16) & 0xff) + + ',' + + (rgba >>> 24) / 0xff + + ')', + translateRGBAToCSSRGBA: (r, g, b, a) => + 'rgba(' + + (r & 0xff) + + ',' + + (g & 0xff) + + ',' + + (b & 0xff) + + ',' + + (a & 0xff) / 255 + + ')', + translateRGBAToColor: (r, g, b, a) => + r | (g << 8) | (b << 16) | (a << 24), + makeSurface( + width, + height, + flags, + usePageCanvas, + source, + rmask, + gmask, + bmask, + amask + ) { + var is_SDL_HWSURFACE = flags & 134217729; + var is_SDL_HWPALETTE = flags & 2097152; + var is_SDL_OPENGL = flags & 67108864; + + var surf = _malloc(60); + var pixelFormat = _malloc(44); + // surface with SDL_HWPALETTE flag is 8bpp surface (1 byte) + var bpp = is_SDL_HWPALETTE ? 1 : 4; + var buffer = 0; + + // preemptively initialize this for software surfaces, + // otherwise it will be lazily initialized inside of SDL_LockSurface + if (!is_SDL_HWSURFACE && !is_SDL_OPENGL) { + buffer = _malloc(width * height * 4); + } + + HEAP32[surf >> 2] = flags; + HEAPU32[(surf + 4) >> 2] = pixelFormat; + HEAP32[(surf + 8) >> 2] = width; + HEAP32[(surf + 12) >> 2] = height; + HEAP32[(surf + 16) >> 2] = width * bpp; // assuming RGBA or indexed for now, + // since that is what ImageData gives us in browsers + HEAPU32[(surf + 20) >> 2] = buffer; + + var canvas = Browser.getCanvas(); + HEAP32[(surf + 36) >> 2] = 0; + HEAP32[(surf + 40) >> 2] = 0; + HEAP32[(surf + 44) >> 2] = canvas.width; + HEAP32[(surf + 48) >> 2] = canvas.height; + + HEAP32[(surf + 56) >> 2] = 1; + + HEAP32[pixelFormat >> 2] = -2042224636; + HEAP32[(pixelFormat + 4) >> 2] = 0; // TODO + HEAP8[pixelFormat + 8] = bpp * 8; + HEAP8[pixelFormat + 9] = bpp; + + HEAP32[(pixelFormat + 12) >> 2] = rmask || 0x000000ff; + HEAP32[(pixelFormat + 16) >> 2] = gmask || 0x0000ff00; + HEAP32[(pixelFormat + 20) >> 2] = bmask || 0x00ff0000; + HEAP32[(pixelFormat + 24) >> 2] = amask || 0xff000000; + + // Decide if we want to use WebGL or not + SDL.GL = SDL.GL || is_SDL_OPENGL; + if (!usePageCanvas) { + if (SDL.canvasPool.length > 0) { + canvas = SDL.canvasPool.pop(); + } else { + canvas = document.createElement('canvas'); + } + canvas.width = width; + canvas.height = height; + } + + var webGLContextAttributes = { + antialias: + SDL.glAttributes[13] != 0 && SDL.glAttributes[14] > 1, + depth: SDL.glAttributes[6] > 0, + stencil: SDL.glAttributes[7] > 0, + alpha: SDL.glAttributes[3] > 0, + }; + + var ctx = Browser.createContext( + canvas, + is_SDL_OPENGL, + usePageCanvas, + webGLContextAttributes + ); + + SDL.surfaces[surf] = { + width, + height, + canvas, + ctx, + surf, + buffer, + pixelFormat, + alpha: 255, + flags, + locked: 0, + usePageCanvas, + source, + + isFlagSet: (flag) => flags & flag, + }; + + return surf; + }, + copyIndexedColorData(surfData, rX, rY, rW, rH) { + // HWPALETTE works with palette + // set by SDL_SetColors + if (!surfData.colors) { + return; + } + + var canvas = Browser.getCanvas(); + var fullWidth = canvas.width; + var fullHeight = canvas.height; + + var startX = rX || 0; + var startY = rY || 0; + var endX = (rW || fullWidth - startX) + startX; + var endY = (rH || fullHeight - startY) + startY; + + var buffer = surfData.buffer; + + if (!surfData.image.data32) { + surfData.image.data32 = new Uint32Array( + surfData.image.data.buffer + ); + } + var data32 = surfData.image.data32; + + var colors32 = surfData.colors32; + + for (var y = startY; y < endY; ++y) { + var base = y * fullWidth; + for (var x = startX; x < endX; ++x) { + data32[base + x] = colors32[HEAPU8[buffer + (base + x)]]; + } + } + }, + freeSurface(surf) { + var refcountPointer = surf + 56; + var refcount = HEAP32[refcountPointer >> 2]; + if (refcount > 1) { + HEAP32[refcountPointer >> 2] = refcount - 1; + return; + } + + var info = SDL.surfaces[surf]; + if (!info.usePageCanvas && info.canvas) + SDL.canvasPool.push(info.canvas); + if (info.buffer) _free(info.buffer); + _free(info.pixelFormat); + _free(surf); + SDL.surfaces[surf] = null; + + if (surf === SDL.screen) { + SDL.screen = null; + } + }, + blitSurface(src, srcrect, dst, dstrect, scale) { + var srcData = SDL.surfaces[src]; + var dstData = SDL.surfaces[dst]; + var sr, dr; + if (srcrect) { + sr = SDL.loadRect(srcrect); + } else { + sr = { x: 0, y: 0, w: srcData.width, h: srcData.height }; + } + if (dstrect) { + dr = SDL.loadRect(dstrect); + } else { + dr = { x: 0, y: 0, w: srcData.width, h: srcData.height }; + } + if (dstData.clipRect) { + var widthScale = !scale || sr.w === 0 ? 1 : sr.w / dr.w; + var heightScale = !scale || sr.h === 0 ? 1 : sr.h / dr.h; + + dr = SDL.intersectionOfRects(dstData.clipRect, dr); + + sr.w = dr.w * widthScale; + sr.h = dr.h * heightScale; + + if (dstrect) { + SDL.updateRect(dstrect, dr); + } + } + var blitw, blith; + if (scale) { + blitw = dr.w; + blith = dr.h; + } else { + blitw = sr.w; + blith = sr.h; + } + if (sr.w === 0 || sr.h === 0 || blitw === 0 || blith === 0) { + return 0; + } + var oldAlpha = dstData.ctx.globalAlpha; + dstData.ctx.globalAlpha = srcData.alpha / 255; + dstData.ctx.drawImage( + srcData.canvas, + sr.x, + sr.y, + sr.w, + sr.h, + dr.x, + dr.y, + blitw, + blith + ); + dstData.ctx.globalAlpha = oldAlpha; + if (dst != SDL.screen) { + // XXX As in IMG_Load, for compatibility we write out |pixels| + warnOnce( + 'WARNING: copying canvas data to memory for compatibility' + ); + _SDL_LockSurface(dst); + dstData.locked--; // The surface is not actually locked in this hack + } + return 0; + }, + downFingers: {}, + savedKeydown: null, + receiveEvent(event) { + function unpressAllPressedKeys() { + // Un-press all pressed keys: TODO + for (var keyCode of Object.values(SDL.keyboardMap)) { + SDL.events.push({ + type: 'keyup', + keyCode, + }); + } + } + switch (event.type) { + case 'touchstart': + case 'touchmove': { + event.preventDefault(); + + var touches = []; + + // Clear out any touchstart events that we've already processed + if (event.type === 'touchstart') { + for (var touch of event.touches) { + if (SDL.downFingers[touch.identifier] != true) { + SDL.downFingers[touch.identifier] = true; + touches.push(touch); + } + } + } else { + touches = event.touches; + } + + var firstTouch = touches[0]; + if (firstTouch) { + if (event.type == 'touchstart') { + SDL.DOMButtons[0] = 1; + } + var mouseEventType; + switch (event.type) { + case 'touchstart': + mouseEventType = 'mousedown'; + break; + case 'touchmove': + mouseEventType = 'mousemove'; + break; + } + var mouseEvent = { + type: mouseEventType, + button: 0, + pageX: firstTouch.clientX, + pageY: firstTouch.clientY, + }; + SDL.events.push(mouseEvent); + } + + for (var touch of touches) { + SDL.events.push({ + type: event.type, + touch, + }); + } + break; + } + case 'touchend': { + event.preventDefault(); + + // Remove the entry in the SDL.downFingers hash + // because the finger is no longer down. + for (var touch of event.changedTouches) { + if (SDL.downFingers[touch.identifier] === true) { + delete SDL.downFingers[touch.identifier]; + } + } + + var mouseEvent = { + type: 'mouseup', + button: 0, + pageX: event.changedTouches[0].clientX, + pageY: event.changedTouches[0].clientY, + }; + SDL.DOMButtons[0] = 0; + SDL.events.push(mouseEvent); + + for (var touch of event.changedTouches) { + SDL.events.push({ + type: 'touchend', + touch, + }); + } + break; + } + case 'DOMMouseScroll': + case 'mousewheel': + case 'wheel': + // Flip the wheel direction to translate from browser wheel direction + // (+:down) to SDL direction (+:up) + var delta = -Browser.getMouseWheelDelta(event); + // Quantize to integer so that minimum scroll is at least +/- 1. + delta = + delta == 0 + ? 0 + : delta > 0 + ? Math.max(delta, 1) + : Math.min(delta, -1); + + // Simulate old-style SDL events representing mouse wheel input as buttons + // Subtract one since JS->C marshalling is defined to add one back. + var button = (delta > 0 ? 4 : 5) - 1; + SDL.events.push({ + type: 'mousedown', + button, + pageX: event.pageX, + pageY: event.pageY, + }); + SDL.events.push({ + type: 'mouseup', + button, + pageX: event.pageX, + pageY: event.pageY, + }); + + // Pass a delta motion event. + SDL.events.push({ + type: 'wheel', + deltaX: 0, + deltaY: delta, + }); + // If we don't prevent this, then 'wheel' event will be sent again by + // the browser as 'DOMMouseScroll' and we will receive this same event + // the second time. + event.preventDefault(); + break; + case 'mousemove': + if (SDL.DOMButtons[0] === 1) { + SDL.events.push({ + type: 'touchmove', + touch: { + identifier: 0, + deviceID: -1, + pageX: event.pageX, + pageY: event.pageY, + }, + }); + } + if (Browser.pointerLock) { + // workaround for firefox bug 750111 + if ('mozMovementX' in event) { + event['movementX'] = event['mozMovementX']; + event['movementY'] = event['mozMovementY']; + } + // workaround for Firefox bug 782777 + if ( + event['movementX'] == 0 && + event['movementY'] == 0 + ) { + // ignore a mousemove event if it doesn't contain any movement info + // (without pointer lock, we infer movement from pageX/pageY, so this check is unnecessary) + event.preventDefault(); + return; + } + } + // fall through + case 'keydown': + case 'keyup': + case 'keypress': + case 'mousedown': + case 'mouseup': + // If we preventDefault on keydown events, the subsequent keypress events + // won't fire. However, it's fine (and in some cases necessary) to + // preventDefault for keys that don't generate a character. Otherwise, + // preventDefault is the right thing to do in general. + if ( + event.type !== 'keydown' || + (!SDL.unicode && !SDL.textInput) || + event.key == 'Backspace' || + event.key == 'Tab' + ) { + event.preventDefault(); + } + + if (event.type == 'mousedown') { + SDL.DOMButtons[event.button] = 1; + SDL.events.push({ + type: 'touchstart', + touch: { + identifier: 0, + deviceID: -1, + pageX: event.pageX, + pageY: event.pageY, + }, + }); + } else if (event.type == 'mouseup') { + // ignore extra ups, can happen if we leave the canvas while pressing down, then return, + // since we add a mouseup in that case + if (!SDL.DOMButtons[event.button]) { + return; + } + + SDL.events.push({ + type: 'touchend', + touch: { + identifier: 0, + deviceID: -1, + pageX: event.pageX, + pageY: event.pageY, + }, + }); + SDL.DOMButtons[event.button] = 0; + } + + // We can only request fullscreen as the result of user input. + // Due to this limitation, we toggle a boolean on keydown which + // SDL_WM_ToggleFullScreen will check and subsequently set another + // flag indicating for us to request fullscreen on the following + // keyup. This isn't perfect, but it enables SDL_WM_ToggleFullScreen + // to work as the result of a keypress (which is an extremely + // common use case). + if ( + event.type === 'keydown' || + event.type === 'mousedown' + ) { + SDL.canRequestFullscreen = true; + } else if ( + event.type === 'keyup' || + event.type === 'mouseup' + ) { + if (SDL.isRequestingFullscreen) { + Module['requestFullscreen']( + /*lockPointer=*/ true, + /*resizeCanvas=*/ true + ); + SDL.isRequestingFullscreen = false; + } + SDL.canRequestFullscreen = false; + } + + // SDL expects a unicode character to be passed to its keydown events. + // Unfortunately, the browser APIs only provide a charCode property on + // keypress events, so we must backfill in keydown events with their + // subsequent keypress event's charCode. + if (event.type === 'keypress' && SDL.savedKeydown) { + // charCode is read-only + SDL.savedKeydown.keypressCharCode = event.charCode; + SDL.savedKeydown = null; + } else if (event.type === 'keydown') { + SDL.savedKeydown = event; + } + + // Don't push keypress events unless SDL_StartTextInput has been called. + if (event.type !== 'keypress' || SDL.textInput) { + SDL.events.push(event); + } + break; + case 'mouseout': + // Un-press all pressed mouse buttons, because we might miss the release outside of the canvas + for (var i = 0; i < 3; i++) { + if (SDL.DOMButtons[i]) { + SDL.events.push({ + type: 'mouseup', + button: i, + pageX: event.pageX, + pageY: event.pageY, + }); + SDL.DOMButtons[i] = 0; + } + } + event.preventDefault(); + break; + case 'focus': + SDL.events.push(event); + event.preventDefault(); + break; + case 'blur': + SDL.events.push(event); + unpressAllPressedKeys(); + event.preventDefault(); + break; + case 'visibilitychange': + SDL.events.push({ + type: 'visibilitychange', + visible: !document.hidden, + }); + unpressAllPressedKeys(); + event.preventDefault(); + break; + case 'unload': + if (MainLoop.runner) { + SDL.events.push(event); + // Force-run a main event loop, since otherwise this event will never be caught! + MainLoop.runner(); + } + return; + case 'resize': + SDL.events.push(event); + // manually triggered resize event doesn't have a preventDefault member + if (event.preventDefault) { + event.preventDefault(); + } + break; + } + if (SDL.events.length >= 10000) { + err('SDL event queue full, dropping events'); + SDL.events = SDL.events.slice(0, 10000); + } + // If we have a handler installed, this will push the events to the app + // instead of the app polling for them. + SDL.flushEventsToHandler(); + return; + }, + lookupKeyCodeForEvent(event) { + var code = event.keyCode; + if (code >= 65 && code <= 90) { + // ASCII A-Z + code += 32; // make lowercase for SDL + } else { + // Look up DOM code in the keyCodes table with fallback for ASCII codes + // which can match between DOM codes and SDL keycodes (allows keyCodes + // to be smaller). + code = SDL.keyCodes[code] || (code < 128 ? code : 0); + // If this is one of the modifier keys (224 | 1<<10 - 227 | 1<<10), and the event specifies that it is + // a right key, add 4 to get the right key SDL key code. + if ( + event.location === + 2 /*KeyboardEvent.DOM_KEY_LOCATION_RIGHT*/ && + code >= (224 | (1 << 10)) && + code <= (227 | (1 << 10)) + ) { + code += 4; + } + } + return code; + }, + handleEvent(event) { + if (event.handled) return; + event.handled = true; + + switch (event.type) { + case 'touchstart': + case 'touchend': + case 'touchmove': { + Browser.calculateMouseEvent(event); + break; + } + case 'keydown': + case 'keyup': { + var down = event.type === 'keydown'; + var code = SDL.lookupKeyCodeForEvent(event); + // Ignore key events that we don't (yet) map to SDL keys + if (!code) return; + // Assigning a boolean to HEAP8, that's alright but Closure would like to warn about it. + // TODO(https://github.com/emscripten-core/emscripten/issues/16311): + // This is kind of ugly hack. Perhaps we can find a better way? + /** @suppress{checkTypes} */ + HEAP8[SDL.keyboardState + code] = down; + // TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED + SDL.modState = + (HEAP8[SDL.keyboardState + 1248] ? 64 : 0) | + (HEAP8[SDL.keyboardState + 1249] ? 1 : 0) | + (HEAP8[SDL.keyboardState + 1250] ? 256 : 0) | + (HEAP8[SDL.keyboardState + 1252] ? 128 : 0) | + (HEAP8[SDL.keyboardState + 1253] ? 2 : 0) | + (HEAP8[SDL.keyboardState + 1254] ? 512 : 0); + if (down) { + SDL.keyboardMap[code] = event.keyCode; // save the DOM input, which we can use to unpress it during blur + } else { + delete SDL.keyboardMap[code]; + } + + break; + } + case 'mousedown': + case 'mouseup': + if (event.type == 'mousedown') { + // SDL_BUTTON(x) is defined as (1 << ((x)-1)). SDL buttons are 1-3, + // and DOM buttons are 0-2, so this means that the below formula is + // correct. + SDL.buttonState |= 1 << event.button; + } else if (event.type == 'mouseup') { + SDL.buttonState &= ~(1 << event.button); + } + // fall through + case 'mousemove': { + Browser.calculateMouseEvent(event); + break; + } + } + }, + flushEventsToHandler() { + if (!SDL.eventHandler) return; + + while (SDL.pollEvent(SDL.eventHandlerTemp)) { + (( + a1, + a2 + ) => {}) /* a dynamic function call to signature iii, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */( + SDL.eventHandlerContext, + SDL.eventHandlerTemp + ); + } + }, + pollEvent(ptr) { + if (SDL.initFlags & 512 && SDL.joystickEventState) { + // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured + // to automatically query for events, query for joystick events. + SDL.queryJoysticks(); + } + if (ptr) { + while (SDL.events.length > 0) { + if (SDL.makeCEvent(SDL.events.shift(), ptr) !== false) + return 1; + } + return 0; + } + // XXX: somewhat risky in that we do not check if the event is real or not + // (makeCEvent returns false) if no pointer supplied + return SDL.events.length > 0; + }, + makeCEvent(event, ptr) { + if (typeof event == 'number') { + // This is a pointer to a copy of a native C event that was SDL_PushEvent'ed + _memcpy(ptr, event, 28); + _free(event); // the copy is no longer needed + return; + } + + SDL.handleEvent(event); + + switch (event.type) { + case 'keydown': + case 'keyup': { + var down = event.type === 'keydown'; + var key = SDL.lookupKeyCodeForEvent(event); + // Ignore key events that we don't (yet) map to SDL keys + if (!key) return false; + var scan; + if (key >= 1024) { + scan = key - 1024; + } else { + scan = SDL.scanCodes[key] || key; + } + + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP8[ptr + 8] = down ? 1 : 0; + HEAP8[ptr + 9] = 0; // TODO + HEAP32[(ptr + 12) >> 2] = scan; + HEAP32[(ptr + 16) >> 2] = key; + HEAP16[(ptr + 20) >> 1] = SDL.modState; + // some non-character keys (e.g. backspace and tab) won't have keypressCharCode set, fill in with the keyCode. + HEAP32[(ptr + 24) >> 2] = event.keypressCharCode || key; + + break; + } + case 'keypress': { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + // Not filling in windowID for now + var cStr = intArrayFromString( + String.fromCharCode(event.charCode) + ); + for (var i = 0; i < cStr.length; ++i) { + HEAP8[ptr + (8 + i)] = cStr[i]; + } + break; + } + case 'mousedown': + case 'mouseup': + case 'mousemove': { + if (event.type != 'mousemove') { + var down = event.type === 'mousedown'; + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 4) >> 2] = 0; + HEAP32[(ptr + 8) >> 2] = 0; + HEAP32[(ptr + 12) >> 2] = 0; + HEAP8[ptr + 16] = event.button + 1; // DOM buttons are 0-2, SDL 1-3 + HEAP8[ptr + 17] = down ? 1 : 0; + HEAP32[(ptr + 20) >> 2] = Browser.mouseX; + HEAP32[(ptr + 24) >> 2] = Browser.mouseY; + } else { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 4) >> 2] = 0; + HEAP32[(ptr + 8) >> 2] = 0; + HEAP32[(ptr + 12) >> 2] = 0; + HEAP32[(ptr + 16) >> 2] = SDL.buttonState; + HEAP32[(ptr + 20) >> 2] = Browser.mouseX; + HEAP32[(ptr + 24) >> 2] = Browser.mouseY; + HEAP32[(ptr + 28) >> 2] = Browser.mouseMovementX; + HEAP32[(ptr + 32) >> 2] = Browser.mouseMovementY; + } + break; + } + case 'wheel': { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 16) >> 2] = event.deltaX; + HEAP32[(ptr + 20) >> 2] = event.deltaY; + break; + } + case 'touchstart': + case 'touchend': + case 'touchmove': { + var touch = event.touch; + if (!Browser.touches[touch.identifier]) break; + var canvas = Browser.getCanvas(); + var x = Browser.touches[touch.identifier].x / canvas.width; + var y = Browser.touches[touch.identifier].y / canvas.height; + var lx = + Browser.lastTouches[touch.identifier].x / canvas.width; + var ly = + Browser.lastTouches[touch.identifier].y / canvas.height; + var dx = x - lx; + var dy = y - ly; + if (touch['deviceID'] === undefined) + touch.deviceID = SDL.TOUCH_DEFAULT_ID; + if (dx === 0 && dy === 0 && event.type === 'touchmove') + return false; // don't send these if nothing happened + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 4) >> 2] = _SDL_GetTicks(); + HEAP64[(ptr + 8) >> 3] = BigInt(touch.deviceID); + HEAP64[(ptr + 16) >> 3] = BigInt(touch.identifier); + HEAPF32[(ptr + 24) >> 2] = x; + HEAPF32[(ptr + 28) >> 2] = y; + HEAPF32[(ptr + 32) >> 2] = dx; + HEAPF32[(ptr + 36) >> 2] = dy; + if (touch.force !== undefined) { + HEAPF32[(ptr + 40) >> 2] = touch.force; + } else { + // No pressure data, send a digital 0/1 pressure. + HEAPF32[(ptr + 40) >> 2] = + event.type == 'touchend' ? 0 : 1; + } + break; + } + case 'unload': { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + break; + } + case 'resize': { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 4) >> 2] = event.w; + HEAP32[(ptr + 8) >> 2] = event.h; + break; + } + case 'joystick_button_up': + case 'joystick_button_down': { + var state = event.type === 'joystick_button_up' ? 0 : 1; + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP8[ptr + 4] = event.index; + HEAP8[ptr + 5] = event.button; + HEAP8[ptr + 6] = state; + break; + } + case 'joystick_axis_motion': { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP8[ptr + 4] = event.index; + HEAP8[ptr + 5] = event.axis; + HEAP32[(ptr + 8) >> 2] = SDL.joystickAxisValueConversion( + event.value + ); + break; + } + case 'focus': { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 4) >> 2] = 0; + HEAP8[ptr + 8] = 12; + break; + } + case 'blur': { + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 4) >> 2] = 0; + HEAP8[ptr + 8] = 13; + break; + } + case 'visibilitychange': { + var visibilityEventID = event.visible ? 1 : 2; + HEAP32[ptr >> 2] = SDL.DOMEventToSDLEvent[event.type]; + HEAP32[(ptr + 4) >> 2] = 0; + HEAP8[ptr + 8] = visibilityEventID; + break; + } + default: + throw 'Unhandled SDL event: ' + event.type; + } + }, + makeFontString(height, fontName) { + if (fontName.charAt(0) != "'" && fontName.charAt(0) != '"') { + // https://developer.mozilla.org/ru/docs/Web/CSS/font-family + // Font family names containing whitespace should be quoted. + // BTW, quote all font names is easier than searching spaces + fontName = '"' + fontName + '"'; + } + return height + 'px ' + fontName + ', serif'; + }, + estimateTextWidth(fontData, text) { + var h = fontData.size; + var fontString = SDL.makeFontString(h, fontData.name); + var tempCtx = SDL.ttfContext; + tempCtx.font = fontString; + var ret = tempCtx.measureText(text).width | 0; + return ret; + }, + allocateChannels(num) { + // called from Mix_AllocateChannels and init + if (SDL.numChannels >= num && num != 0) return; + SDL.numChannels = num; + SDL.channels = []; + for (var i = 0; i < num; i++) { + SDL.channels[i] = { + audio: null, + volume: 1.0, + }; + } + }, + setGetVolume(info, volume) { + if (!info) return 0; + var ret = info.volume * 128; // MIX_MAX_VOLUME + if (volume != -1) { + info.volume = Math.min(Math.max(volume, 0), 128) / 128; + if (info.audio) { + try { + info.audio.volume = info.volume; // For