Add support for Rust code in DDNet

The glue is done using the [cxx crate](https://cxx.rs/) on the Rust
side.

As a proof-of-concept, only a small console command (`rust_version`)
printing the currently used Rust version was added.

You can generate and open the Rust documentation using
`DDNET_TEST_NO_LINK=1 cargo doc --open`.

You can run the Rust tests using `cmake --build <build dir> --target
run_rust_tests`, they're automatically included in the `run_tests`
target as well.

Rust tests don't work on Windows in debug mode on Windows because Rust
cannot currently link with the debug version of the C stdlib on Windows:
https://github.com/rust-lang/rust/issues/39016.

---

The stuff in `src/rust-bridge` is generated using
```
cxxbridge src/engine/shared/rust_version.rs --output src/rust-bridge/engine/shared/rust_version.cpp --output src/rust-bridge/engine/shared/rust_version.h
cxxbridge src/engine/console.rs --output src/rust-bridge/cpp/console.cpp --output src/rust-bridge/cpp/console.h
```
This commit is contained in:
heinrich5991 2022-10-19 23:46:06 +02:00
parent 1b114b2565
commit dcd76fd3e1
48 changed files with 1914 additions and 61 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[target.'cfg(target_env = "msvc")']
rustflags = ["-C", "target-feature=+crt-static"]

View File

@ -25,10 +25,9 @@ jobs:
cmake-path: /usr/bin/
cmake-args: -G Ninja -DTEST_MYSQL=ON
cmake-init-env: CXXFLAGS=-Werror
gtest-env: GTEST_FILTER=-*SQLite*
package-file: "*-linux_x86_64.tar.xz"
fancy: false
env:
GTEST_FILTER: -*SQLite*
- os: macOS-latest
cmake-args: -G Ninja
cmake-init-env: CXXFLAGS=-Werror
@ -55,6 +54,7 @@ jobs:
- name: Prepare Linux (non-fancy)
if: ${{ contains(matrix.os, 'ubuntu') && !matrix.fancy }}
run: |
rustup default 1.48.0
sudo rm -rf /var/lib/mysql/ /var/run/mysqld
sudo mkdir /var/lib/mysql/ /var/run/mysqld/
sudo chown mysql:mysql /var/lib/mysql/ /var/run/mysqld/
@ -93,7 +93,6 @@ jobs:
sudo rm -rf /Library/Developer/CommandLineTools
- name: Build in debug mode
env: ${{ matrix.env }}
run: |
mkdir debug
cd debug
@ -101,19 +100,16 @@ jobs:
${{ matrix.cmake-path }}cmake --build . --config Debug --target everything ${{ matrix.build-args }}
- name: Test debug
env: ${{ matrix.env }}
run: |
cd debug
${{ matrix.cmake-path }}cmake --build . --config Debug --target run_tests ${{ matrix.build-args }}
${{ matrix.cmake-path }}cmake -E env ${{ matrix.gtest-env }} ${{ matrix.cmake-path }}cmake --build . --config Debug --target run_tests ${{ matrix.build-args }}
- name: Run debug server
env: ${{ matrix.env }}
run: |
cd debug
./DDNet-Server shutdown
- name: Build in release mode
env: ${{ matrix.env }}
run: |
mkdir release
cd release
@ -121,20 +117,17 @@ jobs:
${{ matrix.cmake-path }}cmake --build . --config Release --target everything ${{ matrix.build-args }}
- name: Test release
env: ${{ matrix.env }}
run: |
cd release
${{ matrix.cmake-path }}cmake --build . --config Release --target run_tests ${{ matrix.build-args }}
${{ matrix.cmake-path }}cmake -E env ${{ matrix.gtest-env }} ${{ matrix.cmake-path }}cmake --build . --config Release --target run_tests ${{ matrix.build-args }}
- name: Run release server
env: ${{ matrix.env }}
run: |
cd release
./DDNet-Server shutdown
- name: Build headless client
if: contains(matrix.os, 'ubuntu-latest')
env: ${{ matrix.env }}
run: |
mkdir headless
cd headless
@ -178,7 +171,6 @@ jobs:
- name: Build in release mode with debug info and all features on
if: matrix.fancy
env: ${{ matrix.env }}
run: |
mkdir fancy
cd fancy
@ -187,14 +179,13 @@ jobs:
- name: Test fancy
if: matrix.fancy
env: ${{ matrix.env }}
run: |
find /usr/lib/ -name '*libwebsockets*'
cd fancy
${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target run_tests ${{ matrix.build-args }}
${{ matrix.cmake-path }}cmake -E env ${{ matrix.gtest-env }} ${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target run_tests ${{ matrix.build-args }}
- name: Run fancy server
if: matrix.fancy
env: ${{ matrix.env }}
run: |
cd fancy
./DDNet-Server shutdown

42
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Check Rust
on:
push:
branches-ignore:
- staging.tmp
- trying.tmp
- staging-squash-merge.tmp
pull_request:
jobs:
rustdoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Rustdoc
run: |
RUSTDOCFLAGS=-Dwarnings DDNET_TEST_NO_LINK=1 cargo doc
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Rustfmt
run:
cargo fmt -- --check
cargo-deny:
runs-on: ubuntu-latest
strategy:
matrix:
checks:
- advisories
- bans licenses sources
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@v2
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check ${{ matrix.checks }}

5
.gitignore vendored
View File

@ -11,6 +11,7 @@ bundle/
.DS_Store
.ninja_deps
.ninja_log
CACHEDIR.TAG
CMakeCache.txt
CMakeFiles
CMakeSettings*
@ -20,10 +21,12 @@ CTestTestfile.cmake
Debug
Makefile
Release
SAN.*
_CPack_Packages/
build.ninja
checksummed_*
cmake_install.cmake
debug
gmock.pc
gmock_main.pc
googletest-build/
@ -34,8 +37,8 @@ gtest_main.pc
install_manifest*.txt
ninja_package
pack_*/
release
rules.ninja
SAN.*
testrunner\[1\]_include.cmake
vulkan_shaders_sha256.txt

View File

@ -547,6 +547,7 @@ find_package(Opus)
find_package(Opusfile)
find_package(PNG)
find_package(PythonInterp 3)
find_package(Rust)
find_package(SDL2)
find_package(SQLite3)
if(DISCORD)
@ -590,6 +591,7 @@ if(TARGET_OS STREQUAL "mac")
endif()
message(STATUS ${TARGET})
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
message(STATUS "Rust version: ${RUST_VERSION_STRING}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Dependencies:")
@ -654,6 +656,9 @@ endif()
if(NOT(PYTHONINTERP_FOUND))
message(SEND_ERROR "You must install Python to compile ${CMAKE_PROJECT_NAME}")
endif()
if(NOT(RUST_FOUND))
message(SEND_ERROR "You must install Rust and Cargo to compile ${CMAKE_PROJECT_NAME}")
endif()
if(NOT(SQLite3_FOUND))
message(SEND_ERROR "You must install SQLite3 to compile ${CMAKE_PROJECT_NAME}")
endif()
@ -721,7 +726,12 @@ endif()
if(TARGET_OS STREQUAL "windows")
set(PLATFORM_CLIENT)
set(PLATFORM_CLIENT_LIBS opengl32 winmm)
set(PLATFORM_LIBS shlwapi version ws2_32) # Windows sockets
set(PLATFORM_LIBS)
list(APPEND PLATFORM_LIBS shlwapi) # PathIsRelativeW
list(APPEND PLATFORM_LIBS version ws2_32) # Windows sockets
list(APPEND PLATFORM_LIBS bcrypt userenv) # for Rust (https://github.com/rust-lang/rust/issues/91974)
list(APPEND PLATFORM_LIBS ole32) # CoInitialize(Ex)
list(APPEND PLATFORM_LIBS shell32)
elseif(TARGET_OS STREQUAL "mac")
find_library(CARBON Carbon)
find_library(COCOA Cocoa)
@ -753,7 +763,9 @@ else()
set(PLATFORM_CLIENT_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR} ${NOTIFY_INCLUDE_DIRS})
set(PLATFORM_CLIENT)
if(TARGET_OS STREQUAL "linux")
set(PLATFORM_LIBS rt) # clock_gettime for glibc < 2.17
set(PLATFORM_LIBS)
list(APPEND PLATFORM_LIBS rt) # clock_gettime for glibc < 2.17
list(APPEND PLATFORM_LIBS dl) # for Rust
else()
set(PLATFORM_LIBS)
endif()
@ -834,6 +846,161 @@ if(NOT CRYPTO_FOUND)
set(DEP_MD5 $<TARGET_OBJECTS:md5>)
endif()
########################################################################
# RUST
########################################################################
set_glob(RUST_BASE GLOB_RECURSE "rs;toml" src/base
Cargo.toml
color.rs
lib.rs
rust.rs
)
set_glob(RUST_ENGINE_INTERFACE GLOB "rs;toml" src/engine
Cargo.toml
console.rs
lib.rs
)
set_glob(RUST_ENGINE_SHARED GLOB_RECURSE "rs;toml" src/engine/shared
Cargo.toml
build.rs
config.rs
lib.rs
rust_version.rs
)
set_src(RUST_BRIDGE_SHARED GLOB_RECURSE src/rust-bridge
cpp/console.cpp
cpp/console.h
engine/shared/rust_version.cpp
engine/shared/rust_version.h
)
set_glob(RUST_MASTERSRV GLOB "rs;toml" src/mastersrv/src
addr.rs
locations.rs
main.rs
)
add_library(rust-bridge-shared EXCLUDE_FROM_ALL OBJECT ${RUST_BRIDGE_SHARED})
list(APPEND TARGETS_OWN rust-bridge-shared)
set(CARGO_BUILD_DIR "")
set(CARGO_BUILD ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${PROJECT_BINARY_DIR} DDNET_TEST_NO_LINK=1 ${RUST_CARGO} build)
set(CARGO_TEST ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${PROJECT_BINARY_DIR} ${RUST_CARGO} test)
if(MSVC)
list(INSERT CARGO_BUILD 0 ${CMAKE_COMMAND} -E env $<$<CONFIG:Debug>:CFLAGS=/MTd> $<$<CONFIG:Debug>:CXXFLAGS=/MTd>)
list(INSERT CARGO_TEST 0 ${CMAKE_COMMAND} -E env RUSTFLAGS=-Ctarget-feature=+crt-static)
endif()
if(RUST_NIGHTLY)
list(APPEND CARGO_BUILD -Z build-std=std,panic_abort)
endif()
if(NOT CMAKE_OSX_ARCHITECTURES AND (DEFINED CMAKE_RUST_COMPILER_TARGET OR RUST_NIGHTLY))
if(DEFINED CMAKE_RUST_COMPILER_TARGET)
list(APPEND CARGO_TEST --target ${CMAKE_RUST_COMPILER_TARGET})
set(RUST_TARGET ${CMAKE_RUST_COMPILER_TARGET})
else()
set(RUST_TARGET ${RUST_TARGET_HOST})
endif()
list(APPEND CARGO_BUILD --target ${RUST_TARGET})
set(CARGO_BUILD_DIR "${RUST_TARGET}/")
endif()
set(CARGO_BUILD_DIR_DEBUG "${CARGO_BUILD_DIR}debug")
set(CARGO_BUILD_DIR_RELEASE "${CARGO_BUILD_DIR}release")
if(GENERATOR_IS_MULTI_CONFIG)
if(CMAKE_VERSION VERSION_LESS 3.20)
message(SEND_ERROR "Multi-config generators only supported from CMake 3.20 and up")
else()
set(CARGO_BUILD_DIR "${CARGO_BUILD_DIR}$<$<CONFIG:Debug>:debug>$<$<NOT:$<CONFIG:Debug>>:release>")
endif()
else()
if(CMAKE_BUILD_TYPE STREQUAL Debug)
set(CARGO_BUILD_DIR "${CARGO_BUILD_DIR_DEBUG}")
else()
set(CARGO_BUILD_DIR "${CARGO_BUILD_DIR_RELEASE}")
endif()
endif()
list(APPEND CARGO_BUILD $<$<NOT:$<CONFIG:Debug>>:--release>)
if(CMAKE_OSX_ARCHITECTURES)
set(RUST_OSX_ARCHITECTURES)
foreach(arch ${CMAKE_OSX_ARCHITECTURES})
if(${arch} STREQUAL arm64)
list(APPEND RUST_OSX_ARCHITECTURES aarch64-apple-darwin)
elseif(${arch} STREQUAL x86_64)
list(APPEND RUST_OSX_ARCHITECTURES x86_64-apple-darwin)
else()
message(SEND_ERROR "CMAKE_OSX_ARCHITECTURES' architecture ${arch} unknown, can't build Rust code (known: arm64, x86_64)")
endif()
endforeach()
endif()
set(RUST_SRC
${RUST_BASE}
${RUST_ENGINE_INTERFACE}
${RUST_ENGINE_SHARED}
Cargo.toml
Cargo.lock
)
set(RUST_TARGETS engine_shared)
if(NOT CMAKE_OSX_ARCHITECTURES)
set(RUST_OUTPUTS)
foreach(rust_target ${RUST_TARGETS})
set(LIBRARY_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}ddnet_${rust_target}${CMAKE_STATIC_LIBRARY_SUFFIX}")
add_library(rust_${rust_target} STATIC IMPORTED GLOBAL)
add_custom_target(rust_${rust_target}_target DEPENDS "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR}/${LIBRARY_NAME}")
add_dependencies(rust_${rust_target} rust_${rust_target}_target)
set_target_properties(rust_${rust_target} PROPERTIES
IMPORTED_LOCATION "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR_RELEASE}/${LIBRARY_NAME}"
IMPORTED_LOCATION_DEBUG "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR_DEBUG}/${LIBRARY_NAME}"
)
list(APPEND RUST_OUTPUTS "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR}/${LIBRARY_NAME}")
endforeach()
add_custom_command(
OUTPUT ${RUST_OUTPUTS}
COMMAND ${CARGO_BUILD}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
USES_TERMINAL
DEPENDS ${RUST_SRC}
)
else()
foreach(rust_target ${RUST_TARGETS})
set(LIBRARY_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}ddnet_${rust_target}${CMAKE_STATIC_LIBRARY_SUFFIX}")
add_library(rust_${rust_target} STATIC IMPORTED GLOBAL)
set_target_properties(rust_${rust_target} PROPERTIES
IMPORTED_LOCATION "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR_RELEASE}/${LIBRARY_NAME}"
IMPORTED_LOCATION_DEBUG "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR_DEBUG}/${LIBRARY_NAME}"
)
add_custom_target(rust_${rust_target}_target DEPENDS "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR}/${LIBRARY_NAME}")
add_dependencies(rust_${rust_target} rust_${rust_target}_target)
set(ARCH_LIBRARIES)
foreach(arch ${RUST_OSX_ARCHITECTURES})
list(APPEND ARCH_LIBRARIES "${PROJECT_BINARY_DIR}/${arch}/${CARGO_BUILD_DIR}/${LIBRARY_NAME}")
endforeach()
add_custom_command(
OUTPUT "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR}/${LIBRARY_NAME}"
COMMAND lipo ${ARCH_LIBRARIES} -create -output "${PROJECT_BINARY_DIR}/${CARGO_BUILD_DIR}/${LIBRARY_NAME}"
DEPENDS ${ARCH_LIBRARIES}
)
endforeach()
foreach(arch ${RUST_OSX_ARCHITECTURES})
set(RUST_OUTPUTS)
foreach(rust_target ${RUST_TARGETS})
set(LIBRARY_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}ddnet_${rust_target}${CMAKE_STATIC_LIBRARY_SUFFIX}")
list(APPEND RUST_OUTPUTS "${PROJECT_BINARY_DIR}/${arch}/${CARGO_BUILD_DIR}/${LIBRARY_NAME}")
endforeach()
add_custom_command(
OUTPUT ${RUST_OUTPUTS}
COMMAND ${CARGO_BUILD} --target=${arch}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
USES_TERMINAL
DEPENDS ${RUST_SRC}
)
endforeach()
endif()
########################################################################
# DATA
########################################################################
@ -1703,6 +1870,7 @@ set_src(BASE GLOB_RECURSE src/base
log.h
logger.h
math.h
rust.h
system.cpp
system.h
tl/threading.h
@ -1733,6 +1901,7 @@ set_src(ENGINE_INTERFACE GLOB src/engine
keys.h
map.h
message.h
rust.h
server.h
serverbrowser.h
sound.h
@ -2131,7 +2300,6 @@ if(CLIENT)
# Libraries
set(LIBS_CLIENT
${LIBS}
${FREETYPE_LIBRARIES}
${GLEW_LIBRARIES}
${PNG_LIBRARIES}
@ -2147,12 +2315,10 @@ if(CLIENT)
${VULKAN_LIBRARIES}
${TARGET_STEAMAPI}
rust_engine_shared
${PLATFORM_CLIENT_LIBS}
# Add pthreads (on non-Windows) at the end, so that other libraries can depend
# on it.
${CMAKE_THREAD_LIBS_INIT}
${LIBS}
)
if(DISCORD)
@ -2188,6 +2354,7 @@ if(CLIENT)
$<TARGET_OBJECTS:engine-gfx>
$<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared>
$<TARGET_OBJECTS:rust-bridge-shared>
)
else()
add_executable(${TARGET_CLIENT} WIN32
@ -2198,6 +2365,7 @@ if(CLIENT)
$<TARGET_OBJECTS:engine-gfx>
$<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared>
$<TARGET_OBJECTS:rust-bridge-shared>
)
endif()
target_link_libraries(${TARGET_CLIENT} ${LIBS_CLIENT})
@ -2370,13 +2538,12 @@ if(SERVER)
# Libraries
set(LIBS_SERVER
${LIBS}
${MINIUPNPC_LIBRARIES}
${MYSQL_LIBRARIES}
${TARGET_ANTIBOT}
${MINIUPNPC_LIBRARIES}
# Add pthreads (on non-Windows) at the end, so that other libraries can depend
# on it.
${CMAKE_THREAD_LIBS_INIT}
rust_engine_shared
${LIBS}
)
# Target
@ -2387,6 +2554,7 @@ if(SERVER)
${SERVER_ICON}
$<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared>
$<TARGET_OBJECTS:rust-bridge-shared>
)
target_link_libraries(${TARGET_SERVER} ${LIBS_SERVER})
target_include_directories(${TARGET_SERVER} PRIVATE ${PNG_INCLUDE_DIRS})
@ -2439,7 +2607,7 @@ if(TOOLS)
set(TOOL_LIBS ${LIBS})
if(TOOL MATCHES "^(dilate|map_convert_07|map_create_pixelart|map_optimize|map_extract|map_replace_image)$")
list(APPEND TOOL_INCLUDE_DIRS ${PNG_INCLUDE_DIRS})
list(APPEND TOOL_DEPS $<TARGET_OBJECTS:engine-gfx>)
list(APPEND TOOL_DEPS $<TARGET_OBJECTS:engine-gfx>)
list(APPEND TOOL_LIBS ${PNG_LIBRARIES})
endif()
if(TOOL MATCHES "^config_")
@ -2571,7 +2739,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
$<TARGET_OBJECTS:game-shared>
${DEPS}
)
target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${MYSQL_LIBRARIES} ${PNG_LIBRARIES} ${GTEST_LIBRARIES})
target_link_libraries(${TARGET_TESTRUNNER} ${MYSQL_LIBRARIES} ${PNG_LIBRARIES} ${GTEST_LIBRARIES} ${LIBS})
target_include_directories(${TARGET_TESTRUNNER} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS})
list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER})
@ -2583,8 +2751,37 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
DEPENDS ${TARGET_TESTRUNNER}
USES_TERMINAL
)
if(NOT MSVC OR CMAKE_BUILD_TYPE STREQUAL Release)
# On MSVC, Rust tests only work in the release mode because we link our C++
# code with the debug C standard library (/MTd) but Rust only supports
# linking to the release C standard library (/MT).
#
# See also https://github.com/rust-lang/rust/issues/39016.
add_dependencies(run_tests run_rust_tests)
endif()
endif()
add_library(rust_test STATIC EXCLUDE_FROM_ALL
$<TARGET_OBJECTS:engine-gfx>
$<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared>
$<TARGET_OBJECTS:rust-bridge-shared>
${DEPS}
)
list(APPEND TARGETS_OWN rust_test)
list(APPEND TARGETS_LINK rust_test)
set(RUST_TEST_LIBS ${LIBS} $<TARGET_FILE:rust_test>)
list(REMOVE_ITEM RUST_TEST_LIBS "-pthread")
add_custom_target(run_rust_tests
COMMAND ${CMAKE_COMMAND} -E env "DDNET_TEST_LIBRARIES=${RUST_TEST_LIBS}" ${CARGO_TEST}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
USES_TERMINAL
DEPENDS rust_test
VERBATIM
)
add_custom_target(run_integration_tests
COMMAND ${PROJECT_BINARY_DIR}/integration_test.sh ${INTEGRATIONTESTRUNNER_ARGS}
COMMENT Running integration tests
@ -3060,6 +3257,7 @@ foreach(target ${TARGETS_OWN})
endif()
target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR}/src)
target_include_directories(${target} PRIVATE src)
target_include_directories(${target} PRIVATE src/rust-bridge)
target_compile_definitions(${target} PRIVATE $<$<CONFIG:Debug>:CONF_DEBUG>)
target_include_directories(${target} SYSTEM PRIVATE ${CURL_INCLUDE_DIRS} ${SQLite3_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
target_compile_definitions(${target} PRIVATE GLEW_STATIC)

114
Cargo.lock generated Normal file
View File

@ -0,0 +1,114 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cxx"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5469a6f42296f4fd40789b397383718f9a0bd75d2f9b7cedbb249996811fba27"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fef2b4ffdc935c973bc7817d541fc936fdc8a85194cfdd9c761aca8387edd48"
[[package]]
name = "cxxbridge-macro"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d3a240a54f5526967ffae81fdcda1fc80564964220d90816960b2eae2eab7f4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ddnet-base"
version = "0.0.1"
dependencies = [
"cxx",
"ddnet-test",
]
[[package]]
name = "ddnet-engine"
version = "0.0.1"
dependencies = [
"cxx",
"ddnet-base",
"ddnet-engine-shared",
"ddnet-test",
]
[[package]]
name = "ddnet-engine-shared"
version = "0.0.1"
dependencies = [
"cxx",
"ddnet-base",
"ddnet-engine",
"ddnet-test",
]
[[package]]
name = "ddnet-test"
version = "0.0.1"
[[package]]
name = "link-cplusplus"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cae2cd7ba2f3f63938b9c724475dfb7b9861b545a90324476324ed21dbc8c8"
dependencies = [
"cc",
]
[[package]]
name = "proc-macro2"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"

15
Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[workspace]
members = [
"src/base",
"src/engine",
"src/engine/shared",
"src/rust-bridge/test",
]
[profile.dev]
panic = "abort"
[profile.release]
lto = "thin"
panic = "abort"

View File

@ -185,7 +185,7 @@ Expect a large slow down.
Building on Windows with Visual Studio
--------------------------------------
Download and install some version of [Microsoft Visual Studio](https://www.visualstudio.com/) (as of writing, MSVS Community 2017) with **C++ support**, install [Python 3](https://www.python.org/downloads/) **for all users** and install [CMake](https://cmake.org/download/#latest).
Download and install some version of [Microsoft Visual Studio](https://www.visualstudio.com/) (as of writing, MSVS Community 2017) with **C++ support**, install [Python 3](https://www.python.org/downloads/) **for all users** and install [CMake](https://cmake.org/download/#latest). You also need to install [Rust](https://rustup.rs/).
Start CMake and select the source code folder (where DDNet resides, the directory with `CMakeLists.txt`). Additionally select a build folder, e.g. create a build subdirectory in the source code directory. Click "Configure" and select the Visual Studio generator (it should be pre-selected, so pressing "Finish" will suffice). After configuration finishes and the "Generate" reactivates, click it. When that finishes, click "Open Project". Visual Studio should open. You can compile the DDNet client by right-clicking the DDNet project (not the solution) and select "Select as StartUp project". Now you should be able to compile DDNet by clicking the green, triangular "Run" button.

29
cmake/FindRust.cmake Normal file
View File

@ -0,0 +1,29 @@
find_program(RUST_RUSTC rustc)
find_program(RUST_CARGO cargo)
if(RUST_RUSTC)
execute_process(COMMAND ${RUST_RUSTC} --version --verbose OUTPUT_VARIABLE RUSTC_VERSION_OUTPUT)
string(REPLACE "\n" ";" RUSTC_VERSION_OUTPUT "${RUSTC_VERSION_OUTPUT}")
set(RUST_NIGHTLY OFF)
foreach(line ${RUSTC_VERSION_OUTPUT})
if(NOT DEFINED RUST_VERSION_STRING)
set(RUST_VERSION_STRING ${line})
endif()
if(line MATCHES "^([^:]+): (.*)$")
set(KEY ${CMAKE_MATCH_1})
set(VALUE ${CMAKE_MATCH_2})
if(KEY STREQUAL "release")
set(RUST_VERSION ${VALUE})
if(VALUE MATCHES "nightly")
set(RUST_NIGHTLY ON)
endif()
elseif(KEY STREQUAL "host")
set(RUST_TARGET_HOST ${VALUE})
endif()
endif()
endforeach()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Rust DEFAULT_MSG RUST_RUSTC RUST_CARGO)
mark_as_advanced(RUST_RUSTC RUST_CARGO)

View File

@ -35,3 +35,4 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -D_REENTRANT -g -O3 ${WASM_CXX_
set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -pthread -D_REENTRANT -g -O3 ${WASM_CXX_ENGINE_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread ${WASM_ENGINE_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${WASM_ENGINE_FLAGS}")
set(CMAKE_RUST_COMPILER_TARGET wasm32-unknown-emscripten)

View File

@ -11,6 +11,7 @@ set(CMAKE_C_COMPILER oa64-clang)
set(CMAKE_CXX_COMPILER oa64-clang++)
set(CMAKE_INSTALL_NAME_TOOL aarch64-apple-$ENV{OSXCROSS_TARGET}-install_name_tool)
set(CMAKE_OTOOL aarch64-apple-$ENV{OSXCROSS_TARGET}-otool)
set(CMAKE_RUST_COMPILER_TARGET aarch64-apple-darwin)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

View File

@ -11,6 +11,7 @@ set(CMAKE_C_COMPILER o64-clang)
set(CMAKE_CXX_COMPILER o64-clang++)
set(CMAKE_INSTALL_NAME_TOOL x86_64-apple-$ENV{OSXCROSS_TARGET}-install_name_tool)
set(CMAKE_OTOOL x86_64-apple-$ENV{OSXCROSS_TARGET}-otool)
set(CMAKE_RUST_COMPILER_TARGET x86_64-apple-darwin)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

View File

@ -3,6 +3,7 @@ set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
set(CMAKE_RUST_COMPILER_TARGET i686-pc-windows-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

View File

@ -3,6 +3,7 @@ set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_RUST_COMPILER_TARGET x86_64-pc-windows-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

6
deny.toml Normal file
View File

@ -0,0 +1,6 @@
[licenses]
allow = [
"Apache-2.0",
"MIT",
"Zlib"
]

View File

@ -6,9 +6,9 @@ os.chdir(os.path.dirname(__file__) + "/..")
PATH = "src/"
EXCEPTIONS = [
"src/base/unicode/confusables.h",
"src/base/unicode/confusables.h",
"src/base/unicode/confusables_data.h",
"src/base/unicode/tolower.h",
"src/base/unicode/tolower.h",
"src/base/unicode/tolower_data.h",
"src/tools/config_common.h"
]
@ -40,7 +40,7 @@ def check_dir(directory):
for file in file_list:
path = directory + file
if os.path.isdir(path):
if file not in ("external", "generated"):
if file not in ("external", "generated", "rust-bridge"):
errors += check_dir(path + "/")
elif file.endswith(".h") and file != "keynames.h":
errors += check_file(path)

View File

@ -20,7 +20,8 @@ IGNORE_FILES = [
def filter_ignored(filenames):
return [filename for filename in filenames
if filename not in IGNORE_FILES
and not filename.startswith("src/game/generated/")]
and not filename.startswith("src/game/generated/")
and not filename.startswith("src/rust-bridge")]
def filter_cpp(filenames):
return [filename for filename in filenames

15
src/base/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "ddnet-base"
version = "0.0.1"
edition = "2018"
publish = false
license = "Zlib"
[lib]
path = "lib.rs"
[dependencies]
cxx = "1.0"
[dev-dependencies]
ddnet-test = { path = "../rust-bridge/test", features = ["link-test-libraries"] }

77
src/base/color.rs Normal file
View File

@ -0,0 +1,77 @@
/// Color, in RGBA format. Corresponds to the C++ type `ColorRGBA`.
///
/// The color is represented by red, green, blue and alpha values between `0.0`
/// and `1.0`.
///
/// See also <https://en.wikipedia.org/wiki/RGBA_color_model>.
///
/// # Examples
///
/// ```
/// use ddnet_base::ColorRGBA;
///
/// let white = ColorRGBA { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
/// let black = ColorRGBA { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
/// let red = ColorRGBA { r: 1.0, g: 0.0, b: 0.0, a: 1.0 };
/// let transparent = ColorRGBA { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
///
/// // #ffa500
/// let ddnet_logo_color = ColorRGBA { r: 1.0, g: 0.6470588235294118, b: 0.0, a: 1.0 };
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(C)]
pub struct ColorRGBA {
/// Red
pub r: f32,
/// Green
pub g: f32,
/// Blue
pub b: f32,
/// Alpha (i.e. opacity. `0.0` means fully transparent, `1.0`
/// nontransparent).
pub a: f32,
}
unsafe impl cxx::ExternType for ColorRGBA {
type Id = cxx::type_id!("ColorRGBA");
type Kind = cxx::kind::Trivial;
}
/// Color, in HSLA format. Corresponds to the C++ type `ColorHSLA`.
///
/// The color is represented by hue, saturation, lightness and alpha values
/// between `0.0` and `1.0`.
///
/// See also <https://en.wikipedia.org/wiki/HSL_and_HSV>.
///
/// # Examples
///
/// ```
/// use ddnet_base::ColorHSLA;
///
/// let white = ColorHSLA { h: 0.0, s: 0.0, l: 1.0, a: 1.0 };
/// let black = ColorHSLA { h: 0.0, s: 0.0, l: 0.0, a: 1.0 };
/// let red = ColorHSLA { h: 0.0, s: 1.0, l: 0.5, a: 1.0 };
/// let transparent = ColorHSLA { h: 0.0, s: 0.0, l: 0.0, a: 0.0 };
///
/// // #ffa500
/// let ddnet_logo_color = ColorHSLA { h: 0.10784314, s: 1.0, l: 0.5, a: 1.0 };
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(C)]
pub struct ColorHSLA {
/// Hue
pub h: f32,
/// Saturation
pub s: f32,
/// Lightness
pub l: f32,
/// Alpha (i.e. opacity. `0.0` means fully transparent, `1.0`
/// nontransparent).
pub a: f32,
}
unsafe impl cxx::ExternType for ColorHSLA {
type Id = cxx::type_id!("ColorHSLA");
type Kind = cxx::kind::Trivial;
}

19
src/base/lib.rs Normal file
View File

@ -0,0 +1,19 @@
//! DDNet's base library, Rust part.
//!
//! DDNet's code base is separated into three major parts, `base`, `engine` and
//! `game`.
//!
//! The base library consists of operating system abstractions, and
//! game-independent data structures such as color handling and math vectors.
//! Additionally, it contains some types to support the C++-Rust-translation.
#![warn(missing_docs)]
#[cfg(test)]
extern crate ddnet_test;
mod color;
mod rust;
pub use color::*;
pub use rust::*;

5
src/base/rust.h Normal file
View File

@ -0,0 +1,5 @@
#ifndef BASE_RUST_H
#define BASE_RUST_H
typedef const char *StrRef;
typedef void *UserPtr;
#endif // BASE_RUST_H

268
src/base/rust.rs Normal file
View File

@ -0,0 +1,268 @@
use std::cmp;
use std::ffi::CStr;
use std::fmt;
use std::marker::PhantomData;
use std::ops;
use std::os::raw::c_char;
use std::ptr;
use std::str;
/// User pointer, as used in callbacks. Corresponds to the C++ type `void *`.
///
/// Callbacks in C are usually represented by a function pointer and some
/// "userdata" pointer that is also passed to the function pointer. This allows
/// to hand data to the callback. This type represents such a userdata poiner.
///
/// It is `unsafe` to convert the `UserPtr` back to its original pointer using
/// [`UserPtr::cast`] because its lifetime and type information was lost.
///
/// When dealing with Rust code exclusively, closures are preferred.
///
/// # Examples
///
/// ```
/// use ddnet_base::UserPtr;
///
/// struct CallbackData {
/// favorite_color: &'static str,
/// }
///
/// let data = CallbackData {
/// favorite_color: "green",
/// };
///
/// callback(UserPtr::from(&data));
///
/// fn callback(pointer: UserPtr) {
/// let data: &CallbackData = unsafe { pointer.cast() };
/// println!("favorite color: {}", data.favorite_color);
/// }
/// ```
#[repr(transparent)]
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct UserPtr(*mut ());
unsafe impl cxx::ExternType for UserPtr {
type Id = cxx::type_id!("UserPtr");
type Kind = cxx::kind::Trivial;
}
impl UserPtr {
/// Create a null `UserPtr`.
///
/// # Examples
///
/// ```
/// use ddnet_base::UserPtr;
///
/// // Can't do anything useful with this.
/// let _user = UserPtr::null();
/// ```
pub fn null() -> UserPtr {
UserPtr(ptr::null_mut())
}
/// Cast `UserPtr` back to a reference to its real type.
///
/// # Safety
///
/// The caller is responsible for checking type and lifetime correctness.
/// Also, they must make sure that there are only immutable references or at
/// most one mutable reference live at the same time.
///
/// # Examples
///
/// ```
/// use ddnet_base::UserPtr;
///
/// let the_answer = 42;
/// let user = UserPtr::from(&the_answer);
///
/// assert_eq!(unsafe { *user.cast::<i32>() }, 42);
/// ```
pub unsafe fn cast<T>(&self) -> &T {
&*(self.0 as *const _)
}
/// Cast `UserPtr` back to a mutable reference to its real type.
///
/// See [`UserPtr`] documentation for details and an example.
///
/// # Safety
///
/// The caller is responsible for checking type and lifetime correctness.
/// Also, they must make sure that there are only immutable references or at
/// most one mutable reference live at the same time.
///
/// # Examples
///
/// ```
/// use ddnet_base::UserPtr;
///
/// let mut seen_it = false;
/// let mut user = UserPtr::from(&mut seen_it);
///
/// unsafe {
/// *user.cast_mut() = true;
/// }
///
/// assert_eq!(seen_it, true);
/// ```
pub unsafe fn cast_mut<T>(&mut self) -> &mut T {
&mut *(self.0 as *mut _)
}
}
impl<'a, T> From<&'a T> for UserPtr {
fn from(t: &'a T) -> UserPtr {
UserPtr(t as *const _ as *mut _)
}
}
impl<'a, T> From<&'a mut T> for UserPtr {
fn from(t: &'a mut T) -> UserPtr {
UserPtr(t as *mut _ as *mut _)
}
}
/// C-style string pointer to UTF-8 data. Corresponds to the C++ type `const
/// char *`.
///
/// The lifetime is the lifetime of the underlying string.
///
/// This is a separate type from [`std::ffi::CStr`] because that type is not
/// FFI-safe and does not guarantee UTF-8.
///
/// In Rust code, [`String`] is preferred. For constructing C strings,
/// [`std::ffi::CString`] or this crate's [`s!`](`crate::s!`) macro can be used.
///
/// # Examples
///
/// ```
/// # fn some_c_function(_: StrRef<'_>) {}
/// use ddnet_base::StrRef;
/// use ddnet_base::s;
/// use std::ffi::CStr;
/// use std::ffi::CString;
/// use std::process;
///
/// some_c_function(CStr::from_bytes_with_nul(b"Hello!\0").unwrap().into());
///
/// let string = CString::new(format!("Current PID is {}.", process::id())).unwrap();
/// some_c_function(string.as_ref().into());
///
/// fn c_function_wrapper(s: &CStr) {
/// some_c_function(s.into());
/// }
///
/// some_c_function(s!("こんにちはC言語"));
/// ```
#[repr(transparent)]
#[derive(Eq)]
pub struct StrRef<'a>(*const c_char, PhantomData<&'a ()>);
unsafe impl<'a> cxx::ExternType for StrRef<'a> {
type Id = cxx::type_id!("StrRef");
type Kind = cxx::kind::Trivial;
}
impl<'a> StrRef<'a> {
/// Get the wrapped string reference.
///
/// This does the same as the `Deref` implementation, differing only in the
/// returned lifetime. `Deref`'s return type is bound by `self`'s lifetime,
/// this returns the more correct and longer lifetime.
///
/// This is an O(n) operation as it needs to calculate the length of a C
/// string by finding the first NUL byte.
///
/// # Examples
///
/// ```
/// use ddnet_base::s;
///
/// let str1: &'static str = s!("static string").to_str();
/// ```
///
/// ```compile_fail
/// use ddnet_base::s;
///
/// // Wrong lifetime.
/// let str2: &'static str = &*s!("another static string");
/// ```
///
pub fn to_str(&self) -> &'a str {
unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.0).to_bytes()) }
}
}
impl<'a> From<&'a CStr> for StrRef<'a> {
fn from(s: &'a CStr) -> StrRef<'a> {
let bytes = s.to_bytes_with_nul();
str::from_utf8(bytes).expect("valid UTF-8");
StrRef(bytes.as_ptr() as *const _, PhantomData)
}
}
impl<'a> fmt::Debug for StrRef<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_str().fmt(f)
}
}
impl<'a> fmt::Display for StrRef<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_str().fmt(f)
}
}
impl<'a> cmp::PartialEq for StrRef<'a> {
fn eq(&self, other: &StrRef<'a>) -> bool {
self.to_str().eq(other.to_str())
}
}
impl<'a> cmp::PartialEq<&'a str> for StrRef<'a> {
fn eq(&self, other: &&'a str) -> bool {
self.to_str().eq(*other)
}
}
impl<'a> cmp::PartialOrd for StrRef<'a> {
fn partial_cmp(&self, other: &StrRef<'a>) -> Option<cmp::Ordering> {
self.to_str().partial_cmp(other.to_str())
}
}
impl<'a> cmp::Ord for StrRef<'a> {
fn cmp(&self, other: &StrRef<'a>) -> cmp::Ordering {
self.to_str().cmp(other.to_str())
}
}
impl<'a> ops::Deref for StrRef<'a> {
type Target = str;
fn deref(&self) -> &str {
self.to_str()
}
}
/// Construct a [`StrRef`] statically.
///
/// # Examples
///
/// ```
/// use ddnet_base::StrRef;
/// use ddnet_base::s;
///
/// let greeting: StrRef<'static> = s!("Hallöchen, C!");
/// let status: StrRef<'static> = s!(concat!("Current file: ", file!()));
/// ```
#[macro_export]
macro_rules! s {
($str:expr) => {
::ddnet_base::StrRef::from(
::std::ffi::CStr::from_bytes_with_nul(::std::concat!($str, "\0").as_bytes()).unwrap(),
)
};
}

