mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-07-13 18:24:16 +00:00
Compare commits
203 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f69e336a9 | |||
| f08dfc0585 | |||
| a9dee2bec5 | |||
| 5319c2878a | |||
| 73f494041a | |||
| 530d605bc1 | |||
| 63b2a8e4a3 | |||
| a7a19d9a30 | |||
| 3068a0e5c4 | |||
| f70514a021 | |||
| 94768c916d | |||
| 86b37e8ae1 | |||
| 8f9db10474 | |||
| afa5a04043 | |||
| 4444be0af9 | |||
| 5f7207bc52 | |||
| 9927d2befb | |||
| 60f88916a9 | |||
| 0cc73e70c9 | |||
| bfb2086e05 | |||
| 9d67838f8f | |||
| bbfb85155e | |||
| ce5f2e666d | |||
| e076197ab2 | |||
| ee03afb9a1 | |||
| f775678e2e | |||
| 45bb6ca6f3 | |||
| f5f6b8534d | |||
| f3627ce0bf | |||
| e1aaaf5e63 | |||
| a147edd31a | |||
| 214096409d | |||
| 4a062e5aa0 | |||
| ba723ee106 | |||
| 49ed82bea1 | |||
| 0d848fda7c | |||
| a0040d8c57 | |||
| baa2c86e25 | |||
| 0950d367d4 | |||
| 8b21b6cef3 | |||
| 82a6d4af60 | |||
| 8b753ab6ea | |||
| b097acfd4a | |||
| 5baeaa72c2 | |||
| bd76e28ca6 | |||
| 012ce08b91 | |||
| 9db3619cd8 | |||
| 6f4c3f0ceb | |||
| 4fad047bf4 | |||
| 5502c74229 | |||
| eaedeb5324 | |||
| 72022e3349 | |||
| 08374b1398 | |||
| 29f4d0d286 | |||
| 3c80bcbf01 | |||
| 5919fc6f47 | |||
| 461fb5d896 | |||
| 6731b3e977 | |||
| e7c7f45039 | |||
| 0748267fab | |||
| 8c32d760be | |||
| 7919f81927 | |||
| 26ef39827e | |||
| 2451e08b01 | |||
| 25739cb1bd | |||
| 814927d0a1 | |||
| 6c0a8d1d62 | |||
| 0d3256c429 | |||
| 509225f151 | |||
| 73ecef1a87 | |||
| 28a9690a64 | |||
| 07a8d49046 | |||
| bfb0675efa | |||
| 105fd6d4c9 | |||
| a9385c47e1 | |||
| 1e9c4e357c | |||
| a998a7c091 | |||
| 277036fc52 | |||
| e776848a76 | |||
| 63fa65e9a7 | |||
| c07baeed1a | |||
| 33b5384398 | |||
| e94cfd641d | |||
| 6e590ff18a | |||
| 91bc7dea79 | |||
| 8b94b1f0ef | |||
| 5dab48af92 | |||
| f3060f5247 | |||
| c61816dfeb | |||
| 2fcb53530a | |||
| cee039d922 | |||
| 566f0b55f7 | |||
| 58a7e39419 | |||
| bf7f1ef1a5 | |||
| e35bf4fe15 | |||
| 419a951c29 | |||
| 135008a73c | |||
| 29c3fed374 | |||
| 93192fd9b5 | |||
| eea041e8eb | |||
| a3670bff4a | |||
| 1b60e89f26 | |||
| 06e5805428 | |||
| 04e8d00daa | |||
| d30dccc94a | |||
| 84f5f95e54 | |||
| 67db9358e1 | |||
| fd2f713485 | |||
| c880460a55 | |||
| 7f54bcfaec | |||
| 785c5343cd | |||
| 40e5496819 | |||
| 4c9fbc250a | |||
| 31ce0cc7de | |||
| 3a8f4ded29 | |||
| f567645db6 | |||
| 3609fd77ec | |||
| a5e3fc8fb9 | |||
| bcd4b5a235 | |||
| 8c15b87628 | |||
| 13e641b3a3 | |||
| 5f9726f10f | |||
| fcd408970b | |||
| cf5ebcbd1a | |||
| c9d926f9e3 | |||
| 9f47978f0f | |||
| a0f649288e | |||
| b995a222ff | |||
| c5dff8b913 | |||
| 0c740ccedf | |||
| b0cf5c7838 | |||
| 586510041d | |||
| 1794c3fe45 | |||
| ee599e970d | |||
| 40158dc252 | |||
| 5ece60574a | |||
| ff812429ed | |||
| 639c46ad36 | |||
| 158875a962 | |||
| c6dac55679 | |||
| 37109ae3f1 | |||
| d6b78b9683 | |||
| f5e2f7425f | |||
| 8693b8a2b0 | |||
| 3989961ff9 | |||
| a357ff8ca3 | |||
| 86f0052e07 | |||
| 89034a64e0 | |||
| 55f5437618 | |||
| c605498a2d | |||
| 4d7967d5d9 | |||
| 5dd4c97ed1 | |||
| 13390c5388 | |||
| 2f995a71ae | |||
| 3b5c6491a3 | |||
| db8719b5cd | |||
| a3c4b82a5d | |||
| 3b0e49fb06 | |||
| cf8f10b949 | |||
| 6f50cad76b | |||
| 03d91b1f4d | |||
| e407d246e2 | |||
| 234bdf5877 | |||
| c3b4528c89 | |||
| 5cf6d1d228 | |||
| 65017d0834 | |||
| 1237e7e533 | |||
| d81087b5af | |||
| 7e1f86478d | |||
| c85e026c2d | |||
| e4826e8bf1 | |||
| 590b159f14 | |||
| 40533c04bc | |||
| 946c1362e1 | |||
| 4347cb4af2 | |||
| 88721d4f7f | |||
| 7deea900fb | |||
| cbc1483537 | |||
| e2d7721438 | |||
| 0146a1cbe8 | |||
| 352a3aa536 | |||
| ac8c386c61 | |||
| d1fb15fc9a | |||
| f376d9f7be | |||
| 2f3dcfeb5a | |||
| 913ba1c793 | |||
| 9a0c9d3ce4 | |||
| a0d9be18b7 | |||
| 87d2e3355a | |||
| afb3763eab | |||
| 9de5a08b0c | |||
| bef623a281 | |||
| e852245bae | |||
| 5f5af9b0a7 | |||
| 64f2e08ed2 | |||
| 707682f4a8 | |||
| 913674740d | |||
| 1c575ff1bc | |||
| e72c217e63 | |||
| 75bae8ee5a | |||
| acc2dd29e9 | |||
| 39badf432d | |||
| 03307e71fb |
@@ -0,0 +1 @@
|
|||||||
|
patreon: BeamMP
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
name: Linux
|
name: Linux
|
||||||
|
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'minor'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||||
@@ -22,7 +26,7 @@ jobs:
|
|||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 20.04
|
version: 24.04
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.distro }}:${{ matrix.version }}
|
image: ${{ matrix.distro }}:${{ matrix.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -59,13 +63,13 @@ jobs:
|
|||||||
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
||||||
|
|
||||||
- name: Archive server artifact
|
- name: Archive server artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
||||||
path: ./bin/BeamMP-Server
|
path: ./bin/BeamMP-Server
|
||||||
|
|
||||||
- name: Archive server debug info artifact
|
- name: Archive server debug info artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
||||||
path: ./bin/BeamMP-Server.debug
|
path: ./bin/BeamMP-Server.debug
|
||||||
@@ -93,7 +97,7 @@ jobs:
|
|||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 20.04
|
version: 24.04
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.distro }}:${{ matrix.version }}
|
image: ${{ matrix.distro }}:${{ matrix.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -130,13 +134,13 @@ jobs:
|
|||||||
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
||||||
|
|
||||||
- name: Archive server artifact
|
- name: Archive server artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
||||||
path: ./bin/BeamMP-Server
|
path: ./bin/BeamMP-Server
|
||||||
|
|
||||||
- name: Archive server debug info artifact
|
- name: Archive server debug info artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
||||||
path: ./bin/BeamMP-Server.debug
|
path: ./bin/BeamMP-Server.debug
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 20.04
|
version: 24.04
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.distro }}:${{ matrix.version }}
|
image: ${{ matrix.distro }}:${{ matrix.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -116,7 +116,7 @@ jobs:
|
|||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 20.04
|
version: 24.04
|
||||||
env:
|
env:
|
||||||
VCPKG_DEFAULT_TRIPLET: "arm64-linux"
|
VCPKG_DEFAULT_TRIPLET: "arm64-linux"
|
||||||
VCPKG_FORCE_SYSTEM_BINARIES: 1
|
VCPKG_FORCE_SYSTEM_BINARIES: 1
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
name: Windows
|
name: Windows
|
||||||
|
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'minor'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VCPKG_DEFAULT_TRIPLET: x64-windows-static
|
VCPKG_DEFAULT_TRIPLET: x64-windows-static
|
||||||
@@ -36,7 +41,7 @@ jobs:
|
|||||||
run: bash ./scripts/windows/2-build.sh
|
run: bash ./scripts/windows/2-build.sh
|
||||||
|
|
||||||
- name: Archive server artifact
|
- name: Archive server artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: BeamMP-Server-windows
|
name: BeamMP-Server-windows
|
||||||
path: ./bin/Release/BeamMP-Server.exe
|
path: ./bin/Release/BeamMP-Server.exe
|
||||||
|
|||||||
@@ -4,3 +4,6 @@
|
|||||||
[submodule "vcpkg"]
|
[submodule "vcpkg"]
|
||||||
path = vcpkg
|
path = vcpkg
|
||||||
url = https://github.com/Microsoft/vcpkg.git
|
url = https://github.com/Microsoft/vcpkg.git
|
||||||
|
[submodule "deps/toml11"]
|
||||||
|
path = deps/toml11
|
||||||
|
url = https://github.com/ToruNiina/toml11
|
||||||
|
|||||||
+18
-6
@@ -20,7 +20,7 @@ include(cmake/Git.cmake)
|
|||||||
### SETTINGS ###
|
### SETTINGS ###
|
||||||
|
|
||||||
# add all headers (.h, .hpp) to this
|
# add all headers (.h, .hpp) to this
|
||||||
set(PRJ_HEADERS
|
set(PRJ_HEADERS
|
||||||
include/ArgsParser.h
|
include/ArgsParser.h
|
||||||
include/BoostAliases.h
|
include/BoostAliases.h
|
||||||
include/Client.h
|
include/Client.h
|
||||||
@@ -49,6 +49,9 @@ set(PRJ_HEADERS
|
|||||||
include/TServer.h
|
include/TServer.h
|
||||||
include/VehicleData.h
|
include/VehicleData.h
|
||||||
include/Env.h
|
include/Env.h
|
||||||
|
include/Settings.h
|
||||||
|
include/Profiling.h
|
||||||
|
include/ChronoWrapper.h
|
||||||
)
|
)
|
||||||
# add all source files (.cpp) to this, except the one with main()
|
# add all source files (.cpp) to this, except the one with main()
|
||||||
set(PRJ_SOURCES
|
set(PRJ_SOURCES
|
||||||
@@ -72,6 +75,9 @@ set(PRJ_SOURCES
|
|||||||
src/TServer.cpp
|
src/TServer.cpp
|
||||||
src/VehicleData.cpp
|
src/VehicleData.cpp
|
||||||
src/Env.cpp
|
src/Env.cpp
|
||||||
|
src/Settings.cpp
|
||||||
|
src/Profiling.cpp
|
||||||
|
src/ChronoWrapper.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(Lua REQUIRED)
|
find_package(Lua REQUIRED)
|
||||||
@@ -87,7 +93,7 @@ set(PRJ_COMPILE_FEATURES cxx_std_20)
|
|||||||
# set #defines (test enable/disable not included here)
|
# set #defines (test enable/disable not included here)
|
||||||
set(PRJ_DEFINITIONS CPPHTTPLIB_OPENSSL_SUPPORT)
|
set(PRJ_DEFINITIONS CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||||
# add all libraries used by the project (WARNING: also set them in vcpkg.json!)
|
# add all libraries used by the project (WARNING: also set them in vcpkg.json!)
|
||||||
set(PRJ_LIBRARIES
|
set(PRJ_LIBRARIES
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
doctest::doctest
|
doctest::doctest
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
@@ -110,7 +116,7 @@ find_package(httplib CONFIG REQUIRED)
|
|||||||
find_package(libzip CONFIG REQUIRED)
|
find_package(libzip CONFIG REQUIRED)
|
||||||
find_package(RapidJSON CONFIG REQUIRED)
|
find_package(RapidJSON CONFIG REQUIRED)
|
||||||
find_package(sol2 CONFIG REQUIRED)
|
find_package(sol2 CONFIG REQUIRED)
|
||||||
find_package(toml11 CONFIG REQUIRED)
|
add_subdirectory("deps/toml11")
|
||||||
|
|
||||||
include_directories(include)
|
include_directories(include)
|
||||||
|
|
||||||
@@ -119,7 +125,7 @@ include(FindThreads)
|
|||||||
|
|
||||||
### END SETTINGS ###
|
### END SETTINGS ###
|
||||||
|
|
||||||
# DONT change anything beyond this point unless you've read the cmake bible and
|
# DONT change anything beyond this point unless you've read the cmake bible and
|
||||||
# swore on it not to bonk up the ci/cd pipelines with your changes.
|
# swore on it not to bonk up the ci/cd pipelines with your changes.
|
||||||
|
|
||||||
####################
|
####################
|
||||||
@@ -141,6 +147,7 @@ endif(UNIX)
|
|||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_compile_options("-D_WIN32_WINNT=0x0601")
|
add_compile_options("-D_WIN32_WINNT=0x0601")
|
||||||
|
add_compile_options("/bigobj")
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
|
|
||||||
@@ -157,7 +164,7 @@ set(PRJ_DEFINITIONS ${PRJ_DEFINITIONS}
|
|||||||
)
|
)
|
||||||
|
|
||||||
# build commandline manually for funky windows flags to carry over without a custom toolchain file
|
# build commandline manually for funky windows flags to carry over without a custom toolchain file
|
||||||
add_library(commandline_static
|
add_library(commandline_static
|
||||||
deps/commandline/src/impls.h
|
deps/commandline/src/impls.h
|
||||||
deps/commandline/src/windows_impl.cpp
|
deps/commandline/src/windows_impl.cpp
|
||||||
deps/commandline/src/linux_impl.cpp
|
deps/commandline/src/linux_impl.cpp
|
||||||
@@ -170,6 +177,11 @@ add_library(commandline_static
|
|||||||
deps/commandline/src/backends/BufferedBackend.cpp
|
deps/commandline/src/backends/BufferedBackend.cpp
|
||||||
deps/commandline/src/backends/BufferedBackend.h
|
deps/commandline/src/backends/BufferedBackend.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Ensure the commandline library uses C++11
|
||||||
|
set_target_properties(commandline_static PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES)
|
||||||
|
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_compile_definitions(commandline_static PRIVATE -DPLATFORM_WINDOWS=1)
|
target_compile_definitions(commandline_static PRIVATE -DPLATFORM_WINDOWS=1)
|
||||||
else ()
|
else ()
|
||||||
@@ -194,6 +206,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE ${PRJ_DEFINITIONS} ${PRJ_WARN
|
|||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
|
target_compile_options(${PROJECT_NAME} PUBLIC "/bigobj")
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE "/SUBSYSTEM:CONSOLE")
|
target_link_options(${PROJECT_NAME} PRIVATE "/SUBSYSTEM:CONSOLE")
|
||||||
endif(MSVC)
|
endif(MSVC)
|
||||||
|
|
||||||
@@ -212,4 +225,3 @@ if(${PROJECT_NAME}_ENABLE_UNIT_TESTING)
|
|||||||
target_link_options(${PROJECT_NAME}-tests PRIVATE "/SUBSYSTEM:CONSOLE")
|
target_link_options(${PROJECT_NAME}-tests PRIVATE "/SUBSYSTEM:CONSOLE")
|
||||||
endif(MSVC)
|
endif(MSVC)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
+20
-3
@@ -53,11 +53,28 @@ Do **NOT** pull with merge. This is the default git behavior for `git pull`, but
|
|||||||
|
|
||||||
The only acceptable merge commits are those which actually merge functionally different branches into each other, for example for merging one feature branch into another.
|
The only acceptable merge commits are those which actually merge functionally different branches into each other, for example for merging one feature branch into another.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Making an issue and fixing it
|
||||||
|
|
||||||
|
1. Create an issue detailing the feature or bug.
|
||||||
|
2. Assign a milestone to the issue, or wait for a maintainer to add a milestone to your issue.
|
||||||
|
3. Fork the repository and base your work on the branch mentioned in the milestone attached to your issue (e.g. `v3.0.0 (develop)` -> `develop`).
|
||||||
|
4. Program your feature or bug fix.
|
||||||
|
5. Open a PR that references the issue by number in the format: `#12345`.
|
||||||
|
6. Someone will review your PR and merge it, or ask for changes.
|
||||||
|
|
||||||
|
### Fixing an existing issue
|
||||||
|
|
||||||
|
1. Fork the repository and base your work on the branch mentioned in the milestone attached to your issue (e.g. `v3.0.0 (develop)` -> `develop`).
|
||||||
|
2. Program your feature or bug fix.
|
||||||
|
3. Open a PR that references the issue by number in the format: `#12345`.
|
||||||
|
4. Someone will review your PR and merge it, or ask for changes.
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
||||||
### Which branch should I base my work on?
|
- `minor`: Minor releases, like `v1.2.3` -> `v1.3.0` or `v1.2.3` -> `v1.2.4`.
|
||||||
|
- `develop`: Major releases, like `v1.2.3` -> `v2.0.0`, and larger feature/minor releases.
|
||||||
Each *feature* or *bug-fix* is implemented on a new Git branch, branched off of the branch it should be based on. The `master` branch is usually stable, so we don't do development on it. It is always a safe bet to branch off of `master`, but it may be more work to merge later. Branches to base your work on are usually branches like `rc-v3.3.0`, when the latest public version is `3.2.0`, for example. These can often be found in Pull-Requests on GitHub which are tagged `Release Candidate`.
|
|
||||||
|
|
||||||
## Unit tests & CI/CD
|
## Unit tests & CI/CD
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Feel free to ask any questions via the following channels:
|
|||||||
|
|
||||||
These values are guesstimated and are subject to change with each release.
|
These values are guesstimated and are subject to change with each release.
|
||||||
|
|
||||||
* RAM: 50+ MiB usable (not counting OS overhead)
|
* RAM: 30-100 MiB usable (not counting OS overhead)
|
||||||
* CPU: >1GHz, preferably multicore
|
* CPU: >1GHz, preferably multicore
|
||||||
* OS: Windows, Linux (theoretically any POSIX)
|
* OS: Windows, Linux (theoretically any POSIX)
|
||||||
* GPU: None
|
* GPU: None
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md
|
# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md
|
||||||
# Courtesy of Jason Turner
|
# Courtesy of Jason Turner
|
||||||
# License here: https://github.com/cpp-best-practices/cppbestpractices/blob/master/LICENSE
|
# License here: https://github.com/cpp-best-practices/cppbestpractices/blob/master/LICENSE
|
||||||
#
|
#
|
||||||
# This version has been modified by the owners of the current respository.
|
# This version has been modified by the owners of the current respository.
|
||||||
# Modifications have mostly been marked with "modified" or similar, though this is not
|
# Modifications have mostly been marked with "modified" or similar, though this is not
|
||||||
# strictly required.
|
# strictly required.
|
||||||
|
|
||||||
function(set_project_warnings project_name)
|
function(set_project_warnings project_name)
|
||||||
@@ -73,7 +73,6 @@ function(set_project_warnings project_name)
|
|||||||
-Werror=missing-declarations
|
-Werror=missing-declarations
|
||||||
-Werror=missing-field-initializers
|
-Werror=missing-field-initializers
|
||||||
-Werror=ctor-dtor-privacy
|
-Werror=ctor-dtor-privacy
|
||||||
-Werror=switch-enum
|
|
||||||
-Wswitch-default
|
-Wswitch-default
|
||||||
-Werror=unused-result
|
-Werror=unused-result
|
||||||
-Werror=implicit-fallthrough
|
-Werror=implicit-fallthrough
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ find_package(Git)
|
|||||||
if(${PROJECT_NAME}_CHECKOUT_GIT_SUBMODULES)
|
if(${PROJECT_NAME}_CHECKOUT_GIT_SUBMODULES)
|
||||||
if(Git_FOUND)
|
if(Git_FOUND)
|
||||||
message(STATUS "Git found, submodule update and init")
|
message(STATUS "Git found, submodule update and init")
|
||||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
|
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
RESULT_VARIABLE GIT_SUBMOD_RESULT)
|
RESULT_VARIABLE GIT_SUBMOD_RESULT)
|
||||||
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
|
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
|
||||||
|
|||||||
Vendored
+1
-1
Submodule deps/commandline updated: b2a29733f9...04952e4811
+1
Submodule deps/toml11 added at f33ca743fb
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ChronoWrapper {
|
||||||
|
std::chrono::high_resolution_clock::duration TimeFromStringWithLiteral(const std::string& time_str);
|
||||||
|
}
|
||||||
+4
-4
@@ -88,7 +88,7 @@ public:
|
|||||||
void ClearCars();
|
void ClearCars();
|
||||||
[[nodiscard]] int GetID() const { return mID; }
|
[[nodiscard]] int GetID() const { return mID; }
|
||||||
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
|
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
|
||||||
[[nodiscard]] bool IsConnected() const { return mIsConnected; }
|
[[nodiscard]] bool IsUDPConnected() const { return mIsUDPConnected; }
|
||||||
[[nodiscard]] bool IsSynced() const { return mIsSynced; }
|
[[nodiscard]] bool IsSynced() const { return mIsSynced; }
|
||||||
[[nodiscard]] bool IsSyncing() const { return mIsSyncing; }
|
[[nodiscard]] bool IsSyncing() const { return mIsSyncing; }
|
||||||
[[nodiscard]] bool IsGuest() const { return mIsGuest; }
|
[[nodiscard]] bool IsGuest() const { return mIsGuest; }
|
||||||
@@ -100,7 +100,7 @@ public:
|
|||||||
[[nodiscard]] const std::queue<std::vector<uint8_t>>& MissedPacketQueue() const { return mPacketsSync; }
|
[[nodiscard]] const std::queue<std::vector<uint8_t>>& MissedPacketQueue() const { return mPacketsSync; }
|
||||||
[[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); }
|
[[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); }
|
||||||
[[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; }
|
[[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; }
|
||||||
void SetIsConnected(bool NewIsConnected) { mIsConnected = NewIsConnected; }
|
void SetIsUDPConnected(bool NewIsConnected) { mIsUDPConnected = NewIsConnected; }
|
||||||
[[nodiscard]] TServer& Server() const;
|
[[nodiscard]] TServer& Server() const;
|
||||||
void UpdatePingTime();
|
void UpdatePingTime();
|
||||||
int SecondsSinceLastPing();
|
int SecondsSinceLastPing();
|
||||||
@@ -109,7 +109,7 @@ private:
|
|||||||
void InsertVehicle(int ID, const std::string& Data);
|
void InsertVehicle(int ID, const std::string& Data);
|
||||||
|
|
||||||
TServer& mServer;
|
TServer& mServer;
|
||||||
bool mIsConnected = false;
|
bool mIsUDPConnected = false;
|
||||||
bool mIsSynced = false;
|
bool mIsSynced = false;
|
||||||
bool mIsSyncing = false;
|
bool mIsSyncing = false;
|
||||||
mutable std::mutex mMissedPacketsMutex;
|
mutable std::mutex mMissedPacketsMutex;
|
||||||
@@ -128,7 +128,7 @@ private:
|
|||||||
std::string mRole;
|
std::string mRole;
|
||||||
std::string mDID;
|
std::string mDID;
|
||||||
int mID = -1;
|
int mID = -1;
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime;
|
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime = std::chrono::high_resolution_clock::now();
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
|
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
|
||||||
|
|||||||
+21
-84
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
#include <span>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
@@ -36,6 +38,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
#include "Settings.h"
|
||||||
#include "TConsole.h"
|
#include "TConsole.h"
|
||||||
|
|
||||||
struct Version {
|
struct Version {
|
||||||
@@ -58,32 +61,6 @@ using SparseArray = std::unordered_map<size_t, T>;
|
|||||||
class Application final {
|
class Application final {
|
||||||
public:
|
public:
|
||||||
// types
|
// types
|
||||||
struct TSettings {
|
|
||||||
std::string ServerName { "BeamMP Server" };
|
|
||||||
std::string ServerDesc { "BeamMP Default Description" };
|
|
||||||
std::string ServerTags { "Freeroam" };
|
|
||||||
std::string Resource { "Resources" };
|
|
||||||
std::string MapName { "/levels/gridmap_v2/info.json" };
|
|
||||||
std::string Key {};
|
|
||||||
std::string Password{};
|
|
||||||
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
|
|
||||||
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
|
|
||||||
bool HTTPServerEnabled { false };
|
|
||||||
int MaxPlayers { 8 };
|
|
||||||
bool Private { true };
|
|
||||||
int MaxCars { 1 };
|
|
||||||
bool DebugModeEnabled { false };
|
|
||||||
int Port { 30814 };
|
|
||||||
std::string CustomIP {};
|
|
||||||
bool LogChat { true };
|
|
||||||
bool SendErrors { true };
|
|
||||||
bool SendErrorsMessageEnabled { true };
|
|
||||||
int HTTPServerPort { 8080 };
|
|
||||||
std::string HTTPServerIP { "127.0.0.1" };
|
|
||||||
bool HTTPServerUseSSL { false };
|
|
||||||
bool HideUpdateMessages { false };
|
|
||||||
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
using TShutdownHandler = std::function<void()>;
|
using TShutdownHandler = std::function<void()>;
|
||||||
|
|
||||||
@@ -101,13 +78,11 @@ public:
|
|||||||
static std::string PPS() { return mPPS; }
|
static std::string PPS() { return mPPS; }
|
||||||
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
|
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
|
||||||
|
|
||||||
static TSettings Settings;
|
static inline struct Settings Settings { };
|
||||||
|
|
||||||
static std::vector<std::string> GetBackendUrlsInOrder() {
|
static std::vector<std::string> GetBackendUrlsInOrder() {
|
||||||
return {
|
return {
|
||||||
"backend.beammp.com",
|
"backend.beammp.com",
|
||||||
"backup1.beammp.com",
|
|
||||||
"backup2.beammp.com"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +127,7 @@ private:
|
|||||||
static inline std::mutex mShutdownHandlersMutex {};
|
static inline std::mutex mShutdownHandlersMutex {};
|
||||||
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
|
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
|
||||||
|
|
||||||
static inline Version mVersion { 3, 2, 2 };
|
static inline Version mVersion { 3, 5, 1 };
|
||||||
};
|
};
|
||||||
|
|
||||||
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
|
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
|
||||||
@@ -214,6 +189,10 @@ void RegisterThread(const std::string& str);
|
|||||||
do { \
|
do { \
|
||||||
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
#define beammp_lua_log(level, plugin, x) \
|
||||||
|
do { \
|
||||||
|
Application::Console().Write(_this_location + fmt::format("[{}] [{}] ", plugin, level) + (x)); \
|
||||||
|
} while (false)
|
||||||
#define beammp_lua_warn(x) \
|
#define beammp_lua_warn(x) \
|
||||||
do { \
|
do { \
|
||||||
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
|
||||||
@@ -221,13 +200,13 @@ void RegisterThread(const std::string& str);
|
|||||||
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
|
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
|
||||||
#define beammp_debug(x) \
|
#define beammp_debug(x) \
|
||||||
do { \
|
do { \
|
||||||
if (Application::Settings.DebugModeEnabled) { \
|
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
|
||||||
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
#define beammp_event(x) \
|
#define beammp_event(x) \
|
||||||
do { \
|
do { \
|
||||||
if (Application::Settings.DebugModeEnabled) { \
|
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
|
||||||
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
@@ -235,7 +214,7 @@ void RegisterThread(const std::string& str);
|
|||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
#define beammp_trace(x) \
|
#define beammp_trace(x) \
|
||||||
do { \
|
do { \
|
||||||
if (Application::Settings.DebugModeEnabled) { \
|
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
|
||||||
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
@@ -269,6 +248,7 @@ void RegisterThread(const std::string& str);
|
|||||||
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
|
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
|
||||||
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
|
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
|
||||||
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
|
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
|
||||||
|
#define beammp_lua_log(level, plugin, x) /* x */
|
||||||
|
|
||||||
#endif // DOCTEST_CONFIG_DISABLE
|
#endif // DOCTEST_CONFIG_DISABLE
|
||||||
|
|
||||||
@@ -283,57 +263,14 @@ void RegisterThread(const std::string& str);
|
|||||||
|
|
||||||
void LogChatMessage(const std::string& name, int id, const std::string& msg);
|
void LogChatMessage(const std::string& name, int id, const std::string& msg);
|
||||||
|
|
||||||
#define Biggest 30000
|
std::vector<uint8_t> Comp(std::span<const uint8_t> input);
|
||||||
|
std::vector<uint8_t> DeComp(std::span<const uint8_t> input);
|
||||||
template <typename T>
|
|
||||||
inline T Comp(const T& Data) {
|
|
||||||
std::array<char, Biggest> C {};
|
|
||||||
// obsolete
|
|
||||||
C.fill(0);
|
|
||||||
z_stream defstream;
|
|
||||||
defstream.zalloc = nullptr;
|
|
||||||
defstream.zfree = nullptr;
|
|
||||||
defstream.opaque = nullptr;
|
|
||||||
defstream.avail_in = uInt(Data.size());
|
|
||||||
defstream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(&Data[0]));
|
|
||||||
defstream.avail_out = Biggest;
|
|
||||||
defstream.next_out = reinterpret_cast<Bytef*>(C.data());
|
|
||||||
deflateInit(&defstream, Z_BEST_COMPRESSION);
|
|
||||||
deflate(&defstream, Z_SYNC_FLUSH);
|
|
||||||
deflate(&defstream, Z_FINISH);
|
|
||||||
deflateEnd(&defstream);
|
|
||||||
size_t TotalOut = defstream.total_out;
|
|
||||||
T Ret;
|
|
||||||
Ret.resize(TotalOut);
|
|
||||||
std::fill(Ret.begin(), Ret.end(), 0);
|
|
||||||
std::copy_n(C.begin(), TotalOut, Ret.begin());
|
|
||||||
return Ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
inline T DeComp(const T& Compressed) {
|
|
||||||
std::array<char, Biggest> C {};
|
|
||||||
// not needed
|
|
||||||
C.fill(0);
|
|
||||||
z_stream infstream;
|
|
||||||
infstream.zalloc = nullptr;
|
|
||||||
infstream.zfree = nullptr;
|
|
||||||
infstream.opaque = nullptr;
|
|
||||||
infstream.avail_in = Biggest;
|
|
||||||
infstream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(&Compressed[0]));
|
|
||||||
infstream.avail_out = Biggest;
|
|
||||||
infstream.next_out = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(C.data()));
|
|
||||||
inflateInit(&infstream);
|
|
||||||
inflate(&infstream, Z_SYNC_FLUSH);
|
|
||||||
inflate(&infstream, Z_FINISH);
|
|
||||||
inflateEnd(&infstream);
|
|
||||||
size_t TotalOut = infstream.total_out;
|
|
||||||
T Ret;
|
|
||||||
Ret.resize(TotalOut);
|
|
||||||
std::fill(Ret.begin(), Ret.end(), 0);
|
|
||||||
std::copy_n(C.begin(), TotalOut, Ret.begin());
|
|
||||||
return Ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetPlatformAgnosticErrorString();
|
std::string GetPlatformAgnosticErrorString();
|
||||||
#define S_DSN SU_RAW
|
#define S_DSN SU_RAW
|
||||||
|
|
||||||
|
class InvalidDataError : std::runtime_error {
|
||||||
|
public:
|
||||||
|
InvalidDataError() : std::runtime_error("Invalid data") {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
#include "Compat.h"
|
||||||
|
|
||||||
static const char* const ANSI_RESET = "\u001b[0m";
|
static const char* const ANSI_RESET = "\u001b[0m";
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ static const char* const ANSI_WHITE_BOLD = "\u001b[37;1m";
|
|||||||
static const char* const ANSI_BOLD = "\u001b[1m";
|
static const char* const ANSI_BOLD = "\u001b[1m";
|
||||||
static const char* const ANSI_UNDERLINE = "\u001b[4m";
|
static const char* const ANSI_UNDERLINE = "\u001b[4m";
|
||||||
|
|
||||||
#ifdef DEBUG
|
#if defined(DEBUG) && !defined(BEAMMP_FREEBSD)
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line,
|
inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line,
|
||||||
[[maybe_unused]] const char* condition_string, [[maybe_unused]] bool result) {
|
[[maybe_unused]] const char* condition_string, [[maybe_unused]] bool result) {
|
||||||
@@ -81,8 +82,8 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch
|
|||||||
beammp_errorf("Assertion failed in '{}:{}': {}.", __func__, _line, #cond); \
|
beammp_errorf("Assertion failed in '{}:{}': {}.", __func__, _line, #cond); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
#define beammp_assert_not_reachable() \
|
#define beammp_assert_not_reachable() \
|
||||||
do { \
|
do { \
|
||||||
beammp_errorf("Assertion failed in '{}:{}': Unreachable code reached. This may result in a crash or undefined state of the program.", __func__, _line); \
|
beammp_errorf("Assertion failed in '{}:{}': Unreachable code reached. This may result in a crash or undefined state of the program.", __func__, _line); \
|
||||||
} while (false)
|
} while (false)
|
||||||
#endif // DEBUG
|
#endif // DEBUG
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace Env {
|
|||||||
enum class Key {
|
enum class Key {
|
||||||
// provider settings
|
// provider settings
|
||||||
PROVIDER_UPDATE_MESSAGE,
|
PROVIDER_UPDATE_MESSAGE,
|
||||||
|
PROVIDER_DISABLE_CONFIG,
|
||||||
|
PROVIDER_PORT_ENV,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<std::string> Get(Key key);
|
std::optional<std::string> Get(Key key);
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "rapidjson/stringbuffer.h"
|
|
||||||
#include "rapidjson/prettywriter.h"
|
|
||||||
#include "rapidjson/document.h"
|
#include "rapidjson/document.h"
|
||||||
|
#include "rapidjson/prettywriter.h"
|
||||||
|
#include "rapidjson/stringbuffer.h"
|
||||||
#include "rapidjson/writer.h"
|
#include "rapidjson/writer.h"
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/thread/synchronized_value.hpp>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace prof {
|
||||||
|
|
||||||
|
using Duration = std::chrono::duration<double, std::milli>;
|
||||||
|
using TimePoint = std::chrono::high_resolution_clock::time_point;
|
||||||
|
|
||||||
|
/// Returns the current time.
|
||||||
|
TimePoint now();
|
||||||
|
|
||||||
|
/// Returns a sub-millisecond resolution duration between start and end.
|
||||||
|
Duration duration(const TimePoint& start, const TimePoint& end);
|
||||||
|
|
||||||
|
struct Stats {
|
||||||
|
double mean;
|
||||||
|
double stdev;
|
||||||
|
double min;
|
||||||
|
double max;
|
||||||
|
size_t n;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Calculates and stores the moving average over K samples of execution time data
|
||||||
|
/// for some single unit of code. Threadsafe.
|
||||||
|
struct UnitExecutionTime {
|
||||||
|
UnitExecutionTime();
|
||||||
|
|
||||||
|
/// Adds a sample to the collection, overriding the oldest sample if needed.
|
||||||
|
void add_sample(const Duration& dur);
|
||||||
|
|
||||||
|
/// Calculates the mean duration over the `measurement_count()` measurements,
|
||||||
|
/// as well as the standard deviation.
|
||||||
|
Stats stats() const;
|
||||||
|
|
||||||
|
/// Returns the number of elements the moving average is calculated over.
|
||||||
|
size_t measurement_count() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::mutex m_mtx {};
|
||||||
|
size_t m_total_calls {};
|
||||||
|
double m_sum {};
|
||||||
|
// sum of measurements squared (for running stdev)
|
||||||
|
double m_measurement_sqr_sum {};
|
||||||
|
double m_min { std::numeric_limits<double>::max() };
|
||||||
|
double m_max { std::numeric_limits<double>::min() };
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Holds profiles for multiple units by name. Threadsafe.
|
||||||
|
struct UnitProfileCollection {
|
||||||
|
/// Adds a sample to the collection, overriding the oldest sample if needed.
|
||||||
|
void add_sample(const std::string& unit, const Duration& duration);
|
||||||
|
|
||||||
|
/// Calculates the mean duration over the `measurement_count()` measurements,
|
||||||
|
/// as well as the standard deviation.
|
||||||
|
Stats stats(const std::string& unit);
|
||||||
|
|
||||||
|
/// Returns the number of elements the moving average is calculated over.
|
||||||
|
size_t measurement_count(const std::string& unit);
|
||||||
|
|
||||||
|
/// Returns the stats for all stored units.
|
||||||
|
std::unordered_map<std::string, Stats> all_stats();
|
||||||
|
|
||||||
|
private:
|
||||||
|
boost::synchronized_value<std::unordered_map<std::string, UnitExecutionTime>> m_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -23,8 +23,8 @@
|
|||||||
* and write locks and read locks are mutually exclusive.
|
* and write locks and read locks are mutually exclusive.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
// Use ReadLock(m) and WriteLock(m) to lock it.
|
// Use ReadLock(m) and WriteLock(m) to lock it.
|
||||||
using RWMutex = std::shared_mutex;
|
using RWMutex = std::shared_mutex;
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// BeamMP, the BeamNG.drive multiplayer mod.
|
||||||
|
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
|
||||||
|
//
|
||||||
|
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "Sync.h"
|
||||||
|
#include <concepts>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <doctest/doctest.h>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
struct ComposedKey {
|
||||||
|
std::string Category;
|
||||||
|
std::string Key;
|
||||||
|
|
||||||
|
bool operator==(const ComposedKey& rhs) const {
|
||||||
|
return (this->Category == rhs.Category && this->Key == rhs.Key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<ComposedKey> : formatter<std::string> {
|
||||||
|
auto format(ComposedKey key, format_context& ctx) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline auto fmt::formatter<ComposedKey>::format(ComposedKey key, fmt::format_context& ctx) const {
|
||||||
|
std::string key_metadata = fmt::format("{}::{}", key.Category, key.Key);
|
||||||
|
return formatter<std::string>::format(key_metadata, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <>
|
||||||
|
class hash<ComposedKey> {
|
||||||
|
public:
|
||||||
|
std::uint64_t operator()(const ComposedKey& key) const {
|
||||||
|
std::hash<std::string> hash_fn;
|
||||||
|
return hash_fn(key.Category + key.Key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
using SettingsTypeVariant = std::variant<std::string, bool, int>;
|
||||||
|
|
||||||
|
Settings();
|
||||||
|
|
||||||
|
enum Key {
|
||||||
|
// Keys that correspond to the keys set in TOML
|
||||||
|
// Keys have their TOML section name as prefix
|
||||||
|
|
||||||
|
// [Misc]
|
||||||
|
Misc_SendErrorsShowMessage,
|
||||||
|
Misc_SendErrors,
|
||||||
|
Misc_ImScaredOfUpdates,
|
||||||
|
Misc_UpdateReminderTime,
|
||||||
|
|
||||||
|
// [General]
|
||||||
|
General_Description,
|
||||||
|
General_Tags,
|
||||||
|
General_MaxPlayers,
|
||||||
|
General_Name,
|
||||||
|
General_Map,
|
||||||
|
General_AuthKey,
|
||||||
|
General_Private,
|
||||||
|
General_Port,
|
||||||
|
General_MaxCars,
|
||||||
|
General_LogChat,
|
||||||
|
General_ResourceFolder,
|
||||||
|
General_Debug,
|
||||||
|
General_AllowGuests
|
||||||
|
};
|
||||||
|
|
||||||
|
Sync<std::unordered_map<Key, SettingsTypeVariant>> SettingsMap;
|
||||||
|
enum SettingsAccessMask {
|
||||||
|
READ_ONLY, // Value can be read from console
|
||||||
|
READ_WRITE, // Value can be read and written to from console
|
||||||
|
NO_ACCESS // Value is inaccessible from console (no read OR write)
|
||||||
|
};
|
||||||
|
|
||||||
|
using SettingsAccessControl = std::pair<
|
||||||
|
Key, // The Key's corresponding enum encoding
|
||||||
|
SettingsAccessMask // Console read/write permissions
|
||||||
|
>;
|
||||||
|
|
||||||
|
Sync<std::unordered_map<ComposedKey, SettingsAccessControl>> InputAccessMapping;
|
||||||
|
std::string getAsString(Key key);
|
||||||
|
|
||||||
|
int getAsInt(Key key);
|
||||||
|
|
||||||
|
bool getAsBool(Key key);
|
||||||
|
|
||||||
|
SettingsTypeVariant get(Key key);
|
||||||
|
|
||||||
|
void set(Key key, const std::string& value);
|
||||||
|
|
||||||
|
template <typename Integer, std::enable_if_t<std::is_same_v<Integer, int>, bool> = true>
|
||||||
|
void set(Key key, Integer value) {
|
||||||
|
auto map = SettingsMap.synchronize();
|
||||||
|
if (!map->contains(key)) {
|
||||||
|
throw std::logic_error { "Undefined setting key accessed in Settings::set(int)" };
|
||||||
|
}
|
||||||
|
if (!std::holds_alternative<int>(map->at(key))) {
|
||||||
|
throw std::logic_error { fmt::format("Wrong value type in Settings::set(int): index {}", map->at(key).index()) };
|
||||||
|
}
|
||||||
|
map->at(key) = value;
|
||||||
|
}
|
||||||
|
template <typename Boolean, std::enable_if_t<std::is_same_v<bool, Boolean>, bool> = true>
|
||||||
|
void set(Key key, Boolean value) {
|
||||||
|
auto map = SettingsMap.synchronize();
|
||||||
|
if (!map->contains(key)) {
|
||||||
|
throw std::logic_error { "Undefined setting key accessed in Settings::set(bool)" };
|
||||||
|
}
|
||||||
|
if (!std::holds_alternative<bool>(map->at(key))) {
|
||||||
|
throw std::logic_error { fmt::format("Wrong value type in Settings::set(bool): index {}", map->at(key).index()) };
|
||||||
|
}
|
||||||
|
map->at(key) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<ComposedKey, SettingsAccessControl> getAccessControlMap() const;
|
||||||
|
SettingsAccessControl getConsoleInputAccessMapping(const ComposedKey& keyName);
|
||||||
|
|
||||||
|
void setConsoleInputAccessMapping(const ComposedKey& keyName, const std::string& value);
|
||||||
|
void setConsoleInputAccessMapping(const ComposedKey& keyName, int value);
|
||||||
|
void setConsoleInputAccessMapping(const ComposedKey& keyName, bool value);
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/thread/synchronized_value.hpp>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
/// This header provides convenience aliases for synchronization primitives.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using Sync = boost::synchronized_value<T, std::recursive_mutex>;
|
||||||
+4
-4
@@ -19,6 +19,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -35,18 +36,17 @@ public:
|
|||||||
[[nodiscard]] bool Failed() const { return mFailed; }
|
[[nodiscard]] bool Failed() const { return mFailed; }
|
||||||
|
|
||||||
void FlushToFile();
|
void FlushToFile();
|
||||||
|
void PrintDebug();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateConfigFile();
|
void CreateConfigFile();
|
||||||
void ParseFromFile(std::string_view name);
|
void ParseFromFile(std::string_view name);
|
||||||
void PrintDebug();
|
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, Settings::Key key);
|
||||||
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, std::string& OutValue);
|
|
||||||
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, bool& OutValue);
|
|
||||||
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, int& OutValue);
|
|
||||||
|
|
||||||
void ParseOldFormat();
|
void ParseOldFormat();
|
||||||
std::string TagsAsPrettyArray() const;
|
std::string TagsAsPrettyArray() const;
|
||||||
bool IsDefault();
|
bool IsDefault();
|
||||||
bool mFailed { false };
|
bool mFailed { false };
|
||||||
|
bool mDisableConfig { false };
|
||||||
std::string mConfigFileName;
|
std::string mConfigFileName;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ private:
|
|||||||
void Command_Status(const std::string& cmd, const std::vector<std::string>& args);
|
void Command_Status(const std::string& cmd, const std::vector<std::string>& args);
|
||||||
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
|
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
|
||||||
void Command_Clear(const std::string&, const std::vector<std::string>& args);
|
void Command_Clear(const std::string&, const std::vector<std::string>& args);
|
||||||
|
void Command_Version(const std::string& cmd, const std::vector<std::string>& args);
|
||||||
|
|
||||||
void Command_Say(const std::string& FullCommand);
|
void Command_Say(const std::string& FullCommand);
|
||||||
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
|
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
|
||||||
@@ -75,6 +76,7 @@ private:
|
|||||||
{ "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } },
|
{ "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } },
|
||||||
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
|
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
|
||||||
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
|
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
|
||||||
|
{ "version", [this](const auto& a, const auto& b) { Command_Version(a, b); } },
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<Commandline> mCommandline { nullptr };
|
std::unique_ptr<Commandline> mCommandline { nullptr };
|
||||||
|
|||||||
+38
-14
@@ -18,9 +18,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Profiling.h"
|
||||||
#include "TNetwork.h"
|
#include "TNetwork.h"
|
||||||
#include "TServer.h"
|
#include "TServer.h"
|
||||||
#include <any>
|
#include <any>
|
||||||
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
#include <lua.hpp>
|
#include <lua.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -36,19 +39,34 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define SOL_ALL_SAFETIES_ON 1
|
#define SOL_ALL_SAFETIES_ON 1
|
||||||
|
#define SOL_USER_C_ASSERT SOL_ON
|
||||||
|
#define SOL_C_ASSERT(...) \
|
||||||
|
beammp_lua_errorf("SOL2 assertion failure: Assertion `{}` failed in {}:{}. This *should* be a fatal error, but BeamMP Server overrides it to not be fatal. This may cause the Lua Engine to crash, or cause other issues.", #__VA_ARGS__, __FILE__, __LINE__)
|
||||||
#include <sol/sol.hpp>
|
#include <sol/sol.hpp>
|
||||||
|
|
||||||
|
struct JsonString {
|
||||||
|
std::string value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// value used to keep nils in a table or array, across serialization boundaries like
|
||||||
|
// JsonEncode, so that the nil stays at the same index and isn't treated like a special
|
||||||
|
// value (e.g. one that can be ignored or discarded).
|
||||||
|
const inline std::string BEAMMP_INTERNAL_NIL = "BEAMMP_SERVER_INTERNAL_NIL_VALUE";
|
||||||
|
|
||||||
using TLuaStateId = std::string;
|
using TLuaStateId = std::string;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
/**
|
/**
|
||||||
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args
|
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args
|
||||||
*/
|
*/
|
||||||
using TLuaArgTypes = std::variant<std::string, int, sol::variadic_args, bool, std::unordered_map<std::string, std::string>>;
|
using TLuaValue = std::variant<std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, float>;
|
||||||
static constexpr size_t TLuaArgTypes_String = 0;
|
enum TLuaType {
|
||||||
static constexpr size_t TLuaArgTypes_Int = 1;
|
String = 0,
|
||||||
static constexpr size_t TLuaArgTypes_VariadicArgs = 2;
|
Int = 1,
|
||||||
static constexpr size_t TLuaArgTypes_Bool = 3;
|
Json = 2,
|
||||||
static constexpr size_t TLuaArgTypes_StringStringMap = 4;
|
Bool = 3,
|
||||||
|
StringStringMap = 4,
|
||||||
|
Float = 5,
|
||||||
|
};
|
||||||
|
|
||||||
class TLuaPlugin;
|
class TLuaPlugin;
|
||||||
|
|
||||||
@@ -96,7 +114,7 @@ public:
|
|||||||
struct QueuedFunction {
|
struct QueuedFunction {
|
||||||
std::string FunctionName;
|
std::string FunctionName;
|
||||||
std::shared_ptr<TLuaResult> Result;
|
std::shared_ptr<TLuaResult> Result;
|
||||||
std::vector<TLuaArgTypes> Args;
|
std::vector<TLuaValue> Args;
|
||||||
std::string EventName; // optional, may be empty
|
std::string EventName; // optional, may be empty
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,7 +167,7 @@ public:
|
|||||||
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
|
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
|
||||||
bool HasState(TLuaStateId StateId);
|
bool HasState(TLuaStateId StateId);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName);
|
||||||
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
|
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
|
||||||
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
|
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
|
||||||
/**
|
/**
|
||||||
@@ -169,12 +187,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::shared_ptr<TLuaResult>> Results;
|
std::vector<std::shared_ptr<TLuaResult>> Results;
|
||||||
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
|
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
|
||||||
|
|
||||||
for (const auto& Event : mLuaEvents.at(EventName)) {
|
for (const auto& Event : mLuaEvents.at(EventName)) {
|
||||||
for (const auto& Function : Event.second) {
|
for (const auto& Function : Event.second) {
|
||||||
if (Event.first != IgnoreId) {
|
if (Event.first != IgnoreId) {
|
||||||
Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments));
|
auto Result = EnqueueFunctionCall(Event.first, Function, Arguments, EventName);
|
||||||
|
Results.push_back(Result);
|
||||||
|
AddResultToCheck(Result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,10 +208,10 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
std::vector<std::shared_ptr<TLuaResult>> Results;
|
std::vector<std::shared_ptr<TLuaResult>> Results;
|
||||||
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
|
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
|
||||||
const auto Handlers = GetEventHandlersForState(EventName, StateId);
|
const auto Handlers = GetEventHandlersForState(EventName, StateId);
|
||||||
for (const auto& Handler : Handlers) {
|
for (const auto& Handler : Handlers) {
|
||||||
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments));
|
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments, EventName));
|
||||||
}
|
}
|
||||||
return Results;
|
return Results;
|
||||||
}
|
}
|
||||||
@@ -225,8 +245,8 @@ private:
|
|||||||
StateThreadData(const StateThreadData&) = delete;
|
StateThreadData(const StateThreadData&) = delete;
|
||||||
virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
|
virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy);
|
||||||
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
|
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
|
||||||
void AddPath(const fs::path& Path); // to be added to path and cpath
|
void AddPath(const fs::path& Path); // to be added to path and cpath
|
||||||
void operator()() override;
|
void operator()() override;
|
||||||
@@ -253,6 +273,9 @@ private:
|
|||||||
sol::table Lua_FS_ListFiles(const std::string& Path);
|
sol::table Lua_FS_ListFiles(const std::string& Path);
|
||||||
sol::table Lua_FS_ListDirectories(const std::string& Path);
|
sol::table Lua_FS_ListDirectories(const std::string& Path);
|
||||||
|
|
||||||
|
prof::UnitProfileCollection mProfile {};
|
||||||
|
std::unordered_map<std::string, prof::TimePoint> mProfileStarts;
|
||||||
|
|
||||||
std::string mName;
|
std::string mName;
|
||||||
TLuaStateId mStateId;
|
TLuaStateId mStateId;
|
||||||
lua_State* mState;
|
lua_State* mState;
|
||||||
@@ -268,6 +291,7 @@ private:
|
|||||||
std::recursive_mutex mPathsMutex;
|
std::recursive_mutex mPathsMutex;
|
||||||
std::mt19937 mMersenneTwister;
|
std::mt19937 mMersenneTwister;
|
||||||
std::uniform_real_distribution<double> mUniformRealDistribution01;
|
std::uniform_real_distribution<double> mUniformRealDistribution01;
|
||||||
|
std::vector<sol::object> JsonStringToArray(JsonString Str);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TimedEvent {
|
struct TimedEvent {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ private:
|
|||||||
TResourceManager& mResourceManager;
|
TResourceManager& mResourceManager;
|
||||||
std::thread mUDPThread;
|
std::thread mUDPThread;
|
||||||
std::thread mTCPThread;
|
std::thread mTCPThread;
|
||||||
|
std::mutex mOpenIDMutex;
|
||||||
|
|
||||||
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
|
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
|
||||||
void HandleDownload(TConnection&& TCPSock);
|
void HandleDownload(TConnection&& TCPSock);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TNetwork;
|
|||||||
class TPPSMonitor : public IThreaded {
|
class TPPSMonitor : public IThreaded {
|
||||||
public:
|
public:
|
||||||
explicit TPPSMonitor(TServer& Server);
|
explicit TPPSMonitor(TServer& Server);
|
||||||
virtual ~TPPSMonitor() {}
|
virtual ~TPPSMonitor() { }
|
||||||
|
|
||||||
void operator()() override;
|
void operator()() override;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "ChronoWrapper.h"
|
||||||
|
#include "Common.h"
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
std::chrono::high_resolution_clock::duration ChronoWrapper::TimeFromStringWithLiteral(const std::string& time_str) {
|
||||||
|
// const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|ms|us|ns|[dhs]))"); //i.e one of: "25ns, 6us, 256ms, 2s, 13min, 69h, 356d" will get matched (only available in newer C++ versions)
|
||||||
|
const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|[dhs]))"); // i.e one of: "2.01s, 13min, 69h, 356.69d" will get matched
|
||||||
|
std::smatch match;
|
||||||
|
float time_value;
|
||||||
|
if (!std::regex_search(time_str, match, time_regex))
|
||||||
|
return std::chrono::nanoseconds(0);
|
||||||
|
time_value = stof(match.str(1));
|
||||||
|
if (match.str(2) == "d") {
|
||||||
|
return std::chrono::seconds((uint64_t)(time_value * 86400)); // 86400 seconds in a day
|
||||||
|
} else if (match.str(2) == "h") {
|
||||||
|
return std::chrono::seconds((uint64_t)(time_value * 3600)); // 3600 seconds in an hour
|
||||||
|
} else if (match.str(2) == "min") {
|
||||||
|
return std::chrono::seconds((uint64_t)(time_value * 60));
|
||||||
|
} else if (match.str(2) == "s") {
|
||||||
|
return std::chrono::seconds((uint64_t)time_value);
|
||||||
|
}
|
||||||
|
return std::chrono::nanoseconds(0);
|
||||||
|
}
|
||||||
@@ -121,6 +121,14 @@ void TClient::SetCarData(int Ident, const std::string& Data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int TClient::GetCarCount() const {
|
int TClient::GetCarCount() const {
|
||||||
|
// mVechileData holds both unicycle and cars which both count towards the maximum car count
|
||||||
|
// spawning a unicycle meant reaching the max, hence being unable to spawn car. this dirty fixes the problem for now.
|
||||||
|
std::unique_lock lock(mVehicleDataMutex);
|
||||||
|
for (auto& v : mVehicleData) {
|
||||||
|
if (v.ID() == mUnicycleID) {
|
||||||
|
return int(mVehicleData.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
return int(mVehicleData.size());
|
return int(mVehicleData.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+74
-10
@@ -22,7 +22,9 @@
|
|||||||
#include "TConsole.h"
|
#include "TConsole.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <chrono>
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
@@ -33,8 +35,6 @@
|
|||||||
#include "CustomAssert.h"
|
#include "CustomAssert.h"
|
||||||
#include "Http.h"
|
#include "Http.h"
|
||||||
|
|
||||||
Application::TSettings Application::Settings = {};
|
|
||||||
|
|
||||||
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
|
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
|
||||||
std::unique_lock Lock(mShutdownHandlersMutex);
|
std::unique_lock Lock(mShutdownHandlersMutex);
|
||||||
if (Handler) {
|
if (Handler) {
|
||||||
@@ -222,7 +222,7 @@ void Application::CheckForUpdates() {
|
|||||||
auto RemoteVersion = Version(VersionStrToInts(Response));
|
auto RemoteVersion = Version(VersionStrToInts(Response));
|
||||||
if (IsOutdated(MyVersion, RemoteVersion)) {
|
if (IsOutdated(MyVersion, RemoteVersion)) {
|
||||||
std::string RealVersionString = std::string("v") + RemoteVersion.AsString();
|
std::string RealVersionString = std::string("v") + RemoteVersion.AsString();
|
||||||
const std::string DefaultUpdateMsg = "NEW VERSION IS OUT! Please update to the new version ({}) of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://wiki.beammp.com/en/home/server-maintenance#updating-the-server";
|
const std::string DefaultUpdateMsg = "NEW VERSION IS OUT! Please update to the new version ({}) of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://docs.beammp.com/server/server-maintenance/#updating-the-server";
|
||||||
auto UpdateMsg = Env::Get(Env::Key::PROVIDER_UPDATE_MESSAGE).value_or(DefaultUpdateMsg);
|
auto UpdateMsg = Env::Get(Env::Key::PROVIDER_UPDATE_MESSAGE).value_or(DefaultUpdateMsg);
|
||||||
UpdateMsg = fmt::vformat(std::string_view(UpdateMsg), fmt::make_format_args(RealVersionString));
|
UpdateMsg = fmt::vformat(std::string_view(UpdateMsg), fmt::make_format_args(RealVersionString));
|
||||||
beammp_warnf("{}{}{}", ANSI_YELLOW_BOLD, UpdateMsg, ANSI_RESET);
|
beammp_warnf("{}{}{}", ANSI_YELLOW_BOLD, UpdateMsg, ANSI_RESET);
|
||||||
@@ -256,7 +256,7 @@ static std::mutex ThreadNameMapMutex {};
|
|||||||
|
|
||||||
std::string ThreadName(bool DebugModeOverride) {
|
std::string ThreadName(bool DebugModeOverride) {
|
||||||
auto Lock = std::unique_lock(ThreadNameMapMutex);
|
auto Lock = std::unique_lock(ThreadNameMapMutex);
|
||||||
if (DebugModeOverride || Application::Settings.DebugModeEnabled) {
|
if (DebugModeOverride || Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
||||||
auto id = std::this_thread::get_id();
|
auto id = std::this_thread::get_id();
|
||||||
if (threadNameMap.find(id) != threadNameMap.end()) {
|
if (threadNameMap.find(id) != threadNameMap.end()) {
|
||||||
// found
|
// found
|
||||||
@@ -268,21 +268,21 @@ std::string ThreadName(bool DebugModeOverride) {
|
|||||||
|
|
||||||
TEST_CASE("ThreadName") {
|
TEST_CASE("ThreadName") {
|
||||||
RegisterThread("MyThread");
|
RegisterThread("MyThread");
|
||||||
auto OrigDebug = Application::Settings.DebugModeEnabled;
|
auto OrigDebug = Application::Settings.getAsBool(Settings::Key::General_Debug);
|
||||||
|
|
||||||
// ThreadName adds a space at the end, legacy but we need it still
|
// ThreadName adds a space at the end, legacy but we need it still
|
||||||
SUBCASE("Debug mode enabled") {
|
SUBCASE("Debug mode enabled") {
|
||||||
Application::Settings.DebugModeEnabled = true;
|
Application::Settings.set(Settings::Key::General_Debug, true);
|
||||||
CHECK(ThreadName(true) == "MyThread ");
|
CHECK(ThreadName(true) == "MyThread ");
|
||||||
CHECK(ThreadName(false) == "MyThread ");
|
CHECK(ThreadName(false) == "MyThread ");
|
||||||
}
|
}
|
||||||
SUBCASE("Debug mode disabled") {
|
SUBCASE("Debug mode disabled") {
|
||||||
Application::Settings.DebugModeEnabled = false;
|
Application::Settings.set(Settings::Key::General_Debug, false);
|
||||||
CHECK(ThreadName(true) == "MyThread ");
|
CHECK(ThreadName(true) == "MyThread ");
|
||||||
CHECK(ThreadName(false) == "");
|
CHECK(ThreadName(false) == "");
|
||||||
}
|
}
|
||||||
// cleanup
|
// cleanup
|
||||||
Application::Settings.DebugModeEnabled = OrigDebug;
|
Application::Settings.set(Settings::Key::General_Debug, OrigDebug);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterThread(const std::string& str) {
|
void RegisterThread(const std::string& str) {
|
||||||
@@ -296,7 +296,7 @@ void RegisterThread(const std::string& str) {
|
|||||||
#elif defined(BEAMMP_FREEBSD)
|
#elif defined(BEAMMP_FREEBSD)
|
||||||
ThreadId = std::to_string(getpid());
|
ThreadId = std::to_string(getpid());
|
||||||
#endif
|
#endif
|
||||||
if (Application::Settings.DebugModeEnabled) {
|
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
||||||
std::ofstream ThreadFile(".Threads.log", std::ios::app);
|
std::ofstream ThreadFile(".Threads.log", std::ios::app);
|
||||||
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
|
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
|
||||||
}
|
}
|
||||||
@@ -329,7 +329,7 @@ TEST_CASE("Version::AsString") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
|
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
|
||||||
if (Application::Settings.LogChat) {
|
if (Application::Settings.getAsBool(Settings::Key::General_LogChat)) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << ThreadName();
|
ss << ThreadName();
|
||||||
ss << "[CHAT] ";
|
ss << "[CHAT] ";
|
||||||
@@ -384,3 +384,67 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri
|
|||||||
out.push_back(str.substr(start, end - start));
|
out.push_back(str.substr(start, end - start));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static constexpr size_t STARTING_MAX_DECOMPRESSION_BUFFER_SIZE = 15 * 1024 * 1024;
|
||||||
|
static constexpr size_t MAX_DECOMPRESSION_BUFFER_SIZE = 30 * 1024 * 1024;
|
||||||
|
|
||||||
|
std::vector<uint8_t> DeComp(std::span<const uint8_t> input) {
|
||||||
|
beammp_debugf("got {} bytes of input data", input.size());
|
||||||
|
|
||||||
|
// start with a decompression buffer of 5x the input size, clamped to a maximum of 15 MB.
|
||||||
|
// this buffer can and will grow, but we don't want to start it too large. A 5x compression ratio
|
||||||
|
// is pretty optimistic.
|
||||||
|
std::vector<uint8_t> output_buffer(std::min<size_t>(input.size() * 5, STARTING_MAX_DECOMPRESSION_BUFFER_SIZE));
|
||||||
|
|
||||||
|
uLongf output_size = output_buffer.size();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int res = uncompress(
|
||||||
|
reinterpret_cast<Bytef*>(output_buffer.data()),
|
||||||
|
&output_size,
|
||||||
|
reinterpret_cast<const Bytef*>(input.data()),
|
||||||
|
static_cast<uLongf>(input.size()));
|
||||||
|
if (res == Z_BUF_ERROR) {
|
||||||
|
// We assume that a reasonable maximum size for decompressed packets exists. We want to avoid
|
||||||
|
// a client effectively "zip bombing" us by sending a lot of small packets which decompress
|
||||||
|
// into huge data.
|
||||||
|
// If this limit were to be an issue, this could be made configurable, however clients have a similar
|
||||||
|
// limit. For that reason, we just reject packets which decompress into too much data.
|
||||||
|
if (output_buffer.size() >= MAX_DECOMPRESSION_BUFFER_SIZE) {
|
||||||
|
throw std::runtime_error(fmt::format("decompressed packet size of {} bytes exceeded", MAX_DECOMPRESSION_BUFFER_SIZE));
|
||||||
|
}
|
||||||
|
// if decompression fails, we double the buffer size (up to the allowed limit) and try again
|
||||||
|
output_buffer.resize(std::max<size_t>(output_buffer.size() * 2, MAX_DECOMPRESSION_BUFFER_SIZE));
|
||||||
|
beammp_warnf("zlib uncompress() failed, trying with a larger buffer size of {}", output_buffer.size());
|
||||||
|
output_size = output_buffer.size();
|
||||||
|
} else if (res != Z_OK) {
|
||||||
|
beammp_error("zlib uncompress() failed: " + std::to_string(res));
|
||||||
|
if (res == Z_DATA_ERROR) {
|
||||||
|
throw InvalidDataError {};
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("zlib uncompress() failed");
|
||||||
|
}
|
||||||
|
} else if (res == Z_OK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output_buffer.resize(output_size);
|
||||||
|
return output_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> Comp(std::span<const uint8_t> input) {
|
||||||
|
auto max_size = compressBound(input.size());
|
||||||
|
std::vector<uint8_t> output(max_size);
|
||||||
|
uLongf output_size = output.size();
|
||||||
|
int res = compress(
|
||||||
|
reinterpret_cast<Bytef*>(output.data()),
|
||||||
|
&output_size,
|
||||||
|
reinterpret_cast<const Bytef*>(input.data()),
|
||||||
|
static_cast<uLongf>(input.size()));
|
||||||
|
if (res != Z_OK) {
|
||||||
|
beammp_error("zlib compress() failed: " + std::to_string(res));
|
||||||
|
throw std::runtime_error("zlib compress() failed");
|
||||||
|
}
|
||||||
|
beammp_debug("zlib compressed " + std::to_string(input.size()) + " B to " + std::to_string(output_size) + " B");
|
||||||
|
output.resize(output_size);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ TEST_CASE("init and reset termios") {
|
|||||||
CHECK_EQ(current.c_lflag, original.c_lflag);
|
CHECK_EQ(current.c_lflag, original.c_lflag);
|
||||||
#ifndef BEAMMP_FREEBSD // The 'c_line' attribute seems to only exist on Linux, so we need to omit it on other platforms
|
#ifndef BEAMMP_FREEBSD // The 'c_line' attribute seems to only exist on Linux, so we need to omit it on other platforms
|
||||||
CHECK_EQ(current.c_line, original.c_line);
|
CHECK_EQ(current.c_line, original.c_line);
|
||||||
#endif
|
#endif
|
||||||
CHECK_EQ(current.c_oflag, original.c_oflag);
|
CHECK_EQ(current.c_oflag, original.c_oflag);
|
||||||
CHECK_EQ(current.c_ospeed, original.c_ospeed);
|
CHECK_EQ(current.c_ospeed, original.c_ospeed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ std::string_view Env::ToString(Env::Key key) {
|
|||||||
case Key::PROVIDER_UPDATE_MESSAGE:
|
case Key::PROVIDER_UPDATE_MESSAGE:
|
||||||
return "BEAMMP_PROVIDER_UPDATE_MESSAGE";
|
return "BEAMMP_PROVIDER_UPDATE_MESSAGE";
|
||||||
break;
|
break;
|
||||||
|
case Key::PROVIDER_DISABLE_CONFIG:
|
||||||
|
return "BEAMMP_PROVIDER_DISABLE_CONFIG";
|
||||||
|
break;
|
||||||
|
case Key::PROVIDER_PORT_ENV:
|
||||||
|
return "BEAMMP_PROVIDER_PORT_ENV";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-17
@@ -28,15 +28,42 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
// TODO: Add sentry error handling back
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
struct Connection {
|
||||||
|
std::string host {};
|
||||||
|
int port {};
|
||||||
|
Connection() = default;
|
||||||
|
Connection(std::string host, int port)
|
||||||
|
: host(host)
|
||||||
|
, port(port) {};
|
||||||
|
};
|
||||||
|
constexpr uint8_t CONNECTION_AMOUNT = 10;
|
||||||
|
static thread_local uint8_t write_index = 0;
|
||||||
|
static thread_local std::array<Connection, CONNECTION_AMOUNT> connections;
|
||||||
|
static thread_local std::array<std::shared_ptr<httplib::SSLClient>, CONNECTION_AMOUNT> clients;
|
||||||
|
|
||||||
|
[[nodiscard]] static std::shared_ptr<httplib::SSLClient> getClient(Connection connectionInfo) {
|
||||||
|
for (uint8_t i = 0; i < CONNECTION_AMOUNT; i++) {
|
||||||
|
if (connectionInfo.host == connections[i].host
|
||||||
|
&& connectionInfo.port == connections[i].port) {
|
||||||
|
beammp_tracef("Old client reconnected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
|
||||||
|
return clients[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t i = write_index;
|
||||||
|
write_index++;
|
||||||
|
write_index %= CONNECTION_AMOUNT;
|
||||||
|
clients[i] = std::make_shared<httplib::SSLClient>(connectionInfo.host, connectionInfo.port);
|
||||||
|
connections[i] = { connectionInfo.host, connectionInfo.port };
|
||||||
|
beammp_tracef("New client connected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
|
||||||
|
return clients[i];
|
||||||
|
}
|
||||||
|
|
||||||
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
|
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
|
||||||
httplib::SSLClient client(host, port);
|
std::shared_ptr<httplib::SSLClient> client = getClient({ host, port });
|
||||||
client.enable_server_certificate_verification(false);
|
client->enable_server_certificate_verification(false);
|
||||||
client.set_address_family(AF_INET);
|
client->set_address_family(AF_INET);
|
||||||
auto res = client.Get(target.c_str());
|
auto res = client->Get(target.c_str());
|
||||||
if (res) {
|
if (res) {
|
||||||
if (status) {
|
if (status) {
|
||||||
*status = res->status;
|
*status = res->status;
|
||||||
@@ -48,12 +75,12 @@ std::string Http::GET(const std::string& host, int port, const std::string& targ
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) {
|
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) {
|
||||||
httplib::SSLClient client(host, port);
|
std::shared_ptr<httplib::SSLClient> client = getClient({ host, port });
|
||||||
client.set_read_timeout(std::chrono::seconds(10));
|
client->set_read_timeout(std::chrono::seconds(10));
|
||||||
beammp_assert(client.is_valid());
|
beammp_assert(client->is_valid());
|
||||||
client.enable_server_certificate_verification(false);
|
client->enable_server_certificate_verification(false);
|
||||||
client.set_address_family(AF_INET);
|
client->set_address_family(AF_INET);
|
||||||
auto res = client.Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
|
auto res = client->Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
|
||||||
if (res) {
|
if (res) {
|
||||||
if (status) {
|
if (status) {
|
||||||
*status = res->status;
|
*status = res->status;
|
||||||
@@ -172,7 +199,6 @@ Http::Server::THttpServerInstance::THttpServerInstance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Http::Server::THttpServerInstance::operator()() try {
|
void Http::Server::THttpServerInstance::operator()() try {
|
||||||
beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort));
|
|
||||||
std::unique_ptr<httplib::Server> HttpLibServerInstance;
|
std::unique_ptr<httplib::Server> HttpLibServerInstance;
|
||||||
HttpLibServerInstance = std::make_unique<httplib::Server>();
|
HttpLibServerInstance = std::make_unique<httplib::Server>();
|
||||||
// todo: make this IP agnostic so people can set their own IP
|
// todo: make this IP agnostic so people can set their own IP
|
||||||
@@ -214,10 +240,6 @@ void Http::Server::THttpServerInstance::operator()() try {
|
|||||||
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
|
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
|
||||||
});
|
});
|
||||||
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
|
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
|
||||||
auto ret = HttpLibServerInstance->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort);
|
|
||||||
if (!ret) {
|
|
||||||
beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it.");
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what()));
|
beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-33
@@ -20,6 +20,7 @@
|
|||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "CustomAssert.h"
|
#include "CustomAssert.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "TLuaEngine.h"
|
#include "TLuaEngine.h"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
@@ -60,7 +61,11 @@ std::string LuaAPI::LuaToString(const sol::object Value, size_t Indent, bool Quo
|
|||||||
}
|
}
|
||||||
case sol::type::number: {
|
case sol::type::number: {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << Value.as<float>();
|
if (Value.is<int>()) {
|
||||||
|
ss << Value.as<int>();
|
||||||
|
} else {
|
||||||
|
ss << Value.as<float>();
|
||||||
|
}
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
case sol::type::lua_nil:
|
case sol::type::lua_nil:
|
||||||
@@ -212,6 +217,7 @@ std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
|
|||||||
auto c = MaybeClient.value().lock();
|
auto c = MaybeClient.value().lock();
|
||||||
if (!c->GetCarData(VID).empty()) {
|
if (!c->GetCarData(VID).empty()) {
|
||||||
std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
|
std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
|
||||||
|
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", PID, VID));
|
||||||
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
|
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
|
||||||
c->DeleteCar(VID);
|
c->DeleteCar(VID);
|
||||||
Result.first = true;
|
Result.first = true;
|
||||||
@@ -226,56 +232,56 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
|
|||||||
switch (ConfigID) {
|
switch (ConfigID) {
|
||||||
case 0: // debug
|
case 0: // debug
|
||||||
if (NewValue.is<bool>()) {
|
if (NewValue.is<bool>()) {
|
||||||
Application::Settings.DebugModeEnabled = NewValue.as<bool>();
|
Application::Settings.set(Settings::Key::General_Debug, NewValue.as<bool>());
|
||||||
beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false"));
|
beammp_info(std::string("Set `Debug` to ") + (Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false"));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected boolean");
|
beammp_lua_error("set invalid argument [2] expected boolean");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1: // private
|
case 1: // private
|
||||||
if (NewValue.is<bool>()) {
|
if (NewValue.is<bool>()) {
|
||||||
Application::Settings.Private = NewValue.as<bool>();
|
Application::Settings.set(Settings::Key::General_Private, NewValue.as<bool>());
|
||||||
beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false"));
|
beammp_info(std::string("Set `Private` to ") + (Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false"));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected boolean");
|
beammp_lua_error("set invalid argument [2] expected boolean");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2: // max cars
|
case 2: // max cars
|
||||||
if (NewValue.is<int>()) {
|
if (NewValue.is<int>()) {
|
||||||
Application::Settings.MaxCars = NewValue.as<int>();
|
Application::Settings.set(Settings::Key::General_MaxCars, NewValue.as<int>());
|
||||||
beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars));
|
beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars)));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected integer");
|
beammp_lua_error("set invalid argument [2] expected integer");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3: // max players
|
case 3: // max players
|
||||||
if (NewValue.is<int>()) {
|
if (NewValue.is<int>()) {
|
||||||
Application::Settings.MaxPlayers = NewValue.as<int>();
|
Application::Settings.set(Settings::Key::General_MaxPlayers, NewValue.as<int>());
|
||||||
beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers));
|
beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected integer");
|
beammp_lua_error("set invalid argument [2] expected integer");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 4: // Map
|
case 4: // Map
|
||||||
if (NewValue.is<std::string>()) {
|
if (NewValue.is<std::string>()) {
|
||||||
Application::Settings.MapName = NewValue.as<std::string>();
|
Application::Settings.set(Settings::Key::General_Map, NewValue.as<std::string>());
|
||||||
beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName);
|
beammp_info(std::string("Set `Map` to ") + Application::Settings.getAsString(Settings::Key::General_Map));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected string");
|
beammp_lua_error("set invalid argument [2] expected string");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 5: // Name
|
case 5: // Name
|
||||||
if (NewValue.is<std::string>()) {
|
if (NewValue.is<std::string>()) {
|
||||||
Application::Settings.ServerName = NewValue.as<std::string>();
|
Application::Settings.set(Settings::Key::General_Name, NewValue.as<std::string>());
|
||||||
beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName);
|
beammp_info(std::string("Set `Name` to ") + Application::Settings.getAsString(Settings::Key::General_Name));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected string");
|
beammp_lua_error("set invalid argument [2] expected string");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 6: // Desc
|
case 6: // Desc
|
||||||
if (NewValue.is<std::string>()) {
|
if (NewValue.is<std::string>()) {
|
||||||
Application::Settings.ServerDesc = NewValue.as<std::string>();
|
Application::Settings.set(Settings::Key::General_Description, NewValue.as<std::string>());
|
||||||
beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc);
|
beammp_info(std::string("Set `Description` to ") + Application::Settings.getAsString(Settings::Key::General_Description));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected string");
|
beammp_lua_error("set invalid argument [2] expected string");
|
||||||
}
|
}
|
||||||
@@ -293,7 +299,7 @@ void LuaAPI::MP::Sleep(size_t Ms) {
|
|||||||
bool LuaAPI::MP::IsPlayerConnected(int ID) {
|
bool LuaAPI::MP::IsPlayerConnected(int ID) {
|
||||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||||
return MaybeClient.value().lock()->IsConnected();
|
return MaybeClient.value().lock()->IsUDPConnected();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -561,7 +567,11 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
|
|||||||
key = left.as<std::string>();
|
key = left.as<std::string>();
|
||||||
break;
|
break;
|
||||||
case sol::type::number:
|
case sol::type::number:
|
||||||
key = std::to_string(left.as<double>());
|
if (left.is<int>()) {
|
||||||
|
key = std::to_string(left.as<int>());
|
||||||
|
} else {
|
||||||
|
key = std::to_string(left.as<double>());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
beammp_assert_not_reachable();
|
beammp_assert_not_reachable();
|
||||||
@@ -589,21 +599,30 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
|
|||||||
case sol::type::string:
|
case sol::type::string:
|
||||||
value = right.as<std::string>();
|
value = right.as<std::string>();
|
||||||
break;
|
break;
|
||||||
case sol::type::number:
|
case sol::type::number: {
|
||||||
value = right.as<double>();
|
if (right.is<int>()) {
|
||||||
|
value = right.as<int>();
|
||||||
|
} else {
|
||||||
|
value = right.as<double>();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case sol::type::function:
|
case sol::type::function:
|
||||||
beammp_lua_warn("unsure what to do with function in JsonEncode, ignoring");
|
beammp_lua_warn("unsure what to do with function in JsonEncode, ignoring");
|
||||||
return;
|
return;
|
||||||
case sol::type::table: {
|
case sol::type::table: {
|
||||||
bool local_is_array = true;
|
if (right.as<sol::table>().empty()) {
|
||||||
for (const auto& pair : right.as<sol::table>()) {
|
value = nlohmann::json::object();
|
||||||
if (pair.first.get_type() != sol::type::number) {
|
} else {
|
||||||
local_is_array = false;
|
bool local_is_array = true;
|
||||||
|
for (const auto& pair : right.as<sol::table>()) {
|
||||||
|
if (pair.first.get_type() != sol::type::number) {
|
||||||
|
local_is_array = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& pair : right.as<sol::table>()) {
|
||||||
|
JsonEncodeRecursive(value, pair.first, pair.second, local_is_array, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (const auto& pair : right.as<sol::table>()) {
|
|
||||||
JsonEncodeRecursive(value, pair.first, pair.second, local_is_array, depth + 1);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -620,14 +639,18 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
|
|||||||
std::string LuaAPI::MP::JsonEncode(const sol::table& object) {
|
std::string LuaAPI::MP::JsonEncode(const sol::table& object) {
|
||||||
nlohmann::json json;
|
nlohmann::json json;
|
||||||
// table
|
// table
|
||||||
bool is_array = true;
|
if (object.as<sol::table>().empty()) {
|
||||||
for (const auto& pair : object.as<sol::table>()) {
|
json = nlohmann::json::object();
|
||||||
if (pair.first.get_type() != sol::type::number) {
|
} else {
|
||||||
is_array = false;
|
bool is_array = true;
|
||||||
|
for (const auto& pair : object.as<sol::table>()) {
|
||||||
|
if (pair.first.get_type() != sol::type::number) {
|
||||||
|
is_array = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& entry : object) {
|
||||||
|
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (const auto& entry : object) {
|
|
||||||
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
|
|
||||||
}
|
}
|
||||||
return json.dump();
|
return json.dump();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include "Profiling.h"
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
prof::Duration prof::duration(const TimePoint& start, const TimePoint& end) {
|
||||||
|
return end - start;
|
||||||
|
}
|
||||||
|
prof::TimePoint prof::now() {
|
||||||
|
return std::chrono::high_resolution_clock::now();
|
||||||
|
}
|
||||||
|
prof::Stats prof::UnitProfileCollection::stats(const std::string& unit) {
|
||||||
|
return m_map->operator[](unit).stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t prof::UnitProfileCollection::measurement_count(const std::string& unit) {
|
||||||
|
return m_map->operator[](unit).measurement_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void prof::UnitProfileCollection::add_sample(const std::string& unit, const Duration& duration) {
|
||||||
|
m_map->operator[](unit).add_sample(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
prof::Stats prof::UnitExecutionTime::stats() const {
|
||||||
|
std::unique_lock lock(m_mtx);
|
||||||
|
Stats result {};
|
||||||
|
// calculate sum
|
||||||
|
result.n = m_total_calls;
|
||||||
|
result.max = m_min;
|
||||||
|
result.min = m_max;
|
||||||
|
// calculate mean: mean = sum_x / n
|
||||||
|
result.mean = m_sum / double(m_total_calls);
|
||||||
|
// calculate stdev: stdev = sqrt((sum_x2 / n) - (mean * mean))
|
||||||
|
result.stdev = std::sqrt((m_measurement_sqr_sum / double(result.n)) - (result.mean * result.mean));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void prof::UnitExecutionTime::add_sample(const Duration& dur) {
|
||||||
|
std::unique_lock lock(m_mtx);
|
||||||
|
m_sum += dur.count();
|
||||||
|
m_measurement_sqr_sum += dur.count() * dur.count();
|
||||||
|
m_min = std::min(dur.count(), m_min);
|
||||||
|
m_max = std::max(dur.count(), m_max);
|
||||||
|
++m_total_calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
prof::UnitExecutionTime::UnitExecutionTime() {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, prof::Stats> prof::UnitProfileCollection::all_stats() {
|
||||||
|
auto map = m_map.synchronize();
|
||||||
|
std::unordered_map<std::string, Stats> result {};
|
||||||
|
for (const auto& [name, time] : *map) {
|
||||||
|
result[name] = time.stats();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
size_t prof::UnitExecutionTime::measurement_count() const {
|
||||||
|
std::unique_lock lock(m_mtx);
|
||||||
|
return m_total_calls;
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
// BeamMP, the BeamNG.drive multiplayer mod.
|
||||||
|
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
|
||||||
|
//
|
||||||
|
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
|
Settings::Settings() {
|
||||||
|
SettingsMap = std::unordered_map<Key, SettingsTypeVariant> {
|
||||||
|
// All entries which contain std::strings must be explicitly constructed, otherwise they become 'bool'
|
||||||
|
{ General_Description, std::string("BeamMP Default Description") },
|
||||||
|
{ General_Tags, std::string("Freeroam") },
|
||||||
|
{ General_MaxPlayers, 8 },
|
||||||
|
{ General_Name, std::string("BeamMP Server") },
|
||||||
|
{ General_Map, std::string("/levels/gridmap_v2/info.json") },
|
||||||
|
{ General_AuthKey, std::string("") },
|
||||||
|
{ General_Private, true },
|
||||||
|
{ General_Port, 30814 },
|
||||||
|
{ General_MaxCars, 1 },
|
||||||
|
{ General_LogChat, true },
|
||||||
|
{ General_ResourceFolder, std::string("Resources") },
|
||||||
|
{ General_Debug, false },
|
||||||
|
{ General_AllowGuests, true },
|
||||||
|
{ Misc_SendErrorsShowMessage, true },
|
||||||
|
{ Misc_SendErrors, true },
|
||||||
|
{ Misc_ImScaredOfUpdates, true },
|
||||||
|
{ Misc_UpdateReminderTime, "30s" }
|
||||||
|
};
|
||||||
|
|
||||||
|
InputAccessMapping = std::unordered_map<ComposedKey, SettingsAccessControl> {
|
||||||
|
{ { "General", "Description" }, { General_Description, READ_WRITE } },
|
||||||
|
{ { "General", "Tags" }, { General_Tags, READ_WRITE } },
|
||||||
|
{ { "General", "MaxPlayers" }, { General_MaxPlayers, READ_WRITE } },
|
||||||
|
{ { "General", "Name" }, { General_Name, READ_WRITE } },
|
||||||
|
{ { "General", "Map" }, { General_Map, READ_WRITE } },
|
||||||
|
{ { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } },
|
||||||
|
{ { "General", "Private" }, { General_Private, READ_ONLY } },
|
||||||
|
{ { "General", "Port" }, { General_Port, READ_ONLY } },
|
||||||
|
{ { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } },
|
||||||
|
{ { "General", "LogChat" }, { General_LogChat, READ_ONLY } },
|
||||||
|
{ { "General", "ResourceFolder" }, { General_ResourceFolder, READ_ONLY } },
|
||||||
|
{ { "General", "Debug" }, { General_Debug, READ_WRITE } },
|
||||||
|
{ { "General", "AllowGuests" }, { General_AllowGuests, READ_WRITE } },
|
||||||
|
{ { "Misc", "SendErrorsShowMessage" }, { Misc_SendErrorsShowMessage, READ_WRITE } },
|
||||||
|
{ { "Misc", "SendErrors" }, { Misc_SendErrors, READ_WRITE } },
|
||||||
|
{ { "Misc", "ImScaredOfUpdates" }, { Misc_ImScaredOfUpdates, READ_WRITE } },
|
||||||
|
{ { "Misc", "UpdateReminderTime" }, { Misc_UpdateReminderTime, READ_WRITE } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Settings::getAsString(Key key) {
|
||||||
|
auto map = SettingsMap.synchronize();
|
||||||
|
if (!map->contains(key)) {
|
||||||
|
throw std::logic_error { "Undefined key accessed in Settings::getAsString" };
|
||||||
|
}
|
||||||
|
return std::get<std::string>(map->at(key));
|
||||||
|
}
|
||||||
|
int Settings::getAsInt(Key key) {
|
||||||
|
auto map = SettingsMap.synchronize();
|
||||||
|
if (!map->contains(key)) {
|
||||||
|
throw std::logic_error { "Undefined key accessed in Settings::getAsInt" };
|
||||||
|
}
|
||||||
|
return std::get<int>(map->at(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Settings::getAsBool(Key key) {
|
||||||
|
auto map = SettingsMap.synchronize();
|
||||||
|
if (!map->contains(key)) {
|
||||||
|
throw std::logic_error { "Undefined key accessed in Settings::getAsBool" };
|
||||||
|
}
|
||||||
|
return std::get<bool>(map->at(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings::SettingsTypeVariant Settings::get(Key key) {
|
||||||
|
auto map = SettingsMap.synchronize();
|
||||||
|
if (!map->contains(key)) {
|
||||||
|
throw std::logic_error { "Undefined setting key accessed in Settings::get" };
|
||||||
|
}
|
||||||
|
return map->at(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::set(Key key, const std::string& value) {
|
||||||
|
auto map = SettingsMap.synchronize();
|
||||||
|
if (!map->contains(key)) {
|
||||||
|
throw std::logic_error { "Undefined setting key accessed in Settings::set(std::string)" };
|
||||||
|
}
|
||||||
|
if (!std::holds_alternative<std::string>(map->at(key))) {
|
||||||
|
throw std::logic_error { fmt::format("Wrong value type in Settings::set(std::string): index {}", map->at(key).index()) };
|
||||||
|
}
|
||||||
|
map->at(key) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<ComposedKey, Settings::SettingsAccessControl> Settings::getAccessControlMap() const {
|
||||||
|
return *InputAccessMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings::SettingsAccessControl Settings::getConsoleInputAccessMapping(const ComposedKey& keyName) {
|
||||||
|
auto acl_map = InputAccessMapping.synchronize();
|
||||||
|
if (!acl_map->contains(keyName)) {
|
||||||
|
throw std::logic_error { "Unknown key name accessed in Settings::getConsoleInputAccessMapping" };
|
||||||
|
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
||||||
|
throw std::logic_error { "Setting '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
||||||
|
}
|
||||||
|
return acl_map->at(keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setConsoleInputAccessMapping(const ComposedKey& keyName, const std::string& value) {
|
||||||
|
auto [map, acl_map] = boost::synchronize(SettingsMap, InputAccessMapping);
|
||||||
|
if (!acl_map->contains(keyName)) {
|
||||||
|
throw std::logic_error { "Unknown key name accessed in Settings::setConsoleInputAccessMapping" };
|
||||||
|
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
||||||
|
throw std::logic_error { "Setting '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
||||||
|
} else if (acl_map->at(keyName).second == SettingsAccessMask::READ_ONLY) {
|
||||||
|
throw std::logic_error { "Setting '" + keyName.Category + "::" + keyName.Key + "' is not writeable from within the runtime!" };
|
||||||
|
}
|
||||||
|
|
||||||
|
Key key = acl_map->at(keyName).first;
|
||||||
|
|
||||||
|
if (!std::holds_alternative<std::string>(map->at(key))) {
|
||||||
|
throw std::logic_error { "Wrong value type in Settings::setConsoleInputAccessMapping: expected std::string" };
|
||||||
|
}
|
||||||
|
|
||||||
|
map->at(key) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setConsoleInputAccessMapping(const ComposedKey& keyName, int value) {
|
||||||
|
auto [map, acl_map] = boost::synchronize(SettingsMap, InputAccessMapping);
|
||||||
|
if (!acl_map->contains(keyName)) {
|
||||||
|
throw std::logic_error { "Unknown key name accessed in Settings::setConsoleInputAccessMapping" };
|
||||||
|
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
||||||
|
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
||||||
|
} else if (acl_map->at(keyName).second == SettingsAccessMask::READ_ONLY) {
|
||||||
|
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not writeable from within the runtime!" };
|
||||||
|
}
|
||||||
|
|
||||||
|
Key key = acl_map->at(keyName).first;
|
||||||
|
|
||||||
|
if (!std::holds_alternative<int>(map->at(key))) {
|
||||||
|
throw std::logic_error { "Wrong value type in Settings::setConsoleInputAccessMapping: expected int" };
|
||||||
|
}
|
||||||
|
|
||||||
|
map->at(key) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setConsoleInputAccessMapping(const ComposedKey& keyName, bool value) {
|
||||||
|
auto [map, acl_map] = boost::synchronize(SettingsMap, InputAccessMapping);
|
||||||
|
if (!acl_map->contains(keyName)) {
|
||||||
|
throw std::logic_error { "Unknown key name accessed in Settings::setConsoleInputAccessMapping" };
|
||||||
|
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
||||||
|
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
||||||
|
} else if (acl_map->at(keyName).second == SettingsAccessMask::READ_ONLY) {
|
||||||
|
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not writeable from within the runtime!" };
|
||||||
|
}
|
||||||
|
|
||||||
|
Key key = acl_map->at(keyName).first;
|
||||||
|
|
||||||
|
if (!std::holds_alternative<bool>(map->at(key))) {
|
||||||
|
throw std::logic_error { "Wrong value type in Settings::setConsoleInputAccessMapping: expected bool" };
|
||||||
|
}
|
||||||
|
|
||||||
|
map->at(key) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("settings get/set") {
|
||||||
|
Settings settings;
|
||||||
|
settings.set(Settings::General_Name, "hello, world");
|
||||||
|
CHECK_EQ(settings.getAsString(Settings::General_Name), "hello, world");
|
||||||
|
settings.set(Settings::General_Name, std::string("hello, world"));
|
||||||
|
CHECK_EQ(settings.getAsString(Settings::General_Name), "hello, world");
|
||||||
|
settings.set(Settings::General_MaxPlayers, 12);
|
||||||
|
CHECK_EQ(settings.getAsInt(Settings::General_MaxPlayers), 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("settings check for exception on wrong input type") {
|
||||||
|
Settings settings;
|
||||||
|
CHECK_THROWS(settings.set(Settings::General_Debug, "hello, world"));
|
||||||
|
CHECK_NOTHROW(settings.set(Settings::General_Debug, false));
|
||||||
|
}
|
||||||
+132
-143
@@ -18,12 +18,16 @@
|
|||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
|
||||||
|
#include "Env.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "TConfig.h"
|
#include "TConfig.h"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <exception>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
// General
|
// General
|
||||||
static constexpr std::string_view StrDebug = "Debug";
|
static constexpr std::string_view StrDebug = "Debug";
|
||||||
@@ -50,12 +54,15 @@ static constexpr std::string_view StrAuthKey = "AuthKey";
|
|||||||
static constexpr std::string_view EnvStrAuthKey = "BEAMMP_AUTH_KEY";
|
static constexpr std::string_view EnvStrAuthKey = "BEAMMP_AUTH_KEY";
|
||||||
static constexpr std::string_view StrLogChat = "LogChat";
|
static constexpr std::string_view StrLogChat = "LogChat";
|
||||||
static constexpr std::string_view EnvStrLogChat = "BEAMMP_LOG_CHAT";
|
static constexpr std::string_view EnvStrLogChat = "BEAMMP_LOG_CHAT";
|
||||||
|
static constexpr std::string_view StrAllowGuests = "AllowGuests";
|
||||||
|
static constexpr std::string_view EnvStrAllowGuests = "BEAMMP_ALLOW_GUESTS";
|
||||||
static constexpr std::string_view StrPassword = "Password";
|
static constexpr std::string_view StrPassword = "Password";
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
static constexpr std::string_view StrSendErrors = "SendErrors";
|
static constexpr std::string_view StrSendErrors = "SendErrors";
|
||||||
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
|
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
|
||||||
static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
|
static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
|
||||||
|
static constexpr std::string_view StrUpdateReminderTime = "UpdateReminderTime";
|
||||||
|
|
||||||
TEST_CASE("TConfig::TConfig") {
|
TEST_CASE("TConfig::TConfig") {
|
||||||
const std::string CfgFile = "beammp_server_testconfig.toml";
|
const std::string CfgFile = "beammp_server_testconfig.toml";
|
||||||
@@ -87,7 +94,9 @@ TEST_CASE("TConfig::TConfig") {
|
|||||||
TConfig::TConfig(const std::string& ConfigFileName)
|
TConfig::TConfig(const std::string& ConfigFileName)
|
||||||
: mConfigFileName(ConfigFileName) {
|
: mConfigFileName(ConfigFileName) {
|
||||||
Application::SetSubsystemStatus("Config", Application::Status::Starting);
|
Application::SetSubsystemStatus("Config", Application::Status::Starting);
|
||||||
if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) {
|
auto DisableConfig = Env::Get(Env::Key::PROVIDER_DISABLE_CONFIG).value_or("false");
|
||||||
|
mDisableConfig = DisableConfig == "true" || DisableConfig == "1";
|
||||||
|
if (!mDisableConfig && (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName))) {
|
||||||
beammp_info("No config file found! Generating one...");
|
beammp_info("No config file found! Generating one...");
|
||||||
CreateConfigFile();
|
CreateConfigFile();
|
||||||
}
|
}
|
||||||
@@ -117,35 +126,39 @@ void SetComment(CommentsT& Comments, const std::string& Comment) {
|
|||||||
void TConfig::FlushToFile() {
|
void TConfig::FlushToFile() {
|
||||||
// auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
|
// auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
|
||||||
auto data = toml::value {};
|
auto data = toml::value {};
|
||||||
data["General"][StrAuthKey.data()] = Application::Settings.Key;
|
data["General"][StrAuthKey.data()] = Application::Settings.getAsString(Settings::Key::General_AuthKey);
|
||||||
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
|
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
|
||||||
data["General"][StrLogChat.data()] = Application::Settings.LogChat;
|
data["General"][StrLogChat.data()] = Application::Settings.getAsBool(Settings::Key::General_LogChat);
|
||||||
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
|
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
|
||||||
data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
|
data["General"][StrDebug.data()] = Application::Settings.getAsBool(Settings::Key::General_Debug);
|
||||||
data["General"][StrPrivate.data()] = Application::Settings.Private;
|
data["General"][StrPrivate.data()] = Application::Settings.getAsBool(Settings::Key::General_Private);
|
||||||
data["General"][StrPort.data()] = Application::Settings.Port;
|
data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests);
|
||||||
data["General"][StrName.data()] = Application::Settings.ServerName;
|
SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests");
|
||||||
|
data["General"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port);
|
||||||
|
data["General"][StrName.data()] = Application::Settings.getAsString(Settings::Key::General_Name);
|
||||||
SetComment(data["General"][StrTags.data()].comments(), " Add custom identifying tags to your server to make it easier to find. Format should be TagA,TagB,TagC. Note the comma seperation.");
|
SetComment(data["General"][StrTags.data()].comments(), " Add custom identifying tags to your server to make it easier to find. Format should be TagA,TagB,TagC. Note the comma seperation.");
|
||||||
data["General"][StrTags.data()] = Application::Settings.ServerTags;
|
data["General"][StrTags.data()] = Application::Settings.getAsString(Settings::Key::General_Tags);
|
||||||
data["General"][StrMaxCars.data()] = Application::Settings.MaxCars;
|
data["General"][StrMaxCars.data()] = Application::Settings.getAsInt(Settings::Key::General_MaxCars);
|
||||||
data["General"][StrMaxPlayers.data()] = Application::Settings.MaxPlayers;
|
data["General"][StrMaxPlayers.data()] = Application::Settings.getAsInt(Settings::Key::General_MaxPlayers);
|
||||||
data["General"][StrMap.data()] = Application::Settings.MapName;
|
data["General"][StrMap.data()] = Application::Settings.getAsString(Settings::Key::General_Map);
|
||||||
data["General"][StrDescription.data()] = Application::Settings.ServerDesc;
|
data["General"][StrDescription.data()] = Application::Settings.getAsString(Settings::Key::General_Description);
|
||||||
data["General"][StrResourceFolder.data()] = Application::Settings.Resource;
|
data["General"][StrResourceFolder.data()] = Application::Settings.getAsString(Settings::Key::General_ResourceFolder);
|
||||||
// data["General"][StrPassword.data()] = Application::Settings.Password;
|
// data["General"][StrPassword.data()] = Application::Settings.Password;
|
||||||
// SetComment(data["General"][StrPassword.data()].comments(), " Sets a password on this server, which restricts people from joining. To join, a player must enter this exact password. Leave empty ("") to disable the password.");
|
// SetComment(data["General"][StrPassword.data()].comments(), " Sets a password on this server, which restricts people from joining. To join, a player must enter this exact password. Leave empty ("") to disable the password.");
|
||||||
// Misc
|
// Misc
|
||||||
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages;
|
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates);
|
||||||
SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless.");
|
SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless.");
|
||||||
data["Misc"][StrSendErrors.data()] = Application::Settings.SendErrors;
|
data["Misc"][StrSendErrors.data()] = Application::Settings.getAsBool(Settings::Key::Misc_SendErrors);
|
||||||
|
data["Misc"][StrUpdateReminderTime.data()] = Application::Settings.getAsString(Settings::Key::Misc_UpdateReminderTime);
|
||||||
|
SetComment(data["Misc"][StrUpdateReminderTime.data()].comments(), " Specifies the time between update reminders. You can use any of \"s, min, h, d\" at the end to specify the units seconds, minutes, hours or days. So 30d or 0.5min will print the update message every 30 days or half a minute.");
|
||||||
SetComment(data["Misc"][StrSendErrors.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
|
SetComment(data["Misc"][StrSendErrors.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
|
||||||
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled;
|
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.getAsBool(Settings::Key::Misc_SendErrorsShowMessage);
|
||||||
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
|
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
|
||||||
std::stringstream Ss;
|
std::stringstream Ss;
|
||||||
Ss << "# This is the BeamMP-Server config file.\n"
|
Ss << "# This is the BeamMP-Server config file.\n"
|
||||||
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\n"
|
"# Help & Documentation: `https://docs.beammp.com/server/server-maintenance/`\n"
|
||||||
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://keymaster.beammp.com/` on the left under \"Keys\"\n"
|
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://keymaster.beammp.com/` on the left under \"Keys\"\n"
|
||||||
<< data;
|
<< toml::format(data);
|
||||||
auto File = std::fopen(mConfigFileName.c_str(), "w+");
|
auto File = std::fopen(mConfigFileName.c_str(), "w+");
|
||||||
if (!File) {
|
if (!File) {
|
||||||
beammp_error("Failed to create/write to config file: " + GetPlatformAgnosticErrorString());
|
beammp_error("Failed to create/write to config file: " + GetPlatformAgnosticErrorString());
|
||||||
@@ -161,172 +174,148 @@ void TConfig::FlushToFile() {
|
|||||||
|
|
||||||
void TConfig::CreateConfigFile() {
|
void TConfig::CreateConfigFile() {
|
||||||
// build from old config Server.cfg
|
// build from old config Server.cfg
|
||||||
|
if (mDisableConfig) {
|
||||||
try {
|
return;
|
||||||
if (fs::exists("Server.cfg")) {
|
|
||||||
// parse it (this is weird and bad and should be removed in some future version)
|
|
||||||
ParseOldFormat();
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
beammp_error("an error occurred and was ignored during config transfer: " + std::string(e.what()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FlushToFile();
|
FlushToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, std::string& OutValue) {
|
// This arcane template magic is needed for using lambdas as overloaded visitors
|
||||||
if (!Env.empty()) {
|
// See https://en.cppreference.com/w/cpp/utility/variant/visit for reference
|
||||||
if (const char* envp = std::getenv(Env.data()); envp != nullptr && std::strcmp(envp, "") != 0) {
|
template <class... Ts>
|
||||||
OutValue = std::string(envp);
|
struct overloaded : Ts... {
|
||||||
return;
|
using Ts::operator()...;
|
||||||
}
|
};
|
||||||
}
|
template <class... Ts>
|
||||||
if (Table[Category.c_str()][Key.data()].is_string()) {
|
overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
OutValue = Table[Category.c_str()][Key.data()].as_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, bool& OutValue) {
|
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, Settings::Key key) {
|
||||||
if (!Env.empty()) {
|
if (!Env.empty()) {
|
||||||
if (const char* envp = std::getenv(Env.data()); envp != nullptr && std::strcmp(envp, "") != 0) {
|
if (const char* envp = std::getenv(Env.data());
|
||||||
auto Str = std::string(envp);
|
envp != nullptr && std::strcmp(envp, "") != 0) {
|
||||||
OutValue = Str == "1" || Str == "true";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Table[Category.c_str()][Key.data()].is_boolean()) {
|
|
||||||
OutValue = Table[Category.c_str()][Key.data()].as_boolean();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, int& OutValue) {
|
std::visit(
|
||||||
if (!Env.empty()) {
|
overloaded {
|
||||||
if (const char* envp = std::getenv(Env.data()); envp != nullptr && std::strcmp(envp, "") != 0) {
|
[&envp, &key](std::string) {
|
||||||
OutValue = int(std::strtol(envp, nullptr, 10));
|
Application::Settings.set(key, std::string(envp));
|
||||||
|
},
|
||||||
|
[&envp, &key](int) {
|
||||||
|
Application::Settings.set(key, int(std::strtol(envp, nullptr, 10)));
|
||||||
|
},
|
||||||
|
[&envp, &key](bool) {
|
||||||
|
auto Str = std::string(envp);
|
||||||
|
Application::Settings.set(key, bool(Str == "1" || Str == "true"));
|
||||||
|
} },
|
||||||
|
|
||||||
|
Application::Settings.get(key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Table[Category.c_str()][Key.data()].is_integer()) {
|
|
||||||
OutValue = int(Table[Category.c_str()][Key.data()].as_integer());
|
std::visit([&Table, &Category, &Key, &key](auto&& arg) {
|
||||||
}
|
using T = std::decay_t<decltype(arg)>;
|
||||||
|
if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
if (Table[Category.c_str()][Key.data()].is_string())
|
||||||
|
Application::Settings.set(key, Table[Category.c_str()][Key.data()].as_string());
|
||||||
|
else
|
||||||
|
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'string'", Category, Key);
|
||||||
|
} else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
if (Table[Category.c_str()][Key.data()].is_integer())
|
||||||
|
Application::Settings.set(key, int(Table[Category.c_str()][Key.data()].as_integer()));
|
||||||
|
else
|
||||||
|
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'integer'", Category, Key);
|
||||||
|
} else if constexpr (std::is_same_v<T, bool>) {
|
||||||
|
if (Table[Category.c_str()][Key.data()].is_boolean())
|
||||||
|
Application::Settings.set(key, Table[Category.c_str()][Key.data()].as_boolean());
|
||||||
|
else
|
||||||
|
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'boolean'", Category, Key);
|
||||||
|
} else {
|
||||||
|
throw std::logic_error { "Invalid type for config value during read attempt" };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Application::Settings.get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TConfig::ParseFromFile(std::string_view name) {
|
void TConfig::ParseFromFile(std::string_view name) {
|
||||||
try {
|
try {
|
||||||
toml::value data = toml::parse<toml::preserve_comments>(name.data());
|
toml::value data {};
|
||||||
|
if (!mDisableConfig) {
|
||||||
|
data = toml::parse(name.data());
|
||||||
|
}
|
||||||
|
|
||||||
// GENERAL
|
// GENERAL
|
||||||
TryReadValue(data, "General", StrDebug, EnvStrDebug, Application::Settings.DebugModeEnabled);
|
|
||||||
TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Application::Settings.Private);
|
// Read into new Settings Singleton
|
||||||
TryReadValue(data, "General", StrPort, EnvStrPort, Application::Settings.Port);
|
TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug);
|
||||||
TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Application::Settings.MaxCars);
|
TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private);
|
||||||
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Application::Settings.MaxPlayers);
|
TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port);
|
||||||
TryReadValue(data, "General", StrMap, EnvStrMap, Application::Settings.MapName);
|
TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars);
|
||||||
TryReadValue(data, "General", StrName, EnvStrName, Application::Settings.ServerName);
|
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers);
|
||||||
TryReadValue(data, "General", StrDescription, EnvStrDescription, Application::Settings.ServerDesc);
|
TryReadValue(data, "General", StrMap, EnvStrMap, Settings::Key::General_Map);
|
||||||
TryReadValue(data, "General", StrTags, EnvStrTags, Application::Settings.ServerTags);
|
TryReadValue(data, "General", StrName, EnvStrName, Settings::Key::General_Name);
|
||||||
TryReadValue(data, "General", StrResourceFolder, EnvStrResourceFolder, Application::Settings.Resource);
|
TryReadValue(data, "General", StrDescription, EnvStrDescription, Settings::Key::General_Description);
|
||||||
TryReadValue(data, "General", StrAuthKey, EnvStrAuthKey, Application::Settings.Key);
|
TryReadValue(data, "General", StrTags, EnvStrTags, Settings::Key::General_Tags);
|
||||||
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Application::Settings.LogChat);
|
TryReadValue(data, "General", StrResourceFolder, EnvStrResourceFolder, Settings::Key::General_ResourceFolder);
|
||||||
TryReadValue(data, "General", StrPassword, "", Application::Settings.Password);
|
TryReadValue(data, "General", StrAuthKey, EnvStrAuthKey, Settings::Key::General_AuthKey);
|
||||||
|
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Settings::Key::General_LogChat);
|
||||||
|
TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Settings::Key::General_AllowGuests);
|
||||||
// Misc
|
// Misc
|
||||||
TryReadValue(data, "Misc", StrSendErrors, "", Application::Settings.SendErrors);
|
TryReadValue(data, "Misc", StrSendErrors, "", Settings::Key::Misc_SendErrors);
|
||||||
TryReadValue(data, "Misc", StrHideUpdateMessages, "", Application::Settings.HideUpdateMessages);
|
TryReadValue(data, "Misc", StrHideUpdateMessages, "", Settings::Key::Misc_ImScaredOfUpdates);
|
||||||
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, "", Application::Settings.SendErrorsMessageEnabled);
|
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, "", Settings::Key::Misc_SendErrorsShowMessage);
|
||||||
|
TryReadValue(data, "Misc", StrUpdateReminderTime, "", Settings::Key::Misc_UpdateReminderTime);
|
||||||
|
|
||||||
} catch (const std::exception& err) {
|
} catch (const std::exception& err) {
|
||||||
beammp_error("Error parsing config file value: " + std::string(err.what()));
|
beammp_error("Error parsing config file value: " + std::string(err.what()));
|
||||||
mFailed = true;
|
mFailed = true;
|
||||||
Application::SetSubsystemStatus("Config", Application::Status::Bad);
|
Application::SetSubsystemStatus("Config", Application::Status::Bad);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PrintDebug();
|
|
||||||
|
|
||||||
// Update in any case
|
// Update in any case
|
||||||
FlushToFile();
|
if (!mDisableConfig) {
|
||||||
|
FlushToFile();
|
||||||
|
}
|
||||||
// all good so far, let's check if there's a key
|
// all good so far, let's check if there's a key
|
||||||
if (Application::Settings.Key.empty()) {
|
if (Application::Settings.getAsString(Settings::Key::General_AuthKey).empty()) {
|
||||||
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
|
if (mDisableConfig) {
|
||||||
|
beammp_error("No AuthKey specified in the environment.");
|
||||||
|
} else {
|
||||||
|
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
|
||||||
|
}
|
||||||
Application::SetSubsystemStatus("Config", Application::Status::Bad);
|
Application::SetSubsystemStatus("Config", Application::Status::Bad);
|
||||||
mFailed = true;
|
mFailed = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Application::SetSubsystemStatus("Config", Application::Status::Good);
|
Application::SetSubsystemStatus("Config", Application::Status::Good);
|
||||||
if (Application::Settings.Key.size() != 36) {
|
if (Application::Settings.getAsString(Settings::Key::General_AuthKey).size() != 36) {
|
||||||
beammp_warn("AuthKey specified is the wrong length and likely isn't valid.");
|
beammp_warn("AuthKey specified is the wrong length and likely isn't valid.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TConfig::PrintDebug() {
|
void TConfig::PrintDebug() {
|
||||||
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false"));
|
if (mDisableConfig) {
|
||||||
beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.Private ? "true" : "false"));
|
beammp_debug("Provider turned off the generation and parsing of the ServerConfig.toml");
|
||||||
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port));
|
}
|
||||||
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.MaxCars));
|
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false"));
|
||||||
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.MaxPlayers));
|
beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false"));
|
||||||
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\"");
|
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)));
|
||||||
beammp_debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\"");
|
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars)));
|
||||||
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
|
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
|
||||||
|
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Map) + "\"");
|
||||||
|
beammp_debug(std::string(StrName) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Name) + "\"");
|
||||||
|
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Description) + "\"");
|
||||||
beammp_debug(std::string(StrTags) + ": " + TagsAsPrettyArray());
|
beammp_debug(std::string(StrTags) + ": " + TagsAsPrettyArray());
|
||||||
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.LogChat ? "true" : "false") + "\"");
|
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.getAsBool(Settings::Key::General_LogChat) ? "true" : "false") + "\"");
|
||||||
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\"");
|
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "\"");
|
||||||
|
beammp_debug(std::string(StrAllowGuests) + ": \"" + (Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false") + "\"");
|
||||||
// special!
|
// special!
|
||||||
beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + "");
|
beammp_debug("Key Length: " + std::to_string(Application::Settings.getAsString(Settings::Key::General_AuthKey).length()) + "");
|
||||||
beammp_debug("Password Protected: " + std::string(Application::Settings.Password.empty() ? "false" : "true"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TConfig::ParseOldFormat() {
|
|
||||||
std::ifstream File("Server.cfg");
|
|
||||||
// read all, strip comments
|
|
||||||
std::string Content;
|
|
||||||
for (;;) {
|
|
||||||
std::string Line;
|
|
||||||
std::getline(File, Line);
|
|
||||||
if (!Line.empty() && Line.at(0) != '#') {
|
|
||||||
Line = Line.substr(0, Line.find_first_of('#'));
|
|
||||||
Content += Line + "\n";
|
|
||||||
}
|
|
||||||
if (!File.good()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::stringstream Str(Content);
|
|
||||||
std::string Key, Ignore, Value;
|
|
||||||
for (;;) {
|
|
||||||
Str >> Key >> std::ws >> Ignore >> std::ws;
|
|
||||||
std::getline(Str, Value);
|
|
||||||
if (Str.eof()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
std::stringstream ValueStream(Value);
|
|
||||||
ValueStream >> std::ws; // strip leading whitespace if any
|
|
||||||
Value = ValueStream.str();
|
|
||||||
if (Key == "Debug") {
|
|
||||||
Application::Settings.DebugModeEnabled = Value.find("true") != std::string::npos;
|
|
||||||
} else if (Key == "Private") {
|
|
||||||
Application::Settings.Private = Value.find("true") != std::string::npos;
|
|
||||||
} else if (Key == "Port") {
|
|
||||||
ValueStream >> Application::Settings.Port;
|
|
||||||
} else if (Key == "Cars") {
|
|
||||||
ValueStream >> Application::Settings.MaxCars;
|
|
||||||
} else if (Key == "MaxPlayers") {
|
|
||||||
ValueStream >> Application::Settings.MaxPlayers;
|
|
||||||
} else if (Key == "Map") {
|
|
||||||
Application::Settings.MapName = Value.substr(1, Value.size() - 3);
|
|
||||||
} else if (Key == "Name") {
|
|
||||||
Application::Settings.ServerName = Value.substr(1, Value.size() - 3);
|
|
||||||
} else if (Key == "Desc") {
|
|
||||||
Application::Settings.ServerDesc = Value.substr(1, Value.size() - 3);
|
|
||||||
} else if (Key == "use") {
|
|
||||||
Application::Settings.Resource = Value.substr(1, Value.size() - 3);
|
|
||||||
} else if (Key == "AuthKey") {
|
|
||||||
Application::Settings.Key = Value.substr(1, Value.size() - 3);
|
|
||||||
} else {
|
|
||||||
beammp_warn("unknown key in old auth file (ignored): " + Key);
|
|
||||||
}
|
|
||||||
Str >> std::ws;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string TConfig::TagsAsPrettyArray() const {
|
std::string TConfig::TagsAsPrettyArray() const {
|
||||||
std::vector<std::string> TagsArray = {};
|
std::vector<std::string> TagsArray = {};
|
||||||
SplitString(Application::Settings.ServerTags, ',', TagsArray);
|
SplitString(Application::Settings.getAsString(Settings::General_Tags), ',', TagsArray);
|
||||||
std::string Pretty = {};
|
std::string Pretty = {};
|
||||||
for (size_t i = 0; i < TagsArray.size() - 1; ++i) {
|
for (size_t i = 0; i < TagsArray.size() - 1; ++i) {
|
||||||
Pretty += '\"' + TagsArray[i] + "\", ";
|
Pretty += '\"' + TagsArray[i] + "\", ";
|
||||||
|
|||||||
+170
-40
@@ -26,8 +26,12 @@
|
|||||||
#include "TLuaEngine.h"
|
#include "TLuaEngine.h"
|
||||||
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <lua.hpp>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <openssl/opensslv.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
static inline bool StringStartsWith(const std::string& What, const std::string& StartsWith) {
|
static inline bool StringStartsWith(const std::string& What, const std::string& StartsWith) {
|
||||||
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
|
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
|
||||||
@@ -76,7 +80,7 @@ static std::string GetDate() {
|
|||||||
auto local_tm = std::localtime(&tt);
|
auto local_tm = std::localtime(&tt);
|
||||||
char buf[30];
|
char buf[30];
|
||||||
std::string date;
|
std::string date;
|
||||||
if (Application::Settings.DebugModeEnabled) {
|
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
||||||
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T.", local_tm);
|
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T.", local_tm);
|
||||||
date += buf;
|
date += buf;
|
||||||
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
|
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
|
||||||
@@ -104,41 +108,6 @@ void TConsole::BackupOldLog() {
|
|||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_warn(e.what());
|
beammp_warn(e.what());
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
int err = 0;
|
|
||||||
zip* z = zip_open("ServerLogs.zip", ZIP_CREATE, &err);
|
|
||||||
if (!z) {
|
|
||||||
std::cerr << GetPlatformAgnosticErrorString() << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FILE* File = std::fopen(Path.string().c_str(), "r");
|
|
||||||
if (!File) {
|
|
||||||
std::cerr << GetPlatformAgnosticErrorString() << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::vector<uint8_t> Buffer;
|
|
||||||
Buffer.resize(fs::file_size(Path));
|
|
||||||
std::fread(Buffer.data(), 1, Buffer.size(), File);
|
|
||||||
std::fclose(File);
|
|
||||||
|
|
||||||
auto s = zip_source_buffer(z, Buffer.data(), Buffer.size(), 0);
|
|
||||||
|
|
||||||
auto TimePoint = fs::last_write_time(Path);
|
|
||||||
auto Secs = TimePoint.time_since_epoch().count();
|
|
||||||
auto MyTimeT = std::time(&Secs);
|
|
||||||
|
|
||||||
std::string NewName = Path.stem().string();
|
|
||||||
NewName += "_";
|
|
||||||
std::string Time;
|
|
||||||
Time.resize(32);
|
|
||||||
size_t n = strftime(Time.data(), Time.size(), "%F_%H.%M.%S", localtime(&MyTimeT));
|
|
||||||
Time.resize(n);
|
|
||||||
NewName += Time;
|
|
||||||
NewName += ".log";
|
|
||||||
|
|
||||||
zip_file_add(z, NewName.c_str(), s, 0);
|
|
||||||
zip_close(z);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +216,8 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
|
|||||||
lua [state id] switches to lua, optionally into a specific state id's lua
|
lua [state id] switches to lua, optionally into a specific state id's lua
|
||||||
settings [command] sets or gets settings for the server, run `settings help` for more info
|
settings [command] sets or gets settings for the server, run `settings help` for more info
|
||||||
status how the server is doing and what it's up to
|
status how the server is doing and what it's up to
|
||||||
clear clears the console window)";
|
clear clears the console window
|
||||||
|
version displays the server version)";
|
||||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +237,32 @@ void TConsole::Command_Clear(const std::string&, const std::vector<std::string>&
|
|||||||
mCommandline->write("\x1b[;H\x1b[2J");
|
mCommandline->write("\x1b[;H\x1b[2J");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TConsole::Command_Version(const std::string& cmd, const std::vector<std::string>& args) {
|
||||||
|
if (!EnsureArgsCount(args, 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string platform;
|
||||||
|
#if defined(BEAMMP_WINDOWS)
|
||||||
|
platform = "Windows";
|
||||||
|
#elif defined(BEAMMP_LINUX)
|
||||||
|
platform = "Linux";
|
||||||
|
#elif defined(BEAMMP_FREEBSD)
|
||||||
|
platform = "FreeBSD";
|
||||||
|
#elif defined(BEAMMP_APPLE)
|
||||||
|
platform = "Apple";
|
||||||
|
#else
|
||||||
|
platform = "Unknown";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Application::Console().WriteRaw("Platform: " + platform);
|
||||||
|
Application::Console().WriteRaw("Server: v" + Application::ServerVersionString());
|
||||||
|
std::string lua_version = fmt::format("Lua: v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
|
||||||
|
Application::Console().WriteRaw(lua_version);
|
||||||
|
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
|
||||||
|
Application::Console().WriteRaw(openssl_version);
|
||||||
|
}
|
||||||
|
|
||||||
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
|
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
|
||||||
if (!EnsureArgsCount(args, 1, size_t(-1))) {
|
if (!EnsureArgsCount(args, 1, size_t(-1))) {
|
||||||
return;
|
return;
|
||||||
@@ -357,8 +353,142 @@ std::tuple<std::string, std::vector<std::string>> TConsole::ParseCommand(const s
|
|||||||
return { Command, Args };
|
return { Command, Args };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class... Ts>
|
||||||
|
struct overloaded : Ts... {
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
template <class... Ts>
|
||||||
|
overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
void TConsole::Command_Settings(const std::string&, const std::vector<std::string>& args) {
|
void TConsole::Command_Settings(const std::string&, const std::vector<std::string>& args) {
|
||||||
if (!EnsureArgsCount(args, 1, 2)) {
|
|
||||||
|
static constexpr const char* sHelpString = R"(
|
||||||
|
Settings:
|
||||||
|
settings help displays this help
|
||||||
|
settings list lists all settings
|
||||||
|
settings get <category> <setting> prints current value of specified setting
|
||||||
|
settings set <category> <setting> <value> sets specified setting to value
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (args.size() == 0) {
|
||||||
|
beammp_errorf("No arguments specified for command 'settings'!");
|
||||||
|
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.front() == "help") {
|
||||||
|
|
||||||
|
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||||
|
return;
|
||||||
|
} else if (args.front() == "get") {
|
||||||
|
if (args.size() < 3) {
|
||||||
|
beammp_errorf("'settings get' needs at least two arguments!");
|
||||||
|
|
||||||
|
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Settings::SettingsAccessControl acl = Application::Settings.getConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) });
|
||||||
|
Settings::SettingsTypeVariant keyType = Application::Settings.get(acl.first);
|
||||||
|
|
||||||
|
std::visit(
|
||||||
|
overloaded {
|
||||||
|
[&args](std::string keyValue) {
|
||||||
|
Application::Console().WriteRaw(fmt::format("'{}::{}' = {}", args.at(1), args.at(2), keyValue));
|
||||||
|
},
|
||||||
|
[&args](int keyValue) {
|
||||||
|
Application::Console().WriteRaw(fmt::format("'{}::{}' = {}", args.at(1), args.at(2), keyValue));
|
||||||
|
},
|
||||||
|
[&args](bool keyValue) {
|
||||||
|
Application::Console().WriteRaw(fmt::format("'{}::{}' = {}", args.at(1), args.at(2), keyValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
keyType);
|
||||||
|
|
||||||
|
} catch (std::logic_error& e) {
|
||||||
|
beammp_errorf("Error when getting key: {}", e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (args.front() == "set") {
|
||||||
|
if (args.size() <= 3) {
|
||||||
|
beammp_errorf("'settings set' needs at least three arguments!");
|
||||||
|
|
||||||
|
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Settings::SettingsAccessControl acl = Application::Settings.getConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) });
|
||||||
|
Settings::SettingsTypeVariant keyType = Application::Settings.get(acl.first);
|
||||||
|
|
||||||
|
std::visit(
|
||||||
|
overloaded {
|
||||||
|
[&args](std::string keyValue) {
|
||||||
|
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, std::string(args.at(3)));
|
||||||
|
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), std::string(args.at(3))));
|
||||||
|
},
|
||||||
|
[&args](int keyValue) {
|
||||||
|
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, std::stoi(args.at(3)));
|
||||||
|
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), std::stoi(args.at(3))));
|
||||||
|
},
|
||||||
|
[&args](bool keyValue) {
|
||||||
|
if (args.at(3) == "true") {
|
||||||
|
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, true);
|
||||||
|
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), "true"));
|
||||||
|
} else if (args.at(3) == "false") {
|
||||||
|
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, false);
|
||||||
|
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), "false"));
|
||||||
|
} else {
|
||||||
|
beammp_errorf("Error when setting key: {}::{} : Unknown literal, use either 'true', or 'false' to set boolean values.", args.at(1), args.at(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
keyType);
|
||||||
|
|
||||||
|
} catch (std::logic_error& e) {
|
||||||
|
beammp_errorf("Exception when setting settings key via console: {}", e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (args.front() == "list") {
|
||||||
|
for (const auto& [composedKey, keyACL] : Application::Settings.getAccessControlMap()) {
|
||||||
|
// even though we have the value, we want to ignore it in order to make use of access
|
||||||
|
// control checks
|
||||||
|
|
||||||
|
if (keyACL.second != Settings::SettingsAccessMask::NO_ACCESS) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Settings::SettingsAccessControl acl = Application::Settings.getConsoleInputAccessMapping(composedKey);
|
||||||
|
Settings::SettingsTypeVariant keyType = Application::Settings.get(acl.first);
|
||||||
|
|
||||||
|
std::visit(
|
||||||
|
overloaded {
|
||||||
|
[&composedKey](std::string keyValue) {
|
||||||
|
Application::Console().WriteRaw(fmt::format("{} = {}", composedKey, keyValue));
|
||||||
|
},
|
||||||
|
[&composedKey](int keyValue) {
|
||||||
|
Application::Console().WriteRaw(fmt::format("{} = {}", composedKey, keyValue));
|
||||||
|
},
|
||||||
|
[&composedKey](bool keyValue) {
|
||||||
|
Application::Console().WriteRaw(fmt::format("{} = {}", composedKey, keyValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
keyType);
|
||||||
|
} catch (std::logic_error& e) {
|
||||||
|
beammp_errorf("Error when getting key: {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
beammp_errorf("Unknown argument for command 'settings': {}", args.front());
|
||||||
|
|
||||||
|
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,7 +497,7 @@ void TConsole::Command_Say(const std::string& FullCmd) {
|
|||||||
if (FullCmd.size() > 3) {
|
if (FullCmd.size() > 3) {
|
||||||
auto Message = FullCmd.substr(4);
|
auto Message = FullCmd.substr(4);
|
||||||
LuaAPI::MP::SendChatMessage(-1, Message);
|
LuaAPI::MP::SendChatMessage(-1, Message);
|
||||||
if (!Application::Settings.LogChat) {
|
if (!Application::Settings.getAsBool(Settings::Key::General_LogChat)) {
|
||||||
Application::Console().WriteRaw("Chat message sent!");
|
Application::Console().WriteRaw("Chat message sent!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,7 +543,7 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
|
|||||||
if (!Client.expired()) {
|
if (!Client.expired()) {
|
||||||
auto Locked = Client.lock();
|
auto Locked = Client.lock();
|
||||||
CarCount += Locked->GetCarCount();
|
CarCount += Locked->GetCarCount();
|
||||||
ConnectedCount += Locked->IsConnected() ? 1 : 0;
|
ConnectedCount += Locked->IsUDPConnected() ? 1 : 0;
|
||||||
GuestCount += Locked->IsGuest() ? 1 : 0;
|
GuestCount += Locked->IsGuest() ? 1 : 0;
|
||||||
SyncedCount += Locked->IsSynced() ? 1 : 0;
|
SyncedCount += Locked->IsSynced() ? 1 : 0;
|
||||||
SyncingCount += Locked->IsSyncing() ? 1 : 0;
|
SyncingCount += Locked->IsSyncing() ? 1 : 0;
|
||||||
|
|||||||
+21
-20
@@ -18,9 +18,10 @@
|
|||||||
|
|
||||||
#include "THeartbeatThread.h"
|
#include "THeartbeatThread.h"
|
||||||
|
|
||||||
|
#include "ChronoWrapper.h"
|
||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "Http.h"
|
#include "Http.h"
|
||||||
//#include "SocketIO.h"
|
// #include "SocketIO.h"
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/rapidjson.h>
|
#include <rapidjson/rapidjson.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -36,15 +37,17 @@ void THeartbeatThread::operator()() {
|
|||||||
static std::string Last;
|
static std::string Last;
|
||||||
|
|
||||||
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
|
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
|
||||||
|
static std::chrono::high_resolution_clock::time_point LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
|
||||||
bool isAuth = false;
|
bool isAuth = false;
|
||||||
size_t UpdateReminderCounter = 0;
|
std::chrono::high_resolution_clock::duration UpdateReminderTimePassed;
|
||||||
while (!Application::IsShuttingDown()) {
|
while (!Application::IsShuttingDown()) {
|
||||||
++UpdateReminderCounter;
|
auto UpdateReminderTimeout = ChronoWrapper::TimeFromStringWithLiteral(Application::Settings.getAsString(Settings::Key::Misc_UpdateReminderTime));
|
||||||
Body = GenerateCall();
|
Body = GenerateCall();
|
||||||
// a hot-change occurs when a setting has changed, to update the backend of that change.
|
// a hot-change occurs when a setting has changed, to update the backend of that change.
|
||||||
auto Now = std::chrono::high_resolution_clock::now();
|
auto Now = std::chrono::high_resolution_clock::now();
|
||||||
bool Unchanged = Last == Body;
|
bool Unchanged = Last == Body;
|
||||||
auto TimePassed = (Now - LastNormalUpdateTime);
|
auto TimePassed = (Now - LastNormalUpdateTime);
|
||||||
|
UpdateReminderTimePassed = (Now - LastUpdateReminderTime);
|
||||||
auto Threshold = Unchanged ? 30 : 5;
|
auto Threshold = Unchanged ? 30 : 5;
|
||||||
if (TimePassed < std::chrono::seconds(Threshold)) {
|
if (TimePassed < std::chrono::seconds(Threshold)) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
@@ -54,9 +57,6 @@ void THeartbeatThread::operator()() {
|
|||||||
|
|
||||||
Last = Body;
|
Last = Body;
|
||||||
LastNormalUpdateTime = Now;
|
LastNormalUpdateTime = Now;
|
||||||
if (!Application::Settings.CustomIP.empty()) {
|
|
||||||
Body += "&ip=" + Application::Settings.CustomIP;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Target = "/heartbeat";
|
auto Target = "/heartbeat";
|
||||||
unsigned int ResponseCode = 0;
|
unsigned int ResponseCode = 0;
|
||||||
@@ -67,7 +67,7 @@ void THeartbeatThread::operator()() {
|
|||||||
T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
|
T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
|
||||||
Doc.Parse(T.data(), T.size());
|
Doc.Parse(T.data(), T.size());
|
||||||
if (Doc.HasParseError() || !Doc.IsObject()) {
|
if (Doc.HasParseError() || !Doc.IsObject()) {
|
||||||
if (!Application::Settings.Private) {
|
if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
||||||
beammp_trace("Backend response failed to parse as valid json");
|
beammp_trace("Backend response failed to parse as valid json");
|
||||||
beammp_trace("Response was: `" + T + "`");
|
beammp_trace("Response was: `" + T + "`");
|
||||||
}
|
}
|
||||||
@@ -107,12 +107,12 @@ void THeartbeatThread::operator()() {
|
|||||||
beammp_error("Missing/invalid json members in backend response");
|
beammp_error("Missing/invalid json members in backend response");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!Application::Settings.Private) {
|
if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
||||||
beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work.");
|
beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Ok && !isAuth && !Application::Settings.Private) {
|
if (Ok && !isAuth && !Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
||||||
if (Status == "2000") {
|
if (Status == "2000") {
|
||||||
beammp_info(("Authenticated! " + Message));
|
beammp_info(("Authenticated! " + Message));
|
||||||
isAuth = true;
|
isAuth = true;
|
||||||
@@ -126,10 +126,11 @@ void THeartbeatThread::operator()() {
|
|||||||
beammp_error("Backend REFUSED the auth key. Reason: " + Message);
|
beammp_error("Backend REFUSED the auth key. Reason: " + Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isAuth || Application::Settings.Private) {
|
if (isAuth || Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
||||||
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
|
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
|
||||||
}
|
}
|
||||||
if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) {
|
if (!Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates) && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) {
|
||||||
|
LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
|
||||||
Application::CheckForUpdates();
|
Application::CheckForUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,22 +139,22 @@ void THeartbeatThread::operator()() {
|
|||||||
std::string THeartbeatThread::GenerateCall() {
|
std::string THeartbeatThread::GenerateCall() {
|
||||||
std::stringstream Ret;
|
std::stringstream Ret;
|
||||||
|
|
||||||
Ret << "uuid=" << Application::Settings.Key
|
Ret << "uuid=" << Application::Settings.getAsString(Settings::Key::General_AuthKey)
|
||||||
<< "&players=" << mServer.ClientCount()
|
<< "&players=" << mServer.ClientCount()
|
||||||
<< "&maxplayers=" << Application::Settings.MaxPlayers
|
<< "&maxplayers=" << Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)
|
||||||
<< "&port=" << Application::Settings.Port
|
<< "&port=" << Application::Settings.getAsInt(Settings::Key::General_Port)
|
||||||
<< "&map=" << Application::Settings.MapName
|
<< "&map=" << Application::Settings.getAsString(Settings::Key::General_Map)
|
||||||
<< "&private=" << (Application::Settings.Private ? "true" : "false")
|
<< "&private=" << (Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false")
|
||||||
<< "&version=" << Application::ServerVersionString()
|
<< "&version=" << Application::ServerVersionString()
|
||||||
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
|
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
|
||||||
<< "&name=" << Application::Settings.ServerName
|
<< "&name=" << Application::Settings.getAsString(Settings::Key::General_Name)
|
||||||
<< "&tags=" << Application::Settings.ServerTags
|
<< "&tags=" << Application::Settings.getAsString(Settings::Key::General_Tags)
|
||||||
|
<< "&guests=" << (Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false")
|
||||||
<< "&modlist=" << mResourceManager.TrimmedList()
|
<< "&modlist=" << mResourceManager.TrimmedList()
|
||||||
<< "&modstotalsize=" << mResourceManager.MaxModSize()
|
<< "&modstotalsize=" << mResourceManager.MaxModSize()
|
||||||
<< "&modstotal=" << mResourceManager.ModsLoaded()
|
<< "&modstotal=" << mResourceManager.ModsLoaded()
|
||||||
<< "&playerslist=" << GetPlayers()
|
<< "&playerslist=" << GetPlayers()
|
||||||
<< "&desc=" << Application::Settings.ServerDesc
|
<< "&desc=" << Application::Settings.getAsString(Settings::Key::General_Description);
|
||||||
<< "&pass=" << (Application::Settings.Password.empty() ? "false" : "true");
|
|
||||||
return Ret.str();
|
return Ret.str();
|
||||||
}
|
}
|
||||||
THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
|
THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
|
||||||
|
|||||||
+202
-37
@@ -18,27 +18,46 @@
|
|||||||
|
|
||||||
#include "TLuaEngine.h"
|
#include "TLuaEngine.h"
|
||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
|
#include "Common.h"
|
||||||
#include "CustomAssert.h"
|
#include "CustomAssert.h"
|
||||||
#include "Http.h"
|
#include "Http.h"
|
||||||
#include "LuaAPI.h"
|
#include "LuaAPI.h"
|
||||||
|
#include "Profiling.h"
|
||||||
#include "TLuaPlugin.h"
|
#include "TLuaPlugin.h"
|
||||||
#include "sol/object.hpp"
|
#include "sol/object.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <fmt/core.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <optional>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <sol/stack_core.hpp>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
TLuaEngine* LuaAPI::MP::Engine;
|
TLuaEngine* LuaAPI::MP::Engine;
|
||||||
|
|
||||||
|
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn);
|
||||||
|
|
||||||
|
static std::optional<sol::function> GetLuaHandler(sol::state_view StateView, const std::string Handler, const std::string EventName);
|
||||||
|
|
||||||
|
static std::optional<sol::function> GetLuaHandler(sol::state_view StateView, const std::string Handler, const std::string EventName) {
|
||||||
|
auto Res = StateView.safe_script("return " + Handler, sol::script_pass_on_error);
|
||||||
|
if (!Res.valid()) {
|
||||||
|
beammp_errorf("invalid handler for event \"{}\". handler: \"{}\"", EventName, Handler);
|
||||||
|
} else if (Res.get_type() == sol::type::function) {
|
||||||
|
return Res.get<sol::function>();
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
TLuaEngine::TLuaEngine()
|
TLuaEngine::TLuaEngine()
|
||||||
: mResourceServerPath(fs::path(Application::Settings.Resource) / "Server") {
|
: mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") {
|
||||||
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
|
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
|
||||||
LuaAPI::MP::Engine = this;
|
LuaAPI::MP::Engine = this;
|
||||||
if (!fs::exists(Application::Settings.Resource)) {
|
if (!fs::exists(Application::Settings.getAsString(Settings::Key::General_ResourceFolder))) {
|
||||||
fs::create_directory(Application::Settings.Resource);
|
fs::create_directory(Application::Settings.getAsString(Settings::Key::General_ResourceFolder));
|
||||||
}
|
}
|
||||||
if (!fs::exists(mResourceServerPath)) {
|
if (!fs::exists(mResourceServerPath)) {
|
||||||
fs::create_directory(mResourceServerPath);
|
fs::create_directory(mResourceServerPath);
|
||||||
@@ -54,7 +73,7 @@ TLuaEngine::TLuaEngine()
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("TLuaEngine ctor & dtor") {
|
TEST_CASE("TLuaEngine ctor & dtor") {
|
||||||
Application::Settings.Resource = "beammp_server_test_resources";
|
Application::Settings.set(Settings::Key::General_ResourceFolder, "beammp_server_test_resources");
|
||||||
TLuaEngine engine;
|
TLuaEngine engine;
|
||||||
Application::GracefullyShutdown();
|
Application::GracefullyShutdown();
|
||||||
}
|
}
|
||||||
@@ -63,6 +82,7 @@ void TLuaEngine::operator()() {
|
|||||||
RegisterThread("LuaEngine");
|
RegisterThread("LuaEngine");
|
||||||
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
|
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
|
||||||
// lua engine main thread
|
// lua engine main thread
|
||||||
|
beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
|
||||||
CollectAndInitPlugins();
|
CollectAndInitPlugins();
|
||||||
// now call all onInit's
|
// now call all onInit's
|
||||||
auto Futures = TriggerEvent("onInit", "");
|
auto Futures = TriggerEvent("onInit", "");
|
||||||
@@ -265,7 +285,7 @@ std::vector<std::string> TLuaEngine::StateThreadData::GetStateTableKeys(const st
|
|||||||
|
|
||||||
for (size_t i = 0; i < keys.size(); ++i) {
|
for (size_t i = 0; i < keys.size(); ++i) {
|
||||||
auto obj = current.get<sol::object>(keys.at(i));
|
auto obj = current.get<sol::object>(keys.at(i));
|
||||||
if (obj.get_type() == sol::type::nil) {
|
if (obj.get_type() == sol::type::lua_nil) {
|
||||||
// error
|
// error
|
||||||
break;
|
break;
|
||||||
} else if (i == keys.size() - 1) {
|
} else if (i == keys.size() - 1) {
|
||||||
@@ -350,26 +370,39 @@ std::shared_ptr<TLuaResult> TLuaEngine::EnqueueScript(TLuaStateId StateID, const
|
|||||||
return mLuaStates.at(StateID)->EnqueueScript(Script);
|
return mLuaStates.at(StateID)->EnqueueScript(Script);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
|
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName) {
|
||||||
std::unique_lock Lock(mLuaStatesMutex);
|
std::unique_lock Lock(mLuaStatesMutex);
|
||||||
return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName, Args);
|
return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName, Args, EventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TLuaEngine::CollectAndInitPlugins() {
|
void TLuaEngine::CollectAndInitPlugins() {
|
||||||
if (!fs::exists(mResourceServerPath)) {
|
if (!fs::exists(mResourceServerPath)) {
|
||||||
fs::create_directories(mResourceServerPath);
|
fs::create_directories(mResourceServerPath);
|
||||||
}
|
}
|
||||||
for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) {
|
|
||||||
auto Path = Dir.path();
|
std::vector<fs::path> PluginsEntries;
|
||||||
Path = fs::relative(Path);
|
for (const auto& Entry : fs::directory_iterator(mResourceServerPath)) {
|
||||||
if (!Dir.is_directory()) {
|
if (Entry.is_directory()) {
|
||||||
beammp_error("\"" + Dir.path().string() + "\" is not a directory, skipping");
|
PluginsEntries.push_back(Entry);
|
||||||
} else {
|
} else {
|
||||||
TLuaPluginConfig Config { Path.stem().string() };
|
beammp_error("\"" + Entry.path().string() + "\" is not a directory, skipping");
|
||||||
FindAndParseConfig(Path, Config);
|
|
||||||
InitializePlugin(Path, Config);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::sort(PluginsEntries.begin(), PluginsEntries.end(), [](const fs::path& first, const fs::path& second) {
|
||||||
|
auto firstStr = first.string();
|
||||||
|
auto secondStr = second.string();
|
||||||
|
std::transform(firstStr.begin(), firstStr.end(), firstStr.begin(), ::tolower);
|
||||||
|
std::transform(secondStr.begin(), secondStr.end(), secondStr.begin(), ::tolower);
|
||||||
|
return firstStr < secondStr;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& Dir : PluginsEntries) {
|
||||||
|
auto Path = fs::relative(Dir);
|
||||||
|
TLuaPluginConfig Config { Path.stem().string() };
|
||||||
|
FindAndParseConfig(Path, Config);
|
||||||
|
InitializePlugin(Path, Config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TLuaEngine::InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config) {
|
void TLuaEngine::InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config) {
|
||||||
@@ -412,7 +445,7 @@ void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name,
|
|||||||
mLuaStates[StateId] = std::move(DataPtr);
|
mLuaStates[StateId] = std::move(DataPtr);
|
||||||
RegisterEvent("onInit", StateId, "onInit");
|
RegisterEvent("onInit", StateId, "onInit");
|
||||||
if (!DontCallOnInit) {
|
if (!DontCallOnInit) {
|
||||||
auto Res = EnqueueFunctionCall(StateId, "onInit", {});
|
auto Res = EnqueueFunctionCall(StateId, "onInit", {}, "onInit");
|
||||||
Res->WaitUntilReady();
|
Res->WaitUntilReady();
|
||||||
if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) {
|
if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) {
|
||||||
beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Res->ErrorMessage);
|
beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Res->ErrorMessage);
|
||||||
@@ -430,20 +463,64 @@ std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& Ev
|
|||||||
return mLuaEvents[EventName][StateId];
|
return mLuaEvents[EventName][StateId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<sol::object> TLuaEngine::StateThreadData::JsonStringToArray(JsonString Str) {
|
||||||
|
auto LocalTable = Lua_JsonDecode(Str.value).as<std::vector<sol::object>>();
|
||||||
|
for (auto& value : LocalTable) {
|
||||||
|
if (value.is<std::string>() && value.as<std::string>() == BEAMMP_INTERNAL_NIL) {
|
||||||
|
value = sol::object {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LocalTable;
|
||||||
|
}
|
||||||
|
|
||||||
sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
|
sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
|
||||||
auto Return = mEngine->TriggerEvent(EventName, mStateId, EventArgs);
|
auto Table = mStateView.create_table();
|
||||||
|
int i = 1;
|
||||||
|
for (auto Arg : EventArgs) {
|
||||||
|
switch (Arg.get_type()) {
|
||||||
|
case sol::type::none:
|
||||||
|
case sol::type::userdata:
|
||||||
|
case sol::type::lightuserdata:
|
||||||
|
case sol::type::thread:
|
||||||
|
case sol::type::function:
|
||||||
|
case sol::type::poly:
|
||||||
|
Table.set(i, BEAMMP_INTERNAL_NIL);
|
||||||
|
beammp_warnf("Passed a value of type '{}' to TriggerGlobalEvent(\"{}\", ...). This type can not be serialized, and cannot be passed between states. It will arrive as <nil> in handlers.", sol::type_name(EventArgs.lua_state(), Arg.get_type()), EventName);
|
||||||
|
break;
|
||||||
|
case sol::type::lua_nil:
|
||||||
|
Table.set(i, BEAMMP_INTERNAL_NIL);
|
||||||
|
break;
|
||||||
|
case sol::type::string:
|
||||||
|
case sol::type::number:
|
||||||
|
case sol::type::boolean:
|
||||||
|
case sol::type::table:
|
||||||
|
Table.set(i, Arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
JsonString Str { LuaAPI::MP::JsonEncode(Table) };
|
||||||
|
beammp_debugf("json: {}", Str.value);
|
||||||
|
auto Return = mEngine->TriggerEvent(EventName, mStateId, Str);
|
||||||
auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId);
|
auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId);
|
||||||
|
|
||||||
|
sol::variadic_results LocalArgs = JsonStringToArray(Str);
|
||||||
for (const auto& Handler : MyHandlers) {
|
for (const auto& Handler : MyHandlers) {
|
||||||
auto Fn = mStateView[Handler];
|
auto Res = GetLuaHandler(mStateView, Handler, EventName);
|
||||||
if (Fn.valid()) {
|
if (Res.has_value()) {
|
||||||
auto LuaResult = Fn(EventArgs);
|
sol::function Fn = Res.value();
|
||||||
|
Fn = AddTraceback(mStateView, Fn);
|
||||||
|
|
||||||
|
auto LuaResult = Fn(LocalArgs);
|
||||||
auto Result = std::make_shared<TLuaResult>();
|
auto Result = std::make_shared<TLuaResult>();
|
||||||
if (LuaResult.valid()) {
|
if (LuaResult.valid()) {
|
||||||
Result->Error = false;
|
Result->Error = false;
|
||||||
Result->Result = LuaResult;
|
Result->Result = LuaResult;
|
||||||
} else {
|
} else {
|
||||||
Result->Error = true;
|
Result->Error = true;
|
||||||
Result->ErrorMessage = "Function result in TriggerGlobalEvent was invalid";
|
sol::error Err = LuaResult;
|
||||||
|
Result->ErrorMessage = Err.what();
|
||||||
|
beammp_errorf("An error occured while executing local event handler \"{}\" for event \"{}\": {}", Handler, EventName, Result->ErrorMessage);
|
||||||
}
|
}
|
||||||
Result->MarkAsReady();
|
Result->MarkAsReady();
|
||||||
Return.push_back(Result);
|
Return.push_back(Result);
|
||||||
@@ -467,11 +544,13 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
|
|||||||
sol::state_view StateView(mState);
|
sol::state_view StateView(mState);
|
||||||
sol::table Result = StateView.create_table();
|
sol::table Result = StateView.create_table();
|
||||||
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
|
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
|
||||||
|
int i = 1;
|
||||||
for (const auto& Value : Vector) {
|
for (const auto& Value : Vector) {
|
||||||
if (!Value->Ready) {
|
if (!Value->Ready) {
|
||||||
return sol::lua_nil;
|
return sol::lua_nil;
|
||||||
}
|
}
|
||||||
Result.add(Value->Result);
|
Result.set(i, Value->Result);
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
});
|
});
|
||||||
@@ -481,12 +560,15 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
|
|||||||
sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
|
sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
|
||||||
// TODO: make asynchronous?
|
// TODO: make asynchronous?
|
||||||
sol::table Result = mStateView.create_table();
|
sol::table Result = mStateView.create_table();
|
||||||
|
int i = 1;
|
||||||
for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
|
for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
|
||||||
auto Fn = mStateView[Handler];
|
auto Res = GetLuaHandler(mStateView, Handler, EventName);
|
||||||
if (Fn.valid() && Fn.get_type() == sol::type::function) {
|
if (Res.has_value()) {
|
||||||
|
sol::function Fn = Res.value();
|
||||||
auto FnRet = Fn(EventArgs);
|
auto FnRet = Fn(EventArgs);
|
||||||
if (FnRet.valid()) {
|
if (FnRet.valid()) {
|
||||||
Result.add(FnRet);
|
Result.set(i, FnRet);
|
||||||
|
++i;
|
||||||
} else {
|
} else {
|
||||||
sol::error Err = FnRet;
|
sol::error Err = FnRet;
|
||||||
beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
|
beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
|
||||||
@@ -658,6 +740,7 @@ static void AddToTable(sol::table& table, const std::string& left, const T& valu
|
|||||||
static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
|
static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
|
||||||
switch (right.type()) {
|
switch (right.type()) {
|
||||||
case nlohmann::detail::value_t::null:
|
case nlohmann::detail::value_t::null:
|
||||||
|
AddToTable(table, left, sol::lua_nil_t {});
|
||||||
return;
|
return;
|
||||||
case nlohmann::detail::value_t::object: {
|
case nlohmann::detail::value_t::object: {
|
||||||
auto value = table.create();
|
auto value = table.create();
|
||||||
@@ -829,6 +912,40 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
|||||||
MPTable.set_function("Set", &LuaAPI::MP::Set);
|
MPTable.set_function("Set", &LuaAPI::MP::Set);
|
||||||
|
|
||||||
auto UtilTable = StateView.create_named_table("Util");
|
auto UtilTable = StateView.create_named_table("Util");
|
||||||
|
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {
|
||||||
|
std::string ToPrint = "";
|
||||||
|
for (const auto& arg : args) {
|
||||||
|
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
|
||||||
|
ToPrint += "\t";
|
||||||
|
}
|
||||||
|
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
||||||
|
beammp_lua_log("DEBUG", mStateId, ToPrint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
UtilTable.set_function("LogInfo", [this](sol::variadic_args args) {
|
||||||
|
std::string ToPrint = "";
|
||||||
|
for (const auto& arg : args) {
|
||||||
|
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
|
||||||
|
ToPrint += "\t";
|
||||||
|
}
|
||||||
|
beammp_lua_log("INFO", mStateId, ToPrint);
|
||||||
|
});
|
||||||
|
UtilTable.set_function("LogWarn", [this](sol::variadic_args args) {
|
||||||
|
std::string ToPrint = "";
|
||||||
|
for (const auto& arg : args) {
|
||||||
|
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
|
||||||
|
ToPrint += "\t";
|
||||||
|
}
|
||||||
|
beammp_lua_log("WARN", mStateId, ToPrint);
|
||||||
|
});
|
||||||
|
UtilTable.set_function("LogError", [this](sol::variadic_args args) {
|
||||||
|
std::string ToPrint = "";
|
||||||
|
for (const auto& arg : args) {
|
||||||
|
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
|
||||||
|
ToPrint += "\t";
|
||||||
|
}
|
||||||
|
beammp_lua_log("ERROR", mStateId, ToPrint);
|
||||||
|
});
|
||||||
UtilTable.set_function("JsonEncode", &LuaAPI::MP::JsonEncode);
|
UtilTable.set_function("JsonEncode", &LuaAPI::MP::JsonEncode);
|
||||||
UtilTable.set_function("JsonDecode", [this](const std::string& str) {
|
UtilTable.set_function("JsonDecode", [this](const std::string& str) {
|
||||||
return Lua_JsonDecode(str);
|
return Lua_JsonDecode(str);
|
||||||
@@ -847,6 +964,30 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
|||||||
UtilTable.set_function("RandomIntRange", [this](int64_t min, int64_t max) -> int64_t {
|
UtilTable.set_function("RandomIntRange", [this](int64_t min, int64_t max) -> int64_t {
|
||||||
return std::uniform_int_distribution(min, max)(mMersenneTwister);
|
return std::uniform_int_distribution(min, max)(mMersenneTwister);
|
||||||
});
|
});
|
||||||
|
UtilTable.set_function("DebugExecutionTime", [this]() -> sol::table {
|
||||||
|
sol::state_view StateView(mState);
|
||||||
|
sol::table Result = StateView.create_table();
|
||||||
|
auto stats = mProfile.all_stats();
|
||||||
|
for (const auto& [name, stat] : stats) {
|
||||||
|
Result[name] = StateView.create_table();
|
||||||
|
Result[name]["mean"] = stat.mean;
|
||||||
|
Result[name]["stdev"] = stat.stdev;
|
||||||
|
Result[name]["min"] = stat.min;
|
||||||
|
Result[name]["max"] = stat.max;
|
||||||
|
Result[name]["n"] = stat.n;
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
});
|
||||||
|
UtilTable.set_function("DebugStartProfile", [this](const std::string& name) {
|
||||||
|
mProfileStarts[name] = prof::now();
|
||||||
|
});
|
||||||
|
UtilTable.set_function("DebugStopProfile", [this](const std::string& name) {
|
||||||
|
if (!mProfileStarts.contains(name)) {
|
||||||
|
beammp_lua_errorf("DebugStopProfile('{}') failed, because a profile for '{}' wasn't started", name, name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mProfile.add_sample(name, prof::duration(mProfileStarts.at(name), prof::now()));
|
||||||
|
});
|
||||||
|
|
||||||
auto HttpTable = StateView.create_named_table("Http");
|
auto HttpTable = StateView.create_named_table("Http");
|
||||||
HttpTable.set_function("CreateConnection", [this](const std::string& host, uint16_t port) {
|
HttpTable.set_function("CreateConnection", [this](const std::string& host, uint16_t port) {
|
||||||
@@ -894,7 +1035,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const TLu
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy) {
|
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy) {
|
||||||
// TODO: Document all this
|
// TODO: Document all this
|
||||||
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
|
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
|
||||||
if (Strategy == CallStrategy::BestEffort) {
|
if (Strategy == CallStrategy::BestEffort) {
|
||||||
@@ -916,12 +1057,12 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
|
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName) {
|
||||||
auto Result = std::make_shared<TLuaResult>();
|
auto Result = std::make_shared<TLuaResult>();
|
||||||
Result->StateId = mStateId;
|
Result->StateId = mStateId;
|
||||||
Result->Function = FunctionName;
|
Result->Function = FunctionName;
|
||||||
std::unique_lock Lock(mStateFunctionQueueMutex);
|
std::unique_lock Lock(mStateFunctionQueueMutex);
|
||||||
mStateFunctionQueue.push_back({ FunctionName, Result, Args, "" });
|
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
|
||||||
mStateFunctionQueueCond.notify_all();
|
mStateFunctionQueueCond.notify_all();
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
@@ -930,6 +1071,21 @@ void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, co
|
|||||||
mEngine->RegisterEvent(EventName, mStateId, FunctionName);
|
mEngine->RegisterEvent(EventName, mStateId, FunctionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn) {
|
||||||
|
StateView["INTERNAL_ERROR_HANDLER"] = [](lua_State *L) {
|
||||||
|
auto Error = sol::stack::get<std::optional<std::string>>(L);
|
||||||
|
std::string ErrorString = "<Unknown error>";
|
||||||
|
if (Error.has_value()) {
|
||||||
|
ErrorString = Error.value();
|
||||||
|
}
|
||||||
|
auto DebugTracebackFn = sol::state_view(L).globals().get<sol::table>("debug").get<sol::protected_function>("traceback");
|
||||||
|
// 2 = start collecting the trace one above the current function (1=current function)
|
||||||
|
std::string Traceback = DebugTracebackFn(ErrorString, 2);
|
||||||
|
return sol::stack::push(L, Traceback);
|
||||||
|
};
|
||||||
|
return sol::protected_function(RawFn, StateView["INTERNAL_ERROR_HANDLER"]);
|
||||||
|
}
|
||||||
|
|
||||||
void TLuaEngine::StateThreadData::operator()() {
|
void TLuaEngine::StateThreadData::operator()() {
|
||||||
RegisterThread("Lua:" + mStateId);
|
RegisterThread("Lua:" + mStateId);
|
||||||
while (!Application::IsShuttingDown()) {
|
while (!Application::IsShuttingDown()) {
|
||||||
@@ -984,6 +1140,7 @@ void TLuaEngine::StateThreadData::operator()() {
|
|||||||
std::chrono::milliseconds(500),
|
std::chrono::milliseconds(500),
|
||||||
[&]() -> bool { return !mStateFunctionQueue.empty(); });
|
[&]() -> bool { return !mStateFunctionQueue.empty(); });
|
||||||
if (NotExpired) {
|
if (NotExpired) {
|
||||||
|
auto ProfStart = prof::now();
|
||||||
auto TheQueuedFunction = std::move(mStateFunctionQueue.front());
|
auto TheQueuedFunction = std::move(mStateFunctionQueue.front());
|
||||||
mStateFunctionQueue.erase(mStateFunctionQueue.begin());
|
mStateFunctionQueue.erase(mStateFunctionQueue.begin());
|
||||||
Lock.unlock();
|
Lock.unlock();
|
||||||
@@ -993,27 +1150,31 @@ void TLuaEngine::StateThreadData::operator()() {
|
|||||||
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
|
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
|
||||||
Result->StateId = mStateId;
|
Result->StateId = mStateId;
|
||||||
sol::state_view StateView(mState);
|
sol::state_view StateView(mState);
|
||||||
auto Fn = StateView[FnName];
|
|
||||||
if (Fn.valid() && Fn.get_type() == sol::type::function) {
|
auto Res = GetLuaHandler(StateView, FnName, TheQueuedFunction.EventName);
|
||||||
|
if (Res.has_value()) {
|
||||||
|
sol::function Fn = Res.value();
|
||||||
std::vector<sol::object> LuaArgs;
|
std::vector<sol::object> LuaArgs;
|
||||||
for (const auto& Arg : Args) {
|
for (const auto& Arg : Args) {
|
||||||
if (Arg.valueless_by_exception()) {
|
if (Arg.valueless_by_exception()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
switch (Arg.index()) {
|
switch (Arg.index()) {
|
||||||
case TLuaArgTypes_String:
|
case TLuaType::String:
|
||||||
LuaArgs.push_back(sol::make_object(StateView, std::get<std::string>(Arg)));
|
LuaArgs.push_back(sol::make_object(StateView, std::get<std::string>(Arg)));
|
||||||
break;
|
break;
|
||||||
case TLuaArgTypes_Int:
|
case TLuaType::Int:
|
||||||
LuaArgs.push_back(sol::make_object(StateView, std::get<int>(Arg)));
|
LuaArgs.push_back(sol::make_object(StateView, std::get<int>(Arg)));
|
||||||
break;
|
break;
|
||||||
case TLuaArgTypes_VariadicArgs:
|
case TLuaType::Json: {
|
||||||
LuaArgs.push_back(sol::make_object(StateView, std::get<sol::variadic_args>(Arg)));
|
auto LocalArgs = JsonStringToArray(std::get<JsonString>(Arg));
|
||||||
|
LuaArgs.insert(LuaArgs.end(), LocalArgs.begin(), LocalArgs.end());
|
||||||
break;
|
break;
|
||||||
case TLuaArgTypes_Bool:
|
}
|
||||||
|
case TLuaType::Bool:
|
||||||
LuaArgs.push_back(sol::make_object(StateView, std::get<bool>(Arg)));
|
LuaArgs.push_back(sol::make_object(StateView, std::get<bool>(Arg)));
|
||||||
break;
|
break;
|
||||||
case TLuaArgTypes_StringStringMap: {
|
case TLuaType::StringStringMap: {
|
||||||
auto Map = std::get<std::unordered_map<std::string, std::string>>(Arg);
|
auto Map = std::get<std::unordered_map<std::string, std::string>>(Arg);
|
||||||
auto Table = StateView.create_table();
|
auto Table = StateView.create_table();
|
||||||
for (const auto& [k, v] : Map) {
|
for (const auto& [k, v] : Map) {
|
||||||
@@ -1027,6 +1188,7 @@ void TLuaEngine::StateThreadData::operator()() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Fn = AddTraceback(StateView, Fn);
|
||||||
auto Res = Fn(sol::as_args(LuaArgs));
|
auto Res = Fn(sol::as_args(LuaArgs));
|
||||||
if (Res.valid()) {
|
if (Res.valid()) {
|
||||||
Result->Error = false;
|
Result->Error = false;
|
||||||
@@ -1042,6 +1204,9 @@ void TLuaEngine::StateThreadData::operator()() {
|
|||||||
Result->ErrorMessage = BeamMPFnNotFoundError; // special error kind that we can ignore later
|
Result->ErrorMessage = BeamMPFnNotFoundError; // special error kind that we can ignore later
|
||||||
Result->MarkAsReady();
|
Result->MarkAsReady();
|
||||||
}
|
}
|
||||||
|
auto ProfEnd = prof::now();
|
||||||
|
auto ProfDuration = prof::duration(ProfStart, ProfEnd);
|
||||||
|
mProfile.add_sample(FnName, ProfDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1101,7 +1266,7 @@ void TLuaResult::MarkAsReady() {
|
|||||||
void TLuaResult::WaitUntilReady() {
|
void TLuaResult::WaitUntilReady() {
|
||||||
std::unique_lock readyLock(*this->ReadyMutex);
|
std::unique_lock readyLock(*this->ReadyMutex);
|
||||||
// wait if not ready yet
|
// wait if not ready yet
|
||||||
if(!this->Ready)
|
if (!this->Ready)
|
||||||
this->ReadyCondition->wait(readyLock);
|
this->ReadyCondition->wait(readyLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+159
-61
@@ -21,13 +21,17 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "LuaAPI.h"
|
#include "LuaAPI.h"
|
||||||
#include "TLuaEngine.h"
|
#include "TLuaEngine.h"
|
||||||
|
#include "TScopedTimer.h"
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include <CustomAssert.h>
|
#include <CustomAssert.h>
|
||||||
#include <Http.h>
|
#include <Http.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <boost/asio/ip/address.hpp>
|
#include <boost/asio/ip/address.hpp>
|
||||||
#include <boost/asio/ip/address_v4.hpp>
|
#include <boost/asio/ip/address_v4.hpp>
|
||||||
|
#include <boost/asio/ip/address_v6.hpp>
|
||||||
|
#include <boost/asio/ip/v6_only.hpp>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
|
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
|
||||||
|
|
||||||
@@ -80,7 +84,8 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
|
|||||||
|
|
||||||
void TNetwork::UDPServerMain() {
|
void TNetwork::UDPServerMain() {
|
||||||
RegisterThread("UDPServer");
|
RegisterThread("UDPServer");
|
||||||
ip::udp::endpoint UdpListenEndpoint(ip::address::from_string("0.0.0.0"), Application::Settings.Port);
|
// listen on all ipv6 addresses
|
||||||
|
ip::udp::endpoint UdpListenEndpoint(ip::address::from_string("::"), Application::Settings.getAsInt(Settings::Key::General_Port));
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
mUDPSock.open(UdpListenEndpoint.protocol(), ec);
|
mUDPSock.open(UdpListenEndpoint.protocol(), ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
@@ -88,6 +93,12 @@ void TNetwork::UDPServerMain() {
|
|||||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
Application::GracefullyShutdown();
|
Application::GracefullyShutdown();
|
||||||
}
|
}
|
||||||
|
// set IP_V6ONLY to false to allow both v4 and v6
|
||||||
|
boost::asio::ip::v6_only option(false);
|
||||||
|
mUDPSock.set_option(option, ec);
|
||||||
|
if (ec) {
|
||||||
|
beammp_warnf("Failed to unset IP_V6ONLY on UDP, only IPv6 will work: {}", ec.message());
|
||||||
|
}
|
||||||
mUDPSock.bind(UdpListenEndpoint, ec);
|
mUDPSock.bind(UdpListenEndpoint, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
beammp_error("bind() failed: " + ec.message());
|
beammp_error("bind() failed: " + ec.message());
|
||||||
@@ -95,12 +106,12 @@ void TNetwork::UDPServerMain() {
|
|||||||
Application::GracefullyShutdown();
|
Application::GracefullyShutdown();
|
||||||
}
|
}
|
||||||
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
|
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
|
||||||
beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ")
|
beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) + (" with a Max of ")
|
||||||
+ std::to_string(Application::Settings.MaxPlayers) + (" Clients"));
|
+ std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
|
||||||
while (!Application::IsShuttingDown()) {
|
while (!Application::IsShuttingDown()) {
|
||||||
try {
|
try {
|
||||||
ip::udp::endpoint client {};
|
ip::udp::endpoint remote_client_ep {};
|
||||||
std::vector<uint8_t> Data = UDPRcvFromClient(client); // Receives any data from Socket
|
std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep);
|
||||||
auto Pos = std::find(Data.begin(), Data.end(), ':');
|
auto Pos = std::find(Data.begin(), Data.end(), ':');
|
||||||
if (Data.empty() || Pos > Data.begin() + 2)
|
if (Data.empty() || Pos > Data.begin() + 2)
|
||||||
continue;
|
continue;
|
||||||
@@ -116,16 +127,31 @@ void TNetwork::UDPServerMain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Client->GetID() == ID) {
|
if (Client->GetID() == ID) {
|
||||||
Client->SetUDPAddr(client);
|
// not initialized yet
|
||||||
Client->SetIsConnected(true);
|
if (Client->GetUDPAddr() == ip::udp::endpoint {} || !Client->IsUDPConnected()) {
|
||||||
Data.erase(Data.begin(), Data.begin() + 2);
|
// same IP (just a sanity check)
|
||||||
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this);
|
if (remote_client_ep.address() == Client->GetTCPSock().remote_endpoint().address()) {
|
||||||
|
Client->SetUDPAddr(remote_client_ep);
|
||||||
|
Client->SetIsUDPConnected(true);
|
||||||
|
beammp_debugf("UDP connected for client {}", ID);
|
||||||
|
} else {
|
||||||
|
beammp_debugf("Denied initial UDP packet due to IP mismatch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Client->GetUDPAddr() == remote_client_ep) {
|
||||||
|
Data.erase(Data.begin(), Data.begin() + 2);
|
||||||
|
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this);
|
||||||
|
} else {
|
||||||
|
beammp_debugf("Ignored UDP packet due to remote address mismatch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_error(("fatal: ") + std::string(e.what()));
|
beammp_warnf("Failed to receive/parse packet via UDP: {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +159,10 @@ void TNetwork::UDPServerMain() {
|
|||||||
void TNetwork::TCPServerMain() {
|
void TNetwork::TCPServerMain() {
|
||||||
RegisterThread("TCPServer");
|
RegisterThread("TCPServer");
|
||||||
|
|
||||||
ip::tcp::endpoint ListenEp(ip::address::from_string("0.0.0.0"), Application::Settings.Port);
|
// listen on all ipv6 addresses
|
||||||
|
auto port = uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port));
|
||||||
|
ip::tcp::endpoint ListenEp(ip::address::from_string("::"), port);
|
||||||
|
beammp_infof("Listening on 0.0.0.0:{0} and [::]:{0}", port);
|
||||||
ip::tcp::socket Listener(mServer.IoCtx());
|
ip::tcp::socket Listener(mServer.IoCtx());
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
Listener.open(ListenEp.protocol(), ec);
|
Listener.open(ListenEp.protocol(), ec);
|
||||||
@@ -141,6 +170,16 @@ void TNetwork::TCPServerMain() {
|
|||||||
beammp_errorf("Failed to open socket: {}", ec.message());
|
beammp_errorf("Failed to open socket: {}", ec.message());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// set IP_V6ONLY to false to allow both v4 and v6
|
||||||
|
boost::asio::ip::v6_only option(false);
|
||||||
|
Listener.set_option(option, ec);
|
||||||
|
if (ec) {
|
||||||
|
beammp_warnf("Failed to unset IP_V6ONLY on TCP, only IPv6 will work: {}", ec.message());
|
||||||
|
}
|
||||||
|
#if defined(BEAMMP_FREEBSD)
|
||||||
|
beammp_warnf("WARNING: On FreeBSD, for IPv4 to work, you must run `sysctl net.inet6.ip6.v6only=0`!");
|
||||||
|
beammp_debugf("This is due to an annoying detail in the *BSDs: In the name of security, unsetting the IPV6_V6ONLY option does not work by default (but does not fail???), as it allows IPv4 mapped IPv6 like ::ffff:127.0.0.1, which they deem a security issue. For more information, see RFC 2553, section 3.7.");
|
||||||
|
#endif
|
||||||
socket_base::linger LingerOpt {};
|
socket_base::linger LingerOpt {};
|
||||||
LingerOpt.enabled(false);
|
LingerOpt.enabled(false);
|
||||||
Listener.set_option(LingerOpt, ec);
|
Listener.set_option(LingerOpt, ec);
|
||||||
@@ -169,13 +208,13 @@ void TNetwork::TCPServerMain() {
|
|||||||
ip::tcp::endpoint ClientEp;
|
ip::tcp::endpoint ClientEp;
|
||||||
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
|
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
beammp_errorf("failed to accept: {}", ec.message());
|
beammp_errorf("Failed to accept() new client: {}", ec.message());
|
||||||
}
|
}
|
||||||
TConnection Conn { std::move(ClientSocket), ClientEp };
|
TConnection Conn { std::move(ClientSocket), ClientEp };
|
||||||
std::thread ID(&TNetwork::Identify, this, std::move(Conn));
|
std::thread ID(&TNetwork::Identify, this, std::move(Conn));
|
||||||
ID.detach(); // TODO: Add to a queue and attempt to join periodically
|
ID.detach(); // TODO: Add to a queue and attempt to join periodically
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_error("fatal: " + std::string(e.what()));
|
beammp_errorf("Exception in accept routine: {}", e.what());
|
||||||
}
|
}
|
||||||
} while (!Application::IsShuttingDown());
|
} while (!Application::IsShuttingDown());
|
||||||
}
|
}
|
||||||
@@ -209,7 +248,7 @@ void TNetwork::Identify(TConnection&& RawConnection) {
|
|||||||
} else {
|
} else {
|
||||||
beammp_errorf("Invalid code got in Identify: '{}'", Code);
|
beammp_errorf("Invalid code got in Identify: '{}'", Code);
|
||||||
}
|
}
|
||||||
} catch(const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket", Code);
|
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket", Code);
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
||||||
@@ -256,8 +295,14 @@ std::string HashPassword(const std::string& str) {
|
|||||||
|
|
||||||
std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||||
auto Client = CreateClient(std::move(RawConnection.Socket));
|
auto Client = CreateClient(std::move(RawConnection.Socket));
|
||||||
Client->SetIdentifier("ip", RawConnection.SockAddr.address().to_string());
|
std::string ip = "";
|
||||||
beammp_tracef("This thread is ip {}", RawConnection.SockAddr.address().to_string());
|
if (RawConnection.SockAddr.address().to_v6().is_v4_mapped()) {
|
||||||
|
ip = boost::asio::ip::make_address_v4(ip::v4_mapped_t::v4_mapped, RawConnection.SockAddr.address().to_v6()).to_string();
|
||||||
|
} else {
|
||||||
|
ip = RawConnection.SockAddr.address().to_string();
|
||||||
|
}
|
||||||
|
Client->SetIdentifier("ip", ip);
|
||||||
|
beammp_tracef("This thread is ip {} ({})", ip, RawConnection.SockAddr.address().to_v6().is_v4_mapped() ? "IPv4 mapped IPv6" : "IPv6");
|
||||||
|
|
||||||
beammp_info("Identifying new ClientConnection...");
|
beammp_info("Identifying new ClientConnection...");
|
||||||
|
|
||||||
@@ -278,7 +323,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TCPSend(*Client, StringToVector("A"))) { //changed to A for Accepted version
|
if (!TCPSend(*Client, StringToVector("A"))) { // changed to A for Accepted version
|
||||||
// TODO: handle
|
// TODO: handle
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,16 +334,21 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string key(reinterpret_cast<const char*>(Data.data()), Data.size());
|
std::string Key(reinterpret_cast<const char*>(Data.data()), Data.size());
|
||||||
|
std::string AuthKey = Application::Settings.getAsString(Settings::Key::General_AuthKey);
|
||||||
|
std::string ClientIp = Client->GetIdentifiers().at("ip");
|
||||||
|
|
||||||
nlohmann::json AuthReq{};
|
nlohmann::json AuthReq {};
|
||||||
std::string AuthResStr{};
|
std::string AuthResStr {};
|
||||||
try {
|
try {
|
||||||
AuthReq = nlohmann::json {
|
AuthReq = nlohmann::json {
|
||||||
{ "key", key }
|
{ "key", Key },
|
||||||
|
{ "auth_key", AuthKey },
|
||||||
|
{ "client_ip", ClientIp }
|
||||||
};
|
};
|
||||||
|
|
||||||
auto Target = "/pkToUser";
|
auto Target = "/pkToUser";
|
||||||
|
|
||||||
unsigned int ResponseCode = 0;
|
unsigned int ResponseCode = 0;
|
||||||
AuthResStr = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, AuthReq.dump(), "application/json", &ResponseCode);
|
AuthResStr = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, AuthReq.dump(), "application/json", &ResponseCode);
|
||||||
|
|
||||||
@@ -334,22 +384,6 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Application::Settings.Password.empty()) { // ask password
|
|
||||||
if(!TCPSend(*Client, StringToVector("S"))) {
|
|
||||||
// TODO: handle
|
|
||||||
}
|
|
||||||
beammp_info("Waiting for password");
|
|
||||||
Data = TCPRcv(*Client);
|
|
||||||
std::string Pass = std::string(reinterpret_cast<const char*>(Data.data()), Data.size());
|
|
||||||
if(Pass != HashPassword(Application::Settings.Password)) {
|
|
||||||
beammp_debug(Client->GetName() + " attempted to connect with a wrong password");
|
|
||||||
ClientKick(*Client, "Wrong password!");
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
beammp_debug(Client->GetName() + " used the correct password");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
|
beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
|
||||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||||
std::shared_ptr<TClient> Cl;
|
std::shared_ptr<TClient> Cl;
|
||||||
@@ -384,15 +418,29 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (NotAllowed) {
|
if (!NotAllowedWithReason && !Application::Settings.getAsBool(Settings::Key::General_AllowGuests) && Client->IsGuest()) { //! NotAllowedWithReason because this message has the lowest priority
|
||||||
ClientKick(*Client, "you are not allowed on the server!");
|
NotAllowedWithReason = true;
|
||||||
return {};
|
Reason = "No guests are allowed on this server! To join, sign up at: forum.beammp.com.";
|
||||||
} else if (NotAllowedWithReason) {
|
|
||||||
ClientKick(*Client, Reason);
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mServer.ClientCount() < size_t(Application::Settings.MaxPlayers)) {
|
bool Allowed = true;
|
||||||
|
if (NotAllowed) {
|
||||||
|
Allowed = false;
|
||||||
|
}
|
||||||
|
if (NotAllowedWithReason) {
|
||||||
|
Allowed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NotAllowed) {
|
||||||
|
ClientKick(*Client, "you are not allowed on the server!");
|
||||||
|
} else if (NotAllowedWithReason) {
|
||||||
|
ClientKick(*Client, Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!Allowed) {
|
||||||
|
return {};
|
||||||
|
} else if (mServer.ClientCount() < size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers))) {
|
||||||
beammp_info("Identification success");
|
beammp_info("Identification success");
|
||||||
mServer.InsertClient(Client);
|
mServer.InsertClient(Client);
|
||||||
TCPClient(Client);
|
TCPClient(Client);
|
||||||
@@ -400,6 +448,10 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
ClientKick(*Client, "Server full!");
|
ClientKick(*Client, "Server full!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postPlayerAuth", "", Allowed, Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
||||||
|
// the post event is not cancellable so we dont wait for it
|
||||||
|
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
||||||
|
|
||||||
return Client;
|
return Client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +546,17 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
|
|||||||
constexpr std::string_view ABG = "ABG:";
|
constexpr std::string_view ABG = "ABG:";
|
||||||
if (Data.size() >= ABG.size() && std::equal(Data.begin(), Data.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
if (Data.size() >= ABG.size() && std::equal(Data.begin(), Data.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
||||||
Data.erase(Data.begin(), Data.begin() + ABG.size());
|
Data.erase(Data.begin(), Data.begin() + ABG.size());
|
||||||
return DeComp(Data);
|
try {
|
||||||
|
return DeComp(Data);
|
||||||
|
} catch (const InvalidDataError& ) {
|
||||||
|
beammp_errorf("Failed to decompress packet from a client. The receive failed and the client may be disconnected as a result");
|
||||||
|
// return empty -> error
|
||||||
|
return std::vector<uint8_t>();
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
beammp_errorf("Failed to decompress packet from a client: {}. The server may be out of RAM! The receive failed and the client may be disconnected as a result", e.what());
|
||||||
|
// return empty -> error
|
||||||
|
return std::vector<uint8_t>();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Data;
|
return Data;
|
||||||
}
|
}
|
||||||
@@ -585,7 +647,7 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::UpdatePlayer(TClient& Client) {
|
void TNetwork::UpdatePlayer(TClient& Client) {
|
||||||
std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::Settings.MaxPlayers) + ":";
|
std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + ":";
|
||||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||||
ReadLock Lock(mServer.GetClientMutex());
|
ReadLock Lock(mServer.GetClientMutex());
|
||||||
if (!ClientPtr.expired()) {
|
if (!ClientPtr.expired()) {
|
||||||
@@ -617,6 +679,7 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
|
|||||||
VehicleData = *LockedData.VehicleData;
|
VehicleData = *LockedData.VehicleData;
|
||||||
} // End Vehicle Data Lock Scope
|
} // End Vehicle Data Lock Scope
|
||||||
for (auto& v : VehicleData) {
|
for (auto& v : VehicleData) {
|
||||||
|
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), v.ID()));
|
||||||
Packet = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(v.ID());
|
Packet = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(v.ID());
|
||||||
SendToAll(&c, StringToVector(Packet), false, true);
|
SendToAll(&c, StringToVector(Packet), false, true);
|
||||||
}
|
}
|
||||||
@@ -630,6 +693,7 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int TNetwork::OpenID() {
|
int TNetwork::OpenID() {
|
||||||
|
std::unique_lock OpenIDLock(mOpenIDMutex);
|
||||||
int ID = 0;
|
int ID = 0;
|
||||||
bool found;
|
bool found;
|
||||||
do {
|
do {
|
||||||
@@ -659,7 +723,7 @@ void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
|
|||||||
SyncResources(*LockedClient);
|
SyncResources(*LockedClient);
|
||||||
if (LockedClient->IsDisconnected())
|
if (LockedClient->IsDisconnected())
|
||||||
return;
|
return;
|
||||||
(void)Respond(*LockedClient, StringToVector("M" + Application::Settings.MapName), true); // Send the Map on connect
|
(void)Respond(*LockedClient, StringToVector("M" + Application::Settings.getAsString(Settings::Key::General_Map)), true); // Send the Map on connect
|
||||||
beammp_info(LockedClient->GetName() + " : Connected");
|
beammp_info(LockedClient->GetName() + " : Connected");
|
||||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID()));
|
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID()));
|
||||||
}
|
}
|
||||||
@@ -718,7 +782,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto FileName = fs::path(UnsafeName).filename().string();
|
auto FileName = fs::path(UnsafeName).filename().string();
|
||||||
FileName = Application::Settings.Resource + "/Client/" + FileName;
|
FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName;
|
||||||
|
|
||||||
if (!std::filesystem::exists(FileName)) {
|
if (!std::filesystem::exists(FileName)) {
|
||||||
if (!TCPSend(c, StringToVector("CO"))) {
|
if (!TCPSend(c, StringToVector("CO"))) {
|
||||||
@@ -746,7 +810,8 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Size = size_t(std::filesystem::file_size(FileName)), MSize = Size / 2;
|
size_t Size = size_t(std::filesystem::file_size(FileName));
|
||||||
|
size_t MSize = Size / 2;
|
||||||
|
|
||||||
std::thread SplitThreads[2] {
|
std::thread SplitThreads[2] {
|
||||||
std::thread([&] {
|
std::thread([&] {
|
||||||
@@ -814,41 +879,74 @@ const uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, ip::tcp::socket& So
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) {
|
#if defined(BEAMMP_LINUX)
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/sendfile.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#endif
|
||||||
|
void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const std::string& Name) {
|
||||||
|
TScopedTimer timer(fmt::format("Download of {}-{} for '{}'", Offset, End, Name));
|
||||||
|
#if defined(BEAMMP_LINUX)
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
// on linux, we can use sendfile(2)!
|
||||||
|
int fd = ::open(Name.c_str(), O_RDONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
beammp_errorf("Failed to open mod '{}' for sending, error: {}", Name, std::strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// native handle, needed in order to make native syscalls with it
|
||||||
|
int socket = D ? c.GetDownSock().native_handle() : c.GetTCPSock().native_handle();
|
||||||
|
|
||||||
|
ssize_t ret = 0;
|
||||||
|
auto ToSendTotal = End - Offset;
|
||||||
|
auto Start = Offset;
|
||||||
|
while (ret < ssize_t(ToSendTotal)) {
|
||||||
|
auto SysOffset = off_t(Start + size_t(ret));
|
||||||
|
ret = sendfile(socket, fd, &SysOffset, ToSendTotal - size_t(ret));
|
||||||
|
if (ret < 0) {
|
||||||
|
beammp_errorf("Failed to send mod '{}' to client {}: {}", Name, c.GetID(), std::strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
std::ifstream f(Name.c_str(), std::ios::binary);
|
std::ifstream f(Name.c_str(), std::ios::binary);
|
||||||
uint32_t Split = 125 * MB;
|
uint32_t Split = 125 * MB;
|
||||||
std::vector<uint8_t> Data;
|
std::vector<uint8_t> Data;
|
||||||
if (Size > Split)
|
if (End > Split)
|
||||||
Data.resize(Split);
|
Data.resize(Split);
|
||||||
else
|
else
|
||||||
Data.resize(Size);
|
Data.resize(End);
|
||||||
ip::tcp::socket* TCPSock { nullptr };
|
ip::tcp::socket* TCPSock { nullptr };
|
||||||
if (D)
|
if (D)
|
||||||
TCPSock = &c.GetDownSock();
|
TCPSock = &c.GetDownSock();
|
||||||
else
|
else
|
||||||
TCPSock = &c.GetTCPSock();
|
TCPSock = &c.GetTCPSock();
|
||||||
while (!c.IsDisconnected() && Sent < Size) {
|
while (!c.IsDisconnected() && Offset < End) {
|
||||||
size_t Diff = Size - Sent;
|
size_t Diff = End - Offset;
|
||||||
if (Diff > Split) {
|
if (Diff > Split) {
|
||||||
f.seekg(Sent, std::ios_base::beg);
|
f.seekg(Offset, std::ios_base::beg);
|
||||||
f.read(reinterpret_cast<char*>(Data.data()), Split);
|
f.read(reinterpret_cast<char*>(Data.data()), Split);
|
||||||
if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) {
|
if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) {
|
||||||
if (!c.IsDisconnected())
|
if (!c.IsDisconnected())
|
||||||
c.Disconnect("TCPSendRaw failed in mod download (1)");
|
c.Disconnect("TCPSendRaw failed in mod download (1)");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Sent += Split;
|
Offset += Split;
|
||||||
} else {
|
} else {
|
||||||
f.seekg(Sent, std::ios_base::beg);
|
f.seekg(Offset, std::ios_base::beg);
|
||||||
f.read(reinterpret_cast<char*>(Data.data()), Diff);
|
f.read(reinterpret_cast<char*>(Data.data()), Diff);
|
||||||
if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) {
|
if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) {
|
||||||
if (!c.IsDisconnected())
|
if (!c.IsDisconnected())
|
||||||
c.Disconnect("TCPSendRaw failed in mod download (2)");
|
c.Disconnect("TCPSendRaw failed in mod download (2)");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Sent += Diff;
|
Offset += Diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TNetwork::TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size) {
|
bool TNetwork::TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size) {
|
||||||
@@ -871,7 +969,7 @@ bool TNetwork::SendLarge(TClient& c, std::vector<uint8_t> Data, bool isSync) {
|
|||||||
|
|
||||||
bool TNetwork::Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync) {
|
bool TNetwork::Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync) {
|
||||||
char C = MSG.at(0);
|
char C = MSG.at(0);
|
||||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || compressBound(MSG.size()) > 1024) {
|
||||||
if (C == 'O' || C == 'T' || MSG.size() > 1000) {
|
if (C == 'O' || C == 'T' || MSG.size() > 1000) {
|
||||||
return SendLarge(c, MSG, isSync);
|
return SendLarge(c, MSG, isSync);
|
||||||
} else {
|
} else {
|
||||||
@@ -954,7 +1052,7 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
|
|||||||
}
|
}
|
||||||
if (Self || Client.get() != c) {
|
if (Self || Client.get() != c) {
|
||||||
if (Client->IsSynced() || Client->IsSyncing()) {
|
if (Client->IsSynced() || Client->IsSyncing()) {
|
||||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || compressBound(Data.size()) > 1024) {
|
||||||
if (C == 'O' || C == 'T' || Data.size() > 1000) {
|
if (C == 'O' || C == 'T' || Data.size() > 1000) {
|
||||||
if (Data.size() > 400) {
|
if (Data.size() > 400) {
|
||||||
auto CompressedData = Data;
|
auto CompressedData = Data;
|
||||||
@@ -982,7 +1080,7 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
|
bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
|
||||||
if (!Client.IsConnected() || Client.IsDisconnected()) {
|
if (!Client.IsUDPConnected() || Client.IsDisconnected()) {
|
||||||
// this can happen if we try to send a packet to a client that is either
|
// this can happen if we try to send a packet to a client that is either
|
||||||
// 1. not yet fully connected, or
|
// 1. not yet fully connected, or
|
||||||
// 2. disconnected and not yet fully removed
|
// 2. disconnected and not yet fully removed
|
||||||
|
|||||||
+5
-2
@@ -66,14 +66,17 @@ void TPPSMonitor::operator()() {
|
|||||||
}
|
}
|
||||||
// kick on "no ping"
|
// kick on "no ping"
|
||||||
if (c->SecondsSinceLastPing() > (20 * 60)) {
|
if (c->SecondsSinceLastPing() > (20 * 60)) {
|
||||||
beammp_debug("client " + std::string("(") + std::to_string(c->GetID()) + ")" + c->GetName() + " timing out: " + std::to_string(c->SecondsSinceLastPing()) + ", pps: " + Application::PPS());
|
beammp_debugf("client {} ({}) timing out: {}", c->GetID(), c->GetName(), c->SecondsSinceLastPing());
|
||||||
|
TimedOutClients.push_back(c);
|
||||||
|
} else if (c->IsSynced() && c->SecondsSinceLastPing() > (1 * 60)) {
|
||||||
|
beammp_debugf("client {} ({}) timing out: {}", c->GetName(), c->GetID(), c->SecondsSinceLastPing());
|
||||||
TimedOutClients.push_back(c);
|
TimedOutClients.push_back(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
for (auto& ClientToKick : TimedOutClients) {
|
for (auto& ClientToKick : TimedOutClients) {
|
||||||
Network().ClientKick(*ClientToKick, "Timeout (no ping for way too long)");
|
ClientToKick->Disconnect("Timeout");
|
||||||
}
|
}
|
||||||
TimedOutClients.clear();
|
TimedOutClients.clear();
|
||||||
if (C == 0 || mInternalPPS == 0) {
|
if (C == 0 || mInternalPPS == 0) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
TResourceManager::TResourceManager() {
|
TResourceManager::TResourceManager() {
|
||||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
|
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
|
||||||
std::string Path = Application::Settings.Resource + "/Client";
|
std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client";
|
||||||
if (!fs::exists(Path))
|
if (!fs::exists(Path))
|
||||||
fs::create_directories(Path);
|
fs::create_directories(Path);
|
||||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||||
|
|||||||
+53
-27
@@ -20,6 +20,7 @@
|
|||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "CustomAssert.h"
|
#include "CustomAssert.h"
|
||||||
|
#include "TLuaEngine.h"
|
||||||
#include "TNetwork.h"
|
#include "TNetwork.h"
|
||||||
#include "TPPSMonitor.h"
|
#include "TPPSMonitor.h"
|
||||||
#include <TLuaPlugin.h>
|
#include <TLuaPlugin.h>
|
||||||
@@ -123,17 +124,6 @@ TEST_CASE("GetPidVid") {
|
|||||||
TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
||||||
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
||||||
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
||||||
if (Arguments.size() > 1) {
|
|
||||||
Application::Settings.CustomIP = Arguments[0];
|
|
||||||
size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.');
|
|
||||||
auto p = Application::Settings.CustomIP.find_first_not_of(".0123456789");
|
|
||||||
if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == "127") {
|
|
||||||
Application::Settings.CustomIP.clear();
|
|
||||||
beammp_warn("IP Specified is invalid! Ignoring");
|
|
||||||
} else {
|
|
||||||
beammp_info("server started with custom IP");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Application::SetSubsystemStatus("Server", Application::Status::Good);
|
Application::SetSubsystemStatus("Server", Application::Status::Good);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +138,6 @@ void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
|
|||||||
beammp_assert(LockedClientPtr != nullptr);
|
beammp_assert(LockedClientPtr != nullptr);
|
||||||
TClient& Client = *LockedClientPtr;
|
TClient& Client = *LockedClientPtr;
|
||||||
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
|
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
|
||||||
// TODO: Send delete packets for all cars
|
|
||||||
Client.ClearCars();
|
Client.ClearCars();
|
||||||
WriteLock Lock(mClientsMutex);
|
WriteLock Lock(mClientsMutex);
|
||||||
mClients.erase(WeakClientPtr.lock());
|
mClients.erase(WeakClientPtr.lock());
|
||||||
@@ -176,7 +165,19 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
|||||||
constexpr std::string_view ABG = "ABG:";
|
constexpr std::string_view ABG = "ABG:";
|
||||||
if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
||||||
Packet.erase(Packet.begin(), Packet.begin() + ABG.size());
|
Packet.erase(Packet.begin(), Packet.begin() + ABG.size());
|
||||||
Packet = DeComp(Packet);
|
try {
|
||||||
|
Packet = DeComp(Packet);
|
||||||
|
} catch (const InvalidDataError& ) {
|
||||||
|
auto LockedClient = Client.lock();
|
||||||
|
beammp_errorf("Failed to decompress packet from client {}. The client sent invalid data and will now be disconnected.", LockedClient->GetID());
|
||||||
|
Network.ClientKick(*LockedClient, "Sent invalid compressed packet (this is likely a bug on your end)");
|
||||||
|
return;
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
auto LockedClient = Client.lock();
|
||||||
|
beammp_errorf("Failed to decompress packet from client {}: {}. The server might be out of RAM! The client will now be disconnected.", LockedClient->GetID(), e.what());
|
||||||
|
Network.ClientKick(*LockedClient, "Decompression failed (likely a server-side problem)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Packet.empty()) {
|
if (Packet.empty()) {
|
||||||
return;
|
return;
|
||||||
@@ -234,16 +235,18 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
|||||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message);
|
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message);
|
||||||
TLuaEngine::WaitForAll(Futures);
|
TLuaEngine::WaitForAll(Futures);
|
||||||
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
|
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
|
||||||
if (std::any_of(Futures.begin(), Futures.end(),
|
bool Rejected = std::any_of(Futures.begin(), Futures.end(),
|
||||||
[](const std::shared_ptr<TLuaResult>& Elem) {
|
[](const std::shared_ptr<TLuaResult>& Elem) {
|
||||||
return !Elem->Error
|
return !Elem->Error
|
||||||
&& Elem->Result.is<int>()
|
&& Elem->Result.is<int>()
|
||||||
&& bool(Elem->Result.as<int>());
|
&& bool(Elem->Result.as<int>());
|
||||||
})) {
|
});
|
||||||
break;
|
if (!Rejected) {
|
||||||
|
std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message);
|
||||||
|
Network.SendToAll(nullptr, StringToVector(SanitizedPacket), true, true);
|
||||||
}
|
}
|
||||||
std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message);
|
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postChatMessage", "", !Rejected, LockedClient->GetID(), LockedClient->GetName(), Message);
|
||||||
Network.SendToAll(nullptr, StringToVector(SanitizedPacket), true, true);
|
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'E':
|
case 'E':
|
||||||
@@ -297,7 +300,7 @@ bool TServer::ShouldSpawn(TClient& c, const std::string& CarJson, int ID) {
|
|||||||
c.SetUnicycleID(ID);
|
c.SetUnicycleID(ID);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return c.GetCarCount() < Application::Settings.MaxCars;
|
return c.GetCarCount() < Application::Settings.getAsInt(Settings::Key::General_MaxCars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,19 +328,26 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
|
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bool SpawnConfirmed = false;
|
||||||
if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn) {
|
if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn) {
|
||||||
c.AddNewCar(CarID, Packet);
|
c.AddNewCar(CarID, Packet);
|
||||||
Network.SendToAll(nullptr, StringToVector(Packet), true, true);
|
Network.SendToAll(nullptr, StringToVector(Packet), true, true);
|
||||||
|
SpawnConfirmed = true;
|
||||||
} else {
|
} else {
|
||||||
if (!Network.Respond(c, StringToVector(Packet), true)) {
|
if (!Network.Respond(c, StringToVector(Packet), true)) {
|
||||||
// TODO: handle
|
// TODO: handle
|
||||||
}
|
}
|
||||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(CarID);
|
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(CarID);
|
||||||
|
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), CarID));
|
||||||
if (!Network.Respond(c, StringToVector(Destroy), true)) {
|
if (!Network.Respond(c, StringToVector(Destroy), true)) {
|
||||||
// TODO: handle
|
// TODO: handle
|
||||||
}
|
}
|
||||||
beammp_debugf("{} (force : car limit/lua) removed ID {}", c.GetName(), CarID);
|
beammp_debugf("{} (force : car limit/lua) removed ID {}", c.GetName(), CarID);
|
||||||
|
SpawnConfirmed = false;
|
||||||
}
|
}
|
||||||
|
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postVehicleSpawn", "", SpawnConfirmed, c.GetID(), CarID, Packet.substr(3));
|
||||||
|
// the post event is not cancellable so we dont wait for it
|
||||||
|
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'c': {
|
case 'c': {
|
||||||
@@ -356,18 +366,26 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
|
|
||||||
auto FoundPos = Packet.find('{');
|
auto FoundPos = Packet.find('{');
|
||||||
FoundPos = FoundPos == std::string::npos ? 0 : FoundPos; // attempt at sanitizing this
|
FoundPos = FoundPos == std::string::npos ? 0 : FoundPos; // attempt at sanitizing this
|
||||||
|
bool Allowed = false;
|
||||||
if ((c.GetUnicycleID() != VID || IsUnicycle(c, Packet.substr(FoundPos)))
|
if ((c.GetUnicycleID() != VID || IsUnicycle(c, Packet.substr(FoundPos)))
|
||||||
&& !ShouldntAllow) {
|
&& !ShouldntAllow) {
|
||||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
||||||
Apply(c, VID, Packet);
|
Apply(c, VID, Packet);
|
||||||
|
Allowed = true;
|
||||||
} else {
|
} else {
|
||||||
if (c.GetUnicycleID() == VID) {
|
if (c.GetUnicycleID() == VID) {
|
||||||
c.SetUnicycleID(-1);
|
c.SetUnicycleID(-1);
|
||||||
}
|
}
|
||||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID);
|
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID);
|
||||||
Network.SendToAll(nullptr, StringToVector(Destroy), true, true);
|
Network.SendToAll(nullptr, StringToVector(Destroy), true, true);
|
||||||
|
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), VID));
|
||||||
c.DeleteCar(VID);
|
c.DeleteCar(VID);
|
||||||
|
Allowed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postVehicleEdited", "", Allowed, c.GetID(), VID, Packet.substr(3));
|
||||||
|
// the post event is not cancellable so we dont wait for it
|
||||||
|
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -403,13 +421,21 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 't':
|
case 't': {
|
||||||
beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||||
|
auto MaybePidVid = GetPidVid(Data.substr(0, Data.find(':', 1)));
|
||||||
|
if (MaybePidVid) {
|
||||||
|
std::tie(PID, VID) = MaybePidVid.value();
|
||||||
|
}
|
||||||
|
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
||||||
|
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'm': {
|
||||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
||||||
return;
|
return;
|
||||||
case 'm':
|
}
|
||||||
Network.SendToAll(&c, StringToVector(Packet), true, true);
|
|
||||||
return;
|
|
||||||
default:
|
default:
|
||||||
beammp_trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
|
beammp_trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
|
||||||
return;
|
return;
|
||||||
|
|||||||
+29
-6
@@ -20,6 +20,7 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Http.h"
|
#include "Http.h"
|
||||||
#include "LuaAPI.h"
|
#include "LuaAPI.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "SignalHandling.h"
|
#include "SignalHandling.h"
|
||||||
#include "TConfig.h"
|
#include "TConfig.h"
|
||||||
#include "THeartbeatThread.h"
|
#include "THeartbeatThread.h"
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
#include "TResourceManager.h"
|
#include "TResourceManager.h"
|
||||||
#include "TServer.h"
|
#include "TServer.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -40,6 +42,9 @@ USAGE:
|
|||||||
ARGUMENTS:
|
ARGUMENTS:
|
||||||
--help
|
--help
|
||||||
Displays this help and exits.
|
Displays this help and exits.
|
||||||
|
--port=1234
|
||||||
|
Sets the server's listening TCP and
|
||||||
|
UDP port. Overrides ENV and ServerConfig.
|
||||||
--config=/path/to/ServerConfig.toml
|
--config=/path/to/ServerConfig.toml
|
||||||
Absolute or relative path to the
|
Absolute or relative path to the
|
||||||
Server Config file, including the
|
Server Config file, including the
|
||||||
@@ -91,6 +96,7 @@ int BeamMPServerMain(MainArguments Arguments) {
|
|||||||
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
|
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
|
||||||
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
|
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
|
||||||
Parser.RegisterArgument({ "config" }, ArgsParser::HAS_VALUE);
|
Parser.RegisterArgument({ "config" }, ArgsParser::HAS_VALUE);
|
||||||
|
Parser.RegisterArgument({ "port" }, ArgsParser::HAS_VALUE);
|
||||||
Parser.RegisterArgument({ "working-directory" }, ArgsParser::HAS_VALUE);
|
Parser.RegisterArgument({ "working-directory" }, ArgsParser::HAS_VALUE);
|
||||||
Parser.Parse(Arguments.List);
|
Parser.Parse(Arguments.List);
|
||||||
if (!Parser.Verify()) {
|
if (!Parser.Verify()) {
|
||||||
@@ -124,7 +130,7 @@ int BeamMPServerMain(MainArguments Arguments) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TConfig Config(ConfigPath);
|
TConfig Config(ConfigPath);
|
||||||
|
|
||||||
if (Config.Failed()) {
|
if (Config.Failed()) {
|
||||||
@@ -135,6 +141,24 @@ int BeamMPServerMain(MainArguments Arguments) {
|
|||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// override port if provided via arguments
|
||||||
|
if (Parser.FoundArgument({ "port" })) {
|
||||||
|
auto Port = Parser.GetValueOfArgument({ "port" });
|
||||||
|
if (Port.has_value()) {
|
||||||
|
auto P = int(std::strtoul(Port.value().c_str(), nullptr, 10));
|
||||||
|
if (P == 0 || P < 0 || P > UINT16_MAX) {
|
||||||
|
beammp_errorf("Custom port requested via --port is invalid: '{}'", Port.value());
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
Application::Settings.set(Settings::Key::General_Port, P);
|
||||||
|
beammp_info("Custom port requested via commandline arguments: " + Port.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.PrintDebug();
|
||||||
|
|
||||||
Application::InitializeConsole();
|
Application::InitializeConsole();
|
||||||
Application::Console().StartLoggingToFile();
|
Application::Console().StartLoggingToFile();
|
||||||
|
|
||||||
@@ -142,6 +166,9 @@ int BeamMPServerMain(MainArguments Arguments) {
|
|||||||
|
|
||||||
SetupSignalHandlers();
|
SetupSignalHandlers();
|
||||||
|
|
||||||
|
Settings settings {};
|
||||||
|
beammp_infof("Server name set in new impl: {}", settings.getAsString(Settings::Key::General_Name));
|
||||||
|
|
||||||
bool Shutdown = false;
|
bool Shutdown = false;
|
||||||
Application::RegisterShutdownHandler([&Shutdown] {
|
Application::RegisterShutdownHandler([&Shutdown] {
|
||||||
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
|
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
|
||||||
@@ -170,11 +197,7 @@ int BeamMPServerMain(MainArguments Arguments) {
|
|||||||
PPSMonitor.SetNetwork(Network);
|
PPSMonitor.SetNetwork(Network);
|
||||||
Application::CheckForUpdates();
|
Application::CheckForUpdates();
|
||||||
|
|
||||||
TPluginMonitor PluginMonitor(fs::path(Application::Settings.Resource) / "Server", LuaEngine);
|
TPluginMonitor PluginMonitor(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server", LuaEngine);
|
||||||
|
|
||||||
if (Application::Settings.HTTPServerEnabled) {
|
|
||||||
Http::Server::THttpServerInstance HttpServerInstance {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Application::SetSubsystemStatus("Main", Application::Status::Good);
|
Application::SetSubsystemStatus("Main", Application::Status::Good);
|
||||||
RegisterThread("Main(Waiting)");
|
RegisterThread("Main(Waiting)");
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
local function assert_eq(x, y, explain)
|
||||||
|
if x ~= y then
|
||||||
|
print("assertion '"..explain.."' failed:\n\tgot:\t", x, "\n\texpected:", y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param o1 any|table First object to compare
|
||||||
|
---@param o2 any|table Second object to compare
|
||||||
|
---@param ignore_mt boolean True to ignore metatables (a recursive function to tests tables inside tables)
|
||||||
|
function equals(o1, o2, ignore_mt)
|
||||||
|
if o1 == o2 then return true end
|
||||||
|
local o1Type = type(o1)
|
||||||
|
local o2Type = type(o2)
|
||||||
|
if o1Type ~= o2Type then return false end
|
||||||
|
if o1Type ~= 'table' then return false end
|
||||||
|
|
||||||
|
if not ignore_mt then
|
||||||
|
local mt1 = getmetatable(o1)
|
||||||
|
if mt1 and mt1.__eq then
|
||||||
|
--compare using built in method
|
||||||
|
return o1 == o2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local keySet = {}
|
||||||
|
|
||||||
|
for key1, value1 in pairs(o1) do
|
||||||
|
local value2 = o2[key1]
|
||||||
|
if value2 == nil or equals(value1, value2, ignore_mt) == false then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
keySet[key1] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for key2, _ in pairs(o2) do
|
||||||
|
if not keySet[key2] then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function assert_table_eq(x, y, explain)
|
||||||
|
if not equals(x, y, true) then
|
||||||
|
print("assertion '"..explain.."' failed:\n\tgot:\t", x, "\n\texpected:", y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_eq(Util.JsonEncode({1, 2, 3, 4, 5}), "[1,2,3,4,5]", "table to array")
|
||||||
|
assert_eq(Util.JsonEncode({"a", 1, 2, 3, 4, 5}), '["a",1,2,3,4,5]', "table to array")
|
||||||
|
assert_eq(Util.JsonEncode({"a", 1, 2.0, 3, 4, 5}), '["a",1,2.0,3,4,5]', "table to array")
|
||||||
|
assert_eq(Util.JsonEncode({hello="world", john={doe = 1, jane = 2.5, mike = {2, 3, 4}}, dave={}}), '{"dave":{},"hello":"world","john":{"doe":1,"jane":2.5,"mike":[2,3,4]}}', "table to obj")
|
||||||
|
assert_eq(Util.JsonEncode({a = nil}), "{}", "null obj member")
|
||||||
|
assert_eq(Util.JsonEncode({1, nil, 3}), "[1,3]", "null array member")
|
||||||
|
assert_eq(Util.JsonEncode({}), "{}", "empty array/table")
|
||||||
|
assert_eq(Util.JsonEncode({1234}), "[1234]", "int")
|
||||||
|
assert_eq(Util.JsonEncode({1234.0}), "[1234.0]", "double")
|
||||||
|
|
||||||
|
assert_table_eq(Util.JsonDecode("[1,2,3,4,5]"), {1, 2, 3, 4, 5}, "decode table to array")
|
||||||
|
assert_table_eq(Util.JsonDecode('["a",1,2,3,4,5]'), {"a", 1, 2, 3, 4, 5}, "decode table to array")
|
||||||
|
assert_table_eq(Util.JsonDecode('["a",1,2.0,3,4,5]'), {"a", 1, 2.0, 3, 4, 5}, "decode table to array")
|
||||||
|
assert_table_eq(Util.JsonDecode('{"dave":{},"hello":"world","john":{"doe":1,"jane":2.5,"mike":[2,3,4]}}'), {hello="world", john={doe = 1, jane = 2.5, mike = {2, 3, 4}}, dave={}}, "decode table to obj")
|
||||||
|
assert_table_eq(Util.JsonDecode("{}"), {a = nil}, "decode null obj member")
|
||||||
|
assert_table_eq(Util.JsonDecode("[1,3]"), {1, 3}, "decode null array member")
|
||||||
|
assert_table_eq(Util.JsonDecode("{}"), {}, "decode empty array/table")
|
||||||
|
assert_table_eq(Util.JsonDecode("[1234]"), {1234}, "decode int")
|
||||||
|
assert_table_eq(Util.JsonDecode("[1234.0]"), {1234.0}, "decode double")
|
||||||
+1
-1
Submodule vcpkg updated: 8397227251...6978381401
+1
-2
@@ -13,7 +13,6 @@
|
|||||||
"nlohmann-json",
|
"nlohmann-json",
|
||||||
"openssl",
|
"openssl",
|
||||||
"rapidjson",
|
"rapidjson",
|
||||||
"sol2",
|
"sol2"
|
||||||
"toml11"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user