Lots of windows fixes and some more improvements.
This commit is contained in:
parent
36a908ab8a
commit
573c431dbd
50
source/mijin/async/boxed_signal.hpp
Normal file
50
source/mijin/async/boxed_signal.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)
|
||||
#define MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED 1
|
||||
|
||||
#include "./signal.hpp"
|
||||
#include "../container/boxed_object.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
|
||||
template<template<typename> typename TAllocator, typename... TArgs>
|
||||
class BaseBoxedSignal : private BoxedObject<BaseSignal<TAllocator, TArgs...>>
|
||||
{
|
||||
private:
|
||||
using base_t = BoxedObject<BaseSignal<TAllocator, TArgs...>>;
|
||||
public:
|
||||
using base_t::construct;
|
||||
using base_t::destroy;
|
||||
using base_t::moveTo;
|
||||
|
||||
MIJIN_BOXED_PROXY_FUNC(connect)
|
||||
MIJIN_BOXED_PROXY_FUNC(disconnect)
|
||||
MIJIN_BOXED_PROXY_FUNC(emit)
|
||||
};
|
||||
|
||||
template<typename... TArgs>
|
||||
using BoxedSignal = BaseBoxedSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)
|
@ -24,6 +24,7 @@
|
||||
#include "../memory/memutil.hpp"
|
||||
#include "../util/flag.hpp"
|
||||
#include "../util/iterators.hpp"
|
||||
#include "../util/misc.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
#include "../debug/stacktrace.hpp"
|
||||
@ -269,7 +270,14 @@ struct TaskAllocatorTraits
|
||||
{
|
||||
return TAllocator<T>(taskLoop->getAllocator());
|
||||
}
|
||||
return TAllocator<T>();
|
||||
if constexpr (std::is_default_constructible_v<TAllocator<T>>)
|
||||
{
|
||||
return TAllocator<T>();
|
||||
}
|
||||
else
|
||||
{
|
||||
MIJIN_FATAL("Could not create task allocator.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -501,6 +509,17 @@ private:
|
||||
template<typename TTask, template<typename> typename TAllocator2>
|
||||
friend class WrappedTask;
|
||||
};
|
||||
template<typename T>
|
||||
struct is_task : std::false_type {};
|
||||
|
||||
template<typename TResult, template<typename...> typename TAllocator>
|
||||
struct is_task<TaskBase<TResult, TAllocator>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
inline constexpr bool is_task_v = is_task<T>::value;
|
||||
|
||||
template<typename T>
|
||||
concept task_type = is_task_v<T>;
|
||||
|
||||
template<template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
class WrappedTaskBase
|
||||
@ -573,16 +592,24 @@ public:
|
||||
MIJIN_DEFINE_FLAG(IgnoreWaiting);
|
||||
|
||||
using wrapped_task_t = WrappedTaskBase<TAllocator>;
|
||||
using wrapped_task_base_ptr_t = std::unique_ptr<wrapped_task_t, AllocatorDeleter<TAllocator<wrapped_task_t>>>;
|
||||
using wrapped_allocator_t = TAllocator<wrapped_task_t>;
|
||||
using wrapped_deleter_t = AllocatorDeleter<wrapped_allocator_t >;
|
||||
using wrapped_task_base_ptr_t = std::unique_ptr<wrapped_task_t, wrapped_deleter_t>;
|
||||
struct StoredTask
|
||||
{
|
||||
using set_future_t = std::function<void(StoredTask&)>;
|
||||
wrapped_task_base_ptr_t task;
|
||||
std::function<void(StoredTask&)> setFuture;
|
||||
set_future_t setFuture;
|
||||
std::any resultData;
|
||||
|
||||
StoredTask(wrapped_task_base_ptr_t&& task_, set_future_t&& setFuture_, std::any&& resultData_)
|
||||
: task(std::move(task_)), setFuture(std::move(setFuture_)), resultData(std::move(resultData_)) {}
|
||||
template<typename T>
|
||||
StoredTask(TAllocator<T> allocator_) : task(nullptr, wrapped_deleter_t(wrapped_allocator_t(allocator_))) {}
|
||||
};
|
||||
|
||||
using exception_handler_t = std::function<void(std::exception_ptr)>;
|
||||
using allocator_t = TAllocator<int>;
|
||||
using allocator_t = TAllocator<void>;
|
||||
protected:
|
||||
using task_vector_t = std::vector<StoredTask, TAllocator<StoredTask>>;
|
||||
|
||||
@ -661,7 +688,8 @@ private:
|
||||
std::thread::id threadId_;
|
||||
public:
|
||||
explicit BaseSimpleTaskLoop(const allocator_t& allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<allocator_t>)
|
||||
: base_t(std::move(allocator)), tasks_(TAllocator<StoredTask>(allocator_)), newTasks_(TAllocator<StoredTask>(allocator_)) {}
|
||||
: base_t(std::move(allocator)), tasks_(TAllocator<StoredTask>(allocator_)), newTasks_(TAllocator<StoredTask>(allocator_)),
|
||||
queuedTasks_(constructArray<StoredTask, MessageQueue<StoredTask>::BUFFER_SIZE>(allocator_)) {}
|
||||
public: // TaskLoop implementation
|
||||
void transferCurrentTask(TaskLoop<TAllocator>& otherLoop) MIJIN_NOEXCEPT override;
|
||||
void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT override;
|
||||
@ -778,7 +806,7 @@ FuturePtr<TResult> TaskLoop<TAllocator>::addTaskImpl(TaskBase<TResult, TAllocato
|
||||
MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!");
|
||||
task.setLoop(this);
|
||||
|
||||
auto future = std::allocate_shared<Future<TResult>>(TAllocator<Future<TResult>>(allocator_));
|
||||
FuturePtr<TResult> future = std::allocate_shared<Future<TResult>>(TAllocator<Future<TResult>>(allocator_), allocator_);
|
||||
auto setFuture = &setFutureHelper<TResult>;
|
||||
|
||||
if (outHandle != nullptr)
|
||||
@ -787,11 +815,8 @@ FuturePtr<TResult> TaskLoop<TAllocator>::addTaskImpl(TaskBase<TResult, TAllocato
|
||||
}
|
||||
|
||||
// add tasks to a seperate vector first as we might be running another task right now
|
||||
addStoredTask(StoredTask{
|
||||
.task = wrapTask(TAllocator<WrappedTask<TaskBase<TResult, TAllocator>>>(allocator_), std::move(task)),
|
||||
.setFuture = setFuture,
|
||||
.resultData = future
|
||||
});
|
||||
TAllocator<WrappedTask<TaskBase<TResult, TAllocator>>> allocator(allocator_);
|
||||
addStoredTask(StoredTask(wrapTask(std::move(allocator), std::move(task)), std::move(setFuture), std::move(future)));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace mijin
|
||||
//
|
||||
// public types
|
||||
//
|
||||
template<typename TValue>
|
||||
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
class Future;
|
||||
|
||||
// TODO: add support for mutexes and waiting for futures
|
||||
@ -57,16 +57,17 @@ struct FutureStorage<void>
|
||||
};
|
||||
} // namespace impl
|
||||
|
||||
template<typename TValue>
|
||||
template<typename TValue, template<typename> typename TAllocator>
|
||||
class Future
|
||||
{
|
||||
private:
|
||||
impl::FutureStorage<TValue> value_;
|
||||
[[no_unique_address]] impl::FutureStorage<TValue> value_;
|
||||
bool isSet_ = false;
|
||||
public:
|
||||
Future() = default;
|
||||
Future(const Future&) = delete;
|
||||
Future(Future&&) MIJIN_NOEXCEPT = default;
|
||||
explicit Future(TAllocator<void> allocator) : sigSet(std::move(allocator)) {}
|
||||
public:
|
||||
Future& operator=(const Future&) = delete;
|
||||
Future& operator=(Future&&) MIJIN_NOEXCEPT = default;
|
||||
@ -126,11 +127,11 @@ public: // modification
|
||||
}
|
||||
}
|
||||
public: // signals
|
||||
Signal<> sigSet;
|
||||
BaseSignal<TAllocator> sigSet;
|
||||
};
|
||||
|
||||
template<typename TValue>
|
||||
using FuturePtr = std::shared_ptr<Future<TValue>>;
|
||||
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
using FuturePtr = std::shared_ptr<Future<TValue, TAllocator>>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
|
@ -79,15 +79,20 @@ class MessageQueue
|
||||
public:
|
||||
using message_t = TMessage;
|
||||
using iterator_t = MessageQueueIterator<MessageQueue<TMessage, bufferSize>>;
|
||||
static constexpr std::size_t BUFFER_SIZE = bufferSize;
|
||||
private:
|
||||
std::array<TMessage, bufferSize> messages;
|
||||
mijin::BitArray<bufferSize, true> messageReady;
|
||||
std::atomic_uint writePos = 0;
|
||||
std::atomic_uint readPos = 0;
|
||||
std::array<TMessage, bufferSize> messages_;
|
||||
mijin::BitArray<bufferSize, true> messageReady_;
|
||||
std::atomic_uint writePos_ = 0;
|
||||
std::atomic_uint readPos_ = 0;
|
||||
public:
|
||||
MessageQueue() = default;
|
||||
MessageQueue(const MessageQueue&) = delete;
|
||||
MessageQueue(MessageQueue&&) = delete;
|
||||
explicit MessageQueue(const std::array<TMessage, bufferSize>& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<TMessage>)
|
||||
: messages_(messages) {}
|
||||
explicit MessageQueue(std::array<TMessage, bufferSize>&& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TMessage>)
|
||||
: messages_(std::move(messages)) {}
|
||||
|
||||
MessageQueue& operator=(const MessageQueue&) = delete;
|
||||
MessageQueue& operator=(MessageQueue&&) = delete;
|
||||
@ -118,22 +123,22 @@ struct TaskMessageQueue
|
||||
template<typename TMessage, std::size_t bufferSize>
|
||||
bool MessageQueue<TMessage, bufferSize>::tryPushMaybeMove(TMessage& message)
|
||||
{
|
||||
unsigned oldWritePos = writePos.load(std::memory_order_relaxed);
|
||||
unsigned oldWritePos = writePos_.load(std::memory_order_relaxed);
|
||||
unsigned newWritePos = 0;
|
||||
do
|
||||
{
|
||||
newWritePos = (oldWritePos + 1) % bufferSize;
|
||||
if (newWritePos == readPos) {
|
||||
if (newWritePos == readPos_) {
|
||||
return false;
|
||||
}
|
||||
} while (!writePos.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
|
||||
} while (!writePos_.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
|
||||
|
||||
while (messageReady.get(oldWritePos)) {
|
||||
while (messageReady_.get(oldWritePos)) {
|
||||
std::this_thread::yield(); // someone is still reading, wait...
|
||||
}
|
||||
|
||||
messages[oldWritePos] = std::move(message);
|
||||
messageReady.set(oldWritePos, true);
|
||||
messages_[oldWritePos] = std::move(message);
|
||||
messageReady_.set(oldWritePos, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -149,22 +154,22 @@ void MessageQueue<TMessage, bufferSize>::push(TMessage message)
|
||||
template<typename TMessage, std::size_t bufferSize>
|
||||
std::optional<TMessage> MessageQueue<TMessage, bufferSize>::tryPop()
|
||||
{
|
||||
unsigned oldReadPos = readPos.load(std::memory_order_relaxed);
|
||||
unsigned oldReadPos = readPos_.load(std::memory_order_relaxed);
|
||||
unsigned newReadPos = 0;
|
||||
do
|
||||
{
|
||||
if (oldReadPos == writePos) {
|
||||
if (oldReadPos == writePos_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
newReadPos = (oldReadPos + 1) % bufferSize;
|
||||
} while (!readPos.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed));
|
||||
} while (!readPos_.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed));
|
||||
|
||||
while (!messageReady.get(oldReadPos)) {
|
||||
while (!messageReady_.get(oldReadPos)) {
|
||||
std::this_thread::yield(); // no harm in busy-waiting here, should be fast
|
||||
};
|
||||
|
||||
TMessage message = std::move(messages[oldReadPos]);
|
||||
messageReady.set(oldReadPos, false);
|
||||
TMessage message = std::move(messages_[oldReadPos]);
|
||||
messageReady_.set(oldReadPos, false);
|
||||
return message;
|
||||
}
|
||||
|
||||
|
@ -56,12 +56,6 @@ public:
|
||||
explicit BaseSignal(TAllocator<void> allocator = {}) : handlers_(TAllocator<RegisteredHandler>(std::move(allocator))) {}
|
||||
BaseSignal(const BaseSignal&) = delete;
|
||||
BaseSignal(BaseSignal&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
void reinit(TAllocator<void> allocator = {})
|
||||
{
|
||||
MIJIN_ASSERT(handlers_.empty(), "Attempting to re-initialize a signal that already has handlers.");
|
||||
handlers_ = handler_vector_t(TAllocator<RegisteredHandler>(std::move(allocator)));
|
||||
}
|
||||
public:
|
||||
BaseSignal& operator=(const BaseSignal&) = delete;
|
||||
BaseSignal& operator=(BaseSignal&&) MIJIN_NOEXCEPT = default;
|
||||
|
@ -16,8 +16,26 @@ namespace mijin
|
||||
//
|
||||
|
||||
#if !defined(MIJIN_BOXED_OBJECT_DEBUG)
|
||||
#if defined(MIJIN_DEBUG)
|
||||
#define MIJIN_BOXED_OBJECT_DEBUG MIJIN_DEBUG
|
||||
#else
|
||||
#define MIJIN_BOXED_OBJECT_DEBUG 0
|
||||
#endif
|
||||
#endif // !defined(MIJIN_BOXED_OBJECT_DEBUG)
|
||||
|
||||
#define MIJIN_BOXED_PROXY_FUNC(funcname) \
|
||||
template<typename... TFuncArgs> \
|
||||
decltype(auto) funcname(TFuncArgs&&... args) \
|
||||
{ \
|
||||
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
|
||||
}
|
||||
|
||||
#define MIJIN_BOXED_PROXY_FUNC_CONST(funcname) \
|
||||
template<typename... TFuncArgs> \
|
||||
decltype(auto) funcname(TFuncArgs&&... args) const \
|
||||
{ \
|
||||
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
|
||||
}
|
||||
|
||||
//
|
||||
// public constants
|
||||
|
@ -100,7 +100,7 @@ Result<Stacktrace> captureStacktrace(unsigned skipFrames) MIJIN_NOEXCEPT
|
||||
return Stacktrace(std::move(btData.stackframes));
|
||||
#else // MIJIN_USE_LIBBACKTRACE
|
||||
(void) skipFrames;
|
||||
return {}; // TODO
|
||||
return ResultError("not implemented");
|
||||
#endif // MIJIN_USE_LIBBACKTRACE
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1
|
||||
|
||||
#include <flat_map>
|
||||
#include <format>
|
||||
#include <variant>
|
||||
#include "./logger.hpp"
|
||||
|
@ -208,7 +208,7 @@ inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_DEBUG;
|
||||
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_VERBOSE;
|
||||
#endif
|
||||
|
||||
#define MIJIN_DEFINE_DEFAULT_LOG_EVELS \
|
||||
#define MIJIN_DEFINE_DEFAULT_LOG_LEVELS \
|
||||
MIJIN_DEFINE_LOG_LEVEL(DEBUG, MIJIN_LOG_LEVEL_VALUE_DEBUG) \
|
||||
MIJIN_DEFINE_LOG_LEVEL(VERBOSE, MIJIN_LOG_LEVEL_VALUE_VERBOSE) \
|
||||
MIJIN_DEFINE_LOG_LEVEL(INFO, MIJIN_LOG_LEVEL_VALUE_INFO) \
|
||||
|
@ -9,10 +9,24 @@
|
||||
#include "../util/align.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
#if !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
|
||||
#if defined(MIJIN_DEBUG)
|
||||
#define MIJIN_STACK_ALLOCATOR_DEBUG 1
|
||||
#else
|
||||
#define MIJIN_STACK_ALLOCATOR_DEBUG 0
|
||||
#endif
|
||||
#endif // !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
|
||||
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
#include <print>
|
||||
#include <unordered_map>
|
||||
#include "../debug/stacktrace.hpp"
|
||||
#endif
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TValue, typename TStackAllocator>
|
||||
struct StlStackAllocator
|
||||
class StlStackAllocator
|
||||
{
|
||||
public:
|
||||
using value_type = TValue;
|
||||
@ -21,7 +35,7 @@ private:
|
||||
public:
|
||||
explicit StlStackAllocator(TStackAllocator& base) MIJIN_NOEXCEPT : base_(&base) {}
|
||||
template<typename TOtherValue>
|
||||
StlStackAllocator(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT : base_(other.base) {}
|
||||
StlStackAllocator(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT : base_(other.base_) {}
|
||||
|
||||
template<typename TOtherValue>
|
||||
StlStackAllocator& operator=(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT
|
||||
@ -35,10 +49,35 @@ public:
|
||||
[[nodiscard]]
|
||||
TValue* allocate(std::size_t count) const
|
||||
{
|
||||
return static_cast<TValue*>(base_->allocate(alignof(TValue), count * sizeof(TValue)));
|
||||
void* result = base_->allocate(alignof(TValue), count * sizeof(TValue));
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
++base_->numAllocations_;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
base_->activeAllocations_.emplace(result, captureStacktrace(1));
|
||||
#endif
|
||||
return static_cast<TValue*>(result);
|
||||
}
|
||||
|
||||
void deallocate(TValue* /* ptr */, std::size_t /* count */) const MIJIN_NOEXCEPT {}
|
||||
void deallocate([[maybe_unused]] TValue* ptr, std::size_t /* count */) const MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
MIJIN_ASSERT(base_->numAllocations_ > 0, "Unbalanced allocations in stack allocators!");
|
||||
--base_->numAllocations_;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
auto it = base_->activeAllocations_.find(ptr);
|
||||
if (it != base_->activeAllocations_.end())
|
||||
{
|
||||
base_->activeAllocations_.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
MIJIN_ERROR("Deallocating invalid pointer from StackAllocator.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename TOtherValue, typename TOtherAllocator>
|
||||
friend class StlStackAllocator;
|
||||
};
|
||||
|
||||
template<std::size_t chunkSize = 4096, template<typename> typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl<TBacking>)
|
||||
@ -59,6 +98,12 @@ private:
|
||||
};
|
||||
[[no_unique_address]] TBacking<Chunk> backing_;
|
||||
Chunk* firstChunk_ = nullptr;
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
std::size_t numAllocations_ = 0;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
// just for debugging, so we don't care what memory this uses...
|
||||
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
|
||||
#endif
|
||||
public:
|
||||
StackAllocator() MIJIN_NOEXCEPT_IF(std::is_nothrow_default_constructible_v<backing_t>) = default;
|
||||
explicit StackAllocator(backing_t backing) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TBacking<Chunk>, backing_t&&>))
|
||||
@ -146,6 +191,26 @@ public:
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
MIJIN_ASSERT(numAllocations_ == 0, "Missing deallocation in StackAllocator!");
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
if (!activeAllocations_.empty())
|
||||
{
|
||||
std::println(stderr, "{} active allocations in StackAllocator when resetting!", activeAllocations_.size());
|
||||
for (const auto& [ptr, stack] : activeAllocations_)
|
||||
{
|
||||
if (stack.isError())
|
||||
{
|
||||
std::println(stderr, "at {}, no stacktrace ({})", ptr, stack.getError().message);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println(stderr, "at 0x{}:\n{}", ptr, stack.getValue());
|
||||
}
|
||||
}
|
||||
MIJIN_TRAP();
|
||||
}
|
||||
#endif
|
||||
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
|
||||
{
|
||||
chunk->allocated = 0;
|
||||
@ -186,6 +251,9 @@ private:
|
||||
newChunk->next = firstChunk_;
|
||||
firstChunk_ = newChunk;
|
||||
}
|
||||
|
||||
template<typename TValue, typename TStackAllocator>
|
||||
friend class StlStackAllocator;
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
|
@ -44,6 +44,11 @@ public:
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
|
||||
// some compilers apparently need this since they are unable to do proper pattern matching ...
|
||||
NotNullable(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
|
||||
NotNullable(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
|
||||
|
||||
template<typename TOther> requires(std::is_constructible_v<T, const TOther&>)
|
||||
constexpr NotNullable(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, const TOther&>))
|
||||
: base_(other.base_)
|
||||
@ -70,6 +75,12 @@ public:
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
|
||||
requires(std::is_copy_constructible_v<T>)
|
||||
: base_(other.base_)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
requires(std::is_move_constructible_v<T>)
|
||||
: base_(std::exchange(other.base_, nullptr))
|
||||
@ -78,8 +89,12 @@ public:
|
||||
}
|
||||
constexpr NotNullable(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
|
||||
constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
|
||||
requires(std::is_copy_constructible_v<T>)
|
||||
// some compilers apparently need this since they are unable to do proper pattern matching ...
|
||||
NotNullable& operator=(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
|
||||
NotNullable& operator=(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
|
||||
|
||||
constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_assignable_v<T>)
|
||||
requires(std::is_copy_assignable_v<T>)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
@ -114,13 +129,13 @@ public:
|
||||
return base_ != other.base_;
|
||||
}
|
||||
|
||||
template<std::equality_comparable_with<T> TOther> requires(!std::is_same_v<TOther, std::nullptr_t>)
|
||||
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
|
||||
bool operator==(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
|
||||
{
|
||||
return base_ == other;
|
||||
}
|
||||
|
||||
template<std::equality_comparable_with<T> TOther> requires(!std::is_same_v<TOther, std::nullptr_t>)
|
||||
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
|
||||
bool operator!=(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
|
||||
{
|
||||
return base_ != other;
|
||||
|
42
source/mijin/util/misc.hpp
Normal file
42
source/mijin/util/misc.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_MISC_HPP_INCLUDED 1
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<auto V, typename T>
|
||||
constexpr decltype(auto) idValue(T&& value)
|
||||
{
|
||||
return std::forward<T>(value);
|
||||
}
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<typename T, typename... TArgs>
|
||||
struct ConstructArrayHelper
|
||||
{
|
||||
template<std::size_t... I>
|
||||
static constexpr std::array<T, sizeof...(I)> construct(const TArgs&... args, std::index_sequence<I...>)
|
||||
{
|
||||
return {idValue<I>(T(args...))...};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, std::size_t count, typename... TArgs>
|
||||
constexpr std::array<T, count> constructArray(const TArgs&... args)
|
||||
{
|
||||
return impl::ConstructArrayHelper<T, TArgs...>::construct(args..., std::make_index_sequence<count>());
|
||||
}
|
||||
}
|
||||
#endif // !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)
|
@ -145,7 +145,7 @@ std::string getExecutablePath() MIJIN_NOEXCEPT
|
||||
void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
return _aligned_alloc(size, alignment);
|
||||
return _aligned_malloc(size, alignment);
|
||||
#else
|
||||
return std::aligned_alloc(alignment, size);
|
||||
#endif
|
||||
|
@ -21,6 +21,9 @@ namespace mijin
|
||||
// public types
|
||||
//
|
||||
|
||||
struct Type_; // use as typevar
|
||||
struct Any_; // use as placeholder in templates
|
||||
|
||||
template<typename T>
|
||||
struct always_false
|
||||
{
|
||||
@ -95,11 +98,88 @@ struct map_template {
|
||||
using type_t = TTemplate<TPredicate<T>>;
|
||||
};
|
||||
|
||||
template<typename T, typename... Types>
|
||||
struct is_any_type : std::disjunction<std::is_same<T, Types>...> {};
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
struct is_template_instance : std::false_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename... TArgs>
|
||||
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<template<typename...> typename TTemplate, typename... TArgsTTmpl>
|
||||
struct tmpl_param_comparator
|
||||
{
|
||||
template<typename TTmpl, typename TInstance>
|
||||
static constexpr bool compare_single = std::is_same_v<TTmpl, TInstance> or std::is_same_v<TTmpl, Any_>;
|
||||
|
||||
template<typename T>
|
||||
struct matches : std::false_type {};
|
||||
|
||||
// original (which MSVC didn't like for some reason :/)
|
||||
// template<typename... TArgsInstance>
|
||||
// struct matches<TTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
|
||||
|
||||
template<template<typename...> typename TOtherTemplate, typename... TArgsInstance> requires(is_template_instance_v<TTemplate, TOtherTemplate<TArgsInstance...>>)
|
||||
struct matches<TOtherTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
|
||||
|
||||
template<typename T>
|
||||
using matches_t = matches<T>;
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TTemplate, typename TType>
|
||||
struct match_template_type : std::false_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType, typename... TArgs>
|
||||
struct match_template_type<TTemplate<TArgs...>, TType> : impl::tmpl_param_comparator<TTemplate, TArgs...>::template matches_t<TType> {};
|
||||
|
||||
template<typename TTemplate, typename TType>
|
||||
constexpr bool match_template_type_v = match_template_type<TTemplate, TType>::value;
|
||||
|
||||
// similar to std::is_same, but allows placeholders
|
||||
// - is_type_or_impl<some_tmpl<Type_>, some_type> resolves to some_tmpl<some_type>
|
||||
// - is_type_or_impl<some_tmpl<Any_, int>, some_type> checks if some_type is an instance of some_tmpl with int as 2nd parameter
|
||||
template<typename TMatch, typename TConcrete>
|
||||
struct type_matches
|
||||
{
|
||||
using type = std::is_same<TMatch, TConcrete>;
|
||||
};
|
||||
|
||||
template<template<typename> typename TTmplMatch, typename TConcrete>
|
||||
struct type_matches<TTmplMatch<Type_>, TConcrete>
|
||||
{
|
||||
using type = TTmplMatch<TConcrete>;
|
||||
};
|
||||
|
||||
template<template<typename...> typename TTmplMatch, typename TConcrete, typename... TArgs>
|
||||
struct type_matches<TTmplMatch<TArgs...>, TConcrete>
|
||||
{
|
||||
using type = match_template_type<TTmplMatch<TArgs...>, TConcrete>;
|
||||
};
|
||||
|
||||
template<typename TMatch, typename TConcrete>
|
||||
using type_matches_t = type_matches<TMatch, TConcrete>::type;
|
||||
|
||||
template<typename TMatch, typename TConcrete>
|
||||
inline constexpr bool type_matches_v = type_matches_t<TMatch, TConcrete>::value;
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
struct is_any_type : std::disjunction<std::is_same<TConcrete, TMatches>...> {};
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
static constexpr bool is_any_type_v = is_any_type<TConcrete, TMatches...>::value;
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
struct match_any_type : std::disjunction<type_matches_t<TMatches, TConcrete>...> {};
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
static constexpr bool match_any_type_v = match_any_type<TConcrete, TMatches...>::value;
|
||||
|
||||
template<typename T, typename... Types>
|
||||
static constexpr bool is_any_type_v = is_any_type<T, Types...>::value;
|
||||
concept union_type = match_any_type_v<T, Types...>;
|
||||
|
||||
template<typename TElement, typename TCollection>
|
||||
struct is_type_member;
|
||||
@ -139,15 +219,6 @@ struct TypeAtHelper<0, TArg, TArgs...>
|
||||
template<std::size_t I, typename... TArgs>
|
||||
using type_at_t = TypeAtHelper<I, TArgs...>::type_t;
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
struct is_template_instance : std::false_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename... TArgs>
|
||||
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
|
||||
|
||||
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
|
||||
struct detect_or
|
||||
{
|
||||
@ -192,6 +263,34 @@ decltype(auto) delayEvaluation(TType&& value)
|
||||
return static_cast<TType&&>(value);
|
||||
}
|
||||
|
||||
#define MIJIN_STATIC_TESTS 1
|
||||
#if MIJIN_STATIC_TESTS
|
||||
namespace test
|
||||
{
|
||||
template<typename T, typename U>
|
||||
struct MyTemplate {};
|
||||
|
||||
static_assert(match_template_type_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
|
||||
static_assert(match_template_type_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
|
||||
static_assert(type_matches_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
|
||||
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
|
||||
static_assert(!type_matches_v<MyTemplate<double, int>, MyTemplate<int, int>>);
|
||||
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, double>>);
|
||||
static_assert(type_matches_v<std::is_pointer<Type_>, void*>);
|
||||
|
||||
static_assert(union_type<int, int>);
|
||||
static_assert(!union_type<int>);
|
||||
static_assert(!union_type<int, double>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<int, int>>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, int>>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, Any_>>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<double, double>, MyTemplate<Any_, Any_>>);
|
||||
static_assert(!union_type<int*, int>);
|
||||
static_assert(union_type<int*, std::is_pointer<Type_>>);
|
||||
static_assert(!union_type<int, std::is_pointer<Type_>>);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)
|
||||
|
Loading…
x
Reference in New Issue
Block a user