diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml index efcfcde4e..90aa7c146 100644 --- a/.github/workflows/continuous.yml +++ b/.github/workflows/continuous.yml @@ -80,12 +80,16 @@ jobs: echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)" echo "SIMD_ARGS=-DSIMD_CXX_FLAGS=-march=x86-64-v3" >> "$GITHUB_ENV" - - name: Cache Build - id: cache-build - uses: actions/cache@v5 + # Cache entries are immutable, so a fixed key is saved once and never + # updated. We give each run its own key and restore the newest one by + # prefix instead. Only pushes to main save (see "Save ccache" below), and + # PRs restore from those. + - name: Restore ccache + uses: actions/cache/restore@v5 with: path: ${{ env.CACHE_PATH }} - key: ${{ runner.os }}-${{ matrix.config }}-cache + key: ${{ runner.os }}-${{ matrix.config }}-ccache-${{ github.run_id }} + restore-keys: ${{ runner.os }}-${{ matrix.config }}-ccache- - name: Prepare ccache run: | @@ -93,9 +97,12 @@ jobs: ccache -V && ccache --show-config ccache --show-stats && ccache --zero-stats - # Debug keeps the asserts (NDEBUG stays undefined) and -O0 but drops -g. - # Debug info made the Debug ccache about 20x larger than Release, and CI - # never reads it: assert and Catch2 messages carry __FILE__ and __LINE__. + # Debug keeps the asserts (NDEBUG stays undefined) but drops -g and builds + # at -O1. Debug info made the Debug ccache about 20x larger than Release, + # and CI never reads it: assert and Catch2 messages carry __FILE__ and + # __LINE__. At -O0 none of Eigen's small template functions get inlined, + # which made the Debug tests about 10x slower than Release. -O1 lets the + # compiler inline most of them, and every assert is still checked. - name: Configure (Linux/macOS) if: runner.os != 'Windows' run: | @@ -103,17 +110,22 @@ jobs: cd build cmake .. \ -DIPC_TOOLKIT_BUILD_TESTS=ON \ - -DCMAKE_C_FLAGS_DEBUG=-O0 \ - -DCMAKE_CXX_FLAGS_DEBUG=-O0 \ + -DCMAKE_C_FLAGS_DEBUG=-O1 \ + -DCMAKE_CXX_FLAGS_DEBUG=-O1 \ $SIMD_ARGS \ -DCMAKE_BUILD_TYPE=${{ matrix.config }} + # MSVC's default Debug flags include /Zi, which writes debug info to a + # shared PDB that ccache cannot cache, so we drop it the same way the + # Linux/macOS configure drops -g. - name: Configure (Windows) if: runner.os == 'Windows' shell: cmd run: | cmake -G Ninja ^ -DIPC_TOOLKIT_BUILD_TESTS=ON ^ + -DCMAKE_C_FLAGS_DEBUG="/Od /Ob0 /RTC1" ^ + -DCMAKE_CXX_FLAGS_DEBUG="/Od /Ob0 /RTC1" ^ -DCMAKE_BUILD_TYPE=${{ matrix.config }} ^ -B build ^ -S . @@ -130,5 +142,14 @@ jobs: run: | cmake --build build -j ${{ steps.cpu-cores.outputs.count }} && ccache --show-stats + # We save after a failed build too, so the next run reuses what did + # compile. + - name: Save ccache + if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/cache/save@v5 + with: + path: ${{ env.CACHE_PATH }} + key: ${{ runner.os }}-${{ matrix.config }}-ccache-${{ github.run_id }} + - name: Tests run: cd build; ctest --verbose -j ${{ steps.cpu-cores.outputs.count }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e05462490..38b8d6671 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -46,12 +46,15 @@ jobs: done echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)" - - name: Cache Build - id: cache-build - uses: actions/cache@v5 + # Cache entries are immutable, so a fixed key is saved once and never + # updated. We give each run its own key and restore the newest one by + # prefix instead. Only pushes to main save, and PRs restore from those. + - name: Restore ccache + uses: actions/cache/restore@v5 with: path: ${{ env.CACHE_PATH }} - key: coverage-cache + key: coverage-ccache-${{ github.run_id }} + restore-keys: coverage-ccache- - name: Prepare ccache run: | @@ -74,6 +77,13 @@ jobs: cmake --build build -j ${{ steps.cpu-cores.outputs.count }} ccache --show-stats + - name: Save ccache + if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/cache/save@v5 + with: + path: ${{ env.CACHE_PATH }} + key: coverage-ccache-${{ github.run_id }} + - name: Run Coverage run: | cd build diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 08a673a07..717ec8cbb 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -117,14 +117,16 @@ jobs: echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)" # The CUDA key gets its own prefix because continuous.yml uses the same - # - names, and cache entries are immutable. Whichever workflow - # saved first would otherwise own the entry for both builds. - - name: Cache Build - id: cache-build - uses: actions/cache@v5 + # - names. Cache entries are immutable, so a fixed key is + # saved once and never updated. We give each run its own key and restore + # the newest one by prefix instead. Only pushes to main save, and PRs + # restore from those. + - name: Restore ccache + uses: actions/cache/restore@v5 with: path: ${{ env.CACHE_PATH }} - key: cuda-${{ runner.os }}-${{ matrix.config }}-cache + key: cuda-${{ runner.os }}-${{ matrix.config }}-ccache-${{ github.run_id }} + restore-keys: cuda-${{ runner.os }}-${{ matrix.config }}-ccache- - name: Prepare ccache shell: bash @@ -185,6 +187,13 @@ jobs: cmake --build build -j ${{ steps.cpu-cores.outputs.count }} ccache --show-stats + - name: Save ccache + if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/cache/save@v5 + with: + path: ${{ env.CACHE_PATH }} + key: cuda-${{ runner.os }}-${{ matrix.config }}-ccache-${{ github.run_id }} + # The GPU unit tests, minus the CCD and STQ ones on the cloth and puffer # ball meshes, which take several minutes each on a simulated GPU. - name: Tests diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a9f51ddba..b6aa3c1de 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,12 +34,15 @@ jobs: python -m pip install --upgrade pip pip install -r docs/requirements.txt - - name: Cache Build - id: cache-build - uses: actions/cache@v5 + # Cache entries are immutable, so a fixed key is saved once and never + # updated. We give each run its own key and restore the newest one by + # prefix instead. + - name: Restore ccache + uses: actions/cache/restore@v5 with: path: ${{ env.CACHE_PATH }} - key: docs-ccache + key: docs-ccache-${{ github.run_id }} + restore-keys: docs-ccache- - name: Prepare Ccache run: | @@ -120,6 +123,13 @@ jobs: pip install --verbose . ccache --show-stats + - name: Save ccache + if: ${{ !cancelled() }} + uses: actions/cache/save@v5 + with: + path: ${{ env.CACHE_PATH }} + key: docs-ccache-${{ github.run_id }} + - name: Build docs run: | cd docs diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 139e8f5cf..aa92b2b17 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -91,6 +91,21 @@ jobs: choco install ccache "CACHE_PATH=${env:LOCALAPPDATA}\ccache" | Out-File -FilePath $env:GITHUB_ENV -Append + # scikit-build-core picks the Visual Studio generator on Windows. That + # generator ignores CMAKE__COMPILER_LAUNCHER, so ccache never runs, + # and it compiles the files of each target one at a time. We use Ninja + # instead, which needs the MSVC environment set up first. + - name: Setup MSVC (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Use Ninja (Windows) + if: runner.os == 'Windows' + run: | + "CMAKE_GENERATOR=Ninja" | Out-File -FilePath $env:GITHUB_ENV -Append + # On Linux, explicitly build for x86-64-v3 instead of -march=native. This # avoids issues with ccache restoring from a runner with a different ISA. - name: Check x86-64-v3 support (Linux) @@ -101,12 +116,15 @@ jobs: done echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)" - - name: Cache Build - id: cache-build - uses: actions/cache@v5 + # Cache entries are immutable, so a fixed key is saved once and never + # updated. We give each run its own key and restore the newest one by + # prefix instead. Only pushes to main save, and PRs restore from those. + - name: Restore ccache + uses: actions/cache/restore@v5 with: path: ${{ env.CACHE_PATH }} - key: ${{ runner.os }}-Python${{ matrix.python-version }}-cache + key: ${{ runner.os }}-Python${{ matrix.python-version }}-ccache-${{ github.run_id }} + restore-keys: ${{ runner.os }}-Python${{ matrix.python-version }}-ccache- - name: Prepare Ccache run: | @@ -120,6 +138,13 @@ jobs: run: | pip install --verbose . && ccache --show-stats + - name: Save ccache + if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/cache/save@v5 + with: + path: ${{ env.CACHE_PATH }} + key: ${{ runner.os }}-Python${{ matrix.python-version }}-ccache-${{ github.run_id }} + - name: Tests run: | pip install -r python/tests/requirements.txt diff --git a/tests/src/tests/collisions/test_thickness.cpp b/tests/src/tests/collisions/test_thickness.cpp index c9b92d251..d0dc68c65 100644 --- a/tests/src/tests/collisions/test_thickness.cpp +++ b/tests/src/tests/collisions/test_thickness.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -242,13 +243,17 @@ TEST_CASE("Plane-vertex with a thickness", "[collisions][thickness][plane]") TEST_CASE("CCD stops at the thickness", "[ccd][thickness]") { -#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(NDEBUG) - SKIP("'CCD stops at the thickness' test is skipped in debug mode"); -#endif - const auto broad_phase = GENERATE(tests::BroadPhaseGenerator::create()); CAPTURE(broad_phase->name()); + // Tight Inclusion slows down sharply as the minimum separation grows, + // because every point within the thickness counts as a root it has to + // refine. With a 0.2 separation and the default 1e-6 tolerance, this test + // took 30-100 s in Release and over 20 minutes in Debug. We only check the + // step to within 0.1, so a 1e-3 tolerance loses nothing and runs about + // 20x faster. + const TightInclusionCCD ccd(/*tolerance=*/1e-3); + Eigen::MatrixXd V0, V1; Eigen::MatrixXi E, F; two_triangles(/*gap=*/1, V0, E, F); @@ -259,13 +264,13 @@ TEST_CASE("CCD stops at the thickness", "[ccd][thickness]") const double thickness = 0.1; // per face, so a pair rests 0.2 apart const double plain_step = compute_collision_free_stepsize( - mesh, V0, V1, /*min_distance=*/std::nullopt, broad_phase.get()); + mesh, V0, V1, /*min_distance=*/std::nullopt, broad_phase.get(), ccd); CHECK(plain_step <= 0.5); CHECK(plain_step > 0.3); mesh.set_face_thickness(Eigen::Vector2d::Constant(thickness)); const double step = compute_collision_free_stepsize( - mesh, V0, V1, /*min_distance=*/std::nullopt, broad_phase.get()); + mesh, V0, V1, /*min_distance=*/std::nullopt, broad_phase.get(), ccd); // Contact at a gap of 0.2, so z = 0.2 at t = 0.4. CHECK(step <= 0.4); CHECK(step > 0.3); @@ -274,18 +279,20 @@ TEST_CASE("CCD stops at the thickness", "[ccd][thickness]") // A min_distance (deprecated) overrides the thickness. CHECK( compute_collision_free_stepsize( - mesh, V0, V1, /*min_distance=*/0.0, broad_phase.get()) + mesh, V0, V1, /*min_distance=*/0.0, broad_phase.get(), ccd) == Catch::Approx(plain_step)); Eigen::MatrixXd V_close = V0; V_close.bottomRows(3).col(2).setConstant(0.15); CHECK(!is_step_collision_free( - mesh, V0, V_close, /*min_distance=*/std::nullopt, broad_phase.get())); + mesh, V0, V_close, /*min_distance=*/std::nullopt, broad_phase.get(), + ccd)); CHECK(is_step_collision_free( - mesh, V0, V_close, /*min_distance=*/0.0, broad_phase.get())); + mesh, V0, V_close, /*min_distance=*/0.0, broad_phase.get(), ccd)); mesh.clear_thickness(); CHECK(is_step_collision_free( - mesh, V0, V_close, /*min_distance=*/std::nullopt, broad_phase.get())); + mesh, V0, V_close, /*min_distance=*/std::nullopt, broad_phase.get(), + ccd)); } TEST_CASE(