intial commit

This commit is contained in:
Patrick 2023-05-29 14:51:44 +02:00
commit da781b87f2
38 changed files with 4842 additions and 0 deletions

54
.gitignore vendored Normal file
View File

@ -0,0 +1,54 @@
# Compile commands
compile_commands.json
# whatever this is
.cache
# Environment setup
/.env
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Debug Info
*.pdb
# for projects that use SCons for building: http://http://www.scons.org/
.sconsign.dblite
# Byte-compiled / optimized python files
__pycache__/
*.py[cod]
# Backup files
*.bak

24
source/mijin/SConscript Normal file
View File

@ -0,0 +1,24 @@
Import('env')
mijin_sources = Split("""
async/coroutine.cpp
debug/symbol_info.cpp
io/stream.cpp
memory/data_pool.cpp
util/os.cpp
types/name.cpp
virtual_filesystem/filesystem.cpp
virtual_filesystem/stacked.cpp
""")
env.UnityStaticLibrary(
target = env['LIB_DIR'] + '/mijin_sekiei',
source = mijin_sources
)
LIB_CONFIG = {
'LIBS': ['mijin_sekiei']
}
Return('LIB_CONFIG')

View File

@ -0,0 +1,213 @@
#include "./coroutine.hpp"
#include <array>
#include <cstdio>
#include "../util/os.hpp"
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
thread_local TaskLoop::StoredTask* MultiThreadedTaskLoop::currentTask_ = nullptr;
//
// internal functions
//
void MultiThreadedTaskLoop::managerThread(std::stop_token stopToken) // NOLINT(performance-unnecessary-value-param)
{
setCurrentThreadName("Task Manager");
while (!stopToken.stop_requested())
{
// first clear out any parked tasks that are actually finished
auto it = std::remove_if(parkedTasks_.begin(), parkedTasks_.end(), [](StoredTask& task) {
return !task.task || task.task->status() == TaskStatus::FINISHED;
});
parkedTasks_.erase(it, parkedTasks_.end());
// then try to push any task from the buffer into the queue, if possible
for (auto it = parkedTasks_.begin(); it != parkedTasks_.end();)
{
if (!it->task->canResume())
{
++it;
continue;
}
if (readyTasks_.tryPushMaybeMove(*it)) {
it = parkedTasks_.erase(it);
}
else {
break;
}
}
// then clear the incoming task queue
while (true)
{
std::optional<StoredTask> task = queuedTasks_.tryPop();
if (!task.has_value()) {
break;
}
// try to directly move it into the next queue
if (readyTasks_.tryPushMaybeMove(*task)) {
continue;
}
// otherwise park it
parkedTasks_.push_back(std::move(*task));
}
// next collect tasks returning from the worker threads
while (true)
{
std::optional<StoredTask> task = returningTasks_.tryPop();
if (!task.has_value()) {
break;
}
if (task->task == nullptr || task->task->status() == TaskStatus::FINISHED) {
continue; // task has been transferred or finished
}
if (task->task->canResume() && readyTasks_.tryPushMaybeMove(*task)) {
continue; // instantly resume, no questions asked
}
// otherwise park it for future processing
parkedTasks_.push_back(std::move(*task));
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void MultiThreadedTaskLoop::workerThread(std::stop_token stopToken, std::size_t workerId) // NOLINT(performance-unnecessary-value-param)
{
currentLoopStorage() = this; // forever (on this thread)
std::array<char, 16> threadName;
(void) std::snprintf(threadName.data(), 16, "Task Worker %lu", static_cast<unsigned long>(workerId));
setCurrentThreadName(threadName.data());
while (!stopToken.stop_requested())
{
// try to fetch a task to run
std::optional<StoredTask> task = readyTasks_.tryPop();
if (!task.has_value())
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
// run it
currentTask_ = &*task;
tickTask(*task);
currentTask_ = nullptr;
// and give it back
returningTasks_.push(std::move(*task));
}
}
//
// public functions
//
void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
{
assertCorrectThread();
if (&otherLoop == this) {
return;
}
MIJIN_ASSERT_FATAL(currentTask_ != tasks_.end(), "Trying to call transferCurrentTask() while not running a task!");
// now start the transfer, first disown the task
StoredTask storedTask = std::move(*currentTask_);
currentTask_->task = nullptr; // just to be sure
// then send it over to the other loop
otherLoop.addStoredTask(std::move(storedTask));
}
void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
{
storedTask.task->setLoop(this);
if (threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id())
{
// same thread, just copy it over
if (currentLoopStorage() != nullptr) {
// currently running, can't append to tasks_ directly
newTasks_.push_back(std::move(storedTask));
}
else {
tasks_.push_back(std::move(storedTask));
}
}
else
{
// other thread, better be safe
queuedTasks_.push(std::move(storedTask));
}
}
void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
{
if (&otherLoop == this) {
return;
}
MIJIN_ASSERT_FATAL(currentTask_ != nullptr, "Trying to call transferCurrentTask() while not running a task!");
// now start the transfer, first disown the task
StoredTask storedTask = std::move(*currentTask_);
currentTask_->task = nullptr; // just to be sure
// then send it over to the other loop
otherLoop.addStoredTask(std::move(storedTask));
}
void MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
{
storedTask.task->setLoop(this);
// just assume we are not on the manager thread, as that wouldn't make sense
queuedTasks_.push(std::move(storedTask));
}
void MultiThreadedTaskLoop::start(std::size_t numWorkerThreads)
{
managerThread_ = std::jthread([this](std::stop_token stopToken) { managerThread(std::move(stopToken)); });
workerThreads_.reserve(numWorkerThreads);
for (std::size_t workerId = 0; workerId < numWorkerThreads; ++workerId) {
workerThreads_.emplace_back([this, workerId](std::stop_token stopToken) { workerThread(std::move(stopToken), workerId); });
}
}
void MultiThreadedTaskLoop::stop()
{
workerThreads_.clear(); // will also set the stop token
managerThread_ = {}; // this too
}
} // namespace mijin

View File

@ -0,0 +1,648 @@
#pragma once
#ifndef MIJIN_ASYNC_COROUTINE_HPP_INCLUDED
#define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1
#include <any>
#include <coroutine>
#include <memory>
#include <thread>
#include <tuple>
#include "./future.hpp"
#include "./message_queue.hpp"
#include "../container/optional.hpp"
#include "../util/flag.hpp"
#include "../util/traits.hpp"
namespace mijin
{
//
// public defines
//
#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO)
#define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0
#endif
//
// public types
//
enum class TaskStatus
{
SUSPENDED = 0,
RUNNING = 1,
WAITING = 2,
FINISHED = 3,
YIELDED = 4
};
// forward declarations
template<typename T>
struct TaskState;
class TaskLoop;
template<typename TResult = void>
class TaskBase;
namespace impl
{
template<typename TReturn, typename TPromise>
struct TaskReturn
{
template<typename... TArgs>
constexpr void return_value(TArgs&&... args) noexcept {
*(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::forward<TArgs>(args)...), TaskStatus::FINISHED);
}
constexpr void return_value(TReturn value) noexcept {
*(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::move(value)), TaskStatus::FINISHED);
}
};
template<typename TPromise>
struct TaskReturn<void, TPromise>
{
constexpr void return_void() noexcept {
static_cast<TPromise&>(*this).state_->status = TaskStatus::FINISHED;
}
};
} // namespace impl
template<typename T>
struct TaskState
{
Optional<T> value;
TaskStatus status = TaskStatus::SUSPENDED;
TaskState() = default;
TaskState(const TaskState&) = default;
TaskState(TaskState&&) noexcept = default;
constexpr TaskState(T _value, TaskStatus _status) noexcept : value(std::move(_value)), status(_status) {}
TaskState& operator=(const TaskState&) = default;
TaskState& operator=(TaskState&&) noexcept = default;
};
template<>
struct TaskState<void>
{
TaskStatus status = TaskStatus::SUSPENDED;
TaskState() = default;
TaskState(const TaskState&) = default;
TaskState(TaskState&&) noexcept = default;
constexpr TaskState(TaskStatus _status) noexcept : status(_status) {}
TaskState& operator=(const TaskState&) = default;
TaskState& operator=(TaskState&&) noexcept = default;
};
template<typename TValue>
struct TaskAwaitableFuture
{
FuturePtr<TValue> future;
[[nodiscard]] constexpr bool await_ready() const noexcept { return future->ready(); }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr TValue await_resume() const noexcept {
if constexpr (std::is_same_v<TValue, void>) {
return;
}
else {
return std::move(future->get());
}
}
};
template<typename... TArgs>
struct TaskAwaitableSignal
{
std::shared_ptr<std::tuple<TArgs...>> data;
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr auto& await_resume() const noexcept {
return *data;
}
};
template<typename TSingleArg>
struct TaskAwaitableSignal<TSingleArg>
{
std::shared_ptr<TSingleArg> data;
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr auto& await_resume() const noexcept {
return *data;
}
};
template<>
struct TaskAwaitableSignal<>
{
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
template<typename TTraits>
struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TTraits>>
{
using handle_t = std::coroutine_handle<TaskPromise>;
using task_t = typename TTraits::task_t;
using result_t = typename TTraits::result_t;
std::shared_ptr<TaskState<result_t>> state_ = std::make_shared<TaskState<result_t>>();
TaskLoop* loop_ = nullptr;
constexpr task_t get_return_object() noexcept { return task_t(handle_t::from_promise(*this)); }
constexpr std::suspend_always initial_suspend() noexcept { return {}; }
constexpr std::suspend_always final_suspend() noexcept { return {}; }
// template<typename TValue>
// constexpr std::suspend_always yield_value(TValue value) noexcept {
// *state_ = TaskState<result_t>(std::move(value), TaskStatus::YIELDED);
// return {};
// }
// TODO: implement yielding (can't use futures for this)
constexpr void unhandled_exception() noexcept {}
template<typename TValue>
auto await_transform(FuturePtr<TValue> future) noexcept
{
MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!");
TaskAwaitableFuture<TValue> awaitable{future};
if (!awaitable.await_ready())
{
state_->status = TaskStatus::WAITING;
future->sigSet.connect([this, future]() mutable
{
state_->status = TaskStatus::SUSPENDED;
}, Oneshot::YES);
}
return awaitable;
}
template<typename TResultOther>
auto await_transform(TaskBase<TResultOther> task) noexcept
{
MIJIN_ASSERT(loop_ != nullptr, "Cannot await another task outside of a loop!");
auto future = delayEvaluation<TResultOther>(loop_)->addTask(std::move(task)); // hackidyhack: delay evaluation of the type of loop_ as it is only forward-declared here
return await_transform(future);
}
template<typename TFirstArg, typename TSecondArg, typename... TArgs>
auto await_transform(Signal<TFirstArg, TSecondArg, TArgs...>& signal) noexcept
{
auto data = std::make_shared<std::tuple<TFirstArg, TSecondArg, TArgs...>>();
signal.connect([this, data](TFirstArg arg0, TSecondArg arg1, TArgs... args) mutable
{
*data = std::make_tuple(std::move(arg0), std::move(arg1), std::move(args)...);
state_->status = TaskStatus::SUSPENDED;
}, Oneshot::YES);
TaskAwaitableSignal<TFirstArg, TSecondArg, TArgs...> awaitable{data};
state_->status = TaskStatus::WAITING;
return awaitable;
}
template<typename TFirstArg>
auto await_transform(Signal<TFirstArg>& signal) noexcept
{
auto data = std::make_shared<TFirstArg>();
signal.connect([this, data](TFirstArg arg0) mutable
{
*data = std::move(arg0);
state_->status = TaskStatus::SUSPENDED;
}, Oneshot::YES);
TaskAwaitableSignal<TFirstArg> awaitable{data};
state_->status = TaskStatus::WAITING;
return awaitable;
}
auto await_transform(Signal<>& signal) noexcept
{
signal.connect([this]()
{
state_->status = TaskStatus::SUSPENDED;
}, Oneshot::YES);
TaskAwaitableSignal<> awaitable{};
state_->status = TaskStatus::WAITING;
return awaitable;
}
std::suspend_always await_transform(std::suspend_always) noexcept
{
state_->status = TaskStatus::SUSPENDED;
return std::suspend_always();
}
std::suspend_never await_transform(std::suspend_never) noexcept {
return std::suspend_never();
}
};
template<typename TResult>
class TaskBase
{
public:
using task_t = TaskBase;
using result_t = TResult;
struct Traits
{
using task_t = TaskBase;
using result_t = TResult;
};
public:
using promise_type = TaskPromise<Traits>;
using handle_t = typename promise_type::handle_t;
private:
handle_t handle_;
std::shared_ptr<TaskState<result_t>> state_;
public:
constexpr explicit TaskBase(handle_t handle) noexcept : handle_(handle), state_(handle.promise().state_) {}
TaskBase(const TaskBase&) = default;
TaskBase(TaskBase&& other) noexcept = default;
~TaskBase() noexcept;
public:
TaskBase& operator=(const TaskBase&) = default;
TaskBase& operator=(TaskBase&& other) noexcept = default;
[[nodiscard]]
constexpr bool operator==(const TaskBase& other) const noexcept { return handle_ == other.handle_; }
[[nodiscard]]
constexpr bool operator!=(const TaskBase& other) const noexcept { return handle_ != other.handle_; }
public:
constexpr TaskState<TResult>& resume() noexcept
{
state_->status = TaskStatus::RUNNING;
handle_.resume();
return *state_;
}
[[nodiscard]]
constexpr TaskState<TResult>& state() noexcept
{
return *state_;
}
private:
[[nodiscard]]
constexpr handle_t handle() const noexcept { return handle_; }
[[nodiscard]]
constexpr TaskLoop* getLoop() noexcept
{
return handle_.promise().loop_;
}
constexpr void setLoop(TaskLoop* loop) noexcept
{
// MIJIN_ASSERT(handle_.promise().loop_ == nullptr
// || handle_.promise().loop_ == loop
// || loop == nullptr, "Task already has a loop assigned!");
handle_.promise().loop_ = loop;
}
friend class TaskLoop;
template<typename TTask>
friend class WrappedTask;
};
class WrappedTaskBase
{
public:
virtual ~WrappedTaskBase() = default;
public:
virtual TaskStatus status() noexcept = 0;
// virtual std::any result() noexcept = 0;
virtual void resume() noexcept = 0;
virtual void* raw() noexcept = 0;
virtual std::coroutine_handle<> handle() noexcept = 0;
virtual void setLoop(TaskLoop* loop) noexcept = 0;
[[nodiscard]] inline bool canResume() {
const TaskStatus stat = status();
return (stat == TaskStatus::SUSPENDED || stat == TaskStatus::YIELDED);
}
};
template<typename TTask>
class WrappedTask : public WrappedTaskBase
{
private:
TTask task_;
public:
constexpr explicit WrappedTask(TTask&& task) noexcept : task_(std::move(task)) {}
WrappedTask(const WrappedTask&) = delete;
WrappedTask(WrappedTask&&) noexcept = default;
public:
WrappedTask& operator=(const WrappedTask&) = delete;
WrappedTask& operator=(WrappedTask&&) noexcept = default;
public:
TaskStatus status() noexcept override { return task_.state().status; }
// std::any result() noexcept override
// {
// if constexpr (std::is_same_v<typename TTask::result_t, void>) {
// return {};
// }
// else {
// return std::any(task_.state().value);
// }
// }
void resume() noexcept override { task_.resume(); }
void* raw() noexcept override { return &task_; }
std::coroutine_handle<> handle() noexcept override { return task_.handle(); }
void setLoop(TaskLoop* loop) noexcept override { task_.setLoop(loop); }
};
template<typename TTask>
std::unique_ptr<WrappedTask<TTask>> wrapTask(TTask&& task) noexcept
{
return std::make_unique<WrappedTask<TTask>>(std::forward<TTask>(task));
}
class TaskLoop
{
public:
MIJIN_DEFINE_FLAG(CanContinue);
MIJIN_DEFINE_FLAG(IgnoreWaiting);
protected:
using wrapped_task_t = WrappedTaskBase;
using wrapped_task_base_ptr_t = std::unique_ptr<wrapped_task_t>;
struct StoredTask
{
wrapped_task_base_ptr_t task;
std::function<void(StoredTask&)> setFuture;
std::any resultData;
};
using task_vector_t = std::vector<StoredTask>;
template<typename TTask>
using wrapped_task_ptr_t = std::unique_ptr<WrappedTask<TTask>>;
public:
TaskLoop() = default;
TaskLoop(const TaskLoop&) = delete;
TaskLoop(TaskLoop&&) = delete;
virtual ~TaskLoop() = default;
TaskLoop& operator=(const TaskLoop&) = delete;
TaskLoop& operator=(TaskLoop&&) = delete;
template<typename TResult>
inline FuturePtr<TResult> addTask(TaskBase<TResult> task) noexcept;
virtual void transferCurrentTask(TaskLoop& otherLoop) noexcept = 0;
virtual void addStoredTask(StoredTask&& storedTask) noexcept = 0;
[[nodiscard]] static TaskLoop& current() noexcept;
protected:
inline TaskStatus tickTask(StoredTask& task) noexcept;
protected:
static inline TaskLoop*& currentLoopStorage() noexcept;
template<typename TResult>
static inline void setFutureHelper(StoredTask& storedTask) noexcept;
};
template<typename TResult = void>
using Task = TaskBase<TResult>;
class SimpleTaskLoop : public TaskLoop
{
private:
task_vector_t tasks_;
task_vector_t newTasks_;
task_vector_t::iterator currentTask_;
MessageQueue<StoredTask> queuedTasks_;
std::thread::id threadId_;
public: // TaskLoop implementation
void transferCurrentTask(TaskLoop& otherLoop) noexcept override;
void addStoredTask(StoredTask&& storedTask) noexcept override;
public: // public interface
[[nodiscard]] constexpr bool empty() const noexcept { return tasks_.empty() && newTasks_.empty(); }
inline CanContinue tick() noexcept;
inline void runUntilDone(IgnoreWaiting ignoreWaiting = IgnoreWaiting::NO) noexcept;
private:
inline void assertCorrectThread() { MIJIN_ASSERT(threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id(), "Unsafe to TaskLoop from different thread!"); }
};
class MultiThreadedTaskLoop : public TaskLoop
{
private:
task_vector_t parkedTasks_; // buffer for tasks that don't fit into readyTasks_
MessageQueue<StoredTask> queuedTasks_; // tasks that should be appended to parked tasks
MessageQueue<StoredTask> readyTasks_; // task queue to send tasks to a worker thread
MessageQueue<StoredTask> returningTasks_; // task that have executed on a worker thread and return for further processing
std::jthread managerThread_;
std::vector<std::jthread> workerThreads_;
public: // TaskLoop implementation
void transferCurrentTask(TaskLoop& otherLoop) noexcept override;
void addStoredTask(StoredTask&& storedTask) noexcept override;
public: // public interface
void start(std::size_t numWorkerThreads);
void stop();
private: // private stuff
void managerThread(std::stop_token stopToken);
void workerThread(std::stop_token stopToken, std::size_t workerId);
static thread_local StoredTask* currentTask_;
};
//
// public functions
//
template<typename TResult>
TaskBase<TResult>::~TaskBase() noexcept
{
if (handle_)
{
// handle_.destroy();
}
}
template<typename TResult>
inline FuturePtr<TResult> TaskLoop::addTask(TaskBase<TResult> task) noexcept
{
MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!");
task.setLoop(this);
auto future = std::make_shared<Future<TResult>>();
auto setFuture = &setFutureHelper<TResult>;
// add tasks to a seperate vector first as we might be running another task right now
addStoredTask(StoredTask{
.task = wrapTask(std::move(task)),
.setFuture = setFuture,
.resultData = future
});
return future;
}
inline TaskStatus TaskLoop::tickTask(StoredTask& task) noexcept
{
TaskStatus status = {};
do
{
task.task->resume();
status = task.task ? task.task->status() : TaskStatus::WAITING; // no inner task -> task switch context (and will be removed later)
}
while (status == TaskStatus::RUNNING);
if (status == TaskStatus::YIELDED || status == TaskStatus::FINISHED)
{
task.setFuture(task);
}
return status;
}
/* static */ inline auto TaskLoop::current() noexcept -> TaskLoop&
{
MIJIN_ASSERT(currentLoopStorage() != nullptr, "Attempting to fetch current loop while no coroutine is running!");
return *currentLoopStorage();
}
/* static */ auto TaskLoop::currentLoopStorage() noexcept -> TaskLoop*&
{
static thread_local TaskLoop* storage = nullptr;
return storage;
}
template<typename TResult>
/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) noexcept
{
TaskBase<TResult>& task = *static_cast<TaskBase<TResult>*>(storedTask.task->raw());
auto future = std::any_cast<FuturePtr<TResult>>(storedTask.resultData);
if constexpr (!std::is_same_v<TResult, void>)
{
MIJIN_ASSERT(!task.state().value.empty(), "Task did not produce a value?");
future->set(std::move(task.state().value.get()));
}
else {
future->set();
}
}
inline std::suspend_always switchContext(TaskLoop& taskLoop)
{
TaskLoop& currentTaskLoop = TaskLoop::current();
if (&currentTaskLoop == &taskLoop) {
return {};
}
currentTaskLoop.transferCurrentTask(taskLoop);
return {};
}
inline auto SimpleTaskLoop::tick() noexcept -> CanContinue
{
// set current taskloop
MIJIN_ASSERT(currentLoopStorage() == nullptr, "Trying to tick a loop from a coroutine, this is not supported.");
currentLoopStorage() = this;
threadId_ = std::this_thread::get_id();
// move over all tasks from newTasks
for (StoredTask& task : newTasks_)
{
tasks_.push_back(std::move(task));
}
newTasks_.clear();
// also pick up tasks from other threads
while(true)
{
std::optional<StoredTask> task = queuedTasks_.tryPop();
if (!task.has_value()) {
break;
}
tasks_.push_back(std::move(*task));
}
// remove any tasks that are finished executing
auto it = std::remove_if(tasks_.begin(), tasks_.end(), [](StoredTask& task) {
return task.task->status() == TaskStatus::FINISHED;
});
tasks_.erase(it, tasks_.end());
CanContinue canContinue = CanContinue::NO;
// then execute all tasks that can be executed
for (currentTask_ = tasks_.begin(); currentTask_ != tasks_.end(); ++currentTask_)
{
StoredTask& task = *currentTask_;
TaskStatus status = task.task->status();
if (status != TaskStatus::SUSPENDED && status != TaskStatus::YIELDED)
{
MIJIN_ASSERT(status == TaskStatus::WAITING, "Task with invalid status in task list!");
continue;
}
status = tickTask(task);
if (status == TaskStatus::SUSPENDED || status == TaskStatus::YIELDED)
{
canContinue = CanContinue::YES;
}
}
// reset current loop
currentLoopStorage() = nullptr;
// remove any tasks that have been transferred to another queue
it = std::remove_if(tasks_.begin(), tasks_.end(), [](const StoredTask& task) {
return task.task == nullptr;
});
tasks_.erase(it, tasks_.end());
return canContinue;
}
inline void SimpleTaskLoop::runUntilDone(IgnoreWaiting ignoreWaiting) noexcept
{
while (!tasks_.empty() || !newTasks_.empty())
{
const CanContinue canContinue = tick();
if (ignoreWaiting && !canContinue)
{
break;
}
}
}
// utility stuff
inline std::suspend_always c_suspend() {
return std::suspend_always();
}
template<template<typename...> typename TCollection, typename TType, typename... TTemplateArgs>
Task<> c_allDone(const TCollection<FuturePtr<TType>, TTemplateArgs...>& futures)
{
bool allDone = true;
do
{
allDone = true;
for (const FuturePtr<TType>& future : futures)
{
if (future && !future->ready()) {
allDone = false;
break;
}
}
co_await c_suspend();
} while (!allDone);
}
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
#endif
}
#endif // MIJIN_ASYNC_COROUTINE_HPP_INCLUDED

