diff --git a/.github/workflows/RELEASE_CHECK.yml b/.github/workflows/RELEASE_CHECK.yml new file mode 100644 index 0000000..782686a --- /dev/null +++ b/.github/workflows/RELEASE_CHECK.yml @@ -0,0 +1,367 @@ +name: Release check + +on: + push: + branches: + - main + - dev + - release/** + paths: + - ".github/workflows/RELEASE_CHECK.yml" + - ".github/workflows/release.yml" + - "CMakeLists.txt" + - "cmake/**" + - "config/**" + - "examples/**" + - "tests/**" + - "modules/**" + - "scripts/**" + - ".gitmodules" + - "vcpkg.json" + - "README.md" + - "CHANGELOG.md" + + pull_request: + branches: + - main + - dev + - release/** + paths: + - ".github/workflows/RELEASE_CHECK.yml" + - ".github/workflows/release.yml" + - "CMakeLists.txt" + - "cmake/**" + - "config/**" + - "examples/**" + - "tests/**" + - "modules/**" + - "scripts/**" + - ".gitmodules" + - "vcpkg.json" + - "README.md" + - "CHANGELOG.md" + + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-check-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release-check: + name: check (${{ matrix.vixos }} / ${{ matrix.arch }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + vixos: linux + arch: x86_64 + + - os: ubuntu-24.04-arm + vixos: linux + arch: aarch64 + + - os: macos-15-intel + vixos: macos + arch: x86_64 + + - os: macos-14 + vixos: macos + arch: aarch64 + + - os: windows-2022 + vixos: windows + arch: x86_64 + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Setup CMake + uses: lukka/get-cmake@latest + + - name: Install deps (Linux x86_64) + if: runner.os == 'Linux' && matrix.arch == 'x86_64' + shell: bash + run: | + set -euxo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + ninja-build \ + ca-certificates \ + git \ + curl \ + nlohmann-json3-dev \ + libssl-dev \ + zlib1g-dev \ + libsqlite3-dev \ + libbrotli-dev \ + libspdlog-dev \ + libfmt-dev + + - name: Setup Linux aarch64 toolchain + if: runner.os == 'Linux' && matrix.arch == 'aarch64' + shell: bash + run: | + set -euxo pipefail + + sudo dpkg --add-architecture arm64 + sudo apt-get update + + sudo apt-get install -y --no-install-recommends \ + build-essential \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu \ + pkg-config \ + ninja-build \ + ca-certificates \ + git \ + curl \ + nlohmann-json3-dev:arm64 \ + libssl-dev:arm64 \ + zlib1g-dev:arm64 \ + libsqlite3-dev:arm64 \ + libbrotli-dev:arm64 \ + libspdlog-dev \ + libfmt-dev + + cat > toolchain-aarch64.cmake <<'EOF' + set(CMAKE_SYSTEM_NAME Linux) + set(CMAKE_SYSTEM_PROCESSOR aarch64) + + set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) + set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) + + set(CMAKE_FIND_ROOT_PATH + /usr/aarch64-linux-gnu + /usr/lib/aarch64-linux-gnu + /usr/include/aarch64-linux-gnu + /usr + ) + + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) + + list(APPEND CMAKE_PREFIX_PATH + /usr/lib/x86_64-linux-gnu/cmake + /usr/lib/cmake + /lib/x86_64-linux-gnu/cmake + ) + + set(PKG_CONFIG_EXECUTABLE /usr/bin/pkg-config) + set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig") + set(ENV{PKG_CONFIG_PATH} "") + EOF + + - name: Install deps (macOS) + if: runner.os == 'macOS' + shell: bash + run: | + set -euxo pipefail + brew update + brew install pkg-config openssl@3 spdlog fmt + + - name: Setup vcpkg (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + git clone https://github.com/microsoft/vcpkg $env:GITHUB_WORKSPACE\vcpkg + "VCPKG_ROOT=$env:GITHUB_WORKSPACE\vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Append + cd $env:GITHUB_WORKSPACE\vcpkg + .\bootstrap-vcpkg.bat + + - name: Configure (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + set -euxo pipefail + + CMAKE_ARGS=( + -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DVIX_ENABLE_INSTALL=OFF + -DVIX_DB_USE_MYSQL=OFF + -DVIX_CORE_WITH_MYSQL=OFF + -DVIX_ENABLE_HTTP_COMPRESSION=OFF + ) + + if [ "${{ runner.os }}" = "Linux" ] && [ "${{ matrix.arch }}" = "aarch64" ]; then + CMAKE_ARGS+=( + -DCMAKE_TOOLCHAIN_FILE="${GITHUB_WORKSPACE}/toolchain-aarch64.cmake" + ) + fi + + if [ "${{ runner.os }}" = "macOS" ]; then + BREW_PREFIX="$(brew --prefix)" + BREW_SSL="$(brew --prefix openssl@3 2>/dev/null || true)" + + if [ -n "$BREW_SSL" ] && [ -d "$BREW_SSL" ]; then + CMAKE_ARGS+=(-DOPENSSL_ROOT_DIR="$BREW_SSL") + CMAKE_ARGS+=(-DCMAKE_PREFIX_PATH="$BREW_PREFIX") + fi + fi + + : > cmake_output.log + cmake "${CMAKE_ARGS[@]}" 2>&1 | tee -a cmake_output.log + + - name: Build (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + set -euxo pipefail + : > build_output.log + cmake --build build -j 2 2>&1 | tee -a build_output.log + + - name: Package and validate (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + set -euxo pipefail + + BIN="" + for p in "build/vix" "build/bin/vix" "build/Release/vix"; do + if [ -f "$p" ]; then + BIN="$p" + break + fi + done + + if [ -z "$BIN" ]; then + echo "::error::Expected vix binary not found" + find build -maxdepth 6 -type f -name vix -print || true + exit 1 + fi + + mkdir -p dist smoke + cp "$BIN" dist/vix + chmod +x dist/vix + + ASSET="vix-${{ matrix.vixos }}-${{ matrix.arch }}.tar.gz" + tar -C dist -czf "dist/$ASSET" vix + rm -f dist/vix + + tar -xzf "dist/$ASSET" -C smoke + file smoke/vix + + if [ "${{ matrix.vixos }}" = "linux" ] && [ "${{ matrix.arch }}" = "x86_64" ]; then + ldd smoke/vix || true + smoke/vix --version + fi + + if [ "${{ matrix.vixos }}" = "macos" ]; then + smoke/vix --version + fi + + - name: Vcpkg install (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + cd $env:GITHUB_WORKSPACE + & "$env:VCPKG_ROOT\vcpkg.exe" install --triplet x64-windows --x-manifest-root "$env:GITHUB_WORKSPACE" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Configure (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + "" | Out-File -FilePath cmake_output.log -Encoding utf8 + + cmake -S . -B build -A x64 ` + -DVIX_ENABLE_INSTALL=OFF ` + -DVIX_DB_USE_MYSQL=OFF ` + -DVIX_CORE_WITH_MYSQL=OFF ` + -DVIX_ENABLE_HTTP_COMPRESSION=OFF ` + -DVCPKG_TARGET_TRIPLET=x64-windows ` + -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake" 2>&1 | Tee-Object -FilePath cmake_output.log -Append + + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Build (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + "" | Out-File -FilePath build_output.log -Encoding utf8 + cmake --build build --config Release 2>&1 | Tee-Object -FilePath build_output.log -Append + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Package and validate (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $candidates = @( + "build\vix.exe", + "build\Release\vix.exe", + "build\bin\vix.exe", + "build\Release\bin\vix.exe" + ) + + $bin = $null + foreach ($p in $candidates) { + if (Test-Path $p) { + $bin = $p + break + } + } + + if (!$bin) { + Write-Host "::error::Expected vix.exe not found" + if (Test-Path "build") { + Get-ChildItem -Recurse build | Select-Object FullName + } + exit 1 + } + + New-Item -ItemType Directory -Force -Path dist | Out-Null + Copy-Item $bin dist\vix.exe + + $asset = "vix-windows-${{ matrix.arch }}.zip" + Compress-Archive -Path dist\vix.exe -DestinationPath "dist\$asset" -Force + + New-Item -ItemType Directory -Force -Path smoke | Out-Null + Expand-Archive -Path "dist\$asset" -DestinationPath smoke -Force + .\smoke\vix.exe --version + + - name: Collect logs + if: always() + shell: bash + run: | + set +e + OUT="logs/${{ matrix.vixos }}-${{ matrix.arch }}" + mkdir -p "$OUT" + + test -f cmake_output.log && cp -f cmake_output.log "$OUT/" || true + test -f build_output.log && cp -f build_output.log "$OUT/" || true + + if [ -d build ]; then + find build -maxdepth 4 -type f \ + \( -name "CMakeCache.txt" -o -name "CMakeError.log" -o -name "CMakeOutput.log" -o -name "CMakeConfigureLog.yaml" \) \ + -exec cp -f {} "$OUT/" \; || true + fi + + - name: Upload logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: release-check-logs-${{ matrix.vixos }}-${{ matrix.arch }} + path: logs/${{ matrix.vixos }}-${{ matrix.arch }}/* + if-no-files-found: warn + + - name: Upload checked package + uses: actions/upload-artifact@v4 + with: + name: release-check-dist-${{ matrix.vixos }}-${{ matrix.arch }} + path: dist/* + if-no-files-found: error diff --git a/.github/workflows/sdk-release.yml b/.github/workflows/sdk-release.yml index 522f2ec..c3e702e 100644 --- a/.github/workflows/sdk-release.yml +++ b/.github/workflows/sdk-release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: tag: - description: "Optional tag to build/release (e.g. v2.5.3). If empty, uses the current ref." + description: "Optional tag to build/release (e.g. v2.6.0). If empty, uses the current ref." required: false default: "" @@ -20,37 +20,13 @@ concurrency: jobs: build-sdk: - name: build-sdk (${{ matrix.variant }} / linux / x86_64) - - strategy: - fail-fast: false - matrix: - include: - - variant: core - os: ubuntu-24.04 - sdkos: linux - arch: x86_64 - asset: vix-sdk-linux-x86_64.tar.gz - install_dir: install-core - build_dir: build-core - - - variant: db - os: ubuntu-24.04 - sdkos: linux - arch: x86_64 - asset: vix-sdk-db-linux-x86_64.tar.gz - install_dir: install-db - build_dir: build-db - - - variant: full - os: ubuntu-24.04 - sdkos: linux - arch: x86_64 - asset: vix-sdk-full-linux-x86_64.tar.gz - install_dir: install-full - build_dir: build-full - - runs-on: ${{ matrix.os }} + name: build-sdk-full (linux / x86_64) + runs-on: ubuntu-24.04 + + env: + BUILD_DIR: build-sdk-full + INSTALL_DIR: install-sdk-full + ASSET: vix-sdk-linux-x86_64.tar.gz steps: - name: Checkout @@ -93,91 +69,84 @@ jobs: libbrotli-dev \ libspdlog-dev \ libfmt-dev \ - libmysqlcppconn-dev + libmysqlcppconn-dev \ + libsdl2-dev \ + libsdl2-image-dev \ + libgl1-mesa-dev - - name: Configure SDK + - name: Configure SDK full shell: bash run: | set -euxo pipefail - COMMON_ARGS=( - -S . - -B "${{ matrix.build_dir }}" - -G Ninja - -DCMAKE_BUILD_TYPE=Release - -DVIX_ENABLE_INSTALL=ON - -DVIX_BUILD_EXAMPLES=OFF - -DVIX_BUILD_TESTS=OFF - -DVIX_TIME_BUILD_TESTS=OFF - -DVIX_TIME_BUILD_BENCH=OFF - -DVIX_TEMPLATE_BUILD_TESTS=OFF - -DVIX_TEMPLATE_BUILD_EXAMPLES=OFF - -DVIX_TEMPLATE_BUILD_BENCH=OFF - -DCMAKE_INSTALL_PREFIX="${GITHUB_WORKSPACE}/${{ matrix.install_dir }}" - ) - - if [ "${{ matrix.variant }}" = "core" ]; then - cmake "${COMMON_ARGS[@]}" \ - -DVIX_ENABLE_DB=OFF \ - -DVIX_ENABLE_ORM=OFF \ - -DVIX_ENABLE_WEBSOCKET=OFF \ - -DVIX_DB_USE_SQLITE=OFF \ - -DVIX_DB_USE_MYSQL=OFF \ - -DVIX_DB_USE_POSTGRES=OFF \ - -DVIX_DB_USE_REDIS=OFF \ - -DVIX_CORE_WITH_MYSQL=OFF \ - -DVIX_ENABLE_HTTP_COMPRESSION=OFF - fi - - if [ "${{ matrix.variant }}" = "db" ]; then - cmake "${COMMON_ARGS[@]}" \ - -DVIX_ENABLE_DB=ON \ - -DVIX_ENABLE_ORM=OFF \ - -DVIX_DB_USE_SQLITE=ON \ - -DVIX_DB_USE_MYSQL=ON \ - -DVIX_DB_USE_POSTGRES=OFF \ - -DVIX_DB_USE_REDIS=OFF \ - -DVIX_CORE_WITH_MYSQL=OFF \ - -DVIX_ENABLE_HTTP_COMPRESSION=OFF - fi - - if [ "${{ matrix.variant }}" = "full" ]; then - cmake "${COMMON_ARGS[@]}" \ - -DVIX_ENABLE_DB=ON \ - -DVIX_ENABLE_ORM=ON \ - -DVIX_DB_USE_SQLITE=ON \ - -DVIX_DB_USE_MYSQL=ON \ - -DVIX_DB_USE_POSTGRES=OFF \ - -DVIX_DB_USE_REDIS=OFF \ - -DVIX_CORE_WITH_MYSQL=OFF \ - -DVIX_ENABLE_HTTP_COMPRESSION=ON \ - -DVIX_ENABLE_WEBSOCKET=ON \ - -DVIX_ENABLE_CLI=ON \ - -DVIX_ENABLE_MIDDLEWARE=ON \ - -DVIX_ENABLE_P2P=ON \ - -DVIX_ENABLE_P2P_HTTP=ON \ - -DVIX_ENABLE_CACHE=ON \ - -DVIX_ENABLE_ASYNC=ON \ - -DVIX_ENABLE_VALIDATION=ON \ - -DVIX_ENABLE_CRYPTO=ON \ - -DVIX_ENABLE_WEBRPC=ON \ - -DVIX_ENABLE_TIME=ON \ - -DVIX_ENABLE_TESTS_MODULE=ON \ - -DVIX_ENABLE_TEMPLATE=ON \ - -DVIX_ENABLE_PROCESS=ON - fi + cmake -S . -B "$BUILD_DIR" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="${GITHUB_WORKSPACE}/${INSTALL_DIR}" \ + -DVIX_ENABLE_INSTALL=ON \ + -DVIX_BUILD_EXAMPLES=OFF \ + -DVIX_BUILD_TESTS=OFF \ + -DVIX_ENABLE_HTTP_COMPRESSION=ON \ + -DVIX_ENABLE_DB=ON \ + -DVIX_DB_USE_SQLITE=ON \ + -DVIX_DB_USE_MYSQL=ON \ + -DVIX_DB_USE_POSTGRES=OFF \ + -DVIX_DB_USE_REDIS=OFF \ + -DVIX_CORE_WITH_MYSQL=OFF \ + -DVIX_ENABLE_ORM=ON \ + -DVIX_ENABLE_WEBSOCKET=ON \ + -DVIX_ENABLE_CLI=ON \ + -DVIX_ENABLE_MIDDLEWARE=ON \ + -DVIX_ENABLE_P2P=ON \ + -DVIX_ENABLE_P2P_HTTP=ON \ + -DVIX_ENABLE_CACHE=ON \ + -DVIX_ENABLE_ASYNC=ON \ + -DVIX_ENABLE_VALIDATION=ON \ + -DVIX_ENABLE_CRYPTO=ON \ + -DVIX_ENABLE_WEBRPC=ON \ + -DVIX_ENABLE_TIME=ON \ + -DVIX_ENABLE_TESTS_MODULE=ON \ + -DVIX_ENABLE_TEMPLATE=ON \ + -DVIX_ENABLE_PROCESS=ON \ + -DVIX_ENABLE_THREADPOOL=ON \ + -DVIX_ENABLE_KV=ON \ + -DVIX_ENABLE_AGENT=ON \ + -DVIX_ENABLE_GAME=ON \ + -DVIX_GAME_ENABLE_SDL=ON \ + -DVIX_GAME_ENABLE_SDL_OPENGL=ON \ + -DVIX_TIME_BUILD_TESTS=OFF \ + -DVIX_TIME_BUILD_BENCH=OFF \ + -DVIX_TEMPLATE_BUILD_TESTS=OFF \ + -DVIX_TEMPLATE_BUILD_EXAMPLES=OFF \ + -DVIX_TEMPLATE_BUILD_BENCH=OFF \ + -DVIX_AGENT_BUILD_TESTS=OFF \ + -DVIX_AGENT_BUILD_EXAMPLES=OFF \ + -DVIX_GAME_BUILD_TESTS=OFF \ + -DVIX_GAME_BUILD_EXAMPLES=OFF - name: Build SDK shell: bash run: | set -euxo pipefail - cmake --build "${{ matrix.build_dir }}" -j2 + cmake --build "$BUILD_DIR" -j2 - name: Install SDK shell: bash run: | set -euxo pipefail - cmake --install "${{ matrix.build_dir }}" + cmake --install "$BUILD_DIR" + + - name: Verify installed SDK tree + shell: bash + run: | + set -euxo pipefail + + test -d "$INSTALL_DIR" + test -f "$INSTALL_DIR/bin/vix" + test -f "$INSTALL_DIR/lib/cmake/Vix/VixConfig.cmake" + test -f "$INSTALL_DIR/lib/cmake/Vix/VixTargets.cmake" + + find "$INSTALL_DIR/include" -maxdepth 4 -type f | sort | head -n 80 + find "$INSTALL_DIR/lib/cmake" -maxdepth 4 -type f | sort - name: Package SDK shell: bash @@ -185,9 +154,7 @@ jobs: set -euxo pipefail mkdir -p dist - - test -d "${{ matrix.install_dir }}" - tar -C "${{ matrix.install_dir }}" -czf "dist/${{ matrix.asset }}" . + tar -C "$INSTALL_DIR" -czf "dist/$ASSET" . ls -lah dist @@ -199,14 +166,14 @@ jobs: rm -rf smoke-bin mkdir -p smoke-bin - tar -xzf "dist/${{ matrix.asset }}" -C smoke-bin + tar -xzf "dist/$ASSET" -C smoke-bin test -f smoke-bin/bin/vix file smoke-bin/bin/vix ldd smoke-bin/bin/vix || true smoke-bin/bin/vix --version - - name: Validate packaged SDK with consumer project + - name: Validate packaged SDK with full consumer project shell: bash run: | set -euxo pipefail @@ -214,7 +181,7 @@ jobs: rm -rf smoke-sdk consumer mkdir -p smoke-sdk consumer - tar -xzf "dist/${{ matrix.asset }}" -C smoke-sdk + tar -xzf "dist/$ASSET" -C smoke-sdk cat > consumer/CMakeLists.txt <<'EOF' cmake_minimum_required(VERSION 3.20) @@ -232,11 +199,37 @@ jobs: cat > consumer/main.cpp <<'EOF' #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include int main() { - std::cout << "Vix SDK consumer build works\n"; + std::cout << "Vix full SDK consumer build works\n"; return 0; } EOF @@ -251,7 +244,7 @@ jobs: - name: Upload dist uses: actions/upload-artifact@v4 with: - name: sdk-dist-${{ matrix.variant }}-${{ matrix.sdkos }}-${{ matrix.arch }} + name: sdk-dist-linux-x86_64 path: dist/* if-no-files-found: error @@ -261,7 +254,7 @@ jobs: runs-on: ubuntu-24.04 steps: - - name: Download all artifacts + - name: Download SDK artifact uses: actions/download-artifact@v4 with: path: dist-all @@ -273,11 +266,9 @@ jobs: mkdir -p dist - find dist-all -type f -name "vix-sdk-*.tar.gz" -exec cp -f {} dist/ \; + find dist-all -type f -name "vix-sdk-linux-x86_64.tar.gz" -exec cp -f {} dist/ \; test -f dist/vix-sdk-linux-x86_64.tar.gz - test -f dist/vix-sdk-db-linux-x86_64.tar.gz - test -f dist/vix-sdk-full-linux-x86_64.tar.gz ls -lah dist diff --git a/.gitignore b/.gitignore index e56e3d7..067c33c 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,4 @@ issues_audit.json # ====================================================== registry/ .vix/ +vix-production-simplicity-checklist.md diff --git a/CMakeLists.txt b/CMakeLists.txt index d94cdb8..d233e7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,8 +222,8 @@ option(VIX_AGENT_BUILD_EXAMPLES "Build vix::ai_agent examples" option(VIX_ENABLE_GAME "Build Vix Game module" ON) option(VIX_GAME_BUILD_TESTS "Build vix::game tests" OFF) option(VIX_GAME_BUILD_EXAMPLES "Build vix::game examples" OFF) -option(VIX_GAME_ENABLE_SDL "Enable SDL backend for vix::game" OFF) -option(VIX_GAME_ENABLE_SDL_OPENGL "Enable SDL OpenGL renderer backend for vix::game" OFF) +option(VIX_GAME_ENABLE_SDL "Enable SDL backend for vix::game" OFF) +option(VIX_GAME_ENABLE_SDL_OPENGL "Enable SDL OpenGL renderer backend for vix::game" OFF) # ---------------------------------------------------- # Tooling / Static analysis diff --git a/examples/agent/ai_analyze_project.cpp b/examples/agent/ai_analyze_project.cpp index 8329458..dd4d6cf 100644 --- a/examples/agent/ai_analyze_project.cpp +++ b/examples/agent/ai_analyze_project.cpp @@ -1,4 +1,4 @@ -#include +#include #include int main() diff --git a/examples/agent/ai_local_llama.cpp b/examples/agent/ai_local_llama.cpp index 91bcac4..1b2bb80 100644 --- a/examples/agent/ai_local_llama.cpp +++ b/examples/agent/ai_local_llama.cpp @@ -1,4 +1,4 @@ -#include +#include #include int main() diff --git a/examples/agent/ai_simple.cpp b/examples/agent/ai_simple.cpp index 99a38b4..f6dbf30 100644 --- a/examples/agent/ai_simple.cpp +++ b/examples/agent/ai_simple.cpp @@ -1,4 +1,4 @@ -#include +#include #include int main() diff --git a/examples/agent/ai_with_memory.cpp b/examples/agent/ai_with_memory.cpp index 1e27d49..209af4a 100644 --- a/examples/agent/ai_with_memory.cpp +++ b/examples/agent/ai_with_memory.cpp @@ -1,4 +1,4 @@ -#include +#include #include int main() diff --git a/examples/agent/ai_with_tools.cpp b/examples/agent/ai_with_tools.cpp index 7568f22..21d0a82 100644 --- a/examples/agent/ai_with_tools.cpp +++ b/examples/agent/ai_with_tools.cpp @@ -1,4 +1,4 @@ -#include +#include #include int main() diff --git a/examples/game/asset_loading.cpp b/examples/game/asset_loading.cpp new file mode 100644 index 0000000..a86003a --- /dev/null +++ b/examples/game/asset_loading.cpp @@ -0,0 +1,33 @@ +#include +#include + +int main() +{ + vix::game::AssetManager assets("assets"); + + auto loaded = assets.load("example.txt"); + if (!loaded) + { + vix::print("failed to load asset:", loaded.error().message()); + return 1; + } + + auto *asset = assets.get(loaded.value()); + if (asset == nullptr) + { + vix::print("asset not found after loading"); + return 1; + } + + vix::print("asset loaded"); + vix::print("id:", asset->id()); + vix::print("path:", asset->path().relative()); + vix::print("size:", asset->size()); + + if (vix::game::is_text_asset(asset->type())) + { + vix::print("content:", asset->text_content()); + } + + return 0; +} diff --git a/examples/game/async_asset_loading.cpp b/examples/game/async_asset_loading.cpp new file mode 100644 index 0000000..45b9ace --- /dev/null +++ b/examples/game/async_asset_loading.cpp @@ -0,0 +1,35 @@ +#include +#include + +int main() +{ + vix::game::AssetManager assets("assets"); + vix::game::JobSystem jobs; + vix::game::AsyncAssetLoader loader(assets, jobs); + + auto handle = loader.load( + "example.txt", + [](vix::game::GameResult result) + { + if (!result) + { + vix::print("async asset failed:", result.error().message()); + return; + } + + vix::print("async asset loaded:", result.value()); + }); + + if (!handle) + { + vix::print("failed to start async load:", handle.error().message()); + return 1; + } + + handle.value().wait(); + handle.value().get(); + + jobs.shutdown(); + + return 0; +} diff --git a/examples/game/basic_loop.cpp b/examples/game/basic_loop.cpp new file mode 100644 index 0000000..ca2c84c --- /dev/null +++ b/examples/game/basic_loop.cpp @@ -0,0 +1,30 @@ +#include +#include + +int main() +{ + vix::game::App app; + + app.set_title("Basic Loop"); + app.set_target_fps(60); + + app.on_update( + [&app](const vix::game::Frame &frame) + { + vix::print("frame:", frame.index, "delta_ms:", frame.delta_ms()); + + if (frame.index >= 5) + { + app.stop(); + } + }); + + auto result = app.run(); + if (!result) + { + vix::print("error:", result.error().message()); + return 1; + } + + return 0; +} diff --git a/examples/game/export_project.cpp b/examples/game/export_project.cpp new file mode 100644 index 0000000..57bf247 --- /dev/null +++ b/examples/game/export_project.cpp @@ -0,0 +1,25 @@ +#include +#include + +int main() +{ + vix::game::GameExportConfig config; + config.project_root = "."; + config.output_directory = "dist"; + config.name = "demo-game"; + + vix::game::GameExporter exporter; + + auto result = exporter.export_project(config); + if (!result) + { + vix::print("export failed:", result.error().message()); + return 1; + } + + vix::print("exported to:", result.value().output_path.string()); + vix::print("copied files:", result.value().copied_files); + vix::print("copied directories:", result.value().copied_directories); + + return 0; +} diff --git a/examples/game/hello_game.cpp b/examples/game/hello_game.cpp new file mode 100644 index 0000000..2120f2b --- /dev/null +++ b/examples/game/hello_game.cpp @@ -0,0 +1,15 @@ +#include +#include + +int main() +{ + vix::game::App app; + + app.set_title("Hello Vix Game"); + app.set_target_fps(60); + + vix::print("Hello from vix/game"); + vix::print("title:", app.config().title); + + return 0; +} diff --git a/examples/game/input_demo.cpp b/examples/game/input_demo.cpp new file mode 100644 index 0000000..d4423d9 --- /dev/null +++ b/examples/game/input_demo.cpp @@ -0,0 +1,40 @@ +#include +#include + +int main() +{ + vix::game::InputSystem input; + + auto jump = input.bind_key("jump", vix::game::InputKey::Space); + if (!jump) + { + vix::print("failed to bind jump:", jump.error().message()); + return 1; + } + + auto shoot = input.bind_button("shoot", vix::game::InputButton::Left); + if (!shoot) + { + vix::print("failed to bind shoot:", shoot.error().message()); + return 1; + } + + input.begin_frame(); + input.press_key(vix::game::InputKey::Space); + input.press_button(vix::game::InputButton::Left); + + vix::print("space down:", input.key_down(vix::game::InputKey::Space)); + vix::print("jump pressed:", input.action_pressed("jump")); + vix::print("shoot pressed:", input.action_pressed("shoot")); + + input.begin_frame(); + + vix::print("jump down:", input.action_down("jump")); + vix::print("jump pressed next frame:", input.action_pressed("jump")); + + input.release_key(vix::game::InputKey::Space); + + vix::print("jump released:", input.action_released("jump")); + + return 0; +} diff --git a/examples/game/jobs_demo.cpp b/examples/game/jobs_demo.cpp new file mode 100644 index 0000000..454bbec --- /dev/null +++ b/examples/game/jobs_demo.cpp @@ -0,0 +1,54 @@ +/** + * + * @file scene_demo.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/game + * + * Use of this source code is governed by a MIT license + * that can be found in the LICENSE file. + * + * Vix.cpp + * + */ + +#include +#include + +int main() +{ + vix::game::JobSystem jobs; + + auto submitted = jobs.submit( + [] + { + vix::print("job executed"); + }); + + if (!submitted) + { + vix::print("failed to submit job:", submitted.error().message()); + return 1; + } + + submitted.value().wait(); + submitted.value().get(); + + auto detached = jobs.submit_detached( + [] + { + vix::print("detached job executed"); + }); + + if (!detached) + { + vix::print("failed to submit detached job:", detached.error().message()); + return 1; + } + + jobs.shutdown(); + + return 0; +} diff --git a/examples/game/null_renderer.cpp b/examples/game/null_renderer.cpp new file mode 100644 index 0000000..8a6621f --- /dev/null +++ b/examples/game/null_renderer.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +int main() +{ + vix::game::Window window(std::make_unique()); + + auto opened = window.open(vix::game::WindowConfig::headless_config()); + if (!opened) + { + vix::print("failed to open window:", opened.error().message()); + return 1; + } + + vix::game::Renderer renderer(std::make_unique()); + + auto initialized = renderer.init(window); + if (!initialized) + { + vix::print("failed to init renderer:", initialized.error().message()); + return 1; + } + + renderer.begin_frame(); + renderer.clear(vix::game::Color::from_rgba8(20, 20, 30)); + renderer.end_frame(); + + vix::print("renderer backend:", renderer.backend_name()); + vix::print("renderer size:", renderer.width(), "x", renderer.height()); + + renderer.shutdown(); + window.close(); + + return 0; +} diff --git a/examples/game/null_window.cpp b/examples/game/null_window.cpp new file mode 100644 index 0000000..f605efb --- /dev/null +++ b/examples/game/null_window.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + +int main() +{ + vix::game::Window window(std::make_unique()); + + auto opened = window.open(vix::game::WindowConfig{ + .title = "Null Window", + .width = 800, + .height = 600, + .fullscreen = false, + .resizable = true, + .visible = false, + .vsync = false, + .headless = true}); + + if (!opened) + { + vix::print("failed to open window:", opened.error().message()); + return 1; + } + + vix::print("window backend:", window.backend_name()); + vix::print("window title:", window.title()); + vix::print("window size:", window.width(), "x", window.height()); + + window.resize(1024, 768); + + for (const auto &event : window.poll_events()) + { + vix::print("event:", vix::game::to_string(event.type)); + } + + window.close(); + + return 0; +} diff --git a/examples/game/opengl_renderer_demo.cpp b/examples/game/opengl_renderer_demo.cpp new file mode 100644 index 0000000..44a793e --- /dev/null +++ b/examples/game/opengl_renderer_demo.cpp @@ -0,0 +1,116 @@ +/** + * + * @file opengl_renderer_demo.cpp + * @author Gaspard Kirira + * + * Vix.cpp + * + */ + +#include + +#include +#include +#include +#include + +class OpenGLDemoScene final : public vix::game::Scene +{ +public: + vix::game::GameBoolResult on_load() override + { + vix::print("SDL OpenGL scene loaded"); + return vix::game::Scene::on_load(); + } + + void on_update(const vix::game::Frame &frame) override + { + if (input().key_pressed(vix::game::InputKey::Escape)) + { + app().stop(); + return; + } + + renderer2d().clear(vix::game::Color{0.05F, 0.06F, 0.08F, 1.0F}); + + if (frame.index % 60 == 0) + { + vix::print("SDL OpenGL frame:", frame.index); + } + } +}; + +int main() +{ + vix::game::AppConfig config = vix::game::AppConfig::defaults(); + config.headless = false; + config.title = "Vix Game SDL OpenGL Demo"; + + config.window = vix::game::WindowConfig::defaults(); + config.window.title = config.title; + config.window.width = 960; + config.window.height = 540; + config.window.resizable = true; + config.window.visible = true; + config.window.vsync = true; + config.window.headless = false; + config.window.opengl = true; + + vix::game::App app(config); + + auto init = app.init(); + if (!init) + { + vix::print("app init failed:", init.error().message()); + return 1; + } + + auto &context = app.runtime().context(); + + context + .set_window_backend(std::make_unique()) + .set_renderer_backend(std::make_unique()); + + auto opened = context.window().open(config.window); + if (!opened) + { + vix::print("window open failed:", opened.error().message()); + return 1; + } + + auto renderer_init = context.renderer().init(context.window()); + if (!renderer_init) + { + vix::print("renderer init failed:", renderer_init.error().message()); + return 1; + } + + context.renderer2d().set_camera( + vix::game::Camera2D::from_size( + context.window().width(), + context.window().height())); + + auto scene = app.scenes().create("main"); + if (!scene) + { + vix::print("scene creation failed:", scene.error().message()); + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + vix::print("scene activation failed:", active.error().message()); + return 1; + } + + auto run = app.run(); + if (!run) + { + vix::print("game failed:", run.error().message()); + return 1; + } + + app.shutdown(); + return 0; +} diff --git a/examples/game/render2d_demo.cpp b/examples/game/render2d_demo.cpp new file mode 100644 index 0000000..a34c5a6 --- /dev/null +++ b/examples/game/render2d_demo.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +int main() +{ + vix::game::Window window(std::make_unique()); + + auto opened = window.open(vix::game::WindowConfig::headless_config()); + if (!opened) + { + vix::print("failed to open window:", opened.error().message()); + return 1; + } + + vix::game::Renderer renderer(std::make_unique()); + + auto initialized = renderer.init(window); + if (!initialized) + { + vix::print("failed to initialize renderer:", initialized.error().message()); + return 1; + } + + vix::game::Renderer2D renderer2d(renderer); + + vix::game::Transform2D transform; + transform.set_position(vix::game::Vec2{100.0F, 80.0F}); + transform.set_scale(2.0F); + + vix::game::Sprite sprite; + sprite.set_texture(vix::game::TextureHandle::from_asset(1)); + sprite.set_size(vix::game::Vec2{64.0F, 64.0F}); + sprite.set_tint(vix::game::Color::white()); + + renderer2d.begin_frame(); + renderer2d.clear(vix::game::Color::black()); + renderer2d.draw_sprite(transform, sprite); + renderer2d.end_frame(); + + vix::print("2D commands:", renderer2d.command_count()); + + renderer.shutdown(); + window.close(); + + return 0; +} diff --git a/examples/game/runtime_v3_demo.cpp b/examples/game/runtime_v3_demo.cpp new file mode 100644 index 0000000..53082fe --- /dev/null +++ b/examples/game/runtime_v3_demo.cpp @@ -0,0 +1,60 @@ +#include +#include + +int main() +{ + vix::game::App app; + + vix::game::GameRuntime runtime(app); + auto init = runtime.init(); + if (!init) + { + vix::print("runtime init failed:", init.error().message()); + return 1; + } + + vix::game::EditorRuntime editor(runtime); + auto editor_init = editor.init(); + if (!editor_init) + { + vix::print("editor init failed:", editor_init.error().message()); + return 1; + } + + vix::game::ScriptRuntime scripts(runtime.context()); + auto scripts_init = scripts.init(); + if (!scripts_init) + { + vix::print("script runtime init failed:", scripts_init.error().message()); + return 1; + } + + vix::game::AudioRuntime audio(runtime.context()); + auto audio_init = audio.init(); + if (!audio_init) + { + vix::print("audio runtime init failed:", audio_init.error().message()); + return 1; + } + + vix::game::PhysicsRuntime physics(runtime.context()); + auto physics_init = physics.init(); + if (!physics_init) + { + vix::print("physics runtime init failed:", physics_init.error().message()); + return 1; + } + + auto package = vix::game::GamePackage::defaults(); + + vix::print("V3 runtime ready"); + vix::print("package:", package.name); + vix::print("entry scene:", package.entry_scene); + + editor.enter_play_mode(); + vix::print("editor play mode:", editor.context().play_mode()); + + runtime.shutdown(); + + return 0; +} diff --git a/examples/game/scene_demo.cpp b/examples/game/scene_demo.cpp new file mode 100644 index 0000000..f5afb00 --- /dev/null +++ b/examples/game/scene_demo.cpp @@ -0,0 +1,83 @@ +/** + * + * @file jobs_demo.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/game + * + * Use of this source code is governed by a MIT license + * that can be found in the LICENSE file. + * + * Vix.cpp + * + */ + +#include +#include + +class MainScene final : public vix::game::Scene +{ +public: + MainScene() + : vix::game::Scene("main") + { + } + + vix::game::GameBoolResult on_load() override + { + vix::print("main scene loaded"); + return vix::game::Scene::on_load(); + } + + void on_enter() override + { + vix::game::Scene::on_enter(); + vix::print("main scene entered"); + } + + void on_update(const vix::game::Frame &frame) override + { + vix::print("scene frame:", frame.index); + + if (frame.index >= 5) + { + app().stop(); + } + } + + vix::game::GameBoolResult on_unload() override + { + vix::print("main scene unloaded"); + return vix::game::Scene::on_unload(); + } +}; + +int main() +{ + vix::game::App app; + + auto added = app.scenes().create("main"); + if (!added) + { + vix::print("error:", added.error().message()); + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + vix::print("error:", active.error().message()); + return 1; + } + + auto result = app.run(); + if (!result) + { + vix::print("error:", result.error().message()); + return 1; + } + + return 0; +} diff --git a/examples/game/scene_serialization.cpp b/examples/game/scene_serialization.cpp new file mode 100644 index 0000000..d2167c5 --- /dev/null +++ b/examples/game/scene_serialization.cpp @@ -0,0 +1,85 @@ +#include +#include + +class MainScene final : public vix::game::Scene +{ +public: + MainScene() + : vix::game::Scene("main") + { + } +}; + +class MenuScene final : public vix::game::Scene +{ +public: + MenuScene() + : vix::game::Scene("menu") + { + } +}; + +static std::unique_ptr make_scene( + const std::string &name) +{ + if (name == "main") + { + return std::make_unique(); + } + + if (name == "menu") + { + return std::make_unique(); + } + + return nullptr; +} + +int main() +{ + vix::game::App app; + + auto menu = app.scenes().create("menu"); + if (!menu) + { + vix::print("failed to create menu scene:", menu.error().message()); + return 1; + } + + auto main = app.scenes().create("main"); + if (!main) + { + vix::print("failed to create main scene:", main.error().message()); + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + vix::print("failed to activate scene:", active.error().message()); + return 1; + } + + auto json = vix::game::SceneSerializer::to_json(app.scenes()); + + vix::print("serialized scene count:", app.scenes().size()); + vix::print("active scene:", app.scenes().active_name()); + + vix::game::App restored; + + auto restored_result = vix::game::SceneSerializer::from_json( + restored.scenes(), + json, + make_scene); + + if (!restored_result) + { + vix::print("failed to restore scenes:", restored_result.error().message()); + return 1; + } + + vix::print("restored scene count:", restored.scenes().size()); + vix::print("restored active scene:", restored.scenes().active_name()); + + return 0; +} diff --git a/examples/game/sdl_renderer_demo.cpp b/examples/game/sdl_renderer_demo.cpp new file mode 100644 index 0000000..03aee82 --- /dev/null +++ b/examples/game/sdl_renderer_demo.cpp @@ -0,0 +1,101 @@ +/** + * + * @file sdl_renderer_demo.cpp + * @author Gaspard Kirira + * + * Vix.cpp + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class SDLRendererDemoScene final : public vix::game::Scene +{ +public: + void on_update(const vix::game::Frame &frame) override + { + if (input().key_pressed(vix::game::InputKey::Escape)) + { + app().stop(); + return; + } + + const float t = static_cast((frame.index % 255) / 255.0); + + renderer2d().clear(vix::game::Color{0.08F, t * 0.25F, 0.18F, 1.0F}); + } +}; + +int main() +{ + vix::game::AppConfig config = vix::game::AppConfig::defaults(); + config.headless = false; + config.title = "Vix Game SDLRenderer Demo"; + + config.window = vix::game::WindowConfig::defaults(); + config.window.title = config.title; + config.window.width = 960; + config.window.height = 540; + config.window.resizable = true; + config.window.visible = true; + config.window.vsync = true; + config.window.headless = false; + + vix::game::App app(config); + + auto init = app.init(); + if (!init) + { + return 1; + } + + auto &context = app.runtime().context(); + + context + .set_window_backend(std::make_unique()) + .set_renderer_backend(std::make_unique()); + + auto opened = context.window().open(config.window); + if (!opened) + { + return 1; + } + + auto renderer_init = context.renderer().init(context.window()); + if (!renderer_init) + { + return 1; + } + + auto scene = app.scenes().create("main"); + if (!scene) + { + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + return 1; + } + + auto run = app.run(); + if (!run) + { + return 1; + } + + app.shutdown(); + return 0; +} diff --git a/examples/game/sdl_window_null_renderer_demo.cpp b/examples/game/sdl_window_null_renderer_demo.cpp new file mode 100644 index 0000000..8c34185 --- /dev/null +++ b/examples/game/sdl_window_null_renderer_demo.cpp @@ -0,0 +1,124 @@ +/** + * + * @file sdl_window_null_renderer_demo.cpp + * @author Gaspard Kirira + * + * Vix.cpp + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class SDLWindowDemoScene final : public vix::game::Scene +{ +public: + void on_update(const vix::game::Frame &frame) override + { + (void)frame; + + if (input().key_pressed(vix::game::InputKey::Escape)) + { + app().stop(); + return; + } + + auto pointer = input().pointer_position(); + auto delta = input().pointer_delta(); + + if (delta.x != 0.0 || delta.y != 0.0) + { + // The demo intentionally avoids logging every frame. + // Pointer state is updated through SDL events and InputSystem. + (void)pointer; + } + + vix::game::Sprite sprite = vix::game::Sprite::from_asset(1); + sprite.set_size(vix::game::Vec2{64.0F, 64.0F}); + + vix::game::Transform2D transform = + vix::game::Transform2D::at(vix::game::Vec2{100.0F, 100.0F}); + + renderer2d().draw_sprite(transform, sprite); + } +}; + +int main() +{ + vix::game::AppConfig config = vix::game::AppConfig::defaults(); + config.headless = false; + config.title = "Vix Game SDL Window + NullRenderer"; + config.window = vix::game::WindowConfig::defaults(); + config.window.title = config.title; + config.window.width = 960; + config.window.height = 540; + config.window.resizable = true; + config.window.visible = true; + config.window.vsync = true; + config.window.headless = false; + + vix::game::App app(config); + + auto init = app.init(); + if (!init) + { + return 1; + } + + auto &context = app.runtime().context(); + + context + .set_window_backend(std::make_unique()) + .set_renderer_backend(std::make_unique()); + + auto opened = context.window().open(config.window); + if (!opened) + { + return 1; + } + + auto renderer_init = context.renderer().init(context.window()); + if (!renderer_init) + { + return 1; + } + + context.renderer2d().set_camera( + vix::game::Camera2D::from_size( + context.window().width(), + context.window().height())); + + auto scene = app.scenes().create("main"); + if (!scene) + { + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + return 1; + } + + auto run = app.run(); + if (!run) + { + return 1; + } + + app.shutdown(); + return 0; +} diff --git a/examples/game/sprite_demo.cpp b/examples/game/sprite_demo.cpp new file mode 100644 index 0000000..13e5a1e --- /dev/null +++ b/examples/game/sprite_demo.cpp @@ -0,0 +1,155 @@ +/** + * + * @file sprite_demo.cpp + * @author Gaspard Kirira + * + * Vix.cpp + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class SpriteDemoScene final : public vix::game::Scene +{ +public: + vix::game::GameBoolResult on_load() override + { + auto loaded = assets().load_or_get("player.jpg"); + if (!loaded) + { + return loaded.error(); + } + + texture_id_ = loaded.value(); + + auto *asset = assets().get(texture_id_); + if (asset == nullptr) + { + return vix::game::make_game_error( + vix::game::GameErrorCode::AssetNotFound, + "sprite demo asset not found after loading"); + } + + auto uploaded = renderer().upload_texture(*asset); + if (!uploaded) + { + return uploaded.error(); + } + + return vix::game::Scene::on_load(); + } + + void on_update(const vix::game::Frame &frame) override + { + if (input().key_pressed(vix::game::InputKey::Escape)) + { + app().stop(); + return; + } + + renderer2d().clear(vix::game::Color{0.06F, 0.07F, 0.09F, 1.0F}); + + const float x = 320.0F; + const float y = 180.0F; + + vix::game::Transform2D transform = + vix::game::Transform2D::at(vix::game::Vec2{x, y}); + + transform.rotation = + static_cast(frame.index % 360) * 0.017453292519943295F; + + transform.origin = vix::game::Vec2{64.0F, 64.0F}; + + vix::game::Sprite sprite = vix::game::Sprite::from_asset(texture_id_); + sprite.set_size(vix::game::Vec2{128.0F, 128.0F}); + + renderer2d().draw_sprite(transform, sprite); + } + +private: + vix::game::AssetId texture_id_{vix::game::invalid_asset_id}; +}; + +int main() +{ + vix::game::AppConfig config = vix::game::AppConfig::defaults(); + config.headless = false; + config.title = "Vix Game Sprite Demo"; + config.asset_root = "examples/assets"; + + config.window = vix::game::WindowConfig::defaults(); + config.window.title = config.title; + config.window.width = 960; + config.window.height = 540; + config.window.resizable = true; + config.window.visible = true; + config.window.vsync = true; + config.window.headless = false; + config.window.opengl = true; + + vix::game::App app(config); + + auto init = app.init(); + if (!init) + { + return 1; + } + + auto &context = app.runtime().context(); + + context + .set_window_backend(std::make_unique()) + .set_renderer_backend(std::make_unique()); + + auto opened = context.window().open(config.window); + if (!opened) + { + return 1; + } + + auto renderer_init = context.renderer().init(context.window()); + if (!renderer_init) + { + return 1; + } + + context.renderer2d().set_camera( + vix::game::Camera2D::from_size( + context.window().width(), + context.window().height())); + + auto scene = app.scenes().create("main"); + if (!scene) + { + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + return 1; + } + + auto run = app.run(); + if (!run) + { + return 1; + } + + app.shutdown(); + return 0; +} diff --git a/examples/game/tiny_adventure.cpp b/examples/game/tiny_adventure.cpp new file mode 100644 index 0000000..82cc0d3 --- /dev/null +++ b/examples/game/tiny_adventure.cpp @@ -0,0 +1,351 @@ +/** + * + * @file tiny_adventure.cpp + * @author Gaspard Kirira + * + * Vix.cpp + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + struct Player + { + vix::game::Vec2 position{420.0F, 260.0F}; + vix::game::Vec2 size{96.0F, 96.0F}; + float speed{260.0F}; + }; + + struct Coin + { + vix::game::Vec2 position{680.0F, 260.0F}; + vix::game::Vec2 size{64.0F, 64.0F}; + bool collected{false}; + }; + + bool intersects( + const vix::game::Vec2 &a_position, + const vix::game::Vec2 &a_size, + const vix::game::Vec2 &b_position, + const vix::game::Vec2 &b_size) + { + const float a_left = a_position.x; + const float a_right = a_position.x + a_size.x; + const float a_top = a_position.y; + const float a_bottom = a_position.y + a_size.y; + + const float b_left = b_position.x; + const float b_right = b_position.x + b_size.x; + const float b_top = b_position.y; + const float b_bottom = b_position.y + b_size.y; + + return a_left < b_right && + a_right > b_left && + a_top < b_bottom && + a_bottom > b_top; + } + + float clamp_float( + float value, + float minimum, + float maximum) + { + if (value < minimum) + { + return minimum; + } + + if (value > maximum) + { + return maximum; + } + + return value; + } +} + +class TinyAdventureScene final : public vix::game::Scene +{ +public: + vix::game::GameBoolResult on_load() override + { + vix::print("Tiny Adventure loaded"); + vix::print("Move with arrow keys or WASD. Press Escape to quit."); + + auto player_asset = assets().load_or_get("player.jpg"); + if (!player_asset) + { + vix::print("failed to load player.jpg:", player_asset.error().message()); + return player_asset.error(); + } + + player_texture_ = player_asset.value(); + + auto *player_image = assets().get(player_texture_); + if (player_image == nullptr) + { + return vix::game::make_game_error( + vix::game::GameErrorCode::AssetNotFound, + "player.jpg was loaded but cannot be found"); + } + + auto player_upload = renderer().upload_texture(*player_image); + if (!player_upload) + { + vix::print("failed to upload player texture:", player_upload.error().message()); + return player_upload.error(); + } + + auto coin_asset = assets().load_or_get("coin.jpeg"); + if (!coin_asset) + { + vix::print("failed to load coin.jpg:", coin_asset.error().message()); + return coin_asset.error(); + } + + coin_texture_ = coin_asset.value(); + + auto *coin_image = assets().get(coin_texture_); + if (coin_image == nullptr) + { + return vix::game::make_game_error( + vix::game::GameErrorCode::AssetNotFound, + "coin.jpg was loaded but cannot be found"); + } + + auto coin_upload = renderer().upload_texture(*coin_image); + if (!coin_upload) + { + vix::print("failed to upload coin texture:", coin_upload.error().message()); + return coin_upload.error(); + } + + return vix::game::Scene::on_load(); + } + + void on_update(const vix::game::Frame &frame) override + { + if (input().key_pressed(vix::game::InputKey::Escape)) + { + app().stop(); + return; + } + + update_player(frame); + update_coin(); + render(frame); + } + +private: + void update_player(const vix::game::Frame &frame) + { + vix::game::Vec2 direction{0.0F, 0.0F}; + + if (input().key_down(vix::game::InputKey::A) || + input().key_down(vix::game::InputKey::ArrowLeft)) + { + direction.x -= 1.0F; + } + + if (input().key_down(vix::game::InputKey::D) || + input().key_down(vix::game::InputKey::ArrowRight)) + { + direction.x += 1.0F; + } + + if (input().key_down(vix::game::InputKey::W) || + input().key_down(vix::game::InputKey::ArrowUp)) + { + direction.y -= 1.0F; + } + + if (input().key_down(vix::game::InputKey::S) || + input().key_down(vix::game::InputKey::ArrowDown)) + { + direction.y += 1.0F; + } + + const float length = + std::sqrt(direction.x * direction.x + direction.y * direction.y); + + if (length > 0.0F) + { + direction.x /= length; + direction.y /= length; + } + + const float delta_seconds = + static_cast(frame.delta_ms()) / 1000.0F; + + player_.position.x += direction.x * player_.speed * delta_seconds; + player_.position.y += direction.y * player_.speed * delta_seconds; + + player_.position.x = clamp_float(player_.position.x, 0.0F, 960.0F - player_.size.x); + player_.position.y = clamp_float(player_.position.y, 0.0F, 540.0F - player_.size.y); + } + + void update_coin() + { + if (coin_.collected) + { + return; + } + + if (intersects( + player_.position, + player_.size, + coin_.position, + coin_.size)) + { + coin_.collected = true; + ++score_; + + vix::print("coin collected. score:", score_); + } + } + + void render(const vix::game::Frame &frame) + { + const float pulse = + 0.5F + 0.5F * std::sin(static_cast(frame.index) * 0.04F); + + renderer2d().clear(vix::game::Color{0.05F, 0.06F, 0.09F, 1.0F}); + + draw_player(); + + if (!coin_.collected) + { + draw_coin(pulse); + } + } + + void draw_player() + { + vix::game::Transform2D transform = + vix::game::Transform2D::at(player_.position); + + vix::game::Sprite sprite = vix::game::Sprite::from_asset(player_texture_); + sprite.set_size(player_.size); + + renderer2d().draw_sprite(transform, sprite); + } + + void draw_coin(float pulse) + { + vix::game::Transform2D transform = + vix::game::Transform2D::at(coin_.position); + + const float scale = 1.0F + pulse * 0.15F; + transform.scale = vix::game::Vec2{scale, scale}; + + vix::game::Sprite sprite = vix::game::Sprite::from_asset(coin_texture_); + sprite.set_size(coin_.size); + sprite.tint = vix::game::Color{1.0F, 0.9F, 0.35F, 1.0F}; + + renderer2d().draw_sprite(transform, sprite); + } + +private: + Player player_{}; + Coin coin_{}; + + int score_{0}; + + vix::game::AssetId player_texture_{vix::game::invalid_asset_id}; + vix::game::AssetId coin_texture_{vix::game::invalid_asset_id}; +}; + +int main() +{ + vix::game::AppConfig config = vix::game::AppConfig::defaults(); + config.headless = false; + config.title = "Vix Game Tiny Adventure"; + config.asset_root = "examples/assets"; + + config.window = vix::game::WindowConfig::defaults(); + config.window.title = config.title; + config.window.width = 960; + config.window.height = 540; + config.window.resizable = true; + config.window.visible = true; + config.window.vsync = true; + config.window.headless = false; + config.window.opengl = true; + + vix::game::App app(config); + + auto init = app.init(); + if (!init) + { + vix::print("app init failed:", init.error().message()); + return 1; + } + + auto &context = app.runtime().context(); + + context + .set_window_backend(std::make_unique()) + .set_renderer_backend(std::make_unique()); + + auto opened = context.window().open(config.window); + if (!opened) + { + vix::print("window open failed:", opened.error().message()); + return 1; + } + + auto renderer_init = context.renderer().init(context.window()); + if (!renderer_init) + { + vix::print("renderer init failed:", renderer_init.error().message()); + return 1; + } + + context.renderer2d().set_camera( + vix::game::Camera2D::from_size( + context.window().width(), + context.window().height())); + + auto scene = app.scenes().create("main"); + if (!scene) + { + vix::print("scene creation failed:", scene.error().message()); + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + vix::print("scene activation failed:", active.error().message()); + return 1; + } + + auto run = app.run(); + if (!run) + { + vix::print("game failed:", run.error().message()); + return 1; + } + + app.shutdown(); + return 0; +} diff --git a/examples/game/window_input_demo.cpp b/examples/game/window_input_demo.cpp new file mode 100644 index 0000000..58c6566 --- /dev/null +++ b/examples/game/window_input_demo.cpp @@ -0,0 +1,132 @@ +/** + * + * @file window_input_demo.cpp + * @author Gaspard Kirira + * + * Vix.cpp + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +class WindowInputDemoScene final : public vix::game::Scene +{ +public: + void on_update(const vix::game::Frame &frame) override + { + (void)frame; + + auto &ctx = app().runtime().context(); + + auto pointer = ctx.input().pointer_position(); + auto pointer_delta = ctx.input().pointer_delta(); + + if (ctx.input().key_pressed(vix::game::InputKey::Escape)) + { + vix::print("Escape pressed, closing app"); + app().stop(); + return; + } + + if (pointer_delta.x != 0.0 || pointer_delta.y != 0.0) + { + vix::print( + "pointer:", + pointer.x, + pointer.y, + "delta:", + pointer_delta.x, + pointer_delta.y); + } + + const auto width = ctx.window().width(); + const auto height = ctx.window().height(); + + if (width != last_width_ || height != last_height_) + { + last_width_ = width; + last_height_ = height; + + vix::print("window resized:", width, "x", height); + } + } + +private: + std::uint32_t last_width_{0}; + std::uint32_t last_height_{0}; +}; + +int main() +{ + vix::game::AppConfig config = vix::game::AppConfig::defaults(); + config.headless = false; + config.title = "Vix Game Window/Input Demo"; + + config.window = vix::game::WindowConfig::defaults(); + config.window.title = config.title; + config.window.width = 960; + config.window.height = 540; + config.window.resizable = true; + config.window.visible = true; + config.window.vsync = true; + config.window.headless = false; + + vix::game::App app(config); + + auto init = app.init(); + if (!init) + { + return 1; + } + + auto &context = app.runtime().context(); + + context + .set_window_backend(std::make_unique()) + .set_renderer_backend(std::make_unique()); + + auto opened = context.window().open(config.window); + if (!opened) + { + return 1; + } + + auto renderer_init = context.renderer().init(context.window()); + if (!renderer_init) + { + return 1; + } + + auto scene = app.scenes().create("main"); + if (!scene) + { + return 1; + } + + auto active = app.scenes().set_active("main"); + if (!active) + { + return 1; + } + + auto run = app.run(); + if (!run) + { + return 1; + } + + app.shutdown(); + return 0; +} diff --git a/examples/static_files/public/hello.txt b/examples/static_files/public/hello.txt new file mode 100644 index 0000000..b6fc4c6 --- /dev/null +++ b/examples/static_files/public/hello.txt @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/examples/static_files/public/index.html b/examples/static_files/public/index.html index 4db2faf..d35d4db 100644 --- a/examples/static_files/public/index.html +++ b/examples/static_files/public/index.html @@ -1,11 +1 @@ - - - - Vix Static - - - -

