diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 83d5cab25..ccef86073 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,16 +19,16 @@ jobs: - name: Checkout uses: actions/checkout@master - name: Set Environment - run: echo ::set-env name=DEPS::%HOME%\deps + run: echo "::set-env name=DEPS::$Env:HOME\deps" - name: Install SDL2 SDK run: | curl -s -S -o SDL2-devel-2.0.9-VC.zip -L https://www.libsdl.org/release/SDL2-devel-2.0.9-VC.zip - unzip SDL2-devel-2.0.9-VC.zip -d %DEPS% - echo ::set-env name=SDL2DIR::%DEPS%\SDL2-2.0.9 + unzip SDL2-devel-2.0.9-VC.zip -d "$Env:DEPS" + echo "::set-env name=SDL2DIR::$Env:DEPS\SDL2-2.0.9" - name: Install Assimp SDK run: | curl -s -S -o assimp-4.1.0.zip -L https://grimmdeps.blob.core.windows.net/deps/assimp-4.1.0.zip - unzip assimp-4.1.0.zip -d %DEPS% + unzip assimp-4.1.0.zip -d "$Env:DEPS" - name: Install Ninja uses: seanmiddleditch/gha-setup-ninja@master - name: Setup VS Environment @@ -41,7 +41,7 @@ jobs: run: | mkdir build cd build - cmake -G Ninja -DCMAKE_BUILD_TYPE:STRING=${{ matrix.config }} -DBUILD_SHARED_LIBS=YES -DASSIMP_ROOT_DIR:PATH="%DEPS%/assimp-4.1.0" .. || exit /b 1 + cmake -G Ninja -DCMAKE_BUILD_TYPE:STRING=${{ matrix.config }} -DBUILD_SHARED_LIBS=YES -DASSIMP_ROOT_DIR:PATH="$Env:DEPS/assimp-4.1.0" .. - name: Build run: cmake --build build --parallel - name: Test @@ -62,7 +62,6 @@ jobs: cc: gcc-7 - name: macOS os: macos-latest - xcode: 10.3 cxxflags: -Wall -Werror cxx: clang++ cc: clang @@ -76,7 +75,7 @@ jobs: uses: seanmiddleditch/gha-setup-ninja@master - name: Select XCode run: sudo /usr/bin/xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app - if: success() && runner.os == 'macOS' + if: success() && matrix.xcode != '' - name: Install uuid-dev run: | sudo apt-get -yq update diff --git a/source/libassetdb/public/potato/assetdb/hash_cache.h b/source/libassetdb/public/potato/assetdb/hash_cache.h index b7083c651..b3e990f34 100644 --- a/source/libassetdb/public/potato/assetdb/hash_cache.h +++ b/source/libassetdb/public/potato/assetdb/hash_cache.h @@ -16,8 +16,7 @@ namespace up { class HashCache { public: - HashCache() = default; - HashCache(FileSystem& fileSystem) : _fileSystem(fileSystem) {} + explicit HashCache(FileSystem& fileSystem) : _fileSystem(fileSystem) {} static UP_ASSETDB_API uint64 hashAssetContent(span contents) noexcept; static UP_ASSETDB_API uint64 hashAssetStream(Stream& stream); diff --git a/source/libruntime/public/potato/runtime/filesystem.h b/source/libruntime/public/potato/runtime/filesystem.h index 319f7807c..2e447173d 100644 --- a/source/libruntime/public/potato/runtime/filesystem.h +++ b/source/libruntime/public/potato/runtime/filesystem.h @@ -15,8 +15,8 @@ namespace up { public: virtual ~FileSystem() = default; - FileSystem(FileSystem const&) = default; - FileSystem& operator=(FileSystem const&) = default; + FileSystem(FileSystem const&) = delete; + FileSystem& operator=(FileSystem const&) = delete; virtual bool fileExists(zstring_view path) const noexcept = 0; virtual bool directoryExists(zstring_view path) const noexcept = 0; diff --git a/source/libruntime/public/potato/runtime/lock_free_queue.h b/source/libruntime/public/potato/runtime/lock_free_queue.h index 31bedca04..79be96b84 100644 --- a/source/libruntime/public/potato/runtime/lock_free_queue.h +++ b/source/libruntime/public/potato/runtime/lock_free_queue.h @@ -11,42 +11,57 @@ namespace up { template struct alignas(CacheLineWidth) AlignedAtomic : std::atomic { + using std::atomic::atomic; char _padding[CacheLineWidth - sizeof(std::atomic)]; }; - template + template class LockFreeQueue { - static constexpr std::uint32_t kBufferSize = Size; + static constexpr std::uint32_t kBufferSize = Capacity; static constexpr std::uint32_t kBufferMask = kBufferSize - 1; - static_assert((kBufferSize & kBufferMask) == 0, "ConcurrentQueue size must be a power of 2"); - - AlignedAtomic _sequence[kBufferSize]; - AlignedAtomic _enque; - AlignedAtomic _deque; - std::aligned_storage_t _buffer[kBufferSize]; + static_assert((kBufferSize & kBufferMask) == 0, "LockFreeQueue size must be a power of 2"); public: - inline LockFreeQueue() noexcept; + LockFreeQueue(); + ~LockFreeQueue(); + LockFreeQueue(LockFreeQueue const&) = delete; LockFreeQueue& operator=(LockFreeQueue const&) = delete; + constexpr auto capacity() const noexcept { return Capacity; } + template [[nodiscard]] inline bool tryEnque(InsertT&& value); [[nodiscard]] inline bool tryDeque(T& out); + + private: + AlignedAtomic _enque; + AlignedAtomic _deque; + AlignedAtomic* _sequence = nullptr; + std::aligned_storage_t* _buffer = nullptr; }; - template - LockFreeQueue::LockFreeQueue() noexcept { - _enque.store(0, std::memory_order_relaxed); - _deque.store(0, std::memory_order_relaxed); + template + LockFreeQueue::LockFreeQueue() + : _enque(0) + , _deque(0) + , _sequence(new AlignedAtomic[kBufferSize]) + , _buffer(new std::aligned_storage_t[kBufferSize]) + { for (std::uint32_t i = 0; i != kBufferSize; ++i) _sequence[i].store(i, std::memory_order_relaxed); } - template + template + LockFreeQueue::~LockFreeQueue() { + delete[] _sequence; + delete[] _buffer; + } + + template template - bool LockFreeQueue::tryEnque(InsertT&& value) { + bool LockFreeQueue::tryEnque(InsertT&& value) { std::uint32_t target = _enque.load(std::memory_order_relaxed); std::uint32_t id = _sequence[target & kBufferMask].load(std::memory_order_acquire); std::int32_t delta = id - target; @@ -66,8 +81,8 @@ namespace up { return true; } - template - bool LockFreeQueue::tryDeque(T& out) { + template + bool LockFreeQueue::tryDeque(T& out) { std::uint32_t target = _deque.load(std::memory_order_relaxed); std::uint32_t id = _sequence[target & kBufferMask].load(std::memory_order_acquire); std::int32_t delta = id - (target + 1); diff --git a/source/libruntime/public/potato/runtime/task_worker.h b/source/libruntime/public/potato/runtime/task_worker.h index 22bceabc2..48d75631a 100644 --- a/source/libruntime/public/potato/runtime/task_worker.h +++ b/source/libruntime/public/potato/runtime/task_worker.h @@ -18,8 +18,8 @@ namespace up { UP_RUNTIME_API explicit TaskWorker(TaskQueue& queue, zstring_view name); UP_RUNTIME_API ~TaskWorker(); - TaskWorker(TaskWorker&&) = default; - TaskWorker& operator=(TaskWorker&&) = default; + TaskWorker(TaskWorker&&) = delete; + TaskWorker& operator=(TaskWorker&&) = delete; SmallThreadId smallThreadId() const noexcept { return _threadId; } diff --git a/source/libruntime/tests/test_lock_free_queue.cpp b/source/libruntime/tests/test_lock_free_queue.cpp index 746961a70..82cdd4448 100644 --- a/source/libruntime/tests/test_lock_free_queue.cpp +++ b/source/libruntime/tests/test_lock_free_queue.cpp @@ -4,48 +4,82 @@ DOCTEST_TEST_SUITE("[potato][runtime] LockFreeQueue") { using namespace up; - using namespace up; DOCTEST_TEST_CASE("default") { LockFreeQueue queue; } - DOCTEST_TEST_CASE("thread") { - constexpr int size = 1024; - LockFreeQueue queue; + DOCTEST_TEST_CASE("fill") { + LockFreeQueue queue; + + for (std::size_t i = 0; i < queue.capacity(); ++i) { + DOCTEST_CHECK(queue.tryEnque(i)); + } - for (int i = 0; i < size; ++i) { + DOCTEST_CHECK(!queue.tryEnque(0)); + } + + DOCTEST_TEST_CASE("sequential") { + LockFreeQueue queue; + + for (std::size_t i = 0; i < queue.capacity(); ++i) { DOCTEST_CHECK(queue.tryEnque(i)); } - int total1 = 0; + DOCTEST_CHECK(!queue.tryEnque(0)); + + for (std::size_t i = 0; i < queue.capacity(); ++i) { + std::size_t result; + DOCTEST_CHECK(queue.tryDeque(result)); + DOCTEST_CHECK_EQ(i, result); + } + + std::size_t empty; + DOCTEST_CHECK(!queue.tryDeque(empty)); + } + + DOCTEST_TEST_CASE("thread") { + LockFreeQueue queue; + + std::size_t total1 = 0; auto thread1 = std::thread([&] { - int count; - while (queue.tryDeque(count)) { - total1 += count; + for (;;) { + std::size_t count; + if (queue.tryDeque(count)) { + if (count == 0) { + break; + } + total1 += count; + } } }); - int total2 = 0; + std::size_t total2 = 0; auto thread2 = std::thread([&] { - int count; - while (queue.tryDeque(count)) { - total2 += count; + for (;;) { + std::size_t count; + if (queue.tryDeque(count)) { + if (count == 0) { + break; + } + total2 += count; + } } }); + std::size_t expected = 0; + for (std::size_t i = 2; i < queue.capacity(); ++i) { + DOCTEST_CHECK(queue.tryEnque(i)); + expected += i; + } + + // signals end to threads + DOCTEST_CHECK(queue.tryEnque(0)); + DOCTEST_CHECK(queue.tryEnque(0)); + thread1.join(); thread2.join(); - // sum of [0, size] is sum(0...size-1) - // identities: - // sum(1...N) = N(N+1)/2 - // 0+N=N - // expand identities: - // sum(0...N-1) = sum(1...N-1) - // sum(1...N-1) = (N-1)(N-1+1)/2 - // simplify: - // (N-1)(N-1+1)/2 = N(N-1)/2 - DOCTEST_CHECK_EQ(total1 + total2, (size * (size - 1)) / 2); + DOCTEST_CHECK_EQ(total1 + total2, expected); } } diff --git a/source/spud/public/potato/spud/traits.h b/source/spud/public/potato/spud/traits.h index 7b1be0180..a5543988e 100644 --- a/source/spud/public/potato/spud/traits.h +++ b/source/spud/public/potato/spud/traits.h @@ -43,11 +43,11 @@ namespace up { template using enable_if_t = typename std::enable_if_t; -#if defined(__cpp_lib_invoke) +#if defined(__cpp_lib_constexpr_invoke) using std::invoke; #else template - decltype(auto) invoke(Return Class::*func, First&& first, Rest&&... rest) { + constexpr decltype(auto) invoke(Return Class::*func, First&& first, Rest&&... rest) { if constexpr (std::is_member_function_pointer_v) { return (std::forward(first).*func)(std::forward(rest)...); }