View File

@ -0,0 +1,140 @@
#pragma once
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
#include <optional>
#include <memory>
#include <type_traits>
#include "./signal.hpp"
#include "../debug/assert.hpp"
#include "../container/optional.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename TValue>
class Future;
// TODO: add support for mutexes and waiting for futures
namespace impl
{
template<typename TValue>
struct FutureStorage
{
std::optional<TValue> value;
void setValue(TValue value_) noexcept { value = std::move(value_); }
[[nodiscard]] TValue& getValue() noexcept { return value.value(); }
};
template<typename TValue>
struct FutureStorage<TValue&>
{
std::optional<TValue*> value;
void setValue(TValue& value_) noexcept { value = &value_; }
[[nodiscard]] TValue& getValue() const noexcept { return *value.value(); }
};
template<>
struct FutureStorage<void>
{
};
} // namespace impl
template<typename TValue>
class Future
{
private:
impl::FutureStorage<TValue> value_;
bool isSet_ = false;
public:
Future() = default;
Future(const Future&) = delete;
Future(Future&&) noexcept = default;
public:
Future& operator=(const Future&) = delete;
Future& operator=(Future&&) noexcept = default;
[[nodiscard]]
constexpr explicit operator bool() const noexcept { return ready(); }
[[nodiscard]]
constexpr bool operator!() const noexcept { return !ready(); }
public: // access
[[nodiscard]]
constexpr decltype(auto) get() noexcept
{
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) {
return;
}
else {
return value_.getValue();
}
}
[[nodiscard]]
constexpr decltype(auto) get() const noexcept
{
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) {
return;
}
else {
return value_.getValue();
}
}
[[nodiscard]]
constexpr bool ready() const noexcept
{
return isSet_;
}
public: // modification
template<typename TArg> requires (!std::is_same_v<TValue, void>)
constexpr void set(TArg&& value) noexcept
{
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
value_.setValue(std::move(value));
isSet_ = true;
sigSet.emit();
}
constexpr void set() noexcept
{
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
isSet_ = true;
if constexpr (std::is_same_v<TValue, void>) {
sigSet.emit();
}
else {
// would love to make this a compile-time error :/
MIJIN_ERROR("Attempting to call set(void) on future with value.");
}
}
public: // signals
Signal<> sigSet;
};
template<typename TValue>
using FuturePtr = std::shared_ptr<Future<TValue>>;
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)

