diff --git a/Source/Shared/arcana/functional/inplace_function.h b/Source/Shared/arcana/functional/inplace_function.h index 6788623..bee4aab 100644 --- a/Source/Shared/arcana/functional/inplace_function.h +++ b/Source/Shared/arcana/functional/inplace_function.h @@ -34,36 +34,239 @@ namespace stdext { - constexpr size_t InplaceFunctionDefaultCapacity = 32; - - template - struct inplace_function_operation + namespace internals { - enum class operations_enum + constexpr size_t InplaceFunctionDefaultCapacity = 32; + + struct inplace_function_operation { - Destroy, - Copy, - Move + enum class operations_enum + { + Destroy, + Copy, + Move + }; }; - }; - template<> - struct inplace_function_operation - { - enum class operations_enum + + template + struct manage_function { - Destroy, - Move + using Operation = inplace_function_operation::operations_enum; + static void call(void* dataPtr, const void* fromPtr, Operation op) + { + FunctorT* thisFunctor = reinterpret_cast(dataPtr); + switch (op) + { + case Operation::Copy: + { + if constexpr (SupportsCopy) + { + const FunctorT* source = (const FunctorT*)const_cast(fromPtr); + new (thisFunctor) FunctorT(*source); + break; + } + else + { + assert(false && "Unexpected operation"); + } + } + case Operation::Destroy: + { + thisFunctor->~FunctorT(); + break; + } + case Operation::Move: + { + FunctorT* source = (FunctorT*)fromPtr; + new (thisFunctor) FunctorT(std::move(*source)); + break; + } + default: + { + assert(false && "Unexpected operation"); + } + } + } }; - }; + + template + struct inplace_function_impl + { + using BufferType = typename std::aligned_storage::type; + + inplace_function_impl() + : m_InvokeFctPtr(&DefaultFunction) + , m_ManagerFctPtr(nullptr) + {} + + // Converts to 'true' if assigned + explicit operator bool() const throw() + { + return m_InvokeFctPtr != &DefaultFunction; + } + + // Invokes the target + // Throws std::bad_function_call if not assigned + RetT operator()(ArgsT... args) const + { + return m_InvokeFctPtr(std::forward(args)..., data()); + } + + void swap(inplace_function_impl& other) + { + BufferType tempData; + this->move(m_Data, tempData); + other.move(other.m_Data, m_Data); + this->move(tempData, other.m_Data); + std::swap(m_InvokeFctPtr, other.m_InvokeFctPtr); + std::swap(m_ManagerFctPtr, other.m_ManagerFctPtr); + } + + void clear() + { + m_InvokeFctPtr = &DefaultFunction; + if (m_ManagerFctPtr) + m_ManagerFctPtr(data(), nullptr, Operation::Destroy); + m_ManagerFctPtr = nullptr; + } + + template + void copy(const inplace_function_impl& other) + { + static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); + static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + + if (other.m_ManagerFctPtr) + other.m_ManagerFctPtr(data(), other.data(), Operation::Copy); + + m_InvokeFctPtr = other.m_InvokeFctPtr; + m_ManagerFctPtr = other.m_ManagerFctPtr; + } + + void move(BufferType& from, BufferType& to) + { + if (m_ManagerFctPtr) + m_ManagerFctPtr(&from, &to, Operation::Move); + else + to = from; + } + + template + void move(inplace_function_impl&& other) + { + static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); + static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + static_assert(!Copyable || OtherCopyable, "Cannot move an uncopyable inplace_function into a copyable one"); + + if (other.m_ManagerFctPtr) + other.m_ManagerFctPtr(data(), other.data(), Operation::Move); + + m_InvokeFctPtr = other.m_InvokeFctPtr; + m_ManagerFctPtr = other.m_ManagerFctPtr; + + other.m_InvokeFctPtr = &DefaultFunction; + // don't reset the others management function + // because it still needs to destroy the lambda its holding. + } + + void* data() + { + return &m_Data; + } + const void* data() const + { + return &m_Data; + } + + using CompatibleFunctionPointer = RetT (*)(ArgsT...); + using InvokeFctPtrType = RetT (*)(ArgsT..., const void* thisPtr); + using Operation = internals::inplace_function_operation::operations_enum; + using ManagerFctPtrType = void (*)(void* thisPtr, const void* fromPtr, Operation); + + InvokeFctPtrType m_InvokeFctPtr; + ManagerFctPtrType m_ManagerFctPtr; + + BufferType m_Data; + + static RetT DefaultFunction(ArgsT..., const void*) + { + throw std::bad_function_call(); + } + + void set(std::nullptr_t) + { + m_ManagerFctPtr = nullptr; + m_InvokeFctPtr = &DefaultFunction; + } + + // For function pointers + void set(CompatibleFunctionPointer ptr) + { + // this is dodgy, and - according to standard - undefined behaviour. But it works + // see: http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type + m_ManagerFctPtr = nullptr; + m_InvokeFctPtr = reinterpret_cast(ptr); + } + + // Set - for functors + // enable_if makes sure this is excluded for function references and pointers. + template + typename std::enable_if::value && !std::is_function::value>::type + set(const FunctorArgT& ftor) + { + using FunctorT = typename std::remove_reference::type; + static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); + static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); + + // copy functor into the mem buffer + FunctorT* buffer = reinterpret_cast(&m_Data); + new (buffer) FunctorT(ftor); + + // generate destructor, copy-constructor and move-constructor + m_ManagerFctPtr = &manage_function::call; + + // generate entry call + m_InvokeFctPtr = &invoke; + } + + // Set - for functors + // enable_if makes sure this is excluded for function references and pointers. + template + typename std::enable_if::value && !std::is_function::value>::type + set(FunctorArgT&& ftor) + { + using FunctorT = typename std::remove_reference::type; + static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); + static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); + + // copy functor into the mem buffer + FunctorT* buffer = reinterpret_cast(&m_Data); + new (buffer) FunctorT(std::move(ftor)); + + // generate destructor, copy-constructor and move-constructor + m_ManagerFctPtr = &manage_function::call; + + // generate entry call + m_InvokeFctPtr = &invoke; + } + + template + static RetT invoke(ArgsT... args, const void* dataPtr) + { + FunctorT* functor = (FunctorT*)const_cast(dataPtr); + return (*functor)(std::forward(args)...); + } + }; + } template class /*alignas(Alignment)*/ inplace_function; - template - class /*alignas(Alignment)*/ inplace_function + template + class /*alignas(Alignment)*/ inplace_function { public: template @@ -71,16 +274,13 @@ namespace stdext // TODO create free operator overloads, to handle switched arguments - // Creates and empty inplace_function - inplace_function() - : m_InvokeFctPtr(&DefaultFunction) - , m_ManagerFctPtr(nullptr) - {} + // Creates an empty inplace_function + inplace_function() = default; // Destroys the inplace_function. If the stored callable is valid, it is destroyed also ~inplace_function() { - this->clear(); + m_impl.clear(); } // Creates an implace function, copying the target of other within the internal buffer @@ -89,9 +289,7 @@ namespace stdext template inplace_function(const CallableT& c) { - static_assert(!Copyable || std::is_copy_constructible::value, - "Cannot create a copyable inplace function from a non-copyable callable."); - this->set(c); + m_impl.set(c); } // Moves the target of an implace function, storing the callable within the internal buffer @@ -100,24 +298,21 @@ namespace stdext template::value>::type> inplace_function(CallableT&& c) { - static_assert(!Copyable || std::is_copy_constructible::value, - "Cannot create a copyable inplace function from a non-copyable callable."); - this->set(std::move(c)); + m_impl.set(std::move(c)); } // Copy construct an implace_function, storing a copy of other�s target internally // May throw any exception encountered by the constructor when copying the target object inplace_function(const inplace_function& other) { - static_assert(Copyable, "Cannot copy-construct from a non-copyable inplace function."); - this->copy(other); + m_impl.copy(other.m_impl); } // Move construct an implace_function, moving the other�s target to this inplace_function�s internal buffer // May throw any exception encountered by the constructor when moving the target object inplace_function(inplace_function&& other) { - this->move(std::move(other)); + m_impl.move(std::move(other.m_impl)); } // Allows for copying from inplace_function object of the same type, but with a smaller buffer @@ -126,7 +321,7 @@ namespace stdext template inplace_function(const inplace_function& other) { - this->copy(other); + m_impl.copy(other.m_impl); } // Allows for moving an inplace_function object of the same type, but with a smaller buffer @@ -135,16 +330,15 @@ namespace stdext template inplace_function(inplace_function&& other) { - this->move(std::move(other)); + m_impl.move(std::move(other.m_impl)); } // Assigns a copy of other�s target // May throw any exception encountered by the assignment operator when copying the target object inplace_function& operator=(const inplace_function& other) { - static_assert(Copyable, "Cannot copy-assign from a non-copyable inplace function"); - this->clear(); - this->copy(other); + m_impl.clear(); + m_impl.copy(other.m_impl); return *this; } @@ -152,8 +346,8 @@ namespace stdext // May throw any exception encountered by the assignment operator when moving the target object inplace_function& operator=(inplace_function&& other) { - this->clear(); - this->move(std::move(other)); + m_impl.clear(); + m_impl.move(std::move(other.m_impl)); return *this; } @@ -163,8 +357,8 @@ namespace stdext template inplace_function& operator=(const inplace_function& other) { - this->clear(); - this->copy(other); + m_impl.clear(); + m_impl.copy(other.m_impl); return *this; } @@ -174,8 +368,8 @@ namespace stdext template inplace_function& operator=(inplace_function&& other) { - this->clear(); - this->move(std::move(other)); + m_impl.clear(); + m_impl.move(std::move(other.m_impl)); return *this; } @@ -184,8 +378,8 @@ namespace stdext template::value>::type> inplace_function& operator=(const CallableT& target) { - this->clear(); - this->set(target); + m_impl.clear(); + m_impl.set(target); return *this; } @@ -194,8 +388,8 @@ namespace stdext template inplace_function& operator=(Callable&& target) { - this->clear(); - this->set(std::move(target)); + m_impl.clear(); + m_impl.set(std::move(target)); return *this; } @@ -216,206 +410,180 @@ namespace stdext // Converts to 'true' if assigned explicit operator bool() const throw() { - return m_InvokeFctPtr != &DefaultFunction; + return m_impl.operator bool(); } // Invokes the target // Throws std::bad_function_call if not assigned RetT operator()(ArgsT... args) const { - return m_InvokeFctPtr(std::forward(args)..., data()); + return m_impl(std::forward(args)...); } // Swaps two targets void swap(inplace_function& other) { - BufferType tempData; - this->move(m_Data, tempData); - other.move(other.m_Data, m_Data); - this->move(tempData, other.m_Data); - std::swap(m_InvokeFctPtr, other.m_InvokeFctPtr); - std::swap(m_ManagerFctPtr, other.m_ManagerFctPtr); + m_impl.swap(other.m_impl); } private: - using BufferType = typename std::aligned_storage::type; - void clear() - { - m_InvokeFctPtr = &DefaultFunction; - if (m_ManagerFctPtr) - m_ManagerFctPtr(data(), nullptr, Operation::Destroy); - m_ManagerFctPtr = nullptr; - } + using ImplT = internals::inplace_function_impl; + ImplT m_impl{}; + }; - template - void copy(const inplace_function& other) - { - static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); - static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + template + class /*alignas(Alignment)*/ inplace_function + { + public: + template + friend class inplace_function; - if (other.m_ManagerFctPtr) - other.m_ManagerFctPtr(data(), other.data(), Operation::Copy); + // TODO create free operator overloads, to handle switched arguments - m_InvokeFctPtr = other.m_InvokeFctPtr; - m_ManagerFctPtr = other.m_ManagerFctPtr; - } + // Creates an empty inplace_function + inplace_function() = default; - void move(BufferType& from, BufferType& to) + // Destroys the inplace_function. If the stored callable is valid, it is destroyed also + ~inplace_function() { - if (m_ManagerFctPtr) - m_ManagerFctPtr(&from, &to, Operation::Move); - else - to = from; + m_impl.clear(); } - template - void move(inplace_function&& other) + // Creates an implace function, copying the target of other within the internal buffer + // If the callable is larger than the internal buffer, a compile-time error is issued + // May throw any exception encountered by the constructor when copying the target object + template + inplace_function(const CallableT& c) { - static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); - static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + m_impl.set(c); + } - if (other.m_ManagerFctPtr) - other.m_ManagerFctPtr(data(), other.data(), Operation::Move); + // Moves the target of an implace function, storing the callable within the internal buffer + // If the callable is larger than the internal buffer, a compile-time error is issued + // May throw any exception encountered by the constructor when moving the target object + template::value>::type> + inplace_function(CallableT&& c) + { + m_impl.set(std::move(c)); + } - m_InvokeFctPtr = other.m_InvokeFctPtr; - m_ManagerFctPtr = other.m_ManagerFctPtr; + // Copy construction is not possible from uncopyable inplace_functions. + inplace_function(const inplace_function& other) = delete; - other.m_InvokeFctPtr = &DefaultFunction; - // don't reset the others management function - // because it still needs to destroy the lambda its holding. + // Move construct an implace_function, moving the other�s target to this inplace_function�s internal buffer + // May throw any exception encountered by the constructor when moving the target object + inplace_function(inplace_function&& other) + { + m_impl.move(std::move(other.m_impl)); } - void* data() + // Allows for copying from inplace_function object of the same type, but with a smaller buffer + // May throw any exception encountered by the constructor when copying the target object + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function(const inplace_function& other) { - return &m_Data; + m_impl.copy(other.m_impl); } - const void* data() const + + // Allows for moving an inplace_function object of the same type, but with a smaller buffer + // May throw any exception encountered by the constructor when moving the target object + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function(inplace_function&& other) { - return &m_Data; + m_impl.move(std::move(other.m_impl)); } - using CompatibleFunctionPointer = RetT (*)(ArgsT...); - using InvokeFctPtrType = RetT (*)(ArgsT..., const void* thisPtr); - using Operation = typename inplace_function_operation::operations_enum; - using ManagerFctPtrType = void (*)(void* thisPtr, const void* fromPtr, Operation); - - InvokeFctPtrType m_InvokeFctPtr; - ManagerFctPtrType m_ManagerFctPtr; - - BufferType m_Data; + // Copy assignment is not possible from uncopyable inplace_functions + inplace_function& operator=(const inplace_function& other) = delete; - static RetT DefaultFunction(ArgsT..., const void*) + // Assigns the other�s target by way of moving + // May throw any exception encountered by the assignment operator when moving the target object + inplace_function& operator=(inplace_function&& other) { - throw std::bad_function_call(); + m_impl.clear(); + m_impl.move(std::move(other.m_impl)); + return *this; } - void set(std::nullptr_t) + // Allows for copy assignment of an inplace_function object of the same type, but with a smaller buffer + // If the copy constructor of target object throws, this is left in uninitialized state + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function& operator=(const inplace_function& other) { - m_ManagerFctPtr = nullptr; - m_InvokeFctPtr = &DefaultFunction; + m_impl.clear(); + m_impl.copy(other); + return *this; } - // For function pointers - void set(CompatibleFunctionPointer ptr) + // Allows for move assignment of an inplace_function object of the same type, but with a smaller buffer + // If the move constructor of target object throws, this is left in uninitialized state + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function& operator=(inplace_function&& other) { - // this is dodgy, and - according to standard - undefined behaviour. But it works - // see: http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type - m_ManagerFctPtr = nullptr; - m_InvokeFctPtr = reinterpret_cast(ptr); + m_impl.clear(); + m_impl.move(std::move(other)); + return *this; } - // Set - for functors - // enable_if makes sure this is excluded for function references and pointers. - template - typename std::enable_if::value && !std::is_function::value>::type - set(const FunctorArgT& ftor) + // Assign a new target + // If the copy constructor of target object throws, this is left in uninitialized state + template::value>::type> + inplace_function& operator=(const CallableT& target) { - using FunctorT = typename std::remove_reference::type; - static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); - static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); - - // copy functor into the mem buffer - FunctorT* buffer = reinterpret_cast(&m_Data); - new (buffer) FunctorT(ftor); - - // generate destructor, copy-constructor and move-constructor - m_ManagerFctPtr = &manage_function::call; - - // generate entry call - m_InvokeFctPtr = &invoke; + m_impl.clear(); + m_impl.set(target); + return *this; } - // Set - for functors - // enable_if makes sure this is excluded for function references and pointers. - template - typename std::enable_if::value && !std::is_function::value>::type - set(FunctorArgT&& ftor) + // Assign a new target by way of moving + // If the move constructor of target object throws, this is left in uninitialized state + template + inplace_function& operator=(Callable&& target) { - using FunctorT = typename std::remove_reference::type; - static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); - static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); - - // copy functor into the mem buffer - FunctorT* buffer = reinterpret_cast(&m_Data); - new (buffer) FunctorT(std::move(ftor)); + m_impl.clear(); + m_impl.set(std::move(target)); + return *this; + } - // generate destructor, copy-constructor and move-constructor - m_ManagerFctPtr = &manage_function::call; + // Compares this inplace function with a null pointer + // Empty functions compare equal, non-empty functions compare unequal + bool operator==(std::nullptr_t) + { + return !operator bool(); + } - // generate entry call - m_InvokeFctPtr = &invoke; + // Compares this inplace function with a null pointer + // Empty functions compare equal, non-empty functions compare unequal + bool operator!=(std::nullptr_t) + { + return operator bool(); } - template - static RetT invoke(ArgsT... args, const void* dataPtr) + // Converts to 'true' if assigned + explicit operator bool() const throw() { - FunctorT* functor = (FunctorT*)const_cast(dataPtr); - return (*functor)(std::forward(args)...); + return m_impl.operator bool(); } - template - struct manage_function + // Invokes the target + // Throws std::bad_function_call if not assigned + RetT operator()(ArgsT... args) const { - static void call(void* dataPtr, const void* fromPtr, Operation op) - { - FunctorT* thisFunctor = reinterpret_cast(dataPtr); - switch (op) - { - case Operation::Destroy: - { - thisFunctor->~FunctorT(); - break; - } - case Operation::Move: - { - FunctorT* source = (FunctorT*)fromPtr; - new (thisFunctor) FunctorT(std::move(*source)); - break; - } - default: - { - assert(false && "Unexpected operation"); - } - } - } - }; + return m_impl(std::forward(args)...); + } - template - struct manage_function + // Swaps two targets + void swap(inplace_function& other) { - static void call(void* dataPtr, const void* fromPtr, Operation op) - { - if (op == Operation::Copy) - { - FunctorT* thisFunctor = reinterpret_cast(dataPtr); - const FunctorT* source = (const FunctorT*)const_cast(fromPtr); - new (thisFunctor) FunctorT(*source); - } - else - { - manage_function::call(dataPtr, fromPtr, op); - } - } - }; + m_impl.swap(other.m_impl); + } + + private: + using ImplT = internals::inplace_function_impl; + ImplT m_impl{}; }; } diff --git a/Source/Shared/arcana/threading/dispatcher.h b/Source/Shared/arcana/threading/dispatcher.h index fbcb4b0..080b000 100644 --- a/Source/Shared/arcana/threading/dispatcher.h +++ b/Source/Shared/arcana/threading/dispatcher.h @@ -15,7 +15,7 @@ namespace arcana class dispatcher { public: - using callback_t = stdext::inplace_function; + using callback_t = stdext::inplace_function; static constexpr size_t work_size = WorkSize; template