CultLib/native/GameCult.Mesh.Quic.Native/CMakeLists.txt
Meta Vens 62e53df447 Hold host calls inside the bridge so the quiesce is tested, not assumed
The closerace scenario pinned the order of the count and the notify but never
that a host call is counted at all. Deleting the call scope from
`cultmesh_quic_next_event` outright leaves every configuration green — the
pollers finish before the teardown reaches them — and so does inverting the
comparison that makes a positive timeout block, which empties the library before
the close even begins. A scenario that only sleeps and hopes cannot tell any of
that apart from the real thing.

So the bridge says it itself, in development builds only (section 7 of the
header, behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from every shipped binary):
the peak number of host calls counted inside the library, the number counted
when the close began its wait, and a hold that parks a call inside the library
until the scenario releases it. A held call returns the 0 an empty queue would
have and touches the runtime no further, so a bridge that frees the runtime
under it is reported by the scenario rather than crashing inside it.

`holdclose` drives the quiesce through that seam: the calls are counted, they
were still counted when the close began, and the close did not return while they
were held. `closerace` keeps hunting the race and now also refuses to pass when
a poller left before the close began, which is a scenario that raced nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 00:23:04 +02:00

94 lines
5.4 KiB
CMake

cmake_minimum_required(VERSION 3.24)
project(gamecult_mesh_quic_native LANGUAGES CXX)
# Two platforms, one source. Windows takes MsQuic from the pinned OpenSSL NuGet
# layout (MSQUIC_ROOT); Linux takes it from the extracted libmsquic deb, whose
# headers and library land in unrelated places, so they are named separately.
if(NOT WIN32 AND NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux"))
message(FATAL_ERROR "The CultMesh native QUIC bridge supports Windows x64 and Linux x64")
endif()
add_library(gamecult_mesh_quic_native SHARED cultmesh_quic_native.cpp)
target_compile_features(gamecult_mesh_quic_native PRIVATE cxx_std_20)
# include/cultmesh_quic_native.h is the host-facing contract, not an internal
# detail: the bridge includes it, and so does any consumer that can.
target_include_directories(gamecult_mesh_quic_native PUBLIC include)
if(WIN32)
if(NOT DEFINED MSQUIC_ROOT)
message(FATAL_ERROR "MSQUIC_ROOT must point at the extracted Microsoft.Native.Quic.MsQuic.OpenSSL package build/native directory")
endif()
# Static CRT. A Node or Unity host without the Visual C++ redistributable
# installed cannot load a DLL that imports vcruntime140.dll, and the failure
# arrives as "the specified module could not be found" with nothing naming the
# missing runtime. The bridge carries its own instead.
set_property(TARGET gamecult_mesh_quic_native PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
target_compile_definitions(gamecult_mesh_quic_native PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_include_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_ROOT}/include")
target_link_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_ROOT}/lib/x64")
# crypt32 and bcrypt belong to the v1 shim alone: CryptHashCertificate2
# compares the Unity pin. Nothing in the v2 path needs them. ntdll carries
# RtlIpv4/6StringToAddressEx, which msquic_winuser.h's inline QuicAddrFromString
# calls to parse the listener host; the Linux header parses it in-tree instead.
target_link_libraries(gamecult_mesh_quic_native PRIVATE msquic crypt32 bcrypt ntdll)
# The DLL is committed beside its source, so a rebuild must reproduce it: /Brepro drops the link timestamps.
target_compile_options(gamecult_mesh_quic_native PRIVATE /Brepro)
target_link_options(gamecult_mesh_quic_native PRIVATE /Brepro)
else()
if(NOT DEFINED MSQUIC_INCLUDE_DIR OR NOT DEFINED MSQUIC_LIB_DIR)
message(FATAL_ERROR "MSQUIC_INCLUDE_DIR and MSQUIC_LIB_DIR must point at the msquic v2.5.9 headers and the extracted libmsquic library directory")
endif()
target_include_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_INCLUDE_DIR}")
target_link_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_LIB_DIR}")
target_link_libraries(gamecult_mesh_quic_native PRIVATE msquic pthread)
# Only the CULTMESH_API exports leave the .so; everything else stays internal.
target_compile_options(gamecult_mesh_quic_native PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
# libmsquic.so.2 ships beside the bridge rather than being installed
# system-wide, so the loader is pointed at the bridge's own directory.
set_target_properties(gamecult_mesh_quic_native PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "$ORIGIN")
endif()
set_target_properties(gamecult_mesh_quic_native PROPERTIES
OUTPUT_NAME "gamecult_mesh_quic_native"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
# The runtime-lifetime scenarios. Off by default: the shipped artifact is the
# library alone, and a delivery build has no business producing an executable.
# CULTMESH_QUIC_DEBUG_ASSERTS goes with them, never into a shipped binary — it
# makes the bridge state the quiesce invariant instead of leaving it to whatever
# a sanitizer happens to catch.
option(CULTMESH_QUIC_BUILD_TESTS "Build the runtime-lifetime scenario runner" OFF)
option(CULTMESH_QUIC_DEBUG_ASSERTS "Abort the bridge on a violated internal invariant" OFF)
if(CULTMESH_QUIC_DEBUG_ASSERTS)
target_compile_definitions(gamecult_mesh_quic_native PRIVATE CULTMESH_QUIC_DEBUG_ASSERTS)
endif()
if(CULTMESH_QUIC_BUILD_TESTS)
add_executable(cultmesh_quic_native_tests tests/cultmesh_quic_native_tests.cpp)
target_compile_features(cultmesh_quic_native_tests PRIVATE cxx_std_20)
# The header only. The scenarios drive the bridge as a host does, so they get
# no more of it than a host has.
target_link_libraries(cultmesh_quic_native_tests PRIVATE gamecult_mesh_quic_native)
# The scenarios see the development seam (section 7 of the header) exactly when
# the library has one, so `holdclose` compiles out of the same builds the
# bridge's own assertions compile out of.
if(CULTMESH_QUIC_DEBUG_ASSERTS)
target_compile_definitions(cultmesh_quic_native_tests PRIVATE CULTMESH_QUIC_DEBUG_ASSERTS)
endif()
set_target_properties(cultmesh_quic_native_tests PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
if(NOT WIN32)
target_link_libraries(cultmesh_quic_native_tests PRIVATE pthread)
# The linker resolves the bridge's own NEEDED libmsquic.so.2 while linking
# this executable, and the runtime copy only lands beside the binaries after
# the build; point it at the extracted package instead.
target_link_options(cultmesh_quic_native_tests PRIVATE "-Wl,-rpath-link,${MSQUIC_LIB_DIR}")
set_target_properties(cultmesh_quic_native_tests PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "$ORIGIN")
endif()
endif()