Compare commits
7 Commits
a097f4d4b0
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ee1cdeaeca | |||
| 2508f12e86 | |||
| 0260b4a0a0 | |||
| b71f740b9a | |||
| 0e639abcd2 | |||
| 2e95c941ba | |||
| 2f0662e2bb |
+1
-4
@@ -12,8 +12,5 @@ cmake-build-debug/
|
||||
compile_commands.json
|
||||
*.md
|
||||
|
||||
# cloned by FetchContent at configure time
|
||||
external/glm/
|
||||
external/entt/
|
||||
external/jolt/
|
||||
# cloned by FetchContent into the build tree at configure time
|
||||
external/tracy/
|
||||
|
||||
@@ -2,6 +2,7 @@ build/
|
||||
Debug/
|
||||
Release/
|
||||
Testing/
|
||||
dist/
|
||||
.cache
|
||||
imgui.ini
|
||||
|
||||
|
||||
+4
-8
@@ -1,7 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
project(towards)
|
||||
project(tw_common)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
include(cmake/CompilerOptions.cmake)
|
||||
include(cmake/Dependencies.cmake)
|
||||
@@ -40,18 +42,12 @@ target_include_directories(${PROJECT_NAME}
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
tw::network
|
||||
tw::protocol
|
||||
loft::common
|
||||
loft::base
|
||||
loft::render_graph
|
||||
spdlog::spdlog
|
||||
glm::glm
|
||||
EnTT::EnTT
|
||||
Jolt
|
||||
tl::expected
|
||||
Tracy::TracyClient
|
||||
pqxx
|
||||
pq
|
||||
)
|
||||
|
||||
include(cmake/Modules.cmake)
|
||||
|
||||
@@ -4,6 +4,7 @@ Game engine built as a modular monolith.
|
||||
|
||||
## Building
|
||||
|
||||
### Locally
|
||||
Dependencies:
|
||||
- Conan 2 (`pipx install conan`, or `pip install conan`)
|
||||
- Vulkan SDK
|
||||
@@ -27,8 +28,8 @@ $ conan install . --output-folder=build --build=missing -s build_type=Debug
|
||||
Configure and build using the generated preset:
|
||||
|
||||
```bash
|
||||
$ cmake --preset conan-debug
|
||||
$ cmake --build --preset conan-debug
|
||||
$ cmake -S . -B ./build -DCMAKE_TOOLCHAIN_FILE="build/conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Debug
|
||||
$ cmake --build ./build
|
||||
```
|
||||
|
||||
For a release build, repeat both steps with `-s build_type=Release` and the
|
||||
@@ -41,3 +42,43 @@ Server is at `build/modules/client/mmo_server`
|
||||
Client is at `build/modules/client/mmo_client`
|
||||
|
||||
First run the server, so that client can connect. The server will try to use port 8080, but if it is already occupied, it will try use the next free higher one.
|
||||
|
||||
### Docker
|
||||
|
||||
It also possible to build the project using Docker. Advantage is that you do not have to set up the environment. The current Dockerfile also uses older linux distribution to be backwards compatible with older systems. To build everything, build the Dockerfile at `docker/Dockerfile` with until target `builder`:
|
||||
|
||||
```
|
||||
$ docker build -f docker/Dockerfile --target builder -t tw_builder:jammy .
|
||||
```
|
||||
|
||||
To easily export the built executables, run the target `export` of the same Dockerfile.
|
||||
|
||||
```
|
||||
$ docker build -f docker/Dockerfile --target export --output type=local,dest=dist .
|
||||
```
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
### Server
|
||||
|
||||
To run the system as inteded, run the `tw_server` executable first. It should output something like this:
|
||||
|
||||
```
|
||||
[2026-08-05 16:43:53.806] [info] quicr-port=8101 cluster-port=8102 timescaledb=disabled
|
||||
[2026-08-05 16:43:53.807] [info] Running on port: 8101
|
||||
[2026-08-05 16:43:53.807] [info] ZoneClusterLink listening on port 8102
|
||||
[2026-08-05 16:43:53.810] [info] Registered as zone 1 (-5000,-5000) (0,5000)
|
||||
```
|
||||
|
||||
Note the `Running on port: 8101` as it reports on which port it is currently running. Use it when connecting the clients.
|
||||
|
||||
### Client
|
||||
|
||||
Run the `tw_client` for connecting to the game. It should open into a lobby, where you can input an address of the server. It should remember the last address used and you can make some favourites. This is useful for testing on multiple cloud instances for example.
|
||||
|
||||
### Mock Client
|
||||
|
||||
The `tw_mock_client` module is for stress testing the server. It simulates 300 connected clients and moves them randomly. All those simulated players are actual established connections, which means the bandwidth of the server will be accurate.
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ set(LFT_ENABLE_EXAMPLES OFF CACHE BOOL "Disable loft examples" FORCE)
|
||||
set(FASTNOISE2_NOISETOOL OFF CACHE BOOL "Disable FastNoise2 graph tool" FORCE)
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(Protobuf CONFIG REQUIRED)
|
||||
find_package(SDL3 CONFIG REQUIRED)
|
||||
find_library(LIBDECOR_LIB decor-0)
|
||||
find_package(Protobuf CONFIG REQUIRED VERSION 3.75)
|
||||
|
||||
include(FetchContent)
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Release;Distribution")
|
||||
|
||||
+22
-7
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
timescaledb:
|
||||
image: timescale/timescaledb:latest-pg16
|
||||
image: timescale/timescaledb:latest-pg15
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: mmo
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- timescaledb_data:/var/lib/postgresql/data
|
||||
- timescaledb_data:/var/lib/towards_db/data
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
@@ -24,13 +24,28 @@ services:
|
||||
depends_on:
|
||||
- timescaledb
|
||||
|
||||
zone-server:
|
||||
build: .
|
||||
chat-server:
|
||||
profiles: [e2e]
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
target: chat-server
|
||||
ports:
|
||||
- "8101:8101/udp"
|
||||
- "8102:8102/udp"
|
||||
- "8099:8099/udp"
|
||||
|
||||
chat-mock-client:
|
||||
profiles: [e2e]
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
target: chat-mock-client
|
||||
# Address.hpp parses peers with inet_addr, which takes literal IPv4 only and
|
||||
# never consults DNS, so a service name is not addressable. Sharing the
|
||||
# server's network namespace makes its default 127.0.0.1 land on the server.
|
||||
network_mode: "service:chat-server"
|
||||
depends_on:
|
||||
- timescaledb
|
||||
- chat-server
|
||||
command: ["--channel", "1"]
|
||||
|
||||
volumes:
|
||||
timescaledb_data:
|
||||
|
||||
+20
-3
@@ -1,14 +1,31 @@
|
||||
[requires]
|
||||
protobuf/7.35.0
|
||||
sdl/2.32.10
|
||||
sdl/3.4.8
|
||||
libpqxx/8.0.2
|
||||
|
||||
[tool_requires]
|
||||
protobuf/7.35.0
|
||||
|
||||
[options]
|
||||
sdl/*:pulse=False
|
||||
libffi/*:shared=True
|
||||
protobuf/*:shared=False
|
||||
libpqxx/*:shared=False
|
||||
libpq/*:shared=True
|
||||
sdl/*:shared=False
|
||||
sdl/*:camera=False
|
||||
sdl/*:dialog=False
|
||||
sdl/*:tray=False
|
||||
sdl/*:sensor=False
|
||||
sdl/*:alsa=True
|
||||
sdl/*:pulseaudio=False
|
||||
sdl/*:sndio=False
|
||||
sdl/*:opengl=False
|
||||
sdl/*:opengles=False
|
||||
sdl/*:vulkan=True
|
||||
sdl/*:joystick=False
|
||||
sdl/*:haptic=False
|
||||
sdl/*:hidapi=False
|
||||
|
||||
[generators]
|
||||
CMakeDeps
|
||||
CMakeToolchain
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# Builds against Ubuntu 22.04 (glibc 2.35) so the binaries also run on
|
||||
# distributions older than the developer machine. Two things make that work:
|
||||
#
|
||||
# * GCC 14 from the toolchain PPA — jammy's own gcc-11 has no <print>, which
|
||||
# eight translation units use.
|
||||
# * -static-libstdc++ -static-libgcc — the target then needs nothing newer
|
||||
# than the glibc of this base image.
|
||||
#
|
||||
# docker build -f docker/Dockerfile --target mock-client -t tw_mock_client .
|
||||
# docker build -f docker/Dockerfile --target test .
|
||||
# docker build -f docker/Dockerfile --target export --output type=local,dest=dist .
|
||||
|
||||
ARG UBUNTU_VERSION=22.04
|
||||
|
||||
FROM ubuntu:${UBUNTU_VERSION} AS toolchain
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG VULKAN_SDK_VERSION=1.4.313
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
software-properties-common ca-certificates gnupg wget \
|
||||
&& add-apt-repository -y ppa:ubuntu-toolchain-r/test \
|
||||
&& wget -qO /etc/apt/trusted.gpg.d/lunarg.asc \
|
||||
https://packages.lunarg.com/lunarg-signing-key-pub.asc \
|
||||
&& wget -qO /etc/apt/sources.list.d/lunarg-vulkan.list \
|
||||
"https://packages.lunarg.com/vulkan/${VULKAN_SDK_VERSION}/lunarg-vulkan-${VULKAN_SDK_VERSION}-jammy.list" \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc-14 g++-14 \
|
||||
git make ninja-build ccache pkg-config \
|
||||
python3-pip \
|
||||
vulkan-headers libvulkan-dev glslang-tools \
|
||||
libdecor-0-dev libpqxx-dev
|
||||
|
||||
ARG CMAKE_VERSION=4.4.0
|
||||
ARG CONAN_VERSION=2.31.1
|
||||
RUN pip3 install --no-cache-dir "cmake==${CMAKE_VERSION}" "conan==${CONAN_VERSION}"
|
||||
|
||||
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-14 \
|
||||
--slave /usr/bin/gcov gcov /usr/bin/gcov-14 \
|
||||
&& update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-14 100 \
|
||||
&& update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-14 100
|
||||
|
||||
ENV CC=gcc
|
||||
ENV CXX=g++
|
||||
|
||||
COPY docker/conan/jammy /etc/conan/jammy
|
||||
|
||||
|
||||
|
||||
# toolchain installs Conan packages
|
||||
FROM toolchain AS deps
|
||||
|
||||
COPY conanfile.txt /src/conanfile.txt
|
||||
|
||||
RUN conan install /src/conanfile.txt \
|
||||
--profile:all=/etc/conan/jammy \
|
||||
--build=missing \
|
||||
--output-folder=/deps
|
||||
|
||||
|
||||
|
||||
# Builds the project
|
||||
FROM deps AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
RUN --mount=type=cache,target=/build \
|
||||
--mount=type=cache,target=/root/.ccache \
|
||||
cmake -S /src -B /build -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE=/deps/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DUSE_MOLD_LINKER=OFF \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-static-libstdc++ -static-libgcc" \
|
||||
&& cmake --build /build \
|
||||
&& mkdir -p /out/bin /out/shaders \
|
||||
&& cp /build/modules/client/tw_client \
|
||||
/build/modules/mock_client/tw_mock_client \
|
||||
/build/modules/server/tw_server \
|
||||
/build/modules/chat_service/chat_server_exe/tw_chat_server_exe \
|
||||
/build/modules/chat_service/chat_service/tests/chat_mock_client/tw_chat_mock_client \
|
||||
/out/bin/ \
|
||||
&& cp /build/modules/client/shaders/*.spirv /out/shaders/
|
||||
|
||||
|
||||
|
||||
# Extracts client
|
||||
FROM scratch AS export
|
||||
|
||||
COPY --from=builder /out/bin/tw_client /tw_client
|
||||
COPY --from=builder /out/bin/tw_server /tw_server
|
||||
COPY --from=builder /out/bin/tw_mock_client /tw_mock_client
|
||||
COPY --from=builder /out/shaders/ /shaders/
|
||||
|
||||
|
||||
|
||||
# Mock client
|
||||
FROM gcr.io/distroless/base-debian12:nonroot AS mock-client
|
||||
|
||||
COPY --from=builder /out/bin/tw_mock_client /usr/local/bin/
|
||||
ENTRYPOINT ["/usr/local/bin/tw_mock_client"]
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[settings]
|
||||
arch=x86_64
|
||||
build_type=Release
|
||||
compiler=gcc
|
||||
compiler.version=14
|
||||
compiler.cppstd=gnu23
|
||||
compiler.libcxx=libstdc++11
|
||||
os=Linux
|
||||
|
||||
[conf]
|
||||
# xorg/system and egl/system resolve to apt packages; the build runs as root so
|
||||
# no sudo is available or needed.
|
||||
tools.system.package_manager:mode=install
|
||||
tools.system.package_manager:sudo=False
|
||||
Vendored
+2
-4
@@ -3,10 +3,9 @@ project(imgui)
|
||||
file(GLOB FILES
|
||||
./*.cpp
|
||||
./backends/imgui_impl_vulkan.cpp
|
||||
./backends/imgui_impl_sdl2.cpp
|
||||
./backends/imgui_impl_sdl3.cpp
|
||||
)
|
||||
|
||||
find_package(SDL2 QUIET)
|
||||
find_package(Vulkan REQUIRED)
|
||||
|
||||
add_library(${PROJECT_NAME} OBJECT ${FILES})
|
||||
@@ -21,11 +20,10 @@ target_include_directories(${PROJECT_NAME}
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
volk::volk
|
||||
${SDL2_LIBRARIES}
|
||||
sdl::sdl
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${volk_INCLUDE_DIRS}
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
Vendored
+1
-1
@@ -25,7 +25,7 @@ target_include_directories(${PROJECT_NAME}
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
volk::volk
|
||||
${SDL2_LIBRARIES}
|
||||
sdl::sdl
|
||||
imgui::imgui
|
||||
)
|
||||
#
|
||||
|
||||
Vendored
+2
-3
@@ -5,13 +5,13 @@ option(LFT_ENABLE_EXAMPLES "Enable building examples" ON)
|
||||
if (WIN32)
|
||||
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_WIN32_KHR)
|
||||
elseif(UNIX)
|
||||
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_XCB_KHR)
|
||||
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_XCB_KHR VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
endif()
|
||||
|
||||
|
||||
# Please set default paths for vulkan and SDL2
|
||||
set(VULKAN_PATH "$ENV{VULKAN_SDK}" CACHE STRING "Path to vulkan directory")
|
||||
set(SDL2_PATH "$ENV{VULKAN_SDK}/cmake" CACHE STRING "Path to SDL2 directory")
|
||||
# set(SDL2_PATH "$ENV{VULKAN_SDK}/cmake" CACHE STRING "Path to SDL2 directory")
|
||||
|
||||
list(APPEND CMAKE_PREFIX_PATH "$ENV{VULKAN_SDK}/cmake")
|
||||
|
||||
@@ -51,7 +51,6 @@ add_definitions(
|
||||
)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
|
||||
Vendored
-140
@@ -1,140 +0,0 @@
|
||||
# CMAKE generated file: DO NOT EDIT!
|
||||
# Generated by "Unix Makefiles" Generator, CMake Version 3.30
|
||||
|
||||
# Default target executed when no arguments are given to make.
|
||||
default_target: all
|
||||
.PHONY : default_target
|
||||
|
||||
# Allow only one "make -f Makefile2" at a time, but pass parallelism.
|
||||
.NOTPARALLEL:
|
||||
|
||||
#=============================================================================
|
||||
# Special targets provided by cmake.
|
||||
|
||||
# Disable implicit rules so canonical targets will work.
|
||||
.SUFFIXES:
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : %,v
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : RCS/%
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : RCS/%,v
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : SCCS/s.%
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : s.%
|
||||
|
||||
.SUFFIXES: .hpux_make_needs_suffix_list
|
||||
|
||||
# Command-line flag to silence nested $(MAKE).
|
||||
$(VERBOSE)MAKESILENT = -s
|
||||
|
||||
#Suppress display of executed commands.
|
||||
$(VERBOSE).SILENT:
|
||||
|
||||
# A target that is always out of date.
|
||||
cmake_force:
|
||||
.PHONY : cmake_force
|
||||
|
||||
#=============================================================================
|
||||
# Set environment variables for the build.
|
||||
|
||||
# The shell in which to execute make rules.
|
||||
SHELL = /bin/sh
|
||||
|
||||
# The CMake executable.
|
||||
CMAKE_COMMAND = /usr/bin/cmake
|
||||
|
||||
# The command to remove a file.
|
||||
RM = /usr/bin/cmake -E rm -f
|
||||
|
||||
# Escaping for special characters.
|
||||
EQUALS = =
|
||||
|
||||
# The top-level source directory on which CMake was run.
|
||||
CMAKE_SOURCE_DIR = /home/martin/projects/mmo
|
||||
|
||||
# The top-level build directory on which CMake was run.
|
||||
CMAKE_BINARY_DIR = /home/martin/projects/mmo
|
||||
|
||||
#=============================================================================
|
||||
# Targets provided globally by CMake.
|
||||
|
||||
# Special rule for the target edit_cache
|
||||
edit_cache:
|
||||
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..."
|
||||
/usr/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
|
||||
.PHONY : edit_cache
|
||||
|
||||
# Special rule for the target edit_cache
|
||||
edit_cache/fast: edit_cache
|
||||
.PHONY : edit_cache/fast
|
||||
|
||||
# Special rule for the target rebuild_cache
|
||||
rebuild_cache:
|
||||
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..."
|
||||
/usr/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
|
||||
.PHONY : rebuild_cache
|
||||
|
||||
# Special rule for the target rebuild_cache
|
||||
rebuild_cache/fast: rebuild_cache
|
||||
.PHONY : rebuild_cache/fast
|
||||
|
||||
# The main all target
|
||||
all: cmake_check_build_system
|
||||
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -E cmake_progress_start /home/martin/projects/mmo/CMakeFiles /home/martin/projects/mmo/external/loft//CMakeFiles/progress.marks
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/all
|
||||
$(CMAKE_COMMAND) -E cmake_progress_start /home/martin/projects/mmo/CMakeFiles 0
|
||||
.PHONY : all
|
||||
|
||||
# The main clean target
|
||||
clean:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/clean
|
||||
.PHONY : clean
|
||||
|
||||
# The main clean target
|
||||
clean/fast: clean
|
||||
.PHONY : clean/fast
|
||||
|
||||
# Prepare targets for installation.
|
||||
preinstall: all
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/preinstall
|
||||
.PHONY : preinstall
|
||||
|
||||
# Prepare targets for installation.
|
||||
preinstall/fast:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/preinstall
|
||||
.PHONY : preinstall/fast
|
||||
|
||||
# clear depends
|
||||
depend:
|
||||
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
|
||||
.PHONY : depend
|
||||
|
||||
# Help Target
|
||||
help:
|
||||
@echo "The following are some of the valid targets for this Makefile:"
|
||||
@echo "... all (the default if no target is provided)"
|
||||
@echo "... clean"
|
||||
@echo "... depend"
|
||||
@echo "... edit_cache"
|
||||
@echo "... rebuild_cache"
|
||||
.PHONY : help
|
||||
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# Special targets to cleanup operation of make.
|
||||
|
||||
# Special rule to run CMake to check the build system integrity.
|
||||
# No rule that depends on this can have commands that come from listfiles
|
||||
# because they might be regenerated.
|
||||
cmake_check_build_system:
|
||||
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
|
||||
.PHONY : cmake_check_build_system
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ target_link_libraries(
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
${SDL2_LIBRARIES}
|
||||
sdl::sdl
|
||||
)
|
||||
|
||||
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
|
||||
|
||||
@@ -17,7 +17,8 @@ target_include_directories(${PROJECT_NAME}
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
volk::volk
|
||||
${SDL2_LIBRARIES})
|
||||
sdl::sdl
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
|
||||
Vendored
-560
@@ -1,560 +0,0 @@
|
||||
# CMAKE generated file: DO NOT EDIT!
|
||||
# Generated by "Unix Makefiles" Generator, CMake Version 3.30
|
||||
|
||||
# Default target executed when no arguments are given to make.
|
||||
default_target: all
|
||||
.PHONY : default_target
|
||||
|
||||
# Allow only one "make -f Makefile2" at a time, but pass parallelism.
|
||||
.NOTPARALLEL:
|
||||
|
||||
#=============================================================================
|
||||
# Special targets provided by cmake.
|
||||
|
||||
# Disable implicit rules so canonical targets will work.
|
||||
.SUFFIXES:
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : %,v
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : RCS/%
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : RCS/%,v
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : SCCS/s.%
|
||||
|
||||
# Disable VCS-based implicit rules.
|
||||
% : s.%
|
||||
|
||||
.SUFFIXES: .hpux_make_needs_suffix_list
|
||||
|
||||
# Command-line flag to silence nested $(MAKE).
|
||||
$(VERBOSE)MAKESILENT = -s
|
||||
|
||||
#Suppress display of executed commands.
|
||||
$(VERBOSE).SILENT:
|
||||
|
||||
# A target that is always out of date.
|
||||
cmake_force:
|
||||
.PHONY : cmake_force
|
||||
|
||||
#=============================================================================
|
||||
# Set environment variables for the build.
|
||||
|
||||
# The shell in which to execute make rules.
|
||||
SHELL = /bin/sh
|
||||
|
||||
# The CMake executable.
|
||||
CMAKE_COMMAND = /usr/bin/cmake
|
||||
|
||||
# The command to remove a file.
|
||||
RM = /usr/bin/cmake -E rm -f
|
||||
|
||||
# Escaping for special characters.
|
||||
EQUALS = =
|
||||
|
||||
# The top-level source directory on which CMake was run.
|
||||
CMAKE_SOURCE_DIR = /home/martin/projects/mmo
|
||||
|
||||
# The top-level build directory on which CMake was run.
|
||||
CMAKE_BINARY_DIR = /home/martin/projects/mmo
|
||||
|
||||
#=============================================================================
|
||||
# Targets provided globally by CMake.
|
||||
|
||||
# Special rule for the target edit_cache
|
||||
edit_cache:
|
||||
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..."
|
||||
/usr/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
|
||||
.PHONY : edit_cache
|
||||
|
||||
# Special rule for the target edit_cache
|
||||
edit_cache/fast: edit_cache
|
||||
.PHONY : edit_cache/fast
|
||||
|
||||
# Special rule for the target rebuild_cache
|
||||
rebuild_cache:
|
||||
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..."
|
||||
/usr/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
|
||||
.PHONY : rebuild_cache
|
||||
|
||||
# Special rule for the target rebuild_cache
|
||||
rebuild_cache/fast: rebuild_cache
|
||||
.PHONY : rebuild_cache/fast
|
||||
|
||||
# The main all target
|
||||
all: cmake_check_build_system
|
||||
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -E cmake_progress_start /home/martin/projects/mmo/CMakeFiles /home/martin/projects/mmo/external/loft/modules/base//CMakeFiles/progress.marks
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/all
|
||||
$(CMAKE_COMMAND) -E cmake_progress_start /home/martin/projects/mmo/CMakeFiles 0
|
||||
.PHONY : all
|
||||
|
||||
# The main clean target
|
||||
clean:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/clean
|
||||
.PHONY : clean
|
||||
|
||||
# The main clean target
|
||||
clean/fast: clean
|
||||
.PHONY : clean/fast
|
||||
|
||||
# Prepare targets for installation.
|
||||
preinstall: all
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/preinstall
|
||||
.PHONY : preinstall
|
||||
|
||||
# Prepare targets for installation.
|
||||
preinstall/fast:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/preinstall
|
||||
.PHONY : preinstall/fast
|
||||
|
||||
# clear depends
|
||||
depend:
|
||||
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
|
||||
.PHONY : depend
|
||||
|
||||
# Convenience name for target.
|
||||
external/loft/modules/base/CMakeFiles/loft_base.dir/rule:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/CMakeFiles/loft_base.dir/rule
|
||||
.PHONY : external/loft/modules/base/CMakeFiles/loft_base.dir/rule
|
||||
|
||||
# Convenience name for target.
|
||||
loft_base: external/loft/modules/base/CMakeFiles/loft_base.dir/rule
|
||||
.PHONY : loft_base
|
||||
|
||||
# fast build rule for target.
|
||||
loft_base/fast:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/build
|
||||
.PHONY : loft_base/fast
|
||||
|
||||
src/FramebufferBuilder.o: src/FramebufferBuilder.cpp.o
|
||||
.PHONY : src/FramebufferBuilder.o
|
||||
|
||||
# target to build an object file
|
||||
src/FramebufferBuilder.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/FramebufferBuilder.cpp.o
|
||||
.PHONY : src/FramebufferBuilder.cpp.o
|
||||
|
||||
src/FramebufferBuilder.i: src/FramebufferBuilder.cpp.i
|
||||
.PHONY : src/FramebufferBuilder.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/FramebufferBuilder.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/FramebufferBuilder.cpp.i
|
||||
.PHONY : src/FramebufferBuilder.cpp.i
|
||||
|
||||
src/FramebufferBuilder.s: src/FramebufferBuilder.cpp.s
|
||||
.PHONY : src/FramebufferBuilder.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/FramebufferBuilder.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/FramebufferBuilder.cpp.s
|
||||
.PHONY : src/FramebufferBuilder.cpp.s
|
||||
|
||||
src/Gpu.o: src/Gpu.cpp.o
|
||||
.PHONY : src/Gpu.o
|
||||
|
||||
# target to build an object file
|
||||
src/Gpu.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Gpu.cpp.o
|
||||
.PHONY : src/Gpu.cpp.o
|
||||
|
||||
src/Gpu.i: src/Gpu.cpp.i
|
||||
.PHONY : src/Gpu.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/Gpu.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Gpu.cpp.i
|
||||
.PHONY : src/Gpu.cpp.i
|
||||
|
||||
src/Gpu.s: src/Gpu.cpp.s
|
||||
.PHONY : src/Gpu.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/Gpu.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Gpu.cpp.s
|
||||
.PHONY : src/Gpu.cpp.s
|
||||
|
||||
src/Instance.o: src/Instance.cpp.o
|
||||
.PHONY : src/Instance.o
|
||||
|
||||
# target to build an object file
|
||||
src/Instance.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Instance.cpp.o
|
||||
.PHONY : src/Instance.cpp.o
|
||||
|
||||
src/Instance.i: src/Instance.cpp.i
|
||||
.PHONY : src/Instance.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/Instance.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Instance.cpp.i
|
||||
.PHONY : src/Instance.cpp.i
|
||||
|
||||
src/Instance.s: src/Instance.cpp.s
|
||||
.PHONY : src/Instance.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/Instance.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Instance.cpp.s
|
||||
.PHONY : src/Instance.cpp.s
|
||||
|
||||
src/io/file.o: src/io/file.cpp.o
|
||||
.PHONY : src/io/file.o
|
||||
|
||||
# target to build an object file
|
||||
src/io/file.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/io/file.cpp.o
|
||||
.PHONY : src/io/file.cpp.o
|
||||
|
||||
src/io/file.i: src/io/file.cpp.i
|
||||
.PHONY : src/io/file.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/io/file.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/io/file.cpp.i
|
||||
.PHONY : src/io/file.cpp.i
|
||||
|
||||
src/io/file.s: src/io/file.cpp.s
|
||||
.PHONY : src/io/file.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/io/file.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/io/file.cpp.s
|
||||
.PHONY : src/io/file.cpp.s
|
||||
|
||||
src/resources/Buffer.o: src/resources/Buffer.cpp.o
|
||||
.PHONY : src/resources/Buffer.o
|
||||
|
||||
# target to build an object file
|
||||
src/resources/Buffer.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Buffer.cpp.o
|
||||
.PHONY : src/resources/Buffer.cpp.o
|
||||
|
||||
src/resources/Buffer.i: src/resources/Buffer.cpp.i
|
||||
.PHONY : src/resources/Buffer.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/resources/Buffer.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Buffer.cpp.i
|
||||
.PHONY : src/resources/Buffer.cpp.i
|
||||
|
||||
src/resources/Buffer.s: src/resources/Buffer.cpp.s
|
||||
.PHONY : src/resources/Buffer.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/resources/Buffer.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Buffer.cpp.s
|
||||
.PHONY : src/resources/Buffer.cpp.s
|
||||
|
||||
src/resources/BufferBusWriter.o: src/resources/BufferBusWriter.cpp.o
|
||||
.PHONY : src/resources/BufferBusWriter.o
|
||||
|
||||
# target to build an object file
|
||||
src/resources/BufferBusWriter.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/BufferBusWriter.cpp.o
|
||||
.PHONY : src/resources/BufferBusWriter.cpp.o
|
||||
|
||||
src/resources/BufferBusWriter.i: src/resources/BufferBusWriter.cpp.i
|
||||
.PHONY : src/resources/BufferBusWriter.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/resources/BufferBusWriter.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/BufferBusWriter.cpp.i
|
||||
.PHONY : src/resources/BufferBusWriter.cpp.i
|
||||
|
||||
src/resources/BufferBusWriter.s: src/resources/BufferBusWriter.cpp.s
|
||||
.PHONY : src/resources/BufferBusWriter.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/resources/BufferBusWriter.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/BufferBusWriter.cpp.s
|
||||
.PHONY : src/resources/BufferBusWriter.cpp.s
|
||||
|
||||
src/resources/DefaultAllocator.o: src/resources/DefaultAllocator.cpp.o
|
||||
.PHONY : src/resources/DefaultAllocator.o
|
||||
|
||||
# target to build an object file
|
||||
src/resources/DefaultAllocator.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/DefaultAllocator.cpp.o
|
||||
.PHONY : src/resources/DefaultAllocator.cpp.o
|
||||
|
||||
src/resources/DefaultAllocator.i: src/resources/DefaultAllocator.cpp.i
|
||||
.PHONY : src/resources/DefaultAllocator.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/resources/DefaultAllocator.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/DefaultAllocator.cpp.i
|
||||
.PHONY : src/resources/DefaultAllocator.cpp.i
|
||||
|
||||
src/resources/DefaultAllocator.s: src/resources/DefaultAllocator.cpp.s
|
||||
.PHONY : src/resources/DefaultAllocator.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/resources/DefaultAllocator.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/DefaultAllocator.cpp.s
|
||||
.PHONY : src/resources/DefaultAllocator.cpp.s
|
||||
|
||||
src/resources/Image.o: src/resources/Image.cpp.o
|
||||
.PHONY : src/resources/Image.o
|
||||
|
||||
# target to build an object file
|
||||
src/resources/Image.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Image.cpp.o
|
||||
.PHONY : src/resources/Image.cpp.o
|
||||
|
||||
src/resources/Image.i: src/resources/Image.cpp.i
|
||||
.PHONY : src/resources/Image.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/resources/Image.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Image.cpp.i
|
||||
.PHONY : src/resources/Image.cpp.i
|
||||
|
||||
src/resources/Image.s: src/resources/Image.cpp.s
|
||||
.PHONY : src/resources/Image.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/resources/Image.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Image.cpp.s
|
||||
.PHONY : src/resources/Image.cpp.s
|
||||
|
||||
src/resources/ImageBusWriter.o: src/resources/ImageBusWriter.cpp.o
|
||||
.PHONY : src/resources/ImageBusWriter.o
|
||||
|
||||
# target to build an object file
|
||||
src/resources/ImageBusWriter.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageBusWriter.cpp.o
|
||||
.PHONY : src/resources/ImageBusWriter.cpp.o
|
||||
|
||||
src/resources/ImageBusWriter.i: src/resources/ImageBusWriter.cpp.i
|
||||
.PHONY : src/resources/ImageBusWriter.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/resources/ImageBusWriter.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageBusWriter.cpp.i
|
||||
.PHONY : src/resources/ImageBusWriter.cpp.i
|
||||
|
||||
src/resources/ImageBusWriter.s: src/resources/ImageBusWriter.cpp.s
|
||||
.PHONY : src/resources/ImageBusWriter.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/resources/ImageBusWriter.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageBusWriter.cpp.s
|
||||
.PHONY : src/resources/ImageBusWriter.cpp.s
|
||||
|
||||
src/resources/ImageView.o: src/resources/ImageView.cpp.o
|
||||
.PHONY : src/resources/ImageView.o
|
||||
|
||||
# target to build an object file
|
||||
src/resources/ImageView.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageView.cpp.o
|
||||
.PHONY : src/resources/ImageView.cpp.o
|
||||
|
||||
src/resources/ImageView.i: src/resources/ImageView.cpp.i
|
||||
.PHONY : src/resources/ImageView.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/resources/ImageView.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageView.cpp.i
|
||||
.PHONY : src/resources/ImageView.cpp.i
|
||||
|
||||
src/resources/ImageView.s: src/resources/ImageView.cpp.s
|
||||
.PHONY : src/resources/ImageView.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/resources/ImageView.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageView.cpp.s
|
||||
.PHONY : src/resources/ImageView.cpp.s
|
||||
|
||||
src/resources/MipmapGenerator.o: src/resources/MipmapGenerator.cpp.o
|
||||
.PHONY : src/resources/MipmapGenerator.o
|
||||
|
||||
# target to build an object file
|
||||
src/resources/MipmapGenerator.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/MipmapGenerator.cpp.o
|
||||
.PHONY : src/resources/MipmapGenerator.cpp.o
|
||||
|
||||
src/resources/MipmapGenerator.i: src/resources/MipmapGenerator.cpp.i
|
||||
.PHONY : src/resources/MipmapGenerator.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/resources/MipmapGenerator.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/MipmapGenerator.cpp.i
|
||||
.PHONY : src/resources/MipmapGenerator.cpp.i
|
||||
|
||||
src/resources/MipmapGenerator.s: src/resources/MipmapGenerator.cpp.s
|
||||
.PHONY : src/resources/MipmapGenerator.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/resources/MipmapGenerator.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/MipmapGenerator.cpp.s
|
||||
.PHONY : src/resources/MipmapGenerator.cpp.s
|
||||
|
||||
src/shaders/GlslShaderBuilder.o: src/shaders/GlslShaderBuilder.cpp.o
|
||||
.PHONY : src/shaders/GlslShaderBuilder.o
|
||||
|
||||
# target to build an object file
|
||||
src/shaders/GlslShaderBuilder.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/GlslShaderBuilder.cpp.o
|
||||
.PHONY : src/shaders/GlslShaderBuilder.cpp.o
|
||||
|
||||
src/shaders/GlslShaderBuilder.i: src/shaders/GlslShaderBuilder.cpp.i
|
||||
.PHONY : src/shaders/GlslShaderBuilder.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/shaders/GlslShaderBuilder.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/GlslShaderBuilder.cpp.i
|
||||
.PHONY : src/shaders/GlslShaderBuilder.cpp.i
|
||||
|
||||
src/shaders/GlslShaderBuilder.s: src/shaders/GlslShaderBuilder.cpp.s
|
||||
.PHONY : src/shaders/GlslShaderBuilder.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/shaders/GlslShaderBuilder.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/GlslShaderBuilder.cpp.s
|
||||
.PHONY : src/shaders/GlslShaderBuilder.cpp.s
|
||||
|
||||
src/shaders/Pipeline.o: src/shaders/Pipeline.cpp.o
|
||||
.PHONY : src/shaders/Pipeline.o
|
||||
|
||||
# target to build an object file
|
||||
src/shaders/Pipeline.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/Pipeline.cpp.o
|
||||
.PHONY : src/shaders/Pipeline.cpp.o
|
||||
|
||||
src/shaders/Pipeline.i: src/shaders/Pipeline.cpp.i
|
||||
.PHONY : src/shaders/Pipeline.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/shaders/Pipeline.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/Pipeline.cpp.i
|
||||
.PHONY : src/shaders/Pipeline.cpp.i
|
||||
|
||||
src/shaders/Pipeline.s: src/shaders/Pipeline.cpp.s
|
||||
.PHONY : src/shaders/Pipeline.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/shaders/Pipeline.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/Pipeline.cpp.s
|
||||
.PHONY : src/shaders/Pipeline.cpp.s
|
||||
|
||||
src/shaders/PipelineBuilder.o: src/shaders/PipelineBuilder.cpp.o
|
||||
.PHONY : src/shaders/PipelineBuilder.o
|
||||
|
||||
# target to build an object file
|
||||
src/shaders/PipelineBuilder.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/PipelineBuilder.cpp.o
|
||||
.PHONY : src/shaders/PipelineBuilder.cpp.o
|
||||
|
||||
src/shaders/PipelineBuilder.i: src/shaders/PipelineBuilder.cpp.i
|
||||
.PHONY : src/shaders/PipelineBuilder.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/shaders/PipelineBuilder.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/PipelineBuilder.cpp.i
|
||||
.PHONY : src/shaders/PipelineBuilder.cpp.i
|
||||
|
||||
src/shaders/PipelineBuilder.s: src/shaders/PipelineBuilder.cpp.s
|
||||
.PHONY : src/shaders/PipelineBuilder.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/shaders/PipelineBuilder.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/PipelineBuilder.cpp.s
|
||||
.PHONY : src/shaders/PipelineBuilder.cpp.s
|
||||
|
||||
src/shaders/SpirvShaderBuilder.o: src/shaders/SpirvShaderBuilder.cpp.o
|
||||
.PHONY : src/shaders/SpirvShaderBuilder.o
|
||||
|
||||
# target to build an object file
|
||||
src/shaders/SpirvShaderBuilder.cpp.o:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/SpirvShaderBuilder.cpp.o
|
||||
.PHONY : src/shaders/SpirvShaderBuilder.cpp.o
|
||||
|
||||
src/shaders/SpirvShaderBuilder.i: src/shaders/SpirvShaderBuilder.cpp.i
|
||||
.PHONY : src/shaders/SpirvShaderBuilder.i
|
||||
|
||||
# target to preprocess a source file
|
||||
src/shaders/SpirvShaderBuilder.cpp.i:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/SpirvShaderBuilder.cpp.i
|
||||
.PHONY : src/shaders/SpirvShaderBuilder.cpp.i
|
||||
|
||||
src/shaders/SpirvShaderBuilder.s: src/shaders/SpirvShaderBuilder.cpp.s
|
||||
.PHONY : src/shaders/SpirvShaderBuilder.s
|
||||
|
||||
# target to generate assembly for a file
|
||||
src/shaders/SpirvShaderBuilder.cpp.s:
|
||||
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/SpirvShaderBuilder.cpp.s
|
||||
.PHONY : src/shaders/SpirvShaderBuilder.cpp.s
|
||||
|
||||
# Help Target
|
||||
help:
|
||||
@echo "The following are some of the valid targets for this Makefile:"
|
||||
@echo "... all (the default if no target is provided)"
|
||||
@echo "... clean"
|
||||
@echo "... depend"
|
||||
@echo "... edit_cache"
|
||||
@echo "... rebuild_cache"
|
||||
@echo "... loft_base"
|
||||
@echo "... src/FramebufferBuilder.o"
|
||||
@echo "... src/FramebufferBuilder.i"
|
||||
@echo "... src/FramebufferBuilder.s"
|
||||
@echo "... src/Gpu.o"
|
||||
@echo "... src/Gpu.i"
|
||||
@echo "... src/Gpu.s"
|
||||
@echo "... src/Instance.o"
|
||||
@echo "... src/Instance.i"
|
||||
@echo "... src/Instance.s"
|
||||
@echo "... src/io/file.o"
|
||||
@echo "... src/io/file.i"
|
||||
@echo "... src/io/file.s"
|
||||
@echo "... src/resources/Buffer.o"
|
||||
@echo "... src/resources/Buffer.i"
|
||||
@echo "... src/resources/Buffer.s"
|
||||
@echo "... src/resources/BufferBusWriter.o"
|
||||
@echo "... src/resources/BufferBusWriter.i"
|
||||
@echo "... src/resources/BufferBusWriter.s"
|
||||
@echo "... src/resources/DefaultAllocator.o"
|
||||
@echo "... src/resources/DefaultAllocator.i"
|
||||
@echo "... src/resources/DefaultAllocator.s"
|
||||
@echo "... src/resources/Image.o"
|
||||
@echo "... src/resources/Image.i"
|
||||
@echo "... src/resources/Image.s"
|
||||
@echo "... src/resources/ImageBusWriter.o"
|
||||
@echo "... src/resources/ImageBusWriter.i"
|
||||
@echo "... src/resources/ImageBusWriter.s"
|
||||
@echo "... src/resources/ImageView.o"
|
||||
@echo "... src/resources/ImageView.i"
|
||||
@echo "... src/resources/ImageView.s"
|
||||
@echo "... src/resources/MipmapGenerator.o"
|
||||
@echo "... src/resources/MipmapGenerator.i"
|
||||
@echo "... src/resources/MipmapGenerator.s"
|
||||
@echo "... src/shaders/GlslShaderBuilder.o"
|
||||
@echo "... src/shaders/GlslShaderBuilder.i"
|
||||
@echo "... src/shaders/GlslShaderBuilder.s"
|
||||
@echo "... src/shaders/Pipeline.o"
|
||||
@echo "... src/shaders/Pipeline.i"
|
||||
@echo "... src/shaders/Pipeline.s"
|
||||
@echo "... src/shaders/PipelineBuilder.o"
|
||||
@echo "... src/shaders/PipelineBuilder.i"
|
||||
@echo "... src/shaders/PipelineBuilder.s"
|
||||
@echo "... src/shaders/SpirvShaderBuilder.o"
|
||||
@echo "... src/shaders/SpirvShaderBuilder.i"
|
||||
@echo "... src/shaders/SpirvShaderBuilder.s"
|
||||
.PHONY : help
|
||||
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# Special targets to cleanup operation of make.
|
||||
|
||||
# Special rule to run CMake to check the build system integrity.
|
||||
# No rule that depends on this can have commands that come from listfiles
|
||||
# because they might be regenerated.
|
||||
cmake_check_build_system:
|
||||
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
|
||||
.PHONY : cmake_check_build_system
|
||||
|
||||
@@ -9,7 +9,6 @@ set(LIBS
|
||||
loft_base
|
||||
loft_common
|
||||
loft_window
|
||||
${SDL2_LIBRARIES}
|
||||
volk)
|
||||
|
||||
set(FILES
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@ file(GLOB FILES src/*.c src/*.cpp)
|
||||
|
||||
add_library(${PROJECT_NAME} ${FILES})
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL3 REQUIRED)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/include/"
|
||||
@@ -13,7 +13,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
loft_base
|
||||
${SDL2_LIBRARIES}
|
||||
sdl::sdl
|
||||
${LIBDECOR_LIB}
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
#define LOFT_SDLWINDOW_H
|
||||
|
||||
#include "Window.hpp"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <utility>
|
||||
#include <volk.h>
|
||||
#include <string>
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <volk.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
||||
+23
-19
@@ -1,10 +1,12 @@
|
||||
#include <SDL2/SDL_error.h>
|
||||
#include <SDL_video.h>
|
||||
#include "SDLWindow.h"
|
||||
|
||||
#include <SDL3/SDL_error.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
#include <volk.h>
|
||||
#include <stdexcept>
|
||||
#include "SDLWindow.h"
|
||||
|
||||
#include "LoftException.hpp"
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
@@ -27,16 +29,16 @@ VkExtent2D SDLWindow::get_size() const {
|
||||
SDLWindow::SDLWindow(std::string name, VkRect2D rect) :
|
||||
m_extent{rect.extent}
|
||||
{
|
||||
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVERYTHING) < 0) {
|
||||
if(!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
throw std::runtime_error("Failed to initialize sdl");
|
||||
}
|
||||
|
||||
m_pWindow = SDL_CreateWindow(name.c_str(),
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
// SDL_WINDOWPOS_CENTERED,
|
||||
// SDL_WINDOWPOS_CENTERED,
|
||||
rect.extent.width,
|
||||
rect.extent.height,
|
||||
SDL_WINDOW_VULKAN | SDL_WINDOW_ALLOW_HIGHDPI |
|
||||
SDL_WINDOW_VULKAN | SDL_WINDOW_HIGH_PIXEL_DENSITY |
|
||||
SDL_WINDOW_RESIZABLE);
|
||||
|
||||
if(m_pWindow == nullptr) {
|
||||
@@ -46,7 +48,7 @@ m_extent{rect.extent}
|
||||
|
||||
Surface SDLWindow::create_surface(const Instance* instance) const {
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
SDL_Vulkan_CreateSurface(m_pWindow, instance->instance(), &surface);
|
||||
SDL_Vulkan_CreateSurface(m_pWindow, instance->instance(), NULL, &surface);
|
||||
return Surface(instance, surface);
|
||||
}
|
||||
|
||||
@@ -60,19 +62,21 @@ int32_t SDLWindow::poll_event(SDL_Event *pOutEvent) const {
|
||||
|
||||
std::vector<std::string> SDLWindow::get_required_extensions() const {
|
||||
uint32_t count = 0;
|
||||
SDL_Vulkan_GetInstanceExtensions(m_pWindow, &count, nullptr);
|
||||
const char* const *instance_extensions = SDL_Vulkan_GetInstanceExtensions(&count);
|
||||
|
||||
std::vector<const char*> extensions(count);
|
||||
SDL_Vulkan_GetInstanceExtensions(m_pWindow, &count, extensions.data());
|
||||
std::vector<std::string> extensions(count);
|
||||
for(int i = 0; i < count; i++) {
|
||||
extensions[i] = std::string(instance_extensions[i]);
|
||||
}
|
||||
|
||||
std::vector<std::string> extension_strings(count);
|
||||
std::transform(extensions.begin(), extensions.end(),
|
||||
extension_strings.begin(),
|
||||
[](const char* str) {
|
||||
return std::string(str);
|
||||
});
|
||||
// std::vector<std::string> extension_strings(count);
|
||||
// std::transform(extensions.begin(), extensions.end(),
|
||||
// extension_strings.begin(),
|
||||
// [](const char* str) {
|
||||
// return std::string(str);
|
||||
// });
|
||||
|
||||
return extension_strings;
|
||||
return extensions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (!channel_set) {
|
||||
std::println(stderr, "Usage: {} [--host <ip>] [--port <port>] --channel <id>", argv[0]);
|
||||
// std::println(stderr, "Usage: {} [--host <ip>] [--port <port>] --channel <id>", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
messages.set_handler<mmo::chat::ChatMessageBroadcastRequest>(
|
||||
[](tw::msg::PeerId, const mmo::chat::ChatMessageBroadcastRequest& bcast) {
|
||||
std::println("[ch:{}] <{}> {}", bcast.channel_id(), bcast.sender_id(), bcast.message());
|
||||
// std::println("[ch:{}] <{}> {}", bcast.channel_id(), bcast.sender_id(), bcast.message());
|
||||
});
|
||||
|
||||
mmo::chat::JoinChannelRequest join;
|
||||
@@ -98,10 +98,10 @@ int main(int argc, char* argv[]) {
|
||||
[channel_id](std::span<const std::byte> data) {
|
||||
mmo::chat::JoinChannelResponse resp;
|
||||
resp.ParseFromArray(data.data(), static_cast<int>(data.size()));
|
||||
std::println("Joined channel {} successfully.", channel_id);
|
||||
// std::println("Joined channel {} successfully.", channel_id);
|
||||
});
|
||||
|
||||
std::println("Joined channel {}. Type a message and press Enter. Ctrl+C to quit.", channel_id);
|
||||
// std::println("Joined channel {}. Type a message and press Enter. Ctrl+C to quit.", channel_id);
|
||||
|
||||
while (!g_quit.load()) {
|
||||
fd_set fds;
|
||||
@@ -121,7 +121,7 @@ int main(int argc, char* argv[]) {
|
||||
[channel_id](std::span<const std::byte> data) {
|
||||
mmo::chat::SendChatMessageResponse resp;
|
||||
resp.ParseFromArray(data.data(), static_cast<int>(data.size()));
|
||||
std::println("Message sent to channel {}.", channel_id);
|
||||
// std::println("Message sent to channel {}.", channel_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,13 @@ add_dependencies(tw_shaders imgui::plot imgui::plot)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
towards
|
||||
tw_common
|
||||
tw::network
|
||||
tw::metrics
|
||||
tw::quicr
|
||||
loft::common
|
||||
loft::base
|
||||
${LIBDECOR_LIB}
|
||||
loft_window
|
||||
loft::render_graph
|
||||
tw::protocol
|
||||
@@ -40,8 +41,6 @@ target_link_libraries(${PROJECT_NAME}
|
||||
Jolt
|
||||
Tracy::TracyClient
|
||||
TracyClient
|
||||
# pqxx
|
||||
# pq
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(location = 0) in vec3 inNormal;
|
||||
|
||||
void main() {
|
||||
float diffuse = max(0.1, dot(vec3(0.8, 1.5, -1.0), inNormal));
|
||||
outColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
layout(location = 0) in vec3 pos;
|
||||
layout(location = 1) in vec3 norm;
|
||||
layout(location = 0) out vec3 outNormal;
|
||||
|
||||
layout(set = 0, binding = 0) uniform Camera {
|
||||
mat4 proj;
|
||||
@@ -21,5 +22,6 @@ void main() {
|
||||
// outPos = pos.xyz * 0.05;
|
||||
// outNormal = norm.xyz;
|
||||
// outUV = uv;
|
||||
outNormal = norm;
|
||||
gl_Position = cam.proj * cam.view * PushConstants.transform * vec4(pos, 1.0);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "ClientArgs.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <charconv>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@@ -25,12 +24,6 @@ std::string_view trim(std::string_view str) {
|
||||
return str.substr(start, end - start);
|
||||
}
|
||||
|
||||
bool is_valid_ipv4(std::string_view ip_str) {
|
||||
// Use inet_pton to validate IPv4 format
|
||||
struct in_addr addr;
|
||||
return inet_pton(AF_INET, std::string(ip_str).c_str(), &addr) == 1;
|
||||
}
|
||||
|
||||
tl::expected<int, std::string> parse_port(std::string_view port_str) {
|
||||
if(port_str.empty()) {
|
||||
return 8080; // Default port
|
||||
@@ -63,15 +56,27 @@ tl::expected<net::Address, std::string> parse_address(std::string_view text) {
|
||||
return tl::make_unexpected("address cannot be empty");
|
||||
}
|
||||
|
||||
// Find the colon to split host and port
|
||||
size_t colon_pos = text.rfind(':');
|
||||
|
||||
std::string_view host;
|
||||
std::string_view port_str;
|
||||
|
||||
if(colon_pos == std::string_view::npos) {
|
||||
// No colon found: treat entire string as port or host
|
||||
// If it's all digits, treat as port; otherwise as host (will fail validation)
|
||||
// An IPv6 literal carries colons of its own, so the brackets it is written
|
||||
// in are what says where the host ends. This is the form to_string() emits.
|
||||
if(text.front() == '[') {
|
||||
size_t closing = text.find(']');
|
||||
if(closing == std::string_view::npos) {
|
||||
return tl::make_unexpected("address is missing a closing bracket");
|
||||
}
|
||||
|
||||
host = text.substr(1, closing - 1);
|
||||
port_str = text.substr(closing + 1);
|
||||
|
||||
if(!port_str.empty()) {
|
||||
if(port_str.front() != ':') {
|
||||
return tl::make_unexpected("expected a port after the closing bracket");
|
||||
}
|
||||
port_str.remove_prefix(1);
|
||||
}
|
||||
} else if(size_t colon_pos = text.rfind(':'); colon_pos == std::string_view::npos) {
|
||||
bool all_digits = !text.empty() && std::all_of(text.begin(), text.end(),
|
||||
[](unsigned char c) { return std::isdigit(c); });
|
||||
|
||||
@@ -79,7 +84,6 @@ tl::expected<net::Address, std::string> parse_address(std::string_view text) {
|
||||
host = "127.0.0.1";
|
||||
port_str = text;
|
||||
} else {
|
||||
// Treat as host with no port
|
||||
host = text;
|
||||
port_str = "";
|
||||
}
|
||||
@@ -93,10 +97,6 @@ tl::expected<net::Address, std::string> parse_address(std::string_view text) {
|
||||
return tl::make_unexpected("host cannot be empty");
|
||||
}
|
||||
|
||||
if(!is_valid_ipv4(host)) {
|
||||
return tl::make_unexpected("not a valid IPv4 address");
|
||||
}
|
||||
|
||||
// Parse port
|
||||
auto port_result = parse_port(port_str);
|
||||
if(!port_result) {
|
||||
@@ -104,7 +104,13 @@ tl::expected<net::Address, std::string> parse_address(std::string_view text) {
|
||||
}
|
||||
|
||||
int port = port_result.value();
|
||||
return net::Address(std::optional<std::string>(std::string(host)), port);
|
||||
|
||||
auto address_r = net::Address::resolve(std::string(host), port);
|
||||
if(!address_r) {
|
||||
return tl::make_unexpected(address_r.error().message());
|
||||
}
|
||||
|
||||
return address_r.value();
|
||||
}
|
||||
|
||||
std::optional<std::string> server_arg(int argc, char** argv) {
|
||||
|
||||
@@ -12,10 +12,13 @@ namespace tw::app {
|
||||
/**
|
||||
* Parse an address string into a network address.
|
||||
*
|
||||
* Accepts "host:port" or a bare port number. Bare port uses 127.0.0.1.
|
||||
* Missing port defaults to 8080. Trims surrounding whitespace.
|
||||
* Validates the host with inet_pton and returns an error string
|
||||
* for non-IPv4 addresses or invalid ports.
|
||||
* Accepts "host:port" or a bare port number, where the host may be a name as
|
||||
* well as an address literal; an IPv6 literal has to be bracketed, as
|
||||
* "[::1]:8080". Bare port uses 127.0.0.1. Missing port defaults to 8080.
|
||||
* Trims surrounding whitespace.
|
||||
*
|
||||
* Looks the host up, so it blocks for as long as that takes, and returns an
|
||||
* error string for a host that does not resolve or an invalid port.
|
||||
*/
|
||||
tl::expected<net::Address, std::string> parse_address(std::string_view text);
|
||||
|
||||
|
||||
@@ -10,34 +10,5 @@ class tw::dbg::ComponentGui<tw::CharacterController> {
|
||||
public:
|
||||
void draw(tw::CharacterController* instance) {
|
||||
ImGui::SeparatorText("Character Controller Component");
|
||||
|
||||
if(ImGui::BeginTable("history", 3)) {
|
||||
for(auto key : instance->input_history().buffer()) {
|
||||
// ImGui::TableNextRow();
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text("%u", key.first);
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// if(instance->input_history().get(key.first).has_value()) {
|
||||
// glm::vec3 input_vec = *instance->input_history().get(key.first).value();
|
||||
// std::string input = std::format("{} {} {}", input_vec.x, input_vec.y, input_vec.z);
|
||||
// ImGui::Text("%s", input.c_str());
|
||||
// } else {
|
||||
// ImGui::Text("None");
|
||||
// }
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// if(instance->position_history().get(key).has_value()) {
|
||||
// glm::vec3 position_vec = *instance->position_history().get(key).value();
|
||||
// std::string position = std::format("{} {} {}", position_vec.x, position_vec.y, position_vec.z);
|
||||
// ImGui::Text("%s", position.c_str());
|
||||
// } else {
|
||||
// ImGui::Text("None");
|
||||
// }
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,10 +101,54 @@ class tw::dbg::ComponentGui<tw::net::EntityPositionInterpolation> {
|
||||
private:
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
/** How far back the plot reaches. */
|
||||
static constexpr float WINDOW_IN_SECONDS = 10.0f;
|
||||
|
||||
/** Ten seconds of frames at sixty a second. */
|
||||
static constexpr size_t CAPACITY = 600;
|
||||
|
||||
inline static const net::EntityPositionInterpolation* m_subject = nullptr;
|
||||
|
||||
inline static int m_last_frame = -1;
|
||||
inline static size_t m_head = 0;
|
||||
inline static size_t m_count = 0;
|
||||
|
||||
inline static std::vector<Clock::time_point> m_read_times;
|
||||
inline static std::vector<glm::vec3> m_read_values;
|
||||
|
||||
static int64_t millis_since(Clock::time_point point) {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - point).count();
|
||||
}
|
||||
|
||||
void restart(const net::EntityPositionInterpolation* instance) {
|
||||
m_subject = instance;
|
||||
m_last_frame = -1;
|
||||
m_head = 0;
|
||||
m_count = 0;
|
||||
|
||||
m_read_times.resize(CAPACITY);
|
||||
m_read_values.resize(CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps one reading per frame, so drawing twice does not double up. The
|
||||
* whole value is kept rather than the axis on show, so switching axis
|
||||
* still shows the trace that was already gathered.
|
||||
*/
|
||||
void sample(Clock::time_point time, glm::vec3 value) {
|
||||
const int frame = ImGui::GetFrameCount();
|
||||
if(m_last_frame == frame) {
|
||||
return;
|
||||
}
|
||||
m_last_frame = frame;
|
||||
|
||||
m_read_times[m_head] = time;
|
||||
m_read_values[m_head] = value;
|
||||
|
||||
m_head = (m_head + 1) % CAPACITY;
|
||||
m_count = std::min(m_count + 1, CAPACITY);
|
||||
}
|
||||
|
||||
public:
|
||||
void draw(net::EntityPositionInterpolation* instance) {
|
||||
ImGui::SeparatorText("Received Positions");
|
||||
@@ -122,5 +166,73 @@ public:
|
||||
instance->values()[0].y,
|
||||
instance->values()[0].z,
|
||||
millis_since(instance->times()[0]));
|
||||
|
||||
static int axis = 0;
|
||||
ImGui::Combo("Axis##received", &axis, "X\0Y\0Z\0");
|
||||
|
||||
/** How far behind the present the reader looks; matches the caller. */
|
||||
static int delay_in_millis = 1000;
|
||||
ImGui::SliderInt("Delay (ms)", &delay_in_millis, 0, 2000);
|
||||
|
||||
if(m_subject != instance) {
|
||||
restart(instance);
|
||||
}
|
||||
|
||||
// The buffer wraps, so the index says nothing about when a sample
|
||||
// arrived. Placing each one at its own age puts it where it belongs
|
||||
// without having to know where the ring currently starts. Slots
|
||||
// nothing was pushed into are left out.
|
||||
const auto now = Clock::now();
|
||||
const size_t count = instance->size();
|
||||
|
||||
std::vector<float> ages(count);
|
||||
std::vector<float> positions(count);
|
||||
|
||||
for(size_t i = 0; i < count; i++) {
|
||||
ages[i] = -std::chrono::duration<float>(now - instance->times()[i]).count();
|
||||
positions[i] = instance->values()[i][axis];
|
||||
}
|
||||
|
||||
if(ImPlot::BeginPlot("Buffer", ImVec2(-1.0f, 180.0f))) {
|
||||
ImPlot::SetupAxes("seconds ago", "position", ImPlotAxisFlags_None, ImPlotAxisFlags_AutoFit);
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1,
|
||||
-WINDOW_IN_SECONDS, 0.0f, ImGuiCond_Always);
|
||||
|
||||
ImPlot::PlotScatter("Received", ages.data(), positions.data(), (int)count);
|
||||
|
||||
// Reading the same way the position is read for drawing shows
|
||||
// which two samples the delay lands between, and how far the
|
||||
// result sits from either of them.
|
||||
const auto point = now - std::chrono::milliseconds(delay_in_millis);
|
||||
const auto [from, to, alpha] = instance->get_values_around(point);
|
||||
|
||||
const float read_at = -std::chrono::duration<float>(now - point).count();
|
||||
const float read_out = glm::mix(from, to, alpha)[axis];
|
||||
|
||||
ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle, 5.0f);
|
||||
ImPlot::PlotScatter("Interpolated", &read_at, &read_out, 1);
|
||||
|
||||
// Each reading is kept at the moment it was read for, so the trace
|
||||
// ends on the marker above and runs back along the same timeline
|
||||
// the received samples sit on.
|
||||
sample(point, glm::mix(from, to, alpha));
|
||||
|
||||
std::vector<float> trace_ages(m_count);
|
||||
std::vector<float> trace_positions(m_count);
|
||||
|
||||
const size_t oldest = m_count == CAPACITY ? m_head : 0;
|
||||
|
||||
for(size_t i = 0; i < m_count; i++) {
|
||||
const size_t index = (oldest + i) % CAPACITY;
|
||||
|
||||
trace_ages[i] = -std::chrono::duration<float>(now - m_read_times[index]).count();
|
||||
trace_positions[i] = m_read_values[index][axis];
|
||||
}
|
||||
|
||||
ImPlot::PlotLine("Interpolated Trace",
|
||||
trace_ages.data(), trace_positions.data(), (int)m_count);
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,7 +49,9 @@ Instance create_instance(const std::string& name, const lft::win::Window* window
|
||||
// required_extensions.push_back(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
|
||||
std::vector<std::string> required_layers = {
|
||||
#if DEBUG
|
||||
"VK_LAYER_KHRONOS_validation"
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL_keycode.h>
|
||||
#include <SDL_mouse.h>
|
||||
#include <imgui_impl_sdl2.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <SDL3/SDL_keycode.h>
|
||||
#include <imgui_impl_sdl3.h>
|
||||
|
||||
#include <Window.hpp>
|
||||
|
||||
@@ -69,30 +69,30 @@ public:
|
||||
m_motion_y = 0.0f;
|
||||
|
||||
while(m_window->poll_event(&event)) {
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
ImGui_ImplSDL3_ProcessEvent(&event);
|
||||
|
||||
switch(event.type) {
|
||||
case SDL_QUIT:
|
||||
case SDL_EVENT_QUIT:
|
||||
m_is_quit = true;
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP: {
|
||||
switch(event.key.keysym.sym) {
|
||||
case SDLK_w:
|
||||
m_is_pressed[0] = event.key.state;
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
switch(event.key.key) {
|
||||
case SDLK_W:
|
||||
m_is_pressed[0] = event.key.down;
|
||||
break;
|
||||
case SDLK_s:
|
||||
m_is_pressed[1] = event.key.state;
|
||||
case SDLK_S:
|
||||
m_is_pressed[1] = event.key.down;
|
||||
break;
|
||||
case SDLK_a:
|
||||
m_is_pressed[2] = event.key.state;
|
||||
case SDLK_A:
|
||||
m_is_pressed[2] = event.key.down;
|
||||
break;
|
||||
case SDLK_d:
|
||||
m_is_pressed[3] = event.key.state;
|
||||
case SDLK_D:
|
||||
m_is_pressed[3] = event.key.down;
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
m_motion_x = event.motion.xrel;
|
||||
m_motion_y = event.motion.yrel;
|
||||
break;
|
||||
@@ -25,7 +25,7 @@ void EntityPositionInterpolator::set_position(Clock::time_point time_point, entt
|
||||
return;
|
||||
}
|
||||
|
||||
interpolation->push(Clock::now(), position);
|
||||
interpolation->push(time_point, position);
|
||||
}
|
||||
|
||||
glm::vec3 EntityPositionInterpolator::get_position(Clock::time_point time_point, entt::entity entity) {
|
||||
@@ -35,9 +35,7 @@ glm::vec3 EntityPositionInterpolator::get_position(Clock::time_point time_point,
|
||||
return glm::vec3();
|
||||
}
|
||||
|
||||
auto now = Clock::now() - std::chrono::milliseconds(m_bufferingIntervalInMillis);
|
||||
|
||||
auto [from, to, value] = interpolation->get_values_around(now);
|
||||
auto [from, to, value] = interpolation->get_values_around(time_point);
|
||||
return glm::mix(from, to, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,21 @@
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
template<typename T, int length = 3>
|
||||
template<typename T, int length = 10>
|
||||
class InterpolatedProperty {
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
std::array<T, length> m_buffer;
|
||||
std::array<Clock::time_point, length> m_time_buffer;
|
||||
std::vector<T> m_buffer;
|
||||
std::vector<Clock::time_point> m_time_buffer;
|
||||
|
||||
uint32_t m_head;
|
||||
uint32_t m_size;
|
||||
|
||||
public:
|
||||
InterpolatedProperty(T initial_value) {
|
||||
InterpolatedProperty(T initial_value) :
|
||||
m_buffer(length),
|
||||
m_time_buffer(length),
|
||||
m_size(0), m_head(0) {
|
||||
for(int i = 0; i < length; i++) {
|
||||
m_buffer[i] = initial_value;
|
||||
m_time_buffer[i] = Clock::now();
|
||||
@@ -23,7 +29,21 @@ public:
|
||||
GET_REF(m_buffer, values);
|
||||
GET_REF(m_time_buffer, times);
|
||||
|
||||
/** How many slots hold something that was pushed. */
|
||||
inline uint32_t size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void push(Clock::time_point point, T value) {
|
||||
// m_buffer[m_head] = std::move(value);
|
||||
// m_time_buffer[m_head] = point;
|
||||
//
|
||||
// m_head = (m_head + 1) % m_buffer.size();
|
||||
//
|
||||
// if (m_size < m_buffer.size())
|
||||
// {
|
||||
// ++m_size;
|
||||
// }
|
||||
if(point < m_time_buffer.at(0)) {
|
||||
return;
|
||||
}
|
||||
@@ -64,6 +84,46 @@ public:
|
||||
}
|
||||
|
||||
inline std::tuple<T, T, float> get_values_around(Clock::time_point point) const {
|
||||
/* const std::size_t capacity = m_buffer.size();
|
||||
const std::size_t oldest = (m_head + capacity - m_size) % capacity;
|
||||
|
||||
if (m_size == 0)
|
||||
{
|
||||
return { T(), T(), 0.0f };
|
||||
}
|
||||
|
||||
// Asking for a time the buffer no longer reaches back to. Standing on
|
||||
// the oldest is wrong by however much was missed, but it is wrong in
|
||||
// the direction the values were heading, unlike the newest.
|
||||
if (point < m_time_buffer[oldest])
|
||||
{
|
||||
return { m_buffer[oldest], m_buffer[oldest], 0.0f };
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i + 1 < m_size; ++i)
|
||||
{
|
||||
const std::size_t idx0 = (oldest + i) % capacity;
|
||||
const std::size_t idx1 = (oldest + i + 1) % capacity;
|
||||
|
||||
const auto t0 = m_time_buffer[idx0];
|
||||
const auto t1 = m_time_buffer[idx1];
|
||||
|
||||
if (point >= t0 && point <= t1)
|
||||
{
|
||||
const auto total =
|
||||
std::chrono::duration<float>(t1 - t0).count();
|
||||
const auto elapsed =
|
||||
std::chrono::duration<float>(point - t0).count();
|
||||
|
||||
const float alpha = total > 0.0f ? elapsed / total : 0.0f;
|
||||
|
||||
return { m_buffer[idx0], m_buffer[idx1], alpha };
|
||||
}
|
||||
}
|
||||
|
||||
// Handle exact last sample (or clamp)
|
||||
const std::size_t last = (oldest + m_size - 1) % capacity;
|
||||
return { m_buffer[last], m_buffer[last], 0.0f }; */
|
||||
T prev = m_buffer.at(0);
|
||||
Clock::time_point prev_point = m_time_buffer.at(0);
|
||||
|
||||
|
||||
@@ -151,6 +151,10 @@ void ReplicatorClient::handle_snapshot_entity(
|
||||
// return;
|
||||
}
|
||||
|
||||
// Applies correction from record_frame_idx to current_frame_idx (if any) on entity. Compares previous position on
|
||||
// frame record_frame_idx with p
|
||||
m_rollback.apply_correction(record_frame_idx, p, entity.value(), &m_world->registry(), current_frame_idx);
|
||||
|
||||
//snap_player_to(current_frame_idx, entity.value(), p);
|
||||
|
||||
// bool reconcile_happened = m_reconciler.reconcile(record_frame_idx, p, entity.value(), &m_world->registry(), current_frame_idx);
|
||||
@@ -168,15 +172,29 @@ void ReplicatorClient::handle_snapshot_entity(
|
||||
// }
|
||||
// m_network_metrics->record_ack_lag(ack_lag);
|
||||
// }
|
||||
}
|
||||
|
||||
} else {
|
||||
m_entity_interpolator->set_position(std::chrono::high_resolution_clock::now(), entity.value(), p);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplicatorClient::handle_snapshot(uint32_t current_frame_idx, serial::WorldStateReader& reader) {
|
||||
auto header = reader.read_header();
|
||||
measure_response_time(current_frame_idx, header.frame_idx);
|
||||
|
||||
// Snapshots are sent unreliably, so one can overtake another. Taking a
|
||||
// late one would put entities back where they have already been seen.
|
||||
if(header.tick_idx <= m_latest_tick_idx) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_latest_tick_idx = header.tick_idx;
|
||||
|
||||
// Arrival is the only time the client can be sure of, and it grows with
|
||||
// every snapshot, which is what reading the buffer back relies on. What it
|
||||
// costs is spacing: the gaps between samples carry the jitter of the way
|
||||
// here rather than the even gaps the server sent them on.
|
||||
const auto taken_at = std::chrono::high_resolution_clock::now();
|
||||
|
||||
while(reader.has_entity()) {
|
||||
auto entity_r = reader.read_entity();
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ private:
|
||||
net::PlayerRollback m_rollback;
|
||||
World *m_world;
|
||||
net::ServerConnection* m_server_connection;
|
||||
uint32_t m_latest_tick_idx = 0;
|
||||
|
||||
std::optional<entt::entity> m_player_entity;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "imgui_impl_sdl2.h"
|
||||
#include "imgui_impl_sdl3.h"
|
||||
|
||||
#include "implot.h"
|
||||
#include "implot_internal.h"
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <memory>
|
||||
@@ -105,7 +104,7 @@ void Runtime::run() {
|
||||
}
|
||||
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui_ImplSDL3_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
m_input_manager.update();
|
||||
|
||||
@@ -50,7 +50,12 @@ void ThirdPersonPlayerController::update(const tw::io::InputManager* input, doub
|
||||
m_camera_rotation = yaw * m_camera_rotation * pitch;
|
||||
|
||||
glm::vec3 offset = m_camera_rotation * glm::vec3(0, 0, m_camera_zoom);
|
||||
m_camera->view().look_at(get_target_position() + offset, get_target_position());
|
||||
|
||||
glm::vec3 from = m_camera->view().position();
|
||||
glm::vec3 to = get_target_position() + offset;
|
||||
glm::vec3 interpolated = glm::mix(from, to, 0.8f);
|
||||
|
||||
m_camera->view().look_at(interpolated, get_target_position());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
|
||||
namespace tw {
|
||||
|
||||
typedef HistoryBuffer<long, glm::vec3> EntityPositionHistory;
|
||||
|
||||
entt::entity
|
||||
ClientWorldController::create_entity(const std::string& name, glm::vec3 position) {
|
||||
const auto entity = m_world->registry().create();
|
||||
@@ -82,9 +80,9 @@ void ClientWorldController::spawn_entity(const std::string& name, uint32_t serve
|
||||
if(m_controlled_server_id.has_value() && m_controlled_server_id.value() == server_id) {
|
||||
try_bind_player_entity();
|
||||
} else {
|
||||
}
|
||||
m_interpolator.register_entity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
ClientWorldController::ClientWorldController(
|
||||
const io::InputManager* inputs,
|
||||
@@ -165,9 +163,9 @@ void ClientWorldController::try_bind_player_entity() {
|
||||
position
|
||||
));
|
||||
|
||||
// if(m_world->registry().all_of<net::EntityPositionInterpolation>(entity)) {
|
||||
// m_world->registry().remove<net::EntityPositionInterpolation>(entity);
|
||||
// }
|
||||
if(m_world->registry().all_of<net::EntityPositionInterpolation>(entity)) {
|
||||
m_world->registry().remove<net::EntityPositionInterpolation>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +185,6 @@ void ClientWorldController::update_network() {
|
||||
});
|
||||
|
||||
|
||||
m_replicator_client.record_prediction(m_frame_idx);
|
||||
|
||||
|
||||
// m_world->registry().view<Transform>()
|
||||
@@ -216,10 +213,11 @@ void ClientWorldController::update(double delta_time) {
|
||||
if(controller) {
|
||||
// controller->set_input(m_network_frame_idx, input);
|
||||
m_replicator_client.set_input(m_network_frame_idx, input);
|
||||
m_replicator_client.record_prediction(m_network_frame_idx);
|
||||
}
|
||||
}
|
||||
|
||||
// m_physics_world->step(m_network_frame_idx, JoltPhysicsWorld::FIXED_DELTA_TIME, true);
|
||||
m_physics_world->step(m_network_frame_idx, JoltPhysicsWorld::FIXED_DELTA_TIME, true);
|
||||
m_network_frame_idx++;
|
||||
}
|
||||
|
||||
@@ -230,7 +228,7 @@ void ClientWorldController::update(double delta_time) {
|
||||
// TODO: The third person controller could pull
|
||||
Transform* player_transform = m_world->registry().try_get<Transform>(m_player_entity.value());
|
||||
if(player_transform) {
|
||||
m_player_controller.set_target(player_transform->position());
|
||||
// m_player_controller.set_target(player_transform->position());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "imgui.h"
|
||||
#include "backends/imgui_impl_vulkan.h"
|
||||
#include "backends/imgui_impl_sdl2.h"
|
||||
#include "backends/imgui_impl_sdl3.h"
|
||||
|
||||
namespace tw::drw::gui {
|
||||
|
||||
@@ -73,7 +73,7 @@ lft::rg::RenderTaskBuilder ImGuiRenderPass::create_render_task() {
|
||||
const std::string font_path = "/home/martin/projects/mmo/external/imgui/misc/fonts/ProggyClean.ttf";
|
||||
io.Fonts->AddFontFromFileTTF(font_path.c_str(), 13.0f);
|
||||
auto init_info = create_imgui_init_info(info.gpu(), info.renderpass());
|
||||
ImGui_ImplSDL2_InitForVulkan(context->m_window->get_handle());
|
||||
ImGui_ImplSDL3_InitForVulkan(context->m_window->get_handle());
|
||||
ImGui_ImplVulkan_Init(&init_info);
|
||||
|
||||
context->is_initialized = true;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <netdb.h>
|
||||
#include <string>
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
/**
|
||||
* A failed host lookup.
|
||||
*
|
||||
* Kept apart from NetworkError because getaddrinfo reports EAI_ codes, which
|
||||
* are their own mostly-negative space: sharing one enum would map a lookup
|
||||
* failure onto whichever errno happened to carry the same number.
|
||||
*/
|
||||
struct ResolutionError {
|
||||
int m_code;
|
||||
int m_errno;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Only EAI_SYSTEM defers to errno, and errno will not have survived by the
|
||||
* time message() runs, so it is captured here.
|
||||
*/
|
||||
static ResolutionError from_gai(int code) {
|
||||
return { code, errno };
|
||||
}
|
||||
|
||||
std::string message() const {
|
||||
switch (m_code) {
|
||||
case EAI_NONAME:
|
||||
return "The host name is not known.";
|
||||
case EAI_AGAIN:
|
||||
return "The name server is unreachable or busy; the lookup may succeed later.";
|
||||
case EAI_FAIL:
|
||||
return "The name server returned a permanent failure.";
|
||||
case EAI_FAMILY:
|
||||
return "The requested address family is not supported.";
|
||||
case EAI_SERVICE:
|
||||
return "The requested port is not available for this socket type.";
|
||||
case EAI_MEMORY:
|
||||
return "Insufficient memory was available to complete the lookup.";
|
||||
case EAI_SYSTEM:
|
||||
return std::string(strerror(m_errno));
|
||||
default:
|
||||
return std::string(gai_strerror(m_code));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "ResolutionError.hpp"
|
||||
#include "tl/expected.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <netdb.h>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
/**
|
||||
* Turns a host name or an address literal into a socket address.
|
||||
*
|
||||
* `family` is the family of the socket the result will be given to. AF_INET6
|
||||
* asks for IPv4-only names as ::ffff: mapped addresses, so that one dual-stack
|
||||
* socket reaches both; AF_UNSPEC takes the name as it comes and suits addresses
|
||||
* that are only being validated, displayed or stored.
|
||||
*
|
||||
* Blocks for the length of a DNS round trip when the name is not already known,
|
||||
* so it belongs at connect time rather than anywhere periodic.
|
||||
*/
|
||||
inline tl::expected<sockaddr_storage, ResolutionError>
|
||||
resolve_host(const std::string& host, int port, sa_family_t family = AF_UNSPEC) {
|
||||
addrinfo hints {};
|
||||
hints.ai_family = family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
// AI_ADDRCONFIG is deliberately absent. Together with AF_INET6 it discards
|
||||
// every result on a host that carries no global IPv6 address, which is the
|
||||
// default state of a container on a bridge network.
|
||||
if(family == AF_INET6) {
|
||||
hints.ai_flags = AI_V4MAPPED | AI_ALL;
|
||||
}
|
||||
|
||||
// Passing the port as the service spares us setting sin_port or sin6_port
|
||||
// by hand once the family of the answer is known.
|
||||
const std::string service = std::to_string(port);
|
||||
|
||||
addrinfo* results = nullptr;
|
||||
const int rc = ::getaddrinfo(host.c_str(), service.c_str(), &hints, &results);
|
||||
if(rc != 0) {
|
||||
return tl::make_unexpected(ResolutionError::from_gai(rc));
|
||||
}
|
||||
|
||||
// The list arrives ordered by RFC 6724, so the head is the address the
|
||||
// system would have picked for itself.
|
||||
sockaddr_storage storage {};
|
||||
std::memcpy(&storage, results->ai_addr, results->ai_addrlen);
|
||||
|
||||
::freeaddrinfo(results);
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,7 +70,13 @@ MessageConnection* MessageEndpoint::add_peer(net::quicr::QuicrConnection* connec
|
||||
}
|
||||
|
||||
tl::expected<MessageConnection*, MessageError> MessageEndpoint::connect(const std::string& host, int port) {
|
||||
auto connection_r = m_endpoint->connect(net::quicr::QuicrAddress(host, port));
|
||||
auto address_r = net::quicr::QuicrAddress::resolve(host, port, m_endpoint->family());
|
||||
if(!address_r) {
|
||||
return tl::make_unexpected(
|
||||
MessageError(MessageErrorType::ConnectFailed, address_r.error().message()));
|
||||
}
|
||||
|
||||
auto connection_r = m_endpoint->connect(address_r.value());
|
||||
if(!connection_r) {
|
||||
return tl::make_unexpected(
|
||||
MessageError(MessageErrorType::ConnectFailed, connection_r.error().message()));
|
||||
|
||||
@@ -8,7 +8,7 @@ add_executable(${PROJECT_NAME} ${FILES})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
towards
|
||||
tw_common
|
||||
tw::network
|
||||
tw::quicr
|
||||
tw::protocol
|
||||
|
||||
@@ -112,9 +112,25 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
const int NUM_CLIENTS = 10;
|
||||
tw::net::Address address = {"127.0.0.1", 8101};
|
||||
int main(int argc, char** argv) {
|
||||
if(argc > 4) {
|
||||
spdlog::error("Usage: {} [client_count] [host] [port]", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const uint32_t NUM_CLIENTS = argc > 1 ? std::strtoul(argv[1], nullptr, 10) : 300;
|
||||
const std::string host = argc > 2 ? argv[2] : "127.0.0.1";
|
||||
const int port = argc > 3 ? std::atoi(argv[3]) : 8101;
|
||||
|
||||
auto address_r = tw::net::Address::resolve(host, port);
|
||||
if(!address_r) {
|
||||
spdlog::error("Failed to resolve {}:{}: {}", host, port, address_r.error().message());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const tw::net::Address address = address_r.value();
|
||||
|
||||
spdlog::info("Starting {} clients against {}", NUM_CLIENTS, address.to_string());
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for(uint32_t i = 0; i < NUM_CLIENTS; i++) {
|
||||
@@ -129,7 +145,7 @@ int main() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < NUM_CLIENTS; i++) {
|
||||
for(uint32_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "io/HostResolver.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string>
|
||||
@@ -7,6 +9,7 @@
|
||||
#include <optional>
|
||||
#include <sys/socket.h>
|
||||
#include <format>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
@@ -38,6 +41,24 @@ public:
|
||||
: m_storage(storage)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Look a host up, accepting a name where the constructor above takes only
|
||||
* an address literal.
|
||||
*
|
||||
* `family` should be the family of the socket the address will be used
|
||||
* with. The default suits an address that is only being validated or
|
||||
* displayed, and takes whatever the name resolves to.
|
||||
*/
|
||||
static tl::expected<Address, ResolutionError>
|
||||
resolve(const std::string& host, int port, sa_family_t family = AF_UNSPEC) {
|
||||
auto storage_r = resolve_host(host, port, family);
|
||||
if(!storage_r) {
|
||||
return tl::make_unexpected(storage_r.error());
|
||||
}
|
||||
|
||||
return Address(std::move(storage_r.value()));
|
||||
}
|
||||
|
||||
/** Return a const pointer suitable for connect / sendto / bind. */
|
||||
const struct sockaddr* sockaddr() const {
|
||||
return reinterpret_cast<const struct sockaddr*>(&m_storage);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <fmt/format.h>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
#include <barrier>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <span>
|
||||
#include <iostream>
|
||||
|
||||
#include "UdpStream.hpp"
|
||||
#include "Address.hpp"
|
||||
#include "quicr/QuicrConnection.hpp"
|
||||
#include "quicr/QuicrConnectionListener.hpp"
|
||||
#include "quicr/QuicrEndpoint.hpp"
|
||||
|
||||
TEST_CASE("Start two sockets and send message", "[udp]") {
|
||||
std::barrier create_sync_point(2);
|
||||
std::barrier send_sync_point(2);
|
||||
|
||||
const std::string message = "Hello, server!";
|
||||
|
||||
std::thread server_thread([&]() {
|
||||
auto r = tw::net::UdpStream::bind(tw::net::Address {"127.0.0.1", 6969});
|
||||
if(!r.has_value()) {
|
||||
std::cout << ("Failed to bind UDP stream: {}") << r.error().message() << std::endl;
|
||||
}
|
||||
|
||||
auto flag_result = r->set_non_blocking();
|
||||
|
||||
auto server = std::move(r.value());
|
||||
|
||||
create_sync_point.arrive_and_wait();
|
||||
send_sync_point.arrive_and_wait();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::vector<std::byte> buffer(1024);
|
||||
tw::net::Address from {{}, 0};
|
||||
auto read_result = server.read_into(std::span{buffer.data(), buffer.size()}, &from);
|
||||
|
||||
if(!read_result.has_value()) {
|
||||
spdlog::error("Failed to read from UDP stream: {}", read_result.error().message());
|
||||
}
|
||||
|
||||
// convert the buffer to a string
|
||||
std::string mesg(buffer.data(), buffer.data() + read_result.value());
|
||||
REQUIRE(mesg == message);
|
||||
});
|
||||
|
||||
std::thread client_thread([&]() {
|
||||
create_sync_point.arrive_and_wait();
|
||||
|
||||
auto connect_result = tw::net::UdpStream::to(tw::net::Address{"127.0.0.1", 6969});
|
||||
if(!connect_result.has_value()) {
|
||||
spdlog::error("Failed to bind UDP stream: {}", connect_result.error().mesg());
|
||||
}
|
||||
|
||||
auto client = std::move(connect_result.value());
|
||||
|
||||
std::string mesg = message;
|
||||
auto write_result = client.write(std::as_writable_bytes(std::span(mesg.begin(), mesg.end())));
|
||||
|
||||
if(!write_result.has_value() && write_result.value() == mesg.length()) {
|
||||
spdlog::error("Failed to write to UDP stream: {}", write_result.error().message());
|
||||
}
|
||||
|
||||
send_sync_point.arrive_and_wait();
|
||||
});
|
||||
|
||||
server_thread.join();
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
using namespace tw::net;
|
||||
using namespace tw::net::quicr;
|
||||
// #include <barrier>
|
||||
// #include <catch2/catch_test_macros.hpp>
|
||||
// #include <span>
|
||||
// #include <iostream>
|
||||
//
|
||||
// #include "UdpStream.hpp"
|
||||
// #include "Address.hpp"
|
||||
// #include "quicr/QuicrConnection.hpp"
|
||||
// #include "quicr/QuicrConnectionListener.hpp"
|
||||
// #include "quicr/QuicrEndpoint.hpp"
|
||||
//
|
||||
// TEST_CASE("Start two sockets and send message", "[udp]") {
|
||||
// std::barrier create_sync_point(2);
|
||||
// std::barrier send_sync_point(2);
|
||||
//
|
||||
// const std::string message = "Hello, server!";
|
||||
//
|
||||
// std::thread server_thread([&]() {
|
||||
// auto r = tw::net::UdpStream::bind(tw::net::Address {"127.0.0.1", 6969});
|
||||
// if(!r.has_value()) {
|
||||
// std::cout << ("Failed to bind UDP stream: {}") << r.error().message() << std::endl;
|
||||
// }
|
||||
//
|
||||
// auto flag_result = r->set_non_blocking();
|
||||
//
|
||||
// auto server = std::move(r.value());
|
||||
//
|
||||
// create_sync_point.arrive_and_wait();
|
||||
// send_sync_point.arrive_and_wait();
|
||||
//
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
//
|
||||
// std::vector<std::byte> buffer(1024);
|
||||
// tw::net::Address from {{}, 0};
|
||||
// auto read_result = server.read_into(std::span{buffer.data(), buffer.size()}, &from);
|
||||
//
|
||||
// if(!read_result.has_value()) {
|
||||
// spdlog::error("Failed to read from UDP stream: {}", read_result.error().message());
|
||||
// }
|
||||
//
|
||||
// // convert the buffer to a string
|
||||
// std::string mesg(buffer.data(), buffer.data() + read_result.value());
|
||||
// REQUIRE(mesg == message);
|
||||
// });
|
||||
//
|
||||
// std::thread client_thread([&]() {
|
||||
// create_sync_point.arrive_and_wait();
|
||||
//
|
||||
// auto connect_result = tw::net::UdpStream::to(tw::net::Address{"127.0.0.1", 6969});
|
||||
// if(!connect_result.has_value()) {
|
||||
// spdlog::error("Failed to bind UDP stream: {}", connect_result.error().mesg());
|
||||
// }
|
||||
//
|
||||
// auto client = std::move(connect_result.value());
|
||||
//
|
||||
// std::string mesg = message;
|
||||
// auto write_result = client.write(std::as_writable_bytes(std::span(mesg.begin(), mesg.end())));
|
||||
//
|
||||
// if(!write_result.has_value() && write_result.value() == mesg.length()) {
|
||||
// spdlog::error("Failed to write to UDP stream: {}", write_result.error().message());
|
||||
// }
|
||||
//
|
||||
// send_sync_point.arrive_and_wait();
|
||||
// });
|
||||
//
|
||||
// server_thread.join();
|
||||
// client_thread.join();
|
||||
// }
|
||||
//
|
||||
// using namespace tw::net;
|
||||
// using namespace tw::net::quicr;
|
||||
|
||||
@@ -22,7 +22,7 @@ target_include_directories(tw_peer_to_peer_lib
|
||||
|
||||
target_link_libraries(tw_peer_to_peer_lib
|
||||
PUBLIC
|
||||
towards
|
||||
tw_common
|
||||
tw::network
|
||||
tw::quicr
|
||||
tw::protocol
|
||||
@@ -32,8 +32,6 @@ target_link_libraries(tw_peer_to_peer_lib
|
||||
spdlog::spdlog
|
||||
)
|
||||
|
||||
# ── standalone executable ─────────────────────────────────────────────────────
|
||||
|
||||
add_executable(${PROJECT_NAME} src/PeerToPeer.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
@@ -41,6 +39,4 @@ target_link_libraries(${PROJECT_NAME}
|
||||
tw_peer_to_peer_lib
|
||||
)
|
||||
|
||||
# ── tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
@@ -15,7 +15,18 @@ QuicrPeerLink::QuicrPeerLink(uint32_t self_id, uint16_t port)
|
||||
{}
|
||||
|
||||
void QuicrPeerLink::connect_to(uint32_t peer_id, const tw::net::Address& addr) {
|
||||
auto r = m_endpoint->connect(net::quicr::QuicrAddress(addr.ip_string(), addr.port()));
|
||||
// The address is rebuilt from its text, which for a mapped or IPv6 peer is
|
||||
// more than the literal constructor can parse, so it goes back through the
|
||||
// resolver — into the endpoint's family, since that is what will send it.
|
||||
auto address_r = net::quicr::QuicrAddress::resolve(addr.ip_string(), addr.port(),
|
||||
m_endpoint->family());
|
||||
if (!address_r) {
|
||||
spdlog::warn("QuicrPeerLink[{}]: address of peer {} failed to resolve: {}",
|
||||
m_self_id, peer_id, address_r.error().message());
|
||||
return;
|
||||
}
|
||||
|
||||
auto r = m_endpoint->connect(address_r.value());
|
||||
if (!r) {
|
||||
spdlog::warn("QuicrPeerLink[{}]: connect to peer {} failed", m_self_id, peer_id);
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "Serialization.hpp"
|
||||
#include "Serializers.hpp"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "io/HostResolver.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string>
|
||||
@@ -7,6 +9,7 @@
|
||||
#include <optional>
|
||||
#include <sys/socket.h>
|
||||
#include <format>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
namespace tw::net::quicr {
|
||||
|
||||
@@ -38,6 +41,24 @@ public:
|
||||
: m_storage(storage)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Look a host up, accepting a name where the constructor above takes only
|
||||
* an address literal.
|
||||
*
|
||||
* `family` should be the family of the endpoint socket the address will be
|
||||
* sent from, so that a dual-stack socket is handed a mapped address rather
|
||||
* than a bare IPv4 one.
|
||||
*/
|
||||
static tl::expected<QuicrAddress, ResolutionError>
|
||||
resolve(const std::string& host, int port, sa_family_t family = AF_UNSPEC) {
|
||||
auto storage_r = resolve_host(host, port, family);
|
||||
if(!storage_r) {
|
||||
return tl::make_unexpected(storage_r.error());
|
||||
}
|
||||
|
||||
return QuicrAddress(std::move(storage_r.value()));
|
||||
}
|
||||
|
||||
/** Return a const pointer suitable for connect / sendto / bind. */
|
||||
const struct sockaddr* sockaddr() const {
|
||||
return reinterpret_cast<const struct sockaddr*>(&m_storage);
|
||||
|
||||
@@ -18,6 +18,7 @@ class QuicrConnectionListener;
|
||||
|
||||
class QuicrEndpoint {
|
||||
int32_t m_socket_fd;
|
||||
sa_family_t m_family;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<QuicrConnection>> m_connections;
|
||||
|
||||
std::vector<std::byte> m_inbound_buffer;
|
||||
@@ -26,7 +27,7 @@ class QuicrEndpoint {
|
||||
|
||||
void process_datagram(std::span<std::byte> datagram, QuicrAddress from);
|
||||
|
||||
QuicrEndpoint(int socket_fd);
|
||||
QuicrEndpoint(int socket_fd, sa_family_t family);
|
||||
|
||||
public:
|
||||
QuicrEndpoint(const QuicrEndpoint&) = delete;
|
||||
@@ -47,6 +48,12 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The family the socket was opened with. Addresses have to be resolved
|
||||
* into it before they can be sent to.
|
||||
*/
|
||||
sa_family_t family() const { return m_family; }
|
||||
|
||||
static tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> create();
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
namespace tw::net::quicr {
|
||||
|
||||
QuicrEndpoint::QuicrEndpoint(int socket_fd)
|
||||
: m_inbound_buffer(64 * 1024), m_socket_fd(socket_fd),
|
||||
QuicrEndpoint::QuicrEndpoint(int socket_fd, sa_family_t family)
|
||||
: m_inbound_buffer(64 * 1024), m_socket_fd(socket_fd), m_family(family),
|
||||
m_new_connection_handler(nullptr) {
|
||||
|
||||
}
|
||||
@@ -31,29 +31,57 @@ tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> QuicrEndpoint::create_a
|
||||
}
|
||||
|
||||
tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> QuicrEndpoint::create() {
|
||||
const int domain = AF_INET;
|
||||
// An IPv6 socket with IPV6_V6ONLY cleared also carries IPv4 peers, which
|
||||
// arrive as ::ffff: mapped addresses. A host with IPv6 switched off answers
|
||||
// EAFNOSUPPORT instead, and there the endpoint stays IPv4 as it was.
|
||||
sa_family_t domain = AF_INET6;
|
||||
int socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(socket_fd < 0 && errno == EAFNOSUPPORT) {
|
||||
domain = AF_INET;
|
||||
socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
|
||||
}
|
||||
|
||||
if(socket_fd < 0) {
|
||||
spdlog::error("Failed to create socket: {}", strerror(errno));
|
||||
return tl::make_unexpected(QuicrError::from_errno(errno));
|
||||
}
|
||||
|
||||
if(domain == AF_INET6) {
|
||||
const int v6_only = 0;
|
||||
if(setsockopt(socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6_only, sizeof(v6_only)) < 0) {
|
||||
spdlog::error("Failed to accept IPv4 peers on the socket: {}", strerror(errno));
|
||||
::close(socket_fd);
|
||||
return tl::make_unexpected(QuicrError::from_errno(errno));
|
||||
}
|
||||
}
|
||||
|
||||
if(fcntl(socket_fd, F_SETFL, fcntl(socket_fd, F_GETFL, 0) | O_NONBLOCK, 1) == -1) {
|
||||
spdlog::error("Failed to set non-blocking mode: {}", strerror(errno));
|
||||
return tl::make_unexpected(QuicrError::from_errno(errno));
|
||||
}
|
||||
|
||||
return std::unique_ptr<QuicrEndpoint>(new QuicrEndpoint(socket_fd));
|
||||
return std::unique_ptr<QuicrEndpoint>(new QuicrEndpoint(socket_fd, domain));
|
||||
}
|
||||
|
||||
tl::expected<void, QuicrError> QuicrEndpoint::bind(int port) {
|
||||
const int domain = AF_INET;
|
||||
struct sockaddr_in addr = {};
|
||||
addr.sin_family = domain;
|
||||
sockaddr_storage storage = {};
|
||||
socklen_t length;
|
||||
|
||||
if(m_family == AF_INET6) {
|
||||
auto& addr = reinterpret_cast<sockaddr_in6&>(storage);
|
||||
addr.sin6_family = AF_INET6;
|
||||
addr.sin6_port = htons(port);
|
||||
addr.sin6_addr = in6addr_any;
|
||||
length = sizeof(sockaddr_in6);
|
||||
} else {
|
||||
auto& addr = reinterpret_cast<sockaddr_in&>(storage);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
length = sizeof(sockaddr_in);
|
||||
}
|
||||
|
||||
if(::bind(m_socket_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
if(::bind(m_socket_fd, reinterpret_cast<struct sockaddr*>(&storage), length) < 0) {
|
||||
spdlog::error("Failed to bind socket: {}", strerror(errno));
|
||||
return tl::make_unexpected(QuicrError::from_errno(errno));
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ target_link_libraries(tw_serialization
|
||||
EnTT::EnTT
|
||||
)
|
||||
|
||||
# Require C++20 for concepts / span
|
||||
target_compile_features(tw_serialization INTERFACE cxx_std_20)
|
||||
|
||||
# ── Tests / benchmarks ───────────────────────────────────────────────────────
|
||||
add_subdirectory(tests)
|
||||
# add_subdirectory(tests)
|
||||
|
||||
@@ -23,7 +23,7 @@ class WorldStateWriter {
|
||||
public:
|
||||
explicit WorldStateWriter(BinaryBuffer& buf) noexcept : m_w(buf) {}
|
||||
|
||||
void begin(uint32_t frame_idx, uint32_t message_type) noexcept {
|
||||
void begin(uint32_t frame_idx, uint32_t tick_idx, uint32_t message_type) noexcept {
|
||||
m_entity_count = 0;
|
||||
|
||||
// message_type — lets the receiver dispatch without peeking further
|
||||
@@ -35,6 +35,10 @@ public:
|
||||
// frame_idx
|
||||
m_w.encode<uint32_t>(frame_idx);
|
||||
|
||||
// tick_idx — counts the states that went out, so the receiver can tell
|
||||
// a late one from a new one. Says nothing about the frame answered.
|
||||
m_w.encode<uint32_t>(tick_idx);
|
||||
|
||||
// entity_count placeholder — patched when end() is called
|
||||
m_entity_count_offset = m_w.reserve_u32();
|
||||
}
|
||||
@@ -85,6 +89,7 @@ public:
|
||||
struct WorldStateHeader {
|
||||
uint32_t packet_type;
|
||||
uint32_t frame_idx;
|
||||
uint32_t tick_idx;
|
||||
uint32_t entity_count;
|
||||
};
|
||||
|
||||
@@ -109,10 +114,11 @@ public:
|
||||
explicit WorldStateReader(std::span<const std::byte> data) noexcept
|
||||
: m_r(data) {}
|
||||
|
||||
/** Read the 12-byte header. Must be called first. */
|
||||
/** Read the header. Must be called first. */
|
||||
WorldStateHeader read_header() noexcept {
|
||||
// m_header.packet_type = m_r.decode<uint32_t>();
|
||||
m_header.frame_idx = m_r.decode<uint32_t>();
|
||||
m_header.tick_idx = m_r.decode<uint32_t>();
|
||||
m_header.entity_count = m_r.decode<uint32_t>();
|
||||
|
||||
// spawn count follows immediately
|
||||
|
||||
@@ -17,7 +17,7 @@ file(GLOB FILES
|
||||
# )
|
||||
|
||||
# add_executable(${PROJECT_NAME})
|
||||
add_executable(SerializationBenchmarks ./SerializationBenchmarks.cpp)
|
||||
# add_executable(SerializationBenchmarks ./SerializationBenchmarks.cpp)
|
||||
|
||||
target_compile_definitions(SerializationBenchmarks PRIVATE TRACY_ON_DEMAND=1)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ file(GLOB FILES
|
||||
src/network/*.cpp
|
||||
)
|
||||
|
||||
find_package(libpqxx CONFIG REQUIRED)
|
||||
add_library(tw_server_lib STATIC ${FILES})
|
||||
|
||||
target_include_directories(tw_server_lib
|
||||
@@ -23,7 +24,7 @@ target_include_directories(tw_server_lib
|
||||
|
||||
target_link_libraries(tw_server_lib
|
||||
PUBLIC
|
||||
towards
|
||||
tw_common
|
||||
tw::io
|
||||
tw::network
|
||||
tw::protocol
|
||||
@@ -36,8 +37,7 @@ target_link_libraries(tw_server_lib
|
||||
protobuf::libprotobuf
|
||||
${Boost_LIBRARIES}
|
||||
Tracy::TracyClient
|
||||
pqxx
|
||||
pq
|
||||
libpqxx::pqxx
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} src/server.cpp)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# Build the toolchain stage on its own and pass it back in to skip the apt step:
|
||||
# docker build -f modules/server/Dockerfile --target toolchain -t tw_toolchain .
|
||||
# docker build -f modules/server/Dockerfile --build-arg TOOLCHAIN_IMAGE=tw_toolchain .
|
||||
ARG TOOLCHAIN_IMAGE=toolchain
|
||||
|
||||
# ── toolchain stage ──────────────────────────────────────────────────────────
|
||||
FROM ubuntu:24.04 AS toolchain
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
clang \
|
||||
libstdc++-14-dev \
|
||||
cmake \
|
||||
ninja-build \
|
||||
mold \
|
||||
git \
|
||||
ca-certificates \
|
||||
pkg-config \
|
||||
glslang-tools \
|
||||
libvulkan-dev \
|
||||
libsdl2-dev \
|
||||
libprotobuf-dev protobuf-compiler \
|
||||
libpq-dev \
|
||||
libpqxx-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV CC=clang
|
||||
ENV CXX=clang++
|
||||
|
||||
# ── build stage ──────────────────────────────────────────────────────────────
|
||||
FROM ${TOOLCHAIN_IMAGE} AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
# glm, entt, Jolt, spdlog, expected, Catch2 and tracy are cloned at configure time
|
||||
RUN cmake -B /build -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
&& cmake --build /build --target tw_server
|
||||
|
||||
# Stage the shared libraries the binary was linked against; the runtime image has
|
||||
# no package manager. glibc and its loader stay behind, they come with that image.
|
||||
RUN mkdir -p /rootfs/usr/lib/x86_64-linux-gnu \
|
||||
&& ldd /build/modules/server/tw_server \
|
||||
| awk '/=> \//{ print $3 }' \
|
||||
| grep -vE '/(libc|libm|libdl|libpthread|librt|libresolv|libanl)\.so' \
|
||||
| xargs -I{} cp -L {} /rootfs/usr/lib/x86_64-linux-gnu/
|
||||
|
||||
# ── runtime stage ─────────────────────────────────────────────────────────────
|
||||
FROM gcr.io/distroless/cc-debian13:nonroot
|
||||
|
||||
COPY --from=builder /rootfs/ /
|
||||
COPY --from=builder /build/modules/server/tw_server /usr/local/bin/tw_server
|
||||
|
||||
# player connections, zone-server peering
|
||||
EXPOSE 8101/udp 8102/udp
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/tw_server"]
|
||||
@@ -62,6 +62,9 @@ void ZoneServer::player_update_handler(SessionId session_id, mmo::PlayerMoveMess
|
||||
if (it == m_session_zone.end()) return;
|
||||
|
||||
if (auto* session = m_player_session_registry->session(session_id)) {
|
||||
if(session->last_received_frame >= message.frame_idx()) {
|
||||
return;
|
||||
}
|
||||
session->last_received_frame = message.frame_idx();
|
||||
}
|
||||
|
||||
@@ -141,7 +144,7 @@ void ZoneServer::run() {
|
||||
}
|
||||
}
|
||||
|
||||
m_replicator->replicate(zone->registry(), zone->interest());
|
||||
m_replicator->replicate(frame_idx, zone->registry(), zone->interest());
|
||||
}
|
||||
|
||||
m_network_receiver->update();
|
||||
|
||||
@@ -7,7 +7,7 @@ TimescaleDbMetricsReporter::TimescaleDbMetricsReporter(const TimescaleDbConfigur
|
||||
m_last_flush_time(std::chrono::steady_clock::now())
|
||||
{
|
||||
try {
|
||||
m_connection = pqxx::connection(config.connection.to_string());
|
||||
m_connection.emplace(config.connection.to_string());
|
||||
spdlog::info("Connected to TimescaleDB at {}:{}", config.connection.host, config.connection.port);
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Failed to connect to TimescaleDB: {}", e.what());
|
||||
@@ -23,9 +23,9 @@ void TimescaleDbMetricsReporter::flush() {
|
||||
pqxx::work tx{*m_connection};
|
||||
|
||||
const auto table = m_connection->quote_name(m_metrics_table);
|
||||
tx.exec("INSERT INTO " + table + " (time, outbound, inbound, player_count) VALUES (NOW(), $1, $2, $3)",
|
||||
pqxx::params(m_outbound_bucket, m_inbound_bucket, m_player_count))
|
||||
.no_rows();
|
||||
tx.exec_prepared("INSERT INTO " + table + " (time, outbound, inbound, player_count) VALUES (NOW(), $1, $2, $3)",
|
||||
m_outbound_bucket, m_inbound_bucket, m_player_count)
|
||||
.empty();
|
||||
tx.commit();
|
||||
|
||||
TracyPlot("outbound", (int64_t)m_outbound_bucket);
|
||||
|
||||
@@ -29,8 +29,8 @@ class StateReplicator {
|
||||
// Per-client backing buffers reused every frame.
|
||||
std::vector<tw::serial::BinaryBuffer> m_frames;
|
||||
|
||||
// Header(16) + spawn_hdr(4) + despawn_hdr(4) + 512 entities × 16 bytes
|
||||
static constexpr std::size_t kHeaderCapacity = 24;
|
||||
// Header(20) + spawn_hdr(4) + despawn_hdr(4) + 512 entities × 16 bytes
|
||||
static constexpr std::size_t kHeaderCapacity = 28;
|
||||
static constexpr std::size_t kInitialCapacity = kHeaderCapacity + 512 * 16;
|
||||
|
||||
public:
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
* Replicates the current world state for one zone to its connected clients.
|
||||
*/
|
||||
void replicate(
|
||||
uint32_t frame_idx,
|
||||
const entt::registry& registry,
|
||||
const im::InterestSystem<Backend>* interest_manager
|
||||
) {
|
||||
@@ -88,7 +89,8 @@ public:
|
||||
|
||||
m_frames[i].reserve(needed);
|
||||
writers[i].reset();
|
||||
writers[i].begin(session->last_received_frame, Message<mmo::WorldStateMessage>::value);
|
||||
writers[i].begin(session->last_received_frame, frame_idx,
|
||||
Message<mmo::WorldStateMessage>::value);
|
||||
writers[i].write_spawns(state->spawn());
|
||||
writers[i].write_despawns(state->despawn());
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ target_link_libraries(
|
||||
tw::protocol
|
||||
tw::message_protocol
|
||||
tw::quicr
|
||||
towards
|
||||
tw_common
|
||||
tw::gui
|
||||
imgui::imgui
|
||||
imgui::plot
|
||||
|
||||
@@ -33,8 +33,6 @@ target_link_libraries(tw_server_lib
|
||||
protobuf::libprotobuf
|
||||
${Boost_LIBRARIES}
|
||||
Tracy::TracyClient
|
||||
pqxx
|
||||
pq
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} src/server.cpp)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <Jolt/Jolt.h>
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
/**
|
||||
@@ -17,9 +15,6 @@ private:
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
HistoryBuffer<Clock::time_point, glm::vec3> m_history;
|
||||
HistoryBuffer<Clock::time_point, glm::vec3> m_position_history;
|
||||
|
||||
struct InputSlot {
|
||||
uint32_t frame;
|
||||
glm::vec3 input;
|
||||
@@ -34,13 +29,9 @@ private:
|
||||
|
||||
public:
|
||||
GET(m_speed, speed);
|
||||
GET_MUT_REF(m_history, input_history);
|
||||
GET_MUT_REF(m_position_history, position_history);
|
||||
|
||||
CharacterController(float speed) :
|
||||
m_speed(speed),
|
||||
m_history(Clock::now(), glm::vec3(), 10 * 20),
|
||||
m_position_history(Clock::now(), glm::vec3(), 10 * 20),
|
||||
m_input_ring(64),
|
||||
m_last_input_frame(0),
|
||||
m_frame_idx(0)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "world/RigidBody.hpp"
|
||||
#include <iostream>
|
||||
#include <print>
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
Reference in New Issue
Block a user