Hello from Vix static server

- - - +

OK

\ No newline at end of file diff --git a/examples/static_files/static_files_app_simple.cpp b/examples/static_files/static_files_app_simple.cpp index 8fbc6dd..ff467ef 100644 --- a/examples/static_files/static_files_app_simple.cpp +++ b/examples/static_files/static_files_app_simple.cpp @@ -13,8 +13,7 @@ */ #include -#include -#include +#include #include #include @@ -24,7 +23,6 @@ using namespace vix; static std::filesystem::path source_dir() { - // __FILE__ = /home/softadastra/dev/tmp/static_files_app_simple.cpp return std::filesystem::path(__FILE__).parent_path(); } @@ -36,22 +34,22 @@ int main() { std::ofstream(root / "index.html") << "

OK

"; } + { std::ofstream(root / "hello.txt") << "hello"; } + vix::middleware::register_static_dir(); + App app; - app.use(vix::middleware::app::adapt_ctx( - vix::middleware::performance::static_files( - root, - { - .mount = "/", - .index_file = "index.html", - .add_cache_control = true, - .cache_control = "public, max-age=3600", - .fallthrough = true, - }))); + app.static_dir( + root, + "/", + "index.html", + true, + "public, max-age=3600", + true); app.get("/api/ping", [](Request &, Response &res) { res.json({"ok", true}); }); diff --git a/modules/agent b/modules/agent index dec919e..2fc2c3a 160000 --- a/modules/agent +++ b/modules/agent @@ -1 +1 @@ -Subproject commit dec919e97b4952a66408f02bf9bfda6727dd2aac +Subproject commit 2fc2c3a1e4d10d3f5bdfc27deb4e17458ed80f74 diff --git a/modules/cli b/modules/cli index 269facf..8937c37 160000 --- a/modules/cli +++ b/modules/cli @@ -1 +1 @@ -Subproject commit 269facf321101d6a2ed4214f33892496ae6840a0 +Subproject commit 8937c375a027094a6b8f9b69bf02ce709692bbda diff --git a/modules/core b/modules/core index f03b693..7cda6f4 160000 --- a/modules/core +++ b/modules/core @@ -1 +1 @@ -Subproject commit f03b693e5cb0d6515e932ec28c7b7417bbfa568c +Subproject commit 7cda6f4e23f3a8e1ffa928db75a8be1523302610 diff --git a/modules/crypto b/modules/crypto index 7f7c8d8..51017bd 160000 --- a/modules/crypto +++ b/modules/crypto @@ -1 +1 @@ -Subproject commit 7f7c8d8080179c181ddd6761bb62f2313995c029 +Subproject commit 51017bd53ca9b8ceb961fc982b262be035c71021 diff --git a/modules/json b/modules/json index 093d978..544a597 160000 --- a/modules/json +++ b/modules/json @@ -1 +1 @@ -Subproject commit 093d9787379277b505de1ea4ac2d82fbb18c0823 +Subproject commit 544a597841c9dcffcc6cc1bb0b1d941a160f5ba7 diff --git a/modules/middleware b/modules/middleware index 06dd129..1eefa0e 160000 --- a/modules/middleware +++ b/modules/middleware @@ -1 +1 @@ -Subproject commit 06dd12992ba7ac4f34e1147ac94b821b3c8b836f +Subproject commit 1eefa0e2abe48d8af8dd62201b9f921c1b8bfaa5 diff --git a/modules/reply b/modules/reply index d8a3d57..34fff02 160000 --- a/modules/reply +++ b/modules/reply @@ -1 +1 @@ -Subproject commit d8a3d57652cb47519a74c90ce42ee59160bb7c70 +Subproject commit 34fff02a2e3c84e5b9b9e8211ac4703cf85d897b diff --git a/modules/utils b/modules/utils index 873944d..13016cc 160000 --- a/modules/utils +++ b/modules/utils @@ -1 +1 @@ -Subproject commit 873944d9170491b23c785c247d43ebc69a62b009 +Subproject commit 13016cc28ef31df16fe07a6c47e358051fc6537d diff --git a/modules/websocket b/modules/websocket index fb36597..5a02658 160000 --- a/modules/websocket +++ b/modules/websocket @@ -1 +1 @@ -Subproject commit fb36597bfc436184438ca50792922b5cb838fa21 +Subproject commit 5a02658da01d780b3dfcefd6202705a6a21b9e1e diff --git a/vix/ai.hpp b/vix/agent.hpp similarity index 95% rename from vix/ai.hpp rename to vix/agent.hpp index e7ece86..e8abe46 100644 --- a/vix/ai.hpp +++ b/vix/agent.hpp @@ -1,6 +1,6 @@ /** * - * @file ai.hpp + * @file agent.hpp * @author Gaspard Kirira * * @brief Public umbrella header for the Vix AI module. diff --git a/vix/all.hpp b/vix/all.hpp new file mode 100644 index 0000000..e296ed1 --- /dev/null +++ b/vix/all.hpp @@ -0,0 +1,67 @@ +/** + * + * @file all.hpp + * @author Gaspard Kirira + * + * @brief Complete public aggregation header for Vix.cpp. + * + * Includes every public Vix module entry point. This header is intended + * for SDK validation, smoke tests, examples, and quick prototyping. + * + * For production code, prefer including only the modules you need: + * #include + * #include + * #include + * #include + * + * Usage: + * #include + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/vix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + */ +#ifndef VIX_ALL_HPP +#define VIX_ALL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // VIX_ALL_HPP diff --git a/vix/cli.hpp b/vix/cli.hpp new file mode 100644 index 0000000..1012a61 --- /dev/null +++ b/vix/cli.hpp @@ -0,0 +1,32 @@ +/** + * + * @file cli.hpp + * @author Gaspard Kirira + * + * @brief Public entry point for the Vix CLI module. + * + * Provides a clean and stable API for the Vix command-line runtime, + * including command dispatching, project scaffolding, build orchestration, + * diagnostics, package management, and developer tooling. + * + * Usage: + * #include + * + * For advanced usage, include specific components from: + * + * + * Copyright 2025, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/vix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + */ +#ifndef VIX_CLI_HPP +#define VIX_CLI_HPP + +#include + +#endif // VIX_CLI_HPP diff --git a/vix/kv.hpp b/vix/kv.hpp new file mode 100644 index 0000000..31988e4 --- /dev/null +++ b/vix/kv.hpp @@ -0,0 +1,32 @@ +/** + * + * @file kv.hpp + * @author Gaspard Kirira + * + * @brief Public entry point for the Vix KV module. + * + * Provides a clean and stable API for the Vix key-value storage engine, + * including records, keys, values, checksums, write-ahead logging, + * memtables, snapshots, indexing, compaction, and storage backends. + * + * Usage: + * #include + * + * For advanced usage, include specific components from: + * + * + * Copyright 2025, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/vix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + */ +#ifndef VIX_KV_HPP +#define VIX_KV_HPP + +#include + +#endif // VIX_KV_HPP diff --git a/vix/process.hpp b/vix/process.hpp new file mode 100644 index 0000000..f367d62 --- /dev/null +++ b/vix/process.hpp @@ -0,0 +1,32 @@ +/** + * + * @file process.hpp + * @author Gaspard Kirira + * + * @brief Public entry point for the Vix process module. + * + * Provides a clean and stable API for process execution, command + * spawning, child process management, status handling, output capture, + * process termination, asynchronous process operations, and pipelines. + * + * Usage: + * #include + * + * For advanced usage, include specific components from: + * + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/vix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + */ +#ifndef VIX_PROCESS_HPP +#define VIX_PROCESS_HPP + +#include + +#endif // VIX_PROCESS_HPP diff --git a/vix/reply.hpp b/vix/reply.hpp new file mode 100644 index 0000000..308fbbf --- /dev/null +++ b/vix/reply.hpp @@ -0,0 +1,32 @@ +/** + * + * @file reply.hpp + * @author Gaspard Kirira + * + * @brief Public entry point for the Vix reply module. + * + * Provides a clean and stable API for the Vix interactive reply and REPL + * subsystem, including API dispatching, call parsing, console handling, + * line editing, history, math helpers, flow control, and REPL utilities. + * + * Usage: + * #include + * + * For advanced usage, include specific components from: + * + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/vix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + */ +#ifndef VIX_REPLY_HPP +#define VIX_REPLY_HPP + +#include + +#endif // VIX_REPLY_HPP diff --git a/vix/websocket.hpp b/vix/websocket.hpp new file mode 100644 index 0000000..4f18602 --- /dev/null +++ b/vix/websocket.hpp @@ -0,0 +1,32 @@ +/** + * + * @file websocket.hpp + * @author Gaspard Kirira + * + * @brief Public entry point for the Vix WebSocket module. + * + * Provides a clean and stable API for WebSocket applications, + * including server runtime, sessions, routing, clients, protocol helpers, + * message persistence, metrics, HTTP integration, and long-polling fallback. + * + * Usage: + * #include + * + * For advanced usage, include specific components from: + * + * + * Copyright 2025, Gaspard Kirira. + * All rights reserved. + * https://github.com/vixcpp/vix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + */ +#ifndef VIX_WEBSOCKET_HPP +#define VIX_WEBSOCKET_HPP + +#include + +#endif // VIX_WEBSOCKET_HPP