├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── CppCon2020-coroutine-job-system.pptx ├── LICENSE ├── README.md ├── main.cpp ├── schedule ├── LockQueue.hpp ├── algorithms.hpp ├── event.cpp ├── event.hpp ├── future.hpp ├── scheduler.cpp ├── scheduler.hpp ├── task.hpp └── token.hpp ├── span.hpp └── utils.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | out -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeList.txt : CMake project for cpp-coroutine-job, include source and define 2 | # project specific logic here. 3 | # 4 | cmake_minimum_required (VERSION 3.8) 5 | 6 | project (cpp-coroutine-job) 7 | 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | 11 | 12 | file(GLOB "*.h" "*.cpp" fsource) 13 | # Add source to this project's executable. 14 | add_executable (cpp-coroutine-job 15 | "main.cpp" 16 | "utils.hpp" 17 | "schedule/event.cpp" 18 | "schedule/scheduler.cpp" 19 | "span.hpp") 20 | 21 | # TODO: Add tests and install targets if needed. 22 | 23 | # target_include_directories(cpp-coroutine-job public "${PROJECT_SOURCE_DIR}") 24 | # target_include_directories(cpp-coroutine-job public "${PROJECT_SOURCE_DIR}/schedule") 25 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "", 13 | "variables": [] 14 | }, 15 | { 16 | "name": "x64-Release", 17 | "generator": "Ninja", 18 | "configurationType": "RelWithDebInfo", 19 | "buildRoot": "${projectDir}\\out\\build\\${name}", 20 | "installRoot": "${projectDir}\\out\\install\\${name}", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "", 23 | "ctestCommandArgs": "", 24 | "inheritEnvironments": [ "msvc_x64_x64" ], 25 | "variables": [] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /CppCon2020-coroutine-job-system.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tankiJong/cpp-coroutine-job-system/5a3e297db52e7c11317ce500f867d6c1b9fb6e76/CppCon2020-coroutine-job-system.pptx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tianyi Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpp-coroutine-job-system 2 | `./CppCon2020-coroutine-job-system.pptx`: the slide deck for CppCon 2020 3 | 4 | # Environment 5 | I only tested on windows with MSVC because I used some OS API and did not bother to make the system portable. But the code should be able to compile with some change of utility functions according to the target platform. User will also need to change the cmake file to update corresponding compiler switch. 6 | 7 | # Build 8 | - Tested on *visual studio 16.8 Preview 3*, which has fully implemented the coroutine TS 9 | - Open folder in VS 10 | - Run 11 | 12 | # Issue Report 13 | - The code is for demonstration purpose, so I do not have plan to make the system robust. 14 | - Issue reports/Pull requests are generally welcomed. I will either fix the bug if it's conceptionally wrong, or add comments if I decide not to fix. 15 | 16 | # Disclaimer 17 | - This is coded mainly for learning purpose, completely under-tested, so be careful :) -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // cpp-coroutine-job.cpp : Defines the entry point for the application. 2 | // 3 | 4 | #include 5 | #include "utils.hpp" 6 | #include "schedule/algorithms.hpp" 7 | #include "schedule/scheduler.hpp" 8 | #include "schedule/task.hpp" 9 | 10 | using namespace std; 11 | 12 | using namespace co; 13 | 14 | constexpr uint kLabCount = 5; 15 | constexpr uint kFactoryCount = 10; 16 | 17 | deferred_token<> LabDevelopVaccine(uint index, float chance, single_consumer_counter_event& e) 18 | { 19 | // printf( "lab %u doing research...\n", index ); 20 | 21 | do 22 | { 23 | std::this_thread::sleep_for( 1s ); 24 | float v = random::Between01(); 25 | if( v > chance ) 26 | { 27 | printf( "lab %u did not find a vaccine.... Retry....\n", index ); 28 | } else 29 | { 30 | printf( "lab %u found a vaccine!\n", index ); 31 | break; 32 | } 33 | } while(!e.IsReady()); 34 | 35 | e.decrement(); 36 | 37 | co_return; 38 | } 39 | 40 | 41 | deferred_token<> Factory(std::atomic& stock, bool& terminationSignal) 42 | { 43 | while(!terminationSignal) 44 | { 45 | std::this_thread::sleep_for( 1s ); 46 | uint vaccine = random::Between( 50, 100 ); 47 | stock += vaccine; 48 | // printf( "A factory produced %u vaccine\n", vaccine ); 49 | } 50 | co_return; 51 | } 52 | 53 | deferred_token<> TryMakeVaccine() 54 | { 55 | 56 | printf( "Trying to find a vaccine......\n" ); 57 | 58 | single_consumer_counter_event counter(1); 59 | 60 | for(uint i = 0; i < kLabCount; i++) 61 | { 62 | float chance = random::Between( 0.01f, 0.2f ); 63 | LabDevelopVaccine(i, chance, counter).Launch(); 64 | } 65 | 66 | co_await counter; 67 | 68 | // printf( "Found a vaccine!\n"); 69 | } 70 | 71 | deferred_token<> ProduceVaccine(std::atomic& vaccineStock, bool& vaccineProductionTermniationSignal) 72 | { 73 | std::vector> factories; 74 | for(uint i = 0; i < kFactoryCount; i++) 75 | { 76 | factories.push_back(Factory( vaccineStock, vaccineProductionTermniationSignal )); 77 | } 78 | co_await parallel_for(std::move(factories)); 79 | } 80 | 81 | deferred_token<> ClinicApplyVaccine(uint& peopleNeedVaccine, std::atomic& stock, bool& vaccineProductionTermniationSignal) 82 | { 83 | while(peopleNeedVaccine > 0) 84 | { 85 | if( stock == 0 ) continue; 86 | peopleNeedVaccine--; 87 | stock--; 88 | } 89 | 90 | vaccineProductionTermniationSignal = true; 91 | co_return; 92 | } 93 | 94 | auto worldSavedString = R"( 95 | 96 | ____ __ ____ ______ .______ __ _______ _______ ___ ____ ____ _______ _______ 97 | \ \ / \ / / / __ \ | _ \ | | | \ / | / \ \ \ / / | ____|| \ 98 | \ \/ \/ / | | | | | |_) | | | | .--. | | (----` / ^ \ \ \/ / | |__ | .--. | 99 | \ / | | | | | / | | | | | | \ \ / /_\ \ \ / | __| | | | | 100 | \ /\ / | `--' | | |\ \---.| `----.| '--' | .----) | / _____ \ \ / | |____ | '--' | 101 | \__/ \__/ \______/ | _| `.____||_______||_______/ |_______/ /__/ \__\ \__/ |_______||_______/ 102 | 103 | )"; 104 | task<> ApplyImmunization(uint healthPeople) 105 | { 106 | uint i = 0; 107 | 108 | std::atomic vaccineStock = 0; 109 | bool vaccineProductionTermniationSignal = false; 110 | 111 | [&]() -> deferred_token<> 112 | { 113 | while( !vaccineProductionTermniationSignal ) 114 | { 115 | std::this_thread::sleep_for( 1s ); 116 | printf( "\n\n============== current status ================\n" ); 117 | printf( "People left: %u\n", healthPeople ); 118 | printf( "vaccine left: %u\n", vaccineStock.load() ); 119 | printf( "==============================================\n\n\n" ); 120 | } 121 | 122 | co_return; 123 | }().Launch(); 124 | 125 | std::vector> saveWorldSteps; 126 | 127 | std::vector> step2; 128 | step2.push_back( ProduceVaccine( vaccineStock, vaccineProductionTermniationSignal ) ); 129 | step2.push_back( ClinicApplyVaccine( healthPeople, vaccineStock, vaccineProductionTermniationSignal ) ); 130 | 131 | saveWorldSteps.push_back(TryMakeVaccine()); 132 | saveWorldSteps.push_back( parallel_for( std::move( step2 ) ) ); 133 | 134 | co_await sequential_for( std::move( saveWorldSteps ) ); 135 | 136 | printf( worldSavedString ); 137 | 138 | } 139 | 140 | 141 | 142 | int main() 143 | { 144 | Scheduler::Get(); 145 | uint healthyPeople = 3000; 146 | 147 | auto saveWorld = ApplyImmunization( healthyPeople ); 148 | saveWorld.Result(); 149 | 150 | printf( "\n\nDone!" ); 151 | scanf( "%d" ); 152 | return 0; 153 | } 154 | -------------------------------------------------------------------------------- /schedule/LockQueue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | template< typename T > 7 | class LockQueue 8 | { 9 | public: 10 | using size_type = typename std::queue::size_type; 11 | size_type Enqueue( const T& ele ) 12 | { 13 | std::scoped_lock lock( mAccessLock ); 14 | size_t index = mItems.size(); 15 | mItems.push_back( ele ); 16 | return index; 17 | } 18 | 19 | bool Dequeue( T& outEle ) 20 | { 21 | std::scoped_lock lock( mAccessLock ); 22 | if( mItems.empty() ) return false; 23 | 24 | outEle = std::move( mItems.front() ); 25 | mItems.pop_front(); 26 | return true; 27 | } 28 | 29 | size_type Count() const 30 | { 31 | mAccessLock.lock_shared(); 32 | size_type size = mItems.size(); 33 | mAccessLock.unlock_shared(); 34 | return size; 35 | } 36 | 37 | void FlushAndClear(std::vector& container) 38 | { 39 | std::scoped_lock lock( mAccessLock ); 40 | container.insert( container.end(), mItems.begin(), mItems.end() ); 41 | mItems.clear(); 42 | } 43 | 44 | void Lock() 45 | { 46 | mAccessLock.lock(); 47 | } 48 | 49 | void Unlock() 50 | { 51 | mAccessLock.unlock(); 52 | } 53 | 54 | protected: 55 | std::deque mItems; 56 | mutable std::shared_mutex mAccessLock; 57 | }; 58 | 59 | template< typename T > 60 | class ClosableLockQueue 61 | { 62 | public: 63 | bool Enqueue( const T& ele ) 64 | { 65 | std::scoped_lock lock( mAccessLock ); 66 | if(mIsClosed) 67 | return false; 68 | mItems.push_back( ele ); 69 | return true; 70 | } 71 | 72 | bool Enqueue( std::span eles ) 73 | { 74 | std::scoped_lock lock( mAccessLock ); 75 | if(mIsClosed) 76 | return false; 77 | mItems.insert( mItems.end(), eles.begin(), eles.end() ); 78 | return true; 79 | } 80 | 81 | void Dequeue( T& outEle ) 82 | { 83 | std::scoped_lock lock( mAccessLock ); 84 | ASSERT_DIE( !mItems.empty() ); 85 | outEle = std::move( mItems.front() ); 86 | mItems.pop_front(); 87 | } 88 | 89 | void CloseAndFlush( std::vector& container ) 90 | { 91 | Close(); 92 | container.insert( container.end(), mItems.begin(), mItems.end() ); 93 | } 94 | 95 | void Close() 96 | { 97 | std::scoped_lock lock( mAccessLock ); 98 | mIsClosed = true; 99 | } 100 | 101 | bool IsClosed() const 102 | { 103 | std::scoped_lock lock( mAccessLock ); 104 | return mIsClosed; 105 | } 106 | 107 | size_t Count() const 108 | { 109 | std::scoped_lock lock( mAccessLock ); 110 | return mItems.size(); 111 | } 112 | 113 | protected: 114 | std::deque mItems = {}; 115 | bool mIsClosed = false; 116 | mutable std::mutex mAccessLock; 117 | }; 118 | -------------------------------------------------------------------------------- /schedule/algorithms.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "token.hpp" 3 | #include 4 | 5 | #include "event.hpp" 6 | #include "task.hpp" 7 | namespace co 8 | { 9 | template 10 | co::deferred_token<> parallel_for( std::vector deferred ) 11 | { 12 | static_assert(Deferred::IsDeferred, "deferred jobs only"); 13 | 14 | single_consumer_counter_event counter(deferred.size()); 15 | 16 | auto makeTask = [&counter]( Deferred job ) -> co::token<> 17 | { 18 | co_await job; 19 | counter.decrement( 1 ); 20 | }; 21 | 22 | for(auto& d: deferred) { 23 | makeTask( std::move(d) ); 24 | } 25 | 26 | co_await counter; 27 | } 28 | 29 | template 30 | co::deferred_token<> sequential_for( std::vector deferred ) 31 | { 32 | static_assert(Deferred::IsDeferred, "deferred jobs only"); 33 | 34 | auto makeTask = []( co::token<> before, Deferred job ) -> co::token<> 35 | { 36 | co_await before; 37 | co_await job; 38 | }; 39 | 40 | co::token<> dependent; 41 | for(size_t i = 0; i < deferred.size(); ++i) { 42 | dependent = makeTask( std::move(dependent), std::move(deferred[i]) ); 43 | } 44 | 45 | co_await dependent; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /schedule/event.cpp: -------------------------------------------------------------------------------- 1 | #include "event.hpp" 2 | #include "scheduler.hpp" 3 | 4 | void co::single_consumer_counter_event::Wait() 5 | { 6 | co::Scheduler::Get().RegisterAsTempWorker( mEvent ); 7 | } -------------------------------------------------------------------------------- /schedule/event.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | 6 | #include "scheduler.hpp" 7 | #include "../utils.hpp" 8 | 9 | namespace co 10 | { 11 | // can only wait by one consumer 12 | class single_consumer_counter_event 13 | { 14 | public: 15 | struct awaitable 16 | { 17 | single_consumer_counter_event& e; 18 | 19 | awaitable( single_consumer_counter_event& e ): e( e ) {} 20 | bool await_ready() const noexcept { return e.IsReady(); } 21 | template 22 | bool await_suspend( std::coroutine_handle awaitingCoroutine ) noexcept 23 | { 24 | if( !e.IsReady() ) { 25 | e.Wait(); 26 | } 27 | Scheduler::Get().Schedule( awaitingCoroutine ); 28 | return true; 29 | } 30 | void await_resume() {} 31 | 32 | }; 33 | 34 | friend struct awaitable; 35 | 36 | single_consumer_counter_event(int targetVal): mCounter( targetVal ) {} 37 | 38 | bool IsReady() const noexcept { return mCounter.load(std::memory_order_acquire) <= 0; } 39 | 40 | void decrement(int v = 1) noexcept 41 | { 42 | if(mCounter.fetch_sub( v, std::memory_order_acq_rel ) == 1) { 43 | mEvent.Trigger(); 44 | } 45 | } 46 | 47 | void Wait(); 48 | 49 | awaitable operator co_await() noexcept { return awaitable{ *this }; }; 50 | protected: 51 | std::atomic mCounter; 52 | SysEvent mEvent; 53 | }; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /schedule/future.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "scheduler.hpp" 5 | #include "event.hpp" 6 | namespace co 7 | { 8 | 9 | // simple future type that is implemented based on OS wait object 10 | template 11 | class future 12 | { 13 | public: 14 | future(): mSetEvent( 1 ) { } 15 | void Set( T&& v ) 16 | { 17 | EXPECTS( !IsReady() ); 18 | value = v; 19 | mSetEvent.decrement(); 20 | } 21 | 22 | bool IsReady() { return mSetEvent.IsReady(); } 23 | 24 | const T& Get() const 25 | { 26 | mSetEvent.Wait(); 27 | return value; 28 | } 29 | 30 | 31 | protected: 32 | T value = {}; 33 | mutable single_consumer_counter_event mSetEvent; 34 | }; 35 | 36 | template<> 37 | class future 38 | { 39 | public: 40 | future(): mSetEvent( 1 ) { } 41 | void Set() 42 | { 43 | EXPECTS( !IsReady() ); 44 | mSetEvent.decrement(); 45 | } 46 | 47 | bool IsReady() { return mSetEvent.IsReady(); } 48 | 49 | void Get() const 50 | { 51 | mSetEvent.Wait(); 52 | } 53 | 54 | 55 | protected: 56 | mutable single_consumer_counter_event mSetEvent; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /schedule/scheduler.cpp: -------------------------------------------------------------------------------- 1 | #include "scheduler.hpp" 2 | using namespace co; 3 | 4 | static thread_local Worker* gWorkerContext = nullptr; 5 | static thread_local Scheduler* gScheduler = nullptr; 6 | static thread_local bool gIsWorker = false; 7 | static Scheduler* theScheduler = nullptr; 8 | void Scheduler::Shutdown() 9 | { 10 | mIsRunning.store( false, std::memory_order_relaxed ); 11 | } 12 | 13 | bool Scheduler::IsRunning() const 14 | { 15 | return mIsRunning.load(std::memory_order_relaxed); 16 | } 17 | 18 | Scheduler& Scheduler::Get() 19 | { 20 | if(theScheduler == nullptr) { 21 | theScheduler = new Scheduler(QuerySystemCoreCount()); 22 | } 23 | 24 | return *theScheduler; 25 | } 26 | 27 | Scheduler::~Scheduler() 28 | { 29 | for(auto& workerThread: mWorkerThreads) { 30 | workerThread.join(); 31 | } 32 | } 33 | 34 | uint Scheduler::GetThreadIndex() const 35 | { 36 | 37 | return gWorkerContext ? gWorkerContext->threadId : Worker::kMainThread; 38 | } 39 | 40 | uint Scheduler::GetMainThreadIndex() const 41 | { 42 | return Worker::kMainThread; 43 | } 44 | 45 | bool Scheduler::IsCurrentThreadWorker() const 46 | { 47 | return gIsWorker; 48 | } 49 | 50 | Scheduler::Scheduler( uint workerCount ) 51 | : mWorkerCount( workerCount ) 52 | { 53 | ASSERT_DIE( workerCount > 0 ); 54 | 55 | gWorkerContext = new Worker{ Worker::kMainThread }; 56 | mWorkerThreads.reserve( workerCount ); 57 | mWorkerContexts = std::make_unique( workerCount ); 58 | mIsRunning = true; 59 | 60 | mFreeWorkerCount = workerCount; 61 | for(uint i = 0; i < workerCount; ++i) { 62 | mWorkerThreads.emplace_back( [this, i] { WorkerThreadEntry( i ); } ); 63 | } 64 | } 65 | 66 | 67 | void Scheduler::WorkerThreadEntry( uint threadIndex ) 68 | { 69 | wchar_t name[100]; 70 | swprintf_s( name, 100, L"co worker thread %u", threadIndex ); 71 | SetThreadName( mWorkerThreads[threadIndex], name ); 72 | 73 | auto& context = mWorkerContexts[threadIndex]; 74 | context.threadId = threadIndex; 75 | gWorkerContext = &context; 76 | gScheduler = this; 77 | gIsWorker = true; 78 | while(true) { 79 | Job* op = FetchNextJob(); 80 | if(op == nullptr) { 81 | std::this_thread::yield(); 82 | } else { 83 | mFreeWorkerCount--; 84 | 85 | op->Resume(); 86 | 87 | // whatever the state the op is, release the op, the ownership of the coroutine is either finished, or transfered to somewhere else. 88 | ReleaseOp( op ); 89 | 90 | mFreeWorkerCount++; 91 | } 92 | 93 | if( !IsRunning() ) break; 94 | } 95 | gIsWorker = false; 96 | } 97 | 98 | void Scheduler::WorkerThreadEntry( const SysEvent& exitSignal ) 99 | { 100 | // this path only will run when it's blocked by something, 101 | // so instead, it will try to run something else at the same time. 102 | // In that sense, we need to first register itself as a free worker 103 | mFreeWorkerCount++; 104 | gIsWorker = true; 105 | 106 | while( true ) { 107 | Job* op = FetchNextJob(); 108 | if( op == nullptr ) { 109 | std::this_thread::yield(); 110 | } 111 | else { 112 | mFreeWorkerCount--; 113 | 114 | { 115 | op->Resume(); 116 | 117 | // op could be either suspended or done, if it's done, we will also release the coroutine frame 118 | ReleaseOp( op ); 119 | } 120 | 121 | mFreeWorkerCount++; 122 | } 123 | 124 | if( exitSignal.IsTriggered() ) break; 125 | } 126 | 127 | mFreeWorkerCount--; 128 | gIsWorker = false; 129 | 130 | } 131 | 132 | Scheduler::Job* Scheduler::FetchNextJob() 133 | { 134 | Job* op = nullptr; 135 | mJobs.Dequeue( op ); 136 | return op; 137 | } 138 | 139 | void Scheduler::EnqueueJob( Job* op ) 140 | { 141 | mJobs.Enqueue( op ); 142 | } 143 | -------------------------------------------------------------------------------- /schedule/scheduler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "LockQueue.hpp" 8 | #include "../utils.hpp" 9 | using uint = std::uint32_t; 10 | 11 | namespace co { 12 | struct Worker 13 | { 14 | static constexpr uint kMainThread = 0xff; 15 | uint threadId; 16 | }; 17 | 18 | using job_id_t = int64_t; 19 | 20 | enum class eOpState: uint 21 | { 22 | UnKnown, 23 | Created, 24 | Scheduled, 25 | Processing, 26 | Suspended, // pending reschedule 27 | Done, 28 | Canceled, 29 | }; 30 | 31 | 32 | /** 33 | * \brief All coroutine promise types should derive from this 34 | */ 35 | struct promise_base 36 | { 37 | friend class Scheduler; 38 | friend struct final_awaitable; 39 | 40 | inline static std::atomic sAllocated = 0; 41 | 42 | enum class ParentScheduleStatus : uint8_t 43 | { 44 | Closed = 0, // It's closed, cannot assign parent to it 45 | Open, // It can accept parent 46 | Assigned, // Parent is assigned 47 | }; 48 | 49 | promise_base() noexcept 50 | : mOwner( nullptr ) 51 | , mState( eOpState::Created ) 52 | , mJobId( sJobID.fetch_add( 1 ) ) 53 | , mHasParent( ParentScheduleStatus::Open ) 54 | { 55 | // sAllocated++; 56 | } 57 | 58 | struct final_awaitable 59 | { 60 | bool await_ready() { return false; } 61 | 62 | template 63 | void await_suspend( std::coroutine_handle handle ); 64 | 65 | void await_resume() {} 66 | }; 67 | 68 | ~promise_base() 69 | { 70 | // in case it is resumed in final_suspend as continuation, it's remain suspended 71 | // if it's never scheduled, it's in created state 72 | // sAllocated--; 73 | } 74 | void unhandled_exception() { ERROR_DIE( "unhandled exception in promise_base" ); } 75 | 76 | ///////////////////////////////////////////////////// 77 | /////// scheduler related api start from here /////// 78 | ///////////////////////////////////////////////////// 79 | 80 | bool Ready() const { return mState.load( std::memory_order_relaxed ) == eOpState::Done; } 81 | 82 | bool Cancel() 83 | { 84 | mState.store( eOpState::Canceled ); 85 | return true; 86 | } 87 | 88 | bool SetExecutor( Scheduler& scheduler ) 89 | { 90 | if(mOwner == nullptr) { 91 | mOwner = &scheduler; 92 | return true; 93 | } 94 | return mOwner == &scheduler; 95 | } 96 | 97 | bool IsScheduled() const { return mOwner != nullptr; } 98 | void MarkWaited() { mAwaiter.fetch_add(1, std::memory_order_release); } 99 | void UnMarkWaited() { mAwaiter.fetch_sub(1, std::memory_order_release); } 100 | bool AnyWaited() const { return mAwaiter.load(std::memory_order_acquire) > 0; } 101 | int WaiterCount() const { return mAwaiter.load( std::memory_order_acquire ); } 102 | 103 | template 104 | bool SetContinuation(const std::coroutine_handle& parent) 105 | { 106 | promise_base& parentPromise = parent.promise(); 107 | 108 | auto expectedState = eOpState::Processing; 109 | bool updated = parentPromise.SetState( expectedState, eOpState::Suspended ); 110 | ENSURES( updated || expectedState == eOpState::Suspended); 111 | 112 | mParent = parent; 113 | mScheduleParent = &ScheduleParentTyped; 114 | // Expect the status is `open`. This means it is safe to resume the parent coroutine as a continuation. 115 | // If it's not, that means it has already gone through `ScheduleParent`, which is triggered in final_suspend, so if we set parent here, it won't be resumed properly. 116 | ParentScheduleStatus oldStatus = ParentScheduleStatus::Open; 117 | bool expected = mHasParent.compare_exchange_strong(oldStatus, ParentScheduleStatus::Assigned); 118 | ENSURES(expected || (!expected && oldStatus == ParentScheduleStatus::Closed)); 119 | return expected; 120 | } 121 | 122 | bool SetState(eOpState&& expectState, eOpState newState) 123 | { 124 | return mState.compare_exchange_strong( expectState, newState, std::memory_order_release); 125 | } 126 | 127 | bool SetState(eOpState& expectState, eOpState newState) 128 | { 129 | return mState.compare_exchange_strong( expectState, newState, std::memory_order_release); 130 | } 131 | 132 | eOpState State() const { return mState.load( std::memory_order_acquire ); } 133 | void ScheduleParent() 134 | { 135 | auto status = mHasParent.exchange(ParentScheduleStatus::Closed, std::memory_order_acq_rel); 136 | ENSURES(status == ParentScheduleStatus::Assigned || status == ParentScheduleStatus::Open); 137 | if(status == ParentScheduleStatus::Assigned) { 138 | mScheduleParent( *this ); 139 | } 140 | } 141 | 142 | protected: 143 | Scheduler* mOwner = nullptr; 144 | std::atomic mAwaiter = 0; 145 | std::atomic mState; 146 | job_id_t mJobId{}; 147 | std::coroutine_handle<> mParent; 148 | std::atomic mHasParent; 149 | void(*mScheduleParent)(promise_base&); 150 | inline static std::atomic sJobID; 151 | 152 | template 153 | static void ScheduleParentTyped( promise_base& self ); 154 | }; 155 | 156 | 157 | 158 | /** 159 | * \brief Scheduler, manage workers, enqueue/dispatch jobs 160 | */ 161 | class Scheduler 162 | { 163 | public: 164 | 165 | 166 | /** 167 | * \brief Base type to describe a job 168 | */ 169 | struct Job 170 | { 171 | virtual promise_base* Promise() = 0; 172 | 173 | virtual ~Job() 174 | { 175 | if(mShouldRelease) { 176 | mCoroutine.destroy(); 177 | } 178 | 179 | } 180 | Job(const std::coroutine_handle<>& handle): mCoroutine( handle ) {} 181 | // bool RescheduleOp(Scheduler& newScheduler) 182 | // { 183 | // promise_base* promise = Promise(); 184 | // EXPECTS( promise->mState == eOpState::Suspended ); 185 | // 186 | // eOpState expectedState = eOpState::Suspended; 187 | // bool updated = promise->mState.compare_exchange_strong( expectedState, eOpState::Scheduled ); 188 | // 189 | // // TODO: this might be a time bomb 190 | // // this can be potentially troublesome because after setting the state, it's possible we fail to enqueue the job. 191 | // // but let's assume it will successful for now 192 | // newScheduler.EnqueueJob( this ); 193 | // } 194 | 195 | bool ScheduleOp(Scheduler& newScheduler) 196 | { 197 | // TODO: this might be a time bomb 198 | // this can be potentially troublesome because after setting the state, it's possible we fail to enqueue the job. 199 | // but let's assume it will successful for now 200 | newScheduler.EnqueueJob( this ); 201 | return true; 202 | } 203 | 204 | void Resume() 205 | { 206 | promise_base& promise = *Promise(); 207 | 208 | if( promise.mState == eOpState::Canceled ) return; 209 | mCoroutine.resume(); 210 | } 211 | 212 | bool Done() { return mCoroutine.done(); } 213 | 214 | std::coroutine_handle<> mCoroutine; 215 | bool mShouldRelease = false; 216 | }; 217 | 218 | 219 | /** 220 | * \brief templated job type that can access promise 221 | * \tparam P Promise Type 222 | */ 223 | template 224 | struct JobT: public Job 225 | { 226 | JobT(const std::coroutine_handle