View File

@ -0,0 +1,132 @@
#pragma once
#if !defined(MIJIN_MESSAGE_QUEUE_HPP_INCLUDED)
#define MIJIN_MESSAGE_QUEUE_HPP_INCLUDED 1
#include <array>
#include <atomic>
#include <optional>
#include <thread>
#include "../util/bitarray.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename TMessage, std::size_t bufferSize = 32>
class MessageQueue
{
private:
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&&) noexcept = delete;
MessageQueue& operator=(const MessageQueue&) = delete;
MessageQueue& operator=(MessageQueue&&) noexcept = delete;
[[nodiscard]] bool tryPushMaybeMove(TMessage& message);
[[nodiscard]] bool tryPush(TMessage message) {
return tryPushMaybeMove(message);
}
void push(TMessage message);
[[nodiscard]] std::optional<TMessage> tryPop();
[[nodiscard]] TMessage wait();
};
template<typename TRequest, typename TResponse, std::size_t requestBufferSize = 32, std::size_t responseBufferSize = 32>
struct TaskMessageQueue
{
MessageQueue<TRequest, requestBufferSize> requests;
MessageQueue<TResponse, responseBufferSize> responses;
};
//
// public functions
//
template<typename TMessage, std::size_t bufferSize>
bool MessageQueue<TMessage, bufferSize>::tryPushMaybeMove(TMessage& message)
{
unsigned oldWritePos = writePos.load(std::memory_order_relaxed);
unsigned newWritePos = 0;
do
{
newWritePos = (oldWritePos + 1) % bufferSize;
if (newWritePos == readPos) {
return false;
}
} while (!writePos.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
while (messageReady.get(oldWritePos)) {
std::this_thread::yield(); // someone is still reading, wait...
}
messages[oldWritePos] = std::move(message);
messageReady.set(oldWritePos, true);
return true;
}
template<typename TMessage, std::size_t bufferSize>
void MessageQueue<TMessage, bufferSize>::push(TMessage message)
{
while (!tryPushMaybeMove(message)) {
std::this_thread::yield();
}
}
template<typename TMessage, std::size_t bufferSize>
std::optional<TMessage> MessageQueue<TMessage, bufferSize>::tryPop()
{
unsigned oldReadPos = readPos.load(std::memory_order_relaxed);
unsigned newReadPos = 0;
do
{
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 (!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);
return message;
}
template<typename TMessage, std::size_t bufferSize>
TMessage MessageQueue<TMessage, bufferSize>::wait()
{
while (true)
{
std::optional<TMessage> message = tryPop();
if (message.has_value()) {
return message.value();
}
std::this_thread::yield();
}
}
} // namespace mijin
#endif // !defined(MIJIN_MESSAGE_QUEUE_HPP_INCLUDED)

View File

@ -0,0 +1,126 @@
#pragma once
#if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED)
#define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <vector>
#include "../util/flag.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
MIJIN_DEFINE_FLAG(Oneshot);
template<typename... TArgs>
class Signal
{
public:
using handler_t = std::function<void(TArgs...)>;
using token_t = std::uint32_t;
private:
struct RegisteredHandler
{
handler_t callable;
std::weak_ptr<void> referenced;
token_t token;
Oneshot oneshot = Oneshot::NO;
};
using handler_vector_t = std::vector<RegisteredHandler>;
private:
handler_vector_t handlers_;
token_t nextToken = 1;
std::mutex handlersMutex_;
public:
Signal() = default;
Signal(const Signal&) = delete;
Signal(Signal&&) noexcept = default;
public:
Signal& operator=(const Signal&) = delete;
Signal& operator=(Signal&&) noexcept = default;
public:
template<typename THandler, typename TWeak = void>
inline token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) noexcept;
inline void disconnect(token_t token) noexcept;
inline void emit(TArgs&&... args) noexcept;
};
//
// public functions
//
template<typename... TArgs>
template<typename THandler, typename TWeak>
inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) noexcept -> token_t
{
std::lock_guard lock(handlersMutex_);
auto callable = handler_t(handler);
handlers_.push_back({
.callable = std::move(callable),
.referenced = std::move(referenced),
.token = nextToken,
.oneshot = oneshot
});
return nextToken++;
}
template<typename... TArgs>
inline void Signal<TArgs...>::disconnect(token_t token) noexcept
{
std::lock_guard lock(handlersMutex_);
auto it = std::remove_if(handlers_.begin(), handlers_.end(), [token](const RegisteredHandler& handler)
{
return handler.token == token;
});
handlers_.erase(it, handlers_.end());
}
template<typename... TArgs>
inline void Signal<TArgs...>::emit(TArgs&&... args) noexcept
{
std::lock_guard lock(handlersMutex_);
// first erase any handlers with expired references
// auto it = std::remove_if(handlers_.begin(), handlers_.end(), [](const RegisteredHandler& handler)
// {
// return handler.referenced.expired();
// });
// handlers_.erase(it, handlers_.end());
// TODO: this doesn't really work since expired() also returns true if the pointer was never set
// invoke all handlers
for (RegisteredHandler& handler : handlers_)
{
handler.callable(forward<TArgs>(args)...);
}
// remove any oneshot
auto it = std::remove_if(handlers_.begin(), handlers_.end(), [](const RegisteredHandler& handler)
{
return handler.oneshot;
});
handlers_.erase(it, handlers_.end());
}
} // namespace mijin
#endif // !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED)

View File

@ -0,0 +1,237 @@
#pragma once
#if !defined(MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED)
#define MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED 1
#include <iterator>
#include <memory>
#include "../debug/assert.hpp"
namespace mijin
{
//
// public defines
//
#if !defined(MIJIN_BOXED_OBJECT_DEBUG)
#define MIJIN_BOXED_OBJECT_DEBUG MIJIN_DEBUG
#endif
//
// public constants
//
//
// public types
//
template<typename T>
class BoxedObject
{
private:
union {
T object_;
};
#if MIJIN_BOXED_OBJECT_DEBUG
bool constructed = false;
#endif
public:
BoxedObject() = default;
explicit BoxedObject(T object)
#if MIJIN_BOXED_OBJECT_DEBUG
: constructed(true)
#endif
{
std::construct_at(&object_, std::move(object));
}
BoxedObject(const BoxedObject&) = delete;
BoxedObject(BoxedObject&&) = delete;
#if MIJIN_BOXED_OBJECT_DEBUG
~BoxedObject()
{
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroy prior to destructor!")
}
#endif
BoxedObject& operator=(const BoxedObject&) = delete;
BoxedObject& operator=(BoxedObject&&) = delete;
template<typename... TArgs>
void construct(TArgs&&... args)
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(!constructed, "BoxedObject::construct(): Attempt to construct an already constructed object!")
constructed = true;
#endif
std::construct_at(&object_, std::forward<TArgs>(args)...);
}
void destroy()
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::destroy(): Attempt to destroy a not constructed object!")
constructed = false;
#endif
std::destroy_at(&object_);
}
void copyTo(BoxedObject& other) const
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!")
#endif
other.construct(object_);
}
void moveTo(BoxedObject& other)
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!")
#endif
other.construct(std::move(object_));
destroy();
}
[[nodiscard]] T& get()
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!")
#endif
return object_;
}
[[nodiscard]] const T& get() const
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!")
#endif
return object_;
}
};
template<typename T>
class BoxedObjectIterator
{
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
private:
BoxedObject<T>* box_ = nullptr;
#if MIJIN_CHECKED_ITERATORS
BoxedObject<T>* start_ = nullptr;
BoxedObject<T>* end_ = nullptr;
#endif
public:
BoxedObjectIterator() = default;
explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* end)
: box_(box)
#if MIJIN_CHECKED_ITERATORS
, start_(start), end_(end)
#endif
{}
BoxedObjectIterator(const BoxedObjectIterator&) = default;
BoxedObjectIterator(BoxedObjectIterator&&) noexcept = default;
BoxedObjectIterator& operator=(const BoxedObjectIterator&) = default;
BoxedObjectIterator& operator=(BoxedObjectIterator&&) noexcept = default;
BoxedObjectIterator& operator+=(difference_type diff);
BoxedObjectIterator& operator-=(difference_type diff) { return (*this += -diff); }
constexpr auto operator<=>(const BoxedObjectIterator& other) const noexcept = default;
[[nodiscard]] T& operator*() const;
[[nodiscard]] T* operator->() const;
BoxedObjectIterator& operator++();
BoxedObjectIterator operator++(int);
BoxedObjectIterator& operator--();
BoxedObjectIterator operator--(int);
[[nodiscard]] difference_type operator-(const BoxedObjectIterator& other) const { return box_ - other.box_; }
[[nodiscard]] BoxedObjectIterator operator+(difference_type diff) const;
[[nodiscard]] BoxedObjectIterator operator-(difference_type diff) const { return (*this + -diff); }
[[nodiscard]] T& operator[](difference_type diff) const { return *(*this + diff); }
};
template<typename T>
inline BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) {
return iter + diff;
}
static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
//
// public functions
//
template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff)
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(diff <= (end_ - box_) && diff >= (box_ - start_), "BoxedObjectIterator::operator+=(): Attempt to iterate out of range.");
#endif
box_ += diff;
return *this;
}
template<typename T>
T& BoxedObjectIterator<T>::operator*() const
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ >= start_ && box < end_, "BoxedObjectIterator::operator->(): Attempt to dereference out-of-range iterator.");
#endif
return box_->get();
}
template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++()
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end.");
#endif
++box_;
}
template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int)
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(int): Attempt to iterator past the end.");
#endif
BoxedObjectIterator copy(*this);
++box_;
return copy;
}
template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--()
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start.");
#endif
--box_;
}
template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int)
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(int): Attempt to iterator past start.");
#endif
BoxedObjectIterator copy(*this);
--box_;
return copy;
}
template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const
{
BoxedObjectIterator copy(*this);
copy += diff;
return copy;
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED)

View File

@ -0,0 +1,145 @@
#pragma once
#if !defined(MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED)
#define MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED 1
#include <algorithm>
#include <array>
#include <cstddef>
#include <limits>
#include <optional>
#include <vector>
#include "../debug/assert.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename T, std::size_t cacheSize = 32>
class CachedArray
{
public:
using element_type = T;
using value_type = std::remove_cv_t<T>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = T*;
using const_iterator = const T*;
static constexpr std::size_t INVALID_INDEX = std::numeric_limits<size_type>::max();
private:
struct CacheEntry
{
size_type index = INVALID_INDEX;
std::size_t useIndex = 0;
std::optional<T> value;
};
mutable std::array<CacheEntry, cacheSize> cache_;
std::vector<std::optional<T>> backbuffer_;
std::size_t nextUseIndex_ = 1;
public:
[[nodiscard]] reference at(size_type index);
[[nodiscard]] const_reference at(size_type index) const;
[[nodiscard]] size_type size() const { return backbuffer_.size(); }
void resize(size_type newSize);
void reserve(size_type elements);
// TODO: make iterable (not that easy if we move from the backbuffer!)
// [[nodiscard]] iterator begin() { return &backbuffer_[0]; }
// [[nodiscard]] const_iterator begin() const { return &backbuffer_[0]; }
// [[nodiscard]] const_iterator cbegin() const { return &backbuffer_[0]; }
// [[nodiscard]] iterator end() { return begin() + size(); }
// [[nodiscard]] const_iterator end() const { return begin() + size(); }
// [[nodiscard]] const_iterator cend() const { return begin() + size(); }
[[nodiscard]] reference operator[](size_type index) { return at(index); }
[[nodiscard]] const_reference operator[](size_type index) const { return at(index); }
private:
// TODO: use deducing this once it is available
template<typename TSelf>
static auto atImpl(TSelf&& self, std::size_t index);
};
//
// public functions
//
template<typename T, std::size_t cacheSize>
auto CachedArray<T, cacheSize>::at(std::size_t index) -> reference
{
return atImpl(*this, index);
}
template<typename T, std::size_t cacheSize>
auto CachedArray<T, cacheSize>::at(std::size_t index) const -> const_reference
{
return atImpl(*this, index);
}
template<typename T, std::size_t cacheSize>
void CachedArray<T, cacheSize>::resize(size_type newSize)
{
if (newSize < size())
{
// invalidate any cache entries that shouldn't exist anymore
for (CacheEntry& entry : cache_)
{
if (entry.index < newSize)
{
entry.value = std::nullopt;
entry.useIndex = 0;
}
}
}
backbuffer_.resize(newSize);
}
template<typename T, std::size_t cacheSize>
template<typename TSelf>
auto CachedArray<T, cacheSize>::atImpl(TSelf&& self, std::size_t index)
{
MIJIN_ASSERT(index < size(), "CachedArray::at(): Attempting to access index out of range.");
// first try the cache
for (CacheEntry& entry : self.cache_)
{
if (entry.index == index)
{
entry.useIndex = self.nextUseIndex_++;
return *entry.value;
}
}
// otherwise copy from backbuffer to cache
auto itCache = std::min_element(self.cache_.begin(), self.cache_.end(), [](const CacheEntry& left, const CacheEntry& right) {
return left.useIndex < right.useIndex;
});
if (itCache->index != INVALID_INDEX)
{
// move back to the backbuffer
self.backbuffer_[itCache->index] = std::move(itCache->value);
}
itCache->index = index;
itCache->value = std::move(self.backbuffer_.at(index));
itCache->useIndex = self.nextUseIndex_++;
return *itCache->value;
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED)