18
src/engine/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "ddnet-engine"
version = "0.0.1"
edition = "2018"
publish = false
license = "Zlib"
[lib]
path = "lib.rs"
[dependencies]
ddnet-base = { path = "../base" }
cxx = "1.0"
[dev-dependencies]
ddnet-engine-shared = { path = "shared" }
ddnet-test = { path = "../rust-bridge/test", features = ["link-test-libraries"] }

View File

@ -46,6 +46,7 @@
#include <engine/shared/packer.h>
#include <engine/shared/protocol.h>
#include <engine/shared/protocol_ex.h>
#include <engine/shared/rust_version.h>
#include <engine/shared/snapshot.h>
#include <engine/shared/uuid_manager.h>
@ -4448,9 +4449,11 @@ void CClient::RegisterCommands()
m_pConsole->Register("demo_play", "", CFGFLAG_CLIENT, Con_DemoPlay, this, "Play demo");
m_pConsole->Register("demo_speed", "i[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set demo speed");
m_pConsole->Register("save_replay", "?i[length] ?s[filename]", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of the last defined amount of seconds");
m_pConsole->Register("save_replay", "?i[length] s[filename]", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of the last defined amount of seconds");
m_pConsole->Register("benchmark_quit", "i[seconds] r[file]", CFGFLAG_CLIENT | CFGFLAG_STORE, Con_BenchmarkQuit, this, "Benchmark frame times for number of seconds to file, then quit");
RustVersionRegister(*m_pConsole);
m_pConsole->Chain("cl_timeout_seed", ConchainTimeoutSeed, this);
m_pConsole->Chain("cl_replays", ConchainReplays, this);
@ -4607,7 +4610,7 @@ int main(int argc, const char **argv)
// create the components
IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2);
IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT);
IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT).release();
IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_CLIENT, argc, (const char **)argv);
IConfigManager *pConfigManager = CreateConfigManager();
IEngineSound *pEngineSound = CreateEngineSound();

