diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d152dce --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,262 @@ +name: Build + +on: + workflow_dispatch: + inputs: + linux: + type: boolean + description: Linux + default: true + windows: + type: boolean + description: Windows + default: false + macOS: + type: boolean + description: macOS + default: false + + push: + branches: + - "*" + + pull_request: + branches: + - "*" + +jobs: + build_windows: + name: Build OpenStarbound Windows x64 + runs-on: windows-latest + if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.windows == true) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Install CMake & Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.29.2 + + - name: sccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + variant: sccache + key: ${{ github.job }}-${{ runner.os }} + max-size: 1000M + + - uses: ilammy/msvc-dev-cmd@v1 + + - name: vcpkg + uses: lukka/run-vcpkg@v11 + id: runvcpkg + with: + vcpkgJsonGlob: '**/source/vcpkg.json' + vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' + + - name: Run CMake + uses: lukka/run-cmake@v10 + with: + cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' + configurePreset: 'windows-release' + buildPreset: 'windows-release' + testPreset: 'windows-release' + + - name: Run Post-Build Task + working-directory: ${{ github.workspace }} + run: scripts\ci\windows\post_build.bat + + - name: Assemble Files + working-directory: ${{ github.workspace }} + run: scripts\ci\windows\assemble.bat + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-Windows-All-DevOnly + path: dist/* + + - name: Upload Client + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-Windows-Client + path: client_distribution/* + + - name: Upload Server + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-Windows-Server + path: server_distribution/* + + - name: Create Installer + working-directory: ${{ github.workspace }} + run: | + & "C:\Program Files (x86)\Inno Setup 6\iscc.exe" /Oinstaller scripts\inno\setup.iss + + - name: Upload Installer + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-Windows-Installer + path: installer/* + + build_linux: + name: Build OpenStarbound Linux x86_64 + runs-on: ubuntu-22.04 + if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.linux == true) }} + env: + CC: clang + CXX: clang++ + + steps: + - name: Install Packages + run: | + sudo apt-get update + sudo apt-get install -y pkg-config libxmu-dev libxi-dev libgl-dev libglu1-mesa-dev libsdl2-dev + + - name: Install CMake & Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.29.2 + + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: sccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + variant: sccache + key: ${{ github.job }}-${{ runner.os }} + max-size: 250M + + - name: vcpkg + uses: lukka/run-vcpkg@v11 + id: runvcpkg + with: + vcpkgJsonGlob: '**/source/vcpkg.json' + vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' + + - name: Run CMake + uses: lukka/run-cmake@v10 + with: + cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' + configurePreset: 'linux-release' + buildPreset: 'linux-release' + + - name: Prepare Artifacts + working-directory: ${{ github.workspace }} + run: tar -cvf dist.tar dist + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-Linux + path: dist.tar + + - name: Run Tests + uses: lukka/run-cmake@v10 + with: + cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' + testPreset: 'linux-release' + + - name: Assemble Files + working-directory: ${{ github.workspace }} + run: scripts/ci/linux/assemble.sh + + - name: Upload Client Files + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-Linux-Client + path: client.tar + + - name: Upload Server Files + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-Linux-Server + path: server.tar + + build-mac-intel: + name: Build OpenStarbound macOS x86_64 + runs-on: macos-13 + if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.macOS == true) }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Install CMake & Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.29.0 + + - name: sccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + variant: sccache + key: ${{ github.job }}-Intel-${{ runner.os }} + max-size: 250M + + - name: vcpkg + uses: lukka/run-vcpkg@v11 + id: runvcpkg + with: + vcpkgJsonGlob: '**/source/vcpkg.json' + vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' + + - name: Run CMake + uses: lukka/run-cmake@v10 + with: + cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' + configurePreset: 'macos-release' + buildPreset: 'macos-release' + testPreset: 'macos-release' + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-macOS-Intel + path: dist/* + + build-mac-arm: + name: Build OpenStarbound macOS arm64 + runs-on: macos-14 + if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.macOS == true) }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Install CMake & Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.29.2 + + - name: sccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + variant: sccache + key: ${{ github.job }}-ARM-${{ runner.os }} + max-size: 250M + + - name: vcpkg + uses: lukka/run-vcpkg@v11 + id: runvcpkg + with: + vcpkgJsonGlob: '**/source/vcpkg.json' + vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' + + - name: Run CMake + uses: lukka/run-cmake@v10 + with: + cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' + configurePreset: 'macos-arm-release' + buildPreset: 'macos-arm-release' + testPreset: 'macos-arm-release' + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: OpenStarbound-macOS-Silicon + path: dist/* diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml deleted file mode 100644 index 307b30f..0000000 --- a/.github/workflows/build_linux.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Ubuntu Linux - -on: - push: - branches: - - "*" - tags: - - "*" - - pull_request: - branches: - - "*" - -jobs: - build: - name: OpenStarbound Linux x86_64 - runs-on: ubuntu-20.04 - - steps: - - name: Install Packages - run: | - sudo apt-get update - sudo apt-get install -y pkg-config libxmu-dev libxi-dev libgl-dev libglu1-mesa-dev libsdl2-dev - - - name: Install CMake & Ninja - uses: lukka/get-cmake@latest - with: - cmakeVersion: 3.29.2 - - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: sccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - variant: sccache - key: ${{ github.job }}-${{ runner.os }} - max-size: 250M - - - name: vcpkg - uses: lukka/run-vcpkg@v11 - id: runvcpkg - with: - vcpkgJsonGlob: '**/source/vcpkg.json' - vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' - - - name: Run CMake - uses: lukka/run-cmake@v10 - with: - cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' - configurePreset: 'linux-release' - buildPreset: 'linux-release' - testPreset: 'linux-release' - - - name: Assemble Files - working-directory: ${{ github.workspace }} - run: scripts/ci/linux/assemble.sh - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-Linux - path: dist.tar - - - name: Upload Client Files - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-Linux-Client - path: client.tar - - - name: Upload Server Files - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-Linux-Server - path: server.tar \ No newline at end of file diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml deleted file mode 100644 index 1cfd12c..0000000 --- a/.github/workflows/build_macos.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: macOS - -on: - push: - branches: - - "*" - tags: - - "*" - - pull_request: - branches: - - "*" - -jobs: - build-intel: - name: OpenStarbound macOS x86_64 - runs-on: macos-13 - - steps: - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Install CMake & Ninja - uses: lukka/get-cmake@latest - with: - cmakeVersion: 3.29.0 - - - name: sccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - variant: sccache - key: ${{ github.job }}-Intel-${{ runner.os }} - max-size: 250M - - - name: vcpkg - uses: lukka/run-vcpkg@v11 - id: runvcpkg - with: - vcpkgJsonGlob: '**/source/vcpkg.json' - vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' - - - name: Run CMake - uses: lukka/run-cmake@v10 - with: - cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' - configurePreset: 'macos-release' - buildPreset: 'macos-release' - testPreset: 'macos-release' - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-macOS-Intel - path: dist/* - - build-arm: - name: OpenStarbound macOS arm64 - runs-on: macos-14 - - steps: - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Install CMake & Ninja - uses: lukka/get-cmake@latest - with: - cmakeVersion: 3.29.2 - - - name: sccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - variant: sccache - key: ${{ github.job }}-ARM-${{ runner.os }} - max-size: 250M - - - name: vcpkg - uses: lukka/run-vcpkg@v11 - id: runvcpkg - with: - vcpkgJsonGlob: '**/source/vcpkg.json' - vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' - - - name: Run CMake - uses: lukka/run-cmake@v10 - with: - cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' - configurePreset: 'macos-arm-release' - buildPreset: 'macos-arm-release' - testPreset: 'macos-arm-release' - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-macOS-Silicon - path: dist/* \ No newline at end of file diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml deleted file mode 100644 index d3ee36b..0000000 --- a/.github/workflows/build_windows.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Windows - -on: - push: - branches: - - "*" - tags: - - "*" - - pull_request: - branches: - - "*" - -jobs: - build: - name: Build OpenStarbound Windows x64 - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Install CMake & Ninja - uses: lukka/get-cmake@latest - with: - cmakeVersion: 3.29.2 - - - name: sccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - variant: sccache - key: ${{ github.job }}-${{ runner.os }} - max-size: 1000M - - - uses: ilammy/msvc-dev-cmd@v1 - - - name: vcpkg - uses: lukka/run-vcpkg@v11 - id: runvcpkg - with: - vcpkgJsonGlob: '**/source/vcpkg.json' - vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json' - - - name: Run CMake - uses: lukka/run-cmake@v10 - with: - cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt' - configurePreset: 'windows-release' - buildPreset: 'windows-release' - testPreset: 'windows-release' - - - name: Run Post-Build Task - working-directory: ${{ github.workspace }} - run: scripts\ci\windows\post_build.bat - - - name: Assemble Files - working-directory: ${{ github.workspace }} - run: scripts\ci\windows\assemble.bat - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-Windows-All-DevOnly - path: dist/* - - - name: Upload Client - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-Windows-Client - path: client_distribution/* - - - name: Upload Server - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-Windows-Server - path: server_distribution/* - - - name: Create Installer - working-directory: ${{ github.workspace }} - run: | - & "C:\Program Files (x86)\Inno Setup 6\iscc.exe" /Oinstaller scripts\inno\setup.iss - - - name: Upload Installer - uses: actions/upload-artifact@v4 - with: - name: OpenStarbound-Windows-Installer - path: installer/* diff --git a/.gitignore b/.gitignore index 9420f19..9979294 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ enc_temp_folder/ .cache/ /attic/user/ /attic/chucklefish/ +/attic/debug/ /tiled/ /assets/user/ /assets/devel/ diff --git a/README.md b/README.md index 5dd2b87..b97cca0 100644 --- a/README.md +++ b/README.md @@ -14,22 +14,26 @@ It is still **work-in-progress**. ## Installation You can download a nightly build below, or the [latest release](https://github.com/OpenStarbound/OpenStarbound/releases/latest). At the moment, you must copy the game assets (**packed.pak**) from your normal Starbound install to the OpenStarbound assets directory before playing. +OpenStarbound is a separate installation/executable than Starbound. You can copy your `storage` folder from Starbound to transfer your save data and settings. Launching OpenStarbound with Steam open will load your subscribed Steam mods. + An installer is available for Windows. otherwise, extract the client/server zip for your platform and copy the game assets (packed.pak) to the OpenStarbound assets folder. the macOS releases currently lack the sbinit.config and folder structure that the Linux & Windows zips have, so you'll need to create those before running them. For macOS releases, it is recommended to build them from source (See guide below). ### Nightly Builds These link directly to the latest build from the [Actions](https://github.com/OpenStarbound/OpenStarbound/actions?query=branch%3Amain) tab. -[**Windows**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main): -[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Installer.zip), -[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Client.zip), -[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Server.zip) +**Windows** +[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Installer.zip), +[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Client.zip), +[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Server.zip) -[**Linux**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main): -[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main/OpenStarbound-Linux-Client.zip), -[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main/OpenStarbound-Linux-Server.zip) +**Linux** +[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Linux-Client.zip), +[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Linux-Server.zip) -[**macOS**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main "overpriced aluminium"): -[Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Intel.zip), -[ARM](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Silicon.zip) +**macOS** +[Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Dev-macOS-Intel.zip), +[ARM](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Dev-macOS-Silicon.zip) + +[All Nightly Builds](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main) ## Changes Note: Not every function from [StarExtensions](https://github.com/StarExtensions/StarExtensions) has been ported yet, but near-full compatibility with mods that use StarExtensions features is planned. @@ -44,8 +48,16 @@ Note: Not every function from [StarExtensions](https://github.com/StarExtensions * These scripts can modify, read, patch and create new assets! * Lua patch files now exist - **.patch.lua** * These can patch JSON assets, as well as images! +### Commands +**View OpenStarbound commands with `/help`! You can also view them [here](https://github.com/OpenStarbound/OpenStarbound/blob/main/assets/opensb/help.config.patch)** + * Changes to vanilla commands: + * `/settileprotection` + * You can now specify as many dungeon IDs as you want: `/settileprotection 69 420 false` + * You can now specify a range: /settileprotection 0..65535 true ### Bug Fixes * Invalid character inventories are updated when loading in, allowing players to swap inventory mods with pre-existing characters. +* Fix vanilla world file size bloating issue. +* Modifying a single status property no longer re-networks every status property on the entity (server and client must be running at least OpenStarbound 0.15) ### Misc * Player functions for saving/loading, modifying the humanoid identity, manipulating the inventory. [Documentation](https://github.com/OpenStarbound/OpenStarbound/blob/main/doc/lua/openstarbound.md) * Character swapping (rewrite from StarExtensions, currently command-only: `/swap name` case-insensitive, only substring required) diff --git a/assets/opensb/binds/opensb.binds b/assets/opensb/binds/opensb.binds index c52385a..d06b6f4 100644 --- a/assets/opensb/binds/opensb.binds +++ b/assets/opensb/binds/opensb.binds @@ -3,7 +3,9 @@ "groups": { "camera": { "name": "Camera" }, "voice": { "name": "Voice" }, - "building": { "name": "Building" } + "building": { "name": "Building" }, + "inventory": { "name": "Inventory" }, + "editing" : { "name" : "Editing" } }, "name": "Open^#ebd74a;Starbound", "binds": { @@ -15,6 +17,11 @@ "group": "camera", "name": "Zoom In" }, + "takeAll": { + "default": [], + "group": "inventory", + "name": "Take All From Container" + }, "zoomOut": { "default": [{ "type": "key", @@ -42,6 +49,18 @@ "default": [], "group": "building", "name": "Shrink Building Radius" + }, + "editingCopyItemJson": { + "default": [], // [{"type": "key", "value": "C","mods": ["LShift"]}], + "name": "Copy Item in Cursor", + "group": "editing", + "tags" : ["clipboard"] + }, + "editingPasteItemJson": { + "default": [], // [{"type": "key", "value": "V", "mods": ["LShift"]}], + "name": "Paste Item in Cursor", + "group": "editing", + "tags" : ["clipboard"] } } } diff --git a/assets/opensb/client.config.patch b/assets/opensb/client.config.patch index 619a767..7bd18d6 100644 --- a/assets/opensb/client.config.patch +++ b/assets/opensb/client.config.patch @@ -10,5 +10,6 @@ "scissor" : false, "letterbox" : false }, - "postProcessLayers": [] + "postProcessLayers": [], + "postProcessGroups": {} } diff --git a/assets/opensb/font/sources.txt b/assets/opensb/font/sources.txt new file mode 100644 index 0000000..6265300 --- /dev/null +++ b/assets/opensb/font/sources.txt @@ -0,0 +1,7 @@ +m6x11: managore.itch.io/m6x11 +beech: 04.jp.org +twemoji: github.com/jdecked/twemoji +unifont: github.com/stgiga/UnifontEX +dotsies: dotsies.org +newspaper: onlygfx.com/newspaper-cutout +pixelhobo: Chucklefish \ No newline at end of file diff --git a/assets/opensb/font/unifont.woff2 b/assets/opensb/font/unifont.woff2 index 5da6c98..3563d52 100644 Binary files a/assets/opensb/font/unifont.woff2 and b/assets/opensb/font/unifont.woff2 differ diff --git a/assets/opensb/interface.config.patch b/assets/opensb/interface.config.patch index eedd728..a43a7bc 100644 --- a/assets/opensb/interface.config.patch +++ b/assets/opensb/interface.config.patch @@ -41,6 +41,7 @@ // Change planet name to support the new internal string formatting. "planetNameFormatString" : "- {} -", + "planetTextOffset" : [0, 120], "planetTextStyle" : { "backDirectives" : "border=5;fff;fff?border=1;fff;fff7?multiply=000", "fontSize" : 24 diff --git a/assets/opensb/interface/actionbar/actionbarbottombg.png b/assets/opensb/interface/actionbar/actionbarbottombg.png index 685ce2a..2d3246d 100644 Binary files a/assets/opensb/interface/actionbar/actionbarbottombg.png and b/assets/opensb/interface/actionbar/actionbarbottombg.png differ diff --git a/assets/opensb/interface/building/collisionblock.png b/assets/opensb/interface/building/collisionblock.png index 5f78e28..09bea46 100644 Binary files a/assets/opensb/interface/building/collisionblock.png and b/assets/opensb/interface/building/collisionblock.png differ diff --git a/assets/opensb/interface/building/collisionempty.png b/assets/opensb/interface/building/collisionempty.png index 6f8f66f..261ed74 100644 Binary files a/assets/opensb/interface/building/collisionempty.png and b/assets/opensb/interface/building/collisionempty.png differ diff --git a/assets/opensb/interface/building/collisionplatform.png b/assets/opensb/interface/building/collisionplatform.png index 4744f82..97a00f2 100644 Binary files a/assets/opensb/interface/building/collisionplatform.png and b/assets/opensb/interface/building/collisionplatform.png differ diff --git a/assets/opensb/interface/graphicsmenu/shine.png b/assets/opensb/interface/graphicsmenu/shine.png index fb130dd..909cef3 100644 Binary files a/assets/opensb/interface/graphicsmenu/shine.png and b/assets/opensb/interface/graphicsmenu/shine.png differ diff --git a/assets/opensb/interface/merchant/shine.png b/assets/opensb/interface/merchant/shine.png deleted file mode 100644 index 639bae9..0000000 Binary files a/assets/opensb/interface/merchant/shine.png and /dev/null differ diff --git a/assets/opensb/interface/opensb/bindings/bind.png b/assets/opensb/interface/opensb/bindings/bind.png index fcf492a..8ae7370 100644 Binary files a/assets/opensb/interface/opensb/bindings/bind.png and b/assets/opensb/interface/opensb/bindings/bind.png differ diff --git a/assets/opensb/interface/opensb/bindings/bindings.lua b/assets/opensb/interface/opensb/bindings/bindings.lua index cafcc15..32c19e8 100644 --- a/assets/opensb/interface/opensb/bindings/bindings.lua +++ b/assets/opensb/interface/opensb/bindings/bindings.lua @@ -35,7 +35,7 @@ local function finishBind(a, b) if (type(a) == "table") then snareFinished(a) else - snareFinished{ type = a, value = b, mods = getMods(value) } + snareFinished{ type = a, value = b, mods = getMods(b) } for i, mod in ipairs(mods) do mod.active = false end diff --git a/assets/opensb/interface/opensb/bindings/bindname.png b/assets/opensb/interface/opensb/bindings/bindname.png index f7c4500..71de45f 100644 Binary files a/assets/opensb/interface/opensb/bindings/bindname.png and b/assets/opensb/interface/opensb/bindings/bindname.png differ diff --git a/assets/opensb/interface/opensb/bindings/body.png b/assets/opensb/interface/opensb/bindings/body.png index 272ed29..07d1cad 100644 Binary files a/assets/opensb/interface/opensb/bindings/body.png and b/assets/opensb/interface/opensb/bindings/body.png differ diff --git a/assets/opensb/interface/opensb/bindings/categoryback.png b/assets/opensb/interface/opensb/bindings/categoryback.png index ed838f3..df244c4 100644 Binary files a/assets/opensb/interface/opensb/bindings/categoryback.png and b/assets/opensb/interface/opensb/bindings/categoryback.png differ diff --git a/assets/opensb/interface/opensb/bindings/footer.png b/assets/opensb/interface/opensb/bindings/footer.png index 426f8a6..6c6c483 100644 Binary files a/assets/opensb/interface/opensb/bindings/footer.png and b/assets/opensb/interface/opensb/bindings/footer.png differ diff --git a/assets/opensb/interface/opensb/bindings/garbage.png b/assets/opensb/interface/opensb/bindings/garbage.png index 3069741..2f3ffaa 100644 Binary files a/assets/opensb/interface/opensb/bindings/garbage.png and b/assets/opensb/interface/opensb/bindings/garbage.png differ diff --git a/assets/opensb/interface/opensb/bindings/groupname.png b/assets/opensb/interface/opensb/bindings/groupname.png index 3019556..c4d837e 100644 Binary files a/assets/opensb/interface/opensb/bindings/groupname.png and b/assets/opensb/interface/opensb/bindings/groupname.png differ diff --git a/assets/opensb/interface/opensb/bindings/header.png b/assets/opensb/interface/opensb/bindings/header.png index 287d220..ccc4785 100644 Binary files a/assets/opensb/interface/opensb/bindings/header.png and b/assets/opensb/interface/opensb/bindings/header.png differ diff --git a/assets/opensb/interface/opensb/bindings/reset.png b/assets/opensb/interface/opensb/bindings/reset.png index a75fd9c..c91f11c 100644 Binary files a/assets/opensb/interface/opensb/bindings/reset.png and b/assets/opensb/interface/opensb/bindings/reset.png differ diff --git a/assets/opensb/interface/opensb/shaders/body.png b/assets/opensb/interface/opensb/shaders/body.png new file mode 100644 index 0000000..07d1cad Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/body.png differ diff --git a/assets/opensb/interface/opensb/shaders/categoryname.png b/assets/opensb/interface/opensb/shaders/categoryname.png new file mode 100644 index 0000000..c4d837e Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/categoryname.png differ diff --git a/assets/opensb/interface/opensb/shaders/footer.png b/assets/opensb/interface/opensb/shaders/footer.png new file mode 100644 index 0000000..6c6c483 Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/footer.png differ diff --git a/assets/opensb/interface/opensb/shaders/group.png b/assets/opensb/interface/opensb/shaders/group.png new file mode 100644 index 0000000..66f5cdb Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/group.png differ diff --git a/assets/opensb/interface/opensb/shaders/groupback.png b/assets/opensb/interface/opensb/shaders/groupback.png new file mode 100644 index 0000000..df244c4 Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/groupback.png differ diff --git a/assets/opensb/interface/opensb/shaders/header.png b/assets/opensb/interface/opensb/shaders/header.png new file mode 100644 index 0000000..d8042ac Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/header.png differ diff --git a/assets/opensb/interface/opensb/shaders/optionname.png b/assets/opensb/interface/opensb/shaders/optionname.png new file mode 100644 index 0000000..71de45f Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/optionname.png differ diff --git a/assets/opensb/interface/opensb/shaders/select.png b/assets/opensb/interface/opensb/shaders/select.png new file mode 100644 index 0000000..dc649b4 Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/select.png differ diff --git a/assets/opensb/interface/opensb/shaders/selectend.png b/assets/opensb/interface/opensb/shaders/selectend.png new file mode 100644 index 0000000..99908d6 Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/selectend.png differ diff --git a/assets/opensb/interface/opensb/shaders/shaders.config b/assets/opensb/interface/opensb/shaders/shaders.config new file mode 100644 index 0000000..864574e --- /dev/null +++ b/assets/opensb/interface/opensb/shaders/shaders.config @@ -0,0 +1,150 @@ +{ + "scripts" : ["/interface/opensb/shaders/shaders.lua"], + "scriptDelta" : 0, + "scriptWidgetCallbacks" : [ + "selectGroup", + "toggleGroupEnabled", + "sliderOptionModified", + "textboxOptionModified", + "selectOptionPressed" + ], + + "gui" : { + "panefeature" : { + "type" : "panefeature", + "positionLocked" : false + }, + "background" : { + "type" : "background", + "fileHeader" : "/interface/opensb/shaders/header.png", + "fileBody" : "/interface/opensb/shaders/body.png", + "fileFooter" : "/interface/opensb/shaders/footer.png" + }, + "banner" : { + "type" : "canvas", + "rect" : [146, 187, 398, 215] + }, + "groups" : { + "type" : "scrollArea", + "rect" : [4, 16, 145, 214], + "children" : { + "list" : { + "type" : "list", + "schema" : { + "selectedBG" : "/interface/opensb/shaders/groupback.png?multiply=0f0", + "unselectedBG" : "/interface/opensb/shaders/groupback.png?multiply=222", + "spacing" : [0, 1], + "memberSize" : [130, 16], + "listTemplate" : { + "background" : { + "type" : "image", + "file" : "/interface/opensb/shaders/groupback.png?multiply=222", + "position" : [0, 0], + "zlevel" : -1 + }, + "button" : { + "type" : "button", + "callback" : "selectGroup", + "caption" : "Unnamed", + "base" : "/interface/opensb/shaders/group.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/shaders/group.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/shaders/group.png?replace;fff=000;000=7777", + "pressedOffset" : [0, 0], + "position" : [0, 0] + } + } + } + } + }, + "buttons" : { + "horizontal" : { + "forward" : { "base" : "", "hover" : "", "pressed" : "" }, + "backward" : { "base" : "", "hover": "", "pressed" : "" } + }, + "vertical" : { + "forward" : { + "base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff", + "hover" : "/interface/scrollarea/varrow-forwardhover.png", + "pressed" : "" + }, + "backward" : { + "base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff", + "hover" : "/interface/scrollarea/varrow-backwardhover.png", + "pressed" : "" + } + } + }, + "thumbs" : { + "horizontal" : { + "base" : { "begin" : "", "end" : "", "inner" : "" }, + "hover" : { "begin" : "", "end" : "", "inner" : "" }, + "pressed" : { "begin" : "", "end" : "", "inner" : "" } + }, + "vertical" : { + "base" : { + "begin" : "/interface/scrollarea/vthumb-begin.png", + "end" : "/interface/scrollarea/vthumb-end.png", + "inner" : "/interface/scrollarea/vthumb-inner.png" + }, + "hover" : { + "begin" : "/interface/scrollarea/vthumb-beginhover.png", + "end" : "/interface/scrollarea/vthumb-endhover.png", + "inner" : "/interface/scrollarea/vthumb-innerhover.png" + }, + "pressed" : { + "begin" : "/interface/scrollarea/vthumb-beginhover.png", + "end" : "/interface/scrollarea/vthumb-endhover.png", + "inner" : "/interface/scrollarea/vthumb-innerhover.png" + } + } + } + }, + "options" : { + "type" : "scrollArea", + "rect" : [147, 16, 398, 185], + "children" : {}, + "buttons" : { + "horizontal" : { + "forward" : { "base" : "", "hover" : "", "pressed" : "" }, + "backward" : { "base" : "", "hover": "", "pressed" : "" } + }, + "vertical" : { + "forward" : { + "base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff", + "hover" : "/interface/scrollarea/varrow-forwardhover.png", + "pressed" : "" + }, + "backward" : { + "base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff", + "hover" : "/interface/scrollarea/varrow-backwardhover.png", + "pressed" : "" + } + } + }, + "thumbs" : { + "horizontal" : { + "base" : { "begin" : "", "end" : "", "inner" : "" }, + "hover" : { "begin" : "", "end" : "", "inner" : "" }, + "pressed" : { "begin" : "", "end" : "", "inner" : "" } + }, + "vertical" : { + "base" : { + "begin" : "/interface/scrollarea/vthumb-begin.png", + "end" : "/interface/scrollarea/vthumb-end.png", + "inner" : "/interface/scrollarea/vthumb-inner.png" + }, + "hover" : { + "begin" : "/interface/scrollarea/vthumb-beginhover.png", + "end" : "/interface/scrollarea/vthumb-endhover.png", + "inner" : "/interface/scrollarea/vthumb-innerhover.png" + }, + "pressed" : { + "begin" : "/interface/scrollarea/vthumb-beginhover.png", + "end" : "/interface/scrollarea/vthumb-endhover.png", + "inner" : "/interface/scrollarea/vthumb-innerhover.png" + } + } + } + } + } +} diff --git a/assets/opensb/interface/opensb/shaders/shaders.lua b/assets/opensb/interface/opensb/shaders/shaders.lua new file mode 100644 index 0000000..f9b6a32 --- /dev/null +++ b/assets/opensb/interface/opensb/shaders/shaders.lua @@ -0,0 +1,355 @@ +--constants +local PATH = "/interface/opensb/shaders/" +local GROUP_LIST_WIDGET = "groups.list" +local OPTIONS_WIDGET = "options" + +local fmt = string.format +local log = function() end + +function update() +end + + +local function alphabeticalNameSortGreater(a, b) return a.name > b.name end +local function alphabeticalNameSortLesser(a, b) return a.name < b.name end +local sortedGroups = {} +local groups = {} + +local widgetsToGroups = {} +local allSettings = {} +local shaderConfig + +local function addGroupToList(data) + local name = widget.addListItem(GROUP_LIST_WIDGET) + widget.setText(fmt("%s.%s.button", GROUP_LIST_WIDGET, name), data.friendlyName) + log("Added group ^cyan;%s^reset; to list", data.friendlyName) + return name +end + +local function parseGroups() + for name, data in next, renderer.postProcessGroups() do + if not data.hidden then + if not data.friendlyName then data.friendlyName = name end + data.groupId = name + data.name = data.friendlyName + groups[data.groupId] = data + end + end + + for groupId, data in pairs(groups) do + sortedGroups[#sortedGroups + 1] = data + end + table.sort(sortedGroups, alphabeticalNameSortLesser) + for i = 1, #sortedGroups do + local data = sortedGroups[i] + data.index = i + local name = addGroupToList(data) + data.widget = name + widgetsToGroups[name] = data + end + if sortedGroups[1] then + local first = sortedGroups[1].widget + widget.setListSelected(GROUP_LIST_WIDGET, first) + selectGroup(first) + end +end + +local activeGroup + +local function addOptionGroup(data, i, added) + local y = (i - 1) * -14 + local bg = { + type = "image", + file = PATH .. "groupname.png", + position = {-12, y} + } + local name = "group_" .. i + widget.addChild(BINDS_WIDGET, bg, name) + added[#added + 1] = name + local label = { + type = "label", + value = data.label, + wrapWidth = 120, + fontSize = 8, + hAnchor = "mid", + vAnchor = "mid", + position = {120, 6} + } + widget.addChild(fmt("%s.%s", BINDS_WIDGET, name), label, "text") +end + +local function digitRegex(n) + local i = 0 + while n ~= math.floor(n) do + n = n * 10 + i = i + 1 + end + return fmt("%%.%df",i) -- create format string %.nf, where n = %d (i with no decimal points) +end +local optionPrefix = "option_" +local optionOffset = #optionPrefix+1 +local function addOption(data, i, added) + local y = (i - 1) * -14 + local bg = { + type = "image", + file = PATH .. "optionname.png", + position = {-12, y} + } + local name = "label_" .. data.name + widget.addChild(OPTIONS_WIDGET, bg, name) + added[#added + 1] = name + local label = { + type = "label", + value = data.label, + wrapWidth = 120, + fontSize = 8, + hAnchor = "mid", + vAnchor = "mid", + position = {62, 6} + } + widget.addChild(fmt("%s.%s", OPTIONS_WIDGET, name), label, "text") + local value = data.default or 0 + if not shaderConfig[activeGroup] then + shaderConfig[activeGroup] = {parameters={}} + end + if not shaderConfig[activeGroup].parameters then + shaderConfig[activeGroup].parameters = {} + end + if shaderConfig[activeGroup].parameters[data.name] ~= nil then + value = shaderConfig[activeGroup].parameters[data.name] + end + + -- todo: finish this + if data.type == "slider" then + local range = data.range or {0,1} + local delta = data.delta or 0.01 + -- for some reason ranges require ints so + local r = {range[1]/delta, range[2]/delta, 1} + + local slider = { + type = "slider", + callback = "sliderOptionModified", + position = {110, y + 2}, + gridImage = "/interface/optionsmenu/smallselection.png", + range = r + } + name = optionPrefix..data.name + added[#added + 1] = name + widget.addChild(OPTIONS_WIDGET, slider, name) + + widget.setSliderValue(fmt("%s.%s",OPTIONS_WIDGET,name), value/delta) + local valLabel = { + type = "label", + value = fmt(digitRegex(delta),value), + position = {186, y + 2} + } + added[#added + 1] = name.."_value" + widget.addChild(OPTIONS_WIDGET, valLabel, name.."_value") + elseif data.type == "select" then + local n = #data.values + local width = math.floor(118/n) + local by = y + local buttons = {} + for i=0,n-1 do + local img = fmt("%sselect.png?crop=0;0;%.0f;13",PATH,width) + local button = { + type = "button", + callback="selectOptionPressed", + caption = data.values[i+1], + base = img, + hover = img.."?brightness=-25", + baseImageChecked = img.."?multiply=00ff00", + hoverImageChecked = img.."?multiply=00ff00?brightness=-25", + position = {110+width*i, by}, + pressedOffset = {0, 0}, + checkable = true, + checked = (value == i) + } + name = "select_"..data.name.."_"..i + added[#added + 1] = name + widget.addChild(OPTIONS_WIDGET, button, name) + table.insert(buttons,{widget=fmt("%s.%s",OPTIONS_WIDGET,name),index=i}) + end + for k,v in next, buttons do + widget.setData(v.widget,{option=data,index=v.index,buttons=buttons}) + end + --[[local bge = { + type = "image", + file = PATH.."selectend.png", + position = {110+width*3, by} + } + name = "bgend_"..data.name + added[#added + 1] = name + widget.addChild(OPTIONS_WIDGET, bge, name)]] + end +end + +function sliderOptionModified(option, odata) + local oname = string.sub(option, optionOffset) + local parameter = groups[activeGroup].parameters[oname] + local value = widget.getSliderValue(fmt("%s.%s",OPTIONS_WIDGET,option))*parameter.delta + + widget.setText(fmt("%s.%s",OPTIONS_WIDGET,option.."_value"), fmt(digitRegex(parameter.delta),value)) + + for k,v in next, parameter.effects do + renderer.setEffectParameter(v, oname, value) + end + shaderConfig[activeGroup].parameters[oname] = value + root.setConfiguration("postProcessGroups",shaderConfig) +end + +function selectOptionPressed(button,bdata) + sb.logInfo(sb.print(bdata)) + + for k,v in next, bdata.buttons do + if v.index ~= bdata.index then + widget.setChecked(v.widget, false) + end + end + + value = bdata.index + + local oname = bdata.option.name + local parameter = groups[activeGroup].parameters[oname] + + for k,v in next, parameter.effects do + renderer.setEffectParameter(v, oname, value) + end + shaderConfig[activeGroup].parameters[oname] = value + root.setConfiguration("postProcessGroups",shaderConfig) +end + +local function addEnabled(groupname, i, added) + local y = (i - 1) * -14 + local bg = { + type = "image", + file = PATH .. "optionname.png", + position = {-12, y} + } + local name = "label_" .. i + widget.addChild(OPTIONS_WIDGET, bg, name) + added[#added + 1] = name + local label = { + type = "label", + value = "Enabled", + wrapWidth = 120, + fontSize = 8, + hAnchor = "mid", + vAnchor = "mid", + position = {62, 6} + } + widget.addChild(fmt("%s.%s", OPTIONS_WIDGET, name), label, "text") + local checkbox = { + type = "button", + callback = "toggleGroupEnabled", + position = {165, y + 2}, + pressedOffset = {0, 0}, + base = "/interface/optionsmenu/checkboxnocheck.png", + hover = "/interface/optionsmenu/checkboxnocheckhover.png", + baseImageChecked = "/interface/optionsmenu/checkboxcheck.png", + hoverImageChecked = "/interface/optionsmenu/checkboxcheckhover.png", + checkable = true, + checked = renderer.postProcessGroupEnabled(groupname) + } + name = "check_"..groupname + added[#added + 1] = name + widget.addChild(OPTIONS_WIDGET, checkbox, name) +end + +function toggleGroupEnabled(checkbox, cdata) + renderer.setPostProcessGroupEnabled(activeGroup, widget.getChecked(fmt("%s.%s",OPTIONS_WIDGET,checkbox)), true) + shaderConfig = root.getConfiguration("postProcessGroups") +end + +function selectGroup() + local selected = widget.getListSelected(GROUP_LIST_WIDGET) + local group = widgetsToGroups[selected] + local dataFromPrev = widget.getData(OPTIONS_WIDGET) + if dataFromPrev then + for i, v in pairs(dataFromPrev) do + widget.removeChild(OPTIONS_WIDGET, v) + end + end + widgetsToOptions = {} + + activeGroup = group.groupId + + local bannerOptions = widget.bindCanvas("banner") + bannerOptions:clear() + + local bannerName = tostring(group.bannerName or group.name or group.groupId) + bannerOptions:drawText(bannerName, {position = {127, 13}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16) + + local added = {} + local index = 0 + addEnabled(group.groupId,index,added) + if group.categories then + + elseif group.parameters then + local sortedParams = {} + for k,v in next, group.parameters do + v.name = k + sortedParams[#sortedParams+1] = v + end + table.sort(sortedParams, alphabeticalNameSortLesser) + + for k,v in next, sortedParams do + index = index + 1 + addOption(v, index, added) + end + end + --[[ + local categories = group.categories or {} + if not categories.unsorted then + categories.unsorted = {name = "Unsorted"} + end + + local sortedCategories = {} + for categoryId, data in pairs(categories) do + data.name = tostring(data.name or categoryId) + data.sortedOptions = {} + sortedCategories[#sortedCategories + 1] = data + end + + table.sort(sortedCategories, alphabeticalNameSortLesser) + + for categoryId, data in pairs(categories) do + table.sort(data.sortedOptions, alphabeticalNameSortLesser) + end + + local onlyUnsorted = not sortedGroups[2] and sortedGroups[1] == categories.unsorted + + for iA = 1, #sortedCategories do + local category = sortedCategories[iA] + local optionsCount = #category.sortedOptions + if optionsCount > 0 then + if not onlyUnsorted then + index = index + 1 + addOptionCategory(category, index, added) + end + for iB = 1, optionsCount do + index = index + 1 + addOption(category.sortedOptions[iB], index, added) + end + end + end + ]] + + widget.setData(OPTIONS_WIDGET, added) +end + + +local function initCallbacks() + widget.registerMemberCallback(GROUP_LIST_WIDGET, "selectGroup", selectGroup) +end + +function init() + shaderConfig = root.getConfiguration("postProcessGroups") + --log = chat and chat.addMessage or sb.logInfo + + widget.clearListItems(GROUP_LIST_WIDGET) + initCallbacks() + parseGroups() + + script.setUpdateDelta(1) +end diff --git a/assets/opensb/interface/opensb/voicechat/activityback.png b/assets/opensb/interface/opensb/voicechat/activityback.png index a88f7f0..b603eca 100644 Binary files a/assets/opensb/interface/opensb/voicechat/activityback.png and b/assets/opensb/interface/opensb/voicechat/activityback.png differ diff --git a/assets/opensb/interface/opensb/voicechat/bigbuttonback.png b/assets/opensb/interface/opensb/voicechat/bigbuttonback.png index 663da60..6287d8d 100644 Binary files a/assets/opensb/interface/opensb/voicechat/bigbuttonback.png and b/assets/opensb/interface/opensb/voicechat/bigbuttonback.png differ diff --git a/assets/opensb/interface/opensb/voicechat/body.png b/assets/opensb/interface/opensb/voicechat/body.png index c76bbb4..0eb134c 100644 Binary files a/assets/opensb/interface/opensb/voicechat/body.png and b/assets/opensb/interface/opensb/voicechat/body.png differ diff --git a/assets/opensb/interface/opensb/voicechat/deviceback.png b/assets/opensb/interface/opensb/voicechat/deviceback.png index 8f25473..0d0509f 100644 Binary files a/assets/opensb/interface/opensb/voicechat/deviceback.png and b/assets/opensb/interface/opensb/voicechat/deviceback.png differ diff --git a/assets/opensb/interface/opensb/voicechat/footer.png b/assets/opensb/interface/opensb/voicechat/footer.png index 98fa09b..244d106 100644 Binary files a/assets/opensb/interface/opensb/voicechat/footer.png and b/assets/opensb/interface/opensb/voicechat/footer.png differ diff --git a/assets/opensb/interface/opensb/voicechat/header.png b/assets/opensb/interface/opensb/voicechat/header.png index ab6b123..01977d5 100644 Binary files a/assets/opensb/interface/opensb/voicechat/header.png and b/assets/opensb/interface/opensb/voicechat/header.png differ diff --git a/assets/opensb/interface/opensb/voicechat/indicator/back.png b/assets/opensb/interface/opensb/voicechat/indicator/back.png index 44b8147..0e31e86 100644 Binary files a/assets/opensb/interface/opensb/voicechat/indicator/back.png and b/assets/opensb/interface/opensb/voicechat/indicator/back.png differ diff --git a/assets/opensb/interface/opensb/voicechat/indicator/front.png b/assets/opensb/interface/opensb/voicechat/indicator/front.png index 3a8e511..0982dbd 100644 Binary files a/assets/opensb/interface/opensb/voicechat/indicator/front.png and b/assets/opensb/interface/opensb/voicechat/indicator/front.png differ diff --git a/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png b/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png index e444545..0a33038 100644 Binary files a/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png and b/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png differ diff --git a/assets/opensb/interface/opensb/voicechat/pushtotalkback.png b/assets/opensb/interface/opensb/voicechat/pushtotalkback.png index 7fc85c0..a30c306 100644 Binary files a/assets/opensb/interface/opensb/voicechat/pushtotalkback.png and b/assets/opensb/interface/opensb/voicechat/pushtotalkback.png differ diff --git a/assets/opensb/interface/optionsmenu/body_blank.png b/assets/opensb/interface/optionsmenu/body_blank.png index d770341..4127905 100644 Binary files a/assets/opensb/interface/optionsmenu/body_blank.png and b/assets/opensb/interface/optionsmenu/body_blank.png differ diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbutton.png b/assets/opensb/interface/optionsmenu/duocontrolsbutton.png index 5ae49fa..d491bd6 100644 Binary files a/assets/opensb/interface/optionsmenu/duocontrolsbutton.png and b/assets/opensb/interface/optionsmenu/duocontrolsbutton.png differ diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png b/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png index b43f732..6709e71 100644 Binary files a/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png and b/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png differ diff --git a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch index a169bef..4755b45 100644 --- a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch +++ b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch @@ -54,6 +54,24 @@ "instrumentSlider" : { "type" : "slider", "position" : [62, 158], "gridImage" : "/interface/optionsmenu/largeselection.png" }, "instrumentLabel" : { "type" : "label", "position" : [32, 158], "value" : "Tunes" }, - "instrumentValueLabel" : { "type" : "label", "position" : [192, 158], "hAnchor" : "mid", "value" : "Replace Me" } + "instrumentValueLabel" : { "type" : "label", "position" : [192, 158], "hAnchor" : "mid", "value" : "Replace Me" }, + + "headRotationLabel" : { + "type" : "label", + "position" : [25, 51], + "hAnchor" : "left", + "value" : "HEAD ROTATION" + }, + "headRotationCheckbox" : { + "type" : "button", + "pressedOffset" : [0, 0], + "position" : [104, 51], + "base" : "/interface/optionsmenu/checkboxnocheck.png", + "hover" : "/interface/optionsmenu/checkboxnocheckhover.png", + "baseImageChecked" : "/interface/optionsmenu/checkboxcheck.png", + "hoverImageChecked" : "/interface/optionsmenu/checkboxcheckhover.png", + "checkable" : true, + "checked" : true + } } } \ No newline at end of file diff --git a/assets/opensb/interface/optionsmenu/shine.png b/assets/opensb/interface/optionsmenu/shine.png index bbc7c5e..ad962fe 100644 Binary files a/assets/opensb/interface/optionsmenu/shine.png and b/assets/opensb/interface/optionsmenu/shine.png differ diff --git a/assets/opensb/interface/optionsmenu/tricontrolsbutton.png b/assets/opensb/interface/optionsmenu/tricontrolsbutton.png index 0c2ad7a..5d505aa 100644 Binary files a/assets/opensb/interface/optionsmenu/tricontrolsbutton.png and b/assets/opensb/interface/optionsmenu/tricontrolsbutton.png differ diff --git a/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png b/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png index 7bf9653..3147be6 100644 Binary files a/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png and b/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png differ diff --git a/assets/opensb/interface/title/barstound.png b/assets/opensb/interface/title/barstound.png index 6c17d8d..d6e14af 100644 Binary files a/assets/opensb/interface/title/barstound.png and b/assets/opensb/interface/title/barstound.png differ diff --git a/assets/opensb/interface/title/starbound.png b/assets/opensb/interface/title/starbound.png index 24ea4d4..33647cc 100644 Binary files a/assets/opensb/interface/title/starbound.png and b/assets/opensb/interface/title/starbound.png differ diff --git a/assets/opensb/interface/windowconfig/charselection.config.patch b/assets/opensb/interface/windowconfig/charselection.config.patch new file mode 100644 index 0000000..c904b44 --- /dev/null +++ b/assets/opensb/interface/windowconfig/charselection.config.patch @@ -0,0 +1,9 @@ +{ + "createCharButton" : { + "type" : "button", + "base" : "/interface/title/createcharacter.png", + "hover" : "/interface/title/createcharacterover.png", + "position" : [23, 241], + "pressedOffset" : [0, 0] + } +} diff --git a/assets/opensb/interface/windowconfig/crafting.config.patch b/assets/opensb/interface/windowconfig/crafting.config.patch index 0728507..9febcd4 100644 --- a/assets/opensb/interface/windowconfig/crafting.config.patch +++ b/assets/opensb/interface/windowconfig/crafting.config.patch @@ -4,7 +4,11 @@ // Disables the crafting timer if true. "disableTimer" : false, - // This is only used if the crafting timer is enabled. - // This is how many crafts are ran when the crafting timer wraps. - "craftCount" : 1 -} } \ No newline at end of file + // This is only used if the crafting timer is enabled. + // This is how many crafts are ran when the crafting timer wraps. + "craftCount" : 1, + + // This is how many of any item can be crafted at once. + // This is also used by merchants. + "maxSpinCount" : 9999 +} } diff --git a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua index 7d7ab44..b17053a 100644 --- a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua +++ b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua @@ -13,6 +13,12 @@ local function shift(thing, x, y) return thing end +local function moveto(thing, otherthing) + thing.position[1] = otherthing.position[1] + thing.position[2] = otherthing.position[2] + return thing +end + -- patch function, called by the game function patch(config) local layout = config.paneLayout @@ -41,10 +47,14 @@ function patch(config) -- Create hardware cursor toggle shift(clone(layout, "multiTextureLabel", "hardwareCursorLabel"), 98, -11).value = "HARDWARE CURSOR" shift(clone(layout, "multiTextureCheckbox", "hardwareCursorCheckbox"), 99, -11) + + -- Create shader menu button + shift(moveto(clone(layout, "accept", "showShadersMenu"), layout.interfaceScaleSlider), 112, -2).caption = "Shaders" + shift(layout.title, 0, 24) shift(layout.resLabel, 0, 28) shift(layout.resSlider, 0, 28) shift(layout.resValueLabel, 0, 28) return config -end \ No newline at end of file +end diff --git a/assets/opensb/interface/windowconfig/merchant.config.patch b/assets/opensb/interface/windowconfig/merchant.config.patch new file mode 100644 index 0000000..fd483c2 --- /dev/null +++ b/assets/opensb/interface/windowconfig/merchant.config.patch @@ -0,0 +1,12 @@ +[ + { + "op" : "test", + "path" : "/paneLayout/bgShine/position/1", + "value" : -12 + }, + { + "op" : "replace", + "path" : "/paneLayout/bgShine/position/1", + "value" : -19 + } +] diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/bsleeve.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/bsleeve.png new file mode 100644 index 0000000..7d52748 Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/bsleeve.png differ diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/chestf.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestf.png new file mode 100644 index 0000000..5779930 Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestf.png differ diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/chestm.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestm.png new file mode 100644 index 0000000..4073c18 Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestm.png differ diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/fsleeve.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/fsleeve.png new file mode 100644 index 0000000..db91be2 Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/fsleeve.png differ diff --git a/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png b/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png index 2ba0112..b110a66 100644 Binary files a/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png and b/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png differ diff --git a/assets/opensb/opensb/coconut.png b/assets/opensb/opensb/coconut.png index 3a5192b..265dfdc 100644 Binary files a/assets/opensb/opensb/coconut.png and b/assets/opensb/opensb/coconut.png differ diff --git a/assets/opensb/player.config.patch b/assets/opensb/player.config.patch index 5960507..bd5cbc8 100644 --- a/assets/opensb/player.config.patch +++ b/assets/opensb/player.config.patch @@ -12,5 +12,7 @@ "maxWireTrans" : 0.4 }, + "inventoryFilters" : { "default" : {} }, + "swapDance" : null // Set this to a valid dance to trigger on character swap. } \ No newline at end of file diff --git a/assets/opensb/rendering/sprites/error_left.png b/assets/opensb/rendering/sprites/error_left.png index 2f4ec6f..a2cf6b6 100644 Binary files a/assets/opensb/rendering/sprites/error_left.png and b/assets/opensb/rendering/sprites/error_left.png differ diff --git a/assets/opensb/rendering/sprites/error_right.png b/assets/opensb/rendering/sprites/error_right.png index 1fadd17..d7c8820 100644 Binary files a/assets/opensb/rendering/sprites/error_right.png and b/assets/opensb/rendering/sprites/error_right.png differ diff --git a/assets/opensb/scripts/opensb/player/commands.lua b/assets/opensb/scripts/opensb/player/commands.lua index 2b42f48..9696e0d 100644 --- a/assets/opensb/scripts/opensb/player/commands.lua +++ b/assets/opensb/scripts/opensb/player/commands.lua @@ -2,7 +2,7 @@ local module = {} modules.commands = module local commands = {} -local function command(name, func) +local function register(name, func) commands[name] = func end @@ -12,8 +12,7 @@ function module.init() end end - -command("run", function(src) +register("run", function(src) local success, result = pcall(loadstring, src, "/run") if not success then return "^#f00;compile error: " .. result @@ -35,4 +34,6 @@ command("run", function(src) end end end -end) \ No newline at end of file +end) + +module.register = register \ No newline at end of file diff --git a/assets/opensb/scripts/opensb/player/copy_paste.lua b/assets/opensb/scripts/opensb/player/copy_paste.lua new file mode 100644 index 0000000..2a15af4 --- /dev/null +++ b/assets/opensb/scripts/opensb/player/copy_paste.lua @@ -0,0 +1,81 @@ +local module = {} +modules.copy_paste = module + +local commands = modules.commands +local function getItemName(item) + return item.parameters.shortdescription + or root.itemConfig(item.name).config.shortdescription + or item.name +end + +local function popupError(prefix, msg) + sb.logError("%s: %s", prefix, msg) + msg = #msg > 80 and msg:sub(1, 80) .. "..." or msg + local findNewLine = msg:find("\n", 1, true) + interface.queueMessage("^#f00;error:^reset; " .. (findNewLine and msg:sub(1, findNewLine - 1) or msg), 7) +end + +local function getClipboardText() + local text = clipboard.hasText() and clipboard.getText() + return text and text:sub(1, 1) == "{" and text or nil +end + +local function copyItem() + local item = player.swapSlotItem() or player.primaryHandItem() or player.altHandItem() + if not item then return end + + clipboard.setText(sb.printJson(item, 2)) + local message = string.format("Copied ^cyan;%s^reset; to clipboard", getItemName(item)) + interface.queueMessage(message, 4, 0.5) +end + +local function pasteItem() + if player.swapSlotItem() then return end + local data = getClipboardText() + if not data then return end + + local success, result = pcall(sb.parseJson, data) + if not success then + popupError("Error parsing clipboard item", result) + else + local success, err = pcall(player.setSwapSlotItem, result) + if not success then popupError("Error loading clipboard item", err) + else + local message = string.format("Pasted ^cyan;%s^reset; from clipboard", getItemName(result)) + interface.queueMessage(message, 4, 0.5) + end + end +end + +function module.update() + if input.bindDown("opensb", "editingCopyItemJson") then + copyItem() + end + + if input.bindDown("opensb", "editingPasteItemJson") then + pasteItem() + end +end + +commands.register("exportplayer", function() + if not clipboard.available() then + return "Clipboard unavailable" + end + local success, text = pcall(sb.printJson, player.save(), 2) + if not success then return text end + clipboard.setText(text) + return "Exported player to clipboard" +end) + +commands.register("importplayer", function() + local data = getClipboardText() + if not data then return "Clipboard does not contain JSON" end + + local success, result = pcall(sb.parseJson, data) + if not success then + return "Error parsing player: " .. result + else + success, result = pcall(player.load, result) + return success and "Loaded player from clipboard" or "Error loading player: " .. result + end +end) \ No newline at end of file diff --git a/assets/opensb/scripts/opensb/player/player.lua b/assets/opensb/scripts/opensb/player/player.lua index 05cbfb9..7670595 100644 --- a/assets/opensb/scripts/opensb/player/player.lua +++ b/assets/opensb/scripts/opensb/player/player.lua @@ -1,2 +1,2 @@ require "/scripts/opensb/util/modules.lua" -modules("/scripts/opensb/player/", {"commands"}) \ No newline at end of file +modules("/scripts/opensb/player/", {"commands", "copy_paste"}) \ No newline at end of file diff --git a/assets/opensb/scripts/opensb/universeclient/loadconfig.lua b/assets/opensb/scripts/opensb/universeclient/loadconfig.lua new file mode 100644 index 0000000..2bb3ef8 --- /dev/null +++ b/assets/opensb/scripts/opensb/universeclient/loadconfig.lua @@ -0,0 +1,22 @@ +-- Meant to manage loading various miscellaneous things from configuration, such as shader parameters. + +local module = {} +modules.config_loader = module + +function module.init() + local shaderConfig = root.getConfiguration("postProcessGroups") or {} + local postProcessGroups = renderer.postProcessGroups() + local changes = false + for k,v in next, shaderConfig do + local group = postProcessGroups[k] + if v.parameters then + for k2,v2 in next, group.parameters do + if v.parameters[k2] ~= nil then + for _,e in next, v2.effects do + renderer.setEffectParameter(e,k2,v.parameters[k2]) + end + end + end + end + end +end diff --git a/assets/opensb/scripts/opensb/universeclient/universeclient.lua b/assets/opensb/scripts/opensb/universeclient/universeclient.lua index cad342b..a534c25 100644 --- a/assets/opensb/scripts/opensb/universeclient/universeclient.lua +++ b/assets/opensb/scripts/opensb/universeclient/universeclient.lua @@ -1,2 +1,2 @@ require "/scripts/opensb/util/modules.lua" -modules("/scripts/opensb/universeclient/", {"voicemanager"}) \ No newline at end of file +modules("/scripts/opensb/universeclient/", {"voicemanager","loadconfig"}) diff --git a/assets/opensb/sky/orbitals/orangestar.png b/assets/opensb/sky/orbitals/orangestar.png index 788a070..b2ea72e 100644 Binary files a/assets/opensb/sky/orbitals/orangestar.png and b/assets/opensb/sky/orbitals/orangestar.png differ diff --git a/assets/opensb/sky/orbitals/redstar.png b/assets/opensb/sky/orbitals/redstar.png index 7f6837b..6adb1a9 100644 Binary files a/assets/opensb/sky/orbitals/redstar.png and b/assets/opensb/sky/orbitals/redstar.png differ diff --git a/assets/opensb/sky/orbitals/whitestar.png b/assets/opensb/sky/orbitals/whitestar.png index f4eb5a2..87e11eb 100644 Binary files a/assets/opensb/sky/orbitals/whitestar.png and b/assets/opensb/sky/orbitals/whitestar.png differ diff --git a/assets/opensb/sky/orbitals/yellowstar.png b/assets/opensb/sky/orbitals/yellowstar.png index 596d8ef..8979000 100644 Binary files a/assets/opensb/sky/orbitals/yellowstar.png and b/assets/opensb/sky/orbitals/yellowstar.png differ diff --git a/assets/opensb/tiles/shadows.png b/assets/opensb/tiles/shadows.png index 28bb7a3..af4df9e 100644 Binary files a/assets/opensb/tiles/shadows.png and b/assets/opensb/tiles/shadows.png differ diff --git a/doc/lua/openstarbound.md b/doc/lua/openstarbound.md index ad8483d..f4eff06 100644 --- a/doc/lua/openstarbound.md +++ b/doc/lua/openstarbound.md @@ -76,9 +76,13 @@ Returns the asset source path of an asset, or nil if the asset doesn't exist. If Without metadata: Returns an array with all the asset source paths. With metadata: Returns a table, key/value being source path/metadata. -#### `?` root.assetImage(`String` image) +#### `Image` root.assetImage(`String` image) -*TODO* +Returns an image. + +#### `Json` root.assetFrames(`String` imagePath) + +Returns an array containing a `file` (the frames file used for the image) and `frames` (the frame data of the image). #### `JsonArray` root.assetPatches(`String` asset) @@ -112,6 +116,131 @@ Sets a configuration value in `/storage/starbound.config` by path. Returns all recipes. +--- +# Interface + +The interface table contains bindings which allow scripts to display a message at the bottom of the screen, among other things. + +#### `void` interface.queueMessage(`String` message, [`float` cooldown, [`float` springState]]) + +Queues a message popup at the bottom of the screen with an optional **cooldown** and **springState**. + +#### `void` interface.setHudVisible(`bool` visible) + +Sets the HUD's visibility. + +#### `bool` interface.hudVisible() + +Returns the HUD's visibility. + +#### `PaneId` interface.bindRegisteredPane(`string` paneName) +Binds a registered pane (defined in `/source/frontend/StarMainInterfaceTypes`) to a Lua value, which can then call widget functions on that pane. +
Panes +EscapeDialog
+Inventory
+Codex
+Cockpit
+Tech
+Songbook
+Ai
+Popup
+Confirmation
+JoinRequest
+Options
+QuestLog
+ActionBar
+TeamBar
+StatusPane
+Chat
+WireInterface
+PlanetText
+RadioMessagePopup
+CraftingPlain
+QuestTracker
+MmUpgrade
+Collections
+
+ +#### `void` interface.displayRegisteredPane(`string` paneName) +Displays a registered pane. + + +#### `?` interface.bindCanvas() +TODO + + +#### `int` interface.scale() +Returns the scale used for interfaces. + + +--- + +# World + +The world table now contains extra bindings. + +--- + +#### `bool` world.isServer() + +Returns whether the script is running on the server or client. + +--- + +#### `bool` world.isClient() + +Returns whether the script is running on the server or client. + +--- + +The following additional world bindings are available only for scripts running on the client. + +--- + +#### `entityId` world.mainPlayer() + +Returns the entity ID of the player hosting the world. + +--- + +#### `Vec2F` world.entityAimPosition(`entityId` entityId) + +Returns the current cursor aim position of the specified entity. + +--- + +#### `bool` world.inWorld() + +Returns whether any players are in the world. + +--- + +The following additional world bindings are available only for scripts running on the server. + +--- + +#### `void` world.setExpiryTime(`float` expiryTime) + +Sets the amount of time to persist a ephemeral world when it is inactive. + +--- + +#### `string` world.id() + +Returns a `String` representation of the world's id. + +--- + +#### `?` world.callScriptContext(`?` ?) + +TODO + +--- + +#### `?` world.sendPacket(`?` ?) + +? + --- # Player @@ -392,9 +521,9 @@ Returns whether the specified item can enter the specified item bag. Returns the contents of the specified action bar link slot's specified hand. -#### `bool` player.setActionBarSlotLink(`String` itemBagName, `ItemDescriptor` item) +#### `bool` player.setActionBarSlotLink(`int` slot, `String` hand, `ItemSlot` itemSlot) -Returns whether the specified item can enter the specified item bag. +Links the specified slot's hand to the specified itemSlot. --- @@ -413,3 +542,64 @@ Sets the player's interact radius. This does not persist upon returning to the m Returns all the recipes the player can craft with their currently held items and currencies. --- +#### `String` player.currentState() + +Returns the player's current movement state. + +
Player States +idle
+walk
+run
+jump
+fall
+swim
+swimIdle
+lounge
+crouch
+teleportIn
+teleportOut
+
+--- + +#### `List` player.teamMembers() + +Returns an array, each entry being a table with `name`, `uuid`, `entity`, `healthPercentage` and `energyPercentage` + +--- + +# Renderer + +The new renderer table is accessible from almost every clientside script and allows configuring shaders. + +--- + +#### `void` renderer.setPostProcessGroupEnabled(String group, bool enabled, [bool save]) + +Enables or disables a post process shader group. If save is true, this change is saved to configuration as well. + +--- + +#### `bool` renderer.postProcessGroupEnabled(String group) + +Returns true if the specified post process group is enabled. + +--- + +#### `Json` renderer.postProcessGroups() + +Returns every post process group. Identical to grabbing them from client.config with root.assetJson. + +--- + +#### `Json` renderer.setEffectParameter(String effectName, String parameterName, RenderEffectParameter value) + +Sets the specified scriptable parameter of the specified shader effect to the provided value. +This is accessed from the shader as a uniform and must be defined in the effect's configuration. + +--- + +#### `RenderEffectParameter` renderer.getEffectParameter(String effectName, String parameterName) + +Returns the specified scriptable parameter of the specified shader effect. + +--- diff --git a/scripts/ci/linux/assemble.sh b/scripts/ci/linux/assemble.sh index dbdfbe0..06a25aa 100755 --- a/scripts/ci/linux/assemble.sh +++ b/scripts/ci/linux/assemble.sh @@ -51,6 +51,5 @@ cp \ scripts/steam_appid.txt \ server_distribution/linux/ -tar -cvf dist.tar dist tar -cvf client.tar client_distribution tar -cvf server.tar server_distribution diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d13d59d..ad3026b 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -80,6 +80,8 @@ if(NOT DEFINED STAR_SYSTEM) set(STAR_SYSTEM "linux") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") set(STAR_SYSTEM "freebsd") + elseif(${CMAKE_SYSTEM_NAME} STREQUAL "NetBSD") + set(STAR_SYSTEM "netbsd") elseif(UNIX) set(STAR_SYSTEM "unix") else() @@ -208,6 +210,8 @@ elseif(STAR_SYSTEM STREQUAL "linux") set_flag(STAR_SYSTEM_LINUX) elseif(STAR_SYSTEM STREQUAL "freebsd") set_flag(STAR_SYSTEM_FREEBSD) +elseif(STAR_SYSTEM STREQUAL "netbsd") + set_flag(STAR_SYSTEM_NETBSD) endif() if(STAR_SYSTEM_FAMILY STREQUAL "windows") @@ -281,14 +285,14 @@ if(STAR_COMPILER_GNU) set(CMAKE_C_FLAGS_DEBUG "-g -Og") set(CMAKE_CXX_FLAGS_DEBUG "-g -Og") - set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast") - set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast") + set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math") + set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math") - set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast") - set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast") + set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math") + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math") set(CMAKE_SKIP_BUILD_RPATH TRUE) @@ -312,14 +316,14 @@ elseif(STAR_COMPILER_CLANG) set(CMAKE_C_FLAGS_DEBUG "-g") set(CMAKE_CXX_FLAGS_DEBUG "-g") - set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast") - set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast") + set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math") + set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math") - set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast") - set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast") + set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math") + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math") set(CMAKE_SKIP_BUILD_RPATH TRUE) @@ -445,6 +449,10 @@ elseif(STAR_SYSTEM_FREEBSD) set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -lrt") set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -lrt") +elseif(STAR_SYSTEM_NETBSD) + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -lrt -lexecinfo") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -lrt -lexecinfo") + endif() # Find all required external libraries, based on build settings... diff --git a/source/CMakePresets.json b/source/CMakePresets.json index c19adae..46ebbb7 100644 --- a/source/CMakePresets.json +++ b/source/CMakePresets.json @@ -46,12 +46,14 @@ "displayName": "Linux x64", "binaryDir": "${sourceParentDir}/build/linux-release", "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", "VCPKG_TARGET_TRIPLET": "x64-linux-mixed", "CMAKE_INCLUDE_PATH": "${sourceParentDir}/lib/linux/include", "CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/linux", "STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX": true, - "STAR_USE_JEMALLOC": true + "STAR_USE_JEMALLOC": false }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index 6b0ca4c..557e02d 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -314,10 +314,12 @@ public: int height; SDL_GetWindowSize(m_sdlWindow, &width, &height); m_windowSize = Vec2U(width, height); - + +#ifdef __APPLE__ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); +#endif m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow); if (!m_sdlGlContext) diff --git a/source/application/StarRenderer.hpp b/source/application/StarRenderer.hpp index 2a156f7..2b106ff 100644 --- a/source/application/StarRenderer.hpp +++ b/source/application/StarRenderer.hpp @@ -120,7 +120,7 @@ public: virtual void set(List& primitives) = 0; }; -typedef Variant RenderEffectParameter; +typedef Variant RenderEffectParameter; class Renderer { public: @@ -141,6 +141,9 @@ public: // The effect config will specify named parameters and textures which can be // set here. virtual void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) = 0; + virtual void setEffectScriptableParameter(String const& effectName, String const& parameterName, RenderEffectParameter const& parameter) = 0; + virtual Maybe getEffectScriptableParameter(String const& effectName, String const& parameterName) = 0; + virtual Maybe getEffectScriptableParameterType(String const& effectName, String const& parameterName) = 0; virtual void setEffectTexture(String const& textureName, ImageView const& image) = 0; virtual bool switchEffectConfig(String const& name) = 0; diff --git a/source/application/StarRenderer_opengl.cpp b/source/application/StarRenderer_opengl.cpp index 0c9e7ec..ceb945d 100644 --- a/source/application/StarRenderer_opengl.cpp +++ b/source/application/StarRenderer_opengl.cpp @@ -107,8 +107,10 @@ OpenGlRenderer::OpenGlRenderer() { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); - //glEnable(GL_DEBUG_OUTPUT); - //glDebugMessageCallback(GlMessageCallback, this); + if (GLEW_VERSION_4_3) { + //glEnable(GL_DEBUG_OUTPUT); + //glDebugMessageCallback(GlMessageCallback, this); + } m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32), TextureAddressing::Clamp, @@ -306,21 +308,39 @@ void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConf throw RendererException::format("Unrecognized effect parameter type '{}'", type); } - effect.parameters[p.first] = effectParameter; - - if (Json def = p.second.get("default", {})) { - if (type == "bool") { - setEffectParameter(p.first, def.toBool()); - } else if (type == "int") { - setEffectParameter(p.first, (int)def.toInt()); - } else if (type == "float") { - setEffectParameter(p.first, def.toFloat()); - } else if (type == "vec2") { - setEffectParameter(p.first, jsonToVec2F(def)); - } else if (type == "vec3") { - setEffectParameter(p.first, jsonToVec3F(def)); - } else if (type == "vec4") { - setEffectParameter(p.first, jsonToVec4F(def)); + if (p.second.getBool("scriptable",false)) { + if (Json def = p.second.get("default", {})) { + if (type == "bool") { + effectParameter.parameterValue = (RenderEffectParameter)def.toBool(); + } else if (type == "int") { + effectParameter.parameterValue = (RenderEffectParameter)(int)def.toInt(); + } else if (type == "float") { + effectParameter.parameterValue = (RenderEffectParameter)def.toFloat(); + } else if (type == "vec2") { + effectParameter.parameterValue = (RenderEffectParameter)jsonToVec2F(def); + } else if (type == "vec3") { + effectParameter.parameterValue = (RenderEffectParameter)jsonToVec3F(def); + } else if (type == "vec4") { + effectParameter.parameterValue = (RenderEffectParameter)jsonToVec4F(def); + } + } + effect.scriptables[p.first] = effectParameter; + } else { + effect.parameters[p.first] = effectParameter; + if (Json def = p.second.get("default", {})) { + if (type == "bool") { + setEffectParameter(p.first, def.toBool()); + } else if (type == "int") { + setEffectParameter(p.first, (int)def.toInt()); + } else if (type == "float") { + setEffectParameter(p.first, def.toFloat()); + } else if (type == "vec2") { + setEffectParameter(p.first, jsonToVec2F(def)); + } else if (type == "vec3") { + setEffectParameter(p.first, jsonToVec3F(def)); + } else if (type == "vec4") { + setEffectParameter(p.first, jsonToVec4F(def)); + } } } } @@ -382,6 +402,50 @@ void OpenGlRenderer::setEffectParameter(String const& parameterName, RenderEffec ptr->parameterValue = value; } +void OpenGlRenderer::setEffectScriptableParameter(String const& effectName, String const& parameterName, RenderEffectParameter const& value) { + auto find = m_effects.find(effectName); + if (find == m_effects.end()) + return; + + Effect& effect = find->second; + + auto ptr = effect.scriptables.ptr(parameterName); + if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value)) + return; + + if (ptr->parameterType != value.typeIndex()) + throw RendererException::format("OpenGlRenderer::setEffectScriptableParameter '{}' parameter type mismatch", parameterName); + + ptr->parameterValue = value; +} + +Maybe OpenGlRenderer::getEffectScriptableParameter(String const& effectName, String const& parameterName) { + auto find = m_effects.find(effectName); + if (find == m_effects.end()) + return {}; + + Effect& effect = find->second; + + auto ptr = effect.scriptables.ptr(parameterName); + if (!ptr) + return {}; + + return ptr->parameterValue; +} +Maybe OpenGlRenderer::getEffectScriptableParameterType(String const& effectName, String const& parameterName) { + auto find = m_effects.find(effectName); + if (find == m_effects.end()) + return {}; + + Effect& effect = find->second; + + auto ptr = effect.scriptables.ptr(parameterName); + if (!ptr) + return {}; + + return ptr->parameterType; +} + void OpenGlRenderer::setEffectTexture(String const& textureName, ImageView const& image) { auto ptr = m_currentEffect->textures.ptr(textureName); if (!ptr) @@ -1061,6 +1125,26 @@ void OpenGlRenderer::setupGlUniforms(Effect& effect, Vec2U screenSize) { } glUniform2f(m_screenSizeUniform, screenSize[0], screenSize[1]); + + for (auto& param : effect.scriptables) { + auto ptr = ¶m.second; + auto mvalue = ptr->parameterValue; + if (mvalue) { + RenderEffectParameter value = mvalue.value(); + if (auto v = value.ptr()) + glUniform1i(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform1i(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform1f(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]); + else if (auto v = value.ptr()) + glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]); + else if (auto v = value.ptr()) + glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]); + } + } } RefPtr OpenGlRenderer::getGlFrameBuffer(String const& id) { diff --git a/source/application/StarRenderer_opengl.hpp b/source/application/StarRenderer_opengl.hpp index 36b8354..79e5400 100644 --- a/source/application/StarRenderer_opengl.hpp +++ b/source/application/StarRenderer_opengl.hpp @@ -25,6 +25,9 @@ public: void loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) override; void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override; + void setEffectScriptableParameter(String const& effectName, String const& parameterName, RenderEffectParameter const& parameter) override; + Maybe getEffectScriptableParameter(String const& effectName, String const& parameterName) override; + Maybe getEffectScriptableParameterType(String const& effectName, String const& parameterName) override; void setEffectTexture(String const& textureName, ImageView const& image) override; void setScissorRect(Maybe const& scissorRect) override; @@ -191,6 +194,7 @@ private: GLuint program = 0; Json config; StringMap parameters; + StringMap scriptables; // scriptable parameters which can be changed when the effect is not loaded StringMap textures; StringMap attributes; diff --git a/source/base/CMakeLists.txt b/source/base/CMakeLists.txt index e813e45..e77d9ff 100644 --- a/source/base/CMakeLists.txt +++ b/source/base/CMakeLists.txt @@ -18,7 +18,6 @@ SET (star_base_HEADERS StarMixer.hpp StarPackedAssetSource.hpp StarRootBase.hpp - StarVersion.hpp StarVersionOptionParser.hpp StarWorldGeometry.hpp scripting/StarImageLuaBindings.hpp @@ -40,8 +39,7 @@ SET (star_base_SOURCES scripting/StarImageLuaBindings.cpp ) -CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) -ADD_LIBRARY (star_base OBJECT ${star_base_SOURCES} ${star_base_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) +ADD_LIBRARY (star_base OBJECT ${star_base_SOURCES} ${star_base_HEADERS}) IF(STAR_PRECOMPILED_HEADERS) TARGET_PRECOMPILE_HEADERS (star_base REUSE_FROM star_core) diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index a188291..ecd2068 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -68,7 +68,7 @@ static void validatePath(AssetPath const& components, bool canContainSubPath, bo throw AssetException::format("Path '{}' cannot contain directives", components); } -static void validatePath(StringView const& path, bool canContainSubPath, bool canContainDirectives) { +static void validatePath(StringView path, bool canContainSubPath, bool canContainDirectives) { std::string_view const& str = path.utf8(); size_t end = str.find_first_of(":?"); @@ -102,6 +102,14 @@ Maybe FramesSpecification::getRect(String const& frame) const { } } +Json FramesSpecification::toJson() const { + return JsonObject{ + {"aliases", jsonFromMap(aliases)}, + {"frames", jsonFromMapV(frames, jsonFromRectU)}, + {"file", framesFile} + }; +} + Assets::Assets(Settings settings, StringList assetSources) { const char* AssetsPatchSuffix = ".patch"; const char* AssetsPatchListSuffix = ".patchlist"; @@ -139,6 +147,12 @@ Assets::Assets(Settings settings, StringList assetSources) { return *assetImage; }); + callbacks.registerCallback("frames", [this](String const& path) -> Json { + if (auto frames = imageFrames(path)) + return frames->toJson(); + return Json(); + }); + callbacks.registerCallback("scan", [this](Maybe const& a, Maybe const& b) -> StringList { return b ? scan(a.value(), *b) : scan(a.value()); }); diff --git a/source/base/StarAssets.hpp b/source/base/StarAssets.hpp index 6b454e8..e7b8611 100644 --- a/source/base/StarAssets.hpp +++ b/source/base/StarAssets.hpp @@ -27,7 +27,8 @@ struct FramesSpecification { // Get the target sub-rect of a given frame name (which can be an alias). // Returns nothing if the frame name is not found. Maybe getRect(String const& frame) const; - + // Converts to Json. + Json toJson() const; // The full path to the .frames file from which this was loaded. String framesFile; // Named sub-frames diff --git a/source/client/CMakeLists.txt b/source/client/CMakeLists.txt index 57b8231..474f2f8 100644 --- a/source/client/CMakeLists.txt +++ b/source/client/CMakeLists.txt @@ -12,10 +12,12 @@ INCLUDE_DIRECTORIES ( SET (star_client_HEADERS StarClientApplication.hpp + StarRenderingLuaBindings.hpp ) SET (star_client_SOURCES StarClientApplication.cpp + StarRenderingLuaBindings.cpp ) IF (STAR_SYSTEM_WINDOWS) @@ -29,12 +31,16 @@ ADD_EXECUTABLE (starbound WIN32 $ $ $ $ ${star_client_HEADERS} ${star_client_SOURCES} ${star_client_RESOURCES}) -IF(STAR_PRECOMPILED_HEADERS) +IF (STAR_PRECOMPILED_HEADERS) TARGET_PRECOMPILE_HEADERS (starbound REUSE_FROM star_core) ENDIF() -IF(UNIX) - set_target_properties (starbound PROPERTIES LINK_FLAGS "-Wl,-rpath,'$ORIGIN'") +IF (UNIX) + SET_TARGET_PROPERTIES (starbound PROPERTIES LINK_FLAGS "-Wl,-rpath,'$ORIGIN'") ENDIF() -TARGET_LINK_LIBRARIES (starbound ${STAR_EXT_LIBS} ${STAR_EXT_GUI_LIBS}) \ No newline at end of file +IF (STAR_SYSTEM_MACOS) + SET_TARGET_PROPERTIES (starbound PROPERTIES XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES) +ENDIF() + +TARGET_LINK_LIBRARIES (starbound ${STAR_EXT_LIBS} ${STAR_EXT_GUI_LIBS}) diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 5f0c22e..09ebf0c 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -22,7 +22,9 @@ #include "StarInterfaceLuaBindings.hpp" #include "StarInputLuaBindings.hpp" #include "StarVoiceLuaBindings.hpp" +#include "StarCameraLuaBindings.hpp" #include "StarClipboardLuaBindings.hpp" +#include "StarRenderingLuaBindings.hpp" #if defined STAR_SYSTEM_WINDOWS #include @@ -404,10 +406,12 @@ void ClientApplication::render() { auto size = Vec2F(renderer->screenSize()); auto quad = renderFlatRect(RectF::withSize(size / -2, size), Vec4B::filled(0), 0.0f); for (auto& layer : m_postProcessLayers) { - for (unsigned i = 0; i < layer.passes; i++) { - for (auto& effect : layer.effects) { - renderer->switchEffectConfig(effect); - renderer->render(quad); + if (layer.group ? layer.group->enabled : true) { + for (unsigned i = 0; i < layer.passes; i++) { + for (auto& effect : layer.effects) { + renderer->switchEffectConfig(effect); + renderer->render(quad); + } } } } @@ -433,6 +437,8 @@ void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) { } } +auto postProcessGroupsRoot = "postProcessGroups"; + void ClientApplication::renderReload() { auto assets = m_root->assets(); auto renderer = Application::renderer(); @@ -463,18 +469,55 @@ void ClientApplication::renderReload() { loadEffectConfig("world"); + // define post process groups and set them to be enabled/disabled based on config + + auto config = m_root->configuration(); + if (!config->get(postProcessGroupsRoot).isType(Json::Type::Object)) + config->set(postProcessGroupsRoot, JsonObject()); + auto groupsConfig = config->get(postProcessGroupsRoot); + + m_postProcessGroups.clear(); + auto postProcessGroups = assets->json("/client.config:postProcessGroups").toObject(); + for (auto& pair : postProcessGroups) { + auto name = pair.first; + auto groupConfig = groupsConfig.opt(name); + auto def = pair.second.getBool("enabledDefault",true); + if (!groupConfig) + config->setPath(strf("{}.{}", postProcessGroupsRoot, name),JsonObject()); + m_postProcessGroups.add(name,PostProcessGroup{ groupConfig ? groupConfig.value().getBool("enabled", def) : def }); + } + + // define post process layers and optionally assign them to groups m_postProcessLayers.clear(); auto postProcessLayers = assets->json("/client.config:postProcessLayers").toArray(); for (auto& layer : postProcessLayers) { auto effects = jsonToStringList(layer.getArray("effects")); for (auto& effect : effects) loadEffectConfig(effect); - m_postProcessLayers.append(PostProcessLayer{ std::move(effects), (unsigned)layer.getUInt("passes", 1) }); + PostProcessGroup* group = nullptr; + auto gname = layer.optString("group"); + if (gname) { + group = &m_postProcessGroups.get(gname.value()); + } + m_postProcessLayers.append(PostProcessLayer{ std::move(effects), (unsigned)layer.getUInt("passes", 1), group }); } loadEffectConfig("interface"); } +void ClientApplication::setPostProcessGroupEnabled(String group, bool enabled, Maybe save) { + m_postProcessGroups.get(group).enabled = enabled; + if (save && save.value()) + m_root->configuration()->setPath(strf("{}.{}.enabled", postProcessGroupsRoot, group),enabled); +} +bool ClientApplication::postProcessGroupEnabled(String group) { + return m_postProcessGroups.get(group).enabled; +} + +Json ClientApplication::postProcessGroups() { + return m_root->assets()->json("/client.config:postProcessGroups"); +} + void ClientApplication::changeState(MainAppState newState) { MainAppState oldState = m_state; m_state = newState; @@ -541,8 +584,11 @@ void ClientApplication::changeState(MainAppState newState) { m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks()); - if (!m_root->configuration()->get("safeScripts").toBool()) - m_universeClient->setLuaCallbacks("clipboard", LuaBindings::makeClipboardCallbacks(appController())); + m_universeClient->setLuaCallbacks("camera", LuaBindings::makeCameraCallbacks(&m_worldPainter->camera())); + m_universeClient->setLuaCallbacks("renderer", LuaBindings::makeRenderingCallbacks(this)); + + Json alwaysAllow = m_root->configuration()->getPath("safe.alwaysAllowClipboard"); + m_universeClient->setLuaCallbacks("clipboard", LuaBindings::makeClipboardCallbacks(appController(), alwaysAllow && alwaysAllow.toBool())); auto heldScriptPanes = make_shared>(); @@ -565,7 +611,7 @@ void ClientApplication::changeState(MainAppState newState) { }; m_mainMixer->setUniverseClient(m_universeClient); - m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer()); + m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer(), m_universeClient); if (auto renderer = Application::renderer()) m_titleScreen->renderInit(renderer); } @@ -682,8 +728,9 @@ void ClientApplication::changeState(MainAppState newState) { m_mainInterface = make_shared(m_universeClient, m_worldPainter, m_cinematicOverlay); m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get())); m_universeClient->setLuaCallbacks("chat", LuaBindings::makeChatCallbacks(m_mainInterface.get(), m_universeClient.get())); - m_universeClient->startLua(); + m_mainInterface->displayDefaultPanes(); + m_universeClient->startLua(); m_mainMixer->setWorldPainter(m_worldPainter); if (auto renderer = Application::renderer()) { diff --git a/source/client/StarClientApplication.hpp b/source/client/StarClientApplication.hpp index 840dc9a..323f2b7 100644 --- a/source/client/StarClientApplication.hpp +++ b/source/client/StarClientApplication.hpp @@ -18,6 +18,11 @@ STAR_CLASS(Input); STAR_CLASS(Voice); class ClientApplication : public Application { +public: + void setPostProcessGroupEnabled(String group, bool enabled, Maybe save); + bool postProcessGroupEnabled(String group); + Json postProcessGroups(); + protected: virtual void startup(StringList const& cmdLineArgs) override; virtual void shutdown() override; @@ -53,9 +58,14 @@ private: String password; }; + struct PostProcessGroup { + bool enabled; + }; + struct PostProcessLayer { List effects; unsigned passes; + PostProcessGroup* group; }; void renderReload(); @@ -104,6 +114,7 @@ private: WorldRenderData m_renderData; MainInterfacePtr m_mainInterface; + StringMap m_postProcessGroups; List m_postProcessLayers; // Valid if main app state == SinglePlayer diff --git a/source/client/StarRenderingLuaBindings.cpp b/source/client/StarRenderingLuaBindings.cpp new file mode 100644 index 0000000..39dc1a1 --- /dev/null +++ b/source/client/StarRenderingLuaBindings.cpp @@ -0,0 +1,45 @@ +#include "StarRenderingLuaBindings.hpp" +#include "StarJsonExtra.hpp" +#include "StarLuaConverters.hpp" +#include "StarClientApplication.hpp" +#include "StarRenderer.hpp" + +namespace Star { + +LuaCallbacks LuaBindings::makeRenderingCallbacks(ClientApplication* app) { + LuaCallbacks callbacks; + + // if the last argument is defined and true, this change will also be saved to starbound.config and read on next game start, use for things such as an interface that does this + callbacks.registerCallbackWithSignature>("setPostProcessGroupEnabled", bind(mem_fn(&ClientApplication::setPostProcessGroupEnabled), app, _1, _2, _3)); + callbacks.registerCallbackWithSignature("postProcessGroupEnabled", bind(mem_fn(&ClientApplication::postProcessGroupEnabled), app, _1)); + + + // not entirely necessary (root.assetJson can achieve the same purpose) but may as well + callbacks.registerCallbackWithSignature("postProcessGroups", bind(mem_fn(&ClientApplication::postProcessGroups), app)); + + // typedef Variant RenderEffectParameter; + // feel free to change this if there's a better way to do this + // specifically checks if the effect parameter is an int since Lua prefers converting the values to floats + callbacks.registerCallback("setEffectParameter", [app](String const& effectName, String const& effectParameter, RenderEffectParameter const& value) { + auto renderer = app->renderer(); + auto mtype = renderer->getEffectScriptableParameterType(effectName, effectParameter); + if (mtype) { + auto type = mtype.value(); + if (type == 1 && value.is()) { + renderer->setEffectScriptableParameter(effectName, effectParameter, (int)value.get()); + } else { + renderer->setEffectScriptableParameter(effectName, effectParameter, value); + } + } + }); + + callbacks.registerCallback("getEffectParameter", [app](String const& effectName, String const& effectParameter) { + auto renderer = app->renderer(); + return renderer->getEffectScriptableParameter(effectName, effectParameter); + }); + + return callbacks; +} + + +} diff --git a/source/client/StarRenderingLuaBindings.hpp b/source/client/StarRenderingLuaBindings.hpp new file mode 100644 index 0000000..68e709f --- /dev/null +++ b/source/client/StarRenderingLuaBindings.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "StarLua.hpp" + +namespace Star { + +STAR_CLASS(ClientApplication); + +namespace LuaBindings { + LuaCallbacks makeRenderingCallbacks(ClientApplication* app); +} + +} diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index fb31fca..3d00baf 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -41,6 +41,7 @@ SET (star_core_HEADERS StarIdMap.hpp StarImage.hpp StarImageProcessing.hpp + StarImageScaling.hpp StarInputEvent.hpp StarInterpolation.hpp StarRefPtr.hpp @@ -70,10 +71,12 @@ SET (star_core_HEADERS StarMultiArray.hpp StarMultiArrayInterpolator.hpp StarMultiTable.hpp + StarNetCompatibility.hpp StarNetElement.hpp StarNetElementBasicFields.hpp StarNetElementContainers.hpp StarNetElementDynamicGroup.hpp + StarNetElementExt.hpp StarNetElementFloatFields.hpp StarNetElementGroup.hpp StarNetElementSignal.hpp @@ -122,6 +125,7 @@ SET (star_core_HEADERS StarUnicode.hpp StarUuid.hpp StarVector.hpp + StarVersion.hpp StarVlqEncoding.hpp StarWeightedPool.hpp StarWorkerPool.hpp @@ -149,6 +153,7 @@ SET (star_core_SOURCES StarIODevice.cpp StarImage.cpp StarImageProcessing.cpp + StarImageScaling.cpp StarInputEvent.cpp StarJson.cpp StarJsonBuilder.cpp @@ -157,11 +162,13 @@ SET (star_core_SOURCES StarJsonPatch.cpp StarJsonRpc.cpp StarFormattedJson.cpp + StarLexicalCast.cpp StarListener.cpp StarLogging.cpp StarLua.cpp StarLuaConverters.cpp StarMemory.cpp + StarNetCompatibility.cpp StarNetElement.cpp StarNetElementBasicFields.cpp StarNetElementGroup.cpp @@ -217,7 +224,8 @@ ELSEIF (STAR_SYSTEM_FAMILY_WINDOWS) ENDIF () -ADD_LIBRARY (star_core OBJECT ${star_core_SOURCES} ${star_core_HEADERS}) +CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) +ADD_LIBRARY (star_core OBJECT ${star_core_SOURCES} ${star_core_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) IF(STAR_PRECOMPILED_HEADERS) TARGET_PRECOMPILE_HEADERS (star_core PUBLIC StarPch.hpp) diff --git a/source/core/StarBTreeDatabase.cpp b/source/core/StarBTreeDatabase.cpp index 19b0bd2..f66bea1 100644 --- a/source/core/StarBTreeDatabase.cpp +++ b/source/core/StarBTreeDatabase.cpp @@ -1,6 +1,7 @@ #include "StarBTreeDatabase.hpp" #include "StarSha256.hpp" #include "StarVlqEncoding.hpp" +#include "StarLogging.hpp" namespace Star { @@ -243,7 +244,7 @@ uint32_t BTreeDatabase::freeBlockCount() { indexBlockIndex = indexBlock.nextFreeBlock; } - count += m_availableBlocks.size() + m_pendingFree.size(); + count += m_availableBlocks.size(); // Include untracked blocks at the end of the file in the free count. count += (m_device->size() - m_deviceSize) / m_blockSize; @@ -272,7 +273,7 @@ uint32_t BTreeDatabase::leafBlockCount() { return true; } - BTreeDatabase* parent; + BTreeDatabase* parent = nullptr; BlockIndex leafBlockCount = 0; }; @@ -293,8 +294,8 @@ void BTreeDatabase::rollback() { m_availableBlocks.clear(); m_indexCache.clear(); + m_uncommittedWrites.clear(); m_uncommitted.clear(); - m_pendingFree.clear(); readRoot(); @@ -305,7 +306,8 @@ void BTreeDatabase::rollback() { void BTreeDatabase::close(bool closeDevice) { WriteLocker writeLocker(m_lock); if (m_open) { - doCommit(); + if (!tryFlatten()) + doCommit(); m_indexCache.clear(); @@ -536,7 +538,7 @@ auto BTreeDatabase::BTreeImpl::loadIndex(Pointer pointer) -> Index { index->pointers.resize(s); for (uint32_t i = 0; i < s; ++i) { auto& e = index->pointers[i]; - e.key =buffer.readBytes(parent->m_keySize); + e.key = buffer.readBytes(parent->m_keySize); e.pointer = buffer.read(); } @@ -896,17 +898,25 @@ void BTreeDatabase::rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char if (size <= 0) return; - m_device->readFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size); + if (auto buffer = m_uncommittedWrites.ptr(blockIndex)) + buffer->copyTo(block, blockOffset, size); + else + m_device->readFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size); } -void BTreeDatabase::rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) const { +void BTreeDatabase::rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) { if (blockOffset > m_blockSize || size > m_blockSize - blockOffset) throw DBException::format("Write past end of block, offset: {} size {}", blockOffset, size); if (size <= 0) return; - m_device->writeFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size); + StreamOffset blockStart = HeaderSize + blockIndex * (StreamOffset)m_blockSize; + auto buffer = m_uncommittedWrites.find(blockIndex); + if (buffer == m_uncommittedWrites.end()) + buffer = m_uncommittedWrites.emplace(blockIndex, m_device->readBytesAbsolute(blockStart, m_blockSize)).first; + + buffer->second.writeFrom(block, blockOffset, size); } auto BTreeDatabase::readFreeIndexBlock(BlockIndex blockIndex) -> FreeIndexBlock { @@ -991,12 +1001,12 @@ auto BTreeDatabase::leafTailBlocks(BlockIndex leafPointer) -> List { } void BTreeDatabase::freeBlock(BlockIndex b) { - if (m_uncommitted.contains(b)) { + if (m_uncommitted.contains(b)) m_uncommitted.remove(b); - m_availableBlocks.add(b); - } else { - m_pendingFree.append(b); - } + if (m_uncommittedWrites.contains(b)) + m_uncommittedWrites.remove(b); + + m_availableBlocks.add(b); } auto BTreeDatabase::reserveBlock() -> BlockIndex { @@ -1007,10 +1017,7 @@ auto BTreeDatabase::reserveBlock() -> BlockIndex { FreeIndexBlock indexBlock = readFreeIndexBlock(m_headFreeIndexBlock); for (auto const& b : indexBlock.freeBlocks) m_availableBlocks.add(b); - // We cannot make available the block itself, because we must maintain - // atomic consistency. We will need to free this block later and commit - // the new free index block chain. - m_pendingFree.append(m_headFreeIndexBlock); + m_availableBlocks.add(m_headFreeIndexBlock); m_headFreeIndexBlock = indexBlock.nextFreeBlock; } @@ -1068,65 +1075,168 @@ void BTreeDatabase::readRoot() { } void BTreeDatabase::doCommit() { - if (m_availableBlocks.empty() && m_pendingFree.empty() && m_uncommitted.empty()) + if (m_availableBlocks.empty() && m_uncommitted.empty()) return; - if (!m_availableBlocks.empty() || !m_pendingFree.empty()) { + if (!m_availableBlocks.empty()) { // First, read the existing head FreeIndexBlock, if it exists FreeIndexBlock indexBlock = FreeIndexBlock{InvalidBlockIndex, {}}; - if (m_headFreeIndexBlock != InvalidBlockIndex) { + + auto newBlock = [&]() -> BlockIndex { + if (!m_availableBlocks.empty()) + return m_availableBlocks.takeFirst(); + else + return makeEndBlock(); + }; + + if (m_headFreeIndexBlock != InvalidBlockIndex) indexBlock = readFreeIndexBlock(m_headFreeIndexBlock); - if (indexBlock.freeBlocks.size() >= maxFreeIndexLength()) { - // If the existing head free index block is full, then we should start a - // new one and leave it alone - indexBlock.nextFreeBlock = m_headFreeIndexBlock; - indexBlock.freeBlocks.clear(); - } else { - // If we are copying an existing free index block, the old free index - // block will be a newly freed block - indexBlock.freeBlocks.append(m_headFreeIndexBlock); - } - } + else + m_headFreeIndexBlock = newBlock(); - // Then, we need to write all the available blocks, which are safe to write - // to, and the pending free blocks, which are NOT safe to write to, to the - // FreeIndexBlock chain. + // Then, we need to write all the available blocks to the FreeIndexBlock chain. while (true) { - if (indexBlock.freeBlocks.size() < maxFreeIndexLength() && (!m_availableBlocks.empty() || !m_pendingFree.empty())) { - // If we have room on our current FreeIndexblock, just add a block to - // it. Prioritize the pending free blocks, because we cannot use those - // to write to. - BlockIndex toAdd; - if (m_pendingFree.empty()) - toAdd = m_availableBlocks.takeFirst(); - else - toAdd = m_pendingFree.takeFirst(); - + // If we have room on our current FreeIndexBlock, just add a block to it. + if (!m_availableBlocks.empty() && indexBlock.freeBlocks.size() < maxFreeIndexLength()) { + BlockIndex toAdd = m_availableBlocks.takeFirst(); indexBlock.freeBlocks.append(toAdd); } else { - // If our index block is full OR we are out of blocks to free, then - // need to write a new head free index block. - if (m_availableBlocks.empty()) - m_headFreeIndexBlock = makeEndBlock(); - else - m_headFreeIndexBlock = m_availableBlocks.takeFirst(); + // Update the current head free index block. writeFreeIndexBlock(m_headFreeIndexBlock, indexBlock); // If we're out of blocks to free, then we're done - if (m_availableBlocks.empty() && m_pendingFree.empty()) + if (m_availableBlocks.empty()) break; - indexBlock.nextFreeBlock = m_headFreeIndexBlock; - indexBlock.freeBlocks.clear(); + // If our head free index block is full, then + // need to write a new head free index block. + if (indexBlock.freeBlocks.size() >= maxFreeIndexLength()) { + indexBlock.nextFreeBlock = m_headFreeIndexBlock; + indexBlock.freeBlocks.clear(); + + m_headFreeIndexBlock = newBlock(); + writeFreeIndexBlock(m_headFreeIndexBlock, indexBlock); + } } } } + + commitWrites(); writeRoot(); - m_uncommitted.clear(); } +void BTreeDatabase::commitWrites() { + for (auto& write : m_uncommittedWrites) + m_device->writeFullAbsolute(HeaderSize + write.first * (StreamOffset)m_blockSize, write.second.ptr(), m_blockSize); + + m_device->sync(); + m_uncommittedWrites.clear(); +} + +bool BTreeDatabase::tryFlatten() { + if (m_headFreeIndexBlock == InvalidBlockIndex || m_rootIsLeaf || !m_device->isWritable()) + return false; + + BlockIndex freeBlockCount = 0; + BlockIndex indexBlockIndex = m_headFreeIndexBlock; + while (indexBlockIndex != InvalidBlockIndex) { + FreeIndexBlock indexBlock = readFreeIndexBlock(indexBlockIndex); + freeBlockCount += 1 + indexBlock.freeBlocks.size(); + indexBlockIndex = indexBlock.nextFreeBlock; + } + + BlockIndex expectedBlockCount = (m_deviceSize - HeaderSize) / m_blockSize; + float free = float(freeBlockCount) / float(expectedBlockCount); + if (free < 0.05f) + return false; + + Logger::info("[BTreeDatabase] File '{}' is {:.2f}% free space, flattening", m_device->deviceName(), free * 100.f); + + indexBlockIndex = m_headFreeIndexBlock; + { + List availableBlocksList; + do { + FreeIndexBlock indexBlock = readFreeIndexBlock(indexBlockIndex); + availableBlocksList.appendAll(indexBlock.freeBlocks); + availableBlocksList.append(indexBlockIndex); + indexBlockIndex = indexBlock.nextFreeBlock; + } while (indexBlockIndex != InvalidBlockIndex); + m_headFreeIndexBlock = InvalidBlockIndex; + + sort(availableBlocksList); + for (auto& availableBlock : availableBlocksList) + m_availableBlocks.insert(m_availableBlocks.end(), availableBlock); + } + + BlockIndex count = 1; // 1 to include root index + + double start = Time::monotonicTime(); + auto index = m_impl.loadIndex(m_impl.rootPointer()); + if (flattenVisitor(index, count)) { + m_impl.deleteIndex(index); + index->self = InvalidBlockIndex; + m_root = m_impl.storeIndex(index); + } + + m_availableBlocks.clear(); + m_device->resize(m_deviceSize = HeaderSize + (StreamOffset)m_blockSize * count); + + m_indexCache.clear(); + commitWrites(); + writeRoot(); + m_uncommitted.clear(); + + Logger::info("[BTreeDatabase] Finished flattening '{}' in {:.2f} milliseconds", m_device->deviceName(), (Time::monotonicTime() - start) * 1000.f); + return true; +} + +bool BTreeDatabase::flattenVisitor(BTreeImpl::Index& index, BlockIndex& count) { + auto pointerCount = index->pointerCount(); + count += pointerCount; + bool canStore = !m_availableBlocks.empty(); + + bool needsStore = false; + if (m_impl.indexLevel(index) == 0) { + for (size_t i = 0; i != pointerCount; ++i) { + auto indexPointer = index->pointer(i); + auto tailBlocks = leafTailBlocks(indexPointer); + if (canStore) { + bool leafNeedsStore = m_availableBlocks.first() < indexPointer; + + if (!leafNeedsStore) + for (size_t i = 0; !leafNeedsStore && i != tailBlocks.size(); ++i) + if (m_availableBlocks.first() < tailBlocks[i]) + leafNeedsStore = true; + + if (leafNeedsStore) { + auto leaf = m_impl.loadLeaf(indexPointer); + m_impl.deleteLeaf(leaf); + leaf->self = InvalidBlockIndex; + index->updatePointer(i, m_impl.storeLeaf(leaf)); + tailBlocks = leafTailBlocks(leaf->self); + needsStore = true; + } + canStore = !m_availableBlocks.empty(); + } + count += tailBlocks.size(); + } + } else { + for (size_t i = 0; i != pointerCount; ++i) { + auto childIndex = m_impl.loadIndex(index->pointer(i)); + if (canStore && flattenVisitor(childIndex, count)) { + m_impl.deleteIndex(childIndex); + childIndex->self = InvalidBlockIndex; + index->updatePointer(i, m_impl.storeIndex(childIndex)); + canStore = !m_availableBlocks.empty(); + needsStore = true; + } + } + } + return needsStore || (canStore && m_availableBlocks.first() < index->self); +} + void BTreeDatabase::checkIfOpen(char const* methodName, bool shouldBeOpen) const { if (shouldBeOpen && !m_open) throw DBException::format("BTreeDatabase method '{}' called when not open, must be open.", methodName); @@ -1146,7 +1256,7 @@ void BTreeDatabase::checkKeySize(ByteArray const& k) const { } uint32_t BTreeDatabase::maxFreeIndexLength() const { - return (m_blockSize - 2 - sizeof(BlockIndex) - 4) / sizeof(BlockIndex); + return (m_blockSize / sizeof(BlockIndex)) - 2 - sizeof(BlockIndex) - 4; } BTreeSha256Database::BTreeSha256Database() { diff --git a/source/core/StarBTreeDatabase.hpp b/source/core/StarBTreeDatabase.hpp index f1b88a1..b3a530f 100644 --- a/source/core/StarBTreeDatabase.hpp +++ b/source/core/StarBTreeDatabase.hpp @@ -230,7 +230,7 @@ private: void updateBlock(BlockIndex blockIndex, ByteArray const& block); void rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char* block, size_t size) const; - void rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) const; + void rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size); void updateHeadFreeIndexBlock(BlockIndex newHead); @@ -251,6 +251,9 @@ private: void writeRoot(); void readRoot(); void doCommit(); + void commitWrites(); + bool tryFlatten(); + bool flattenVisitor(BTreeImpl::Index& index, BlockIndex& count); void checkIfOpen(char const* methodName, bool shouldBeOpen) const; void checkBlockIndex(size_t blockIndex) const; @@ -285,14 +288,14 @@ private: bool m_dirty; // Blocks that can be freely allocated and written to without violating - // atomic consistency + // atomic consistency. Set m_availableBlocks; - // Blocks to be freed on next commit. - Deque m_pendingFree; - // Blocks that have been written in uncommitted portions of the tree. Set m_uncommitted; + + // Temporarily holds written data so that it can be rolled back. + mutable Map m_uncommittedWrites; }; // Version of BTreeDatabase that hashes keys with SHA-256 to produce a unique diff --git a/source/core/StarColor.cpp b/source/core/StarColor.cpp index 7da8dd4..4eb726c 100644 --- a/source/core/StarColor.cpp +++ b/source/core/StarColor.cpp @@ -322,6 +322,8 @@ Vec3F Color::toRgbF() const { return Vec3F(redF(), greenF(), blueF()); } +#pragma GCC push_options +#pragma GCC optimize("-fno-fast-math") Vec4F Color::toHsva() const { float h, s, v; @@ -365,6 +367,7 @@ Vec4F Color::toHsva() const { return Vec4F(h, s, v, alphaF()); } +#pragma GCC pop_options String Color::toHex() const { auto rgba = toRgba(); diff --git a/source/core/StarDataStream.cpp b/source/core/StarDataStream.cpp index 18456b6..7097d63 100644 --- a/source/core/StarDataStream.cpp +++ b/source/core/StarDataStream.cpp @@ -6,6 +6,8 @@ namespace Star { +unsigned const CurrentStreamVersion = 3; + DataStream::DataStream() : m_byteOrder(ByteOrder::BigEndian), m_nullTerminatedStrings(false), @@ -35,6 +37,10 @@ void DataStream::setStreamCompatibilityVersion(unsigned streamCompatibilityVersi m_streamCompatibilityVersion = streamCompatibilityVersion; } +void DataStream::setStreamCompatibilityVersion(NetCompatibilityRules const& rules) { + m_streamCompatibilityVersion = rules.version(); +} + ByteArray DataStream::readBytes(size_t len) { ByteArray ba; ba.resize(len); diff --git a/source/core/StarDataStream.hpp b/source/core/StarDataStream.hpp index 02cb922..d5f9bcb 100644 --- a/source/core/StarDataStream.hpp +++ b/source/core/StarDataStream.hpp @@ -1,10 +1,12 @@ #pragma once #include "StarString.hpp" +#include "StarNetCompatibility.hpp" namespace Star { STAR_EXCEPTION(DataStreamException, IOException); +extern unsigned const CurrentStreamVersion; // Writes complex types to bytes in a portable big-endian fashion. class DataStream { @@ -12,8 +14,6 @@ public: DataStream(); virtual ~DataStream() = default; - static unsigned const CurrentStreamVersion = 1; - // DataStream defaults to big-endian order for all primitive types ByteOrder byteOrder() const; void setByteOrder(ByteOrder byteOrder); @@ -27,7 +27,7 @@ public: // changed for compatibility with older versions of DataStream serialization. unsigned streamCompatibilityVersion() const; void setStreamCompatibilityVersion(unsigned streamCompatibilityVersion); - + void setStreamCompatibilityVersion(NetCompatibilityRules const& rules); // Do direct reads and writes virtual void readData(char* data, size_t len) = 0; virtual void writeData(char const* data, size_t len) = 0; diff --git a/source/core/StarDataStreamDevices.hpp b/source/core/StarDataStreamDevices.hpp index 3f34a72..4452592 100644 --- a/source/core/StarDataStreamDevices.hpp +++ b/source/core/StarDataStreamDevices.hpp @@ -126,8 +126,8 @@ private: class DataStreamExternalBuffer : public DataStream { public: DataStreamExternalBuffer(); - explicit DataStreamExternalBuffer(ByteArray const& byteArray); - explicit DataStreamExternalBuffer(DataStreamBuffer const& buffer); + DataStreamExternalBuffer(ByteArray const& byteArray); + DataStreamExternalBuffer(DataStreamBuffer const& buffer); DataStreamExternalBuffer(DataStreamExternalBuffer const& buffer) = default; DataStreamExternalBuffer(char const* externalData, size_t len); diff --git a/source/core/StarDirectives.cpp b/source/core/StarDirectives.cpp index ea18bc9..ea9d565 100644 --- a/source/core/StarDirectives.cpp +++ b/source/core/StarDirectives.cpp @@ -156,6 +156,17 @@ void Directives::parse(String&& directives) { } } +StringView Directives::prefix() const { + if (!m_shared) + return ""; + else if (m_shared->empty()) + return m_shared->string; + else if (m_shared->string.utf8().at(0) == '?') + return ""; + else + return m_shared->entries.front().string(*m_shared); +} + String Directives::string() const { if (!m_shared) return ""; diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp index 9290649..d83a3e8 100644 --- a/source/core/StarDirectives.hpp +++ b/source/core/StarDirectives.hpp @@ -55,6 +55,7 @@ public: void loadOperations() const; void parse(String&& directives); + StringView prefix() const; String string() const; String const* stringPtr() const; String buildString() const; diff --git a/source/core/StarFile_windows.cpp b/source/core/StarFile_windows.cpp index 0b5c286..6352bb7 100644 --- a/source/core/StarFile_windows.cpp +++ b/source/core/StarFile_windows.cpp @@ -18,13 +18,11 @@ namespace Star { -namespace { - OVERLAPPED makeOverlapped(StreamOffset offset) { - OVERLAPPED overlapped = {}; - overlapped.Offset = offset; - overlapped.OffsetHigh = offset >> 32; - return overlapped; - } +OVERLAPPED makeOverlapped(StreamOffset offset) { + OVERLAPPED overlapped = {}; + overlapped.Offset = offset; + overlapped.OffsetHigh = offset >> 32; + return overlapped; } String File::convertDirSeparators(String const& path) { @@ -378,6 +376,7 @@ size_t File::pread(void* f, char* data, size_t len, StreamOffset position) { DWORD numRead = 0; OVERLAPPED overlapped = makeOverlapped(position); int ret = ReadFile(file, data, len, &numRead, &overlapped); + fseek(f, -(StreamOffset)numRead, IOSeek::Relative); if (ret == 0) { auto err = GetLastError(); if (err != ERROR_IO_PENDING) @@ -392,6 +391,7 @@ size_t File::pwrite(void* f, char const* data, size_t len, StreamOffset position DWORD numWritten = 0; OVERLAPPED overlapped = makeOverlapped(position); int ret = WriteFile(file, data, len, &numWritten, &overlapped); + fseek(f, -(StreamOffset)numWritten, IOSeek::Relative); if (ret == 0) { auto err = GetLastError(); if (err != ERROR_IO_PENDING) diff --git a/source/core/StarFont.cpp b/source/core/StarFont.cpp index 47569f2..d854d52 100644 --- a/source/core/StarFont.cpp +++ b/source/core/StarFont.cpp @@ -30,8 +30,6 @@ FTContext ftContext; struct FontImpl { FT_Face face; - unsigned loadedPixelSize = 0; - String::Char loadedChar = 0; }; FontPtr Font::loadFont(String const& fileName, unsigned pixelSize) { @@ -97,19 +95,11 @@ tuple Font::render(String::Char c) { throw FontException("Font::render called on uninitialized font."); FT_Face face = m_fontImpl->face; + if (FT_Load_Glyph(face, FT_Get_Char_Index(face, c), FontLoadFlags) != 0) + return {}; - if (m_fontImpl->loadedPixelSize != m_pixelSize || m_fontImpl->loadedChar != c) { - FT_UInt glyph_index = FT_Get_Char_Index(face, c); - if (FT_Load_Glyph(face, glyph_index, FontLoadFlags) != 0) - return {}; - - // convert to an anti-aliased bitmap - if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) != 0) - return {}; - } - - m_fontImpl->loadedPixelSize = m_pixelSize; - m_fontImpl->loadedChar = c; + if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) != 0) + return {}; FT_GlyphSlot slot = face->glyph; if (!slot->bitmap.buffer) @@ -134,7 +124,7 @@ tuple Font::render(String::Char c) { } } } - } else if (colored = slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + } else if (colored = (slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA)) { unsigned bpp = image.bytesPerPixel(); uint8_t* data = image.data() + bpp + ((image.width() * (image.height() - 2)) * bpp); // offset by 1 pixel as it's padded for (size_t y = 0; y != height; ++y) { diff --git a/source/core/StarImage.cpp b/source/core/StarImage.cpp index 82e3b05..c7d4962 100644 --- a/source/core/StarImage.cpp +++ b/source/core/StarImage.cpp @@ -17,8 +17,13 @@ void readPngData(png_structp pngPtr, png_bytep data, png_size_t length) { ((IODevice*)png_get_io_ptr(pngPtr))->readFull((char*)data, length); }; +bool Image::isPng(IODevicePtr device) { + png_byte header[8]{}; + return !png_sig_cmp(header, 0, device->readAbsolute(0, (char*)header, sizeof(header))); +} + Image Image::readPng(IODevicePtr device) { - png_byte header[8]; + png_byte header[8]{}; device->readFull((char*)header, sizeof(header)); if (png_sig_cmp(header, 0, sizeof(header))) diff --git a/source/core/StarImage.hpp b/source/core/StarImage.hpp index 478d074..aac0105 100644 --- a/source/core/StarImage.hpp +++ b/source/core/StarImage.hpp @@ -27,6 +27,7 @@ STAR_CLASS(Image); class Image { public: static Image readPng(IODevicePtr device); + static bool isPng(IODevicePtr device); // Returns the size and pixel format that would be constructed from the given // png file, without actually loading it. static tuple readPngMetadata(IODevicePtr device); diff --git a/source/core/StarImageProcessing.cpp b/source/core/StarImageProcessing.cpp index 6d2c51d..1cfd847 100644 --- a/source/core/StarImageProcessing.cpp +++ b/source/core/StarImageProcessing.cpp @@ -1,4 +1,5 @@ #include "StarImageProcessing.hpp" +#include "StarImageScaling.hpp" #include "StarMatrix3.hpp" #include "StarInterpolation.hpp" #include "StarLexicalCast.hpp" @@ -10,109 +11,6 @@ namespace Star { -Image scaleNearest(Image const& srcImage, Vec2F scale) { - if (scale[0] < 0.0f || scale[1] < 0.0f) { - Logger::warn("Negative scale in scaleNearest({})", scale); - scale = scale.piecewiseMax(Vec2F::filled(0.f)); - } - Vec2U srcSize = srcImage.size(); - Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale)); - destSize[0] = max(destSize[0], 1u); - destSize[1] = max(destSize[1], 1u); - - Image destImage(destSize, srcImage.pixelFormat()); - - for (unsigned y = 0; y < destSize[1]; ++y) { - for (unsigned x = 0; x < destSize[0]; ++x) - destImage.set({x, y}, srcImage.clamp(Vec2I::round(vdiv(Vec2F(x, y), scale)))); - } - return destImage; -} - -Image scaleBilinear(Image const& srcImage, Vec2F scale) { - if (scale[0] < 0.0f || scale[1] < 0.0f) { - Logger::warn("Negative scale in scaleBilinear({})", scale); - scale = scale.piecewiseMax(Vec2F::filled(0.f)); - } - Vec2U srcSize = srcImage.size(); - Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale)); - destSize[0] = max(destSize[0], 1u); - destSize[1] = max(destSize[1], 1u); - - Image destImage(destSize, srcImage.pixelFormat()); - - for (unsigned y = 0; y < destSize[1]; ++y) { - for (unsigned x = 0; x < destSize[0]; ++x) { - auto pos = vdiv(Vec2F(x, y), scale); - auto ipart = Vec2I::floor(pos); - auto fpart = pos - Vec2F(ipart); - - auto result = lerp(fpart[1], lerp(fpart[0], Vec4F(srcImage.clamp(ipart[0], ipart[1])), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1]))), lerp(fpart[0], - Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1)))); - - destImage.set({x, y}, Vec4B(result)); - } - } - - return destImage; -} - -Image scaleBicubic(Image const& srcImage, Vec2F scale) { - if (scale[0] < 0.0f || scale[1] < 0.0f) { - Logger::warn("Negative scale in scaleBicubic({})", scale); - scale = scale.piecewiseMax(Vec2F::filled(0.f)); - } - Vec2U srcSize = srcImage.size(); - Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale)); - destSize[0] = max(destSize[0], 1u); - destSize[1] = max(destSize[1], 1u); - - Image destImage(destSize, srcImage.pixelFormat()); - - for (unsigned y = 0; y < destSize[1]; ++y) { - for (unsigned x = 0; x < destSize[0]; ++x) { - auto pos = vdiv(Vec2F(x, y), scale); - auto ipart = Vec2I::floor(pos); - auto fpart = pos - Vec2F(ipart); - - Vec4F a = cubic4(fpart[0], - Vec4F(srcImage.clamp(ipart[0], ipart[1])), - Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1])), - Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1])), - Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1]))); - - Vec4F b = cubic4(fpart[0], - Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)), - Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1)), - Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 1)), - Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 1))); - - Vec4F c = cubic4(fpart[0], - Vec4F(srcImage.clamp(ipart[0], ipart[1] + 2)), - Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 2)), - Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 2)), - Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 2))); - - Vec4F d = cubic4(fpart[0], - Vec4F(srcImage.clamp(ipart[0], ipart[1] + 3)), - Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 3)), - Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 3)), - Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 3))); - - auto result = cubic4(fpart[1], a, b, c, d); - - destImage.set({x, y}, Vec4B( - clamp(result[0], 0.0f, 255.0f), - clamp(result[1], 0.0f, 255.0f), - clamp(result[2], 0.0f, 255.0f), - clamp(result[3], 0.0f, 255.0f) - )); - } - } - - return destImage; -} - StringList colorDirectivesFromConfig(JsonArray const& directives) { List result; @@ -214,7 +112,7 @@ ImageOperation imageOperationFromString(StringView string) { else // we're in A of A=B. In vanilla only A=B pairs are evaluated, so only throw an error if B is also there. return operation; - if (which = !which) + if ((which = !which)) operation.colorReplaceMap[*(Vec4B*)&a] = *(Vec4B*)&b; hexLen = 0; @@ -631,12 +529,17 @@ void processImageOperation(ImageOperation const& operation, Image& image, ImageR image = borderImage; } else if (auto op = operation.ptr()) { + auto scale = op->scale; + if (scale[0] < 0.0f || scale[1] < 0.0f) { + Logger::warn("Negative scale in ScaleImageOperation ({})", scale); + scale = scale.piecewiseMax(Vec2F::filled(0.f)); + } if (op->mode == ScaleImageOperation::Nearest) - image = scaleNearest(image, op->scale); + image = scaleNearest(image, scale); else if (op->mode == ScaleImageOperation::Bilinear) - image = scaleBilinear(image, op->scale); + image = scaleBilinear(image, scale); else if (op->mode == ScaleImageOperation::Bicubic) - image = scaleBicubic(image, op->scale); + image = scaleBicubic(image, scale); } else if (auto op = operation.ptr()) { image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size())); diff --git a/source/core/StarImageProcessing.hpp b/source/core/StarImageProcessing.hpp index 4022d38..dfc9420 100644 --- a/source/core/StarImageProcessing.hpp +++ b/source/core/StarImageProcessing.hpp @@ -10,10 +10,6 @@ STAR_CLASS(Image); STAR_EXCEPTION(ImageOperationException, StarException); -Image scaleNearest(Image const& srcImage, Vec2F scale); -Image scaleBilinear(Image const& srcImage, Vec2F scale); -Image scaleBicubic(Image const& srcImage, Vec2F scale); - StringList colorDirectivesFromConfig(JsonArray const& directives); String paletteSwapDirectivesFromConfig(Json const& swaps); diff --git a/source/core/StarImageScaling.cpp b/source/core/StarImageScaling.cpp new file mode 100644 index 0000000..aa94359 --- /dev/null +++ b/source/core/StarImageScaling.cpp @@ -0,0 +1,97 @@ +#include "StarImage.hpp" +#include "StarImageScaling.hpp" +#include "StarInterpolation.hpp" + +namespace Star { + +Image scaleNearest(Image const& srcImage, Vec2F const& scale) { + Vec2U srcSize = srcImage.size(); + Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale)); + destSize[0] = max(destSize[0], 1u); + destSize[1] = max(destSize[1], 1u); + + Image destImage(destSize, srcImage.pixelFormat()); + + for (unsigned y = 0; y < destSize[1]; ++y) { + for (unsigned x = 0; x < destSize[0]; ++x) + destImage.set({x, y}, srcImage.clamp(Vec2I::round(vdiv(Vec2F(x, y), scale)))); + } + return destImage; +} + +Image scaleBilinear(Image const& srcImage, Vec2F const& scale) { + Vec2U srcSize = srcImage.size(); + Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale)); + destSize[0] = max(destSize[0], 1u); + destSize[1] = max(destSize[1], 1u); + + Image destImage(destSize, srcImage.pixelFormat()); + + for (unsigned y = 0; y < destSize[1]; ++y) { + for (unsigned x = 0; x < destSize[0]; ++x) { + auto pos = vdiv(Vec2F(x, y), scale); + auto ipart = Vec2I::floor(pos); + auto fpart = pos - Vec2F(ipart); + + auto result = lerp(fpart[1], lerp(fpart[0], Vec4F(srcImage.clamp(ipart[0], ipart[1])), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1]))), lerp(fpart[0], Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1)))); + + destImage.set({x, y}, Vec4B(result)); + } + } + + return destImage; +} + +Image scaleBicubic(Image const& srcImage, Vec2F const& scale) { + Vec2U srcSize = srcImage.size(); + Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale)); + destSize[0] = max(destSize[0], 1u); + destSize[1] = max(destSize[1], 1u); + + Image destImage(destSize, srcImage.pixelFormat()); + + for (unsigned y = 0; y < destSize[1]; ++y) { + for (unsigned x = 0; x < destSize[0]; ++x) { + auto pos = vdiv(Vec2F(x, y), scale); + auto ipart = Vec2I::floor(pos); + auto fpart = pos - Vec2F(ipart); + + Vec4F a = cubic4(fpart[0], + Vec4F(srcImage.clamp(ipart[0], ipart[1])), + Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1])), + Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1])), + Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1]))); + + Vec4F b = cubic4(fpart[0], + Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)), + Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1)), + Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 1)), + Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 1))); + + Vec4F c = cubic4(fpart[0], + Vec4F(srcImage.clamp(ipart[0], ipart[1] + 2)), + Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 2)), + Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 2)), + Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 2))); + + Vec4F d = cubic4(fpart[0], + Vec4F(srcImage.clamp(ipart[0], ipart[1] + 3)), + Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 3)), + Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 3)), + Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 3))); + + auto result = cubic4(fpart[1], a, b, c, d); + + destImage.set({x, y}, Vec4B( + clamp(result[0], 0.0f, 255.0f), + clamp(result[1], 0.0f, 255.0f), + clamp(result[2], 0.0f, 255.0f), + clamp(result[3], 0.0f, 255.0f) + )); + } + } + + return destImage; +} + +} \ No newline at end of file diff --git a/source/core/StarImageScaling.hpp b/source/core/StarImageScaling.hpp new file mode 100644 index 0000000..d758169 --- /dev/null +++ b/source/core/StarImageScaling.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace Star { + +STAR_CLASS(Image); +Image scaleNearest(Image const& srcImage, Vec2F const& scale); +Image scaleBilinear(Image const& srcImage, Vec2F const& scale); +Image scaleBicubic(Image const& srcImage, Vec2F const& scale); + +} \ No newline at end of file diff --git a/source/core/StarJsonExtra.hpp b/source/core/StarJsonExtra.hpp index 2a0ca28..c8fc185 100644 --- a/source/core/StarJsonExtra.hpp +++ b/source/core/StarJsonExtra.hpp @@ -211,32 +211,32 @@ Json jsonFromList(List const& list, Converter&& valueConvert) { return res; } -template -Set jsonToSet(Json const& v) { - return jsonToSet(v, construct()); +template +SetType jsonToSet(Json const& v) { + return jsonToSet(v, construct()); } -template -Set jsonToSet(Json const& v, Converter&& valueConvert) { +template +SetType jsonToSet(Json const& v, Converter&& valueConvert) { if (v.type() != Json::Type::Array) throw JsonException("Json type is not an array in jsonToSet"); - Set res; + SetType res; for (auto const& entry : v.iterateArray()) res.add(valueConvert(entry)); return res; } -template -Json jsonFromSet(Set const& Set) { - return jsonFromSet(Set, construct()); +template +Json jsonFromSet(SetType const& Set) { + return jsonFromSet(Set, construct()); } -template -Json jsonFromSet(Set const& Set, Converter&& valueConvert) { +template +Json jsonFromSet(SetType const& Set, Converter&& valueConvert) { JsonArray res; - for (auto entry : Set) + for (auto& entry : Set) res.push_back(valueConvert(entry)); return res; diff --git a/source/core/StarJsonPatch.cpp b/source/core/StarJsonPatch.cpp index 14505e5..88f9377 100644 --- a/source/core/StarJsonPatch.cpp +++ b/source/core/StarJsonPatch.cpp @@ -12,7 +12,7 @@ Json jsonPatch(Json const& base, JsonArray const& patch) { } return res; } catch (JsonException const& e) { - throw JsonPatchException(strf("Could not apply patch to base. {}", e.what())); + throw JsonPatchException(strf("Could not apply patch to base. {}", e.what()), false); } } @@ -26,7 +26,7 @@ size_t findJsonMatch(Json const& searchable, Json const& value, JsonPath::Pointe return i + 1; } } else { - throw JsonPatchException(strf("Search operation failure, value at '{}' is not an array.", pointer.path())); + throw JsonPatchException(strf("Search operation failure, value at '{}' is not an array.", pointer.path()), false); } return 0; } @@ -49,9 +49,9 @@ namespace JsonPatching { auto operation = op.getString("op"); return JsonPatching::functionMap.get(operation)(base, op); } catch (JsonException const& e) { - throw JsonPatchException(strf("Could not apply operation to base. {}", e.what())); + throw JsonPatchException(strf("Could not apply operation to base. {}", e.what()), false); } catch (MapException const&) { - throw JsonPatchException(strf("Invalid operation: {}", op.getString("op"))); + throw JsonPatchException(strf("Invalid operation: {}", op.getString("op")), false); } } @@ -66,28 +66,28 @@ namespace JsonPatching { auto searchValue = op.get("search"); bool found = findJsonMatch(searchable, searchValue, pointer); if (found && inverseTest) - throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", searchValue)); + throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", searchValue), false); else if (!found && !inverseTest) - throw JsonPatchTestFail(strf("Test operation failure, could not find {}.", searchValue)); + throw JsonPatchTestFail(strf("Test operation failure, could not find {}.", searchValue), false); return base; } else { auto value = op.opt("value"); auto testValue = pointer.get(base); if (!value) { if (inverseTest) - throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", path)); + throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", path), false); return base; } if ((value && (testValue == *value)) ^ inverseTest) return base; else - throw JsonPatchTestFail(strf("Test operation failure, expected {} found {}.", value, testValue)); + throw JsonPatchTestFail(strf("Test operation failure, expected {} found {}.", value, testValue), false); } } catch (JsonPath::TraversalException& e) { if (inverseTest) return base; - throw JsonPatchTestFail(strf("Test operation failure: {}", e.what())); + throw JsonPatchTestFail(strf("Test operation failure: {}", e.what()), false); } } diff --git a/source/core/StarLexicalCast.cpp b/source/core/StarLexicalCast.cpp new file mode 100644 index 0000000..e607ee5 --- /dev/null +++ b/source/core/StarLexicalCast.cpp @@ -0,0 +1,36 @@ +#include "StarLexicalCast.hpp" + +namespace Star { + +void throwLexicalCastError(std::errc ec, const char* first, const char* last) { + StringView str(first, last - first); + if (ec == std::errc::invalid_argument) + throw BadLexicalCast(strf("Lexical cast failed on '{}' (invalid argument)", str)); + else + throw BadLexicalCast(strf("Lexical cast failed on '{}'", str)); +} + +template <> +bool tryLexicalCast(bool& result, const char* first, const char* last) { + size_t len = last - first; + if (strncmp(first, "true", len) == 0) + result = true; + else if (strncmp(first, "false", len) != 0) + return false; + + result = false; + return true; +} + +template <> +bool lexicalCast(const char* first, const char* last) { + size_t len = last - first; + if (strncmp(first, "true", len) == 0) + return true; + else if (strncmp(first, "false", len) != 0) + throwLexicalCastError(std::errc(), first, last); + + return false; +} + +} \ No newline at end of file diff --git a/source/core/StarLexicalCast.hpp b/source/core/StarLexicalCast.hpp index 9c671e9..08dab14 100644 --- a/source/core/StarLexicalCast.hpp +++ b/source/core/StarLexicalCast.hpp @@ -5,40 +5,63 @@ #include "StarStringView.hpp" #include "StarMaybe.hpp" -#include -#include +#include "fast_float.h" namespace Star { STAR_EXCEPTION(BadLexicalCast, StarException); -// Very simple basic lexical cast using stream input. Always operates in the -// "C" locale. +void throwLexicalCastError(std::errc ec, const char* first, const char* last); + template -Maybe maybeLexicalCast(StringView s, std::ios_base::fmtflags flags = std::ios_base::boolalpha) { - Type result; - std::istringstream stream(std::string(s.utf8())); - stream.flags(flags); - stream.imbue(std::locale::classic()); +bool tryLexicalCast(Type& result, const char* first, const char* last) { + auto res = fast_float::from_chars(first, last, result); + return res.ptr == last && (res.ec == std::errc() || res.ec == std::errc::result_out_of_range); +} - if (!(stream >> result)) +template <> +bool tryLexicalCast(bool& result, const char* first, const char* last); + +template +bool tryLexicalCast(Type& result, String const& s) { + return tryLexicalCast(result, s.utf8Ptr(), s.utf8Ptr() + s.utf8Size()); +} + +template +bool tryLexicalCast(Type& result, StringView s) { + return tryLexicalCast(result, s.utf8Ptr(), s.utf8Ptr() + s.utf8Size()); +} + +template +Maybe maybeLexicalCast(const char* first, const char* last) { + Type result{}; + if (tryLexicalCast(result, first, last)) + return result; + else return {}; +} - // Confirm that we read everything out of the stream - char ch; - if (stream >> ch) - return {}; +template +Maybe maybeLexicalCast(StringView s) { + return maybeLexicalCast(s.utf8Ptr(), s.utf8Ptr() + s.utf8Size()); +} +template +Type lexicalCast(const char* first, const char* last) { + Type result{}; + auto res = fast_float::from_chars(first, last, result); + if ((res.ec != std::errc() && res.ec != std::errc::result_out_of_range) || res.ptr != last) + throwLexicalCastError(res.ec, first, last); + return result; } +template <> +bool lexicalCast(const char* first, const char* last); + template -Type lexicalCast(StringView s, std::ios_base::fmtflags flags = std::ios_base::boolalpha) { - auto m = maybeLexicalCast(s, flags); - if (m) - return m.take(); - else - throw BadLexicalCast(strf("Lexical cast failed on '{}'", s)); +Type lexicalCast(StringView s) { + return lexicalCast(s.utf8Ptr(), s.utf8Ptr() + s.utf8Size()); } } diff --git a/source/core/StarLua.cpp b/source/core/StarLua.cpp index 5a2effa..91aebab 100644 --- a/source/core/StarLua.cpp +++ b/source/core/StarLua.cpp @@ -537,8 +537,16 @@ LuaContext LuaEngine::createContext() { LuaDetail::shallowCopy(m_state, -1, -2); lua_pop(m_state, 1); + auto context = LuaContext(LuaDetail::LuaHandle(RefPtr(this), popHandle(m_state))); + // Add loadstring + auto handleIndex = context.handleIndex(); + context.set("loadstring", createFunction([this, handleIndex](String const& source, Maybe const& name, Maybe const& env) -> LuaFunction { + String functionName = name ? strf("loadstring: {}", *name) : "loadstring"; + return createFunctionFromSource(env ? env->handleIndex() : handleIndex, source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr()); + })); + // Then set that environment as the new context environment in the registry. - return LuaContext(LuaDetail::LuaHandle(RefPtr(this), popHandle(m_state))); + return context; } void LuaEngine::collectGarbage(Maybe steps) { diff --git a/source/core/StarLuaConverters.hpp b/source/core/StarLuaConverters.hpp index d9dfc67..c79cbdc 100644 --- a/source/core/StarLuaConverters.hpp +++ b/source/core/StarLuaConverters.hpp @@ -168,11 +168,11 @@ struct LuaConverter> { template struct LuaConverter> { static LuaValue from(LuaEngine& engine, Variant const& variant) { - return variant.call([&engine](auto const& a) { return luaFrom(engine, a); }); + return variant.call([&engine](auto const& a) { return engine.luaFrom(a); }); } static LuaValue from(LuaEngine& engine, Variant&& variant) { - return variant.call([&engine](auto& a) { return luaFrom(engine, std::move(a)); }); + return variant.call([&engine](auto& a) { return engine.luaFrom(std::move(a)); }); } static Maybe> to(LuaEngine& engine, LuaValue const& v) { @@ -217,7 +217,7 @@ struct LuaConverter> { static LuaValue from(LuaEngine& engine, MVariant const& variant) { LuaValue value; variant.call([&value, &engine](auto const& a) { - value = luaFrom(engine, a); + value = engine.luaFrom(a); }); return value; } @@ -225,7 +225,7 @@ struct LuaConverter> { static LuaValue from(LuaEngine& engine, MVariant&& variant) { LuaValue value; variant.call([&value, &engine](auto& a) { - value = luaFrom(engine, std::move(a)); + value = engine.luaFrom(std::move(a)); }); return value; } diff --git a/source/core/StarMathCommon.hpp b/source/core/StarMathCommon.hpp index 1627edb..3890403 100644 --- a/source/core/StarMathCommon.hpp +++ b/source/core/StarMathCommon.hpp @@ -10,11 +10,11 @@ namespace Star { STAR_EXCEPTION(MathException, StarException); namespace Constants { - double const pi = 3.14159265358979323846; - double const rad2deg = 57.2957795130823208768; - double const deg2rad = 1 / rad2deg; - double const sqrt2 = 1.41421356237309504880; - double const log2e = 1.44269504088896340736; + double constexpr pi = 3.14159265358979323846; + double constexpr rad2deg = 57.2957795130823208768; + double constexpr deg2rad = 1 / rad2deg; + double constexpr sqrt2 = 1.41421356237309504880; + double constexpr log2e = 1.44269504088896340736; } // Really common std namespace includes, and replacements for std libraries diff --git a/source/core/StarMemory.cpp b/source/core/StarMemory.cpp index 6ecc509..dc13d08 100644 --- a/source/core/StarMemory.cpp +++ b/source/core/StarMemory.cpp @@ -51,7 +51,7 @@ namespace Star { void free(void* ptr, size_t size) { if (ptr) - ::sdallocx(ptr, size, 0); + je_sdallocx(ptr, size, 0); } #endif #elif STAR_USE_MIMALLOC diff --git a/source/core/StarNetCompatibility.cpp b/source/core/StarNetCompatibility.cpp new file mode 100644 index 0000000..94e1ca5 --- /dev/null +++ b/source/core/StarNetCompatibility.cpp @@ -0,0 +1,7 @@ +#include "StarNetCompatibility.hpp" + +namespace Star { + +VersionNumber const OpenProtocolVersion = 3; + +} \ No newline at end of file diff --git a/source/core/StarNetCompatibility.hpp b/source/core/StarNetCompatibility.hpp new file mode 100644 index 0000000..4b950ab --- /dev/null +++ b/source/core/StarNetCompatibility.hpp @@ -0,0 +1,55 @@ +#pragma once +#include "StarVersion.hpp" +#include "StarHash.hpp" + +namespace Star { + +extern VersionNumber const OpenProtocolVersion; + +constexpr VersionNumber AnyVersion = 0xFFFFFFFF; +constexpr VersionNumber LegacyVersion = 0; + +class NetCompatibilityRules { +public: + NetCompatibilityRules(); + NetCompatibilityRules(uint64_t) = delete; + NetCompatibilityRules(VersionNumber version); + + VersionNumber version() const; + void setVersion(VersionNumber version); + bool isLegacy() const; + + bool operator==(NetCompatibilityRules const& a) const; + +private: + VersionNumber m_version = OpenProtocolVersion; +}; + +inline NetCompatibilityRules::NetCompatibilityRules() : m_version(OpenProtocolVersion) {} + +inline NetCompatibilityRules::NetCompatibilityRules(VersionNumber v) : m_version(v) {} + +inline VersionNumber NetCompatibilityRules::version() const { + return m_version; +} + +inline void NetCompatibilityRules::setVersion(VersionNumber version) { + m_version = version; +} + +inline bool NetCompatibilityRules::isLegacy() const { + return m_version == LegacyVersion; +} + +inline bool NetCompatibilityRules::operator==(NetCompatibilityRules const& a) const { + return m_version == a.m_version; +} + +template <> +struct hash { + size_t operator()(NetCompatibilityRules const& s) const { + return s.version(); + } +}; + +} \ No newline at end of file diff --git a/source/core/StarNetElement.cpp b/source/core/StarNetElement.cpp index 914badd..343d45a 100644 --- a/source/core/StarNetElement.cpp +++ b/source/core/StarNetElement.cpp @@ -7,8 +7,8 @@ uint64_t NetElementVersion::current() const { return m_version; } -void NetElementVersion::increment() { - ++m_version; +uint64_t NetElementVersion::increment() { + return ++m_version; } void NetElement::enableNetInterpolation(float) {} diff --git a/source/core/StarNetElement.hpp b/source/core/StarNetElement.hpp index dd909b8..7d73b85 100644 --- a/source/core/StarNetElement.hpp +++ b/source/core/StarNetElement.hpp @@ -9,7 +9,7 @@ namespace Star { class NetElementVersion { public: uint64_t current() const; - void increment(); + uint64_t increment(); private: uint64_t m_version = 0; @@ -20,15 +20,15 @@ class NetElement { public: virtual ~NetElement() = default; - // A network of NetElements will have a shared monotinically increasing + // A network of NetElements will have a shared monotonically increasing // NetElementVersion. When elements are updated, they will mark the version // number at the time they are updated so that a delta can be constructed // that contains only changes since any past version. virtual void initNetVersion(NetElementVersion const* version = nullptr) = 0; // Full store / load of the entire element. - virtual void netStore(DataStream& ds) const = 0; - virtual void netLoad(DataStream& ds) = 0; + virtual void netStore(DataStream& ds, NetCompatibilityRules rules) const = 0; + virtual void netLoad(DataStream& ds, NetCompatibilityRules rules) = 0; // Enables interpolation mode. If interpolation mode is enabled, then // NetElements will delay presenting incoming delta data for the @@ -46,14 +46,34 @@ public: // the version at the time of the *last* call to writeDelta, + 1. If // fromVersion is 0, this will always write the full state. Should return // true if a delta was needed and was written to DataStream, false otherwise. - virtual bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const = 0; + virtual bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const = 0; // Read a delta written by writeNetDelta. 'interpolationTime' is the time in // the future that data from this delta should be delayed and smoothed into, // if interpolation is enabled. - virtual void readNetDelta(DataStream& ds, float interpolationTime = 0.0) = 0; + virtual void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) = 0; // When extrapolating, it is important to notify when a delta WOULD have been // received even if no deltas are produced, so no extrapolation takes place. virtual void blankNetDelta(float interpolationTime); + + VersionNumber compatibilityVersion() const; + void setCompatibilityVersion(VersionNumber version); + bool checkWithRules(NetCompatibilityRules const& rules) const; +private: + VersionNumber m_netCompatibilityVersion = AnyVersion; }; +inline VersionNumber NetElement::compatibilityVersion() const { + return m_netCompatibilityVersion; +} + +inline void NetElement::setCompatibilityVersion(VersionNumber version) { + m_netCompatibilityVersion = version; +} + +inline bool NetElement::checkWithRules(NetCompatibilityRules const& rules) const { + if (m_netCompatibilityVersion != AnyVersion) + return rules.version() >= m_netCompatibilityVersion; + return true; +} + } diff --git a/source/core/StarNetElementBasicFields.cpp b/source/core/StarNetElementBasicFields.cpp index bbb5c5b..618c4a6 100644 --- a/source/core/StarNetElementBasicFields.cpp +++ b/source/core/StarNetElementBasicFields.cpp @@ -49,8 +49,8 @@ void NetElementEvent::setIgnoreOccurrencesOnNetLoad(bool ignoreOccurrencesOnNetL m_ignoreOccurrencesOnNetLoad = ignoreOccurrencesOnNetLoad; } -void NetElementEvent::netLoad(DataStream& ds) { - NetElementUInt::netLoad(ds); +void NetElementEvent::netLoad(DataStream& ds, NetCompatibilityRules rules) { + NetElementUInt::netLoad(ds, rules); if (m_ignoreOccurrencesOnNetLoad) ignoreOccurrences(); } diff --git a/source/core/StarNetElementBasicFields.hpp b/source/core/StarNetElementBasicFields.hpp index 693c6c3..3b46cc0 100644 --- a/source/core/StarNetElementBasicFields.hpp +++ b/source/core/StarNetElementBasicFields.hpp @@ -38,11 +38,11 @@ public: void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; protected: virtual void readData(DataStream& ds, T& t) const = 0; @@ -107,7 +107,7 @@ public: void ignoreOccurrences(); void setIgnoreOccurrencesOnNetLoad(bool ignoreOccurrencesOnNetLoad); - void netLoad(DataStream& ds) override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; protected: void updated() override; @@ -211,7 +211,8 @@ void NetElementBasicField::tickNetInterpolation(float dt) { } template -void NetElementBasicField::netStore(DataStream& ds) const { +void NetElementBasicField::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; if (m_pendingInterpolatedValues && !m_pendingInterpolatedValues->empty()) writeData(ds, m_pendingInterpolatedValues->last().second); else @@ -219,7 +220,8 @@ void NetElementBasicField::netStore(DataStream& ds) const { } template -void NetElementBasicField::netLoad(DataStream& ds) { +void NetElementBasicField::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; readData(ds, m_value); m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0; updated(); @@ -228,7 +230,8 @@ void NetElementBasicField::netLoad(DataStream& ds) { } template -bool NetElementBasicField::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { +bool NetElementBasicField::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; if (m_latestUpdateVersion < fromVersion) return false; @@ -236,11 +239,13 @@ bool NetElementBasicField::writeNetDelta(DataStream& ds, uint64_t fromVersion writeData(ds, m_pendingInterpolatedValues->last().second); else writeData(ds, m_value); + return true; } template -void NetElementBasicField::readNetDelta(DataStream& ds, float interpolationTime) { +void NetElementBasicField::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; T t; readData(ds, t); m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0; diff --git a/source/core/StarNetElementContainers.hpp b/source/core/StarNetElementContainers.hpp index 62cce86..2e16b30 100644 --- a/source/core/StarNetElementContainers.hpp +++ b/source/core/StarNetElementContainers.hpp @@ -26,11 +26,12 @@ public: void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; + bool shouldWriteNetDelta(uint64_t fromVersion, NetCompatibilityRules rules = {}) const; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; mapped_type const& get(key_type const& key) const; mapped_type const* ptr(key_type const& key) const; @@ -77,6 +78,8 @@ public: template void setContents(MapType const& values); + uint64_t changeDataLastVersion() const; + private: // If a delta is written from further back than this many steps, the delta // will fall back to a full serialization of the entire state. @@ -152,7 +155,8 @@ void NetElementMapWrapper::tickNetInterpolation(float dt) { } template -void NetElementMapWrapper::netStore(DataStream& ds) const { +void NetElementMapWrapper::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; ds.writeVlqU(BaseMap::size() + m_pendingChangeData.size()); for (auto const& pair : *this) writeChange(ds, SetChange{pair.first, pair.second}); @@ -162,7 +166,8 @@ void NetElementMapWrapper::netStore(DataStream& ds) const { } template -void NetElementMapWrapper::netLoad(DataStream& ds) { +void NetElementMapWrapper::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; m_changeData.clear(); m_changeDataLastVersion = m_netVersion ? m_netVersion->current() : 0; m_pendingChangeData.clear(); @@ -181,13 +186,27 @@ void NetElementMapWrapper::netLoad(DataStream& ds) { } template -bool NetElementMapWrapper::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { +bool NetElementMapWrapper::shouldWriteNetDelta(uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; + if (fromVersion < m_changeDataLastVersion) + return true; + + for (auto const& p : m_changeData) + if (p.first >= fromVersion) + return true; + + return false; +} + +template +bool NetElementMapWrapper::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; bool deltaWritten = false; if (fromVersion < m_changeDataLastVersion) { deltaWritten = true; ds.writeVlqU(1); - netStore(ds); + netStore(ds, rules); } else { for (auto const& p : m_changeData) { @@ -206,13 +225,14 @@ bool NetElementMapWrapper::writeNetDelta(DataStream& ds, uint64_t fromV } template -void NetElementMapWrapper::readNetDelta(DataStream& ds, float interpolationTime) { +void NetElementMapWrapper::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; while (true) { uint64_t code = ds.readVlqU(); if (code == 0) { break; } else if (code == 1) { - netLoad(ds); + netLoad(ds, rules); } else if (code == 2) { auto change = readChange(ds); addChangeData(change); @@ -381,6 +401,11 @@ void NetElementMapWrapper::setContents(MapType const& values) { reset(BaseMap::from(values)); } +template +uint64_t NetElementMapWrapper::changeDataLastVersion() const { + return m_changeDataLastVersion; +} + template void NetElementMapWrapper::writeChange(DataStream& ds, ElementChange const& change) { if (auto sc = change.template ptr()) { diff --git a/source/core/StarNetElementDynamicGroup.hpp b/source/core/StarNetElementDynamicGroup.hpp index 4f3a2d3..dbd0c12 100644 --- a/source/core/StarNetElementDynamicGroup.hpp +++ b/source/core/StarNetElementDynamicGroup.hpp @@ -45,11 +45,11 @@ public: void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime = 0.0f) override; private: @@ -89,7 +89,7 @@ template auto NetElementDynamicGroup::addNetElement(ElementPtr element) -> ElementId { readyElement(element); DataStreamBuffer storeBuffer; - element->netStore(storeBuffer); + element->netStore(storeBuffer, {}); auto id = m_idMap.add(std::move(element)); addChangeData(ElementAddition(id, storeBuffer.takeData())); @@ -134,7 +134,7 @@ void NetElementDynamicGroup::initNetVersion(NetElementVersion const* ve for (auto& pair : m_idMap) { pair.second->initNetVersion(m_netVersion); DataStreamBuffer storeBuffer; - pair.second->netStore(storeBuffer); + pair.second->netStore(storeBuffer, {}); addChangeData(ElementAddition(pair.first, storeBuffer.takeData())); } } @@ -162,19 +162,22 @@ void NetElementDynamicGroup::tickNetInterpolation(float dt) { } template -void NetElementDynamicGroup::netStore(DataStream& ds) const { +void NetElementDynamicGroup::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; ds.writeVlqU(m_idMap.size()); + m_buffer.setStreamCompatibilityVersion(rules); for (auto& pair : m_idMap) { ds.writeVlqU(pair.first); - pair.second->netStore(m_buffer); + pair.second->netStore(m_buffer, rules); ds.write(m_buffer.data()); m_buffer.clear(); } } template -void NetElementDynamicGroup::netLoad(DataStream& ds) { +void NetElementDynamicGroup::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; m_changeData.clear(); m_changeDataLastVersion = m_netVersion ? m_netVersion->current() : 0; m_idMap.clear(); @@ -188,7 +191,7 @@ void NetElementDynamicGroup::netLoad(DataStream& ds) { DataStreamBuffer storeBuffer(ds.read()); ElementPtr element = make_shared(); - element->netLoad(storeBuffer); + element->netLoad(storeBuffer, rules); readyElement(element); m_idMap.add(id, std::move(element)); @@ -197,10 +200,11 @@ void NetElementDynamicGroup::netLoad(DataStream& ds) { } template -bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { +bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; if (fromVersion < m_changeDataLastVersion) { ds.write(true); - netStore(ds); + netStore(ds, rules); return true; } else { @@ -220,8 +224,9 @@ bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fro } } + m_buffer.setStreamCompatibilityVersion(rules); for (auto& p : m_idMap) { - if (p.second->writeNetDelta(m_buffer, fromVersion)) { + if (p.second->writeNetDelta(m_buffer, fromVersion, rules)) { willWrite(); ds.writeVlqU(p.first + 1); ds.writeBytes(m_buffer.data()); @@ -237,10 +242,11 @@ bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fro } template -void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpolationTime) { +void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; bool isFull = ds.read(); if (isFull) { - netLoad(ds); + netLoad(ds, rules); } else { while (true) { uint64_t code = ds.readVlqU(); @@ -256,7 +262,7 @@ void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpo } else if (auto addition = changeUpdate.template ptr()) { ElementPtr element = make_shared(); DataStreamBuffer storeBuffer(std::move(get<1>(*addition))); - element->netLoad(storeBuffer); + element->netLoad(storeBuffer, rules); readyElement(element); m_idMap.add(get<0>(*addition), std::move(element)); } else if (auto removal = changeUpdate.template ptr()) { @@ -265,7 +271,7 @@ void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpo } else { ElementId elementId = code - 1; auto const& element = m_idMap.get(elementId); - element->readNetDelta(ds, interpolationTime); + element->readNetDelta(ds, interpolationTime, rules); if (m_interpolationEnabled) m_receivedDeltaIds.add(elementId); } diff --git a/source/core/StarNetElementExt.hpp b/source/core/StarNetElementExt.hpp new file mode 100644 index 0000000..50802fe --- /dev/null +++ b/source/core/StarNetElementExt.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "StarNetElement.hpp" + +namespace Star { + +template +class NetElementOverride : public BaseNetElement { +public: + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; + + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; + + typedef std::function NetStorer; + typedef std::function NetLoader; + typedef std::function NetDeltaWriter; + typedef std::function NetDeltaReader; + + void setNetStorer(NetStorer); + void setNetLoader(NetLoader); + void setNetDeltaWriter(NetDeltaWriter); + void setNetDeltaReader(NetDeltaReader); + void setOverrides(NetStorer netStorer, NetLoader netLoader, + NetDeltaWriter netDeltaWriter, NetDeltaReader netDeltaReader); + +private: + NetStorer m_netStorer; + NetLoader m_netLoader; + + NetDeltaWriter m_netDeltaWriter; + NetDeltaReader m_netDeltaReader; +}; + +template +void NetElementOverride::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (m_netStorer) + m_netStorer(ds, rules); + else + BaseNetElement::netStore(ds, rules); +} + +template +void NetElementOverride::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (m_netLoader) + m_netLoader(ds, rules); + else + BaseNetElement::netLoad(ds, rules); +} + +template +bool NetElementOverride::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (m_netDeltaWriter) + return m_netDeltaWriter(ds, fromVersion, rules); + else + return BaseNetElement::writeNetDelta(ds, fromVersion, rules); +} + +template +void NetElementOverride::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (m_netDeltaReader) + m_netDeltaReader(ds, interpolationTime, rules); + else + BaseNetElement::readNetDelta(ds, interpolationTime, rules); +} + +template +inline void NetElementOverride::setNetStorer(NetStorer f) { m_netStorer = std::move(f); } +template +inline void NetElementOverride::setNetLoader(NetLoader f) { m_netLoader = std::move(f); } +template +inline void NetElementOverride::setNetDeltaWriter(NetDeltaWriter f) { m_netDeltaWriter = std::move(f); } +template +inline void NetElementOverride::setNetDeltaReader(NetDeltaReader f) { m_netDeltaReader = std::move(f); } + +template +inline void NetElementOverride::setOverrides(NetStorer netStorer, NetLoader netLoader, NetDeltaWriter netDeltaWriter, NetDeltaReader netDeltaReader) { + m_netStorer = std::move(netStorer); + m_netLoader = std::move(netLoader); + m_netDeltaWriter = std::move(netDeltaWriter); + m_netDeltaReader = std::move(netDeltaReader); +} + +} \ No newline at end of file diff --git a/source/core/StarNetElementFloatFields.hpp b/source/core/StarNetElementFloatFields.hpp index 57f74a9..97cacb7 100644 --- a/source/core/StarNetElementFloatFields.hpp +++ b/source/core/StarNetElementFloatFields.hpp @@ -36,11 +36,11 @@ public: void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime = 0.0f) override; private: @@ -131,7 +131,8 @@ void NetElementFloating::tickNetInterpolation(float dt) { } template -void NetElementFloating::netStore(DataStream& ds) const { +void NetElementFloating::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; if (m_interpolationDataPoints) writeValue(ds, m_interpolationDataPoints->last().second); else @@ -139,7 +140,8 @@ void NetElementFloating::netStore(DataStream& ds) const { } template -void NetElementFloating::netLoad(DataStream& ds) { +void NetElementFloating::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; m_value = readValue(ds); m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0; if (m_interpolationDataPoints) { @@ -149,7 +151,8 @@ void NetElementFloating::netLoad(DataStream& ds) { } template -bool NetElementFloating::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { +bool NetElementFloating::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; if (m_latestUpdateVersion < fromVersion) return false; @@ -162,7 +165,8 @@ bool NetElementFloating::writeNetDelta(DataStream& ds, uint64_t fromVersion) } template -void NetElementFloating::readNetDelta(DataStream& ds, float interpolationTime) { +void NetElementFloating::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + _unused(rules); T t = readValue(ds); m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0; diff --git a/source/core/StarNetElementGroup.cpp b/source/core/StarNetElementGroup.cpp index 249c978..6626e2b 100644 --- a/source/core/StarNetElementGroup.cpp +++ b/source/core/StarNetElementGroup.cpp @@ -21,14 +21,18 @@ void NetElementGroup::initNetVersion(NetElementVersion const* version) { p.first->initNetVersion(m_version); } -void NetElementGroup::netStore(DataStream& ds) const { +void NetElementGroup::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; for (auto& p : m_elements) - p.first->netStore(ds); + if (p.first->checkWithRules(rules)) + p.first->netStore(ds, rules); } -void NetElementGroup::netLoad(DataStream& ds) { +void NetElementGroup::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; for (auto& p : m_elements) - p.first->netLoad(ds); + if (p.first->checkWithRules(rules)) + p.first->netLoad(ds, rules); } void NetElementGroup::enableNetInterpolation(float extrapolationHint) { @@ -56,17 +60,23 @@ void NetElementGroup::tickNetInterpolation(float dt) { } } -bool NetElementGroup::writeNetDelta(DataStream& ds, uint64_t fromStep) const { +bool NetElementGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; if (m_elements.size() == 0) { return false; } else if (m_elements.size() == 1) { - return m_elements[0].first->writeNetDelta(ds, fromStep); + return m_elements[0].first->writeNetDelta(ds, fromVersion, rules); } else { bool deltaWritten = false; - for (uint64_t i = 0; i < m_elements.size(); ++i) { - if (m_elements[i].first->writeNetDelta(m_buffer, fromStep)) { + uint64_t i = 0; + m_buffer.setStreamCompatibilityVersion(rules); + for (auto& element : m_elements) { + if (!element.first->checkWithRules(rules)) + continue; + ++i; + if (element.first->writeNetDelta(m_buffer, fromVersion, rules)) { deltaWritten = true; - ds.writeVlqU(i + 1); + ds.writeVlqU(i); ds.writeBytes(m_buffer.data()); m_buffer.clear(); } @@ -77,23 +87,28 @@ bool NetElementGroup::writeNetDelta(DataStream& ds, uint64_t fromStep) const { } } -void NetElementGroup::readNetDelta(DataStream& ds, float interpolationTime) { +void NetElementGroup::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; if (m_elements.size() == 0) { throw IOException("readNetDelta called on empty NetElementGroup"); } else if (m_elements.size() == 1) { - m_elements[0].first->readNetDelta(ds, interpolationTime); + m_elements[0].first->readNetDelta(ds, interpolationTime, rules); } else { uint64_t readIndex = ds.readVlqU(); - for (uint64_t i = 0; i < m_elements.size(); ++i) { + uint64_t i = 0; + for (auto& element : m_elements) { + if (!element.first->checkWithRules(rules)) + continue; if (readIndex == 0 || readIndex - 1 > i) { if (m_interpolationEnabled) m_elements[i].first->blankNetDelta(interpolationTime); } else if (readIndex - 1 == i) { - m_elements[i].first->readNetDelta(ds, interpolationTime); + m_elements[i].first->readNetDelta(ds, interpolationTime, rules); readIndex = ds.readVlqU(); } else { throw IOException("group indexes out of order in NetElementGroup::readNetDelta"); } + ++i; } } } diff --git a/source/core/StarNetElementGroup.hpp b/source/core/StarNetElementGroup.hpp index fd1bf58..1904a33 100644 --- a/source/core/StarNetElementGroup.hpp +++ b/source/core/StarNetElementGroup.hpp @@ -24,15 +24,15 @@ public: void initNetVersion(NetElementVersion const* version = nullptr) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; void enableNetInterpolation(float extrapolationHint = 0.0f) override; void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime) override; NetElementVersion const* netVersion() const; diff --git a/source/core/StarNetElementSignal.hpp b/source/core/StarNetElementSignal.hpp index 9faa127..61a108d 100644 --- a/source/core/StarNetElementSignal.hpp +++ b/source/core/StarNetElementSignal.hpp @@ -16,15 +16,15 @@ public: void initNetVersion(NetElementVersion const* version = nullptr) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; void enableNetInterpolation(float extrapolationHint = 0.0f) override; void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void send(Signal signal); List receive(); @@ -55,10 +55,10 @@ void NetElementSignal::initNetVersion(NetElementVersion const* version) } template -void NetElementSignal::netStore(DataStream&) const {} +void NetElementSignal::netStore(DataStream&, NetCompatibilityRules) const {} template -void NetElementSignal::netLoad(DataStream&) { +void NetElementSignal::netLoad(DataStream&, NetCompatibilityRules) { } template @@ -83,7 +83,8 @@ void NetElementSignal::tickNetInterpolation(float dt) { } template -bool NetElementSignal::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { +bool NetElementSignal::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; size_t numToWrite = 0; for (auto const& p : m_signals) { if (p.version >= fromVersion) @@ -103,7 +104,8 @@ bool NetElementSignal::writeNetDelta(DataStream& ds, uint64_t fromVersio } template -void NetElementSignal::readNetDelta(DataStream& ds, float interpolationTime) { +void NetElementSignal::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; size_t numToRead = ds.readVlqU(); for (size_t i = 0; i < numToRead; ++i) { Signal s; diff --git a/source/core/StarNetElementSyncGroup.cpp b/source/core/StarNetElementSyncGroup.cpp index ac8da92..cd98638 100644 --- a/source/core/StarNetElementSyncGroup.cpp +++ b/source/core/StarNetElementSyncGroup.cpp @@ -26,23 +26,27 @@ void NetElementSyncGroup::tickNetInterpolation(float dt) { } } -void NetElementSyncGroup::netStore(DataStream& ds) const { +void NetElementSyncGroup::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; const_cast(this)->netElementsNeedStore(); - return NetElementGroup::netStore(ds); + return NetElementGroup::netStore(ds, rules); } -void NetElementSyncGroup::netLoad(DataStream& ds) { - NetElementGroup::netLoad(ds); +void NetElementSyncGroup::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; + NetElementGroup::netLoad(ds, rules); netElementsNeedLoad(true); } -bool NetElementSyncGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { +bool NetElementSyncGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; const_cast(this)->netElementsNeedStore(); - return NetElementGroup::writeNetDelta(ds, fromVersion); + return NetElementGroup::writeNetDelta(ds, fromVersion, rules); } -void NetElementSyncGroup::readNetDelta(DataStream& ds, float interpolationTime) { - NetElementGroup::readNetDelta(ds, interpolationTime); +void NetElementSyncGroup::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; + NetElementGroup::readNetDelta(ds, interpolationTime, rules); m_hasRecentChanges = true; m_recentDeltaTime = interpolationTime; diff --git a/source/core/StarNetElementSyncGroup.hpp b/source/core/StarNetElementSyncGroup.hpp index b01200b..30dafe8 100644 --- a/source/core/StarNetElementSyncGroup.hpp +++ b/source/core/StarNetElementSyncGroup.hpp @@ -13,11 +13,11 @@ public: void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; - bool writeNetDelta(DataStream& ds, uint64_t fromStep) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime = 0.0f) override; protected: diff --git a/source/core/StarNetElementTop.hpp b/source/core/StarNetElementTop.hpp index 454b34f..cf3cfb4 100644 --- a/source/core/StarNetElementTop.hpp +++ b/source/core/StarNetElementTop.hpp @@ -1,5 +1,4 @@ -#ifndef STAR_NET_ELEMENT_TOP_HPP -#define STAR_NET_ELEMENT_TOP_HPP +#pragma once #include "StarNetElement.hpp" @@ -12,10 +11,11 @@ class NetElementTop : public BaseNetElement { public: NetElementTop(); - // Returns the state update, combined with the version code that should be - // passed to the next call to writeState. If 'fromVersion' is 0, then this - // is a full write for an initial read of a slave NetElementTop. - pair writeNetState(uint64_t fromVersion = 0); + // Writes the state update to the given DataStream then returns the version + // code that should be passed to the next call to writeState. If + // 'fromVersion' is 0, then this is a full write for an initial read of a + // slave NetElementTop. + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}); // Reads a state produced by a call to writeState, optionally with the // interpolation delay time for the data contained in this state update. If // the state is a full update rather than a delta, the interoplation delay @@ -23,7 +23,7 @@ public: // readState, *unless* extrapolation is enabled. If extrapolation is // enabled, reading a blank update calls 'blankNetDelta' which is necessary // to not improperly extrapolate past the end of incoming deltas. - void readNetState(ByteArray data, float interpolationTime = 0.0); + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}); private: using BaseNetElement::initNetVersion; @@ -32,6 +32,7 @@ private: using BaseNetElement::writeNetDelta; using BaseNetElement::readNetDelta; using BaseNetElement::blankNetDelta; + using BaseNetElement::checkWithRules; NetElementVersion m_netVersion; }; @@ -42,41 +43,34 @@ NetElementTop::NetElementTop() { } template -pair NetElementTop::writeNetState(uint64_t fromVersion) { +pair NetElementTop::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); if (fromVersion == 0) { - DataStreamBuffer ds; ds.write(true); - BaseNetElement::netStore(ds); - m_netVersion.increment(); - return {ds.takeData(), m_netVersion.current()}; - + BaseNetElement::netStore(ds, rules); + return {ds.takeData(), m_netVersion.increment()}; } else { - DataStreamBuffer ds; ds.write(false); - if (!BaseNetElement::writeNetDelta(ds, fromVersion)) { + if (!BaseNetElement::writeNetDelta(ds, fromVersion, rules)) return {ByteArray(), m_netVersion.current()}; - } else { - m_netVersion.increment(); - return {ds.takeData(), m_netVersion.current()}; - } + else + return {ds.takeData(), m_netVersion.increment()}; } } template -void NetElementTop::readNetState(ByteArray data, float interpolationTime) { +void NetElementTop::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { if (data.empty()) { BaseNetElement::blankNetDelta(interpolationTime); - } else { DataStreamBuffer ds(std::move(data)); - + ds.setStreamCompatibilityVersion(rules); if (ds.read()) - BaseNetElement::netLoad(ds); + BaseNetElement::netLoad(ds, rules); else - BaseNetElement::readNetDelta(ds, interpolationTime); + BaseNetElement::readNetDelta(ds, interpolationTime, rules); } } -} - -#endif +} \ No newline at end of file diff --git a/source/core/StarSignalHandler_windows.cpp b/source/core/StarSignalHandler_windows.cpp index 29e93c5..676734c 100644 --- a/source/core/StarSignalHandler_windows.cpp +++ b/source/core/StarSignalHandler_windows.cpp @@ -4,11 +4,35 @@ #include "StarLogging.hpp" #include +#include "minidumpapiset.h" namespace Star { String g_sehMessage; +static DWORD WINAPI writeMiniDump(void* ExceptionInfo) { + auto hFile = CreateFileA("starbound.dmp", GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (hFile == INVALID_HANDLE_VALUE) + return 0; + MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo{}; + dumpExceptionInfo.ThreadId = GetCurrentThreadId(); + dumpExceptionInfo.ExceptionPointers = (PEXCEPTION_POINTERS)ExceptionInfo; + dumpExceptionInfo.ClientPointers = FALSE; + MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + hFile, + MiniDumpNormal, + &dumpExceptionInfo, + NULL, + NULL); + CloseHandle(hFile); + if (dumpExceptionInfo.ExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { + MessageBoxA(NULL, "Stack overflow encountered\nA minidump has been generated", NULL, MB_OK | MB_ICONERROR | MB_SETFOREGROUND); + } + return 0; +}; + struct SignalHandlerImpl { bool handlingFatal; bool handlingInterrupt; @@ -98,17 +122,19 @@ struct SignalHandlerImpl { } static LONG CALLBACK vectoredExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) { + HANDLE thread = NULL; + LONG result = EXCEPTION_CONTINUE_SEARCH; if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { - fatalError("Stack overflow encountered", false); + thread = CreateThread(NULL, 0, writeMiniDump, (void*)ExceptionInfo, 0, NULL); } if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { handleFatalError("Access violation detected", ExceptionInfo); - return EXCEPTION_CONTINUE_EXECUTION; + result = EXCEPTION_CONTINUE_EXECUTION; } if ((ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) || (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_PRIV_INSTRUCTION)) { handleFatalError("Illegal instruction encountered", ExceptionInfo); - return EXCEPTION_CONTINUE_EXECUTION; + result = EXCEPTION_CONTINUE_EXECUTION; } if ((ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_FLT_DENORMAL_OPERAND) @@ -121,17 +147,17 @@ struct SignalHandlerImpl { ) { handleFatalError("Floating point exception", ExceptionInfo); - return EXCEPTION_CONTINUE_EXECUTION; + result = EXCEPTION_CONTINUE_EXECUTION; } if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { handleFatalError("Division by zero", ExceptionInfo); - return EXCEPTION_CONTINUE_EXECUTION; + result = EXCEPTION_CONTINUE_EXECUTION; } if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_OVERFLOW) { handleFatalError("Integer overflow", ExceptionInfo); - return EXCEPTION_CONTINUE_EXECUTION; + result = EXCEPTION_CONTINUE_EXECUTION; } if ((ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT) @@ -141,10 +167,13 @@ struct SignalHandlerImpl { || (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INVALID_DISPOSITION) || (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INVALID_HANDLE)) { handleFatalError("Error occured", ExceptionInfo); - return EXCEPTION_CONTINUE_EXECUTION; + result = EXCEPTION_CONTINUE_EXECUTION; } - - return EXCEPTION_CONTINUE_SEARCH; + if (thread != NULL) { + WaitForSingleObject(thread, 10000); + CloseHandle(thread); + } + return result; } static BOOL WINAPI consoleCtrlHandler(DWORD) { diff --git a/source/core/StarThread_unix.cpp b/source/core/StarThread_unix.cpp index a87ee29..fe82ea3 100644 --- a/source/core/StarThread_unix.cpp +++ b/source/core/StarThread_unix.cpp @@ -22,6 +22,10 @@ #define MAX_THREAD_NAMELEN 16 #endif +//#ifndef STAR_SYSTEM_MACOS +//#define STAR_MUTEX_TIMED +//#endif + namespace Star { struct ThreadImpl { @@ -74,6 +78,8 @@ struct ThreadImpl { #ifdef STAR_SYSTEM_FREEBSD pthread_set_name_np(pthread, tname); +#elif defined(STAR_SYSTEM_NETBSD) + pthread_setname_np(pthread, "%s", tname); #elif not defined STAR_SYSTEM_MACOS pthread_setname_np(pthread, tname); #endif @@ -131,7 +137,7 @@ struct MutexImpl { } void lock() { -#ifndef STAR_SYSTEM_MACOS +#ifdef STAR_MUTEX_TIMED timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 60; @@ -210,7 +216,7 @@ struct RecursiveMutexImpl { } void lock() { -#ifndef STAR_SYSTEM_MACOS +#ifdef STAR_MUTEX_TIMED timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 60; diff --git a/source/base/StarVersion.cpp.in b/source/core/StarVersion.cpp.in similarity index 100% rename from source/base/StarVersion.cpp.in rename to source/core/StarVersion.cpp.in diff --git a/source/base/StarVersion.hpp b/source/core/StarVersion.hpp similarity index 100% rename from source/base/StarVersion.hpp rename to source/core/StarVersion.hpp diff --git a/source/extern/CMakeLists.txt b/source/extern/CMakeLists.txt index 336ca0d..1fd48e1 100644 --- a/source/extern/CMakeLists.txt +++ b/source/extern/CMakeLists.txt @@ -15,6 +15,7 @@ SET (star_extern_HEADERS fmt/printf.h fmt/ranges.h fmt/std.h + fast_float.h lauxlib.h lua.h lua.hpp diff --git a/source/extern/fast_float.h b/source/extern/fast_float.h new file mode 100644 index 0000000..860001d --- /dev/null +++ b/source/extern/fast_float.h @@ -0,0 +1,3913 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// with contributions from Lénárd Szolnoki +// with contributions from Jan Pharago +// with contributions from Maya Warrier +// with contributions from Taha Khokhar +// +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License or the Boost License. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// +// BOOST License Notice +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +// Testing for https://wg21.link/N3652, adopted in C++14 +#if __cpp_constexpr >= 201304 +#define FASTFLOAT_CONSTEXPR14 constexpr +#else +#define FASTFLOAT_CONSTEXPR14 +#endif + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +#define FASTFLOAT_HAS_BIT_CAST 1 +#else +#define FASTFLOAT_HAS_BIT_CAST 0 +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && \ + __cpp_lib_is_constant_evaluated >= 201811L +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#else +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#endif + +// Testing for relevant C++20 constexpr library features +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ + __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ +#define FASTFLOAT_CONSTEXPR20 constexpr +#define FASTFLOAT_IS_CONSTEXPR 1 +#else +#define FASTFLOAT_CONSTEXPR20 +#define FASTFLOAT_IS_CONSTEXPR 0 +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 +#else +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 +#endif + +#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include +#include +#include +#include +#ifdef __has_include +#if __has_include() && (__cplusplus > 202002L || _MSVC_LANG > 202002L) +#include +#endif +#endif + +namespace fast_float { + +#define FASTFLOAT_JSONFMT (1 << 5) +#define FASTFLOAT_FORTRANFMT (1 << 6) + +enum chars_format { + scientific = 1 << 0, + fixed = 1 << 2, + hex = 1 << 3, + no_infnan = 1 << 4, + // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 + json = FASTFLOAT_JSONFMT | fixed | scientific | no_infnan, + // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. + json_or_infnan = FASTFLOAT_JSONFMT | fixed | scientific, + fortran = FASTFLOAT_FORTRANFMT | fixed | scientific, + general = fixed | scientific +}; + +template struct from_chars_result_t { + UC const *ptr; + std::errc ec; +}; +using from_chars_result = from_chars_result_t; + +template struct parse_options_t { + constexpr explicit parse_options_t(chars_format fmt = chars_format::general, + UC dot = UC('.')) + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + UC decimal_point; +}; +using parse_options = parse_options_t; + +} // namespace fast_float + +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ + defined(__MINGW64__) || defined(__s390x__) || \ + (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ + defined(__PPC64LE__)) || \ + defined(__loongarch64)) +#define FASTFLOAT_64BIT 1 +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ + defined(__MINGW32__) || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_32BIT 1 +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. +// We can never tell the register width, but the SIZE_MAX is a good +// approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max +// portability. +#if SIZE_MAX == 0xffff +#error Unknown platform (16-bit, unsupported) +#elif SIZE_MAX == 0xffffffff +#define FASTFLOAT_32BIT 1 +#elif SIZE_MAX == 0xffffffffffffffff +#define FASTFLOAT_64BIT 1 +#else +#error Unknown platform (not 32-bit, not 64-bit?) +#endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ + (defined(_M_ARM64) && !defined(__MINGW32__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ + (defined(_M_AMD64) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP == 2))) +#define FASTFLOAT_SSE2 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +#define FASTFLOAT_NEON 1 +#endif + +#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) +#define FASTFLOAT_HAS_SIMD 1 +#endif + +#if defined(__GNUC__) +// disable -Wcast-align=strict (GCC only) +#define FASTFLOAT_SIMD_DISABLE_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-align\"") +#else +#define FASTFLOAT_SIMD_DISABLE_WARNINGS +#endif + +#if defined(__GNUC__) +#define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +#else +#define FASTFLOAT_SIMD_RESTORE_WARNINGS +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) \ + { ((void)(x)); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#define FASTFLOAT_DEBUG_ASSERT(x) \ + { ((void)(x)); } +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) \ + { \ + if (!(x)) \ + return false; \ + } + +#define FASTFLOAT_ENABLE_IF(...) \ + typename std::enable_if<(__VA_ARGS__), int>::type + +namespace fast_float { + +fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +template +fastfloat_really_inline constexpr bool is_supported_float_type() { + return std::is_same::value || std::is_same::value +#if __STDCPP_FLOAT32_T__ + || std::is_same::value +#endif +#if __STDCPP_FLOAT64_T__ + || std::is_same::value +#endif + ; +} + +template +fastfloat_really_inline constexpr bool is_supported_char_type() { + return std::is_same::value || std::is_same::value || + std::is_same::value || std::is_same::value; +} + +// Compares two ASCII strings in a case insensitive manner. +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp(UC const *input1, UC const *input2, size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; ++i) { + running_diff |= (char(input1[i]) ^ char(input2[i])); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template struct span { + const T *ptr; + size_t length; + constexpr span(const T *_ptr, size_t _length) : ptr(_ptr), length(_length) {} + constexpr span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { return length; } + + FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + constexpr value128() : low(0), high(0) {} +}; + +/* Helper C++14 constexpr generic implementation of leading_zeroes */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int +leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { + if (input_num & uint64_t(0xffffffff00000000)) { + input_num >>= 32; + last_bit |= 32; + } + if (input_num & uint64_t(0xffff0000)) { + input_num >>= 16; + last_bit |= 16; + } + if (input_num & uint64_t(0xff00)) { + input_num >>= 8; + last_bit |= 8; + } + if (input_num & uint64_t(0xf0)) { + input_num >>= 4; + last_bit |= 4; + } + if (input_num & uint64_t(0xc)) { + input_num >>= 2; + last_bit |= 2; + } + if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ + last_bit |= 1; + } + return 63 - last_bit; +} + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int +leading_zeroes(uint64_t input_num) { + assert(input_num > 0); + if (cpp20_and_in_constexpr()) { + return leading_zeroes_generic(input_num); + } +#ifdef FASTFLOAT_VISUAL_STUDIO +#if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); +#else + return leading_zeroes_generic(input_num); +#endif +#else + return __builtin_clzll(input_num); +#endif +} + +// slow emulation routine for 32-bit +fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = (uint64_t)(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + (uint64_t)(lo < bd); + return lo; +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, + uint64_t cd, + uint64_t *hi) { + return umul128_generic(ab, cd, hi); +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + +// compute 64-bit a*b +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +full_multiplication(uint64_t a, uint64_t b) { + if (cpp20_and_in_constexpr()) { + value128 answer; + answer.low = umul128_generic(a, b, &answer.high); + return answer; + } + value128 answer; +#if defined(_M_ARM64) && !defined(__MINGW32__) + // ARM64 has native support for 64-bit multiplications, no need to emulate + // But MinGW on ARM64 doesn't have native support for 64-bit multiplications + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || \ + (defined(_WIN64) && !defined(__clang__) && !defined(_M_ARM64)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + answer.low = umul128_generic(a, b, &answer.high); +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + constexpr bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + constexpr bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +// used for binary_format_lookup_tables::max_mantissa +constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; + +template struct binary_format_lookup_tables; + +template struct binary_format : binary_format_lookup_tables { + using equiv_uint = + typename std::conditional::type; + + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int + min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(int64_t power); + static inline constexpr uint64_t + max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); + static inline constexpr size_t max_digits(); + static inline constexpr equiv_uint exponent_mask(); + static inline constexpr equiv_uint mantissa_mask(); + static inline constexpr equiv_uint hidden_bit_mask(); +}; + +template struct binary_format_lookup_tables { + static constexpr double powers_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Largest integer value v so that (5**index * v) <= 1<<53. + // 0x20000000000000 == 1 << 53 + static constexpr uint64_t max_mantissa[] = { + 0x20000000000000, + 0x20000000000000 / 5, + 0x20000000000000 / (5 * 5), + 0x20000000000000 / (5 * 5 * 5), + 0x20000000000000 / (5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555), + 0x20000000000000 / (constant_55555 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr double binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template struct binary_format_lookup_tables { + static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, + 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; + + // Largest integer value v so that (5**index * v) <= 1<<24. + // 0x1000000 == 1<<24 + static constexpr uint64_t max_mantissa[] = { + 0x1000000, + 0x1000000 / 5, + 0x1000000 / (5 * 5), + 0x1000000 / (5 * 5 * 5), + 0x1000000 / (5 * 5 * 5 * 5), + 0x1000000 / (constant_55555), + 0x1000000 / (constant_55555 * 5), + 0x1000000 / (constant_55555 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * constant_55555), + 0x1000000 / (constant_55555 * constant_55555 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr float binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 63; +} +template <> inline constexpr int binary_format::sign_index() { + return 31; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 22 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 10 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr double +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> inline constexpr int binary_format::smallest_power_of_ten() { + return -64; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F800000; +} +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007FFFFF; +} +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x00800000; +} +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +to_float(bool negative, adjusted_mantissa am, T &value) { + using fastfloat_uint = typename binary_format::equiv_uint; + fastfloat_uint word = (fastfloat_uint)am.mantissa; + word |= fastfloat_uint(am.power2) + << binary_format::mantissa_explicit_bits(); + word |= fastfloat_uint(negative) << binary_format::sign_index(); +#if FASTFLOAT_HAS_BIT_CAST + value = std::bit_cast(word); +#else + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default +template struct space_lut { + static constexpr bool value[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr bool space_lut::value[]; + +#endif + +inline constexpr bool is_space(uint8_t c) { return space_lut<>::value[c]; } +#endif + +template static constexpr uint64_t int_cmp_zeros() { + static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), + "Unsupported character size"); + return (sizeof(UC) == 1) ? 0x3030303030303030 + : (sizeof(UC) == 2) + ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | + uint64_t(UC('0')) << 16 | UC('0')) + : (uint64_t(UC('0')) << 32 | UC('0')); +} +template static constexpr int int_cmp_len() { + return sizeof(uint64_t) / sizeof(UC); +} +template static constexpr UC const *str_const_nan() { + return nullptr; +} +template <> constexpr char const *str_const_nan() { return "nan"; } +template <> constexpr wchar_t const *str_const_nan() { return L"nan"; } +template <> constexpr char16_t const *str_const_nan() { + return u"nan"; +} +template <> constexpr char32_t const *str_const_nan() { + return U"nan"; +} +template static constexpr UC const *str_const_inf() { + return nullptr; +} +template <> constexpr char const *str_const_inf() { return "infinity"; } +template <> constexpr wchar_t const *str_const_inf() { + return L"infinity"; +} +template <> constexpr char16_t const *str_const_inf() { + return u"infinity"; +} +template <> constexpr char32_t const *str_const_inf() { + return U"infinity"; +} + +template struct int_luts { + static constexpr uint8_t chdigit[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + + static constexpr size_t maxdigits_u64[] = { + 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, + 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; + + static constexpr uint64_t min_safe_u64[] = { + 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, + 7450580596923828125, 4738381338321616896, 3909821048582988049, + 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, + 5559917313492231481, 2218611106740436992, 8650415919381337933, + 2177953337809371136, 6568408355712890625, 1152921504606846976, + 2862423051509815793, 6746640616477458432, 15181127029874798299ull, + 1638400000000000000, 3243919932521508681, 6221821273427820544, + 11592836324538749809ull, 876488338465357824, 1490116119384765625, + 2481152873203736576, 4052555153018976267, 6502111422497947648, + 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, + 1152921504606846976, 1667889514952984961, 2386420683693101056, + 3379220508056640625, 4738381338321616896}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint8_t int_luts::chdigit[]; + +template constexpr size_t int_luts::maxdigits_u64[]; + +template constexpr uint64_t int_luts::min_safe_u64[]; + +#endif + +template +fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { + return int_luts<>::chdigit[static_cast(c)]; +} + +fastfloat_really_inline constexpr size_t max_digits_u64(int base) { + return int_luts<>::maxdigits_u64[base - 2]; +} + +// If a u64 is exactly max_digits_u64() in length, this is +// the value below which it has definitely overflowed. +fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { + return int_luts<>::min_safe_u64[base - 2]; +} + +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + + +namespace fast_float { +/** + * This function parses the character sequence [first,last) for a number. It + * parses floating-point numbers expecting a locale-indepent format equivalent + * to what is used by std::strtod in the default ("C") locale. The resulting + * floating-point value is the closest floating-point values (using either float + * or double), using the "round to even" convention for values that would + * otherwise fall right in-between two values. That is, we provide exact parsing + * according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to + * point right after the parsed number, and the `value` referenced is set to the + * parsed value. In case of error, the returned `ec` contains a representative + * error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with + * `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an + * optional last argument of the type `fast_float::chars_format`. It is a bitset + * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & + * fast_float::chars_format::scientific` are set to determine whether we allow + * the fixed point and scientific notation respectively. The default is + * `fast_float::chars_format::general` which allows both `fixed` and + * `scientific`. + */ +template ())> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept; +/** + * from_chars for integer types. + */ +template ())> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; + +} // namespace fast_float +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include +#include +#include + + +#ifdef FASTFLOAT_SSE2 +#include +#endif + +#ifdef FASTFLOAT_NEON +#include +#endif + +namespace fast_float { + +template fastfloat_really_inline constexpr bool has_simd_opt() { +#ifdef FASTFLOAT_HAS_SIMD + return std::is_same::value; +#else + return false; +#endif +} + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +template +fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { + return !(c > UC('9') || c < UC('0')); +} + +fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | + (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | + (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | + (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; +} + +// Read 8 UC into a u64. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +read8_to_u64(const UC *chars) { + if (cpp20_and_in_constexpr() || !std::is_same::value) { + uint64_t val = 0; + for (int i = 0; i < 8; ++i) { + val |= uint64_t(uint8_t(*chars)) << (i * 8); + ++chars; + } + return val; + } + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +#ifdef FASTFLOAT_SSE2 + +fastfloat_really_inline uint64_t simd_read8_to_u64(const __m128i data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + const __m128i packed = _mm_packus_epi16(data, data); +#ifdef FASTFLOAT_64BIT + return uint64_t(_mm_cvtsi128_si64(packed)); +#else + uint64_t value; + // Visual Studio + older versions of GCC don't support _mm_storeu_si64 + _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); + return value; +#endif + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(const char16_t *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + _mm_loadu_si128(reinterpret_cast(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#elif defined(FASTFLOAT_NEON) + +fastfloat_really_inline uint64_t simd_read8_to_u64(const uint16x8_t data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint8x8_t utf8_packed = vmovn_u16(data); + return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(const char16_t *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + vld1q_u16(reinterpret_cast(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#endif // FASTFLOAT_SSE2 + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +uint64_t simd_read8_to_u64(UC const *) { + return 0; +} + +// credit @aqrit +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t +parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +// Call this if chars are definitely 8 digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t +parse_eight_digits_unrolled(UC const *chars) noexcept { + if (cpp20_and_in_constexpr() || !has_simd_opt()) { + return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay + } + return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline constexpr bool +is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +#ifdef FASTFLOAT_HAS_SIMD + +// Call this if chars might not be 8 digits. +// Using this style (instead of is_made_of_eight_digits_fast() then +// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +simd_parse_if_eight_digits_unrolled(const char16_t *chars, + uint64_t &i) noexcept { + if (cpp20_and_in_constexpr()) { + return false; + } +#ifdef FASTFLOAT_SSE2 + FASTFLOAT_SIMD_DISABLE_WARNINGS + const __m128i data = + _mm_loadu_si128(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + const __m128i t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); + const __m128i t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); + + if (_mm_movemask_epi8(t1) == 0) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#elif defined(FASTFLOAT_NEON) + FASTFLOAT_SIMD_DISABLE_WARNINGS + const uint16x8_t data = vld1q_u16(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + const uint16x8_t t0 = vsubq_u16(data, vmovq_n_u16('0')); + const uint16x8_t mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); + + if (vminvq_u16(mask) == 0xFFFF) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#else + (void)chars; + (void)i; + return false; +#endif // FASTFLOAT_SSE2 +} + +#endif // FASTFLOAT_HAS_SIMD + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { + return 0; +} + +template ::value) = 0> +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(const UC *&p, const UC *const pend, uint64_t &i) { + if (!has_simd_opt()) { + return; + } + while ((std::distance(p, pend) >= 8) && + simd_parse_if_eight_digits_unrolled( + p, i)) { // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(const char *&p, const char *const pend, + uint64_t &i) { + // optimizes better than parse_if_eight_digits_unrolled() for UC = char. + while ((std::distance(p, pend) >= 8) && + is_made_of_eight_digits_fast(read8_to_u64(p))) { + i = i * 100000000 + + parse_eight_digits_unrolled(read8_to_u64( + p)); // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +enum class parse_error { + no_error, + // [JSON-only] The minus sign must be followed by an integer. + missing_integer_after_sign, + // A sign must be followed by an integer or dot. + missing_integer_or_dot_after_sign, + // [JSON-only] The integer part must not have leading zeros. + leading_zeros_in_integer_part, + // [JSON-only] The integer part must have at least one digit. + no_digits_in_integer_part, + // [JSON-only] If there is a decimal point, there must be digits in the + // fractional part. + no_digits_in_fractional_part, + // The mantissa must have at least one digit. + no_digits_in_mantissa, + // Scientific notation requires an exponential part. + missing_exponential_part, +}; + +template struct parsed_number_string_t { + int64_t exponent{0}; + uint64_t mantissa{0}; + UC const *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + span integer{}; // non-nullable + span fraction{}; // nullable + parse_error error{parse_error::no_error}; +}; + +using byte_span = span; +using parsed_number_string = parsed_number_string_t; + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +report_parse_error(UC const *p, parse_error error) { + parsed_number_string_t answer; + answer.valid = false; + answer.lastmatch = p; + answer.error = error; + return answer; +} + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string(UC const *p, UC const *pend, + parse_options_t options) noexcept { + chars_format const fmt = options.format; + UC const decimal_point = options.decimal_point; + + parsed_number_string_t answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == UC('-')); +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if ((*p == UC('-')) || (!(fmt & FASTFLOAT_JSONFMT) && *p == UC('+'))) { +#else + if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here +#endif + ++p; + if (p == pend) { + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + if (fmt & FASTFLOAT_JSONFMT) { + if (!is_integer(*p)) { // a sign must be followed by an integer + return report_parse_error(p, + parse_error::missing_integer_after_sign); + } + } else { + if (!is_integer(*p) && + (*p != + decimal_point)) { // a sign must be followed by an integer or the dot + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + } + } + UC const *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - + UC('0')); // might overflow, we will handle the overflow later + ++p; + } + UC const *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = span(start_digits, size_t(digit_count)); + if (fmt & FASTFLOAT_JSONFMT) { + // at least 1 digit in integer part, without leading zeros + if (digit_count == 0) { + return report_parse_error(p, parse_error::no_digits_in_integer_part); + } + if ((start_digits[0] == UC('0') && digit_count > 1)) { + return report_parse_error(start_digits, + parse_error::leading_zeros_in_integer_part); + } + } + + int64_t exponent = 0; + const bool has_decimal_point = (p != pend) && (*p == decimal_point); + if (has_decimal_point) { + ++p; + UC const *before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + loop_parse_if_eight_digits(p, pend, i); + + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = span(before, size_t(p - before)); + digit_count -= exponent; + } + if (fmt & FASTFLOAT_JSONFMT) { + // at least 1 digit in fractional part + if (has_decimal_point && exponent == 0) { + return report_parse_error(p, + parse_error::no_digits_in_fractional_part); + } + } else if (digit_count == + 0) { // we must have encountered at least one integer! + return report_parse_error(p, parse_error::no_digits_in_mantissa); + } + int64_t exp_number = 0; // explicit exponential part + if (((fmt & chars_format::scientific) && (p != pend) && + ((UC('e') == *p) || (UC('E') == *p))) || + ((fmt & FASTFLOAT_FORTRANFMT) && (p != pend) && + ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || + (UC('D') == *p)))) { + UC const *location_of_e = p; + if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || + (UC('D') == *p)) { + ++p; + } + bool neg_exp = false; + if ((p != pend) && (UC('-') == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && + (UC('+') == + *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if (!(fmt & chars_format::fixed)) { + // The exponential part is invalid for scientific notation, so it must + // be a trailing token for fixed notation. However, fixed notation is + // disabled, so report a scientific notation error. + return report_parse_error(p, parse_error::missing_exponential_part); + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if (neg_exp) { + exp_number = -exp_number; + } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if ((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { + return report_parse_error(p, parse_error::missing_exponential_part); + } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + UC const *start = start_digits; + while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { + if (*start == UC('0')) { + digit_count--; + } + start++; + } + + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + UC const *int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + UC const *frac_end = p + answer.fraction.len(); + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +parse_int_string(UC const *p, UC const *pend, T &value, int base) { + from_chars_result_t answer; + + UC const *const first = p; + + bool negative = (*p == UC('-')); + if (!std::is_signed::value && negative) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if ((*p == UC('-')) || (*p == UC('+'))) { +#else + if (*p == UC('-')) { +#endif + ++p; + } + + UC const *const start_num = p; + + while (p != pend && *p == UC('0')) { + ++p; + } + + const bool has_leading_zeros = p > start_num; + + UC const *const start_digits = p; + + uint64_t i = 0; + if (base == 10) { + loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible + } + while (p != pend) { + uint8_t digit = ch_to_digit(*p); + if (digit >= base) { + break; + } + i = uint64_t(base) * i + digit; // might overflow, check this later + p++; + } + + size_t digit_count = size_t(p - start_digits); + + if (digit_count == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + answer.ptr = p; + + // check u64 overflow + size_t max_digits = max_digits_u64(base); + if (digit_count > max_digits) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + // this check can be eliminated for all other types, but they will all require + // a max_digits(base) equivalent + if (digit_count == max_digits && i < min_safe_u64(base)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + + // check other types overflow + if (!std::is_same::value) { + if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + } + + if (negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4146) +#endif + // this weird workaround is required because: + // - converting unsigned to signed when its value is greater than signed max + // is UB pre-C++23. + // - reinterpret_casting (~i + 1) would work, but it is not constexpr + // this is always optimized into a neg instruction (note: T is an integer + // type) + value = T(-std::numeric_limits::max() - + T(i - uint64_t(std::numeric_limits::max()))); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + } else { + value = T(i); + } + + answer.ec = std::errc(); + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^-1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template struct powers_template { + + constexpr static int smallest_power_of_five = + binary_format::smallest_power_of_ten(); + constexpr static int largest_power_of_five = + binary_format::largest_power_of_ten(); + constexpr static int number_of_entries = + 2 * (largest_power_of_five - smallest_power_of_five + 1); + // Powers of five from 5^-342 all the way to 5^308 rounded toward one. + constexpr static uint64_t power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a, 0x113faa2906a13b3f, + 0x9558b4661b6565f8, 0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, + 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, + 0x91d8a02bb6c10594, 0x79071b9b8a4be869, + 0xb64ec836a47146f9, 0x9748e2826cdee284, + 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f, 0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723, 0xad2c788035e61382, + 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, + 0xa9c98d8ccb009506, 0x680efdaf511f18c2, + 0xd43bf0effdc0ba48, 0x212bd1b2566def2, + 0x84a57695fe98746d, 0x14bb630f7604b57, + 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, + 0xcf42894a5dce35ea, 0x52064cac828675b9, + 0x818995ce7aa0e1b2, 0x7343efebd1940993, + 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6, 0xd41a26e077774ef6, + 0xfd00b897478238d0, 0x8920b098955522b4, + 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, + 0xc5a890362fddbc62, 0xeb2189f734aa831d, + 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d, 0x47b233c92125366e, + 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, + 0xf148440a256e2c76, 0xc00670ea43ca250d, + 0x96cd2a865764dbca, 0x380406926a5e5728, + 0xbc807527ed3e12bc, 0xc605083704f5ecf2, + 0xeba09271e88d976b, 0xf7864a44c633682e, + 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c, 0x5960ea05bad82964, + 0xe61acf033d1a45df, 0x6fb92487298e33bd, + 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, + 0xe0b62e2929aba83c, 0x331acdabfe94de87, + 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, + 0x892731ac9faf056e, 0xbe311c083a225cd2, + 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, + 0xd64d3d9db981787d, 0x92cbbccdad5b108, + 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, + 0xa76c582338ed2621, 0xaf2af2b80af6f24e, + 0xd1476e2c07286faa, 0x1af5af660db4aee1, + 0x82cca4db847945ca, 0x50d98d9fc890ed4d, + 0xa37fce126597973c, 0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1, 0x77b191618c54e9ac, + 0xc795830d75038c1d, 0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, + 0x9becce62836ac577, 0x4ee367f9430aec32, + 0xc2e801fb244576d5, 0x229c41f793cda73f, + 0xf3a20279ed56d48a, 0x6b43527578c1110f, + 0x9845418c345644d6, 0x830a13896b78aaa9, + 0xbe5691ef416bd60c, 0x23cc986bc656d553, + 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, + 0x91376c36d99995be, 0x23100809b9c21fa1, + 0xb58547448ffffb2d, 0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, + 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, + 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d, 0x40405643d711d583, + 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, + 0xad1c8eab5ee43b66, 0xda3243650005eecf, + 0xd863b256369d4a40, 0x90bed43e40076a82, + 0x873e4f75e2224e68, 0x5a7744a6e804a291, + 0xa90de3535aaae202, 0x711515d0a205cb36, + 0xd3515c2831559a83, 0xd5a5b44ca873e03, + 0x8412d9991ed58091, 0xe858790afe9486c2, + 0xa5178fff668ae0b6, 0x626e974dbe39a872, + 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, + 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, + 0xc987434744ac874e, 0xa327ffb266b56220, + 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, + 0xc4ce17b399107c22, 0xcb550fb4384d21d3, + 0xf6019da07f549b2b, 0x7e2a53a146606a48, + 0x99c102844f94e0fb, 0x2eda7444cbfc426d, + 0xc0314325637a1939, 0xfa911155fefb5308, + 0xf03d93eebc589f88, 0x793555ab7eba27ca, + 0x96267c7535b763b5, 0x4bc1558b2f3458de, + 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb, 0x465e15a979c1cadc, + 0x92a1958a7675175f, 0xbfacd89ec191ec9, + 0xb749faed14125d36, 0xcef980ec671f667b, + 0xe51c79a85916f484, 0x82b7e12780e7401a, + 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9, 0x67a791e093e1d49a, + 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d, 0x58fae9f773886e18, + 0xda7f5bf590966848, 0xaf39a475506a899e, + 0x888f99797a5e012d, 0x6d8406c952429603, + 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, + 0xd0601d8efc57b08b, 0xf13b94daf124da26, + 0x823c12795db6ce57, 0x76c53d08d6b70858, + 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, + 0xf867241c8cc6d4c0, 0xc30163d203c94b62, + 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, + 0xc21094364dfb5636, 0x985915fc12f542e4, + 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, + 0xbd8430bd08277231, 0x50c6ff782a838353, + 0xece53cec4a314ebd, 0xa4f8bf5635246428, + 0x940f4613ae5ed136, 0x871b7795e136be99, + 0xb913179899f68584, 0x28e2557b59846e3f, + 0xe757dd7ec07426e5, 0x331aeada2fe589cf, + 0x9096ea6f3848984f, 0x3ff0d2c85def7621, + 0xb4bca50b065abe63, 0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, + 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, + 0xb080392cc4349dec, 0xbd8d794d96aacfb3, + 0xdca04777f541c567, 0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60, 0xf41686c49db57244, + 0xac5d37d5b79b6239, 0x311c2875c522ced5, + 0xd77485cb25823ac7, 0x7d633293366b828b, + 0x86a8d39ef77164bc, 0xae5dff9c02033197, + 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, + 0xd267caa862a12d66, 0xd072df63c324fd7b, + 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, + 0xa46116538d0deb78, 0x52d9be85f074e608, + 0xcd795be870516656, 0x67902e276c921f8b, + 0x806bd9714632dff6, 0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c, 0x796b805720085f81, + 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, + 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b, 0xee92fb5515482d44, + 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, + 0xef340a98172aace4, 0x86fb897116c87c34, + 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, + 0xbae0a846d2195712, 0x8974836059cca109, + 0xe998d258869facd7, 0x2bd1a438703fc94b, + 0x91ff83775423cc06, 0x7b6306a34627ddcf, + 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, + 0x8e938662882af53e, 0x547eb47b7282ee9c, + 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, + 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, + 0xae0b158b4738705e, 0x9624ab50b148d445, + 0xd98ddaee19068c76, 0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b, 0x7647c3200069671f, + 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, + 0xa5fb0a17c777cf09, 0xf468107100525890, + 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, + 0x81ac1fe293d599bf, 0xc6f14cd848405530, + 0xa21727db38cb002f, 0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, + 0xfd442e4688bd304a, 0x908f4a166d1da663, + 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, + 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, + 0xf7549530e188c128, 0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, + 0xc13a148e3032d6e7, 0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, + 0xebdf661791d60f56, 0x111b495b3464ad21, + 0x936b9fcebb25c995, 0xcab10dd900beec34, + 0xb84687c269ef3bfb, 0x3d5d514f40eea742, + 0xe65829b3046b0afa, 0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, + 0xb3f4e093db73a093, 0x59ed216765690f56, + 0xe0f218b8d25088b8, 0x306869c13ec3532c, + 0x8c974f7383725573, 0x1e414218c73a13fb, + 0xafbd2350644eeacf, 0xe5d1929ef90898fa, + 0xdbac6c247d62a583, 0xdf45f746b74abf39, + 0x894bc396ce5da772, 0x6b8bba8c328eb783, + 0xab9eb47c81f5114f, 0x66ea92f3f326564, + 0xd686619ba27255a2, 0xc80a537b0efefebd, + 0x8613fd0145877585, 0xbd06742ce95f5f36, + 0xa798fc4196e952e7, 0x2c48113823b73704, + 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, + 0x82ef85133de648c4, 0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3, 0x318df905079926a8, + 0xffbbcfe994e5c61f, 0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d, 0x6bea10ca65c084e, + 0xc31bfa0fe5698db8, 0x486e494fcff30a62, + 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, + 0xbe89523386091465, 0xf6bbb397f1135823, + 0xee2ba6c0678b597f, 0x746aa07ded582e2c, + 0x94db483840b717ef, 0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb, 0x92f34d62616ce413, + 0xe896a0d7e51e1566, 0x77b020baf9c81d17, + 0x915e2486ef32cd60, 0xace1474dc1d122e, + 0xb5b5ada8aaff80b8, 0xd819992132456ba, + 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, + 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, + 0xd89d64d57a607744, 0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b, 0x11471cd764ad4972, + 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, + 0xd389b47879823479, 0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb, 0xcedf722a585139ba, + 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, + 0xce947a3da6a9273e, 0x733d226229feea32, + 0x811ccc668829b887, 0x806357d5a3f525f, + 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, + 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, + 0xc5029163f384a931, 0xa9e795e65d4df11, + 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e, 0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, + 0xf07da27a82c37088, 0x5d767327bb4e5a4c, + 0x964e858c91ba2655, 0x3a6a07f8d510f86f, + 0xbbe226efb628afea, 0x890489f70a55368b, + 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, + 0xe55990879ddcaabd, 0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, + 0xb32df8e9f3546564, 0x47939822dc96abf9, + 0xdff9772470297ebd, 0x59787e2b93bc56f7, + 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, + 0xaefae51477a06b03, 0xede622920b6b23f1, + 0xdab99e59958885c4, 0xe95fab368e45eced, + 0x88b402f7fd75539b, 0x11dbcb0218ebb414, + 0xaae103b5fcd2a881, 0xd652bdc29f26a119, + 0xd59944a37c0752a2, 0x4be76d3346f0495f, + 0x857fcae62d8493a5, 0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, + 0x825ecc24c873782f, 0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b, 0x728900802f0f32fa, + 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc, 0xe2f610c84987bfa8, + 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143, 0x91503d1c79720dbb, + 0xf8a95fcf88747d94, 0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, + 0xc24452da229b021b, 0xfbe85badce996168, + 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, + 0x97c560ba6b0919a5, 0xdccd879fc967d41a, + 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, + 0xed246723473e3813, 0x290123e9aab23b68, + 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, + 0xb94470938fa89bce, 0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, + 0x8d590723948a535f, 0x579c487e5a38ad0e, + 0xb0af48ec79ace837, 0x2d835a9df0c6d851, + 0xdcdb1b2798182244, 0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, + 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, + 0xa87fea27a539e9a5, 0x3f2398d747b36224, + 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89, 0x1953cf68300424ac, + 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, + 0xcdb02555653131b6, 0x3792f412cb06794d, + 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b, 0xf245825a5a445275, + 0xfb158592be068d2e, 0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d, 0x55464dd69685606b, + 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, + 0xf53304714d9265df, 0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab, 0xe546a8038efe4029, + 0xbf8fdb78849a5f96, 0xde98520472bdd033, + 0xef73d256a5c0f77c, 0x963e66858f6d4440, + 0x95a8637627989aad, 0xdde7001379a44aa8, + 0xbb127c53b17ec159, 0x5560c018580d5d52, + 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, + 0x9226712162ab070d, 0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, + 0xb267ed1940f1c61c, 0x55f038b237591ed3, + 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, + 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, + 0xae397d8aa96c1b77, 0xabec975e0a0d081a, + 0xd9c7dced53c72255, 0x96e7bd358c904a21, + 0x881cea14545c7575, 0x7e50d64177da2e54, + 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, + 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba, 0x67de18eda5814af2, + 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, + 0xa2425ff75e14fc31, 0xa1258379a94d028d, + 0xcad2f7f5359a3b3e, 0x96ee45813a04330, + 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, + 0x9e74d1b791e07e48, 0x775ea264cf55347e, + 0xc612062576589dda, 0x95364afe032a819e, + 0xf79687aed3eec551, 0x3a83ddbd83f52205, + 0x9abe14cd44753b52, 0xc4926a9672793543, + 0xc16d9a0095928a27, 0x75b7053c0f178294, + 0xf1c90080baf72cb1, 0x5324c68b12dd6339, + 0x971da05074da7bee, 0xd3f6fc16ebca5e04, + 0xbce5086492111aea, 0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07, 0x3aff322e62439fd0, + 0xb877aa3236a4b449, 0x9befeb9fad487c3, + 0xe69594bec44de15b, 0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, + 0xb424dc35095cd80f, 0x538484c19ef38c95, + 0xe12e13424bb40e13, 0x2865a5f206b06fba, + 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, + 0xafebff0bcb24aafe, 0xf78f69a51539d749, + 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, + 0x89705f4136b4a597, 0x31680a88f8953031, + 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, + 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, + 0xa7c5ac471b478423, 0xfcf80dc33721d54, + 0xd1b71758e219652b, 0xd3c36113404ea4a9, + 0x83126e978d4fdf3b, 0x645a1cac083126ea, + 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, + 0xcccccccccccccccc, 0xcccccccccccccccd, + 0x8000000000000000, 0x0, + 0xa000000000000000, 0x0, + 0xc800000000000000, 0x0, + 0xfa00000000000000, 0x0, + 0x9c40000000000000, 0x0, + 0xc350000000000000, 0x0, + 0xf424000000000000, 0x0, + 0x9896800000000000, 0x0, + 0xbebc200000000000, 0x0, + 0xee6b280000000000, 0x0, + 0x9502f90000000000, 0x0, + 0xba43b74000000000, 0x0, + 0xe8d4a51000000000, 0x0, + 0x9184e72a00000000, 0x0, + 0xb5e620f480000000, 0x0, + 0xe35fa931a0000000, 0x0, + 0x8e1bc9bf04000000, 0x0, + 0xb1a2bc2ec5000000, 0x0, + 0xde0b6b3a76400000, 0x0, + 0x8ac7230489e80000, 0x0, + 0xad78ebc5ac620000, 0x0, + 0xd8d726b7177a8000, 0x0, + 0x878678326eac9000, 0x0, + 0xa968163f0a57b400, 0x0, + 0xd3c21bcecceda100, 0x0, + 0x84595161401484a0, 0x0, + 0xa56fa5b99019a5c8, 0x0, + 0xcecb8f27f4200f3a, 0x0, + 0x813f3978f8940984, 0x4000000000000000, + 0xa18f07d736b90be5, 0x5000000000000000, + 0xc9f2c9cd04674ede, 0xa400000000000000, + 0xfc6f7c4045812296, 0x4d00000000000000, + 0x9dc5ada82b70b59d, 0xf020000000000000, + 0xc5371912364ce305, 0x6c28000000000000, + 0xf684df56c3e01bc6, 0xc732000000000000, + 0x9a130b963a6c115c, 0x3c7f400000000000, + 0xc097ce7bc90715b3, 0x4b9f100000000000, + 0xf0bdc21abb48db20, 0x1e86d40000000000, + 0x96769950b50d88f4, 0x1314448000000000, + 0xbc143fa4e250eb31, 0x17d955a000000000, + 0xeb194f8e1ae525fd, 0x5dcfab0800000000, + 0x92efd1b8d0cf37be, 0x5aa1cae500000000, + 0xb7abc627050305ad, 0xf14a3d9e40000000, + 0xe596b7b0c643c719, 0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, + 0xb35dbf821ae4f38b, 0xdda2802c8a800000, + 0xe0352f62a19e306e, 0xd50b2037ad200000, + 0x8c213d9da502de45, 0x4526f422cc340000, + 0xaf298d050e4395d6, 0x9670b12b7f410000, + 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, + 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, + 0xab0e93b6efee0053, 0x8eea0d047a457a00, + 0xd5d238a4abe98068, 0x72a4904598d6d880, + 0x85a36366eb71f041, 0x47a6da2b7f864750, + 0xa70c3c40a64e6c51, 0x999090b65f67d924, + 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, + 0x82818f1281ed449f, 0xbff8f10e7a8921a4, + 0xa321f2d7226895c7, 0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, + 0xfee50b7025c36a08, 0x2f236d04753d5b4, + 0x9f4f2726179a2245, 0x1d762422c946590, + 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, + 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a, 0x8bef464e3945ef7a, + 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, + 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44, 0x60dbbca87196b616, + 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, + 0xb51d13aea4a488dd, 0x6babab6398bdbe41, + 0xe264589a4dcdab14, 0xc696963c7eed2dd1, + 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, + 0xdd15fe86affad912, 0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, + 0xacb92ed9397bf996, 0x49c2c37f07965404, + 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, + 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, + 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b, 0xf50a3fa490c30190, + 0x83c7088e1aab65db, 0x792667c6da79e0fa, + 0xa4b8cab1a1563f52, 0x577001b891185938, + 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, + 0x80b05e5ac60b6178, 0x544f8158315b05b4, + 0xa0dc75f1778e39d6, 0x696361ae3db1c721, + 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f, 0x4ab48a04065c723, + 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, + 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, + 0xf5746577930d6500, 0xca8f44ec7ee36479, + 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5, 0xbba1f1d158724a12, + 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, + 0xea1575143cf97226, 0xf52d09d71a3293bd, + 0x924d692ca61be758, 0x593c2626705f9c56, + 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, + 0xe498f455c38b997a, 0xb6dfb9c0f956447, + 0x8edf98b59a373fec, 0x4724bd4189bd5eac, + 0xb2977ee300c50fe7, 0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, + 0x8b865b215899f46c, 0xbd79e0d20082ee74, + 0xae67f1e9aec07187, 0xecd8590680a3aa11, + 0xda01ee641a708de9, 0xe80e6f4820cc9495, + 0x884134fe908658b2, 0x3109058d147fdcdd, + 0xaa51823e34a7eede, 0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, + 0x850fadc09923329e, 0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45, 0x84db8346b786151c, + 0xcfe87f7cef46ff16, 0xe612641865679a63, + 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749, 0xe3be5e330f38f09d, + 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, + 0xc646d63501a1511d, 0xb281e1fd541501b8, + 0xf7d88bc24209a565, 0x1f225a7ca91a4226, + 0x9ae757596946075f, 0x3375788de9b06958, + 0xc1a12d2fc3978937, 0x52d6b1641c83ae, + 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332, 0xf840b7ba963646e0, + 0xbd176620a501fbff, 0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf, 0xc66f336c36b10137, + 0xb8a8d9bbe123f017, 0xb80b0047445d4184, + 0xe6d3102ad96cec1d, 0xa60dc059157491e5, + 0x9043ea1ac7e41392, 0x87c89837ad68db2f, + 0xb454e4a179dd1877, 0x29babe4598c311fb, + 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, + 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d, 0x76707543f4fa1f73, + 0x899504ae72497eba, 0x6a06494a791c53a8, + 0xabfa45da0edbde69, 0x487db9d17636892, + 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, + 0xa7f26836f282b732, 0x8e6cac7768d7141e, + 0xd1ef0244af2364ff, 0x3207d795430cd926, + 0x8335616aed761f1f, 0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, + 0xcd036837130890a1, 0x36dba887c37a8c0f, + 0x802221226be55a64, 0xc2494954da2c9789, + 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d, 0x6f92829494e5acc7, + 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, + 0x9c69a97284b578d7, 0xff2a760414536efb, + 0xc38413cf25e2d70d, 0xfef5138519684aba, + 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, + 0x98bf2f79d5993802, 0xef2f773ffbd97a61, + 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2, 0xdd945a747bf26183, + 0xba756174393d88df, 0x94f971119aeef9e4, + 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, + 0x91abb422ccb812ee, 0xac62e055c10ab33a, + 0xb616a12b7fe617aa, 0x577b986b314d6009, + 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d, 0x14588f13be847307, + 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee, 0x25de7bb9480d5854, + 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, + 0xd910f7ff28069da4, 0x1b2ba1518094da04, + 0x87aa9aff79042286, 0x90fb44d2f05d0842, + 0xa99541bf57452b28, 0x353a1607ac744a53, + 0xd3fa922f2d1675f2, 0x42889b8997915ce8, + 0x847c9b5d7c2e09b7, 0x69956135febada11, + 0xa59bc234db398c25, 0x43fab9837e699095, + 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, + 0x8161afb94b44f57d, 0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc, 0x6462d92a69731732, + 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78, 0x5cda735244c3d43e, + 0x9defbf01b061adab, 0x3a0888136afa64a7, + 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b, 0x8aad549e57273d45, + 0x9a3c2087a63f6399, 0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, + 0x969eb7c47859e743, 0x9f644ae5a4b1b325, + 0xbc4665b596706114, 0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, + 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, + 0x8fa475791a569d10, 0xf96e017d694487bc, + 0xb38d92d760ec4455, 0x37c981dcc395a9ac, + 0xe070f78d3927556a, 0x85bbe253f47b1417, + 0x8c469ab843b89562, 0x93956d7478ccec8e, + 0xaf58416654a6babb, 0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, + 0x88fcf317f22241e2, 0x441fece3bdf81f03, + 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, + 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, + 0x85c7056562757456, 0xf6872d5667844e49, + 0xa738c6bebb12d16c, 0xb428f8ac016561db, + 0xd106f86e69d785c7, 0xe13336d701beba52, + 0x82a45b450226b39c, 0xecc0024661173473, + 0xa34d721642b06084, 0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, + 0xff290242c83396ce, 0x7e67047175a15271, + 0x9f79a169bd203e41, 0xf0062c6e984d386, + 0xc75809c42c684dd1, 0x52c07b78a3e60868, + 0xf92e0c3537826145, 0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb, 0x88a66076400bb691, + 0xc2abf989935ddbfe, 0x6acff893d00ea435, + 0xf356f7ebf83552fe, 0x583f6b8c4124d43, + 0x98165af37b2153de, 0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, + 0xeda2ee1c7064130c, 0x1162def06f79df73, + 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, + 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, + 0x8da471a9de737e24, 0x5ceaecfed289e5d2, + 0xb10d8e1456105dad, 0x7425a83e872c5f47, + 0xdd50f1996b947518, 0xd12f124e28f77719, + 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, + 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, + 0x8714a775e3e95c78, 0x65acfaec34810a71, + 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, + 0xd31045a8341ca07c, 0x1ede48111209a050, + 0x83ea2b892091e44d, 0x934aed0aab460432, + 0xa4e4b66b68b65d60, 0xf81da84d5617853f, + 0xce1de40642e3f4b9, 0x36251260ab9d668e, + 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, + 0xa1075a24e4421730, 0xb24cf65b8612f81f, + 0xc94930ae1d529cfc, 0xdee033f26797b627, + 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, + 0x9d412e0806e88aa5, 0x8e1f289560ee864e, + 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, + 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, + 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, + 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513, 0x84c86189216dc5ed, + 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515, 0xfabaf3feaa5334a, + 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8, 0x743e20e9ef511012, + 0xdf78e4b2bd342cf6, 0x914da9246b255416, + 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, + 0xae9672aba3d0c320, 0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, + 0x8865899617fb1871, 0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, + 0xd51ea6fa85785631, 0x552a74227f3ea565, + 0x8533285c936b35de, 0xd53a88958f87275f, + 0xa67ff273b8460356, 0x8a892abaf368f137, + 0xd01fef10a657842c, 0x2d2b7569b0432d85, + 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, + 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, + 0xcb3f2f7642717713, 0x241c70a936219a73, + 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, + 0x9ec95d1463e8a506, 0xf4363804324a40aa, + 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, + 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, + 0x976e41088617ca01, 0xd5be0503e085d813, + 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, + 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45, 0xcabb90e5c942b503, + 0xb8da1662e7b00a17, 0x3d6a751f3b936243, + 0xe7109bfba19c0c9d, 0xcc512670a783ad4, + 0x906a617d450187e2, 0x27fb2b80668b24c5, + 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, + 0xe1a63853bbd26451, 0x5e7873f8a0396973, + 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, + 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, + 0xac2820d9623bf429, 0x546345fa9fbdcd44, + 0xd732290fbacaf133, 0xa97c177947ad4095, + 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, + 0xa81f301449ee8c70, 0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c, 0x73832eec6fff3111, + 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, + 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, + 0xa0555e361951c366, 0xd7e105bcc332621f, + 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, + 0xfa856334878fc150, 0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07, 0xa862f80ec4700c8, + 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, + 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, + 0xeeea5d5004981478, 0x1858ccfce06cac74, + 0x95527a5202df0ccb, 0xf37801e0c43ebc8, + 0xbaa718e68396cffd, 0xd30560258f54e6ba, + 0xe950df20247c83fd, 0x47c6b82ef32a2069, + 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, + 0xb6472e511c81471d, 0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5, 0x58180fddd97723a6, + 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, + }; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr uint64_t + powers_template::power_of_five_128[number_of_entries]; + +#endif + +using powers = powers_template<>; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit +// words approximating the result, with the "high" part corresponding to the +// most significant bits and the low part corresponding to the least significant +// bits. +// +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact + // because The line value128 firstproduct = full_multiplication(w, + // power_of_five_128[index]); gives the exact answer. + value128 firstproduct = + full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), + " precision should be in (0,64]"); + constexpr uint64_t precision_mask = + (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if ((firstproduct.high & precision_mask) == + precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our + // expectation is that the compiler will optimize this extra work away if + // needed. + value128 secondproduct = + full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if (secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ +constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; +} +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa +compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = + compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be +// packed. However, in some very rare cases, the computation will fail. In such +// cases, we return an adjusted_mantissa with a negative power of 2: the caller +// should recompute in such cases. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, + // powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, + // requiring a shift) + + value128 product = + compute_product_approximation(q, w); + // The computed 'product' is always sufficient. + // Mathematical proof: + // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to + // appear) See script/mushtak_lemire.py + + // The "compute_product_approximation" function can be slightly slower than a + // branchless approach: value128 product = compute_product(q, w); but in + // practice, we can win big with the compute_product_approximation if its + // additional branch is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; + + answer.mantissa = product.high >> shift; + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - + binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if (-answer.power2 + 1 >= + 64) { // if we have more than 64 bits below the minimum exponent, you + // have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = + (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) + ? 0 + : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && + (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1)) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - + // binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go + // back!!! + if ((answer.mantissa << shift) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include +#include +#include +#include + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB 1 +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(const stackvec &) = delete; + stackvec &operator=(const stackvec &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + // index from the end of the container + FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { + length = uint16_t(len); + } + constexpr size_t len() const noexcept { return length; } + constexpr bool is_empty() const noexcept { return length == 0; } + constexpr size_t capacity() const noexcept { return size; } + // append item to vector, without bounds checking + FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + // append item to vector, returning if item was added + FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + // add items to the vector, from a span, without bounds checking + FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { + limb *ptr = data + length; + std::copy_n(s.ptr, s.len(), ptr); + set_len(len() + s.len()); + } + // try to add items to the vector, returning if items were added + FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + FASTFLOAT_CONSTEXPR20 + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb *first = data + len(); + limb *last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + // try to resize the vector, returning if the vector was resized. + FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + // normalize the big integer, so most-significant zero limbs are removed. + FASTFLOAT_CONSTEXPR14 void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +empty_hi64(bool &truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, bool &truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, bool &truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_add(limb x, limb y, bool &overflow) noexcept { + limb z; +// gcc and clang +#if defined(__has_builtin) +#if __has_builtin(__builtin_add_overflow) + if (!cpp20_and_in_constexpr()) { + overflow = __builtin_add_overflow(x, y, &z); + return z; + } +#endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_mul(limb x, limb y, limb &carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB +#if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; +#endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, + size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +small_add(stackvec &vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, + limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, + size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +large_add_from(stackvec &x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +template struct pow5_tables { + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, + 5UL, + 25UL, + 125UL, + 625UL, + 3125UL, + 15625UL, + 78125UL, + 390625UL, + 1953125UL, + 9765625UL, + 48828125UL, + 244140625UL, + 1220703125UL, + 6103515625UL, + 30517578125UL, + 152587890625UL, + 762939453125UL, + 3814697265625UL, + 19073486328125UL, + 95367431640625UL, + 476837158203125UL, + 2384185791015625UL, + 11920928955078125UL, + 59604644775390625UL, + 298023223876953125UL, + 1490116119384765625UL, + 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint32_t pow5_tables::large_step; + +template constexpr uint64_t pow5_tables::small_power_of_5[]; + +template constexpr limb pow5_tables::large_power_of_5[]; + +#endif + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint : pow5_tables<> { + // storage of the limbs, in little-endian order. + stackvec vec; + + FASTFLOAT_CONSTEXPR20 bigint() : vec() {} + bigint(const bigint &) = delete; + bigint &operator=(const bigint &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = + uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + FASTFLOAT_CONSTEXPR20 int compare(const bigint &other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb *dst = vec.data + n; + const limb *src = vec.data; + std::copy_backward(src, src + vec.len(), dst + vec.len()); + // fill in empty limbs + limb *first = vec.data; + limb *last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } + + FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } + + // multiply as if by 2 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } + + // multiply as if by 5 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + // This is similar to https://github.com/llvm/llvm-project/issues/47746, + // except the workaround described there don't work here + FASTFLOAT_TRY(small_mul( + vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include +#include +#include +#include + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = {1UL, + 10UL, + 100UL, + 1000UL, + 10000UL, + 100000UL, + 1000000UL, + 10000000UL, + 100000000UL, + 1000000000UL, + 10000000000UL, + 100000000000UL, + 1000000000000UL, + 10000000000000UL, + 100000000000000UL, + 1000000000000000UL, + 10000000000000000UL, + 100000000000000000UL, + 1000000000000000000UL, + 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t +scientific_exponent(parsed_number_string_t &num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended(T value) noexcept { + using equiv_uint = typename binary_format::equiv_uint; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + equiv_uint bits; +#if FASTFLOAT_HAS_BIT_CAST + bits = std::bit_cast(value); +#else + ::memcpy(&bits, &value, sizeof(T)); +#endif + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> + binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, + callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < + (uint64_t(1) << binary_format::mantissa_explicit_bits())) + ? 0 + : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= + (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, + callback cb) noexcept { + const uint64_t mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; + const uint64_t halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); + uint64_t truncated_bits = am.mantissa & mask; + bool is_above = truncated_bits > halfway; + bool is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_down(adjusted_mantissa &am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +skip_zeros(UC const *&first, UC const *last) noexcept { + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + break; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(UC const *first, UC const *last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + return true; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + return true; + } + ++first; + } + return false; +} +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +parse_eight_digits(const UC *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +parse_one_digit(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 10 + limb(*p - UC('0')); + p++; + counter++; + count++; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +add_native(bigint &big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +round_up_bigint(bigint &big, size_t &count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +template +inline FASTFLOAT_CONSTEXPR20 void +parse_mantissa(bigint &result, parsed_number_string_t &num, + size_t max_digits, size_t &digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + UC const *p = num.integer.ptr; + UC const *pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, + [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || + (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( + bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint &real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with + // -Wnoexcept-type. + round(am_b, + [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +#include +#include +#include +#include +namespace fast_float { + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result_t FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, + UC const *last, + T &value) noexcept { + from_chars_result_t answer{}; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == + UC('-')) { // assume first < last, so dereference without checks; + // C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if (*first == UC('+')) { + ++first; + } +#endif + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() + : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, + // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if (first != last && *first == UC('(')) { + for (UC const *ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == UC(')')) { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || + (UC('A') <= *ptr && *ptr <= UC('Z')) || + (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { + if ((last - first >= 8) && + fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +/** + * Returns true if the floating-pointing rounding mode is to 'nearest'. + * It is the default on most system. This function is meant to be inexpensive. + * Credit : @mwalcott3 + */ +fastfloat_really_inline bool rounds_to_nearest() noexcept { + // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return false; +#endif + // See + // A fast function to check your floating-point rounding mode + // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ + // + // This function is meant to be equivalent to : + // prior: #include + // return fegetround() == FE_TONEAREST; + // However, it is expected to be much faster than the fegetround() + // function call. + // + // The volatile keywoard prevents the compiler from computing the function + // at compile-time. + // There might be other ways to prevent compile-time optimizations (e.g., + // asm). The value does not need to be std::numeric_limits::min(), any + // small value so that 1 + x should round to 1 would do (after accounting for + // excess precision, as in 387 instructions). + static volatile float fmin = std::numeric_limits::min(); + float fmini = fmin; // we copy it so that it gets loaded at most once. +// +// Explanation: +// Only when fegetround() == FE_TONEAREST do we have that +// fmin + 1.0f == 1.0f - fmin. +// +// FE_UPWARD: +// fmin + 1.0f > 1 +// 1.0f - fmin == 1 +// +// FE_DOWNWARD or FE_TOWARDZERO: +// fmin + 1.0f == 1 +// 1.0f - fmin < 1 +// +// Note: This may fail to be accurate if fast-math has been +// enabled, as rounding conventions may not apply. +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +// todo: is there a VS warning? +// see +// https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + return (fmini + 1.0f == 1.0f - fmini); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#elif defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +} // namespace detail + +template struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced(first, last, value, options); + } +}; + +#if __STDCPP_FLOAT32_T__ == 1 +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float32_t &value, + parse_options_t options) noexcept { + // if std::float32_t is defined, and we are in C++23 mode; macro set for + // float32; set value to float due to equivalence between float and + // float32_t + float val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +#if __STDCPP_FLOAT64_T__ == 1 +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float64_t &value, + parse_options_t options) noexcept { + // if std::float64_t is defined, and we are in C++23 mode; macro set for + // float64; set value as double due to equivalence between double and + // float64_t + double val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_caller::call(first, last, value, + parse_options_t(fmt)); +} + +/** + * This function overload takes parsed_number_string_t structure that is created + * and populated either by from_chars_advanced function taking chars range and + * parsing options or other parsing custom function implemented by user. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { + + static_assert(is_supported_float_type(), + "only some floating-point types are supported"); + static_assert(is_supported_char_type(), + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; + + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // The implementation of the Clinger's fast path is convoluted because + // we want round-to-nearest in all cases, irrespective of the rounding mode + // selected on the thread. + // We proceed optimistically, assuming that detail::rounds_to_nearest() + // returns true. + if (binary_format::min_exponent_fast_path() <= pns.exponent && + pns.exponent <= binary_format::max_exponent_fast_path() && + !pns.too_many_digits) { + // Unfortunately, the conventional Clinger's fast path is only possible + // when the system rounds to the nearest float. + // + // We expect the next branch to almost always be selected. + // We could check it first (before the previous branch), but + // there might be performance advantages at having the check + // be last. + if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { + // We have that fegetround() == FE_TONEAREST. + // Next is Clinger's fast path. + if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { + value = T(pns.mantissa); + if (pns.exponent < 0) { + value = value / binary_format::exact_power_of_ten(-pns.exponent); + } else { + value = value * binary_format::exact_power_of_ten(pns.exponent); + } + if (pns.negative) { + value = -value; + } + return answer; + } + } else { + // We do not have that fegetround() == FE_TONEAREST. + // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's + // proposal + if (pns.exponent >= 0 && + pns.mantissa <= + binary_format::max_mantissa_fast_path(pns.exponent)) { +#if defined(__clang__) || defined(FASTFLOAT_32BIT) + // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if (pns.mantissa == 0) { + value = pns.negative ? T(-0.) : T(0.); + return answer; + } +#endif + value = T(pns.mantissa) * + binary_format::exact_power_of_ten(pns.exponent); + if (pns.negative) { + value = -value; + } + return answer; + } + } + } + adjusted_mantissa am = + compute_float>(pns.exponent, pns.mantissa); + if (pns.too_many_digits && am.power2 >= 0) { + if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) + // and we have an invalid power (am.power2 < 0), then we need to go the long + // way around again. This is very uncommon. + if (am.power2 < 0) { + am = digit_comp(pns, am); + } + to_float(pns.negative, am, value); + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || + am.power2 == binary_format::infinite_power()) { + answer.ec = std::errc::result_out_of_range; + } + return answer; +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_float_type(), + "only some floating-point types are supported"); + static_assert(is_supported_char_type(), + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default + while ((first != last) && fast_float::is_space(uint8_t(*first))) { + first++; + } +#endif + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string_t pns = + parse_number_string(first, last, options); + if (!pns.valid) { + if (options.format & chars_format::no_infnan) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } else { + return detail::parse_infnan(first, last, value); + } + } + + // call overload that takes parsed_number_string_t directly. + return from_chars_advanced(pns, value); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base) noexcept { + static_assert(is_supported_char_type(), + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default + while ((first != last) && fast_float::is_space(uint8_t(*first))) { + first++; + } +#endif + if (first == last || base < 2 || base > 36) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + return parse_int_string(first, last, value, base); +} + +} // namespace fast_float + +#endif + diff --git a/source/extern/lauxlib.h b/source/extern/lauxlib.h index 0bac246..9857d3a 100644 --- a/source/extern/lauxlib.h +++ b/source/extern/lauxlib.h @@ -1,5 +1,5 @@ /* -** $Id: lauxlib.h,v 1.128 2014/10/29 16:11:17 roberto Exp $ +** $Id: lauxlib.h,v 1.131.1.1 2017/04/19 17:20:42 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ @@ -16,10 +16,18 @@ -/* extra error code for 'luaL_load' */ +/* extra error code for 'luaL_loadfilex' */ #define LUA_ERRFILE (LUA_ERRERR+1) +/* key, in the registry, for table of loaded modules */ +#define LUA_LOADED_TABLE "_LOADED" + + +/* key, in the registry, for table of preloaded loaders */ +#define LUA_PRELOAD_TABLE "_PRELOAD" + + typedef struct luaL_Reg { const char *name; lua_CFunction func; @@ -65,7 +73,7 @@ LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); LUALIB_API int (luaL_execresult) (lua_State *L, int stat); -/* pre-defined references */ +/* predefined references */ #define LUA_NOREF (-2) #define LUA_REFNIL (-1) diff --git a/source/extern/lua.h b/source/extern/lua.h index 1c2b95a..9394c5e 100644 --- a/source/extern/lua.h +++ b/source/extern/lua.h @@ -1,5 +1,4 @@ /* -** $Id: lua.h,v 1.328 2015/06/03 13:03:38 roberto Exp $ ** Lua - A Scripting Language ** Lua.org, PUC-Rio, Brazil (http://www.lua.org) ** See Copyright Notice at the end of this file @@ -19,11 +18,11 @@ #define LUA_VERSION_MAJOR "5" #define LUA_VERSION_MINOR "3" #define LUA_VERSION_NUM 503 -#define LUA_VERSION_RELEASE "1" +#define LUA_VERSION_RELEASE "6" #define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE -#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2015 Lua.org, PUC-Rio" +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2020 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" @@ -361,7 +360,7 @@ LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); #define lua_pushliteral(L, s) lua_pushstring(L, "" s) #define lua_pushglobaltable(L) \ - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) #define lua_tostring(L,i) lua_tolstring(L, (i), NULL) @@ -460,7 +459,7 @@ struct lua_Debug { /****************************************************************************** -* Copyright (C) 1994-2015 Lua.org, PUC-Rio. +* Copyright (C) 1994-2020 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/source/extern/lua/lapi.c b/source/extern/lua/lapi.c index fea9eb2..711895b 100644 --- a/source/extern/lua/lapi.c +++ b/source/extern/lua/lapi.c @@ -1,5 +1,5 @@ /* -** $Id: lapi.c,v 2.249 2015/04/06 12:23:48 roberto Exp $ +** $Id: lapi.c,v 2.259.1.2 2017/12/06 18:35:12 roberto Exp $ ** Lua API ** See Copyright Notice in lua.h */ @@ -121,11 +121,11 @@ LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { lua_lock(to); api_checknelems(from, n); api_check(from, G(from) == G(to), "moving among independent states"); - api_check(from, to->ci->top - to->top >= n, "not enough elements to move"); + api_check(from, to->ci->top - to->top >= n, "stack overflow"); from->top -= n; for (i = 0; i < n; i++) { setobj2s(to, to->top, from->top + i); - api_incr_top(to); + to->top++; /* stack already checked by previous 'api_check' */ } lua_unlock(to); } @@ -378,9 +378,9 @@ LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { return NULL; } lua_lock(L); /* 'luaO_tostring' may create a new string */ + luaO_tostring(L, o); luaC_checkGC(L); o = index2addr(L, idx); /* previous call may reallocate the stack */ - luaO_tostring(L, o); lua_unlock(L); } if (len != NULL) @@ -471,13 +471,18 @@ LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { } +/* +** Pushes on the stack a string with given length. Avoid using 's' when +** 'len' == 0 (as 's' can be NULL in that case), due to later use of +** 'memcmp' and 'memcpy'. +*/ LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { TString *ts; lua_lock(L); - luaC_checkGC(L); - ts = luaS_newlstr(L, s, len); + ts = (len == 0) ? luaS_new(L, "") : luaS_newlstr(L, s, len); setsvalue2s(L, L->top, ts); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return getstr(ts); } @@ -489,12 +494,12 @@ LUA_API const char *lua_pushstring (lua_State *L, const char *s) { setnilvalue(L->top); else { TString *ts; - luaC_checkGC(L); ts = luaS_new(L, s); setsvalue2s(L, L->top, ts); s = getstr(ts); /* internal copy's address */ } api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return s; } @@ -504,8 +509,8 @@ LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp) { const char *ret; lua_lock(L); - luaC_checkGC(L); ret = luaO_pushvfstring(L, fmt, argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -515,10 +520,10 @@ LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { const char *ret; va_list argp; lua_lock(L); - luaC_checkGC(L); va_start(argp, fmt); ret = luaO_pushvfstring(L, fmt, argp); va_end(argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -528,12 +533,12 @@ LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { lua_lock(L); if (n == 0) { setfvalue(L->top, fn); + api_incr_top(L); } else { CClosure *cl; api_checknelems(L, n); api_check(L, n <= MAXUPVAL, "upvalue index too large"); - luaC_checkGC(L); cl = luaF_newCclosure(L, n); cl->f = fn; L->top -= n; @@ -542,8 +547,9 @@ LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { /* does not need barrier because closure is white */ } setclCvalue(L, L->top, cl); + api_incr_top(L); + luaC_checkGC(L); } - api_incr_top(L); lua_unlock(L); } @@ -579,19 +585,30 @@ LUA_API int lua_pushthread (lua_State *L) { */ -LUA_API int lua_getglobal (lua_State *L, const char *name) { - Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ - lua_lock(L); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top, luaS_new(L, name)); - api_incr_top(L); - luaV_gettable(L, gt, L->top - 1, L->top - 1); +static int auxgetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + setobj2s(L, L->top, slot); + api_incr_top(L); + } + else { + setsvalue2s(L, L->top, str); + api_incr_top(L); + luaV_finishget(L, t, L->top - 1, L->top - 1, slot); + } lua_unlock(L); return ttnov(L->top - 1); } +LUA_API int lua_getglobal (lua_State *L, const char *name) { + Table *reg = hvalue(&G(L)->l_registry); + lua_lock(L); + return auxgetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); +} + + LUA_API int lua_gettable (lua_State *L, int idx) { StkId t; lua_lock(L); @@ -603,24 +620,25 @@ LUA_API int lua_gettable (lua_State *L, int idx) { LUA_API int lua_getfield (lua_State *L, int idx, const char *k) { - StkId t; lua_lock(L); - t = index2addr(L, idx); - setsvalue2s(L, L->top, luaS_new(L, k)); - api_incr_top(L); - luaV_gettable(L, t, L->top - 1, L->top - 1); - lua_unlock(L); - return ttnov(L->top - 1); + return auxgetstr(L, index2addr(L, idx), k); } LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) { StkId t; + const TValue *slot; lua_lock(L); t = index2addr(L, idx); - setivalue(L->top, n); - api_incr_top(L); - luaV_gettable(L, t, L->top - 1, L->top - 1); + if (luaV_fastget(L, t, n, slot, luaH_getint)) { + setobj2s(L, L->top, slot); + api_incr_top(L); + } + else { + setivalue(L->top, n); + api_incr_top(L); + luaV_finishget(L, t, L->top - 1, L->top - 1, slot); + } lua_unlock(L); return ttnov(L->top - 1); } @@ -666,12 +684,12 @@ LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) { LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { Table *t; lua_lock(L); - luaC_checkGC(L); t = luaH_new(L); sethvalue(L, L->top, t); api_incr_top(L); if (narray > 0 || nrec > 0) luaH_resize(L, t, narray, nrec); + luaC_checkGC(L); lua_unlock(L); } @@ -719,18 +737,29 @@ LUA_API int lua_getuservalue (lua_State *L, int idx) { ** set functions (stack -> Lua) */ +/* +** t[k] = value at the top of the stack (where 'k' is a string) +*/ +static void auxsetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + api_checknelems(L, 1); + if (luaV_fastset(L, t, str, slot, luaH_getstr, L->top - 1)) + L->top--; /* pop value */ + else { + setsvalue2s(L, L->top, str); /* push 'str' (to make it a TValue) */ + api_incr_top(L); + luaV_finishset(L, t, L->top - 1, L->top - 2, slot); + L->top -= 2; /* pop value and key */ + } + lua_unlock(L); /* lock done by caller */ +} + LUA_API void lua_setglobal (lua_State *L, const char *name) { Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ - lua_lock(L); - api_checknelems(L, 1); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top, luaS_new(L, name)); - api_incr_top(L); - luaV_settable(L, gt, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ - lua_unlock(L); + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); } @@ -746,42 +775,40 @@ LUA_API void lua_settable (lua_State *L, int idx) { LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { - StkId t; - lua_lock(L); - api_checknelems(L, 1); - t = index2addr(L, idx); - setsvalue2s(L, L->top, luaS_new(L, k)); - api_incr_top(L); - luaV_settable(L, t, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ - lua_unlock(L); + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, index2addr(L, idx), k); } LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { StkId t; + const TValue *slot; lua_lock(L); api_checknelems(L, 1); t = index2addr(L, idx); - setivalue(L->top, n); - api_incr_top(L); - luaV_settable(L, t, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ + if (luaV_fastset(L, t, n, slot, luaH_getint, L->top - 1)) + L->top--; /* pop value */ + else { + setivalue(L->top, n); + api_incr_top(L); + luaV_finishset(L, t, L->top - 1, L->top - 2, slot); + L->top -= 2; /* pop value and key */ + } lua_unlock(L); } LUA_API void lua_rawset (lua_State *L, int idx) { StkId o; - Table *t; + TValue *slot; lua_lock(L); api_checknelems(L, 2); o = index2addr(L, idx); api_check(L, ttistable(o), "table expected"); - t = hvalue(o); - setobj2t(L, luaH_set(L, t, L->top-2), L->top-1); - invalidateTMcache(t); - luaC_barrierback(L, t, L->top-1); + slot = luaH_set(L, hvalue(o), L->top - 2); + setobj2t(L, slot, L->top - 1); + invalidateTMcache(hvalue(o)); + luaC_barrierback(L, hvalue(o), L->top-1); L->top -= 2; lua_unlock(L); } @@ -789,14 +816,12 @@ LUA_API void lua_rawset (lua_State *L, int idx) { LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { StkId o; - Table *t; lua_lock(L); api_checknelems(L, 1); o = index2addr(L, idx); api_check(L, ttistable(o), "table expected"); - t = hvalue(o); - luaH_setint(L, t, n, L->top - 1); - luaC_barrierback(L, t, L->top-1); + luaH_setint(L, hvalue(o), n, L->top - 1); + luaC_barrierback(L, hvalue(o), L->top-1); L->top--; lua_unlock(L); } @@ -804,16 +829,15 @@ LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { StkId o; - Table *t; - TValue k; + TValue k, *slot; lua_lock(L); api_checknelems(L, 1); o = index2addr(L, idx); api_check(L, ttistable(o), "table expected"); - t = hvalue(o); setpvalue(&k, cast(void *, p)); - setobj2t(L, luaH_set(L, t, &k), L->top - 1); - luaC_barrierback(L, t, L->top - 1); + slot = luaH_set(L, hvalue(o), &k); + setobj2t(L, slot, L->top - 1); + luaC_barrierback(L, hvalue(o), L->top - 1); L->top--; lua_unlock(L); } @@ -895,10 +919,10 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults, if (k != NULL && L->nny == 0) { /* need to prepare continuation? */ L->ci->u.c.k = k; /* save continuation */ L->ci->u.c.ctx = ctx; /* save context */ - luaD_call(L, func, nresults, 1); /* do the call */ + luaD_call(L, func, nresults); /* do the call */ } else /* no continuation or no yieldable */ - luaD_call(L, func, nresults, 0); /* just do the call */ + luaD_callnoyield(L, func, nresults); /* just do the call */ adjustresults(L, nresults); lua_unlock(L); } @@ -916,7 +940,7 @@ struct CallS { /* data to 'f_call' */ static void f_call (lua_State *L, void *ud) { struct CallS *c = cast(struct CallS *, ud); - luaD_call(L, c->func, c->nresults, 0); + luaD_callnoyield(L, c->func, c->nresults); } @@ -954,7 +978,7 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, L->errfunc = func; setoah(ci->callstatus, L->allowhook); /* save value of 'allowhook' */ ci->callstatus |= CIST_YPCALL; /* function can do error recovery */ - luaD_call(L, c.func, nresults, 1); /* do the call */ + luaD_call(L, c.func, nresults); /* do the call */ ci->callstatus &= ~CIST_YPCALL; L->errfunc = ci->u.c.old_errfunc; status = LUA_OK; /* if it is here, there were no errors */ @@ -1043,7 +1067,7 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { } case LUA_GCSTEP: { l_mem debt = 1; /* =1 to signal that it did an actual step */ - int oldrunning = g->gcrunning; + lu_byte oldrunning = g->gcrunning; g->gcrunning = 1; /* allow GC to run */ if (data == 0) { luaE_setdebt(g, -GCSTEPSIZE); /* to do a "small" step */ @@ -1117,7 +1141,6 @@ LUA_API void lua_concat (lua_State *L, int n) { lua_lock(L); api_checknelems(L, n); if (n >= 2) { - luaC_checkGC(L); luaV_concat(L, n); } else if (n == 0) { /* push empty string */ @@ -1125,6 +1148,7 @@ LUA_API void lua_concat (lua_State *L, int n) { api_incr_top(L); } /* else n == 1; nothing to do */ + luaC_checkGC(L); lua_unlock(L); } @@ -1160,10 +1184,10 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { LUA_API void *lua_newuserdata (lua_State *L, size_t size) { Udata *u; lua_lock(L); - luaC_checkGC(L); u = luaS_newudata(L, size); setuvalue(L, L->top, u); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return getudatamem(u); } @@ -1230,13 +1254,12 @@ LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { } -static UpVal **getupvalref (lua_State *L, int fidx, int n, LClosure **pf) { +static UpVal **getupvalref (lua_State *L, int fidx, int n) { LClosure *f; StkId fi = index2addr(L, fidx); api_check(L, ttisLclosure(fi), "Lua function expected"); f = clLvalue(fi); api_check(L, (1 <= n && n <= f->p->sizeupvalues), "invalid upvalue index"); - if (pf) *pf = f; return &f->upvals[n - 1]; /* get its upvalue pointer */ } @@ -1245,7 +1268,7 @@ LUA_API void *lua_upvalueid (lua_State *L, int fidx, int n) { StkId fi = index2addr(L, fidx); switch (ttype(fi)) { case LUA_TLCL: { /* lua closure */ - return *getupvalref(L, fidx, n, NULL); + return *getupvalref(L, fidx, n); } case LUA_TCCL: { /* C closure */ CClosure *f = clCvalue(fi); @@ -1262,9 +1285,10 @@ LUA_API void *lua_upvalueid (lua_State *L, int fidx, int n) { LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1, int fidx2, int n2) { - LClosure *f1; - UpVal **up1 = getupvalref(L, fidx1, n1, &f1); - UpVal **up2 = getupvalref(L, fidx2, n2, NULL); + UpVal **up1 = getupvalref(L, fidx1, n1); + UpVal **up2 = getupvalref(L, fidx2, n2); + if (*up1 == *up2) + return; luaC_upvdeccount(L, *up1); *up1 = *up2; (*up1)->refcount++; diff --git a/source/extern/lua/lapi.h b/source/extern/lua/lapi.h index 6d36dee..8e16ad5 100644 --- a/source/extern/lua/lapi.h +++ b/source/extern/lua/lapi.h @@ -1,5 +1,5 @@ /* -** $Id: lapi.h,v 2.9 2015/03/06 19:49:50 roberto Exp $ +** $Id: lapi.h,v 2.9.1.1 2017/04/19 17:20:42 roberto Exp $ ** Auxiliary functions from Lua API ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/lauxlib.c b/source/extern/lua/lauxlib.c index b8bace7..ac68bd3 100644 --- a/source/extern/lua/lauxlib.c +++ b/source/extern/lua/lauxlib.c @@ -1,5 +1,5 @@ /* -** $Id: lauxlib.c,v 1.280 2015/02/03 17:38:24 roberto Exp $ +** $Id: lauxlib.c,v 1.289.1.1 2017/04/19 17:20:42 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ @@ -17,7 +17,8 @@ #include -/* This file uses only the official API of Lua. +/* +** This file uses only the official API of Lua. ** Any function declared here could be written as an application function. */ @@ -33,8 +34,8 @@ */ -#define LEVELS1 12 /* size of the first part of the stack */ -#define LEVELS2 10 /* size of the second part of the stack */ +#define LEVELS1 10 /* size of the first part of the stack */ +#define LEVELS2 11 /* size of the second part of the stack */ @@ -68,12 +69,11 @@ static int findfield (lua_State *L, int objidx, int level) { /* ** Search for a name for a function in all loaded modules -** (registry._LOADED). */ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { int top = lua_gettop(L); lua_getinfo(L, "f", ar); /* push function */ - lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); if (findfield(L, top + 1, 2)) { const char *name = lua_tostring(L, -1); if (strncmp(name, "_G.", 3) == 0) { /* name start with '_G.'? */ @@ -107,7 +107,7 @@ static void pushfuncname (lua_State *L, lua_Debug *ar) { } -static int countlevels (lua_State *L) { +static int lastlevel (lua_State *L) { lua_Debug ar; int li = 1, le = 1; /* find an upper bound */ @@ -126,14 +126,16 @@ LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level) { lua_Debug ar; int top = lua_gettop(L); - int numlevels = countlevels(L1); - int mark = (numlevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; - if (msg) lua_pushfstring(L, "%s\n", msg); + int last = lastlevel(L1); + int n1 = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1; + if (msg) + lua_pushfstring(L, "%s\n", msg); + luaL_checkstack(L, 10, NULL); lua_pushliteral(L, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { - if (level == mark) { /* too many levels? */ + if (n1-- == 0) { /* too many levels? */ lua_pushliteral(L, "\n\t..."); /* add a '...' */ - level = numlevels - LEVELS2; /* and skip to last ones */ + level = last - LEVELS2 + 1; /* and skip to last ones */ } else { lua_getinfo(L1, "Slnt", &ar); @@ -196,6 +198,10 @@ static void tag_error (lua_State *L, int arg, int tag) { } +/* +** The use of 'lua_pushfstring' ensures this function does not +** need reserved stack space when called. +*/ LUALIB_API void luaL_where (lua_State *L, int level) { lua_Debug ar; if (lua_getstack(L, level, &ar)) { /* check function at level */ @@ -205,10 +211,15 @@ LUALIB_API void luaL_where (lua_State *L, int level) { return; } } - lua_pushliteral(L, ""); /* else, no information available... */ + lua_pushfstring(L, ""); /* else, no information available... */ } +/* +** Again, the use of 'lua_pushvfstring' ensures this function does +** not need reserved stack space when called. (At worst, it generates +** an error with "stack overflow" instead of the given message.) +*/ LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); @@ -289,7 +300,7 @@ LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { if (luaL_getmetatable(L, tname) != LUA_TNIL) /* name already in use? */ return 0; /* leave previous value on top, but return 0 */ lua_pop(L, 1); - lua_newtable(L); /* create metatable */ + lua_createtable(L, 0, 2); /* create metatable */ lua_pushstring(L, tname); lua_setfield(L, -2, "__name"); /* metatable.__name = tname */ lua_pushvalue(L, -1); @@ -347,10 +358,15 @@ LUALIB_API int luaL_checkoption (lua_State *L, int arg, const char *def, } +/* +** Ensures the stack has at least 'space' extra slots, raising an error +** if it cannot fulfill the request. (The error handling needs a few +** extra slots to format the error message. In case of an error without +** this extra space, Lua will generate the same 'stack overflow' error, +** but without 'msg'.) +*/ LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { - /* keep some extra space to run error routines, if needed */ - const int extra = LUA_MINSTACK; - if (!lua_checkstack(L, space + extra)) { + if (!lua_checkstack(L, space)) { if (msg) luaL_error(L, "stack overflow (%s)", msg); else @@ -435,6 +451,47 @@ LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int arg, ** ======================================================= */ +/* userdata to box arbitrary data */ +typedef struct UBox { + void *box; + size_t bsize; +} UBox; + + +static void *resizebox (lua_State *L, int idx, size_t newsize) { + void *ud; + lua_Alloc allocf = lua_getallocf(L, &ud); + UBox *box = (UBox *)lua_touserdata(L, idx); + void *temp = allocf(ud, box->box, box->bsize, newsize); + if (temp == NULL && newsize > 0) { /* allocation error? */ + resizebox(L, idx, 0); /* free buffer */ + luaL_error(L, "not enough memory for buffer allocation"); + } + box->box = temp; + box->bsize = newsize; + return temp; +} + + +static int boxgc (lua_State *L) { + resizebox(L, 1, 0); + return 0; +} + + +static void *newbox (lua_State *L, size_t newsize) { + UBox *box = (UBox *)lua_newuserdata(L, sizeof(UBox)); + box->box = NULL; + box->bsize = 0; + if (luaL_newmetatable(L, "LUABOX")) { /* creating metatable? */ + lua_pushcfunction(L, boxgc); + lua_setfield(L, -2, "__gc"); /* metatable.__gc = boxgc */ + } + lua_setmetatable(L, -2); + return resizebox(L, -1, newsize); +} + + /* ** check whether buffer is using a userdata on the stack as a temporary ** buffer @@ -455,11 +512,12 @@ LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { if (newsize < B->n || newsize - B->n < sz) luaL_error(L, "buffer too large"); /* create larger buffer */ - newbuff = (char *)lua_newuserdata(L, newsize * sizeof(char)); - /* move content to new buffer */ - memcpy(newbuff, B->b, B->n * sizeof(char)); if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + newbuff = (char *)resizebox(L, -1, newsize); + else { /* no buffer yet */ + newbuff = (char *)newbox(L, newsize); + memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */ + } B->b = newbuff; B->size = newsize; } @@ -468,9 +526,11 @@ LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { - char *b = luaL_prepbuffsize(B, l); - memcpy(b, s, l * sizeof(char)); - luaL_addsize(B, l); + if (l > 0) { /* avoid 'memcpy' when 's' can be NULL */ + char *b = luaL_prepbuffsize(B, l); + memcpy(b, s, l * sizeof(char)); + luaL_addsize(B, l); + } } @@ -482,8 +542,10 @@ LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { LUALIB_API void luaL_pushresult (luaL_Buffer *B) { lua_State *L = B->L; lua_pushlstring(L, B->b, B->n); - if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + if (buffonstack(B)) { + resizebox(L, -2, 0); /* delete old buffer */ + lua_remove(L, -2); /* remove its header from the stack */ + } } @@ -605,7 +667,7 @@ static int errfile (lua_State *L, const char *what, int fnameindex) { static int skipBOM (LoadF *lf) { - const char *p = "\xEF\xBB\xBF"; /* Utf8 BOM mark */ + const char *p = "\xEF\xBB\xBF"; /* UTF-8 BOM mark */ int c; lf->n = 0; do { @@ -630,7 +692,7 @@ static int skipcomment (LoadF *lf, int *cp) { if (c == '#') { /* first line is a comment (Unix exec. file)? */ do { /* skip first line */ c = getc(lf->f); - } while (c != EOF && c != '\n') ; + } while (c != EOF && c != '\n'); *cp = getc(lf->f); /* skip end-of-line, if present */ return 1; /* there was a comment */ } @@ -746,13 +808,17 @@ LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) { LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { - if (!luaL_callmeta(L, idx, "__tostring")) { /* no metafield? */ + if (luaL_callmeta(L, idx, "__tostring")) { /* metafield? */ + if (!lua_isstring(L, -1)) + luaL_error(L, "'__tostring' must return a string"); + } + else { switch (lua_type(L, idx)) { case LUA_TNUMBER: { if (lua_isinteger(L, idx)) - lua_pushfstring(L, "%I", lua_tointeger(L, idx)); + lua_pushfstring(L, "%I", (LUAI_UACINT)lua_tointeger(L, idx)); else - lua_pushfstring(L, "%f", lua_tonumber(L, idx)); + lua_pushfstring(L, "%f", (LUAI_UACNUMBER)lua_tonumber(L, idx)); break; } case LUA_TSTRING: @@ -764,10 +830,15 @@ LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { case LUA_TNIL: lua_pushliteral(L, "nil"); break; - default: - lua_pushfstring(L, "%s: %p", luaL_typename(L, idx), - lua_topointer(L, idx)); + default: { + int tt = luaL_getmetafield(L, idx, "__name"); /* try name */ + const char *kind = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : + luaL_typename(L, idx); + lua_pushfstring(L, "%s: %p", kind, lua_topointer(L, idx)); + if (tt != LUA_TNIL) + lua_remove(L, -2); /* remove '__name' */ break; + } } } return lua_tolstring(L, -1, len); @@ -819,23 +890,23 @@ static int libsize (const luaL_Reg *l) { /* ** Find or create a module table with a given name. The function -** first looks at the _LOADED table and, if that fails, try a +** first looks at the LOADED table and, if that fails, try a ** global variable with that name. In any case, leaves on the stack ** the module table. */ LUALIB_API void luaL_pushmodule (lua_State *L, const char *modname, int sizehint) { - luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); /* get _LOADED table */ - if (lua_getfield(L, -1, modname) != LUA_TTABLE) { /* no _LOADED[modname]? */ + luaL_findtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE, 1); + if (lua_getfield(L, -1, modname) != LUA_TTABLE) { /* no LOADED[modname]? */ lua_pop(L, 1); /* remove previous result */ /* try global variable (and create one if it does not exist) */ lua_pushglobaltable(L); if (luaL_findtable(L, 0, modname, sizehint) != NULL) luaL_error(L, "name conflict for module '%s'", modname); lua_pushvalue(L, -1); - lua_setfield(L, -3, modname); /* _LOADED[modname] = new table */ + lua_setfield(L, -3, modname); /* LOADED[modname] = new table */ } - lua_remove(L, -2); /* remove _LOADED table */ + lua_remove(L, -2); /* remove LOADED table */ } @@ -899,17 +970,17 @@ LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { */ LUALIB_API void luaL_requiref (lua_State *L, const char *modname, lua_CFunction openf, int glb) { - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_getfield(L, -1, modname); /* _LOADED[modname] */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, -1, modname); /* LOADED[modname] */ if (!lua_toboolean(L, -1)) { /* package not already loaded? */ lua_pop(L, 1); /* remove field */ lua_pushcfunction(L, openf); lua_pushstring(L, modname); /* argument to open function */ lua_call(L, 1, 1); /* call 'openf' to open module */ lua_pushvalue(L, -1); /* make copy of module (call result) */ - lua_setfield(L, -3, modname); /* _LOADED[modname] = module */ + lua_setfield(L, -3, modname); /* LOADED[modname] = module */ } - lua_remove(L, -2); /* remove _LOADED table */ + lua_remove(L, -2); /* remove LOADED table */ if (glb) { lua_pushvalue(L, -1); /* copy of module */ lua_setglobal(L, modname); /* _G[modname] = module */ @@ -940,8 +1011,13 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { free(ptr); return NULL; } - else - return realloc(ptr, nsize); + else { /* cannot fail when shrinking a block */ + void *newptr = realloc(ptr, nsize); + if (newptr == NULL && ptr != NULL && nsize <= osize) + return ptr; /* keep the original block */ + else /* no fail or not shrinking */ + return newptr; /* use the new block */ + } } @@ -967,6 +1043,6 @@ LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) { luaL_error(L, "multiple Lua VMs detected"); else if (*v != ver) luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f", - ver, *v); + (LUAI_UACNUMBER)ver, (LUAI_UACNUMBER)*v); } diff --git a/source/extern/lua/lbaselib.c b/source/extern/lua/lbaselib.c index 9a15124..6460e4f 100644 --- a/source/extern/lua/lbaselib.c +++ b/source/extern/lua/lbaselib.c @@ -1,5 +1,5 @@ /* -** $Id: lbaselib.c,v 1.310 2015/03/28 19:14:47 roberto Exp $ +** $Id: lbaselib.c,v 1.314.1.1 2017/04/19 17:39:34 roberto Exp $ ** Basic library ** See Copyright Notice in lua.h */ @@ -86,8 +86,8 @@ static int luaB_tonumber (lua_State *L) { const char *s; lua_Integer n = 0; /* to avoid warnings */ lua_Integer base = luaL_checkinteger(L, 2); - luaL_checktype(L, 1, LUA_TSTRING); /* before 'luaL_checklstring'! */ - s = luaL_checklstring(L, 1, &l); + luaL_checktype(L, 1, LUA_TSTRING); /* no numbers as strings */ + s = lua_tolstring(L, 1, &l); luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); if (b_str2int(s, (int)base, &n) == s + l) { lua_pushinteger(L, n); @@ -102,8 +102,8 @@ static int luaB_tonumber (lua_State *L) { static int luaB_error (lua_State *L) { int level = (int)luaL_optinteger(L, 2, 1); lua_settop(L, 1); - if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ - luaL_where(L, level); + if (lua_type(L, 1) == LUA_TSTRING && level > 0) { + luaL_where(L, level); /* add extra information */ lua_pushvalue(L, 1); lua_concat(L, 2); } @@ -198,20 +198,18 @@ static int luaB_collectgarbage (lua_State *L) { } -/* -** This function has all type names as upvalues, to maximize performance. -*/ static int luaB_type (lua_State *L) { - luaL_checkany(L, 1); - lua_pushvalue(L, lua_upvalueindex(lua_type(L, 1) + 1)); + int t = lua_type(L, 1); + luaL_argcheck(L, t != LUA_TNONE, 1, "value expected"); + lua_pushstring(L, lua_typename(L, t)); return 1; } static int pairsmeta (lua_State *L, const char *method, int iszero, lua_CFunction iter) { + luaL_checkany(L, 1); if (luaL_getmetafield(L, 1, method) == LUA_TNIL) { /* no metamethod? */ - luaL_checktype(L, 1, LUA_TTABLE); /* argument must be a table */ lua_pushcfunction(L, iter); /* will return generator, */ lua_pushvalue(L, 1); /* state, */ if (iszero) lua_pushinteger(L, 0); /* and initial value */ @@ -243,18 +241,7 @@ static int luaB_pairs (lua_State *L) { /* -** Traversal function for 'ipairs' for raw tables -*/ -static int ipairsaux_raw (lua_State *L) { - lua_Integer i = luaL_checkinteger(L, 2) + 1; - luaL_checktype(L, 1, LUA_TTABLE); - lua_pushinteger(L, i); - return (lua_rawgeti(L, 1, i) == LUA_TNIL) ? 1 : 2; -} - - -/* -** Traversal function for 'ipairs' for tables with metamethods +** Traversal function for 'ipairs' */ static int ipairsaux (lua_State *L) { lua_Integer i = luaL_checkinteger(L, 2) + 1; @@ -264,18 +251,15 @@ static int ipairsaux (lua_State *L) { /* -** This function will use either 'ipairsaux' or 'ipairsaux_raw' to -** traverse a table, depending on whether the table has metamethods -** that can affect the traversal. +** 'ipairs' function. Returns 'ipairsaux', given "table", 0. +** (The given "table" may not be a table.) */ static int luaB_ipairs (lua_State *L) { - lua_CFunction iter = (luaL_getmetafield(L, 1, "__index") != LUA_TNIL) - ? ipairsaux : ipairsaux_raw; #if defined(LUA_COMPAT_IPAIRS) - return pairsmeta(L, "__ipairs", 1, iter); + return pairsmeta(L, "__ipairs", 1, ipairsaux); #else luaL_checkany(L, 1); - lua_pushcfunction(L, iter); /* iteration function */ + lua_pushcfunction(L, ipairsaux); /* iteration function */ lua_pushvalue(L, 1); /* state */ lua_pushinteger(L, 0); /* initial value */ return 3; @@ -490,9 +474,9 @@ static const luaL_Reg base_funcs[] = { {"setmetatable", luaB_setmetatable}, {"tonumber", luaB_tonumber}, {"tostring", luaB_tostring}, + {"type", luaB_type}, {"xpcall", luaB_xpcall}, /* placeholders */ - {"type", NULL}, {"_G", NULL}, {"_VERSION", NULL}, {NULL, NULL} @@ -500,7 +484,6 @@ static const luaL_Reg base_funcs[] = { LUAMOD_API int luaopen_base (lua_State *L) { - int i; /* open lib into global table */ lua_pushglobaltable(L); luaL_setfuncs(L, base_funcs, 0); @@ -510,11 +493,6 @@ LUAMOD_API int luaopen_base (lua_State *L) { /* set global _VERSION */ lua_pushliteral(L, LUA_VERSION); lua_setfield(L, -2, "_VERSION"); - /* set function 'type' with proper upvalues */ - for (i = 0; i < LUA_NUMTAGS; i++) /* push all type names as upvalues */ - lua_pushstring(L, lua_typename(L, i)); - lua_pushcclosure(L, luaB_type, LUA_NUMTAGS); - lua_setfield(L, -2, "type"); return 1; } diff --git a/source/extern/lua/lbitlib.c b/source/extern/lua/lbitlib.c index 15d5f0c..4786c0d 100644 --- a/source/extern/lua/lbitlib.c +++ b/source/extern/lua/lbitlib.c @@ -1,5 +1,5 @@ /* -** $Id: lbitlib.c,v 1.28 2014/11/02 19:19:04 roberto Exp $ +** $Id: lbitlib.c,v 1.30.1.1 2017/04/19 17:20:42 roberto Exp $ ** Standard library for bitwise operations ** See Copyright Notice in lua.h */ @@ -19,6 +19,10 @@ #if defined(LUA_COMPAT_BITLIB) /* { */ +#define pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define checkunsigned(L,i) ((lua_Unsigned)luaL_checkinteger(L,i)) + + /* number of bits to consider in a number */ #if !defined(LUA_NBITS) #define LUA_NBITS 32 @@ -46,14 +50,14 @@ static lua_Unsigned andaux (lua_State *L) { int i, n = lua_gettop(L); lua_Unsigned r = ~(lua_Unsigned)0; for (i = 1; i <= n; i++) - r &= luaL_checkunsigned(L, i); + r &= checkunsigned(L, i); return trim(r); } static int b_and (lua_State *L) { lua_Unsigned r = andaux(L); - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } @@ -69,8 +73,8 @@ static int b_or (lua_State *L) { int i, n = lua_gettop(L); lua_Unsigned r = 0; for (i = 1; i <= n; i++) - r |= luaL_checkunsigned(L, i); - lua_pushunsigned(L, trim(r)); + r |= checkunsigned(L, i); + pushunsigned(L, trim(r)); return 1; } @@ -79,15 +83,15 @@ static int b_xor (lua_State *L) { int i, n = lua_gettop(L); lua_Unsigned r = 0; for (i = 1; i <= n; i++) - r ^= luaL_checkunsigned(L, i); - lua_pushunsigned(L, trim(r)); + r ^= checkunsigned(L, i); + pushunsigned(L, trim(r)); return 1; } static int b_not (lua_State *L) { - lua_Unsigned r = ~luaL_checkunsigned(L, 1); - lua_pushunsigned(L, trim(r)); + lua_Unsigned r = ~checkunsigned(L, 1); + pushunsigned(L, trim(r)); return 1; } @@ -104,23 +108,23 @@ static int b_shift (lua_State *L, lua_Unsigned r, lua_Integer i) { else r <<= i; r = trim(r); } - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } static int b_lshift (lua_State *L) { - return b_shift(L, luaL_checkunsigned(L, 1), luaL_checkinteger(L, 2)); + return b_shift(L, checkunsigned(L, 1), luaL_checkinteger(L, 2)); } static int b_rshift (lua_State *L) { - return b_shift(L, luaL_checkunsigned(L, 1), -luaL_checkinteger(L, 2)); + return b_shift(L, checkunsigned(L, 1), -luaL_checkinteger(L, 2)); } static int b_arshift (lua_State *L) { - lua_Unsigned r = luaL_checkunsigned(L, 1); + lua_Unsigned r = checkunsigned(L, 1); lua_Integer i = luaL_checkinteger(L, 2); if (i < 0 || !(r & ((lua_Unsigned)1 << (LUA_NBITS - 1)))) return b_shift(L, r, -i); @@ -128,19 +132,19 @@ static int b_arshift (lua_State *L) { if (i >= LUA_NBITS) r = ALLONES; else r = trim((r >> i) | ~(trim(~(lua_Unsigned)0) >> i)); /* add signal bit */ - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } } static int b_rot (lua_State *L, lua_Integer d) { - lua_Unsigned r = luaL_checkunsigned(L, 1); + lua_Unsigned r = checkunsigned(L, 1); int i = d & (LUA_NBITS - 1); /* i = d % NBITS */ r = trim(r); if (i != 0) /* avoid undefined shift of LUA_NBITS when i == 0 */ r = (r << i) | (r >> (LUA_NBITS - i)); - lua_pushunsigned(L, trim(r)); + pushunsigned(L, trim(r)); return 1; } @@ -175,23 +179,22 @@ static int fieldargs (lua_State *L, int farg, int *width) { static int b_extract (lua_State *L) { int w; - lua_Unsigned r = trim(luaL_checkunsigned(L, 1)); + lua_Unsigned r = trim(checkunsigned(L, 1)); int f = fieldargs(L, 2, &w); r = (r >> f) & mask(w); - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } static int b_replace (lua_State *L) { int w; - lua_Unsigned r = trim(luaL_checkunsigned(L, 1)); - lua_Unsigned v = luaL_checkunsigned(L, 2); + lua_Unsigned r = trim(checkunsigned(L, 1)); + lua_Unsigned v = trim(checkunsigned(L, 2)); int f = fieldargs(L, 3, &w); - int m = mask(w); - v &= m; /* erase bits outside given width */ - r = (r & ~(m << f)) | (v << f); - lua_pushunsigned(L, r); + lua_Unsigned m = mask(w); + r = (r & ~(m << f)) | ((v & m) << f); + pushunsigned(L, r); return 1; } diff --git a/source/extern/lua/lcode.c b/source/extern/lua/lcode.c index d6f0fcd..dc7271d 100644 --- a/source/extern/lua/lcode.c +++ b/source/extern/lua/lcode.c @@ -1,5 +1,5 @@ /* -** $Id: lcode.c,v 2.101 2015/04/29 18:24:11 roberto Exp $ +** $Id: lcode.c,v 2.112.1.1 2017/04/19 17:20:42 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ @@ -36,8 +36,12 @@ #define hasjumps(e) ((e)->t != (e)->f) -static int tonumeral(expdesc *e, TValue *v) { - if (e->t != NO_JUMP || e->f != NO_JUMP) +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +static int tonumeral(const expdesc *e, TValue *v) { + if (hasjumps(e)) return 0; /* not a numeral */ switch (e->k) { case VKINT: @@ -51,13 +55,19 @@ static int tonumeral(expdesc *e, TValue *v) { } +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ void luaK_nil (FuncState *fs, int from, int n) { Instruction *previous; int l = from + n - 1; /* last register to set nil */ if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ previous = &fs->f->code[fs->pc-1]; - if (GET_OPCODE(*previous) == OP_LOADNIL) { - int pfrom = GETARG_A(*previous); + if (GET_OPCODE(*previous) == OP_LOADNIL) { /* previous is LOADNIL? */ + int pfrom = GETARG_A(*previous); /* get previous range */ int pl = pfrom + GETARG_B(*previous); if ((pfrom <= from && from <= pl + 1) || (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ @@ -73,37 +83,84 @@ void luaK_nil (FuncState *fs, int from, int n) { } +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sBx(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +static void fixjump (FuncState *fs, int pc, int dest) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + lua_assert(dest != NO_JUMP); + if (abs(offset) > MAXARG_sBx) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_sBx(*jmp, offset); +} + + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; /* nothing to concatenate? */ + else if (*l1 == NO_JUMP) /* no original list? */ + *l1 = l2; /* 'l1' points to 'l2' */ + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); /* last element links to 'l2' */ + } +} + + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). If there are jumps to +** this position (kept in 'jpc'), link them all together so that +** 'patchlistaux' will fix all them directly to the final destination. +*/ int luaK_jump (FuncState *fs) { int jpc = fs->jpc; /* save list of jumps to here */ int j; - fs->jpc = NO_JUMP; + fs->jpc = NO_JUMP; /* no more jumps to here */ j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); luaK_concat(fs, &j, jpc); /* keep them on hold */ return j; } +/* +** Code a 'return' instruction +*/ void luaK_ret (FuncState *fs, int first, int nret) { luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); } +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { luaK_codeABC(fs, op, A, B, C); return luaK_jump(fs); } -static void fixjump (FuncState *fs, int pc, int dest) { - Instruction *jmp = &fs->f->code[pc]; - int offset = dest-(pc+1); - lua_assert(dest != NO_JUMP); - if (abs(offset) > MAXARG_sBx) - luaX_syntaxerror(fs->ls, "control structure too long"); - SETARG_sBx(*jmp, offset); -} - - /* ** returns current 'pc' and marks it as a jump target (to avoid wrong ** optimizations with consecutive instructions not in the same basic block). @@ -114,15 +171,11 @@ int luaK_getlabel (FuncState *fs) { } -static int getjump (FuncState *fs, int pc) { - int offset = GETARG_sBx(fs->f->code[pc]); - if (offset == NO_JUMP) /* point to itself represents end of list */ - return NO_JUMP; /* end of list */ - else - return (pc+1)+offset; /* turn offset into absolute position */ -} - - +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ static Instruction *getjumpcontrol (FuncState *fs, int pc) { Instruction *pi = &fs->f->code[pc]; if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) @@ -133,37 +186,41 @@ static Instruction *getjumpcontrol (FuncState *fs, int pc) { /* -** check whether list has any jump that do not produce a value -** (or produce an inverted value) +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) */ -static int need_value (FuncState *fs, int list) { - for (; list != NO_JUMP; list = getjump(fs, list)) { - Instruction i = *getjumpcontrol(fs, list); - if (GET_OPCODE(i) != OP_TESTSET) return 1; - } - return 0; /* not found */ -} - - static int patchtestreg (FuncState *fs, int node, int reg) { Instruction *i = getjumpcontrol(fs, node); if (GET_OPCODE(*i) != OP_TESTSET) return 0; /* cannot patch other instructions */ if (reg != NO_REG && reg != GETARG_B(*i)) SETARG_A(*i, reg); - else /* no register to put value or register already has the value */ + else { + /* no register to put value or register already has the value; + change instruction to simple test */ *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); - + } return 1; } +/* +** Traverse a list of tests ensuring no one produces a value +*/ static void removevalues (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) patchtestreg(fs, list, NO_REG); } +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, int dtarget) { while (list != NO_JUMP) { @@ -177,15 +234,35 @@ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, } +/* +** Ensure all pending jumps to current position are fixed (jumping +** to current position with no values) and reset list of pending +** jumps +*/ static void dischargejpc (FuncState *fs) { patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); fs->jpc = NO_JUMP; } +/* +** Add elements in 'list' to list of pending jumps to "here" +** (current position) +*/ +void luaK_patchtohere (FuncState *fs, int list) { + luaK_getlabel(fs); /* mark "here" as a jump target */ + luaK_concat(fs, &fs->jpc, list); +} + + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ void luaK_patchlist (FuncState *fs, int list, int target) { - if (target == fs->pc) - luaK_patchtohere(fs, list); + if (target == fs->pc) /* 'target' is current position? */ + luaK_patchtohere(fs, list); /* add list to pending jumps */ else { lua_assert(target < fs->pc); patchlistaux(fs, list, target, NO_REG, target); @@ -193,39 +270,26 @@ void luaK_patchlist (FuncState *fs, int list, int target) { } +/* +** Path all jumps in 'list' to close upvalues up to given 'level' +** (The assertion checks that jumps either were closing nothing +** or were closing higher levels, from inner blocks.) +*/ void luaK_patchclose (FuncState *fs, int list, int level) { level++; /* argument is +1 to reserve 0 as non-op */ - while (list != NO_JUMP) { - int next = getjump(fs, list); + for (; list != NO_JUMP; list = getjump(fs, list)) { lua_assert(GET_OPCODE(fs->f->code[list]) == OP_JMP && (GETARG_A(fs->f->code[list]) == 0 || GETARG_A(fs->f->code[list]) >= level)); SETARG_A(fs->f->code[list], level); - list = next; - } -} - - -void luaK_patchtohere (FuncState *fs, int list) { - luaK_getlabel(fs); - luaK_concat(fs, &fs->jpc, list); -} - - -void luaK_concat (FuncState *fs, int *l1, int l2) { - if (l2 == NO_JUMP) return; - else if (*l1 == NO_JUMP) - *l1 = l2; - else { - int list = *l1; - int next; - while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ - list = next; - fixjump(fs, list, l2); } } +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ static int luaK_code (FuncState *fs, Instruction i) { Proto *f = fs->f; dischargejpc(fs); /* 'pc' will change */ @@ -241,6 +305,10 @@ static int luaK_code (FuncState *fs, Instruction i) { } +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { lua_assert(getOpMode(o) == iABC); lua_assert(getBMode(o) != OpArgN || b == 0); @@ -250,6 +318,9 @@ int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { } +/* +** Format and emit an 'iABx' instruction. +*/ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); lua_assert(getCMode(o) == OpArgN); @@ -258,12 +329,20 @@ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { } +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ static int codeextraarg (FuncState *fs, int a) { lua_assert(a <= MAXARG_Ax); return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a)); } +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ int luaK_codek (FuncState *fs, int reg, int k) { if (k <= MAXARG_Bx) return luaK_codeABx(fs, OP_LOADK, reg, k); @@ -275,6 +354,10 @@ int luaK_codek (FuncState *fs, int reg, int k) { } +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ void luaK_checkstack (FuncState *fs, int n) { int newstack = fs->freereg + n; if (newstack > fs->f->maxstacksize) { @@ -286,12 +369,20 @@ void luaK_checkstack (FuncState *fs, int n) { } +/* +** Reserve 'n' registers in register stack +*/ void luaK_reserveregs (FuncState *fs, int n) { luaK_checkstack(fs, n); fs->freereg += n; } +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +) +*/ static void freereg (FuncState *fs, int reg) { if (!ISK(reg) && reg >= fs->nactvar) { fs->freereg--; @@ -300,6 +391,9 @@ static void freereg (FuncState *fs, int reg) { } +/* +** Free register used by expression 'e' (if any) +*/ static void freeexp (FuncState *fs, expdesc *e) { if (e->k == VNONRELOC) freereg(fs, e->u.info); @@ -307,8 +401,29 @@ static void freeexp (FuncState *fs, expdesc *e) { /* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { + int r1 = (e1->k == VNONRELOC) ? e1->u.info : -1; + int r2 = (e2->k == VNONRELOC) ? e2->u.info : -1; + if (r1 > r2) { + freereg(fs, r1); + freereg(fs, r2); + } + else { + freereg(fs, r2); + freereg(fs, r1); + } +} + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). ** Use scanner's table to cache position of constants in constant list -** and try to reuse constants +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. */ static int addk (FuncState *fs, TValue *key, TValue *v) { lua_State *L = fs->ls->L; @@ -337,17 +452,21 @@ static int addk (FuncState *fs, TValue *key, TValue *v) { } +/* +** Add a string to list of constants and return its index. +*/ int luaK_stringK (FuncState *fs, TString *s) { TValue o; setsvalue(fs->ls->L, &o, s); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use string itself as key */ } /* -** Integers use userdata as keys to avoid collision with floats with same -** value; conversion to 'void*' used only for hashing, no "precision" -** problems +** Add an integer to list of constants and return its index. +** Integers use userdata as keys to avoid collision with floats with +** same value; conversion to 'void*' is used only for hashing, so there +** are no "precision" problems. */ int luaK_intK (FuncState *fs, lua_Integer n) { TValue k, o; @@ -356,21 +475,29 @@ int luaK_intK (FuncState *fs, lua_Integer n) { return addk(fs, &k, &o); } - +/* +** Add a float to list of constants and return its index. +*/ static int luaK_numberK (FuncState *fs, lua_Number r) { TValue o; setfltvalue(&o, r); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use number itself as key */ } +/* +** Add a boolean to list of constants and return its index. +*/ static int boolK (FuncState *fs, int b) { TValue o; setbvalue(&o, b); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use boolean itself as key */ } +/* +** Add nil to list of constants and return its index. +*/ static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); @@ -380,54 +507,79 @@ static int nilK (FuncState *fs) { } +/* +** Fix an expression to return the number of results 'nresults'. +** Either 'e' is a multi-ret expression (function call or vararg) +** or 'nresults' is LUA_MULTRET (as any expression can satisfy that). +*/ void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { if (e->k == VCALL) { /* expression is an open function call? */ - SETARG_C(getcode(fs, e), nresults+1); + SETARG_C(getinstruction(fs, e), nresults + 1); } else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), nresults+1); - SETARG_A(getcode(fs, e), fs->freereg); + Instruction *pc = &getinstruction(fs, e); + SETARG_B(*pc, nresults + 1); + SETARG_A(*pc, fs->freereg); luaK_reserveregs(fs, 1); } + else lua_assert(nresults == LUA_MULTRET); } +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOCABLE (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ void luaK_setoneret (FuncState *fs, expdesc *e) { if (e->k == VCALL) { /* expression is an open function call? */ - e->k = VNONRELOC; - e->u.info = GETARG_A(getcode(fs, e)); + /* already returns 1 value */ + lua_assert(GETARG_C(getinstruction(fs, e)) == 2); + e->k = VNONRELOC; /* result has fixed position */ + e->u.info = GETARG_A(getinstruction(fs, e)); } else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), 2); + SETARG_B(getinstruction(fs, e), 2); e->k = VRELOCABLE; /* can relocate its simple result */ } } +/* +** Ensure that expression 'e' is not a variable. +*/ void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { - case VLOCAL: { - e->k = VNONRELOC; + case VLOCAL: { /* already in a register */ + e->k = VNONRELOC; /* becomes a non-relocatable value */ break; } - case VUPVAL: { + case VUPVAL: { /* move value to some (pending) register */ e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); e->k = VRELOCABLE; break; } case VINDEXED: { - OpCode op = OP_GETTABUP; /* assume 't' is in an upvalue */ + OpCode op; freereg(fs, e->u.ind.idx); - if (e->u.ind.vt == VLOCAL) { /* 't' is in a register? */ + if (e->u.ind.vt == VLOCAL) { /* is 't' in a register? */ freereg(fs, e->u.ind.t); op = OP_GETTABLE; } + else { + lua_assert(e->u.ind.vt == VUPVAL); + op = OP_GETTABUP; /* 't' is in an upvalue */ + } e->u.info = luaK_codeABC(fs, op, 0, e->u.ind.t, e->u.ind.idx); e->k = VRELOCABLE; break; } - case VVARARG: - case VCALL: { + case VVARARG: case VCALL: { luaK_setoneret(fs, e); break; } @@ -436,12 +588,10 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { } -static int code_label (FuncState *fs, int A, int b, int jump) { - luaK_getlabel(fs); /* those instructions may be jump targets */ - return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); -} - - +/* +** Ensures expression value is in register 'reg' (and therefore +** 'e' will become a non-relocatable expression). +*/ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { @@ -466,8 +616,8 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { break; } case VRELOCABLE: { - Instruction *pc = &getcode(fs, e); - SETARG_A(*pc, reg); + Instruction *pc = &getinstruction(fs, e); + SETARG_A(*pc, reg); /* instruction will put result in 'reg' */ break; } case VNONRELOC: { @@ -476,7 +626,7 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { break; } default: { - lua_assert(e->k == VVOID || e->k == VJMP); + lua_assert(e->k == VJMP); return; /* nothing to do... */ } } @@ -485,17 +635,46 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { } +/* +** Ensures expression value is in any register. +*/ static void discharge2anyreg (FuncState *fs, expdesc *e) { - if (e->k != VNONRELOC) { - luaK_reserveregs(fs, 1); - discharge2reg(fs, e, fs->freereg-1); + if (e->k != VNONRELOC) { /* no fixed register yet? */ + luaK_reserveregs(fs, 1); /* get a register */ + discharge2reg(fs, e, fs->freereg-1); /* put value there */ } } +static int code_loadbool (FuncState *fs, int A, int b, int jump) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); +} + + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; + } + return 0; /* not found */ +} + + +/* +** Ensures final expression result (including results from its jump +** lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ static void exp2reg (FuncState *fs, expdesc *e, int reg) { discharge2reg(fs, e, reg); - if (e->k == VJMP) + if (e->k == VJMP) /* expression itself is a test? */ luaK_concat(fs, &e->t, e->u.info); /* put this jump in 't' list */ if (hasjumps(e)) { int final; /* position after whole expression */ @@ -503,8 +682,8 @@ static void exp2reg (FuncState *fs, expdesc *e, int reg) { int p_t = NO_JUMP; /* position of an eventual LOAD true */ if (need_value(fs, e->t) || need_value(fs, e->f)) { int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); - p_f = code_label(fs, reg, 0, 1); - p_t = code_label(fs, reg, 1, 0); + p_f = code_loadbool(fs, reg, 0, 1); + p_t = code_loadbool(fs, reg, 1, 0); luaK_patchtohere(fs, fj); } final = luaK_getlabel(fs); @@ -517,6 +696,10 @@ static void exp2reg (FuncState *fs, expdesc *e, int reg) { } +/* +** Ensures final expression result (including results from its jump +** lists) is in next available register. +*/ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); freeexp(fs, e); @@ -525,26 +708,39 @@ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { } +/* +** Ensures final expression result (including results from its jump +** lists) is in some (any) register and return that register. +*/ int luaK_exp2anyreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); - if (e->k == VNONRELOC) { - if (!hasjumps(e)) return e->u.info; /* exp is already in a register */ + if (e->k == VNONRELOC) { /* expression already has a register? */ + if (!hasjumps(e)) /* no jumps? */ + return e->u.info; /* result is already in a register */ if (e->u.info >= fs->nactvar) { /* reg. is not a local? */ - exp2reg(fs, e, e->u.info); /* put value on it */ + exp2reg(fs, e, e->u.info); /* put final result in it */ return e->u.info; } } - luaK_exp2nextreg(fs, e); /* default */ + luaK_exp2nextreg(fs, e); /* otherwise, use next available register */ return e->u.info; } +/* +** Ensures final expression result is either in a register or in an +** upvalue. +*/ void luaK_exp2anyregup (FuncState *fs, expdesc *e) { if (e->k != VUPVAL || hasjumps(e)) luaK_exp2anyreg(fs, e); } +/* +** Ensures final expression result is either in a register or it is +** a constant. +*/ void luaK_exp2val (FuncState *fs, expdesc *e) { if (hasjumps(e)) luaK_exp2anyreg(fs, e); @@ -553,35 +749,26 @@ void luaK_exp2val (FuncState *fs, expdesc *e) { } +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns R/K index. +*/ int luaK_exp2RK (FuncState *fs, expdesc *e) { luaK_exp2val(fs, e); - switch (e->k) { - case VTRUE: - case VFALSE: - case VNIL: { - if (fs->nk <= MAXINDEXRK) { /* constant fits in RK operand? */ - e->u.info = (e->k == VNIL) ? nilK(fs) : boolK(fs, (e->k == VTRUE)); - e->k = VK; - return RKASK(e->u.info); - } - else break; - } - case VKINT: { - e->u.info = luaK_intK(fs, e->u.ival); - e->k = VK; - goto vk; - } - case VKFLT: { - e->u.info = luaK_numberK(fs, e->u.nval); - e->k = VK; - } - /* FALLTHROUGH */ - case VK: { + switch (e->k) { /* move constants to 'k' */ + case VTRUE: e->u.info = boolK(fs, 1); goto vk; + case VFALSE: e->u.info = boolK(fs, 0); goto vk; + case VNIL: e->u.info = nilK(fs); goto vk; + case VKINT: e->u.info = luaK_intK(fs, e->u.ival); goto vk; + case VKFLT: e->u.info = luaK_numberK(fs, e->u.nval); goto vk; + case VK: vk: + e->k = VK; if (e->u.info <= MAXINDEXRK) /* constant fits in 'argC'? */ return RKASK(e->u.info); else break; - } default: break; } /* not a constant in the right range: put it in a register */ @@ -589,11 +776,14 @@ int luaK_exp2RK (FuncState *fs, expdesc *e) { } +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { switch (var->k) { case VLOCAL: { freeexp(fs, ex); - exp2reg(fs, ex, var->u.info); + exp2reg(fs, ex, var->u.info); /* compute 'ex' into proper place */ return; } case VUPVAL: { @@ -607,29 +797,32 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { luaK_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); break; } - default: { - lua_assert(0); /* invalid var kind to store */ - break; - } + default: lua_assert(0); /* invalid var kind to store */ } freeexp(fs, ex); } +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { int ereg; luaK_exp2anyreg(fs, e); ereg = e->u.info; /* register where 'e' was placed */ freeexp(fs, e); e->u.info = fs->freereg; /* base register for op_self */ - e->k = VNONRELOC; + e->k = VNONRELOC; /* self expression has a fixed register */ luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ luaK_codeABC(fs, OP_SELF, e->u.info, ereg, luaK_exp2RK(fs, key)); freeexp(fs, key); } -static void invertjump (FuncState *fs, expdesc *e) { +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +static void negatecondition (FuncState *fs, expdesc *e) { Instruction *pc = getjumpcontrol(fs, e->u.info); lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && GET_OPCODE(*pc) != OP_TEST); @@ -637,9 +830,15 @@ static void invertjump (FuncState *fs, expdesc *e) { } +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ static int jumponcond (FuncState *fs, expdesc *e, int cond) { if (e->k == VRELOCABLE) { - Instruction ie = getcode(fs, e); + Instruction ie = getinstruction(fs, e); if (GET_OPCODE(ie) == OP_NOT) { fs->pc--; /* remove previous OP_NOT */ return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); @@ -652,13 +851,16 @@ static int jumponcond (FuncState *fs, expdesc *e, int cond) { } +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ void luaK_goiftrue (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { - case VJMP: { - invertjump(fs, e); - pc = e->u.info; + case VJMP: { /* condition? */ + negatecondition(fs, e); /* jump when it is false */ + pc = e->u.info; /* save jump position */ break; } case VK: case VKFLT: case VKINT: case VTRUE: { @@ -666,22 +868,25 @@ void luaK_goiftrue (FuncState *fs, expdesc *e) { break; } default: { - pc = jumponcond(fs, e, 0); + pc = jumponcond(fs, e, 0); /* jump when false */ break; } } - luaK_concat(fs, &e->f, pc); /* insert last jump in 'f' list */ - luaK_patchtohere(fs, e->t); + luaK_concat(fs, &e->f, pc); /* insert new jump in false list */ + luaK_patchtohere(fs, e->t); /* true list jumps to here (to go through) */ e->t = NO_JUMP; } +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ void luaK_goiffalse (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { case VJMP: { - pc = e->u.info; + pc = e->u.info; /* already jump if true */ break; } case VNIL: case VFALSE: { @@ -689,29 +894,32 @@ void luaK_goiffalse (FuncState *fs, expdesc *e) { break; } default: { - pc = jumponcond(fs, e, 1); + pc = jumponcond(fs, e, 1); /* jump if true */ break; } } - luaK_concat(fs, &e->t, pc); /* insert last jump in 't' list */ - luaK_patchtohere(fs, e->f); + luaK_concat(fs, &e->t, pc); /* insert new jump in 't' list */ + luaK_patchtohere(fs, e->f); /* false list jumps to here (to go through) */ e->f = NO_JUMP; } +/* +** Code 'not e', doing constant folding. +*/ static void codenot (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { - e->k = VTRUE; + e->k = VTRUE; /* true == not nil == not false */ break; } case VK: case VKFLT: case VKINT: case VTRUE: { - e->k = VFALSE; + e->k = VFALSE; /* false == not "x" == not 0.5 == not 1 == not true */ break; } case VJMP: { - invertjump(fs, e); + negatecondition(fs, e); break; } case VRELOCABLE: @@ -722,30 +930,32 @@ static void codenot (FuncState *fs, expdesc *e) { e->k = VRELOCABLE; break; } - default: { - lua_assert(0); /* cannot happen */ - break; - } + default: lua_assert(0); /* cannot happen */ } /* interchange true and false lists */ { int temp = e->f; e->f = e->t; e->t = temp; } - removevalues(fs, e->f); + removevalues(fs, e->f); /* values are useless when negated */ removevalues(fs, e->t); } +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. +*/ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { - lua_assert(!hasjumps(t)); - t->u.ind.t = t->u.info; - t->u.ind.idx = luaK_exp2RK(fs, k); - t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL - : check_exp(vkisinreg(t->k), VLOCAL); + lua_assert(!hasjumps(t) && (vkisinreg(t->k) || t->k == VUPVAL)); + t->u.ind.t = t->u.info; /* register or upvalue index */ + t->u.ind.idx = luaK_exp2RK(fs, k); /* R/K index for key */ + t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL : VLOCAL; t->k = VINDEXED; } /* -** return false if folding can raise an error +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. */ static int validop (int op, TValue *v1, TValue *v2) { switch (op) { @@ -762,9 +972,11 @@ static int validop (int op, TValue *v1, TValue *v2) { /* -** Try to "constant-fold" an operation; return 1 iff successful +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) */ -static int constfolding (FuncState *fs, int op, expdesc *e1, expdesc *e2) { +static int constfolding (FuncState *fs, int op, expdesc *e1, + const expdesc *e2) { TValue v1, v2, res; if (!tonumeral(e1, &v1) || !tonumeral(e2, &v2) || !validop(op, &v1, &v2)) return 0; /* non-numeric operands or not safe to fold */ @@ -773,7 +985,7 @@ static int constfolding (FuncState *fs, int op, expdesc *e1, expdesc *e2) { e1->k = VKINT; e1->u.ival = ivalue(&res); } - else { /* folds neither NaN nor 0.0 (to avoid collapsing with -0.0) */ + else { /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ lua_Number n = fltvalue(&res); if (luai_numisnan(n) || n == 0) return 0; @@ -785,81 +997,100 @@ static int constfolding (FuncState *fs, int op, expdesc *e1, expdesc *e2) { /* -** Code for binary and unary expressions that "produce values" -** (arithmetic operations, bitwise operations, concat, length). First -** try to do constant folding (only for numeric [arithmetic and -** bitwise] operations, which is what 'lua_arith' accepts). -** Expression to produce final result will be encoded in 'e1'. +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. */ -static void codeexpval (FuncState *fs, OpCode op, - expdesc *e1, expdesc *e2, int line) { - lua_assert(op >= OP_ADD); - if (op <= OP_BNOT && constfolding(fs, (op - OP_ADD) + LUA_OPADD, e1, e2)) - return; /* result has been folded */ - else { - int o1, o2; - /* move operands to registers (if needed) */ - if (op == OP_UNM || op == OP_BNOT || op == OP_LEN) { /* unary op? */ - o2 = 0; /* no second expression */ - o1 = luaK_exp2anyreg(fs, e1); /* cannot operate on constants */ - } - else { /* regular case (binary operators) */ - o2 = luaK_exp2RK(fs, e2); /* both operands are "RK" */ - o1 = luaK_exp2RK(fs, e1); - } - if (o1 > o2) { /* free registers in proper order */ - freeexp(fs, e1); - freeexp(fs, e2); - } - else { - freeexp(fs, e2); - freeexp(fs, e1); - } - e1->u.info = luaK_codeABC(fs, op, 0, o1, o2); /* generate opcode */ - e1->k = VRELOCABLE; /* all those operations are relocable */ - luaK_fixline(fs, line); - } +static void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) { + int r = luaK_exp2anyreg(fs, e); /* opcodes operate only on registers */ + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, op, 0, r, 0); /* generate opcode */ + e->k = VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); } -static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, - expdesc *e2) { - int o1 = luaK_exp2RK(fs, e1); - int o2 = luaK_exp2RK(fs, e2); - freeexp(fs, e2); - freeexp(fs, e1); - if (cond == 0 && op != OP_EQ) { - int temp; /* exchange args to replace by '<' or '<=' */ - temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ - cond = 1; +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). +** Expression to produce final result will be encoded in 'e1'. +** Because 'luaK_exp2RK' can free registers, its calls must be +** in "stack order" (that is, first on 'e2', which may have more +** recent registers to be released). +*/ +static void codebinexpval (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int line) { + int rk2 = luaK_exp2RK(fs, e2); /* both operands are "RK" */ + int rk1 = luaK_exp2RK(fs, e1); + freeexps(fs, e1, e2); + e1->u.info = luaK_codeABC(fs, op, 0, rk1, rk2); /* generate opcode */ + e1->k = VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for comparisons. +** 'e1' was already put in R/K form by 'luaK_infix'. +*/ +static void codecomp (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int rk1 = (e1->k == VK) ? RKASK(e1->u.info) + : check_exp(e1->k == VNONRELOC, e1->u.info); + int rk2 = luaK_exp2RK(fs, e2); + freeexps(fs, e1, e2); + switch (opr) { + case OPR_NE: { /* '(a ~= b)' ==> 'not (a == b)' */ + e1->u.info = condjump(fs, OP_EQ, 0, rk1, rk2); + break; + } + case OPR_GT: case OPR_GE: { + /* '(a > b)' ==> '(b < a)'; '(a >= b)' ==> '(b <= a)' */ + OpCode op = cast(OpCode, (opr - OPR_NE) + OP_EQ); + e1->u.info = condjump(fs, op, 1, rk2, rk1); /* invert operands */ + break; + } + default: { /* '==', '<', '<=' use their own opcodes */ + OpCode op = cast(OpCode, (opr - OPR_EQ) + OP_EQ); + e1->u.info = condjump(fs, op, 1, rk1, rk2); + break; + } } - e1->u.info = condjump(fs, op, cond, o1, o2); e1->k = VJMP; } +/* +** Apply prefix operation 'op' to expression 'e'. +*/ void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e, int line) { - expdesc e2; - e2.t = e2.f = NO_JUMP; e2.k = VKINT; e2.u.ival = 0; + static const expdesc ef = {VKINT, {0}, NO_JUMP, NO_JUMP}; switch (op) { - case OPR_MINUS: case OPR_BNOT: case OPR_LEN: { - codeexpval(fs, cast(OpCode, (op - OPR_MINUS) + OP_UNM), e, &e2, line); + case OPR_MINUS: case OPR_BNOT: /* use 'ef' as fake 2nd operand */ + if (constfolding(fs, op + LUA_OPUNM, e, &ef)) + break; + /* FALLTHROUGH */ + case OPR_LEN: + codeunexpval(fs, cast(OpCode, op + OP_UNM), e, line); break; - } case OPR_NOT: codenot(fs, e); break; default: lua_assert(0); } } +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { switch (op) { case OPR_AND: { - luaK_goiftrue(fs, v); + luaK_goiftrue(fs, v); /* go ahead only if 'v' is true */ break; } case OPR_OR: { - luaK_goiffalse(fs, v); + luaK_goiffalse(fs, v); /* go ahead only if 'v' is false */ break; } case OPR_CONCAT: { @@ -871,7 +1102,9 @@ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { case OPR_MOD: case OPR_POW: case OPR_BAND: case OPR_BOR: case OPR_BXOR: case OPR_SHL: case OPR_SHR: { - if (!tonumeral(v, NULL)) luaK_exp2RK(fs, v); + if (!tonumeral(v, NULL)) + luaK_exp2RK(fs, v); + /* else keep numeral, which may be folded with 2nd operand */ break; } default: { @@ -882,18 +1115,24 @@ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { } +/* +** Finalize code for binary operation, after reading 2nd operand. +** For '(a .. b .. c)' (which is '(a .. (b .. c))', because +** concatenation is right associative), merge second CONCAT into first +** one. +*/ void luaK_posfix (FuncState *fs, BinOpr op, expdesc *e1, expdesc *e2, int line) { switch (op) { case OPR_AND: { - lua_assert(e1->t == NO_JUMP); /* list must be closed */ + lua_assert(e1->t == NO_JUMP); /* list closed by 'luK_infix' */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->f, e1->f); *e1 = *e2; break; } case OPR_OR: { - lua_assert(e1->f == NO_JUMP); /* list must be closed */ + lua_assert(e1->f == NO_JUMP); /* list closed by 'luK_infix' */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->t, e1->t); *e1 = *e2; @@ -901,15 +1140,16 @@ void luaK_posfix (FuncState *fs, BinOpr op, } case OPR_CONCAT: { luaK_exp2val(fs, e2); - if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { - lua_assert(e1->u.info == GETARG_B(getcode(fs, e2))-1); + if (e2->k == VRELOCABLE && + GET_OPCODE(getinstruction(fs, e2)) == OP_CONCAT) { + lua_assert(e1->u.info == GETARG_B(getinstruction(fs, e2))-1); freeexp(fs, e1); - SETARG_B(getcode(fs, e2), e1->u.info); + SETARG_B(getinstruction(fs, e2), e1->u.info); e1->k = VRELOCABLE; e1->u.info = e2->u.info; } else { luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ - codeexpval(fs, OP_CONCAT, e1, e2, line); + codebinexpval(fs, OP_CONCAT, e1, e2, line); } break; } @@ -917,15 +1157,13 @@ void luaK_posfix (FuncState *fs, BinOpr op, case OPR_IDIV: case OPR_MOD: case OPR_POW: case OPR_BAND: case OPR_BOR: case OPR_BXOR: case OPR_SHL: case OPR_SHR: { - codeexpval(fs, cast(OpCode, (op - OPR_ADD) + OP_ADD), e1, e2, line); - break; - } - case OPR_EQ: case OPR_LT: case OPR_LE: { - codecomp(fs, cast(OpCode, (op - OPR_EQ) + OP_EQ), 1, e1, e2); + if (!constfolding(fs, op + LUA_OPADD, e1, e2)) + codebinexpval(fs, cast(OpCode, op + OP_ADD), e1, e2, line); break; } + case OPR_EQ: case OPR_LT: case OPR_LE: case OPR_NE: case OPR_GT: case OPR_GE: { - codecomp(fs, cast(OpCode, (op - OPR_NE) + OP_EQ), 0, e1, e2); + codecomp(fs, op, e1, e2); break; } default: lua_assert(0); @@ -933,15 +1171,25 @@ void luaK_posfix (FuncState *fs, BinOpr op, } +/* +** Change line information associated with current position. +*/ void luaK_fixline (FuncState *fs, int line) { fs->f->lineinfo[fs->pc - 1] = line; } +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; int b = (tostore == LUA_MULTRET) ? 0 : tostore; - lua_assert(tostore != 0); + lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH); if (c <= MAXARG_C) luaK_codeABC(fs, OP_SETLIST, base, b, c); else if (c <= MAXARG_Ax) { diff --git a/source/extern/lua/lcode.h b/source/extern/lua/lcode.h index 43ab86d..882dc9c 100644 --- a/source/extern/lua/lcode.h +++ b/source/extern/lua/lcode.h @@ -1,5 +1,5 @@ /* -** $Id: lcode.h,v 1.63 2013/12/30 20:47:58 roberto Exp $ +** $Id: lcode.h,v 1.64.1.1 2017/04/19 17:20:42 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ @@ -40,7 +40,8 @@ typedef enum BinOpr { typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; -#define getcode(fs,e) ((fs)->f->code[(e)->u.info]) +/* get (pointer to) instruction of given 'expdesc' */ +#define getinstruction(fs,e) ((fs)->f->code[(e)->u.info]) #define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) diff --git a/source/extern/lua/lcorolib.c b/source/extern/lua/lcorolib.c index 0c0b7fa..0b17af9 100644 --- a/source/extern/lua/lcorolib.c +++ b/source/extern/lua/lcorolib.c @@ -1,5 +1,5 @@ /* -** $Id: lcorolib.c,v 1.9 2014/11/02 19:19:04 roberto Exp $ +** $Id: lcorolib.c,v 1.10.1.1 2017/04/19 17:20:42 roberto Exp $ ** Coroutine Library ** See Copyright Notice in lua.h */ @@ -75,7 +75,7 @@ static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); if (r < 0) { - if (lua_isstring(L, -1)) { /* error object is a string? */ + if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */ luaL_where(L, 1); /* add extra info */ lua_insert(L, -2); lua_concat(L, 2); diff --git a/source/extern/lua/lctype.c b/source/extern/lua/lctype.c index ae9367e..f8ad7a2 100644 --- a/source/extern/lua/lctype.c +++ b/source/extern/lua/lctype.c @@ -1,5 +1,5 @@ /* -** $Id: lctype.c,v 1.12 2014/11/02 19:19:04 roberto Exp $ +** $Id: lctype.c,v 1.12.1.1 2017/04/19 17:20:42 roberto Exp $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/lctype.h b/source/extern/lua/lctype.h index 99c7d12..b09b21a 100644 --- a/source/extern/lua/lctype.h +++ b/source/extern/lua/lctype.h @@ -1,5 +1,5 @@ /* -** $Id: lctype.h,v 1.12 2011/07/15 12:50:29 roberto Exp $ +** $Id: lctype.h,v 1.12.1.1 2013/04/12 18:48:47 roberto Exp $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/ldblib.c b/source/extern/lua/ldblib.c index 9151458..9d29afb 100644 --- a/source/extern/lua/ldblib.c +++ b/source/extern/lua/ldblib.c @@ -1,5 +1,5 @@ /* -** $Id: ldblib.c,v 1.149 2015/02/19 17:06:21 roberto Exp $ +** $Id: ldblib.c,v 1.151.1.1 2017/04/19 17:20:42 roberto Exp $ ** Interface from Lua to its debug API ** See Copyright Notice in lua.h */ @@ -28,8 +28,8 @@ static const int HOOKKEY = 0; /* -** If L1 != L, L1 can be in any state, and therefore there is no -** garanties about its stack space; any push in L1 must be +** If L1 != L, L1 can be in any state, and therefore there are no +** guarantees about its stack space; any push in L1 must be ** checked. */ static void checkstack (lua_State *L, lua_State *L1, int n) { diff --git a/source/extern/lua/ldebug.c b/source/extern/lua/ldebug.c index f76582c..bb0e1d4 100644 --- a/source/extern/lua/ldebug.c +++ b/source/extern/lua/ldebug.c @@ -1,5 +1,5 @@ /* -** $Id: ldebug.c,v 2.115 2015/05/22 17:45:56 roberto Exp $ +** $Id: ldebug.c,v 2.121.1.2 2017/07/10 17:21:50 roberto Exp $ ** Debug Interface ** See Copyright Notice in lua.h */ @@ -38,7 +38,8 @@ #define ci_func(ci) (clLvalue((ci)->func)) -static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); +static const char *funcnamefromcode (lua_State *L, CallInfo *ci, + const char **name); static int currentpc (CallInfo *ci) { @@ -69,7 +70,13 @@ static void swapextra (lua_State *L) { /* -** this function can be called asynchronous (e.g. during a signal) +** This function can be called asynchronously (e.g. during a signal). +** Fields 'oldpc', 'basehookcount', and 'hookcount' (set by +** 'resethookcount') are for debug only, and it is no problem if they +** get arbitrary values (causes at most one wrong hook call). 'hookmask' +** is an atomic value. We assume that pointers are atomic too (e.g., gcc +** ensures that for all platforms where it runs). Moreover, 'hook' is +** always checked before being called (see 'luaD_hook'). */ LUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { if (func == NULL || mask == 0) { /* turn off hooks? */ @@ -126,10 +133,11 @@ static const char *upvalname (Proto *p, int uv) { static const char *findvararg (CallInfo *ci, int n, StkId *pos) { int nparams = clLvalue(ci->func)->p->numparams; - if (n >= cast_int(ci->u.l.base - ci->func) - nparams) + int nvararg = cast_int(ci->u.l.base - ci->func) - nparams; + if (n <= -nvararg) return NULL; /* no such vararg */ else { - *pos = ci->func + nparams + n; + *pos = ci->func + nparams - n; return "(*vararg)"; /* generic name for any vararg */ } } @@ -141,7 +149,7 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n, StkId base; if (isLua(ci)) { if (n < 0) /* access to vararg values? */ - return findvararg(ci, -n, pos); + return findvararg(ci, n, pos); else { base = ci->u.l.base; name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci)); @@ -238,6 +246,20 @@ static void collectvalidlines (lua_State *L, Closure *f) { } +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { + if (ci == NULL) /* no 'ci'? */ + return NULL; /* no info */ + else if (ci->callstatus & CIST_FIN) { /* is this a finalizer? */ + *name = "__gc"; + return "metamethod"; /* report it as such */ + } + /* calling function is a known Lua function? */ + else if (!(ci->callstatus & CIST_TAIL) && isLua(ci->previous)) + return funcnamefromcode(L, ci->previous, name); + else return NULL; /* no way to find a name */ +} + + static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, Closure *f, CallInfo *ci) { int status = 1; @@ -268,11 +290,7 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, break; } case 'n': { - /* calling function is a known Lua function? */ - if (ci && !(ci->callstatus & CIST_TAIL) && isLua(ci->previous)) - ar->namewhat = getfuncname(L, ci->previous, &ar->name); - else - ar->namewhat = NULL; + ar->namewhat = getfuncname(L, ci, &ar->name); if (ar->namewhat == NULL) { ar->namewhat = ""; /* not found */ ar->name = NULL; @@ -465,8 +483,15 @@ static const char *getobjname (Proto *p, int lastpc, int reg, } -static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { - TMS tm = (TMS)0; /* to avoid warnings */ +/* +** Try to find a name for a function based on the code that called it. +** (Only works when function was called by a Lua function.) +** Returns what the name is (e.g., "for iterator", "method", +** "metamethod") and sets '*name' to point to the name. +*/ +static const char *funcnamefromcode (lua_State *L, CallInfo *ci, + const char **name) { + TMS tm = (TMS)0; /* (initial value avoids warnings) */ Proto *p = ci_func(ci)->p; /* calling function */ int pc = currentpc(ci); /* calling instruction index */ Instruction i = p->code[pc]; /* calling instruction */ @@ -476,13 +501,13 @@ static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { } switch (GET_OPCODE(i)) { case OP_CALL: - case OP_TAILCALL: /* get function name */ - return getobjname(p, pc, GETARG_A(i), name); + case OP_TAILCALL: + return getobjname(p, pc, GETARG_A(i), name); /* get function name */ case OP_TFORCALL: { /* for iterator */ *name = "for iterator"; return "for iterator"; } - /* all other instructions can call only through metamethods */ + /* other instructions can do calls through metamethods */ case OP_SELF: case OP_GETTABUP: case OP_GETTABLE: tm = TM_INDEX; break; @@ -503,7 +528,8 @@ static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { case OP_EQ: tm = TM_EQ; break; case OP_LT: tm = TM_LT; break; case OP_LE: tm = TM_LE; break; - default: lua_assert(0); /* other instructions cannot call a function */ + default: + return NULL; /* cannot find a reasonable name */ } *name = getstr(G(L)->tmname[tm]); return "metamethod"; @@ -558,7 +584,7 @@ static const char *varinfo (lua_State *L, const TValue *o) { l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { - const char *t = objtypename(o); + const char *t = luaT_objtypename(L, o); luaG_runerror(L, "attempt to %s a %s value%s", op, t, varinfo(L, o)); } @@ -590,9 +616,9 @@ l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) { l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { - const char *t1 = objtypename(p1); - const char *t2 = objtypename(p2); - if (t1 == t2) + const char *t1 = luaT_objtypename(L, p1); + const char *t2 = luaT_objtypename(L, p2); + if (strcmp(t1, t2) == 0) luaG_runerror(L, "attempt to compare two %s values", t1); else luaG_runerror(L, "attempt to compare %s with %s", t1, t2); @@ -618,7 +644,7 @@ l_noret luaG_errormsg (lua_State *L) { setobjs2s(L, L->top, L->top - 1); /* move argument */ setobjs2s(L, L->top - 1, errfunc); /* push function */ L->top++; /* assume EXTRA_STACK */ - luaD_call(L, L->top - 2, 1, 0); /* call it */ + luaD_callnoyield(L, L->top - 2, 1); /* call it */ } luaD_throw(L, LUA_ERRRUN); } @@ -628,6 +654,7 @@ l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { CallInfo *ci = L->ci; const char *msg; va_list argp; + luaC_checkGC(L); /* error message uses memory */ va_start(argp, fmt); msg = luaO_pushvfstring(L, fmt, argp); /* format message */ va_end(argp); @@ -640,9 +667,11 @@ l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { void luaG_traceexec (lua_State *L) { CallInfo *ci = L->ci; lu_byte mask = L->hookmask; - int counthook = ((mask & LUA_MASKCOUNT) && L->hookcount == 0); + int counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT)); if (counthook) resethookcount(L); /* reset count */ + else if (!(mask & LUA_MASKLINE)) + return; /* no line hook and count != 0; nothing to be done */ if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ return; /* do not call hook again (VM yielded, so it did not move) */ diff --git a/source/extern/lua/ldebug.h b/source/extern/lua/ldebug.h index 0e31546..8cea0ee 100644 --- a/source/extern/lua/ldebug.h +++ b/source/extern/lua/ldebug.h @@ -1,5 +1,5 @@ /* -** $Id: ldebug.h,v 2.14 2015/05/22 17:45:56 roberto Exp $ +** $Id: ldebug.h,v 2.14.1.1 2017/04/19 17:20:42 roberto Exp $ ** Auxiliary functions from Debug Interface module ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/ldo.c b/source/extern/lua/ldo.c index 5c93a25..316e45c 100644 --- a/source/extern/lua/ldo.c +++ b/source/extern/lua/ldo.c @@ -1,5 +1,5 @@ /* -** $Id: ldo.c,v 2.138 2015/05/22 17:48:19 roberto Exp $ +** $Id: ldo.c,v 2.157.1.1 2017/04/19 17:20:42 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ @@ -150,6 +150,11 @@ int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { /* }====================================================== */ +/* +** {================================================================== +** Stack reallocation +** =================================================================== +*/ static void correctstack (lua_State *L, TValue *oldstack) { CallInfo *ci; UpVal *up; @@ -206,9 +211,9 @@ static int stackinuse (lua_State *L) { CallInfo *ci; StkId lim = L->top; for (ci = L->ci; ci != NULL; ci = ci->previous) { - lua_assert(ci->top <= L->stack_last); if (lim < ci->top) lim = ci->top; } + lua_assert(lim <= L->stack_last); return cast_int(lim - L->stack) + 1; /* part of stack in use */ } @@ -216,22 +221,38 @@ static int stackinuse (lua_State *L) { void luaD_shrinkstack (lua_State *L) { int inuse = stackinuse(L); int goodsize = inuse + (inuse / 8) + 2*EXTRA_STACK; - if (goodsize > LUAI_MAXSTACK) goodsize = LUAI_MAXSTACK; - if (L->stacksize > LUAI_MAXSTACK) /* was handling stack overflow? */ + if (goodsize > LUAI_MAXSTACK) + goodsize = LUAI_MAXSTACK; /* respect stack limit */ + if (L->stacksize > LUAI_MAXSTACK) /* had been handling stack overflow? */ luaE_freeCI(L); /* free all CIs (list grew because of an error) */ else luaE_shrinkCI(L); /* shrink list */ - if (inuse > LUAI_MAXSTACK || /* still handling stack overflow? */ - goodsize >= L->stacksize) /* would grow instead of shrink? */ - condmovestack(L); /* don't change stack (change only for debugging) */ - else - luaD_reallocstack(L, goodsize); /* shrink it */ + /* if thread is currently not handling a stack overflow and its + good size is smaller than current size, shrink its stack */ + if (inuse <= (LUAI_MAXSTACK - EXTRA_STACK) && + goodsize < L->stacksize) + luaD_reallocstack(L, goodsize); + else /* don't change stack */ + condmovestack(L,{},{}); /* (change only for debugging) */ } +void luaD_inctop (lua_State *L) { + luaD_checkstack(L, 1); + L->top++; +} + +/* }================================================================== */ + + +/* +** Call a hook for the given event. Make sure there is a hook to be +** called. (Both 'L->hook' and 'L->hookmask', which triggers this +** function, can be changed asynchronously by signals.) +*/ void luaD_hook (lua_State *L, int event, int line) { lua_Hook hook = L->hook; - if (hook && L->allowhook) { + if (hook && L->allowhook) { /* make sure there is a hook */ CallInfo *ci = L->ci; ptrdiff_t top = savestack(L, L->top); ptrdiff_t ci_top = savestack(L, ci->top); @@ -273,15 +294,15 @@ static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { int i; int nfixargs = p->numparams; StkId base, fixed; - lua_assert(actual >= nfixargs); /* move fixed parameters to final position */ - luaD_checkstack(L, p->maxstacksize); /* check again for new 'base' */ fixed = L->top - actual; /* first fixed argument */ base = L->top; /* final position of first argument */ - for (i=0; itop++, fixed + i); - setnilvalue(fixed + i); + setnilvalue(fixed + i); /* erase original copy (for GC) */ } + for (; i < nfixargs; i++) + setnilvalue(L->top++); /* complete missing arguments */ return base; } @@ -304,85 +325,57 @@ static void tryfuncTM (lua_State *L, StkId func) { } - -#define next_ci(L) (L->ci = (L->ci->next ? L->ci->next : luaE_extendCI(L))) - - /* -** returns true if function has been executed (C function) +** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'. +** Handle most typical cases (zero results for commands, one result for +** expressions, multiple results for tail calls/single parameters) +** separated. */ -int luaD_precall (lua_State *L, StkId func, int nresults) { - lua_CFunction f; - CallInfo *ci; - int n; /* number of arguments (Lua) or returns (C) */ - ptrdiff_t funcr = savestack(L, func); - switch (ttype(func)) { - case LUA_TLCF: /* light C function */ - f = fvalue(func); - goto Cfunc; - case LUA_TCCL: { /* C closure */ - f = clCvalue(func)->f; - Cfunc: - luaC_checkGC(L); /* stack grow uses memory */ - luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ - ci = next_ci(L); /* now 'enter' new function */ - ci->nresults = nresults; - ci->func = restorestack(L, funcr); - ci->top = L->top + LUA_MINSTACK; - lua_assert(ci->top <= L->stack_last); - ci->callstatus = 0; - if (L->hookmask & LUA_MASKCALL) - luaD_hook(L, LUA_HOOKCALL, -1); - lua_unlock(L); - n = (*f)(L); /* do the actual call */ - lua_lock(L); - api_checknelems(L, n); - luaD_poscall(L, L->top - n, n); - return 1; +static int moveresults (lua_State *L, const TValue *firstResult, StkId res, + int nres, int wanted) { + switch (wanted) { /* handle typical cases separately */ + case 0: break; /* nothing to move */ + case 1: { /* one result needed */ + if (nres == 0) /* no results? */ + firstResult = luaO_nilobject; /* adjust with nil */ + setobjs2s(L, res, firstResult); /* move it to proper place */ + break; } - case LUA_TLCL: { /* Lua function: prepare its call */ - StkId base; - Proto *p = clLvalue(func)->p; - n = cast_int(L->top - func) - 1; /* number of real arguments */ - luaC_checkGC(L); /* stack grow uses memory */ - luaD_checkstack(L, p->maxstacksize); - for (; n < p->numparams; n++) - setnilvalue(L->top++); /* complete missing arguments */ - if (!p->is_vararg) { - func = restorestack(L, funcr); - base = func + 1; - } - else { - base = adjust_varargs(L, p, n); - func = restorestack(L, funcr); /* previous call can change stack */ - } - ci = next_ci(L); /* now 'enter' new function */ - ci->nresults = nresults; - ci->func = func; - ci->u.l.base = base; - ci->top = base + p->maxstacksize; - lua_assert(ci->top <= L->stack_last); - ci->u.l.savedpc = p->code; /* starting point */ - ci->callstatus = CIST_LUA; - L->top = ci->top; - if (L->hookmask & LUA_MASKCALL) - callhook(L, ci); - return 0; + case LUA_MULTRET: { + int i; + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstResult + i); + L->top = res + nres; + return 0; /* wanted == LUA_MULTRET */ } - default: { /* not a function */ - luaD_checkstack(L, 1); /* ensure space for metamethod */ - func = restorestack(L, funcr); /* previous call may change stack */ - tryfuncTM(L, func); /* try to get '__call' metamethod */ - return luaD_precall(L, func, nresults); /* now it must be a function */ + default: { + int i; + if (wanted <= nres) { /* enough results? */ + for (i = 0; i < wanted; i++) /* move wanted results to correct place */ + setobjs2s(L, res + i, firstResult + i); + } + else { /* not enough results; use all of them plus nils */ + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstResult + i); + for (; i < wanted; i++) /* complete wanted number of results */ + setnilvalue(res + i); + } + break; } } + L->top = res + wanted; /* top points after the last result */ + return 1; } -int luaD_poscall (lua_State *L, StkId firstResult, int nres) { +/* +** Finishes a function call: calls hook if necessary, removes CallInfo, +** moves current number of results to proper place; returns 0 iff call +** wanted multiple (variable number of) results. +*/ +int luaD_poscall (lua_State *L, CallInfo *ci, StkId firstResult, int nres) { StkId res; - int wanted, i; - CallInfo *ci = L->ci; + int wanted = ci->nresults; if (L->hookmask & (LUA_MASKRET | LUA_MASKLINE)) { if (L->hookmask & LUA_MASKRET) { ptrdiff_t fr = savestack(L, firstResult); /* hook may change stack */ @@ -392,15 +385,104 @@ int luaD_poscall (lua_State *L, StkId firstResult, int nres) { L->oldpc = ci->previous->u.l.savedpc; /* 'oldpc' for caller function */ } res = ci->func; /* res == final position of 1st result */ - wanted = ci->nresults; L->ci = ci->previous; /* back to caller */ - /* move results to correct place */ - for (i = wanted; i != 0 && nres-- > 0; i--) - setobjs2s(L, res++, firstResult++); - while (i-- > 0) - setnilvalue(res++); - L->top = res; - return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ + /* move results to proper place */ + return moveresults(L, firstResult, res, nres, wanted); +} + + + +#define next_ci(L) (L->ci = (L->ci->next ? L->ci->next : luaE_extendCI(L))) + + +/* macro to check stack size, preserving 'p' */ +#define checkstackp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p); /* save 'p' */ \ + luaC_checkGC(L), /* stack grow uses memory */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ + + +/* +** Prepares a function call: checks the stack, creates a new CallInfo +** entry, fills in the relevant information, calls hook if needed. +** If function is a C function, does the call, too. (Otherwise, leave +** the execution ('luaV_execute') to the caller, to allow stackless +** calls.) Returns true iff function has been executed (C function). +*/ +int luaD_precall (lua_State *L, StkId func, int nresults) { + lua_CFunction f; + CallInfo *ci; + switch (ttype(func)) { + case LUA_TCCL: /* C closure */ + f = clCvalue(func)->f; + goto Cfunc; + case LUA_TLCF: /* light C function */ + f = fvalue(func); + Cfunc: { + int n; /* number of returns */ + checkstackp(L, LUA_MINSTACK, func); /* ensure minimum stack size */ + ci = next_ci(L); /* now 'enter' new function */ + ci->nresults = nresults; + ci->func = func; + ci->top = L->top + LUA_MINSTACK; + lua_assert(ci->top <= L->stack_last); + ci->callstatus = 0; + if (L->hookmask & LUA_MASKCALL) + luaD_hook(L, LUA_HOOKCALL, -1); + lua_unlock(L); + n = (*f)(L); /* do the actual call */ + lua_lock(L); + api_checknelems(L, n); + luaD_poscall(L, ci, L->top - n, n); + return 1; + } + case LUA_TLCL: { /* Lua function: prepare its call */ + StkId base; + Proto *p = clLvalue(func)->p; + int n = cast_int(L->top - func) - 1; /* number of real arguments */ + int fsize = p->maxstacksize; /* frame size */ + checkstackp(L, fsize, func); + if (p->is_vararg) + base = adjust_varargs(L, p, n); + else { /* non vararg function */ + for (; n < p->numparams; n++) + setnilvalue(L->top++); /* complete missing arguments */ + base = func + 1; + } + ci = next_ci(L); /* now 'enter' new function */ + ci->nresults = nresults; + ci->func = func; + ci->u.l.base = base; + L->top = ci->top = base + fsize; + lua_assert(ci->top <= L->stack_last); + ci->u.l.savedpc = p->code; /* starting point */ + ci->callstatus = CIST_LUA; + if (L->hookmask & LUA_MASKCALL) + callhook(L, ci); + return 0; + } + default: { /* not a function */ + checkstackp(L, 1, func); /* ensure space for metamethod */ + tryfuncTM(L, func); /* try to get '__call' metamethod */ + return luaD_precall(L, func, nresults); /* now it must be a function */ + } + } +} + + +/* +** Check appropriate error for stack overflow ("regular" overflow or +** overflow while handling stack overflow). If 'nCalls' is larger than +** LUAI_MAXCCALLS (which means it is handling a "regular" overflow) but +** smaller than 9/8 of LUAI_MAXCCALLS, does not report an error (to +** allow overflow handling to work) +*/ +static void stackerror (lua_State *L) { + if (L->nCcalls == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) + luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ } @@ -410,21 +492,25 @@ int luaD_poscall (lua_State *L, StkId firstResult, int nres) { ** When returns, all the results are on the stack, starting at the original ** function position. */ -void luaD_call (lua_State *L, StkId func, int nResults, int allowyield) { - if (++L->nCcalls >= LUAI_MAXCCALLS) { - if (L->nCcalls == LUAI_MAXCCALLS) - luaG_runerror(L, "C stack overflow"); - else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) - luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ - } - if (!allowyield) L->nny++; +void luaD_call (lua_State *L, StkId func, int nResults) { + if (++L->nCcalls >= LUAI_MAXCCALLS) + stackerror(L); if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ luaV_execute(L); /* call it */ - if (!allowyield) L->nny--; L->nCcalls--; } +/* +** Similar to 'luaD_call', but does not allow yields during the call +*/ +void luaD_callnoyield (lua_State *L, StkId func, int nResults) { + L->nny++; + luaD_call(L, func, nResults); + L->nny--; +} + + /* ** Completes the execution of an interrupted C function, calling its ** continuation function. @@ -437,19 +523,17 @@ static void finishCcall (lua_State *L, int status) { /* error status can only happen in a protected call */ lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD); if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ - ci->callstatus &= ~CIST_YPCALL; /* finish 'lua_pcall' */ - L->errfunc = ci->u.c.old_errfunc; + ci->callstatus &= ~CIST_YPCALL; /* continuation is also inside it */ + L->errfunc = ci->u.c.old_errfunc; /* with the same error function */ } /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already handled */ adjustresults(L, ci->nresults); - /* call continuation function */ lua_unlock(L); - n = (*ci->u.c.k)(L, status, ci->u.c.ctx); + n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation function */ lua_lock(L); api_checknelems(L, n); - /* finish 'luaD_precall' */ - luaD_poscall(L, L->top - n, n); + luaD_poscall(L, ci, L->top - n, n); /* finish 'luaD_precall' */ } @@ -512,15 +596,16 @@ static int recover (lua_State *L, int status) { /* -** signal an error in the call to 'resume', not in the execution of the -** coroutine itself. (Such errors should not be handled by any coroutine -** error handler and should not kill the coroutine.) +** Signal an error in the call to 'lua_resume', not in the execution +** of the coroutine itself. (Such errors should not be handled by any +** coroutine error handler and should not kill the coroutine.) */ -static l_noret resume_error (lua_State *L, const char *msg, StkId firstArg) { - L->top = firstArg; /* remove args from the stack */ +static int resume_error (lua_State *L, const char *msg, int narg) { + L->top -= narg; /* remove args from the stack */ setsvalue2s(L, L->top, luaS_new(L, msg)); /* push error message */ api_incr_top(L); - luaD_throw(L, -1); /* jump back to 'lua_resume' */ + lua_unlock(L); + return LUA_ERRRUN; } @@ -532,22 +617,15 @@ static l_noret resume_error (lua_State *L, const char *msg, StkId firstArg) { ** coroutine. */ static void resume (lua_State *L, void *ud) { - int nCcalls = L->nCcalls; int n = *(cast(int*, ud)); /* number of arguments */ StkId firstArg = L->top - n; /* first argument */ CallInfo *ci = L->ci; - if (nCcalls >= LUAI_MAXCCALLS) - resume_error(L, "C stack overflow", firstArg); - if (L->status == LUA_OK) { /* may be starting a coroutine */ - if (ci != &L->base_ci) /* not in base level? */ - resume_error(L, "cannot resume non-suspended coroutine", firstArg); - /* coroutine is in base level; start running it */ + if (L->status == LUA_OK) { /* starting a coroutine? */ if (!luaD_precall(L, firstArg - 1, LUA_MULTRET)) /* Lua function? */ luaV_execute(L); /* call it */ } - else if (L->status != LUA_YIELD) - resume_error(L, "cannot resume dead coroutine", firstArg); else { /* resuming from previous yield */ + lua_assert(L->status == LUA_YIELD); L->status = LUA_OK; /* mark that it is running (again) */ ci->func = restorestack(L, ci->extra); if (isLua(ci)) /* yielded inside a hook? */ @@ -560,20 +638,27 @@ static void resume (lua_State *L, void *ud) { api_checknelems(L, n); firstArg = L->top - n; /* yield results come from continuation */ } - luaD_poscall(L, firstArg, n); /* finish 'luaD_precall' */ + luaD_poscall(L, ci, firstArg, n); /* finish 'luaD_precall' */ } unroll(L, NULL); /* run continuation */ } - lua_assert(nCcalls == L->nCcalls); } LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { int status; - int oldnny = L->nny; /* save "number of non-yieldable" calls */ + unsigned short oldnny = L->nny; /* save "number of non-yieldable" calls */ lua_lock(L); - luai_userstateresume(L, nargs); + if (L->status == LUA_OK) { /* may be starting a coroutine */ + if (L->ci != &L->base_ci) /* not in base level? */ + return resume_error(L, "cannot resume non-suspended coroutine", nargs); + } + else if (L->status != LUA_YIELD) + return resume_error(L, "cannot resume dead coroutine", nargs); L->nCcalls = (from) ? from->nCcalls + 1 : 1; + if (L->nCcalls >= LUAI_MAXCCALLS) + return resume_error(L, "C stack overflow", nargs); + luai_userstateresume(L, nargs); L->nny = 0; /* allow yields */ api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); status = luaD_rawrunprotected(L, resume, &nargs); @@ -684,7 +769,7 @@ static void f_parser (lua_State *L, void *ud) { int c = zgetc(p->z); /* read first character */ if (c == LUA_SIGNATURE[0]) { checkmode(L, p->mode, "binary"); - cl = luaU_undump(L, p->z, &p->buff, p->name); + cl = luaU_undump(L, p->z, p->name); } else { checkmode(L, p->mode, "text"); diff --git a/source/extern/lua/ldo.h b/source/extern/lua/ldo.h index edade65..3b2983a 100644 --- a/source/extern/lua/ldo.h +++ b/source/extern/lua/ldo.h @@ -1,5 +1,5 @@ /* -** $Id: ldo.h,v 2.22 2015/05/22 17:48:19 roberto Exp $ +** $Id: ldo.h,v 2.29.1.1 2017/04/19 17:20:42 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ @@ -13,11 +13,21 @@ #include "lzio.h" -#define luaD_checkstack(L,n) if (L->stack_last - L->top <= (n)) \ - luaD_growstack(L, n); else condmovestack(L); +/* +** Macro to check stack size and grow stack if needed. Parameters +** 'pre'/'pos' allow the macro to preserve a pointer into the +** stack across reallocations, doing the work only when needed. +** 'condmovestack' is used in heavy tests to force a stack reallocation +** at every check. +*/ +#define luaD_checkstackaux(L,n,pre,pos) \ + if (L->stack_last - L->top <= (n)) \ + { pre; luaD_growstack(L, n); pos; } else { condmovestack(L,pre,pos); } + +/* In general, 'pre'/'pos' are empty (nothing to save) */ +#define luaD_checkstack(L,n) luaD_checkstackaux(L,n,(void)0,(void)0) -#define incr_top(L) {L->top++; luaD_checkstack(L,0);} #define savestack(L,p) ((char *)(p) - (char *)L->stack) #define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) @@ -30,14 +40,16 @@ LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, const char *mode); LUAI_FUNC void luaD_hook (lua_State *L, int event, int line); LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); -LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults, - int allowyield); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); -LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult, int nres); +LUAI_FUNC int luaD_poscall (lua_State *L, CallInfo *ci, StkId firstResult, + int nres); LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); LUAI_FUNC void luaD_growstack (lua_State *L, int n); LUAI_FUNC void luaD_shrinkstack (lua_State *L); +LUAI_FUNC void luaD_inctop (lua_State *L); LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); diff --git a/source/extern/lua/ldump.c b/source/extern/lua/ldump.c index 4c04812..f025aca 100644 --- a/source/extern/lua/ldump.c +++ b/source/extern/lua/ldump.c @@ -1,5 +1,5 @@ /* -** $Id: ldump.c,v 2.36 2015/03/30 15:43:51 roberto Exp $ +** $Id: ldump.c,v 2.37.1.1 2017/04/19 17:20:42 roberto Exp $ ** save precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -38,7 +38,7 @@ typedef struct { static void DumpBlock (const void *b, size_t size, DumpState *D) { - if (D->status == 0) { + if (D->status == 0 && size > 0) { lua_unlock(D->L); D->status = (*D->writer)(D->L, b, size, D->data); lua_lock(D->L); diff --git a/source/extern/lua/lfunc.c b/source/extern/lua/lfunc.c index 67967da..ccafbb8 100644 --- a/source/extern/lua/lfunc.c +++ b/source/extern/lua/lfunc.c @@ -1,5 +1,5 @@ /* -** $Id: lfunc.c,v 2.45 2014/11/02 19:19:04 roberto Exp $ +** $Id: lfunc.c,v 2.45.1.1 2017/04/19 17:39:34 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/lfunc.h b/source/extern/lua/lfunc.h index 2eeb0d5..c916e98 100644 --- a/source/extern/lua/lfunc.h +++ b/source/extern/lua/lfunc.h @@ -1,5 +1,5 @@ /* -** $Id: lfunc.h,v 2.15 2015/01/13 15:49:11 roberto Exp $ +** $Id: lfunc.h,v 2.15.1.1 2017/04/19 17:39:34 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/lgc.c b/source/extern/lua/lgc.c index 973c269..db4df82 100644 --- a/source/extern/lua/lgc.c +++ b/source/extern/lua/lgc.c @@ -1,5 +1,5 @@ /* -** $Id: lgc.c,v 2.205 2015/03/25 13:42:19 roberto Exp $ +** $Id: lgc.c,v 2.215.1.2 2017/08/31 16:15:27 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ @@ -114,8 +114,13 @@ static void reallymarkobject (global_State *g, GCObject *o); /* -** if key is not marked, mark its entry as dead (therefore removing it -** from the table) +** If key is not marked, mark its entry as dead. This allows key to be +** collected, but keeps its entry in the table. A dead node is needed +** when Lua looks up for a key (it may be part of a chain) and when +** traversing a weak table (key might be removed from the table during +** traversal). Other places never manipulate dead keys, because its +** associated nil value is enough to signal that the entry is logically +** empty. */ static void removeentry (Node *n) { lua_assert(ttisnil(gval(n))); @@ -462,7 +467,7 @@ static lu_mem traversetable (global_State *g, Table *h) { else /* not weak */ traversestrongtable(g, h); return sizeof(Table) + sizeof(TValue) * h->sizearray + - sizeof(Node) * cast(size_t, sizenode(h)); + sizeof(Node) * cast(size_t, allocsizenode(h)); } @@ -534,7 +539,7 @@ static lu_mem traversethread (global_State *g, lua_State *th) { StkId lim = th->stack + th->stacksize; /* real end of stack */ for (; o < lim; o++) /* clear not-marked stack slice */ setnilvalue(o); - /* 'remarkupvals' may have removed thread from 'twups' list */ + /* 'remarkupvals' may have removed thread from 'twups' list */ if (!isintwups(th) && th->openupval != NULL) { th->twups = g->twups; /* link it back to the list */ g->twups = th; @@ -542,7 +547,8 @@ static lu_mem traversethread (global_State *g, lua_State *th) { } else if (g->gckind != KGC_EMERGENCY) luaD_shrinkstack(th); /* do not change stack in emergency cycle */ - return (sizeof(lua_State) + sizeof(TValue) * th->stacksize); + return (sizeof(lua_State) + sizeof(TValue) * th->stacksize + + sizeof(CallInfo) * th->nci); } @@ -637,8 +643,9 @@ static void clearkeys (global_State *g, GCObject *l, GCObject *f) { for (n = gnode(h, 0); n < limit; n++) { if (!ttisnil(gval(n)) && (iscleared(g, gkey(n)))) { setnilvalue(gval(n)); /* remove value ... */ - removeentry(n); /* and remove entry from table */ } + if (ttisnil(gval(n))) /* is entry empty? */ + removeentry(n); /* remove entry from table */ } } } @@ -748,14 +755,11 @@ static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { /* ** sweep a list until a live object (or end of list) */ -static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { +static GCObject **sweeptolive (lua_State *L, GCObject **p) { GCObject **old = p; - int i = 0; do { - i++; p = sweeplist(L, p, 1); } while (p == old); - if (n) *n += i; return p; } @@ -769,12 +773,11 @@ static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { */ /* -** If possible, free concatenation buffer and shrink string table +** If possible, shrink string table */ static void checkSizes (lua_State *L, global_State *g) { if (g->gckind != KGC_EMERGENCY) { l_mem olddebt = g->GCdebt; - luaZ_freebuffer(L, &g->buff); /* free concatenation buffer */ if (g->strt.nuse < g->strt.size / 4) /* string table too big? */ luaS_resize(L, g->strt.size / 2); /* shrink it a little */ g->GCestimate += g->GCdebt - olddebt; /* update estimate */ @@ -797,7 +800,7 @@ static GCObject *udata2finalize (global_State *g) { static void dothecall (lua_State *L, void *ud) { UNUSED(ud); - luaD_call(L, L->top - 2, 0, 0); + luaD_callnoyield(L, L->top - 2, 0); } @@ -816,7 +819,9 @@ static void GCTM (lua_State *L, int propagateerrors) { setobj2s(L, L->top, tm); /* push finalizer... */ setobj2s(L, L->top + 1, &v); /* ... and its argument */ L->top += 2; /* and (next line) call the finalizer */ + L->ci->callstatus |= CIST_FIN; /* will run a finalizer */ status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0); + L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ L->allowhook = oldah; /* restore hooks */ g->gcrunning = running; /* restore state */ if (status != LUA_OK && propagateerrors) { /* error while running __gc? */ @@ -851,10 +856,10 @@ static int runafewfinalizers (lua_State *L) { /* ** call all pending finalizers */ -static void callallpendingfinalizers (lua_State *L, int propagateerrors) { +static void callallpendingfinalizers (lua_State *L) { global_State *g = G(L); while (g->tobefnz) - GCTM(L, propagateerrors); + GCTM(L, 0); } @@ -904,7 +909,7 @@ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { if (issweepphase(g)) { makewhite(g, o); /* "sweep" object 'o' */ if (g->sweepgc == &o->next) /* should not remove 'sweepgc' object */ - g->sweepgc = sweeptolive(L, g->sweepgc, NULL); /* change 'sweepgc' */ + g->sweepgc = sweeptolive(L, g->sweepgc); /* change 'sweepgc' */ } /* search for pointer pointing to 'o' */ for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ } @@ -946,19 +951,16 @@ static void setpause (global_State *g) { /* ** Enter first sweep phase. -** The call to 'sweeptolive' makes pointer point to an object inside -** the list (instead of to the header), so that the real sweep do not -** need to skip objects created between "now" and the start of the real -** sweep. -** Returns how many objects it swept. +** The call to 'sweeplist' tries to make pointer point to an object +** inside the list (instead of to the header), so that the real sweep do +** not need to skip objects created between "now" and the start of the +** real sweep. */ -static int entersweep (lua_State *L) { +static void entersweep (lua_State *L) { global_State *g = G(L); - int n = 0; g->gcstate = GCSswpallgc; lua_assert(g->sweepgc == NULL); - g->sweepgc = sweeptolive(L, &g->allgc, &n); - return n; + g->sweepgc = sweeplist(L, &g->allgc, 1); } @@ -966,7 +968,7 @@ void luaC_freeallobjects (lua_State *L) { global_State *g = G(L); separatetobefnz(g, 1); /* separate all objects with finalizers */ lua_assert(g->finobj == NULL); - callallpendingfinalizers(L, 0); + callallpendingfinalizers(L); lua_assert(g->tobefnz == NULL); g->currentwhite = WHITEBITS; /* this "white" makes all objects look dead */ g->gckind = KGC_NORMAL; @@ -1059,12 +1061,11 @@ static lu_mem singlestep (lua_State *L) { } case GCSatomic: { lu_mem work; - int sw; propagateall(g); /* make sure gray list is empty */ work = atomic(L); /* work is what was traversed by 'atomic' */ - sw = entersweep(L); + entersweep(L); g->GCestimate = gettotalbytes(g); /* first estimate */; - return work + sw * GCSWEEPCOST; + return work; } case GCSswpallgc: { /* sweep "regular" objects */ return sweepstep(L, g, GCSswpfinobj, &g->finobj); @@ -1114,9 +1115,12 @@ void luaC_runtilstate (lua_State *L, int statesmask) { static l_mem getdebt (global_State *g) { l_mem debt = g->GCdebt; int stepmul = g->gcstepmul; - debt = (debt / STEPMULADJ) + 1; - debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; - return debt; + if (debt <= 0) return 0; /* minimal debt */ + else { + debt = (debt / STEPMULADJ) + 1; + debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; + return debt; + } } /* diff --git a/source/extern/lua/lgc.h b/source/extern/lua/lgc.h index 0eedf84..425cd7c 100644 --- a/source/extern/lua/lgc.h +++ b/source/extern/lua/lgc.h @@ -1,5 +1,5 @@ /* -** $Id: lgc.h,v 2.86 2014/10/25 11:50:46 roberto Exp $ +** $Id: lgc.h,v 2.91.1.1 2017/04/19 17:39:34 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ @@ -101,26 +101,35 @@ #define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) -#define luaC_condGC(L,c) \ - {if (G(L)->GCdebt > 0) {c;}; condchangemem(L);} -#define luaC_checkGC(L) luaC_condGC(L, luaC_step(L);) +/* +** Does one step of collection when debt becomes positive. 'pre'/'pos' +** allows some adjustments to be done only when needed. macro +** 'condchangemem' is used only for heavy tests (forcing a full +** GC cycle on every opportunity) +*/ +#define luaC_condGC(L,pre,pos) \ + { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \ + condchangemem(L,pre,pos); } + +/* more often than not, 'pre'/'pos' are empty */ +#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0) -#define luaC_barrier(L,p,v) { \ - if (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) \ - luaC_barrier_(L,obj2gco(p),gcvalue(v)); } +#define luaC_barrier(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrier_(L,obj2gco(p),gcvalue(v)) : cast_void(0)) -#define luaC_barrierback(L,p,v) { \ - if (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) \ - luaC_barrierback_(L,p); } +#define luaC_barrierback(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrierback_(L,p) : cast_void(0)) -#define luaC_objbarrier(L,p,o) { \ - if (isblack(p) && iswhite(o)) \ - luaC_barrier_(L,obj2gco(p),obj2gco(o)); } +#define luaC_objbarrier(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? \ + luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) -#define luaC_upvalbarrier(L,uv) \ - { if (iscollectable((uv)->v) && !upisopen(uv)) \ - luaC_upvalbarrier_(L,uv); } +#define luaC_upvalbarrier(L,uv) ( \ + (iscollectable((uv)->v) && !upisopen(uv)) ? \ + luaC_upvalbarrier_(L,uv) : cast_void(0)) LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); LUAI_FUNC void luaC_freeallobjects (lua_State *L); diff --git a/source/extern/lua/linit.c b/source/extern/lua/linit.c index 8ce94cc..480da52 100644 --- a/source/extern/lua/linit.c +++ b/source/extern/lua/linit.c @@ -1,5 +1,5 @@ /* -** $Id: linit.c,v 1.38 2015/01/05 13:48:33 roberto Exp $ +** $Id: linit.c,v 1.39.1.1 2017/04/19 17:20:42 roberto Exp $ ** Initialization of libraries for lua.c and other clients ** See Copyright Notice in lua.h */ @@ -18,10 +18,10 @@ ** open the library, which is already linked to the application. ** For that, do the following code: ** -** luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); +** luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); ** lua_pushcfunction(L, luaopen_modname); ** lua_setfield(L, -2, modname); -** lua_pop(L, 1); // remove _PRELOAD table +** lua_pop(L, 1); // remove PRELOAD table */ #include "lprefix.h" diff --git a/source/extern/lua/liolib.c b/source/extern/lua/liolib.c index 193cac6..027d4bd 100644 --- a/source/extern/lua/liolib.c +++ b/source/extern/lua/liolib.c @@ -1,5 +1,5 @@ /* -** $Id: liolib.c,v 2.144 2015/04/03 18:41:57 roberto Exp $ +** $Id: liolib.c,v 2.151.1.1 2017/04/19 17:29:57 roberto Exp $ ** Standard I/O (and system) library ** See Copyright Notice in lua.h */ @@ -23,18 +23,25 @@ #include "lualib.h" -#if !defined(l_checkmode) + /* -** Check whether 'mode' matches '[rwa]%+?b?'. ** Change this macro to accept other modes for 'fopen' besides ** the standard ones. */ -#define l_checkmode(mode) \ - (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && \ - (*mode != '+' || ++mode) && /* skip if char is '+' */ \ - (*mode != 'b' || ++mode) && /* skip if char is 'b' */ \ - (*mode == '\0')) +#if !defined(l_checkmode) + +/* accepted extensions to 'mode' in 'fopen' */ +#if !defined(L_MODEEXT) +#define L_MODEEXT "b" +#endif + +/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ +static int l_checkmode (const char *mode) { + return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && + (*mode != '+' || (++mode, 1)) && /* skip if char is '+' */ + (strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */ +} #endif @@ -176,7 +183,7 @@ static FILE *tofile (lua_State *L) { /* ** When creating file handles, always creates a 'closed' file handle ** before opening the actual file; so, if there is a memory error, the -** file is not left opened. +** handle is in a consistent state. */ static LStream *newprefile (lua_State *L) { LStream *p = (LStream *)lua_newuserdata(L, sizeof(LStream)); @@ -199,11 +206,16 @@ static int aux_close (lua_State *L) { } +static int f_close (lua_State *L) { + tofile(L); /* make sure argument is an open stream */ + return aux_close(L); +} + + static int io_close (lua_State *L) { if (lua_isnone(L, 1)) /* no argument? */ lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use standard output */ - tofile(L); /* make sure argument is an open stream */ - return aux_close(L); + return f_close(L); } @@ -265,6 +277,8 @@ static int io_popen (lua_State *L) { const char *filename = luaL_checkstring(L, 1); const char *mode = luaL_optstring(L, 2, "r"); LStream *p = newprefile(L); + luaL_argcheck(L, ((mode[0] == 'r' || mode[0] == 'w') && mode[1] == '\0'), + 2, "invalid mode"); p->f = l_popen(L, filename, mode); p->closef = &io_pclose; return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; @@ -318,8 +332,15 @@ static int io_output (lua_State *L) { static int io_readline (lua_State *L); +/* +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit +** in the limit for upvalues of a closure) +*/ +#define MAXARGLINE 250 + static void aux_lines (lua_State *L, int toclose) { int n = lua_gettop(L) - 1; /* number of arguments to read */ + luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); lua_pushinteger(L, n); /* number of arguments to read */ lua_pushboolean(L, toclose); /* close/not close file when finished */ lua_rotate(L, 2, 2); /* move 'n' and 'toclose' to their positions */ @@ -362,14 +383,17 @@ static int io_lines (lua_State *L) { /* maximum length of a numeral */ -#define MAXRN 200 +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + /* auxiliary structure used by 'read_number' */ typedef struct { FILE *f; /* file being read */ int c; /* current character (look ahead) */ int n; /* number of elements in buffer 'buff' */ - char buff[MAXRN + 1]; /* +1 for ending '\0' */ + char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ } RN; @@ -377,7 +401,7 @@ typedef struct { ** Add current char to buffer (if not out of space) and read next one */ static int nextc (RN *rn) { - if (rn->n >= MAXRN) { /* buffer overflow? */ + if (rn->n >= L_MAXLENNUM) { /* buffer overflow? */ rn->buff[0] = '\0'; /* invalidate result */ return 0; /* fail */ } @@ -390,10 +414,10 @@ static int nextc (RN *rn) { /* -** Accept current char if it is in 'set' (of size 1 or 2) +** Accept current char if it is in 'set' (of size 2) */ static int test2 (RN *rn, const char *set) { - if (rn->c == set[0] || (rn->c == set[1] && rn->c != '\0')) + if (rn->c == set[0] || rn->c == set[1]) return nextc(rn); else return 0; } @@ -422,11 +446,11 @@ static int read_number (lua_State *L, FILE *f) { char decp[2]; rn.f = f; rn.n = 0; decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ - decp[1] = '\0'; + decp[1] = '.'; /* always accept a dot */ l_lockfile(rn.f); do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ test2(&rn, "-+"); /* optional signal */ - if (test2(&rn, "0")) { + if (test2(&rn, "00")) { if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ else count = 1; /* count initial '0' as a valid digit */ } @@ -462,7 +486,7 @@ static int read_line (lua_State *L, FILE *f, int chop) { int c = '\0'; luaL_buffinit(L, &b); while (c != EOF && c != '\n') { /* repeat until end of line */ - char *buff = luaL_prepbuffer(&b); /* pre-allocate buffer */ + char *buff = luaL_prepbuffer(&b); /* preallocate buffer */ int i = 0; l_lockfile(f); /* no memory errors can happen inside the lock */ while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') @@ -483,7 +507,7 @@ static void read_all (lua_State *L, FILE *f) { luaL_Buffer b; luaL_buffinit(L, &b); do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ - char *p = luaL_prepbuffsize(&b, LUAL_BUFFERSIZE); + char *p = luaL_prepbuffer(&b); nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f); luaL_addsize(&b, nr); } while (nr == LUAL_BUFFERSIZE); @@ -602,8 +626,10 @@ static int g_write (lua_State *L, FILE *f, int arg) { if (lua_type(L, arg) == LUA_TNUMBER) { /* optimization: could be done exactly as for strings */ int len = lua_isinteger(L, arg) - ? fprintf(f, LUA_INTEGER_FMT, lua_tointeger(L, arg)) - : fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)); + ? fprintf(f, LUA_INTEGER_FMT, + (LUAI_UACINT)lua_tointeger(L, arg)) + : fprintf(f, LUA_NUMBER_FMT, + (LUAI_UACNUMBER)lua_tonumber(L, arg)); status = status && (len > 0); } else { @@ -693,7 +719,7 @@ static const luaL_Reg iolib[] = { ** methods for file handles */ static const luaL_Reg flib[] = { - {"close", io_close}, + {"close", f_close}, {"flush", f_flush}, {"lines", f_lines}, {"read", f_read}, diff --git a/source/extern/lua/llex.c b/source/extern/lua/llex.c index c35bd55..b6d9a46 100644 --- a/source/extern/lua/llex.c +++ b/source/extern/lua/llex.c @@ -1,5 +1,5 @@ /* -** $Id: llex.c,v 2.93 2015/05/22 17:45:56 roberto Exp $ +** $Id: llex.c,v 2.96.1.1 2017/04/19 17:20:42 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ @@ -162,7 +162,6 @@ static void inclinenumber (LexState *ls) { void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, int firstchar) { ls->t.token = 0; - ls->decpoint = '.'; ls->L = L; ls->current = firstchar; ls->lookahead.token = TK_EOS; /* no look-ahead token */ @@ -207,37 +206,6 @@ static int check_next2 (LexState *ls, const char *set) { } -/* -** change all characters 'from' in buffer to 'to' -*/ -static void buffreplace (LexState *ls, char from, char to) { - if (from != to) { - size_t n = luaZ_bufflen(ls->buff); - char *p = luaZ_buffer(ls->buff); - while (n--) - if (p[n] == from) p[n] = to; - } -} - - -#define buff2num(b,o) (luaO_str2num(luaZ_buffer(b), o) != 0) - -/* -** in case of format error, try to change decimal point separator to -** the one defined in the current locale and check again -*/ -static void trydecpoint (LexState *ls, TValue *o) { - char old = ls->decpoint; - ls->decpoint = lua_getlocaledecpoint(); - buffreplace(ls, old, ls->decpoint); /* try new decimal separator */ - if (!buff2num(ls->buff, o)) { - /* format error with correct decimal point: no more options */ - buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ - lexerror(ls, "malformed number", TK_FLT); - } -} - - /* LUA_NUMBER */ /* ** this function is quite liberal in what it accepts, as 'luaO_str2num' @@ -261,9 +229,8 @@ static int read_numeral (LexState *ls, SemInfo *seminfo) { else break; } save(ls, '\0'); - buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ - if (!buff2num(ls->buff, &obj)) /* format error? */ - trydecpoint(ls, &obj); /* try to update decimal point separator */ + if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */ + lexerror(ls, "malformed number", TK_FLT); if (ttisinteger(&obj)) { seminfo->i = ivalue(&obj); return TK_INT; @@ -277,12 +244,12 @@ static int read_numeral (LexState *ls, SemInfo *seminfo) { /* -** skip a sequence '[=*[' or ']=*]'; if sequence is wellformed, return -** its number of '='s; otherwise, return a negative number (-1 iff there -** are no '='s after initial bracket) +** reads a sequence '[=*[' or ']=*]', leaving the last bracket. +** If sequence is well formed, return its number of '='s + 2; otherwise, +** return 1 if there is no '='s or 0 otherwise (an unfinished '[==...'). */ -static int skip_sep (LexState *ls) { - int count = 0; +static size_t skip_sep (LexState *ls) { + size_t count = 0; int s = ls->current; lua_assert(s == '[' || s == ']'); save_and_next(ls); @@ -290,11 +257,14 @@ static int skip_sep (LexState *ls) { save_and_next(ls); count++; } - return (ls->current == s) ? count : (-count) - 1; + return (ls->current == s) ? count + 2 + : (count == 0) ? 1 + : 0; + } -static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { +static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) { int line = ls->linenumber; /* initial line (for error message) */ save_and_next(ls); /* skip 2nd '[' */ if (currIsNewline(ls)) /* string starts with a newline? */ @@ -328,8 +298,8 @@ static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { } } endloop: if (seminfo) - seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep), - luaZ_bufflen(ls->buff) - 2*(2 + sep)); + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep, + luaZ_bufflen(ls->buff) - 2 * sep); } @@ -477,9 +447,9 @@ static int llex (LexState *ls, SemInfo *seminfo) { /* else is a comment */ next(ls); if (ls->current == '[') { /* long comment? */ - int sep = skip_sep(ls); + size_t sep = skip_sep(ls); luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */ - if (sep >= 0) { + if (sep >= 2) { read_long_string(ls, NULL, sep); /* skip long comment */ luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */ break; @@ -491,12 +461,12 @@ static int llex (LexState *ls, SemInfo *seminfo) { break; } case '[': { /* long string or simply '[' */ - int sep = skip_sep(ls); - if (sep >= 0) { + size_t sep = skip_sep(ls); + if (sep >= 2) { read_long_string(ls, seminfo, sep); return TK_STRING; } - else if (sep != -1) /* '[=...' missing second bracket */ + else if (sep == 0) /* '[=...' missing second bracket */ lexerror(ls, "invalid long string delimiter", TK_STRING); return '['; } diff --git a/source/extern/lua/llex.h b/source/extern/lua/llex.h index afb40b5..2ed0af6 100644 --- a/source/extern/lua/llex.h +++ b/source/extern/lua/llex.h @@ -1,5 +1,5 @@ /* -** $Id: llex.h,v 1.78 2014/10/29 15:38:24 roberto Exp $ +** $Id: llex.h,v 1.79.1.1 2017/04/19 17:20:42 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ @@ -69,7 +69,6 @@ typedef struct LexState { struct Dyndata *dyd; /* dynamic structures used by the parser */ TString *source; /* current source name */ TString *envn; /* environment variable name */ - char decpoint; /* locale decimal point */ } LexState; diff --git a/source/extern/lua/llimits.h b/source/extern/lua/llimits.h index 277c724..d1036f6 100644 --- a/source/extern/lua/llimits.h +++ b/source/extern/lua/llimits.h @@ -1,5 +1,5 @@ /* -** $Id: llimits.h,v 1.135 2015/06/09 14:21:00 roberto Exp $ +** $Id: llimits.h,v 1.141.1.1 2017/04/19 17:20:42 roberto Exp $ ** Limits, basic types, and some other 'installation-dependent' definitions ** See Copyright Notice in lua.h */ @@ -64,7 +64,13 @@ typedef unsigned char lu_byte; #if defined(LUAI_USER_ALIGNMENT_T) typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; #else -typedef union { double u; void *s; lua_Integer i; long l; } L_Umaxalign; +typedef union { + lua_Number n; + double u; + void *s; + lua_Integer i; + long l; +} L_Umaxalign; #endif @@ -78,7 +84,7 @@ typedef LUAI_UACINT l_uacInt; #if defined(lua_assert) #define check_exp(c,e) (lua_assert(c), (e)) /* to avoid problems with conditions too long */ -#define lua_longassert(c) { if (!(c)) lua_assert(0); } +#define lua_longassert(c) ((c) ? (void)0 : lua_assert(0)) #else #define lua_assert(c) ((void)0) #define check_exp(c,e) (e) @@ -184,10 +190,13 @@ typedef unsigned long Instruction; /* -** Size of cache for strings in the API (better be a prime) +** Size of cache for strings in the API. 'N' is the number of +** sets (better be a prime) and "M" is the size of each set (M == 1 +** makes a direct cache.) */ -#if !defined(STRCACHE_SIZE) -#define STRCACHE_SIZE 127 +#if !defined(STRCACHE_N) +#define STRCACHE_N 53 +#define STRCACHE_M 2 #endif @@ -198,7 +207,7 @@ typedef unsigned long Instruction; /* -** macros that are executed whenether program enters the Lua core +** macros that are executed whenever program enters the Lua core ** ('lua_lock') and leaves the core ('lua_unlock') */ #if !defined(lua_lock) @@ -297,17 +306,18 @@ typedef unsigned long Instruction; ** macro to control inclusion of some hard tests on stack reallocation */ #if !defined(HARDSTACKTESTS) -#define condmovestack(L) ((void)0) +#define condmovestack(L,pre,pos) ((void)0) #else /* realloc stack keeping its size */ -#define condmovestack(L) luaD_reallocstack((L), (L)->stacksize) +#define condmovestack(L,pre,pos) \ + { int sz_ = (L)->stacksize; pre; luaD_reallocstack((L), sz_); pos; } #endif #if !defined(HARDMEMTESTS) -#define condchangemem(L) condmovestack(L) +#define condchangemem(L,pre,pos) ((void)0) #else -#define condchangemem(L) \ - ((void)(!(G(L)->gcrunning) || (luaC_fullgc(L, 0), 1))) +#define condchangemem(L,pre,pos) \ + { if (G(L)->gcrunning) { pre; luaC_fullgc(L, 0); pos; } } #endif #endif diff --git a/source/extern/lua/lmathlib.c b/source/extern/lua/lmathlib.c index 4f2ec60..7ef7e59 100644 --- a/source/extern/lua/lmathlib.c +++ b/source/extern/lua/lmathlib.c @@ -1,5 +1,5 @@ /* -** $Id: lmathlib.c,v 1.115 2015/03/12 14:04:04 roberto Exp $ +** $Id: lmathlib.c,v 1.119.1.1 2017/04/19 17:20:42 roberto Exp $ ** Standard mathematical library ** See Copyright Notice in lua.h */ @@ -39,7 +39,7 @@ static int math_abs (lua_State *L) { if (lua_isinteger(L, 1)) { lua_Integer n = lua_tointeger(L, 1); - if (n < 0) n = (lua_Integer)(0u - n); + if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n); lua_pushinteger(L, n); } else @@ -184,10 +184,13 @@ static int math_log (lua_State *L) { else { lua_Number base = luaL_checknumber(L, 2); #if !defined(LUA_USE_C89) - if (base == 2.0) res = l_mathop(log2)(x); else + if (base == l_mathop(2.0)) + res = l_mathop(log2)(x); else #endif - if (base == 10.0) res = l_mathop(log10)(x); - else res = l_mathop(log)(x)/l_mathop(log)(base); + if (base == l_mathop(10.0)) + res = l_mathop(log10)(x); + else + res = l_mathop(log)(x)/l_mathop(log)(base); } lua_pushnumber(L, res); return 1; @@ -262,7 +265,7 @@ static int math_random (lua_State *L) { default: return luaL_error(L, "wrong number of arguments"); } /* random integer in the interval [low, up] */ - luaL_argcheck(L, low <= up, 1, "interval is empty"); + luaL_argcheck(L, low <= up, 1, "interval is empty"); luaL_argcheck(L, low >= 0 || up <= LUA_MAXINTEGER + low, 1, "interval too large"); r *= (double)(up - low) + 1.0; @@ -273,7 +276,7 @@ static int math_random (lua_State *L) { static int math_randomseed (lua_State *L) { l_srand((unsigned int)(lua_Integer)luaL_checknumber(L, 1)); - (void)rand(); /* discard first value to avoid undesirable correlations */ + (void)l_rand(); /* discard first value to avoid undesirable correlations */ return 0; } @@ -281,9 +284,9 @@ static int math_randomseed (lua_State *L) { static int math_type (lua_State *L) { if (lua_type(L, 1) == LUA_TNUMBER) { if (lua_isinteger(L, 1)) - lua_pushliteral(L, "integer"); + lua_pushliteral(L, "integer"); else - lua_pushliteral(L, "float"); + lua_pushliteral(L, "float"); } else { luaL_checkany(L, 1); diff --git a/source/extern/lua/lmem.c b/source/extern/lua/lmem.c index 0a0476c..0241cc3 100644 --- a/source/extern/lua/lmem.c +++ b/source/extern/lua/lmem.c @@ -1,5 +1,5 @@ /* -** $Id: lmem.c,v 1.91 2015/03/06 19:45:54 roberto Exp $ +** $Id: lmem.c,v 1.91.1.1 2017/04/19 17:20:42 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/lmem.h b/source/extern/lua/lmem.h index 30f4848..357b1e4 100644 --- a/source/extern/lua/lmem.h +++ b/source/extern/lua/lmem.h @@ -1,5 +1,5 @@ /* -** $Id: lmem.h,v 1.43 2014/12/19 17:26:14 roberto Exp $ +** $Id: lmem.h,v 1.43.1.1 2017/04/19 17:20:42 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/loadlib.c b/source/extern/lua/loadlib.c index bbf8f67..45f44d3 100644 --- a/source/extern/lua/loadlib.c +++ b/source/extern/lua/loadlib.c @@ -1,5 +1,5 @@ /* -** $Id: loadlib.c,v 1.126 2015/02/16 13:14:33 roberto Exp $ +** $Id: loadlib.c,v 1.130.1.1 2017/04/19 17:20:42 roberto Exp $ ** Dynamic library loader for Lua ** See Copyright Notice in lua.h ** @@ -25,40 +25,9 @@ /* -** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment -** variables that Lua check to set its paths. -*/ -#if !defined(LUA_PATH_VAR) -#define LUA_PATH_VAR "LUA_PATH" -#endif - -#if !defined(LUA_CPATH_VAR) -#define LUA_CPATH_VAR "LUA_CPATH" -#endif - -#define LUA_PATHSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR - -#define LUA_PATHVARVERSION LUA_PATH_VAR LUA_PATHSUFFIX -#define LUA_CPATHVARVERSION LUA_CPATH_VAR LUA_PATHSUFFIX - -/* -** LUA_PATH_SEP is the character that separates templates in a path. -** LUA_PATH_MARK is the string that marks the substitution points in a -** template. -** LUA_EXEC_DIR in a Windows path is replaced by the executable's -** directory. ** LUA_IGMARK is a mark to ignore all before it when building the ** luaopen_ function name. */ -#if !defined (LUA_PATH_SEP) -#define LUA_PATH_SEP ";" -#endif -#if !defined (LUA_PATH_MARK) -#define LUA_PATH_MARK "?" -#endif -#if !defined (LUA_EXEC_DIR) -#define LUA_EXEC_DIR "!" -#endif #if !defined (LUA_IGMARK) #define LUA_IGMARK "-" #endif @@ -94,7 +63,8 @@ static const int CLIBS = 0; #define LIB_FAIL "open" -#define setprogdir(L) ((void)0) + +#define setprogdir(L) ((void)0) /* @@ -179,7 +149,6 @@ static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { #include -#undef setprogdir /* ** optional flags for LoadLibraryEx @@ -189,21 +158,30 @@ static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { #endif +#undef setprogdir + + +/* +** Replace in the path (on the top of the stack) any occurrence +** of LUA_EXEC_DIR with the executable's path. +*/ static void setprogdir (lua_State *L) { char buff[MAX_PATH + 1]; char *lb; DWORD nsize = sizeof(buff)/sizeof(char); - DWORD n = GetModuleFileNameA(NULL, buff, nsize); + DWORD n = GetModuleFileNameA(NULL, buff, nsize); /* get exec. name */ if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL) luaL_error(L, "unable to get ModuleFileName"); else { - *lb = '\0'; + *lb = '\0'; /* cut name on the last '\\' to get the path */ luaL_gsub(L, lua_tostring(L, -1), LUA_EXEC_DIR, buff); lua_remove(L, -2); /* remove original string */ } } + + static void pusherror (lua_State *L) { int error = GetLastError(); char buffer[128]; @@ -272,6 +250,67 @@ static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { #endif /* } */ +/* +** {================================================================== +** Set Paths +** =================================================================== +*/ + +/* +** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment +** variables that Lua check to set its paths. +*/ +#if !defined(LUA_PATH_VAR) +#define LUA_PATH_VAR "LUA_PATH" +#endif + +#if !defined(LUA_CPATH_VAR) +#define LUA_CPATH_VAR "LUA_CPATH" +#endif + + +#define AUXMARK "\1" /* auxiliary mark */ + + +/* +** return registry.LUA_NOENV as a boolean +*/ +static int noenv (lua_State *L) { + int b; + lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); + b = lua_toboolean(L, -1); + lua_pop(L, 1); /* remove value */ + return b; +} + + +/* +** Set a path +*/ +static void setpath (lua_State *L, const char *fieldname, + const char *envname, + const char *dft) { + const char *nver = lua_pushfstring(L, "%s%s", envname, LUA_VERSUFFIX); + const char *path = getenv(nver); /* use versioned name */ + if (path == NULL) /* no environment variable? */ + path = getenv(envname); /* try unversioned name */ + if (path == NULL || noenv(L)) /* no environment variable? */ + lua_pushstring(L, dft); /* use default */ + else { + /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */ + path = luaL_gsub(L, path, LUA_PATH_SEP LUA_PATH_SEP, + LUA_PATH_SEP AUXMARK LUA_PATH_SEP); + luaL_gsub(L, path, AUXMARK, dft); + lua_remove(L, -2); /* remove result from 1st 'gsub' */ + } + setprogdir(L); + lua_setfield(L, -3, fieldname); /* package[fieldname] = path value */ + lua_pop(L, 1); /* pop versioned variable name */ +} + +/* }================================================================== */ + + /* ** return registry.CLIBS[path] */ @@ -520,7 +559,7 @@ static int searcher_Croot (lua_State *L) { static int searcher_preload (lua_State *L) { const char *name = luaL_checkstring(L, 1); - lua_getfield(L, LUA_REGISTRYINDEX, "_PRELOAD"); + lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); if (lua_getfield(L, -1, name) == LUA_TNIL) /* not found? */ lua_pushfstring(L, "\n\tno field package.preload['%s']", name); return 1; @@ -557,9 +596,9 @@ static void findloader (lua_State *L, const char *name) { static int ll_require (lua_State *L) { const char *name = luaL_checkstring(L, 1); - lua_settop(L, 1); /* _LOADED table will be at index 2 */ - lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_getfield(L, 2, name); /* _LOADED[name] */ + lua_settop(L, 1); /* LOADED table will be at index 2 */ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, 2, name); /* LOADED[name] */ if (lua_toboolean(L, -1)) /* is it there? */ return 1; /* package is already loaded */ /* else must load package */ @@ -569,11 +608,11 @@ static int ll_require (lua_State *L) { lua_insert(L, -2); /* name is 1st argument (before search data) */ lua_call(L, 2, 1); /* run loader to load module */ if (!lua_isnil(L, -1)) /* non-nil return? */ - lua_setfield(L, 2, name); /* _LOADED[name] = returned value */ + lua_setfield(L, 2, name); /* LOADED[name] = returned value */ if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */ lua_pushboolean(L, 1); /* use true as result */ lua_pushvalue(L, -1); /* extra copy to be returned */ - lua_setfield(L, 2, name); /* _LOADED[name] = true */ + lua_setfield(L, 2, name); /* LOADED[name] = true */ } return 1; } @@ -666,41 +705,6 @@ static int ll_seeall (lua_State *L) { -/* auxiliary mark (for internal use) */ -#define AUXMARK "\1" - - -/* -** return registry.LUA_NOENV as a boolean -*/ -static int noenv (lua_State *L) { - int b; - lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); - b = lua_toboolean(L, -1); - lua_pop(L, 1); /* remove value */ - return b; -} - - -static void setpath (lua_State *L, const char *fieldname, const char *envname1, - const char *envname2, const char *def) { - const char *path = getenv(envname1); - if (path == NULL) /* no environment variable? */ - path = getenv(envname2); /* try alternative name */ - if (path == NULL || noenv(L)) /* no environment variable? */ - lua_pushstring(L, def); /* use default */ - else { - /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */ - path = luaL_gsub(L, path, LUA_PATH_SEP LUA_PATH_SEP, - LUA_PATH_SEP AUXMARK LUA_PATH_SEP); - luaL_gsub(L, path, AUXMARK, def); - lua_remove(L, -2); - } - setprogdir(L); - lua_setfield(L, -2, fieldname); -} - - static const luaL_Reg pk_funcs[] = { {"loadlib", ll_loadlib}, {"searchpath", ll_searchpath}, @@ -732,7 +736,7 @@ static void createsearcherstable (lua_State *L) { int i; /* create 'searchers' table */ lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0); - /* fill it with pre-defined searchers */ + /* fill it with predefined searchers */ for (i=0; searchers[i] != NULL; i++) { lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */ lua_pushcclosure(L, searchers[i], 1); @@ -764,19 +768,18 @@ LUAMOD_API int luaopen_package (lua_State *L) { createclibstable(L); luaL_newlib(L, pk_funcs); /* create 'package' table */ createsearcherstable(L); - /* set field 'path' */ - setpath(L, "path", LUA_PATHVARVERSION, LUA_PATH_VAR, LUA_PATH_DEFAULT); - /* set field 'cpath' */ - setpath(L, "cpath", LUA_CPATHVARVERSION, LUA_CPATH_VAR, LUA_CPATH_DEFAULT); + /* set paths */ + setpath(L, "path", LUA_PATH_VAR, LUA_PATH_DEFAULT); + setpath(L, "cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT); /* store config information */ lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); lua_setfield(L, -2, "config"); /* set field 'loaded' */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); lua_setfield(L, -2, "loaded"); /* set field 'preload' */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); lua_setfield(L, -2, "preload"); lua_pushglobaltable(L); lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib */ diff --git a/source/extern/lua/lobject.c b/source/extern/lua/lobject.c index 6c53b98..355bf58 100644 --- a/source/extern/lua/lobject.c +++ b/source/extern/lua/lobject.c @@ -1,5 +1,5 @@ /* -** $Id: lobject.c,v 2.104 2015/04/11 18:30:08 roberto Exp $ +** $Id: lobject.c,v 2.113.1.1 2017/04/19 17:29:57 roberto Exp $ ** Some generic functions over Lua objects ** See Copyright Notice in lua.h */ @@ -55,9 +55,7 @@ int luaO_int2fb (unsigned int x) { /* converts back */ int luaO_fb2int (int x) { - int e = (x >> 3) & 0x1f; - if (e == 0) return x; - else return ((x & 7) + 8) << (e - 1); + return (x < 8) ? x : ((x & 7) + 8) << ((x >> 3) - 1); } @@ -245,20 +243,59 @@ static lua_Number lua_strx2number (const char *s, char **endptr) { /* }====================================================== */ -static const char *l_str2d (const char *s, lua_Number *result) { +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + +static const char *l_str2dloc (const char *s, lua_Number *result, int mode) { char *endptr; - if (strpbrk(s, "nN")) /* reject 'inf' and 'nan' */ - return NULL; - else if (strpbrk(s, "xX")) /* hex? */ - *result = lua_strx2number(s, &endptr); - else - *result = lua_str2number(s, &endptr); - if (endptr == s) return NULL; /* nothing recognized */ - while (lisspace(cast_uchar(*endptr))) endptr++; - return (*endptr == '\0' ? endptr : NULL); /* OK if no trailing characters */ + *result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */ + : lua_str2number(s, &endptr); + if (endptr == s) return NULL; /* nothing recognized? */ + while (lisspace(cast_uchar(*endptr))) endptr++; /* skip trailing spaces */ + return (*endptr == '\0') ? endptr : NULL; /* OK if no trailing characters */ } +/* +** Convert string 's' to a Lua number (put in 'result'). Return NULL +** on fail or the address of the ending '\0' on success. +** 'pmode' points to (and 'mode' contains) special things in the string: +** - 'x'/'X' means an hexadecimal numeral +** - 'n'/'N' means 'inf' or 'nan' (which should be rejected) +** - '.' just optimizes the search for the common case (nothing special) +** This function accepts both the current locale or a dot as the radix +** mark. If the conversion fails, it may mean number has a dot but +** locale accepts something else. In that case, the code copies 's' +** to a buffer (because 's' is read-only), changes the dot to the +** current locale radix mark, and tries to convert again. +*/ +static const char *l_str2d (const char *s, lua_Number *result) { + const char *endptr; + const char *pmode = strpbrk(s, ".xXnN"); + int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0; + if (mode == 'n') /* reject 'inf' and 'nan' */ + return NULL; + endptr = l_str2dloc(s, result, mode); /* try to convert */ + if (endptr == NULL) { /* failed? may be a different locale */ + char buff[L_MAXLENNUM + 1]; + const char *pdot = strchr(s, '.'); + if (strlen(s) > L_MAXLENNUM || pdot == NULL) + return NULL; /* string too long or no dot; fail */ + strcpy(buff, s); /* copy string to buffer */ + buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */ + endptr = l_str2dloc(buff, result, mode); /* try again */ + if (endptr != NULL) + endptr = s + (endptr - buff); /* make relative to 's' */ + } + return endptr; +} + + +#define MAXBY10 cast(lua_Unsigned, LUA_MAXINTEGER / 10) +#define MAXLASTD cast_int(LUA_MAXINTEGER % 10) + static const char *l_str2int (const char *s, lua_Integer *result) { lua_Unsigned a = 0; int empty = 1; @@ -275,7 +312,10 @@ static const char *l_str2int (const char *s, lua_Integer *result) { } else { /* decimal */ for (; lisdigit(cast_uchar(*s)); s++) { - a = a * 10 + *s - '0'; + int d = *s - '0'; + if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ + return NULL; /* do not accept it (as integer) */ + a = a * 10 + d; empty = 0; } } @@ -333,9 +373,9 @@ void luaO_tostring (lua_State *L, StkId obj) { size_t len; lua_assert(ttisnumber(obj)); if (ttisinteger(obj)) - len = lua_integer2str(buff, ivalue(obj)); + len = lua_integer2str(buff, sizeof(buff), ivalue(obj)); else { - len = lua_number2str(buff, fltvalue(obj)); + len = lua_number2str(buff, sizeof(buff), fltvalue(obj)); #if !defined(LUA_COMPAT_FLOATSTRING) if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */ buff[len++] = lua_getlocaledecpoint(); @@ -348,27 +388,29 @@ void luaO_tostring (lua_State *L, StkId obj) { static void pushstr (lua_State *L, const char *str, size_t l) { - setsvalue2s(L, L->top++, luaS_newlstr(L, str, l)); + setsvalue2s(L, L->top, luaS_newlstr(L, str, l)); + luaD_inctop(L); } -/* this function handles only '%d', '%c', '%f', '%p', and '%s' - conventional formats, plus Lua-specific '%I' and '%U' */ +/* +** this function handles only '%d', '%c', '%f', '%p', and '%s' + conventional formats, plus Lua-specific '%I' and '%U' +*/ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { int n = 0; for (;;) { const char *e = strchr(fmt, '%'); if (e == NULL) break; - luaD_checkstack(L, 2); /* fmt + item */ pushstr(L, fmt, e - fmt); switch (*(e+1)) { - case 's': { + case 's': { /* zero-terminated string */ const char *s = va_arg(argp, char *); if (s == NULL) s = "(null)"; pushstr(L, s, strlen(s)); break; } - case 'c': { + case 'c': { /* an 'int' as a character */ char buff = cast(char, va_arg(argp, int)); if (lisprint(cast_uchar(buff))) pushstr(L, &buff, 1); @@ -376,28 +418,29 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { luaO_pushfstring(L, "<\\%d>", cast_uchar(buff)); break; } - case 'd': { - setivalue(L->top++, va_arg(argp, int)); + case 'd': { /* an 'int' */ + setivalue(L->top, va_arg(argp, int)); + goto top2str; + } + case 'I': { /* a 'lua_Integer' */ + setivalue(L->top, cast(lua_Integer, va_arg(argp, l_uacInt))); + goto top2str; + } + case 'f': { /* a 'lua_Number' */ + setfltvalue(L->top, cast_num(va_arg(argp, l_uacNumber))); + top2str: /* convert the top element to a string */ + luaD_inctop(L); luaO_tostring(L, L->top - 1); break; } - case 'I': { - setivalue(L->top++, cast(lua_Integer, va_arg(argp, l_uacInt))); - luaO_tostring(L, L->top - 1); - break; - } - case 'f': { - setfltvalue(L->top++, cast_num(va_arg(argp, l_uacNumber))); - luaO_tostring(L, L->top - 1); - break; - } - case 'p': { + case 'p': { /* a pointer */ char buff[4*sizeof(void *) + 8]; /* should be enough space for a '%p' */ - int l = sprintf(buff, "%p", va_arg(argp, void *)); + void *p = va_arg(argp, void *); + int l = lua_pointer2str(buff, sizeof(buff), p); pushstr(L, buff, l); break; } - case 'U': { + case 'U': { /* an 'int' as a UTF-8 sequence */ char buff[UTF8BUFFSZ]; int l = luaO_utf8esc(buff, cast(long, va_arg(argp, long))); pushstr(L, buff + UTF8BUFFSZ - l, l); diff --git a/source/extern/lua/lobject.h b/source/extern/lua/lobject.h index 9230b7a..2408861 100644 --- a/source/extern/lua/lobject.h +++ b/source/extern/lua/lobject.h @@ -1,5 +1,5 @@ /* -** $Id: lobject.h,v 2.111 2015/06/09 14:21:42 roberto Exp $ +** $Id: lobject.h,v 2.117.1.1 2017/04/19 17:39:34 roberto Exp $ ** Type definitions for Lua objects ** See Copyright Notice in lua.h */ @@ -19,8 +19,8 @@ /* ** Extra tags for non-values */ -#define LUA_TPROTO LUA_NUMTAGS -#define LUA_TDEADKEY (LUA_NUMTAGS+1) +#define LUA_TPROTO LUA_NUMTAGS /* function prototypes */ +#define LUA_TDEADKEY (LUA_NUMTAGS+1) /* removed keys in tables */ /* ** number of all possible tags (including LUA_TNONE but excluding DEADKEY) @@ -88,22 +88,32 @@ struct GCObject { -/* -** Union of all Lua values -*/ -typedef union Value Value; - - - /* ** Tagged Values. This is the basic representation of values in Lua, ** an actual value plus a tag with its type. */ +/* +** Union of all Lua values +*/ +typedef union Value { + GCObject *gc; /* collectable objects */ + void *p; /* light userdata */ + int b; /* booleans */ + lua_CFunction f; /* light C functions */ + lua_Integer i; /* integer numbers */ + lua_Number n; /* float numbers */ +} Value; + + #define TValuefields Value value_; int tt_ -typedef struct lua_TValue TValue; + +typedef struct lua_TValue { + TValuefields; +} TValue; + /* macro defining a nil value */ @@ -177,9 +187,9 @@ typedef struct lua_TValue TValue; /* Macros for internal tests */ #define righttt(obj) (ttype(obj) == gcvalue(obj)->tt) -#define checkliveness(g,obj) \ +#define checkliveness(L,obj) \ lua_longassert(!iscollectable(obj) || \ - (righttt(obj) && !isdead(g,gcvalue(obj)))) + (righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj))))) /* Macros to set values */ @@ -215,32 +225,32 @@ typedef struct lua_TValue TValue; #define setsvalue(L,obj,x) \ { TValue *io = (obj); TString *x_ = (x); \ val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \ - checkliveness(G(L),io); } + checkliveness(L,io); } #define setuvalue(L,obj,x) \ { TValue *io = (obj); Udata *x_ = (x); \ val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TUSERDATA)); \ - checkliveness(G(L),io); } + checkliveness(L,io); } #define setthvalue(L,obj,x) \ { TValue *io = (obj); lua_State *x_ = (x); \ val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTHREAD)); \ - checkliveness(G(L),io); } + checkliveness(L,io); } #define setclLvalue(L,obj,x) \ { TValue *io = (obj); LClosure *x_ = (x); \ val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TLCL)); \ - checkliveness(G(L),io); } + checkliveness(L,io); } #define setclCvalue(L,obj,x) \ { TValue *io = (obj); CClosure *x_ = (x); \ val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TCCL)); \ - checkliveness(G(L),io); } + checkliveness(L,io); } #define sethvalue(L,obj,x) \ { TValue *io = (obj); Table *x_ = (x); \ val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTABLE)); \ - checkliveness(G(L),io); } + checkliveness(L,io); } #define setdeadvalue(obj) settt_(obj, LUA_TDEADKEY) @@ -248,7 +258,7 @@ typedef struct lua_TValue TValue; #define setobj(L,obj1,obj2) \ { TValue *io1=(obj1); *io1 = *(obj2); \ - (void)L; checkliveness(G(L),io1); } + (void)L; checkliveness(L,io1); } /* @@ -264,12 +274,13 @@ typedef struct lua_TValue TValue; #define setptvalue2s setptvalue /* from table to same table */ #define setobjt2t setobj -/* to table */ -#define setobj2t setobj /* to new object */ #define setobj2n setobj #define setsvalue2n setsvalue +/* to table (define it as an expression to be used in macros) */ +#define setobj2t(L,o1,o2) ((void)L, *(o1)=*(o2), checkliveness(L,(o1))) + @@ -280,21 +291,6 @@ typedef struct lua_TValue TValue; */ -union Value { - GCObject *gc; /* collectable objects */ - void *p; /* light userdata */ - int b; /* booleans */ - lua_CFunction f; /* light C functions */ - lua_Integer i; /* integer numbers */ - lua_Number n; /* float numbers */ -}; - - -struct lua_TValue { - TValuefields; -}; - - typedef TValue *StkId; /* index to stack elements */ @@ -329,9 +325,9 @@ typedef union UTString { ** Get the actual string (array of bytes) from a 'TString'. ** (Access to 'extra' ensures that value is really a 'TString'.) */ -#define getaddrstr(ts) (cast(char *, (ts)) + sizeof(UTString)) #define getstr(ts) \ - check_exp(sizeof((ts)->extra), cast(const char*, getaddrstr(ts))) + check_exp(sizeof((ts)->extra), cast(char *, (ts)) + sizeof(UTString)) + /* get the actual string (array of bytes) from a Lua value */ #define svalue(o) getstr(tsvalue(o)) @@ -375,13 +371,13 @@ typedef union UUdata { #define setuservalue(L,u,o) \ { const TValue *io=(o); Udata *iu = (u); \ iu->user_ = io->value_; iu->ttuv_ = rttype(io); \ - checkliveness(G(L),io); } + checkliveness(L,io); } #define getuservalue(L,u,o) \ { TValue *io=(o); const Udata *iu = (u); \ io->value_ = iu->user_; settt_(io, iu->ttuv_); \ - checkliveness(G(L),io); } + checkliveness(L,io); } /* @@ -419,8 +415,8 @@ typedef struct Proto { int sizelineinfo; int sizep; /* size of 'p' */ int sizelocvars; - int linedefined; - int lastlinedefined; + int linedefined; /* debug information */ + int lastlinedefined; /* debug information */ TValue *k; /* constants used by the function */ Instruction *code; /* opcodes */ struct Proto **p; /* functions defined inside the function */ @@ -489,7 +485,7 @@ typedef union TKey { #define setnodekey(L,key,obj) \ { TKey *k_=(key); const TValue *io_=(obj); \ k_->nk.value_ = io_->value_; k_->nk.tt_ = io_->tt_; \ - (void)L; checkliveness(G(L),io_); } + (void)L; checkliveness(L,io_); } typedef struct Node { diff --git a/source/extern/lua/lopcodes.c b/source/extern/lua/lopcodes.c index a1cbef8..5ca3eb2 100644 --- a/source/extern/lua/lopcodes.c +++ b/source/extern/lua/lopcodes.c @@ -1,5 +1,5 @@ /* -** $Id: lopcodes.c,v 1.55 2015/01/05 13:48:33 roberto Exp $ +** $Id: lopcodes.c,v 1.55.1.1 2017/04/19 17:20:42 roberto Exp $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/lopcodes.h b/source/extern/lua/lopcodes.h index 864b8e4..6feaa1c 100644 --- a/source/extern/lua/lopcodes.h +++ b/source/extern/lua/lopcodes.h @@ -1,5 +1,5 @@ /* -** $Id: lopcodes.h,v 1.148 2014/10/25 11:50:46 roberto Exp $ +** $Id: lopcodes.h,v 1.149.1.1 2017/04/19 17:20:42 roberto Exp $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ @@ -139,7 +139,9 @@ enum OpMode {iABC, iABx, iAsBx, iAx}; /* basic instruction format */ /* gets the index of the constant */ #define INDEXK(r) ((int)(r) & ~BITRK) +#if !defined(MAXINDEXRK) /* (for debugging only) */ #define MAXINDEXRK (BITRK - 1) +#endif /* code a constant index as a RK value */ #define RKASK(x) ((x) | BITRK) diff --git a/source/extern/lua/loslib.c b/source/extern/lua/loslib.c index cb8a3c3..de590c6 100644 --- a/source/extern/lua/loslib.c +++ b/source/extern/lua/loslib.c @@ -1,5 +1,5 @@ /* -** $Id: loslib.c,v 1.57 2015/04/10 17:41:04 roberto Exp $ +** $Id: loslib.c,v 1.65.1.1 2017/04/19 17:29:57 roberto Exp $ ** Standard Operating System library ** See Copyright Notice in lua.h */ @@ -24,18 +24,29 @@ /* ** {================================================================== -** list of valid conversion specifiers for the 'strftime' function +** List of valid conversion specifiers for the 'strftime' function; +** options are grouped by length; group of length 2 start with '||'. ** =================================================================== */ #if !defined(LUA_STRFTIMEOPTIONS) /* { */ -#if defined(LUA_USE_C89) -#define LUA_STRFTIMEOPTIONS { "aAbBcdHIjmMpSUwWxXyYz%", "" } +/* options for ANSI C 89 (only 1-char options) */ +#define L_STRFTIMEC89 "aAbBcdHIjmMpSUwWxXyYZ%" + +/* options for ISO C 99 and POSIX */ +#define L_STRFTIMEC99 "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%" \ + "||" "EcECExEXEyEY" "OdOeOHOIOmOMOSOuOUOVOwOWOy" /* two-char options */ + +/* options for Windows */ +#define L_STRFTIMEWIN "aAbBcdHIjmMpSUwWxXyYzZ%" \ + "||" "#c#x#d#H#I#j#m#M#S#U#w#W#y#Y" /* two-char options */ + +#if defined(LUA_USE_WINDOWS) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEWIN +#elif defined(LUA_USE_C89) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC89 #else /* C99 specification */ -#define LUA_STRFTIMEOPTIONS \ - { "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%", "", \ - "E", "cCxXyY", \ - "O", "deHImMSuUVwWy" } +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC99 #endif #endif /* } */ @@ -54,7 +65,12 @@ */ #define l_timet lua_Integer #define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) -#define l_checktime(L,a) ((time_t)luaL_checkinteger(L,a)) + +static time_t l_checktime (lua_State *L, int arg) { + lua_Integer t = luaL_checkinteger(L, arg); + luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); + return (time_t)t; +} #endif /* } */ @@ -190,6 +206,23 @@ static void setboolfield (lua_State *L, const char *key, int value) { lua_setfield(L, -2, key); } + +/* +** Set all fields from structure 'tm' in the table on top of the stack +*/ +static void setallfields (lua_State *L, struct tm *stm) { + setfield(L, "sec", stm->tm_sec); + setfield(L, "min", stm->tm_min); + setfield(L, "hour", stm->tm_hour); + setfield(L, "day", stm->tm_mday); + setfield(L, "month", stm->tm_mon + 1); + setfield(L, "year", stm->tm_year + 1900); + setfield(L, "wday", stm->tm_wday + 1); + setfield(L, "yday", stm->tm_yday + 1); + setboolfield(L, "isdst", stm->tm_isdst); +} + + static int getboolfield (lua_State *L, const char *key) { int res; res = (lua_getfield(L, -1, key) == LUA_TNIL) ? -1 : lua_toboolean(L, -1); @@ -198,36 +231,43 @@ static int getboolfield (lua_State *L, const char *key) { } -static int getfield (lua_State *L, const char *key, int d) { - int res, isnum; - lua_getfield(L, -1, key); - res = (int)lua_tointegerx(L, -1, &isnum); - if (!isnum) { - if (d < 0) +/* maximum value for date fields (to avoid arithmetic overflows with 'int') */ +#if !defined(L_MAXDATEFIELD) +#define L_MAXDATEFIELD (INT_MAX / 2) +#endif + +static int getfield (lua_State *L, const char *key, int d, int delta) { + int isnum; + int t = lua_getfield(L, -1, key); /* get field and its type */ + lua_Integer res = lua_tointegerx(L, -1, &isnum); + if (!isnum) { /* field is not an integer? */ + if (t != LUA_TNIL) /* some other value? */ + return luaL_error(L, "field '%s' is not an integer", key); + else if (d < 0) /* absent field; no default? */ return luaL_error(L, "field '%s' missing in date table", key); res = d; } + else { + if (!(-L_MAXDATEFIELD <= res && res <= L_MAXDATEFIELD)) + return luaL_error(L, "field '%s' is out-of-bound", key); + res -= delta; + } lua_pop(L, 1); - return res; + return (int)res; } -static const char *checkoption (lua_State *L, const char *conv, char *buff) { - static const char *const options[] = LUA_STRFTIMEOPTIONS; - unsigned int i; - for (i = 0; i < sizeof(options)/sizeof(options[0]); i += 2) { - if (*conv != '\0' && strchr(options[i], *conv) != NULL) { - buff[1] = *conv; - if (*options[i + 1] == '\0') { /* one-char conversion specifier? */ - buff[2] = '\0'; /* end buffer */ - return conv + 1; - } - else if (*(conv + 1) != '\0' && - strchr(options[i + 1], *(conv + 1)) != NULL) { - buff[2] = *(conv + 1); /* valid two-char conversion specifier */ - buff[3] = '\0'; /* end buffer */ - return conv + 2; - } +static const char *checkoption (lua_State *L, const char *conv, + ptrdiff_t convlen, char *buff) { + const char *option = LUA_STRFTIMEOPTIONS; + int oplen = 1; /* length of options being checked */ + for (; *option != '\0' && oplen <= convlen; option += oplen) { + if (*option == '|') /* next block? */ + oplen++; /* will check options with next length (+1) */ + else if (memcmp(conv, option, oplen) == 0) { /* match? */ + memcpy(buff, conv, oplen); /* copy valid option to buffer */ + buff[oplen] = '\0'; + return conv + oplen; /* return next item */ } } luaL_argerror(L, 1, @@ -236,9 +276,15 @@ static const char *checkoption (lua_State *L, const char *conv, char *buff) { } +/* maximum size for an individual 'strftime' item */ +#define SIZETIMEFMT 250 + + static int os_date (lua_State *L) { - const char *s = luaL_optstring(L, 1, "%c"); + size_t slen; + const char *s = luaL_optlstring(L, 1, "%c", &slen); time_t t = luaL_opt(L, l_checktime, 2, time(NULL)); + const char *se = s + slen; /* 's' end */ struct tm tmr, *stm; if (*s == '!') { /* UTC? */ stm = l_gmtime(&t, &tmr); @@ -247,33 +293,27 @@ static int os_date (lua_State *L) { else stm = l_localtime(&t, &tmr); if (stm == NULL) /* invalid date? */ - lua_pushnil(L); - else if (strcmp(s, "*t") == 0) { + return luaL_error(L, + "time result cannot be represented in this installation"); + if (strcmp(s, "*t") == 0) { lua_createtable(L, 0, 9); /* 9 = number of fields */ - setfield(L, "sec", stm->tm_sec); - setfield(L, "min", stm->tm_min); - setfield(L, "hour", stm->tm_hour); - setfield(L, "day", stm->tm_mday); - setfield(L, "month", stm->tm_mon+1); - setfield(L, "year", stm->tm_year+1900); - setfield(L, "wday", stm->tm_wday+1); - setfield(L, "yday", stm->tm_yday+1); - setboolfield(L, "isdst", stm->tm_isdst); + setallfields(L, stm); } else { - char cc[4]; + char cc[4]; /* buffer for individual conversion specifiers */ luaL_Buffer b; cc[0] = '%'; luaL_buffinit(L, &b); - while (*s) { - if (*s != '%') /* no conversion specifier? */ + while (s < se) { + if (*s != '%') /* not a conversion specifier? */ luaL_addchar(&b, *s++); else { size_t reslen; - char buff[200]; /* should be big enough for any conversion result */ - s = checkoption(L, s + 1, cc); - reslen = strftime(buff, sizeof(buff), cc, stm); - luaL_addlstring(&b, buff, reslen); + char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT); + s++; /* skip '%' */ + s = checkoption(L, s, se - s, cc + 1); /* copy specifier to 'cc' */ + reslen = strftime(buff, SIZETIMEFMT, cc, stm); + luaL_addsize(&b, reslen); } } luaL_pushresult(&b); @@ -290,21 +330,20 @@ static int os_time (lua_State *L) { struct tm ts; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); /* make sure table is at the top */ - ts.tm_sec = getfield(L, "sec", 0); - ts.tm_min = getfield(L, "min", 0); - ts.tm_hour = getfield(L, "hour", 12); - ts.tm_mday = getfield(L, "day", -1); - ts.tm_mon = getfield(L, "month", -1) - 1; - ts.tm_year = getfield(L, "year", -1) - 1900; + ts.tm_sec = getfield(L, "sec", 0, 0); + ts.tm_min = getfield(L, "min", 0, 0); + ts.tm_hour = getfield(L, "hour", 12, 0); + ts.tm_mday = getfield(L, "day", -1, 0); + ts.tm_mon = getfield(L, "month", -1, 1); + ts.tm_year = getfield(L, "year", -1, 1900); ts.tm_isdst = getboolfield(L, "isdst"); t = mktime(&ts); + setallfields(L, &ts); /* update fields with normalized values */ } - if (t != (time_t)(l_timet)t) - luaL_error(L, "time result cannot be represented in this Lua installation"); - else if (t == (time_t)(-1)) - lua_pushnil(L); - else - l_pushtime(L, t); + if (t != (time_t)(l_timet)t || t == (time_t)(-1)) + return luaL_error(L, + "time result cannot be represented in this installation"); + l_pushtime(L, t); return 1; } diff --git a/source/extern/lua/lparser.c b/source/extern/lua/lparser.c index 9a54dfc..2f41e00 100644 --- a/source/extern/lua/lparser.c +++ b/source/extern/lua/lparser.c @@ -1,5 +1,5 @@ /* -** $Id: lparser.c,v 2.147 2014/12/27 20:31:43 roberto Exp $ +** $Id: lparser.c,v 2.155.1.2 2017/04/29 18:11:40 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ @@ -164,7 +164,8 @@ static int registerlocalvar (LexState *ls, TString *varname) { int oldsize = f->sizelocvars; luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, LocVar, SHRT_MAX, "local variables"); - while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; + while (oldsize < f->sizelocvars) + f->locvars[oldsize++].varname = NULL; f->locvars[fs->nlocvars].varname = varname; luaC_objbarrier(ls->L, f, varname); return fs->nlocvars++; @@ -230,7 +231,8 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues, Upvaldesc, MAXUPVAL, "upvalues"); - while (oldsize < f->sizeupvalues) f->upvalues[oldsize++].name = NULL; + while (oldsize < f->sizeupvalues) + f->upvalues[oldsize++].name = NULL; f->upvalues[fs->nups].instack = (v->k == VLOCAL); f->upvalues[fs->nups].idx = cast_byte(v->u.info); f->upvalues[fs->nups].name = name; @@ -255,7 +257,8 @@ static int searchvar (FuncState *fs, TString *n) { */ static void markupval (FuncState *fs, int level) { BlockCnt *bl = fs->bl; - while (bl->nactvar > level) bl = bl->previous; + while (bl->nactvar > level) + bl = bl->previous; bl->upval = 1; } @@ -264,27 +267,26 @@ static void markupval (FuncState *fs, int level) { Find variable with given name 'n'. If it is an upvalue, add this upvalue into all intermediate functions. */ -static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { +static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) /* no more levels? */ - return VVOID; /* default is global */ + init_exp(var, VVOID, 0); /* default is global */ else { int v = searchvar(fs, n); /* look up locals at current level */ if (v >= 0) { /* found? */ init_exp(var, VLOCAL, v); /* variable is local */ if (!base) markupval(fs, v); /* local will be used as an upval */ - return VLOCAL; } else { /* not found as local at current level; try upvalues */ int idx = searchupvalue(fs, n); /* try existing upvalues */ if (idx < 0) { /* not found? */ - if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */ - return VVOID; /* not found; is a global */ + singlevaraux(fs->prev, n, var, 0); /* try upper levels */ + if (var->k == VVOID) /* not found? */ + return; /* it is a global */ /* else was LOCAL or UPVAL */ idx = newupvalue(fs, n, var); /* will be a new upvalue */ } - init_exp(var, VUPVAL, idx); - return VUPVAL; + init_exp(var, VUPVAL, idx); /* new or old upvalue */ } } } @@ -293,10 +295,11 @@ static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; - if (singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */ + singlevaraux(fs, varname, var, 1); + if (var->k == VVOID) { /* global name? */ expdesc key; singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ - lua_assert(var->k == VLOCAL || var->k == VUPVAL); + lua_assert(var->k != VVOID); /* this one must exist */ codestring(ls, &key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ } @@ -320,6 +323,8 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { luaK_nil(fs, reg, extra); } } + if (nexps > nvars) + ls->fs->freereg -= nexps - nvars; /* remove extra values */ } @@ -499,7 +504,8 @@ static Proto *addprototype (LexState *ls) { if (fs->np >= f->sizep) { int oldsize = f->sizep; luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "functions"); - while (oldsize < f->sizep) f->p[oldsize++] = NULL; + while (oldsize < f->sizep) + f->p[oldsize++] = NULL; } f->p[fs->np++] = clp = luaF_newproto(L); luaC_objbarrier(L, f, clp); @@ -538,6 +544,7 @@ static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { fs->bl = NULL; f = fs->f; f->source = ls->source; + luaC_objbarrier(ls->L, f, f->source); f->maxstacksize = 2; /* registers 0/1 are always valid */ enterblock(fs, bl, 0); } @@ -760,7 +767,7 @@ static void parlist (LexState *ls) { } case TK_DOTS: { /* param -> '...' */ luaX_next(ls); - f->is_vararg = 1; + f->is_vararg = 1; /* declared vararg */ break; } default: luaX_syntaxerror(ls, " or '...' expected"); @@ -1155,11 +1162,8 @@ static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { int nexps; checknext(ls, '='); nexps = explist(ls, &e); - if (nexps != nvars) { + if (nexps != nvars) adjust_assign(ls, nvars, nexps, &e); - if (nexps > nvars) - ls->fs->freereg -= nexps - nvars; /* remove extra values */ - } else { luaK_setoneret(ls->fs, &e); /* close last expression */ luaK_storevar(ls->fs, &lh->v, &e); @@ -1225,7 +1229,7 @@ static void labelstat (LexState *ls, TString *label, int line) { checkrepeated(fs, ll, label); /* check for repeated labels */ checknext(ls, TK_DBCOLON); /* skip double colon */ /* create new entry for this label */ - l = newlabelentry(ls, ll, label, line, fs->pc); + l = newlabelentry(ls, ll, label, line, luaK_getlabel(fs)); skipnoopstat(ls); /* skip other no-op statements */ if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ /* assume that locals are already out of scope */ @@ -1389,7 +1393,7 @@ static void test_then_block (LexState *ls, int *escapelist) { luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ enterblock(fs, &bl, 0); /* must enter block before 'goto' */ gotostat(ls, v.t); /* handle goto/break */ - skipnoopstat(ls); /* skip other no-op statements */ + while (testnext(ls, ';')) {} /* skip colons */ if (block_follow(ls, 0)) { /* 'goto' is the entire block? */ leaveblock(fs); return; /* and that is it */ @@ -1493,7 +1497,7 @@ static void exprstat (LexState *ls) { } else { /* stat -> func */ check_condition(ls, v.v.k == VCALL, "syntax error"); - SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ + SETARG_C(getinstruction(fs, &v.v), 1); /* call statement uses no results */ } } @@ -1510,8 +1514,8 @@ static void retstat (LexState *ls) { if (hasmultret(e.k)) { luaK_setmultret(fs, &e); if (e.k == VCALL && nret == 1) { /* tail call? */ - SET_OPCODE(getcode(fs,&e), OP_TAILCALL); - lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); + SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar); } first = fs->nactvar; nret = LUA_MULTRET; /* return all values */ @@ -1610,9 +1614,10 @@ static void mainfunc (LexState *ls, FuncState *fs) { BlockCnt bl; expdesc v; open_func(ls, fs, &bl); - fs->f->is_vararg = 1; /* main function is always vararg */ + fs->f->is_vararg = 1; /* main function is always declared vararg */ init_exp(&v, VLOCAL, 0); /* create and... */ newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */ + luaC_objbarrier(ls->L, fs->f, ls->envn); luaX_next(ls); /* read first token */ statlist(ls); /* parse main body */ check(ls, TK_EOS); @@ -1626,11 +1631,12 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, FuncState funcstate; LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */ setclLvalue(L, L->top, cl); /* anchor it (to avoid being collected) */ - incr_top(L); + luaD_inctop(L); lexstate.h = luaH_new(L); /* create table for scanner */ sethvalue(L, L->top, lexstate.h); /* anchor it */ - incr_top(L); + luaD_inctop(L); funcstate.f = cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); funcstate.f->source = luaS_new(L, name); /* create and anchor TString */ lua_assert(iswhite(funcstate.f)); /* do not need barrier here */ lexstate.buff = buff; diff --git a/source/extern/lua/lparser.h b/source/extern/lua/lparser.h index 62c50ca..f45b23c 100644 --- a/source/extern/lua/lparser.h +++ b/source/extern/lua/lparser.h @@ -1,5 +1,5 @@ /* -** $Id: lparser.h,v 1.74 2014/10/25 11:50:46 roberto Exp $ +** $Id: lparser.h,v 1.76.1.1 2017/04/19 17:20:42 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ @@ -13,25 +13,38 @@ /* -** Expression descriptor +** Expression and variable descriptor. +** Code generation for variables and expressions can be delayed to allow +** optimizations; An 'expdesc' structure describes a potentially-delayed +** variable/expression. It has a description of its "main" value plus a +** list of conditional jumps that can also produce its value (generated +** by short-circuit operators 'and'/'or'). */ +/* kinds of variables/expressions */ typedef enum { - VVOID, /* no value */ - VNIL, - VTRUE, - VFALSE, - VK, /* info = index of constant in 'k' */ - VKFLT, /* nval = numerical float value */ - VKINT, /* nval = numerical integer value */ - VNONRELOC, /* info = result register */ - VLOCAL, /* info = local register */ - VUPVAL, /* info = index of upvalue in 'upvalues' */ - VINDEXED, /* t = table register/upvalue; idx = index R/K */ - VJMP, /* info = instruction pc */ - VRELOCABLE, /* info = instruction pc */ - VCALL, /* info = instruction pc */ - VVARARG /* info = instruction pc */ + VVOID, /* when 'expdesc' describes the last expression a list, + this kind means an empty list (so, no expression) */ + VNIL, /* constant nil */ + VTRUE, /* constant true */ + VFALSE, /* constant false */ + VK, /* constant in 'k'; info = index of constant in 'k' */ + VKFLT, /* floating constant; nval = numerical float value */ + VKINT, /* integer constant; nval = numerical integer value */ + VNONRELOC, /* expression has its value in a fixed register; + info = result register */ + VLOCAL, /* local variable; info = local register */ + VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VINDEXED, /* indexed variable; + ind.vt = whether 't' is register or upvalue; + ind.t = table register or upvalue; + ind.idx = key's R/K index */ + VJMP, /* expression is a test/comparison; + info = pc of corresponding jump instruction */ + VRELOCABLE, /* expression can put result in any register; + info = instruction pc */ + VCALL, /* expression is a function call; info = instruction pc */ + VVARARG /* vararg expression; info = instruction pc */ } expkind; @@ -41,14 +54,14 @@ typedef enum { typedef struct expdesc { expkind k; union { + lua_Integer ival; /* for VKINT */ + lua_Number nval; /* for VKFLT */ + int info; /* for generic use */ struct { /* for indexed variables (VINDEXED) */ short idx; /* index (R/K) */ lu_byte t; /* table (register or upvalue) */ lu_byte vt; /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ } ind; - int info; /* for generic use */ - lua_Number nval; /* for VKFLT */ - lua_Integer ival; /* for VKINT */ } u; int t; /* patch list of 'exit when true' */ int f; /* patch list of 'exit when false' */ diff --git a/source/extern/lua/lprefix.h b/source/extern/lua/lprefix.h index 02daa83..9a749a3 100644 --- a/source/extern/lua/lprefix.h +++ b/source/extern/lua/lprefix.h @@ -1,5 +1,5 @@ /* -** $Id: lprefix.h,v 1.2 2014/12/29 16:54:13 roberto Exp $ +** $Id: lprefix.h,v 1.2.1.1 2017/04/19 17:20:42 roberto Exp $ ** Definitions for Lua code that must come before any other header file ** See Copyright Notice in lua.h */ diff --git a/source/extern/lua/lstate.c b/source/extern/lua/lstate.c index 12e51d2..c1a7664 100644 --- a/source/extern/lua/lstate.c +++ b/source/extern/lua/lstate.c @@ -1,5 +1,5 @@ /* -** $Id: lstate.c,v 2.128 2015/03/04 13:31:21 roberto Exp $ +** $Id: lstate.c,v 2.133.1.1 2017/04/19 17:39:34 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ @@ -76,7 +76,7 @@ typedef struct LG { */ #define addbuff(b,p,e) \ { size_t t = cast(size_t, e); \ - memcpy(buff + p, &t, sizeof(t)); p += sizeof(t); } + memcpy(b + p, &t, sizeof(t)); p += sizeof(t); } static unsigned int makeseed (lua_State *L) { char buff[4 * sizeof(size_t)]; @@ -93,10 +93,14 @@ static unsigned int makeseed (lua_State *L) { /* ** set GCdebt to a new value keeping the value (totalbytes + GCdebt) -** invariant +** invariant (and avoiding underflows in 'totalbytes') */ void luaE_setdebt (global_State *g, l_mem debt) { - g->totalbytes -= (debt - g->GCdebt); + l_mem tb = gettotalbytes(g); + lua_assert(tb > 0); + if (debt < tb - MAX_LMEM) + debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */ + g->totalbytes = tb - debt; g->GCdebt = debt; } @@ -107,6 +111,7 @@ CallInfo *luaE_extendCI (lua_State *L) { L->ci->next = ci; ci->previous = L->ci; ci->next = NULL; + L->nci++; return ci; } @@ -121,6 +126,7 @@ void luaE_freeCI (lua_State *L) { while ((ci = next) != NULL) { next = ci->next; luaM_free(L, ci); + L->nci--; } } @@ -130,13 +136,14 @@ void luaE_freeCI (lua_State *L) { */ void luaE_shrinkCI (lua_State *L) { CallInfo *ci = L->ci; - while (ci->next != NULL) { /* while there is 'next' */ - CallInfo *next2 = ci->next->next; /* next's next */ - if (next2 == NULL) break; - luaM_free(L, ci->next); /* remove next */ + CallInfo *next2; /* next's next */ + /* while there are two nexts */ + while (ci->next != NULL && (next2 = ci->next->next) != NULL) { + luaM_free(L, ci->next); /* free next */ + L->nci--; ci->next = next2; /* remove 'next' from the list */ next2->previous = ci; - ci = next2; + ci = next2; /* keep next's next */ } } @@ -166,6 +173,7 @@ static void freestack (lua_State *L) { return; /* stack not completely built yet */ L->ci = &L->base_ci; /* free the entire 'ci' list */ luaE_freeCI(L); + lua_assert(L->nci == 0); luaM_freearray(L, L->stack, L->stacksize); /* free stack array */ } @@ -214,6 +222,7 @@ static void preinit_thread (lua_State *L, global_State *g) { G(L) = g; L->stack = NULL; L->ci = NULL; + L->nci = 0; L->stacksize = 0; L->twups = L; /* thread has no upvalues */ L->errorJmp = NULL; @@ -237,7 +246,6 @@ static void close_state (lua_State *L) { if (g->version) /* closing a fully built state? */ luai_userstateclose(L); luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); - luaZ_freebuffer(L, &g->buff); freestack(L); lua_assert(gettotalbytes(g) == sizeof(LG)); (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ @@ -306,7 +314,6 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { g->strt.size = g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(&g->l_registry); - luaZ_initbuffer(L, &g->buff); g->panic = NULL; g->version = NULL; g->gcstate = GCSpause; diff --git a/source/extern/lua/lstate.h b/source/extern/lua/lstate.h index eefc217..56b3741 100644 --- a/source/extern/lua/lstate.h +++ b/source/extern/lua/lstate.h @@ -1,5 +1,5 @@ /* -** $Id: lstate.h,v 2.122 2015/06/01 16:34:37 roberto Exp $ +** $Id: lstate.h,v 2.133.1.1 2017/04/19 17:39:34 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ @@ -23,9 +23,27 @@ ** ** 'allgc': all objects not marked for finalization; ** 'finobj': all objects marked for finalization; -** 'tobefnz': all objects ready to be finalized; +** 'tobefnz': all objects ready to be finalized; ** 'fixedgc': all objects that are not to be collected (currently ** only small strings, such as reserved words). +** +** Moreover, there is another set of lists that control gray objects. +** These lists are linked by fields 'gclist'. (All objects that +** can become gray have such a field. The field is not the same +** in all objects, but it always has this name.) Any gray object +** must belong to one of these lists, and all objects in these lists +** must be gray: +** +** 'gray': regular gray objects, still waiting to be visited. +** 'grayagain': objects that must be revisited at the atomic phase. +** That includes +** - black objects got in a write barrier; +** - all kinds of weak tables during propagation phase; +** - all threads. +** 'weak': tables with weak values to be cleared; +** 'ephemeron': ephemeron tables with white->white entries; +** 'allweak': tables with weak keys and/or weak values to be cleared. +** The last three lists are used only during the atomic phase. */ @@ -33,6 +51,15 @@ struct lua_longjmp; /* defined in ldo.c */ +/* +** Atomic type (relative to signals) to better ensure that 'lua_sethook' +** is thread safe +*/ +#if !defined(l_signalT) +#include +#define l_signalT sig_atomic_t +#endif + /* extra stack space to handle TM calls and some other extras */ #define EXTRA_STACK 5 @@ -57,7 +84,7 @@ typedef struct stringtable { ** Information about a call. ** When a thread yields, 'func' is adjusted to pretend that the ** top function has only the yielded values in its stack; in that -** case, the actual 'func' value is saved in field 'extra'. +** case, the actual 'func' value is saved in field 'extra'. ** When a function calls another with a continuation, 'extra' keeps ** the function index so that, in case of errors, the continuation ** function can be called with the correct top. @@ -79,7 +106,7 @@ typedef struct CallInfo { } u; ptrdiff_t extra; short nresults; /* expected number of results from this function */ - lu_byte callstatus; + unsigned short callstatus; } CallInfo; @@ -89,12 +116,13 @@ typedef struct CallInfo { #define CIST_OAH (1<<0) /* original value of 'allowhook' */ #define CIST_LUA (1<<1) /* call is running a Lua function */ #define CIST_HOOKED (1<<2) /* call is running a debug hook */ -#define CIST_REENTRY (1<<3) /* call is running on same invocation of - luaV_execute of previous call */ +#define CIST_FRESH (1<<3) /* call is running on a fresh invocation + of luaV_execute */ #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ #define CIST_TAIL (1<<5) /* call was tail called */ #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ #define CIST_LEQ (1<<7) /* using __lt for __le */ +#define CIST_FIN (1<<8) /* call is running a finalizer */ #define isLua(ci) ((ci)->callstatus & CIST_LUA) @@ -109,7 +137,7 @@ typedef struct CallInfo { typedef struct global_State { lua_Alloc frealloc; /* function to reallocate memory */ void *ud; /* auxiliary data to 'frealloc' */ - lu_mem totalbytes; /* number of bytes currently allocated - GCdebt */ + l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ lu_mem GCmemtrav; /* memory traversed by the GC */ lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ @@ -131,7 +159,6 @@ typedef struct global_State { GCObject *tobefnz; /* list of userdata to be GC */ GCObject *fixedgc; /* list of objects not to be collected */ struct lua_State *twups; /* list of threads with open upvalues */ - Mbuffer buff; /* temporary buffer for string concatenation */ unsigned int gcfinnum; /* number of finalizers to call in each GC step */ int gcpause; /* size of pause between successive GCs */ int gcstepmul; /* GC 'granularity' */ @@ -141,7 +168,7 @@ typedef struct global_State { TString *memerrmsg; /* memory-error message */ TString *tmname[TM_N]; /* array with tag-method names */ struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */ - TString *strcache[STRCACHE_SIZE][1]; /* cache for strings in API */ + TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ } global_State; @@ -150,6 +177,7 @@ typedef struct global_State { */ struct lua_State { CommonHeader; + unsigned short nci; /* number of items in 'ci' list */ lu_byte status; StkId top; /* first free slot in the stack */ global_State *l_G; @@ -162,14 +190,14 @@ struct lua_State { struct lua_State *twups; /* list of threads with open upvalues */ struct lua_longjmp *errorJmp; /* current error recover point */ CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ - lua_Hook hook; + volatile lua_Hook hook; ptrdiff_t errfunc; /* current error handling function (stack index) */ int stacksize; int basehookcount; int hookcount; unsigned short nny; /* number of non-yieldable calls in stack */ unsigned short nCcalls; /* number of nested C calls */ - lu_byte hookmask; + l_signalT hookmask; lu_byte allowhook; }; @@ -212,7 +240,7 @@ union GCUnion { /* actual number of total bytes allocated */ -#define gettotalbytes(g) ((g)->totalbytes + (g)->GCdebt) +#define gettotalbytes(g) cast(lu_mem, (g)->totalbytes + (g)->GCdebt) LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); diff --git a/source/extern/lua/lstring.c b/source/extern/lua/lstring.c index 5e0e3c4..6257f21 100644 --- a/source/extern/lua/lstring.c +++ b/source/extern/lua/lstring.c @@ -1,5 +1,5 @@ /* -** $Id: lstring.c,v 2.49 2015/06/01 16:34:37 roberto Exp $ +** $Id: lstring.c,v 2.56.1.1 2017/04/19 17:20:42 roberto Exp $ ** String table (keeps all strings handled by Lua) ** See Copyright Notice in lua.h */ @@ -48,14 +48,23 @@ int luaS_eqlngstr (TString *a, TString *b) { unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) { unsigned int h = seed ^ cast(unsigned int, l); - size_t l1; size_t step = (l >> LUAI_HASHLIMIT) + 1; - for (l1 = l; l1 >= step; l1 -= step) - h = h ^ ((h<<5) + (h>>2) + cast_byte(str[l1 - 1])); + for (; l >= step; l -= step) + h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1])); return h; } +unsigned int luaS_hashlongstr (TString *ts) { + lua_assert(ts->tt == LUA_TLNGSTR); + if (ts->extra == 0) { /* no hash? */ + ts->hash = luaS_hash(getstr(ts), ts->u.lnglen, ts->hash); + ts->extra = 1; /* now it has its hash */ + } + return ts->hash; +} + + /* ** resizes the string table */ @@ -92,11 +101,12 @@ void luaS_resize (lua_State *L, int newsize) { ** a non-collectable string.) */ void luaS_clearcache (global_State *g) { - int i; - for (i = 0; i < STRCACHE_SIZE; i++) { - if (iswhite(g->strcache[i][0])) /* will entry be collected? */ - g->strcache[i][0] = g->memerrmsg; /* replace it with something fixed */ - } + int i, j; + for (i = 0; i < STRCACHE_N; i++) + for (j = 0; j < STRCACHE_M; j++) { + if (iswhite(g->strcache[i][j])) /* will entry be collected? */ + g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */ + } } @@ -105,13 +115,14 @@ void luaS_clearcache (global_State *g) { */ void luaS_init (lua_State *L) { global_State *g = G(L); - int i; + int i, j; luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ /* pre-create memory-error message */ g->memerrmsg = luaS_newliteral(L, MEMERRMSG); luaC_fix(L, obj2gco(g->memerrmsg)); /* it should never be collected */ - for (i = 0; i < STRCACHE_SIZE; i++) /* fill cache with valid strings */ - g->strcache[i][0] = g->memerrmsg; + for (i = 0; i < STRCACHE_N; i++) /* fill cache with valid strings */ + for (j = 0; j < STRCACHE_M; j++) + g->strcache[i][j] = g->memerrmsg; } @@ -119,8 +130,7 @@ void luaS_init (lua_State *L) { /* ** creates a new string object */ -static TString *createstrobj (lua_State *L, const char *str, size_t l, - int tag, unsigned int h) { +static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { TString *ts; GCObject *o; size_t totalsize; /* total size of TString object */ @@ -129,8 +139,14 @@ static TString *createstrobj (lua_State *L, const char *str, size_t l, ts = gco2ts(o); ts->hash = h; ts->extra = 0; - memcpy(getaddrstr(ts), str, l * sizeof(char)); - getaddrstr(ts)[l] = '\0'; /* ending 0 */ + getstr(ts)[l] = '\0'; /* ending 0 */ + return ts; +} + + +TString *luaS_createlngstrobj (lua_State *L, size_t l) { + TString *ts = createstrobj(L, l, LUA_TLNGSTR, G(L)->seed); + ts->u.lnglen = l; return ts; } @@ -153,6 +169,7 @@ static TString *internshrstr (lua_State *L, const char *str, size_t l) { global_State *g = G(L); unsigned int h = luaS_hash(str, l, g->seed); TString **list = &g->strt.hash[lmod(h, g->strt.size)]; + lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ for (ts = *list; ts != NULL; ts = ts->u.hnext) { if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { @@ -166,7 +183,8 @@ static TString *internshrstr (lua_State *L, const char *str, size_t l) { luaS_resize(L, g->strt.size * 2); list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */ } - ts = createstrobj(L, str, l, LUA_TSHRSTR, h); + ts = createstrobj(L, l, LUA_TSHRSTR, h); + memcpy(getstr(ts), str, l * sizeof(char)); ts->shrlen = cast_byte(l); ts->u.hnext = *list; *list = ts; @@ -183,10 +201,10 @@ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { return internshrstr(L, str, l); else { TString *ts; - if (l + 1 > (MAX_SIZE - sizeof(TString))/sizeof(char)) + if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char)) luaM_toobig(L); - ts = createstrobj(L, str, l, LUA_TLNGSTR, G(L)->seed); - ts->u.lnglen = l; + ts = luaS_createlngstrobj(L, l); + memcpy(getstr(ts), str, l * sizeof(char)); return ts; } } @@ -199,15 +217,19 @@ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { ** check hits. */ TString *luaS_new (lua_State *L, const char *str) { - unsigned int i = point2uint(str) % STRCACHE_SIZE; /* hash */ + unsigned int i = point2uint(str) % STRCACHE_N; /* hash */ + int j; TString **p = G(L)->strcache[i]; - if (strcmp(str, getstr(p[0])) == 0) /* hit? */ - return p[0]; /* that it is */ - else { /* normal route */ - TString *s = luaS_newlstr(L, str, strlen(str)); - p[0] = s; - return s; + for (j = 0; j < STRCACHE_M; j++) { + if (strcmp(str, getstr(p[j])) == 0) /* hit? */ + return p[j]; /* that is it */ } + /* normal route */ + for (j = STRCACHE_M - 1; j > 0; j--) + p[j] = p[j - 1]; /* move out last element */ + /* new element is first in the list */ + p[0] = luaS_newlstr(L, str, strlen(str)); + return p[0]; } diff --git a/source/extern/lua/lstring.h b/source/extern/lua/lstring.h index e746f5f..d612abd 100644 --- a/source/extern/lua/lstring.h +++ b/source/extern/lua/lstring.h @@ -1,5 +1,5 @@ /* -** $Id: lstring.h,v 1.59 2015/03/25 13:42:19 roberto Exp $ +** $Id: lstring.h,v 1.61.1.1 2017/04/19 17:20:42 roberto Exp $ ** String table (keep all strings handled by Lua) ** See Copyright Notice in lua.h */ @@ -34,6 +34,7 @@ LUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed); +LUAI_FUNC unsigned int luaS_hashlongstr (TString *ts); LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); LUAI_FUNC void luaS_resize (lua_State *L, int newsize); LUAI_FUNC void luaS_clearcache (global_State *g); @@ -42,6 +43,7 @@ LUAI_FUNC void luaS_remove (lua_State *L, TString *ts); LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s); LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); LUAI_FUNC TString *luaS_new (lua_State *L, const char *str); +LUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l); #endif diff --git a/source/extern/lua/lstrlib.c b/source/extern/lua/lstrlib.c index 19c350d..b4bed7e 100644 --- a/source/extern/lua/lstrlib.c +++ b/source/extern/lua/lstrlib.c @@ -1,5 +1,5 @@ /* -** $Id: lstrlib.c,v 1.229 2015/05/20 17:39:23 roberto Exp $ +** $Id: lstrlib.c,v 1.254.1.1 2017/04/19 17:29:57 roberto Exp $ ** Standard library for string operations and pattern-matching ** See Copyright Notice in lua.h */ @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,8 @@ /* ** maximum number of captures that a pattern can do during -** pattern-matching. This limit is arbitrary. +** pattern-matching. This limit is arbitrary, but must fit in +** an unsigned char. */ #if !defined(LUA_MAXCAPTURES) #define LUA_MAXCAPTURES 32 @@ -41,8 +43,10 @@ ** Some sizes are better limited to fit in 'int', but must also fit in ** 'size_t'. (We assume that 'lua_Integer' cannot be smaller than 'int'.) */ +#define MAX_SIZET ((size_t)(~(size_t)0)) + #define MAXSIZE \ - (sizeof(size_t) < sizeof(int) ? (~(size_t)0) : (size_t)(INT_MAX)) + (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) @@ -208,12 +212,12 @@ static int str_dump (lua_State *L) { typedef struct MatchState { - int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ const char *src_init; /* init of source string */ const char *src_end; /* end ('\0') of source string */ const char *p_end; /* end ('\0') of pattern */ lua_State *L; - int level; /* total number of captures (finished or unfinished) */ + int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ + unsigned char level; /* total number of captures (finished or unfinished) */ struct { const char *init; ptrdiff_t len; @@ -584,6 +588,22 @@ static int nospecials (const char *p, size_t l) { } +static void prepstate (MatchState *ms, lua_State *L, + const char *s, size_t ls, const char *p, size_t lp) { + ms->L = L; + ms->matchdepth = MAXCCALLS; + ms->src_init = s; + ms->src_end = s + ls; + ms->p_end = p + lp; +} + + +static void reprepstate (MatchState *ms) { + ms->level = 0; + lua_assert(ms->matchdepth == MAXCCALLS); +} + + static int str_find_aux (lua_State *L, int find) { size_t ls, lp; const char *s = luaL_checklstring(L, 1, &ls); @@ -611,15 +631,10 @@ static int str_find_aux (lua_State *L, int find) { if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s + ls; - ms.p_end = p + lp; + prepstate(&ms, L, s, ls, p, lp); do { const char *res; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); + reprepstate(&ms); if ((res=match(&ms, s1, p)) != NULL) { if (find) { lua_pushinteger(L, (s1 - s) + 1); /* start */ @@ -646,29 +661,25 @@ static int str_match (lua_State *L) { } +/* state for 'gmatch' */ +typedef struct GMatchState { + const char *src; /* current position */ + const char *p; /* pattern */ + const char *lastmatch; /* end of last match */ + MatchState ms; /* match state */ +} GMatchState; + + static int gmatch_aux (lua_State *L) { - MatchState ms; - size_t ls, lp; - const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); - const char *p = lua_tolstring(L, lua_upvalueindex(2), &lp); + GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3)); const char *src; - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s+ls; - ms.p_end = p + lp; - for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); - src <= ms.src_end; - src++) { + gm->ms.L = L; + for (src = gm->src; src <= gm->ms.src_end; src++) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - if ((e = match(&ms, src, p)) != NULL) { - lua_Integer newstart = e-s; - if (e == src) newstart++; /* empty match? go at least one position */ - lua_pushinteger(L, newstart); - lua_replace(L, lua_upvalueindex(3)); - return push_captures(&ms, src, e); + reprepstate(&gm->ms); + if ((e = match(&gm->ms, src, gm->p)) != NULL && e != gm->lastmatch) { + gm->src = gm->lastmatch = e; + return push_captures(&gm->ms, src, e); } } return 0; /* not found */ @@ -676,10 +687,14 @@ static int gmatch_aux (lua_State *L) { static int gmatch (lua_State *L) { - luaL_checkstring(L, 1); - luaL_checkstring(L, 2); - lua_settop(L, 2); - lua_pushinteger(L, 0); + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + GMatchState *gm; + lua_settop(L, 2); /* keep them on closure to avoid being collected */ + gm = (GMatchState *)lua_newuserdata(L, sizeof(GMatchState)); + prepstate(&gm->ms, L, s, ls, p, lp); + gm->src = s; gm->p = p; gm->lastmatch = NULL; lua_pushcclosure(L, gmatch_aux, 3); return 1; } @@ -746,12 +761,13 @@ static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, static int str_gsub (lua_State *L) { size_t srcl, lp; - const char *src = luaL_checklstring(L, 1, &srcl); - const char *p = luaL_checklstring(L, 2, &lp); - int tr = lua_type(L, 3); - lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); + const char *src = luaL_checklstring(L, 1, &srcl); /* subject */ + const char *p = luaL_checklstring(L, 2, &lp); /* pattern */ + const char *lastmatch = NULL; /* end of last match */ + int tr = lua_type(L, 3); /* replacement type */ + lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); /* max replacements */ int anchor = (*p == '^'); - lua_Integer n = 0; + lua_Integer n = 0; /* replacement count */ MatchState ms; luaL_Buffer b; luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || @@ -761,25 +777,18 @@ static int str_gsub (lua_State *L) { if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = src; - ms.src_end = src+srcl; - ms.p_end = p + lp; + prepstate(&ms, L, src, srcl, p, lp); while (n < max_s) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - e = match(&ms, src, p); - if (e) { + reprepstate(&ms); /* (re)prepare state for new match */ + if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */ n++; - add_value(&ms, &b, src, e, tr); + add_value(&ms, &b, src, e, tr); /* add replacement to buffer */ + src = lastmatch = e; } - if (e && e>src) /* non empty match? */ - src = e; /* skip it */ - else if (src < ms.src_end) + else if (src < ms.src_end) /* otherwise, skip one character */ luaL_addchar(&b, *src++); - else break; + else break; /* end of subject */ if (anchor) break; } luaL_addlstring(&b, src, ms.src_end-src); @@ -804,7 +813,6 @@ static int str_gsub (lua_State *L) { ** Hexadecimal floating-point formatter */ -#include #include #define SIZELENMOD (sizeof(LUA_NUMBER_FRMLEN)/sizeof(char)) @@ -830,13 +838,13 @@ static lua_Number adddigit (char *buff, int n, lua_Number x) { } -static int num2straux (char *buff, lua_Number x) { - if (x != x || x == HUGE_VAL || x == -HUGE_VAL) /* inf or NaN? */ - return sprintf(buff, LUA_NUMBER_FMT, x); /* equal to '%g' */ +static int num2straux (char *buff, int sz, lua_Number x) { + /* if 'inf' or 'NaN', format it like '%g' */ + if (x != x || x == (lua_Number)HUGE_VAL || x == -(lua_Number)HUGE_VAL) + return l_sprintf(buff, sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)x); else if (x == 0) { /* can be -0... */ - sprintf(buff, LUA_NUMBER_FMT, x); - strcat(buff, "x0p+0"); /* reuses '0/-0' from 'sprintf'... */ - return strlen(buff); + /* create "0" or "-0" followed by exponent */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT "x0p+0", (LUAI_UACNUMBER)x); } else { int e; @@ -855,22 +863,23 @@ static int num2straux (char *buff, lua_Number x) { m = adddigit(buff, n++, m * 16); } while (m > 0); } - n += sprintf(buff + n, "p%+d", e); /* add exponent */ + n += l_sprintf(buff + n, sz - n, "p%+d", e); /* add exponent */ + lua_assert(n < sz); return n; } } -static int lua_number2strx (lua_State *L, char *buff, const char *fmt, - lua_Number x) { - int n = num2straux(buff, x); +static int lua_number2strx (lua_State *L, char *buff, int sz, + const char *fmt, lua_Number x) { + int n = num2straux(buff, sz, x); if (fmt[SIZELENMOD] == 'A') { int i; for (i = 0; i < n; i++) buff[i] = toupper(uchar(buff[i])); } else if (fmt[SIZELENMOD] != 'a') - luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); + return luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); return n; } @@ -879,10 +888,12 @@ static int lua_number2strx (lua_State *L, char *buff, const char *fmt, /* ** Maximum size of each formatted item. This maximum size is produced -** by format('%.99f', minfloat), and is equal to 99 + 2 ('-' and '.') + -** number of decimal digits to represent minfloat. +** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.', +** and '\0') + number of decimal digits to represent maxfloat (which +** is maximum exponent + 1). (99+3+1 then rounded to 120 for "extra +** expenses", such as locale-dependent stuff) */ -#define MAX_ITEM (120 + l_mathlim(MAX_10_EXP)) +#define MAX_ITEM (120 + l_mathlim(MAX_10_EXP)) /* valid flags in a format specification */ @@ -894,21 +905,19 @@ static int lua_number2strx (lua_State *L, char *buff, const char *fmt, #define MAX_FORMAT 32 -static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { - size_t l; - const char *s = luaL_checklstring(L, arg, &l); +static void addquoted (luaL_Buffer *b, const char *s, size_t len) { luaL_addchar(b, '"'); - while (l--) { + while (len--) { if (*s == '"' || *s == '\\' || *s == '\n') { luaL_addchar(b, '\\'); luaL_addchar(b, *s); } - else if (*s == '\0' || iscntrl(uchar(*s))) { + else if (iscntrl(uchar(*s))) { char buff[10]; if (!isdigit(uchar(*(s+1)))) - sprintf(buff, "\\%d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%d", (int)uchar(*s)); else - sprintf(buff, "\\%03d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%03d", (int)uchar(*s)); luaL_addstring(b, buff); } else @@ -918,6 +927,57 @@ static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { luaL_addchar(b, '"'); } + +/* +** Ensures the 'buff' string uses a dot as the radix character. +*/ +static void checkdp (char *buff, int nb) { + if (memchr(buff, '.', nb) == NULL) { /* no dot? */ + char point = lua_getlocaledecpoint(); /* try locale point */ + char *ppoint = (char *)memchr(buff, point, nb); + if (ppoint) *ppoint = '.'; /* change it to a dot */ + } +} + + +static void addliteral (lua_State *L, luaL_Buffer *b, int arg) { + switch (lua_type(L, arg)) { + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(L, arg, &len); + addquoted(b, s, len); + break; + } + case LUA_TNUMBER: { + char *buff = luaL_prepbuffsize(b, MAX_ITEM); + int nb; + if (!lua_isinteger(L, arg)) { /* float? */ + lua_Number n = lua_tonumber(L, arg); /* write as hexa ('%a') */ + nb = lua_number2strx(L, buff, MAX_ITEM, "%" LUA_NUMBER_FRMLEN "a", n); + checkdp(buff, nb); /* ensure it uses a dot */ + } + else { /* integers */ + lua_Integer n = lua_tointeger(L, arg); + const char *format = (n == LUA_MININTEGER) /* corner case? */ + ? "0x%" LUA_INTEGER_FRMLEN "x" /* use hexa */ + : LUA_INTEGER_FMT; /* else use default format */ + nb = l_sprintf(buff, MAX_ITEM, format, (LUAI_UACINT)n); + } + luaL_addsize(b, nb); + break; + } + case LUA_TNIL: case LUA_TBOOLEAN: { + luaL_tolstring(L, arg, NULL); + luaL_addvalue(b); + break; + } + default: { + luaL_argerror(L, arg, "value has no literal form"); + } + } +} + + static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ @@ -975,41 +1035,47 @@ static int str_format (lua_State *L) { strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { - nb = sprintf(buff, form, (int)luaL_checkinteger(L, arg)); + nb = l_sprintf(buff, MAX_ITEM, form, (int)luaL_checkinteger(L, arg)); break; } case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { lua_Integer n = luaL_checkinteger(L, arg); addlenmod(form, LUA_INTEGER_FRMLEN); - nb = sprintf(buff, form, n); + nb = l_sprintf(buff, MAX_ITEM, form, (LUAI_UACINT)n); break; } case 'a': case 'A': addlenmod(form, LUA_NUMBER_FRMLEN); - nb = lua_number2strx(L, buff, form, luaL_checknumber(L, arg)); + nb = lua_number2strx(L, buff, MAX_ITEM, form, + luaL_checknumber(L, arg)); break; case 'e': case 'E': case 'f': case 'g': case 'G': { + lua_Number n = luaL_checknumber(L, arg); addlenmod(form, LUA_NUMBER_FRMLEN); - nb = sprintf(buff, form, luaL_checknumber(L, arg)); + nb = l_sprintf(buff, MAX_ITEM, form, (LUAI_UACNUMBER)n); break; } case 'q': { - addquoted(L, &b, arg); + addliteral(L, &b, arg); break; } case 's': { size_t l; const char *s = luaL_tolstring(L, arg, &l); - if (!strchr(form, '.') && l >= 100) { - /* no precision and string is too long to be formatted; - keep original string */ - luaL_addvalue(&b); - } + if (form[2] == '\0') /* no modifiers? */ + luaL_addvalue(&b); /* keep entire string */ else { - nb = sprintf(buff, form, s); - lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + luaL_argcheck(L, l == strlen(s), arg, "string contains zeros"); + if (!strchr(form, '.') && l >= 100) { + /* no precision and string is too long to be formatted */ + luaL_addvalue(&b); /* keep entire string */ + } + else { /* format the string into 'buff' */ + nb = l_sprintf(buff, MAX_ITEM, form, s); + lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + } } break; } @@ -1018,6 +1084,7 @@ static int str_format (lua_State *L) { *(strfrmt - 1)); } } + lua_assert(nb < MAX_ITEM); luaL_addsize(&b, nb); } } @@ -1036,8 +1103,8 @@ static int str_format (lua_State *L) { /* value used for padding */ -#if !defined(LUA_PACKPADBYTE) -#define LUA_PACKPADBYTE 0x00 +#if !defined(LUAL_PACKPADBYTE) +#define LUAL_PACKPADBYTE 0x00 #endif /* maximum size for the binary representation of an integer */ @@ -1132,8 +1199,8 @@ static int getnum (const char **fmt, int df) { static int getnumlimit (Header *h, const char **fmt, int df) { int sz = getnum(fmt, df); if (sz > MAXINTSIZE || sz <= 0) - luaL_error(h->L, "integral size (%d) out of limits [1,%d]", - sz, MAXINTSIZE); + return luaL_error(h->L, "integral size (%d) out of limits [1,%d]", + sz, MAXINTSIZE); return sz; } @@ -1194,7 +1261,7 @@ static KOption getoption (Header *h, const char **fmt, int *size) { ** 'psize' is filled with option's size, 'notoalign' with its ** alignment requirements. ** Local variable 'size' gets the size to be aligned. (Kpadal option -** always gets its full alignment, other options are limited by +** always gets its full alignment, other options are limited by ** the maximum alignment ('maxalign'). Kchar option needs no alignment ** despite its size. */ @@ -1274,7 +1341,7 @@ static int str_pack (lua_State *L) { KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); totalsize += ntoalign + size; while (ntoalign-- > 0) - luaL_addchar(&b, LUA_PACKPADBYTE); /* fill alignment */ + luaL_addchar(&b, LUAL_PACKPADBYTE); /* fill alignment */ arg++; switch (opt) { case Kint: { /* signed integers */ @@ -1309,8 +1376,11 @@ static int str_pack (lua_State *L) { case Kchar: { /* fixed-size string */ size_t len; const char *s = luaL_checklstring(L, arg, &len); - luaL_argcheck(L, len == (size_t)size, arg, "wrong length"); - luaL_addlstring(&b, s, size); + luaL_argcheck(L, len <= (size_t)size, arg, + "string longer than given size"); + luaL_addlstring(&b, s, len); /* add string */ + while (len++ < (size_t)size) /* pad extra space */ + luaL_addchar(&b, LUAL_PACKPADBYTE); break; } case Kstring: { /* strings with length count */ @@ -1333,7 +1403,7 @@ static int str_pack (lua_State *L) { totalsize += len + 1; break; } - case Kpadding: luaL_addchar(&b, LUA_PACKPADBYTE); /* FALLTHROUGH */ + case Kpadding: luaL_addchar(&b, LUAL_PACKPADBYTE); /* FALLTHROUGH */ case Kpaddalign: case Knop: arg--; /* undo increment */ break; @@ -1360,7 +1430,7 @@ static int str_packsize (lua_State *L) { case Kstring: /* strings with length count */ case Kzstr: /* zero-terminated string */ luaL_argerror(L, 1, "variable-length format"); - break; + /* call never return, but to avoid warnings: *//* FALLTHROUGH */ default: break; } } diff --git a/source/extern/lua/ltable.c b/source/extern/lua/ltable.c index 04f2a34..ea4fe7f 100644 --- a/source/extern/lua/ltable.c +++ b/source/extern/lua/ltable.c @@ -1,5 +1,5 @@ /* -** $Id: ltable.c,v 2.111 2015/06/09 14:21:13 roberto Exp $ +** $Id: ltable.c,v 2.118.1.4 2018/06/08 16:22:51 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ @@ -74,8 +74,6 @@ #define dummynode (&dummynode_) -#define isdummy(n) ((n) == dummynode) - static const Node dummynode_ = { {NILCONSTANT}, /* value */ {{NILCONSTANT, 0}} /* key */ @@ -85,7 +83,7 @@ static const Node dummynode_ = { /* ** Hash for floating-point numbers. ** The main computation should be just -** n = frepx(n, &i); return (n * INT_MAX) + i +** n = frexp(n, &i); return (n * INT_MAX) + i ** but there are some numerical subtleties. ** In a two-complement representation, INT_MAX does not has an exact ** representation as a float, but INT_MIN does; because the absolute @@ -101,7 +99,7 @@ static int l_hashfloat (lua_Number n) { lua_Integer ni; n = l_mathop(frexp)(n, &i) * -cast_num(INT_MIN); if (!lua_numbertointeger(n, &ni)) { /* is 'n' inf/-inf/NaN? */ - lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == HUGE_VAL); + lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == cast_num(HUGE_VAL)); return 0; } else { /* normal case */ @@ -124,14 +122,8 @@ static Node *mainposition (const Table *t, const TValue *key) { return hashmod(t, l_hashfloat(fltvalue(key))); case LUA_TSHRSTR: return hashstr(t, tsvalue(key)); - case LUA_TLNGSTR: { - TString *s = tsvalue(key); - if (s->extra == 0) { /* no hash? */ - s->hash = luaS_hash(getstr(s), s->u.lnglen, s->hash); - s->extra = 1; /* now it has its hash */ - } - return hashstr(t, tsvalue(key)); - } + case LUA_TLNGSTR: + return hashpow2(t, luaS_hashlongstr(tsvalue(key))); case LUA_TBOOLEAN: return hashboolean(t, bvalue(key)); case LUA_TLIGHTUSERDATA: @@ -139,6 +131,7 @@ static Node *mainposition (const Table *t, const TValue *key) { case LUA_TLCF: return hashpointer(t, fvalue(key)); default: + lua_assert(!ttisdeadkey(key)); return hashpointer(t, gcvalue(key)); } } @@ -230,7 +223,9 @@ static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { unsigned int na = 0; /* number of elements to go to array part */ unsigned int optimal = 0; /* optimal size for array part */ /* loop while keys can fill more than half of total size */ - for (i = 0, twotoi = 1; *pna > twotoi / 2; i++, twotoi *= 2) { + for (i = 0, twotoi = 1; + twotoi > 0 && *pna > twotoi / 2; + i++, twotoi *= 2) { if (nums[i] > 0) { a += nums[i]; if (a > twotoi/2) { /* more than half elements present? */ @@ -313,14 +308,14 @@ static void setarrayvector (lua_State *L, Table *t, unsigned int size) { static void setnodevector (lua_State *L, Table *t, unsigned int size) { - int lsize; if (size == 0) { /* no elements to hash part? */ t->node = cast(Node *, dummynode); /* use common 'dummynode' */ - lsize = 0; + t->lsizenode = 0; + t->lastfree = NULL; /* signal that it is using dummy node */ } else { int i; - lsize = luaO_ceillog2(size); + int lsize = luaO_ceillog2(size); if (lsize > MAXHBITS) luaG_runerror(L, "table overflow"); size = twoto(lsize); @@ -331,9 +326,21 @@ static void setnodevector (lua_State *L, Table *t, unsigned int size) { setnilvalue(wgkey(n)); setnilvalue(gval(n)); } + t->lsizenode = cast_byte(lsize); + t->lastfree = gnode(t, size); /* all positions are free */ } - t->lsizenode = cast_byte(lsize); - t->lastfree = gnode(t, size); /* all positions are free */ +} + + +typedef struct { + Table *t; + unsigned int nhsize; +} AuxsetnodeT; + + +static void auxsetnode (lua_State *L, void *ud) { + AuxsetnodeT *asn = cast(AuxsetnodeT *, ud); + setnodevector(L, asn->t, asn->nhsize); } @@ -341,13 +348,18 @@ void luaH_resize (lua_State *L, Table *t, unsigned int nasize, unsigned int nhsize) { unsigned int i; int j; + AuxsetnodeT asn; unsigned int oldasize = t->sizearray; - int oldhsize = t->lsizenode; + int oldhsize = allocsizenode(t); Node *nold = t->node; /* save old hash ... */ if (nasize > oldasize) /* array part must grow? */ setarrayvector(L, t, nasize); /* create new hash part with appropriate size */ - setnodevector(L, t, nhsize); + asn.t = t; asn.nhsize = nhsize; + if (luaD_rawrunprotected(L, auxsetnode, &asn) != LUA_OK) { /* mem. error? */ + setarrayvector(L, t, oldasize); /* array back to its original size */ + luaD_throw(L, LUA_ERRMEM); /* rethrow memory error */ + } if (nasize < oldasize) { /* array part must shrink? */ t->sizearray = nasize; /* re-insert elements from vanishing slice */ @@ -359,7 +371,7 @@ void luaH_resize (lua_State *L, Table *t, unsigned int nasize, luaM_reallocvector(L, t->array, oldasize, nasize, TValue); } /* re-insert elements from hash part */ - for (j = twoto(oldhsize) - 1; j >= 0; j--) { + for (j = oldhsize - 1; j >= 0; j--) { Node *old = nold + j; if (!ttisnil(gval(old))) { /* doesn't need barrier/invalidate cache, as entry was @@ -367,13 +379,13 @@ void luaH_resize (lua_State *L, Table *t, unsigned int nasize, setobjt2t(L, luaH_set(L, t, gkey(old)), gval(old)); } } - if (!isdummy(nold)) - luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old hash */ + if (oldhsize > 0) /* not the dummy node? */ + luaM_freearray(L, nold, cast(size_t, oldhsize)); /* free old hash */ } void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize) { - int nsize = isdummy(t->node) ? 0 : sizenode(t); + int nsize = allocsizenode(t); luaH_resize(L, t, nasize, nsize); } @@ -419,7 +431,7 @@ Table *luaH_new (lua_State *L) { void luaH_free (lua_State *L, Table *t) { - if (!isdummy(t->node)) + if (!isdummy(t)) luaM_freearray(L, t->node, cast(size_t, sizenode(t))); luaM_freearray(L, t->array, t->sizearray); luaM_free(L, t); @@ -427,10 +439,12 @@ void luaH_free (lua_State *L, Table *t) { static Node *getfreepos (Table *t) { - while (t->lastfree > t->node) { - t->lastfree--; - if (ttisnil(gkey(t->lastfree))) - return t->lastfree; + if (!isdummy(t)) { + while (t->lastfree > t->node) { + t->lastfree--; + if (ttisnil(gkey(t->lastfree))) + return t->lastfree; + } } return NULL; /* could not find a free place */ } @@ -450,7 +464,7 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { if (ttisnil(key)) luaG_runerror(L, "table index is nil"); else if (ttisfloat(key)) { lua_Integer k; - if (luaV_tointeger(key, &k, 0)) { /* index is int? */ + if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */ setivalue(&aux, k); key = &aux; /* insert it as an integer */ } @@ -458,15 +472,15 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { luaG_runerror(L, "table index is NaN"); } mp = mainposition(t, key); - if (!ttisnil(gval(mp)) || isdummy(mp)) { /* main position is taken? */ + if (!ttisnil(gval(mp)) || isdummy(t)) { /* main position is taken? */ Node *othern; Node *f = getfreepos(t); /* get a free place */ if (f == NULL) { /* cannot find a free place? */ rehash(L, t, key); /* grow table */ - /* whatever called 'newkey' takes care of TM cache and GC barrier */ + /* whatever called 'newkey' takes care of TM cache */ return luaH_set(L, t, key); /* insert key into grown table */ } - lua_assert(!isdummy(f)); + lua_assert(!isdummy(t)); othern = mainposition(t, gkey(mp)); if (othern != mp) { /* is colliding node out of its main position? */ /* yes; move colliding node into free position */ @@ -501,7 +515,7 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { */ const TValue *luaH_getint (Table *t, lua_Integer key) { /* (1 <= key && key <= t->sizearray) */ - if (l_castS2U(key - 1) < t->sizearray) + if (l_castS2U(key) - 1 < t->sizearray) return &t->array[key - 1]; else { Node *n = hashint(t, key); @@ -513,7 +527,7 @@ const TValue *luaH_getint (Table *t, lua_Integer key) { if (nx == 0) break; n += nx; } - }; + } return luaO_nilobject; } } @@ -522,7 +536,7 @@ const TValue *luaH_getint (Table *t, lua_Integer key) { /* ** search function for short strings */ -const TValue *luaH_getstr (Table *t, TString *key) { +const TValue *luaH_getshortstr (Table *t, TString *key) { Node *n = hashstr(t, key); lua_assert(key->tt == LUA_TSHRSTR); for (;;) { /* check whether 'key' is somewhere in the chain */ @@ -531,11 +545,41 @@ const TValue *luaH_getstr (Table *t, TString *key) { return gval(n); /* that's it */ else { int nx = gnext(n); - if (nx == 0) break; + if (nx == 0) + return luaO_nilobject; /* not found */ n += nx; } - }; - return luaO_nilobject; + } +} + + +/* +** "Generic" get version. (Not that generic: not valid for integers, +** which may be in array part, nor for floats with integral values.) +*/ +static const TValue *getgeneric (Table *t, const TValue *key) { + Node *n = mainposition(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (luaV_rawequalobj(gkey(n), key)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return luaO_nilobject; /* not found */ + n += nx; + } + } +} + + +const TValue *luaH_getstr (Table *t, TString *key) { + if (key->tt == LUA_TSHRSTR) + return luaH_getshortstr(t, key); + else { /* for long strings, use generic case */ + TValue ko; + setsvalue(cast(lua_State *, NULL), &ko, key); + return getgeneric(t, &ko); + } } @@ -544,7 +588,7 @@ const TValue *luaH_getstr (Table *t, TString *key) { */ const TValue *luaH_get (Table *t, const TValue *key) { switch (ttype(key)) { - case LUA_TSHRSTR: return luaH_getstr(t, tsvalue(key)); + case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key)); case LUA_TNUMINT: return luaH_getint(t, ivalue(key)); case LUA_TNIL: return luaO_nilobject; case LUA_TNUMFLT: { @@ -553,19 +597,8 @@ const TValue *luaH_get (Table *t, const TValue *key) { return luaH_getint(t, k); /* use specialized version */ /* else... */ } /* FALLTHROUGH */ - default: { - Node *n = mainposition(t, key); - for (;;) { /* check whether 'key' is somewhere in the chain */ - if (luaV_rawequalobj(gkey(n), key)) - return gval(n); /* that's it */ - else { - int nx = gnext(n); - if (nx == 0) break; - n += nx; - } - }; - return luaO_nilobject; - } + default: + return getgeneric(t, key); } } @@ -596,13 +629,13 @@ void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) { } -static int unbound_search (Table *t, unsigned int j) { - unsigned int i = j; /* i is zero or a present index */ +static lua_Unsigned unbound_search (Table *t, lua_Unsigned j) { + lua_Unsigned i = j; /* i is zero or a present index */ j++; /* find 'i' and 'j' such that i is present and j is not */ while (!ttisnil(luaH_getint(t, j))) { i = j; - if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */ + if (j > l_castS2U(LUA_MAXINTEGER) / 2) { /* overflow? */ /* table was built with bad purposes: resort to linear search */ i = 1; while (!ttisnil(luaH_getint(t, i))) i++; @@ -612,7 +645,7 @@ static int unbound_search (Table *t, unsigned int j) { } /* now do a binary search between them */ while (j - i > 1) { - unsigned int m = (i+j)/2; + lua_Unsigned m = (i+j)/2; if (ttisnil(luaH_getint(t, m))) j = m; else i = m; } @@ -624,7 +657,7 @@ static int unbound_search (Table *t, unsigned int j) { ** Try to find a boundary in table 't'. A 'boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ -int luaH_getn (Table *t) { +lua_Unsigned luaH_getn (Table *t) { unsigned int j = t->sizearray; if (j > 0 && ttisnil(&t->array[j - 1])) { /* there is a boundary in the array part: (binary) search for it */ @@ -637,7 +670,7 @@ int luaH_getn (Table *t) { return i; } /* else must find a boundary in hash part */ - else if (isdummy(t->node)) /* hash part is empty? */ + else if (isdummy(t)) /* hash part is empty? */ return j; /* that is easy... */ else return unbound_search(t, j); } @@ -650,6 +683,6 @@ Node *luaH_mainposition (const Table *t, const TValue *key) { return mainposition(t, key); } -int luaH_isdummy (Node *n) { return isdummy(n); } +int luaH_isdummy (const Table *t) { return isdummy(t); } #endif diff --git a/source/extern/lua/ltable.h b/source/extern/lua/ltable.h index 53d2551..92db0ac 100644 --- a/source/extern/lua/ltable.h +++ b/source/extern/lua/ltable.h @@ -1,5 +1,5 @@ /* -** $Id: ltable.h,v 2.20 2014/09/04 18:15:29 roberto Exp $ +** $Id: ltable.h,v 2.23.1.2 2018/05/24 19:39:05 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ @@ -15,14 +15,26 @@ #define gnext(n) ((n)->i_key.nk.next) -/* 'const' to avoid wrong writings that can mess up field 'next' */ +/* 'const' to avoid wrong writings that can mess up field 'next' */ #define gkey(n) cast(const TValue*, (&(n)->i_key.tvk)) +/* +** writable version of 'gkey'; allows updates to individual fields, +** but not to the whole (which has incompatible type) +*/ #define wgkey(n) (&(n)->i_key.nk) #define invalidateTMcache(t) ((t)->flags = 0) +/* true when 't' is using 'dummynode' as its hash part */ +#define isdummy(t) ((t)->lastfree == NULL) + + +/* allocated size for hash nodes */ +#define allocsizenode(t) (isdummy(t) ? 0 : sizenode(t)) + + /* returns the key, given the value of a table entry */ #define keyfromval(v) \ (gkey(cast(Node *, cast(char *, (v)) - offsetof(Node, i_val)))) @@ -31,6 +43,7 @@ LUAI_FUNC const TValue *luaH_getint (Table *t, lua_Integer key); LUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value); +LUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); LUAI_FUNC TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key); @@ -41,12 +54,12 @@ LUAI_FUNC void luaH_resize (lua_State *L, Table *t, unsigned int nasize, LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize); LUAI_FUNC void luaH_free (lua_State *L, Table *t); LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); -LUAI_FUNC int luaH_getn (Table *t); +LUAI_FUNC lua_Unsigned luaH_getn (Table *t); #if defined(LUA_DEBUG) LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); -LUAI_FUNC int luaH_isdummy (Node *n); +LUAI_FUNC int luaH_isdummy (const Table *t); #endif diff --git a/source/extern/lua/ltablib.c b/source/extern/lua/ltablib.c index a05c885..c534957 100644 --- a/source/extern/lua/ltablib.c +++ b/source/extern/lua/ltablib.c @@ -1,5 +1,5 @@ /* -** $Id: ltablib.c,v 1.80 2015/01/13 16:27:29 roberto Exp $ +** $Id: ltablib.c,v 1.93.1.1 2017/04/19 17:20:42 roberto Exp $ ** Library for Table Manipulation ** See Copyright Notice in lua.h */ @@ -12,6 +12,7 @@ #include #include +#include #include "lua.h" @@ -19,40 +20,42 @@ #include "lualib.h" - /* -** Structure with table-access functions +** Operations that an object must define to mimic a table +** (some functions only need some of them) */ -typedef struct { - int (*geti) (lua_State *L, int idx, lua_Integer n); - void (*seti) (lua_State *L, int idx, lua_Integer n); -} TabA; +#define TAB_R 1 /* read */ +#define TAB_W 2 /* write */ +#define TAB_L 4 /* length */ +#define TAB_RW (TAB_R | TAB_W) /* read/write */ -/* -** Check that 'arg' has a table and set access functions in 'ta' to raw -** or non-raw according to the presence of corresponding metamethods. -*/ -static void checktab (lua_State *L, int arg, TabA *ta) { - ta->geti = NULL; ta->seti = NULL; - if (lua_getmetatable(L, arg)) { - lua_pushliteral(L, "__index"); /* 'index' metamethod */ - if (lua_rawget(L, -2) != LUA_TNIL) - ta->geti = lua_geti; - lua_pushliteral(L, "__newindex"); /* 'newindex' metamethod */ - if (lua_rawget(L, -3) != LUA_TNIL) - ta->seti = lua_seti; - lua_pop(L, 3); /* pop metatable plus both metamethods */ - } - if (ta->geti == NULL || ta->seti == NULL) { - luaL_checktype(L, arg, LUA_TTABLE); /* must be table for raw methods */ - if (ta->geti == NULL) ta->geti = lua_rawgeti; - if (ta->seti == NULL) ta->seti = lua_rawseti; - } +#define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n)) + + +static int checkfield (lua_State *L, const char *key, int n) { + lua_pushstring(L, key); + return (lua_rawget(L, -n) != LUA_TNIL); } -#define aux_getn(L,n,ta) (checktab(L, n, ta), luaL_len(L, n)) +/* +** Check that 'arg' either is a table or can behave like one (that is, +** has a metatable with the required metamethods) +*/ +static void checktab (lua_State *L, int arg, int what) { + if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ + int n = 1; /* number of elements to pop */ + if (lua_getmetatable(L, arg) && /* must have metatable */ + (!(what & TAB_R) || checkfield(L, "__index", ++n)) && + (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && + (!(what & TAB_L) || checkfield(L, "__len", ++n))) { + lua_pop(L, n); /* pop metatable and tested metamethods */ + } + else + luaL_checktype(L, arg, LUA_TTABLE); /* force an error */ + } +} #if defined(LUA_COMPAT_MAXN) @@ -74,8 +77,7 @@ static int maxn (lua_State *L) { static int tinsert (lua_State *L) { - TabA ta; - lua_Integer e = aux_getn(L, 1, &ta) + 1; /* first empty element */ + lua_Integer e = aux_getn(L, 1, TAB_RW) + 1; /* first empty element */ lua_Integer pos; /* where to insert new element */ switch (lua_gettop(L)) { case 2: { /* called with only 2 arguments */ @@ -87,8 +89,8 @@ static int tinsert (lua_State *L) { pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds"); for (i = e; i > pos; i--) { /* move up elements */ - (*ta.geti)(L, 1, i - 1); - (*ta.seti)(L, 1, i); /* t[i] = t[i - 1] */ + lua_geti(L, 1, i - 1); + lua_seti(L, 1, i); /* t[i] = t[i - 1] */ } break; } @@ -96,67 +98,67 @@ static int tinsert (lua_State *L) { return luaL_error(L, "wrong number of arguments to 'insert'"); } } - (*ta.seti)(L, 1, pos); /* t[pos] = v */ + lua_seti(L, 1, pos); /* t[pos] = v */ return 0; } static int tremove (lua_State *L) { - TabA ta; - lua_Integer size = aux_getn(L, 1, &ta); + lua_Integer size = aux_getn(L, 1, TAB_RW); lua_Integer pos = luaL_optinteger(L, 2, size); if (pos != size) /* validate 'pos' if given */ luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds"); - (*ta.geti)(L, 1, pos); /* result = t[pos] */ + lua_geti(L, 1, pos); /* result = t[pos] */ for ( ; pos < size; pos++) { - (*ta.geti)(L, 1, pos + 1); - (*ta.seti)(L, 1, pos); /* t[pos] = t[pos + 1] */ + lua_geti(L, 1, pos + 1); + lua_seti(L, 1, pos); /* t[pos] = t[pos + 1] */ } lua_pushnil(L); - (*ta.seti)(L, 1, pos); /* t[pos] = nil */ + lua_seti(L, 1, pos); /* t[pos] = nil */ return 1; } +/* +** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever +** possible, copy in increasing order, which is better for rehashing. +** "possible" means destination after original range, or smaller +** than origin, or copying to another table. +*/ static int tmove (lua_State *L) { - TabA ta; lua_Integer f = luaL_checkinteger(L, 2); lua_Integer e = luaL_checkinteger(L, 3); lua_Integer t = luaL_checkinteger(L, 4); int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ + checktab(L, 1, TAB_R); + checktab(L, tt, TAB_W); if (e >= f) { /* otherwise, nothing to move */ lua_Integer n, i; - ta.geti = (luaL_getmetafield(L, 1, "__index") == LUA_TNIL) - ? (luaL_checktype(L, 1, LUA_TTABLE), lua_rawgeti) - : lua_geti; - ta.seti = (luaL_getmetafield(L, tt, "__newindex") == LUA_TNIL) - ? (luaL_checktype(L, tt, LUA_TTABLE), lua_rawseti) - : lua_seti; luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3, "too many elements to move"); n = e - f + 1; /* number of elements to move */ luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4, "destination wrap around"); - if (t > f) { - for (i = n - 1; i >= 0; i--) { - (*ta.geti)(L, 1, f + i); - (*ta.seti)(L, tt, t + i); + if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) { + for (i = 0; i < n; i++) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); } } else { - for (i = 0; i < n; i++) { - (*ta.geti)(L, 1, f + i); - (*ta.seti)(L, tt, t + i); + for (i = n - 1; i >= 0; i--) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); } } } - lua_pushvalue(L, tt); /* return "to table" */ + lua_pushvalue(L, tt); /* return destination table */ return 1; } -static void addfield (lua_State *L, luaL_Buffer *b, TabA *ta, lua_Integer i) { - (*ta->geti)(L, 1, i); +static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) { + lua_geti(L, 1, i); if (!lua_isstring(L, -1)) luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", luaL_typename(L, -1), i); @@ -165,21 +167,19 @@ static void addfield (lua_State *L, luaL_Buffer *b, TabA *ta, lua_Integer i) { static int tconcat (lua_State *L) { - TabA ta; luaL_Buffer b; + lua_Integer last = aux_getn(L, 1, TAB_R); size_t lsep; - lua_Integer i, last; const char *sep = luaL_optlstring(L, 2, "", &lsep); - checktab(L, 1, &ta); - i = luaL_optinteger(L, 3, 1); - last = luaL_opt(L, luaL_checkinteger, 4, luaL_len(L, 1)); + lua_Integer i = luaL_optinteger(L, 3, 1); + last = luaL_optinteger(L, 4, last); luaL_buffinit(L, &b); for (; i < last; i++) { - addfield(L, &b, &ta, i); + addfield(L, &b, i); luaL_addlstring(&b, sep, lsep); } if (i == last) /* add last value (if interval was not empty) */ - addfield(L, &b, &ta, i); + addfield(L, &b, i); luaL_pushresult(&b); return 1; } @@ -197,7 +197,7 @@ static int pack (lua_State *L) { lua_createtable(L, n, 1); /* create result table */ lua_insert(L, 1); /* put it at index 1 */ for (i = n; i >= 1; i--) /* assign elements */ - lua_rawseti(L, 1, i); + lua_seti(L, 1, i); lua_pushinteger(L, n); lua_setfield(L, 1, "n"); /* t.n = number of elements */ return 1; /* return table */ @@ -205,20 +205,17 @@ static int pack (lua_State *L) { static int unpack (lua_State *L) { - TabA ta; - lua_Integer i, e; lua_Unsigned n; - checktab(L, 1, &ta); - i = luaL_optinteger(L, 2, 1); - e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); + lua_Integer i = luaL_optinteger(L, 2, 1); + lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); if (i > e) return 0; /* empty range */ n = (lua_Unsigned)e - i; /* number of elements minus 1 (avoid overflows) */ if (n >= (unsigned int)INT_MAX || !lua_checkstack(L, (int)(++n))) return luaL_error(L, "too many results to unpack"); - do { /* must have at least one element */ - (*ta.geti)(L, 1, i); /* push arg[i..e] */ - } while (i++ < e); - + for (; i < e; i++) { /* push arg[i..e - 1] (to avoid overflows) */ + lua_geti(L, 1, i); + } + lua_geti(L, 1, e); /* push last element */ return (int)n; } @@ -235,97 +232,191 @@ static int unpack (lua_State *L) { */ -static void set2 (lua_State *L, TabA *ta, int i, int j) { - (*ta->seti)(L, 1, i); - (*ta->seti)(L, 1, j); +/* type for array indices */ +typedef unsigned int IdxT; + + +/* +** Produce a "random" 'unsigned int' to randomize pivot choice. This +** macro is used only when 'sort' detects a big imbalance in the result +** of a partition. (If you don't want/need this "randomness", ~0 is a +** good choice.) +*/ +#if !defined(l_randomizePivot) /* { */ + +#include + +/* size of 'e' measured in number of 'unsigned int's */ +#define sof(e) (sizeof(e) / sizeof(unsigned int)) + +/* +** Use 'time' and 'clock' as sources of "randomness". Because we don't +** know the types 'clock_t' and 'time_t', we cannot cast them to +** anything without risking overflows. A safe way to use their values +** is to copy them to an array of a known type and use the array values. +*/ +static unsigned int l_randomizePivot (void) { + clock_t c = clock(); + time_t t = time(NULL); + unsigned int buff[sof(c) + sof(t)]; + unsigned int i, rnd = 0; + memcpy(buff, &c, sof(c) * sizeof(unsigned int)); + memcpy(buff + sof(c), &t, sof(t) * sizeof(unsigned int)); + for (i = 0; i < sof(buff); i++) + rnd += buff[i]; + return rnd; } +#endif /* } */ + + +/* arrays larger than 'RANLIMIT' may use randomized pivots */ +#define RANLIMIT 100u + + +static void set2 (lua_State *L, IdxT i, IdxT j) { + lua_seti(L, 1, i); + lua_seti(L, 1, j); +} + + +/* +** Return true iff value at stack index 'a' is less than the value at +** index 'b' (according to the order of the sort). +*/ static int sort_comp (lua_State *L, int a, int b) { - if (!lua_isnil(L, 2)) { /* function? */ + if (lua_isnil(L, 2)) /* no function? */ + return lua_compare(L, a, b, LUA_OPLT); /* a < b */ + else { /* function */ int res; - lua_pushvalue(L, 2); + lua_pushvalue(L, 2); /* push function */ lua_pushvalue(L, a-1); /* -1 to compensate function */ lua_pushvalue(L, b-2); /* -2 to compensate function and 'a' */ - lua_call(L, 2, 1); - res = lua_toboolean(L, -1); - lua_pop(L, 1); + lua_call(L, 2, 1); /* call function */ + res = lua_toboolean(L, -1); /* get result */ + lua_pop(L, 1); /* pop result */ return res; } - else /* a < b? */ - return lua_compare(L, a, b, LUA_OPLT); } -static void auxsort (lua_State *L, TabA *ta, int l, int u) { - while (l < u) { /* for tail recursion */ - int i, j; - /* sort elements a[l], a[(l+u)/2] and a[u] */ - (*ta->geti)(L, 1, l); - (*ta->geti)(L, 1, u); - if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */ - set2(L, ta, l, u); /* swap a[l] - a[u] */ + +/* +** Does the partition: Pivot P is at the top of the stack. +** precondition: a[lo] <= P == a[up-1] <= a[up], +** so it only needs to do the partition from lo + 1 to up - 2. +** Pos-condition: a[lo .. i - 1] <= a[i] == P <= a[i + 1 .. up] +** returns 'i'. +*/ +static IdxT partition (lua_State *L, IdxT lo, IdxT up) { + IdxT i = lo; /* will be incremented before first use */ + IdxT j = up - 1; /* will be decremented before first use */ + /* loop invariant: a[lo .. i] <= P <= a[j .. up] */ + for (;;) { + /* next loop: repeat ++i while a[i] < P */ + while (lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (i == up - 1) /* a[i] < P but a[up - 1] == P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* after the loop, a[i] >= P and a[lo .. i - 1] < P */ + /* next loop: repeat --j while P < a[j] */ + while (lua_geti(L, 1, --j), sort_comp(L, -3, -1)) { + if (j < i) /* j < i but a[j] > P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + /* after the loop, a[j] <= P and a[j + 1 .. up] >= P */ + if (j < i) { /* no elements out of place? */ + /* a[lo .. i - 1] <= P <= a[j + 1 .. i .. up] */ + lua_pop(L, 1); /* pop a[j] */ + /* swap pivot (a[up - 1]) with a[i] to satisfy pos-condition */ + set2(L, up - 1, i); + return i; + } + /* otherwise, swap a[i] - a[j] to restore invariant and repeat */ + set2(L, i, j); + } +} + + +/* +** Choose an element in the middle (2nd-3th quarters) of [lo,up] +** "randomized" by 'rnd' +*/ +static IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) { + IdxT r4 = (up - lo) / 4; /* range/4 */ + IdxT p = rnd % (r4 * 2) + (lo + r4); + lua_assert(lo + r4 <= p && p <= up - r4); + return p; +} + + +/* +** QuickSort algorithm (recursive function) +*/ +static void auxsort (lua_State *L, IdxT lo, IdxT up, + unsigned int rnd) { + while (lo < up) { /* loop for tail recursion */ + IdxT p; /* Pivot index */ + IdxT n; /* to be used later */ + /* sort elements 'lo', 'p', and 'up' */ + lua_geti(L, 1, lo); + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[lo]? */ + set2(L, lo, up); /* swap a[lo] - a[up] */ else - lua_pop(L, 2); - if (u-l == 1) break; /* only 2 elements */ - i = (l+u)/2; - (*ta->geti)(L, 1, i); - (*ta->geti)(L, 1, l); - if (sort_comp(L, -2, -1)) /* a[i]geti)(L, 1, u); - if (sort_comp(L, -1, -2)) /* a[u]geti)(L, 1, i); /* Pivot */ - lua_pushvalue(L, -1); - (*ta->geti)(L, 1, u-1); - set2(L, ta, i, u-1); - /* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */ - i = l; j = u-1; - for (;;) { /* invariant: a[l..i] <= P <= a[j..u] */ - /* repeat ++i until a[i] >= P */ - while ((*ta->geti)(L, 1, ++i), sort_comp(L, -1, -2)) { - if (i>=u) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[i] */ - } - /* repeat --j until a[j] <= P */ - while ((*ta->geti)(L, 1, --j), sort_comp(L, -3, -1)) { - if (j<=l) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[j] */ - } - if (jgeti)(L, 1, u-1); - (*ta->geti)(L, 1, i); - set2(L, ta, u-1, i); /* swap pivot (a[u-1]) with a[i] */ - /* a[l..i-1] <= a[i] == P <= a[i+1..u] */ - /* adjust so that smaller half is in [j..i] and larger one in [l..u] */ - if (i-l < u-i) { - j=l; i=i-1; l=i+2; + if (up - lo == 2) /* only 3 elements? */ + return; /* already sorted */ + lua_geti(L, 1, p); /* get middle element (Pivot) */ + lua_pushvalue(L, -1); /* push Pivot */ + lua_geti(L, 1, up - 1); /* push a[up - 1] */ + set2(L, p, up - 1); /* swap Pivot (a[p]) with a[up - 1] */ + p = partition(L, lo, up); + /* a[lo .. p - 1] <= a[p] == P <= a[p + 1 .. up] */ + if (p - lo < up - p) { /* lower interval is smaller? */ + auxsort(L, lo, p - 1, rnd); /* call recursively for lower interval */ + n = p - lo; /* size of smaller interval */ + lo = p + 1; /* tail call for [p + 1 .. up] (upper interval) */ } else { - j=i+1; i=u; u=j-2; + auxsort(L, p + 1, up, rnd); /* call recursively for upper interval */ + n = up - p; /* size of smaller interval */ + up = p - 1; /* tail call for [lo .. p - 1] (lower interval) */ } - auxsort(L, ta, j, i); /* call recursively the smaller one */ - } /* repeat the routine for the larger one */ + if ((up - lo) / 128 > n) /* partition too imbalanced? */ + rnd = l_randomizePivot(); /* try a new randomization */ + } /* tail call auxsort(L, lo, up, rnd) */ } + static int sort (lua_State *L) { - TabA ta; - int n = (int)aux_getn(L, 1, &ta); - luaL_checkstack(L, 50, ""); /* assume array is smaller than 2^50 */ - if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ - luaL_checktype(L, 2, LUA_TFUNCTION); - lua_settop(L, 2); /* make sure there are two arguments */ - auxsort(L, &ta, 1, n); + lua_Integer n = aux_getn(L, 1, TAB_RW); + if (n > 1) { /* non-trivial interval? */ + luaL_argcheck(L, n < INT_MAX, 1, "array too big"); + if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checktype(L, 2, LUA_TFUNCTION); /* must be a function */ + lua_settop(L, 2); /* make sure there are two arguments */ + auxsort(L, 1, (IdxT)n, 0); + } return 0; } diff --git a/source/extern/lua/ltm.c b/source/extern/lua/ltm.c index c38e5c3..0e7c713 100644 --- a/source/extern/lua/ltm.c +++ b/source/extern/lua/ltm.c @@ -1,5 +1,5 @@ /* -** $Id: ltm.c,v 2.34 2015/03/30 15:42:27 roberto Exp $ +** $Id: ltm.c,v 2.38.1.1 2017/04/19 17:39:34 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ @@ -15,7 +15,7 @@ #include "lua.h" #include "ldebug.h" -#include "ldo.h" +#include "ldo.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" @@ -57,7 +57,7 @@ void luaT_init (lua_State *L) { ** tag methods */ const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { - const TValue *tm = luaH_getstr(events, ename); + const TValue *tm = luaH_getshortstr(events, ename); lua_assert(event <= TM_EQ); if (ttisnil(tm)) { /* no tag method? */ events->flags |= cast_byte(1u<mt[ttnov(o)]; } - return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); + return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : luaO_nilobject); +} + + +/* +** Return the name of the type of an object. For tables and userdata +** with metatable, use their '__name' metafield, if present. +*/ +const char *luaT_objtypename (lua_State *L, const TValue *o) { + Table *mt; + if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) || + (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) { + const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name")); + if (ttisstring(name)) /* is '__name' a string? */ + return getstr(tsvalue(name)); /* use it as type name */ + } + return ttypename(ttnov(o)); /* else use standard type name */ } void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, const TValue *p2, TValue *p3, int hasres) { ptrdiff_t result = savestack(L, p3); - setobj2s(L, L->top++, f); /* push function (assume EXTRA_STACK) */ - setobj2s(L, L->top++, p1); /* 1st argument */ - setobj2s(L, L->top++, p2); /* 2nd argument */ + StkId func = L->top; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + L->top += 3; if (!hasres) /* no result? 'p3' is third argument */ setobj2s(L, L->top++, p3); /* 3rd argument */ /* metamethod may yield only when called from Lua code */ - luaD_call(L, L->top - (4 - hasres), hasres, isLua(L->ci)); + if (isLua(L->ci)) + luaD_call(L, func, hasres); + else + luaD_callnoyield(L, func, hasres); if (hasres) { /* if has result, move it to its place */ p3 = restorestack(L, result); setobjs2s(L, p3, --L->top); diff --git a/source/extern/lua/ltm.h b/source/extern/lua/ltm.h index 180179c..8170688 100644 --- a/source/extern/lua/ltm.h +++ b/source/extern/lua/ltm.h @@ -1,5 +1,5 @@ /* -** $Id: ltm.h,v 2.21 2014/10/25 11:50:46 roberto Exp $ +** $Id: ltm.h,v 2.22.1.1 2017/04/19 17:20:42 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ @@ -51,11 +51,12 @@ typedef enum { #define fasttm(l,et,e) gfasttm(G(l), et, e) #define ttypename(x) luaT_typenames_[(x) + 1] -#define objtypename(x) ttypename(ttnov(x)) LUAI_DDEC const char *const luaT_typenames_[LUA_TOTALTAGS]; +LUAI_FUNC const char *luaT_objtypename (lua_State *L, const TValue *o); + LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event); diff --git a/source/extern/lua/lundump.c b/source/extern/lua/lundump.c index 510f325..edf9eb8 100644 --- a/source/extern/lua/lundump.c +++ b/source/extern/lua/lundump.c @@ -1,5 +1,5 @@ /* -** $Id: lundump.c,v 2.41 2014/11/02 19:19:04 roberto Exp $ +** $Id: lundump.c,v 2.44.1.1 2017/04/19 17:20:42 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -32,7 +32,6 @@ typedef struct { lua_State *L; ZIO *Z; - Mbuffer *b; const char *name; } LoadState; @@ -86,17 +85,28 @@ static lua_Integer LoadInteger (LoadState *S) { } -static TString *LoadString (LoadState *S) { +static TString *LoadString (LoadState *S, Proto *p) { + lua_State *L = S->L; size_t size = LoadByte(S); + TString *ts; if (size == 0xFF) LoadVar(S, size); if (size == 0) return NULL; - else { - char *s = luaZ_openspace(S->L, S->b, --size); - LoadVector(S, s, size); - return luaS_newlstr(S->L, s, size); + else if (--size <= LUAI_MAXSHORTLEN) { /* short string? */ + char buff[LUAI_MAXSHORTLEN]; + LoadVector(S, buff, size); + ts = luaS_newlstr(L, buff, size); } + else { /* long string */ + ts = luaS_createlngstrobj(L, size); + setsvalue2s(L, L->top, ts); /* anchor it ('loadVector' can GC) */ + luaD_inctop(L); + LoadVector(S, getstr(ts), size); /* load directly in final place */ + L->top--; /* pop string */ + } + luaC_objbarrier(L, p, ts); + return ts; } @@ -136,7 +146,7 @@ static void LoadConstants (LoadState *S, Proto *f) { break; case LUA_TSHRSTR: case LUA_TLNGSTR: - setsvalue2n(S->L, o, LoadString(S)); + setsvalue2n(S->L, o, LoadString(S, f)); break; default: lua_assert(0); @@ -154,6 +164,7 @@ static void LoadProtos (LoadState *S, Proto *f) { f->p[i] = NULL; for (i = 0; i < n; i++) { f->p[i] = luaF_newproto(S->L); + luaC_objbarrier(S->L, f, f->p[i]); LoadFunction(S, f->p[i], f->source); } } @@ -185,18 +196,18 @@ static void LoadDebug (LoadState *S, Proto *f) { for (i = 0; i < n; i++) f->locvars[i].varname = NULL; for (i = 0; i < n; i++) { - f->locvars[i].varname = LoadString(S); + f->locvars[i].varname = LoadString(S, f); f->locvars[i].startpc = LoadInt(S); f->locvars[i].endpc = LoadInt(S); } n = LoadInt(S); for (i = 0; i < n; i++) - f->upvalues[i].name = LoadString(S); + f->upvalues[i].name = LoadString(S, f); } static void LoadFunction (LoadState *S, Proto *f, TString *psource) { - f->source = LoadString(S); + f->source = LoadString(S, f); if (f->source == NULL) /* no source in dump? */ f->source = psource; /* reuse parent's source */ f->linedefined = LoadInt(S); @@ -251,8 +262,7 @@ static void checkHeader (LoadState *S) { /* ** load precompiled chunk */ -LClosure *luaU_undump(lua_State *L, ZIO *Z, Mbuffer *buff, - const char *name) { +LClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) { LoadState S; LClosure *cl; if (*name == '@' || *name == '=') @@ -263,12 +273,12 @@ LClosure *luaU_undump(lua_State *L, ZIO *Z, Mbuffer *buff, S.name = name; S.L = L; S.Z = Z; - S.b = buff; checkHeader(&S); cl = luaF_newLclosure(L, LoadByte(&S)); setclLvalue(L, L->top, cl); - incr_top(L); + luaD_inctop(L); cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); LoadFunction(&S, cl->p, NULL); lua_assert(cl->nupvalues == cl->p->sizeupvalues); luai_verifycode(L, buff, cl->p); diff --git a/source/extern/lua/lundump.h b/source/extern/lua/lundump.h index ef43d51..ce492d6 100644 --- a/source/extern/lua/lundump.h +++ b/source/extern/lua/lundump.h @@ -1,5 +1,5 @@ /* -** $Id: lundump.h,v 1.44 2014/06/19 18:27:20 roberto Exp $ +** $Id: lundump.h,v 1.45.1.1 2017/04/19 17:20:42 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -23,8 +23,7 @@ #define LUAC_FORMAT 0 /* this is the official format */ /* load one chunk; from lundump.c */ -LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, - const char* name); +LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, const char* name); /* dump one chunk; from ldump.c */ LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, diff --git a/source/extern/lua/lutf8lib.c b/source/extern/lua/lutf8lib.c index 9042582..10bd238 100644 --- a/source/extern/lua/lutf8lib.c +++ b/source/extern/lua/lutf8lib.c @@ -1,5 +1,5 @@ /* -** $Id: lutf8lib.c,v 1.15 2015/03/28 19:16:55 roberto Exp $ +** $Id: lutf8lib.c,v 1.16.1.1 2017/04/19 17:29:57 roberto Exp $ ** Standard library for UTF-8 manipulation ** See Copyright Notice in lua.h */ @@ -171,7 +171,7 @@ static int byteoffset (lua_State *L) { } else { if (iscont(s + posi)) - luaL_error(L, "initial position is a continuation byte"); + return luaL_error(L, "initial position is a continuation byte"); if (n < 0) { while (n < 0 && posi > 0) { /* move back */ do { /* find beginning of previous character */ @@ -194,7 +194,7 @@ static int byteoffset (lua_State *L) { lua_pushinteger(L, posi + 1); else /* no such character */ lua_pushnil(L); - return 1; + return 1; } diff --git a/source/extern/lua/lvm.c b/source/extern/lua/lvm.c index a8cefc5..cc43d87 100644 --- a/source/extern/lua/lvm.c +++ b/source/extern/lua/lvm.c @@ -1,5 +1,5 @@ /* -** $Id: lvm.c,v 2.245 2015/06/09 15:53:35 roberto Exp $ +** $Id: lvm.c,v 2.268.1.1 2017/04/19 17:39:34 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ @@ -153,75 +153,88 @@ static int forlimit (const TValue *obj, lua_Integer *p, lua_Integer step, /* -** Main function for table access (invoking metamethods if needed). -** Compute 'val = t[key]' +** Finish the table access 'val = t[key]'. +** if 'slot' is NULL, 't' is not a table; otherwise, 'slot' points to +** t[k] entry (which must be nil). */ -void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { +void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, + const TValue *slot) { int loop; /* counter to avoid infinite loops */ + const TValue *tm; /* metamethod */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* 't' is a table? */ - Table *h = hvalue(t); - const TValue *res = luaH_get(h, key); /* do a primitive get */ - if (!ttisnil(res) || /* result is not nil? */ - (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ - setobj2s(L, val, res); /* result is the raw get */ + if (slot == NULL) { /* 't' is not a table? */ + lua_assert(!ttistable(t)); + tm = luaT_gettmbyobj(L, t, TM_INDEX); + if (ttisnil(tm)) + luaG_typeerror(L, t, "index"); /* no metamethod */ + /* else will try the metamethod */ + } + else { /* 't' is a table */ + lua_assert(ttisnil(slot)); + tm = fasttm(L, hvalue(t)->metatable, TM_INDEX); /* table's metamethod */ + if (tm == NULL) { /* no metamethod? */ + setnilvalue(val); /* result is nil */ return; } - /* else will try metamethod */ + /* else will try the metamethod */ } - else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) - luaG_typeerror(L, t, "index"); /* no metamethod */ - if (ttisfunction(tm)) { /* metamethod is a function */ - luaT_callTM(L, tm, t, key, val, 1); + if (ttisfunction(tm)) { /* is metamethod a function? */ + luaT_callTM(L, tm, t, key, val, 1); /* call it */ return; } - t = tm; /* else repeat access over 'tm' */ + t = tm; /* else try to access 'tm[key]' */ + if (luaV_fastget(L,t,key,slot,luaH_get)) { /* fast track? */ + setobj2s(L, val, slot); /* done */ + return; + } + /* else repeat (tail call 'luaV_finishget') */ } - luaG_runerror(L, "gettable chain too long; possible loop"); + luaG_runerror(L, "'__index' chain too long; possible loop"); } /* -** Main function for table assignment (invoking metamethods if needed). -** Compute 't[key] = val' +** Finish a table assignment 't[key] = val'. +** If 'slot' is NULL, 't' is not a table. Otherwise, 'slot' points +** to the entry 't[key]', or to 'luaO_nilobject' if there is no such +** entry. (The value at 'slot' must be nil, otherwise 'luaV_fastset' +** would have done the job.) */ -void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { +void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot) { int loop; /* counter to avoid infinite loops */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* 't' is a table? */ - Table *h = hvalue(t); - TValue *oldval = cast(TValue *, luaH_get(h, key)); - /* if previous value is not nil, there must be a previous entry - in the table; a metamethod has no relevance */ - if (!ttisnil(oldval) || - /* previous value is nil; must check the metamethod */ - ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL && - /* no metamethod; is there a previous entry in the table? */ - (oldval != luaO_nilobject || - /* no previous entry; must create one. (The next test is - always true; we only need the assignment.) */ - (oldval = luaH_newkey(L, h, key), 1)))) { + const TValue *tm; /* '__newindex' metamethod */ + if (slot != NULL) { /* is 't' a table? */ + Table *h = hvalue(t); /* save 't' table */ + lua_assert(ttisnil(slot)); /* old value must be nil */ + tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */ + if (tm == NULL) { /* no metamethod? */ + if (slot == luaO_nilobject) /* no previous entry? */ + slot = luaH_newkey(L, h, key); /* create one */ /* no metamethod and (now) there is an entry with given key */ - setobj2t(L, oldval, val); /* assign new value to that entry */ + setobj2t(L, cast(TValue *, slot), val); /* set its new value */ invalidateTMcache(h); luaC_barrierback(L, h, val); return; } /* else will try the metamethod */ } - else /* not a table; check metamethod */ + else { /* not a table; check metamethod */ if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_typeerror(L, t, "index"); + } /* try the metamethod */ if (ttisfunction(tm)) { luaT_callTM(L, tm, t, key, val, 0); return; } t = tm; /* else repeat assignment over 'tm' */ + if (luaV_fastset(L, t, key, slot, luaH_get, val)) + return; /* done */ + /* else loop */ } - luaG_runerror(L, "settable chain too long; possible loop"); + luaG_runerror(L, "'__newindex' chain too long; possible loop"); } @@ -443,6 +456,17 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { #define isemptystr(o) (ttisshrstring(o) && tsvalue(o)->shrlen == 0) +/* copy strings in stack from top - n up to top - 1 to buffer */ +static void copy2buff (StkId top, int n, char *buff) { + size_t tl = 0; /* size already copied */ + do { + size_t l = vslen(top - n); /* length of string being copied */ + memcpy(buff + tl, svalue(top - n), l * sizeof(char)); + tl += l; + } while (--n > 0); +} + + /* ** Main operation for concatenation: concat 'total' values in the stack, ** from 'L->top - total' up to 'L->top - 1'. @@ -462,24 +486,24 @@ void luaV_concat (lua_State *L, int total) { else { /* at least two non-empty string values; get as many as possible */ size_t tl = vslen(top - 1); - char *buffer; - int i; - /* collect total length */ - for (i = 1; i < total && tostring(L, top-i-1); i++) { - size_t l = vslen(top - i - 1); + TString *ts; + /* collect total length and number of strings */ + for (n = 1; n < total && tostring(L, top - n - 1); n++) { + size_t l = vslen(top - n - 1); if (l >= (MAX_SIZE/sizeof(char)) - tl) luaG_runerror(L, "string length overflow"); tl += l; } - buffer = luaZ_openspace(L, &G(L)->buff, tl); - tl = 0; - n = i; - do { /* copy all strings to buffer */ - size_t l = vslen(top - i); - memcpy(buffer+tl, svalue(top-i), l * sizeof(char)); - tl += l; - } while (--i > 0); - setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); /* create result */ + if (tl <= LUAI_MAXSHORTLEN) { /* is result a short string? */ + char buff[LUAI_MAXSHORTLEN]; + copy2buff(top, n, buff); /* copy strings to buffer */ + ts = luaS_newlstr(L, buff, tl); + } + else { /* long string; copy strings directly to final result */ + ts = luaS_createlngstrobj(L, tl); + copy2buff(top, n, getstr(ts)); + } + setsvalue2s(L, top - n, ts); /* create result */ } total -= n-1; /* got 'n' strings to create 1 new */ L->top -= n-1; /* popped 'n' strings and pushed one */ @@ -700,27 +724,20 @@ void luaV_finishOp (lua_State *L) { ** some macros for common tasks in 'luaV_execute' */ -#if !defined(luai_runtimecheck) -#define luai_runtimecheck(L, c) /* void */ -#endif - #define RA(i) (base+GETARG_A(i)) -/* to be used after possible stack reallocation */ #define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) #define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) #define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) #define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) -#define KBx(i) \ - (k + (GETARG_Bx(i) != 0 ? GETARG_Bx(i) - 1 : GETARG_Ax(*ci->u.l.savedpc++))) /* execute a jump instruction */ #define dojump(ci,i,e) \ { int a = GETARG_A(i); \ - if (a > 0) luaF_close(L, ci->u.l.base + a - 1); \ + if (a != 0) luaF_close(L, ci->u.l.base + a - 1); \ ci->u.l.savedpc += GETARG_sBx(i) + e; } /* for test instructions, execute the jump instruction that follows it */ @@ -730,38 +747,58 @@ void luaV_finishOp (lua_State *L) { #define Protect(x) { {x;}; base = ci->u.l.base; } #define checkGC(L,c) \ - Protect( luaC_condGC(L,{L->top = (c); /* limit of live values */ \ - luaC_step(L); \ - L->top = ci->top;}) /* restore top */ \ - luai_threadyield(L); ) + { luaC_condGC(L, L->top = (c), /* limit of live values */ \ + Protect(L->top = ci->top)); /* restore top */ \ + luai_threadyield(L); } +/* fetch an instruction and prepare its execution */ +#define vmfetch() { \ + i = *(ci->u.l.savedpc++); \ + if (L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) \ + Protect(luaG_traceexec(L)); \ + ra = RA(i); /* WARNING: any stack reallocation invalidates 'ra' */ \ + lua_assert(base == ci->u.l.base); \ + lua_assert(base <= L->top && L->top < L->stack + L->stacksize); \ +} + #define vmdispatch(o) switch(o) #define vmcase(l) case l: #define vmbreak break + +/* +** copy of 'luaV_gettable', but protecting the call to potential +** metamethod (which can reallocate the stack) +*/ +#define gettableProtected(L,t,k,v) { const TValue *slot; \ + if (luaV_fastget(L,t,k,slot,luaH_get)) { setobj2s(L, v, slot); } \ + else Protect(luaV_finishget(L,t,k,v,slot)); } + + +/* same for 'luaV_settable' */ +#define settableProtected(L,t,k,v) { const TValue *slot; \ + if (!luaV_fastset(L,t,k,slot,luaH_get,v)) \ + Protect(luaV_finishset(L,t,k,v,slot)); } + + + void luaV_execute (lua_State *L) { CallInfo *ci = L->ci; LClosure *cl; TValue *k; StkId base; + ci->callstatus |= CIST_FRESH; /* fresh invocation of 'luaV_execute" */ newframe: /* reentry point when frame changes (call/return) */ lua_assert(ci == L->ci); - cl = clLvalue(ci->func); - k = cl->p->k; - base = ci->u.l.base; + cl = clLvalue(ci->func); /* local reference to function's closure */ + k = cl->p->k; /* local reference to function's constant table */ + base = ci->u.l.base; /* local copy of function's base */ /* main loop of interpreter */ for (;;) { - Instruction i = *(ci->u.l.savedpc++); + Instruction i; StkId ra; - if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && - (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { - Protect(luaG_traceexec(L)); - } - /* WARNING: several calls may realloc the stack and invalidate 'ra' */ - ra = RA(i); - lua_assert(base == ci->u.l.base); - lua_assert(base <= L->top && L->top < L->stack + L->stacksize); + vmfetch(); vmdispatch (GET_OPCODE(i)) { vmcase(OP_MOVE) { setobjs2s(L, ra, RB(i)); @@ -797,17 +834,22 @@ void luaV_execute (lua_State *L) { vmbreak; } vmcase(OP_GETTABUP) { - int b = GETARG_B(i); - Protect(luaV_gettable(L, cl->upvals[b]->v, RKC(i), ra)); + TValue *upval = cl->upvals[GETARG_B(i)]->v; + TValue *rc = RKC(i); + gettableProtected(L, upval, rc, ra); vmbreak; } vmcase(OP_GETTABLE) { - Protect(luaV_gettable(L, RB(i), RKC(i), ra)); + StkId rb = RB(i); + TValue *rc = RKC(i); + gettableProtected(L, rb, rc, ra); vmbreak; } vmcase(OP_SETTABUP) { - int a = GETARG_A(i); - Protect(luaV_settable(L, cl->upvals[a]->v, RKB(i), RKC(i))); + TValue *upval = cl->upvals[GETARG_A(i)]->v; + TValue *rb = RKB(i); + TValue *rc = RKC(i); + settableProtected(L, upval, rb, rc); vmbreak; } vmcase(OP_SETUPVAL) { @@ -817,7 +859,9 @@ void luaV_execute (lua_State *L) { vmbreak; } vmcase(OP_SETTABLE) { - Protect(luaV_settable(L, ra, RKB(i), RKC(i))); + TValue *rb = RKB(i); + TValue *rc = RKC(i); + settableProtected(L, ra, rb, rc); vmbreak; } vmcase(OP_NEWTABLE) { @@ -831,9 +875,15 @@ void luaV_execute (lua_State *L) { vmbreak; } vmcase(OP_SELF) { + const TValue *aux; StkId rb = RB(i); - setobjs2s(L, ra+1, rb); - Protect(luaV_gettable(L, rb, RKC(i), ra)); + TValue *rc = RKC(i); + TString *key = tsvalue(rc); /* key must be a string */ + setobjs2s(L, ra + 1, rb); + if (luaV_fastget(L, rb, key, aux, luaH_getstr)) { + setobj2s(L, ra, aux); + } + else Protect(luaV_finishget(L, rb, rc, ra, aux)); vmbreak; } vmcase(OP_ADD) { @@ -1020,7 +1070,7 @@ void luaV_execute (lua_State *L) { StkId rb; L->top = base + c + 1; /* mark the end of concat operands */ Protect(luaV_concat(L, c - b + 1)); - ra = RA(i); /* 'luav_concat' may invoke TMs and move the stack */ + ra = RA(i); /* 'luaV_concat' may invoke TMs and move the stack */ rb = base + b; setobjs2s(L, ra, rb); checkGC(L, (ra >= rb ? ra + 1 : rb)); @@ -1035,7 +1085,7 @@ void luaV_execute (lua_State *L) { TValue *rb = RKB(i); TValue *rc = RKC(i); Protect( - if (cast_int(luaV_equalobj(L, rb, rc)) != GETARG_A(i)) + if (luaV_equalobj(L, rb, rc) != GETARG_A(i)) ci->u.l.savedpc++; else donextjump(ci); @@ -1082,12 +1132,12 @@ void luaV_execute (lua_State *L) { int nresults = GETARG_C(i) - 1; if (b != 0) L->top = ra+b; /* else previous instruction set top */ if (luaD_precall(L, ra, nresults)) { /* C function? */ - if (nresults >= 0) L->top = ci->top; /* adjust results */ - base = ci->u.l.base; + if (nresults >= 0) + L->top = ci->top; /* adjust results */ + Protect((void)0); /* update 'base' */ } else { /* Lua function */ ci = L->ci; - ci->callstatus |= CIST_REENTRY; goto newframe; /* restart luaV_execute over new Lua function */ } vmbreak; @@ -1096,8 +1146,9 @@ void luaV_execute (lua_State *L) { int b = GETARG_B(i); if (b != 0) L->top = ra+b; /* else previous instruction set top */ lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); - if (luaD_precall(L, ra, LUA_MULTRET)) /* C function? */ - base = ci->u.l.base; + if (luaD_precall(L, ra, LUA_MULTRET)) { /* C function? */ + Protect((void)0); /* update 'base' */ + } else { /* tail call: put called frame (n) in place of caller one (o) */ CallInfo *nci = L->ci; /* called frame */ @@ -1125,8 +1176,8 @@ void luaV_execute (lua_State *L) { vmcase(OP_RETURN) { int b = GETARG_B(i); if (cl->p->sizep > 0) luaF_close(L, base); - b = luaD_poscall(L, ra, (b != 0 ? b - 1 : L->top - ra)); - if (!(ci->callstatus & CIST_REENTRY)) /* 'ci' still the called one */ + b = luaD_poscall(L, ci, ra, (b != 0 ? b - 1 : cast_int(L->top - ra))); + if (ci->callstatus & CIST_FRESH) /* local 'ci' still from callee */ return; /* external invocation: return */ else { /* invocation via reentry: continue execution */ ci = L->ci; @@ -1139,7 +1190,7 @@ void luaV_execute (lua_State *L) { vmcase(OP_FORLOOP) { if (ttisinteger(ra)) { /* integer loop? */ lua_Integer step = ivalue(ra + 2); - lua_Integer idx = ivalue(ra) + step; /* increment index */ + lua_Integer idx = intop(+, ivalue(ra), step); /* increment index */ lua_Integer limit = ivalue(ra + 1); if ((0 < step) ? (idx <= limit) : (limit <= idx)) { ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ @@ -1171,7 +1222,7 @@ void luaV_execute (lua_State *L) { /* all values are integer */ lua_Integer initv = (stopnow ? 0 : ivalue(init)); setivalue(plimit, ilimit); - setivalue(init, initv - ivalue(pstep)); + setivalue(init, intop(-, initv, ivalue(pstep))); } else { /* try making all values floats */ lua_Number ninit; lua_Number nlimit; lua_Number nstep; @@ -1194,7 +1245,7 @@ void luaV_execute (lua_State *L) { setobjs2s(L, cb+1, ra+1); setobjs2s(L, cb, ra); L->top = cb + 3; /* func. + 2 args (state and index) */ - Protect(luaD_call(L, cb, GETARG_C(i), 1)); + Protect(luaD_call(L, cb, GETARG_C(i))); L->top = ci->top; i = *(ci->u.l.savedpc++); /* go to next instruction */ ra = RA(i); @@ -1219,11 +1270,10 @@ void luaV_execute (lua_State *L) { lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); c = GETARG_Ax(*ci->u.l.savedpc++); } - luai_runtimecheck(L, ttistable(ra)); h = hvalue(ra); last = ((c-1)*LFIELDS_PER_FLUSH) + n; if (last > h->sizearray) /* needs more space? */ - luaH_resizearray(L, h, last); /* pre-allocate it at once */ + luaH_resizearray(L, h, last); /* preallocate it at once */ for (; n > 0; n--) { TValue *val = ra+n; luaH_setint(L, h, last--, val); @@ -1243,23 +1293,21 @@ void luaV_execute (lua_State *L) { vmbreak; } vmcase(OP_VARARG) { - int b = GETARG_B(i) - 1; + int b = GETARG_B(i) - 1; /* required results */ int j; int n = cast_int(base - ci->func) - cl->p->numparams - 1; + if (n < 0) /* less arguments than parameters? */ + n = 0; /* no vararg arguments */ if (b < 0) { /* B == 0? */ b = n; /* get all var. arguments */ Protect(luaD_checkstack(L, n)); ra = RA(i); /* previous call may change the stack */ L->top = ra + n; } - for (j = 0; j < b; j++) { - if (j < n) { - setobjs2s(L, ra + j, base - n + j); - } - else { - setnilvalue(ra + j); - } - } + for (j = 0; j < b && j < n; j++) + setobjs2s(L, ra + j, base - n + j); + for (; j < b; j++) /* complete required results with nil */ + setnilvalue(ra + j); vmbreak; } vmcase(OP_EXTRAARG) { diff --git a/source/extern/lua/lvm.h b/source/extern/lua/lvm.h index 0613826..a8f954f 100644 --- a/source/extern/lua/lvm.h +++ b/source/extern/lua/lvm.h @@ -1,5 +1,5 @@ /* -** $Id: lvm.h,v 2.35 2015/02/20 14:27:53 roberto Exp $ +** $Id: lvm.h,v 2.41.1.1 2017/04/19 17:20:42 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ @@ -48,15 +48,60 @@ #define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) +/* +** fast track for 'gettable': if 't' is a table and 't[k]' is not nil, +** return 1 with 'slot' pointing to 't[k]' (final result). Otherwise, +** return 0 (meaning it will have to check metamethod) with 'slot' +** pointing to a nil 't[k]' (if 't' is a table) or NULL (otherwise). +** 'f' is the raw get function to use. +*/ +#define luaV_fastget(L,t,k,slot,f) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = f(hvalue(t), k), /* else, do raw access */ \ + !ttisnil(slot))) /* result not nil? */ + +/* +** standard implementation for 'gettable' +*/ +#define luaV_gettable(L,t,k,v) { const TValue *slot; \ + if (luaV_fastget(L,t,k,slot,luaH_get)) { setobj2s(L, v, slot); } \ + else luaV_finishget(L,t,k,v,slot); } + + +/* +** Fast track for set table. If 't' is a table and 't[k]' is not nil, +** call GC barrier, do a raw 't[k]=v', and return true; otherwise, +** return false with 'slot' equal to NULL (if 't' is not a table) or +** 'nil'. (This is needed by 'luaV_finishget'.) Note that, if the macro +** returns true, there is no need to 'invalidateTMcache', because the +** call is not creating a new entry. +*/ +#define luaV_fastset(L,t,k,slot,f,v) \ + (!ttistable(t) \ + ? (slot = NULL, 0) \ + : (slot = f(hvalue(t), k), \ + ttisnil(slot) ? 0 \ + : (luaC_barrierback(L, hvalue(t), v), \ + setobj2t(L, cast(TValue *,slot), v), \ + 1))) + + +#define luaV_settable(L,t,k,v) { const TValue *slot; \ + if (!luaV_fastset(L,t,k,slot,luaH_get,v)) \ + luaV_finishset(L,t,k,v,slot); } + + + LUAI_FUNC int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2); LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_tonumber_ (const TValue *obj, lua_Number *n); LUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode); -LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, - StkId val); -LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, - StkId val); +LUAI_FUNC void luaV_finishget (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); +LUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); LUAI_FUNC void luaV_finishOp (lua_State *L); LUAI_FUNC void luaV_execute (lua_State *L); LUAI_FUNC void luaV_concat (lua_State *L, int total); diff --git a/source/extern/lua/lzio.c b/source/extern/lua/lzio.c index 4649392..6f79094 100644 --- a/source/extern/lua/lzio.c +++ b/source/extern/lua/lzio.c @@ -1,5 +1,5 @@ /* -** $Id: lzio.c,v 1.36 2014/11/02 19:19:04 roberto Exp $ +** $Id: lzio.c,v 1.37.1.1 2017/04/19 17:20:42 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ @@ -66,13 +66,3 @@ size_t luaZ_read (ZIO *z, void *b, size_t n) { return 0; } -/* ------------------------------------------------------------------------ */ -char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { - if (n > buff->buffsize) { - if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; - luaZ_resizebuffer(L, buff, n); - } - return buff->buffer; -} - - diff --git a/source/extern/lua/lzio.h b/source/extern/lua/lzio.h index b2e56bc..d897870 100644 --- a/source/extern/lua/lzio.h +++ b/source/extern/lua/lzio.h @@ -1,5 +1,5 @@ /* -** $Id: lzio.h,v 1.30 2014/12/19 17:26:14 roberto Exp $ +** $Id: lzio.h,v 1.31.1.1 2017/04/19 17:20:42 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ @@ -44,7 +44,6 @@ typedef struct Mbuffer { #define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) -LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data); LUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n); /* read next n bytes */ diff --git a/source/extern/luaconf.h b/source/extern/luaconf.h index 7cfa4fa..9eeeea6 100644 --- a/source/extern/luaconf.h +++ b/source/extern/luaconf.h @@ -1,5 +1,5 @@ /* -** $Id: luaconf.h,v 1.251 2015/05/20 17:39:23 roberto Exp $ +** $Id: luaconf.h,v 1.259.1.1 2017/04/19 17:29:57 roberto Exp $ ** Configuration file for Lua ** See Copyright Notice in lua.h */ @@ -145,7 +145,7 @@ #if !defined(LUA_FLOAT_TYPE) #define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE -#endif /* } */ +#endif /* }================================================================== */ @@ -158,6 +158,18 @@ ** =================================================================== */ +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +*/ +#define LUA_PATH_SEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXEC_DIR "!" + + /* @@ LUA_PATH_DEFAULT is the default path that Lua uses to look for ** Lua libraries. @@ -404,7 +416,7 @@ /* @@ LUA_NUMBER is the floating-point type used by Lua. -@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' @@ over a floating number. @@ l_mathlim(x) corrects limit name 'x' to the proper float type ** by prefixing it with one of FLT/DBL/LDBL. @@ -412,9 +424,34 @@ @@ LUA_NUMBER_FMT is the format for writing floats. @@ lua_number2str converts a float to a string. @@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. @@ lua_str2number converts a decimal numeric string to a number. */ + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) \ + l_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n)) + +/* +@@ lua_numbertointeger converts a float number to an integer, or +** returns 0 if float is not within the range of a lua_Integer. +** (The range comparisons are tricky because of rounding. The tests +** here assume a two-complement representation, where MININTEGER always +** has an exact representation as a float; MAXINTEGER may not have one, +** and therefore its conversion to float may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + #if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ #define LUA_NUMBER float @@ -468,32 +505,13 @@ #endif /* } */ -#define l_floor(x) (l_mathop(floor)(x)) - -#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) - - -/* -@@ lua_numbertointeger converts a float number to an integer, or -** returns 0 if float is not within the range of a lua_Integer. -** (The range comparisons are tricky because of rounding. The tests -** here assume a two-complement representation, where MININTEGER always -** has an exact representation as a float; MAXINTEGER may not have one, -** and therefore its conversion to float may have an ill-defined value.) -*/ -#define lua_numbertointeger(n,p) \ - ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ - (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ - (*(p) = (LUA_INTEGER)(n), 1)) - - /* @@ LUA_INTEGER is the integer type used by Lua. ** @@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. ** -@@ LUAI_UACINT is the result of an 'usual argument conversion' +@@ LUAI_UACINT is the result of a 'default argument promotion' @@ over a lUA_INTEGER. @@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. @@ LUA_INTEGER_FMT is the format for writing integers. @@ -506,10 +524,12 @@ /* The following definitions are good for most cases here */ #define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" -#define lua_integer2str(s,n) sprintf((s), LUA_INTEGER_FMT, (n)) #define LUAI_UACINT LUA_INTEGER +#define lua_integer2str(s,sz,n) \ + l_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n)) + /* ** use LUAI_UACINT here to avoid problems with promotions (which ** can turn a comparison between unsigneds into a signed comparison) @@ -537,6 +557,7 @@ #elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ #if defined(LLONG_MAX) /* { */ /* use ISO C99 stuff */ @@ -577,6 +598,17 @@ ** =================================================================== */ +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif + + /* @@ lua_strx2number converts an hexadecimal numeric string to a number. ** In C99, 'strtod' does that conversion. Otherwise, you can @@ -584,18 +616,26 @@ ** implementation. */ #if !defined(LUA_USE_C89) -#define lua_strx2number(s,p) lua_str2number(s,p) +#define lua_strx2number(s,p) lua_str2number(s,p) #endif /* -@@ lua_number2strx converts a float to an hexadecimal numeric string. +@@ lua_pointer2str converts a pointer to a readable string in a +** non-specified way. +*/ +#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p) + + +/* +@@ lua_number2strx converts a float to an hexadecimal numeric string. ** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. ** Otherwise, you can leave 'lua_number2strx' undefined and Lua will ** provide its own implementation. */ #if !defined(LUA_USE_C89) -#define lua_number2strx(L,b,f,n) sprintf(b,f,n) +#define lua_number2strx(L,b,sz,f,n) \ + ((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n))) #endif @@ -711,11 +751,11 @@ /* @@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. ** CHANGE it if it uses too much C-stack space. (For long double, -** 'string.format("%.99f", 1e4932)' needs ~5030 bytes, so a +** 'string.format("%.99f", -1e4932)' needs 5034 bytes, so a ** smaller buffer would force a memory allocation for each call to ** 'string.format'.) */ -#if defined(LUA_FLOAT_LONGDOUBLE) +#if LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE #define LUAL_BUFFERSIZE 8192 #else #define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer))) diff --git a/source/extern/lualib.h b/source/extern/lualib.h index 5165c0f..f5304aa 100644 --- a/source/extern/lualib.h +++ b/source/extern/lualib.h @@ -1,5 +1,5 @@ /* -** $Id: lualib.h,v 1.44 2014/02/06 17:32:33 roberto Exp $ +** $Id: lualib.h,v 1.45.1.1 2017/04/19 17:20:42 roberto Exp $ ** Lua standard libraries ** See Copyright Notice in lua.h */ @@ -11,6 +11,9 @@ #include "lua.h" +/* version suffix for environment variable names */ +#define LUA_VERSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + LUAMOD_API int (luaopen_base) (lua_State *L); diff --git a/source/frontend/CMakeLists.txt b/source/frontend/CMakeLists.txt index 0934308..b619e90 100644 --- a/source/frontend/CMakeLists.txt +++ b/source/frontend/CMakeLists.txt @@ -55,6 +55,7 @@ SET (star_frontend_HEADERS StarSimpleTooltip.hpp StarSongbookInterface.hpp StarStatusPane.hpp + StarShadersMenu.hpp StarTeleportDialog.hpp StarVoice.hpp StarVoiceLuaBindings.hpp @@ -107,6 +108,7 @@ SET (star_frontend_SOURCES StarSimpleTooltip.cpp StarSongbookInterface.cpp StarStatusPane.cpp + StarShadersMenu.cpp StarTeleportDialog.cpp StarVoice.cpp StarVoiceLuaBindings.cpp @@ -118,4 +120,4 @@ ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADE IF(STAR_PRECOMPILED_HEADERS) TARGET_PRECOMPILE_HEADERS (star_frontend REUSE_FROM star_core) -ENDIF() \ No newline at end of file +ENDIF() diff --git a/source/frontend/StarBaseScriptPane.cpp b/source/frontend/StarBaseScriptPane.cpp index bfeebb7..63dbd08 100644 --- a/source/frontend/StarBaseScriptPane.cpp +++ b/source/frontend/StarBaseScriptPane.cpp @@ -14,7 +14,7 @@ namespace Star { -BaseScriptPane::BaseScriptPane(Json config) : Pane(), m_rawConfig(config) { +BaseScriptPane::BaseScriptPane(Json config, bool construct) : Pane(), m_rawConfig(config) { auto& root = Root::singleton(); auto assets = root.assets(); @@ -35,15 +35,8 @@ BaseScriptPane::BaseScriptPane(Json config) : Pane(), m_rawConfig(config) { }); } - m_reader->construct(assets->fetchJson(m_config.get("gui")), this); - - for (auto pair : m_config.getObject("canvasClickCallbacks", {})) - m_canvasClickCallbacks.set(findChild(pair.first), pair.second.toString()); - for (auto pair : m_config.getObject("canvasKeyCallbacks", {})) - m_canvasKeyCallbacks.set(findChild(pair.first), pair.second.toString()); - - m_script.setScripts(jsonToStringList(m_config.get("scripts", JsonArray()))); - m_script.setUpdateDelta(m_config.getUInt("scriptDelta", 1)); + if (construct) + this->construct(assets->fetchJson(m_config.get("gui"))); m_callbacksAdded = false; } @@ -140,4 +133,16 @@ GuiReaderPtr BaseScriptPane::reader() { return m_reader; } +void BaseScriptPane::construct(Json config) { + m_reader->construct(config, this); + + for (auto pair : m_config.getObject("canvasClickCallbacks", {})) + m_canvasClickCallbacks.set(findChild(pair.first), pair.second.toString()); + for (auto pair : m_config.getObject("canvasKeyCallbacks", {})) + m_canvasKeyCallbacks.set(findChild(pair.first), pair.second.toString()); + + m_script.setScripts(jsonToStringList(m_config.get("scripts", JsonArray()))); + m_script.setUpdateDelta(m_config.getUInt("scriptDelta", 1)); +} + } diff --git a/source/frontend/StarBaseScriptPane.hpp b/source/frontend/StarBaseScriptPane.hpp index 0c00547..08a4d69 100644 --- a/source/frontend/StarBaseScriptPane.hpp +++ b/source/frontend/StarBaseScriptPane.hpp @@ -15,7 +15,7 @@ STAR_CLASS(BaseScriptPane); class BaseScriptPane : public Pane { public: - BaseScriptPane(Json config); + BaseScriptPane(Json config, bool construct = true); virtual void show() override; void displayed() override; @@ -34,6 +34,8 @@ public: Maybe cursorOverride(Vec2I const& screenPosition) override; protected: virtual GuiReaderPtr reader() override; + void construct(Json config); + Json m_config; Json m_rawConfig; @@ -45,7 +47,7 @@ protected: bool m_interactive; bool m_callbacksAdded; - LuaUpdatableComponent m_script; + mutable LuaUpdatableComponent m_script; }; } diff --git a/source/frontend/StarCharSelection.cpp b/source/frontend/StarCharSelection.cpp index 50b527d..4c56d59 100644 --- a/source/frontend/StarCharSelection.cpp +++ b/source/frontend/StarCharSelection.cpp @@ -27,6 +27,7 @@ CharSelectionPane::CharSelectionPane(PlayerStoragePtr playerStorage, guiReader.registerCallback("charSelector2", [=](Widget*) { selectCharacter(1); }); guiReader.registerCallback("charSelector3", [=](Widget*) { selectCharacter(2); }); guiReader.registerCallback("charSelector4", [=](Widget*) { selectCharacter(3); }); + guiReader.registerCallback("createCharButton", [=](Widget*) { m_createCallback(); }); guiReader.construct(root.assets()->json("/interface/windowconfig/charselection.config"), this); } diff --git a/source/frontend/StarChat.cpp b/source/frontend/StarChat.cpp index bbb28c1..da6c421 100644 --- a/source/frontend/StarChat.cpp +++ b/source/frontend/StarChat.cpp @@ -13,14 +13,25 @@ #include "StarPlayerStorage.hpp" #include "StarTeamClient.hpp" +#include "StarPlayer.hpp" +#include "StarConfigLuaBindings.hpp" +#include "StarPlayerLuaBindings.hpp" +#include "StarStatusControllerLuaBindings.hpp" +#include "StarCelestialLuaBindings.hpp" +#include "StarLuaGameConverters.hpp" + namespace Star { -Chat::Chat(UniverseClientPtr client) : m_client(client) { +Chat::Chat(UniverseClientPtr client, Json const& baseConfig) : BaseScriptPane(baseConfig, false) { + m_client = client; + m_scripted = baseConfig.get("scripts", Json()).isType(Json::Type::Array); + m_script.setLuaRoot(m_client->luaRoot()); + m_script.addCallbacks("world", LuaBindings::makeWorldCallbacks((World*)m_client->worldClient().get())); m_chatPrevIndex = 0; m_historyOffset = 0; - + auto assets = Root::singleton().assets(); - auto config = assets->json("/interface/chat/chat.config:config"); + auto config = baseConfig.get("config"); m_timeChatLastActive = Time::monotonicMilliseconds(); m_chatTextStyle = config.get("textStyle"); m_chatTextStyle.lineSpacing = config.get("lineHeight").toFloat(); @@ -45,15 +56,13 @@ Chat::Chat(UniverseClientPtr client) : m_client(client) { m_colorCodes[MessageContext::CommandResult] = config.query("colors.commandResult").toString(); m_colorCodes[MessageContext::RadioMessage] = config.query("colors.radioMessage").toString(); m_colorCodes[MessageContext::World] = config.query("colors.world").toString(); + if (!m_scripted) { + m_reader->registerCallback("textBox", [=](Widget*) { startChat(); }); + m_reader->registerCallback("upButton", [=](Widget*) { scrollUp(); }); + m_reader->registerCallback("downButton", [=](Widget*) { scrollDown(); }); + m_reader->registerCallback("bottomButton", [=](Widget*) { scrollBottom(); }); - GuiReader reader; - - reader.registerCallback("textBox", [=](Widget*) { startChat(); }); - reader.registerCallback("upButton", [=](Widget*) { scrollUp(); }); - reader.registerCallback("downButton", [=](Widget*) { scrollDown(); }); - reader.registerCallback("bottomButton", [=](Widget*) { scrollBottom(); }); - - reader.registerCallback("filterGroup", [=](Widget* widget) { + m_reader->registerCallback("filterGroup", [=](Widget* widget) { Json data = as(widget)->data(); auto filter = data.getArray("filter", {}); m_modeFilter.clear(); @@ -62,41 +71,49 @@ Chat::Chat(UniverseClientPtr client) : m_client(client) { m_sendMode = ChatSendModeNames.getLeft(data.getString("sendMode", "Broadcast")); m_historyOffset = 0; }); + } + + construct(baseConfig.get("gui")); m_sendMode = ChatSendMode::Broadcast; - - reader.construct(assets->json("/interface/chat/chat.config:gui"), this); - - m_textBox = fetchChild("textBox"); - m_say = fetchChild("say"); - m_chatLog = fetchChild("chatLog"); - if (auto logPadding = config.optQuery("padding")) { - m_chatLogPadding = jsonToVec2I(logPadding.get()); - m_chatLog->setSize(m_chatLog->size() + m_chatLogPadding * 2); - m_chatLog->setPosition(m_chatLog->position() - m_chatLogPadding); - } - else - m_chatLogPadding = Vec2I(); - m_bottomButton = fetchChild("bottomButton"); m_upButton = fetchChild("upButton"); + m_textBox = fetchChild("textBox"); + m_say = fetchChild("say"); + if (!m_scripted) { + if (auto logPadding = config.optQuery("padding")) { + m_chatLogPadding = jsonToVec2I(logPadding.get()); + m_chatLog->setSize(m_chatLog->size() + m_chatLogPadding * 2); + m_chatLog->setPosition(m_chatLog->position() - m_chatLogPadding); + } else + m_chatLogPadding = Vec2I(); - m_chatHistory.appendAll(m_client->playerStorage()->getMetadata("chatHistory").opt().apply(jsonToStringList).value()); + m_chatHistory.appendAll(m_client->playerStorage()->getMetadata("chatHistory").opt().apply(jsonToStringList).value()); + } else { + m_script.addCallbacks("player", LuaBindings::makePlayerCallbacks(m_client->mainPlayer().get())); + m_script.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(m_client->mainPlayer()->statusController())); + m_script.addCallbacks("celestial", LuaBindings::makeCelestialCallbacks(m_client.get())); + } show(); - updateBottomButton(); + //if (!m_scripted) { + //updateBottomButton(); - m_background = fetchChild("background"); - m_defaultHeight = m_background->size()[1]; - m_expanded = false; - updateSize(); + m_background = fetchChild("background"); + m_defaultHeight = m_background->size()[1]; + m_expanded = false; + updateSize(); + //} } void Chat::update(float dt) { Pane::update(dt); + if (m_scripted) + return; + auto team = m_client->teamClient()->currentTeam(); for (auto button : fetchChild("filterGroup")->buttons()) { auto mode = ChatSendModeNames.getLeft(button->data().getString("sendMode", "Broadcast")); @@ -108,40 +125,64 @@ void Chat::update(float dt) { } void Chat::startChat() { - show(); - m_textBox->focus(); + if (m_scripted) + m_script.invoke("startChat"); + else { + show(); + m_textBox->focus(); + } } void Chat::startCommand() { - show(); - m_textBox->setText("/"); - m_textBox->focus(); + if (m_scripted) + m_script.invoke("startCommand"); + else { + show(); + m_textBox->setText("/"); + m_textBox->focus(); + } } bool Chat::hasFocus() const { + if (m_scripted) + return m_script.invoke("hasFocus").value(); return m_textBox->hasFocus(); } void Chat::stopChat() { - m_textBox->setText(""); - m_textBox->blur(); - m_timeChatLastActive = Time::monotonicMilliseconds(); + if (m_scripted) + m_script.invoke("stopChat"); + else { + m_textBox->setText(""); + m_textBox->blur(); + m_timeChatLastActive = Time::monotonicMilliseconds(); + } } String Chat::currentChat() const { + if (m_scripted) + return m_script.invoke("currentChat").value(); return m_textBox->getText(); } bool Chat::setCurrentChat(String const& chat, bool moveCursor) { + if (m_scripted) + return m_script.invoke("setCurrentChat").value(); return m_textBox->setText(chat, true, moveCursor); } void Chat::clearCurrentChat() { - m_textBox->setText(""); - m_chatPrevIndex = 0; + if (m_scripted) + m_script.invoke("clearCurrentChat"); + else { + m_textBox->setText(""); + m_chatPrevIndex = 0; + } } ChatSendMode Chat::sendMode() const { + if (m_scripted) + return ChatSendModeNames.getLeft(m_script.invoke("sendMode").value()); return m_sendMode; } @@ -171,6 +212,13 @@ void Chat::addMessages(List const& messages, bool showPane) if (messages.empty()) return; + if (m_scripted) { + m_script.invoke("addMessages", messages.transformed([](ChatReceivedMessage const& message) { + return message.toJson(); + }), showPane); + return; + } + GuiContext& guiContext = GuiContext::singleton(); for (auto const& message : messages) { @@ -209,17 +257,22 @@ void Chat::addMessages(List const& messages, bool showPane) } void Chat::addHistory(String const& chat) { - if (m_chatHistory.size() > 0 && m_chatHistory.get(0).equals(chat)) + if (m_scripted) + m_script.invoke("addHistory", chat); + else if (m_chatHistory.size() > 0 && m_chatHistory.get(0).equals(chat)) return; - - m_chatHistory.prepend(chat); - m_chatHistory.resize(std::min((unsigned)m_chatHistory.size(), m_chatHistoryLimit)); - m_timeChatLastActive = Time::monotonicMilliseconds(); - m_client->playerStorage()->setMetadata("chatHistory", JsonArray::from(m_chatHistory)); + else { + m_chatHistory.prepend(chat); + m_chatHistory.resize(std::min((unsigned)m_chatHistory.size(), m_chatHistoryLimit)); + m_timeChatLastActive = Time::monotonicMilliseconds(); + m_client->playerStorage()->setMetadata("chatHistory", JsonArray::from(m_chatHistory)); + } } void Chat::clear(size_t count) { - if (count > m_receivedMessages.size()) + if (m_scripted) + m_script.invoke("clear", count); + else if (count > m_receivedMessages.size()) m_receivedMessages.clear(); else m_receivedMessages.erase(m_receivedMessages.begin(), m_receivedMessages.begin() + count); @@ -227,6 +280,8 @@ void Chat::clear(size_t count) { void Chat::renderImpl() { Pane::renderImpl(); + if (m_scripted) + return; if (m_textBox->hasFocus()) m_timeChatLastActive = Time::monotonicMilliseconds(); Vec4B fade = {255, 255, 255, 255}; @@ -303,6 +358,9 @@ void Chat::hide() { } float Chat::visible() const { + if (m_scripted) + return m_script.invoke("visible").value(1.0f); + double difference = (Time::monotonicMilliseconds() - m_timeChatLastActive) / 1000.0; if (difference < m_chatVisTime) return 1; @@ -310,7 +368,7 @@ float Chat::visible() const { } bool Chat::sendEvent(InputEvent const& event) { - if (active()) { + if (!m_scripted && active()) { if (hasFocus()) { if (event.is()) { auto actions = context()->actions(event); diff --git a/source/frontend/StarChat.hpp b/source/frontend/StarChat.hpp index df2d63c..53ed578 100644 --- a/source/frontend/StarChat.hpp +++ b/source/frontend/StarChat.hpp @@ -1,6 +1,6 @@ #pragma once -#include "StarPane.hpp" +#include "StarBaseScriptPane.hpp" #include "StarChatTypes.hpp" namespace Star { @@ -13,9 +13,9 @@ STAR_CLASS(ImageStretchWidget); STAR_CLASS(CanvasWidget); STAR_CLASS(Chat); -class Chat : public Pane { +class Chat : public BaseScriptPane { public: - Chat(UniverseClientPtr client); + Chat(UniverseClientPtr client, Json const&); void startChat(); void startCommand(); @@ -57,6 +57,7 @@ private: void updateBottomButton(); UniverseClientPtr m_client; + bool m_scripted; TextBoxWidgetPtr m_textBox; LabelWidgetPtr m_say; diff --git a/source/frontend/StarClientCommandProcessor.cpp b/source/frontend/StarClientCommandProcessor.cpp index ac87397..00a6c37 100644 --- a/source/frontend/StarClientCommandProcessor.cpp +++ b/source/frontend/StarClientCommandProcessor.cpp @@ -52,7 +52,8 @@ ClientCommandProcessor::ClientCommandProcessor(UniverseClientPtr universeClient, {"enabletech", bind(&ClientCommandProcessor::enableTech, this, _1)}, {"upgradeship", bind(&ClientCommandProcessor::upgradeShip, this, _1)}, {"swap", bind(&ClientCommandProcessor::swap, this, _1)}, - {"respawnInWorld", bind(&ClientCommandProcessor::respawnInWorld, this, _1)} + {"respawnInWorld", bind(&ClientCommandProcessor::respawnInWorld, this, _1)}, + {"render", bind(&ClientCommandProcessor::render, this, _1)} }; } @@ -245,7 +246,7 @@ String ClientCommandProcessor::startQuest(String const& argumentsString) { if (!adminCommandAllowed()) return "You must be an admin to use this command."; - auto questArc = QuestArcDescriptor::fromJson(Json::parse(arguments.at(0))); + auto questArc = QuestArcDescriptor::fromJson(Json::parseSequence(arguments.at(0)).get(0)); m_universeClient->questManager()->offer(make_shared(questArc, 0, m_universeClient->mainPlayer().get())); return "Quest started"; } @@ -440,4 +441,154 @@ String ClientCommandProcessor::respawnInWorld(String const& argumentsString) { return strf("Respawn in this world set to {} (This is client-side!)", respawnInWorld ? "true" : "false"); } +// Hardcoded render command, future version will write to the clipboard and possibly be implemented in Lua +String ClientCommandProcessor::render(String const& path) { + if (path.empty()) { + return "Specify a path to render an image, or for worn armor: " + "^cyan;hat^reset;/^cyan;chest^reset;/^cyan;legs^reset;/^cyan;back^reset; " + "or for body parts: " + "^cyan;head^reset;/^cyan;body^reset;/^cyan;hair^reset;/^cyan;facialhair^reset;/" + "^cyan;facialmask^reset;/^cyan;frontarm^reset;/^cyan;backarm^reset;/^cyan;emote^reset;"; + } + AssetPath assetPath; + bool outputSheet = false; + String outputName = "render"; + auto player = m_universeClient->mainPlayer(); + if (player && path.utf8Size() < 100) { + auto args = m_parser.tokenizeToStringList(path); + auto first = args.maybeFirst().value().toLower(); + auto humanoid = player->humanoid(); + auto& identity = humanoid->identity(); + auto species = identity.imagePath.value(identity.species); + outputSheet = true; + outputName = first; + if (first.equals("hat")) { + assetPath.basePath = humanoid->headArmorFrameset(); + assetPath.directives += humanoid->headArmorDirectives(); + } else if (first.equals("chest")) { + if (args.size() <= 1) { + return "Chest armors have multiple spritesheets. Do: " + "^white;/chest torso ^cyan;front^reset;/^cyan;torso^reset;/^cyan;back^reset;. " + "To repair old generated clothes, then also specify ^cyan;old^reset;."; + } + String sheet = args[1].toLower(); + outputName += " " + sheet; + if (sheet == "torso") { + assetPath.basePath = humanoid->chestArmorFrameset(); + assetPath.directives += humanoid->chestArmorDirectives(); + } else if (sheet == "front") { + assetPath.basePath = humanoid->frontSleeveFrameset(); + assetPath.directives += humanoid->chestArmorDirectives(); + } else if (sheet == "back") { + assetPath.basePath = humanoid->backSleeveFrameset(); + assetPath.directives += humanoid->chestArmorDirectives(); + } else { + return strf("^red;Invalid chest sheet type '{}'^reset;", sheet); + } + // recovery for custom chests made by a very old generator + if (args.size() <= 2 && args[2].toLower() == "old" && assetPath.basePath.beginsWith("/items/armors/avian/avian-tier6separator/")) + assetPath.basePath = "/items/armors/avian/avian-tier6separator/old/" + assetPath.basePath.substr(41); + } else if (first.equals("legs")) { + assetPath.basePath = humanoid->legsArmorFrameset(); + assetPath.directives += humanoid->legsArmorDirectives(); + } else if (first.equals("back")) { + assetPath.basePath = humanoid->backArmorFrameset(); + assetPath.directives += humanoid->backArmorDirectives(); + } else if (first.equals("body")) { + assetPath.basePath = humanoid->getBodyFromIdentity(); + assetPath.directives += identity.bodyDirectives; + } else if (first.equals("head")) { + outputSheet = false; + assetPath.basePath = humanoid->getHeadFromIdentity(); + assetPath.directives += identity.bodyDirectives; + } else if (first.equals("hair")) { + outputSheet = false; + assetPath.basePath = humanoid->getHairFromIdentity(); + assetPath.directives += identity.hairDirectives; + } else if (first.equals("facialhair")) { + outputSheet = false; + assetPath.basePath = humanoid->getFacialHairFromIdentity(); + assetPath.directives += identity.facialHairDirectives; + } else if (first.equals("facialmask")) { + outputSheet = false; + assetPath.basePath = humanoid->getFacialMaskFromIdentity(); + assetPath.directives += identity.facialMaskDirectives; + } else if (first.equals("frontarm")) { + assetPath.basePath = humanoid->getFrontArmFromIdentity(); + assetPath.directives += identity.bodyDirectives; + } else if (first.equals("backarm")) { + assetPath.basePath = humanoid->getBackArmFromIdentity(); + assetPath.directives += identity.bodyDirectives; + } else if (first.equals("emote")) { + assetPath.basePath = humanoid->getFacialEmotesFromIdentity(); + assetPath.directives += identity.emoteDirectives; + } else { + outputName = "render"; + } + + if (!outputSheet) + assetPath.subPath = String("normal"); + } + if (assetPath == AssetPath()) { + assetPath = AssetPath::split(path); + if (!assetPath.basePath.beginsWith("/")) + assetPath.basePath = "/assetmissing.png" + assetPath.basePath; + } + auto assets = Root::singleton().assets(); + ImageConstPtr image; + if (outputSheet) { + auto sheet = make_shared(*assets->image(assetPath.basePath)); + sheet->convert(PixelFormat::RGBA32); + AssetPath framePath = assetPath; + + StringMap> frames; + auto imageFrames = assets->imageFrames(assetPath.basePath); + for (auto& pair : imageFrames->frames) + frames[pair.first] = make_pair(pair.second, ImageConstPtr()); + + if (frames.empty()) + return "^red;Failed to save image^reset;"; + + for (auto& entry : frames) { + framePath.subPath = entry.first; + entry.second.second = assets->image(framePath); + } + + Vec2U frameSize = frames.begin()->second.first.size(); + Vec2U imageSize = frames.begin()->second.second->size().piecewiseMin(Vec2U{256, 256}); + if (imageSize.min() == 0) + return "^red;Resulting image is empty^reset;"; + + for (auto& frame : frames) { + RectU& box = frame.second.first; + box.setXMin((box.xMin() / frameSize[0]) * imageSize[0]); + box.setYMin(((sheet->height() - box.yMin() - box.height()) / frameSize[1]) * imageSize[1]); + box.setXMax(box.xMin() + imageSize[0]); + box.setYMax(box.yMin() + imageSize[1]); + } + + if (frameSize != imageSize) { + unsigned sheetWidth = (sheet->width() / frameSize[0]) * imageSize[0]; + unsigned sheetHeight = (sheet->height() / frameSize[0]) * imageSize[0]; + sheet->reset(sheetWidth, sheetHeight, PixelFormat::RGBA32); + } + + for (auto& entry : frames) + sheet->copyInto(entry.second.first.min(), *entry.second.second); + + image = std::move(sheet); + } else { + image = assets->image(assetPath); + } + if (image->size().min() == 0) + return "^red;Resulting image is empty^reset;"; + auto outputDirectory = Root::singleton().toStoragePath("output"); + auto outputPath = File::relativeTo(outputDirectory, strf("{}.png", outputName)); + if (!File::isDirectory(outputDirectory)) + File::makeDirectory(outputDirectory); + image->writePng(File::open(outputPath, IOMode::Write | IOMode::Truncate)); + return strf("Saved {}x{} image to {}.png", image->width(), image->height(), outputName); +} + + } \ No newline at end of file diff --git a/source/frontend/StarClientCommandProcessor.hpp b/source/frontend/StarClientCommandProcessor.hpp index 94dad5d..c3978a4 100644 --- a/source/frontend/StarClientCommandProcessor.hpp +++ b/source/frontend/StarClientCommandProcessor.hpp @@ -59,6 +59,7 @@ private: String upgradeShip(String const& argumentsString); String swap(String const& argumentsString); String respawnInWorld(String const& argumentsString); + String render(String const& imagePath); UniverseClientPtr m_universeClient; CinematicPtr m_cinematicOverlay; diff --git a/source/frontend/StarClipboardLuaBindings.cpp b/source/frontend/StarClipboardLuaBindings.cpp index c8a8dfe..037a7dd 100644 --- a/source/frontend/StarClipboardLuaBindings.cpp +++ b/source/frontend/StarClipboardLuaBindings.cpp @@ -1,21 +1,35 @@ #include "StarClipboardLuaBindings.hpp" #include "StarLuaConverters.hpp" +#include "StarInput.hpp" namespace Star { -LuaCallbacks LuaBindings::makeClipboardCallbacks(ApplicationControllerPtr appController) { +LuaCallbacks LuaBindings::makeClipboardCallbacks(ApplicationControllerPtr appController, bool alwaysAllow) { LuaCallbacks callbacks; - callbacks.registerCallback("hasText", [appController]() -> bool { - return appController->hasClipboard(); + auto available = [alwaysAllow]() { return alwaysAllow || Input::singleton().getTag("clipboard") > 0; }; + + callbacks.registerCallback("available", [=]() -> bool { + return available(); }); - callbacks.registerCallback("getText", [appController]() -> Maybe { - return appController->getClipboard(); + callbacks.registerCallback("hasText", [=]() -> bool { + return available() && appController->hasClipboard(); }); - callbacks.registerCallback("setText", [appController](String const& text) { - appController->setClipboard(text); + callbacks.registerCallback("getText", [=]() -> Maybe { + if (!available()) + return {}; + else + return appController->getClipboard(); + }); + + callbacks.registerCallback("setText", [=](String const& text) -> bool { + if (available()) { + appController->setClipboard(text); + return true; + } + return false; }); return callbacks; diff --git a/source/frontend/StarClipboardLuaBindings.hpp b/source/frontend/StarClipboardLuaBindings.hpp index 7515c0b..f38f8d8 100644 --- a/source/frontend/StarClipboardLuaBindings.hpp +++ b/source/frontend/StarClipboardLuaBindings.hpp @@ -6,7 +6,7 @@ namespace Star { namespace LuaBindings { -LuaCallbacks makeClipboardCallbacks(ApplicationControllerPtr appController); +LuaCallbacks makeClipboardCallbacks(ApplicationControllerPtr appController, bool alwaysAllow); } }// namespace Star diff --git a/source/frontend/StarContainerInterface.cpp b/source/frontend/StarContainerInterface.cpp index 5a06d04..afd2084 100644 --- a/source/frontend/StarContainerInterface.cpp +++ b/source/frontend/StarContainerInterface.cpp @@ -18,6 +18,7 @@ #include "StarStatusControllerLuaBindings.hpp" #include "StarWidgetLuaBindings.hpp" #include "StarAugmentItem.hpp" +#include "StarInput.hpp" namespace Star { @@ -265,6 +266,7 @@ void ContainerPane::update(float dt) { m_script->update(m_script->updateDt(dt)); m_itemBag->clearItems(); + Input& input = Input::singleton(); if (!m_containerInteractor->containerOpen()) { dismiss(); @@ -296,6 +298,10 @@ void ContainerPane::update(float dt) { fuelGauge->setPotentialFuelAmount(totalFuelAmount); fuelGauge->setRequestedFuelAmount(0); } + + if (input.bindDown("opensb", "takeAll")) { + m_containerInteractor->clearContainer(); + } } } } diff --git a/source/frontend/StarCraftingInterface.cpp b/source/frontend/StarCraftingInterface.cpp index 34e0265..c44ad2b 100644 --- a/source/frontend/StarCraftingInterface.cpp +++ b/source/frontend/StarCraftingInterface.cpp @@ -42,6 +42,7 @@ CraftingPane::CraftingPane(WorldClientPtr worldClient, PlayerPtr player, Json co jsonMerge(assets->fetchJson(baseConfig), settings)); m_filter = StringSet::from(jsonToStringList(m_settings.get("filter", JsonArray()))); + m_maxSpinCount = m_settings.getUInt("maxSpinCount", 1000); GuiReader reader; reader.registerCallback("spinCount.up", [=](Widget*) { @@ -752,14 +753,14 @@ List CraftingPane::determineRecipes() { int CraftingPane::maxCraft() { if (m_player->isAdmin()) - return 1000; + return m_maxSpinCount; auto itemDb = Root::singleton().itemDatabase(); int res = 0; if (m_guiList->selectedItem() != NPos && m_guiList->selectedItem() < m_recipes.size()) { HashMap normalizedBag = m_player->inventory()->availableItems(); auto selectedRecipe = recipeFromSelectedWidget(); res = itemDb->maxCraftableInBag(normalizedBag, m_player->inventory()->availableCurrencies(), selectedRecipe); - res = std::min(res, 1000); + res = std::min(res, m_maxSpinCount); } return res; } diff --git a/source/frontend/StarCraftingInterface.hpp b/source/frontend/StarCraftingInterface.hpp index 58dd022..f422406 100644 --- a/source/frontend/StarCraftingInterface.hpp +++ b/source/frontend/StarCraftingInterface.hpp @@ -69,6 +69,8 @@ private: StringSet m_filter; + int m_maxSpinCount; + int m_recipeAutorefreshCooldown; HashMap m_itemCache; diff --git a/source/frontend/StarGraphicsMenu.cpp b/source/frontend/StarGraphicsMenu.cpp index efce444..3974453 100644 --- a/source/frontend/StarGraphicsMenu.cpp +++ b/source/frontend/StarGraphicsMenu.cpp @@ -9,10 +9,12 @@ #include "StarButtonWidget.hpp" #include "StarOrderedSet.hpp" #include "StarJsonExtra.hpp" +#include "StarShadersMenu.hpp" namespace Star { -GraphicsMenu::GraphicsMenu() { +GraphicsMenu::GraphicsMenu(PaneManager* manager,UniverseClientPtr client) + : m_paneManager(manager) { GuiReader reader; reader.registerCallback("cancel", [&](Widget*) { @@ -103,10 +105,14 @@ GraphicsMenu::GraphicsMenu() { Root::singleton().configuration()->set("newLighting", checked); syncGui(); }); + reader.registerCallback("showShadersMenu", [=](Widget*) { + displayShaders(); + }); auto assets = Root::singleton().assets(); - Json paneLayout = assets->json("/interface/windowconfig/graphicsmenu.config:paneLayout"); + auto config = assets->json("/interface/windowconfig/graphicsmenu.config"); + Json paneLayout = config.get("paneLayout"); m_interfaceScaleList = jsonToIntList(assets->json("/interface/windowconfig/graphicsmenu.config:interfaceScaleList")); m_resList = jsonToVec2UList(assets->json("/interface/windowconfig/graphicsmenu.config:resolutionList")); @@ -122,6 +128,8 @@ GraphicsMenu::GraphicsMenu() { initConfig(); syncGui(); + + m_shadersMenu = make_shared(assets->json(config.getString("shadersPanePath", "/interface/opensb/shaders/shaders.config")), client); } void GraphicsMenu::show() { @@ -240,6 +248,10 @@ void GraphicsMenu::apply() { } } +void GraphicsMenu::displayShaders() { + m_paneManager->displayPane(PaneLayer::ModalWindow, m_shadersMenu); +} + void GraphicsMenu::applyWindowSettings() { auto configuration = Root::singleton().configuration(); auto appController = GuiContext::singleton().applicationController(); @@ -253,4 +265,4 @@ void GraphicsMenu::applyWindowSettings() { appController->setNormalWindow(jsonToVec2U(configuration->get("windowedResolution"))); } -} \ No newline at end of file +} diff --git a/source/frontend/StarGraphicsMenu.hpp b/source/frontend/StarGraphicsMenu.hpp index 6d03652..c212f71 100644 --- a/source/frontend/StarGraphicsMenu.hpp +++ b/source/frontend/StarGraphicsMenu.hpp @@ -1,14 +1,17 @@ #pragma once #include "StarPane.hpp" +#include "StarMainInterfaceTypes.hpp" +#include "StarUniverseClient.hpp" namespace Star { STAR_CLASS(GraphicsMenu); +STAR_CLASS(ShadersMenu); class GraphicsMenu : public Pane { public: - GraphicsMenu(); + GraphicsMenu(PaneManager* manager,UniverseClientPtr client); void show() override; void dismissed() override; @@ -23,6 +26,8 @@ private: void apply(); void applyWindowSettings(); + + void displayShaders(); List m_resList; List m_interfaceScaleList; @@ -30,6 +35,9 @@ private: List m_cameraSpeedList; JsonObject m_localChanges; + + ShadersMenuPtr m_shadersMenu; + PaneManager* m_paneManager; }; } diff --git a/source/frontend/StarItemTooltip.cpp b/source/frontend/StarItemTooltip.cpp index 7cec755..492672b 100644 --- a/source/frontend/StarItemTooltip.cpp +++ b/source/frontend/StarItemTooltip.cpp @@ -205,7 +205,7 @@ void ItemTooltipBuilder::describePersistentEffect( auto listItem = container->addItem(); listItem->fetchChild("statusImage") ->setImage(statsConfig.get(valueModifier->statName).getString("icon")); - listItem->setLabel("statusLabel", strf("{}{}", valueModifier->value < 0 ? "-" : "", valueModifier->value)); + listItem->setLabel("statusLabel", strf("{}{:.2f}", valueModifier->value < 0 ? "-" : "", valueModifier->value)); } } else if (auto effectiveMultiplier = modifierEffect->ptr()) { if (statsConfig.contains(effectiveMultiplier->statName)) { diff --git a/source/frontend/StarKeybindingsMenu.cpp b/source/frontend/StarKeybindingsMenu.cpp index c07dae8..b532ca9 100644 --- a/source/frontend/StarKeybindingsMenu.cpp +++ b/source/frontend/StarKeybindingsMenu.cpp @@ -180,14 +180,18 @@ void KeybindingsMenu::setKeybinding(KeyChord desc) { config->set("bindings", base); - String buttonText; + StringList buttonText; for (auto const& entry : base.get(key).iterateArray()) { - auto stored = inputDescriptorFromJson(entry); - buttonText = String::joinWith(", ", buttonText, printInputDescriptor(stored)); + try { + auto stored = inputDescriptorFromJson(entry); + buttonText.push_back(printInputDescriptor(stored)); + } catch (StarException const& e) { + buttonText.push_back("unknown"); + } } - convert(m_activeKeybinding)->setText(buttonText); + convert(m_activeKeybinding)->setText(buttonText.join(", ")); apply(); exitActiveMode(); diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp index 153ce07..8468f7f 100644 --- a/source/frontend/StarMainInterface.cpp +++ b/source/frontend/StarMainInterface.cpp @@ -117,7 +117,7 @@ MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, m_codexInterface = make_shared(m_client->mainPlayer()); m_paneManager.registerPane(MainInterfacePanes::Codex, PaneLayer::Window, m_codexInterface); - m_optionsMenu = make_shared(&m_paneManager); + m_optionsMenu = make_shared(&m_paneManager,m_client); m_paneManager.registerPane(MainInterfacePanes::Options, PaneLayer::ModalWindow, m_optionsMenu); m_popupInterface = make_shared(); @@ -141,7 +141,7 @@ MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, m_collections = make_shared(m_client, "/interface/scripted/collections/collectionsgui.config"); m_paneManager.registerPane(MainInterfacePanes::Collections, PaneLayer::Window, m_collections); - m_chat = make_shared(m_client); + m_chat = make_shared(m_client, Root::singleton().assets()->json("/interface/chat/chat.config")); m_paneManager.registerPane(MainInterfacePanes::Chat, PaneLayer::Hud, m_chat); m_clientCommandProcessor = make_shared(m_client, m_cinematicOverlay, &m_paneManager, m_config->macroCommands); @@ -171,11 +171,6 @@ MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, m_nameplatePainter = make_shared(); m_questIndicatorPainter = make_shared(m_client); m_chatBubbleManager = make_shared(); - - m_paneManager.displayRegisteredPane(MainInterfacePanes::ActionBar); - m_paneManager.displayRegisteredPane(MainInterfacePanes::Chat); - m_paneManager.displayRegisteredPane(MainInterfacePanes::TeamBar); - m_paneManager.displayRegisteredPane(MainInterfacePanes::StatusPane); } MainInterface::~MainInterface() { @@ -997,6 +992,13 @@ void MainInterface::reviveScriptPanes(List& panes) { } } +void MainInterface::displayDefaultPanes() { + m_paneManager.displayRegisteredPane(MainInterfacePanes::ActionBar); + m_paneManager.displayRegisteredPane(MainInterfacePanes::Chat); + m_paneManager.displayRegisteredPane(MainInterfacePanes::TeamBar); + m_paneManager.displayRegisteredPane(MainInterfacePanes::StatusPane); +} + PanePtr MainInterface::createEscapeDialog() { auto assets = Root::singleton().assets(); @@ -1602,4 +1604,4 @@ void MainInterface::displayScriptPane(ScriptPanePtr& scriptPane, EntityId source } } -} \ No newline at end of file +} diff --git a/source/frontend/StarMainInterface.hpp b/source/frontend/StarMainInterface.hpp index 667a2fe..a4f617b 100644 --- a/source/frontend/StarMainInterface.hpp +++ b/source/frontend/StarMainInterface.hpp @@ -132,6 +132,7 @@ public: void takeScriptPanes(List& out); void reviveScriptPanes(List& panes); + void displayDefaultPanes(); private: PanePtr createEscapeDialog(); diff --git a/source/frontend/StarMerchantInterface.cpp b/source/frontend/StarMerchantInterface.cpp index 51bccb6..196dba1 100644 --- a/source/frontend/StarMerchantInterface.cpp +++ b/source/frontend/StarMerchantInterface.cpp @@ -38,6 +38,8 @@ MerchantPane::MerchantPane( m_itemBag = make_shared(m_settings.getUInt("sellContainerSize")); + m_maxBuyCount = m_settings.getUInt("maxSpinCount", assets->json("/interface/windowconfig/crafting.config:default").getUInt("maxSpinCount", 1000)); + GuiReader reader; reader.registerCallback("spinCount.up", [=](Widget*) { if (m_selectedIndex != NPos) { @@ -360,8 +362,8 @@ int MerchantPane::maxBuyCount() { auto assets = Root::singleton().assets(); auto unitPrice = selected->data().toUInt(); if (unitPrice == 0) - return 1000; - return min(1000, (int)floor(m_player->currency("money") / unitPrice)); + return m_maxBuyCount; + return min(m_maxBuyCount, (int)floor(m_player->currency("money") / unitPrice)); } else { return 0; } diff --git a/source/frontend/StarMerchantInterface.hpp b/source/frontend/StarMerchantInterface.hpp index b717a61..967a2ce 100644 --- a/source/frontend/StarMerchantInterface.hpp +++ b/source/frontend/StarMerchantInterface.hpp @@ -76,6 +76,7 @@ private: ItemBagPtr m_itemBag; int m_buyCount; + int m_maxBuyCount; }; } diff --git a/source/frontend/StarOptionsMenu.cpp b/source/frontend/StarOptionsMenu.cpp index 0ea1fa1..28d44f6 100644 --- a/source/frontend/StarOptionsMenu.cpp +++ b/source/frontend/StarOptionsMenu.cpp @@ -10,10 +10,11 @@ #include "StarVoiceSettingsMenu.hpp" #include "StarBindingsMenu.hpp" #include "StarGraphicsMenu.hpp" +#include "StarHumanoid.hpp" namespace Star { -OptionsMenu::OptionsMenu(PaneManager* manager) +OptionsMenu::OptionsMenu(PaneManager* manager, UniverseClientPtr client) : m_sfxRange(0, 100), m_musicRange(0, 100), m_paneManager(manager) { auto root = Root::singletonPtr(); auto assets = root->assets(); @@ -47,6 +48,9 @@ OptionsMenu::OptionsMenu(PaneManager* manager) reader.registerCallback("allowAssetsMismatchCheckbox", [=](Widget*) { updateAllowAssetsMismatch(); }); + reader.registerCallback("headRotationCheckbox", [=](Widget*) { + updateHeadRotation(); + }); reader.registerCallback("backButton", [=](Widget*) { dismiss(); }); @@ -77,6 +81,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager) m_clientIPJoinableButton = fetchChild("clientIPJoinableCheckbox"); m_clientP2PJoinableButton = fetchChild("clientP2PJoinableCheckbox"); m_allowAssetsMismatchButton = fetchChild("allowAssetsMismatchCheckbox"); + m_headRotationButton = fetchChild("headRotationCheckbox"); m_instrumentLabel = fetchChild("instrumentValueLabel"); m_sfxLabel = fetchChild("sfxValueLabel"); @@ -90,7 +95,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager) m_voiceSettingsMenu = make_shared(assets->json(config.getString("voiceSettingsPanePath", "/interface/opensb/voicechat/voicechat.config"))); m_modBindingsMenu = make_shared(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config"))); m_keybindingsMenu = make_shared(); - m_graphicsMenu = make_shared(); + m_graphicsMenu = make_shared(manager,client); initConfig(); } @@ -115,7 +120,8 @@ StringList const OptionsMenu::ConfigKeys = { "tutorialMessages", "clientIPJoinable", "clientP2PJoinable", - "allowAssetsMismatch" + "allowAssetsMismatch", + "humanoidHeadRotation" }; void OptionsMenu::initConfig() { @@ -166,6 +172,12 @@ void OptionsMenu::updateAllowAssetsMismatch() { Root::singleton().configuration()->set("allowAssetsMismatch", m_allowAssetsMismatchButton->isChecked()); } +void OptionsMenu::updateHeadRotation() { + m_localChanges.set("humanoidHeadRotation", m_headRotationButton->isChecked()); + Root::singleton().configuration()->set("humanoidHeadRotation", m_headRotationButton->isChecked()); + Humanoid::globalHeadRotation() = m_headRotationButton->isChecked(); +} + void OptionsMenu::syncGuiToConf() { m_instrumentSlider->setVal(m_localChanges.get("instrumentVol").toInt(), false); m_instrumentLabel->setText(toString(m_instrumentSlider->val())); @@ -180,6 +192,7 @@ void OptionsMenu::syncGuiToConf() { m_clientIPJoinableButton->setChecked(m_localChanges.get("clientIPJoinable").toBool()); m_clientP2PJoinableButton->setChecked(m_localChanges.get("clientP2PJoinable").toBool()); m_allowAssetsMismatchButton->setChecked(m_localChanges.get("allowAssetsMismatch").toBool()); + m_headRotationButton->setChecked(m_localChanges.get("humanoidHeadRotation").optBool().value(true)); auto appController = GuiContext::singleton().applicationController(); if (!appController->p2pNetworkingService()) { diff --git a/source/frontend/StarOptionsMenu.hpp b/source/frontend/StarOptionsMenu.hpp index 4bc36ad..4a28fd0 100644 --- a/source/frontend/StarOptionsMenu.hpp +++ b/source/frontend/StarOptionsMenu.hpp @@ -3,6 +3,7 @@ #include "StarPane.hpp" #include "StarConfiguration.hpp" #include "StarMainInterfaceTypes.hpp" +#include "StarUniverseClient.hpp" namespace Star { @@ -17,7 +18,7 @@ STAR_CLASS(OptionsMenu); class OptionsMenu : public Pane { public: - OptionsMenu(PaneManager* manager); + OptionsMenu(PaneManager* manager, UniverseClientPtr client); virtual void show() override; @@ -35,6 +36,7 @@ private: void updateClientIPJoinable(); void updateClientP2PJoinable(); void updateAllowAssetsMismatch(); + void updateHeadRotation(); void syncGuiToConf(); @@ -51,6 +53,7 @@ private: ButtonWidgetPtr m_clientIPJoinableButton; ButtonWidgetPtr m_clientP2PJoinableButton; ButtonWidgetPtr m_allowAssetsMismatchButton; + ButtonWidgetPtr m_headRotationButton; LabelWidgetPtr m_instrumentLabel; LabelWidgetPtr m_sfxLabel; diff --git a/source/frontend/StarShadersMenu.cpp b/source/frontend/StarShadersMenu.cpp new file mode 100644 index 0000000..4e15b66 --- /dev/null +++ b/source/frontend/StarShadersMenu.cpp @@ -0,0 +1,22 @@ +#include "StarShadersMenu.hpp" + +namespace Star { + +ShadersMenu::ShadersMenu(Json const& config, UniverseClientPtr client) : BaseScriptPane(config) { + m_client = std::move(client); +} + +void ShadersMenu::show() { + BaseScriptPane::show(); +} + +void ShadersMenu::displayed() { + m_script.setLuaRoot(m_client->luaRoot()); + BaseScriptPane::displayed(); +} + +void ShadersMenu::dismissed() { + BaseScriptPane::dismissed(); +} + +} diff --git a/source/frontend/StarShadersMenu.hpp b/source/frontend/StarShadersMenu.hpp new file mode 100644 index 0000000..9b24b0d --- /dev/null +++ b/source/frontend/StarShadersMenu.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "StarBaseScriptPane.hpp" +#include "StarUniverseClient.hpp" + +namespace Star { + +STAR_CLASS(ShadersMenu); + +class ShadersMenu : public BaseScriptPane { +public: + ShadersMenu(Json const& config, UniverseClientPtr client); + + virtual void show() override; + void displayed() override; + void dismissed() override; + +private: + UniverseClientPtr m_client; +}; + +} diff --git a/source/frontend/StarSongbookInterface.cpp b/source/frontend/StarSongbookInterface.cpp index 0c5fa4a..fa9a7c4 100644 --- a/source/frontend/StarSongbookInterface.cpp +++ b/source/frontend/StarSongbookInterface.cpp @@ -64,7 +64,7 @@ void SongbookInterface::refresh(bool reloadFiles) { m_files = Root::singleton().assets()->scanExtension(".abc").values(); eraseWhere(m_files, [](String& song) { if (!song.beginsWith(SongPathPrefix, String::CaseInsensitive)) { - Logger::warn("Song '{}' isn't in {}, ignoring", SongPathPrefix.size(), song); + Logger::warn("Song '{}' isn't in {}, ignoring", song, SongPathPrefix); return true; } return false; diff --git a/source/frontend/StarTeamBar.hpp b/source/frontend/StarTeamBar.hpp index ca50452..2ca40d6 100644 --- a/source/frontend/StarTeamBar.hpp +++ b/source/frontend/StarTeamBar.hpp @@ -1,5 +1,4 @@ -#ifndef STAR_TEAMBAR_HPP -#define STAR_TEAMBAR_HPP +#pragma once #include "StarPane.hpp" #include "StarUuid.hpp" @@ -113,6 +112,4 @@ private: friend class TeamMemberMenu; }; -} - -#endif +} \ No newline at end of file diff --git a/source/frontend/StarTitleScreen.cpp b/source/frontend/StarTitleScreen.cpp index 895e88a..b48ac13 100644 --- a/source/frontend/StarTitleScreen.cpp +++ b/source/frontend/StarTitleScreen.cpp @@ -18,7 +18,7 @@ namespace Star { -TitleScreen::TitleScreen(PlayerStoragePtr playerStorage, MixerPtr mixer) +TitleScreen::TitleScreen(PlayerStoragePtr playerStorage, MixerPtr mixer, UniverseClientPtr client) : m_playerStorage(playerStorage), m_skipMultiPlayerConnection(false), m_mixer(mixer) { m_titleState = TitleState::Quit; @@ -43,7 +43,7 @@ TitleScreen::TitleScreen(PlayerStoragePtr playerStorage, MixerPtr mixer) initCharSelectionMenu(); initCharCreationMenu(); initMultiPlayerMenu(); - initOptionsMenu(); + initOptionsMenu(client); initModsMenu(); resetState(); @@ -345,8 +345,8 @@ void TitleScreen::initMultiPlayerMenu() { m_paneManager.registerPane("multiplayerMenu", PaneLayer::Hud, m_multiPlayerMenu); } -void TitleScreen::initOptionsMenu() { - auto optionsMenu = make_shared(&m_paneManager); +void TitleScreen::initOptionsMenu(UniverseClientPtr client) { + auto optionsMenu = make_shared(&m_paneManager,client); optionsMenu->setAnchor(PaneAnchor::Center); optionsMenu->lockPosition(); diff --git a/source/frontend/StarTitleScreen.hpp b/source/frontend/StarTitleScreen.hpp index 65ebb57..9aaac96 100644 --- a/source/frontend/StarTitleScreen.hpp +++ b/source/frontend/StarTitleScreen.hpp @@ -4,6 +4,7 @@ #include "StarAmbient.hpp" #include "StarRegisteredPaneManager.hpp" #include "StarInterfaceCursor.hpp" +#include "StarUniverseClient.hpp" namespace Star { @@ -39,7 +40,7 @@ enum class TitleState { class TitleScreen { public: - TitleScreen(PlayerStoragePtr playerStorage, MixerPtr mixer); + TitleScreen(PlayerStoragePtr playerStorage, MixerPtr mixer, UniverseClientPtr client); void renderInit(RendererPtr renderer); @@ -80,7 +81,7 @@ private: void initCharSelectionMenu(); void initCharCreationMenu(); void initMultiPlayerMenu(); - void initOptionsMenu(); + void initOptionsMenu(UniverseClientPtr client); void initModsMenu(); void renderCursor(); diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 1328846..8f1b329 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -31,71 +31,71 @@ EnumMap const VoiceChannelModeNames{ }; inline float getAudioChunkLoudness(int16_t* data, size_t samples, float volume) { - if (!samples) - return 0.f; + if (!samples) + return 0.f; - double rms = 0.; - for (size_t i = 0; i != samples; ++i) { - float sample = ((float)data[i] / 32767.f) * volume; - rms += (double)(sample * sample); - } + double rms = 0.; + for (size_t i = 0; i != samples; ++i) { + float sample = ((float)data[i] / 32767.f) * volume; + rms += (double)(sample * sample); + } float fRms = sqrtf((float)(rms / samples)); - if (fRms > 0) - return std::clamp(20.f * log10f(fRms), -127.f, 0.f); - else - return -127.f; + if (fRms > 0) + return std::clamp(20.f * log10f(fRms), -127.f, 0.f); + else + return -127.f; } float getAudioLoudness(int16_t* data, size_t samples, float volume = 1.0f) { - constexpr size_t CHUNK_SIZE = 50; + constexpr size_t CHUNK_SIZE = 50; - float highest = -127.f; - for (size_t i = 0; i < samples; i += CHUNK_SIZE) { - float level = getAudioChunkLoudness(data + i, std::min(i + CHUNK_SIZE, samples) - i, volume); - if (level > highest) + float highest = -127.f; + for (size_t i = 0; i < samples; i += CHUNK_SIZE) { + float level = getAudioChunkLoudness(data + i, std::min(i + CHUNK_SIZE, samples) - i, volume); + if (level > highest) highest = level; - } + } - return highest; + return highest; } class VoiceAudioStream { public: // TODO: This should really be a ring buffer instead. std::queue samples; - SDL_AudioStream* sdlAudioStreamMono; - SDL_AudioStream* sdlAudioStreamStereo; + SDL_AudioStream* sdlAudioStreamMono; + SDL_AudioStream* sdlAudioStreamStereo; Mutex mutex; - VoiceAudioStream() - : sdlAudioStreamMono (SDL_NewAudioStream(AUDIO_S16, 1, 48000, AUDIO_S16SYS, 1, 44100)) - , sdlAudioStreamStereo(SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100)) {}; - ~VoiceAudioStream() { - SDL_FreeAudioStream(sdlAudioStreamMono); - SDL_FreeAudioStream(sdlAudioStreamStereo); - } + VoiceAudioStream() + : sdlAudioStreamMono (SDL_NewAudioStream(AUDIO_S16, 1, 48000, AUDIO_S16SYS, 1, 44100)) + , sdlAudioStreamStereo(SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100)) {}; + ~VoiceAudioStream() { + SDL_FreeAudioStream(sdlAudioStreamMono); + SDL_FreeAudioStream(sdlAudioStreamStereo); + } - inline int16_t take() { - int16_t sample = 0; - if (!samples.empty()) { - sample = samples.front(); - samples.pop(); - } - return sample; - } + inline int16_t take() { + int16_t sample = 0; + if (!samples.empty()) { + sample = samples.front(); + samples.pop(); + } + return sample; + } - size_t resample(int16_t* in, size_t inSamples, std::vector& out, bool mono) { - SDL_AudioStream* stream = mono ? sdlAudioStreamMono : sdlAudioStreamStereo; - SDL_AudioStreamPut(stream, in, inSamples * sizeof(int16_t)); - if (int available = SDL_AudioStreamAvailable(stream)) { - out.resize(available / 2); - SDL_AudioStreamGet(stream, out.data(), available); - return available; - } - return 0; - } + size_t resample(int16_t* in, size_t inSamples, std::vector& out, bool mono) { + SDL_AudioStream* stream = mono ? sdlAudioStreamMono : sdlAudioStreamStereo; + SDL_AudioStreamPut(stream, in, inSamples * sizeof(int16_t)); + if (int available = SDL_AudioStreamAvailable(stream)) { + out.resize(available / 2); + SDL_AudioStreamGet(stream, out.data(), available); + return available; + } + return 0; + } }; Voice::Speaker::Speaker(SpeakerId id) @@ -106,16 +106,16 @@ Voice::Speaker::Speaker(SpeakerId id) } Json Voice::Speaker::toJson() const { - return JsonObject{ - {"speakerId", speakerId}, - {"entityId", entityId }, - {"name", name }, - {"playing", (bool)playing}, - {"muted", (bool)muted }, - {"decibels", (float)decibelLevel}, - {"smoothDecibels", (float)smoothDb }, - {"position", jsonFromVec2F(position)} - }; + return JsonObject{ + {"speakerId", speakerId}, + {"entityId", entityId }, + {"name", name }, + {"playing", (bool)playing}, + {"muted", (bool)muted }, + {"decibels", (float)decibelLevel}, + {"smoothDecibels", (float)smoothDb }, + {"position", jsonFromVec2F(position)} + }; } Voice* Voice::s_singleton; @@ -140,99 +140,99 @@ Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_e m_channelMode = VoiceChannelMode::Mono; m_applicationController = appController; - m_stopThread = false; - m_thread = Thread::invoke("Voice::thread", mem_fn(&Voice::thread), this); + m_stopThread = false; + m_thread = Thread::invoke("Voice::thread", mem_fn(&Voice::thread), this); s_singleton = this; } Voice::~Voice() { - m_stopThread = true; + m_stopThread = true; - { - MutexLocker locker(m_threadMutex); - m_threadCond.broadcast(); - } + { + MutexLocker locker(m_threadMutex); + m_threadCond.broadcast(); + } - m_thread.finish(); + m_thread.finish(); - if (m_nextSaveTime) - save(); + if (m_nextSaveTime) + save(); - closeDevice(); + closeDevice(); s_singleton = nullptr; } void Voice::init() { - resetEncoder(); - resetDevice(); + resetEncoder(); + resetDevice(); } template inline bool change(T& value, T newValue, bool& out) { - bool changed = value != newValue; - out |= changed; - value = std::move(newValue); - return changed; + bool changed = value != newValue; + out |= changed; + value = std::move(newValue); + return changed; } void Voice::loadJson(Json const& config, bool skipSave) { // Not all keys are required - bool changed = false; + bool changed = false; - { - bool enabled = shouldEnableInput(); - m_enabled = config.getBool("enabled", m_enabled); - m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled); - if (shouldEnableInput() != enabled) { - changed = true; - resetDevice(); - } - } + { + bool enabled = shouldEnableInput(); + m_enabled = config.getBool("enabled", m_enabled); + m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled); + if (shouldEnableInput() != enabled) { + changed = true; + resetDevice(); + } + } - if (config.contains("deviceName") // Make sure null-type key exists - && change(m_deviceName, config.optString("deviceName"), changed)) - resetDevice(); + if (config.contains("deviceName") // Make sure null-type key exists + && change(m_deviceName, config.optString("deviceName"), changed)) + resetDevice(); m_threshold = config.getFloat("threshold", m_threshold); m_inputAmplitude = perceptualToAmplitude( - m_inputVolume = config.getFloat("inputVolume", m_inputVolume)); + m_inputVolume = config.getFloat("inputVolume", m_inputVolume)); m_outputAmplitude = perceptualToAmplitude( - m_outputVolume = config.getFloat("outputVolume", m_outputVolume)); - - if (change(m_loopback, config.getBool("loopback", m_loopback), changed)) - m_clientSpeaker->playing = false; + m_outputVolume = config.getFloat("outputVolume", m_outputVolume)); + + if (change(m_loopback, config.getBool("loopback", m_loopback), changed)) + m_clientSpeaker->playing = false; - if (auto inputMode = config.optString("inputMode")) { - if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode), changed)) - m_lastInputTime = 0; - } + if (auto inputMode = config.optString("inputMode")) { + if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode), changed)) + m_lastInputTime = 0; + } - bool shouldResetEncoder = false; - if (auto channelMode = config.optString("channelMode")) { - if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode), changed)) { - closeDevice(); - shouldResetEncoder = true; - resetDevice(); - } - } + bool shouldResetEncoder = false; + if (auto channelMode = config.optString("channelMode")) { + if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode), changed)) { + closeDevice(); + shouldResetEncoder = true; + resetDevice(); + } + } - // not saving this setting to disk, as it's just for audiophiles - // don't want someone fudging their bitrate from the intended defaults and forgetting - if (auto bitrate = config.opt("bitrate")) { + // not saving this setting to disk, as it's just for audiophiles + // don't want someone fudging their bitrate from the intended defaults and forgetting + if (auto bitrate = config.opt("bitrate")) { unsigned newBitrate = bitrate->canConvert(Json::Type::Int) - ? clamp((unsigned)bitrate->toUInt(), 6000u, 510000u) : 0; + ? clamp((unsigned)bitrate->toUInt(), 6000u, 510000u) : 0; shouldResetEncoder |= change(m_bitrate, newBitrate, changed); } if (shouldResetEncoder) resetEncoder(); - if (changed && !skipSave) - scheduleSave(); + if (changed && !skipSave) + scheduleSave(); } @@ -240,14 +240,14 @@ void Voice::loadJson(Json const& config, bool skipSave) { Json Voice::saveJson() const { return JsonObject{ {"enabled", m_enabled}, - {"deviceName", m_deviceName ? *m_deviceName : Json()}, + {"deviceName", m_deviceName ? *m_deviceName : Json()}, {"inputEnabled", m_inputEnabled}, {"threshold", m_threshold}, {"inputVolume", m_inputVolume}, {"outputVolume", m_outputVolume}, {"inputMode", VoiceInputModeNames.getRight(m_inputMode)}, {"channelMode", VoiceChannelModeNames.getRight(m_channelMode)}, - {"loopback", m_loopback}, + {"loopback", m_loopback}, {"version", 1} }; } @@ -261,7 +261,7 @@ void Voice::save() const { void Voice::scheduleSave() { if (!m_nextSaveTime) - m_nextSaveTime = Time::monotonicMilliseconds() + 2000; + m_nextSaveTime = Time::monotonicMilliseconds() + 2000; } Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) { @@ -273,7 +273,7 @@ Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) { } Voice::SpeakerPtr Voice::localSpeaker() { - return m_clientSpeaker; + return m_clientSpeaker; } Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { @@ -288,178 +288,181 @@ Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { } HashMap& Voice::speakers() { - return m_speakers; + return m_speakers; } List Voice::sortedSpeakers(bool onlyPlaying) { - List result; + List result; - auto sorter = [](SpeakerPtr const& a, SpeakerPtr const& b) -> bool { - if (a->lastPlayTime != b->lastPlayTime) - return a->lastPlayTime < b->lastPlayTime; - else - return a->speakerId < b->speakerId; - }; + auto sorter = [](SpeakerPtr const& a, SpeakerPtr const& b) -> bool { + if (a->lastPlayTime != b->lastPlayTime) + return a->lastPlayTime < b->lastPlayTime; + else + return a->speakerId < b->speakerId; + }; - for (auto& p : m_speakers) { - if (!onlyPlaying || p.second->playing) - result.insertSorted(p.second, sorter); - } + for (auto& p : m_speakers) { + if (!onlyPlaying || p.second->playing) + result.insertSorted(p.second, sorter); + } - return result; + return result; } void Voice::clearSpeakers() { - auto it = m_speakers.begin(); - while (it != m_speakers.end()) { - if (it->second == m_clientSpeaker) - it = ++it; - else - it = m_speakers.erase(it); - } + auto it = m_speakers.begin(); + while (it != m_speakers.end()) { + if (it->second == m_clientSpeaker) + it = ++it; + else + it = m_speakers.erase(it); + } } void Voice::readAudioData(uint8_t* stream, int len) { - auto now = Time::monotonicMilliseconds(); - bool active = m_encoder && m_encodedChunksLength < 2048 + auto now = Time::monotonicMilliseconds(); + bool active = m_encoder && m_encodedChunksLength < 2048 && (m_inputMode == VoiceInputMode::VoiceActivity || now < m_lastInputTime); - size_t sampleCount = len / 2; + size_t sampleCount = len / 2; - if (active) { - float decibels = getAudioLoudness((int16_t*)stream, sampleCount); + if (active) { + float decibels = getAudioLoudness((int16_t*)stream, sampleCount); - if (m_inputMode == VoiceInputMode::VoiceActivity) { - if (decibels > m_threshold) - m_lastThresholdTime = now; - active = now - m_lastThresholdTime < 50; - } - } + if (m_inputMode == VoiceInputMode::VoiceActivity) { + if (decibels > m_threshold) + m_lastThresholdTime = now; + active = now - m_lastThresholdTime < 50; + } + } - m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputAmplitude); + m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputAmplitude); - if (!m_loopback) { - if (active && !m_clientSpeaker->playing) - m_clientSpeaker->lastPlayTime = now; + if (!m_loopback) { + if (active && !m_clientSpeaker->playing) + m_clientSpeaker->lastPlayTime = now; - m_clientSpeaker->playing = active; - } + m_clientSpeaker->playing = active; + } - MutexLocker captureLock(m_captureMutex); - if (active) { - m_capturedChunksFrames += sampleCount / m_deviceChannels; - auto data = (opus_int16*)malloc(len); - memcpy(data, stream, len); - m_capturedChunks.emplace(data, sampleCount); // takes ownership - m_threadCond.signal(); - } - else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is - while (!m_capturedChunks.empty()) - m_capturedChunks.pop(); + MutexLocker captureLock(m_captureMutex); + if (active) { + m_capturedChunksFrames += sampleCount / m_deviceChannels; + auto data = (opus_int16*)malloc(len); + memcpy(data, stream, len); + m_capturedChunks.emplace(data, sampleCount); // takes ownership + m_threadCond.signal(); + } + else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is + while (!m_capturedChunks.empty()) + m_capturedChunks.pop(); - m_capturedChunksFrames = 0; - } + m_capturedChunksFrames = 0; + } } void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { - size_t samples = frameCount * channels; - static std::vector finalBuffer, speakerBuffer; - static std::vector sharedBuffer; //int32 to reduce clipping - speakerBuffer.resize(samples); - sharedBuffer.resize(samples); + size_t samples = frameCount * channels; + static std::vector finalBuffer, speakerBuffer; + static std::vector sharedBuffer; //int32 to reduce clipping + speakerBuffer.resize(samples); + sharedBuffer.resize(samples); - bool mix = false; - { - MutexLocker lock(m_activeSpeakersMutex); - auto it = m_activeSpeakers.begin(); - while (it != m_activeSpeakers.end()) { - SpeakerPtr const& speaker = *it; - VoiceAudioStream* audio = speaker->audioStream.get(); - MutexLocker audioLock(audio->mutex); - if (speaker->playing && !audio->samples.empty()) { - for (size_t i = 0; i != samples; ++i) - speakerBuffer[i] = audio->take(); + bool mix = false; + { + MutexLocker lock(m_activeSpeakersMutex); + auto it = m_activeSpeakers.begin(); + while (it != m_activeSpeakers.end()) { + SpeakerPtr const& speaker = *it; + VoiceAudioStream* audio = speaker->audioStream.get(); + MutexLocker audioLock(audio->mutex); + if (speaker->playing && !audio->samples.empty()) { + for (size_t i = 0; i != samples; ++i) + speakerBuffer[i] = audio->take(); - if (speaker != m_clientSpeaker) - speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); + if (speaker != m_clientSpeaker) + speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); - if (!speaker->muted) { - mix = true; + if (!speaker->muted) { + mix = true; - float volume = speaker->volume; - Array2F levels = speaker->channelVolumes; - for (size_t i = 0; i != samples; ++i) - sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * levels[i % 2] * volume; - //Blends the weaker channel into the stronger one, - /* unused, is a bit too strong on stereo music. - float maxLevel = max(levels[0], levels[1]); - float leftToRight = maxLevel != 0.0f ? 1.0f - (levels[0] / maxLevel) : 0.0f; - float rightToLeft = maxLevel != 0.0f ? 1.0f - (levels[1] / maxLevel) : 0.0f; + float volume = speaker->volume; + std::array levels; + { + MutexLocker locker(speaker->mutex); + levels = speaker->channelVolumes; + } + for (size_t i = 0; i != samples; ++i) + sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * levels[i % 2] * volume; + //Blends the weaker channel into the stronger one, + /* unused, is a bit too strong on stereo music. + float maxLevel = max(levels[0], levels[1]); + float leftToRight = maxLevel != 0.0f ? 1.0f - (levels[0] / maxLevel) : 0.0f; + float rightToLeft = maxLevel != 0.0f ? 1.0f - (levels[1] / maxLevel) : 0.0f; - int16_t* speakerData = speakerBuffer.data(); - int32_t* sharedData = sharedBuffer.data(); - for (size_t i = 0; i != frameCount; ++i) { - auto leftSample = (float)*speakerData++; - auto rightSample = (float)*speakerData++; - - if (rightToLeft != 0.0f) - leftSample = ( leftSample + rightSample * rightToLeft) / (1.0f + rightToLeft); - if (leftToRight != 0.0f) - rightSample = (rightSample + leftSample * leftToRight) / (1.0f + leftToRight); + int16_t* speakerData = speakerBuffer.data(); + int32_t* sharedData = sharedBuffer.data(); + for (size_t i = 0; i != frameCount; ++i) { + auto leftSample = (float)*speakerData++; + auto rightSample = (float)*speakerData++; + + if (rightToLeft != 0.0f) + leftSample = ( leftSample + rightSample * rightToLeft) / (1.0f + rightToLeft); + if (leftToRight != 0.0f) + rightSample = (rightSample + leftSample * leftToRight) / (1.0f + leftToRight); - *sharedData++ += (int32_t)leftSample * levels[0]; - *sharedData++ += (int32_t)rightSample * levels[1]; - } - //*/ - } - ++it; - } - else { - speaker->playing = false; - if (speaker != m_clientSpeaker) - speaker->decibelLevel = -96.0f; - it = m_activeSpeakers.erase(it); - } - } - } + *sharedData++ += (int32_t)leftSample * levels[0]; + *sharedData++ += (int32_t)rightSample * levels[1]; + } + //*/ + } + ++it; + } + else { + speaker->playing = false; + if (speaker != m_clientSpeaker) + speaker->decibelLevel = -96.0f; + it = m_activeSpeakers.erase(it); + } + } + } - if (mix) { - finalBuffer.resize(sharedBuffer.size(), 0); + if (mix) { + finalBuffer.resize(sharedBuffer.size(), 0); - float vol = m_outputAmplitude; - for (size_t i = 0; i != sharedBuffer.size(); ++i) - finalBuffer[i] = (int16_t)clamp(sharedBuffer[i] * vol, INT16_MIN, INT16_MAX); + float vol = m_outputAmplitude; + for (size_t i = 0; i != sharedBuffer.size(); ++i) + finalBuffer[i] = (int16_t)clamp(sharedBuffer[i] * vol, INT16_MIN, INT16_MAX); - SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finalBuffer.data(), AUDIO_S16, finalBuffer.size() * sizeof(int16_t), SDL_MIX_MAXVOLUME); - memset(sharedBuffer.data(), 0, sharedBuffer.size() * sizeof(int32_t)); - } + SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finalBuffer.data(), AUDIO_S16, finalBuffer.size() * sizeof(int16_t), SDL_MIX_MAXVOLUME); + memset(sharedBuffer.data(), 0, sharedBuffer.size() * sizeof(int32_t)); + } } void Voice::update(float, PositionalAttenuationFunction positionalAttenuationFunction) { for (auto& entry : m_speakers) { if (SpeakerPtr& speaker = entry.second) { - if (positionalAttenuationFunction) { - speaker->channelVolumes = { - 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), - 1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f) - }; - } - else - speaker->channelVolumes = Vec2F::filled(1.0f); - - auto& dbHistory = speaker->dbHistory; - memmove(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); - dbHistory[0] = speaker->decibelLevel; - float smoothDb = 0.0f; - for (float dB : dbHistory) - smoothDb += dB; + Vec2F newChannelVolumes = positionalAttenuationFunction ? Vec2F{ + 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), + 1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f) + } : Vec2F{1.0f, 1.0f}; + { + MutexLocker locker(speaker->mutex); + speaker->channelVolumes = newChannelVolumes; + } + auto& dbHistory = speaker->dbHistory; + memmove(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); + dbHistory[0] = speaker->decibelLevel; + float smoothDb = 0.0f; + for (float dB : dbHistory) + smoothDb += dB; - speaker->smoothDb = smoothDb / dbHistory.size(); + speaker->smoothDb = smoothDb / dbHistory.size(); } } if (m_nextSaveTime && Time::monotonicMilliseconds() > m_nextSaveTime) { - m_nextSaveTime = 0; + m_nextSaveTime = 0; save(); } } @@ -475,122 +478,122 @@ void Voice::setDeviceName(Maybe deviceName) { } StringList Voice::availableDevices() { - int devices = SDL_GetNumAudioDevices(1); - StringList deviceList; - if (devices > 0) { - deviceList.reserve(devices); - for (int i = 0; i != devices; ++i) - deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1)); - } - deviceList.sort(); - return deviceList; + int devices = SDL_GetNumAudioDevices(1); + StringList deviceList; + if (devices > 0) { + deviceList.reserve(devices); + for (int i = 0; i != devices; ++i) + deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1)); + } + deviceList.sort(); + return deviceList; } int Voice::send(DataStreamBuffer& out, size_t budget) { - out.setByteOrder(ByteOrder::LittleEndian); - out.write(VOICE_VERSION); + out.setByteOrder(ByteOrder::LittleEndian); + out.write(VOICE_VERSION); - MutexLocker encodeLock(m_encodeMutex); + MutexLocker encodeLock(m_encodeMutex); - if (m_encodedChunks.empty()) - return 0; + if (m_encodedChunks.empty()) + return 0; - std::vector encodedChunks = std::move(m_encodedChunks); - m_encodedChunksLength = 0; + std::vector encodedChunks = std::move(m_encodedChunks); + m_encodedChunksLength = 0; - encodeLock.unlock(); + encodeLock.unlock(); - for (auto& chunk : encodedChunks) { - out.write(chunk.size()); - out.writeBytes(chunk); - if (budget && (budget -= min(budget, chunk.size())) == 0) - break; - } + for (auto& chunk : encodedChunks) { + out.write(chunk.size()); + out.writeBytes(chunk); + if (budget && (budget -= min(budget, chunk.size())) == 0) + break; + } - m_lastSentTime = Time::monotonicMilliseconds(); - if (m_loopback) - receive(m_clientSpeaker, { out.ptr(), out.size() }); - return 1; + m_lastSentTime = Time::monotonicMilliseconds(); + if (m_loopback) + receive(m_clientSpeaker, { out.ptr(), out.size() }); + return 1; } bool Voice::receive(SpeakerPtr speaker, std::string_view view) { - if (!m_enabled || !speaker || view.empty()) - return false; + if (!m_enabled || !speaker || view.empty()) + return false; - try { - DataStreamExternalBuffer reader(view.data(), view.size()); - reader.setByteOrder(ByteOrder::LittleEndian); + try { + DataStreamExternalBuffer reader(view.data(), view.size()); + reader.setByteOrder(ByteOrder::LittleEndian); - if (reader.read() > VOICE_VERSION) - return false; + if (reader.read() > VOICE_VERSION) + return false; - uint32_t opusLength = 0; - while (!reader.atEnd()) { - reader >> opusLength; - if (reader.pos() + opusLength > reader.size()) - throw VoiceException("Opus packet length goes past end of buffer"s, false); - auto opusData = (unsigned char*)reader.ptr() + reader.pos(); - reader.seek(opusLength, IOSeek::Relative); + uint32_t opusLength = 0; + while (!reader.atEnd()) { + reader >> opusLength; + if (reader.pos() + opusLength > reader.size()) + throw VoiceException("Opus packet length goes past end of buffer"s, false); + auto opusData = (unsigned char*)reader.ptr() + reader.pos(); + reader.seek(opusLength, IOSeek::Relative); - int channels = opus_packet_get_nb_channels(opusData); - if (channels == OPUS_INVALID_PACKET) - continue; + int channels = opus_packet_get_nb_channels(opusData); + if (channels == OPUS_INVALID_PACKET) + continue; - bool mono = channels == 1; - OpusDecoder* decoder = mono ? speaker->decoderMono.get() : speaker->decoderStereo.get(); - int samples = opus_decoder_get_nb_samples(decoder, opusData, opusLength); - if (samples < 0) - throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); + bool mono = channels == 1; + OpusDecoder* decoder = mono ? speaker->decoderMono.get() : speaker->decoderStereo.get(); + int samples = opus_decoder_get_nb_samples(decoder, opusData, opusLength); + if (samples < 0) + throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); - m_decodeBuffer.resize(samples * (size_t)channels); + m_decodeBuffer.resize(samples * (size_t)channels); - int decodedSamples = opus_decode(decoder, opusData, opusLength, m_decodeBuffer.data(), m_decodeBuffer.size() * sizeof(int16_t), 0); - if (decodedSamples <= 0) { - if (decodedSamples < 0) - throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); - return true; - } + int decodedSamples = opus_decode(decoder, opusData, opusLength, m_decodeBuffer.data(), m_decodeBuffer.size() * sizeof(int16_t), 0); + if (decodedSamples <= 0) { + if (decodedSamples < 0) + throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); + return true; + } - //Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples * channels); + //Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples * channels); - speaker->audioStream->resample(m_decodeBuffer.data(), (size_t)decodedSamples * channels, m_resampleBuffer, mono); + speaker->audioStream->resample(m_decodeBuffer.data(), (size_t)decodedSamples * channels, m_resampleBuffer, mono); - { - MutexLocker lock(speaker->audioStream->mutex); - auto& samples = speaker->audioStream->samples; + { + MutexLocker lock(speaker->audioStream->mutex); + auto& samples = speaker->audioStream->samples; - auto now = Time::monotonicMilliseconds(); - if (now - speaker->lastReceiveTime < 1000) { - auto limit = (size_t)speaker->minimumPlaySamples + 22050; - if (samples.size() > limit) { // skip ahead if we're getting too far - for (size_t i = samples.size(); i >= limit; --i) - samples.pop(); - } - } - else - samples = std::queue(); + auto now = Time::monotonicMilliseconds(); + if (now - speaker->lastReceiveTime < 1000) { + auto limit = (size_t)speaker->minimumPlaySamples + 22050; + if (samples.size() > limit) { // skip ahead if we're getting too far + for (size_t i = samples.size(); i >= limit; --i) + samples.pop(); + } + } + else + samples = std::queue(); - speaker->lastReceiveTime = now; + speaker->lastReceiveTime = now; - if (mono) { - for (int16_t sample : m_resampleBuffer) { - samples.push(sample); - samples.push(sample); - } - } - else { - for (int16_t sample : m_resampleBuffer) - samples.push(sample); - } - } - playSpeaker(speaker, channels); - } - return true; - } - catch (StarException const& e) { - Logger::error("Voice: Error receiving voice data for speaker #{} ('{}'): {}", speaker->speakerId, speaker->name, e.what()); - return false; - } + if (mono) { + for (int16_t sample : m_resampleBuffer) { + samples.push(sample); + samples.push(sample); + } + } + else { + for (int16_t sample : m_resampleBuffer) + samples.push(sample); + } + } + playSpeaker(speaker, channels); + } + return true; + } + catch (StarException const& e) { + Logger::error("Voice: Error receiving voice data for speaker #{} ('{}'): {}", speaker->speakerId, speaker->name, e.what()); + return false; + } } void Voice::setInput(bool input) { @@ -617,31 +620,31 @@ OpusEncoder* Voice::createEncoder(int channels) { void Voice::resetEncoder() { int channels = encoderChannels(); - MutexLocker locker(m_threadMutex); + MutexLocker locker(m_threadMutex); m_encoder.reset(createEncoder(channels)); - int bitrate = m_bitrate > 0 ? (int)m_bitrate : (channels == 2 ? 50000 : 24000); + int bitrate = m_bitrate > 0 ? (int)m_bitrate : (channels == 2 ? 50000 : 24000); opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(bitrate)); } void Voice::resetDevice() { - if (shouldEnableInput()) - openDevice(); - else - closeDevice(); + if (shouldEnableInput()) + openDevice(); + else + closeDevice(); } void Voice::openDevice() { closeDevice(); m_applicationController->openAudioInputDevice( - m_deviceName ? m_deviceName->utf8Ptr() : nullptr, - VOICE_SAMPLE_RATE, - m_deviceChannels = encoderChannels(), - this, - [](void* userdata, uint8_t* stream, int len) { + m_deviceName ? m_deviceName->utf8Ptr() : nullptr, + VOICE_SAMPLE_RATE, + m_deviceChannels = encoderChannels(), + this, + [](void* userdata, uint8_t* stream, int len) { ((Voice*)(userdata))->readAudioData(stream, len); } - ); + ); m_deviceOpen = true; } @@ -651,81 +654,81 @@ void Voice::closeDevice() { return; m_applicationController->closeAudioInputDevice(); - m_clientSpeaker->playing = false; - m_clientSpeaker->decibelLevel = -96.0f; + m_clientSpeaker->playing = false; + m_clientSpeaker->decibelLevel = -96.0f; m_deviceOpen = false; } bool Voice::playSpeaker(SpeakerPtr const& speaker, int) { - if (speaker->playing || speaker->audioStream->samples.size() < speaker->minimumPlaySamples) - return false; + if (speaker->playing || speaker->audioStream->samples.size() < speaker->minimumPlaySamples) + return false; - if (!speaker->playing) { - speaker->lastPlayTime = Time::monotonicMilliseconds(); - speaker->playing = true; - MutexLocker lock(m_activeSpeakersMutex); - m_activeSpeakers.insert(speaker); - } - return true; + if (!speaker->playing) { + speaker->lastPlayTime = Time::monotonicMilliseconds(); + speaker->playing = true; + MutexLocker lock(m_activeSpeakersMutex); + m_activeSpeakers.insert(speaker); + } + return true; } void Voice::thread() { - while (true) { - MutexLocker locker(m_threadMutex); + while (true) { + MutexLocker locker(m_threadMutex); - m_threadCond.wait(m_threadMutex); - if (m_stopThread) - return; + m_threadCond.wait(m_threadMutex); + if (m_stopThread) + return; - { - MutexLocker locker(m_captureMutex); - ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0); - size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; - while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { - std::vector samples; - samples.reserve(frameSamples); - size_t samplesLeft = frameSamples; - while (samplesLeft && !m_capturedChunks.empty()) { - auto& front = m_capturedChunks.front(); - if (front.exhausted()) - m_capturedChunks.pop(); - else - samplesLeft -= front.takeSamples(samples, samplesLeft); - } - m_capturedChunksFrames -= VOICE_FRAME_SIZE; + { + MutexLocker locker(m_captureMutex); + ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0); + size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; + while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { + std::vector samples; + samples.reserve(frameSamples); + size_t samplesLeft = frameSamples; + while (samplesLeft && !m_capturedChunks.empty()) { + auto& front = m_capturedChunks.front(); + if (front.exhausted()) + m_capturedChunks.pop(); + else + samplesLeft -= front.takeSamples(samples, samplesLeft); + } + m_capturedChunksFrames -= VOICE_FRAME_SIZE; - if (m_inputAmplitude != 1.0f) { - for (size_t i = 0; i != samples.size(); ++i) - samples[i] *= m_inputAmplitude; - } + if (m_inputAmplitude != 1.0f) { + for (size_t i = 0; i != samples.size(); ++i) + samples[i] *= m_inputAmplitude; + } - if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { - if (encodedSize == 1) - continue; + if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { + if (encodedSize == 1) + continue; - encoded.resize(encodedSize); + encoded.resize(encodedSize); - { - MutexLocker lock(m_encodeMutex); - m_encodedChunks.emplace_back(std::move(encoded)); - m_encodedChunksLength += encodedSize; + { + MutexLocker locker(m_encodeMutex); + m_encodedChunks.emplace_back(std::move(encoded)); + m_encodedChunksLength += encodedSize; - encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0); - } + encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0); + } - //Logger::info("Voice: encoded Opus chunk {} samples -> {} bytes", frameSamples, encodedSize); - } - else if (encodedSize < 0) - Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); - } - } + //Logger::info("Voice: encoded Opus chunk {} samples -> {} bytes", frameSamples, encodedSize); + } + else if (encodedSize < 0) + Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); + } + } - continue; + continue; - locker.unlock(); - Thread::yield(); - } - return; + locker.unlock(); + Thread::yield(); + } + return; } } \ No newline at end of file diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index c914d36..59daeaa 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -89,7 +89,7 @@ public: atomic playing = 0; atomic decibelLevel = -96.0f; atomic volume = 1.0f; - atomic> channelVolumes = Array::filled(1); + Vec2F channelVolumes = Vec2F::filled(1.f); unsigned int minimumPlaySamples = 4096; diff --git a/source/game/CMakeLists.txt b/source/game/CMakeLists.txt index 3b85e2a..affe2b3 100644 --- a/source/game/CMakeLists.txt +++ b/source/game/CMakeLists.txt @@ -162,6 +162,7 @@ SET (star_game_HEADERS StarWeatherTypes.hpp StarWireProcessor.hpp StarWiring.hpp + StarWorldCamera.hpp StarWorldClient.hpp StarWorldClientState.hpp StarWorldGeneration.hpp @@ -231,6 +232,7 @@ SET (star_game_HEADERS objects/StarPhysicsObject.hpp objects/StarTeleporterObject.hpp + scripting/StarCameraLuaBindings.hpp scripting/StarCelestialLuaBindings.hpp scripting/StarBehaviorLuaBindings.hpp scripting/StarConfigLuaBindings.hpp @@ -421,6 +423,7 @@ SET (star_game_SOURCES StarWeatherTypes.cpp StarWireProcessor.cpp StarWiring.cpp + StarWorldCamera.cpp StarWorldClient.cpp StarWorldClientState.cpp StarWorldGeneration.cpp @@ -470,6 +473,7 @@ SET (star_game_SOURCES objects/StarPhysicsObject.cpp objects/StarTeleporterObject.cpp + scripting/StarCameraLuaBindings.cpp scripting/StarCelestialLuaBindings.cpp scripting/StarBehaviorLuaBindings.cpp scripting/StarConfigLuaBindings.cpp diff --git a/source/game/StarArmorWearer.cpp b/source/game/StarArmorWearer.cpp index acc3de2..89bebe3 100644 --- a/source/game/StarArmorWearer.cpp +++ b/source/game/StarArmorWearer.cpp @@ -350,13 +350,13 @@ void ArmorWearer::netElementsNeedLoad(bool) { m_backNeedsSync |= itemDatabase->loadItem(m_backCosmeticItemDataNetState.get(), m_backCosmeticItem); if (m_headItemDataNetState.pullUpdated()) - m_headNeedsSync |= !m_headCosmeticItem && itemDatabase->loadItem(m_headItemDataNetState.get(), m_headItem); + m_headNeedsSync |= itemDatabase->loadItem(m_headItemDataNetState.get(), m_headItem); if (m_chestItemDataNetState.pullUpdated()) - m_chestNeedsSync |= !m_chestCosmeticItem && itemDatabase->loadItem(m_chestItemDataNetState.get(), m_chestItem); + m_chestNeedsSync |= itemDatabase->loadItem(m_chestItemDataNetState.get(), m_chestItem); if (m_legsItemDataNetState.pullUpdated()) - m_legsNeedsSync |= !m_legsCosmeticItem && itemDatabase->loadItem(m_legsItemDataNetState.get(), m_legsItem); + m_legsNeedsSync |= itemDatabase->loadItem(m_legsItemDataNetState.get(), m_legsItem); if (m_backItemDataNetState.pullUpdated()) - m_backNeedsSync |= !m_backCosmeticItem && itemDatabase->loadItem(m_backItemDataNetState.get(), m_backItem); + m_backNeedsSync |= itemDatabase->loadItem(m_backItemDataNetState.get(), m_backItem); } void ArmorWearer::netElementsNeedStore() { diff --git a/source/game/StarClientContext.cpp b/source/game/StarClientContext.cpp index a91c40e..3061368 100644 --- a/source/game/StarClientContext.cpp +++ b/source/game/StarClientContext.cpp @@ -77,11 +77,12 @@ ShipUpgrades ClientContext::shipUpgrades() const { return m_shipUpgrades.get(); } -void ClientContext::readUpdate(ByteArray data) { +void ClientContext::readUpdate(ByteArray data, NetCompatibilityRules rules) { if (data.empty()) return; DataStreamBuffer ds(std::move(data)); + ds.setStreamCompatibilityVersion(rules); m_rpc->receive(ds.read()); @@ -89,10 +90,10 @@ void ClientContext::readUpdate(ByteArray data) { if (!shipUpdates.empty()) m_newShipUpdates.merge(DataStreamBuffer::deserialize(std::move(shipUpdates)), true); - m_netGroup.readNetState(ds.read()); + m_netGroup.readNetState(ds.read(), 0.0f, rules); } -ByteArray ClientContext::writeUpdate() { +ByteArray ClientContext::writeUpdate(NetCompatibilityRules rules) { return m_rpc->send(); } @@ -104,4 +105,12 @@ ConnectionId ClientContext::connectionId() const { return m_connectionId; } +void ClientContext::setNetCompatibilityRules(NetCompatibilityRules netCompatibilityRules) { + m_netCompatibilityRules = netCompatibilityRules; +} + +NetCompatibilityRules ClientContext::netCompatibilityRules() const { + return m_netCompatibilityRules; +} + } diff --git a/source/game/StarClientContext.hpp b/source/game/StarClientContext.hpp index 5cdd727..9c08d32 100644 --- a/source/game/StarClientContext.hpp +++ b/source/game/StarClientContext.hpp @@ -40,16 +40,20 @@ public: WorldChunks newShipUpdates(); ShipUpgrades shipUpgrades() const; - void readUpdate(ByteArray data); - ByteArray writeUpdate(); + void readUpdate(ByteArray data, NetCompatibilityRules rules); + ByteArray writeUpdate(NetCompatibilityRules rules); void setConnectionId(ConnectionId connectionId); ConnectionId connectionId() const; + void setNetCompatibilityRules(NetCompatibilityRules netCompatibilityRules); + NetCompatibilityRules netCompatibilityRules() const; + private: Uuid m_serverUuid; Uuid m_playerUuid; ConnectionId m_connectionId = 0; + NetCompatibilityRules m_netCompatibilityRules; JsonRpcPtr m_rpc; diff --git a/source/game/StarCommandProcessor.cpp b/source/game/StarCommandProcessor.cpp index b5beb20..74fc33a 100644 --- a/source/game/StarCommandProcessor.cpp +++ b/source/game/StarCommandProcessor.cpp @@ -262,16 +262,33 @@ String CommandProcessor::setTileProtection(ConnectionId connectionId, String con return "Not enough arguments to /settileprotection. Use /settileprotection "; try { - DungeonId dungeonId = lexicalCast(arguments.at(0)); - bool isProtected = lexicalCast(arguments.at(1)); - - bool done = m_universe->executeForClient(connectionId, [dungeonId, isProtected](WorldServer* world, PlayerPtr const&) { - world->setTileProtection(dungeonId, isProtected); - }); - - return done ? "" : "Failed to set block protection."; + bool isProtected = Json::parse(arguments.takeLast()).toBool(); + List dungeonIds; + for (auto& banana : arguments) { + auto slices = banana.split(".."); + auto it = slices.begin(); + DungeonId previous = 0; + while (it != slices.end()) { + DungeonId current = lexicalCast(*it); + dungeonIds.append(current); + if (it++ != slices.begin() && previous != current) { + if (current < previous) swap(previous, current); + for (DungeonId id = previous + 1; id != current; ++id) + dungeonIds.append(id); + } + previous = current; + } + } + size_t changed = 0; + if (!m_universe->executeForClient(connectionId, [&](WorldServer* world, PlayerPtr const&) { + changed = world->setTileProtection(dungeonIds, isProtected); + })) { + return "Invalid client state"; + } + String output = strf("{} {} dungeon IDs", isProtected ? "Protected" : "Unprotected", changed); + return changed < dungeonIds.size() ? strf("{} ({} unchanged)", output, dungeonIds.size() - changed) : output; } catch (BadLexicalCast const&) { - return strf("Could not parse /settileprotection parameters. Use /settileprotection ", argumentString); + return strf("Could not parse /settileprotection parameters. Use /settileprotection ", argumentString); } } diff --git a/source/game/StarDungeonGenerator.cpp b/source/game/StarDungeonGenerator.cpp index 8b2c9c2..7905fee 100644 --- a/source/game/StarDungeonGenerator.cpp +++ b/source/game/StarDungeonGenerator.cpp @@ -1429,27 +1429,28 @@ DungeonGenerator::DungeonGenerator(String const& dungeonName, uint64_t seed, flo } Maybe, Set>> DungeonGenerator::generate(DungeonGeneratorWorldFacadePtr facade, Vec2I position, bool markSurfaceAndTerrain, bool forcePlacement) { + String name = m_def->name(); try { Dungeon::DungeonGeneratorWriter writer(facade, markSurfaceAndTerrain ? position[1] : Maybe(), m_def->extendSurfaceFreeSpace()); - Logger::debug(forcePlacement ? "Forcing generation of dungeon {}" : "Generating dungeon {}", m_def->name()); + Logger::debug(forcePlacement ? "Forcing generation of dungeon {}" : "Generating dungeon {}", name); Dungeon::PartConstPtr anchor = pickAnchor(); if (!anchor) { - Logger::error("No valid anchor piece found for dungeon at {}", position); + Logger::error("No valid anchor piece found for dungeon {} at {}", name, position); return {}; } auto pos = position + Vec2I(0, -anchor->placementLevelConstraint()); if (forcePlacement || anchor->canPlace(pos, &writer)) { - Logger::info("Placing dungeon at {}", position); + Logger::info("Placing dungeon {} at {}", name, position); return buildDungeon(anchor, pos, &writer, forcePlacement); } else { - Logger::debug("Failed to place a dungeon at {}", position); + Logger::debug("Failed to place dungeon {} at {}", name, position); return {}; } } catch (std::exception const& e) { - throw DungeonException(strf("Error generating dungeon named '{}'", m_def->name()), e); + throw DungeonException(strf("Error generating dungeon named '{}'", name), e); } } diff --git a/source/game/StarEffectEmitter.cpp b/source/game/StarEffectEmitter.cpp index ddb0355..28e2986 100644 --- a/source/game/StarEffectEmitter.cpp +++ b/source/game/StarEffectEmitter.cpp @@ -94,14 +94,14 @@ void EffectEmitter::render(RenderCallback* renderCallback) { Json EffectEmitter::toJson() const { return JsonObject{{"activeSources", - jsonFromSet>(m_activeSources.get(), + jsonFromSet>>(m_activeSources.get(), [](pair const& entry) { return JsonObject{{"position", entry.first}, {"source", entry.second}}; })}}; } void EffectEmitter::fromJson(Json const& diskStore) { - m_activeSources.set(jsonToSet>(diskStore.get("activeSources"), + m_activeSources.set(jsonToSet>>(diskStore.get("activeSources"), [](Json const& v) { return pair{v.getString("position"), v.getString("source")}; })); diff --git a/source/game/StarEntityFactory.cpp b/source/game/StarEntityFactory.cpp index 333dec1..0202ea7 100644 --- a/source/game/StarEntityFactory.cpp +++ b/source/game/StarEntityFactory.cpp @@ -40,57 +40,57 @@ EntityFactory::EntityFactory() { m_versioningDatabase = root.versioningDatabase(); } -ByteArray EntityFactory::netStoreEntity(EntityPtr const& entity) const { +ByteArray EntityFactory::netStoreEntity(EntityPtr const& entity, NetCompatibilityRules rules) const { RecursiveMutexLocker locker(m_mutex); if (auto player = as(entity)) { - return player->netStore(); + return player->netStore(rules); } else if (auto monster = as(entity)) { - return monster->netStore(); + return monster->netStore(rules); } else if (auto object = as(entity)) { - return object->netStore(); + return object->netStore(rules); } else if (auto plant = as(entity)) { - return plant->netStore(); + return plant->netStore(rules); } else if (auto plantDrop = as(entity)) { - return plantDrop->netStore(); + return plantDrop->netStore(rules); } else if (auto projectile = as(entity)) { - return projectile->netStore(); + return projectile->netStore(rules); } else if (auto itemDrop = as(entity)) { - return itemDrop->netStore(); + return itemDrop->netStore(rules); } else if (auto npc = as(entity)) { - return npc->netStore(); + return npc->netStore(rules); } else if (auto stagehand = as(entity)) { - return stagehand->netStore(); + return stagehand->netStore(rules); } else if (auto vehicle = as(entity)) { - return m_vehicleDatabase->netStore(vehicle); + return m_vehicleDatabase->netStore(vehicle, rules); } else { throw EntityFactoryException::format("Don't know how to make net store for entity type '{}'", EntityTypeNames.getRight(entity->entityType())); } } -EntityPtr EntityFactory::netLoadEntity(EntityType type, ByteArray const& netStore) const { +EntityPtr EntityFactory::netLoadEntity(EntityType type, ByteArray const& netStore, NetCompatibilityRules rules) const { RecursiveMutexLocker locker(m_mutex); if (type == EntityType::Player) { - return m_playerFactory->netLoadPlayer(netStore); + return m_playerFactory->netLoadPlayer(netStore, rules); } else if (type == EntityType::Monster) { - return m_monsterDatabase->netLoadMonster(netStore); + return m_monsterDatabase->netLoadMonster(netStore, rules); } else if (type == EntityType::Object) { - return m_objectDatabase->netLoadObject(netStore); + return m_objectDatabase->netLoadObject(netStore, rules); } else if (type == EntityType::Plant) { - return make_shared(netStore); + return make_shared(netStore, rules); } else if (type == EntityType::PlantDrop) { - return make_shared(netStore); + return make_shared(netStore, rules); } else if (type == EntityType::Projectile) { - return m_projectileDatabase->netLoadProjectile(netStore); + return m_projectileDatabase->netLoadProjectile(netStore, rules); } else if (type == EntityType::ItemDrop) { - return make_shared(netStore); + return make_shared(netStore, rules); } else if (type == EntityType::Npc) { - return m_npcDatabase->netLoadNpc(netStore); + return m_npcDatabase->netLoadNpc(netStore, rules); } else if (type == EntityType::Stagehand) { - return make_shared(netStore); + return make_shared(netStore, rules); } else if (type == EntityType::Vehicle) { - return m_vehicleDatabase->netLoad(netStore); + return m_vehicleDatabase->netLoad(netStore, rules); } else { throw EntityFactoryException::format("Don't know how to create entity type '{}' from net store", EntityTypeNames.getRight(type)); } diff --git a/source/game/StarEntityFactory.hpp b/source/game/StarEntityFactory.hpp index 09d4e99..7d5c014 100644 --- a/source/game/StarEntityFactory.hpp +++ b/source/game/StarEntityFactory.hpp @@ -20,8 +20,8 @@ class EntityFactory { public: EntityFactory(); - ByteArray netStoreEntity(EntityPtr const& entity) const; - EntityPtr netLoadEntity(EntityType type, ByteArray const& netStore) const; + ByteArray netStoreEntity(EntityPtr const& entity, NetCompatibilityRules rules = {}) const; + EntityPtr netLoadEntity(EntityType type, ByteArray const& netStore, NetCompatibilityRules rules = {}) const; Json diskStoreEntity(EntityPtr const& entity) const; EntityPtr diskLoadEntity(EntityType type, Json const& diskStore) const; diff --git a/source/game/StarHumanoid.cpp b/source/game/StarHumanoid.cpp index 47368b1..2acc632 100644 --- a/source/game/StarHumanoid.cpp +++ b/source/game/StarHumanoid.cpp @@ -242,7 +242,58 @@ EnumMap const Humanoid::StateNames{ {Humanoid::State::Lay, "lay"}, }; +// gross, but I don't want to make config calls more than I need to +bool& Humanoid::globalHeadRotation() { + static Maybe s_headRotation; + if (!s_headRotation) + s_headRotation = Root::singleton().configuration()->get("humanoidHeadRotation").optBool().value(true); + return *s_headRotation; +}; + Humanoid::Humanoid(Json const& config) { + loadConfig(config); + + m_twoHanded = false; + m_primaryHand.holdingItem = false; + m_altHand.holdingItem = false; + + m_movingBackwards = false; + m_altHand.angle = 0; + m_facingDirection = Direction::Left; + m_headRotationTarget = m_headRotation = m_rotation = 0; + m_scale = Vec2F::filled(1.f); + m_drawVaporTrail = false; + m_state = State::Idle; + m_emoteState = HumanoidEmote::Idle; + m_dance = {}; + + m_primaryHand.angle = 0; + m_animationTimer = m_emoteAnimationTimer = m_danceTimer = 0.0f; +} + +Humanoid::Humanoid(HumanoidIdentity const& identity) + : Humanoid(Root::singleton().speciesDatabase()->species(identity.species)->humanoidConfig()) { + setIdentity(identity); +} + +void Humanoid::setIdentity(HumanoidIdentity const& identity) { + m_identity = identity; + m_headFrameset = getHeadFromIdentity(); + m_bodyFrameset = getBodyFromIdentity(); + m_emoteFrameset = getFacialEmotesFromIdentity(); + m_hairFrameset = getHairFromIdentity(); + m_facialHairFrameset = getFacialHairFromIdentity(); + m_facialMaskFrameset = getFacialMaskFromIdentity(); + m_backArmFrameset = getBackArmFromIdentity(); + m_frontArmFrameset = getFrontArmFromIdentity(); + m_vaporTrailFrameset = getVaporTrailFrameset(); +} + +HumanoidIdentity const& Humanoid::identity() const { + return m_identity; +} + +void Humanoid::loadConfig(Json const& config) { m_timing = HumanoidTiming(config.getObject("humanoidTiming")); m_globalOffset = jsonToVec2F(config.get("globalOffset")) / TilePixels; @@ -271,10 +322,13 @@ Humanoid::Humanoid(Json const& config) { m_armWalkSeq = jsonToIntList(config.get("armWalkSeq")); m_armRunSeq = jsonToIntList(config.get("armRunSeq")); + m_walkBob.clear(); for (auto const& v : config.get("walkBob").toArray()) m_walkBob.append(v.toDouble() / TilePixels); + m_runBob.clear(); for (auto const& v : config.get("runBob").toArray()) m_runBob.append(v.toDouble() / TilePixels); + m_swimBob.clear(); for (auto const& v : config.get("swimBob").toArray()) m_swimBob.append(v.toDouble() / TilePixels); @@ -290,46 +344,6 @@ Humanoid::Humanoid(Json const& config) { m_particleEmitters = config.get("particleEmitters"); m_defaultMovementParameters = config.get("movementParameters"); - - m_twoHanded = false; - m_primaryHand.holdingItem = false; - m_altHand.holdingItem = false; - - m_movingBackwards = false; - m_altHand.angle = 0; - m_facingDirection = Direction::Left; - m_rotation = 0; - m_scale = Vec2F::filled(1.f); - m_drawVaporTrail = false; - m_state = State::Idle; - m_emoteState = HumanoidEmote::Idle; - m_dance = {}; - m_emoteAnimationTimer = 0; - - m_primaryHand.angle = 0; - m_animationTimer = 0.0f; -} - -Humanoid::Humanoid(HumanoidIdentity const& identity) - : Humanoid(Root::singleton().speciesDatabase()->species(identity.species)->humanoidConfig()) { - setIdentity(identity); -} - -void Humanoid::setIdentity(HumanoidIdentity const& identity) { - m_identity = identity; - m_headFrameset = getHeadFromIdentity(); - m_bodyFrameset = getBodyFromIdentity(); - m_emoteFrameset = getFacialEmotesFromIdentity(); - m_hairFrameset = getHairFromIdentity(); - m_facialHairFrameset = getFacialHairFromIdentity(); - m_facialMaskFrameset = getFacialMaskFromIdentity(); - m_backArmFrameset = getBackArmFromIdentity(); - m_frontArmFrameset = getFrontArmFromIdentity(); - m_vaporTrailFrameset = getVaporTrailFrameset(); -} - -HumanoidIdentity const& Humanoid::identity() const { - return m_identity; } void Humanoid::setHeadArmorDirectives(Directives directives) { @@ -376,6 +390,46 @@ void Humanoid::setHelmetMaskDirectives(Directives helmetMaskDirectives) { m_helmetMaskDirectives = std::move(helmetMaskDirectives); } +Directives const& Humanoid::headArmorDirectives() const { + return m_headArmorDirectives; +}; + +String const& Humanoid::headArmorFrameset() const { + return m_headArmorFrameset; +}; + +Directives const& Humanoid::chestArmorDirectives() const { + return m_chestArmorDirectives; +}; + +String const& Humanoid::chestArmorFrameset() const { + return m_chestArmorFrameset; +}; + +String const& Humanoid::backSleeveFrameset() const { + return m_backSleeveFrameset; +}; + +String const& Humanoid::frontSleeveFrameset() const { + return m_frontSleeveFrameset; +}; + +Directives const& Humanoid::legsArmorDirectives() const { + return m_legsArmorDirectives; +}; + +String const& Humanoid::legsArmorFrameset() const { + return m_legsArmorFrameset; +}; + +Directives const& Humanoid::backArmorDirectives() const { + return m_backArmorDirectives; +}; + +String const& Humanoid::backArmorFrameset() const { + return m_backArmorFrameset; +}; + void Humanoid::setBodyHidden(bool hidden) { m_bodyHidden = hidden; } @@ -406,6 +460,10 @@ void Humanoid::setMovingBackwards(bool movingBackwards) { m_movingBackwards = movingBackwards; } +void Humanoid::setHeadRotation(float headRotation) { + m_headRotationTarget = headRotation; +} + void Humanoid::setRotation(float rotation) { m_rotation = rotation; } @@ -470,6 +528,10 @@ void Humanoid::setPrimaryHandNonRotatedDrawables(List drawables) { m_primaryHand.nonRotatedDrawables = std::move(drawables); } +bool Humanoid::primaryHandHoldingItem() const { + return m_primaryHand.holdingItem; +} + void Humanoid::setAltHandParameters(bool holdingItem, float angle, float itemAngle, bool recoil, bool outsideOfHand) { m_altHand.holdingItem = holdingItem; @@ -492,16 +554,24 @@ void Humanoid::setAltHandNonRotatedDrawables(List drawables) { m_altHand.nonRotatedDrawables = std::move(drawables); } +bool Humanoid::altHandHoldingItem() const { + return m_altHand.holdingItem; +} + void Humanoid::animate(float dt) { m_animationTimer += dt; m_emoteAnimationTimer += dt; m_danceTimer += dt; + float headRotationTarget = globalHeadRotation() ? m_headRotationTarget : 0.f; + float diff = angleDiff(m_headRotation, headRotationTarget); + m_headRotation = (headRotationTarget - (headRotationTarget - m_headRotation) * powf(.333333f, dt * 60.f)); } void Humanoid::resetAnimation() { m_animationTimer = 0.0f; m_emoteAnimationTimer = 0.0f; m_danceTimer = 0.0f; + m_headRotation = globalHeadRotation() ? 0.f : m_headRotationTarget; } List Humanoid::render(bool withItems, bool withRotationAndScale) { @@ -538,7 +608,7 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { }; auto backArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable { - String image = strf("{}:{}", frameSet, backHand.backFrame); + String image = strf("{}:{}{}", frameSet, backHand.backFrame, directives.prefix()); Drawable backArm = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, backArmFrameOffset); backArm.imagePart().addDirectives(directives, true); backArm.rotate(backHand.angle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset); @@ -547,15 +617,16 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { if (!m_backArmorFrameset.empty()) { auto frameGroup = frameBase(m_state); + auto prefix = m_backArmorDirectives.prefix(); if (m_movingBackwards && (m_state == State::Run)) frameGroup = "runbackwards"; String image; if (dance.isValid() && danceStep->bodyFrame) - image = strf("{}:{}", m_backArmorFrameset, *danceStep->bodyFrame); + image = strf("{}:{}{}", m_backArmorFrameset, *danceStep->bodyFrame, prefix); else if (m_state == Idle) - image = strf("{}:{}", m_backArmorFrameset, m_identity.personality.idle); + image = strf("{}:{}{}", m_backArmorFrameset, m_identity.personality.idle, prefix); else - image = strf("{}:{}.{}", m_backArmorFrameset, frameGroup, bodyStateSeq); + image = strf("{}:{}.{}{}", m_backArmorFrameset, frameGroup, bodyStateSeq, prefix); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, Vec2F()); drawable.imagePart().addDirectives(getBackDirectives(), true); @@ -582,16 +653,18 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { if (!m_backArmFrameset.empty() && !m_bodyHidden) { String image; Vec2F position; + auto bodyDirectives = getBodyDirectives(); + auto prefix = bodyDirectives.prefix(); if (dance.isValid() && danceStep->backArmFrame) { - image = strf("{}:{}", m_backArmFrameset, *danceStep->backArmFrame); + image = strf("{}:{}{}", m_backArmFrameset, *danceStep->backArmFrame, prefix); position = danceStep->backArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("{}:{}", m_backArmFrameset, m_identity.personality.armIdle); + image = strf("{}:{}{}", m_backArmFrameset, m_identity.personality.armIdle, prefix); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("{}:{}.{}", m_backArmFrameset, frameBase(m_state), armStateSeq); + image = strf("{}:{}.{}{}", m_backArmFrameset, frameBase(m_state), armStateSeq, prefix); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position); - drawable.imagePart().addDirectives(getBodyDirectives(), true); + drawable.imagePart().addDirectives(bodyDirectives, true); if (dance.isValid()) drawable.rotate(danceStep->backArmRotation); addDrawable(std::move(drawable), m_bodyFullbright); @@ -599,14 +672,15 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { if (!m_backSleeveFrameset.empty()) { String image; Vec2F position; + auto prefix = m_chestArmorDirectives.prefix(); if (dance.isValid() && danceStep->backArmFrame) { - image = strf("{}:{}", m_backSleeveFrameset, *danceStep->backArmFrame); + image = strf("{}:{}{}", m_backSleeveFrameset, *danceStep->backArmFrame, prefix); position = danceStep->backArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("{}:{}", m_backSleeveFrameset, m_identity.personality.armIdle); + image = strf("{}:{}{}", m_backSleeveFrameset, m_identity.personality.armIdle, prefix); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("{}:{}.{}", m_backSleeveFrameset, frameBase(m_state), armStateSeq); + image = strf("{}:{}.{}{}", m_backSleeveFrameset, frameBase(m_state), armStateSeq, prefix); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position); drawable.imagePart().addDirectives(getChestDirectives(), true); if (dance.isValid()) @@ -631,48 +705,69 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { else if (m_state == Lay) headPosition += m_headLayOffset; + auto addHeadDrawable = [&](Drawable drawable, bool forceFullbright = false) { + if (m_facingDirection == Direction::Left) + drawable.scale(Vec2F(-1, 1)); + drawable.fullbright |= forceFullbright; + if (m_headRotation != 0.f) { + float dir = numericalDirection(m_facingDirection); + Vec2F rotationPoint = headPosition; + rotationPoint[0] *= dir; + rotationPoint[1] -= .25f; + float headX = (m_headRotation / ((float)Constants::pi * 2.f)); + drawable.rotate(m_headRotation, rotationPoint); + drawable.position[0] -= state() == State::Run ? (fmaxf(headX * dir, 0.f) * 2.f) * dir : headX; + drawable.position[1] -= fabsf(m_headRotation / ((float)Constants::pi * 4.f)); + } + drawables.append(std::move(drawable)); + }; + if (!m_headFrameset.empty() && !m_bodyHidden) { String image = strf("{}:normal", m_headFrameset); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition); drawable.imagePart().addDirectives(getBodyDirectives(), true); - addDrawable(std::move(drawable), m_bodyFullbright); + addHeadDrawable(std::move(drawable), m_bodyFullbright); } if (!m_emoteFrameset.empty() && !m_bodyHidden) { - String image = strf("{}:{}.{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq); + auto emoteDirectives = getEmoteDirectives(); + String image = strf("{}:{}.{}{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, emoteDirectives.prefix()); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition); - drawable.imagePart().addDirectives(getEmoteDirectives(), true); - addDrawable(std::move(drawable), m_bodyFullbright); + drawable.imagePart().addDirectives(emoteDirectives, true); + addHeadDrawable(std::move(drawable), m_bodyFullbright); } if (!m_hairFrameset.empty() && !m_bodyHidden) { String image = strf("{}:normal", m_hairFrameset); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition); drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true); - addDrawable(std::move(drawable), m_bodyFullbright); + addHeadDrawable(std::move(drawable), m_bodyFullbright); } if (!m_bodyFrameset.empty() && !m_bodyHidden) { String image; + auto bodyDirectives = getBodyDirectives(); + auto prefix = bodyDirectives.prefix(); if (dance.isValid() && danceStep->bodyFrame) - image = strf("{}:{}", m_bodyFrameset, *danceStep->bodyFrame); + image = strf("{}:{}{}", m_bodyFrameset, *danceStep->bodyFrame, prefix); else if (m_state == Idle) - image = strf("{}:{}", m_bodyFrameset, m_identity.personality.idle); + image = strf("{}:{}{}", m_bodyFrameset, m_identity.personality.idle, prefix); else - image = strf("{}:{}.{}", m_bodyFrameset, frameBase(m_state), bodyStateSeq); + image = strf("{}:{}.{}{}", m_bodyFrameset, frameBase(m_state), bodyStateSeq, prefix); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, {}); - drawable.imagePart().addDirectives(getBodyDirectives(), true); + drawable.imagePart().addDirectives(bodyDirectives, true); addDrawable(std::move(drawable), m_bodyFullbright); } if (!m_legsArmorFrameset.empty()) { String image; + auto prefix = m_legsArmorDirectives.prefix(); if (dance.isValid() && danceStep->bodyFrame) - image = strf("{}:{}", m_legsArmorFrameset, *danceStep->bodyFrame); + image = strf("{}:{}{}", m_legsArmorFrameset, *danceStep->bodyFrame, prefix); else if (m_state == Idle) - image = strf("{}:{}", m_legsArmorFrameset, m_identity.personality.idle); + image = strf("{}:{}{}", m_legsArmorFrameset, m_identity.personality.idle, prefix); else - image = strf("{}:{}.{}", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq); + image = strf("{}:{}.{}{}", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq, prefix); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, {}); drawable.imagePart().addDirectives(getLegsDirectives(), true); addDrawable(std::move(drawable)); @@ -681,18 +776,19 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { if (!m_chestArmorFrameset.empty()) { String image; Vec2F position; + auto prefix = m_chestArmorDirectives.prefix(); if (dance.isValid() && danceStep->bodyFrame) - image = strf("{}:{}", m_chestArmorFrameset, *danceStep->bodyFrame); + image = strf("{}:{}{}", m_chestArmorFrameset, *danceStep->bodyFrame, prefix); else if (m_state == Run) - image = strf("{}:run", m_chestArmorFrameset); + image = strf("{}:run{}", m_chestArmorFrameset, prefix); else if (m_state == Idle) - image = strf("{}:{}", m_chestArmorFrameset, m_identity.personality.idle); + image = strf("{}:{}{}", m_chestArmorFrameset, m_identity.personality.idle, prefix); else if (m_state == Duck) - image = strf("{}:duck", m_chestArmorFrameset); + image = strf("{}:duck{}", m_chestArmorFrameset, prefix); else if ((m_state == Swim) || (m_state == SwimIdle)) - image = strf("{}:swim", m_chestArmorFrameset); + image = strf("{}:swim{}", m_chestArmorFrameset, prefix); else - image = strf("{}:chest.1", m_chestArmorFrameset); + image = strf("{}:chest.1{}", m_chestArmorFrameset, prefix); if (m_state != Duck) position[1] += bobYOffset; auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position); @@ -704,25 +800,25 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { String image = strf("{}:normal", m_facialHairFrameset); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition); drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true); - addDrawable(std::move(drawable), m_bodyFullbright); + addHeadDrawable(std::move(drawable), m_bodyFullbright); } if (!m_facialMaskFrameset.empty() && !m_bodyHidden) { String image = strf("{}:normal", m_facialMaskFrameset); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition); drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(getHelmetMaskDirectives(), true); - addDrawable(std::move(drawable)); + addHeadDrawable(std::move(drawable)); } if (!m_headArmorFrameset.empty()) { - String image = strf("{}:normal", m_headArmorFrameset); + String image = strf("{}:normal{}", m_headArmorFrameset, m_headArmorDirectives.prefix()); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition); drawable.imagePart().addDirectives(getHeadDirectives(), true); - addDrawable(std::move(drawable)); + addHeadDrawable(std::move(drawable)); } auto frontArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable { - String image = strf("{}:{}", frameSet, frontHand.frontFrame); + String image = strf("{}:{}{}", frameSet, frontHand.frontFrame, directives.prefix()); Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset); frontArm.imagePart().addDirectives(directives, true); frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter); @@ -749,16 +845,18 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { if (!m_frontArmFrameset.empty() && !m_bodyHidden) { String image; Vec2F position; + auto bodyDirectives = getBodyDirectives(); + auto prefix = bodyDirectives.prefix(); if (dance.isValid() && danceStep->frontArmFrame) { - image = strf("{}:{}", m_frontArmFrameset, *danceStep->frontArmFrame); + image = strf("{}:{}{}", m_frontArmFrameset, *danceStep->frontArmFrame, prefix); position = danceStep->frontArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("{}:{}", m_frontArmFrameset, m_identity.personality.armIdle); + image = strf("{}:{}{}", m_frontArmFrameset, m_identity.personality.armIdle, prefix); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("{}:{}.{}", m_frontArmFrameset, frameBase(m_state), armStateSeq); + image = strf("{}:{}.{}{}", m_frontArmFrameset, frameBase(m_state), armStateSeq, prefix); auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position); - drawable.imagePart().addDirectives(getBodyDirectives(), true); + drawable.imagePart().addDirectives(bodyDirectives, true); if (dance.isValid()) drawable.rotate(danceStep->frontArmRotation); addDrawable(drawable, m_bodyFullbright); @@ -767,14 +865,15 @@ List Humanoid::render(bool withItems, bool withRotationAndScale) { if (!m_frontSleeveFrameset.empty()) { String image; Vec2F position; + auto prefix = m_chestArmorDirectives.prefix(); if (dance.isValid() && danceStep->frontArmFrame) { - image = strf("{}:{}", m_frontSleeveFrameset, *danceStep->frontArmFrame); + image = strf("{}:{}{}", m_frontSleeveFrameset, *danceStep->frontArmFrame, prefix); position = danceStep->frontArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("{}:{}", m_frontSleeveFrameset, m_identity.personality.armIdle); + image = strf("{}:{}{}", m_frontSleeveFrameset, m_identity.personality.armIdle, prefix); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("{}:{}.{}", m_frontSleeveFrameset, frameBase(m_state), armStateSeq); + image = strf("{}:{}.{}{}", m_frontSleeveFrameset, frameBase(m_state), armStateSeq, prefix); auto drawable = Drawable::makeImage(image, 1.0f / TilePixels, true, position); drawable.imagePart().addDirectives(getChestDirectives(), true); if (dance.isValid()) @@ -834,104 +933,118 @@ List Humanoid::renderPortrait(PortraitMode mode) const { if (mode != PortraitMode::Head) { if (!m_backArmFrameset.empty()) { - String image = strf("{}:{}", m_backArmFrameset, personality.armIdle); + auto bodyDirectives = getBodyDirectives(); + String image = strf("{}:{}{}", m_backArmFrameset, personality.armIdle, bodyDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getBodyDirectives(), true); + drawable.imagePart().addDirectives(bodyDirectives, true); addDrawable(std::move(drawable)); } if (dressed && !m_backSleeveFrameset.empty()) { - String image = strf("{}:{}", m_backSleeveFrameset, personality.armIdle); + auto chestDirectives = getChestDirectives(); + String image = strf("{}:{}{}", m_backSleeveFrameset, personality.armIdle, chestDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getChestDirectives(), true); + drawable.imagePart().addDirectives(chestDirectives, true); addDrawable(std::move(drawable)); } if (mode != PortraitMode::Bust) { if (dressed && !m_backArmorFrameset.empty()) { - String image = strf("{}:{}", m_backArmorFrameset, personality.idle); + auto backDirectives = getBackDirectives(); + String image = strf("{}:{}{}", m_backArmorFrameset, personality.idle, backDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getBackDirectives(), true); + drawable.imagePart().addDirectives(backDirectives, true); addDrawable(std::move(drawable)); } } } if (!m_headFrameset.empty()) { - String image = strf("{}:normal", m_headFrameset); + auto bodyDirectives = getBodyDirectives(); + String image = strf("{}:normal{}", m_headFrameset, bodyDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getBodyDirectives(), true); + drawable.imagePart().addDirectives(bodyDirectives, true); addDrawable(std::move(drawable)); } if (!m_emoteFrameset.empty()) { - String image = strf("{}:{}.{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq); + auto emoteDirectives = getEmoteDirectives(); + String image = strf("{}:{}.{}{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, emoteDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getEmoteDirectives(), true); + drawable.imagePart().addDirectives(emoteDirectives, true); addDrawable(std::move(drawable)); } if (!m_hairFrameset.empty()) { - String image = strf("{}:normal", m_hairFrameset); + auto hairDirectives = getHairDirectives(); + String image = strf("{}:normal{}", m_hairFrameset, hairDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(helmetMaskDirective, true); + drawable.imagePart().addDirectives(hairDirectives, true).addDirectives(helmetMaskDirective, true); addDrawable(std::move(drawable)); } if (!m_bodyFrameset.empty()) { - String image = strf("{}:{}", m_bodyFrameset, personality.idle); + auto bodyDirectives = getBodyDirectives(); + String image = strf("{}:{}{}", m_bodyFrameset, personality.idle, bodyDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getBodyDirectives(), true); + drawable.imagePart().addDirectives(bodyDirectives, true); addDrawable(std::move(drawable)); } if (mode != PortraitMode::Head) { if (dressed && !m_legsArmorFrameset.empty()) { - String image = strf("{}:{}", m_legsArmorFrameset, personality.idle); + auto legsDirectives = getLegsDirectives(); + String image = strf("{}:{}{}", m_legsArmorFrameset, personality.idle, legsDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getLegsDirectives(), true); + drawable.imagePart().addDirectives(legsDirectives, true); addDrawable(std::move(drawable)); } if (dressed && !m_chestArmorFrameset.empty()) { - String image = strf("{}:{}", m_chestArmorFrameset, personality.idle); + auto chestDirectives = getChestDirectives(); + String image = strf("{}:{}{}", m_chestArmorFrameset, personality.idle, chestDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getChestDirectives(), true); + drawable.imagePart().addDirectives(chestDirectives, true); addDrawable(std::move(drawable)); } } if (!m_facialHairFrameset.empty()) { - String image = strf("{}:normal", m_facialHairFrameset); + auto facialHairDirectives = getFacialHairDirectives(); + String image = strf("{}:normal{}", m_facialHairFrameset, facialHairDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(helmetMaskDirective, true); + drawable.imagePart().addDirectives(facialHairDirectives, true).addDirectives(helmetMaskDirective, true); addDrawable(std::move(drawable)); } if (!m_facialMaskFrameset.empty()) { - String image = strf("{}:normal", m_facialMaskFrameset); + auto facialMaskDirectives = getFacialMaskDirectives(); + String image = strf("{}:normal{}", m_facialMaskFrameset, facialMaskDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(helmetMaskDirective, true); + drawable.imagePart().addDirectives(facialMaskDirectives, true).addDirectives(helmetMaskDirective, true); addDrawable(std::move(drawable)); } if (dressed && !m_headArmorFrameset.empty()) { - String image = strf("{}:normal", m_headArmorFrameset); + auto headDirectives = getHeadDirectives(); + String image = strf("{}:normal{}", m_headArmorFrameset, headDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getHeadDirectives(), true); + drawable.imagePart().addDirectives(headDirectives, true); addDrawable(std::move(drawable)); } if (mode != PortraitMode::Head) { if (!m_frontArmFrameset.empty()) { - String image = strf("{}:{}", m_frontArmFrameset, personality.armIdle); + auto bodyDirectives = getBodyDirectives(); + String image = strf("{}:{}{}", m_frontArmFrameset, personality.armIdle, bodyDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getBodyDirectives(), true); + drawable.imagePart().addDirectives(bodyDirectives, true); addDrawable(std::move(drawable)); } if (dressed && !m_frontSleeveFrameset.empty()) { - String image = strf("{}:{}", m_frontSleeveFrameset, personality.armIdle); + auto chestDirectives = getChestDirectives(); + String image = strf("{}:{}{}", m_frontSleeveFrameset, personality.armIdle, chestDirectives.prefix()); Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getChestDirectives(), true); + drawable.imagePart().addDirectives(chestDirectives, true); addDrawable(std::move(drawable)); } } diff --git a/source/game/StarHumanoid.hpp b/source/game/StarHumanoid.hpp index 283f7e9..d43c5ef 100644 --- a/source/game/StarHumanoid.hpp +++ b/source/game/StarHumanoid.hpp @@ -100,6 +100,8 @@ public: }; static EnumMap const StateNames; + static bool& globalHeadRotation(); + Humanoid(Json const& config); Humanoid(HumanoidIdentity const& identity); Humanoid(Humanoid const&) = default; @@ -125,6 +127,8 @@ public: void setIdentity(HumanoidIdentity const& identity); HumanoidIdentity const& identity() const; + void loadConfig(Json const& config); + // All of the image identifiers here are meant to be image *base* names, with // a collection of frames specific to each piece. If an image is set to // empty string, it is disabled. @@ -154,6 +158,18 @@ public: void setHelmetMaskDirectives(Directives helmetMaskDirectives); + // Getters for all of the above + Directives const& headArmorDirectives() const; + String const& headArmorFrameset() const; + Directives const& chestArmorDirectives() const; + String const& chestArmorFrameset() const; + String const& backSleeveFrameset() const; + String const& frontSleeveFrameset() const; + Directives const& legsArmorDirectives() const; + String const& legsArmorFrameset() const; + Directives const& backArmorDirectives() const; + String const& backArmorFrameset() const; + void setBodyHidden(bool hidden); void setState(State state); @@ -161,6 +177,7 @@ public: void setDance(Maybe const& dance); void setFacingDirection(Direction facingDirection); void setMovingBackwards(bool movingBackwards); + void setHeadRotation(float headRotation); void setRotation(float rotation); void setScale(Vec2F scale); @@ -181,6 +198,7 @@ public: void setPrimaryHandFrameOverrides(String backFrameOverride, String frontFrameOverride); void setPrimaryHandDrawables(List drawables); void setPrimaryHandNonRotatedDrawables(List drawables); + bool primaryHandHoldingItem() const; // Same as primary hand. void setAltHandParameters(bool holdingItem, float angle, float itemAngle, bool recoil, @@ -188,6 +206,7 @@ public: void setAltHandFrameOverrides(String backFrameOverride, String frontFrameOverride); void setAltHandDrawables(List drawables); void setAltHandNonRotatedDrawables(List drawables); + bool altHandHoldingItem() const; // Updates the animation based on whatever the current animation state is, // wrapping or clamping animation time as appropriate. @@ -240,6 +259,16 @@ public: List particles(String const& name) const; Json const& defaultMovementParameters() const; + + String getHeadFromIdentity() const; + String getBodyFromIdentity() const; + String getFacialEmotesFromIdentity() const; + String getHairFromIdentity() const; + String getFacialHairFromIdentity() const; + String getFacialMaskFromIdentity() const; + String getBackArmFromIdentity() const; + String getFrontArmFromIdentity() const; + String getVaporTrailFrameset() const; // Extracts scalenearest from directives and returns the combined scale and // a new Directives without those scalenearest directives. @@ -262,16 +291,6 @@ private: String frameBase(State state) const; String emoteFrameBase(HumanoidEmote state) const; - String getHeadFromIdentity() const; - String getBodyFromIdentity() const; - String getFacialEmotesFromIdentity() const; - String getHairFromIdentity() const; - String getFacialHairFromIdentity() const; - String getFacialMaskFromIdentity() const; - String getBackArmFromIdentity() const; - String getFrontArmFromIdentity() const; - String getVaporTrailFrameset() const; - Directives getBodyDirectives() const; Directives getHairDirectives() const; Directives getEmoteDirectives() const; @@ -353,6 +372,8 @@ private: Maybe m_dance; Direction m_facingDirection; bool m_movingBackwards; + float m_headRotation; + float m_headRotationTarget; float m_rotation; Vec2F m_scale; bool m_drawVaporTrail; diff --git a/source/game/StarImageMetadataDatabase.cpp b/source/game/StarImageMetadataDatabase.cpp index bf84e51..af6ec49 100644 --- a/source/game/StarImageMetadataDatabase.cpp +++ b/source/game/StarImageMetadataDatabase.cpp @@ -10,17 +10,28 @@ namespace Star { +ImageMetadataDatabase::ImageMetadataDatabase() { + MutexLocker locker(m_mutex); + int timeSmear = 2000; + int64_t timeToLive = 60000; + m_sizeCache.setTimeSmear(timeSmear); + m_spacesCache.setTimeSmear(timeSmear); + m_regionCache.setTimeSmear(timeSmear); + m_sizeCache.setTimeToLive(timeToLive); + m_spacesCache.setTimeToLive(timeToLive); + m_regionCache.setTimeToLive(timeToLive); +} + Vec2U ImageMetadataDatabase::imageSize(AssetPath const& path) const { MutexLocker locker(m_mutex); - auto i = m_sizeCache.find(path); - if (i != m_sizeCache.end()) - return i->second; + if (auto cached = m_sizeCache.ptr(path)) + return *cached; locker.unlock(); Vec2U size = calculateImageSize(path); locker.lock(); - m_sizeCache[path] = size; + m_sizeCache.set(path, size); return size; } @@ -28,19 +39,16 @@ List ImageMetadataDatabase::imageSpaces(AssetPath const& path, Vec2F posi SpacesEntry key = make_tuple(path, Vec2I::round(position), fillLimit, flip); MutexLocker locker(m_mutex); - auto i = m_spacesCache.find(key); - if (i != m_spacesCache.end()) { - return i->second; + if (auto cached = m_spacesCache.ptr(key)) { + return *cached; } auto filteredPath = filterProcessing(path); SpacesEntry filteredKey = make_tuple(filteredPath, Vec2I::round(position), fillLimit, flip); - auto j = m_spacesCache.find(filteredKey); - if (j != m_spacesCache.end()) { - auto spaces = j->second; - m_spacesCache[key] = spaces; - return spaces; + if (auto cached = m_spacesCache.ptr(filteredKey)) { + m_spacesCache.set(key, *cached); + return *cached; } locker.unlock(); @@ -82,24 +90,23 @@ List ImageMetadataDatabase::imageSpaces(AssetPath const& path, Vec2F posi } locker.lock(); - m_spacesCache[key] = spaces; - m_spacesCache[filteredKey] = spaces; + m_spacesCache.set(key, spaces); + m_spacesCache.set(filteredKey, spaces); return spaces; } RectU ImageMetadataDatabase::nonEmptyRegion(AssetPath const& path) const { MutexLocker locker(m_mutex); - auto i = m_regionCache.find(path); - if (i != m_regionCache.end()) { - return i->second; + + if (auto cached = m_regionCache.ptr(path)) { + return *cached; } auto filteredPath = filterProcessing(path); - auto j = m_regionCache.find(filteredPath); - if (j != m_regionCache.end()) { - m_regionCache[path] = j->second; - return j->second; + if (auto cached = m_regionCache.ptr(filteredPath)) { + m_regionCache.set(path, *cached); + return *cached; } locker.unlock(); @@ -111,12 +118,20 @@ RectU ImageMetadataDatabase::nonEmptyRegion(AssetPath const& path) const { }); locker.lock(); - m_regionCache[path] = region; - m_regionCache[filteredPath] = region; + m_regionCache.set(path, region); + m_regionCache.set(filteredPath, region); return region; } +void ImageMetadataDatabase::cleanup() const { + MutexLocker locker(m_mutex); + + m_sizeCache.cleanup(); + m_spacesCache.cleanup(); + m_regionCache.cleanup(); +} + AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) { AssetPath newPath = { path.basePath, path.subPath, {} }; @@ -170,13 +185,17 @@ Vec2U ImageMetadataDatabase::calculateImageSize(AssetPath const& path) const { // so we don't have to call Image::readPngMetadata on the same file more // than once. MutexLocker locker(m_mutex); - if (auto size = m_sizeCache.maybe(path.basePath)) { + if (auto size = m_sizeCache.ptr(path.basePath)) { imageSize = *size; } else { locker.unlock(); - imageSize = get<0>(Image::readPngMetadata(assets->openFile(path.basePath))); + auto file = assets->openFile(path.basePath); + if (Image::isPng(file)) + imageSize = get<0>(Image::readPngMetadata(file)); + else + imageSize = fallback(); locker.lock(); - m_sizeCache[path.basePath] = imageSize; + m_sizeCache.set(path.basePath, imageSize); } } diff --git a/source/game/StarImageMetadataDatabase.hpp b/source/game/StarImageMetadataDatabase.hpp index 69e7a07..0a04b1f 100644 --- a/source/game/StarImageMetadataDatabase.hpp +++ b/source/game/StarImageMetadataDatabase.hpp @@ -5,6 +5,7 @@ #include "StarString.hpp" #include "StarThread.hpp" #include "StarAssetPath.hpp" +#include "StarTtlCache.hpp" namespace Star { @@ -15,9 +16,11 @@ STAR_CLASS(ImageMetadataDatabase); // because they are expensive to compute and cheap to keep around. class ImageMetadataDatabase { public: + ImageMetadataDatabase(); Vec2U imageSize(AssetPath const& path) const; List imageSpaces(AssetPath const& path, Vec2F position, float fillLimit, bool flip) const; RectU nonEmptyRegion(AssetPath const& path) const; + void cleanup() const; private: // Removes image processing directives that don't affect image spaces / @@ -30,9 +33,9 @@ private: typedef tuple SpacesEntry; mutable Mutex m_mutex; - mutable HashMap m_sizeCache; - mutable HashMap> m_spacesCache; - mutable HashMap m_regionCache; + mutable HashTtlCache m_sizeCache; + mutable HashTtlCache> m_spacesCache; + mutable HashTtlCache m_regionCache; }; } diff --git a/source/game/StarInput.cpp b/source/game/StarInput.cpp index 7869b52..20e6f18 100644 --- a/source/game/StarInput.cpp +++ b/source/game/StarInput.cpp @@ -223,7 +223,7 @@ Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory con category = &parentCategory; id = entryId; name = config.getString("name", id); - + tags = jsonToStringList(config.get("tags", JsonArray())); for (Json const& jBind : config.getArray("default", {})) { try { defaultBinds.emplace_back(bindFromJson(jBind)); } @@ -342,6 +342,15 @@ Input::InputState* Input::bindStatePtr(String const& categoryId, String const& b return nullptr; } +Input::InputState& Input::addBindState(BindEntry const* bindEntry) { + auto insertion = m_bindStates.insert(bindEntry, InputState()); + if (insertion.second) { + for (auto& tag : bindEntry->tags) + ++m_activeTags[tag]; + } + return insertion.first->second; +} + Input* Input::s_singleton; Input* Input::singletonPtr() { @@ -393,7 +402,19 @@ void Input::reset() { eraseWhere(m_keyStates, eraseCond); eraseWhere(m_mouseStates, eraseCond); eraseWhere(m_controllerStates, eraseCond); - eraseWhere(m_bindStates, eraseCond); + eraseWhere(m_bindStates, [&](auto& p) { + if (p.second.held) + p.second.reset(); + else { + for (auto& tag : p.first->tags) { + auto find = m_activeTags.find(tag); + if (find != m_activeTags.end() && !--find->second) + m_activeTags.erase(find); + } + return true; + } + return false; + }); } void Input::update() { @@ -415,7 +436,7 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) { if (auto binds = m_bindMappings.ptr(keyDown->key)) { for (auto bind : filterBindEntries(*binds, keyDown->mods)) - m_bindStates[bind].press(); + addBindState(bind).press(); } } } @@ -447,7 +468,7 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) { if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) { for (auto bind : filterBindEntries(*binds, m_pressedMods)) - m_bindStates[bind].press(); + addBindState(bind).press(); } } } @@ -475,7 +496,7 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) { if (auto binds = m_bindMappings.ptr(controllerDown->controllerButton)) { for (auto bind : filterBindEntries(*binds, m_pressedMods)) - m_bindStates[bind].press(); + addBindState(bind).press(); } } } @@ -650,4 +671,8 @@ void Input::setBinds(String const& categoryId, String const& bindId, Json const& entry.updated(); } +unsigned Input::getTag(String const& tag) { + return m_activeTags.maybe(tag).value(0); +} + } \ No newline at end of file diff --git a/source/game/StarInput.hpp b/source/game/StarInput.hpp index 113eea9..90a1e05 100644 --- a/source/game/StarInput.hpp +++ b/source/game/StarInput.hpp @@ -61,6 +61,8 @@ public: String name; // The category this entry belongs to. BindCategory const* category; + // Associated string tags that become active when this bind is pressed. + StringList tags; // The default binds. List defaultBinds; @@ -176,6 +178,8 @@ public: void setBinds(String const& categoryId, String const& bindId, Json const& binds); Json getDefaultBinds(String const& categoryId, String const& bindId); Json getBinds(String const& categoryId, String const& bindId); + unsigned getTag(String const& tag); + private: List filterBindEntries(List const& binds, KeyMod mods) const; @@ -184,6 +188,8 @@ private: InputState* bindStatePtr(String const& categoryId, String const& bindId); + InputState& addBindState(BindEntry const* bindEntry); + static Input* s_singleton; // Regenerated on reload. @@ -203,6 +209,7 @@ private: HashMap m_controllerStates; //Bind states HashMap m_bindStates; + StringMap m_activeTags; KeyMod m_pressedMods; bool m_textInputActive; diff --git a/source/game/StarItemDrop.cpp b/source/game/StarItemDrop.cpp index bc3d15f..4d9dab1 100644 --- a/source/game/StarItemDrop.cpp +++ b/source/game/StarItemDrop.cpp @@ -92,9 +92,9 @@ ItemDrop::ItemDrop(Json const& diskStore) m_itemDescriptor.set(m_item->descriptor()); } -ItemDrop::ItemDrop(ByteArray store) - : ItemDrop() { +ItemDrop::ItemDrop(ByteArray store, NetCompatibilityRules rules) : ItemDrop() { DataStreamBuffer ds(std::move(store)); + ds.setStreamCompatibilityVersion(rules); Root::singleton().itemDatabase()->loadItem(ds.read(), m_item); ds.read(m_eternal); @@ -116,8 +116,9 @@ Json ItemDrop::diskStore() const { }; } -ByteArray ItemDrop::netStore() const { +ByteArray ItemDrop::netStore(NetCompatibilityRules rules) const { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); ds.write(itemSafeDescriptor(m_item)); ds.write(m_eternal); @@ -146,12 +147,12 @@ String ItemDrop::description() const { return m_item->description(); } -pair ItemDrop::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair ItemDrop::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void ItemDrop::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void ItemDrop::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void ItemDrop::enableInterpolation(float extrapolationHint) { diff --git a/source/game/StarItemDrop.hpp b/source/game/StarItemDrop.hpp index 3d6e931..c551c01 100644 --- a/source/game/StarItemDrop.hpp +++ b/source/game/StarItemDrop.hpp @@ -27,10 +27,10 @@ public: ItemDrop(ItemPtr item); ItemDrop(Json const& diskStore); - ItemDrop(ByteArray netStore); + ItemDrop(ByteArray netStore, NetCompatibilityRules rules = {}); Json diskStore() const; - ByteArray netStore() const; + ByteArray netStore(NetCompatibilityRules rules = {}) const; EntityType entityType() const override; @@ -39,8 +39,8 @@ public: String description() const override; - pair writeNetState(uint64_t fromVersion = 0) override; - void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint = 0.0f) override; void disableInterpolation() override; diff --git a/source/game/StarMonster.cpp b/source/game/StarMonster.cpp index 05eaeb3..d9ce986 100644 --- a/source/game/StarMonster.cpp +++ b/source/game/StarMonster.cpp @@ -110,8 +110,8 @@ Json Monster::diskStore() const { }; } -ByteArray Monster::netStore() { - return Root::singleton().monsterDatabase()->writeMonsterVariant(m_monsterVariant); +ByteArray Monster::netStore(NetCompatibilityRules rules) { + return Root::singleton().monsterDatabase()->writeMonsterVariant(m_monsterVariant, rules); } EntityType Monster::entityType() const { @@ -210,12 +210,12 @@ Vec2F Monster::velocity() const { return m_movementController->velocity(); } -pair Monster::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair Monster::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Monster::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void Monster::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void Monster::enableInterpolation(float extrapolationHint) { diff --git a/source/game/StarMonster.hpp b/source/game/StarMonster.hpp index d6cae3e..5c964e3 100644 --- a/source/game/StarMonster.hpp +++ b/source/game/StarMonster.hpp @@ -42,7 +42,7 @@ public: Monster(Json const& diskStore); Json diskStore() const; - ByteArray netStore(); + ByteArray netStore(NetCompatibilityRules rules = {}); EntityType entityType() const override; ClientEntityMode clientEntityMode() const override; @@ -60,8 +60,8 @@ public: RectF collisionArea() const override; - pair writeNetState(uint64_t fromVersion = 0) override; - void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint) override; void disableInterpolation() override; diff --git a/source/game/StarMonsterDatabase.cpp b/source/game/StarMonsterDatabase.cpp index 963cf87..2b86d44 100644 --- a/source/game/StarMonsterDatabase.cpp +++ b/source/game/StarMonsterDatabase.cpp @@ -170,8 +170,9 @@ MonsterVariant MonsterDatabase::monsterVariant(String const& typeName, uint64_t }); } -ByteArray MonsterDatabase::writeMonsterVariant(MonsterVariant const& variant) const { +ByteArray MonsterDatabase::writeMonsterVariant(MonsterVariant const& variant, NetCompatibilityRules rules) const { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); ds.write(variant.type); ds.write(variant.seed); @@ -180,8 +181,9 @@ ByteArray MonsterDatabase::writeMonsterVariant(MonsterVariant const& variant) co return ds.data(); } -MonsterVariant MonsterDatabase::readMonsterVariant(ByteArray const& data) const { +MonsterVariant MonsterDatabase::readMonsterVariant(ByteArray const& data, NetCompatibilityRules rules) const { DataStreamBuffer ds(data); + ds.setStreamCompatibilityVersion(rules); String type = ds.read(); uint64_t seed = ds.read(); @@ -216,8 +218,8 @@ MonsterPtr MonsterDatabase::diskLoadMonster(Json const& diskStore) const { return make_shared(diskStore); } -MonsterPtr MonsterDatabase::netLoadMonster(ByteArray const& netStore) const { - return make_shared(readMonsterVariant(netStore)); +MonsterPtr MonsterDatabase::netLoadMonster(ByteArray const& netStore, NetCompatibilityRules rules) const { + return make_shared(readMonsterVariant(netStore, rules)); } List MonsterDatabase::monsterPortrait(MonsterVariant const& variant) const { diff --git a/source/game/StarMonsterDatabase.hpp b/source/game/StarMonsterDatabase.hpp index 9147542..0220267 100644 --- a/source/game/StarMonsterDatabase.hpp +++ b/source/game/StarMonsterDatabase.hpp @@ -96,8 +96,8 @@ public: MonsterVariant randomMonster(String const& typeName, Json const& uniqueParameters = JsonObject()) const; MonsterVariant monsterVariant(String const& typeName, uint64_t seed, Json const& uniqueParameters = JsonObject()) const; - ByteArray writeMonsterVariant(MonsterVariant const& variant) const; - MonsterVariant readMonsterVariant(ByteArray const& data) const; + ByteArray writeMonsterVariant(MonsterVariant const& variant, NetCompatibilityRules rules = {}) const; + MonsterVariant readMonsterVariant(ByteArray const& data, NetCompatibilityRules rules = {}) const; Json writeMonsterVariantToJson(MonsterVariant const& mVar) const; MonsterVariant readMonsterVariantFromJson(Json const& variant) const; @@ -106,7 +106,7 @@ public: // whatever world they're spawned in. MonsterPtr createMonster(MonsterVariant monsterVariant, Maybe level = {}, Json uniqueParameters = {}) const; MonsterPtr diskLoadMonster(Json const& diskStore) const; - MonsterPtr netLoadMonster(ByteArray const& netStore) const; + MonsterPtr netLoadMonster(ByteArray const& netStore, NetCompatibilityRules rules = {}) const; List monsterPortrait(MonsterVariant const& variant) const; diff --git a/source/game/StarNetPacketSocket.cpp b/source/game/StarNetPacketSocket.cpp index 4caeea0..b6320fa 100644 --- a/source/game/StarNetPacketSocket.cpp +++ b/source/game/StarNetPacketSocket.cpp @@ -62,9 +62,8 @@ Maybe PacketSocket::incomingStats() const { Maybe PacketSocket::outgoingStats() const { return {}; } - -void PacketSocket::setLegacy(bool legacy) { m_legacy = legacy; } -bool PacketSocket::legacy() const { return m_legacy; } +void PacketSocket::setNetRules(NetCompatibilityRules netRules) { m_netRules = netRules; } +NetCompatibilityRules PacketSocket::netRules() const { return m_netRules; } void CompressedPacketSocket::setCompressionStreamEnabled(bool enabled) { m_useCompressionStream = enabled; } bool CompressedPacketSocket::compressionStreamEnabled() const { return m_useCompressionStream; } @@ -155,7 +154,8 @@ void TcpPacketSocket::sendPackets(List packets) { PacketPtr& packet = it.next(); auto packetType = packet->type(); DataStreamBuffer packetBuffer; - packet->write(packetBuffer); + packetBuffer.setStreamCompatibilityVersion(netRules()); + packet->write(packetBuffer, netRules()); outBuffer.write(packetType); outBuffer.writeVlqI((int)packetBuffer.size()); outBuffer.writeData(packetBuffer.ptr(), packetBuffer.size()); @@ -168,13 +168,11 @@ void TcpPacketSocket::sendPackets(List packets) { PacketCompressionMode currentCompressionMode = it.peekNext()->compressionMode(); DataStreamBuffer packetBuffer; + packetBuffer.setStreamCompatibilityVersion(netRules()); while (it.hasNext() && it.peekNext()->type() == currentType && it.peekNext()->compressionMode() == currentCompressionMode) { - if (legacy()) - it.next()->writeLegacy(packetBuffer); - else - it.next()->write(packetBuffer); + it.next()->write(packetBuffer, netRules()); } // Packets must read and write actual data, because this is used to @@ -239,6 +237,7 @@ List TcpPacketSocket::receivePackets() { m_incomingStats.mix(packetType, packetSize, !compressionStreamEnabled()); DataStreamExternalBuffer packetStream(ds.ptr() + ds.pos(), packetSize); + packetStream.setStreamCompatibilityVersion(netRules()); ByteArray uncompressed; if (packetCompressed) { uncompressed = uncompressData(packetStream.ptr(), packetSize, PacketSizeLimit); @@ -255,10 +254,7 @@ List TcpPacketSocket::receivePackets() { } PacketPtr packet = createPacket(packetType); packet->setCompressionMode(packetCompressed ? PacketCompressionMode::Enabled : PacketCompressionMode::Disabled); - if (legacy()) - packet->readLegacy(packetStream); - else - packet->read(packetStream); + packet->read(packetStream, netRules()); packets.append(std::move(packet)); } while (!packetStream.atEnd()); } @@ -347,10 +343,6 @@ Maybe TcpPacketSocket::outgoingStats() const { return m_outgoingStats.stats(); } -void TcpPacketSocket::setLegacy(bool legacy) { - PacketSocket::setLegacy(legacy); -} - TcpPacketSocket::TcpPacketSocket(TcpSocketPtr socket) : m_socket(std::move(socket)) {} P2PPacketSocketUPtr P2PPacketSocket::open(P2PSocketUPtr socket) { @@ -373,8 +365,9 @@ void P2PPacketSocket::sendPackets(List packets) { while (it.hasNext()) { PacketType currentType = it.peekNext()->type(); DataStreamBuffer packetBuffer; + packetBuffer.setStreamCompatibilityVersion(netRules()); while (it.hasNext() && it.peekNext()->type() == currentType) - it.next()->write(packetBuffer); + it.next()->write(packetBuffer, netRules()); outBuffer.write(currentType); outBuffer.write(false); outBuffer.writeData(packetBuffer.ptr(), packetBuffer.size()); @@ -387,13 +380,11 @@ void P2PPacketSocket::sendPackets(List packets) { PacketCompressionMode currentCompressionMode = it.peekNext()->compressionMode(); DataStreamBuffer packetBuffer; + packetBuffer.setStreamCompatibilityVersion(netRules()); while (it.hasNext() && it.peekNext()->type() == currentType && it.peekNext()->compressionMode() == currentCompressionMode) { - if (legacy()) - it.next()->writeLegacy(packetBuffer); - else - it.next()->write(packetBuffer); + it.next()->write(packetBuffer, netRules()); } // Packets must read and write actual data, because this is used to @@ -440,13 +431,11 @@ List P2PPacketSocket::receivePackets() { m_incomingStats.mix(packetType, packetSize, !compressionStreamEnabled()); DataStreamExternalBuffer packetStream(packetBytes); + packetStream.setStreamCompatibilityVersion(netRules()); do { PacketPtr packet = createPacket(packetType); packet->setCompressionMode(packetCompressed ? PacketCompressionMode::Enabled : PacketCompressionMode::Disabled); - if (legacy()) - packet->readLegacy(packetStream); - else - packet->read(packetStream); + packet->read(packetStream, netRules()); packets.append(std::move(packet)); } while (!packetStream.atEnd()); } diff --git a/source/game/StarNetPacketSocket.hpp b/source/game/StarNetPacketSocket.hpp index c2c06fa..7c3e7ac 100644 --- a/source/game/StarNetPacketSocket.hpp +++ b/source/game/StarNetPacketSocket.hpp @@ -5,6 +5,7 @@ #include "StarP2PNetworkingService.hpp" #include "StarNetPackets.hpp" #include "StarZSTDCompression.hpp" +#include "StarNetCompatibility.hpp" namespace Star { @@ -75,10 +76,11 @@ public: virtual Maybe incomingStats() const; virtual Maybe outgoingStats() const; - virtual void setLegacy(bool legacy); - virtual bool legacy() const; + virtual void setNetRules(NetCompatibilityRules netRules); + virtual NetCompatibilityRules netRules() const; + private: - bool m_legacy = false; + NetCompatibilityRules m_netRules; }; class CompressedPacketSocket : public PacketSocket { @@ -142,8 +144,6 @@ public: Maybe incomingStats() const override; Maybe outgoingStats() const override; - - void setLegacy(bool legacy) override; private: TcpPacketSocket(TcpSocketPtr socket); diff --git a/source/game/StarNetPackets.cpp b/source/game/StarNetPackets.cpp index b8605ed..2e64eee 100644 --- a/source/game/StarNetPackets.cpp +++ b/source/game/StarNetPackets.cpp @@ -85,9 +85,11 @@ EnumMap const NetCompressionModeNames { Packet::~Packet() {} -void Packet::readLegacy(DataStream& ds) { read(ds); } -void Packet::writeLegacy(DataStream& ds) const { write(ds); } -void Packet::readJson(Json const& json) {} +void Packet::read(DataStream& ds, NetCompatibilityRules netRules) { read(ds); _unused(netRules); } +void Packet::read(DataStream& ds) { _unused(ds); } +void Packet::write(DataStream& ds, NetCompatibilityRules netRules) const { write(ds); _unused(netRules); } +void Packet::write(DataStream& ds) const { _unused(ds); } +void Packet::readJson(Json const& json) { _unused(json); } Json Packet::writeJson() const { return JsonObject{}; } PacketCompressionMode Packet::compressionMode() const { return m_compressionMode; } @@ -206,13 +208,10 @@ void ProtocolResponsePacket::read(DataStream& ds) { } } -void ProtocolResponsePacket::writeLegacy(DataStream& ds) const { +void ProtocolResponsePacket::write(DataStream& ds, NetCompatibilityRules netRules) const { ds.write(allowed); -} - -void ProtocolResponsePacket::write(DataStream& ds) const { - writeLegacy(ds); - ds.write(info); + if (!netRules.isLegacy()) + ds.write(info); } ConnectSuccessPacket::ConnectSuccessPacket() {} @@ -339,23 +338,18 @@ PausePacket::PausePacket() {} PausePacket::PausePacket(bool pause, float timescale) : pause(pause), timescale(timescale) {} -void PausePacket::readLegacy(DataStream& ds) { +void PausePacket::read(DataStream& ds, NetCompatibilityRules netRules) { ds.read(pause); - timescale = 1.0f; + if (!netRules.isLegacy()) + ds.read(timescale); + else + timescale = 1.0f; } -void PausePacket::read(DataStream& ds) { - readLegacy(ds); - ds.read(timescale); -} - -void PausePacket::writeLegacy(DataStream& ds) const { +void PausePacket::write(DataStream& ds, NetCompatibilityRules netRules) const { ds.write(pause); -} - -void PausePacket::write(DataStream& ds) const { - writeLegacy(ds); - ds.write(timescale); + if (!netRules.isLegacy()) + ds.write(timescale); } void PausePacket::readJson(Json const& json) { @@ -409,7 +403,8 @@ ClientConnectPacket::ClientConnectPacket(ByteArray assetsDigest, bool allowAsset playerName(std::move(playerName)), playerSpecies(std::move(playerSpecies)), shipChunks(std::move(shipChunks)), shipUpgrades(std::move(shipUpgrades)), introComplete(std::move(introComplete)), account(std::move(account)), info(std::move(info)) {} -void ClientConnectPacket::readLegacy(DataStream& ds) { + +void ClientConnectPacket::read(DataStream& ds, NetCompatibilityRules netRules) { ds.read(assetsDigest); ds.read(allowAssetsMismatch); ds.read(playerUuid); @@ -419,15 +414,11 @@ void ClientConnectPacket::readLegacy(DataStream& ds) { ds.read(shipUpgrades); ds.read(introComplete); ds.read(account); - info = Json(); + if (!netRules.isLegacy()) + ds.read(info); } -void ClientConnectPacket::read(DataStream& ds) { - readLegacy(ds); - ds.read(info); -} - -void ClientConnectPacket::writeLegacy(DataStream& ds) const { +void ClientConnectPacket::write(DataStream& ds, NetCompatibilityRules netRules) const { ds.write(assetsDigest); ds.write(allowAssetsMismatch); ds.write(playerUuid); @@ -437,11 +428,8 @@ void ClientConnectPacket::writeLegacy(DataStream& ds) const { ds.write(shipUpgrades); ds.write(introComplete); ds.write(account); -} - -void ClientConnectPacket::write(DataStream& ds) const { - writeLegacy(ds); - ds.write(info); + if (!netRules.isLegacy()) + ds.write(info); } ClientDisconnectRequestPacket::ClientDisconnectRequestPacket() {} @@ -484,16 +472,20 @@ void PlayerWarpPacket::write(DataStream& ds) const { FlyShipPacket::FlyShipPacket() {} -FlyShipPacket::FlyShipPacket(Vec3I system, SystemLocation location) : system(std::move(system)), location(std::move(location)) {} +FlyShipPacket::FlyShipPacket(Vec3I system, SystemLocation location, Json settings) : system(std::move(system)), location(std::move(location)), settings(std::move(settings)) {} -void FlyShipPacket::read(DataStream& ds) { +void FlyShipPacket::read(DataStream& ds, NetCompatibilityRules netRules) { ds.read(system); ds.read(location); + if (netRules.version() >= 3) + ds.read(settings); } -void FlyShipPacket::write(DataStream& ds) const { +void FlyShipPacket::write(DataStream& ds, NetCompatibilityRules netRules) const { ds.write(system); ds.write(location); + if (netRules.version() >= 3) + ds.write(settings); } ChatSendPacket::ChatSendPacket() : sendMode(ChatSendMode::Broadcast) {} @@ -944,24 +936,23 @@ void WorldStartAcknowledgePacket::write(DataStream& ds) const { PingPacket::PingPacket() {} PingPacket::PingPacket(int64_t time) : time(time) {} -void PingPacket::readLegacy(DataStream& ds) { - // Packets can't be empty, read the trash data - ds.read(); - time = 0; +void PingPacket::read(DataStream& ds, NetCompatibilityRules netRules) { + if (netRules.isLegacy()) { + // Packets can't be empty, read the trash data + ds.read(); + time = 0; + } else { + ds.readVlqI(time); + } } -void PingPacket::read(DataStream& ds) { - ds.readVlqI(time); -} - - -void PingPacket::writeLegacy(DataStream& ds) const { - // Packets can't be empty, write some trash data - ds.write(false); -} - -void PingPacket::write(DataStream& ds) const { - ds.writeVlqI(time); +void PingPacket::write(DataStream& ds, NetCompatibilityRules netRules) const { + if (netRules.isLegacy()) { + // Packets can't be empty, write some trash data + ds.write(false); + } else { + ds.writeVlqI(time); + } } EntityCreatePacket::EntityCreatePacket(EntityType entityType, ByteArray storeData, ByteArray firstNetState, EntityId entityId) @@ -1265,44 +1256,47 @@ void FindUniqueEntityResponsePacket::write(DataStream& ds) const { PongPacket::PongPacket() {} PongPacket::PongPacket(int64_t time) : time(time) {} -void PongPacket::readLegacy(DataStream& ds) { - // Packets can't be empty, read the trash data - ds.read(); - time = 0; + +void PongPacket::read(DataStream& ds, NetCompatibilityRules netRules) { + if (netRules.isLegacy()) { + // Packets can't be empty, read the trash data + ds.read(); + time = 0; + } else { + ds.readVlqI(time); + } + } -void PongPacket::read(DataStream& ds) { - ds.readVlqI(time); -} - -void PongPacket::writeLegacy(DataStream& ds) const { - // Packets can't be empty, write some trash data - ds.write(false); -} - -void PongPacket::write(DataStream& ds) const { - ds.writeVlqI(time); +void PongPacket::write(DataStream& ds, NetCompatibilityRules netRules) const { + if (netRules.isLegacy()) { + // Packets can't be empty, write some trash data + ds.write(false); + } else { + ds.writeVlqI(time); + } } StepUpdatePacket::StepUpdatePacket() : remoteTime(0.0) {} StepUpdatePacket::StepUpdatePacket(double remoteTime) : remoteTime(remoteTime) {} -void StepUpdatePacket::readLegacy(DataStream& ds) { - auto steps = ds.readVlqU(); - remoteTime = double(steps) / 60.0; + +void StepUpdatePacket::read(DataStream& ds, NetCompatibilityRules netRules) { + if (netRules.isLegacy()) { + auto steps = ds.readVlqU(); + remoteTime = double(steps) / 60.0; + } else { + ds.read(remoteTime); + } } -void StepUpdatePacket::read(DataStream& ds) { - ds.read(remoteTime); -} - -void StepUpdatePacket::writeLegacy(DataStream& ds) const { - ds.writeVlqU((uint64_t)round(remoteTime * 60.0)); -} - -void StepUpdatePacket::write(DataStream& ds) const { - ds.write(remoteTime); +void StepUpdatePacket::write(DataStream& ds, NetCompatibilityRules netRules) const { + if (netRules.isLegacy()) { + ds.writeVlqU((uint64_t)round(remoteTime * 60.0)); + } else { + ds.write(remoteTime); + } } SystemWorldStartPacket::SystemWorldStartPacket() {} diff --git a/source/game/StarNetPackets.hpp b/source/game/StarNetPackets.hpp index 6181b30..4002e90 100644 --- a/source/game/StarNetPackets.hpp +++ b/source/game/StarNetPackets.hpp @@ -14,6 +14,7 @@ #include "StarWiring.hpp" #include "StarClientContext.hpp" #include "StarSystemWorld.hpp" +#include "StarNetCompatibility.hpp" namespace Star { @@ -133,10 +134,10 @@ struct Packet { virtual PacketType type() const = 0; - virtual void readLegacy(DataStream& ds); - virtual void read(DataStream& ds) = 0; - virtual void writeLegacy(DataStream& ds) const; - virtual void write(DataStream& ds) const = 0; + virtual void read(DataStream& ds, NetCompatibilityRules netRules); + virtual void read(DataStream& ds); + virtual void write(DataStream& ds, NetCompatibilityRules netRules) const; + virtual void write(DataStream& ds) const; virtual void readJson(Json const& json); virtual Json writeJson() const; @@ -171,8 +172,7 @@ struct ProtocolResponsePacket : PacketBase { ProtocolResponsePacket(bool allowed = false, Json info = {}); void read(DataStream& ds) override; - void writeLegacy(DataStream& ds) const override; - void write(DataStream& ds) const override; + void write(DataStream& ds, NetCompatibilityRules netRules) const override; bool allowed; Json info; @@ -280,10 +280,8 @@ struct PausePacket : PacketBase { PausePacket(); PausePacket(bool pause, float timescale = 1.0f); - void readLegacy(DataStream& ds) override; - void read(DataStream& ds) override; - void writeLegacy(DataStream& ds) const override; - void write(DataStream& ds) const override; + void read(DataStream& ds, NetCompatibilityRules netRules) override; + void write(DataStream& ds, NetCompatibilityRules netRules) const override; void readJson(Json const& json) override; Json writeJson() const override; @@ -312,10 +310,8 @@ struct ClientConnectPacket : PacketBase { String playerSpecies, WorldChunks shipChunks, ShipUpgrades shipUpgrades, bool introComplete, String account, Json info = {}); - void readLegacy(DataStream& ds) override; - void read(DataStream& ds) override; - void writeLegacy(DataStream& ds) const override; - void write(DataStream& ds) const override; + void read(DataStream& ds, NetCompatibilityRules netRules) override; + void write(DataStream& ds, NetCompatibilityRules netRules) const override; ByteArray assetsDigest; bool allowAssetsMismatch; @@ -359,13 +355,14 @@ struct PlayerWarpPacket : PacketBase { struct FlyShipPacket : PacketBase { FlyShipPacket(); - FlyShipPacket(Vec3I system, SystemLocation location); + FlyShipPacket(Vec3I system, SystemLocation location, Json settings = {}); - void read(DataStream& ds) override; - void write(DataStream& ds) const override; + void read(DataStream& ds, NetCompatibilityRules netRules) override; + void write(DataStream& ds, NetCompatibilityRules netRules) const override; Vec3I system; SystemLocation location; + Json settings; }; struct ChatSendPacket : PacketBase { @@ -414,7 +411,7 @@ struct WorldStartPacket : PacketBase { bool respawnInWorld; HashMap dungeonIdGravity; HashMap dungeonIdBreathable; - Set protectedDungeonIds; + StableHashSet protectedDungeonIds; Json worldProperties; ConnectionId clientId; bool localInterpolationMode; @@ -613,10 +610,8 @@ struct PongPacket : PacketBase { PongPacket(); PongPacket(int64_t time); - void readLegacy(DataStream& ds) override; - void read(DataStream& ds) override; - void writeLegacy(DataStream& ds) const override; - void write(DataStream& ds) const override; + void read(DataStream& ds, NetCompatibilityRules netRules) override; + void write(DataStream& ds, NetCompatibilityRules netRules) const override; int64_t time = 0; }; @@ -732,10 +727,8 @@ struct PingPacket : PacketBase { PingPacket(); PingPacket(int64_t time); - void readLegacy(DataStream& ds) override; - void read(DataStream& ds) override; - void writeLegacy(DataStream& ds) const override; - void write(DataStream& ds) const override; + void read(DataStream& ds, NetCompatibilityRules netRules) override; + void write(DataStream& ds, NetCompatibilityRules netRules) const override; int64_t time = 0; }; @@ -878,10 +871,8 @@ struct StepUpdatePacket : PacketBase { StepUpdatePacket(); StepUpdatePacket(double remoteTime); - void readLegacy(DataStream& ds) override; - void read(DataStream& ds) override; - void writeLegacy(DataStream& ds) const override; - void write(DataStream& ds) const override; + void read(DataStream& ds, NetCompatibilityRules netRules) override; + void write(DataStream& ds, NetCompatibilityRules netRules) const override; double remoteTime; }; diff --git a/source/game/StarNpc.cpp b/source/game/StarNpc.cpp index c68fd08..388f6e5 100644 --- a/source/game/StarNpc.cpp +++ b/source/game/StarNpc.cpp @@ -152,8 +152,8 @@ Json Npc::diskStore() const { }; } -ByteArray Npc::netStore() { - return Root::singleton().npcDatabase()->writeNpcVariant(m_npcVariant); +ByteArray Npc::netStore(NetCompatibilityRules rules) { + return Root::singleton().npcDatabase()->writeNpcVariant(m_npcVariant, rules); } EntityType Npc::entityType() const { @@ -252,7 +252,7 @@ RectF Npc::collisionArea() const { return m_movementController->collisionPoly().boundBox(); } -pair Npc::writeNetState(uint64_t fromVersion) { +pair Npc::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { // client-side npcs error nearby vanilla NPC scripts because callScriptedEntity // for now, scrungle the collision poly to avoid their queries. hacky :( if (m_npcVariant.overrides && m_npcVariant.overrides.getBool("overrideNetPoly", false)) { @@ -260,18 +260,18 @@ pair Npc::writeNetState(uint64_t fromVersion) { if (*mode == EntityMode::Master && connectionForEntity(entityId()) != ServerConnectionId) { PolyF poly = m_movementController->collisionPoly(); m_movementController->setCollisionPoly({ { 0.0f, -3.402823466e+38F }}); - auto result = m_netGroup.writeNetState(fromVersion); + auto result = m_netGroup.writeNetState(fromVersion, rules); m_movementController->setCollisionPoly(poly); return result; } } } - return m_netGroup.writeNetState(fromVersion); + return m_netGroup.writeNetState(fromVersion, rules); } -void Npc::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void Npc::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } String Npc::description() const { diff --git a/source/game/StarNpc.hpp b/source/game/StarNpc.hpp index ab8bc0f..0b22702 100644 --- a/source/game/StarNpc.hpp +++ b/source/game/StarNpc.hpp @@ -42,11 +42,12 @@ class Npc public virtual PhysicsEntity, public virtual EmoteEntity { public: + Npc(ByteArray const& netStore, NetCompatibilityRules rules = {}); Npc(NpcVariant const& npcVariant); Npc(NpcVariant const& npcVariant, Json const& initialState); Json diskStore() const; - ByteArray netStore(); + ByteArray netStore(NetCompatibilityRules rules = {}); EntityType entityType() const override; ClientEntityMode clientEntityMode() const override; @@ -66,8 +67,8 @@ public: RectF collisionArea() const override; - pair writeNetState(uint64_t fromVersion = 0) override; - void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint = 0.0f) override; void disableInterpolation() override; diff --git a/source/game/StarNpcDatabase.cpp b/source/game/StarNpcDatabase.cpp index bd70df5..392b29d 100644 --- a/source/game/StarNpcDatabase.cpp +++ b/source/game/StarNpcDatabase.cpp @@ -157,8 +157,9 @@ NpcVariant NpcDatabase::generateNpcVariant( return variant; } -ByteArray NpcDatabase::writeNpcVariant(NpcVariant const& variant) const { +ByteArray NpcDatabase::writeNpcVariant(NpcVariant const& variant, NetCompatibilityRules rules) const { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); ds.write(variant.species); ds.write(variant.typeName); @@ -179,8 +180,9 @@ ByteArray NpcDatabase::writeNpcVariant(NpcVariant const& variant) const { return ds.data(); } -NpcVariant NpcDatabase::readNpcVariant(ByteArray const& data) const { +NpcVariant NpcDatabase::readNpcVariant(ByteArray const& data, NetCompatibilityRules rules) const { DataStreamBuffer ds(data); + ds.setStreamCompatibilityVersion(rules); NpcVariant variant; @@ -325,8 +327,8 @@ NpcPtr NpcDatabase::diskLoadNpc(Json const& diskStore) const { return make_shared(npcVariant, diskStore); } -NpcPtr NpcDatabase::netLoadNpc(ByteArray const& netStore) const { - return make_shared(readNpcVariant(netStore)); +NpcPtr NpcDatabase::netLoadNpc(ByteArray const& netStore, NetCompatibilityRules rules) const { + return make_shared(readNpcVariant(netStore, rules)); } List NpcDatabase::npcPortrait(NpcVariant const& npcVariant, PortraitMode mode) const { diff --git a/source/game/StarNpcDatabase.hpp b/source/game/StarNpcDatabase.hpp index 01dec97..a97d0f5 100644 --- a/source/game/StarNpcDatabase.hpp +++ b/source/game/StarNpcDatabase.hpp @@ -58,15 +58,15 @@ public: NpcVariant generateNpcVariant(String const& species, String const& typeName, float level) const; NpcVariant generateNpcVariant(String const& species, String const& typeName, float level, uint64_t seed, Json const& overrides) const; - ByteArray writeNpcVariant(NpcVariant const& variant) const; - NpcVariant readNpcVariant(ByteArray const& data) const; + ByteArray writeNpcVariant(NpcVariant const& variant, NetCompatibilityRules rules = {}) const; + NpcVariant readNpcVariant(ByteArray const& data, NetCompatibilityRules rules = {}) const; Json writeNpcVariantToJson(NpcVariant const& variant) const; NpcVariant readNpcVariantFromJson(Json const& data) const; NpcPtr createNpc(NpcVariant const& npcVariant) const; - NpcPtr diskLoadNpc(Json const& diskStoree) const; - NpcPtr netLoadNpc(ByteArray const& netStore) const; + NpcPtr diskLoadNpc(Json const& diskStore) const; + NpcPtr netLoadNpc(ByteArray const& netStore, NetCompatibilityRules rules = {}) const; List npcPortrait(NpcVariant const& npcVariant, PortraitMode mode) const; diff --git a/source/game/StarObject.cpp b/source/game/StarObject.cpp index ece0b20..66f7917 100644 --- a/source/game/StarObject.cpp +++ b/source/game/StarObject.cpp @@ -118,8 +118,9 @@ Json Object::diskStore() const { return writeStoredData().setAll({{"name", m_config->name}, {"parameters", m_parameters.baseMap()}}); } -ByteArray Object::netStore() { +ByteArray Object::netStore(NetCompatibilityRules rules) { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); ds.write(m_config->name); ds.write(m_parameters.baseMap()); return ds.takeData(); @@ -297,13 +298,12 @@ RectF Object::metaBoundBox() const { } } -pair Object::writeNetState(uint64_t fromVersion) { - DataStreamBuffer ds; - return m_netGroup.writeNetState(fromVersion); +pair Object::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Object::readNetState(ByteArray delta, float interpolationTime) { - m_netGroup.readNetState(std::move(delta), interpolationTime); +void Object::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } Vec2I Object::tilePosition() const { diff --git a/source/game/StarObject.hpp b/source/game/StarObject.hpp index a797842..e768ca8 100644 --- a/source/game/StarObject.hpp +++ b/source/game/StarObject.hpp @@ -37,7 +37,7 @@ public: Object(ObjectConfigConstPtr config, Json const& parameters = JsonObject()); Json diskStore() const; - ByteArray netStore(); + ByteArray netStore(NetCompatibilityRules rules = {}); virtual EntityType entityType() const override; virtual ClientEntityMode clientEntityMode() const override; @@ -48,8 +48,8 @@ public: virtual Vec2F position() const override; virtual RectF metaBoundBox() const override; - virtual pair writeNetState(uint64_t fromVersion = 0) override; - virtual void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + virtual pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + virtual void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; virtual String description() const override; diff --git a/source/game/StarObjectDatabase.cpp b/source/game/StarObjectDatabase.cpp index 8ed56e4..71eb166 100644 --- a/source/game/StarObjectDatabase.cpp +++ b/source/game/StarObjectDatabase.cpp @@ -380,8 +380,9 @@ ObjectPtr ObjectDatabase::diskLoadObject(Json const& diskStore) const { return object; } -ObjectPtr ObjectDatabase::netLoadObject(ByteArray const& netStore) const { +ObjectPtr ObjectDatabase::netLoadObject(ByteArray const& netStore, NetCompatibilityRules rules) const { DataStreamBuffer ds(netStore); + ds.setStreamCompatibilityVersion(rules); String name = ds.read(); Json parameters = ds.read(); return createObject(name, parameters); diff --git a/source/game/StarObjectDatabase.hpp b/source/game/StarObjectDatabase.hpp index ca44c2e..b431445 100644 --- a/source/game/StarObjectDatabase.hpp +++ b/source/game/StarObjectDatabase.hpp @@ -198,7 +198,7 @@ public: ObjectPtr createObject(String const& objectName, Json const& objectParameters = JsonObject()) const; ObjectPtr diskLoadObject(Json const& diskStore) const; - ObjectPtr netLoadObject(ByteArray const& netStore) const; + ObjectPtr netLoadObject(ByteArray const& netStore, NetCompatibilityRules rules = {}) const; bool canPlaceObject(World const* world, Vec2I const& position, String const& objectName) const; // If the object is placeable in the given position, creates the given object diff --git a/source/game/StarPlant.cpp b/source/game/StarPlant.cpp index 7b4e426..32e1575 100644 --- a/source/game/StarPlant.cpp +++ b/source/game/StarPlant.cpp @@ -411,8 +411,9 @@ Json Plant::diskStore() const { }; } -ByteArray Plant::netStore() const { +ByteArray Plant::netStore(NetCompatibilityRules rules) const { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); ds.viwrite(m_tilePosition[0]); ds.viwrite(m_tilePosition[1]); ds.write(m_ceiling); @@ -423,7 +424,7 @@ ByteArray Plant::netStore() const { ds.write(m_ephemeral); ds.write(m_tileDamageParameters); ds.write(m_fallsWhenDead); - m_tileDamageStatus.netStore(ds); + m_tileDamageStatus.netStore(ds, rules); ds.write(writePieces()); return ds.takeData(); @@ -534,7 +535,7 @@ Plant::Plant(Json const& diskStore) : Plant() { setupNetStates(); } -Plant::Plant(ByteArray const& netStore) : Plant() { +Plant::Plant(ByteArray const& netStore, NetCompatibilityRules rules) : Plant() { m_broken = false; m_tilePosition = Vec2I(); m_ceiling = false; @@ -545,6 +546,7 @@ Plant::Plant(ByteArray const& netStore) : Plant() { m_piecesUpdated = true; DataStreamBuffer ds(netStore); + ds.setStreamCompatibilityVersion(rules); ds.viread(m_tilePosition[0]); ds.viread(m_tilePosition[1]); ds.read(m_ceiling); @@ -555,7 +557,7 @@ Plant::Plant(ByteArray const& netStore) : Plant() { ds.read(m_ephemeral); ds.read(m_tileDamageParameters); ds.read(m_fallsWhenDead); - m_tileDamageStatus.netLoad(ds); + m_tileDamageStatus.netLoad(ds, rules); readPieces(ds.read()); setupNetStates(); @@ -586,12 +588,12 @@ void Plant::init(World* world, EntityId entityId, EntityMode mode) { m_tilePosition = world->geometry().xwrap(m_tilePosition); } -pair Plant::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair Plant::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Plant::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void Plant::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void Plant::enableInterpolation(float extrapolationHint) { diff --git a/source/game/StarPlant.hpp b/source/game/StarPlant.hpp index 53aed5f..ddb9501 100644 --- a/source/game/StarPlant.hpp +++ b/source/game/StarPlant.hpp @@ -57,10 +57,10 @@ public: Plant(GrassVariant const& config, uint64_t seed); Plant(BushVariant const& config, uint64_t seed); Plant(Json const& diskStore); - Plant(ByteArray const& netStore); + Plant(ByteArray const& netStore, NetCompatibilityRules rules = {}); Json diskStore() const; - ByteArray netStore() const; + ByteArray netStore(NetCompatibilityRules rules = {}) const; EntityType entityType() const override; @@ -68,8 +68,8 @@ public: virtual String description() const override; - pair writeNetState(uint64_t fromVersion = 0) override; - void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint) override; void disableInterpolation() override; diff --git a/source/game/StarPlantDrop.cpp b/source/game/StarPlantDrop.cpp index ccb57d8..b41705d 100644 --- a/source/game/StarPlantDrop.cpp +++ b/source/game/StarPlantDrop.cpp @@ -86,11 +86,12 @@ PlantDrop::PlantDrop(List pieces, Vec2F const& position, Vec2 m_collisionRect = fullBounds; } -PlantDrop::PlantDrop(ByteArray const& netStore) { +PlantDrop::PlantDrop(ByteArray const& netStore, NetCompatibilityRules rules) { m_netGroup.addNetElement(&m_movementController); m_netGroup.addNetElement(&m_spawnedDrops); DataStreamBuffer ds(netStore); + ds.setStreamCompatibilityVersion(rules); ds >> m_time; ds >> m_master; ds >> m_description; @@ -113,7 +114,7 @@ PlantDrop::PlantDrop(ByteArray const& netStore) { m_spawnedDropEffects = true; } -ByteArray PlantDrop::netStore() { +ByteArray PlantDrop::netStore(NetCompatibilityRules rules) { DataStreamBuffer ds; ds << m_time; ds << m_master; @@ -358,12 +359,12 @@ void PlantDrop::render(RenderCallback* renderCallback) { } } -pair PlantDrop::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair PlantDrop::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void PlantDrop::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void PlantDrop::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void PlantDrop::enableInterpolation(float extrapolationHint) { diff --git a/source/game/StarPlantDrop.hpp b/source/game/StarPlantDrop.hpp index 1b630f4..7528e5d 100644 --- a/source/game/StarPlantDrop.hpp +++ b/source/game/StarPlantDrop.hpp @@ -15,9 +15,9 @@ public: PlantDrop(List pieces, Vec2F const& position, Vec2F const& strikeVector, String const& description, bool upsideDown, Json stemConfig, Json foliageConfig, Json saplingConfig, bool master, float random); - PlantDrop(ByteArray const& netStore); + PlantDrop(ByteArray const& netStore, NetCompatibilityRules rules = {}); - ByteArray netStore(); + ByteArray netStore(NetCompatibilityRules rules = {}); EntityType entityType() const override; @@ -26,8 +26,8 @@ public: String description() const override; - pair writeNetState(uint64_t fromVersion = 0) override; - void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint = 0.0f) override; void disableInterpolation() override; diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index 9c9c482..c094bbb 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -187,8 +187,9 @@ Player::Player(PlayerConfigPtr config, Uuid uuid) { m_netGroup.setNeedsStoreCallback(bind(&Player::setNetStates, this)); } -Player::Player(PlayerConfigPtr config, ByteArray const& netStore) : Player(config) { +Player::Player(PlayerConfigPtr config, ByteArray const& netStore, NetCompatibilityRules rules) : Player(config) { DataStreamBuffer ds(netStore); + ds.setStreamCompatibilityVersion(rules); setUniqueId(ds.read()); @@ -292,6 +293,10 @@ void Player::setUniverseClient(UniverseClient* client) { m_questManager->setUniverseClient(client); } +UniverseClient* Player::universeClient() const { + return m_client; +} + EntityType Player::entityType() const { return EntityType::Player; } @@ -1022,7 +1027,7 @@ void Player::update(float dt, uint64_t) { m_tools->suppressItems(suppressedItems); m_tools->tick(dt, m_shifting, m_pendingMoves); - + if (auto overrideFacingDirection = m_tools->setupHumanoidHandItems(*m_humanoid, position(), aimPosition())) m_movementController->controlFace(*overrideFacingDirection); @@ -1070,6 +1075,36 @@ void Player::update(float dt, uint64_t) { m_effectEmitter->tick(dt, *entityMode()); + if (isClient) { + bool calculateHeadRotation = isMaster(); + if (!calculateHeadRotation) { + auto headRotationProperty = getSecretProperty("humanoid.headRotation"); + if (headRotationProperty.isType(Json::Type::Float)) { + m_humanoid->setHeadRotation(headRotationProperty.toFloat()); + } else + calculateHeadRotation = true; + } + if (calculateHeadRotation) { // master or not an OpenStarbound player + float headRotation = 0.f; + if (m_humanoid->primaryHandHoldingItem() || m_humanoid->altHandHoldingItem() || m_humanoid->dance()) { + auto primary = m_tools->primaryHandItem(); + auto alt = m_tools->altHandItem(); + String const disableFlag = "disableHeadRotation"; + auto statusFlag = m_statusController->statusProperty(disableFlag); + if (!(statusFlag.isType(Json::Type::Bool) && statusFlag.toBool()) + && !(primary && primary->instanceValue(disableFlag)) + && !(alt && alt->instanceValue(disableFlag))) { + auto diff = world()->geometry().diff(aimPosition(), mouthPosition()); + diff.setX(fabsf(diff.x())); + headRotation = diff.angle() * .25f * numericalDirection(m_humanoid->facingDirection()); + } + } + m_humanoid->setHeadRotation(headRotation); + if (isMaster()) + setSecretProperty("humanoid.headRotation", headRotation); + } + } + m_humanoid->setFacingDirection(m_movementController->facingDirection()); m_humanoid->setMovingBackwards(m_movementController->facingDirection() != m_movementController->movingDirection()); @@ -1618,12 +1653,12 @@ Direction Player::facingDirection() const { return m_movementController->facingDirection(); } -pair Player::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair Player::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Player::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void Player::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void Player::enableInterpolation(float) { @@ -2319,8 +2354,9 @@ Json Player::diskStore() { }; } -ByteArray Player::netStore() { +ByteArray Player::netStore(NetCompatibilityRules rules) { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); ds.write(*uniqueId()); ds.write(m_description); @@ -2604,6 +2640,7 @@ Maybe Player::getSecretPropertyView(String const& name) const { return {}; } + Json Player::getSecretProperty(String const& name, Json defaultValue) const { if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) { DataStreamExternalBuffer buffer(tag->utf8Ptr(), tag->utf8Size()); diff --git a/source/game/StarPlayer.hpp b/source/game/StarPlayer.hpp index 871ca47..1a6a5c2 100644 --- a/source/game/StarPlayer.hpp +++ b/source/game/StarPlayer.hpp @@ -76,7 +76,7 @@ public: static EnumMap const StateNames; Player(PlayerConfigPtr config, Uuid uuid = Uuid()); - Player(PlayerConfigPtr config, ByteArray const& netStore); + Player(PlayerConfigPtr config, ByteArray const& netStore, NetCompatibilityRules rules = {}); Player(PlayerConfigPtr config, Json const& diskStore); void diskLoad(Json const& diskStore); @@ -88,11 +88,12 @@ public: void setStatistics(StatisticsPtr statistics); void setUniverseClient(UniverseClient* universeClient); + UniverseClient* universeClient() const; QuestManagerPtr questManager() const; Json diskStore(); - ByteArray netStore(); + ByteArray netStore(NetCompatibilityRules rules = {}); EntityType entityType() const override; ClientEntityMode clientEntityMode() const override; @@ -118,8 +119,8 @@ public: // relative to current position RectF collisionArea() const override; - pair writeNetState(uint64_t fromStep = 0) override; - void readNetState(ByteArray data, float interpolationStep = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationStep = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint = 0.0f) override; void disableInterpolation() override; diff --git a/source/game/StarPlayerFactory.cpp b/source/game/StarPlayerFactory.cpp index f3dbf29..43854b6 100644 --- a/source/game/StarPlayerFactory.cpp +++ b/source/game/StarPlayerFactory.cpp @@ -60,8 +60,8 @@ PlayerPtr PlayerFactory::diskLoadPlayer(Json const& diskStore) const { return make_shared(m_config, diskStore); } -PlayerPtr PlayerFactory::netLoadPlayer(ByteArray const& netStore) const { - return make_shared(m_config, netStore); +PlayerPtr PlayerFactory::netLoadPlayer(ByteArray const& netStore, NetCompatibilityRules rules) const { + return make_shared(m_config, netStore, rules); } } diff --git a/source/game/StarPlayerFactory.hpp b/source/game/StarPlayerFactory.hpp index a366221..6712b81 100644 --- a/source/game/StarPlayerFactory.hpp +++ b/source/game/StarPlayerFactory.hpp @@ -60,7 +60,7 @@ public: PlayerPtr create() const; PlayerPtr diskLoadPlayer(Json const& diskStore) const; - PlayerPtr netLoadPlayer(ByteArray const& netStore) const; + PlayerPtr netLoadPlayer(ByteArray const& netStore, NetCompatibilityRules rules = {}) const; private: PlayerConfigPtr m_config; diff --git a/source/game/StarPlayerInventory.cpp b/source/game/StarPlayerInventory.cpp index 1edcdca..4b9cff2 100644 --- a/source/game/StarPlayerInventory.cpp +++ b/source/game/StarPlayerInventory.cpp @@ -781,19 +781,14 @@ void PlayerInventory::load(Json const& store) { //reuse ItemBags so the Inventory pane still works after load()'ing into the same PlayerInventory again (from swap) auto itemBags = store.get("itemBags").toObject(); - eraseWhere(m_bags, [&](auto const& p) { return !itemBags.contains(p.first); }); m_inventoryLoadOverflow.clear(); for (auto const& p : itemBags) { auto& bagType = p.first; auto newBag = ItemBag::loadStore(p.second); if (m_bags.contains(bagType)) { - auto& bag = m_bags[bagType]; - auto size = bag->size(); - if (bag) - *bag = std::move(newBag); - else - bag = make_shared(std::move(newBag)); - m_inventoryLoadOverflow.appendAll(bag->resize(size)); + auto& bag = m_bags.at(bagType); + m_inventoryLoadOverflow.appendAll(newBag.resize(bag->size())); + *bag = std::move(newBag); } else { m_inventoryLoadOverflow.appendAll(newBag.items()); } @@ -948,11 +943,24 @@ void PlayerInventory::cleanup() { } bool PlayerInventory::checkInventoryFilter(ItemPtr const& items, String const& filterName) { - auto config = Root::singleton().assets()->json("/player.config:inventoryFilters"); + Json filterConfig; + + auto itemFilters = items->instanceValue("inventoryFilters"); + if (itemFilters.isType(Json::Type::Object)) { + filterConfig = itemFilters.opt(filterName).value(); + if (!filterConfig.isType(Json::Type::Object)) + filterConfig = itemFilters.opt("default").value(); + } + + if (!filterConfig.isType(Json::Type::Object)) { + auto config = Root::singleton().assets()->json("/player.config:inventoryFilters"); + filterConfig = config.opt(filterName).value(); + if (!filterConfig.isType(Json::Type::Object)) + filterConfig = config.get("default"); + } // filter by item type if an itemTypes filter is set auto itemDatabase = Root::singleton().itemDatabase(); - auto filterConfig = config.get(filterName); auto itemTypeName = ItemTypeNames.getRight(itemDatabase->itemType(items->name())); if (filterConfig.contains("typeWhitelist") && !filterConfig.getArray("typeWhitelist").contains(itemTypeName)) return false; diff --git a/source/game/StarProjectile.cpp b/source/game/StarProjectile.cpp index 1503f4c..47cb973 100644 --- a/source/game/StarProjectile.cpp +++ b/source/game/StarProjectile.cpp @@ -27,7 +27,7 @@ Projectile::Projectile(ProjectileConfigPtr const& config, Json const& parameters setup(); } -Projectile::Projectile(ProjectileConfigPtr const& config, DataStreamBuffer& data) { +Projectile::Projectile(ProjectileConfigPtr const& config, DataStreamBuffer& data, NetCompatibilityRules rules) { m_config = config; data.read(m_parameters); setup(); @@ -41,8 +41,9 @@ Projectile::Projectile(ProjectileConfigPtr const& config, DataStreamBuffer& data setTeam(data.read()); } -ByteArray Projectile::netStore() const { +ByteArray Projectile::netStore(NetCompatibilityRules rules) const { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); ds.write(m_config->typeName); ds.write(m_parameters); @@ -141,12 +142,12 @@ Vec2F Projectile::velocity() const { return m_movementController->velocity(); } -pair Projectile::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair Projectile::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Projectile::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void Projectile::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void Projectile::enableInterpolation(float extrapolationHint) { diff --git a/source/game/StarProjectile.hpp b/source/game/StarProjectile.hpp index 2aec4b4..fdafdbd 100644 --- a/source/game/StarProjectile.hpp +++ b/source/game/StarProjectile.hpp @@ -22,9 +22,9 @@ STAR_CLASS(Projectile); class Projectile : public virtual Entity, public virtual ScriptedEntity, public virtual PhysicsEntity, public virtual StatusEffectEntity { public: Projectile(ProjectileConfigPtr const& config, Json const& parameters); - Projectile(ProjectileConfigPtr const& config, DataStreamBuffer& netState); + Projectile(ProjectileConfigPtr const& config, DataStreamBuffer& netState, NetCompatibilityRules rules = {}); - ByteArray netStore() const; + ByteArray netStore(NetCompatibilityRules rules = {}) const; EntityType entityType() const override; @@ -43,8 +43,8 @@ public: ClientEntityMode clientEntityMode() const override; bool masterOnly() const override; - pair writeNetState(uint64_t fromVersion = 0) override; - void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint = 0.0f) override; void disableInterpolation() override; diff --git a/source/game/StarProjectileDatabase.cpp b/source/game/StarProjectileDatabase.cpp index c2a0c26..bb7fbc7 100644 --- a/source/game/StarProjectileDatabase.cpp +++ b/source/game/StarProjectileDatabase.cpp @@ -58,10 +58,11 @@ float ProjectileDatabase::gravityMultiplier(String const& type) const { return config->movementSettings.getFloat("gravityMultiplier", 1); } -ProjectilePtr ProjectileDatabase::netLoadProjectile(ByteArray const& netStore) const { +ProjectilePtr ProjectileDatabase::netLoadProjectile(ByteArray const& netStore, NetCompatibilityRules rules) const { DataStreamBuffer ds(netStore); + ds.setStreamCompatibilityVersion(rules); String typeName = ds.read(); - return make_shared(m_configs.get(typeName), ds); + return make_shared(m_configs.get(typeName), ds, rules); } ProjectileConfigPtr ProjectileDatabase::readConfig(String const& path) { diff --git a/source/game/StarProjectileDatabase.hpp b/source/game/StarProjectileDatabase.hpp index d3cdd3d..dbd7299 100644 --- a/source/game/StarProjectileDatabase.hpp +++ b/source/game/StarProjectileDatabase.hpp @@ -111,7 +111,7 @@ public: float gravityMultiplier(String const& type) const; ProjectilePtr createProjectile(String const& type, Json const& parameters = JsonObject()) const; - ProjectilePtr netLoadProjectile(ByteArray const& netStore) const; + ProjectilePtr netLoadProjectile(ByteArray const& netStore, NetCompatibilityRules rules = {}) const; private: ProjectileConfigPtr readConfig(String const& path); diff --git a/source/game/StarQuestManager.cpp b/source/game/StarQuestManager.cpp index e9736c7..2aa22fb 100644 --- a/source/game/StarQuestManager.cpp +++ b/source/game/StarQuestManager.cpp @@ -73,7 +73,11 @@ void QuestManager::setUniverseClient(UniverseClient* client) { void QuestManager::init(World* world) { m_world = world; - serverQuests().exec([this, world](QuestPtr const& quest) { quest->init(m_player, world, m_client); }); + for (auto& quest : m_quests.values()) { + if (!questValidOnServer(quest)) + continue; + quest->init(m_player, world, m_client); + } m_trackOnWorldQuests = true; // untrack tracked quest if it's not cross-server, and we're on a different server @@ -124,6 +128,21 @@ void QuestManager::offer(QuestPtr const& quest) { quest->offer(); } +StringMap QuestManager::quests() const { + return m_quests; +} + +StringMap QuestManager::serverQuests() const { + StringMap filtered; + for (auto& pair : m_quests) { + QuestPtr q = pair.second; + if (!questValidOnServer(q)) + continue; + filtered.insert(pair.first, q); + } + return filtered; +} + QuestPtr QuestManager::getQuest(String const& questId) const { return m_quests.get(questId); } @@ -184,33 +203,33 @@ bool QuestManager::canTurnIn(String const& questId) const { } Maybe QuestManager::getFirstNewQuest() { - for (auto& q : serverQuests()) { - if (q->state() == QuestState::Offer) - return q; + for (auto& q : m_quests) { + if (questValidOnServer(q.second) && q.second->state() == QuestState::Offer) + return q.second; } return {}; } Maybe QuestManager::getFirstCompletableQuest() { - for (auto& q : serverQuests()) { - if (q->state() == QuestState::Complete && q->showDialog()) - return q; + for (auto& q : m_quests) { + if (questValidOnServer(q.second) && q.second->state() == QuestState::Complete && q.second->showDialog()) + return q.second; } return {}; } Maybe QuestManager::getFirstFailableQuest() { - for (auto& q : serverQuests()) { - if (q->state() == QuestState::Failed&& q->showDialog()) - return q; + for (auto& q : m_quests) { + if (questValidOnServer(q.second) && q.second->state() == QuestState::Failed && q.second->showDialog()) + return q.second; } return {}; } Maybe QuestManager::getFirstMainQuest() { - for (auto& q : serverQuests()) { - if (q->state() == QuestState::Active && q->mainQuest()) - return q; + for (auto& q : m_quests) { + if (questValidOnServer(q.second) && q.second->state() == QuestState::Active && q.second->mainQuest()) + return q.second; } return {}; } @@ -228,7 +247,7 @@ void sortQuests(List& quests) { } List QuestManager::listActiveQuests() const { - List result = serverQuests(); + List result = serverQuests().values(); result.filter([&](QuestPtr quest) { return quest->state() == QuestState::Active && quest->showInLog(); }); @@ -237,7 +256,7 @@ List QuestManager::listActiveQuests() const { } List QuestManager::listCompletedQuests() const { - List result = serverQuests(); + List result = serverQuests().values(); result.filter([](QuestPtr quest) { return quest->state() == QuestState::Complete && quest->showInLog(); }); @@ -246,7 +265,7 @@ List QuestManager::listCompletedQuests() const { } List QuestManager::listFailedQuests() const { - List result = serverQuests(); + List result = serverQuests().values(); result.filter([](QuestPtr quest) { return quest->state() == QuestState::Failed && quest->showInLog(); }); @@ -254,6 +273,10 @@ List QuestManager::listFailedQuests() const { return result; } +Maybe QuestManager::currentQuestId() const { + return m_trackedQuestId; +} + Maybe QuestManager::currentQuest() const { auto questId = m_onWorldQuestId.orMaybe(m_trackedQuestId); if (questId && isActive(*questId)) { @@ -264,6 +287,10 @@ Maybe QuestManager::currentQuest() const { return {}; } +Maybe QuestManager::trackedQuestId() const { + return m_trackedQuestId; +} + Maybe QuestManager::trackedQuest() const { if (m_trackedQuestId && isActive(*m_trackedQuestId)) { auto current = getQuest(*m_trackedQuestId); @@ -371,25 +398,24 @@ void QuestManager::update(float dt) { } } + List expiredQuests; for (auto& entry : m_quests) { auto quest = entry.second; QuestState state = quest->state(); bool finished = state == QuestState::Complete || state == QuestState::Failed; if (state == QuestState::New || (finished && quest->ephemeral() && !quest->showDialog())) { quest->uninit(); - m_quests.remove(entry.first); + expiredQuests.append(entry.first); } } - serverQuests().exec([dt](QuestPtr const& quest) { quest->update(dt); }); -} + for (auto& questId : expiredQuests) + m_quests.remove(questId); -List QuestManager::serverQuests() const { - return m_quests.values().filtered([this](QuestPtr const& q) -> bool { - if (q->hideCrossServer() && q->serverUuid().isValid() && *q->serverUuid() != m_player->clientContext()->serverUuid()) - return false; - return true; - }); + for (auto& q : m_quests.values()) { + if (questValidOnServer(q)) + q->update(dt); + } } void QuestManager::startInitialQuests() { @@ -408,4 +434,8 @@ void QuestManager::setMostRecentQuestCurrent() { setAsTracked(sortedActiveQuests.last()->questId()); } +bool QuestManager::questValidOnServer(QuestPtr q) const { + return !(q->hideCrossServer() && q->serverUuid().isValid() && *q->serverUuid() != m_player->clientContext()->serverUuid()); +} + } diff --git a/source/game/StarQuestManager.hpp b/source/game/StarQuestManager.hpp index 1dfcb2c..549f13b 100644 --- a/source/game/StarQuestManager.hpp +++ b/source/game/StarQuestManager.hpp @@ -28,6 +28,9 @@ public: // Show a dialog offering the player a quest, and later start it if they // accept it. void offer(QuestPtr const& quest); + StringMap quests() const; + // Only returns quests that are exclusive to the current server. + StringMap serverQuests() const; QuestPtr getQuest(String const& questId) const; bool hasQuest(String const& questId) const; @@ -49,7 +52,9 @@ public: List listCompletedQuests() const; List listFailedQuests() const; + Maybe currentQuestId() const; Maybe currentQuest() const; + Maybe trackedQuestId() const; Maybe trackedQuest() const; Maybe getQuestIndicator(EntityPtr const& entity) const; @@ -60,9 +65,9 @@ public: void update(float dt); private: - List serverQuests() const; void startInitialQuests(); void setMostRecentQuestCurrent(); + bool questValidOnServer(QuestPtr quest) const; Player* m_player; World* m_world; diff --git a/source/game/StarQuests.cpp b/source/game/StarQuests.cpp index 6e1dba7..d0d9d99 100644 --- a/source/game/StarQuests.cpp +++ b/source/game/StarQuests.cpp @@ -216,6 +216,13 @@ Maybe Quest::receiveMessage(String const& message, bool localMessage, Json return m_scriptComponent.handleMessage(message, localMessage, args); } + +Maybe Quest::callScript(String const& func, LuaVariadic const& args) { + if (!m_inited) + return {}; + return m_scriptComponent.invoke(func, args); +} + void Quest::update(float dt) { if (!m_inited) return; @@ -599,12 +606,20 @@ void Quest::uninitScript() { LuaCallbacks Quest::makeQuestCallbacks(Player* player) { LuaCallbacks callbacks; + callbacks.registerCallback("context", [this]() { m_scriptComponent.context(); }); + callbacks.registerCallback("state", [this]() { return QuestStateNames.getRight(state()); }); callbacks.registerCallback("complete", [this](Maybe followup) { complete(followup); }); callbacks.registerCallback("fail", [this]() { fail(); }); + callbacks.registerCallback("abandon", [this]() { abandon(); }); + + callbacks.registerCallback("decline", [this]() { declineOffer(); }); + + callbacks.registerCallback("cancel", [this]() { cancelOffer(); }); + callbacks.registerCallback("setCanTurnIn", [this](bool value) { this->m_canTurnIn = value; }); callbacks.registerCallback("questId", [this]() { return questId(); }); diff --git a/source/game/StarQuests.hpp b/source/game/StarQuests.hpp index fc35f2b..6a4b128 100644 --- a/source/game/StarQuests.hpp +++ b/source/game/StarQuests.hpp @@ -45,6 +45,7 @@ public: void uninit(); Maybe receiveMessage(String const& message, bool localMessage, JsonArray const& args = {}); + Maybe callScript(String const& func, LuaVariadic const& args); void update(float dt); void offer(); diff --git a/source/game/StarRoot.cpp b/source/game/StarRoot.cpp index 626e298..bda228c 100644 --- a/source/game/StarRoot.cpp +++ b/source/game/StarRoot.cpp @@ -137,6 +137,13 @@ Root::Root(Settings settings) : RootBase() { tenantDb->cleanup(); } } + { + MutexLocker locker(m_imageMetadataDatabaseMutex); + if (ImageMetadataDatabasePtr imgMetaDb = m_imageMetadataDatabase) { + locker.unlock(); + imgMetaDb->cleanup(); + } + } Random::addEntropy(); @@ -578,6 +585,7 @@ StringList Root::scanForAssetSources(StringList const& directories, StringList c struct AssetSource { String path; Maybe name; + Maybe version; float priority; StringList requires_; StringList includes; @@ -605,6 +613,7 @@ StringList Root::scanForAssetSources(StringList const& directories, StringList c auto assetSource = make_shared(); assetSource->path = sourcePath; assetSource->name = metadata.maybe("name").apply(mem_fn(&Json::toString)); + assetSource->version = metadata.maybe("version").apply(mem_fn(&Json::toString)); assetSource->priority = metadata.value("priority", 0.0f).toFloat(); assetSource->requires_ = jsonToStringList(metadata.value("requires", JsonArray{})); assetSource->includes = jsonToStringList(metadata.value("includes", JsonArray{})); @@ -698,7 +707,7 @@ StringList Root::scanForAssetSources(StringList const& directories, StringList c for (auto const& source : dependencySortedSources) { auto path = File::convertDirSeparators(source->path); if (source->name) - Logger::info("Root: Detected asset source named '{}' at '{}'", *source->name, path); + Logger::info("Root: Detected asset source named '{}'{} at '{}'", *source->name, source->version ? strf(" version '{}'", *source->version) : "", path); else Logger::info("Root: Detected unnamed asset source at '{}'", path); sourcePaths.append(path); diff --git a/source/game/StarServerClientContext.cpp b/source/game/StarServerClientContext.cpp index 838dae0..cd7c6cd 100644 --- a/source/game/StarServerClientContext.cpp +++ b/source/game/StarServerClientContext.cpp @@ -10,10 +10,11 @@ namespace Star { -ServerClientContext::ServerClientContext(ConnectionId clientId, Maybe remoteAddress, Uuid playerUuid, +ServerClientContext::ServerClientContext(ConnectionId clientId, Maybe remoteAddress, NetCompatibilityRules netRules, Uuid playerUuid, String playerName, String playerSpecies, bool canBecomeAdmin, WorldChunks initialShipChunks) : m_clientId(clientId), m_remoteAddress(remoteAddress), + m_netRules(netRules), m_playerUuid(playerUuid), m_playerName(playerName), m_playerSpecies(playerSpecies), @@ -62,6 +63,8 @@ ServerClientContext::ServerClientContext(ConnectionId clientId, Maybe remoteAddress, Uuid playerUuid, + ServerClientContext(ConnectionId clientId, Maybe remoteAddress, NetCompatibilityRules netRules, Uuid playerUuid, String playerName, String playerSpecies, bool canBecomeAdmin, WorldChunks initialShipChunks); ConnectionId clientId() const; @@ -28,6 +28,7 @@ public: String const& playerName() const; String const& playerSpecies() const; bool canBecomeAdmin() const; + NetCompatibilityRules netRules() const; String descriptiveName() const; // Register additional rpc methods from other server side services. @@ -84,9 +85,12 @@ public: void loadServerData(Json const& store); Json storeServerData(); + int64_t creationTime() const; + private: ConnectionId const m_clientId; Maybe const m_remoteAddress; + NetCompatibilityRules m_netRules; Uuid const m_playerUuid; String const m_playerName; String const m_playerSpecies; @@ -107,6 +111,7 @@ private: NetElementTopGroup m_netGroup; uint64_t m_netVersion = 0; + int64_t m_creationTime; NetElementData>> m_orbitWarpActionNetState; NetElementData m_playerWorldIdNetState; diff --git a/source/game/StarSky.cpp b/source/game/StarSky.cpp index d9ac03a..9b3363d 100644 --- a/source/game/StarSky.cpp +++ b/source/game/StarSky.cpp @@ -13,11 +13,7 @@ namespace Star { Sky::Sky() { - m_settings = Root::singleton().assets()->json("/sky.config"); - - m_starFrames = m_settings.queryInt("stars.frames"); - m_starList = jsonToStringList(m_settings.query("stars.list")); - m_hyperStarList = jsonToStringList(m_settings.query("stars.hyperlist")); + skyParametersUpdated(); m_netInit = false; @@ -38,7 +34,7 @@ Sky::Sky() { Sky::Sky(SkyParameters const& skyParameters, bool inOrbit) : Sky() { m_skyParameters = skyParameters; - m_skyParametersUpdated = true; + skyParametersUpdated(); if (inOrbit) m_skyType = SkyType::Orbital; @@ -46,7 +42,7 @@ Sky::Sky(SkyParameters const& skyParameters, bool inOrbit) : Sky() { m_skyType = m_skyParameters.skyType; } -void Sky::startFlying(bool enterHyperspace, bool startInWarp) { +void Sky::startFlying(bool enterHyperspace, bool startInWarp, Json settings) { if (startInWarp) m_flyingType = FlyingType::Warp; else @@ -55,6 +51,10 @@ void Sky::startFlying(bool enterHyperspace, bool startInWarp) { m_flyingTimer = 0; m_enterHyperspace = enterHyperspace; m_startInWarp = startInWarp; + if (settings.isType(Json::Type::Object)) { + m_skyParameters.settings = settings; + skyParametersUpdated(); + } } void Sky::stopFlyingAt(Maybe dest) { @@ -63,15 +63,15 @@ void Sky::stopFlyingAt(Maybe dest) { void Sky::jumpTo(SkyParameters skyParameters) { m_skyParameters = skyParameters; - m_skyParametersUpdated = true; + skyParametersUpdated(); } -pair Sky::writeUpdate(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair Sky::writeUpdate(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Sky::readUpdate(ByteArray data) { - m_netGroup.readNetState(std::move(data)); +void Sky::readUpdate(ByteArray data, NetCompatibilityRules rules) { + m_netGroup.readNetState(std::move(data), 0.0f, rules); } void Sky::stateUpdate() { @@ -531,8 +531,10 @@ void Sky::writeNetStates() { } void Sky::readNetStates() { - if (m_skyParametersNetState.pullUpdated()) + if (m_skyParametersNetState.pullUpdated()) { m_skyParameters = SkyParameters(DataStreamBuffer::deserialize(m_skyParametersNetState.get())); + skyParametersUpdated(); + } m_skyType = (SkyType)m_skyTypeNetState.get(); m_time = m_timeNetState.get(); @@ -651,4 +653,12 @@ float Sky::slowdownTime() const { return m_settings.queryFloat("slowdownTime"); } +void Sky::skyParametersUpdated() { + m_skyParametersUpdated = true; + m_settings = jsonMerge(Root::singleton().assets()->json("/sky.config"), m_skyParameters.settings); + m_starFrames = m_settings.queryInt("stars.frames"); + m_starList = jsonToStringList(m_settings.query("stars.list")); + m_hyperStarList = jsonToStringList(m_settings.query("stars.hyperlist")); +} + } diff --git a/source/game/StarSky.hpp b/source/game/StarSky.hpp index 4f6ec0c..dcb3383 100644 --- a/source/game/StarSky.hpp +++ b/source/game/StarSky.hpp @@ -23,15 +23,15 @@ public: Sky(SkyParameters const& skyParameters, bool inOrbit); // Controls the space sky "flight" system - void startFlying(bool enterHyperspace, bool startInWarp); + void startFlying(bool enterHyperspace, bool startInWarp, Json settings = {}); // Stops flying animation copying the new pertinant sky data from the given // sky, as though the sky as moved to a new world. void stopFlyingAt(Maybe SkyParameters); void jumpTo(SkyParameters SkyParameters); - pair writeUpdate(uint64_t fromVersion = 0); - void readUpdate(ByteArray data); + pair writeUpdate(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}); + void readUpdate(ByteArray data, NetCompatibilityRules rules = {}); // handles flying and warp state transitions void stateUpdate(); @@ -117,6 +117,8 @@ private: float speedupTime() const; float slowdownTime() const; + void skyParametersUpdated(); + Json m_settings; SkyParameters m_skyParameters; bool m_skyParametersUpdated; diff --git a/source/game/StarSkyParameters.cpp b/source/game/StarSkyParameters.cpp index 3eddce5..e2ed885 100644 --- a/source/game/StarSkyParameters.cpp +++ b/source/game/StarSkyParameters.cpp @@ -7,7 +7,7 @@ namespace Star { -SkyParameters::SkyParameters() : seed(), skyType(SkyType::Barren), skyColoring(makeRight(Color::Black)) {} +SkyParameters::SkyParameters() : seed(), skyType(SkyType::Barren), skyColoring(makeRight(Color::Black)), settings(JsonObject()) {} SkyParameters::SkyParameters(CelestialCoordinate const& coordinate, CelestialDatabasePtr const& celestialDatabase) : SkyParameters() { @@ -113,6 +113,8 @@ SkyParameters::SkyParameters(Json const& config) : SkyParameters() { surfaceLevel = config.optFloat("surfaceLevel"); sunType = config.getString("sunType", ""); + + settings = config.get("settings", JsonObject()); } Json SkyParameters::toJson() const { @@ -155,6 +157,7 @@ Json SkyParameters::toJson() const { {"spaceLevel", jsonFromMaybe(spaceLevel)}, {"surfaceLevel", jsonFromMaybe(surfaceLevel)}, {"sunType", sunType}, + {"settings", settings} }; } @@ -170,6 +173,8 @@ void SkyParameters::read(DataStream& ds) { ds >> spaceLevel; ds >> surfaceLevel; ds >> sunType; + if (ds.streamCompatibilityVersion() >= 3) + ds >> settings; } void SkyParameters::write(DataStream& ds) const { @@ -184,6 +189,8 @@ void SkyParameters::write(DataStream& ds) const { ds << spaceLevel; ds << surfaceLevel; ds << sunType; + if (ds.streamCompatibilityVersion() >= 3) + ds << settings; } void SkyParameters::readVisitableParameters(VisitableWorldParametersConstPtr visitableParameters) { diff --git a/source/game/StarSkyParameters.hpp b/source/game/StarSkyParameters.hpp index 50b476f..35d4284 100644 --- a/source/game/StarSkyParameters.hpp +++ b/source/game/StarSkyParameters.hpp @@ -45,6 +45,7 @@ struct SkyParameters { Maybe spaceLevel; Maybe surfaceLevel; String sunType; + Json settings; }; DataStream& operator>>(DataStream& ds, SkyParameters& sky); diff --git a/source/game/StarSkyRenderData.cpp b/source/game/StarSkyRenderData.cpp index 53a2c4f..54db722 100644 --- a/source/game/StarSkyRenderData.cpp +++ b/source/game/StarSkyRenderData.cpp @@ -6,7 +6,7 @@ namespace Star { -StringList SkyRenderData::starTypes() const { +StringList const& SkyRenderData::starTypes() const { if (type == SkyType::Warp) return hyperStarList; else diff --git a/source/game/StarSkyRenderData.hpp b/source/game/StarSkyRenderData.hpp index 0e004b0..8d47282 100644 --- a/source/game/StarSkyRenderData.hpp +++ b/source/game/StarSkyRenderData.hpp @@ -32,7 +32,7 @@ struct SkyRenderData { Color bottomRectColor; Color flashColor; - StringList starTypes() const; + StringList const& starTypes() const; // Star and orbiter positions here are in view space, from (0, 0) to viewSize diff --git a/source/game/StarStagehand.cpp b/source/game/StarStagehand.cpp index 83b7673..d85f77b 100644 --- a/source/game/StarStagehand.cpp +++ b/source/game/StarStagehand.cpp @@ -14,8 +14,7 @@ Stagehand::Stagehand(Json const& config) readConfig(config); } -Stagehand::Stagehand(ByteArray const& netStore) - : Stagehand() { +Stagehand::Stagehand(ByteArray const& netStore, NetCompatibilityRules rules) : Stagehand() { readConfig(DataStreamBuffer::deserialize(netStore)); } @@ -31,7 +30,7 @@ Json Stagehand::diskStore() const { return saveData.set("scriptStorage", m_scriptComponent.getScriptStorage()); } -ByteArray Stagehand::netStore() { +ByteArray Stagehand::netStore(NetCompatibilityRules rules) { return DataStreamBuffer::serialize(m_config); } @@ -77,12 +76,12 @@ RectF Stagehand::metaBoundBox() const { return m_boundBox; } -pair Stagehand::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair Stagehand::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Stagehand::readNetState(ByteArray data, float) { - m_netGroup.readNetState(std::move(data)); +void Stagehand::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void Stagehand::update(float dt, uint64_t) { diff --git a/source/game/StarStagehand.hpp b/source/game/StarStagehand.hpp index 4297370..4403946 100644 --- a/source/game/StarStagehand.hpp +++ b/source/game/StarStagehand.hpp @@ -15,10 +15,10 @@ STAR_CLASS(Stagehand); class Stagehand : public virtual ScriptedEntity { public: Stagehand(Json const& config); - Stagehand(ByteArray const& netStore); + Stagehand(ByteArray const& netStore, NetCompatibilityRules rules = {}); Json diskStore() const; - ByteArray netStore(); + ByteArray netStore(NetCompatibilityRules rules = {}); void init(World* world, EntityId entityId, EntityMode mode) override; void uninit() override; @@ -31,8 +31,8 @@ public: RectF metaBoundBox() const override; - pair writeNetState(uint64_t fromVersion = 0) override; - void readNetState(ByteArray data, float interpolationTime = 0.0f) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void update(float dt, uint64_t currentStep) override; diff --git a/source/game/StarStatusController.cpp b/source/game/StarStatusController.cpp index 5a87874..26c83d0 100644 --- a/source/game/StarStatusController.cpp +++ b/source/game/StarStatusController.cpp @@ -18,7 +18,32 @@ StatusController::StatusController(Json const& config) : m_statCollection(config m_parentEntity = nullptr; m_movementController = nullptr; - m_statusProperties.set(config.getObject("statusProperties", {})); + m_statusProperties.reset(config.getObject("statusProperties", {})); + m_statusProperties.setOverrides( + [&](DataStream& ds, NetCompatibilityRules rules) { + if (rules.version() <= 1) ds << m_statusProperties.baseMap(); + else m_statusProperties.NetElementHashMap::netStore(ds, rules); + }, + [&](DataStream& ds, NetCompatibilityRules rules) { + if (rules.version() <= 1) m_statusProperties.reset(ds.read()); + else m_statusProperties.NetElementHashMap::netLoad(ds, rules); + }, + [&](DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) { + if (rules.version() <= 1) { + if (m_statusProperties.shouldWriteNetDelta(fromVersion, rules)) { + ds << m_statusProperties.baseMap(); + return true; + } + return false; + } + return m_statusProperties.NetElementHashMap::writeNetDelta(ds, fromVersion, rules); + }, + [&](DataStream& ds, float interp, NetCompatibilityRules rules) { + if (rules.version() <= 1) m_statusProperties.reset(ds.read()); + else m_statusProperties.NetElementHashMap::readNetDelta(ds, interp, rules); + } + ); + m_minimumLiquidStatusEffectPercentage = config.getFloat("minimumLiquidStatusEffectPercentage"); m_appliesEnvironmentStatusEffects = config.getBool("appliesEnvironmentStatusEffects"); m_appliesWeatherStatusEffects = config.getBool("appliesWeatherStatusEffects"); @@ -72,7 +97,7 @@ Json StatusController::diskStore() const { } return JsonObject{ - {"statusProperties", m_statusProperties.get()}, + {"statusProperties", m_statusProperties.baseMap()}, {"persistentEffectCategories", std::move(persistentEffectCategories)}, {"ephemeralEffects", std::move(ephemeralEffects)}, {"resourceValues", std::move(resourceValues)}, @@ -84,7 +109,7 @@ void StatusController::diskLoad(Json const& store) { clearAllPersistentEffects(); clearEphemeralEffects(); - m_statusProperties.set(store.getObject("statusProperties")); + m_statusProperties.reset(store.getObject("statusProperties")); for (auto const& p : store.getObject("persistentEffectCategories", {})) addPersistentEffects(p.first, p.second.toArray().transformed(jsonToPersistentStatusEffect)); @@ -103,17 +128,11 @@ void StatusController::diskLoad(Json const& store) { } Json StatusController::statusProperty(String const& name, Json const& def) const { - return m_statusProperties.get().value(name, def); + return m_statusProperties.value(name, def); } void StatusController::setStatusProperty(String const& name, Json value) { - m_statusProperties.update([&](JsonObject& statusProperties) { - if (statusProperties[name] != value) { - statusProperties[name] = std::move(value); - return true; - } - return false; - }); + m_statusProperties.set(name, value); } StringList StatusController::statNames() const { @@ -391,8 +410,9 @@ void StatusController::init(Entity* parentEntity, ActorMovementController* movem if (m_parentEntity->isMaster()) { initPrimaryScript(); - for (auto& p : m_uniqueEffects) - initUniqueEffectScript(p.second); + for (auto& p : m_uniqueEffects.keys()) + if (auto effect = m_uniqueEffects.ptr(p)) + initUniqueEffectScript(*effect); } m_environmentStatusEffectUpdateTimer.reset(); @@ -402,8 +422,9 @@ void StatusController::uninit() { m_parentEntity = nullptr; m_movementController = nullptr; - for (auto& p : m_uniqueEffects) - uninitUniqueEffectScript(p.second); + for (auto& p : m_uniqueEffects.keys()) + if (auto effect = m_uniqueEffects.ptr(p)) + uninitUniqueEffectScript(*effect); uninitPrimaryScript(); m_recentHitsGiven.reset(); @@ -415,15 +436,17 @@ void StatusController::initNetVersion(NetElementVersion const* version) { m_netGroup.initNetVersion(version); } -void StatusController::netStore(DataStream& ds) const { - m_netGroup.netStore(ds); +void StatusController::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; + m_netGroup.netStore(ds, rules); } -void StatusController::netLoad(DataStream& ds) { +void StatusController::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; clearAllPersistentEffects(); clearEphemeralEffects(); - m_netGroup.netLoad(ds); + m_netGroup.netLoad(ds, rules); } void StatusController::enableNetInterpolation(float extrapolationHint) { @@ -438,12 +461,12 @@ void StatusController::tickNetInterpolation(float dt) { m_netGroup.tickNetInterpolation(dt); } -bool StatusController::writeNetDelta(DataStream& ds, uint64_t fromStep) const { - return m_netGroup.writeNetDelta(ds, fromStep); +bool StatusController::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + return m_netGroup.writeNetDelta(ds, fromVersion, rules); } -void StatusController::readNetDelta(DataStream& ds, float interpolationTime) { - m_netGroup.readNetDelta(ds, interpolationTime); +void StatusController::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetDelta(ds, interpolationTime, rules); } void StatusController::blankNetDelta(float interpolationTime) { @@ -576,15 +599,17 @@ void StatusController::EffectAnimator::initNetVersion(NetElementVersion const* v animator.initNetVersion(version); } -void StatusController::EffectAnimator::netStore(DataStream& ds) const { +void StatusController::EffectAnimator::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; ds.write(animationConfig); - animator.netStore(ds); + animator.netStore(ds, rules); } -void StatusController::EffectAnimator::netLoad(DataStream& ds) { +void StatusController::EffectAnimator::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; ds.read(animationConfig); animator = animationConfig ? NetworkedAnimator(*animationConfig) : NetworkedAnimator(); - animator.netLoad(ds); + animator.netLoad(ds, rules); } void StatusController::EffectAnimator::enableNetInterpolation(float extrapolationHint) { @@ -599,12 +624,12 @@ void StatusController::EffectAnimator::tickNetInterpolation(float dt) { animator.tickNetInterpolation(dt); } -bool StatusController::EffectAnimator::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { - return animator.writeNetDelta(ds, fromVersion); +bool StatusController::EffectAnimator::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + return animator.writeNetDelta(ds, fromVersion, rules); } -void StatusController::EffectAnimator::readNetDelta(DataStream& ds, float interpolationTime) { - animator.readNetDelta(ds, interpolationTime); +void StatusController::EffectAnimator::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + animator.readNetDelta(ds, interpolationTime, rules); } void StatusController::EffectAnimator::blankNetDelta(float interpolationTime) { diff --git a/source/game/StarStatusController.hpp b/source/game/StarStatusController.hpp index 810a2c9..039cc3b 100644 --- a/source/game/StarStatusController.hpp +++ b/source/game/StarStatusController.hpp @@ -2,6 +2,7 @@ #include "StarObserverStream.hpp" #include "StarNetElementSystem.hpp" +#include "StarNetElementExt.hpp" #include "StarStatCollection.hpp" #include "StarStatusEffectDatabase.hpp" #include "StarDamage.hpp" @@ -103,15 +104,15 @@ public: void initNetVersion(NetElementVersion const* version = nullptr) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; void enableNetInterpolation(float extrapolationHint = 0.0f) override; void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime) override; void tickMaster(float dt); @@ -136,15 +137,15 @@ private: void initNetVersion(NetElementVersion const* version = nullptr) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; void enableNetInterpolation(float extrapolationHint = 0.0f) override; void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime) override; Maybe animationConfig; @@ -203,7 +204,7 @@ private: NetElementGroup m_netGroup; StatCollection m_statCollection; - NetElementData m_statusProperties; + NetElementOverride> m_statusProperties; NetElementData m_parentDirectives; UniqueEffectMetadataGroup m_uniqueEffectMetadata; diff --git a/source/game/StarSystemWorld.cpp b/source/game/StarSystemWorld.cpp index c7e1213..9823b8f 100644 --- a/source/game/StarSystemWorld.cpp +++ b/source/game/StarSystemWorld.cpp @@ -453,12 +453,12 @@ void SystemObject::serverUpdate(SystemWorldServer* system, float dt) { } } -pair SystemObject::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair SystemObject::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void SystemObject::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void SystemObject::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } ByteArray SystemObject::netStore() const { @@ -615,12 +615,12 @@ void SystemClientShip::serverUpdate(SystemWorld* system, float dt) { } } -pair SystemClientShip::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair SystemClientShip::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void SystemClientShip::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void SystemClientShip::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } ByteArray SystemClientShip::netStore() const { diff --git a/source/game/StarSystemWorld.hpp b/source/game/StarSystemWorld.hpp index db12833..cde3323 100644 --- a/source/game/StarSystemWorld.hpp +++ b/source/game/StarSystemWorld.hpp @@ -155,8 +155,8 @@ public: void clientUpdate(float dt); void serverUpdate(SystemWorldServer* system, float dt); - pair writeNetState(uint64_t fromVersion); - void readNetState(ByteArray data, float interpolationTime); + pair writeNetState(uint64_t fromVersion, NetCompatibilityRules rules = {}); + void readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules = {}); ByteArray netStore() const; Json diskStore() const; @@ -198,8 +198,8 @@ public: void clientUpdate(float dt); void serverUpdate(SystemWorld* system, float dt); - pair writeNetState(uint64_t fromVersion); - void readNetState(ByteArray data, float interpolationTime); + pair writeNetState(uint64_t fromVersion, NetCompatibilityRules rules = {}); + void readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules = {}); ByteArray netStore() const; private: diff --git a/source/game/StarSystemWorldServer.cpp b/source/game/StarSystemWorldServer.cpp index 3b847eb..201b90b 100644 --- a/source/game/StarSystemWorldServer.cpp +++ b/source/game/StarSystemWorldServer.cpp @@ -266,7 +266,7 @@ void SystemWorldServer::queueUpdatePackets() { HashMap shipUpdates; for (auto ship : m_ships.values()) { uint64_t version = versions->ships.maybe(ship->uuid()).value(0); - auto shipUpdate = ship->writeNetState(version); + auto shipUpdate = ship->writeNetState(version, {}); versions->ships.set(ship->uuid(), shipUpdate.second); if (!shipUpdate.first.empty()) shipUpdates.set(ship->uuid(), shipUpdate.first); @@ -275,7 +275,7 @@ void SystemWorldServer::queueUpdatePackets() { HashMap objectUpdates; for (auto object : m_objects.values()) { uint64_t version = versions->objects.maybe(object->uuid()).value(0); - auto objectUpdate = object->writeNetState(version); + auto objectUpdate = object->writeNetState(version, {}); versions->objects.set(object->uuid(), objectUpdate.second); if (!objectUpdate.first.empty()) objectUpdates.set(object->uuid(), objectUpdate.first); diff --git a/source/game/StarTechController.cpp b/source/game/StarTechController.cpp index 90926de..4c0ed55 100644 --- a/source/game/StarTechController.cpp +++ b/source/game/StarTechController.cpp @@ -358,15 +358,17 @@ void TechController::TechAnimator::initNetVersion(NetElementVersion const* versi netGroup.initNetVersion(version); } -void TechController::TechAnimator::netStore(DataStream& ds) const { +void TechController::TechAnimator::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; ds << animationConfig; - netGroup.netStore(ds); + netGroup.netStore(ds, rules); } -void TechController::TechAnimator::netLoad(DataStream& ds) { +void TechController::TechAnimator::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; ds >> animationConfig; animator = animationConfig ? NetworkedAnimator(*animationConfig) : NetworkedAnimator(); - netGroup.netLoad(ds); + netGroup.netLoad(ds, rules); } void TechController::TechAnimator::enableNetInterpolation(float extrapolationHint) { @@ -381,12 +383,12 @@ void TechController::TechAnimator::tickNetInterpolation(float dt) { netGroup.tickNetInterpolation(dt); } -bool TechController::TechAnimator::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { - return netGroup.writeNetDelta(ds, fromVersion); +bool TechController::TechAnimator::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + return netGroup.writeNetDelta(ds, fromVersion, rules); } -void TechController::TechAnimator::readNetDelta(DataStream& ds, float interpolationTime) { - netGroup.readNetDelta(ds, interpolationTime); +void TechController::TechAnimator::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + netGroup.readNetDelta(ds, interpolationTime, rules); } void TechController::TechAnimator::blankNetDelta(float interpolationTime) { diff --git a/source/game/StarTechController.hpp b/source/game/StarTechController.hpp index 3d642df..c399cb5 100644 --- a/source/game/StarTechController.hpp +++ b/source/game/StarTechController.hpp @@ -90,15 +90,15 @@ private: void initNetVersion(NetElementVersion const* version = nullptr) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; void enableNetInterpolation(float extrapolationHint = 0.0f) override; void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime) override; // If setting invisible, stops all playing audio diff --git a/source/game/StarToolUser.cpp b/source/game/StarToolUser.cpp index 4135dd0..be239c1 100644 --- a/source/game/StarToolUser.cpp +++ b/source/game/StarToolUser.cpp @@ -612,15 +612,17 @@ void ToolUser::NetItem::initNetVersion(NetElementVersion const* version) { netItem->initNetVersion(m_netVersion); } -void ToolUser::NetItem::netStore(DataStream& ds) const { +void ToolUser::NetItem::netStore(DataStream& ds, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return; const_cast(this)->updateItemDescriptor(); - m_itemDescriptor.netStore(ds); + m_itemDescriptor.netStore(ds, rules); if (auto netItem = as(m_item.get())) - netItem->netStore(ds); + netItem->netStore(ds, rules); } -void ToolUser::NetItem::netLoad(DataStream& ds) { - m_itemDescriptor.netLoad(ds); +void ToolUser::NetItem::netLoad(DataStream& ds, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; + m_itemDescriptor.netLoad(ds, rules); auto itemDatabase = Root::singleton().itemDatabase(); if (itemDatabase->loadItem(m_itemDescriptor.get(), m_item)) { @@ -633,7 +635,7 @@ void ToolUser::NetItem::netLoad(DataStream& ds) { } if (auto netItem = as(m_item.get())) - netItem->netLoad(ds); + netItem->netLoad(ds, rules); } void ToolUser::NetItem::enableNetInterpolation(float extrapolationHint) { @@ -657,23 +659,24 @@ void ToolUser::NetItem::tickNetInterpolation(float dt) { } } -bool ToolUser::NetItem::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { +bool ToolUser::NetItem::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const { + if (!checkWithRules(rules)) return false; bool deltaWritten = false; const_cast(this)->updateItemDescriptor(); m_buffer.clear(); - if (m_itemDescriptor.writeNetDelta(m_buffer, fromVersion)) { + if (m_itemDescriptor.writeNetDelta(m_buffer, fromVersion, rules)) { deltaWritten = true; ds.write(1); ds.writeBytes(m_buffer.data()); if (auto netItem = as(m_item.get())) { ds.write(2); - netItem->netStore(ds); + netItem->netStore(ds, rules); } } if (auto netItem = as(m_item.get())) { m_buffer.clear(); - if (netItem->writeNetDelta(m_buffer, fromVersion)) { + if (netItem->writeNetDelta(m_buffer, fromVersion, rules)) { deltaWritten = true; ds.write(3); ds.writeBytes(m_buffer.data()); @@ -685,13 +688,14 @@ bool ToolUser::NetItem::writeNetDelta(DataStream& ds, uint64_t fromVersion) cons return deltaWritten; } -void ToolUser::NetItem::readNetDelta(DataStream& ds, float interpolationTime) { +void ToolUser::NetItem::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) { + if (!checkWithRules(rules)) return; while (true) { uint8_t code = ds.read(); if (code == 0) { break; } else if (code == 1) { - m_itemDescriptor.readNetDelta(ds); + m_itemDescriptor.readNetDelta(ds, 0.0f, rules); if (!m_item || !m_item->matches(m_itemDescriptor.get(), true)) { auto itemDatabase = Root::singleton().itemDatabase(); if (itemDatabase->loadItem(m_itemDescriptor.get(), m_item)) { @@ -705,12 +709,12 @@ void ToolUser::NetItem::readNetDelta(DataStream& ds, float interpolationTime) { } } else if (code == 2) { if (auto netItem = as(m_item.get())) - netItem->netLoad(ds); + netItem->netLoad(ds, rules); else throw IOException("Server/Client disagreement about whether an Item is a NetElement in NetItem::readNetDelta"); } else if (code == 3) { if (auto netItem = as(m_item.get())) - netItem->readNetDelta(ds, interpolationTime); + netItem->readNetDelta(ds, interpolationTime, rules); else throw IOException("Server/Client disagreement about whether an Item is a NetElement in NetItem::readNetDelta"); } else { diff --git a/source/game/StarToolUser.hpp b/source/game/StarToolUser.hpp index 41fe98f..0b954e2 100644 --- a/source/game/StarToolUser.hpp +++ b/source/game/StarToolUser.hpp @@ -81,15 +81,15 @@ private: public: void initNetVersion(NetElementVersion const* version = nullptr) override; - void netStore(DataStream& ds) const override; - void netLoad(DataStream& ds) override; + void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override; + void netLoad(DataStream& ds, NetCompatibilityRules rules) override; void enableNetInterpolation(float extrapolationHint = 0.0f) override; void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; - bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; - void readNetDelta(DataStream& ds, float interpolationTime = 0.0) override; + bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void blankNetDelta(float interpolationTime) override; ItemPtr const& get() const; diff --git a/source/game/StarUniverseClient.cpp b/source/game/StarUniverseClient.cpp index ed59c4b..70a227e 100644 --- a/source/game/StarUniverseClient.cpp +++ b/source/game/StarUniverseClient.cpp @@ -28,11 +28,17 @@ namespace Star { UniverseClient::UniverseClient(PlayerStoragePtr playerStorage, StatisticsPtr statistics) { + auto& root = Root::singleton(); + auto assets = root.assets(); + m_storageTriggerDeadline = 0; m_playerStorage = std::move(playerStorage); m_statistics = std::move(statistics); m_pause = false; m_luaRoot = make_shared(); + + auto clientConfig = assets->json("/client.config"); + m_luaRoot->tuneAutoGarbageCollection(clientConfig.getFloat("luaGcPause"), clientConfig.getFloat("luaGcStepMultiplier")); reset(); } @@ -94,27 +100,38 @@ Maybe UniverseClient::connect(UniverseConnection connection, bool allowA else if (!protocolResponsePacket->allowed) return String(strf("Join failed! Server does not support connections with protocol version {}", StarProtocolVersion)); - if (!(m_legacyServer = protocolResponsePacket->compressionMode() != PacketCompressionMode::Enabled)) { - if (auto compressedSocket = as(&connection.packetSocket())) { - if (protocolResponsePacket->info) { - auto compressionName = protocolResponsePacket->info.getString("compression", "None"); + NetCompatibilityRules compatibilityRules; + compatibilityRules.setVersion(LegacyVersion); + bool legacyServer = protocolResponsePacket->compressionMode() != PacketCompressionMode::Enabled; + if (!legacyServer) { + auto compressedSocket = as(&connection.packetSocket()); + if (protocolResponsePacket->info) { + compatibilityRules.setVersion(protocolResponsePacket->info.getUInt("openProtocolVersion", 1)); + auto compressionName = protocolResponsePacket->info.getString("compression", "None"); + if (compressedSocket) { auto compressionMode = NetCompressionModeNames.maybeLeft(compressionName); if (!compressionMode) return String(strf("Join failed! Unknown net stream connection type '{}'", compressionName)); Logger::info("UniverseClient: Using '{}' network stream compression", NetCompressionModeNames.getRight(*compressionMode)); compressedSocket->setCompressionStreamEnabled(compressionMode == NetCompressionMode::Zstd); - } else if (!m_legacyServer) { + } + } else { + compatibilityRules.setVersion(1); // A version of 1 is OpenStarbound prior to the NetElement compatibility stuff + if (compressedSocket) { Logger::info("UniverseClient: Defaulting to Zstd network stream compression (older server version)"); - compressedSocket->setCompressionStreamEnabled(true);// old OpenSB server version always expects it! + compressedSocket->setCompressionStreamEnabled(true); } } } - connection.packetSocket().setLegacy(m_legacyServer); + connection.packetSocket().setNetRules(compatibilityRules); auto clientConnect = make_shared(Root::singleton().assets()->digest(), allowAssetsMismatch, m_mainPlayer->uuid(), m_mainPlayer->name(), m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), m_mainPlayer->shipUpgrades(), m_mainPlayer->log()->introComplete(), account); - clientConnect->info = JsonObject{ {"brand", "OpenStarbound"} }; + clientConnect->info = JsonObject{ + {"brand", "OpenStarbound"}, + {"openProtocolVersion", OpenProtocolVersion } + }; connection.pushSingle(std::move(clientConnect)); connection.sendAll(timeout); @@ -136,20 +153,19 @@ Maybe UniverseClient::connect(UniverseConnection connection, bool allowA if (auto success = as(packet)) { m_universeClock = make_shared(); m_clientContext = make_shared(success->serverUuid, m_mainPlayer->uuid()); + m_clientContext->setNetCompatibilityRules(compatibilityRules); m_teamClient = make_shared(m_mainPlayer, m_clientContext); m_mainPlayer->setClientContext(m_clientContext); m_mainPlayer->setStatistics(m_statistics); - m_worldClient = make_shared(m_mainPlayer); - m_worldClient->clientState().setLegacy(m_legacyServer); + m_worldClient = make_shared(m_mainPlayer, m_luaRoot); + m_worldClient->clientState().setNetCompatibilityRules(compatibilityRules); m_worldClient->setAsyncLighting(true); - for (auto& pair : m_luaCallbacks) - m_worldClient->setLuaCallbacks(pair.first, pair.second); m_connection = std::move(connection); m_celestialDatabase = make_shared(std::move(success->celestialInformation)); m_systemWorldClient = make_shared(m_universeClock, m_celestialDatabase, m_mainPlayer->universeMap()); - Logger::info("UniverseClient: Joined {} server as client {}", m_legacyServer ? "Starbound" : "OpenStarbound", success->clientId); + Logger::info("UniverseClient: Joined {} server as client {}", legacyServer ? "Starbound" : "OpenStarbound", success->clientId); return {}; } else if (auto failure = as(packet)) { Logger::error("UniverseClient: Join failed: {}", failure->reason); @@ -263,7 +279,7 @@ void UniverseClient::update(float dt) { m_teamClient->update(); - auto contextUpdate = m_clientContext->writeUpdate(); + auto contextUpdate = m_clientContext->writeUpdate(m_clientContext->netCompatibilityRules()); if (!contextUpdate.empty()) m_connection->pushSingle(make_shared(std::move(contextUpdate))); @@ -276,7 +292,8 @@ void UniverseClient::update(float dt) { if (Time::monotonicMilliseconds() >= m_storageTriggerDeadline) { if (m_mainPlayer) { m_playerStorage->savePlayer(m_mainPlayer); - m_playerStorage->moveToFront(m_mainPlayer->uuid()); + if (playerIsOriginal()) + m_playerStorage->moveToFront(m_mainPlayer->uuid()); } m_storageTriggerDeadline = Time::monotonicMilliseconds() + assets->json("/client.config:storageTriggerInterval").toUInt(); @@ -296,7 +313,7 @@ void UniverseClient::update(float dt) { {"mode", PlayerModeNames.getRight(m_mainPlayer->modeType())} }); m_mainPlayer->setPendingCinematic(Json(std::move(cinematic))); - if (!m_worldClient->respawnInWorld()) + if (!playerOnOwnShip() && !m_worldClient->respawnInWorld()) m_pendingWarp = WarpAlias::OwnShip; m_warpDelay.reset(); } @@ -409,8 +426,8 @@ void UniverseClient::warpPlayer(WarpAction const& warpAction, bool animate, Stri m_pendingWarp = warpAction; } -void UniverseClient::flyShip(Vec3I const& system, SystemLocation const& destination) { - m_connection->pushSingle(make_shared(system, destination)); +void UniverseClient::flyShip(Vec3I const& system, SystemLocation const& destination, Json const& settings) { + m_connection->pushSingle(make_shared(system, destination, settings)); } CelestialDatabasePtr UniverseClient::celestialDatabase() const { @@ -476,13 +493,13 @@ uint16_t UniverseClient::maxPlayers() { } void UniverseClient::setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks) { - m_luaCallbacks[groupName] = callbacks; - if (m_worldClient) - m_worldClient->setLuaCallbacks(groupName, callbacks); + m_luaRoot->addCallbacks(groupName, callbacks); } void UniverseClient::startLua() { + m_luaRoot->restart(); setLuaCallbacks("celestial", LuaBindings::makeCelestialCallbacks(this)); + setLuaCallbacks("world", LuaBindings::makeWorldCallbacks(m_worldClient.get())); auto assets = Root::singleton().assets(); for (auto& p : assets->json("/client.config:universeScriptContexts").toObject()) { @@ -490,9 +507,6 @@ void UniverseClient::startLua() { scriptComponent->setLuaRoot(m_luaRoot); scriptComponent->setScripts(jsonToStringList(p.second.toArray())); - for (auto& pair : m_luaCallbacks) - scriptComponent->addCallbacks(pair.first, pair.second); - m_scriptContexts.set(p.first, scriptComponent); scriptComponent->init(); } @@ -505,6 +519,10 @@ void UniverseClient::stopLua() { m_scriptContexts.clear(); } +LuaRootPtr UniverseClient::luaRoot() { + return m_luaRoot; +} + bool UniverseClient::reloadPlayer(Json const& data, Uuid const&, bool resetInterfaces, bool showIndicator) { auto player = mainPlayer(); bool playerInWorld = player->inWorld(); @@ -649,8 +667,25 @@ void UniverseClient::setPause(bool pause) { void UniverseClient::handlePackets(List const& packets) { for (auto const& packet : packets) { try { + bool skip = false; + Maybe packetJson; + auto functionName = strf("on{}Packet", PacketTypeNames.getRight(packet->type())); + for (auto& context : m_scriptContexts) { + auto& luaContext = *context.second->context(); + auto method = luaContext.get(functionName); + if (method != LuaNil) { + if (!packetJson) + packetJson = packet->writeJson(); + if (skip = luaContext.luaTo(std::move(method)).invoke(*packetJson).maybe().value()) { + break; + } + } + } + if (skip) + continue; + if (auto clientContextUpdate = as(packet)) { - m_clientContext->readUpdate(clientContextUpdate->updateData); + m_clientContext->readUpdate(clientContextUpdate->updateData, m_clientContext->netCompatibilityRules()); m_playerStorage->applyShipUpdates(m_clientContext->playerUuid(), m_clientContext->newShipUpdates()); if (playerIsOriginal()) diff --git a/source/game/StarUniverseClient.hpp b/source/game/StarUniverseClient.hpp index 985124a..b951e25 100644 --- a/source/game/StarUniverseClient.hpp +++ b/source/game/StarUniverseClient.hpp @@ -61,7 +61,7 @@ public: bool canTeleport() const; void warpPlayer(WarpAction const& warpAction, bool animate = true, String const& animationType = "default", bool deploy = false); - void flyShip(Vec3I const& system, SystemLocation const& destination); + void flyShip(Vec3I const& system, SystemLocation const& destination, Json const& settings = {}); CelestialDatabasePtr celestialDatabase() const; @@ -89,6 +89,7 @@ public: void setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks); void startLua(); void stopLua(); + LuaRootPtr luaRoot(); bool reloadPlayer(Json const& data, Uuid const& uuid, bool resetInterfaces = false, bool showIndicator = false); bool switchPlayer(Uuid const& uuid); @@ -126,15 +127,12 @@ private: StatisticsPtr m_statistics; PlayerPtr m_mainPlayer; - bool m_legacyServer; bool m_pause; ClockPtr m_universeClock; WorldClientPtr m_worldClient; SystemWorldClientPtr m_systemWorldClient; Maybe m_connection; Maybe m_serverInfo; - - StringMap m_luaCallbacks; CelestialSlaveDatabasePtr m_celestialDatabase; ClientContextPtr m_clientContext; diff --git a/source/game/StarUniverseServer.cpp b/source/game/StarUniverseServer.cpp index 28f7862..fde8ca6 100644 --- a/source/game/StarUniverseServer.cpp +++ b/source/game/StarUniverseServer.cpp @@ -173,6 +173,15 @@ List UniverseServer::clientIds() const { return m_clients.keys(); } +List> UniverseServer::clientIdsAndCreationTime() const { + List> result; + ReadLocker clientsLocker(m_clientsLock); + result.reserve(m_clients.size()); + for (auto& pair : m_clients) + result.emplaceAppend(pair.first, pair.second->creationTime()); + return result; +} + size_t UniverseServer::numberOfClients() const { ReadLocker clientsLocker(m_clientsLock); return m_clients.size(); @@ -288,7 +297,7 @@ void UniverseServer::clientWarpPlayer(ConnectionId clientId, WarpAction action, m_pendingPlayerWarps[clientId] = pair(std::move(action), std::move(deploy)); } -void UniverseServer::clientFlyShip(ConnectionId clientId, Vec3I const& system, SystemLocation const& location) { +void UniverseServer::clientFlyShip(ConnectionId clientId, Vec3I const& system, SystemLocation const& location, Json const& settings) { RecursiveMutexLocker locker(m_mainLock); ReadLocker clientsLocker(m_clientsLock); @@ -300,7 +309,7 @@ void UniverseServer::clientFlyShip(ConnectionId clientId, Vec3I const& system, S return; if (system == Vec3I()) { - m_pendingFlights.set(clientId, {Vec3I(), {}}); // find starter world + m_pendingFlights.set(clientId, make_tuple(Vec3I(), SystemLocation(), settings)); // find starter world return; } @@ -315,7 +324,7 @@ void UniverseServer::clientFlyShip(ConnectionId clientId, Vec3I const& system, S // don't switch systems while already flying if (!m_pendingArrivals.contains(clientId) || sameSystem) - m_pendingFlights.set(clientId, {system, location}); + m_pendingFlights.set(clientId, make_tuple(system, location, settings)); } WorldId UniverseServer::clientWorld(ConnectionId clientId) const { @@ -673,7 +682,6 @@ void UniverseServer::updateShips() { Json jOldShipLevel = shipWorld->getProperty("ship.level"); unsigned newShipLevel = min(speciesShips.size() - 1, newShipUpgrades.shipLevel); - if (jOldShipLevel.isType(Json::Type::Int)) { auto oldShipLevel = jOldShipLevel.toUInt(); if (oldShipLevel < newShipLevel) { @@ -835,7 +843,10 @@ void UniverseServer::warpPlayers() { // Checking the spawn target validity then adding the client is not // perfect, it can still become invalid in between, if we fail at // adding the client we need to warp them back. - if (toWorld && toWorld->addClient(clientId, warpToWorld.target, !clientContext->remoteAddress(), clientContext->canBecomeAdmin())) { + if (toWorld && toWorld->addClient(clientId, warpToWorld.target, + !clientContext->remoteAddress(), + clientContext->canBecomeAdmin(), + clientContext->netRules())) { clientContext->setPlayerWorld(toWorld); m_chatProcessor->joinChannel(clientId, printWorldId(warpToWorld.world)); @@ -890,10 +901,11 @@ void UniverseServer::flyShips() { } } - eraseWhere(m_pendingFlights, [this](pair> const& p) { + eraseWhere(m_pendingFlights, [this](pair> const& p) { ConnectionId clientId = p.first; - Vec3I system = p.second.first; - SystemLocation location = p.second.second; + Vec3I system = get<0>(p.second); + SystemLocation location = get<1>(p.second); + Json settings = get<2>(p.second); auto clientContext = m_clients.value(clientId); if (!clientContext) @@ -933,7 +945,7 @@ void UniverseServer::flyShips() { clientContext->setSystemWorld({}); if (location) - m_queuedFlights.set(clientId, {{system, location}, {}}); + m_queuedFlights.set(clientId, {make_tuple(system, location, settings), {}}); destination = CelestialCoordinate(system); } @@ -944,8 +956,8 @@ void UniverseServer::flyShips() { Logger::info("Flying ship for player {} to {}", clientId, destination); bool startInWarp = system == Vec3I(); - clientShip->executeAction([interstellar, startInWarp](WorldServerThread*, WorldServer* worldServer) { - worldServer->startFlyingSky(interstellar, startInWarp); + clientShip->executeAction([interstellar, startInWarp, settings](WorldServerThread*, WorldServer* worldServer) { + worldServer->startFlyingSky(interstellar, startInWarp, settings); }); clientContext->setShipCoordinate(CelestialCoordinate(system)); @@ -1505,7 +1517,7 @@ void UniverseServer::packetsReceived(UniverseConnectionServer*, ConnectionId cli clientWarpPlayer(clientId, warpAction->action, warpAction->deploy); } else if (auto flyShip = as(packet)) { - clientFlyShip(clientId, flyShip->system, flyShip->location); + clientFlyShip(clientId, flyShip->system, flyShip->location, flyShip->settings); } else if (auto chatSend = as(packet)) { RecursiveMutexLocker locker(m_mainLock); @@ -1552,7 +1564,8 @@ void UniverseServer::acceptConnection(UniverseConnection connection, MaybecompressionMode() != PacketCompressionMode::Enabled; - connection.packetSocket().setLegacy(legacyClient); + if (legacyClient) + connection.packetSocket().setNetRules(LegacyVersion); auto protocolResponse = make_shared(); protocolResponse->setCompressionMode(PacketCompressionMode::Enabled); // Signal that we're OpenStarbound @@ -1574,7 +1587,8 @@ void UniverseServer::acceptConnection(UniverseConnection connection, Maybeinfo = JsonObject{ - {"compression", NetCompressionModeNames.getRight(compressionMode)} + {"compression", NetCompressionModeNames.getRight(compressionMode)}, + {"openProtocolVersion", OpenProtocolVersion} }; } connection.pushSingle(protocolResponse); @@ -1671,12 +1685,16 @@ void UniverseServer::acceptConnection(UniverseConnection connection, MaybeplayerName, remoteAddressString); + NetCompatibilityRules netRules(legacyClient ? LegacyVersion : 1); if (Json& info = clientConnect->info) { + if (auto openProtocolVersion = info.optUInt("openProtocolVersion")) + netRules.setVersion(*openProtocolVersion); if (Json brand = info.get("brand", "custom")) connectionLog += strf(" ({} client)", brand.toString()); if (info.getBool("legacy", false)) - connection.packetSocket().setLegacy(legacyClient = true); + netRules.setVersion(LegacyVersion); } + connection.packetSocket().setNetRules(netRules); Logger::log(LogLevel::Info, connectionLog.utf8Ptr()); @@ -1698,7 +1716,7 @@ void UniverseServer::acceptConnection(UniverseConnection connection, Maybe(clientId, remoteAddress, clientConnect->playerUuid, + auto clientContext = make_shared(clientId, remoteAddress, netRules, clientConnect->playerUuid, clientConnect->playerName, clientConnect->playerSpecies, administrator, clientConnect->shipChunks); m_clients.add(clientId, clientContext); m_connectionServer->addConnection(clientId, std::move(connection)); @@ -2029,10 +2047,21 @@ Maybe> UniverseServer::shipWorldPromise( shipWorld->setProperty("ship.maxFuel", currentUpgrades.maxFuel); shipWorld->setProperty("ship.crewSize", currentUpgrades.crewSize); shipWorld->setProperty("ship.fuelEfficiency", currentUpgrades.fuelEfficiency); + shipWorld->setProperty("ship.epoch", Time::timeSinceEpoch()); + } + + auto shipClock = make_shared(); + auto shipTime = shipWorld->getProperty("ship.epoch"); + if (!shipTime.canConvert(Json::Type::Float)) { + auto now = Time::timeSinceEpoch(); + shipWorld->setProperty("ship.epoch", now); + } else { + shipClock->setTime(Time::timeSinceEpoch() - shipTime.toDouble()); } shipWorld->setUniverseSettings(m_universeSettings); - shipWorld->setReferenceClock(universeClock); + shipWorld->setReferenceClock(shipClock); + shipClock->start(); if (auto systemWorld = clientContext->systemWorld()) shipWorld->setOrbitalSky(systemWorld->clientSkyParameters(clientContext->clientId())); diff --git a/source/game/StarUniverseServer.hpp b/source/game/StarUniverseServer.hpp index 804073c..a87194f 100644 --- a/source/game/StarUniverseServer.hpp +++ b/source/game/StarUniverseServer.hpp @@ -55,6 +55,7 @@ public: bool isWorldActive(WorldId const& worldId) const; List clientIds() const; + List> clientIdsAndCreationTime() const; size_t numberOfClients() const; uint32_t maxClients() const; bool isConnectedClient(ConnectionId clientId) const; @@ -83,7 +84,7 @@ public: RpcThreadPromise sendWorldMessage(WorldId const& worldId, String const& message, JsonArray const& args = {}); void clientWarpPlayer(ConnectionId clientId, WarpAction action, bool deploy = false); - void clientFlyShip(ConnectionId clientId, Vec3I const& system, SystemLocation const& location); + void clientFlyShip(ConnectionId clientId, Vec3I const& system, SystemLocation const& location, Json const& settings = {}); WorldId clientWorld(ConnectionId clientId) const; CelestialCoordinate clientShipCoordinate(ConnectionId clientId) const; @@ -243,8 +244,8 @@ private: TeamManagerPtr m_teamManager; HashMap> m_pendingPlayerWarps; - HashMap, Maybe>> m_queuedFlights; - HashMap> m_pendingFlights; + HashMap, Maybe>> m_queuedFlights; + HashMap> m_pendingFlights; HashMap m_pendingArrivals; HashMap m_pendingDisconnections; HashMap>> m_pendingCelestialRequests; diff --git a/source/game/StarVehicle.cpp b/source/game/StarVehicle.cpp index 0aec750..29a57b0 100644 --- a/source/game/StarVehicle.cpp +++ b/source/game/StarVehicle.cpp @@ -242,12 +242,12 @@ Vec2F Vehicle::velocity() const { return m_movementController.velocity(); } -pair Vehicle::writeNetState(uint64_t fromVersion) { - return m_netGroup.writeNetState(fromVersion); +pair Vehicle::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { + return m_netGroup.writeNetState(fromVersion, rules); } -void Vehicle::readNetState(ByteArray data, float interpolationTime) { - m_netGroup.readNetState(std::move(data), interpolationTime); +void Vehicle::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) { + m_netGroup.readNetState(data, interpolationTime, rules); } void Vehicle::enableInterpolation(float extrapolationHint) { diff --git a/source/game/StarVehicle.hpp b/source/game/StarVehicle.hpp index b70d4ae..087137f 100644 --- a/source/game/StarVehicle.hpp +++ b/source/game/StarVehicle.hpp @@ -44,8 +44,8 @@ public: RectF collisionArea() const override; Vec2F velocity() const; - pair writeNetState(uint64_t fromVersion) override; - void readNetState(ByteArray data, float interpolationTime = 0) override; + pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}) override; + void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override; void enableInterpolation(float extrapolationHint) override; void disableInterpolation() override; diff --git a/source/game/StarVehicleDatabase.cpp b/source/game/StarVehicleDatabase.cpp index 370aceb..a8739fa 100644 --- a/source/game/StarVehicleDatabase.cpp +++ b/source/game/StarVehicleDatabase.cpp @@ -10,7 +10,7 @@ VehicleDatabase::VehicleDatabase() { auto assets = Root::singleton().assets(); auto& files = assets->scanExtension("vehicle"); assets->queueJsons(files); - for (auto& file : files) { + for (String file : files) { try { auto config = assets->json(file); String name = config.getString("name"); @@ -32,15 +32,18 @@ VehiclePtr VehicleDatabase::create(String const& vehicleName, Json const& extraC return make_shared(configPair->second, configPair->first, extraConfig); } -ByteArray VehicleDatabase::netStore(VehiclePtr const& vehicle) const { +ByteArray VehicleDatabase::netStore(VehiclePtr const& vehicle, NetCompatibilityRules rules) const { DataStreamBuffer ds; + ds.setStreamCompatibilityVersion(rules); + ds.write(vehicle->baseConfig().getString("name")); ds.write(vehicle->dynamicConfig()); return ds.takeData(); } -VehiclePtr VehicleDatabase::netLoad(ByteArray const& netStore) const { +VehiclePtr VehicleDatabase::netLoad(ByteArray const& netStore, NetCompatibilityRules rules) const { DataStreamBuffer ds(netStore); + ds.setStreamCompatibilityVersion(rules); String name = ds.read(); auto dynamicConfig = ds.read(); diff --git a/source/game/StarVehicleDatabase.hpp b/source/game/StarVehicleDatabase.hpp index ba7bf7c..ed90a7d 100644 --- a/source/game/StarVehicleDatabase.hpp +++ b/source/game/StarVehicleDatabase.hpp @@ -13,8 +13,8 @@ public: VehiclePtr create(String const& vehicleName, Json const& extraConfig = Json()) const; - ByteArray netStore(VehiclePtr const& vehicle) const; - VehiclePtr netLoad(ByteArray const& netStore) const; + ByteArray netStore(VehiclePtr const& vehicle, NetCompatibilityRules rules) const; + VehiclePtr netLoad(ByteArray const& netStore, NetCompatibilityRules rules) const; Json diskStore(VehiclePtr const& vehicle) const; VehiclePtr diskLoad(Json const& diskStore) const; diff --git a/source/game/StarWeather.cpp b/source/game/StarWeather.cpp index 6b1c6a7..6fe1a53 100644 --- a/source/game/StarWeather.cpp +++ b/source/game/StarWeather.cpp @@ -55,9 +55,9 @@ void ServerWeather::setClientVisibleRegions(List regions) { m_clientVisibleRegions = std::move(regions); } -pair ServerWeather::writeUpdate(uint64_t fromVersion) { +pair ServerWeather::writeUpdate(uint64_t fromVersion, NetCompatibilityRules rules) { setNetStates(); - return m_netGroup.writeNetState(fromVersion); + return m_netGroup.writeNetState(fromVersion, rules); } void ServerWeather::update(double dt) { @@ -263,9 +263,9 @@ void ClientWeather::setup(WorldGeometry worldGeometry, WeatherEffectsActiveQuery m_currentTime = 0.0; } -void ClientWeather::readUpdate(ByteArray data) { +void ClientWeather::readUpdate(ByteArray data, NetCompatibilityRules rules) { if (!data.empty()) { - m_netGroup.readNetState(std::move(data)); + m_netGroup.readNetState(data, 0.0f, rules); getNetStates(); } } diff --git a/source/game/StarWeather.hpp b/source/game/StarWeather.hpp index 7d699ea..4bde334 100644 --- a/source/game/StarWeather.hpp +++ b/source/game/StarWeather.hpp @@ -29,7 +29,7 @@ public: void setClientVisibleRegions(List regions); - pair writeUpdate(uint64_t fromVersion = 0); + pair writeUpdate(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}); void update(double dt); @@ -80,7 +80,7 @@ public: void setup(WorldGeometry worldGeometry, WeatherEffectsActiveQuery weatherEffectsActiveQuery); - void readUpdate(ByteArray data); + void readUpdate(ByteArray data, NetCompatibilityRules rules); void setVisibleRegion(RectI visibleRegion); diff --git a/source/rendering/StarWorldCamera.cpp b/source/game/StarWorldCamera.cpp similarity index 95% rename from source/rendering/StarWorldCamera.cpp rename to source/game/StarWorldCamera.cpp index de23f3e..a27f84a 100644 --- a/source/rendering/StarWorldCamera.cpp +++ b/source/game/StarWorldCamera.cpp @@ -2,7 +2,7 @@ namespace Star { -void WorldCamera::setCenterWorldPosition(Vec2F const& position, bool force) { +void WorldCamera::setCenterWorldPosition(Vec2F position, bool force) { m_rawWorldCenter = position; // Only actually move the world center if a half pixel distance has been // moved in any direction. This is sort of arbitrary, but helps prevent diff --git a/source/rendering/StarWorldCamera.hpp b/source/game/StarWorldCamera.hpp similarity index 92% rename from source/rendering/StarWorldCamera.hpp rename to source/game/StarWorldCamera.hpp index aa0713b..abc018a 100644 --- a/source/rendering/StarWorldCamera.hpp +++ b/source/game/StarWorldCamera.hpp @@ -20,7 +20,7 @@ public: // Set the camera center position (in world space) to as close to the given // location as possible while keeping the screen within world bounds. - void setCenterWorldPosition(Vec2F const& position, bool force = false); + void setCenterWorldPosition(Vec2F position, bool force = false); // Returns the actual camera position. Vec2F centerWorldPosition() const; @@ -28,10 +28,10 @@ public: // the world is non-euclidean, one world coordinate can transform to // potentially an infinite number of screen coordinates. This will retrun // the closest to the center of the screen. - Vec2F worldToScreen(Vec2F const& worldCoord) const; + Vec2F worldToScreen(Vec2F worldCoord) const; // Assumes top left corner of screen is (0, 0) in screen coordinates. - Vec2F screenToWorld(Vec2F const& screen) const; + Vec2F screenToWorld(Vec2F screen) const; // Returns screen dimensions in world space. RectF worldScreenRect() const; @@ -86,7 +86,7 @@ inline Vec2F WorldCamera::centerWorldPosition() const { return Vec2F(m_worldCenter); } -inline Vec2F WorldCamera::worldToScreen(Vec2F const& worldCoord) const { +inline Vec2F WorldCamera::worldToScreen(Vec2F worldCoord) const { Vec2F wrappedCoord = m_worldGeometry.nearestTo(Vec2F(m_worldCenter), worldCoord); return Vec2F( (wrappedCoord[0] - m_worldCenter[0]) * (TilePixels * m_pixelRatio) + (float)m_screenSize[0] / 2.0, @@ -94,7 +94,7 @@ inline Vec2F WorldCamera::worldToScreen(Vec2F const& worldCoord) const { ); } -inline Vec2F WorldCamera::screenToWorld(Vec2F const& screen) const { +inline Vec2F WorldCamera::screenToWorld(Vec2F screen) const { return Vec2F( (screen[0] - (float)m_screenSize[0] / 2.0) / (TilePixels * m_pixelRatio) + m_worldCenter[0], (screen[1] - (float)m_screenSize[1] / 2.0) / (TilePixels * m_pixelRatio) + m_worldCenter[1] diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 8abfcdf..efc2eb7 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -28,7 +28,7 @@ const std::string SECRET_BROADCAST_PUBLIC_KEY = "SecretBroadcastPublicKey"; const std::string SECRET_BROADCAST_PREFIX = "\0Broadcast\0"s; const float WorldClient::DropDist = 6.0f; -WorldClient::WorldClient(PlayerPtr mainPlayer) { +WorldClient::WorldClient(PlayerPtr mainPlayer, LuaRootPtr luaRoot) { auto& root = Root::singleton(); auto assets = root.assets(); @@ -48,7 +48,7 @@ WorldClient::WorldClient(PlayerPtr mainPlayer) { m_collisionDebug = false; m_inWorld = false; - m_luaRoot = make_shared(); + m_luaRoot = luaRoot; m_mainPlayer = mainPlayer; @@ -172,7 +172,8 @@ void WorldClient::removeEntity(EntityId entityId, bool andDie) { } if (auto version = m_masterEntitiesNetVersion.maybeTake(entity->entityId())) { - ByteArray finalNetState = entity->writeNetState(*version).first; + auto netRules = m_clientState.netCompatibilityRules(); + ByteArray finalNetState = entity->writeNetState(*version, netRules).first; m_outgoingPackets.append(make_shared(entity->entityId(), std::move(finalNetState), andDie)); } @@ -484,7 +485,7 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { const List* directives = nullptr; if (auto& worldTemplate = m_worldTemplate) { if (const auto& parameters = worldTemplate->worldParameters()) - if (auto& globalDirectives = m_worldTemplate->worldParameters()->globalDirectives) + if (auto& globalDirectives = parameters->globalDirectives) directives = &globalDirectives.get(); } m_entityMap->forAllEntities([&](EntityPtr const& entity) { @@ -770,8 +771,9 @@ void WorldClient::handleIncomingPackets(List const& packets) { removeEntity(entityCreate->entityId, false); } - auto entity = entityFactory->netLoadEntity(entityCreate->entityType, entityCreate->storeData); - entity->readNetState(entityCreate->firstNetState); + auto netRules = m_clientState.netCompatibilityRules(); + auto entity = entityFactory->netLoadEntity(entityCreate->entityType, entityCreate->storeData, netRules); + entity->readNetState(entityCreate->firstNetState, 0.0f, netRules); entity->init(this, entityCreate->entityId, EntityMode::Slave); m_entityMap->addEntity(entity); @@ -792,13 +794,13 @@ void WorldClient::handleIncomingPackets(List const& packets) { EntityId entityId = entity->entityId(); if (connectionForEntity(entityId) == entityUpdateSet->forConnection) { starAssert(entity->isSlave()); - entity->readNetState(entityUpdateSet->deltas.value(entityId), interpolationLeadTime); + entity->readNetState(entityUpdateSet->deltas.value(entityId), interpolationLeadTime, m_clientState.netCompatibilityRules()); } }); } else if (auto entityDestroy = as(packet)) { if (auto entity = m_entityMap->entity(entityDestroy->entityId)) { - entity->readNetState(entityDestroy->finalNetState, m_interpolationTracker.interpolationLeadTime()); + entity->readNetState(entityDestroy->finalNetState, m_interpolationTracker.interpolationLeadTime(), m_clientState.netCompatibilityRules()); // Before destroying the entity, we should make sure that the entity is // using the absolute latest data, so we disable interpolation. @@ -913,8 +915,8 @@ void WorldClient::handleIncomingPackets(List const& packets) { m_interpolationTracker.receiveTimeUpdate(stepUpdate->remoteTime); } else if (auto environmentUpdatePacket = as(packet)) { - m_sky->readUpdate(environmentUpdatePacket->skyDelta); - m_weather.readUpdate(environmentUpdatePacket->weatherDelta); + m_sky->readUpdate(environmentUpdatePacket->skyDelta, m_clientState.netCompatibilityRules()); + m_weather.readUpdate(environmentUpdatePacket->weatherDelta, m_clientState.netCompatibilityRules()); } else if (auto hit = as(packet)) { m_damageManager->pushRemoteHitRequest(hit->remoteHitRequest); @@ -1075,10 +1077,6 @@ List WorldClient::getOutgoingPackets() { return std::move(m_outgoingPackets); } -void WorldClient::setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks) { - m_luaRoot->addCallbacks(groupName, callbacks); -} - void WorldClient::update(float dt) { if (!inWorld()) return; @@ -1233,7 +1231,7 @@ void WorldClient::update(float dt) { queueUpdatePackets(m_entityUpdateTimer.wrapTick(dt)); - if ((!m_clientState.legacy() && m_currentStep % 3 == 0) || m_pingTime.isNothing()) { + if ((!m_clientState.netCompatibilityRules().isLegacy() && m_currentStep % 3 == 0) || m_pingTime.isNothing()) { m_pingTime = Time::monotonicMilliseconds(); m_outgoingPackets.append(make_shared(*m_pingTime)); } @@ -1329,7 +1327,8 @@ void WorldClient::addEntity(EntityPtr const& entity, EntityId entityId) { notifyEntityCreate(entity); } else { auto entityFactory = Root::singleton().entityFactory(); - m_outgoingPackets.append(make_shared(entity->entityType(), entityFactory->netStoreEntity(entity), entity->writeNetState().first)); + auto netRules = m_clientState.netCompatibilityRules(); + m_outgoingPackets.append(make_shared(entity->entityType(), entityFactory->netStoreEntity(entity, netRules), entity->writeNetState(0, netRules).first)); } } @@ -1433,7 +1432,7 @@ bool WorldClient::isTileProtected(Vec2I const& pos) const { if (!inWorld()) return true; - auto tile = m_tileArray->tile(pos); + auto const& tile = m_tileArray->tile(pos); return m_protectedDungeonIds.contains(tile.dungeonId); } @@ -1460,9 +1459,10 @@ void WorldClient::queueUpdatePackets(bool sendEntityUpdates) { if (sendEntityUpdates) { auto entityUpdateSet = make_shared(); entityUpdateSet->forConnection = *m_clientId; + auto netRules = m_clientState.netCompatibilityRules(); m_entityMap->forAllEntities([&](EntityPtr const& entity) { if (auto version = m_masterEntitiesNetVersion.ptr(entity->entityId())) { - auto updateAndVersion = entity->writeNetState(*version); + auto updateAndVersion = entity->writeNetState(*version, netRules); if (!updateAndVersion.first.empty()) entityUpdateSet->deltas[entity->entityId()] = std::move(updateAndVersion.first); *version = updateAndVersion.second; @@ -1542,7 +1542,7 @@ void WorldClient::handleDamageNotifications() { const List* directives = nullptr; if (auto& worldTemplate = m_worldTemplate) { if (const auto& parameters = worldTemplate->worldParameters()) - if (auto& globalDirectives = m_worldTemplate->worldParameters()->globalDirectives) + if (auto& globalDirectives = parameters->globalDirectives) directives = &globalDirectives.get(); } if (directives) { @@ -1762,8 +1762,6 @@ void WorldClient::initWorld(WorldStartPacket const& startPacket) { return m_tileArray->tile(pos); }; m_damageManager = make_shared(this, startPacket.clientId); - m_luaRoot->restart(); - m_luaRoot->tuneAutoGarbageCollection(m_clientConfig.getFloat("luaGcPause"), m_clientConfig.getFloat("luaGcStepMultiplier")); m_playerStart = startPacket.playerRespawn; m_respawnInWorld = startPacket.respawnInWorld; m_worldProperties = startPacket.worldProperties.optObject().value(); @@ -1778,34 +1776,34 @@ void WorldClient::initWorld(WorldStartPacket const& startPacket) { setupForceRegions(); - if (!m_mainPlayer->isDead()) { - m_mainPlayer->init(this, m_entityMap->reserveEntityId(), EntityMode::Master); - m_entityMap->addEntity(m_mainPlayer); - } - m_mainPlayer->moveTo(startPacket.playerStart); - if (m_worldTemplate->worldParameters()) - m_mainPlayer->overrideTech(m_worldTemplate->worldParameters()->overrideTech); - else - m_mainPlayer->overrideTech({}); - - // Auto reposition the client window on the player when the main player - // changes position. - centerClientWindowOnPlayer(); - m_sky = make_shared(); - m_sky->readUpdate(startPacket.skyData); + m_sky->readUpdate(startPacket.skyData, m_clientState.netCompatibilityRules()); m_weather.setup(m_geometry, [this](Vec2I const& pos) { auto const& tile = m_tileArray->tile(pos); return !isRealMaterial(tile.background) && !isSolidColliding(tile.getCollision()); }); - m_weather.readUpdate(startPacket.weatherData); + m_weather.readUpdate(startPacket.weatherData, m_clientState.netCompatibilityRules()); m_lightingCalculator.setMonochrome(Root::singleton().configuration()->get("monochromeLighting").toBool()); m_lightingCalculator.setParameters(assets->json("/lighting.config:lighting")); m_lightIntensityCalculator.setParameters(assets->json("/lighting.config:intensity")); m_inWorld = true; + + if (!m_mainPlayer->isDead()) { + m_mainPlayer->init(this, m_entityMap->reserveEntityId(), EntityMode::Master); + m_entityMap->addEntity(m_mainPlayer); + } + m_mainPlayer->moveTo(startPacket.playerStart); + if (const auto& parameters = m_worldTemplate->worldParameters()) + m_mainPlayer->overrideTech(parameters->overrideTech); + else + m_mainPlayer->overrideTech({}); + + // Auto reposition the client window on the player when the main player + // changes position. + centerClientWindowOnPlayer(); } void WorldClient::clearWorld() { @@ -1838,8 +1836,6 @@ void WorldClient::clearWorld() { m_damageManager.reset(); - m_luaRoot->shutdown(); - m_particles.reset(); m_sky.reset(); @@ -1874,10 +1870,11 @@ void WorldClient::tryGiveMainPlayerItem(ItemPtr item, bool silent) { void WorldClient::notifyEntityCreate(EntityPtr const& entity) { if (entity->isMaster() && !m_masterEntitiesNetVersion.contains(entity->entityId())) { // Server was unaware of this entity until now - auto firstNetState = entity->writeNetState(); + auto netRules = m_clientState.netCompatibilityRules(); + auto firstNetState = entity->writeNetState(0, netRules); m_masterEntitiesNetVersion[entity->entityId()] = firstNetState.second; m_outgoingPackets.append(make_shared(entity->entityType(), - Root::singleton().entityFactory()->netStoreEntity(entity), std::move(firstNetState.first), entity->entityId())); + Root::singleton().entityFactory()->netStoreEntity(entity, netRules), std::move(firstNetState.first), entity->entityId())); } } @@ -2117,8 +2114,8 @@ bool WorldClient::isUnderground(Vec2F const& pos) const { } bool WorldClient::disableDeathDrops() const { - if (m_worldTemplate->worldParameters()) - return m_worldTemplate->worldParameters()->disableDeathDrops; + if (const auto& parameters = m_worldTemplate->worldParameters()) + return parameters->disableDeathDrops; return false; } @@ -2240,6 +2237,9 @@ LuaRootPtr WorldClient::luaRoot() { } RpcPromise WorldClient::findUniqueEntity(String const& uniqueId) { + if (!inWorld()) + return RpcPromise::createFailed("Not currently in a world"); + if (auto entity = m_entityMap->uniqueEntity(uniqueId)) return RpcPromise::createFulfilled(entity->position()); @@ -2253,6 +2253,9 @@ RpcPromise WorldClient::findUniqueEntity(String const& uniqueId) { } RpcPromise WorldClient::sendEntityMessage(Variant const& entityId, String const& message, JsonArray const& args) { + if (!inWorld()) + return RpcPromise::createFailed("Not currently in a world"); + EntityPtr entity; if (entityId.is()) entity = m_entityMap->entity(entityId.get()); diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp index 1616b10..502a928 100644 --- a/source/game/StarWorldClient.hpp +++ b/source/game/StarWorldClient.hpp @@ -38,7 +38,7 @@ STAR_EXCEPTION(WorldClientException, StarException); class WorldClient : public World { public: - WorldClient(PlayerPtr mainPlayer); + WorldClient(PlayerPtr mainPlayer, LuaRootPtr luaRoot); ~WorldClient(); ConnectionId connection() const override; @@ -134,9 +134,6 @@ public: void handleIncomingPackets(List const& packets); List getOutgoingPackets(); - - // Sets default callbacks in the LuaRoot. - void setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks); // Set the rendering window for this client. void setClientWindow(RectI window); @@ -367,7 +364,7 @@ private: HashMap m_dungeonIdGravity; HashMap m_dungeonIdBreathable; - Set m_protectedDungeonIds; + StableHashSet m_protectedDungeonIds; HashMap>> m_findUniqueEntityResponses; HashMap> m_entityMessageResponses; diff --git a/source/game/StarWorldClientState.cpp b/source/game/StarWorldClientState.cpp index 21552cd..4fbefc9 100644 --- a/source/game/StarWorldClientState.cpp +++ b/source/game/StarWorldClientState.cpp @@ -22,8 +22,6 @@ WorldClientState::WorldClientState() { m_netGroup.addNetElement(&m_playerId); m_netGroup.addNetElement(&m_clientPresenceEntities); - - m_legacy = false; } RectI WorldClientState::window() const { @@ -81,20 +79,20 @@ List WorldClientState::monitoringRegions(function(EntityId)> ByteArray WorldClientState::writeDelta() { ByteArray delta; - tie(delta, m_netVersion) = m_netGroup.writeNetState(m_netVersion); + tie(delta, m_netVersion) = m_netGroup.writeNetState(m_netVersion, m_netCompatibilityRules); return delta; } void WorldClientState::readDelta(ByteArray delta) { - m_netGroup.readNetState(std::move(delta)); + m_netGroup.readNetState(std::move(delta), 0.0f, m_netCompatibilityRules); } -void WorldClientState::setLegacy(bool legacy) { - m_legacy = legacy; +void WorldClientState::setNetCompatibilityRules(NetCompatibilityRules netCompatibilityRules) { + m_netCompatibilityRules = netCompatibilityRules; } -bool WorldClientState::legacy() const { - return m_legacy; +NetCompatibilityRules WorldClientState::netCompatibilityRules() const { + return m_netCompatibilityRules; } void WorldClientState::reset() { diff --git a/source/game/StarWorldClientState.hpp b/source/game/StarWorldClientState.hpp index 920fef4..746b7c5 100644 --- a/source/game/StarWorldClientState.hpp +++ b/source/game/StarWorldClientState.hpp @@ -36,9 +36,8 @@ public: ByteArray writeDelta(); void readDelta(ByteArray delta); - // Whether the client is connected to a legacy server. - void setLegacy(bool legacy); - bool legacy() const; + void setNetCompatibilityRules(NetCompatibilityRules netCompatibilityRules); + NetCompatibilityRules netCompatibilityRules() const; void reset(); @@ -57,7 +56,7 @@ private: NetElementInt m_playerId; NetElementData> m_clientPresenceEntities; - bool m_legacy; + NetCompatibilityRules m_netCompatibilityRules; }; } diff --git a/source/game/StarWorldServer.cpp b/source/game/StarWorldServer.cpp index a78025c..0c2a089 100644 --- a/source/game/StarWorldServer.cpp +++ b/source/game/StarWorldServer.cpp @@ -209,7 +209,7 @@ bool WorldServer::spawnTargetValid(SpawnTarget const& spawnTarget) const { return true; } -bool WorldServer::addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin) { +bool WorldServer::addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin, NetCompatibilityRules netRules) { if (m_clientInfo.contains(clientId)) return false; @@ -246,6 +246,7 @@ bool WorldServer::addClient(ConnectionId clientId, SpawnTarget const& spawnTarge auto& clientInfo = m_clientInfo.add(clientId, make_shared(clientId, tracker)); clientInfo->local = isLocal; clientInfo->admin = isAdmin; + clientInfo->clientState.setNetCompatibilityRules(netRules); auto worldStartPacket = make_shared(); auto& templateData = worldStartPacket->templateData = m_worldTemplate->store(); @@ -254,8 +255,8 @@ bool WorldServer::addClient(ConnectionId clientId, SpawnTarget const& spawnTarge && Root::singletonPtr()->configuration()->getPath("compatibility.customDungeonWorld").optBool().value(false)) worldStartPacket->templateData = worldStartPacket->templateData.setPath("worldParameters.primaryDungeon", "testarena"); - tie(worldStartPacket->skyData, clientInfo->skyNetVersion) = m_sky->writeUpdate(); - tie(worldStartPacket->weatherData, clientInfo->weatherNetVersion) = m_weather.writeUpdate(); + tie(worldStartPacket->skyData, clientInfo->skyNetVersion) = m_sky->writeUpdate(0, netRules); + tie(worldStartPacket->weatherData, clientInfo->weatherNetVersion) = m_weather.writeUpdate(0, netRules); worldStartPacket->playerStart = playerStart; worldStartPacket->playerRespawn = m_playerStart; worldStartPacket->respawnInWorld = m_respawnInWorld; @@ -380,8 +381,9 @@ void WorldServer::handleIncomingPackets(ConnectionId clientId, List c clientInfo->outgoingPackets.append(make_shared(item)); } else if (auto sepacket = as(packet)) { - auto entity = entityFactory->netLoadEntity(sepacket->entityType, std::move(sepacket->storeData)); - entity->readNetState(std::move(sepacket->firstNetState)); + auto netRules = clientInfo->clientState.netCompatibilityRules(); + auto entity = entityFactory->netLoadEntity(sepacket->entityType, std::move(sepacket->storeData), netRules); + entity->readNetState(std::move(sepacket->firstNetState), 0.0f, netRules); addEntity(std::move(entity)); } else if (auto rdpacket = as(packet)) { @@ -432,9 +434,9 @@ void WorldServer::handleIncomingPackets(ConnectionId clientId, List c Logger::error("WorldServer received duplicate entity create packet from client, deleting old entity {}", entityCreate->entityId); removeEntity(entityCreate->entityId, false); } - - auto entity = entityFactory->netLoadEntity(entityCreate->entityType, entityCreate->storeData); - entity->readNetState(entityCreate->firstNetState); + auto netRules = clientInfo->clientState.netCompatibilityRules(); + auto entity = entityFactory->netLoadEntity(entityCreate->entityType, entityCreate->storeData, netRules); + entity->readNetState(entityCreate->firstNetState, 0.0f, netRules); entity->init(this, entityCreate->entityId, EntityMode::Slave); m_entityMap->addEntity(entity); @@ -448,14 +450,14 @@ void WorldServer::handleIncomingPackets(ConnectionId clientId, List c EntityId entityId = entity->entityId(); if (connectionForEntity(entityId) == clientId) { starAssert(entity->isSlave()); - entity->readNetState(entityUpdateSet->deltas.value(entityId), interpolationLeadTime); + entity->readNetState(entityUpdateSet->deltas.value(entityId), interpolationLeadTime, clientInfo->clientState.netCompatibilityRules()); } }); clientInfo->pendingForward = true; } else if (auto entityDestroy = as(packet)) { if (auto entity = m_entityMap->entity(entityDestroy->entityId)) { - entity->readNetState(entityDestroy->finalNetState, clientInfo->interpolationTracker.interpolationLeadTime()); + entity->readNetState(entityDestroy->finalNetState, clientInfo->interpolationTracker.interpolationLeadTime(), clientInfo->clientState.netCompatibilityRules()); // Before destroying the entity, we should make sure that the entity is // using the absolute latest data, so we disable interpolation. entity->disableInterpolation(); @@ -851,6 +853,10 @@ void WorldServer::setSpawningEnabled(bool spawningEnabled) { m_spawner.setActive(spawningEnabled); } +void WorldServer::setPropertyListener(String const& propertyName, WorldPropertyListener listener) { + m_worldPropertyListeners[propertyName] = listener; +} + TileModificationList WorldServer::validTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) const { return WorldImpl::splitTileModifications(m_entityMap, modificationList, allowEntityOverlap, m_tileGetterFunction, [this](Vec2I pos, TileModification) { return !isTileProtected(pos); @@ -1188,10 +1194,14 @@ bool WorldServer::isTileProtected(Vec2I const& pos) const { if (!m_tileProtectionEnabled) return false; - auto tile = m_tileArray->tile(pos); + auto const& tile = m_tileArray->tile(pos); return m_protectedDungeonIds.contains(tile.dungeonId); } +bool WorldServer::getTileProtection(DungeonId dungeonId) const { + return m_protectedDungeonIds.contains(dungeonId); +} + void WorldServer::setTileProtection(DungeonId dungeonId, bool isProtected) { bool updated = false; if (isProtected) { @@ -1203,9 +1213,28 @@ void WorldServer::setTileProtection(DungeonId dungeonId, bool isProtected) { if (updated) { for (auto const& pair : m_clientInfo) pair.second->outgoingPackets.append(make_shared(dungeonId, isProtected)); + + Logger::info("Protected dungeonIds for world set to {}", m_protectedDungeonIds); } +} - Logger::info("Protected dungeonIds for world set to {}", m_protectedDungeonIds); +size_t WorldServer::setTileProtection(List const& dungeonIds, bool isProtected) { + List updates; + updates.reserve(dungeonIds.size()); + for (auto const& dungeonId : dungeonIds) + if (isProtected ? m_protectedDungeonIds.add(dungeonId) : m_protectedDungeonIds.remove(dungeonId)) + updates.append(make_shared(dungeonId, isProtected)); + + if (updates.empty()) + return 0; + + for (auto const& pair : m_clientInfo) + pair.second->outgoingPackets.appendAll(updates); + + auto newDungeonIds = m_protectedDungeonIds.values(); + sort(newDungeonIds); + Logger::info("Protected dungeonIds for world set to {}", newDungeonIds); + return updates.size(); } void WorldServer::setTileProtectionEnabled(bool enabled) { @@ -1789,10 +1818,10 @@ void WorldServer::queueUpdatePackets(ConnectionId clientId, bool sendRemoteUpdat if (shouldRunThisStep("environmentUpdate")) { ByteArray skyDelta; - tie(skyDelta, clientInfo->skyNetVersion) = m_sky->writeUpdate(clientInfo->skyNetVersion); + tie(skyDelta, clientInfo->skyNetVersion) = m_sky->writeUpdate(clientInfo->skyNetVersion, clientInfo->clientState.netCompatibilityRules()); ByteArray weatherDelta; - tie(weatherDelta, clientInfo->weatherNetVersion) = m_weather.writeUpdate(clientInfo->weatherNetVersion); + tie(weatherDelta, clientInfo->weatherNetVersion) = m_weather.writeUpdate(clientInfo->weatherNetVersion, clientInfo->clientState.netCompatibilityRules()); if (!skyDelta.empty() || !weatherDelta.empty()) clientInfo->outgoingPackets.append(make_shared(std::move(skyDelta), std::move(weatherDelta))); @@ -1866,12 +1895,14 @@ void WorldServer::queueUpdatePackets(ConnectionId clientId, bool sendRemoteUpdat EntityId entityId = monitoredEntity->entityId(); ConnectionId connectionId = connectionForEntity(entityId); if (connectionId != clientId) { + auto netRules = clientInfo->clientState.netCompatibilityRules(); if (auto version = clientInfo->clientSlavesNetVersion.ptr(entityId)) { if (auto updateSetPacket = updateSetPackets.value(connectionId)) { auto pair = make_pair(entityId, *version); - auto i = m_netStateCache.find(pair); - if (i == m_netStateCache.end()) - i = m_netStateCache.insert(pair, monitoredEntity->writeNetState(*version)).first; + auto& cache = m_netStateCache[netRules]; + auto i = cache.find(pair); + if (i == cache.end()) + i = cache.insert(pair, monitoredEntity->writeNetState(*version, netRules)).first; const auto& netState = i->second; if (!netState.first.empty()) updateSetPacket->deltas[entityId] = netState.first; @@ -1879,10 +1910,10 @@ void WorldServer::queueUpdatePackets(ConnectionId clientId, bool sendRemoteUpdat } } else if (!monitoredEntity->masterOnly()) { // Client was unaware of this entity until now - auto firstUpdate = monitoredEntity->writeNetState(); + auto firstUpdate = monitoredEntity->writeNetState(0, netRules); clientInfo->clientSlavesNetVersion.add(entityId, firstUpdate.second); clientInfo->outgoingPackets.append(make_shared(monitoredEntity->entityType(), - entityFactory->netStoreEntity(monitoredEntity), std::move(firstUpdate.first), entityId)); + entityFactory->netStoreEntity(monitoredEntity, netRules), std::move(firstUpdate.first), entityId)); } } } @@ -2075,7 +2106,8 @@ void WorldServer::removeEntity(EntityId entityId, bool andDie) { for (auto const& pair : m_clientInfo) { auto& clientInfo = pair.second; if (auto version = clientInfo->clientSlavesNetVersion.maybeTake(entity->entityId())) { - ByteArray finalDelta = entity->writeNetState(*version).first; + auto netRules = clientInfo->clientState.netCompatibilityRules(); + ByteArray finalDelta = entity->writeNetState(*version, netRules).first; clientInfo->outgoingPackets.append(make_shared(entity->entityId(), std::move(finalDelta), andDie)); } } @@ -2174,14 +2206,17 @@ void WorldServer::setProperty(String const& propertyName, Json const& property) for (auto const& pair : m_clientInfo) pair.second->outgoingPackets.append(make_shared(JsonObject{ {propertyName, property} })); } + auto listener = m_worldPropertyListeners.find(propertyName); + if (listener != m_worldPropertyListeners.end()) + listener->second(property); } void WorldServer::timer(float delay, WorldAction worldAction) { m_timers.append({delay, worldAction}); } -void WorldServer::startFlyingSky(bool enterHyperspace, bool startInWarp) { - m_sky->startFlying(enterHyperspace, startInWarp); +void WorldServer::startFlyingSky(bool enterHyperspace, bool startInWarp, Json settings) { + m_sky->startFlying(enterHyperspace, startInWarp, settings); } void WorldServer::stopFlyingSkyAt(SkyParameters const& destination) { @@ -2340,7 +2375,7 @@ void WorldServer::readMetadata() { m_adjustPlayerStart = metadata.getBool("adjustPlayerStart"); m_worldTemplate = make_shared(metadata.get("worldTemplate")); m_centralStructure = WorldStructure(metadata.get("centralStructure")); - m_protectedDungeonIds = jsonToSet(metadata.get("protectedDungeonIds"), mem_fn(&Json::toUInt)); + m_protectedDungeonIds = jsonToSet>(metadata.get("protectedDungeonIds"), mem_fn(&Json::toUInt)); m_worldProperties = metadata.getObject("worldProperties"); m_spawner.setActive(metadata.getBool("spawningEnabled")); diff --git a/source/game/StarWorldServer.hpp b/source/game/StarWorldServer.hpp index 9e319e5..63414f7 100644 --- a/source/game/StarWorldServer.hpp +++ b/source/game/StarWorldServer.hpp @@ -50,6 +50,7 @@ class WorldServer : public World { public: typedef LuaMessageHandlingComponent>> ScriptComponent; typedef shared_ptr ScriptComponentPtr; + typedef function WorldPropertyListener; // Create a new world with the given template, writing new storage file. WorldServer(WorldTemplatePtr const& worldTemplate, IODevicePtr storage); @@ -87,7 +88,7 @@ public: // Returns false if the client id already exists, or the spawn target is // invalid. - bool addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin = false); + bool addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin = false, NetCompatibilityRules netRules = {}); // Removes client, sends the WorldStopPacket, and returns any pending packets // for that client @@ -108,7 +109,7 @@ public: Maybe receiveMessage(ConnectionId fromConnection, String const& message, JsonArray const& args); - void startFlyingSky(bool enterHyperspace, bool startInWarp); + void startFlyingSky(bool enterHyperspace, bool startInWarp, Json settings = {}); void stopFlyingSkyAt(SkyParameters const& destination); void setOrbitalSky(SkyParameters const& destination); @@ -180,7 +181,10 @@ public: RpcPromise sendEntityMessage(Variant const& entity, String const& message, JsonArray const& args = {}) override; bool isTileProtected(Vec2I const& pos) const override; + bool getTileProtection(DungeonId dungeonId) const; void setTileProtection(DungeonId dungeonId, bool isProtected); + // sets a provided list of DungeonIds all at once and returns how many were changed + size_t setTileProtection(List const& dungeonIds, bool isProtected); // used to globally, temporarily disable protection for certain operations void setTileProtectionEnabled(bool enabled); @@ -228,6 +232,8 @@ public: void setSpawningEnabled(bool spawningEnabled); + void setPropertyListener(String const& propertyName, WorldPropertyListener listener); + // Write all active sectors to disk without unloading them void sync(); // Copy full world to in memory representation @@ -344,6 +350,7 @@ private: bool m_adjustPlayerStart; bool m_respawnInWorld; JsonObject m_worldProperties; + StringMap m_worldPropertyListeners; Maybe> m_newPlanetType; @@ -374,7 +381,7 @@ private: CollisionGenerator m_collisionGenerator; List m_workingCollisionBlocks; - HashMap, pair> m_netStateCache; + HashMap, pair>> m_netStateCache; OrderedHashMap> m_clientInfo; GameTimer m_entityUpdateTimer; @@ -396,7 +403,7 @@ private: bool m_generatingDungeon; HashMap m_dungeonIdGravity; HashMap m_dungeonIdBreathable; - Set m_protectedDungeonIds; + StableHashSet m_protectedDungeonIds; bool m_tileProtectionEnabled; HashMap>>> m_entityMessageResponses; diff --git a/source/game/StarWorldServerThread.cpp b/source/game/StarWorldServerThread.cpp index 1e1f51a..45959ea 100644 --- a/source/game/StarWorldServerThread.cpp +++ b/source/game/StarWorldServerThread.cpp @@ -66,10 +66,10 @@ bool WorldServerThread::spawnTargetValid(SpawnTarget const& spawnTarget) { } } -bool WorldServerThread::addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin) { +bool WorldServerThread::addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin, NetCompatibilityRules netRules) { try { RecursiveMutexLocker locker(m_mutex); - if (m_worldServer->addClient(clientId, spawnTarget, isLocal, isAdmin)) { + if (m_worldServer->addClient(clientId, spawnTarget, isLocal, isAdmin, netRules)) { m_clients.add(clientId); return true; } diff --git a/source/game/StarWorldServerThread.hpp b/source/game/StarWorldServerThread.hpp index ae1e666..3223912 100644 --- a/source/game/StarWorldServerThread.hpp +++ b/source/game/StarWorldServerThread.hpp @@ -38,7 +38,7 @@ public: bool spawnTargetValid(SpawnTarget const& spawnTarget); - bool addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin = false); + bool addClient(ConnectionId clientId, SpawnTarget const& spawnTarget, bool isLocal, bool isAdmin = false, NetCompatibilityRules netRules = {}); // Returns final outgoing packets List removeClient(ConnectionId clientId); diff --git a/source/game/interfaces/StarActivatableItem.hpp b/source/game/interfaces/StarActivatableItem.hpp index c8802c0..6501d0b 100644 --- a/source/game/interfaces/StarActivatableItem.hpp +++ b/source/game/interfaces/StarActivatableItem.hpp @@ -1,5 +1,4 @@ -#ifndef STAR_ACTIVATABLE_ITEM_HPP -#define STAR_ACTIVATABLE_ITEM_HPP +#pragma once #include "StarConfig.hpp" @@ -16,6 +15,4 @@ public: virtual void activate() = 0; }; -} - -#endif +} \ No newline at end of file diff --git a/source/game/interfaces/StarEntity.cpp b/source/game/interfaces/StarEntity.cpp index cbc968f..150fcbd 100644 --- a/source/game/interfaces/StarEntity.cpp +++ b/source/game/interfaces/StarEntity.cpp @@ -1,5 +1,6 @@ #include "StarEntity.hpp" #include "StarDamageManager.hpp" +#include "StarNetCompatibility.hpp" namespace Star { @@ -43,11 +44,11 @@ void Entity::uninit() { m_entityId = NullEntityId; } -pair Entity::writeNetState(uint64_t) { +pair Entity::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) { return {ByteArray(), 0}; } -void Entity::readNetState(ByteArray, float) {} +void Entity::readNetState(ByteArray data, float interpolationTime, NetCompatibilityRules rules) {} void Entity::enableInterpolation(float) {} diff --git a/source/game/interfaces/StarEntity.hpp b/source/game/interfaces/StarEntity.hpp index 7da2a41..56bd5ac 100644 --- a/source/game/interfaces/StarEntity.hpp +++ b/source/game/interfaces/StarEntity.hpp @@ -3,6 +3,7 @@ #include "StarCasting.hpp" #include "StarDamage.hpp" #include "StarLightSource.hpp" +#include "StarDataStream.hpp" namespace Star { @@ -63,11 +64,11 @@ public: // uninitalized. Should return the delta to be written to the slave, along // with the version to pass into writeDeltaState on the next call. The first // delta written to a slave entity will always be the delta starting with 0. - virtual pair writeNetState(uint64_t fromVersion = 0); + virtual pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {}); // Will be called with deltas written by writeDeltaState, including if the // delta is empty. interpolationTime will be provided if interpolation is // enabled. - virtual void readNetState(ByteArray data, float interpolationTime = 0.0); + virtual void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}); virtual void enableInterpolation(float extrapolationHint); virtual void disableInterpolation(); diff --git a/source/game/scripting/StarCameraLuaBindings.cpp b/source/game/scripting/StarCameraLuaBindings.cpp new file mode 100644 index 0000000..b7128ea --- /dev/null +++ b/source/game/scripting/StarCameraLuaBindings.cpp @@ -0,0 +1,31 @@ +#include "StarCameraLuaBindings.hpp" +#include "StarLuaConverters.hpp" +#include "StarWorldCamera.hpp" +#include "StarRoot.hpp" + +namespace Star { + +LuaCallbacks LuaBindings::makeCameraCallbacks(WorldCamera* camera) { + LuaCallbacks callbacks; + + callbacks.registerCallbackWithSignature("position", bind(&WorldCamera::centerWorldPosition, camera)); + callbacks.registerCallbackWithSignature("pixelRatio", bind(&WorldCamera::pixelRatio, camera)); + callbacks.registerCallback("setPixelRatio", [camera](float pixelRatio, Maybe smooth) { + if (smooth.value()) + camera->setTargetPixelRatio(pixelRatio); + else + camera->setPixelRatio(pixelRatio); + Root::singleton().configuration()->set("zoomLevel", pixelRatio); + }); + + callbacks.registerCallbackWithSignature("screenSize", bind(&WorldCamera::screenSize, camera)); + callbacks.registerCallbackWithSignature("worldScreenRect", bind(&WorldCamera::worldScreenRect, camera)); + callbacks.registerCallbackWithSignature("worldTileRect", bind(&WorldCamera::worldTileRect, camera)); + callbacks.registerCallbackWithSignature("tileMinScreen", bind(&WorldCamera::tileMinScreen, camera)); + callbacks.registerCallbackWithSignature("screenToWorld", bind(&WorldCamera::screenToWorld, camera, _1)); + callbacks.registerCallbackWithSignature("worldToScreen", bind(&WorldCamera::worldToScreen, camera, _1)); + + return callbacks; +} + +} diff --git a/source/game/scripting/StarCameraLuaBindings.hpp b/source/game/scripting/StarCameraLuaBindings.hpp new file mode 100644 index 0000000..b944b3d --- /dev/null +++ b/source/game/scripting/StarCameraLuaBindings.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "StarLua.hpp" + +namespace Star { + +STAR_CLASS(WorldCamera); + +namespace LuaBindings { + LuaCallbacks makeCameraCallbacks(WorldCamera* camera); +} +} diff --git a/source/game/scripting/StarCelestialLuaBindings.cpp b/source/game/scripting/StarCelestialLuaBindings.cpp index 2123bf7..03a989f 100644 --- a/source/game/scripting/StarCelestialLuaBindings.cpp +++ b/source/game/scripting/StarCelestialLuaBindings.cpp @@ -30,9 +30,9 @@ LuaCallbacks LuaBindings::makeCelestialCallbacks(UniverseClient* client) { return client->currentSky()->inHyperspace(); }); - callbacks.registerCallback("flyShip", [client,systemWorld](Vec3I const& system, Json const& destination) { + callbacks.registerCallback("flyShip", [client,systemWorld](Vec3I const& system, Json const& destination, Json const& settings) { auto location = jsonToSystemLocation(destination); - client->flyShip(system, location); + client->flyShip(system, location, settings); }); callbacks.registerCallback("flying", [systemWorld]() { return systemWorld->flying(); diff --git a/source/game/scripting/StarInputLuaBindings.cpp b/source/game/scripting/StarInputLuaBindings.cpp index 9a99c4a..565e3b9 100644 --- a/source/game/scripting/StarInputLuaBindings.cpp +++ b/source/game/scripting/StarInputLuaBindings.cpp @@ -53,6 +53,7 @@ LuaCallbacks LuaBindings::makeInputCallbacks() { }); callbacks.registerCallbackWithSignature("mousePosition", bind(mem_fn(&Input::mousePosition), input)); + callbacks.registerCallbackWithSignature("getTag", bind(mem_fn(&Input::getTag), input, _1)); return callbacks; } diff --git a/source/game/scripting/StarLuaRoot.cpp b/source/game/scripting/StarLuaRoot.cpp index a14d10a..592c24b 100644 --- a/source/game/scripting/StarLuaRoot.cpp +++ b/source/game/scripting/StarLuaRoot.cpp @@ -106,13 +106,6 @@ LuaContext LuaRoot::createContext(StringList const& scriptPaths) { } }); - auto handleIndex = newContext.handleIndex(); - auto engine = m_luaEngine.get(); - newContext.set("loadstring", m_luaEngine->createFunction([engine, handleIndex](String const& source, Maybe const& name, Maybe const& env) -> LuaFunction { - String functionName = name ? strf("loadstring: {}", *name) : "loadstring"; - return engine->createFunctionFromSource(env ? env->handleIndex() : handleIndex, source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr()); - })); - auto assets = Root::singleton().assets(); for (auto const& scriptPath : scriptPaths) { diff --git a/source/game/scripting/StarPlayerLuaBindings.cpp b/source/game/scripting/StarPlayerLuaBindings.cpp index a6da4cc..5af86d0 100644 --- a/source/game/scripting/StarPlayerLuaBindings.cpp +++ b/source/game/scripting/StarPlayerLuaBindings.cpp @@ -11,6 +11,8 @@ #include "StarStatistics.hpp" #include "StarPlayerUniverseMap.hpp" #include "StarJsonExtra.hpp" +#include "StarUniverseClient.hpp" +#include "StarTeamClient.hpp" namespace Star { @@ -27,6 +29,20 @@ LuaCallbacks LuaBindings::makePlayerCallbacks(Player* player) { } }); + callbacks.registerCallback("teamMembers", [player]() -> Maybe { + if (auto client = player->universeClient()) { + return client->teamClient()->members().transformed([](TeamClient::Member const& member) -> Json { + return JsonObject{ + {"name", member.name}, + {"uuid", member.uuid.hex()}, + {"entity", member.entity}, + {"healthPercentage", member.healthPercentage}, + {"energyPercentage", member.energyPercentage}}; + }); + } + return {}; + }); + callbacks.registerCallback( "humanoidIdentity", [player]() { return player->humanoid()->identity().toJson(); }); callbacks.registerCallback("setHumanoidIdentity", [player](Json const& id) { player->setIdentity(HumanoidIdentity(id)); }); @@ -453,6 +469,39 @@ LuaCallbacks LuaBindings::makePlayerCallbacks(Player* player) { return followUp->questId(); }); + callbacks.registerCallback("questIds", [player]() { + return player->questManager()->quests().keys(); + }); + + callbacks.registerCallback("serverQuestIds", [player]() { + return player->questManager()->serverQuests().keys(); + }); + + callbacks.registerCallback("quest", [player](String const& questId) -> Json { + if (!player->questManager()->hasQuest(questId)) + return {}; + return player->questManager()->getQuest(questId)->diskStore(); + }); + + callbacks.registerCallback("questPortrait", [player](String const& questId, String const& portraitName) -> Maybe> { + if (!player->questManager()->hasQuest(questId)) + return {}; + return player->questManager()->getQuest(questId)->portrait(portraitName); + }); + + + callbacks.registerCallback("questState", [player](String const& questId) -> Maybe { + if (!player->questManager()->hasQuest(questId)) + return {}; + return QuestStateNames.getRight(player->questManager()->getQuest(questId)->state()); + }); + + callbacks.registerCallback("callQuest", [player](String const& questId, String const& func, LuaVariadic const& args) -> Maybe { + if (!player->questManager()->hasQuest(questId)) + return {}; + return player->questManager()->getQuest(questId)->callScript(func, args); + }); + callbacks.registerCallback("hasQuest", [player](String const& questId) { return player->questManager()->hasQuest(questId); }); @@ -469,11 +518,34 @@ LuaCallbacks LuaBindings::makePlayerCallbacks(Player* player) { return player->questManager()->hasCompleted(questId); }); + callbacks.registerCallback("trackedQuestId", [player]() { + return player->questManager()->trackedQuestId(); + }); + + callbacks.registerCallback("setTrackedQuest", [player](Maybe const& questId) { + return player->questManager()->setAsTracked(questId); + }); + + callbacks.registerCallback("canTurnInQuest", [player](String const& questId) { + return player->questManager()->canTurnIn(questId); + }); + + callbacks.registerCallback("currentQuestId", [player]() { + return player->questManager()->currentQuestId(); + }); + + callbacks.registerCallback("currentQuest", [player]() -> Json { + auto maybeQuest = player->questManager()->currentQuest(); + if (maybeQuest) { + return (*maybeQuest)->diskStore(); + } + return {}; + }); + callbacks.registerCallback("currentQuestWorld", [player]() -> Maybe { auto maybeQuest = player->questManager()->currentQuest(); if (maybeQuest) { - auto quest = *maybeQuest; - if (auto worldId = quest->worldId()) + if (auto worldId = (*maybeQuest)->worldId()) return printWorldId(*worldId); } return {}; diff --git a/source/game/scripting/StarRootLuaBindings.cpp b/source/game/scripting/StarRootLuaBindings.cpp index 50d18e8..d87b423 100644 --- a/source/game/scripting/StarRootLuaBindings.cpp +++ b/source/game/scripting/StarRootLuaBindings.cpp @@ -33,6 +33,7 @@ LuaCallbacks LuaBindings::makeRootCallbacks() { callbacks.registerCallbackWithSignature("assetData", bind(RootCallbacks::assetData, root, _1)); callbacks.registerCallbackWithSignature("assetImage", bind(RootCallbacks::assetImage, root, _1)); + callbacks.registerCallbackWithSignature("assetFrames", bind(RootCallbacks::assetFrames, root, _1)); callbacks.registerCallbackWithSignature("assetJson", bind(RootCallbacks::assetJson, root, _1)); callbacks.registerCallbackWithSignature("makeCurrentVersionedJson", bind(RootCallbacks::makeCurrentVersionedJson, root, _1, _2)); callbacks.registerCallbackWithSignature("loadVersionedJson", bind(RootCallbacks::loadVersionedJson, root, _1, _2)); @@ -229,7 +230,7 @@ LuaCallbacks LuaBindings::makeRootCallbacks() { }); callbacks.registerCallback("setConfiguration", [root](String const& key, Json const& value) { - if (key == "safeScripts") + if (key == "safeScripts" || key == "safe") throw StarException(strf("Cannot set {}", key)); else root->configuration()->set(key, value); @@ -237,14 +238,14 @@ LuaCallbacks LuaBindings::makeRootCallbacks() { callbacks.registerCallback("getConfigurationPath", [root](String const& path) -> Json { - if (path.beginsWith("title")) + if (path.empty() || path.beginsWith("title")) throw ConfigurationException(strf("cannot get {}", path)); else return root->configuration()->getPath(path); }); callbacks.registerCallback("setConfigurationPath", [root](String const& path, Json const& value) { - if (path.beginsWith("safeScripts")) + if (path.empty() || path.beginsWith("safeScripts") || path.splitAny("[].").get(0) == "safe") throw ConfigurationException(strf("cannot set {}", path)); else root->configuration()->setPath(path, value); @@ -262,6 +263,12 @@ Image LuaBindings::RootCallbacks::assetImage(Root* root, String const& path) { return *root->assets()->image(path); } +Json LuaBindings::RootCallbacks::assetFrames(Root* root, String const& path) { + if (auto frames = root->assets()->imageFrames(path)) + return frames->toJson(); + return Json(); +} + Json LuaBindings::RootCallbacks::assetJson(Root* root, String const& path) { return root->assets()->json(path); } diff --git a/source/game/scripting/StarRootLuaBindings.hpp b/source/game/scripting/StarRootLuaBindings.hpp index 0cee77f..b3eeddc 100644 --- a/source/game/scripting/StarRootLuaBindings.hpp +++ b/source/game/scripting/StarRootLuaBindings.hpp @@ -15,6 +15,7 @@ namespace LuaBindings { namespace RootCallbacks { String assetData(Root* root, String const& path); Image assetImage(Root* root, String const& path); + Json assetFrames(Root* root, String const& path); Json assetJson(Root* root, String const& path); Json makeCurrentVersionedJson(Root* root, String const& identifier, Json const& content); Json loadVersionedJson(Root* root, Json const& versionedJson, String const& expectedIdentifier); diff --git a/source/game/scripting/StarStatusControllerLuaBindings.cpp b/source/game/scripting/StarStatusControllerLuaBindings.cpp index 819c517..7edcb6f 100644 --- a/source/game/scripting/StarStatusControllerLuaBindings.cpp +++ b/source/game/scripting/StarStatusControllerLuaBindings.cpp @@ -81,7 +81,7 @@ LuaCallbacks LuaBindings::makeStatusControllerCallbacks(StatusController* statCo callbacks.registerCallbackWithSignature("uniqueStatusEffectActive", bind(&StatusControllerCallbacks::uniqueStatusEffectActive, statController, _1)); - callbacks.registerCallbackWithSignature("primaryDirectives", bind(&StatusController::primaryDirectives, statController)); + callbacks.registerCallbackWithSignature("primaryDirectives", bind(&StatusController::primaryDirectives, statController)); callbacks.registerCallback("setPrimaryDirectives", [statController](Maybe const& directives) { statController->setPrimaryDirectives(directives.value()); }); callbacks.registerCallbackWithSignature("applySelfDamageRequest", bind(&StatusController::applySelfDamageRequest, statController, _1)); diff --git a/source/game/scripting/StarWorldLuaBindings.cpp b/source/game/scripting/StarWorldLuaBindings.cpp index 08a7a43..624660f 100644 --- a/source/game/scripting/StarWorldLuaBindings.cpp +++ b/source/game/scripting/StarWorldLuaBindings.cpp @@ -356,6 +356,8 @@ namespace LuaBindings { }); if (auto clientWorld = as(world)) { + callbacks.registerCallback("inWorld", [clientWorld]() { return clientWorld->inWorld(); }); + callbacks.registerCallback("mainPlayer", [clientWorld]() { return clientWorld->clientState().playerId(); }); callbacks.registerCallback("isClient", []() { return true; }); callbacks.registerCallback("isServer", []() { return false; }); callbacks.registerCallbackWithSignature("clientWindow", bind(ClientWorldCallbacks::clientWindow, clientWorld)); diff --git a/source/rendering/CMakeLists.txt b/source/rendering/CMakeLists.txt index 401f16e..f44f109 100644 --- a/source/rendering/CMakeLists.txt +++ b/source/rendering/CMakeLists.txt @@ -16,7 +16,6 @@ SET (star_rendering_HEADERS StarFontTextureGroup.hpp StarTextPainter.hpp StarTilePainter.hpp - StarWorldCamera.hpp StarWorldPainter.hpp ) @@ -28,7 +27,6 @@ SET (star_rendering_SOURCES StarFontTextureGroup.cpp StarTextPainter.cpp StarTilePainter.cpp - StarWorldCamera.cpp StarWorldPainter.cpp ) diff --git a/source/rendering/StarEnvironmentPainter.cpp b/source/rendering/StarEnvironmentPainter.cpp index b5c514e..d95d968 100644 --- a/source/rendering/StarEnvironmentPainter.cpp +++ b/source/rendering/StarEnvironmentPainter.cpp @@ -39,9 +39,6 @@ void EnvironmentPainter::update(float dt) { } void EnvironmentPainter::renderStars(float pixelRatio, Vec2F const& screenSize, SkyRenderData const& sky) { - if (!sky.settings) - return; - float nightSkyAlpha = 1.0f - min(sky.dayLevel, sky.skyAlpha); if (nightSkyAlpha <= 0.0f) return; @@ -58,6 +55,9 @@ void EnvironmentPainter::renderStars(float pixelRatio, Vec2F const& screenSize, setupStars(sky); } + if (!sky.settings || sky.starFrames == 0 || sky.starTypes().empty()) + return; + float screenBuffer = sky.settings.queryFloat("stars.screenBuffer"); PolyF field = PolyF(RectF::withSize(viewMin, Vec2F(viewSize)).padded(screenBuffer)); @@ -85,8 +85,8 @@ void EnvironmentPainter::renderStars(float pixelRatio, Vec2F const& screenSize, Vec2F screenPos = transform.transformVec2(star.first); if (viewRect.contains(screenPos)) { size_t starFrame = (size_t)(sky.epochTime + star.second.second) % sky.starFrames; - auto const& texture = m_starTextures[star.second.first * sky.starFrames + starFrame]; - primitives.emplace_back(std::in_place_type_t(), texture, screenPos * pixelRatio - Vec2F(texture->size()) / 2, 1.0, color, 0.0f); + if (auto const& texture = m_starTextures[star.second.first * sky.starFrames + starFrame]) + primitives.emplace_back(std::in_place_type_t(), texture, screenPos * pixelRatio - Vec2F(texture->size()) / 2, 1.0, color, 0.0f); } } @@ -455,7 +455,7 @@ void EnvironmentPainter::setupStars(SkyRenderData const& sky) { if (!sky.settings) return; - StringList starTypes = sky.starTypes(); + StringList const& starTypes = sky.starTypes(); size_t starTypesSize = starTypes.size(); m_starTextures.resize(starTypesSize * sky.starFrames); diff --git a/source/server/StarServerQueryThread.cpp b/source/server/StarServerQueryThread.cpp index 2011f42..441fa63 100644 --- a/source/server/StarServerQueryThread.cpp +++ b/source/server/StarServerQueryThread.cpp @@ -107,6 +107,8 @@ bool ServerQueryThread::processPacket(HostAddressWithPort const& address, char c << A2S_TYPE_DEDICATED // dedicated #ifdef STAR_SYSTEM_FAMILY_WINDOWS << A2S_ENV_WINDOWS // os +#elif defined(STAR_SYSTEM_MACOS) + << A2S_ENV_MAC // os #else << A2S_ENV_LINUX // os #endif @@ -153,17 +155,17 @@ void ServerQueryThread::buildPlayerResponse() { return; } - auto clientIds = m_universe->clientIds(); + auto clientIds = m_universe->clientIdsAndCreationTime(); uint8_t cnt = (uint8_t)clientIds.count(); int32_t kills = 0; // Not currently supported - float timeConnected = 60; // Not supported defaults to 1min m_playersResponse.clear(); m_playersResponse << A2S_HEAD_INT << A2S_PLAYER_REPLY << cnt; uint8_t i = 0; - for (auto clientId : clientIds) { - m_playersResponse << i++ << m_universe->clientNick(clientId) << kills << timeConnected; + for (auto& pair : clientIds) { + auto timeConnected = float(now - pair.second) / 1000.f; + m_playersResponse << i++ << m_universe->clientNick(pair.first) << kills << timeConnected; } m_lastPlayersResponse = now; diff --git a/source/server/StarServerQueryThread.hpp b/source/server/StarServerQueryThread.hpp index 54ab93f..d45e1a9 100644 --- a/source/server/StarServerQueryThread.hpp +++ b/source/server/StarServerQueryThread.hpp @@ -42,11 +42,12 @@ private: static const uint8_t A2S_EDF_TAGS = 0x20; static const uint8_t A2S_EDF_STV = 0x40; static const uint8_t A2S_EDF_PORT = 0x80; - static const uint8_t A2S_ENV_WINDOWS = 'W'; - static const uint8_t A2S_ENV_LINUX = 'L'; - static const uint8_t A2S_TYPE_DEDICATED = 'D'; - static const uint8_t A2S_TYPE_LISTEN = 'L'; - static const uint8_t A2S_TYPE_TV = 'P'; + static const uint8_t A2S_ENV_WINDOWS = 'w'; + static const uint8_t A2S_ENV_LINUX = 'l'; + static const uint8_t A2S_ENV_MAC = 'm'; + static const uint8_t A2S_TYPE_DEDICATED = 'd'; + static const uint8_t A2S_TYPE_LISTEN = 'l'; + static const uint8_t A2S_TYPE_TV = 'p'; static const uint8_t A2S_VAC_OFF = 0x00; static const uint8_t A2S_VAC_ON = 0x01; static constexpr const char* A2S_INFO_REQUEST_STRING = "Source Engine Query"; diff --git a/source/vcpkg-configuration.json b/source/vcpkg-configuration.json index 98aa608..9d30be2 100644 --- a/source/vcpkg-configuration.json +++ b/source/vcpkg-configuration.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-configuration.schema.json", "default-registry": { "kind": "git", - "baseline": "1de2026f28ead93ff1773e6e680387643e914ea1", + "baseline": "b322364f06308bdd24823f9d8f03fe0cc86fd46f", "repository": "https://github.com/microsoft/vcpkg" } } diff --git a/source/vcpkg.json b/source/vcpkg.json index 71bc63f..bce60de 100644 --- a/source/vcpkg.json +++ b/source/vcpkg.json @@ -2,7 +2,8 @@ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "dependencies": [ "glew", - "sdl2", + { "name": "sdl2", "default-features": false, "features": ["wayland", "x11", "alsa"], "platform": "linux" }, + { "name": "sdl2", "platform": "!linux" }, "libvorbis", "zlib", "freetype", diff --git a/source/windowing/StarButtonWidget.cpp b/source/windowing/StarButtonWidget.cpp index 258ac55..0412ae5 100644 --- a/source/windowing/StarButtonWidget.cpp +++ b/source/windowing/StarButtonWidget.cpp @@ -165,6 +165,12 @@ void ButtonWidget::mouseOut() { void ButtonWidget::mouseReturnStillDown() { Widget::mouseReturnStillDown(); + if (!isPressed()) { + auto assets = Root::singleton().assets(); + auto sound = Random::randValueFrom(m_clickSounds, ""); + if (!sound.empty()) + context()->playAudio(sound); + } m_hovered = true; m_pressed = true; } diff --git a/source/windowing/StarPane.cpp b/source/windowing/StarPane.cpp index 1511fed..1e20402 100644 --- a/source/windowing/StarPane.cpp +++ b/source/windowing/StarPane.cpp @@ -411,6 +411,18 @@ LuaCallbacks Pane::makePaneCallbacks() { return isDisplayed(); }); + callbacks.registerCallback("hasFocus", [this]() { + hasFocus(); + }); + + callbacks.registerCallback("show", [this]() { + show(); + }); + + callbacks.registerCallback("hide", [this]() { + hide(); + }); + return callbacks; } diff --git a/source/windowing/StarPaneManager.cpp b/source/windowing/StarPaneManager.cpp index 4f4cee7..d8a727a 100644 --- a/source/windowing/StarPaneManager.cpp +++ b/source/windowing/StarPaneManager.cpp @@ -17,11 +17,9 @@ EnumMap const PaneLayerNames{ PaneManager::PaneManager() : m_context(GuiContext::singletonPtr()), m_prevInterfaceScale(1) { auto assets = Root::singleton().assets(); - m_tooltipMouseoverTime = assets->json("/panes.config:tooltipMouseoverTime").toFloat(); m_tooltipMouseoverRadius = assets->json("/panes.config:tooltipMouseoverRadius").toFloat(); m_tooltipMouseOffset = jsonToVec2I(assets->json("/panes.config:tooltipMouseoverOffset")); - - m_tooltipShowTimer = m_tooltipMouseoverTime; + m_tooltipShowTimer = GameTimer(assets->json("/panes.config:tooltipMouseoverTime").toFloat()); } void PaneManager::displayPane(PaneLayer paneLayer, PanePtr const& pane, DismissCallback onDismiss) { @@ -124,7 +122,9 @@ PanePtr PaneManager::getPaneAt(Set const& paneLayers, Vec2I const& po PanePtr PaneManager::getPaneAt(Vec2I const& position) const { for (auto const& layerPair : m_displayedPanes) { for (auto const& panePair : layerPair.second) { - if (panePair.first->inWindow(position) && panePair.first->active()) + if (panePair.first != m_activeTooltip + && panePair.first->inWindow(position) + && panePair.first->active()) return panePair.first; } } @@ -184,13 +184,13 @@ bool PaneManager::sendInputEvent(InputEvent const& event) { } } - if (event.is() || vmag(m_tooltipInitialPosition - m_tooltipLastMousePos) > m_tooltipMouseoverRadius) { - m_tooltipShowTimer = m_tooltipMouseoverTime; + if (event.is()) { + m_tooltipShowTimer.reset(); if (m_activeTooltip) { dismiss(m_activeTooltip); m_activeTooltip.reset(); m_tooltipParentPane.reset(); - m_tooltipShowTimer = m_tooltipMouseoverTime; + m_tooltipShowTimer.reset(); } } @@ -270,37 +270,46 @@ void PaneManager::render() { } void PaneManager::update(float dt) { - m_tooltipShowTimer -= GlobalTimestep; - if (m_tooltipShowTimer < 0 && !m_activeTooltip) { - if (auto parentPane = getPaneAt(m_tooltipLastMousePos)) { - if (auto tooltip = parentPane->createTooltip(m_tooltipLastMousePos)) { + auto newTooltipParentPane = getPaneAt(m_tooltipLastMousePos); + + bool updateTooltip = m_tooltipShowTimer.tick(dt) || (m_activeTooltip && ( + vmag(m_tooltipInitialPosition - m_tooltipLastMousePos) > m_tooltipMouseoverRadius + || m_tooltipParentPane != newTooltipParentPane + || !m_tooltipParentPane->inWindow(m_tooltipLastMousePos))); + + if (updateTooltip) { + if (m_activeTooltip) { + dismiss(m_activeTooltip); + m_activeTooltip.reset(); + m_tooltipParentPane.reset(); + } + + m_tooltipShowTimer.reset(); + if (newTooltipParentPane) { + if (auto tooltip = newTooltipParentPane->createTooltip(m_tooltipLastMousePos)) { m_activeTooltip = std::move(tooltip); - m_tooltipParentPane = std::move(parentPane); + m_tooltipParentPane = std::move(newTooltipParentPane); m_tooltipInitialPosition = m_tooltipLastMousePos; displayPane(PaneLayer::Tooltip, m_activeTooltip); - - Vec2I offsetDirection = Vec2I::filled(1); - Vec2I offsetAdjust = Vec2I(); - - if (m_tooltipLastMousePos[0] + m_tooltipMouseOffset[0] + m_activeTooltip->size()[0] > (int)m_context->windowWidth() / m_context->interfaceScale()) { - offsetDirection[0] = -1; - offsetAdjust[0] = -m_activeTooltip->size()[0]; - } - - if (m_tooltipLastMousePos[1] + m_tooltipMouseOffset[1] - m_activeTooltip->size()[1] < 0) - offsetDirection[1] = -1; - else - offsetAdjust[1] = -m_activeTooltip->size()[1]; - - m_activeTooltip->setPosition(m_tooltipLastMousePos + (offsetAdjust + m_tooltipMouseOffset.piecewiseMultiply(offsetDirection))); - } else { - m_tooltipShowTimer = m_tooltipMouseoverTime; } } - } else if (m_activeTooltip && !m_tooltipParentPane->isDisplayed()) { - dismiss(m_activeTooltip); - m_activeTooltip.reset(); - m_tooltipParentPane.reset(); + } + + if (m_activeTooltip) { + Vec2I offsetDirection = Vec2I::filled(1); + Vec2I offsetAdjust = Vec2I(); + + if (m_tooltipLastMousePos[0] + m_tooltipMouseOffset[0] + m_activeTooltip->size()[0] > (int)m_context->windowWidth() / m_context->interfaceScale()) { + offsetDirection[0] = -1; + offsetAdjust[0] = -m_activeTooltip->size()[0]; + } + + if (m_tooltipLastMousePos[1] + m_tooltipMouseOffset[1] - m_activeTooltip->size()[1] < 0) + offsetDirection[1] = -1; + else + offsetAdjust[1] = -m_activeTooltip->size()[1]; + + m_activeTooltip->setPosition(m_tooltipLastMousePos + (offsetAdjust + m_tooltipMouseOffset.piecewiseMultiply(offsetDirection))); } for (auto const& layerPair : m_displayedPanes) { diff --git a/source/windowing/StarPaneManager.hpp b/source/windowing/StarPaneManager.hpp index ffa46c7..3d507f3 100644 --- a/source/windowing/StarPaneManager.hpp +++ b/source/windowing/StarPaneManager.hpp @@ -3,6 +3,7 @@ #include "StarPane.hpp" #include "StarOrderedMap.hpp" #include "StarBiMap.hpp" +#include "StarGameTimers.hpp" namespace Star { @@ -92,11 +93,9 @@ private: WidgetPtr m_backgroundWidget; - float m_tooltipMouseoverTime; float m_tooltipMouseoverRadius; Vec2I m_tooltipMouseOffset; - - float m_tooltipShowTimer; + GameTimer m_tooltipShowTimer; Vec2I m_tooltipLastMousePos; Vec2I m_tooltipInitialPosition; PanePtr m_activeTooltip; diff --git a/source/windowing/StarScrollArea.cpp b/source/windowing/StarScrollArea.cpp index f4cc140..775462a 100644 --- a/source/windowing/StarScrollArea.cpp +++ b/source/windowing/StarScrollArea.cpp @@ -384,7 +384,8 @@ bool ScrollArea::sendEvent(InputEvent const& event) { return true; } -void ScrollArea::update(float) { +void ScrollArea::update(float dt) { + Widget::update(dt); if (!m_visible) return; diff --git a/toolchains/linux-clang.cmake b/toolchains/linux-clang.cmake new file mode 100644 index 0000000..943c12f --- /dev/null +++ b/toolchains/linux-clang.cmake @@ -0,0 +1,4 @@ +include($ENV{VCPKG_ROOT}/scripts/toolchains/linux.cmake) + +set(CMAKE_C_COMPILER "/usr/bin/clang") +set(CMAKE_CXX_COMPILER "/usr/bin/clang++") \ No newline at end of file diff --git a/triplets/x64-linux-mixed.cmake b/triplets/x64-linux-mixed.cmake index be80364..9a95aff 100644 --- a/triplets/x64-linux-mixed.cmake +++ b/triplets/x64-linux-mixed.cmake @@ -3,6 +3,7 @@ set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CMAKE_SYSTEM_NAME Linux) +set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/../toolchains/linux-clang.cmake) if(PORT MATCHES "discord-") set(VCPKG_LIBRARY_LINKAGE dynamic)