View File

@ -0,0 +1,133 @@
#pragma once
#if !defined(MIJIN_CONTAINER_INLINE_ARRAY_HPP_INCLUDED)
#define MIJIN_CONTAINER_INLINE_ARRAY_HPP_INCLUDED 1
#include "./boxed_object.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename T, std::size_t maxSize>
class InlineArray
{
public:
using element_type = T;
using value_type = std::remove_cv_t<T>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = BoxedObjectIterator<T>;
using const_iterator = BoxedObjectIterator<const T>;
private:
BoxedObject<T> elements_[maxSize]; // NOLINT(modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
size_type size_ = 0;
public:
InlineArray() = default;
InlineArray(const InlineArray& other);
InlineArray(InlineArray&& other) noexcept;
~InlineArray();
reference operator[](size_type index);
const_reference operator[](size_type index) const;
[[nodiscard]] iterator begin() { return BoxedObjectIterator<T>(&elements_[0], &elements_[0], &elements_[size_]); }
[[nodiscard]] const_iterator begin() const { return BoxedObjectIterator<const T>(&elements_[0], &elements_[0], &elements_[size_]); }
[[nodiscard]] const_iterator cbegin() const { return BoxedObjectIterator<const T>(&elements_[0], &elements_[0], &elements_[size_]); }
[[nodiscard]] iterator end() { return BoxedObjectIterator<T>(&elements_[size_], &elements_[0], &elements_[size_]); }
[[nodiscard]] const_iterator end() const { return BoxedObjectIterator<const T>(&elements_[size_], &elements_[0], &elements_[size_]); }
[[nodiscard]] const_iterator cend() const { return BoxedObjectIterator<const T>(&elements_[size_], &elements_[0], &elements_[size_]); }
[[nodiscard]] constexpr size_type size() const { return size_; }
[[nodiscard]] constexpr size_type capacity() const { return maxSize; }
[[nodiscard]] constexpr bool empty() const { return size_ == 0; }
[[nodiscard]] constexpr reference at(size_type index) {
MIJIN_ASSERT(index < size_, "InlineArray::at() attempt to access out-of-bounds index.");
return elements_[index].get();
}
[[nodiscard]] constexpr const_reference at(size_type index) const {
MIJIN_ASSERT(index < size_, "InlineArray::at() attempt to access out-of-bounds index.");
return elements_[index].get();
}
void resize(size_type newSize);
void clear() { resize(0); }
};
//
// public functions
//
template<typename T, std::size_t numElements>
InlineArray<T, numElements>::InlineArray(const InlineArray& other) : size_(other.size_)
{
for (size_type idx = 0; idx < size_; ++idx) {
other.elements_[idx].copyTo(elements_[idx]);
}
}
template<typename T, std::size_t numElements>
InlineArray<T, numElements>::InlineArray(InlineArray&& other) noexcept : size_(other.size_)
{
for (size_type idx = 0; idx < size_; ++idx) {
other.elements_[idx].moveTo(elements_[idx]);
}
other.size_ = 0;
}
template<typename T, std::size_t numElements>
InlineArray<T, numElements>::~InlineArray()
{
for (std::size_t idx = 0; idx < size_; ++idx) {
elements_[idx].destroy();
}
}
template<typename T, std::size_t numElements>
auto InlineArray<T, numElements>::operator[](size_type index) -> reference
{
return at(index);
}
template<typename T, std::size_t numElements>
auto InlineArray<T, numElements>::operator[](size_type index) const -> const_reference
{
return at(index);
}
template<typename T, std::size_t numElements>
void InlineArray<T, numElements>::resize(size_type newSize)
{
MIJIN_ASSERT(newSize <= numElements, "InlineArray::resize(): Attempt to resize beyond maximum size.");
if (newSize < size_) {
for (size_type idx = newSize; idx < size_; ++idx) {
elements_[idx].destroy();
}
}
else {
for (size_type idx = size_; idx < newSize; ++idx) {
elements_[idx].construct();
}
}
size_ = newSize;
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_INLINE_ARRAY_HPP_INCLUDED)

View File

@ -0,0 +1,90 @@
#pragma once
#if !defined(MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED)
#define MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED 1
#include <unordered_map>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename TKey, typename TValue, typename TMap = std::unordered_map<TKey, TValue>>
struct MapView
{
private:
TMap& map;
public:
MapView() = delete;
MapView(const MapView&) = default;
MapView(MapView&&) noexcept = default;
inline MapView(TMap& map_) : map(map_) {}
MapView& operator=(const MapView&) = default;
MapView& operator=(MapView&&) noexcept = default;
[[nodiscard]] TValue& operator[](const TKey& key)
{
return at(key);
}
[[nodiscard]] const TValue& operator[](const TKey& key) const
{
return at(key);
}
[[nodiscard]] TValue& at(const TKey& key)
{
return map.at(key);
}
[[nodiscard]] const TValue& at(const TKey& key) const
{
return map.at(key);
}
[[nodiscard]] auto begin() const
{
return map.begin();
}
[[nodiscard]] auto begin()
{
return map.begin();
}
[[nodiscard]] auto end()
{
return map.end();
}
[[nodiscard]] auto end() const
{
return map.end();
}
};
// template<typename TKey, typename TValue, template<typename, typename, typename...> typename TMap>
// MapView(TMap<TKey, TValue>& map) -> MapView<TKey, TValue, TMap<TKey, TValue>>;
template<typename TMap>
MapView(TMap& map) -> MapView<typename TMap::key_type, typename TMap::mapped_type, TMap>;
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED)

View File

@ -0,0 +1,298 @@
#pragma once
#if !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED)
#define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1
#include <cstdint>
#include <utility>
#include "../debug/assert.hpp"
#include "../util/concepts.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
namespace impl
{
template<typename T>
struct OptionalStorage
{
alignas(T) std::uint8_t data[sizeof(T)]; // NOLINT(modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
std::uint8_t used = 0;
[[nodiscard]]
constexpr bool empty() const noexcept { return !used; }
template<typename... TArgs>
constexpr void emplace(TArgs&&... args) noexcept
{
MIJIN_ASSERT(!used, "Attempting to emplace in an already set OptionalStorage!");
used = 1;
::new (data) T(std::forward<TArgs>(args)...);
}
void clear()
{
MIJIN_ASSERT(used, "Attempting to clear an empty OptionalStorage!");
get().~T();
used = 0;
}
[[nodiscard]] constexpr T& get() noexcept { return *reinterpret_cast<T*>(data); }
[[nodiscard]] constexpr const T& get() const noexcept { return *reinterpret_cast<const T*>(data); }
};
template<pointer_type T>
struct OptionalStorage<T>
{
static constexpr T invalidPointer() noexcept { return reinterpret_cast<T>(1); }
T ptr = invalidPointer();
[[nodiscard]]
constexpr bool empty() const noexcept { return ptr == invalidPointer(); }
void emplace(T value) noexcept
{
ptr = value;
}
void clear()
{
ptr = invalidPointer();
}
[[nodiscard]] T& get() noexcept { return ptr; }
[[nodiscard]] const T& get() const noexcept { return ptr; }
};
template<reference_type T>
struct OptionalStorage<T>
{
using pointer_t = std::remove_reference_t<T>*;
pointer_t ptr = nullptr;
[[nodiscard]]
constexpr bool empty() const noexcept { return ptr == nullptr; }
void emplace(T value) noexcept
{
ptr = &value;
}
void clear()
{
ptr = nullptr;
}
T get() noexcept { return *ptr; }
const T get() const noexcept { return *ptr; } // NOLINT(readability-const-return-type) T already is a reference
};
}
struct NullOptional {};
static constexpr NullOptional NULL_OPTIONAL;
template<typename TValue>
class Optional
{
private:
impl::OptionalStorage<TValue> storage_;
public:
constexpr Optional() = default;
constexpr Optional(NullOptional) noexcept {}
inline Optional(const Optional& other) noexcept;
inline Optional(Optional&& other) noexcept;
inline Optional(TValue value) noexcept;
inline ~Optional() noexcept;
public:
Optional& operator =(const Optional& other) noexcept;
Optional& operator =(Optional&& other) noexcept;
Optional& operator =(TValue value) noexcept;
Optional& operator =(NullOptional) noexcept;
public:
[[nodiscard]]
constexpr bool operator ==(NullOptional) const noexcept { return empty(); }
template<typename TOther>
[[nodiscard]]
constexpr bool operator ==(const Optional<TOther>& other) const noexcept;
template<typename T>
[[nodiscard]]
constexpr bool operator ==(const T& value) const noexcept;
template<typename T>
[[nodiscard]]
constexpr bool operator !=(const T& value) const noexcept { return !(*this == value); }
[[nodiscard]]
constexpr explicit operator bool() const noexcept { return !empty(); }
[[nodiscard]]
constexpr bool operator !() const noexcept { return empty(); }
public:
template<typename... Types>
void emplace(Types&&... params) noexcept;
public:
[[nodiscard]] inline std::remove_reference_t<TValue>& get() noexcept;
[[nodiscard]] inline const std::remove_reference_t<TValue>& get() const noexcept;
[[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); }
inline void reset() noexcept;
};
//
// public functions
//
template<typename TValue>
Optional<TValue>::Optional(const Optional& other) noexcept
{
if (other) {
emplace(other.get());
}
}
template<typename TValue>
Optional<TValue>::Optional(Optional&& other) noexcept
{
if (other)
{
emplace(std::move(other.get()));
other.reset();
}
}
template<typename TValue>
Optional<TValue>::Optional(TValue value) noexcept
{
if constexpr (std::is_reference_v<TValue>) {
emplace(value);
}
else {
emplace(std::move(value));
}
}
template<typename TValue>
Optional<TValue>::~Optional() noexcept
{
reset();
}
template<typename TValue>
auto Optional<TValue>::operator =(const Optional& other) noexcept -> Optional&
{
if (&other == this) {
return *this;
}
reset();
if (other) {
emplace(other.get());
}
return *this;
}
template<typename TValue>
auto Optional<TValue>::operator =(Optional&& other) noexcept -> Optional&
{
if (&other == this) {
return *this;
}
reset();
if (other)
{
emplace(std::move(other.get()));
other.reset();
}
return *this;
}
template<typename TValue>
auto Optional<TValue>::operator =(TValue value) noexcept -> Optional&
{
if constexpr (std::is_reference_v<TValue>) {
emplace(value);
}
else {
emplace(std::move(value));
}
return *this;
}
template<typename TValue>
auto Optional<TValue>::operator =(NullOptional) noexcept -> Optional&
{
reset();
return *this;
}
template<typename TValue>
template<typename TOther>
constexpr bool Optional<TValue>::operator ==(const Optional<TOther>& other) const noexcept
{
if (empty())
{
return other.empty();
}
if (!other.empty())
{
return get() == other.get();
}
return false;
}
template<typename TValue>
template<typename T>
constexpr bool Optional<TValue>::operator ==(const T& value) const noexcept
{
if (empty())
{
return false;
}
return get() == value;
}
template<typename TValue>
template<typename... Types>
void Optional<TValue>::emplace(Types&&... params) noexcept
{
reset();
storage_.emplace(std::forward<Types>(params)...);
MIJIN_ASSERT(!empty(), "Something is wrong.");
}
template<typename TValue>
inline std::remove_reference_t<TValue>& Optional<TValue>::get() noexcept
{
MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!");
return storage_.get();
}
template<typename TValue>
inline const std::remove_reference_t<TValue>& Optional<TValue>::get() const noexcept
{
MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!");
return storage_.get();
}
template<typename TValue>
inline void Optional<TValue>::reset() noexcept
{
if (empty()) {
return;
}
storage_.clear();
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED)

View File