View File

@ -7,6 +7,8 @@
#include <base/color.h>
#include <engine/storage.h>
#include <memory>
static const ColorRGBA gs_ConsoleDefaultColor(1, 1, 1, 1);
enum LEVEL : char;
@ -48,10 +50,10 @@ public:
IResult() { m_NumArgs = 0; }
virtual ~IResult() {}
virtual int GetInteger(unsigned Index) = 0;
virtual float GetFloat(unsigned Index) = 0;
virtual const char *GetString(unsigned Index) = 0;
virtual ColorHSLA GetColor(unsigned Index, bool Light) = 0;
virtual int GetInteger(unsigned Index) const = 0;
virtual float GetFloat(unsigned Index) const = 0;
virtual const char *GetString(unsigned Index) const = 0;
virtual ColorHSLA GetColor(unsigned Index, bool Light) const = 0;
virtual void RemoveArgument(unsigned Index) = 0;
@ -60,7 +62,7 @@ public:
// DDRace
virtual int GetVictim() = 0;
virtual int GetVictim() const = 0;
};
class CCommandInfo
@ -110,7 +112,7 @@ public:
virtual void ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) = 0;
virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) = 0;
virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) = 0;
virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) const = 0;
virtual void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) = 0;
virtual void SetUnknownCommandCallback(FUnknownCommandCallback pfnCallback, void *pUser) = 0;
virtual void InitChecksum(CChecksumData *pData) const = 0;
@ -127,6 +129,6 @@ public:
virtual void SetFlagMask(int FlagMask) = 0;
};
extern IConsole *CreateConsole(int FlagMask);
std::unique_ptr<IConsole> CreateConsole(int FlagMask);
#endif // FILE_ENGINE_CONSOLE_H