& coro) 227 | : Job( coro ) 228 | { 229 | coro.promise().MarkWaited(); 230 | } 231 | 232 | promise_base* Promise() override 233 | { 234 | return &std::coroutine_handle

::from_address( mCoroutine.address() ).promise(); 235 | }; 236 | 237 | ~JobT() 238 | { 239 | bool shouldRelease = Promise()->WaiterCount() == 1 && mCoroutine.done(); 240 | mShouldRelease = shouldRelease; 241 | if(!shouldRelease) { 242 | Promise()->UnMarkWaited(); 243 | } 244 | } 245 | }; 246 | 247 | static Scheduler& Get(); 248 | ~Scheduler(); 249 | 250 | void Shutdown(); 251 | bool IsRunning() const; 252 | 253 | uint GetThreadIndex() const; 254 | uint GetMainThreadIndex() const; 255 | bool IsCurrentThreadWorker() const; 256 | 257 | void EnqueueJob(Job* op); 258 | 259 | size_t EstimateFreeWorkerCount() const { return mFreeWorkerCount.load(std::memory_order_relaxed); } 260 | 261 | template 262 | Job* AllocateOp(const std::coroutine_handle& handle) 263 | { 264 | return new JobT( handle ); 265 | } 266 | 267 | void ReleaseOp(Job* op) 268 | { 269 | delete op; 270 | } 271 | 272 | template 273 | void Schedule( const std::coroutine_handle& handle ) 274 | { 275 | bool assigned = handle.promise().SetExecutor( *this ); 276 | if(assigned) { 277 | Job* op = AllocateOp( handle ); 278 | op->ScheduleOp( *this ); 279 | } 280 | } 281 | 282 | void RegisterAsTempWorker( const SysEvent& exitSignal ) { WorkerThreadEntry( exitSignal ); } 283 | 284 | protected: 285 | 286 | explicit Scheduler(uint workerCount); 287 | 288 | void WorkerThreadEntry(uint threadIndex); 289 | void WorkerThreadEntry( const SysEvent& exitSignal ); 290 | Job* FetchNextJob(); 291 | 292 | ////////// data /////////// 293 | 294 | uint mWorkerCount = 0; 295 | std::vector mWorkerThreads; 296 | std::unique_ptr mWorkerContexts; 297 | std::atomic mIsRunning; 298 | LockQueue mJobs; 299 | std::atomic_size_t mFreeWorkerCount; 300 | }; 301 | 302 | template< typename Promise > void promise_base::ScheduleParentTyped( promise_base& self ) 303 | { 304 | auto parent = std::coroutine_handle::from_address( self.mParent.address() ); 305 | self.mOwner->Schedule( parent ); 306 | } 307 | 308 | template< typename Promise > void promise_base::final_awaitable::await_suspend( 309 | std::coroutine_handle handle ) 310 | { 311 | // we expect that should be derived from promise_base 312 | static_assert(std::is_base_of::value, "Promise should be derived from promise_base"); 313 | promise_base& promise = handle.promise(); 314 | promise.ScheduleParent(); 315 | promise.SetState( eOpState::Processing, eOpState::Done ); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /schedule/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "token.hpp" 3 | 4 | 5 | namespace co 6 | { 7 | // 8 | // `token` is designed as tasks that the user does not care about the result. 9 | // It does not have the overhead to deal with future object 10 | // 11 | template 12 | class meta_token: public base_token 13 | { 14 | public: 15 | using base_t = base_token; 16 | using coro_handle_t = typename base_t::coro_handle_t; 17 | 18 | meta_token() = default; 19 | meta_token( const meta_token& from ) = delete; 20 | meta_token(coro_handle_t handle): base_t(handle, nullptr) {} 21 | meta_token(meta_token&& from) noexcept: base_t(std::move(from)) {} 22 | 23 | meta_token& operator=(meta_token&& from) 24 | { 25 | std::swap(base_t::mHandle, from.mHandle); 26 | return *this; 27 | } 28 | }; 29 | 30 | template 31 | using token = meta_token; 32 | template 33 | using deferred_token = meta_token; 34 | 35 | // 36 | // For `task`, system expects user to call on task::Result() at some moment to perform a block wait 37 | // Notice since users need the result so it makes no sense to instantiate for `void` 38 | // 39 | template 40 | class meta_task: public base_token 41 | { 42 | using base_t = base_token; 43 | using coro_handle_t = typename base_t::coro_handle_t; 44 | public: 45 | meta_task() = default; 46 | meta_task( const meta_task& from ) = delete; 47 | meta_task(coro_handle_t handle): base_t(handle, &mFuture) {} 48 | 49 | meta_task(meta_task&& from) noexcept: base_t(from) 50 | { 51 | auto& promise = base_t::mHandle.promise(); 52 | promise.futuerPtr = &mFuture; 53 | } 54 | 55 | decltype(auto) Result() 56 | { 57 | return mFuture.Get(); 58 | } 59 | 60 | ~meta_task() 61 | { 62 | auto& promise = base_t::mHandle.promise(); 63 | promise.futuerPtr = nullptr; 64 | } 65 | protected: 66 | future mFuture; 67 | }; 68 | 69 | template 70 | using task = meta_task; 71 | template 72 | using deferred_task = meta_task; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /schedule/token.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "scheduler.hpp" 5 | #include "future.hpp" 6 | namespace co 7 | { 8 | 9 | template typename R, typename T> 10 | class base_token; 11 | 12 | template typename R, typename T> 13 | struct token_promise; 14 | 15 | 16 | /** 17 | * \brief This is used in `initial_suspend` to conditionally dispatch a job 18 | * \tparam Instant bool flag to differentiate whether this is a eager task or lazy task 19 | * \tparam R templated token type token, see how this is used in `token_promise` 20 | * \tparam T 21 | */ 22 | template typename R, typename T> 23 | struct token_dispatcher 24 | { 25 | using promise_t = token_promise; 26 | 27 | token_dispatcher(bool shouldSuspend):shouldSuspend( shouldSuspend ) {} 28 | bool shouldSuspend; 29 | bool await_ready() noexcept { 30 | // it will always suspend and make the decision on whether to suspend in `await_suspend` 31 | return false; 32 | } 33 | 34 | bool await_suspend(std::coroutine_handle<> handle) noexcept 35 | { 36 | // for egar task, consider to suspend it accordingly. 37 | // for lazy task, dispatch is handled on `co_await`, so here, we always consider it is scheduled 38 | using namespace std; 39 | 40 | bool scheduled; 41 | 42 | // normally this is not correct, but because this is for initial_suspend 43 | // so we know the exact type of the promise 44 | coroutine_handle realHandle = 45 | coroutine_handle::from_address( handle.address() ); 46 | mSuspendedPromise = &realHandle.promise(); 47 | ENSURES( mSuspendedPromise != nullptr ); 48 | // eOpState expectResumeFromState = (shouldSuspend || Instant) ? eOpState::Scheduled : eOpState::Suspended; 49 | 50 | realHandle.promise().SetState( eOpState::Created, mExpectResumeFromState ); 51 | if constexpr( Deferred ) { 52 | scheduled = true; 53 | } else { 54 | if( shouldSuspend ) { 55 | Scheduler::Get().Schedule( realHandle ); 56 | // printf( "\n schedule on the job system\n" ); 57 | } 58 | scheduled = shouldSuspend; 59 | } 60 | 61 | 62 | return scheduled; 63 | } 64 | 65 | void await_resume() noexcept 66 | { 67 | eOpState expectedState = mExpectResumeFromState; 68 | bool expected = mSuspendedPromise->SetState( expectedState, eOpState::Processing ); 69 | ENSURES( expected ); 70 | } 71 | 72 | protected: 73 | promise_base* mSuspendedPromise = nullptr; 74 | eOpState mExpectResumeFromState = eOpState::UnKnown; 75 | }; 76 | 77 | 78 | template typename R, typename T> 79 | struct token_promise: promise_base 80 | { 81 | friend struct token_dispatcher; 82 | 83 | // When move ctor is invoked in token, we will update this pointer, this might cause race condition. 84 | // consider someone is trying to store value to the old future object after de-ref the pointer, 85 | // and at the same time, the move happens. 86 | future* futuerPtr = nullptr; 87 | T value; 88 | 89 | auto initial_suspend() noexcept 90 | { 91 | 92 | // MSVC seems have a bug here that the promise object is initialized after the initial_suspend 93 | auto& scheduler = Scheduler::Get(); 94 | bool isWorkerThread = scheduler.IsCurrentThreadWorker(); 95 | 96 | return token_dispatcher{ !isWorkerThread }; 97 | } 98 | 99 | template< 100 | typename VALUE, 101 | typename = std::enable_if_t>> 102 | void return_value( VALUE&& v ) 103 | { 104 | value = v; 105 | if(futuerPtr) { 106 | futuerPtr->Set( std::forward(v) ); 107 | } 108 | } 109 | 110 | final_awaitable final_suspend() 111 | { 112 | return {}; 113 | } 114 | 115 | R get_return_object() noexcept; 116 | 117 | const T& result() { return value; } 118 | }; 119 | 120 | template typename R> 121 | struct token_promise: promise_base 122 | { 123 | public: 124 | friend struct token_dispatcher; 125 | future* futuerPtr = nullptr; 126 | 127 | auto initial_suspend() noexcept 128 | { 129 | // MSVC seems have a bug here that the promise object is initialized after the initial_suspend 130 | auto& scheduler = Scheduler::Get(); 131 | bool isWorkerThread = scheduler.IsCurrentThreadWorker(); 132 | 133 | return token_dispatcher( !isWorkerThread ); 134 | } 135 | 136 | final_awaitable final_suspend() { return {}; } 137 | 138 | 139 | R get_return_object() noexcept; 140 | 141 | void return_void() noexcept 142 | { 143 | if(futuerPtr) { 144 | futuerPtr->Set(); 145 | } 146 | } 147 | 148 | void unhandled_exception() noexcept { ERROR_DIE( "unhandled exception in token promsie" ); } 149 | 150 | void result() {} 151 | }; 152 | 153 | /** 154 | * \brief This mega template `base_token` class is designed so that I can quickly experiment different type of task strategy 155 | * It is a CRTP class, see how it is used in `task.hpp` 156 | * \tparam Instant 157 | * \tparam R 158 | * \tparam T 159 | */ 160 | template typename R, typename T> 161 | class base_token 162 | { 163 | public: 164 | static constexpr bool IsDeferred = Deferred; 165 | using promise_type = token_promise; 166 | using coro_handle_t = std::coroutine_handle; 167 | 168 | base_token(coro_handle_t handle, future* future = nullptr) noexcept: mHandle( handle ) 169 | { 170 | auto& promise = handle.promise(); 171 | promise.futuerPtr = future; 172 | promise.MarkWaited(); 173 | } 174 | 175 | base_token() = default; 176 | base_token(base_token&& from) noexcept: mHandle( from.mHandle ) 177 | { 178 | from.mHandle = {}; 179 | }; 180 | 181 | base_token(base_token&) noexcept = delete; 182 | 183 | ~base_token() 184 | { 185 | if( !mHandle ) return; 186 | 187 | auto& promise = mHandle.promise(); 188 | eOpState state = promise.State(); 189 | 190 | // here, we intentionally do not release wait so that we can destory the handle if we know we are the only one waiting 191 | int waiterCount = promise.WaiterCount(); 192 | if(state == eOpState::Done && waiterCount == 1) { 193 | mHandle.destroy(); 194 | return; 195 | } 196 | 197 | promise.UnMarkWaited(); 198 | } 199 | struct awaitable_base 200 | { 201 | coro_handle_t coroutine; 202 | bool requestDestroyCoroutine = false; 203 | awaitable_base( coro_handle_t coroutine ) noexcept 204 | : coroutine( coroutine ) 205 | { 206 | if(coroutine) { 207 | coroutine.promise().MarkWaited(); 208 | } 209 | } 210 | 211 | bool await_ready() const noexcept 212 | { 213 | return !coroutine || coroutine.done(); 214 | } 215 | 216 | template 217 | bool await_suspend( std::coroutine_handle awaitingCoroutine ) noexcept 218 | { 219 | return coroutine.promise().SetContinuation( awaitingCoroutine ); 220 | } 221 | 222 | ~awaitable_base() 223 | { 224 | if( !coroutine ) return; 225 | promise_base& promise = coroutine.promise(); 226 | eOpState state = promise.State(); 227 | 228 | bool shouldRelease = promise.WaiterCount() == 1; 229 | if(state == eOpState::Done && shouldRelease) { 230 | coroutine.destroy(); 231 | return; 232 | } 233 | promise.UnMarkWaited(); 234 | } 235 | }; 236 | 237 | auto operator co_await() const & noexcept 238 | { 239 | struct awaitable: awaitable_base 240 | { 241 | using awaitable_base::awaitable_base; 242 | 243 | decltype(auto) await_resume() 244 | { 245 | using ret_t = decltype(this->coroutine.promise().result()); 246 | if constexpr (std::is_void_v) { 247 | return; 248 | } else { 249 | return this->coroutine ? this->coroutine.promise().result() : T{}; 250 | } 251 | 252 | } 253 | 254 | }; 255 | 256 | if constexpr (Deferred) { 257 | Dispatch(); 258 | } 259 | 260 | promise_base& promise = mHandle.promise(); 261 | 262 | return awaitable{ mHandle }; 263 | } 264 | 265 | auto operator co_await() const && noexcept 266 | { 267 | struct awaitable: awaitable_base 268 | { 269 | using awaitable_base::awaitable_base; 270 | 271 | decltype(auto) await_resume() 272 | { 273 | auto& coro = this->coroutine; 274 | using ret_t = decltype(coro.promise().result()); 275 | if constexpr (std::is_void_v) { 276 | return; 277 | } else { 278 | return coro ? std::move(coro.promise()).result() : T{}; 279 | } 280 | 281 | } 282 | }; 283 | 284 | if constexpr (Deferred) { 285 | Dispatch(); 286 | } 287 | promise_base& promise = mHandle.promise(); 288 | 289 | return awaitable{ mHandle }; 290 | } 291 | 292 | template> 293 | void Launch() const 294 | { 295 | Dispatch(); 296 | } 297 | protected: 298 | 299 | coro_handle_t mHandle; 300 | mutable bool mScheduled = false; 301 | void Dispatch() const 302 | { 303 | if( mHandle.done() ) return; 304 | if( mScheduled ) return; 305 | Scheduler::Get().Schedule( mHandle ); 306 | mScheduled = true; 307 | } 308 | }; 309 | 310 | template typename R, typename T> 311 | R token_promise::get_return_object() noexcept 312 | { 313 | return R{ std::coroutine_handle::from_promise( *this ) }; 314 | } 315 | 316 | template typename R> 317 | R token_promise::get_return_object() noexcept 318 | { 319 | return R{ std::coroutine_handle::from_promise( *this ) }; 320 | } 321 | 322 | } 323 | -------------------------------------------------------------------------------- /span.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (c) 2015 Microsoft Corporation. All rights reserved. 4 | // 5 | // This code is licensed under the MIT License (MIT). 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 13 | // THE SOFTWARE. 14 | // 15 | /////////////////////////////////////////////////////////////////////////////// 16 | 17 | #ifndef GSL_SPAN_H 18 | #define GSL_SPAN_H 19 | 20 | #include "utils.hpp" 21 | #include // for lexicographical_compare 22 | #include // for array 23 | #include // for ptrdiff_t, size_t, nullptr_t 24 | #include // for reverse_iterator, distance, random_access_... 25 | #include 26 | #include 27 | #include // for enable_if_t, declval, is_convertible, inte... 28 | #include 29 | #include // for std::addressof 30 | 31 | #if defined(_MSC_VER) && !defined(__clang__) 32 | #pragma warning(push) 33 | 34 | // turn off some warnings that are noisy about our EXPECTS statements 35 | #pragma warning(disable : 4127) // conditional expression is constant 36 | #pragma warning(disable : 4702) // unreachable code 37 | 38 | // Turn MSVC /analyze rules that generate too much noise. TODO: fix in the tool. 39 | #pragma warning(disable : 26495) // uninitalized member when constructor calls constructor 40 | #pragma warning(disable : 26446) // parser bug does not allow attributes on some templates 41 | 42 | #if _MSC_VER < 1910 43 | #pragma push_macro("constexpr") 44 | #define constexpr /*constexpr*/ 45 | #define GSL_USE_STATIC_CONSTEXPR_WORKAROUND 46 | 47 | #endif // _MSC_VER < 1910 48 | #endif // _MSC_VER 49 | 50 | // See if we have enough C++17 power to use a static constexpr data member 51 | // without needing an out-of-line definition 52 | #if !(defined(__cplusplus) && (__cplusplus >= 201703L)) 53 | #define GSL_USE_STATIC_CONSTEXPR_WORKAROUND 54 | #endif // !(defined(__cplusplus) && (__cplusplus >= 201703L)) 55 | 56 | // GCC 7 does not like the signed unsigned missmatch (size_t ptrdiff_t) 57 | // While there is a conversion from signed to unsigned, it happens at 58 | // compiletime, so the compiler wouldn't have to warn indiscriminently, but 59 | // could check if the source value actually doesn't fit into the target type 60 | // and only warn in those cases. 61 | #if defined(__GNUC__) && __GNUC__ > 6 62 | #pragma GCC diagnostic push 63 | #pragma GCC diagnostic ignored "-Wsign-conversion" 64 | #endif 65 | 66 | namespace details 67 | { 68 | template 69 | struct is_same_signedness 70 | : public std::integral_constant::value == std::is_signed::value> 71 | { 72 | }; 73 | } // namespace details 74 | 75 | template 76 | // GSL_SUPPRESS(type.1) // NO-FORMAT: attribute 77 | constexpr T narrow_cast(U&& u) noexcept 78 | { 79 | return static_cast(std::forward(u)); 80 | } 81 | 82 | template 83 | constexpr T narrow(U u) noexcept(false) 84 | { 85 | T t = narrow_cast(u); 86 | if (static_cast(t) != u) FATAL( "Fail to perform narrow cast" ); 87 | if (!details::is_same_signedness::value && ((t < T{}) != (u < U{}))) 88 | FATAL( "Fail to perform narrow cast" ); 89 | return t; 90 | } 91 | 92 | 93 | // [views.constants], constants 94 | constexpr const std::ptrdiff_t dynamic_extent = -1; 95 | 96 | template 97 | class span; 98 | 99 | // implementation details 100 | namespace details 101 | { 102 | template 103 | struct is_span_oracle : std::false_type 104 | { 105 | }; 106 | 107 | template 108 | struct is_span_oracle> : std::true_type 109 | { 110 | }; 111 | 112 | template 113 | struct is_span : public is_span_oracle> 114 | { 115 | }; 116 | 117 | template 118 | struct is_std_array_oracle : std::false_type 119 | { 120 | }; 121 | 122 | template 123 | struct is_std_array_oracle> : std::true_type 124 | { 125 | }; 126 | 127 | template 128 | struct is_std_array : public is_std_array_oracle> 129 | { 130 | }; 131 | 132 | template 133 | struct is_allowed_extent_conversion 134 | : public std::integral_constant 136 | { 137 | }; 138 | 139 | template 140 | struct is_allowed_element_type_conversion 141 | : public std::integral_constant::value> 142 | { 143 | }; 144 | 145 | template 146 | class span_iterator 147 | { 148 | using element_type_ = typename Span::element_type; 149 | 150 | public: 151 | #ifdef _MSC_VER 152 | // Tell Microsoft standard library that span_iterators are checked. 153 | using _Unchecked_type = typename Span::pointer; 154 | #endif 155 | 156 | using iterator_category = std::random_access_iterator_tag; 157 | using value_type = std::remove_cv_t; 158 | using difference_type = typename Span::index_type; 159 | 160 | using reference = std::conditional_t&; 161 | using pointer = std::add_pointer_t; 162 | 163 | span_iterator() = default; 164 | 165 | constexpr span_iterator(const Span* span, typename Span::index_type idx) noexcept 166 | : span_(span), index_(idx) 167 | {} 168 | 169 | friend span_iterator; 170 | template * = nullptr> 171 | constexpr span_iterator(const span_iterator& other) noexcept 172 | : span_iterator(other.span_, other.index_) 173 | {} 174 | 175 | // GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute 176 | constexpr reference operator*() const 177 | { 178 | EXPECTS(index_ != span_->size()); 179 | return *(span_->data() + index_); 180 | } 181 | 182 | constexpr pointer operator->() const 183 | { 184 | EXPECTS(index_ != span_->size()); 185 | return span_->data() + index_; 186 | } 187 | 188 | constexpr span_iterator& operator++() 189 | { 190 | EXPECTS(0 <= index_ && index_ != span_->size()); 191 | ++index_; 192 | return *this; 193 | } 194 | 195 | constexpr span_iterator operator++(int) 196 | { 197 | auto ret = *this; 198 | ++(*this); 199 | return ret; 200 | } 201 | 202 | constexpr span_iterator& operator--() 203 | { 204 | EXPECTS(index_ != 0 && index_ <= span_->size()); 205 | --index_; 206 | return *this; 207 | } 208 | 209 | constexpr span_iterator operator--(int) 210 | { 211 | auto ret = *this; 212 | --(*this); 213 | return ret; 214 | } 215 | 216 | constexpr span_iterator operator+(difference_type n) const 217 | { 218 | auto ret = *this; 219 | return ret += n; 220 | } 221 | 222 | friend constexpr span_iterator operator+(difference_type n, span_iterator const& rhs) 223 | { 224 | return rhs + n; 225 | } 226 | 227 | constexpr span_iterator& operator+=(difference_type n) 228 | { 229 | EXPECTS((index_ + n) >= 0 && (index_ + n) <= span_->size()); 230 | index_ += n; 231 | return *this; 232 | } 233 | 234 | constexpr span_iterator operator-(difference_type n) const 235 | { 236 | auto ret = *this; 237 | return ret -= n; 238 | } 239 | 240 | constexpr span_iterator& operator-=(difference_type n) { return *this += -n; } 241 | 242 | constexpr difference_type operator-(span_iterator rhs) const 243 | { 244 | EXPECTS(span_ == rhs.span_); 245 | return index_ - rhs.index_; 246 | } 247 | 248 | constexpr reference operator[](difference_type n) const { return *(*this + n); } 249 | 250 | constexpr friend bool operator==(span_iterator lhs, span_iterator rhs) noexcept 251 | { 252 | return lhs.span_ == rhs.span_ && lhs.index_ == rhs.index_; 253 | } 254 | 255 | constexpr friend bool operator!=(span_iterator lhs, span_iterator rhs) noexcept 256 | { 257 | return !(lhs == rhs); 258 | } 259 | 260 | constexpr friend bool operator<(span_iterator lhs, span_iterator rhs) noexcept 261 | { 262 | return lhs.index_ < rhs.index_; 263 | } 264 | 265 | constexpr friend bool operator<=(span_iterator lhs, span_iterator rhs) noexcept 266 | { 267 | return !(rhs < lhs); 268 | } 269 | 270 | constexpr friend bool operator>(span_iterator lhs, span_iterator rhs) noexcept 271 | { 272 | return rhs < lhs; 273 | } 274 | 275 | constexpr friend bool operator>=(span_iterator lhs, span_iterator rhs) noexcept 276 | { 277 | return !(rhs > lhs); 278 | } 279 | 280 | #ifdef _MSC_VER 281 | // MSVC++ iterator debugging support; allows STL algorithms in 15.8+ 282 | // to unwrap span_iterator to a pointer type after a range check in STL 283 | // algorithm calls 284 | friend constexpr void _Verify_range(span_iterator lhs, span_iterator rhs) noexcept 285 | { // test that [lhs, rhs) forms a valid range inside an STL algorithm 286 | EXPECTS(lhs.span_ == rhs.span_ // range spans have to match 287 | && lhs.index_ <= rhs.index_); // range must not be transposed 288 | } 289 | 290 | constexpr void _Verify_offset(const difference_type n) const noexcept 291 | { // test that the iterator *this + n is a valid range in an STL 292 | // algorithm call 293 | EXPECTS((index_ + n) >= 0 && (index_ + n) <= span_->size()); 294 | } 295 | 296 | // GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute 297 | constexpr pointer _Unwrapped() const noexcept 298 | { // after seeking *this to a high water mark, or using one of the 299 | // _Verify_xxx functions above, unwrap this span_iterator to a raw 300 | // pointer 301 | return span_->data() + index_; 302 | } 303 | 304 | // Tell the STL that span_iterator should not be unwrapped if it can't 305 | // validate in advance, even in release / optimized builds: 306 | #if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND) 307 | static constexpr const bool _Unwrap_when_unverified = false; 308 | #else 309 | static constexpr bool _Unwrap_when_unverified = false; 310 | #endif 311 | // GSL_SUPPRESS(con.3) // NO-FORMAT: attribute // TODO: false positive 312 | constexpr void _Seek_to(const pointer p) noexcept 313 | { // adjust the position of *this to previously verified location p 314 | // after _Unwrapped 315 | index_ = p - span_->data(); 316 | } 317 | #endif 318 | 319 | protected: 320 | const Span* span_ = nullptr; 321 | std::ptrdiff_t index_ = 0; 322 | }; 323 | 324 | template 325 | class extent_type 326 | { 327 | public: 328 | using index_type = std::ptrdiff_t; 329 | 330 | static_assert(Ext >= 0, "A fixed-size span must be >= 0 in size."); 331 | 332 | constexpr extent_type() noexcept {} 333 | 334 | template 335 | constexpr extent_type(extent_type ext) 336 | { 337 | static_assert(Other == Ext || Other == dynamic_extent, 338 | "Mismatch between fixed-size extent and size of initializing data."); 339 | EXPECTS(ext.size() == Ext); 340 | } 341 | 342 | constexpr extent_type(index_type size) { EXPECTS(size == Ext); } 343 | 344 | constexpr index_type size() const noexcept { return Ext; } 345 | }; 346 | 347 | template <> 348 | class extent_type 349 | { 350 | public: 351 | using index_type = std::ptrdiff_t; 352 | 353 | template 354 | explicit constexpr extent_type(extent_type ext) : size_(ext.size()) 355 | {} 356 | 357 | explicit constexpr extent_type(index_type size) : size_(size) { EXPECTS(size >= 0); } 358 | 359 | constexpr index_type size() const noexcept { return size_; } 360 | 361 | private: 362 | index_type size_; 363 | }; 364 | 365 | template 366 | struct calculate_subspan_type 367 | { 368 | using type = span; 371 | }; 372 | } // namespace details 373 | 374 | // [span], class template span 375 | template 376 | class span 377 | { 378 | public: 379 | // constants and types 380 | using element_type = ElementType; 381 | using value_type = std::remove_cv_t; 382 | using index_type = std::ptrdiff_t; 383 | using pointer = element_type*; 384 | using reference = element_type&; 385 | 386 | using iterator = details::span_iterator, false>; 387 | using const_iterator = details::span_iterator, true>; 388 | using reverse_iterator = std::reverse_iterator; 389 | using const_reverse_iterator = std::reverse_iterator; 390 | 391 | using size_type = index_type; 392 | 393 | #if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND) 394 | static constexpr const index_type extent{Extent}; 395 | #else 396 | static constexpr index_type extent{Extent}; 397 | #endif 398 | 399 | // [span.cons], span constructors, copy, assignment, and destructor 400 | template " SFINAE, 402 | // since "std::enable_if_t" is ill-formed when Extent is greater than 0. 403 | class = std::enable_if_t<(Dependent || Extent <= 0)>> 404 | constexpr span() noexcept : storage_(nullptr, details::extent_type<0>()) 405 | {} 406 | 407 | constexpr span(pointer ptr, index_type count) : storage_(ptr, count) {} 408 | 409 | constexpr span(pointer firstElem, pointer lastElem) 410 | : storage_(firstElem, std::distance(firstElem, lastElem)) 411 | {} 412 | 413 | template 414 | constexpr span(element_type (&arr)[N]) noexcept 415 | : storage_(KnownNotNull{std::addressof(arr[0])}, details::extent_type()) 416 | {} 417 | 418 | template 0)>> 419 | constexpr span(std::array, N>& arr) noexcept 420 | : storage_(KnownNotNull{arr.data()}, details::extent_type()) 421 | { 422 | } 423 | 424 | constexpr span(std::array, 0>&) noexcept 425 | : storage_(static_cast(nullptr), details::extent_type<0>()) 426 | { 427 | } 428 | 429 | template 0)>> 430 | constexpr span(const std::array, N>& arr) noexcept 431 | : storage_(KnownNotNull{arr.data()}, details::extent_type()) 432 | { 433 | } 434 | 435 | constexpr span(const std::array, 0>&) noexcept 436 | : storage_(static_cast(nullptr), details::extent_type<0>()) 437 | { 438 | } 439 | 440 | // NB: the SFINAE here uses .data() as a incomplete/imperfect proxy for the requirement 441 | // on Container to be a contiguous sequence container. 442 | template ::value && !details::is_std_array::value && 445 | std::is_convertible::value && 446 | std::is_convertible().data())>::value>> 448 | constexpr span(Container& cont) : span(cont.data(), narrow(cont.size())) 449 | {} 450 | 451 | template ::value && !details::is_span::value && 454 | std::is_convertible::value && 455 | std::is_convertible().data())>::value>> 457 | constexpr span(const Container& cont) : span(cont.data(), narrow(cont.size())) 458 | {} 459 | 460 | constexpr span(const span& other) noexcept = default; 461 | 462 | template < 463 | class OtherElementType, std::ptrdiff_t OtherExtent, 464 | class = std::enable_if_t< 465 | details::is_allowed_extent_conversion::value && 466 | details::is_allowed_element_type_conversion::value>> 467 | constexpr span(const span& other) 468 | : storage_(other.data(), details::extent_type(other.size())) 469 | {} 470 | 471 | ~span() noexcept = default; 472 | constexpr span& operator=(const span& other) noexcept = default; 473 | 474 | // [span.sub], span subviews 475 | template 476 | constexpr span first() const 477 | { 478 | EXPECTS(Count >= 0 && Count <= size()); 479 | return {data(), Count}; 480 | } 481 | 482 | template 483 | // GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute 484 | constexpr span last() const 485 | { 486 | EXPECTS(Count >= 0 && size() - Count >= 0); 487 | return {data() + (size() - Count), Count}; 488 | } 489 | 490 | template 491 | // GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute 492 | constexpr auto subspan() const -> 493 | typename details::calculate_subspan_type::type 494 | { 495 | EXPECTS((Offset >= 0 && size() - Offset >= 0) && 496 | (Count == dynamic_extent || (Count >= 0 && Offset + Count <= size()))); 497 | 498 | return {data() + Offset, Count == dynamic_extent ? size() - Offset : Count}; 499 | } 500 | 501 | constexpr span first(index_type count) const 502 | { 503 | EXPECTS(count >= 0 && count <= size()); 504 | return {data(), count}; 505 | } 506 | 507 | constexpr span last(index_type count) const 508 | { 509 | return make_subspan(size() - count, dynamic_extent, subspan_selector{}); 510 | } 511 | 512 | constexpr span subspan(index_type offset, 513 | index_type count = dynamic_extent) const 514 | { 515 | return make_subspan(offset, count, subspan_selector{}); 516 | } 517 | 518 | // [span.obs], span observers 519 | constexpr index_type size() const noexcept { return storage_.size(); } 520 | constexpr index_type size_bytes() const noexcept 521 | { 522 | return size() * narrow_cast(sizeof(element_type)); 523 | } 524 | constexpr bool empty() const noexcept { return size() == 0; } 525 | 526 | // [span.elem], span element access 527 | // GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute 528 | constexpr reference operator[](index_type idx) const 529 | { 530 | EXPECTS(CheckRange(idx, storage_.size())); 531 | return data()[idx]; 532 | } 533 | 534 | constexpr reference at(index_type idx) const { return this->operator[](idx); } 535 | constexpr reference operator()(index_type idx) const { return this->operator[](idx); } 536 | constexpr pointer data() const noexcept { return storage_.data(); } 537 | 538 | // [span.iter], span iterator support 539 | constexpr iterator begin() const noexcept { return {this, 0}; } 540 | constexpr iterator end() const noexcept { return {this, size()}; } 541 | 542 | constexpr const_iterator cbegin() const noexcept { return {this, 0}; } 543 | constexpr const_iterator cend() const noexcept { return {this, size()}; } 544 | 545 | constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } 546 | constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } 547 | 548 | constexpr const_reverse_iterator crbegin() const noexcept 549 | { 550 | return const_reverse_iterator{cend()}; 551 | } 552 | constexpr const_reverse_iterator crend() const noexcept 553 | { 554 | return const_reverse_iterator{cbegin()}; 555 | } 556 | 557 | #ifdef _MSC_VER 558 | // Tell MSVC how to unwrap spans in range-based-for 559 | constexpr pointer _Unchecked_begin() const noexcept { return data(); } 560 | constexpr pointer _Unchecked_end() const noexcept 561 | { 562 | // GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute 563 | return data() + size(); 564 | } 565 | #endif // _MSC_VER 566 | 567 | private: 568 | static constexpr bool CheckRange(index_type idx, index_type size) noexcept 569 | { 570 | // Optimization: 571 | // 572 | // idx >= 0 && idx < size 573 | // => 574 | // static_cast(idx) < static_cast(size) 575 | // 576 | // because size >=0 by span construction, and negative idx will 577 | // wrap around to a value always greater than size when casted. 578 | 579 | // check if we have enough space to wrap around 580 | #if defined(__cpp_if_constexpr) 581 | if constexpr (sizeof(index_type) <= sizeof(size_t)) 582 | #else 583 | if (sizeof(index_type) <= sizeof(size_t)) 584 | #endif 585 | { 586 | return narrow_cast(idx) < narrow_cast(size); 587 | } 588 | else 589 | { 590 | return idx >= 0 && idx < size; 591 | } 592 | } 593 | 594 | // Needed to remove unnecessary null check in subspans 595 | struct KnownNotNull 596 | { 597 | pointer p; 598 | }; 599 | 600 | // this implementation detail class lets us take advantage of the 601 | // empty base class optimization to pay for only storage of a single 602 | // pointer in the case of fixed-size spans 603 | template 604 | class storage_type : public ExtentType 605 | { 606 | public: 607 | // KnownNotNull parameter is needed to remove unnecessary null check 608 | // in subspans and constructors from arrays 609 | template 610 | constexpr storage_type(KnownNotNull data, OtherExtentType ext) 611 | : ExtentType(ext), data_(data.p) 612 | { 613 | EXPECTS(ExtentType::size() >= 0); 614 | } 615 | 616 | template 617 | constexpr storage_type(pointer data, OtherExtentType ext) : ExtentType(ext), data_(data) 618 | { 619 | EXPECTS(ExtentType::size() >= 0); 620 | EXPECTS(data || ExtentType::size() == 0); 621 | } 622 | 623 | constexpr pointer data() const noexcept { return data_; } 624 | 625 | private: 626 | pointer data_; 627 | }; 628 | 629 | storage_type> storage_; 630 | 631 | // The rest is needed to remove unnecessary null check 632 | // in subspans and constructors from arrays 633 | constexpr span(KnownNotNull ptr, index_type count) : storage_(ptr, count) {} 634 | 635 | template 636 | class subspan_selector 637 | { 638 | }; 639 | 640 | template 641 | span make_subspan(index_type offset, index_type count, 642 | subspan_selector) const 643 | { 644 | const span tmp(*this); 645 | return tmp.subspan(offset, count); 646 | } 647 | 648 | // GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute 649 | span make_subspan(index_type offset, index_type count, 650 | subspan_selector) const 651 | { 652 | EXPECTS(offset >= 0 && size() - offset >= 0); 653 | 654 | if (count == dynamic_extent) { return {KnownNotNull{data() + offset}, size() - offset}; } 655 | 656 | EXPECTS(count >= 0 && size() - offset >= count); 657 | return {KnownNotNull{data() + offset}, count}; 658 | } 659 | }; 660 | 661 | #if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND) 662 | template 663 | constexpr const typename span::index_type span::extent; 664 | #endif 665 | 666 | // [span.comparison], span comparison operators 667 | template 668 | constexpr bool operator==(span l, span r) 669 | { 670 | return std::equal(l.begin(), l.end(), r.begin(), r.end()); 671 | } 672 | 673 | template 674 | constexpr bool operator!=(span l, span r) 675 | { 676 | return !(l == r); 677 | } 678 | 679 | template 680 | constexpr bool operator<(span l, span r) 681 | { 682 | return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); 683 | } 684 | 685 | template 686 | constexpr bool operator<=(span l, span r) 687 | { 688 | return !(l > r); 689 | } 690 | 691 | template 692 | constexpr bool operator>(span l, span r) 693 | { 694 | return r < l; 695 | } 696 | 697 | template 698 | constexpr bool operator>=(span l, span r) 699 | { 700 | return !(l < r); 701 | } 702 | 703 | namespace details 704 | { 705 | // if we only supported compilers with good constexpr support then 706 | // this pair of classes could collapse down to a constexpr function 707 | 708 | // we should use a narrow_cast<> to go to std::size_t, but older compilers may not see it as 709 | // constexpr 710 | // and so will fail compilation of the template 711 | template 712 | struct calculate_byte_size 713 | : std::integral_constant(sizeof(ElementType) * 715 | static_cast(Extent))> 716 | { 717 | }; 718 | 719 | template 720 | struct calculate_byte_size 721 | : std::integral_constant 722 | { 723 | }; 724 | } // namespace details 725 | 726 | using byte = unsigned char; 727 | 728 | // [span.objectrep], views of object representation 729 | template 730 | span::value> 731 | as_bytes(span s) noexcept 732 | { 733 | // GSL_SUPPRESS(type.1) // NO-FORMAT: attribute 734 | return {reinterpret_cast(s.data()), s.size_bytes()}; 735 | } 736 | 737 | template ::value>> 739 | span::value> 740 | as_writeable_bytes(span s) noexcept 741 | { 742 | // GSL_SUPPRESS(type.1) // NO-FORMAT: attribute 743 | return {reinterpret_cast(s.data()), s.size_bytes()}; 744 | } 745 | 746 | // 747 | // make_span() - Utility functions for creating spans 748 | // 749 | template 750 | constexpr span make_span(ElementType* ptr, 751 | typename span::index_type count) 752 | { 753 | return span(ptr, count); 754 | } 755 | 756 | template 757 | constexpr span make_span(ElementType* firstElem, ElementType* lastElem) 758 | { 759 | return span(firstElem, lastElem); 760 | } 761 | 762 | template 763 | constexpr span make_span(ElementType (&arr)[N]) noexcept 764 | { 765 | return span(arr); 766 | } 767 | 768 | template 769 | constexpr span make_span(Container& cont) 770 | { 771 | return span(cont); 772 | } 773 | 774 | template 775 | constexpr span make_span(const Container& cont) 776 | { 777 | return span(cont); 778 | } 779 | 780 | template 781 | constexpr span make_span(Ptr& cont, std::ptrdiff_t count) 782 | { 783 | return span(cont, count); 784 | } 785 | 786 | template 787 | constexpr span make_span(Ptr& cont) 788 | { 789 | return span(cont); 790 | } 791 | 792 | // Specialization of at for span 793 | template 794 | constexpr ElementType& at(span s, std::ptrdiff_t i) 795 | { 796 | // No bounds checking here because it is done in span::operator[] called below 797 | return s[i]; 798 | } 799 | 800 | #if defined(_MSC_VER) && !defined(__clang__) 801 | #if _MSC_VER < 1910 802 | #undef constexpr 803 | #pragma pop_macro("constexpr") 804 | 805 | #endif // _MSC_VER < 1910 806 | 807 | #pragma warning(pop) 808 | #endif // _MSC_VER 809 | 810 | #if defined(__GNUC__) && __GNUC__ > 6 811 | #pragma GCC diagnostic pop 812 | #endif // __GNUC__ > 6 813 | 814 | #endif // GSL_SPAN_H -------------------------------------------------------------------------------- /utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include "Windows.h" 5 | #include 6 | #include 7 | #include 8 | ////////////////////////////////// 9 | ///////////// MACROS ///////////// 10 | ////////////////////////////////// 11 | 12 | #define ASSERT_DIE(condition) {if(!(condition)) DebugBreak();} 13 | #define ERROR_DIE(msg) DebugBreak(); 14 | #define ENSURES(condition) ASSERT_DIE(condition) 15 | #define EXPECTS(condition) ASSERT_DIE(condition) 16 | 17 | 18 | ////////////////////////////////// 19 | ////////////// defs ////////////// 20 | ////////////////////////////////// 21 | 22 | using uint = std::uint32_t; 23 | 24 | 25 | ////////////////////////////////// 26 | /////////// functions //////////// 27 | ////////////////////////////////// 28 | 29 | inline uint QuerySystemCoreCount() 30 | { 31 | SYSTEM_INFO info; 32 | GetSystemInfo( &info ); 33 | return info.dwNumberOfProcessors; 34 | } 35 | 36 | inline void SetThreadName( std::thread& thread, const wchar_t* name ) 37 | { 38 | SetThreadDescription( thread.native_handle(), name ); 39 | } 40 | 41 | namespace random { 42 | inline float Between(float fromInclusive, float toInclusive) 43 | { 44 | static thread_local std::mt19937 generator; 45 | std::uniform_real_distribution<> distribution(fromInclusive,toInclusive); 46 | return distribution(generator); 47 | }; 48 | inline float Between01() 49 | { 50 | return Between(0.f, 1.f); 51 | }; 52 | }; 53 | 54 | 55 | ////////////////////////////////// 56 | //////////// SysEvent //////////// 57 | ////////////////////////////////// 58 | 59 | class SysEvent 60 | { 61 | public: 62 | SysEvent( bool manualReset = false ); 63 | ~SysEvent(); 64 | bool Wait(); 65 | void Trigger(); 66 | void Reset(); 67 | bool IsTriggered() const; 68 | protected: 69 | void* mHandle; 70 | bool mManualReset; 71 | std::atomic mIsTriggered; 72 | }; 73 | 74 | 75 | 76 | inline SysEvent::SysEvent( bool manualReset ) 77 | { 78 | mHandle = CreateEvent( NULL, manualReset, 0, nullptr ); 79 | ASSERT_DIE( mHandle != nullptr ); 80 | mManualReset = manualReset; 81 | } 82 | 83 | inline SysEvent::~SysEvent() 84 | { 85 | if(mHandle != nullptr) { 86 | CloseHandle( mHandle ); 87 | } 88 | } 89 | 90 | inline bool SysEvent::Wait() 91 | { 92 | ASSERT_DIE( mHandle != nullptr ); 93 | bool success = WaitForSingleObject( mHandle, INFINITE ) == WAIT_OBJECT_0; 94 | ASSERT_DIE( success ); 95 | mIsTriggered = false; 96 | return success; 97 | } 98 | 99 | inline void SysEvent::Trigger() 100 | { 101 | ASSERT_DIE( mHandle != nullptr ); 102 | BOOL ret = SetEvent( mHandle ); 103 | ASSERT_DIE( ret != 0 ); 104 | mIsTriggered = ret != 0; 105 | } 106 | inline void SysEvent::Reset() 107 | { 108 | ASSERT_DIE( mHandle != nullptr ); 109 | ResetEvent( mHandle ); 110 | mIsTriggered = false; 111 | } 112 | 113 | inline bool SysEvent::IsTriggered() const 114 | { 115 | return mIsTriggered.load(); 116 | } 117 | 118 | 119 | --------------------------------------------------------------------------------