@ -0,0 +1,104 @@
#pragma once
#if !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)
#define MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED 1
#include <cstddef>
#include <vector>
#include "../debug/assert.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename T>
class BufferView;
class TypelessBuffer
{
public:
using size_type = std::size_t;
private:
std::vector<std::byte> bytes_;
public:
[[nodiscard]] void* data() { return bytes_.data(); }
[[nodiscard]] const void* data() const { return bytes_.data(); }
[[nodiscard]] size_type byteSize() const { return bytes_.size(); }
void resize(size_type numBytes) { bytes_.resize(numBytes); }
void reserve(size_type numBytes) { bytes_.reserve(numBytes); }
template<typename T>
[[nodiscard]] BufferView<T> makeBufferView() { return BufferView<T>(this); }
};
template<typename T>
class BufferView
{
public:
using element_type = T;
using value_type = std::remove_cv_t<T>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = T*;
using const_iterator = const T*;
private:
class TypelessBuffer* buffer_ = nullptr;
public:
BufferView() = default;
explicit BufferView(class TypelessBuffer* buffer) : buffer_(buffer) {}
BufferView(const BufferView&) = default;
BufferView& operator=(const BufferView&) = default;
[[nodiscard]] inline size_type size() const { return buffer_ ? buffer_->byteSize() / sizeof(T) : 0; }
inline void resize(size_type numElements) {
MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer.");
buffer_->resize(numElements * sizeof(T));
}
inline void reserve(size_type numElements) {
MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer.");
buffer_->reserve(numElements * sizeof(T));
}
[[nodiscard]] inline iterator begin() { return buffer_ ? static_cast<T*>(buffer_->data()) : nullptr; }
[[nodiscard]] inline const_iterator begin() const { return buffer_ ? static_cast<T*>(buffer_->data()) : nullptr; }
[[nodiscard]] inline const_iterator cbegin() const { return begin(); }
[[nodiscard]] inline iterator end() { return buffer_ ? static_cast<T*>(buffer_->data()) + size() : nullptr; }
[[nodiscard]] inline const_iterator end() const { return buffer_ ? static_cast<T*>(buffer_->data()) + size() : nullptr; }
[[nodiscard]] inline const_iterator cend() const { return end(); }
[[nodiscard]] inline reference at(size_type pos) {
MIJIN_ASSERT(pos < size(), "BufferView::at(): pos is out of range.");
return *(begin() + pos);
}
[[nodiscard]] inline const_reference at(size_type pos) const {
MIJIN_ASSERT(pos < size(), "BufferView::at(): pos is out of range.");
return *(begin() + pos);
}
// operators
inline reference operator[](size_type pos) { return at(pos); }
inline const_reference operator[](size_type pos) const { return at(pos); }
};
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)

View File

@ -0,0 +1,146 @@
#pragma once
#if !defined(MIJIN_DEBUG_ASSERT_HPP_INCLUDED)
#define MIJIN_DEBUG_ASSERT_HPP_INCLUDED 1
#include <cstdio>
#include <cstdlib>
namespace mijin
{
//
// public defines
//
#if MIJIN_DEBUG
#ifdef _WIN32
#pragma comment(lib, "kernel32")
extern "C" __declspec(dllimport) void __stdcall DebugBreak();
#define MIJIN_TRAP() DebugBreak()
#define MIJIN_FUNC() __FUNCSIG__
#else // _WIN32
#include <signal.h>
#define MIJIN_TRAP() raise(SIGTRAP)
#define MIJIN_FUNC() "" // TODO: __PRETTY_FUNCTION__ is not working for some reason -.-
#endif // !_WIN32
#define MIJIN_DO_RAISE_ERROR(msg, func, file, line) \
switch (mijin::handleError(msg, func, file, line)) \
{ \
case mijin::ErrorHandling::CONTINUE: \
break; \
case mijin::ErrorHandling::TRAP: \
MIJIN_TRAP(); \
[[fallthrough]]; \
default: /* ABORT */ \
std::abort(); \
}
#define MIJIN_RAISE_ERROR(msg, func, file, line) MIJIN_DO_RAISE_ERROR(msg, func, file, line)
#define MIJIN_ERROR(msg) \
MIJIN_RAISE_ERROR(msg, MIJIN_FUNC(), __FILE__, __LINE__)
#define MIJIN_FATAL(msg) \
MIJIN_ERROR(msg) \
std::abort()
// TODO: make ignoreAll work (static variables cannot be used in constexpr functions)
#define MIJIN_ASSERT(condition, msg) \
if (!static_cast<bool>(condition)) \
{ \
/* static bool ignoreAll = false; */ \
if (true) /*!ignoreAll */ \
{ \
mijin::AssertionResult assertion_result__ = mijin::handleAssert(#condition,\
msg, MIJIN_FUNC(), __FILE__, __LINE__); \
switch (assertion_result__) \
{ \
case mijin::AssertionResult::ABORT: \
std::abort(); \
break; \
case mijin::AssertionResult::IGNORE: \
break; \
case mijin::AssertionResult::IGNORE_ALL: \
/* ignoreAll = true; */ \
break; \
default: /* ERROR */ \
MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \
break; \
} \
} \
}
#define MIJIN_ASSERT_FATAL(condition, msg) \
if (!static_cast<bool>(condition)) \
{ \
MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \
}
#else // MIJIN_DEBUG
#define MIJIN_ERROR(...)
#define MIJIN_FATAL(...) std::abort()
#define MIJIN_ASSERT(...)
#define MIJIN_ASSERT_FATAL(...)
#endif // !MIJIN_DEBUG
//
// public constants
//
//
// public types
//
enum class AssertionResult
{
ABORT = -1, // call std::abort()
ERROR = 0, // raise an error using MIJIN_ERROR() handling
IGNORE = 1, // do nothing
IGNORE_ALL = 2 // do nothing and nevery halt again (not implemented yet)
};
enum class ErrorHandling
{
TRAP = 0,
CONTINUE = 1,
ABORT = 2
};
//
// public functions
//
#ifdef MIJIN_USE_CUSTOM_ASSERTION_HANDLER
AssertionResult handleAssert(const char* condition,
const char* message, const char* function,
const char* file, int line) noexcept;
#else
constexpr AssertionResult handleAssert(const char* /* condition */,
const char* /* message */, const char* /* function */,
const char* /* file */, int /* line */)
{
return AssertionResult::ERROR;
}
#endif // MIJIN_USE_CUSTOM_ASSERTION_HANDLER
#ifdef MIJIN_USE_CUSTOM_ERROR_HANDLER
ErrorHandling handleError(const char* message, const char* function,
const char* file, int line) noexcept;
#else
inline ErrorHandling handleError(const char* message, const char* function,
const char* file, int line) noexcept
{
std::puts(message);
std::printf("Function: %s\n", function);
std::printf("File: %s\n", file);
std::printf("Line: %d\n", line);
(void) std::fflush(stdout);
return ErrorHandling::TRAP;
}
#endif
} // namespace mijin
#endif // !defined(MIJIN_DEBUG_ASSERT_HPP_INCLUDED)

View File

@ -0,0 +1,87 @@
#include "symbol_info.hpp"
#include <unordered_map>
#include "../detect.hpp"
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#include <dlfcn.h>
#endif
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC
#include <cxxabi.h>
#endif
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
static thread_local std::unordered_map<const void*, std::string> g_functionNameCache;
//
// internal functions
//
//
// public functions
//
const char* lookupFunctionName(const void* function)
{
auto it = g_functionNameCache.find(function);
if (it != g_functionNameCache.end()) {
return it->second.c_str();
}
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
Dl_info info;
dladdr(function, &info);
std::string name = demangleCPPIdentifier(info.dli_sname);
if (name.empty()) {
name = info.dli_sname;
}
#else
std::string name = "<unknown>";
#endif
return g_functionNameCache.insert({function, std::move(name)}).first->second.c_str();
}
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC
std::string demangleCPPIdentifier(const char* identifier)
{
char* demangled = abi::__cxa_demangle(identifier, /* output_buffer = */ nullptr, /* length = */ nullptr, /* status = */ nullptr);
if (demangled != nullptr)
{
std::string name = demangled;
std::free(demangled); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
return name;
}
return "";
}
#else
std::string demangleCPPIdentifier(const char* /* identifier */)
{
return "";
}
#endif
} // namespace mijin

View File

@ -0,0 +1,33 @@
#pragma once
#if !defined(MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED)
#define MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED 1
#include <string>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
//
// public functions
//
[[nodiscard]] const char* lookupFunctionName(const void* function);
[[nodiscard]] std::string demangleCPPIdentifier(const char* identifier);
} // namespace mijin
#endif // !defined(MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED)

78
source/mijin/detect.hpp Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#if !defined(MIJIN_DETECT_HPP_INCLUDED)
#define MIJIN_DETECT_HPP_INCLUDED 1
namespace mijin
{
//
// public defines
//
#if !defined(__has_include)
#define __has_include(x) (false)
#endif
#define MIJIN_OS_UNKNOWN 0
#define MIJIN_OS_LINUX 1
#define MIJIN_OS_WINDOWS 2
#define MIJIN_OS_OSX 3
#if !defined(MIJIN_TARGET_OS)
#if defined(__linux__)
#define MIJIN_TARGET_OS MIJIN_OS_LINUX
#elif defined(_WIN32)
#define MIJIN_TARGET_OS MIJIN_OS_WINDOWS
#else // TODO: macos
#define MIJIN_TARGET_OS MIJIN_OS_UNKNOWN
#endif
#endif
#define MIJIN_COMPILER_UNKNOWN 0
#define MIJIN_COMPILER_GCC 1
#define MIJIN_COMPILER_CLANG 2
#define MIJIN_COMPILER_MSVC 3
#if !defined(MIJIN_COMPILER)
#if defined(__clang__)
#define MIJIN_COMPILER MIJIN_COMPILER_CLANG
#elif defined(__GNUC__)
#define MIJIN_COMPILER MIJIN_COMPILER_GCC
#elif defined(_MSC_VER)
#define MIJIN_COMPILER MIJIN_COMPILER_MSVC
#else
#define MIJIN_COMPILER MIJIN_COMPILER_UNKNOWN
#endif
#endif
#define MIJIN_STDLIB_UNKNOWN 0
#define MIJIN_STDLIB_GLIBC 1
#if !defined(MIJIN_STDLIB)
#if __has_include(<features.h>)
#include <features.h>
#endif
#if defined(__GLIBC__)
#define MIJIN_STDLIB MIJIN_STDLIB_GLIBC
#else
#define MIJIN_STDLIB MIJIN_STDLIB_UNKNOWN
#endif
#endif
//
// public constants
//
//
// public types
//
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_DETECT_HPP_INCLUDED)

308
source/mijin/io/stream.cpp Normal file
View File