544
src/engine/console.rs Normal file
View File

@ -0,0 +1,544 @@
use ddnet_base::ColorRGBA;
use ddnet_base::UserPtr;
pub use self::ffi::CreateConsole;
pub use self::ffi::IConsole;
pub use self::ffi::IConsole_IResult;
/// Command callback for `IConsole`.
///
/// See [`IConsole::Register`] for an example.
#[allow(non_camel_case_types)]
#[repr(transparent)]
pub struct IConsole_FCommandCallback(pub extern "C" fn(result: &IConsole_IResult, user: UserPtr));
unsafe impl cxx::ExternType for IConsole_FCommandCallback {
type Id = cxx::type_id!("IConsole_FCommandCallback");
type Kind = cxx::kind::Trivial;
}
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("base/rust.h");
include!("engine/console.h");
include!("engine/rust.h");
type ColorRGBA = ddnet_base::ColorRGBA;
type ColorHSLA = ddnet_base::ColorHSLA;
type StrRef<'a> = ddnet_base::StrRef<'a>;
type UserPtr = ddnet_base::UserPtr;
type IConsole_FCommandCallback = super::IConsole_FCommandCallback;
/// Represents the arguments to a console command for [`IConsole`].
///
/// You can only obtain this type in the command callback
/// [`IConsole_FCommandCallback`] specified in [`IConsole::Register`].
type IConsole_IResult;
/// Get the n-th parameter of the command as an integer.
///
/// If the index is out of range, this returns 0. If the parameter
/// cannot be parsed as an integer, this also returns 0.
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// # use ddnet_base::UserPtr;
/// # use ddnet_base::s;
/// # use ddnet_engine::CreateConsole;
/// # use ddnet_engine::IConsole;
/// # use ddnet_engine::IConsole_FCommandCallback;
/// # use ddnet_engine::IConsole_IResult;
/// # use ddnet_engine_shared::CFGFLAG_SERVER;
/// #
/// # let mut console = CreateConsole(CFGFLAG_SERVER);
/// # let mut executed = false;
/// # console.pin_mut().Register(s!("command"), s!("sss"), CFGFLAG_SERVER, IConsole_FCommandCallback(callback), UserPtr::from(&mut executed), s!(""));
/// # console.pin_mut().ExecuteLine(s!(r#"command "1337" abc -7331def"#), -1, true);
/// # extern "C" fn callback(result_param: &IConsole_IResult, mut user: UserPtr) {
/// # unsafe { *user.cast_mut::<bool>() = true; }
/// let result: &IConsole_IResult /* = `command "1337" abc -7331def` */;
/// # result = result_param;
/// assert_eq!(result.GetInteger(0), 1337);
/// assert_eq!(result.GetInteger(1), 0); // unparsable
/// assert_eq!(result.GetInteger(2), -7331); // parsable start
/// assert_eq!(result.GetInteger(3), 0); // out of range
/// # }
/// # assert!(executed);
/// ```
pub fn GetInteger(self: &IConsole_IResult, Index: u32) -> i32;
/// Get the n-th parameter of the command as a floating point number.
///
/// If the index is out of range, this returns 0.0. If the parameter
/// cannot be parsed as a floating point number, this also returns 0.0.
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// # use ddnet_base::UserPtr;
/// # use ddnet_base::s;
/// # use ddnet_engine::CreateConsole;
/// # use ddnet_engine::IConsole;
/// # use ddnet_engine::IConsole_FCommandCallback;
/// # use ddnet_engine::IConsole_IResult;
/// # use ddnet_engine_shared::CFGFLAG_SERVER;
/// #
/// # let mut console = CreateConsole(CFGFLAG_SERVER);
/// # let mut executed = false;
/// # console.pin_mut().Register(s!("command"), s!("sss"), CFGFLAG_SERVER, IConsole_FCommandCallback(callback), UserPtr::from(&mut executed), s!(""));
/// # console.pin_mut().ExecuteLine(s!(r#"command "13.37" abc -73.31def"#), -1, true);
/// # extern "C" fn callback(result_param: &IConsole_IResult, mut user: UserPtr) {
/// # unsafe { *user.cast_mut::<bool>() = true; }
/// let result: &IConsole_IResult /* = `command "13.37" abc -73.31def` */;
/// # result = result_param;
/// assert_eq!(result.GetFloat(0), 13.37);
/// assert_eq!(result.GetFloat(1), 0.0); // unparsable
/// assert_eq!(result.GetFloat(2), -73.31); // parsable start
/// assert_eq!(result.GetFloat(3), 0.0); // out of range
/// # }
/// # assert!(executed);
/// ```
pub fn GetFloat(self: &IConsole_IResult, Index: u32) -> f32;
/// Get the n-th parameter of the command as a string.
///
/// If the index is out of range, this returns the empty string `""`.
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// # use ddnet_base::UserPtr;
/// # use ddnet_base::s;
/// # use ddnet_engine::CreateConsole;
/// # use ddnet_engine::IConsole;
/// # use ddnet_engine::IConsole_FCommandCallback;
/// # use ddnet_engine::IConsole_IResult;
/// # use ddnet_engine_shared::CFGFLAG_SERVER;
/// #
/// # let mut console = CreateConsole(CFGFLAG_SERVER);
/// # let mut executed = false;
/// # console.pin_mut().Register(s!("command"), s!("sss"), CFGFLAG_SERVER, IConsole_FCommandCallback(callback), UserPtr::from(&mut executed), s!(""));
/// # console.pin_mut().ExecuteLine(s!(r#"command "I'm in space" '' "\"\\Escapes\?\"\n""#), -1, true);
/// # extern "C" fn callback(result_param: &IConsole_IResult, mut user: UserPtr) {
/// # unsafe { *user.cast_mut::<bool>() = true; }
/// let result: &IConsole_IResult /* = `command "I'm in space" '' "\"\\Escapes\?\"\n"` */;
/// # result = result_param;
/// assert_eq!(result.GetString(0), "I'm in space");
/// assert_eq!(result.GetString(1), "''");
/// assert_eq!(result.GetString(2), r#""\Escapes\?"\n"#); // only \\ and \" escapes
/// assert_eq!(result.GetString(3), ""); // out of range
/// # }
/// # assert!(executed);
/// ```
pub fn GetString(self: &IConsole_IResult, Index: u32) -> StrRef<'_>;
/// Get the n-th parameter of the command as a color.
///
/// If the index is out of range, this returns black. If the parameter
/// cannot be parsed as a color, this also returns black.
///
/// It supports the following formats:
/// - `$XXX` (RGB, e.g. `$f00` for red)
/// - `$XXXXXX` (RGB, e.g. `$ffa500` for DDNet's logo color)
/// - base 10 integers (24/32 bit HSL in base 10, e.g. `0` for black)
/// - the following color names: `red`, `yellow`, `green`, `cyan`,
/// `blue`, `magenta`, `white`, `gray`, `black`.
///
/// The `Light` parameter influences the interpretation of base 10
/// integers, if it is set, the lightness channel is divided by 2 and
/// 0.5 is added, making 0.5 the darkest and 1.0 the lightest possible
/// value.
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// # use ddnet_base::ColorHSLA;
/// # use ddnet_base::UserPtr;
/// # use ddnet_base::s;
/// # use ddnet_engine::CreateConsole;
/// # use ddnet_engine::IConsole;
/// # use ddnet_engine::IConsole_FCommandCallback;
/// # use ddnet_engine::IConsole_IResult;
/// # use ddnet_engine_shared::CFGFLAG_SERVER;
/// #
/// # let mut console = CreateConsole(CFGFLAG_SERVER);
/// # let mut executed = false;
/// # console.pin_mut().Register(s!("command"), s!("ssssss"), CFGFLAG_SERVER, IConsole_FCommandCallback(callback), UserPtr::from(&mut executed), s!(""));
/// # console.pin_mut().ExecuteLine(s!(r#"command "$f00" $ffa500 $1234 shiny cyan -16777216"#), -1, true);
/// # extern "C" fn callback(result_param: &IConsole_IResult, mut user: UserPtr) {
/// # unsafe { *user.cast_mut::<bool>() = true; }
/// let result: &IConsole_IResult /* = `command "$f00" $ffa500 $1234 shiny cyan -16777216` */;
/// # result = result_param;
/// assert_eq!(result.GetColor(0, false), ColorHSLA { h: 0.0, s: 1.0, l: 0.5, a: 1.0 }); // red
/// assert_eq!(result.GetColor(1, false), ColorHSLA { h: 0.10784314, s: 1.0, l: 0.5, a: 1.0 }); // DDNet logo color
/// assert_eq!(result.GetColor(2, false), ColorHSLA { h: 0.0, s: 0.0, l: 0.0, a: 1.0 }); // cannot be parsed => black
/// assert_eq!(result.GetColor(3, false), ColorHSLA { h: 0.0, s: 0.0, l: 0.0, a: 1.0 }); // unknown color name => black
/// assert_eq!(result.GetColor(4, false), ColorHSLA { h: 0.5, s: 1.0, l: 0.5, a: 1.0 }); // cyan
/// assert_eq!(result.GetColor(5, false), ColorHSLA { h: 0.0, s: 0.0, l: 0.0, a: 1.0 }); // black
/// assert_eq!(result.GetColor(6, false), ColorHSLA { h: 0.0, s: 0.0, l: 0.0, a: 1.0 }); // out of range => black
///
/// assert_eq!(result.GetColor(0, true), result.GetColor(0, false));
/// assert_eq!(result.GetColor(1, true), result.GetColor(1, false));
/// assert_eq!(result.GetColor(2, true), result.GetColor(2, false));
/// assert_eq!(result.GetColor(3, true), result.GetColor(3, false));
/// assert_eq!(result.GetColor(4, true), result.GetColor(4, false));
/// assert_eq!(result.GetColor(5, true), ColorHSLA { h: 0.0, s: 0.0, l: 0.5, a: 1.0 }); // black, but has the `Light` parameter set
/// assert_eq!(result.GetColor(6, true), result.GetColor(6, false));
/// # }
/// # assert!(executed);
/// ```
pub fn GetColor(self: &IConsole_IResult, Index: u32, Light: bool) -> ColorHSLA;
/// Get the number of parameters passed.
///
/// This is mostly important for commands that have optional parameters
/// (`?`) and thus support variable numbers of arguments.
///
/// See [`IConsole::Register`] for more details.
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// # use ddnet_base::ColorHSLA;
/// # use ddnet_base::UserPtr;
/// # use ddnet_base::s;
/// # use ddnet_engine::CreateConsole;
/// # use ddnet_engine::IConsole;
/// # use ddnet_engine::IConsole_FCommandCallback;
/// # use ddnet_engine::IConsole_IResult;
/// # use ddnet_engine_shared::CFGFLAG_SERVER;
/// #
/// # let mut console = CreateConsole(CFGFLAG_SERVER);
/// # let mut executed: u32 = 0;
/// # console.pin_mut().Register(s!("command"), s!("s?ss"), CFGFLAG_SERVER, IConsole_FCommandCallback(callback), UserPtr::from(&mut executed), s!(""));
/// # console.pin_mut().ExecuteLine(s!(r#"command one two three"#), -1, true);
/// # console.pin_mut().ExecuteLine(s!(r#"command "one two" "three four""#), -1, true);
/// # extern "C" fn callback(result_param: &IConsole_IResult, mut user: UserPtr) {
/// # let executed;
/// # unsafe { executed = *user.cast_mut::<u32>(); *user.cast_mut::<u32>() += 1; }
/// # if executed == 0 {
/// let result: &IConsole_IResult /* = `command one two three` */;
/// # result = result_param;
/// assert_eq!(result.NumArguments(), 3);
///
/// # } else if executed == 1 {
/// let result: &IConsole_IResult /* = `command "one two" "three four"` */;
/// # result = result_param;
/// assert_eq!(result.NumArguments(), 2);
/// # }
/// # }
/// # assert!(executed == 2);
/// ```
pub fn NumArguments(self: &IConsole_IResult) -> i32;
/// Get the value of the sole victim (`v`) parameter.
///
/// This is mostly important for commands that have optional parameters
/// and thus support variable numbers of arguments.
///
/// See [`IConsole::Register`] for more details.
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// # use ddnet_base::UserPtr;
/// # use ddnet_base::s;
/// # use ddnet_engine::CreateConsole;
/// # use ddnet_engine::IConsole;
/// # use ddnet_engine::IConsole_FCommandCallback;
/// # use ddnet_engine::IConsole_IResult;
/// # use ddnet_engine_shared::CFGFLAG_SERVER;
/// #
/// # let mut console = CreateConsole(CFGFLAG_SERVER);
/// # let mut executed: u32 = 0;
/// # console.pin_mut().Register(s!("command"), s!("v"), CFGFLAG_SERVER, IConsole_FCommandCallback(callback), UserPtr::from(&mut executed), s!(""));
/// # console.pin_mut().ExecuteLine(s!(r#"command me"#), 33, true);
/// # console.pin_mut().ExecuteLine(s!(r#"command string"#), 33, true);
/// # console.pin_mut().ExecuteLine(s!(r#"command 42"#), 33, true);
/// # console.pin_mut().ExecuteLine(s!(r#"command all"#), 33, true);
/// # extern "C" fn callback(result_param: &IConsole_IResult, mut user: UserPtr) {
/// # let executed;
/// # unsafe { executed = *user.cast_mut::<u32>(); *user.cast_mut::<u32>() += 1; }
/// # match executed {
/// # 0 => {
/// let result: &IConsole_IResult /* = `command me` */;
/// # result = result_param;
/// // Assume the executing client ID is 33.
/// assert_eq!(result.GetVictim(), 33);
///
/// # }
/// # 1 => {
/// let result: &IConsole_IResult /* = `command string` */;
/// # result = result_param;
/// assert_eq!(result.GetVictim(), 0);
///
/// # }
/// # 2 => {
/// let result: &IConsole_IResult /* = `command 42` */;
/// # result = result_param;
/// assert_eq!(result.GetVictim(), 42);
///
/// # }
/// # 3 => {
/// let result: &IConsole_IResult /* = `command all` first invocation */;
/// # result = result_param;
/// assert_eq!(result.GetVictim(), 0);
/// # }
/// # 4 => {
/// let result: &IConsole_IResult /* = `command all` second invocation */;
/// # result = result_param;
/// assert_eq!(result.GetVictim(), 1);
/// # }
/// // …
/// # 66 => {
/// let result: &IConsole_IResult /* = `command all` last invocation */;
/// # result = result_param;
/// assert_eq!(result.GetVictim(), 63);
/// # }
/// # _ => {}
/// # }
/// # }
/// # assert!(executed == 67);
/// ```
pub fn GetVictim(self: &IConsole_IResult) -> i32;
/// Console interface, consists of logging output and command execution.
///
/// This is used for the local console in the client and the remote
/// console of the server. It handles commands, their inputs and
/// outputs, command completion and also command and log output.
///
/// Call [`CreateConsole`] to obtain an instance.
type IConsole;
/// Execute a command in the console.
///
/// Commands can be separated by semicolons (`;`), everything after a
/// hash sign (`#`) is treated as a comment and discarded. Parameters
/// are separated by spaces (` `). By quoting arguments with double
/// quotes (`"`), the special meaning of the other characters can be
/// disabled. Double quotes can be escaped as `\"` and backslashes can
/// be escaped as `\\` inside double quotes.
///
/// When `InterpretSemicolons` is `false`, semicolons are not
/// interpreted unless the command starts with `mc;`.
///
/// The `ClientID` parameter defaults to -1, `InterpretSemicolons` to
/// `true` in C++.
pub fn ExecuteLine(
self: Pin<&mut IConsole>,
pStr: StrRef<'_>,
ClientID: i32,
InterpretSemicolons: bool,
);
/// Log a message.
///
/// `Level` is one of
/// - [`IConsole_OUTPUT_LEVEL_STANDARD`](constant.IConsole_OUTPUT_LEVEL_STANDARD.html)
/// - [`IConsole_OUTPUT_LEVEL_ADDINFO`](constant.IConsole_OUTPUT_LEVEL_ADDINFO.html)
/// - [`IConsole_OUTPUT_LEVEL_DEBUG`](constant.IConsole_OUTPUT_LEVEL_DEBUG.html)
///
/// `pFrom` is some sort of module name, e.g. for code in the console,
/// it is usually "console". Other examples include "client",
/// "client/network", …
///
/// `pStr` is the actual log message.
///
/// `PrintColor` is the intended log color.
///
/// `PrintColor` defaults to [`gs_ConsoleDefaultColor`] in C++.
pub fn Print(
self: &IConsole,
Level: i32,
pFrom: StrRef<'_>,
pStr: StrRef<'_>,
PrintColor: ColorRGBA,
);
/// Register a command.
///
/// This function needs a command name, a parameter shortcode, some
/// flags that specify metadata about the command, a callback function
/// with associated `UserPtr` and a help string.
///
/// `Flags` must be a combination of
/// - [`CFGFLAG_SAVE`](../ddnet_engine_shared/constant.CFGFLAG_SAVE.html)
/// - [`CFGFLAG_CLIENT`](../ddnet_engine_shared/constant.CFGFLAG_CLIENT.html)
/// - [`CFGFLAG_SERVER`](../ddnet_engine_shared/constant.CFGFLAG_SERVER.html)
/// - [`CFGFLAG_STORE`](../ddnet_engine_shared/constant.CFGFLAG_STORE.html)
/// - [`CFGFLAG_MASTER`](../ddnet_engine_shared/constant.CFGFLAG_MASTER.html)
/// - [`CFGFLAG_ECON`](../ddnet_engine_shared/constant.CFGFLAG_ECON.html)
/// - [`CMDFLAG_TEST`](../ddnet_engine_shared/constant.CMDFLAG_TEST.html)
/// - [`CFGFLAG_CHAT`](../ddnet_engine_shared/constant.CFGFLAG_CHAT.html)
/// - [`CFGFLAG_GAME`](../ddnet_engine_shared/constant.CFGFLAG_GAME.html)
/// - [`CFGFLAG_NONTEEHISTORIC`](../ddnet_engine_shared/constant.CFGFLAG_NONTEEHISTORIC.html)
/// - [`CFGFLAG_COLLIGHT`](../ddnet_engine_shared/constant.CFGFLAG_COLLIGHT.html)
/// - [`CFGFLAG_INSENSITIVE`](../ddnet_engine_shared/constant.CFGFLAG_INSENSITIVE.html)
///
/// # Parameter shortcode
///
/// The following parameter types are supported:
///
/// - `i`, `f`, `s`: **I**nteger, **f**loat, and **s**tring parameter,
/// respectively. Since they're not type-checked, they're all the
/// same, one word delimited by whitespace or any quoted string.
/// Examples: `12`, `example`, `"Hello, World!"`.
/// - `r`: **R**est of the command line, possibly multiple word, until
/// the next command or the end of line. Alternatively one quoted
/// parameter. Examples: `multiple words even without quotes`,
/// `"quotes are fine, too"`.
/// - `v`: **V**ictim client ID for this command. Supports the special
/// parameters `me` for the executing client ID, and `all` to target
/// all players. Examples: `0`, `63`, `words` (gets parsed as `0`),
/// `me`, `all`.
///
/// The parameter shortcode is now a string consisting of any number of
/// these parameter type characters, a question mark `?` to mark the
/// beginning of optional parameters and `[<string>]` help strings after
/// each parameter that can have a short explanation.
///
/// Examples:
///
/// - `echo` has `r[text]`: Takes the whole command line as the first
/// parameter. This means we get the following arguments:
/// ```text
/// ## "1"
/// echo 1
///
/// ## "multiple words"
/// echo multiple words
///
/// ## error: missing argument
/// echo
///
/// ## "multiple"
/// echo "multiple" "quoted" "arguments"
///
/// ## "comments"
/// echo comments # work too
///
/// ## "multiple"; "commands"
/// echo multiple; echo commands
/// ```
/// - `muteid` has `v[id] i[seconds] ?r[reason]`. Assume the local
/// player has client ID 33, then we get the following
/// arguments:
/// ```text
/// ## "33" "60"
/// muteid me 60
///
/// ## "12" "120" "You're a wonderful person"
/// muteid 12 120 You're a wonderful person
///
/// ## "0" "180"; "1" "180"; …, "63" "180"
/// muteid all 180
/// ```
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// use ddnet_base::UserPtr;
/// use ddnet_base::s;
/// use ddnet_engine::CreateConsole;
/// use ddnet_engine::IConsole;
/// use ddnet_engine::IConsole_FCommandCallback;
/// use ddnet_engine::IConsole_IResult;
/// use ddnet_engine::IConsole_OUTPUT_LEVEL_STANDARD;
/// use ddnet_engine::gs_ConsoleDefaultColor;
/// use ddnet_engine_shared::CFGFLAG_SERVER;
///
/// extern "C" fn print_callback(_: &IConsole_IResult, user: UserPtr) {
/// let console: &IConsole = unsafe { user.cast() };
/// console.Print(IConsole_OUTPUT_LEVEL_STANDARD, s!("example"), s!("print callback"), gs_ConsoleDefaultColor);
/// }
///
/// let mut console = CreateConsole(CFGFLAG_SERVER);
/// let user = (&*console).into();
/// console.pin_mut().Register(s!("example"), s!(""), CFGFLAG_SERVER, IConsole_FCommandCallback(print_callback), user, s!("Example command outputting a single line into the console"));
/// console.pin_mut().ExecuteLine(s!("example"), -1, true);
/// ```
pub fn Register(
self: Pin<&mut IConsole>,
pName: StrRef<'_>,
pParams: StrRef<'_>,
Flags: i32,
pfnFunc: IConsole_FCommandCallback,
pUser: UserPtr,
pHelp: StrRef<'_>,
);
/// Create a console instance.
///
/// It comes with a few registered commands by default. Only the
/// commands sharing a bit with the `FlagMask` parameter are considered
/// when executing commands.
///
/// # Examples
///
/// ```
/// # extern crate ddnet_test;
/// use ddnet_base::s;
/// use ddnet_engine::CreateConsole;
/// use ddnet_engine_shared::CFGFLAG_SERVER;
///
/// let mut console = CreateConsole(CFGFLAG_SERVER);
/// console.pin_mut().ExecuteLine(s!("cmdlist; echo hi!"), -1, true);
/// ```
pub fn CreateConsole(FlagMask: i32) -> UniquePtr<IConsole>;
}
}
/// Default console output color. White.
///
/// Corresponds to the `gs_ConsoleDefaultColor` constant of the same name in
/// C++.
///
/// Used as a last parameter in [`IConsole::Print`].
///
/// It is treated as "no color" in the console code, meaning that the default
/// color of the output medium isn't overriden.
#[allow(non_upper_case_globals)]
pub const gs_ConsoleDefaultColor: ColorRGBA = ColorRGBA {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
/// Default console output level.
///
/// Corresponds to the `IConsole::OUTPUT_LEVEL_STANDARD` enum value in C++.
///
/// Used as a first parameter in [`IConsole::Print`].
///
/// This is the only level that is shown by default.
#[allow(non_upper_case_globals)]
pub const IConsole_OUTPUT_LEVEL_STANDARD: i32 = 0;
/// First more verbose console output level.
///
/// Corresponds to the `IConsole::OUTPUT_LEVEL_ADDINFO` enum value in C++.
///
/// Used as a first parameter in [`IConsole::Print`].
///
/// This output level is not shown by default.
#[allow(non_upper_case_globals)]
pub const IConsole_OUTPUT_LEVEL_ADDINFO: i32 = 1;
/// Most verbose console output level.
///
/// Corresponds to the `IConsole::OUTPUT_LEVEL_DEBUG` enum value in C++.
///
/// Used as a first parameter in [`IConsole::Print`].
///
/// This output level is not shown by default.
#[allow(non_upper_case_globals)]
pub const IConsole_OUTPUT_LEVEL_DEBUG: i32 = 2;

19
src/engine/lib.rs Normal file
View File

@ -0,0 +1,19 @@
//! DDNet's engine interfaces, Rust part.
//!
//! DDNet's code base is separated into three major parts, `base`, `engine` and
//! `game`.
//!
//! The engine consists of game-independent code such as display setup,
//! low-level network protocol, low-level map format, etc.
//!
//! This crate in particular corresponds to the `src/engine` directory that only
//! contains interfaces used by `engine` and `game` code.
#![warn(missing_docs)]
#[cfg(test)]
extern crate ddnet_test;
mod console;
pub use console::*;

7
src/engine/rust.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef ENGINE_RUST_H
#define ENGINE_RUST_H
#include "console.h"
typedef IConsole::IResult IConsole_IResult;
typedef IConsole::FCommandCallback IConsole_FCommandCallback;
#endif // ENGINE_RUST_H

View File

@ -108,7 +108,7 @@ int main(int argc, const char **argv)
IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2);
IEngineMap *pEngineMap = CreateEngineMap();
IGameServer *pGameServer = CreateGameServer();
IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON);
IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON).release();
IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_SERVER, argc, argv);
IConfigManager *pConfigManager = CreateConfigManager();
IEngineAntibot *pEngineAntibot = CreateEngineAntibot();