@ -0,0 +1,308 @@
#include "./stream.hpp"
#include <algorithm>
#include <cassert>
#include <limits>
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
//
// internal functions
//
//
// public functions
//
void Stream::flush() {}
StreamError Stream::readString(std::string& outString)
{
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
StreamError error = read(length);
if (error != StreamError::SUCCESS) {
return error;
}
std::string result;
result.resize(length);
error = readSpan(result.begin(), result.end());
if (error != StreamError::SUCCESS) {
return error;
}
outString = std::move(result);
return StreamError::SUCCESS;
}
StreamError Stream::writeString(std::string_view str)
{
assert(str.length() <= std::numeric_limits<std::uint32_t>::max());
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
StreamError error = write(length);
if (error != StreamError::SUCCESS) {
return error;
}
return writeSpan(str.begin(), str.end());
}
FileStream::~FileStream()
{
if (handle) {
close();
}
}
StreamError FileStream::open(const char* path, FileOpenMode mode_)
{
mode = mode_;
const char* modeStr; // NOLINT(cppcoreguidelines-init-variables)
switch (mode_)
{
case FileOpenMode::READ:
modeStr = "rb";
break;
case FileOpenMode::WRITE:
modeStr = "wb";
break;
case FileOpenMode::APPEND:
modeStr = "ab";
break;
case FileOpenMode::READ_WRITE:
modeStr = "r+b";
break;
}
handle = std::fopen(path, modeStr); // NOLINT(cppcoreguidelines-owning-memory)
if (!handle && mode_ == FileOpenMode::READ_WRITE) {
handle = std::fopen(path, "w+b"); // NOLINT(cppcoreguidelines-owning-memory)
}
if (!handle) {
return StreamError::IO_ERROR;
}
int result = std::fseek(handle, 0, SEEK_END);
assert(result == 0);
length = std::ftell(handle);
result = std::fseek(handle, 0, SEEK_SET);
assert(result == 0);
return StreamError::SUCCESS;
}
void FileStream::close()
{
assert(handle);
const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory)
assert(result == 0);
}
StreamError FileStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
{
assert(handle);
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE);
const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle);
if (std::ferror(handle)) {
return StreamError::IO_ERROR;
}
if (!partial && readBytes < buffer.size()) {
return StreamError::IO_ERROR;
}
if (outBytesRead != nullptr) {
*outBytesRead = readBytes;
}
return StreamError::SUCCESS;
}
StreamError FileStream::writeRaw(std::span<const std::uint8_t> buffer)
{
assert(handle);
assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE);
const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
if (written != buffer.size() || std::ferror(handle)) {
return StreamError::IO_ERROR;
}
length = std::max<std::size_t>(length, std::ftell(handle));
return StreamError::SUCCESS;
}
std::size_t FileStream::tell()
{
assert(handle);
return std::ftell(handle);
}
StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
{
assert(handle);
int origin; // NOLINT(cppcoreguidelines-init-variables)
switch (seekMode)
{
case SeekMode::ABSOLUTE:
origin = SEEK_SET;
break;
case SeekMode::RELATIVE:
origin = SEEK_CUR;
break;
case SeekMode::RELATIVE_TO_END:
origin = SEEK_END;
break;
}
const int result = std::fseek(handle, static_cast<long>(pos), origin);
if (result != 0 || std::ferror(handle)) {
return StreamError::IO_ERROR;
}
return StreamError::SUCCESS;
}
void FileStream::flush()
{
const int result = std::fflush(handle);
assert(result == 0);
}
bool FileStream::isAtEnd()
{
assert(handle);
(void) std::fgetc(handle);
if (std::feof(handle)) {
return true;
}
const int result = std::fseek(handle, -1, SEEK_CUR);
assert(result == 0);
return false;
}
StreamFeatures FileStream::getFeatures()
{
if (handle)
{
return {
.read = (mode == FileOpenMode::READ),
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE),
.tell = true,
.seek = true
};
}
return {};
}
void MemoryStream::openRW(std::span<std::uint8_t> data)
{
assert(!isOpen());
data_ = data;
pos_ = 0;
canWrite_ = true;
}
void MemoryStream::openRO(std::span<const std::uint8_t> data)
{
assert(!isOpen());
data_ = std::span<std::uint8_t>(const_cast<std::uint8_t*>(data.data()), data.size()); // NOLINT(cppcoreguidelines-pro-type-const-cast) we'll be fine
pos_ = 0;
canWrite_ = false;
}
void MemoryStream::close()
{
assert(isOpen());
data_ = {};
}
StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
{
assert(isOpen());
if (!partial && availableBytes() < buffer.size()) {
return StreamError::IO_ERROR; // TODO: need more errors?
}
const std::size_t numBytes = std::min(buffer.size(), availableBytes());
std::copy_n(data_.begin() + static_cast<long>(pos_), numBytes, buffer.begin());
if (outBytesRead) {
*outBytesRead = numBytes;
}
pos_ += numBytes;
return StreamError::SUCCESS;
}
StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer)
{
assert(isOpen());
assert(canWrite_);
if (availableBytes() < buffer.size()) {
return StreamError::IO_ERROR;
}
std::copy(buffer.begin(), buffer.end(), data_.begin() + static_cast<long>(pos_));
pos_ += buffer.size();
return StreamError::SUCCESS;
}
std::size_t MemoryStream::tell()
{
assert(isOpen());
return pos_;
}
StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode)
{
assert(isOpen());
std::intptr_t newPos = -1;
switch (seekMode)
{
case SeekMode::ABSOLUTE:
newPos = pos;
break;
case SeekMode::RELATIVE:
newPos = static_cast<std::intptr_t>(pos_) + pos;
break;
case SeekMode::RELATIVE_TO_END:
newPos = static_cast<std::intptr_t>(data_.size()) + pos;
break;
}
if (newPos < 0 || newPos > data_.size()) {
return StreamError::IO_ERROR;
}
pos_ = newPos;
return StreamError::SUCCESS;
}
bool MemoryStream::isAtEnd()
{
assert(isOpen());
return pos_ == data_.size();
}
StreamFeatures MemoryStream::getFeatures()
{
return {
.read = true,
.write = canWrite_,
.tell = true,
.seek = true
};
}
} // namespace mijin

181
source/mijin/io/stream.hpp Normal file
View File

@ -0,0 +1,181 @@
#pragma once
#if !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
#define MIJIN_IO_STREAM_HPP_INCLUDED 1
#include <cassert>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
enum class SeekMode
{
ABSOLUTE,
RELATIVE,
RELATIVE_TO_END
};
struct StreamFeatures
{
bool read : 1 = false;
bool write : 1 = false;
bool tell : 1 = false;
bool seek : 1 = false;
};
enum class FileOpenMode
{
READ,
WRITE,
APPEND,
READ_WRITE
};
enum class StreamError
{
SUCCESS,
IO_ERROR,
UNKNOWN_ERROR
};
class Stream
{
public:
virtual ~Stream() = default;
public:
virtual StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) = 0;
virtual StreamError writeRaw(std::span<const std::uint8_t> buffer) = 0;
virtual std::size_t tell() = 0;
virtual StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) = 0;
virtual void flush();
virtual bool isAtEnd() = 0;
virtual StreamFeatures getFeatures() = 0;
inline StreamError readRaw(void* outData, std::size_t bytes, bool partial = false, std::size_t* outBytesRead = nullptr)
{
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
return readRaw(std::span(ptr, ptr + bytes), partial, outBytesRead);
}
inline StreamError writeRaw(const void* data, std::size_t bytes)
{
const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data);
return writeRaw(std::span(ptr, ptr + bytes));
}
template<typename T>
inline StreamError read(T& value)
{
return readRaw(&value, sizeof(T));
}
template<typename T>
inline StreamError readSpan(T& values)
{
auto asSpan = std::span(values);
return readRaw(asSpan.data(), asSpan.size_bytes());
}
template<typename TItBegin, typename TItEnd>
inline StreamError readSpan(TItBegin&& begin, TItEnd&& end)
{
auto asSpan = std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end));
return readRaw(asSpan.data(), asSpan.size_bytes());
}
template<typename T>
inline StreamError write(const T& value)
{
return writeRaw(&value, sizeof(T));
}
template<typename T>
inline StreamError writeSpan(const T& values)
{
auto asSpan = std::span(values);
return writeRaw(asSpan.data(), asSpan.size_bytes());
}
template<typename TItBegin, typename TItEnd>
inline StreamError writeSpan(TItBegin&& begin, TItEnd&& end)
{
return writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end)));
}
StreamError readString(std::string& outString);
StreamError writeString(std::string_view str);
};
class FileStream : public Stream
{
private:
std::FILE* handle = nullptr; // TODO: wrap in gsl::owner<>
FileOpenMode mode;
std::size_t length = 0;
public:
~FileStream() override;
StreamError open(const char* path, FileOpenMode mode_);
void close();
[[nodiscard]] inline bool isOpen() const { return handle != nullptr; }
// Stream overrides
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
void flush() override;
bool isAtEnd() override;
StreamFeatures getFeatures() override;
};
class MemoryStream : public Stream
{
private:
std::span<std::uint8_t> data_;
std::size_t pos_ = 0;
bool canWrite_ = false;
public:
void openRW(std::span<std::uint8_t> data);
void openRO(std::span<const std::uint8_t> data);
void close();
[[nodiscard]] inline bool isOpen() const { return data_.data() != nullptr; }
[[nodiscard]] inline std::size_t availableBytes() const {
assert(isOpen());
return data_.size() - pos_;
}
// Stream overrides
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
bool isAtEnd() override;
StreamFeatures getFeatures() override;
};
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_IO_STREAM_HPP_INCLUDED)

View File

@ -0,0 +1,31 @@
#include "data_pool.hpp"
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
//
// internal functions
//
//
// public functions
//
} // namespace mijin

View File

@ -0,0 +1,90 @@
#pragma once
#if !defined(MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED)
#define MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED 1
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<std::size_t PAGE_SIZE = 4096ul * 1024ul>
struct DataPool
{
static_assert(PAGE_SIZE % alignof(std::max_align_t) == 0);
struct alignas(std::max_align_t) Page : std::array<std::uint8_t, PAGE_SIZE> {};
using page_ptr_t = std::unique_ptr<Page>;
std::vector<page_ptr_t> pages;
std::size_t offset = 0;
DataPool() = default;
DataPool(const DataPool&) = delete;
DataPool(DataPool&&) noexcept = default;
DataPool& operator=(const DataPool&) = delete;
DataPool& operator=(DataPool&&) noexcept = default;
void* allocate(std::size_t bytes, std::size_t alignment = 1);
inline void reset() { offset = 0; }
template<typename T>
T* allocateAs(std::size_t elements = 1) {
return static_cast<T*>(allocate(sizeof(T) * elements, alignof(T)));
}
};
//
// public functions
//
template<std::size_t PAGE_SIZE>
void* DataPool<PAGE_SIZE>::allocate(std::size_t bytes, std::size_t alignment)
{
assert(bytes > 0 && bytes <= PAGE_SIZE);
assert(alignment > 0 && alignment <= alignof(std::max_align_t));
if (offset % alignment != 0) {
offset += alignment - (offset % alignment);
}
const std::size_t remainingOnPage = PAGE_SIZE - (offset % PAGE_SIZE);
const std::size_t page = offset / PAGE_SIZE;
const std::size_t localOffset = offset % PAGE_SIZE;
if (remainingOnPage == PAGE_SIZE || remainingOnPage < bytes)
{
// next page
if (page + 1 >= pages.size()) {
pages.push_back(std::make_unique<Page>());
}
offset = PAGE_SIZE * (pages.size() - 1);
}
std::uint8_t* result = &(*pages[page])[localOffset];
offset += bytes;
assert(reinterpret_cast<std::uintptr_t>(result) % alignment == 0);
return result;
}
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED)

128
source/mijin/types/name.cpp Normal file
View File

@ -0,0 +1,128 @@
#include "./name.hpp"
#include <algorithm>
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
#include <string>
#include <vector>
// note: the implementation assumes that std::vector moves the strings on resize and that strings don't reallocate when moved
// while that should be the case for any sane STL implementation it may cause dangling pointers if it's not
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
//
// internal functions
//
static std::vector<std::string>& getGlobalNames()
{
static std::vector<std::string> names;
return names;
}
static std::vector<std::string>& getLocalNames()
{
static thread_local std::vector<std::string> names;
return names;
}
static std::shared_mutex& getGlobalNamesMutex()
{
static std::shared_mutex mutex;
return mutex;
}
static std::unordered_map<std::string_view, std::size_t>& getLocalNameCache()
{
static std::unordered_map<std::string_view, std::size_t> cache;
return cache;
}
static void copyNamesToLocal()
{
const std::size_t oldSize = getLocalNames().size();
getLocalNames().resize(getGlobalNames().size());
std::copy(getGlobalNames().begin() + static_cast<long>(oldSize), getGlobalNames().end(), getLocalNames().begin() + static_cast<long>(oldSize));
}
static std::size_t getOrCreateStringID(std::string_view string)
{
// 1. check the cache
auto it = getLocalNameCache().find(std::string(string));
if (it != getLocalNameCache().end()) {
return it->second;
}
// 2. check the local state
for (std::size_t idx = 0; idx < getLocalNames().size(); ++idx)
{
if (getLocalNames()[idx] == string)
{
getLocalNameCache()[std::string(string)] = idx;
return idx;
}
}
// 3. copy global state and check local again
{
std::shared_lock lock(getGlobalNamesMutex());
copyNamesToLocal();
}
for (std::size_t idx = 0; idx < getLocalNames().size(); ++idx)
{
if (getLocalNames()[idx] == string)
{
getLocalNameCache()[std::string(string)] = idx;
return idx;
}
}
// 4. insert a new ID
std::unique_lock lock(getGlobalNamesMutex());
const std::size_t idx = getGlobalNames().size();
getGlobalNames().emplace_back(string);
copyNamesToLocal();
getLocalNameCache()[std::string(string)] = idx;
return idx;
}
//
// public functions
//
Name::Name(std::string_view string) : id_(getOrCreateStringID(string))
{
}
std::string_view Name::stringView() const
{
if (id_ == std::numeric_limits<std::size_t>::max()) {
return {};
}
return getLocalNames().at(id_);
}
} // namespace mijin

View File

@ -0,0 +1,63 @@
#pragma once
#if !defined(MIJIN_TYPES_NAME_HPP_INCLUDED)
#define MIJIN_TYPES_NAME_HPP_INCLUDED 1
#include <limits>
#include <string_view>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
class Name
{
private:
std::size_t id_ = std::numeric_limits<std::size_t>::max();
public:
Name() = default;
Name(const Name&) = default;
Name(std::string_view string);
Name(const char* cStr) : Name(std::string_view(cStr)) {}
Name& operator=(const Name&) = default;
auto operator<=>(const Name&) const = default;
[[nodiscard]] std::string_view stringView() const;
[[nodiscard]] const char* c_str() const {
return stringView().data();
}
[[nodiscard]] std::size_t getID() const { return id_; }
};
//
// public functions
//
} // namespace mijin
namespace std
{
template<>
struct hash<mijin::Name> : hash<std::size_t>
{
std::size_t operator()(mijin::Name name) const noexcept {
return hash<std::size_t>::operator()(name.getID());
}
};
}
#endif // !defined(MIJIN_TYPES_NAME_HPP_INCLUDED)

View File