View File

@ -29,6 +29,7 @@
#include <engine/shared/packer.h>
#include <engine/shared/protocol.h>
#include <engine/shared/protocol_ex.h>
#include <engine/shared/rust_version.h>
#include <engine/shared/snapshot.h>
#include <game/version.h>
@ -3666,6 +3667,8 @@ void CServer::RegisterCommands()
Console()->Register("name_unban", "s[name]", CFGFLAG_SERVER, ConNameUnban, this, "Unban a certain nickname");
Console()->Register("name_bans", "", CFGFLAG_SERVER, ConNameBans, this, "List all name bans");
RustVersionRegister(*Console());
Console()->Chain("sv_name", ConchainSpecialInfoupdate, this);
Console()->Chain("loglevel", ConchainLoglevel, this);
Console()->Chain("password", ConchainSpecialInfoupdate, this);

View File

@ -0,0 +1,19 @@
[package]
name = "ddnet-engine-shared"
version = "0.0.1"
edition = "2018"
publish = false
license = "Zlib"
[lib]
crate-type = ["rlib", "staticlib"]
path = "lib.rs"
[dependencies]
ddnet-base = { path = "../../base" }
ddnet-engine = { path = ".." }
cxx = "1.0"
[dev-dependencies]
ddnet-test = { path = "../../rust-bridge/test", features = ["link-test-libraries"] }

View File

@ -0,0 +1,18 @@
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
fn main() {
let mut out = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR"));
out.push("rustc-version");
let rustc = env::var_os("RUSTC").expect("RUSTC");
let rustc_output = Command::new(rustc)
.arg("--version")
.output()
.expect("rustc --version");
if !rustc_output.status.success() {
panic!("rustc --version: exit status {}", rustc_output.status);
}
fs::write(&out, rustc_output.stdout).expect("file write");
}

View File

@ -0,0 +1,57 @@
/// Config variable that is saved when the client is closed.
///
/// Has no effect on other commands.
pub const CFGFLAG_SAVE: i32 = 1 << 0;
/// Command that is available in the client.
pub const CFGFLAG_CLIENT: i32 = 1 << 1;
/// Command that is available on the server.
pub const CFGFLAG_SERVER: i32 = 1 << 2;
/// Command that is delayed in the execution until
/// `IConsole::StoreCommands(false)` is called.
pub const CFGFLAG_STORE: i32 = 1 << 3;
/// Command that is available in the master server.
pub const CFGFLAG_MASTER: i32 = 1 << 4;
/// Command that has something to do with the external console (econ).
pub const CFGFLAG_ECON: i32 = 1 << 5;
/// Command that can be used for testing or cheating maps. Only available if
/// `sv_test_cmds 1` is set.
pub const CMDFLAG_TEST: i32 = 1 << 6;
/// Command that can be used from the chat on the server.
pub const CFGFLAG_CHAT: i32 = 1 << 7;
/// Command that can be used from a map config.
///
/// Only commands that are not security sensitive should have this flag.
pub const CFGFLAG_GAME: i32 = 1 << 8;
/// Command that is not recorded into teehistorian.
///
/// This should only be set for security sensitive commands like passwords etc.
/// that should not be recorded.
pub const CFGFLAG_NONTEEHISTORIC: i32 = 1 << 9;
/// Color config variable that can only have lightness 0.5 to 1.0.
///
/// This is achieved by dividing the lightness channel by and adding 0.5, i.e.
/// remapping all the colors.
///
/// Has no effect on other commands or config variables.
pub const CFGFLAG_COLLIGHT: i32 = 1 << 10;
/// Color config variable that includes an alpha (opacity) value.
///
/// Has no effect on other commands or config variables.
pub const CFGFLAG_COLALPHA: i32 = 1 << 11;
/// Config variable with insensitive data that can be included in client
/// integrity checks.
///
/// This should only be set on config variables the server could observe anyway.
pub const CFGFLAG_INSENSITIVE: i32 = 1 << 12;