@ -0,0 +1,75 @@
#pragma once
#if !defined(MIJIN_UTIL_BITARRAY_HPP_INCLUDED)
#define MIJIN_UTIL_BITARRAY_HPP_INCLUDED 1
#include <array>
#include <atomic>
#include <cassert>
#include <cstddef>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<std::size_t numBits, bool threadSafe = false>
struct BitArray
{
private:
using byte_type = std::conditional_t<threadSafe, std::atomic_uint8_t, std::uint8_t>;
std::array<byte_type, (numBits + 7) / 8> bytes;
public:
[[nodiscard]] bool get(std::size_t index) const {
assert(index < numBits);
return (bytes[index / 8] & (1 << (index % 8)));
}
void set(std::size_t index, bool value)
{
byte_type& byte = bytes[index / 8];
const std::uint8_t mask = (1 << (index % 8));
if constexpr (threadSafe)
{
std::uint8_t oldByte = byte.load(std::memory_order_relaxed);
std::uint8_t newByte = 0;
do
{
if (value) {
newByte = oldByte | mask;
}
else {
newByte = oldByte & ~mask;
}
} while (!byte.compare_exchange_weak(oldByte, newByte, std::memory_order_release, std::memory_order_relaxed));
}
else
{
if (value) {
byte |= mask;
}
else {
byte &= ~mask;
}
}
}
};
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_UTIL_BITARRAY_HPP_INCLUDED)

View File

@ -0,0 +1,92 @@
#pragma once
#if !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED)
#define MIJIN_UTIL_BITFLAGS_HPP_INCLUDED 1
#include <bit>
#include <cstddef>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename TBits>
struct BitFlags
{
constexpr TBits& operator |=(const BitFlags& other) {
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
*(std::bit_cast<std::byte*>(asBits()) + idx) |= *(std::bit_cast<const std::byte*>(other.asBits()) + idx);
}
return *asBits();
}
constexpr TBits& operator &=(const BitFlags& other) {
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
*(std::bit_cast<std::byte*>(asBits()) + idx) &= *(std::bit_cast<const std::byte*>(other.asBits()) + idx);
}
return *asBits();
}
constexpr TBits& operator ^=(const BitFlags& other) {
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
*(std::bit_cast<std::byte*>(asBits()) + idx) ^= *(std::bit_cast<const std::byte*>(other.asBits()) + idx);
}
return *asBits();
}
constexpr TBits operator& (const BitFlags& other) const
{
TBits copy(*asBits());
copy &= other;
return copy;
}
constexpr TBits operator| (const BitFlags& other) const
{
TBits copy(*asBits());
copy |= other;
return copy;
}
constexpr TBits operator^ (const BitFlags& other) const
{
TBits copy(*asBits());
copy ^= other;
return copy;
}
constexpr operator bool() const {
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
if (*(std::bit_cast<const std::byte*>(asBits()) + idx) != std::byte(0)) {
return true;
}
}
return false;
}
constexpr bool operator!() const {
return !static_cast<bool>(*this);
}
private:
constexpr TBits* asBits() { return static_cast<TBits*>(this); }
constexpr const TBits* asBits() const { return static_cast<const TBits*>(this); }
};
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED)

View File

@ -0,0 +1,48 @@
#pragma once
#if !defined(MIJIN_UTIL_CONCEPTS_HPP_INCLUDED)
#define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1
#include <type_traits>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename T>
concept standard_type = std::is_standard_layout_v<T>;
template<typename T>
concept trivial_type = std::is_trivial_v<T>;
template<typename T>
concept enum_type = std::is_enum_v<T>;
template<typename T>
concept arithmetic_type = std::is_arithmetic_v<T>;
template<typename T>
concept pointer_type = std::is_pointer_v<T>;
template<typename T>
concept reference_type = std::is_reference_v<T>;
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_UTIL_CONCEPTS_HPP_INCLUDED)

161
source/mijin/util/flag.hpp Normal file
View File

@ -0,0 +1,161 @@
#pragma once
#if !defined(MIJIN_UTIL_FLAG_HPP_INCLUDED)
#define MIJIN_UTIL_FLAG_HPP_INCLUDED 1
#include <cstdint>
#include "./traits.hpp"
#include "./types.hpp"
namespace mijin
{
//
// public defines
//
#define MIJIN_DEFINE_FLAG(name) \
struct name : Flag \
{ \
private: \
struct Proxy_ { \
uint8_t value; \
}; \
public: \
constexpr name() = default; \
constexpr name(const name&) = default; \
constexpr name(Proxy_ proxy) \
: Flag(proxy.value) {} \
constexpr explicit name(bool value) noexcept \
: Flag(value) {} \
name& operator=(const name&) = default; \
static constexpr Proxy_ YES{1}; \
static constexpr Proxy_ NO{0}; \
}
//
// public constants
//
//
// public types
//
struct Flag
{
std::uint8_t value;
Flag() = default;
Flag(const Flag&) = default;
explicit constexpr Flag(uint8_t value) noexcept : value(value) {}
Flag& operator=(const Flag&) = default;
constexpr bool operator ==(const Flag& other) const noexcept
{
return value == other.value;
}
constexpr bool operator !=(const Flag& other) const noexcept
{
return value != other.value;
}
constexpr bool operator !() const noexcept
{
return !value;
}
constexpr operator bool() const noexcept
{
return value != 0;
}
};
template<typename T>
concept FlagType = requires (T& value)
{
value = T::YES;
value = T::NO;
};
namespace impl
{
template<std::size_t offset, FlagType... TFlags>
class FlagSetStorage;
template<std::size_t offset, FlagType TFirst, FlagType... TMore>
class FlagSetStorage<offset, TFirst, TMore...> : public FlagSetStorage<offset + 1, TMore...>
{
private:
using base_t = FlagSetStorage<offset + 1, TMore...>;
static constexpr typename base_t::data_t BIT = (1 << offset);
public:
constexpr void set(TFirst value) noexcept
{
if (value)
{
base_t::data_ |= BIT;
}
else
{
base_t::data_ &= ~BIT;
}
}
constexpr bool get(TFirst) noexcept
{
return (base_t::data_ & BIT) != 0;
}
};
template<std::size_t offset>
class FlagSetStorage<offset>
{
protected:
using data_t = uint_next_t<offset>;
protected:
data_t data_ = data_t(0);
};
} // namespace impl
template<FlagType... TFlags>
class FlagSet
{
private:
using storage_t = impl::FlagSetStorage<0, TFlags...>;
private:
storage_t storage_;
public:
FlagSet() = default;
FlagSet(const FlagSet&) = default;
template<FlagType... TFlags2>
constexpr FlagSet(TFlags2... flags)
{
(set(flags), ...);
}
public:
FlagSet& operator=(const FlagSet&) = default;
template<FlagType T> requires is_any_type_v<T, TFlags...>
FlagSet& operator=(T flag) noexcept
{
reset(flag);
return *this;
}
template<FlagType T> requires is_any_type_v<T, TFlags...>
FlagSet& operator|=(T flag) noexcept
{
set(flag);
return *this;
}
template<FlagType T> requires is_any_type_v<T, TFlags...>
FlagSet& operator&=(T flag) noexcept
{
unset(flag);
}
};
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_UTIL_FLAG_HPP_INCLUDED)

47
source/mijin/util/os.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "os.hpp"
#include "../detect.hpp"
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
#include <pthread.h>
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
// TODO
#endif
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
//
// internal functions
//
//
// public functions
//
void setCurrentThreadName(const char* threadName)
{
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
pthread_setname_np(pthread_self(), threadName);
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
#endif
}
} // namespace mijin

30
source/mijin/util/os.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#if !defined(MIJIN_UTIL_OS_HPP_INCLUDED)
#define MIJIN_UTIL_OS_HPP_INCLUDED 1
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
//
// public functions
//
void setCurrentThreadName(const char* threadName);
} // namespace mijin
#endif // !defined(MIJIN_UTIL_OS_HPP_INCLUDED)

View File

@ -0,0 +1,52 @@
#pragma once
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1
#include <iterator>
#include <sstream>
#include <string>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
//
// public functions
//
template <typename TRange, typename TValue = typename TRange::value_type>
std::string join(const TRange& elements, const char* const delimiter)
{
std::ostringstream oss;
auto first = std::begin(elements);
auto last = std::end(elements);
if (first != last)
{
std::copy(first, std::prev(last), std::ostream_iterator<TValue>(oss, delimiter));
first = prev(last);
}
if (first != last)
{
oss << *first;
}
return oss.str();
}
} // namespace mijin
#endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)

View File

@ -0,0 +1,126 @@
#pragma once
#if !defined(MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED)
#define MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED 1
#include <utility>
#include "../detect.hpp"
namespace mijin
{
//
// public defines
//
// #define MIJIN_MAKE_TEMPLATE_STRING(str) decltype(str ## _mijin_template_string)
#define MIJIN_MAKE_TEMPLATE_STRING(str) \
mijin::impl::template_string_builder< \
mijin::impl::charAt<0>(str), \
mijin::impl::charAt<1>(str), \
mijin::impl::charAt<2>(str), \
mijin::impl::charAt<3>(str), \
mijin::impl::charAt<4>(str), \
mijin::impl::charAt<5>(str), \
mijin::impl::charAt<6>(str), \
mijin::impl::charAt<7>(str), \
mijin::impl::charAt<8>(str), \
mijin::impl::charAt<9>(str), \
mijin::impl::charAt<10>(str), \
mijin::impl::charAt<11>(str), \
mijin::impl::charAt<12>(str), \
mijin::impl::charAt<13>(str), \
mijin::impl::charAt<14>(str), \
mijin::impl::charAt<15>(str), \
mijin::impl::charAt<16>(str), \
mijin::impl::charAt<17>(str), \
mijin::impl::charAt<18>(str), \
mijin::impl::charAt<19>(str), \
mijin::impl::charAt<20>(str), \
mijin::impl::charAt<21>(str), \
mijin::impl::charAt<22>(str), \
mijin::impl::charAt<23>(str), \
mijin::impl::charAt<24>(str), \
mijin::impl::charAt<25>(str), \
mijin::impl::charAt<26>(str), \
mijin::impl::charAt<27>(str), \
mijin::impl::charAt<28>(str), \
mijin::impl::charAt<29>(str) \
>
//
// public constants
//
template<char... chars>
using template_string = std::integer_sequence<char, chars...>;
namespace impl
{
template<typename T>
struct TemplateStringHelper;
template<char... chars>
struct TemplateStringHelper<template_string<chars...>>
{
static constexpr const char value[sizeof...(chars) + 1] = {chars..., '\0'}; // NOLINT
};
template<char chr, char... chars>
constexpr template_string<chr, chars...> prependToString(template_string<chars...>)
{
return {};
}
template<char... chars>
struct TemplateStringBuilder {};
template<char firstChar, char... chars>
struct TemplateStringBuilder<firstChar, chars...>
{
using previous_seq_t = typename TemplateStringBuilder<chars...>::seq_t;
using seq_t = decltype(prependToString<firstChar>(previous_seq_t()));
};
template<char... chars>
struct TemplateStringBuilder<'\0', chars...>
{
using seq_t = template_string<>;
};
template<std::size_t pos, std::size_t len>
constexpr char charAt(const char (&text)[len])
{
if constexpr (pos < len) {
return text[pos];
}
else {
return '\0';
}
}
template<char... chars>
typename TemplateStringBuilder<chars...>::seq_t buildTemplateString()
{
return {};
}
template<char... chars>
using template_string_builder = typename TemplateStringBuilder<chars...>::seq_t;
}
template<typename T>
inline constexpr auto template_string_v = impl::TemplateStringHelper<T>::value;
//
// public types
//
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED)

View File

@ -0,0 +1,118 @@
#pragma once
#if !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)
#define MIJIN_UTIL_TRAITS_HPP_INCLUDED 1
#include <tuple>
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename T>
struct always_false
{
static constexpr bool value = false;
};
template<typename T>
inline constexpr bool always_false_v = always_false<T>::value;
template<auto V>
struct always_false_val
{
static constexpr bool value = false;
};
template<auto V>
inline constexpr bool always_false_val_v = always_false_val<V>::value;
template<template<typename> typename TFilter, typename... TArgs>
struct TypeFilter;
template<template<typename> typename TFilter>
struct TypeFilter<TFilter>
{
using type_t = std::tuple<>;
};
template<template<typename> typename TFilter, typename TFirst, typename... TArgs>
struct TypeFilter<TFilter, TFirst, TArgs...>
{
static consteval auto makeTypeHelper()
{
using base_t = typename TypeFilter<TFilter, TArgs...>::type_t;
// note: not using decltype, as the compiler might think it is being evaluated
if constexpr (!TFilter<TFirst>::value) {
return static_cast<base_t*>(nullptr);
}
else {
auto wrapper = []<typename... TArgsInner>(std::tuple<TArgsInner...>*)
{
return static_cast<std::tuple<TFirst, TArgsInner...>*>(nullptr);
};
return wrapper(static_cast<base_t*>(nullptr));
}
}
using type_t = std::remove_pointer_t<decltype(makeTypeHelper())>;
};
template<template<typename> typename TFilter, typename... TArgs>
auto typeFilterHelper(std::tuple<TArgs...>*)
{
return static_cast<typename TypeFilter<TFilter, TArgs...>::type_t*>(nullptr);
}
template<template<typename> typename TFilter, typename TTuple>
using filter_types_t = std::remove_pointer_t<decltype(typeFilterHelper<TFilter>(static_cast<TTuple*>(nullptr)))>;
template<template<typename> typename TPredicate, typename... TArgs>
auto mapTypesHelper(std::tuple<TArgs...>)
{
return static_cast<std::tuple<TPredicate<TArgs>...>*>(nullptr);
}
template<template<typename> typename TPredicate, typename TTuple>
using map_types_t = std::remove_pointer_t<decltype(mapTypesHelper<TPredicate>(std::declval<TTuple>()))>;
template<template<typename> typename TPredicate, template<typename> typename TTemplate>
struct map_template {
template<typename T>
using type_t = TTemplate<TPredicate<T>>;
};
template<typename T, typename... Types>
struct is_any_type : std::disjunction<std::is_same<T, Types>...> {};
template<typename T, typename... Types>
static constexpr bool is_any_type_v = is_any_type<T, Types...>::value;
//
// public functions
//
template<typename THelper, typename TType>
decltype(auto) delayEvaluation(TType&& value)
{
return static_cast<TType&&>(value);
}
} // namespace mijin
#endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)