View File

@ -19,28 +19,28 @@
// todo: rework this
const char *CConsole::CResult::GetString(unsigned Index)
const char *CConsole::CResult::GetString(unsigned Index) const
{
if(Index >= m_NumArgs)
return "";
return m_apArgs[Index];
}
int CConsole::CResult::GetInteger(unsigned Index)
int CConsole::CResult::GetInteger(unsigned Index) const
{
if(Index >= m_NumArgs)
return 0;
return str_toint(m_apArgs[Index]);
}
float CConsole::CResult::GetFloat(unsigned Index)
float CConsole::CResult::GetFloat(unsigned Index) const
{
if(Index >= m_NumArgs)
return 0.0f;
return str_tofloat(m_apArgs[Index]);
}
ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light)
ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const
{
ColorHSLA Hsla = ColorHSLA(0, 0, 0);
if(Index >= m_NumArgs)
@ -322,7 +322,7 @@ LOG_COLOR ColorToLogColor(ColorRGBA Color)
(uint8_t)(Color.b * 255.0)};
}
void CConsole::Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor)
void CConsole::Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor) const
{
LEVEL LogLevel = IConsole::ToLogLevel(Level);
// if the color is pure white, use default terminal color
@ -1268,7 +1268,7 @@ const IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int Fl
return 0;
}
extern IConsole *CreateConsole(int FlagMask) { return new CConsole(FlagMask); }
std::unique_ptr<IConsole> CreateConsole(int FlagMask) { return std::make_unique<CConsole>(FlagMask); }
void CConsole::ResetServerGameSettings()
{
@ -1307,7 +1307,7 @@ void CConsole::ResetServerGameSettings()
#undef MACRO_CONFIG_STR
}
int CConsole::CResult::GetVictim()
int CConsole::CResult::GetVictim() const
{
return m_Victim;
}

View File

@ -114,10 +114,10 @@ class CConsole : public IConsole
m_apArgs[m_NumArgs++] = pArg;
}
const char *GetString(unsigned Index) override;
int GetInteger(unsigned Index) override;
float GetFloat(unsigned Index) override;
ColorHSLA GetColor(unsigned Index, bool Light) override;
const char *GetString(unsigned Index) const override;
int GetInteger(unsigned Index) const override;
float GetFloat(unsigned Index) const override;
ColorHSLA GetColor(unsigned Index, bool Light) const override;
void RemoveArgument(unsigned Index) override
{
@ -142,7 +142,7 @@ class CConsole : public IConsole
bool HasVictim();
void SetVictim(int Victim);
void SetVictim(const char *pVictim);
int GetVictim() override;
int GetVictim() const override;
};
int ParseStart(CResult *pResult, const char *pString, int Length);
@ -215,7 +215,7 @@ public:
void ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) override;
char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) override;
void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) override;
void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) const override;
void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) override;
void SetUnknownCommandCallback(FUnknownCommandCallback pfnCallback, void *pUser) override;
void InitChecksum(CChecksumData *pData) const override;

21
src/engine/shared/lib.rs Normal file
View File

@ -0,0 +1,21 @@
//! DDNet's engine interfaces, Rust part.
//!
//! DDNet's code base is separated into three major parts, `base`, `engine` and
//! `game`.
//!
//! The engine consists of game-independent code such as display setup,
//! low-level network protocol, low-level map format, etc.
//!
//! This crate in particular corresponds to the `src/engine/shared` directory
//! that contains code shared between client, server and other components.
#![warn(missing_docs)]
#[cfg(test)]
extern crate ddnet_test;
mod config;
mod rust_version;
pub use config::*;
pub use rust_version::*;

View File

@ -0,0 +1,59 @@
use super::CFGFLAG_CLIENT;
use super::CFGFLAG_SERVER;
use ddnet_base::s;
use ddnet_base::UserPtr;
use ddnet_engine::gs_ConsoleDefaultColor;
use ddnet_engine::IConsole;
use ddnet_engine::IConsole_FCommandCallback;
use ddnet_engine::IConsole_IResult;
use ddnet_engine::IConsole_OUTPUT_LEVEL_STANDARD;
use std::pin::Pin;
#[cxx::bridge]
mod ffi {
extern "C++" {
include!("base/rust.h");
include!("engine/console.h");
type IConsole = ddnet_engine::IConsole;
}
extern "Rust" {
fn RustVersionPrint(console: &IConsole);
fn RustVersionRegister(console: Pin<&mut IConsole>);
}
}
/// Print the Rust version used for compiling this crate.
///
/// Uses [`IConsole::Print`] for printing.
#[allow(non_snake_case)]
pub fn RustVersionPrint(console: &IConsole) {
console.Print(
IConsole_OUTPUT_LEVEL_STANDARD,
s!("rust_version"),
s!(include_str!(concat!(env!("OUT_DIR"), "/rustc-version"))),
gs_ConsoleDefaultColor,
);
}
#[allow(non_snake_case)]
extern "C" fn PrintRustVersionCallback(_: &IConsole_IResult, user: UserPtr) {
RustVersionPrint(unsafe { user.cast() })
}
/// Register the `rust_version` command to the given console instance.
///
/// This command calls the [`RustVersionPrint`] function to print the Rust
/// version used for compiling this crate.
#[allow(non_snake_case)]
pub fn RustVersionRegister(console: Pin<&mut IConsole>) {
let user = console.as_ref().get_ref().into();
console.Register(
s!("rust_version"),
s!(""),
CFGFLAG_CLIENT | CFGFLAG_SERVER,
IConsole_FCommandCallback(PrintRustVersionCallback),
user,
s!("Prints the Rust version used to compile DDNet"),
);
}

View File

@ -43,8 +43,8 @@ CONSOLE_COMMAND("move_raw", "i[x] i[y]", CFGFLAG_SERVER | CMDFLAG_TEST, ConMoveR
CONSOLE_COMMAND("force_pause", "v[id] i[seconds]", CFGFLAG_SERVER, ConForcePause, this, "Force i to pause for i seconds")
CONSOLE_COMMAND("force_unpause", "v[id]", CFGFLAG_SERVER, ConForcePause, this, "Set force-pause timer of i to 0.")
CONSOLE_COMMAND("set_team_ddr", "v[id] ?i[team]", CFGFLAG_SERVER, ConSetDDRTeam, this, "Set ddrace team of a player")
CONSOLE_COMMAND("uninvite", "v[id] ?i[team]", CFGFLAG_SERVER, ConUninvite, this, "Uninvite player from team")
CONSOLE_COMMAND("set_team_ddr", "v[id] i[team]", CFGFLAG_SERVER, ConSetDDRTeam, this, "Set ddrace team of a player")
CONSOLE_COMMAND("uninvite", "v[id] i[team]", CFGFLAG_SERVER, ConUninvite, this, "Uninvite player from team")
CONSOLE_COMMAND("vote_mute", "v[id] i[seconds]", CFGFLAG_SERVER, ConVoteMute, this, "Remove v's right to vote for i seconds")
CONSOLE_COMMAND("vote_unmute", "v[id]", CFGFLAG_SERVER, ConVoteUnmute, this, "Give back v's right to vote.")

View File

@ -3,6 +3,10 @@ name = "mastersrv"
version = "0.0.1"
authors = ["heinrich5991 <heinrich5991@gmail.com>"]
edition = "2018"
publish = false
license = "Zlib"
[workspace]
[dependencies]
arrayvec = { version = "0.5.2", features = ["serde"] }

View File

@ -0,0 +1,3 @@
# Need at least one check, otherwise clang-tidy fails, use one that can't
# happen in our code since we don't use OpenMP API.
Checks: '-*,openmp-exception-escape'

View File

@ -0,0 +1,164 @@
#include "base/rust.h"
#include "engine/console.h"
#include "engine/rust.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>
namespace rust {
inline namespace cxxbridge1 {
// #include "rust/cxx.h"
#ifndef CXXBRIDGE1_IS_COMPLETE
#define CXXBRIDGE1_IS_COMPLETE
namespace detail {
namespace {
template <typename T, typename = std::size_t>
struct is_complete : std::false_type {};
template <typename T>
struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
} // namespace
} // namespace detail
#endif // CXXBRIDGE1_IS_COMPLETE
#ifndef CXXBRIDGE1_RELOCATABLE
#define CXXBRIDGE1_RELOCATABLE
namespace detail {
template <typename... Ts>
struct make_void {
using type = void;
};
template <typename... Ts>
using void_t = typename make_void<Ts...>::type;
template <typename Void, template <typename...> class, typename...>
struct detect : std::false_type {};
template <template <typename...> class T, typename... A>
struct detect<void_t<T<A...>>, T, A...> : std::true_type {};
template <template <typename...> class T, typename... A>
using is_detected = detect<void, T, A...>;
template <typename T>
using detect_IsRelocatable = typename T::IsRelocatable;
template <typename T>
struct get_IsRelocatable
: std::is_same<typename T::IsRelocatable, std::true_type> {};
} // namespace detail
template <typename T>
struct IsRelocatable
: std::conditional<
detail::is_detected<detail::detect_IsRelocatable, T>::value,
detail::get_IsRelocatable<T>,
std::integral_constant<
bool, std::is_trivially_move_constructible<T>::value &&
std::is_trivially_destructible<T>::value>>::type {};
#endif // CXXBRIDGE1_RELOCATABLE
namespace {
template <bool> struct deleter_if {
template <typename T> void operator()(T *) {}
};
template <> struct deleter_if<true> {
template <typename T> void operator()(T *ptr) { ptr->~T(); }
};
} // namespace
} // namespace cxxbridge1
} // namespace rust
using IConsole_IResult = ::IConsole_IResult;
using IConsole = ::IConsole;
static_assert(
::rust::IsRelocatable<::ColorRGBA>::value,
"type ColorRGBA should be trivially move constructible and trivially destructible in C++ to be used as an argument of `Print` in Rust");
static_assert(
::rust::IsRelocatable<::ColorHSLA>::value,
"type ColorHSLA should be trivially move constructible and trivially destructible in C++ to be used as a return value of `GetColor` in Rust");
static_assert(
::rust::IsRelocatable<::StrRef>::value,
"type StrRef should be trivially move constructible and trivially destructible in C++ to be used as an argument of `ExecuteLine`, `Print`, `Register` or return value of `GetString` in Rust");
static_assert(
::rust::IsRelocatable<::UserPtr>::value,
"type UserPtr should be trivially move constructible and trivially destructible in C++ to be used as an argument of `Register` in Rust");
static_assert(
::rust::IsRelocatable<::IConsole_FCommandCallback>::value,
"type IConsole_FCommandCallback should be trivially move constructible and trivially destructible in C++ to be used as an argument of `Register` in Rust");
extern "C" {
::std::int32_t cxxbridge1$IConsole_IResult$GetInteger(const ::IConsole_IResult &self, ::std::uint32_t Index) noexcept {
::std::int32_t (::IConsole_IResult::*GetInteger$)(::std::uint32_t) const = &::IConsole_IResult::GetInteger;
return (self.*GetInteger$)(Index);
}
float cxxbridge1$IConsole_IResult$GetFloat(const ::IConsole_IResult &self, ::std::uint32_t Index) noexcept {
float (::IConsole_IResult::*GetFloat$)(::std::uint32_t) const = &::IConsole_IResult::GetFloat;
return (self.*GetFloat$)(Index);
}
void cxxbridge1$IConsole_IResult$GetString(const ::IConsole_IResult &self, ::std::uint32_t Index, ::StrRef *return$) noexcept {
::StrRef (::IConsole_IResult::*GetString$)(::std::uint32_t) const = &::IConsole_IResult::GetString;
new (return$) ::StrRef((self.*GetString$)(Index));
}
void cxxbridge1$IConsole_IResult$GetColor(const ::IConsole_IResult &self, ::std::uint32_t Index, bool Light, ::ColorHSLA *return$) noexcept {
::ColorHSLA (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor;
new (return$) ::ColorHSLA((self.*GetColor$)(Index, Light));
}
::std::int32_t cxxbridge1$IConsole_IResult$NumArguments(const ::IConsole_IResult &self) noexcept {
::std::int32_t (::IConsole_IResult::*NumArguments$)() const = &::IConsole_IResult::NumArguments;
return (self.*NumArguments$)();
}
::std::int32_t cxxbridge1$IConsole_IResult$GetVictim(const ::IConsole_IResult &self) noexcept {
::std::int32_t (::IConsole_IResult::*GetVictim$)() const = &::IConsole_IResult::GetVictim;
return (self.*GetVictim$)();
}
void cxxbridge1$IConsole$ExecuteLine(::IConsole &self, ::StrRef *pStr, ::std::int32_t ClientID, bool InterpretSemicolons) noexcept {
void (::IConsole::*ExecuteLine$)(::StrRef, ::std::int32_t, bool) = &::IConsole::ExecuteLine;
(self.*ExecuteLine$)(::std::move(*pStr), ClientID, InterpretSemicolons);
}
void cxxbridge1$IConsole$Print(const ::IConsole &self, ::std::int32_t Level, ::StrRef *pFrom, ::StrRef *pStr, ::ColorRGBA *PrintColor) noexcept {
void (::IConsole::*Print$)(::std::int32_t, ::StrRef, ::StrRef, ::ColorRGBA) const = &::IConsole::Print;
(self.*Print$)(Level, ::std::move(*pFrom), ::std::move(*pStr), ::std::move(*PrintColor));
}
void cxxbridge1$IConsole$Register(::IConsole &self, ::StrRef *pName, ::StrRef *pParams, ::std::int32_t Flags, ::IConsole_FCommandCallback *pfnFunc, ::UserPtr *pUser, ::StrRef *pHelp) noexcept {
void (::IConsole::*Register$)(::StrRef, ::StrRef, ::std::int32_t, ::IConsole_FCommandCallback, ::UserPtr, ::StrRef) = &::IConsole::Register;
(self.*Register$)(::std::move(*pName), ::std::move(*pParams), Flags, ::std::move(*pfnFunc), ::std::move(*pUser), ::std::move(*pHelp));
}
::IConsole *cxxbridge1$CreateConsole(::std::int32_t FlagMask) noexcept {
::std::unique_ptr<::IConsole> (*CreateConsole$)(::std::int32_t) = ::CreateConsole;
return CreateConsole$(FlagMask).release();
}
static_assert(::rust::detail::is_complete<::IConsole>::value, "definition of IConsole is required");
static_assert(sizeof(::std::unique_ptr<::IConsole>) == sizeof(void *), "");
static_assert(alignof(::std::unique_ptr<::IConsole>) == alignof(void *), "");
void cxxbridge1$unique_ptr$IConsole$null(::std::unique_ptr<::IConsole> *ptr) noexcept {
::new (ptr) ::std::unique_ptr<::IConsole>();
}
void cxxbridge1$unique_ptr$IConsole$raw(::std::unique_ptr<::IConsole> *ptr, ::IConsole *raw) noexcept {
::new (ptr) ::std::unique_ptr<::IConsole>(raw);
}
const ::IConsole *cxxbridge1$unique_ptr$IConsole$get(const ::std::unique_ptr<::IConsole>& ptr) noexcept {
return ptr.get();
}
::IConsole *cxxbridge1$unique_ptr$IConsole$release(::std::unique_ptr<::IConsole>& ptr) noexcept {
return ptr.release();
}
void cxxbridge1$unique_ptr$IConsole$drop(::std::unique_ptr<::IConsole> *ptr) noexcept {
::rust::deleter_if<::rust::detail::is_complete<::IConsole>::value>{}(ptr);
}
} // extern "C"

View File

@ -0,0 +1,9 @@
#pragma once
#include "base/rust.h"
#include "engine/console.h"
#include "engine/rust.h"
#include <cstdint>
#include <memory>
using IConsole_IResult = ::IConsole_IResult;
using IConsole = ::IConsole;

View File

@ -0,0 +1,16 @@
#include "base/rust.h"
#include "engine/console.h"
extern "C" {
void cxxbridge1$RustVersionPrint(const ::IConsole &console) noexcept;
void cxxbridge1$RustVersionRegister(::IConsole &console) noexcept;
} // extern "C"
void RustVersionPrint(const ::IConsole &console) noexcept {
cxxbridge1$RustVersionPrint(console);
}
void RustVersionRegister(::IConsole &console) noexcept {
cxxbridge1$RustVersionRegister(console);
}

View File

@ -0,0 +1,7 @@
#pragma once
#include "base/rust.h"
#include "engine/console.h"
void RustVersionPrint(const ::IConsole &console) noexcept;
void RustVersionRegister(::IConsole &console) noexcept;

View File

@ -0,0 +1,12 @@
[package]
name = "ddnet-test"
version = "0.0.1"
edition = "2018"
publish = false
license = "Zlib"
[lib]
path = "lib.rs"
[features]
link-test-libraries = []

View File

@ -0,0 +1,68 @@
use std::collections::HashSet;
use std::env;
use std::ffi::OsStr;
use std::path::Path;
use std::process::Command;
fn main() {
let rustc = env::var_os("RUSTC").expect("RUSTC");
let rustc_output = Command::new(rustc)
.arg("--version")
.output()
.expect("rustc --version");
if !rustc_output.status.success() {
panic!("rustc --version: exit status {}", rustc_output.status);
}
let rustc_version = &rustc_output.stdout[..];
let supports_whole_archive =
!rustc_version.starts_with(b"rustc ") || rustc_version >= &b"rustc 1.61.0"[..];
println!("cargo:rerun-if-env-changed=DDNET_TEST_LIBRARIES");
println!("cargo:rerun-if-env-changed=DDNET_TEST_NO_LINK");
if env::var_os("DDNET_TEST_NO_LINK").is_some() {
return;
}
if env::var_os("CARGO_FEATURE_LINK_TEST_LIBRARIES").is_some() {
let libraries = env::var("DDNET_TEST_LIBRARIES")
.expect("environment variable DDNET_TEST_LIBRARIES required but not found");
let mut seen_library_dirs = HashSet::new();
for library in libraries.split(';') {
let library = Path::new(library);
let extension = library.extension().and_then(OsStr::to_str);
let kind = match extension {
Some("framework") => "framework=",
Some("so") => "dylib=",
Some("a") => {
if supports_whole_archive {
"static:-whole-archive="
} else {
""
}
}
_ => "",
};
let dir_kind = match extension {
Some("framework") => "framework=",
_ => "",
};
if let Some(parent) = library.parent() {
if parent != Path::new("") {
let parent = parent.to_str().expect("should have errored earlier");
if !seen_library_dirs.contains(&(dir_kind, parent)) {
println!("cargo:rustc-link-search={}{}", dir_kind, parent);
seen_library_dirs.insert((dir_kind, parent));
}
}
}
let mut name = library
.file_stem()
.expect("library name")
.to_str()
.expect("should have errored earlier");
if name.starts_with("lib") {
name = &name[3..];
}
println!("cargo:rustc-link-lib={}{}", kind, name);
}
}
}

View File

@ -0,0 +1,10 @@
//! Helper module to link the C++ code of DDNet for Rust unit tests.
//!
//! Use the global `run_rust_tests` target from CMakeLists.txt to actually run
//! the Rust test; this sets compiles the C++ and sets `DDNET_TEST_LIBRARIES`
//! appropriately, e.g. using `cmake --build build --target run_rust_tests`.
//!
//! To call `cargo doc`, set `DDNET_TEST_NO_LINK=1` so that this crate becomes a
//! stub.
#![warn(missing_docs)]

View File

@ -13,7 +13,7 @@ TEST(ServerBrowser, PingCache)
CTestInfo Info;
Info.m_DeleteTestStorageFilesOnSuccess = true;
auto pConsole = std::unique_ptr<IConsole>(CreateConsole(CFGFLAG_CLIENT));
auto pConsole = CreateConsole(CFGFLAG_CLIENT);
auto pStorage = std::unique_ptr<IStorage>(Info.CreateTestStorage());
auto pPingCache = std::unique_ptr<IServerBrowserPingCache>(CreateServerBrowserPingCache(pConsole.get(), pStorage.get()));