127
source/mijin/util/types.hpp Normal file
View File

@ -0,0 +1,127 @@
#pragma once
#if !defined(MIJIN_UTIL_TYPES_HPP_INCLUDED)
#define MIJIN_UTIL_TYPES_HPP_INCLUDED 1
#include <cstddef>
#include <cstdint>
#include "./traits.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
namespace impl
{
template<std::size_t numBits>
consteval auto getUintType()
{
if constexpr (numBits == 8) {
return std::uint8_t{};
}
else if constexpr (numBits == 16) {
return std::uint16_t{};
}
else if constexpr (numBits == 32) {
return std::uint32_t{};
}
else if constexpr (numBits == 64) {
return std::uint64_t{};
}
else {
static_assert(always_false_val_v<numBits>, "No uint type with this bit count.");
}
}
template<std::size_t numBits>
consteval auto getNextUintType()
{
if constexpr (numBits <= 8) {
return std::uint8_t{};
}
else if constexpr (numBits <= 16) {
return std::uint16_t{};
}
else if constexpr (numBits <= 32) {
return std::uint32_t{};
}
else if constexpr (numBits <= 64) {
return std::uint64_t{};
}
else {
static_assert(always_false_val_v<numBits>, "No uint type with this bit count.");
}
}
template<std::size_t numBits>
consteval auto getIntType()
{
if constexpr (numBits == 8) {
return std::int8_t{};
}
else if constexpr (numBits == 16) {
return std::int16_t{};
}
else if constexpr (numBits == 32) {
return std::int32_t{};
}
else if constexpr (numBits == 64) {
return std::int64_t{};
}
else {
static_assert(always_false_val_v<numBits>, "No int type with this bit count.");
}
}
template<std::size_t numBits>
consteval auto getNextIntType()
{
if constexpr (numBits <= 8) {
return std::int8_t{};
}
else if constexpr (numBits <= 16) {
return std::int16_t{};
}
else if constexpr (numBits <= 32) {
return std::int32_t{};
}
else if constexpr (numBits <= 64) {
return std::int64_t{};
}
else {
static_assert(always_false_val_v<numBits>, "No int type with this bit count.");
}
}
}
template<std::size_t numBits>
using uintx_t = decltype(impl::getUintType<numBits>());
template<std::size_t numBits>
using uint_next_t = decltype(impl::getNextUintType<numBits>());
template<std::size_t numBits>
using intx_t = decltype(impl::getIntType<numBits>());
template<std::size_t numBits>
using int_next_t = decltype(impl::getNextIntType<numBits>());
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_UTIL_TYPES_HPP_INCLUDED)

View File

@ -0,0 +1,128 @@
#include "./filesystem.hpp"
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
//
// internal functions
//
//
// public functions
//
std::vector<fs::path> OSFileSystemAdapter::getRoots()
{
return {
"/" // TODO: other OSs
};
}
fs::path OSFileSystemAdapter::getHomeFolder()
{
return "/home/mewin"; // very TODO
}
std::vector<FileInfo> OSFileSystemAdapter::listFiles(const fs::path& folder)
{
std::vector<FileInfo> entries;
std::error_code err;
fs::directory_iterator iterator(folder, fs::directory_options::skip_permission_denied, err);
if (err) {
return {}; // TODO: propagate?
}
for (const fs::directory_entry& entry : iterator)
{
FileInfo& info = entries.emplace_back();
info.path = entry.path();
info.exists = true;
info.isFolder = entry.is_directory(err);
info.isSymlink = entry.is_symlink(err);
info.isSpecial = !info.isFolder && !entry.is_regular_file(err);
info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux
if (info.isFolder) {
try {
info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator());
}
catch(std::runtime_error&) {
info.size = 0;
}
}
else if (!info.isSpecial)
{
info.size = entry.file_size(err);
if (err) {
info.size = 0;
}
}
}
return entries;
}
FileInfo OSFileSystemAdapter::getFileInfo(const fs::path& file)
{
FileInfo info = {};
std::error_code err;
info.path = file;
info.exists = fs::exists(file, err);
if (info.exists)
{
info.isFolder = fs::is_directory(file, err);
info.isSymlink = fs::is_symlink(file, err);
info.isSpecial = !info.isFolder && !fs::is_regular_file(file, err);
info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux
if (info.isFolder) {
try {
info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator());
}
catch(std::runtime_error&) {
info.size = 0;
}
}
else if (!info.isSpecial)
{
info.size = fs::file_size(file, err);
if (err) {
info.size = 0;
}
}
}
return info;
}
StreamError OSFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{
const std::string pathStr = path.string();
auto stream = std::make_unique<FileStream>();
const StreamError error = stream->open(pathStr.c_str(), mode);
if (error != StreamError::SUCCESS) {
return error;
}
outStream = std::move(stream);
return StreamError::SUCCESS;
}
OSFileSystemAdapter& OSFileSystemAdapter::getInstance() // static
{
static OSFileSystemAdapter instance;
return instance;
}
} // namespace mijin

View File

@ -0,0 +1,105 @@
#pragma once
#if !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED)
#define MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED 1
#include <array>
#include <cmath>
#include <filesystem>
#include <sstream>
#include <string>
#include <vector>
#include "../io/stream.hpp"
namespace fs = std::filesystem;
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
struct FileInfo
{
fs::path path;
std::size_t size = 0;
bool exists : 1 = false;
bool isFolder : 1 = false;
bool isSymlink : 1 = false;
bool isSpecial : 1 = false;
bool isHidden : 1 = false;
};
class FileSystemAdapter
{
public:
virtual ~FileSystemAdapter() = default;
[[nodiscard]] virtual std::vector<fs::path> getRoots() = 0;
[[nodiscard]] virtual fs::path getHomeFolder() = 0;
[[nodiscard]] virtual std::vector<FileInfo> listFiles(const fs::path& folder) = 0;
[[nodiscard]] virtual FileInfo getFileInfo(const fs::path& file) = 0;
[[nodiscard]] virtual StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) = 0;
};
class OSFileSystemAdapter : public FileSystemAdapter
{
public:
std::vector<fs::path> getRoots() override;
fs::path getHomeFolder() override;
std::vector<FileInfo> listFiles(const fs::path& folder) override;
FileInfo getFileInfo(const fs::path& file) override;
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
static OSFileSystemAdapter& getInstance();
};
//
// public functions
//
inline std::string formatFileType(const FileInfo& info)
{
if (info.isFolder) {
return "Folder";
}
if (info.isSpecial) {
return "Special";
}
return "File";
}
inline std::string formatFileSize(std::size_t sizeInBytes)
{
static constexpr std::array suffixes = {"bytes", "KiB", "MiB", "GiB", "TiB"}; // enough?
if (sizeInBytes == 0) {
return "0 bytes";
}
const std::size_t pos = std::min(static_cast<std::size_t>(std::ceil(std::log2(sizeInBytes))) / 10, suffixes.size() - 1);
std::stringstream oss;
oss << std::setprecision(2) << std::fixed;
if (pos == 0) {
oss << sizeInBytes << " bytes";
}
else
{
const float converted = static_cast<float>(sizeInBytes) / std::pow(2.f, 10.f * static_cast<float>(pos));
oss << converted << " " << suffixes[pos];
}
return oss.str();
}
} // namespace mijin
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED)

View File

@ -0,0 +1,87 @@
#pragma once
#if !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED)
#define MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED 1
#include "./filesystem.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename TWrapped>
class RelativeFileSystemAdapter : public FileSystemAdapter
{
private:
TWrapped wrapped_;
fs::path root_;
public:
template<typename... TArgs>
explicit RelativeFileSystemAdapter(fs::path root, TArgs&&... args)
: wrapped_(std::forward<TArgs>(args)...), root_(std::move(root)) {}
RelativeFileSystemAdapter(const RelativeFileSystemAdapter&) = default;
RelativeFileSystemAdapter(RelativeFileSystemAdapter&&) noexcept = default;
RelativeFileSystemAdapter& operator=(const RelativeFileSystemAdapter&) = default;
RelativeFileSystemAdapter& operator=(RelativeFileSystemAdapter&&) noexcept = default;
std::vector<fs::path> getRoots() override;
fs::path getHomeFolder() override;
std::vector<FileInfo> listFiles(const fs::path& folder) override;
FileInfo getFileInfo(const fs::path& file) override;
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
};
//
// public functions
//
template<typename TWrapped>
std::vector<fs::path> RelativeFileSystemAdapter<TWrapped>::getRoots()
{
return { root_ };
}
template<typename TWrapped>
fs::path RelativeFileSystemAdapter<TWrapped>::getHomeFolder()
{
return root_;
}
template<typename TWrapped>
std::vector<FileInfo> RelativeFileSystemAdapter<TWrapped>::listFiles(const fs::path& folder)
{
std::vector<FileInfo> result = wrapped_.listFiles(root_ / folder);
for (FileInfo& fileInfo : result) {
fileInfo.path = "/" / fileInfo.path.lexically_relative(root_);
}
return result;
}
template<typename TWrapped>
FileInfo RelativeFileSystemAdapter<TWrapped>::getFileInfo(const fs::path& file)
{
return wrapped_.getFileInfo(root_ / file);
}
template<typename TWrapped>
StreamError RelativeFileSystemAdapter<TWrapped>::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{
return wrapped_.open(root_ / path, mode, outStream);
}
} // namespace mijin
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED)

View File

@ -0,0 +1,105 @@
#include "./stacked.hpp"
#include <algorithm>
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
//
// internal functions
//
//
// public functions
//
std::vector<fs::path> StackedFileSystemAdapter::getRoots()
{
std::vector<fs::path> roots;
for (auto& adapter : adapters_)
{
for (const fs::path& root : adapter->getRoots())
{
auto it = std::find(roots.begin(), roots.end(), root);
if (it == roots.end()) {
roots.push_back(root);
}
}
}
return roots;
}
fs::path StackedFileSystemAdapter::getHomeFolder()
{
if (adapters_.empty()) {
return fs::path();
}
return adapters_.front()->getHomeFolder();
}
std::vector<FileInfo> StackedFileSystemAdapter::listFiles(const fs::path& folder)
{
std::vector<FileInfo> files;
for (auto& adapter : adapters_)
{
for (const FileInfo& file : adapter->listFiles(folder))
{
auto it = std::find_if(files.begin(), files.end(), [&](const FileInfo& existing)
{
return existing.path == file.path;
});
if (it != files.end()) {
files.push_back(file);
}
}
}
return files;
}
FileInfo StackedFileSystemAdapter::getFileInfo(const fs::path& file)
{
for (auto& adapter : adapters_)
{
FileInfo fileInfo = adapter->getFileInfo(file);
if (fileInfo.exists) {
return fileInfo;
}
}
return {};
}
StreamError StackedFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{
for (auto& adapter : adapters_)
{
FileInfo fileInfo = adapter->getFileInfo(path);
if (fileInfo.exists) {
return adapter->open(path, mode, outStream);
}
}
return StreamError::IO_ERROR;
}
} // namespace mijin

View File

@ -0,0 +1,52 @@
#pragma once
#if !defined(MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED)
#define MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED 1
#include <memory>
#include <vector>
#include "./filesystem.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
class StackedFileSystemAdapter : public FileSystemAdapter
{
private:
std::vector<std::unique_ptr<FileSystemAdapter>> adapters_;
public:
std::vector<fs::path> getRoots() override;
fs::path getHomeFolder() override;
std::vector<FileInfo> listFiles(const fs::path& folder) override;
FileInfo getFileInfo(const fs::path& file) override;
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
inline void addAdapter(std::unique_ptr<FileSystemAdapter>&& adapter) {
adapters_.push_back(std::move(adapter));
}
template<typename TAdapter, typename... TArgs>
inline void emplaceAdapter(TArgs&&... args) {
addAdapter(std::make_unique<TAdapter>(std::forward<TArgs>(args)...));
}
};
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED)