Compare commits
156 Commits
5eb7864928
...
v0.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| cd66b76a8f | |||
| ede7477ffa | |||
| 75e77c53e4 | |||
| c5b9c02342 | |||
| 1c0c928856 | |||
| ce26cda739 | |||
| b6657189d3 | |||
| 3d0f5b9a3f | |||
| 6407c5ca09 | |||
| 8f2cee4968 | |||
| ba23cb0c70 | |||
| 21b3b2c03a | |||
| 55fb360dfa | |||
| 6d2a57485e | |||
| 461d3ec694 | |||
| 75398b89d5 | |||
| 43e772bb89 | |||
| 3c7b7212b9 | |||
| 28ae64169d | |||
| 0d9769f2b7 | |||
| 36a9b60e0b | |||
| 48aeb32ee5 | |||
| aa804a2a99 | |||
| 781bad3c8e | |||
| 41618310e4 | |||
| 713d5ca02d | |||
| 7ffc9df3fc | |||
| f232728f69 | |||
| 34adf3f006 | |||
| 5596ab16a7 | |||
| 49791c4fd0 | |||
| 5bd19369ce | |||
| 81f4c33508 | |||
| 5f0e6e9726 | |||
| f28cc04aa1 | |||
| e747d6d3f7 | |||
| 4be8f387dc | |||
| 71fc1686bb | |||
| aa63f35902 | |||
| 98cf879541 | |||
| d52cc6af6e | |||
| 2af1b5edd5 | |||
| 7dcdf2df22 | |||
| 017bba89e4 | |||
| 009113e566 | |||
| 741ad4603f | |||
| 57a9b6a518 | |||
| 85373e5293 | |||
| c36fc2d6ed | |||
| 6f7d518ca7 | |||
| 1f493d3b57 | |||
| 44aee31747 | |||
| c31dbbd093 | |||
| 161fdb6c93 | |||
| c4e3576bc7 | |||
| 0d00dec8c7 | |||
| 77d46d986c | |||
| 4735fc10f8 | |||
| 6ebcb96444 | |||
| 24033dbfcc | |||
| aff59724fc | |||
| 8e117e0f47 | |||
| a015ad54a7 | |||
| 92d7b5552f | |||
| 4e93898a56 | |||
| 3942c7e074 | |||
| 3d4ae9678f | |||
| 9ba097fc2f | |||
| a43f92fb58 | |||
| 0be34a845a | |||
| f761f2fb07 | |||
| 0acadf994d | |||
| f5ceb25a44 | |||
| 04a28e220c | |||
| a755de5c5f | |||
| 8a611bf4f3 | |||
| 05f0e1474a | |||
| 03f255a7d0 | |||
| 99f5987f4b | |||
| 8002e1f1f8 | |||
| df260808b9 | |||
| 35e7131780 | |||
| 03c899f17e | |||
| d508ccfe2b | |||
| 6d111904d4 | |||
| 9f011952c2 | |||
| d73fa25ed8 | |||
| 66e319fb0d | |||
| 822abb7b30 | |||
| acb5d5b04e | |||
| def91ac1bf | |||
| 9337ad7ddb | |||
| bb05bb0ae5 | |||
| 2e54844989 | |||
| f0d0ee17ea | |||
| da348c1f40 | |||
| efedca0d3c | |||
| 9ef450df33 | |||
| ea97d3af48 | |||
| af51973b2a | |||
| 686dff506d | |||
| 7658e8fbda | |||
| 2942149cb5 | |||
| cdcf99237b | |||
| f6f77f6dc1 | |||
| a9a85aecdf | |||
| 232e2a3e28 | |||
| 8b55786e77 | |||
| e82c697d2e | |||
| 9b4425c495 | |||
| 3a8602edcc | |||
| 543d77859d | |||
| b40814e58b | |||
| 0a4f1d2ce4 | |||
| b66893dda7 | |||
| f35ee5f038 | |||
| 83a46cae15 | |||
| 3b7396c4d6 | |||
| 7aa1edcea0 | |||
| 251393ae48 | |||
| eccaa177ca | |||
| b1fa99428e | |||
| b10f250945 | |||
| e586698fb6 | |||
| c214398fac | |||
| 463f4ca19c | |||
| 80310f3c5c | |||
| 1d14c1f3ff | |||
| 83d3ce9f1f | |||
| de07cf91bc | |||
| faf4489b92 | |||
| 2668e69ae1 | |||
| bb4d31747a | |||
| 8ce77cf8bf | |||
| b692425f83 | |||
| 1d8ef0bac8 | |||
| b89f19ed98 | |||
| 0e877f4769 | |||
| 5e236dae00 | |||
| 6cac3a655b | |||
| 27b163f4db | |||
| c44350269a | |||
| adbb4fd0d1 | |||
| 45845fa31b | |||
| 4f7e8ae39f | |||
| ce7a7b15c7 | |||
| 0e90cabb7e | |||
| 60707421c8 | |||
| ba8c1ebe1e | |||
| 803f1463dc | |||
| 065181fc69 | |||
| d98e14285b | |||
| 4dfc116830 | |||
| a92148aac1 | |||
| 55486b49dc | |||
| 5aecd20c56 |
66
.clang-format
Normal file
66
.clang-format
Normal file
@@ -0,0 +1,66 @@
|
||||
# Generated from CLion C/C++ Code Style settings
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignOperands: Align
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Always
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: true
|
||||
AfterControlStatement: Always
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
ContinuationIndentWidth: 8
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: None
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PointerAlignment: Left
|
||||
ReflowComments: false
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 0
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
73
.clang-tidy
Normal file
73
.clang-tidy
Normal file
@@ -0,0 +1,73 @@
|
||||
Checks: '*,
|
||||
-abseil-*,
|
||||
-altera-*,
|
||||
-android-*,
|
||||
-boost-*,
|
||||
-darwin-*,
|
||||
-fuchsia-*,
|
||||
-google-*,
|
||||
-hicpp-*,
|
||||
-linuxkernel-*,
|
||||
-llvm-*,
|
||||
-llvmlibc-*,
|
||||
-mpi-*,
|
||||
-objc-*,
|
||||
-zircon-*,
|
||||
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-exception-escape,
|
||||
-bugprone-unhandled-exception-at-new,
|
||||
-cert-dcl21-cpp,
|
||||
-cert-err58-cpp,
|
||||
-cppcoreguidelines-avoid-capturing-lambda-coroutines,
|
||||
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
||||
-cppcoreguidelines-avoid-do-while,
|
||||
-cppcoreguidelines-avoid-reference-coroutine-parameters,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-pro-type-member-init,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-special-member-functions,
|
||||
-modernize-macro-to-enum,
|
||||
-misc-include-cleaner,
|
||||
-misc-no-recursion,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-use-anonymous-namespace,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-trailing-return-type,
|
||||
-portability-simd-intrinsics,
|
||||
-readability-avoid-unconditional-preprocessor-if,
|
||||
-readability-container-data-pointer,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers,
|
||||
-readability-named-parameter,
|
||||
-readability-redundant-access-specifiers,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-anyofallof'
|
||||
|
||||
CheckOptions:
|
||||
- key: readability-identifier-length.IgnoredParameterNames
|
||||
value: '^[xyz]$'
|
||||
- key: readability-identifier-length.IgnoredLoopCounterNames
|
||||
value: '^[xyz]$'
|
||||
- key: readability-identifier-length.IgnoredVariableNames
|
||||
value: '(it|NO)'
|
||||
- key: readability-function-cognitive-complexity.Threshold
|
||||
value: 50
|
||||
|
||||
WarningsAsErrors: '*'
|
||||
|
||||
HeaderFilterRegex: 'source/*.hpp$'
|
||||
|
||||
UseColor: false
|
||||
19
LibConf
19
LibConf
@@ -7,27 +7,36 @@ mijin_sources = Split("""
|
||||
source/mijin/debug/symbol_info.cpp
|
||||
source/mijin/io/process.cpp
|
||||
source/mijin/io/stream.cpp
|
||||
source/mijin/net/http.cpp
|
||||
source/mijin/net/ip.cpp
|
||||
source/mijin/net/socket.cpp
|
||||
source/mijin/util/os.cpp
|
||||
source/mijin/types/name.cpp
|
||||
source/mijin/virtual_filesystem/filesystem.cpp
|
||||
source/mijin/virtual_filesystem/stacked.cpp
|
||||
""")
|
||||
|
||||
dependencies = []
|
||||
if env['COMPILER_FAMILY'] in ('gcc', 'clang'):
|
||||
lib_libbacktrace = env.Cook('libbacktrace')
|
||||
dependencies.append(lib_libbacktrace)
|
||||
|
||||
cppdefines = []
|
||||
if env['BUILD_TYPE'] == 'debug':
|
||||
cppdefines += ['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1']
|
||||
|
||||
|
||||
lib_mijin = env.UnityStaticLibrary(
|
||||
target = env['LIB_DIR'] + '/mijin',
|
||||
source = mijin_sources,
|
||||
dependencies = [lib_libbacktrace]
|
||||
dependencies = dependencies,
|
||||
CPPDEFINES = list(env['CPPDEFINES']) + cppdefines
|
||||
)
|
||||
|
||||
LIB_CONFIG = {
|
||||
'CPPPATH': [env.Dir('source')],
|
||||
'CPPDEFINES': [],
|
||||
'CPPDEFINES': cppdefines,
|
||||
'DEPENDENCIES': [lib_mijin]
|
||||
}
|
||||
|
||||
if env['BUILD_TYPE'] == 'debug':
|
||||
LIB_CONFIG['CPPDEFINES'].extend(['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1'])
|
||||
|
||||
Return('LIB_CONFIG')
|
||||
|
||||
58
SModule
Normal file
58
SModule
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
import json
|
||||
|
||||
|
||||
Import('env')
|
||||
|
||||
mijin_sources = Split("""
|
||||
source/mijin/async/coroutine.cpp
|
||||
source/mijin/debug/stacktrace.cpp
|
||||
source/mijin/debug/symbol_info.cpp
|
||||
source/mijin/io/process.cpp
|
||||
source/mijin/io/stream.cpp
|
||||
source/mijin/net/http.cpp
|
||||
source/mijin/net/ip.cpp
|
||||
source/mijin/net/socket.cpp
|
||||
source/mijin/platform/folders.cpp
|
||||
source/mijin/util/os.cpp
|
||||
source/mijin/types/name.cpp
|
||||
source/mijin/virtual_filesystem/filesystem.cpp
|
||||
source/mijin/virtual_filesystem/stacked.cpp
|
||||
""")
|
||||
|
||||
with open('dependencies.json', 'r') as f:
|
||||
dependencies = env.DepsFromJson(json.load(f))
|
||||
|
||||
cppdefines = []
|
||||
if env['BUILD_TYPE'] == 'debug':
|
||||
cppdefines += ['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1']
|
||||
|
||||
|
||||
# SSL libs
|
||||
if env.get('MIJIN_ENABLE_OPENSSL'):
|
||||
cppdefines.append('MIJIN_ENABLE_OPENSSL=1')
|
||||
mijin_sources.extend(Split("""
|
||||
source/mijin/net/ssl.cpp
|
||||
"""))
|
||||
|
||||
# CURL libs
|
||||
if env.get('MIJIN_ENABLE_CURL'):
|
||||
cppdefines.append('MIJIN_ENABLE_CURL=1')
|
||||
mijin_sources.extend(Split("""
|
||||
source/mijin/net/request.cpp
|
||||
"""))
|
||||
|
||||
lib_mijin = env.UnityStaticLibrary(
|
||||
target = env['LIB_DIR'] + '/mijin',
|
||||
source = mijin_sources,
|
||||
dependencies = dependencies,
|
||||
CPPDEFINES = list(env['CPPDEFINES']) + cppdefines
|
||||
)
|
||||
|
||||
LIB_CONFIG = {
|
||||
'CPPPATH': [env.Dir('source')],
|
||||
'CPPDEFINES': cppdefines,
|
||||
'LIBS': [lib_mijin]
|
||||
}
|
||||
|
||||
Return('LIB_CONFIG')
|
||||
19
dependencies.json
Normal file
19
dependencies.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"libbacktrace":
|
||||
{
|
||||
"condition": "compiler_family == 'gcc' or compiler_family == 'clang'"
|
||||
},
|
||||
"winsock2":
|
||||
{
|
||||
"condition": "target_os == 'nt'"
|
||||
},
|
||||
"openssl":
|
||||
{
|
||||
"condition": "getenv('MIJIN_ENABLE_OPENSSL')"
|
||||
},
|
||||
"curl":
|
||||
{
|
||||
"condition": "getenv('MIJIN_ENABLE_CURL')",
|
||||
"max": [8, 9, 1]
|
||||
}
|
||||
}
|
||||
@@ -40,10 +40,10 @@ void MultiThreadedTaskLoop::managerThread(std::stop_token stopToken) // NOLINT(p
|
||||
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) {
|
||||
auto itRem = std::remove_if(parkedTasks_.begin(), parkedTasks_.end(), [](StoredTask& task) {
|
||||
return !task.task || task.task->status() == TaskStatus::FINISHED;
|
||||
});
|
||||
parkedTasks_.erase(it, parkedTasks_.end());
|
||||
parkedTasks_.erase(itRem, parkedTasks_.end());
|
||||
|
||||
// then try to push any task from the buffer into the queue, if possible
|
||||
for (auto it = parkedTasks_.begin(); it != parkedTasks_.end();)
|
||||
@@ -135,7 +135,7 @@ void MultiThreadedTaskLoop::workerThread(std::stop_token stopToken, std::size_t
|
||||
// public functions
|
||||
//
|
||||
|
||||
void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
|
||||
void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT
|
||||
{
|
||||
assertCorrectThread();
|
||||
|
||||
@@ -153,7 +153,7 @@ void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
|
||||
otherLoop.addStoredTask(std::move(storedTask));
|
||||
}
|
||||
|
||||
void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
|
||||
void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT
|
||||
{
|
||||
storedTask.task->setLoop(this);
|
||||
if (threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id())
|
||||
@@ -174,13 +174,27 @@ void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
|
||||
std::size_t SimpleTaskLoop::getActiveTasks() const MIJIN_NOEXCEPT
|
||||
{
|
||||
std::size_t sum = 0;
|
||||
for (const StoredTask& task : mijin::chain(tasks_, newTasks_))
|
||||
{
|
||||
const TaskStatus status = task.task ? task.task->status() : TaskStatus::FINISHED;
|
||||
if (status == TaskStatus::SUSPENDED || status == TaskStatus::RUNNING)
|
||||
{
|
||||
++sum;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (&otherLoop == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
MIJIN_ASSERT_FATAL(currentTask_ != nullptr, "Trying to call transferCurrentTask() while not running a task!");
|
||||
MIJIN_ASSERT_FATAL(impl::gCurrentTask != nullptr, "Trying to call transferCurrentTask() while not running a task!");
|
||||
|
||||
// now start the transfer, first disown the task
|
||||
StoredTask storedTask = std::move(*impl::gCurrentTask);
|
||||
@@ -190,7 +204,7 @@ void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
|
||||
otherLoop.addStoredTask(std::move(storedTask));
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
|
||||
void MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT
|
||||
{
|
||||
storedTask.task->setLoop(this);
|
||||
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
#ifndef MIJIN_ASYNC_COROUTINE_HPP_INCLUDED
|
||||
#define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1
|
||||
|
||||
|
||||
#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO)
|
||||
# define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0 // Capture stack each time a coroutine is started. Warning, expensive! // TODO: maybe implement a lighter version only storing the return address?
|
||||
#endif
|
||||
|
||||
#include <any>
|
||||
#include <coroutine>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
@@ -13,8 +19,25 @@
|
||||
#include "./future.hpp"
|
||||
#include "./message_queue.hpp"
|
||||
#include "../container/optional.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/flag.hpp"
|
||||
#include "../util/iterators.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
#include "../debug/stacktrace.hpp"
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING)
|
||||
# define MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING MIJIN_ENABLE_EXCEPTIONS
|
||||
#elif !__cpp_exceptions
|
||||
# error "Coroutine exception handling enabled, but exceptions are disabled."
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_COROUTINE_ENABLE_CANCEL)
|
||||
# define MIJIN_COROUTINE_ENABLE_CANCEL MIJIN_ENABLE_EXCEPTIONS
|
||||
#elif !__cpp_exceptions
|
||||
# error "Cancelling tasks requires exceptions to be anbled."
|
||||
#endif
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -23,10 +46,6 @@ namespace mijin
|
||||
// public defines
|
||||
//
|
||||
|
||||
#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO)
|
||||
#define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0
|
||||
#endif
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
@@ -49,13 +68,54 @@ class TaskLoop;
|
||||
template<typename TResult = void>
|
||||
class TaskBase;
|
||||
|
||||
#if MIJIN_COROUTINE_ENABLE_CANCEL
|
||||
struct TaskCancelled : std::exception {};
|
||||
#endif
|
||||
|
||||
namespace impl
|
||||
{
|
||||
inline void throwIfCancelled();
|
||||
} // namespace impl
|
||||
|
||||
class TaskHandle
|
||||
{
|
||||
private:
|
||||
std::weak_ptr<struct TaskSharedState> state_;
|
||||
public:
|
||||
TaskHandle() = default;
|
||||
explicit TaskHandle(std::weak_ptr<TaskSharedState> state) MIJIN_NOEXCEPT : state_(std::move(state)) {}
|
||||
TaskHandle(const TaskHandle&) = default;
|
||||
TaskHandle(TaskHandle&&) = default;
|
||||
|
||||
TaskHandle& operator=(const TaskHandle&) = default;
|
||||
TaskHandle& operator=(TaskHandle&&) = default;
|
||||
|
||||
bool operator==(const TaskHandle& other) const MIJIN_NOEXCEPT {
|
||||
return !state_.owner_before(other.state_) && !other.state_.owner_before(state_);
|
||||
}
|
||||
bool operator!=(const TaskHandle& other) const MIJIN_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isValid() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !state_.expired();
|
||||
}
|
||||
|
||||
inline void cancel() const MIJIN_NOEXCEPT;
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
inline Optional<Stacktrace> getCreationStack() const MIJIN_NOEXCEPT;
|
||||
#endif
|
||||
};
|
||||
struct TaskSharedState
|
||||
{
|
||||
std::atomic_bool cancelled_ = false;
|
||||
TaskHandle subTask;
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
Stacktrace creationStack_;
|
||||
#endif
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct TaskState
|
||||
{
|
||||
@@ -65,11 +125,11 @@ struct TaskState
|
||||
|
||||
TaskState() = default;
|
||||
TaskState(const TaskState&) = default;
|
||||
TaskState(TaskState&&) noexcept = default;
|
||||
inline TaskState(T _value, TaskStatus _status) noexcept : value(std::move(_value)), status(_status) {}
|
||||
inline TaskState(std::exception_ptr _exception) noexcept : exception(std::move(_exception)), status(TaskStatus::FINISHED) {}
|
||||
TaskState(TaskState&&) MIJIN_NOEXCEPT = default;
|
||||
inline TaskState(T _value, TaskStatus _status) MIJIN_NOEXCEPT : value(std::move(_value)), status(_status) {}
|
||||
inline TaskState(std::exception_ptr _exception) MIJIN_NOEXCEPT : exception(std::move(_exception)), status(TaskStatus::FINISHED) {}
|
||||
TaskState& operator=(const TaskState&) = default;
|
||||
TaskState& operator=(TaskState&&) noexcept = default;
|
||||
TaskState& operator=(TaskState&&) MIJIN_NOEXCEPT = default;
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -80,11 +140,11 @@ struct TaskState<void>
|
||||
|
||||
TaskState() = default;
|
||||
TaskState(const TaskState&) = default;
|
||||
TaskState(TaskState&&) noexcept = default;
|
||||
inline TaskState(TaskStatus _status) noexcept : status(_status) {}
|
||||
inline TaskState(std::exception_ptr _exception) noexcept : exception(std::move(_exception)), status(TaskStatus::FINISHED) {}
|
||||
TaskState(TaskState&&) MIJIN_NOEXCEPT = default;
|
||||
inline TaskState(TaskStatus _status) MIJIN_NOEXCEPT : status(_status) {}
|
||||
inline TaskState(std::exception_ptr _exception) MIJIN_NOEXCEPT : exception(std::move(_exception)), status(TaskStatus::FINISHED) {}
|
||||
TaskState& operator=(const TaskState&) = default;
|
||||
TaskState& operator=(TaskState&&) noexcept = default;
|
||||
TaskState& operator=(TaskState&&) MIJIN_NOEXCEPT = default;
|
||||
};
|
||||
|
||||
namespace impl
|
||||
@@ -93,15 +153,15 @@ template<typename TReturn, typename TPromise>
|
||||
struct TaskReturn
|
||||
{
|
||||
template<typename... TArgs>
|
||||
constexpr void return_value(TArgs&&... args) noexcept {
|
||||
constexpr void return_value(TArgs&&... args) MIJIN_NOEXCEPT {
|
||||
(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::forward<TArgs>(args)...), TaskStatus::FINISHED);
|
||||
}
|
||||
|
||||
constexpr void return_value(TReturn value) noexcept {
|
||||
constexpr void return_value(TReturn value) MIJIN_NOEXCEPT {
|
||||
(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::move(value)), TaskStatus::FINISHED);
|
||||
}
|
||||
|
||||
constexpr void unhandled_exception() noexcept {
|
||||
constexpr void unhandled_exception() MIJIN_NOEXCEPT {
|
||||
(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(std::current_exception());
|
||||
}
|
||||
};
|
||||
@@ -109,11 +169,11 @@ struct TaskReturn
|
||||
template<typename TPromise>
|
||||
struct TaskReturn<void, TPromise>
|
||||
{
|
||||
constexpr void return_void() noexcept {
|
||||
constexpr void return_void() MIJIN_NOEXCEPT {
|
||||
static_cast<TPromise&>(*this).state_.status = TaskStatus::FINISHED;
|
||||
}
|
||||
|
||||
constexpr void unhandled_exception() noexcept {
|
||||
constexpr void unhandled_exception() MIJIN_NOEXCEPT {
|
||||
(static_cast<TPromise&>(*this).state_) = TaskState<void>(std::current_exception());
|
||||
}
|
||||
};
|
||||
@@ -124,8 +184,8 @@ struct TaskAwaitableFuture
|
||||
{
|
||||
FuturePtr<TValue> future;
|
||||
|
||||
[[nodiscard]] constexpr bool await_ready() const noexcept { return future->ready(); }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
|
||||
[[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return future->ready(); }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
|
||||
constexpr TValue await_resume() const
|
||||
{
|
||||
impl::throwIfCancelled();
|
||||
@@ -143,8 +203,8 @@ 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 {}
|
||||
[[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
|
||||
inline auto& await_resume() const
|
||||
{
|
||||
impl::throwIfCancelled();
|
||||
@@ -157,8 +217,8 @@ 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 {}
|
||||
[[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
|
||||
constexpr auto& await_resume() const
|
||||
{
|
||||
impl::throwIfCancelled();
|
||||
@@ -169,8 +229,8 @@ struct TaskAwaitableSignal<TSingleArg>
|
||||
template<>
|
||||
struct TaskAwaitableSignal<>
|
||||
{
|
||||
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
|
||||
[[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
|
||||
inline void await_resume() const {
|
||||
impl::throwIfCancelled();
|
||||
}
|
||||
@@ -178,8 +238,8 @@ struct TaskAwaitableSignal<>
|
||||
|
||||
struct TaskAwaitableSuspend
|
||||
{
|
||||
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
|
||||
[[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
|
||||
inline void await_resume() const {
|
||||
impl::throwIfCancelled();
|
||||
}
|
||||
@@ -193,24 +253,25 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
|
||||
using result_t = typename TTraits::result_t;
|
||||
|
||||
TaskState<result_t> state_;
|
||||
std::shared_ptr<TaskSharedState> sharedState_ = std::make_shared<TaskSharedState>();
|
||||
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 {}; }
|
||||
constexpr task_t get_return_object() MIJIN_NOEXCEPT { return task_t(handle_t::from_promise(*this)); }
|
||||
constexpr TaskAwaitableSuspend initial_suspend() MIJIN_NOEXCEPT { return {}; }
|
||||
constexpr std::suspend_always final_suspend() noexcept { return {}; } // note: this must always be noexcept, no matter what
|
||||
|
||||
// template<typename TValue>
|
||||
// constexpr std::suspend_always yield_value(TValue value) noexcept {
|
||||
// constexpr std::suspend_always yield_value(TValue value) MIJIN_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 {}
|
||||
// constexpr void unhandled_exception() MIJIN_NOEXCEPT {}
|
||||
|
||||
template<typename TValue>
|
||||
auto await_transform(FuturePtr<TValue> future) noexcept
|
||||
auto await_transform(FuturePtr<TValue> future) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!");
|
||||
TaskAwaitableFuture<TValue> awaitable{future};
|
||||
@@ -226,15 +287,15 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
|
||||
}
|
||||
|
||||
template<typename TResultOther>
|
||||
auto await_transform(TaskBase<TResultOther> task) noexcept
|
||||
auto await_transform(TaskBase<TResultOther> task) MIJIN_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
|
||||
MIJIN_ASSERT(loop_ != nullptr, "Cannot await another task outside of a loop!"); // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||
auto future = delayEvaluation<TResultOther>(loop_)->addTask(std::move(task), &sharedState_->subTask); // 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 await_transform(Signal<TFirstArg, TSecondArg, TArgs...>& signal) MIJIN_NOEXCEPT
|
||||
{
|
||||
auto data = std::make_shared<std::tuple<TFirstArg, TSecondArg, TArgs...>>();
|
||||
signal.connect([this, data](TFirstArg arg0, TSecondArg arg1, TArgs... args) mutable
|
||||
@@ -248,7 +309,7 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
|
||||
}
|
||||
|
||||
template<typename TFirstArg>
|
||||
auto await_transform(Signal<TFirstArg>& signal) noexcept
|
||||
auto await_transform(Signal<TFirstArg>& signal) MIJIN_NOEXCEPT
|
||||
{
|
||||
auto data = std::make_shared<TFirstArg>();
|
||||
signal.connect([this, data](TFirstArg arg0) mutable
|
||||
@@ -261,7 +322,7 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
|
||||
return awaitable;
|
||||
}
|
||||
|
||||
auto await_transform(Signal<>& signal) noexcept
|
||||
auto await_transform(Signal<>& signal) MIJIN_NOEXCEPT
|
||||
{
|
||||
signal.connect([this]()
|
||||
{
|
||||
@@ -272,17 +333,17 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
|
||||
return awaitable;
|
||||
}
|
||||
|
||||
std::suspend_always await_transform(std::suspend_always) noexcept
|
||||
std::suspend_always await_transform(std::suspend_always) MIJIN_NOEXCEPT
|
||||
{
|
||||
state_.status = TaskStatus::SUSPENDED;
|
||||
return std::suspend_always();
|
||||
}
|
||||
|
||||
std::suspend_never await_transform(std::suspend_never) noexcept {
|
||||
std::suspend_never await_transform(std::suspend_never) MIJIN_NOEXCEPT {
|
||||
return std::suspend_never();
|
||||
}
|
||||
|
||||
TaskAwaitableSuspend await_transform(TaskAwaitableSuspend) noexcept
|
||||
TaskAwaitableSuspend await_transform(TaskAwaitableSuspend) MIJIN_NOEXCEPT
|
||||
{
|
||||
state_.status = TaskStatus::SUSPENDED;
|
||||
return TaskAwaitableSuspend();
|
||||
@@ -290,7 +351,7 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
|
||||
};
|
||||
|
||||
template<typename TResult>
|
||||
class TaskBase
|
||||
class [[nodiscard("Tasks should either we awaited or added to a loop.")]] TaskBase
|
||||
{
|
||||
public:
|
||||
using task_t = TaskBase;
|
||||
@@ -306,22 +367,37 @@ public:
|
||||
private:
|
||||
handle_t handle_;
|
||||
public:
|
||||
constexpr explicit TaskBase(handle_t handle) noexcept : handle_(handle) {}
|
||||
constexpr explicit TaskBase(handle_t handle) MIJIN_NOEXCEPT : handle_(handle) {
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
if (Result<Stacktrace> stacktrace = captureStacktrace(2); stacktrace.isSuccess())
|
||||
{
|
||||
handle_.promise().sharedState_->creationStack_ = *stacktrace;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
TaskBase(const TaskBase&) = delete;
|
||||
TaskBase(TaskBase&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}
|
||||
~TaskBase() noexcept;
|
||||
TaskBase(TaskBase&& other) MIJIN_NOEXCEPT : handle_(std::exchange(other.handle_, nullptr))
|
||||
{}
|
||||
~TaskBase() MIJIN_NOEXCEPT;
|
||||
public:
|
||||
TaskBase& operator=(const TaskBase&) = default;
|
||||
TaskBase& operator=(TaskBase&& other) noexcept = default;
|
||||
TaskBase& operator=(const TaskBase&) = delete;
|
||||
TaskBase& operator=(TaskBase&& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (handle_) {
|
||||
handle_.destroy();
|
||||
}
|
||||
handle_ = std::exchange(other.handle_, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool operator==(const TaskBase& other) const noexcept { return handle_ == other.handle_; }
|
||||
constexpr bool operator==(const TaskBase& other) const MIJIN_NOEXCEPT { return handle_ == other.handle_; }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool operator!=(const TaskBase& other) const noexcept { return handle_ != other.handle_; }
|
||||
constexpr bool operator!=(const TaskBase& other) const MIJIN_NOEXCEPT { return handle_ != other.handle_; }
|
||||
public:
|
||||
[[nodiscard]]
|
||||
constexpr TaskState<TResult>& state() noexcept
|
||||
constexpr TaskState<TResult>& state() MIJIN_NOEXCEPT
|
||||
{
|
||||
return handle_.promise().state_;
|
||||
}
|
||||
@@ -331,15 +407,19 @@ public:
|
||||
handle_.resume();
|
||||
return state();
|
||||
}
|
||||
constexpr std::shared_ptr<TaskSharedState>& sharedState() MIJIN_NOEXCEPT
|
||||
{
|
||||
return handle_.promise().sharedState_;
|
||||
}
|
||||
private:
|
||||
[[nodiscard]]
|
||||
constexpr handle_t handle() const noexcept { return handle_; }
|
||||
constexpr handle_t handle() const MIJIN_NOEXCEPT { return handle_; }
|
||||
[[nodiscard]]
|
||||
constexpr TaskLoop* getLoop() noexcept
|
||||
constexpr TaskLoop* getLoop() MIJIN_NOEXCEPT
|
||||
{
|
||||
return handle_.promise().loop_;
|
||||
}
|
||||
constexpr void setLoop(TaskLoop* loop) noexcept
|
||||
constexpr void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT
|
||||
{
|
||||
// MIJIN_ASSERT(handle_.promise().loop_ == nullptr
|
||||
// || handle_.promise().loop_ == loop
|
||||
@@ -358,13 +438,14 @@ class WrappedTaskBase
|
||||
public:
|
||||
virtual ~WrappedTaskBase() = default;
|
||||
public:
|
||||
virtual TaskStatus status() noexcept = 0;
|
||||
virtual std::exception_ptr exception() noexcept = 0;
|
||||
// virtual std::any result() noexcept = 0;
|
||||
virtual TaskStatus status() MIJIN_NOEXCEPT = 0;
|
||||
virtual std::exception_ptr exception() MIJIN_NOEXCEPT = 0;
|
||||
// virtual std::any result() MIJIN_NOEXCEPT = 0;
|
||||
virtual void resume() = 0;
|
||||
virtual void* raw() noexcept = 0;
|
||||
virtual std::coroutine_handle<> handle() noexcept = 0;
|
||||
virtual void setLoop(TaskLoop* loop) noexcept = 0;
|
||||
virtual void* raw() MIJIN_NOEXCEPT = 0;
|
||||
virtual std::coroutine_handle<> handle() MIJIN_NOEXCEPT = 0;
|
||||
virtual void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT = 0;
|
||||
virtual std::shared_ptr<TaskSharedState>& sharedState() MIJIN_NOEXCEPT = 0;
|
||||
|
||||
[[nodiscard]] inline bool canResume() {
|
||||
const TaskStatus stat = status();
|
||||
@@ -378,16 +459,16 @@ class WrappedTask : public WrappedTaskBase
|
||||
private:
|
||||
TTask task_;
|
||||
public:
|
||||
constexpr explicit WrappedTask(TTask&& task) noexcept : task_(std::move(task)) {}
|
||||
constexpr explicit WrappedTask(TTask&& task) MIJIN_NOEXCEPT : task_(std::move(task)) {}
|
||||
WrappedTask(const WrappedTask&) = delete;
|
||||
WrappedTask(WrappedTask&&) noexcept = default;
|
||||
WrappedTask(WrappedTask&&) MIJIN_NOEXCEPT = default;
|
||||
public:
|
||||
WrappedTask& operator=(const WrappedTask&) = delete;
|
||||
WrappedTask& operator=(WrappedTask&&) noexcept = default;
|
||||
WrappedTask& operator=(WrappedTask&&) MIJIN_NOEXCEPT = default;
|
||||
public:
|
||||
TaskStatus status() noexcept override { return task_.state().status; }
|
||||
std::exception_ptr exception() noexcept override { return task_.state().exception; }
|
||||
// std::any result() noexcept override
|
||||
TaskStatus status() MIJIN_NOEXCEPT override { return task_.state().status; }
|
||||
std::exception_ptr exception() MIJIN_NOEXCEPT override { return task_.state().exception; }
|
||||
// std::any result() MIJIN_NOEXCEPT
|
||||
// {
|
||||
// if constexpr (std::is_same_v<typename TTask::result_t, void>) {
|
||||
// return {};
|
||||
@@ -397,45 +478,14 @@ public:
|
||||
// }
|
||||
// }
|
||||
void resume() 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); }
|
||||
};
|
||||
|
||||
struct TaskSharedState
|
||||
{
|
||||
std::atomic_bool cancelled_ = false;
|
||||
};
|
||||
|
||||
class TaskHandle
|
||||
{
|
||||
private:
|
||||
std::weak_ptr<TaskSharedState> state_;
|
||||
public:
|
||||
TaskHandle() = default;
|
||||
explicit TaskHandle(std::weak_ptr<TaskSharedState> state) noexcept : state_(std::move(state)) {}
|
||||
TaskHandle(const TaskHandle&) = default;
|
||||
TaskHandle(TaskHandle&&) = default;
|
||||
|
||||
TaskHandle& operator=(const TaskHandle&) = default;
|
||||
TaskHandle& operator=(TaskHandle&&) = default;
|
||||
|
||||
[[nodiscard]] bool isValid() const noexcept
|
||||
{
|
||||
return !state_.expired();
|
||||
}
|
||||
|
||||
void cancel() const noexcept
|
||||
{
|
||||
if (std::shared_ptr<TaskSharedState> state = state_.lock())
|
||||
{
|
||||
state->cancelled_ = true;
|
||||
}
|
||||
}
|
||||
void* raw() MIJIN_NOEXCEPT override { return &task_; }
|
||||
std::coroutine_handle<> handle() MIJIN_NOEXCEPT override { return task_.handle(); }
|
||||
void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT override { task_.setLoop(loop); }
|
||||
virtual std::shared_ptr<TaskSharedState>& sharedState() MIJIN_NOEXCEPT override { return task_.sharedState(); }
|
||||
};
|
||||
|
||||
template<typename TTask>
|
||||
std::unique_ptr<WrappedTask<TTask>> wrapTask(TTask&& task) noexcept
|
||||
std::unique_ptr<WrappedTask<TTask>> wrapTask(TTask&& task) MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::make_unique<WrappedTask<TTask>>(std::forward<TTask>(task));
|
||||
}
|
||||
@@ -450,7 +500,6 @@ public:
|
||||
using wrapped_task_base_ptr_t = std::unique_ptr<wrapped_task_t>;
|
||||
struct StoredTask
|
||||
{
|
||||
std::shared_ptr<TaskSharedState> sharedState;
|
||||
wrapped_task_base_ptr_t task;
|
||||
std::function<void(StoredTask&)> setFuture;
|
||||
std::any resultData;
|
||||
@@ -465,29 +514,29 @@ protected:
|
||||
|
||||
exception_handler_t uncaughtExceptionHandler_;
|
||||
public:
|
||||
TaskLoop() = default;
|
||||
TaskLoop() MIJIN_NOEXCEPT = default;
|
||||
TaskLoop(const TaskLoop&) = delete;
|
||||
TaskLoop(TaskLoop&&) = delete;
|
||||
virtual ~TaskLoop() = default;
|
||||
virtual ~TaskLoop() MIJIN_NOEXCEPT = default;
|
||||
|
||||
TaskLoop& operator=(const TaskLoop&) = delete;
|
||||
TaskLoop& operator=(TaskLoop&&) = delete;
|
||||
|
||||
void setUncaughtExceptionHandler(exception_handler_t handler) noexcept { uncaughtExceptionHandler_ = std::move(handler); }
|
||||
void setUncaughtExceptionHandler(exception_handler_t handler) MIJIN_NOEXCEPT { uncaughtExceptionHandler_ = std::move(handler); }
|
||||
|
||||
template<typename TResult>
|
||||
inline FuturePtr<TResult> addTask(TaskBase<TResult> task, TaskHandle* outHandle = nullptr) noexcept;
|
||||
inline FuturePtr<TResult> addTask(TaskBase<TResult> task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT;
|
||||
|
||||
virtual void transferCurrentTask(TaskLoop& otherLoop) noexcept = 0;
|
||||
virtual void addStoredTask(StoredTask&& storedTask) noexcept = 0;
|
||||
virtual void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT = 0;
|
||||
virtual void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT = 0;
|
||||
|
||||
[[nodiscard]] static TaskLoop& current() noexcept;
|
||||
[[nodiscard]] static TaskLoop& current() MIJIN_NOEXCEPT;
|
||||
protected:
|
||||
inline TaskStatus tickTask(StoredTask& task);
|
||||
protected:
|
||||
static inline TaskLoop*& currentLoopStorage() noexcept;
|
||||
static inline TaskLoop*& currentLoopStorage() MIJIN_NOEXCEPT;
|
||||
template<typename TResult>
|
||||
static inline void setFutureHelper(StoredTask& storedTask) noexcept;
|
||||
static inline void setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
template<typename TResult = void>
|
||||
@@ -503,13 +552,17 @@ private:
|
||||
std::thread::id threadId_;
|
||||
|
||||
public: // TaskLoop implementation
|
||||
void transferCurrentTask(TaskLoop& otherLoop) noexcept override;
|
||||
void addStoredTask(StoredTask&& storedTask) noexcept override;
|
||||
void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override;
|
||||
void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT override;
|
||||
|
||||
public: // public interface
|
||||
[[nodiscard]] constexpr bool empty() const noexcept { return tasks_.empty() && newTasks_.empty(); }
|
||||
[[nodiscard]] constexpr bool empty() const MIJIN_NOEXCEPT { return tasks_.empty() && newTasks_.empty(); }
|
||||
[[nodiscard]] constexpr std::size_t getNumTasks() const MIJIN_NOEXCEPT { return tasks_.size() + newTasks_.size(); }
|
||||
[[nodiscard]] std::size_t getActiveTasks() const MIJIN_NOEXCEPT;
|
||||
inline CanContinue tick();
|
||||
inline void runUntilDone(IgnoreWaiting ignoreWaiting = IgnoreWaiting::NO);
|
||||
inline void cancelAllTasks() MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] inline std::vector<TaskHandle> getAllTasks() const MIJIN_NOEXCEPT;
|
||||
private:
|
||||
inline void assertCorrectThread() { MIJIN_ASSERT(threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id(), "Unsafe to TaskLoop from different thread!"); }
|
||||
};
|
||||
@@ -525,8 +578,8 @@ private:
|
||||
std::vector<std::jthread> workerThreads_;
|
||||
|
||||
public: // TaskLoop implementation
|
||||
void transferCurrentTask(TaskLoop& otherLoop) noexcept override;
|
||||
void addStoredTask(StoredTask&& storedTask) noexcept override;
|
||||
void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override;
|
||||
void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT override;
|
||||
|
||||
public: // public interface
|
||||
void start(std::size_t numWorkerThreads);
|
||||
@@ -546,15 +599,37 @@ extern thread_local TaskLoop::StoredTask* gCurrentTask;
|
||||
|
||||
inline void throwIfCancelled()
|
||||
{
|
||||
if (gCurrentTask->sharedState->cancelled_)
|
||||
#if MIJIN_COROUTINE_ENABLE_CANCEL
|
||||
if (gCurrentTask->task->sharedState()->cancelled_)
|
||||
{
|
||||
throw TaskCancelled();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void TaskHandle::cancel() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (std::shared_ptr<TaskSharedState> state = state_.lock())
|
||||
{
|
||||
state->cancelled_ = true;
|
||||
state->subTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
Optional<Stacktrace> TaskHandle::getCreationStack() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (std::shared_ptr<TaskSharedState> state = state_.lock())
|
||||
{
|
||||
return state->creationStack_;
|
||||
}
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
#endif // MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
|
||||
template<typename TResult>
|
||||
TaskBase<TResult>::~TaskBase() noexcept
|
||||
TaskBase<TResult>::~TaskBase() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (handle_)
|
||||
{
|
||||
@@ -563,23 +638,21 @@ TaskBase<TResult>::~TaskBase() noexcept
|
||||
}
|
||||
|
||||
template<typename TResult>
|
||||
inline FuturePtr<TResult> TaskLoop::addTask(TaskBase<TResult> task, TaskHandle* outHandle) noexcept
|
||||
inline FuturePtr<TResult> TaskLoop::addTask(TaskBase<TResult> task, TaskHandle* outHandle) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!");
|
||||
task.setLoop(this);
|
||||
|
||||
auto sharedState = std::make_shared<TaskSharedState>();
|
||||
auto future = std::make_shared<Future<TResult>>();
|
||||
auto setFuture = &setFutureHelper<TResult>;
|
||||
|
||||
if (outHandle != nullptr)
|
||||
{
|
||||
*outHandle = TaskHandle(sharedState);
|
||||
*outHandle = TaskHandle(task.sharedState());
|
||||
}
|
||||
|
||||
// add tasks to a seperate vector first as we might be running another task right now
|
||||
addStoredTask(StoredTask{
|
||||
.sharedState = std::move(sharedState),
|
||||
.task = wrapTask(std::move(task)),
|
||||
.setFuture = setFuture,
|
||||
.resultData = future
|
||||
@@ -600,13 +673,16 @@ inline TaskStatus TaskLoop::tickTask(StoredTask& task)
|
||||
while (status == TaskStatus::RUNNING);
|
||||
impl::gCurrentTask = nullptr;
|
||||
|
||||
#if MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING
|
||||
if (task.task && task.task->exception())
|
||||
{
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(task.task->exception());
|
||||
}
|
||||
#if MIJIN_COROUTINE_ENABLE_CANCEL
|
||||
catch(TaskCancelled&) {} // ignore those
|
||||
#endif
|
||||
catch(...)
|
||||
{
|
||||
if (uncaughtExceptionHandler_)
|
||||
@@ -622,6 +698,7 @@ inline TaskStatus TaskLoop::tickTask(StoredTask& task)
|
||||
// TODO: handle the exception somehow, others may be waiting
|
||||
return TaskStatus::FINISHED;
|
||||
}
|
||||
#endif // MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING
|
||||
if (status == TaskStatus::YIELDED || status == TaskStatus::FINISHED)
|
||||
{
|
||||
task.setFuture(task);
|
||||
@@ -629,20 +706,20 @@ inline TaskStatus TaskLoop::tickTask(StoredTask& task)
|
||||
return status;
|
||||
}
|
||||
|
||||
/* static */ inline auto TaskLoop::current() noexcept -> TaskLoop&
|
||||
/* static */ inline auto TaskLoop::current() MIJIN_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 */ auto TaskLoop::currentLoopStorage() MIJIN_NOEXCEPT -> TaskLoop*&
|
||||
{
|
||||
static thread_local TaskLoop* storage = nullptr;
|
||||
return storage;
|
||||
}
|
||||
|
||||
template<typename TResult>
|
||||
/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) noexcept
|
||||
/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT
|
||||
{
|
||||
TaskBase<TResult>& task = *static_cast<TaskBase<TResult>*>(storedTask.task->raw());
|
||||
auto future = std::any_cast<FuturePtr<TResult>>(storedTask.resultData);
|
||||
@@ -703,6 +780,11 @@ inline auto SimpleTaskLoop::tick() -> CanContinue
|
||||
{
|
||||
StoredTask& task = *currentTask_;
|
||||
TaskStatus status = task.task->status();
|
||||
if (status == TaskStatus::WAITING && task.task->sharedState()->cancelled_)
|
||||
{
|
||||
// always continue a cancelled task, even if it was still waiting for a result
|
||||
status = TaskStatus::SUSPENDED;
|
||||
}
|
||||
if (status != TaskStatus::SUSPENDED && status != TaskStatus::YIELDED)
|
||||
{
|
||||
MIJIN_ASSERT(status == TaskStatus::WAITING, "Task with invalid status in task list!");
|
||||
@@ -740,6 +822,29 @@ inline void SimpleTaskLoop::runUntilDone(IgnoreWaiting ignoreWaiting)
|
||||
}
|
||||
}
|
||||
|
||||
inline void SimpleTaskLoop::cancelAllTasks() MIJIN_NOEXCEPT
|
||||
{
|
||||
for (StoredTask& task : mijin::chain(tasks_, newTasks_))
|
||||
{
|
||||
task.task->sharedState()->cancelled_ = true;
|
||||
}
|
||||
for (StoredTask& task : queuedTasks_)
|
||||
{
|
||||
// just discard it
|
||||
(void) task;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::vector<TaskHandle> SimpleTaskLoop::getAllTasks() const MIJIN_NOEXCEPT
|
||||
{
|
||||
std::vector<TaskHandle> result;
|
||||
for (const StoredTask& task : mijin::chain(tasks_, newTasks_))
|
||||
{
|
||||
result.emplace_back(task.task->sharedState());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// utility stuff
|
||||
|
||||
inline TaskAwaitableSuspend c_suspend() {
|
||||
@@ -764,8 +869,11 @@ Task<> c_allDone(const TCollection<FuturePtr<TType>, TTemplateArgs...>& futures)
|
||||
} while (!allDone);
|
||||
}
|
||||
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
#endif
|
||||
[[nodiscard]] inline TaskHandle getCurrentTask() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(impl::gCurrentTask != nullptr, "Attempt to call getCurrentTask() outside of task.");
|
||||
return TaskHandle(impl::gCurrentTask->task->sharedState());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MIJIN_ASYNC_COROUTINE_HPP_INCLUDED
|
||||
|
||||
27
source/mijin/async/coroutine_sleep.hpp
Normal file
27
source/mijin/async/coroutine_sleep.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_ASYNC_COROUTINE_SLEEP_HPP_INCLUDED)
|
||||
#define MIJIN_ASYNC_COROUTINE_SLEEP_HPP_INCLUDED 1
|
||||
|
||||
#include <chrono>
|
||||
#include "./coroutine.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename Rep, typename Period>
|
||||
Task<> c_sleep(std::chrono::duration<Rep, Period> duration)
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
const auto end = now + duration;
|
||||
while (now < end)
|
||||
{
|
||||
co_await c_suspend();
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_ASYNC_COROUTINE_SLEEP_HPP_INCLUDED)
|
||||
@@ -8,8 +8,9 @@
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include "./signal.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../container/optional.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -34,21 +35,21 @@ namespace impl
|
||||
template<typename TValue>
|
||||
struct FutureStorage
|
||||
{
|
||||
std::optional<TValue> value;
|
||||
Optional<TValue> value;
|
||||
|
||||
void setValue(TValue value_) noexcept { value = std::move(value_); }
|
||||
[[nodiscard]] TValue& getValue() noexcept { return value.value(); }
|
||||
void setValue(TValue value_) MIJIN_NOEXCEPT { value = std::move(value_); }
|
||||
[[nodiscard]] TValue& getValue() MIJIN_NOEXCEPT { return value.get(); }
|
||||
|
||||
};
|
||||
|
||||
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<typename TValue>
|
||||
// struct FutureStorage<TValue&>
|
||||
// {
|
||||
// Optional<TValue*> value;
|
||||
//
|
||||
// void setValue(TValue& value_) MIJIN_NOEXCEPT { value = &value_; }
|
||||
// [[nodiscard]] TValue& getValue() const MIJIN_NOEXCEPT { return *value.get(); }
|
||||
// };
|
||||
|
||||
template<>
|
||||
struct FutureStorage<void>
|
||||
@@ -65,19 +66,19 @@ private:
|
||||
public:
|
||||
Future() = default;
|
||||
Future(const Future&) = delete;
|
||||
Future(Future&&) noexcept = default;
|
||||
Future(Future&&) MIJIN_NOEXCEPT = default;
|
||||
public:
|
||||
Future& operator=(const Future&) = delete;
|
||||
Future& operator=(Future&&) noexcept = default;
|
||||
Future& operator=(Future&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr explicit operator bool() const noexcept { return ready(); }
|
||||
constexpr explicit operator bool() const MIJIN_NOEXCEPT { return ready(); }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool operator!() const noexcept { return !ready(); }
|
||||
constexpr bool operator!() const MIJIN_NOEXCEPT { return !ready(); }
|
||||
public: // access
|
||||
[[nodiscard]]
|
||||
constexpr decltype(auto) get() noexcept
|
||||
constexpr decltype(auto) get() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
|
||||
if constexpr(std::is_same_v<TValue, void>) {
|
||||
@@ -88,7 +89,7 @@ public: // access
|
||||
}
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr decltype(auto) get() const noexcept
|
||||
constexpr decltype(auto) get() const MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
|
||||
if constexpr(std::is_same_v<TValue, void>) {
|
||||
@@ -99,20 +100,20 @@ public: // access
|
||||
}
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr bool ready() const noexcept
|
||||
constexpr bool ready() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return isSet_;
|
||||
}
|
||||
public: // modification
|
||||
template<typename TArg> requires (!std::is_same_v<TValue, void>)
|
||||
constexpr void set(TArg&& value) noexcept
|
||||
constexpr void set(TArg&& value) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
|
||||
value_.setValue(std::move(value));
|
||||
isSet_ = true;
|
||||
sigSet.emit();
|
||||
}
|
||||
constexpr void set() noexcept
|
||||
constexpr void set() MIJIN_NOEXCEPT requires (std::is_same_v<TValue, void>)
|
||||
{
|
||||
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
|
||||
isSet_ = true;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/bitarray.hpp"
|
||||
|
||||
namespace mijin
|
||||
@@ -25,9 +26,59 @@ namespace mijin
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename TMessageQueue>
|
||||
class MessageQueueIterator
|
||||
{
|
||||
public:
|
||||
using value_type = typename TMessageQueue::message_t;
|
||||
using reference = value_type&;
|
||||
using pointer = value_type*;
|
||||
private:
|
||||
TMessageQueue* queue_ = nullptr;
|
||||
std::optional<value_type> message_;
|
||||
public:
|
||||
MessageQueueIterator() = default;
|
||||
explicit MessageQueueIterator(TMessageQueue& queue) MIJIN_NOEXCEPT : queue_(&queue) {
|
||||
message_ = queue_->tryPop();
|
||||
}
|
||||
MessageQueueIterator(const MessageQueueIterator&) = delete;
|
||||
MessageQueueIterator(MessageQueueIterator&&) = default;
|
||||
|
||||
MessageQueueIterator& operator=(const MessageQueueIterator&) = delete;
|
||||
MessageQueueIterator& operator=(MessageQueueIterator&&) = default;
|
||||
|
||||
bool operator==(const MessageQueueIterator& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
return message_.has_value() == other.message_.has_value();
|
||||
}
|
||||
bool operator!=(const MessageQueueIterator& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
reference operator*() MIJIN_NOEXCEPT
|
||||
{
|
||||
return message_.value();
|
||||
}
|
||||
|
||||
pointer operator->() MIJIN_NOEXCEPT
|
||||
{
|
||||
return &message_.value();
|
||||
}
|
||||
|
||||
MessageQueueIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
message_ = queue_->tryPop();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TMessage, std::size_t bufferSize = 32>
|
||||
class MessageQueue
|
||||
{
|
||||
public:
|
||||
using message_t = TMessage;
|
||||
using iterator_t = MessageQueueIterator<MessageQueue<TMessage, bufferSize>>;
|
||||
private:
|
||||
std::array<TMessage, bufferSize> messages;
|
||||
mijin::BitArray<bufferSize, true> messageReady;
|
||||
@@ -36,10 +87,10 @@ private:
|
||||
public:
|
||||
MessageQueue() = default;
|
||||
MessageQueue(const MessageQueue&) = delete;
|
||||
MessageQueue(MessageQueue&&) noexcept = delete;
|
||||
MessageQueue(MessageQueue&&) = delete;
|
||||
|
||||
MessageQueue& operator=(const MessageQueue&) = delete;
|
||||
MessageQueue& operator=(MessageQueue&&) noexcept = delete;
|
||||
MessageQueue& operator=(MessageQueue&&) = delete;
|
||||
|
||||
[[nodiscard]] bool tryPushMaybeMove(TMessage& message);
|
||||
[[nodiscard]] bool tryPush(TMessage message) {
|
||||
@@ -48,6 +99,9 @@ public:
|
||||
void push(TMessage message);
|
||||
[[nodiscard]] std::optional<TMessage> tryPop();
|
||||
[[nodiscard]] TMessage wait();
|
||||
|
||||
iterator_t begin() MIJIN_NOEXCEPT { return iterator_t(*this); }
|
||||
iterator_t end() MIJIN_NOEXCEPT { return iterator_t(); }
|
||||
};
|
||||
|
||||
template<typename TRequest, typename TResponse, std::size_t requestBufferSize = 32, std::size_t responseBufferSize = 32>
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
#if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED)
|
||||
#define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/flag.hpp"
|
||||
|
||||
namespace mijin
|
||||
@@ -22,6 +24,9 @@ namespace mijin
|
||||
// public constants
|
||||
//
|
||||
|
||||
using signal_token_t = std::uint32_t;
|
||||
inline constexpr signal_token_t INVALID_SIGNAL_TOKEN = std::numeric_limits<signal_token_t>::max();
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
@@ -33,7 +38,7 @@ class Signal
|
||||
{
|
||||
public:
|
||||
using handler_t = std::function<void(TArgs...)>;
|
||||
using token_t = std::uint32_t;
|
||||
using token_t = signal_token_t;
|
||||
private:
|
||||
struct RegisteredHandler
|
||||
{
|
||||
@@ -50,17 +55,19 @@ private:
|
||||
public:
|
||||
Signal() = default;
|
||||
Signal(const Signal&) = delete;
|
||||
Signal(Signal&&) noexcept = default;
|
||||
Signal(Signal&&) MIJIN_NOEXCEPT = default;
|
||||
public:
|
||||
Signal& operator=(const Signal&) = delete;
|
||||
Signal& operator=(Signal&&) noexcept = default;
|
||||
Signal& operator=(Signal&&) MIJIN_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 token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
|
||||
template<typename TObject, typename TWeak = void>
|
||||
inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...), 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;
|
||||
inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
|
||||
inline void disconnect(token_t token) MIJIN_NOEXCEPT;
|
||||
|
||||
template<typename... TArgs2>
|
||||
inline void emit(TArgs2&&... args) MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -69,7 +76,7 @@ public:
|
||||
|
||||
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
|
||||
inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -86,7 +93,7 @@ inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::we
|
||||
|
||||
template<typename... TArgs>
|
||||
template<typename TObject, typename TWeak>
|
||||
inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) noexcept -> token_t
|
||||
inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -105,7 +112,7 @@ inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
inline void Signal<TArgs...>::disconnect(token_t token) noexcept
|
||||
inline void Signal<TArgs...>::disconnect(token_t token) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -117,7 +124,8 @@ inline void Signal<TArgs...>::disconnect(token_t token) noexcept
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
inline void Signal<TArgs...>::emit(TArgs&&... args) noexcept
|
||||
template<typename... TArgs2>
|
||||
inline void Signal<TArgs...>::emit(TArgs2&&... args) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -132,7 +140,7 @@ inline void Signal<TArgs...>::emit(TArgs&&... args) noexcept
|
||||
// invoke all handlers
|
||||
for (RegisteredHandler& handler : handlers_)
|
||||
{
|
||||
handler.callable(std::forward<TArgs>(args)...);
|
||||
handler.callable(std::forward<TArgs2>(args)...);
|
||||
}
|
||||
|
||||
// remove any oneshot
|
||||
|
||||
65
source/mijin/async/task_mutex.hpp
Normal file
65
source/mijin/async/task_mutex.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef MIJIN_ASYNC_TASK_MUTEX_HPP_INCLUDED
|
||||
#define MIJIN_ASYNC_TASK_MUTEX_HPP_INCLUDED 1
|
||||
|
||||
#include "./coroutine.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
class [[nodiscard]] TaskMutexLock
|
||||
{
|
||||
private:
|
||||
class TaskMutex* mutex_;
|
||||
|
||||
public:
|
||||
explicit TaskMutexLock(class TaskMutex* mutex) MIJIN_NOEXCEPT : mutex_(mutex) {}
|
||||
TaskMutexLock(const TaskMutexLock&) = delete;
|
||||
TaskMutexLock(TaskMutexLock&& other) MIJIN_NOEXCEPT : mutex_(std::exchange(other.mutex_, nullptr)) {}
|
||||
|
||||
TaskMutexLock& operator=(const TaskMutexLock&) = delete;
|
||||
inline TaskMutexLock& operator=(TaskMutexLock&& other) MIJIN_NOEXCEPT;
|
||||
inline ~TaskMutexLock() MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
class TaskMutex
|
||||
{
|
||||
private:
|
||||
bool locked_ = false;
|
||||
public:
|
||||
Task<TaskMutexLock> c_lock()
|
||||
{
|
||||
while (locked_)
|
||||
{
|
||||
co_await c_suspend();
|
||||
}
|
||||
locked_ = true;
|
||||
co_return TaskMutexLock(this);
|
||||
}
|
||||
[[nodiscard]] inline bool isLocked() const MIJIN_NOEXCEPT { return locked_; }
|
||||
|
||||
friend class TaskMutexLock;
|
||||
};
|
||||
|
||||
TaskMutexLock::~TaskMutexLock() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (mutex_)
|
||||
{
|
||||
mutex_->locked_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
TaskMutexLock& TaskMutexLock::operator=(TaskMutexLock&& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (mutex_)
|
||||
{
|
||||
mutex_->locked_ = false;
|
||||
}
|
||||
mutex_ = std::exchange(other.mutex_, nullptr);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MIJIN_ASYNC_TASK_MUTEX_HPP_INCLUDED
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
~BoxedObject()
|
||||
{
|
||||
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!")
|
||||
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!");
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
void construct(TArgs&&... args)
|
||||
{
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
MIJIN_ASSERT(!constructed, "BoxedObject::construct(): Attempt to construct an already constructed object!")
|
||||
MIJIN_ASSERT(!constructed, "BoxedObject::construct(): Attempt to construct an already constructed object!");
|
||||
constructed = true;
|
||||
#endif
|
||||
std::construct_at(&object_, std::forward<TArgs>(args)...);
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
void destroy()
|
||||
{
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::destroy(): Attempt to destroy a not constructed object!")
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::destroy(): Attempt to destroy a not constructed object!");
|
||||
constructed = false;
|
||||
#endif
|
||||
std::destroy_at(&object_);
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
void copyTo(BoxedObject& other) const
|
||||
{
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!")
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!");
|
||||
#endif
|
||||
other.construct(object_);
|
||||
}
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
void moveTo(BoxedObject& other)
|
||||
{
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!")
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!");
|
||||
#endif
|
||||
other.construct(std::move(object_));
|
||||
destroy();
|
||||
@@ -104,7 +104,7 @@ public:
|
||||
[[nodiscard]] T& get()
|
||||
{
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!")
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!");
|
||||
#endif
|
||||
return object_;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
[[nodiscard]] const T& get() const
|
||||
{
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!")
|
||||
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!");
|
||||
#endif
|
||||
return object_;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ public:
|
||||
template<typename TMap>
|
||||
MapView(TMap& map) -> MapView<typename TMap::key_type, typename TMap::mapped_type, TMap>;
|
||||
|
||||
template<typename TMap>
|
||||
MapView(const TMap& map) -> MapView<typename TMap::key_type, const typename TMap::mapped_type, const TMap>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
84
source/mijin/container/memory_view.hpp
Normal file
84
source/mijin/container/memory_view.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)
|
||||
#define MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <span>
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename TBytes>
|
||||
class MemoryViewBase
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
private:
|
||||
std::span<TBytes> bytes_;
|
||||
public:
|
||||
MemoryViewBase() noexcept = default;
|
||||
MemoryViewBase(const MemoryViewBase&) noexcept = default;
|
||||
MemoryViewBase(copy_const_t<TBytes, void>* data, std::size_t size) noexcept : bytes_(static_cast<TBytes*>(data), size) {}
|
||||
|
||||
MemoryViewBase& operator=(const MemoryViewBase&) noexcept = default;
|
||||
|
||||
[[nodiscard]] void* data() noexcept { return bytes_.data(); }
|
||||
[[nodiscard]] const void* data() const noexcept { return bytes_.data(); }
|
||||
[[nodiscard]] size_type byteSize() const noexcept { return bytes_.size(); }
|
||||
[[nodiscard]] bool empty() const noexcept { return bytes_.empty(); }
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::span<T> makeSpan();
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::span<const T> makeSpan() const;
|
||||
};
|
||||
using MemoryView = MemoryViewBase<std::byte>;
|
||||
using ConstMemoryView = MemoryViewBase<const std::byte>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename TBytes>
|
||||
template<typename T>
|
||||
std::span<T> MemoryViewBase<TBytes>::makeSpan()
|
||||
{
|
||||
static_assert(!std::is_const_v<TBytes>, "Cannot create writable spans from const memory views.");
|
||||
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type.");
|
||||
return {
|
||||
std::bit_cast<T*>(bytes_.data()),
|
||||
std::bit_cast<T*>(bytes_.data() + bytes_.size())
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TBytes>
|
||||
template<typename T>
|
||||
std::span<const T> MemoryViewBase<TBytes>::makeSpan() const
|
||||
{
|
||||
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type.");
|
||||
return {
|
||||
std::bit_cast<const T*>(bytes_.data()),
|
||||
std::bit_cast<const T*>(bytes_.data() + bytes_.size())
|
||||
};
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)
|
||||
@@ -5,9 +5,11 @@
|
||||
#define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../util/concepts.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -33,7 +35,7 @@ struct OptionalStorage
|
||||
std::uint8_t used = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool empty() const noexcept { return !used; }
|
||||
constexpr bool empty() const noexcept { return !used; } // NOLINT(clang-analyzer-core.uninitialized.UndefReturn)
|
||||
|
||||
template<typename... TArgs>
|
||||
constexpr void emplace(TArgs&&... args) noexcept
|
||||
@@ -108,8 +110,10 @@ static constexpr NullOptional NULL_OPTIONAL;
|
||||
template<typename TValue>
|
||||
class Optional
|
||||
{
|
||||
public:
|
||||
using value_t = TValue;
|
||||
private:
|
||||
impl::OptionalStorage<TValue> storage_;
|
||||
impl::OptionalStorage<TValue> storage_ = {};
|
||||
public:
|
||||
constexpr Optional() = default;
|
||||
constexpr Optional(NullOptional) noexcept {}
|
||||
@@ -154,6 +158,56 @@ public:
|
||||
[[nodiscard]] inline const std::remove_reference_t<TValue>& get() const noexcept;
|
||||
[[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); }
|
||||
inline void reset() noexcept;
|
||||
|
||||
|
||||
template<typename TCallable, typename TErrorCallable = int>
|
||||
auto then(TCallable&& onSuccess, TErrorCallable&& onError = 0)
|
||||
{
|
||||
return thenImpl(*this, std::forward<TCallable>(onSuccess), std::forward<TErrorCallable>(onError));
|
||||
}
|
||||
|
||||
template<typename TCallable, typename TErrorCallable = int>
|
||||
auto then(TCallable&& onSuccess, TErrorCallable&& onError = 0) const
|
||||
{
|
||||
return thenImpl(*this, std::forward<TCallable>(onSuccess), std::forward<TErrorCallable>(onError));
|
||||
}
|
||||
private:
|
||||
template<typename TSelf, typename TCallable, typename TErrorCallable = int>
|
||||
static auto thenImpl(TSelf&& self, TCallable&& onSuccess, TErrorCallable&& onError = 0)
|
||||
{
|
||||
using result_t = std::invoke_result_t<TCallable, std::add_rvalue_reference_t<TValue>>;
|
||||
|
||||
if constexpr (std::is_same_v<result_t, void>)
|
||||
{
|
||||
if (!self.empty())
|
||||
{
|
||||
std::invoke(std::forward<TCallable>(onSuccess), self.get());
|
||||
}
|
||||
else if constexpr (!std::is_same_v<TErrorCallable, int>)
|
||||
{
|
||||
std::invoke(std::forward<TErrorCallable>(onError));
|
||||
}
|
||||
}
|
||||
else if constexpr (requires(result_t obj) { {obj == NULL_OPTIONAL} -> std::convertible_to<bool>; })
|
||||
{
|
||||
if (!self.empty())
|
||||
{
|
||||
return std::invoke(std::forward<TCallable>(onSuccess), self.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (!std::is_same_v<TErrorCallable, int>)
|
||||
{
|
||||
std::invoke(std::forward<TErrorCallable>(onError));
|
||||
}
|
||||
return result_t(NULL_OPTIONAL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<TCallable>, "Callable must produce void or optional type.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
@@ -173,7 +227,12 @@ Optional<TValue>::Optional(Optional&& other) noexcept
|
||||
{
|
||||
if (other)
|
||||
{
|
||||
if constexpr (!std::is_reference_v<TValue>) {
|
||||
emplace(std::move(other.get()));
|
||||
}
|
||||
else {
|
||||
emplace(other.get());
|
||||
}
|
||||
other.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#if !defined(MIJIN_CONTAINER_STRIDE_SPAN_HPP_INCLUDED)
|
||||
#define MIJIN_CONTAINER_STRIDE_SPAN_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
@@ -117,10 +118,15 @@ public:
|
||||
StrideSpan& operator=(const StrideSpan&) = default;
|
||||
auto operator<=>(const StrideSpan&) const noexcept = default;
|
||||
|
||||
constexpr operator bool() const noexcept { return !empty(); }
|
||||
constexpr bool operator!() const noexcept { return empty(); }
|
||||
explicit operator std::span<T>() const noexcept;
|
||||
|
||||
reference operator[](size_type index);
|
||||
const_reference operator[](size_type index) const;
|
||||
|
||||
[[nodiscard]] constexpr size_type size() const noexcept { return bytediff(begin_, end_) / strideBytes_; }
|
||||
[[nodiscard]] constexpr size_type size() const noexcept { return strideBytes_ == 0 ? 0 : bytediff(begin_, end_) / strideBytes_; }
|
||||
[[nodiscard]] constexpr bool empty() const noexcept { return begin_ == end_; }
|
||||
|
||||
[[nodiscard]] iterator begin() { return StrideSpanIterator<T>(begin_, strideBytes_, begin_, end_); }
|
||||
[[nodiscard]] const_iterator begin() const { return StrideSpanIterator<const T>(begin_, strideBytes_, begin_, end_); }
|
||||
@@ -134,6 +140,13 @@ public:
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename T>
|
||||
StrideSpan<T>::operator std::span<T>() const noexcept
|
||||
{
|
||||
MIJIN_ASSERT(strideBytes_ == sizeof(T), "Can't convert to regular span if stride isn't exactly size of type.");
|
||||
return {&*begin_, &*end_};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
StrideSpanIterator<T>& StrideSpanIterator<T>::operator+=(difference_type diff)
|
||||
{
|
||||
@@ -225,7 +238,7 @@ template<typename T>
|
||||
auto StrideSpan<T>::operator[](size_type index) const -> const_reference
|
||||
{
|
||||
MIJIN_ASSERT(index < size(), "Attempting to access StrideSpan out of bounds.");
|
||||
return byteoffset(begin_, index * strideBytes_);
|
||||
return *byteoffset(begin_, index * strideBytes_);
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
#if !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)
|
||||
#define MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
@@ -34,9 +37,13 @@ public:
|
||||
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(); }
|
||||
auto operator<=>(const TypelessBuffer&) const noexcept = default;
|
||||
|
||||
[[nodiscard]] void* data() noexcept { return bytes_.data(); }
|
||||
[[nodiscard]] const void* data() const noexcept { return bytes_.data(); }
|
||||
[[nodiscard]] size_type byteSize() const noexcept { return bytes_.size(); }
|
||||
[[nodiscard]] size_type byteCapacity() const noexcept { return bytes_.capacity(); }
|
||||
[[nodiscard]] bool empty() const noexcept { return bytes_.empty(); }
|
||||
void resize(size_type numBytes) { bytes_.resize(numBytes); }
|
||||
void reserve(size_type numBytes) { bytes_.reserve(numBytes); }
|
||||
|
||||
@@ -48,6 +55,18 @@ public:
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::span<const T> makeSpan() const;
|
||||
|
||||
template<typename TChar = char, typename TTraits = std::char_traits<TChar>>
|
||||
[[nodiscard]] std::basic_string_view<TChar, TTraits> makeStringView() const ;
|
||||
|
||||
template<typename T>
|
||||
void append(std::span<const T> data);
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] T& dataAt(size_type offset) MIJIN_NOEXCEPT;
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] const T& dataAt(size_type offset) const MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -82,6 +101,12 @@ public:
|
||||
MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer.");
|
||||
buffer_->reserve(numElements * sizeof(T));
|
||||
}
|
||||
void append(std::span<const T> data)
|
||||
{
|
||||
MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot append to a view without a buffer.");
|
||||
resize(size() + data.size());
|
||||
std::copy(data.begin(), data.end(), begin() + size() - data.size());
|
||||
}
|
||||
|
||||
[[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; }
|
||||
@@ -125,6 +150,38 @@ std::span<const T> TypelessBuffer::makeSpan() const
|
||||
std::bit_cast<const T*>(bytes_.data() + bytes_.size())
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> TypelessBuffer::makeStringView() const
|
||||
{
|
||||
MIJIN_ASSERT(bytes_.size() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type.");
|
||||
return {std::bit_cast<const TChar*>(bytes_.data()), bytes_.size() / sizeof(TChar)};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void TypelessBuffer::append(std::span<const T> data)
|
||||
{
|
||||
bytes_.resize(bytes_.size() + data.size_bytes());
|
||||
std::memcpy(bytes_.data() + bytes_.size() - data.size_bytes(), data.data(), data.size_bytes());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& TypelessBuffer::dataAt(size_type offset) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
|
||||
MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range.");
|
||||
|
||||
return *std::bit_cast<T*>(bytes_.data() + offset);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T& TypelessBuffer::dataAt(size_type offset) const MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
|
||||
MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range.");
|
||||
|
||||
return *std::bit_cast<const T*>(bytes_.data() + offset);
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)
|
||||
|
||||
97
source/mijin/container/untyped_vector.hpp
Normal file
97
source/mijin/container/untyped_vector.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_CONTAINER_UNTYPED_VECTOR_HPP_INCLUDED)
|
||||
#define MIJIN_CONTAINER_UNTYPED_VECTOR_HPP_INCLUDED 1
|
||||
|
||||
#include <stdexcept>
|
||||
#include "./typeless_buffer.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
class UntypedVector
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
private:
|
||||
std::size_t elementSize_ = 0;
|
||||
TypelessBuffer buffer_;
|
||||
public:
|
||||
explicit UntypedVector(size_type elementSize = 0, size_type count = 0) : elementSize_(elementSize)
|
||||
{
|
||||
resize(count);
|
||||
}
|
||||
|
||||
std::span<std::byte> operator[](size_type index)
|
||||
{
|
||||
return at(index);
|
||||
}
|
||||
|
||||
std::span<const std::byte> operator[](size_type index) const
|
||||
{
|
||||
return at(index);
|
||||
}
|
||||
|
||||
auto operator<=>(const UntypedVector&) const noexcept = default;
|
||||
|
||||
void resize(size_type count)
|
||||
{
|
||||
MIJIN_ASSERT(elementSize_ > 0, "Cannot allocate without element size.");
|
||||
buffer_.resize(count * elementSize_);
|
||||
}
|
||||
|
||||
void reserve(size_type count)
|
||||
{
|
||||
MIJIN_ASSERT(elementSize_ > 0, "Cannot allocate without element size.");
|
||||
buffer_.reserve(count * elementSize_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::span<std::byte> at(size_type index)
|
||||
{
|
||||
if (index > size())
|
||||
{
|
||||
throw std::out_of_range("Index out of range.");
|
||||
}
|
||||
return buffer_.makeSpan<std::byte>().subspan(index * elementSize_, elementSize_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::span<const std::byte> at(size_type index) const
|
||||
{
|
||||
if (index > size())
|
||||
{
|
||||
throw std::out_of_range("Index out of range.");
|
||||
}
|
||||
return buffer_.makeSpan<std::byte>().subspan(index * elementSize_, elementSize_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
size_type size() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (elementSize_ == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return buffer_.byteSize() / elementSize_;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
size_type capacity() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (elementSize_ == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return buffer_.byteCapacity() / elementSize_;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t elementSize() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return elementSize_;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_CONTAINER_UNTYPED_VECTOR_HPP_INCLUDED)
|
||||
272
source/mijin/container/vector_map.hpp
Normal file
272
source/mijin/container/vector_map.hpp
Normal file
@@ -0,0 +1,272 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED)
|
||||
#define MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED 1
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include "./boxed_object.hpp"
|
||||
#include "./optional.hpp"
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TKey, typename TValue>
|
||||
struct VectorMapIterator
|
||||
{
|
||||
public:
|
||||
using difference_type = std::ptrdiff_t;
|
||||
private:
|
||||
Optional<std::pair<TKey&, TValue&>> ref_;
|
||||
public:
|
||||
VectorMapIterator(TKey* key, TValue* value)
|
||||
{
|
||||
ref_.emplace(std::tie(*key, *value));
|
||||
}
|
||||
VectorMapIterator(const VectorMapIterator& other) MIJIN_NOEXCEPT : VectorMapIterator(&other.ref_->first, &other.ref_->second) {}
|
||||
~VectorMapIterator() noexcept
|
||||
{
|
||||
ref_.reset();
|
||||
}
|
||||
|
||||
VectorMapIterator& operator=(const VectorMapIterator& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
ref_.reset();
|
||||
ref_.emplace(std::tie(other.ref_->first, other.ref_->second));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const VectorMapIterator& other) const MIJIN_NOEXCEPT { return &ref_->first == &other.ref_->first; }
|
||||
bool operator!=(const VectorMapIterator& other) const MIJIN_NOEXCEPT { return &ref_->first != &other.ref_->first; }
|
||||
|
||||
std::pair<TKey&, TValue&> operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return *ref_;
|
||||
}
|
||||
const std::pair<TKey&, TValue&>* operator->() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return &*ref_;
|
||||
}
|
||||
|
||||
VectorMapIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
TKey* oldKey = &ref_->first;
|
||||
TValue* oldValue = &ref_->second;
|
||||
ref_.reset();
|
||||
ref_.emplace(std::tie(*(++oldKey), *(++oldValue)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
VectorMapIterator operator++(int) const MIJIN_NOEXCEPT
|
||||
{
|
||||
VectorMapIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
VectorMapIterator& operator--() MIJIN_NOEXCEPT
|
||||
{
|
||||
TKey* oldKey = &ref_->first;
|
||||
TValue* oldValue = &ref_->second;
|
||||
ref_.reset();
|
||||
ref_.emplace(std::tie(*(--oldKey), *(--oldValue)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
VectorMapIterator operator--(int) const MIJIN_NOEXCEPT
|
||||
{
|
||||
VectorMapIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
VectorMapIterator operator+(difference_type diff) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return VectorMapIterator(&ref_->first + diff, &ref_->second + diff);
|
||||
}
|
||||
|
||||
VectorMapIterator operator-(difference_type diff) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return VectorMapIterator(&ref_->first - diff, &ref_->second - diff);
|
||||
}
|
||||
|
||||
difference_type operator-(const VectorMapIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return &ref_->first - &other.ref_->first;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TKey, typename TValue, typename TKeyAllocator = std::allocator<TKey>, typename TValueAllocator = std::allocator<TValue>>
|
||||
class VectorMap
|
||||
{
|
||||
public:
|
||||
using key_type = TKey;
|
||||
using mapped_type = TValue;
|
||||
using value_type = std::pair<const TKey, TValue>;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using iterator = VectorMapIterator<const TKey, TValue>;
|
||||
using const_iterator = VectorMapIterator<const TKey, const TValue>;
|
||||
private:
|
||||
std::vector<TKey, TKeyAllocator> keys_;
|
||||
std::vector<TValue, TValueAllocator> values_;
|
||||
public:
|
||||
VectorMap() noexcept = default;
|
||||
VectorMap(const VectorMap&) = default;
|
||||
VectorMap(VectorMap&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
VectorMap& operator=(const VectorMap&) = default;
|
||||
VectorMap& operator=(VectorMap&&) MIJIN_NOEXCEPT = default;
|
||||
auto operator<=>(const VectorMap& other) const noexcept = default;
|
||||
|
||||
TValue& operator[](const TKey& key)
|
||||
{
|
||||
auto it = find(key);
|
||||
if (it != end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return emplace(key, TValue()).first->second;
|
||||
}
|
||||
|
||||
const TValue& operator[](const TKey& key) const
|
||||
{
|
||||
return at(key);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
iterator begin() MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
|
||||
[[nodiscard]]
|
||||
const_iterator begin() const MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
|
||||
[[nodiscard]]
|
||||
const_iterator cbegin() const MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
|
||||
[[nodiscard]]
|
||||
iterator end() MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
|
||||
[[nodiscard]]
|
||||
const_iterator end() const MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
|
||||
[[nodiscard]]
|
||||
const_iterator cend() const MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
|
||||
|
||||
[[nodiscard]]
|
||||
TValue& at(const TKey& key)
|
||||
{
|
||||
auto it = find(key);
|
||||
if (it == end())
|
||||
{
|
||||
throw std::out_of_range("key not found in map");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const TValue& at(const TKey& key) const
|
||||
{
|
||||
auto it = find(key);
|
||||
if (it == end())
|
||||
{
|
||||
throw std::out_of_range("key not found in map");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool empty() const MIJIN_NOEXCEPT { return keys_.empty(); }
|
||||
[[nodiscard]]
|
||||
size_type size() const MIJIN_NOEXCEPT { return keys_.size(); }
|
||||
[[nodiscard]]
|
||||
size_type max_size() const MIJIN_NOEXCEPT { return std::min(keys_.max_size(), values_.max_size()); }
|
||||
void reserve(std::size_t size)
|
||||
{
|
||||
keys_.reserve(size);
|
||||
values_.reserve(size);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
keys_.clear();
|
||||
values_.clear();
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
std::pair<iterator, bool> emplace(TArgs&&... args)
|
||||
{
|
||||
std::pair<TKey, TValue> asPair(std::forward<TArgs>(args)...);
|
||||
auto it = find(asPair.first);
|
||||
if (it != end())
|
||||
{
|
||||
return std::make_pair(std::move(it), false);
|
||||
}
|
||||
keys_.push_back(std::move(asPair.first));
|
||||
values_.push_back(std::move(asPair.second));
|
||||
return std::make_pair(iterator(&keys_.back(), &values_.back()), true);
|
||||
}
|
||||
|
||||
iterator erase(iterator pos) MIJIN_NOEXCEPT
|
||||
{
|
||||
const std::ptrdiff_t idx = &pos->first - &keys_[0];
|
||||
return eraseImpl(idx);
|
||||
}
|
||||
|
||||
iterator erase(const_iterator pos) MIJIN_NOEXCEPT
|
||||
{
|
||||
const std::ptrdiff_t idx = &pos->first - &keys_[0];
|
||||
return eraseImpl(idx);
|
||||
}
|
||||
|
||||
iterator erase(iterator first, iterator last) MIJIN_NOEXCEPT
|
||||
{
|
||||
const std::ptrdiff_t idx = &first->first - &keys_[0];
|
||||
const std::ptrdiff_t count = last - first;
|
||||
return eraseImpl(idx, count);
|
||||
}
|
||||
|
||||
iterator erase(const_iterator first, const_iterator last) MIJIN_NOEXCEPT
|
||||
{
|
||||
const std::ptrdiff_t idx = &first->first - &keys_[0];
|
||||
const std::ptrdiff_t count = last - first;
|
||||
return eraseImpl(idx, count);
|
||||
}
|
||||
|
||||
iterator find(const TKey& key) MIJIN_NOEXCEPT
|
||||
{
|
||||
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
|
||||
{
|
||||
if (keys_[idx] == key)
|
||||
{
|
||||
return iterator(&keys_[idx], &values_[idx]);
|
||||
}
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
const_iterator find(const TKey& key) const MIJIN_NOEXCEPT
|
||||
{
|
||||
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
|
||||
{
|
||||
if (keys_[idx] == key)
|
||||
{
|
||||
return const_iterator(&keys_[idx], &values_[idx]);
|
||||
}
|
||||
}
|
||||
return end();
|
||||
}
|
||||
private:
|
||||
iterator eraseImpl(std::ptrdiff_t idx, std::ptrdiff_t count = 1) MIJIN_NOEXCEPT
|
||||
{
|
||||
auto itKey = keys_.begin() + idx;
|
||||
auto itValue = values_.begin() + idx;
|
||||
itKey = keys_.erase(itKey, itKey + count);
|
||||
itValue = values_.erase(itValue, itValue + count);
|
||||
return itKey == keys_.end() ? end() : iterator(&*itKey, &*itValue); // cannot dereference the iterators if the last element was removed
|
||||
}
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
#endif // MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED
|
||||
@@ -8,20 +8,37 @@
|
||||
#include <cstdlib>
|
||||
#include <source_location>
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
#if MIJIN_THROWING_ASSERTS
|
||||
#include <sstream>
|
||||
#include <stdexcept> // I'd prefer mijin Exceptions here, but that would result in a circual include :/
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "kernel32")
|
||||
extern "C" __declspec(dllimport) void __stdcall DebugBreak();
|
||||
#define MIJIN_TRAP() DebugBreak()
|
||||
#define MIJIN_FUNC() __FUNCSIG__
|
||||
#else // _WIN32
|
||||
#include <csignal>
|
||||
#define MIJIN_TRAP() (void) std::raise(SIGTRAP)
|
||||
#include <unistd.h>
|
||||
#define MIJIN_TRAP() \
|
||||
{ \
|
||||
const pid_t tid = gettid(); \
|
||||
const pid_t pid = getpid(); \
|
||||
__asm__ __volatile__ \
|
||||
( \
|
||||
"syscall" \
|
||||
: \
|
||||
: "D"(pid), "S"(tid), "d"(5), "a"(0xea) \
|
||||
: "rcx", "r11", "memory" \
|
||||
); \
|
||||
}
|
||||
#define MIJIN_FUNC() "" // TODO: __PRETTY_FUNCTION__ is not working for some reason -.-
|
||||
#endif // !_WIN32
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
@@ -35,7 +52,7 @@ switch (mijin::handleError(msg, source_loc)) \
|
||||
break; \
|
||||
case mijin::ErrorHandling::TRAP: \
|
||||
MIJIN_TRAP(); \
|
||||
[[fallthrough]]; \
|
||||
break; \
|
||||
default: /* ABORT */ \
|
||||
std::abort(); \
|
||||
}
|
||||
@@ -49,6 +66,8 @@ std::abort()
|
||||
|
||||
// TODO: make ignoreAll work (static variables cannot be used in constexpr functions)
|
||||
#define MIJIN_ASSERT(condition, msg) \
|
||||
do \
|
||||
{ \
|
||||
if (!static_cast<bool>(condition)) \
|
||||
{ \
|
||||
/* static bool ignoreAll = false; */ \
|
||||
@@ -71,12 +90,13 @@ if (!static_cast<bool>(condition))
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
#define MIJIN_ASSERT_FATAL(condition, msg) \
|
||||
if (!static_cast<bool>(condition)) \
|
||||
{ \
|
||||
MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \
|
||||
MIJIN_FATAL("Debug assertion failed: " #condition "\nMessage: " msg); \
|
||||
}
|
||||
#else // MIJIN_DEBUG
|
||||
#define MIJIN_ERROR(...)
|
||||
@@ -93,6 +113,10 @@ if (!static_cast<bool>(condition)) \
|
||||
// public types
|
||||
//
|
||||
|
||||
#if defined(ERROR)
|
||||
#error "Someone included windows.h! Include mijin/util/winundef.h."
|
||||
#endif
|
||||
|
||||
enum class AssertionResult
|
||||
{
|
||||
ABORT = -1, // call std::abort()
|
||||
@@ -108,6 +132,49 @@ enum class ErrorHandling
|
||||
ABORT = 2
|
||||
};
|
||||
|
||||
#if MIJIN_THROWING_ASSERTS
|
||||
namespace impl
|
||||
{
|
||||
inline std::string formatAssertionExceptionMessage(const char* condition, const char* message, const std::source_location& location)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Assertion failed!\n"
|
||||
<< "Condition: " << condition << "\n"
|
||||
<< "Message: " << message << "\n"
|
||||
<< "Location: " << location.file_name() << ":" << location.line() << ":" << location.column() << "\n"
|
||||
<< "Function: " << location.function_name();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
inline std::string formatErrorExceptionMessage(const char* message, const std::source_location& location)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Mijin error!\n"
|
||||
<< "Message: " << message << "\n"
|
||||
<< "Location: " << location.file_name() << ":" << location.line() << ":" << location.column() << "\n"
|
||||
<< "Function: " << location.function_name();
|
||||
return oss.str();
|
||||
}
|
||||
}
|
||||
class AssertionException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
AssertionException(const char* condition, const char* message, const std::source_location& location)
|
||||
: std::runtime_error(impl::formatAssertionExceptionMessage(condition, message, location))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class ErrorException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
ErrorException(const char* message, const std::source_location& location)
|
||||
: std::runtime_error(impl::formatErrorExceptionMessage(message, location))
|
||||
{
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
@@ -115,9 +182,14 @@ enum class ErrorHandling
|
||||
#ifdef MIJIN_USE_CUSTOM_ASSERTION_HANDLER
|
||||
AssertionResult handleAssert(const char* condition,
|
||||
const char* message, const std::source_location& location);
|
||||
#elif MIJIN_THROWING_ASSERTS
|
||||
inline AssertionResult handleAssert(const char* condition, const char* message, const std::source_location& location)
|
||||
{
|
||||
throw AssertionException(condition, message, location);
|
||||
}
|
||||
#else
|
||||
constexpr AssertionResult handleAssert(const char* /* condition */,
|
||||
const char* /* message */, const std::source_location& /* location */)
|
||||
constexpr AssertionResult handleAssert(const char* /* condition */, const char* /* message */,
|
||||
const std::source_location& /* location */)
|
||||
{
|
||||
return AssertionResult::ERROR;
|
||||
}
|
||||
@@ -126,13 +198,17 @@ constexpr AssertionResult handleAssert(const char* /* condition */,
|
||||
#ifdef MIJIN_USE_CUSTOM_ERROR_HANDLER
|
||||
ErrorHandling handleError(const char* message, const std::source_location& location);
|
||||
#else
|
||||
inline ErrorHandling handleError(const char* message, const std::source_location& location) noexcept
|
||||
inline ErrorHandling handleError(const char* message, const std::source_location& location) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_THROWING_ASSERTS
|
||||
throw ErrorException(message, location);
|
||||
#else
|
||||
std::puts(message);
|
||||
std::printf("Function: %s\n", location.function_name());
|
||||
std::printf("Location: %s:%d:%d\n", location.file_name(), location.line(), location.column());
|
||||
(void) std::fflush(stdout);
|
||||
return ErrorHandling::TRAP;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
|
||||
#include "./stacktrace.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "../detect.hpp"
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC
|
||||
#define MIJIN_USE_LIBBACKTRACE 1
|
||||
#else
|
||||
#define MIJIN_USE_LIBBACKTRACE 0
|
||||
#endif
|
||||
|
||||
#if MIJIN_USE_LIBBACKTRACE
|
||||
#include <backtrace.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -32,15 +42,18 @@ struct BacktraceData
|
||||
// internal variables
|
||||
//
|
||||
|
||||
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
|
||||
|
||||
//
|
||||
// internal functions
|
||||
//
|
||||
|
||||
#if MIJIN_USE_LIBBACKTRACE
|
||||
int backtraceFullCallback(void* data, std::uintptr_t programCounter, const char* filename, int lineno, const char* function)
|
||||
{
|
||||
BacktraceData& btData = *static_cast<BacktraceData*>(data);
|
||||
btData.stackframes.push_back({
|
||||
.address = reinterpret_cast<void*>(programCounter),
|
||||
.address = reinterpret_cast<void*>(programCounter), // NOLINT(performance-no-int-to-ptr)
|
||||
.filename = filename ? filename : "<unknown>",
|
||||
.function = function ? function : "<unknown>",
|
||||
.lineNumber = lineno
|
||||
@@ -54,15 +67,17 @@ void backtraceErrorCallback(void* data, const char* msg, int /* errnum */)
|
||||
btData.error = msg;
|
||||
}
|
||||
|
||||
backtrace_state* gBacktraceState = nullptr;
|
||||
thread_local backtrace_state* gBacktraceState = nullptr;
|
||||
#endif // MIJIN_USE_LIBBACKTRACE
|
||||
} // namespace
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
Result<Stacktrace> captureStacktrace(unsigned skipFrames) noexcept
|
||||
Result<Stacktrace> captureStacktrace(unsigned skipFrames) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_USE_LIBBACKTRACE
|
||||
BacktraceData btData;
|
||||
if (gBacktraceState == nullptr)
|
||||
{
|
||||
@@ -83,6 +98,48 @@ Result<Stacktrace> captureStacktrace(unsigned skipFrames) noexcept
|
||||
}
|
||||
|
||||
return Stacktrace(std::move(btData.stackframes));
|
||||
#else // MIJIN_USE_LIBBACKTRACE
|
||||
(void) skipFrames;
|
||||
return {}; // TODO
|
||||
#endif // MIJIN_USE_LIBBACKTRACE
|
||||
}
|
||||
|
||||
const Optional<Stacktrace>& getExceptionStacktrace() MIJIN_NOEXCEPT
|
||||
{
|
||||
return gCurrentExceptionStackTrace;
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#if MIJIN_STDLIB == MIJIN_STDLIB_GLIBC
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using cxa_throw_type = void (*)(void*, std::type_info*, void(*)(void*));
|
||||
using cxa_rethrow_type = void (*)();
|
||||
|
||||
cxa_throw_type orig_cxa_throw = nullptr; // Address of the original __cxa_throw
|
||||
// cxa_rethrow_type orig_cxa_rethrow = nullptr; // Address of the original __cxa_rethrow
|
||||
|
||||
extern "C" void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*)) // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
|
||||
{
|
||||
if (!orig_cxa_throw) {
|
||||
orig_cxa_throw = reinterpret_cast<cxa_throw_type>(dlsym(RTLD_NEXT, "__cxa_throw"));
|
||||
}
|
||||
if (mijin::Result<mijin::Stacktrace> stacktrace = mijin::captureStacktrace(); stacktrace.isSuccess())
|
||||
{
|
||||
mijin::gCurrentExceptionStackTrace = std::move(*stacktrace);
|
||||
}
|
||||
else
|
||||
{
|
||||
mijin::gCurrentExceptionStackTrace.reset();
|
||||
}
|
||||
if (orig_cxa_throw) {
|
||||
orig_cxa_throw(thrown_exception, tinfo, dest);
|
||||
}
|
||||
else {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#if __has_include(<fmt/format.h>)
|
||||
# include <fmt/format.h>
|
||||
#endif
|
||||
#include "./symbol_info.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../types/result.hpp"
|
||||
#include "../util/iterators.hpp"
|
||||
|
||||
@@ -42,19 +46,20 @@ public:
|
||||
Stacktrace() = default;
|
||||
Stacktrace(const Stacktrace&) = default;
|
||||
Stacktrace(Stacktrace&&) = default;
|
||||
explicit Stacktrace(std::vector<Stackframe> frames) noexcept : frames_(std::move(frames)) {}
|
||||
explicit Stacktrace(std::vector<Stackframe> frames) MIJIN_NOEXCEPT : frames_(std::move(frames)) {}
|
||||
|
||||
Stacktrace& operator=(const Stacktrace&) = default;
|
||||
Stacktrace& operator=(Stacktrace&&) = default;
|
||||
|
||||
[[nodiscard]] const std::vector<Stackframe>& getFrames() const noexcept { return frames_; }
|
||||
[[nodiscard]] const std::vector<Stackframe>& getFrames() const MIJIN_NOEXCEPT { return frames_; }
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
[[nodiscard]] Result<Stacktrace> captureStacktrace(unsigned skipFrames = 0) noexcept;
|
||||
[[nodiscard]] Result<Stacktrace> captureStacktrace(unsigned skipFrames = 0) MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] const Optional<Stacktrace>& getExceptionStacktrace() MIJIN_NOEXCEPT;
|
||||
|
||||
template<typename TStream>
|
||||
TStream& operator<<(TStream& stream, const Stackframe& stackframe)
|
||||
@@ -67,7 +72,7 @@ TStream& operator<<(TStream& stream, const Stackframe& stackframe)
|
||||
template<typename TStream>
|
||||
TStream& operator<<(TStream& stream, const Stacktrace& stacktrace)
|
||||
{
|
||||
const int oldWidth = stream.width();
|
||||
const std::streamsize oldWidth = stream.width();
|
||||
const std::ios::fmtflags oldFlags = stream.flags();
|
||||
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
|
||||
stream << std::left;
|
||||
@@ -82,4 +87,56 @@ TStream& operator<<(TStream& stream, const Stacktrace& stacktrace)
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
#if __has_include(<fmt/format.h>)
|
||||
template<>
|
||||
struct fmt::formatter<mijin::Stackframe>
|
||||
{
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
|
||||
if (it != end && *it != '}') FMT_THROW(format_error("invalid format"));
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename TContext>
|
||||
auto format(const mijin::Stackframe& stackframe, TContext& ctx) const -> decltype(ctx.out())
|
||||
{
|
||||
auto it = ctx.out();
|
||||
it = fmt::format_to(it, "[{}] {}:{} in {}", stackframe.address, stackframe.filename,
|
||||
stackframe.lineNumber, mijin::demangleCPPIdentifier(stackframe.function.c_str()));
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct fmt::formatter<mijin::Stacktrace>
|
||||
{
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
|
||||
if (it != end && *it != '}') FMT_THROW(format_error("invalid format"));
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename TContext>
|
||||
auto format(const mijin::Stacktrace& stacktrace, TContext& ctx) const -> decltype(ctx.out())
|
||||
{
|
||||
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
|
||||
auto it = ctx.out();
|
||||
it = fmt::format_to(it, "[{} frames]", stacktrace.getFrames().size());
|
||||
for (const auto& [idx, frame] : mijin::enumerate(stacktrace.getFrames()))
|
||||
{
|
||||
it = fmt::format_to(it, "\n #{:<{}} at {}", idx, numDigits, frame);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
};
|
||||
#endif // __has_include(<fmt/format.h>)
|
||||
|
||||
#endif // !defined(MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED)
|
||||
|
||||
@@ -62,7 +62,7 @@ const char* lookupFunctionName(const void* function)
|
||||
#else
|
||||
std::string name = "<unknown>";
|
||||
#endif
|
||||
return g_functionNameCache.insert({function, std::move(name)}).first->second.c_str();
|
||||
return g_functionNameCache.emplace(function, std::move(name)).first->second.c_str();
|
||||
}
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC
|
||||
|
||||
129
source/mijin/geo/geometry_traits.hpp
Normal file
129
source/mijin/geo/geometry_traits.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_GEO_GEOMETRY_TRAITS_HPP_INCLUDED)
|
||||
#define MIJIN_GEO_GEOMETRY_TRAITS_HPP_INCLUDED 1
|
||||
|
||||
#include <concepts>
|
||||
#include <ranges>
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename T>
|
||||
struct Vector2Traits;
|
||||
|
||||
template<typename T>
|
||||
concept Vector2 = requires(const std::decay_t<T> constObject, std::decay_t<T> nonConstObject)
|
||||
{
|
||||
{ mijin::Vector2Traits<std::decay_t<T>>{}.getX(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector2Traits<std::decay_t<T>>{}.getY(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector2Traits<std::decay_t<T>>{}.setX(nonConstObject, 0.f) };
|
||||
{ mijin::Vector2Traits<std::decay_t<T>>{}.setY(nonConstObject, 0.f) };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Vector3Traits;
|
||||
|
||||
template<typename T>
|
||||
concept Vector3 = requires(const std::decay_t<T> constObject, std::decay_t<T> nonConstObject)
|
||||
{
|
||||
{ mijin::Vector3Traits<std::decay_t<T>>{}.getX(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector3Traits<std::decay_t<T>>{}.getY(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector3Traits<std::decay_t<T>>{}.getZ(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector3Traits<std::decay_t<T>>{}.setX(nonConstObject, 0.f) };
|
||||
{ mijin::Vector3Traits<std::decay_t<T>>{}.setY(nonConstObject, 0.f) };
|
||||
{ mijin::Vector3Traits<std::decay_t<T>>{}.setZ(nonConstObject, 0.f) };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Vector4Traits;
|
||||
|
||||
template<typename T>
|
||||
concept Vector4 = requires(const std::decay_t<T> constObject, std::decay_t<T> nonConstObject)
|
||||
{
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.getX(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.getY(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.getZ(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.getW(constObject) } -> std::convertible_to<float>;
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.setX(nonConstObject, 0.f) };
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.setY(nonConstObject, 0.f) };
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.setZ(nonConstObject, 0.f) };
|
||||
{ mijin::Vector4Traits<std::decay_t<T>>{}.setW(nonConstObject, 0.f) };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Index = std::convertible_to<T, std::size_t>;
|
||||
|
||||
template<typename T>
|
||||
concept Vector2Iterator = requires(T object)
|
||||
{
|
||||
{ std::iterator_traits<T>::value_type } -> Vector2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Vector3Iterator = requires(T object)
|
||||
{
|
||||
{ std::iterator_traits<T>::value_type } -> Vector3;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Vector4Iterator = requires(T object)
|
||||
{
|
||||
{ std::iterator_traits<T>::value_type } -> Vector4;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Vector2Range = std::ranges::random_access_range<T> && Vector2<std::ranges::range_value_t<T>>;
|
||||
|
||||
template<typename T>
|
||||
concept Vector3Range = std::ranges::random_access_range<T> && Vector3<std::ranges::range_value_t<T>>;
|
||||
|
||||
template<typename T>
|
||||
concept Vector4Range = std::ranges::random_access_range<T> && Vector4<std::ranges::range_value_t<T>>;
|
||||
|
||||
template<typename T>
|
||||
concept IndexRange = std::ranges::random_access_range<T> && Index<std::ranges::range_value_t<T>>;
|
||||
|
||||
template<typename T>
|
||||
struct Mesh3DTraits;
|
||||
|
||||
template<typename T>
|
||||
concept Mesh3D = requires(const T constObject, T nonConstObject, mijin::Mesh3DTraits<T>::index_t index, mijin::Mesh3DTraits<T>::vector3_t vec3Value)
|
||||
{
|
||||
{ typename mijin::Mesh3DTraits<T>::index_t() } -> Index;
|
||||
{ typename mijin::Mesh3DTraits<T>::vector3_t() } -> Vector3;
|
||||
{ mijin::Mesh3DTraits<T>{}.getNumFaces(constObject) } -> std::convertible_to<std::size_t>;
|
||||
{ mijin::Mesh3DTraits<T>{}.getNumVertices(constObject) } -> std::convertible_to<std::size_t>;
|
||||
{ mijin::Mesh3DTraits<T>{}.getFaceNumVertices(constObject, index) } -> std::convertible_to<std::size_t>;
|
||||
{ mijin::Mesh3DTraits<T>{}.getPosition(constObject, index, index) } -> Vector3;
|
||||
{ mijin::Mesh3DTraits<T>{}.setPosition(nonConstObject, index, index, vec3Value) };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Mesh3DWithNormals = Mesh3D<T> && requires(const T constObject, T nonConstObject, mijin::Mesh3DTraits<T>::index_t index, mijin::Mesh3DTraits<T>::vector3_t vec3Value)
|
||||
{
|
||||
{ mijin::Mesh3DTraits<T>{}.getNormal(constObject, index, index) } -> Vector3;
|
||||
{ mijin::Mesh3DTraits<T>{}.setNormal(nonConstObject, index, index, vec3Value) };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Mesh3DWithTangents = Mesh3D<T> && requires(const T constObject, T nonConstObject, mijin::Mesh3DTraits<T>::index_t index, mijin::Mesh3DTraits<T>::vector4_t vec4Value)
|
||||
{
|
||||
{ typename mijin::Mesh3DTraits<T>::vector4_t() } -> Vector4;
|
||||
{ mijin::Mesh3DTraits<T>{}.getTangent(constObject, index, index) } -> Vector4;
|
||||
{ mijin::Mesh3DTraits<T>{}.setTangent(nonConstObject, index, index, vec4Value) };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Mesh3DWithTexCoords = Mesh3D<T> && requires(const T constObject, T nonConstObject, mijin::Mesh3DTraits<T>::index_t index, mijin::Mesh3DTraits<T>::vector2_t vec2Value)
|
||||
{
|
||||
{ mijin::Mesh3DTraits<T>{}.getTexCoord(constObject, index, index) } -> Vector2;
|
||||
{ mijin::Mesh3DTraits<T>{}.setTexCoord(nonConstObject, index, index, vec2Value) };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept FullMesh3D = Mesh3DWithNormals<T> && Mesh3DWithTangents<T> && Mesh3DWithTexCoords<T>;
|
||||
}
|
||||
|
||||
#endif // MIJIN_GEO_GEOMETRY_TRAITS_HPP_INCLUDED
|
||||
52
source/mijin/geo/glm_traits.hpp
Normal file
52
source/mijin/geo/glm_traits.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_GEO_GLM_ADAPTER_HPP_INCLUDED)
|
||||
#define MIJIN_GEO_GLM_ADAPTER_HPP_INCLUDED 1
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
#include "./geometry_traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<>
|
||||
struct Vector2Traits<glm::vec2>
|
||||
{
|
||||
constexpr float getX(const glm::vec2& vector) const noexcept { return vector.x; }
|
||||
constexpr float getY(const glm::vec2& vector) const noexcept { return vector.y; }
|
||||
constexpr void setX(glm::vec2& vector, float value) const noexcept { vector.x = value; }
|
||||
constexpr void setY(glm::vec2& vector, float value) const noexcept { vector.y = value; }
|
||||
};
|
||||
static_assert(Vector2<glm::vec2>, "Vector2Adapter not working.");
|
||||
|
||||
template<>
|
||||
struct Vector3Traits<glm::vec3>
|
||||
{
|
||||
constexpr float getX(const glm::vec3& vector) const noexcept { return vector.x; }
|
||||
constexpr float getY(const glm::vec3& vector) const noexcept { return vector.y; }
|
||||
constexpr float getZ(const glm::vec3& vector) const noexcept { return vector.z; }
|
||||
constexpr void setX(glm::vec3& vector, float value) const noexcept { vector.x = value; }
|
||||
constexpr void setY(glm::vec3& vector, float value) const noexcept { vector.y = value; }
|
||||
constexpr void setZ(glm::vec3& vector, float value) const noexcept { vector.z = value; }
|
||||
};
|
||||
static_assert(Vector3<glm::vec3>, "Vector3Adapter not working.");
|
||||
|
||||
template<>
|
||||
struct Vector4Traits<glm::vec4>
|
||||
{
|
||||
constexpr float getX(const glm::vec4& vector) const noexcept { return vector.x; }
|
||||
constexpr float getY(const glm::vec4& vector) const noexcept { return vector.y; }
|
||||
constexpr float getZ(const glm::vec4& vector) const noexcept { return vector.z; }
|
||||
constexpr float getW(const glm::vec4& vector) const noexcept { return vector.w; }
|
||||
constexpr void setX(glm::vec4& vector, float value) const noexcept { vector.x = value; }
|
||||
constexpr void setY(glm::vec4& vector, float value) const noexcept { vector.y = value; }
|
||||
constexpr void setZ(glm::vec4& vector, float value) const noexcept { vector.z = value; }
|
||||
constexpr void setW(glm::vec4& vector, float value) const noexcept { vector.w = value; }
|
||||
};
|
||||
static_assert(Vector4<glm::vec4>, "Vector4Adapter not working.");
|
||||
}
|
||||
|
||||
#endif // MIJIN_GEO_GLM_ADAPTER_HPP_INCLUDED
|
||||
102
source/mijin/geo/tangent_generation.hpp
Normal file
102
source/mijin/geo/tangent_generation.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_GEO_TANGENT_GENERATION_HPP_INCLUDED)
|
||||
#define MIJIN_GEO_TANGENT_GENERATION_HPP_INCLUDED 1
|
||||
|
||||
#include <mikktspace.h>
|
||||
#include "./geometry_traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
template<FullMesh3D TMesh>
|
||||
struct MikktData
|
||||
{
|
||||
Mesh3DTraits<TMesh> traits;
|
||||
Vector2Traits<typename TMesh::vector2_t> vec2Traits;
|
||||
Vector3Traits<typename TMesh::vector3_t> vec3Traits;
|
||||
Vector4Traits<typename TMesh::vector4_t> vec4Traits;
|
||||
TMesh* mesh;
|
||||
};
|
||||
|
||||
template<FullMesh3D TMesh>
|
||||
int mikktGetNumFaces(const SMikkTSpaceContext* context)
|
||||
{
|
||||
const MikktData<TMesh>& data = *static_cast<MikktData<TMesh>*>(context->m_pUserData);
|
||||
return static_cast<int>(data.traits.getNumFaces(*data.mesh));
|
||||
}
|
||||
|
||||
template<FullMesh3D TMesh>
|
||||
int mikktGetNumVerticesOfFace(const SMikkTSpaceContext* context, const int face)
|
||||
{
|
||||
const MikktData<TMesh>& data = *static_cast<MikktData<TMesh>*>(context->m_pUserData);
|
||||
return static_cast<int>(data.traits.getFaceNumVertices(*data.mesh, face));
|
||||
}
|
||||
|
||||
template<FullMesh3D TMesh>
|
||||
void mikktGetPosition(const SMikkTSpaceContext* context, float posOut[], const int face, const int vert) // NOLINT
|
||||
{
|
||||
const MikktData<TMesh>& data = *static_cast<MikktData<TMesh>*>(context->m_pUserData);
|
||||
const auto position = data.traits.getPosition(*data.mesh, face, vert);
|
||||
posOut[0] = data.vec3Traits.getX(position);
|
||||
posOut[1] = data.vec3Traits.getY(position);
|
||||
posOut[2] = data.vec3Traits.getZ(position);
|
||||
}
|
||||
|
||||
template<FullMesh3D TMesh>
|
||||
void mikktGetNormal(const SMikkTSpaceContext* context, float normOut[], const int face, const int vert) // NOLINT
|
||||
{
|
||||
const MikktData<TMesh>& data = *static_cast<MikktData<TMesh>*>(context->m_pUserData);
|
||||
const auto normal = data.traits.getNormal(*data.mesh, face, vert);
|
||||
normOut[0] = data.vec3Traits.getX(normal);
|
||||
normOut[1] = data.vec3Traits.getY(normal);
|
||||
normOut[2] = data.vec3Traits.getZ(normal);
|
||||
}
|
||||
|
||||
template<FullMesh3D TMesh>
|
||||
void mikktGetTexCoord(const SMikkTSpaceContext* context, float texcOut[], const int face, const int vert) // NOLINT
|
||||
{
|
||||
const MikktData<TMesh>& data = *static_cast<MikktData<TMesh>*>(context->m_pUserData);
|
||||
const auto texCoord = data.traits.getTexCoord(*data.mesh, face, vert);
|
||||
texcOut[0] = data.vec2Traits.getX(texCoord);
|
||||
texcOut[1] = data.vec2Traits.getY(texCoord);
|
||||
}
|
||||
|
||||
template<FullMesh3D TMesh>
|
||||
void mikktSetTSpaceBasic(const SMikkTSpaceContext* context, const float resultTangent[], const float sign, const int face, const int vert) // NOLINT
|
||||
{
|
||||
const MikktData<TMesh>& data = *static_cast<MikktData<TMesh>*>(context->m_pUserData);
|
||||
typename Mesh3DTraits<TMesh>::vector4_t tangent;
|
||||
data.vec4Traits.setX(tangent, resultTangent[0]);
|
||||
data.vec4Traits.setY(tangent, resultTangent[1]);
|
||||
data.vec4Traits.setZ(tangent, resultTangent[2]);
|
||||
data.vec4Traits.setW(tangent, sign);
|
||||
data.traits.setTangent(*data.mesh, face, vert, tangent);
|
||||
}
|
||||
}
|
||||
template<FullMesh3D TMesh>
|
||||
bool generateTangentsMikkt(TMesh& mesh)
|
||||
{
|
||||
SMikkTSpaceInterface mikktInterface = {
|
||||
.m_getNumFaces = &impl::mikktGetNumFaces<TMesh>,
|
||||
.m_getNumVerticesOfFace = &impl::mikktGetNumVerticesOfFace<TMesh>,
|
||||
.m_getPosition = &impl::mikktGetPosition<TMesh>,
|
||||
.m_getNormal = &impl::mikktGetNormal<TMesh>,
|
||||
.m_getTexCoord = &impl::mikktGetTexCoord<TMesh>,
|
||||
.m_setTSpaceBasic = &impl::mikktSetTSpaceBasic<TMesh>
|
||||
};
|
||||
impl::MikktData<TMesh> data = {
|
||||
.mesh = &mesh
|
||||
};
|
||||
const SMikkTSpaceContext context = {
|
||||
.m_pInterface = &mikktInterface,
|
||||
.m_pUserData = &data
|
||||
};
|
||||
return genTangSpaceDefault(&context);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MIJIN_GEO_TANGENT_GENERATION_HPP_INCLUDED
|
||||
4
source/mijin/internal/common.hpp
Normal file
4
source/mijin/internal/common.hpp
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./exception.hpp"
|
||||
43
source/mijin/internal/exception.hpp
Normal file
43
source/mijin/internal/exception.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_INTERNAL_EXCEPTION_HPP_INCLUDED)
|
||||
#define MIJIN_INTERNAL_EXCEPTION_HPP_INCLUDED 1
|
||||
|
||||
#if !defined(MIJIN_ENABLE_EXCEPTIONS)
|
||||
# define MIJIN_ENABLE_EXCEPTIONS (__cpp_exceptions)
|
||||
#endif
|
||||
|
||||
#define MIJIN_CATCH_EXCEPTIONS (__cpp_exceptions)
|
||||
|
||||
#if !defined(MIJIN_THROWING_ASSERTS)
|
||||
# define MIJIN_THROWING_ASSERTS 0
|
||||
#endif
|
||||
|
||||
#if MIJIN_THROWING_ASSERTS && !defined(MIJIN_TEST_NO_NOEXCEPT)
|
||||
#define MIJIN_TEST_NO_NOEXCEPT
|
||||
#endif
|
||||
|
||||
#if 0 // TODO: what? MIJIN_WITH_EXCEPTIONS
|
||||
#error "Maybe someday"
|
||||
#else
|
||||
#if defined(MIJIN_TEST_NO_NOEXCEPT) // only use for testing
|
||||
#define MIJIN_NOEXCEPT
|
||||
#define MIJIN_THROWS
|
||||
#define MIJIN_CONDITIONAL_NOEXCEPT(...)
|
||||
#else
|
||||
#define MIJIN_NOEXCEPT noexcept
|
||||
#define MIJIN_THROWS noexcept
|
||||
#define MIJIN_CONDITIONAL_NOEXCEPT(...) noexcept(__VA_ARGS__)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if MIJIN_CATCH_EXCEPTIONS
|
||||
# define MIJIN_TRY try
|
||||
# define MIJIN_CATCH catch
|
||||
#else
|
||||
# define MIJIN_TRY
|
||||
# define MIJIN_CATCH(...) if constexpr(false)
|
||||
#endif
|
||||
|
||||
#endif // !defined(MIJIN_INTERNAL_EXCEPTION_HPP_INCLUDED)
|
||||
197
source/mijin/io/archive.hpp
Normal file
197
source/mijin/io/archive.hpp
Normal file
@@ -0,0 +1,197 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_IO_ARCHIVE_HPP_INCLUDED)
|
||||
#define MIJIN_IO_ARCHIVE_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "mijin/container/optional.hpp"
|
||||
#include "mijin/io/stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
enum class ArchiveMode : std::uint8_t
|
||||
{
|
||||
READ,
|
||||
WRITE
|
||||
};
|
||||
|
||||
template<typename TPointer>
|
||||
class ArchiveBase
|
||||
{
|
||||
private:
|
||||
ArchiveMode mode_ = ArchiveMode::READ;
|
||||
StreamError error_ = StreamError::SUCCESS;
|
||||
TPointer stream_ = nullptr;
|
||||
public:
|
||||
ArchiveBase() MIJIN_NOEXCEPT = default;
|
||||
ArchiveBase(const ArchiveBase&) MIJIN_NOEXCEPT = default;
|
||||
ArchiveBase(ArchiveBase&&) noexcept = default;
|
||||
ArchiveBase(ArchiveMode mode, TPointer stream) MIJIN_NOEXCEPT : mode_(mode), stream_(std::move(stream)) {}
|
||||
|
||||
ArchiveBase& operator=(const ArchiveBase&) MIJIN_NOEXCEPT = default;
|
||||
ArchiveBase& operator=(ArchiveBase&&) noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
ArchiveMode getMode() const MIJIN_NOEXCEPT { return mode_; }
|
||||
|
||||
[[nodiscard]]
|
||||
StreamError getError() const MIJIN_NOEXCEPT { return error_; }
|
||||
|
||||
void setError(StreamError value) MIJIN_NOEXCEPT { error_ = value; }
|
||||
|
||||
[[nodiscard]]
|
||||
Stream& getStream() MIJIN_NOEXCEPT { return *stream_; }
|
||||
};
|
||||
|
||||
using Archive = ArchiveBase<Stream*>;
|
||||
using OwningArchive = ArchiveBase<std::unique_ptr<Stream>>;
|
||||
|
||||
|
||||
template<typename TContent>
|
||||
struct PODWrapper
|
||||
{
|
||||
TContent* content;
|
||||
};
|
||||
|
||||
template<typename TContent>
|
||||
PODWrapper<TContent> pod(TContent& content) MIJIN_NOEXCEPT
|
||||
{
|
||||
return PODWrapper<TContent>(&content);
|
||||
}
|
||||
|
||||
template<typename TPointer, typename TValue> requires (std::is_const_v<TValue>)
|
||||
Task<> c_serialize(ArchiveBase<TPointer>& archive, TValue& value) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (archive.getMode() != ArchiveMode::WRITE)
|
||||
{
|
||||
MIJIN_ERROR("Attempt to write to a const object.");
|
||||
archive.setError(StreamError::IO_ERROR);
|
||||
co_return;
|
||||
}
|
||||
co_await c_serialize(archive, const_cast<std::remove_const_t<TValue>&>(value));
|
||||
}
|
||||
|
||||
template<typename TPointer>
|
||||
Task<> c_serialize(ArchiveBase<TPointer>& archive, std::string& value) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
if (archive.getMode() == ArchiveMode::READ)
|
||||
{
|
||||
archive.setError(co_await archive.getStream().c_readBinaryString(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
archive.setError(co_await archive.getStream().c_writeBinaryString(value));
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
template<typename TPointer, typename TValue> requires (!std::is_const_v<TValue> && (std::is_arithmetic_v<TValue> || std::is_enum_v<TValue>))
|
||||
Task<> c_serialize(ArchiveBase<TPointer>& archive, TValue& value) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
if (archive.getMode() == ArchiveMode::READ)
|
||||
{
|
||||
archive.setError(co_await archive.getStream().c_read(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
archive.setError(co_await archive.getStream().c_write(value));
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
template<typename TPointer, typename TElement, typename TAllocator>
|
||||
Task<> c_serialize(ArchiveBase<TPointer>& archive, std::vector<TElement, TAllocator>& value) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
std::uint64_t size = value.size();
|
||||
co_await c_serialize(archive, size);
|
||||
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
if (archive.getMode() == ArchiveMode::READ)
|
||||
{
|
||||
value.resize(size);
|
||||
}
|
||||
|
||||
for (TElement& element : value)
|
||||
{
|
||||
co_await c_serialize(archive, element);
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TPointer, typename TContent>
|
||||
Task<> c_serialize(ArchiveBase<TPointer> archive, Optional<TContent>& value) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
std::uint8_t exists = !value.empty();
|
||||
co_await c_serialize(archive, exists);
|
||||
if (exists)
|
||||
{
|
||||
if (archive.getMode() == ArchiveMode::READ)
|
||||
{
|
||||
TContent content;
|
||||
co_await c_serialize(archive, content);
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
value.emplace(std::move(content));
|
||||
}
|
||||
else
|
||||
{
|
||||
co_await c_serialize(archive, *value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TPointer, typename TContent>
|
||||
Task<> c_serialize(ArchiveBase<TPointer>& archive, PODWrapper<TContent> value) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (archive.getError() != StreamError::SUCCESS)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
if (archive.getMode() == ArchiveMode::READ)
|
||||
{
|
||||
if constexpr (std::is_const_v<TContent>)
|
||||
{
|
||||
MIJIN_ERROR("Attempt to write to a const object.");
|
||||
archive.setError(StreamError::IO_ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
archive.setError(co_await archive.getStream().c_read(*value.content));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
archive.setError(co_await archive.getStream().c_write(*value.content));
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_IO_ARCHIVE_HPP_INCLUDED)
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include "../util/iterators.hpp"
|
||||
#include "../detect.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_OSX
|
||||
namespace mijin
|
||||
{
|
||||
ProcessStream::~ProcessStream()
|
||||
@@ -32,10 +34,10 @@ StreamError ProcessStream::open(const char* command, FileOpenMode mode_)
|
||||
modeStr = "rw";
|
||||
break;
|
||||
default:
|
||||
assert(!"Unsupported mode for ProcessStream::open()");
|
||||
MIJIN_ERROR("Unsupported mode for ProcessStream::open()");
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
handle = popen(command, modeStr); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
handle = popen(command, modeStr); // NOLINT(cppcoreguidelines-owning-memory,cert-env33-c)
|
||||
if (!handle) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
@@ -45,18 +47,18 @@ StreamError ProcessStream::open(const char* command, FileOpenMode mode_)
|
||||
|
||||
int ProcessStream::close()
|
||||
{
|
||||
assert(handle);
|
||||
MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened.");
|
||||
const int result = pclose(handle);
|
||||
handle = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
StreamError ProcessStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
StreamError ProcessStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE);
|
||||
MIJIN_ASSERT(handle != nullptr, "Process stream has not been openend.");
|
||||
MIJIN_ASSERT(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE, "Cannot read from this process stream.");
|
||||
|
||||
if (bufferedChar >= 0 && buffer.size() > 0)
|
||||
if (bufferedChar >= 0 && !buffer.empty())
|
||||
{
|
||||
buffer[0] = static_cast<std::uint8_t>(bufferedChar);
|
||||
}
|
||||
@@ -65,7 +67,7 @@ StreamError ProcessStream::readRaw(std::span<std::uint8_t> buffer, bool partial,
|
||||
if (std::ferror(handle)) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (!partial && readBytes < buffer.size()) {
|
||||
if (!options.partial && readBytes < buffer.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (outBytesRead != nullptr)
|
||||
@@ -78,8 +80,8 @@ StreamError ProcessStream::readRaw(std::span<std::uint8_t> buffer, bool partial,
|
||||
|
||||
StreamError ProcessStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE);
|
||||
MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened.");
|
||||
MIJIN_ASSERT(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE, "Cannot write to this process stream.");
|
||||
|
||||
const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
|
||||
if (written != buffer.size() || std::ferror(handle)) {
|
||||
@@ -91,7 +93,7 @@ StreamError ProcessStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
|
||||
std::size_t ProcessStream::tell()
|
||||
{
|
||||
assert(handle);
|
||||
MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened.");
|
||||
|
||||
return std::ftell(handle); // TODO: does this work?
|
||||
}
|
||||
@@ -106,22 +108,19 @@ StreamError ProcessStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
void ProcessStream::flush()
|
||||
{
|
||||
const int result = std::fflush(handle);
|
||||
assert(result == 0);
|
||||
MIJIN_ASSERT(result == 0, "Error flushing process stream.");
|
||||
}
|
||||
|
||||
bool ProcessStream::isAtEnd()
|
||||
{
|
||||
assert(handle);
|
||||
MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened.");
|
||||
|
||||
if (bufferedChar >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferedChar = std::fgetc(handle);
|
||||
if (std::feof(handle)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return static_cast<bool>(std::feof(handle));
|
||||
}
|
||||
|
||||
StreamFeatures ProcessStream::getFeatures()
|
||||
@@ -132,13 +131,16 @@ StreamFeatures ProcessStream::getFeatures()
|
||||
.read = (mode == FileOpenMode::READ),
|
||||
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE),
|
||||
.tell = true,
|
||||
.seek = false
|
||||
.seek = false,
|
||||
.readOptions = {
|
||||
.partial = true
|
||||
}
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string shellEscape(const std::string& arg) noexcept
|
||||
std::string shellEscape(const std::string& arg) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::ostringstream oss;
|
||||
const bool requiresQuotes = std::any_of(arg.begin(), arg.end(), [&](const char chr) { return std::isspace(chr); });
|
||||
@@ -156,6 +158,7 @@ std::string shellEscape(const std::string& arg) noexcept
|
||||
case '$':
|
||||
oss << '\\';
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
oss << chr;
|
||||
}
|
||||
@@ -165,7 +168,7 @@ std::string shellEscape(const std::string& arg) noexcept
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string makeShellCommand(const std::vector<std::string>& args) noexcept
|
||||
std::string makeShellCommand(const std::vector<std::string>& args) MIJIN_NOEXCEPT
|
||||
{
|
||||
using namespace mijin::pipe;
|
||||
return args
|
||||
@@ -173,3 +176,4 @@ std::string makeShellCommand(const std::vector<std::string>& args) noexcept
|
||||
| Join(" ");
|
||||
}
|
||||
} // namespace mijin
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_OSX
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "./stream.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -26,7 +27,7 @@ public:
|
||||
[[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 readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead) 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;
|
||||
@@ -35,8 +36,8 @@ public:
|
||||
StreamFeatures getFeatures() override;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::string shellEscape(const std::string& arg) noexcept;
|
||||
[[nodiscard]] std::string makeShellCommand(const std::vector<std::string>& args) noexcept;
|
||||
[[nodiscard]] std::string shellEscape(const std::string& arg) MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] std::string makeShellCommand(const std::vector<std::string>& args) MIJIN_NOEXCEPT;
|
||||
|
||||
StreamError ProcessStream::open(const std::vector<std::string>& args, FileOpenMode mode_)
|
||||
{
|
||||
|
||||
221
source/mijin/io/stlstream.hpp
Normal file
221
source/mijin/io/stlstream.hpp
Normal file
@@ -0,0 +1,221 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_IO_STLSTREAM_HPP_INCLUDED)
|
||||
#define MIJIN_IO_STLSTREAM_HPP_INCLUDED 1
|
||||
|
||||
#include <array>
|
||||
#include <streambuf>
|
||||
#include "./stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename TChar, typename TTraits = std::char_traits<TChar>>
|
||||
class BasicStreambufAdapter : public std::basic_streambuf<TChar, TTraits>
|
||||
{
|
||||
private:
|
||||
using base_t = std::basic_streambuf<TChar, TTraits>;
|
||||
using char_type = base_t::char_type;
|
||||
using int_type = base_t::int_type;
|
||||
using traits_type = base_t::traits_type;
|
||||
|
||||
static const std::size_t BUFFER_SIZE = 4096;
|
||||
|
||||
Stream* stream_ = nullptr;
|
||||
std::array<TChar, BUFFER_SIZE> inBuffer_;
|
||||
std::array<TChar, BUFFER_SIZE> outBuffer_;
|
||||
public:
|
||||
explicit BasicStreambufAdapter(Stream& stream) noexcept : stream_(&stream)
|
||||
{
|
||||
base_t::setp(outBuffer_.data(), outBuffer_.data() + outBuffer_.size());
|
||||
}
|
||||
|
||||
int_type underflow() override
|
||||
{
|
||||
std::size_t bytesRead = 0;
|
||||
while (bytesRead == 0)
|
||||
{
|
||||
if (stream_->isAtEnd())
|
||||
{
|
||||
return traits_type::eof();
|
||||
}
|
||||
throwOnError(stream_->readRaw(inBuffer_, {.partial = true}, &bytesRead));
|
||||
}
|
||||
base_t::setg(inBuffer_.data(), inBuffer_.data(), inBuffer_.data() + bytesRead);
|
||||
return inBuffer_[0];
|
||||
}
|
||||
|
||||
int_type overflow(int_type c) override
|
||||
{
|
||||
if (base_t::pptr() - base_t::pbase() > 0)
|
||||
{
|
||||
throwOnError(stream_->writeRaw(base_t::pbase(), base_t::pptr() - base_t::pbase()));
|
||||
}
|
||||
outBuffer_[0] = traits_type::to_char_type(c);
|
||||
base_t::setp(outBuffer_.data() + 1, outBuffer_.data() + outBuffer_.size());
|
||||
return traits_type::not_eof(c);
|
||||
}
|
||||
|
||||
int sync() override
|
||||
{
|
||||
stream_->flush();
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
using StreambufAdapter = BasicStreambufAdapter<char>;
|
||||
|
||||
template<typename TChar, typename TTraits = std::char_traits<TChar>>
|
||||
class BasicIOStreamAdapter : public std::basic_iostream<TChar, TTraits>
|
||||
{
|
||||
private:
|
||||
StreambufAdapter streambuf_;
|
||||
public:
|
||||
BasicIOStreamAdapter(Stream& stream) noexcept : std::basic_iostream<TChar, TTraits>(&streambuf_), streambuf_(stream) {}
|
||||
};
|
||||
|
||||
using IOStreamAdapter = BasicIOStreamAdapter<char>;
|
||||
|
||||
template<typename TChar, typename TTraits = std::char_traits<TChar>>
|
||||
class BasicSTLStream : public Stream
|
||||
{
|
||||
private:
|
||||
std::basic_istream<TChar, TTraits>* istream_ = nullptr;
|
||||
std::basic_ostream<TChar, TTraits>* ostream_ = nullptr;
|
||||
public:
|
||||
explicit BasicSTLStream(std::basic_istream<TChar, TTraits>& istream) : istream_(&istream) {}
|
||||
explicit BasicSTLStream(std::basic_ostream<TChar, TTraits>& ostream) : ostream_(&ostream) {}
|
||||
explicit BasicSTLStream(std::basic_iostream<TChar, TTraits>& iostream) : istream_(&iostream), ostream_(&ostream_) {}
|
||||
StreamError readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) override
|
||||
{
|
||||
(void) options;
|
||||
|
||||
if (istream_ != nullptr)
|
||||
{
|
||||
if (buffer.size() % sizeof(TChar) != 0)
|
||||
{
|
||||
// buffer size must be divisible by character size
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
istream_->read(std::bit_cast<TChar*>(buffer.data()), buffer.size() / sizeof(TChar));
|
||||
if (!istream_->good())
|
||||
{
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (outBytesRead != nullptr)
|
||||
{
|
||||
*outBytesRead = buffer.size();
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
StreamError writeRaw(std::span<const std::uint8_t> buffer) override
|
||||
{
|
||||
if (ostream_ != nullptr)
|
||||
{
|
||||
if (buffer.size() % sizeof(TChar) != 0)
|
||||
{
|
||||
// buffer size must be divisible by character size
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
ostream_->write(std::bit_cast<const TChar*>(buffer.data()), buffer.size() / sizeof(TChar));
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
std::size_t tell() override
|
||||
{
|
||||
// kind of weird case, since the streams can have different positions
|
||||
// this is fine, I guess
|
||||
if (istream_ != nullptr)
|
||||
{
|
||||
return istream_->tellg();
|
||||
}
|
||||
if (ostream_ != nullptr)
|
||||
{
|
||||
return ostream_->tellp();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
StreamError seek(std::intptr_t pos, mijin::SeekMode seekMode = SeekMode::ABSOLUTE) override
|
||||
{
|
||||
std::ios_base::seekdir seekDir = std::ios_base::beg;
|
||||
switch (seekMode)
|
||||
{
|
||||
case mijin::SeekMode::ABSOLUTE:
|
||||
break;
|
||||
case mijin::SeekMode::RELATIVE:
|
||||
seekDir = std::ios_base::cur;
|
||||
break;
|
||||
case mijin::SeekMode::RELATIVE_TO_END:
|
||||
seekDir = std::ios_base::end;
|
||||
break;
|
||||
}
|
||||
if (istream_ != nullptr)
|
||||
{
|
||||
istream_->seekg(pos, seekDir);
|
||||
if (!istream_->good())
|
||||
{
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
}
|
||||
if (ostream_ != nullptr)
|
||||
{
|
||||
ostream_->seekp(pos, seekDir);
|
||||
if (!ostream_->good())
|
||||
{
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
bool isAtEnd() override
|
||||
{
|
||||
if (istream_ != nullptr)
|
||||
{
|
||||
return istream_->peek() == TTraits::eof();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
StreamFeatures getFeatures() override
|
||||
{
|
||||
return {
|
||||
.read = istream_ != nullptr,
|
||||
.write = ostream_ != nullptr,
|
||||
.tell = istream_ != nullptr || ostream_ != nullptr,
|
||||
.seek = istream_ != nullptr || ostream_ != nullptr,
|
||||
.async = false,
|
||||
.readOptions = {
|
||||
.partial = false,
|
||||
.peek = false,
|
||||
.noBlock = false
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using STLStream = BasicSTLStream<char>;
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_IO_STLSTREAM_HPP_INCLUDED)
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "./stream.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
namespace mijin
|
||||
@@ -34,7 +34,57 @@ namespace mijin
|
||||
|
||||
void Stream::flush() {}
|
||||
|
||||
StreamError Stream::readString(std::string& outString)
|
||||
mijin::Task<StreamError> Stream::c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
std::size_t bytesToRead = buffer.size();
|
||||
if (bytesToRead == 0)
|
||||
{
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
while(true)
|
||||
{
|
||||
bool done = false;
|
||||
std::size_t bytesRead = 0;
|
||||
const StreamError error = readRaw(buffer.data() + buffer.size() - bytesToRead, bytesToRead,
|
||||
{.partial = true, .noBlock = true}, &bytesToRead);
|
||||
switch (error)
|
||||
{
|
||||
case StreamError::SUCCESS:
|
||||
bytesToRead -= bytesRead;
|
||||
if (options.partial || bytesToRead == 0)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
break;
|
||||
case StreamError::WOULD_BLOCK:
|
||||
if (options.noBlock)
|
||||
{
|
||||
co_return StreamError::WOULD_BLOCK;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
co_return error;
|
||||
}
|
||||
if (done)
|
||||
{
|
||||
break;
|
||||
}
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
if (outBytesRead != nullptr)
|
||||
{
|
||||
*outBytesRead = buffer.size() - bytesToRead;
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
co_return writeRaw(buffer);
|
||||
}
|
||||
|
||||
StreamError Stream::readBinaryString(std::string& outString)
|
||||
{
|
||||
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
|
||||
StreamError error = read(length);
|
||||
@@ -52,17 +102,228 @@ StreamError Stream::readString(std::string& outString)
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError Stream::writeString(std::string_view str)
|
||||
StreamError Stream::writeBinaryString(std::string_view str)
|
||||
{
|
||||
assert(str.length() <= std::numeric_limits<std::uint32_t>::max());
|
||||
MIJIN_ASSERT(str.length() <= std::numeric_limits<std::uint32_t>::max(), "Binary string is too long.");
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
|
||||
StreamError error = write(length);
|
||||
const StreamError error = write(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
return error;
|
||||
}
|
||||
return writeSpan(str.begin(), str.end());
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readBinaryString(std::string& outString)
|
||||
{
|
||||
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
|
||||
StreamError error = co_await c_read(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
co_return error;
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result.resize(length);
|
||||
error = co_await c_readSpan(result.begin(), result.end());
|
||||
if (error != StreamError::SUCCESS) {
|
||||
co_return error;
|
||||
}
|
||||
outString = std::move(result);
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_writeBinaryString(std::string_view str)
|
||||
{
|
||||
MIJIN_ASSERT(str.length() <= std::numeric_limits<std::uint32_t>::max(), "Binary string is too long.");
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
|
||||
StreamError error = co_await c_write(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
co_return error;
|
||||
}
|
||||
co_return co_await c_writeSpan(str.begin(), str.end());
|
||||
}
|
||||
|
||||
StreamError Stream::getTotalLength(std::size_t& outLength)
|
||||
{
|
||||
const StreamFeatures features = getFeatures();
|
||||
if (!features.tell || !features.seek) {
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
const std::size_t origPos = tell();
|
||||
if (const StreamError error = seek(0, SeekMode::RELATIVE_TO_END); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
outLength = tell();
|
||||
if (const StreamError error = seek(static_cast<std::intptr_t>(origPos)); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError Stream::readRest(TypelessBuffer& outBuffer)
|
||||
{
|
||||
// first try to allocate everything at once
|
||||
std::size_t length = 0;
|
||||
if (const StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
|
||||
length -= tell();
|
||||
outBuffer.resize(length);
|
||||
if (const StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
// could not determine the size, read chunk-wise
|
||||
static constexpr std::size_t CHUNK_SIZE = 4096;
|
||||
std::array<std::byte, CHUNK_SIZE> chunk = {};
|
||||
|
||||
while (!isAtEnd())
|
||||
{
|
||||
std::size_t bytesRead = 0;
|
||||
if (const StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
outBuffer.resize(outBuffer.byteSize() + bytesRead);
|
||||
const std::span<std::byte> bufferBytes = outBuffer.makeSpan<std::byte>();
|
||||
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readRest(TypelessBuffer& outBuffer)
|
||||
{
|
||||
// first try to allocate everything at once
|
||||
std::size_t length = 0;
|
||||
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
|
||||
length -= tell();
|
||||
outBuffer.resize(length);
|
||||
if (StreamError error = co_await c_readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
// could not determine the size, read chunk-wise
|
||||
static constexpr std::size_t CHUNK_SIZE = 4096;
|
||||
std::array<std::byte, CHUNK_SIZE> chunk = {};
|
||||
|
||||
while (!isAtEnd())
|
||||
{
|
||||
std::size_t bytesRead = 0;
|
||||
if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
|
||||
outBuffer.resize(outBuffer.byteSize() + bytesRead);
|
||||
std::span<std::byte> bufferBytes = outBuffer.makeSpan<std::byte>();
|
||||
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError Stream::readLine(std::string& outString)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
|
||||
|
||||
static constexpr std::size_t BUFFER_SIZE = 4096;
|
||||
std::array<char, BUFFER_SIZE> buffer;
|
||||
|
||||
outString.clear();
|
||||
bool done = false;
|
||||
while (!done)
|
||||
{
|
||||
// read into the buffer
|
||||
std::size_t bytesRead = 0;
|
||||
if (const StreamError error = readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
// try to find a \n
|
||||
auto begin = buffer.begin(); // NOLINT(readability-qualified-auto)
|
||||
auto end = buffer.begin() + bytesRead; // NOLINT(readability-qualified-auto)
|
||||
auto newline = std::find(begin, end, '\n'); // NOLINT(readability-qualified-auto)
|
||||
|
||||
if (newline != end)
|
||||
{
|
||||
// found the end
|
||||
outString.append(begin, newline);
|
||||
end = newline + 1;
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
outString.append(begin, end);
|
||||
}
|
||||
|
||||
// read again, this time to skip
|
||||
if (const StreamError error = readSpan(begin, end); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
if (isAtEnd())
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readLine(std::string& outString)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
|
||||
|
||||
static constexpr std::size_t BUFFER_SIZE = 4096;
|
||||
std::array<char, BUFFER_SIZE> buffer;
|
||||
|
||||
outString.clear();
|
||||
bool done = false;
|
||||
while(!done)
|
||||
{
|
||||
// read into the buffer
|
||||
std::size_t bytesRead = 0;
|
||||
if (StreamError error = co_await c_readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
// try to find a \n
|
||||
auto begin = buffer.begin(); // NOLINT(readability-qualified-auto)
|
||||
auto end = buffer.begin() + bytesRead; // NOLINT(readability-qualified-auto)
|
||||
auto newline = std::find(begin, end, '\n'); // NOLINT(readability-qualified-auto)
|
||||
|
||||
if (newline != end)
|
||||
{
|
||||
// found the end
|
||||
outString.append(begin, newline);
|
||||
end = newline + 1;
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
outString.append(begin, end);
|
||||
}
|
||||
|
||||
// read again, this time to skip
|
||||
if (StreamError error = co_await c_readSpan(begin, end); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
}
|
||||
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
FileStream::~FileStream()
|
||||
{
|
||||
if (handle) {
|
||||
@@ -89,6 +350,9 @@ StreamError FileStream::open(const char* path, FileOpenMode mode_)
|
||||
case FileOpenMode::READ_WRITE:
|
||||
modeStr = "r+b";
|
||||
break;
|
||||
default:
|
||||
MIJIN_FATAL("Invalid value for mode.");
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
handle = std::fopen(path, modeStr); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
if (!handle && mode_ == FileOpenMode::READ_WRITE) {
|
||||
@@ -99,43 +363,52 @@ StreamError FileStream::open(const char* path, FileOpenMode mode_)
|
||||
}
|
||||
|
||||
int result = std::fseek(handle, 0, SEEK_END);
|
||||
assert(result == 0);
|
||||
MIJIN_ASSERT(result == 0, "fseek failed.");
|
||||
length = std::ftell(handle);
|
||||
result = std::fseek(handle, 0, SEEK_SET);
|
||||
assert(result == 0);
|
||||
MIJIN_ASSERT(result == 0, "fseek failed.");
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
void FileStream::close()
|
||||
{
|
||||
assert(handle);
|
||||
const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
assert(result == 0);
|
||||
MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
|
||||
[[maybe_unused]] const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
MIJIN_ASSERT(result == 0, "fclose failed.");
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
StreamError FileStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
StreamError FileStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE);
|
||||
MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
|
||||
MIJIN_ASSERT(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE, "Cannot read from this stream");
|
||||
|
||||
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()) {
|
||||
if (!options.partial && readBytes < buffer.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (outBytesRead != nullptr) {
|
||||
*outBytesRead = readBytes;
|
||||
}
|
||||
if (options.peek)
|
||||
{
|
||||
if (const StreamError error = seek(-static_cast<std::intptr_t>(readBytes), SeekMode::RELATIVE); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
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);
|
||||
MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
|
||||
MIJIN_ASSERT(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE || mode == FileOpenMode::APPEND,
|
||||
"Cannot write to this stream");
|
||||
|
||||
const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
|
||||
if (written != buffer.size() || std::ferror(handle)) {
|
||||
@@ -148,15 +421,13 @@ StreamError FileStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
|
||||
std::size_t FileStream::tell()
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
|
||||
return std::ftell(handle);
|
||||
}
|
||||
|
||||
StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
|
||||
int origin; // NOLINT(cppcoreguidelines-init-variables)
|
||||
switch (seekMode)
|
||||
{
|
||||
@@ -170,7 +441,7 @@ StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
origin = SEEK_END;
|
||||
break;
|
||||
default:
|
||||
assert(!"Invalid value passed as seekMode!");
|
||||
MIJIN_ERROR("Invalid value passed as seekMode!");
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
const int result = std::fseek(handle, static_cast<long>(pos), origin);
|
||||
@@ -182,20 +453,20 @@ StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
|
||||
void FileStream::flush()
|
||||
{
|
||||
const int result = std::fflush(handle);
|
||||
assert(result == 0);
|
||||
[[maybe_unused]] const int result = std::fflush(handle);
|
||||
MIJIN_ASSERT(result == 0, "fflush failed.");
|
||||
}
|
||||
|
||||
bool FileStream::isAtEnd()
|
||||
{
|
||||
assert(handle);
|
||||
MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
|
||||
|
||||
(void) std::fgetc(handle);
|
||||
if (std::feof(handle)) {
|
||||
return true;
|
||||
}
|
||||
const int result = std::fseek(handle, -1, SEEK_CUR);
|
||||
assert(result == 0);
|
||||
[[maybe_unused]] const int result = std::fseek(handle, -1, SEEK_CUR);
|
||||
MIJIN_ASSERT(result == 0, "fseek failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -207,7 +478,11 @@ StreamFeatures FileStream::getFeatures()
|
||||
.read = (mode == FileOpenMode::READ),
|
||||
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE),
|
||||
.tell = true,
|
||||
.seek = true
|
||||
.seek = true,
|
||||
.readOptions = {
|
||||
.partial = true,
|
||||
.peek = true
|
||||
}
|
||||
};
|
||||
}
|
||||
return {};
|
||||
@@ -215,30 +490,30 @@ StreamFeatures FileStream::getFeatures()
|
||||
|
||||
void MemoryStream::openRW(std::span<std::uint8_t> data)
|
||||
{
|
||||
assert(!isOpen());
|
||||
MIJIN_ASSERT(!isOpen(), "MemoryStream is already open.");
|
||||
data_ = data;
|
||||
pos_ = 0;
|
||||
canWrite_ = true;
|
||||
}
|
||||
|
||||
void MemoryStream::openRO(std::span<const std::uint8_t> data)
|
||||
void MemoryStream::openROImpl(const void* data, std::size_t bytes)
|
||||
{
|
||||
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
|
||||
MIJIN_ASSERT(!isOpen(), "MemoryStream is already open.");
|
||||
data_ = std::span<std::uint8_t>(const_cast<std::uint8_t*>(static_cast<const std::uint8_t*>(data)), bytes); // NOLINT(cppcoreguidelines-pro-type-const-cast) we'll be fine
|
||||
pos_ = 0;
|
||||
canWrite_ = false;
|
||||
}
|
||||
|
||||
void MemoryStream::close()
|
||||
{
|
||||
assert(isOpen());
|
||||
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
|
||||
data_ = {};
|
||||
}
|
||||
|
||||
StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(isOpen());
|
||||
if (!partial && availableBytes() < buffer.size()) {
|
||||
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
|
||||
if (!options.partial && availableBytes() < buffer.size()) {
|
||||
return StreamError::IO_ERROR; // TODO: need more errors?
|
||||
}
|
||||
const std::size_t numBytes = std::min(buffer.size(), availableBytes());
|
||||
@@ -246,14 +521,19 @@ StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, bool partial,
|
||||
if (outBytesRead) {
|
||||
*outBytesRead = numBytes;
|
||||
}
|
||||
if (!options.peek) {
|
||||
pos_ += numBytes;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
assert(isOpen());
|
||||
assert(canWrite_);
|
||||
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
|
||||
|
||||
if (!canWrite_) {
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (availableBytes() < buffer.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
@@ -265,13 +545,13 @@ StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
|
||||
std::size_t MemoryStream::tell()
|
||||
{
|
||||
assert(isOpen());
|
||||
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
|
||||
return pos_;
|
||||
}
|
||||
|
||||
StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
{
|
||||
assert(isOpen());
|
||||
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
|
||||
std::intptr_t newPos = -1;
|
||||
switch (seekMode)
|
||||
{
|
||||
@@ -294,7 +574,7 @@ StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
|
||||
bool MemoryStream::isAtEnd()
|
||||
{
|
||||
assert(isOpen());
|
||||
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
|
||||
return pos_ == data_.size();
|
||||
}
|
||||
|
||||
@@ -304,7 +584,10 @@ StreamFeatures MemoryStream::getFeatures()
|
||||
.read = true,
|
||||
.write = canWrite_,
|
||||
.tell = true,
|
||||
.seek = true
|
||||
.seek = true,
|
||||
.readOptions = {
|
||||
.peek = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,17 @@
|
||||
#if !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
|
||||
#define MIJIN_IO_STREAM_HPP_INCLUDED 1
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include "../async/coroutine.hpp"
|
||||
#include "../container/typeless_buffer.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../types/result.hpp"
|
||||
#include "../util/exception.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -32,12 +38,22 @@ enum class SeekMode
|
||||
RELATIVE_TO_END
|
||||
};
|
||||
|
||||
struct ReadOptions
|
||||
{
|
||||
bool partial : 1 = false;
|
||||
bool peek : 1 = false;
|
||||
bool noBlock : 1 = false;
|
||||
};
|
||||
|
||||
struct StreamFeatures
|
||||
{
|
||||
bool read : 1 = false;
|
||||
bool write : 1 = false;
|
||||
bool tell : 1 = false;
|
||||
bool seek : 1 = false;
|
||||
bool async : 1 = false;
|
||||
|
||||
ReadOptions readOptions = {};
|
||||
};
|
||||
|
||||
enum class FileOpenMode
|
||||
@@ -50,10 +66,14 @@ enum class FileOpenMode
|
||||
|
||||
enum class [[nodiscard]] StreamError
|
||||
{
|
||||
SUCCESS,
|
||||
IO_ERROR,
|
||||
NOT_SUPPORTED,
|
||||
UNKNOWN_ERROR
|
||||
SUCCESS = 0,
|
||||
IO_ERROR = 1,
|
||||
NOT_SUPPORTED = 2,
|
||||
CONNECTION_CLOSED = 3,
|
||||
PROTOCOL_ERROR = 4,
|
||||
WOULD_BLOCK = 5,
|
||||
CONNECTION_REFUSED = 6,
|
||||
UNKNOWN_ERROR = -1
|
||||
};
|
||||
|
||||
class Stream
|
||||
@@ -62,7 +82,12 @@ 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 readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) = 0;
|
||||
[[deprecated("Partial parameter has been replaced with options.")]]
|
||||
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
return readRaw(buffer, {.partial = partial}, outBytesRead);
|
||||
}
|
||||
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;
|
||||
@@ -70,59 +95,201 @@ public:
|
||||
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)
|
||||
// async interface (requires getFeatures().async to be set)
|
||||
virtual mijin::Task<StreamError> c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr);
|
||||
virtual mijin::Task<StreamError> c_writeRaw(std::span<const std::uint8_t> buffer);
|
||||
|
||||
StreamError readRaw(void* outData, std::size_t bytes, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
|
||||
return readRaw(std::span(ptr, ptr + bytes), partial, outBytesRead);
|
||||
return readRaw(std::span(ptr, ptr + bytes), options, outBytesRead);
|
||||
}
|
||||
|
||||
inline StreamError writeRaw(const void* data, std::size_t bytes)
|
||||
[[deprecated("Partial parameter has been replaced with options.")]]
|
||||
StreamError readRaw(void* outData, std::size_t bytes, bool partial, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
|
||||
return readRaw(std::span(ptr, ptr + bytes), {.partial = partial}, outBytesRead);
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> c_readRaw(void* outData, std::size_t bytes, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
|
||||
co_return co_await c_readRaw(std::span(ptr, ptr + bytes), options, outBytesRead);
|
||||
}
|
||||
|
||||
template<std::ranges::contiguous_range TRange>
|
||||
StreamError readRaw(TRange& range, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
|
||||
return readRaw(&*range.begin(), bytes, options, outBytesRead);
|
||||
}
|
||||
|
||||
template<std::ranges::contiguous_range TRange>
|
||||
[[deprecated("Partial parameter has been replaced with options.")]]
|
||||
StreamError readRaw(TRange& range, bool partial, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
|
||||
return readRaw(&*range.begin(), bytes, {.partial = partial}, outBytesRead);
|
||||
}
|
||||
|
||||
StreamError readRaw(TypelessBuffer& buffer, const ReadOptions& options = {})
|
||||
{
|
||||
return readRaw(buffer.data(), buffer.byteSize(), options);
|
||||
}
|
||||
|
||||
template<std::ranges::contiguous_range TRange>
|
||||
mijin::Task<StreamError> c_readRaw(TRange& range, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
|
||||
return c_readRaw(&*range.begin(), bytes, options, outBytesRead);
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> c_readRaw(TypelessBuffer& buffer, const ReadOptions& options = {})
|
||||
{
|
||||
return c_readRaw(buffer.data(), buffer.byteSize(), options);
|
||||
}
|
||||
|
||||
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)
|
||||
mijin::Task<StreamError> c_writeRaw(const void* data, std::size_t bytes)
|
||||
{
|
||||
return readRaw(&value, sizeof(T));
|
||||
const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data);
|
||||
return c_writeRaw(std::span(ptr, ptr + bytes));
|
||||
}
|
||||
|
||||
template<std::ranges::contiguous_range TRange>
|
||||
StreamError writeRaw(const TRange& range)
|
||||
{
|
||||
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
|
||||
return writeRaw(&*range.begin(), bytes);
|
||||
}
|
||||
|
||||
template<std::ranges::contiguous_range TRange>
|
||||
mijin::Task<StreamError> c_writeRaw(const TRange& range)
|
||||
{
|
||||
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
|
||||
return c_writeRaw(&*range.begin(), bytes);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline StreamError readSpan(T& values)
|
||||
StreamError read(T& value, const ReadOptions& options = {})
|
||||
{
|
||||
MIJIN_ASSERT(!options.partial, "Cannot partially read a value.");
|
||||
return readRaw(&value, sizeof(T), options);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
mijin::Task<StreamError> c_read(T& value, const ReadOptions& options = {})
|
||||
{
|
||||
MIJIN_ASSERT(!options.partial, "Cannot partially read a value.");
|
||||
return c_readRaw(&value, sizeof(T), options);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
StreamError readSpan(T& values, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
auto asSpan = std::span(values);
|
||||
return readRaw(asSpan.data(), asSpan.size_bytes());
|
||||
return readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
mijin::Task<StreamError> c_readSpan(T& values, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
auto asSpan = std::span(values);
|
||||
return c_readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
|
||||
}
|
||||
|
||||
template<typename TItBegin, typename TItEnd>
|
||||
inline StreamError readSpan(TItBegin&& begin, TItEnd&& end)
|
||||
StreamError readSpan(TItBegin&& begin, TItEnd&& end, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
auto asSpan = std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end));
|
||||
return readRaw(asSpan.data(), asSpan.size_bytes());
|
||||
return readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
|
||||
}
|
||||
|
||||
template<typename TItBegin, typename TItEnd>
|
||||
mijin::Task<StreamError> c_readSpan(TItBegin&& begin, TItEnd&& end, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
|
||||
{
|
||||
auto asSpan = std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end));
|
||||
return c_readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline StreamError write(const T& value)
|
||||
StreamError write(const T& value) requires(std::is_trivial_v<T>)
|
||||
{
|
||||
return writeRaw(&value, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline StreamError writeSpan(const T& values)
|
||||
mijin::Task<StreamError> c_write(const T& value) requires(std::is_trivial_v<T>)
|
||||
{
|
||||
return c_writeRaw(&value, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
StreamError writeSpan(const T& values)
|
||||
{
|
||||
auto asSpan = std::span(values);
|
||||
return writeRaw(asSpan.data(), asSpan.size_bytes());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
mijin::Task<StreamError> c_writeSpan(const T& values)
|
||||
{
|
||||
auto asSpan = std::span(values);
|
||||
return c_writeRaw(asSpan.data(), asSpan.size_bytes());
|
||||
}
|
||||
|
||||
template<typename TItBegin, typename TItEnd>
|
||||
inline StreamError writeSpan(TItBegin&& begin, TItEnd&& end)
|
||||
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);
|
||||
template<typename TItBegin, typename TItEnd>
|
||||
mijin::Task<StreamError> c_writeSpan(TItBegin&& begin, TItEnd&& end)
|
||||
{
|
||||
return c_writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end)));
|
||||
}
|
||||
|
||||
StreamError readBinaryString(std::string& outString);
|
||||
StreamError writeBinaryString(std::string_view str);
|
||||
|
||||
mijin::Task<StreamError> c_readBinaryString(std::string& outString);
|
||||
mijin::Task<StreamError> c_writeBinaryString(std::string_view str);
|
||||
|
||||
[[deprecated("Use readBinaryString() or readAsString() instead.")]]
|
||||
inline StreamError readString(std::string& outString) { return readBinaryString(outString); }
|
||||
|
||||
[[deprecated("Use writeBinaryString() or writeText() instead.")]]
|
||||
inline StreamError writeString(std::string_view str) { return writeBinaryString(str); }
|
||||
|
||||
StreamError getTotalLength(std::size_t& outLength);
|
||||
StreamError readRest(TypelessBuffer& outBuffer);
|
||||
mijin::Task<StreamError> c_readRest(TypelessBuffer& outBuffer);
|
||||
|
||||
StreamError readLine(std::string& outString);
|
||||
mijin::Task<StreamError> c_readLine(std::string& outString);
|
||||
|
||||
template<typename TChar = char>
|
||||
StreamError readAsString(std::basic_string<TChar>& outString);
|
||||
|
||||
template<typename TChar = char>
|
||||
mijin::Task<StreamError> c_readAsString(std::basic_string<TChar>& outString);
|
||||
|
||||
StreamError writeText(std::string_view str)
|
||||
{
|
||||
return writeSpan(str);
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> c_writeText(std::string_view str)
|
||||
{
|
||||
return c_writeSpan(str);
|
||||
}
|
||||
};
|
||||
|
||||
class FileStream : public Stream
|
||||
@@ -142,7 +309,7 @@ public:
|
||||
[[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 readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, 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;
|
||||
@@ -159,27 +326,182 @@ private:
|
||||
bool canWrite_ = false;
|
||||
public:
|
||||
void openRW(std::span<std::uint8_t> data);
|
||||
void openRO(std::span<const std::uint8_t> data);
|
||||
template<typename T>
|
||||
void openRO(std::span<T> data) {
|
||||
openROImpl(data.data(), data.size_bytes());
|
||||
}
|
||||
template<typename TChar>
|
||||
inline void openRO(std::basic_string_view<TChar> stringView) {
|
||||
openROImpl(stringView.data(), stringView.size() * sizeof(TChar));
|
||||
}
|
||||
void close();
|
||||
[[nodiscard]] inline bool isOpen() const { return data_.data() != nullptr; }
|
||||
[[nodiscard]] inline std::size_t availableBytes() const {
|
||||
assert(isOpen());
|
||||
[[nodiscard]] inline std::size_t availableBytes() const
|
||||
{
|
||||
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
|
||||
return data_.size() - pos_;
|
||||
}
|
||||
|
||||
// Stream overrides
|
||||
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
|
||||
StreamError readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, 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;
|
||||
private:
|
||||
void openROImpl(const void* data, std::size_t bytes);
|
||||
};
|
||||
|
||||
template<typename TSuccess>
|
||||
using StreamResult = ResultBase<TSuccess, StreamError>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename TChar>
|
||||
StreamError Stream::readAsString(std::basic_string<TChar>& outString)
|
||||
{
|
||||
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
|
||||
|
||||
// first try to allocate everything at once
|
||||
std::size_t length = 0;
|
||||
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
|
||||
length -= tell();
|
||||
outString.resize(length);
|
||||
if (StreamError error = readRaw(outString.data(), length); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
// could not determine the size, read chunk-wise
|
||||
static constexpr std::size_t CHUNK_SIZE = 4096;
|
||||
std::array<TChar, CHUNK_SIZE> chunk;
|
||||
|
||||
outString.clear();
|
||||
while (!isAtEnd())
|
||||
{
|
||||
std::size_t bytesRead = 0;
|
||||
if (StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
outString.append(chunk.data(), chunk.data() + bytesRead);
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
template<typename TChar>
|
||||
mijin::Task<StreamError> Stream::c_readAsString(std::basic_string<TChar>& outString)
|
||||
{
|
||||
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
|
||||
|
||||
// first try to allocate everything at once
|
||||
std::size_t length = 0;
|
||||
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
|
||||
length -= tell();
|
||||
outString.resize(length);
|
||||
if (StreamError error = co_await c_readRaw(outString.data(), length); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
// could not determine the size, read chunk-wise
|
||||
static constexpr std::size_t CHUNK_SIZE = 4096;
|
||||
std::array<TChar, CHUNK_SIZE> chunk;
|
||||
|
||||
outString.clear();
|
||||
while (!isAtEnd())
|
||||
{
|
||||
std::size_t bytesRead = 0;
|
||||
if (StreamError error = co_await c_readRaw(chunk, true, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
outString.append(chunk.data(), chunk.data() + bytesRead);
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
inline const char* errorName(StreamError error) MIJIN_NOEXCEPT
|
||||
{
|
||||
switch (error)
|
||||
{
|
||||
case StreamError::SUCCESS:
|
||||
return "success";
|
||||
case StreamError::IO_ERROR:
|
||||
return "IO error";
|
||||
case StreamError::NOT_SUPPORTED:
|
||||
return "not supported";
|
||||
case StreamError::CONNECTION_CLOSED:
|
||||
return "connection closed";
|
||||
case StreamError::PROTOCOL_ERROR:
|
||||
return "protocol error";
|
||||
case StreamError::WOULD_BLOCK:
|
||||
return "would block";
|
||||
case StreamError::UNKNOWN_ERROR:
|
||||
return "unknown error";
|
||||
case StreamError::CONNECTION_REFUSED:
|
||||
return "connection refused";
|
||||
}
|
||||
return "<invalid error>";
|
||||
}
|
||||
|
||||
#if MIJIN_ENABLE_EXCEPTIONS // these functions don't make sense with exceptions disabled
|
||||
inline void throwOnError(mijin::StreamError error)
|
||||
{
|
||||
if (error == mijin::StreamError::SUCCESS) {
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(errorName(error));
|
||||
}
|
||||
|
||||
inline void throwOnError(mijin::StreamError error, std::string message)
|
||||
{
|
||||
if (error == mijin::StreamError::SUCCESS) {
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(message + ": " + errorName(error));
|
||||
}
|
||||
|
||||
template<typename TSuccess>
|
||||
inline decltype(auto) throwOnError(StreamResult<TSuccess>&& result)
|
||||
{
|
||||
if (result.isError())
|
||||
{
|
||||
throw Exception(errorName(result.getError()));
|
||||
}
|
||||
else if (!result.isSuccess())
|
||||
{
|
||||
throw Exception("result is empty");
|
||||
}
|
||||
return *result;
|
||||
}
|
||||
|
||||
template<typename TSuccess>
|
||||
inline decltype(auto) throwOnError(StreamResult<TSuccess>&& result, const std::string& message)
|
||||
{
|
||||
if (result.isError())
|
||||
{
|
||||
throw Exception(message + ": " + errorName(result.getError()));
|
||||
}
|
||||
else if (!result.isSuccess())
|
||||
{
|
||||
throw Exception(message + ": result is empty");
|
||||
}
|
||||
return *result;
|
||||
}
|
||||
#endif // MIJIN_ENABLE_EXCEPTIONS
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
|
||||
|
||||
263
source/mijin/net/curl_wrappers.hpp
Normal file
263
source/mijin/net/curl_wrappers.hpp
Normal file
@@ -0,0 +1,263 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_CURL_WRAPPERS_HPP_INCLUDED)
|
||||
#define MIJIN_NET_CURL_WRAPPERS_HPP_INCLUDED 1
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "./url.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../types/result.hpp"
|
||||
|
||||
namespace curl
|
||||
{
|
||||
struct [[nodiscard]] Error
|
||||
{
|
||||
CURLcode code = CURLE_OK;
|
||||
|
||||
[[nodiscard]]
|
||||
bool isSuccess() const MIJIN_NOEXCEPT { return code == CURLE_OK; }
|
||||
};
|
||||
template<typename TSuccess>
|
||||
using Result = mijin::ResultBase<TSuccess, Error>;
|
||||
|
||||
#define MIJIN_CURL_VERIFY_RESULT(curlResult) \
|
||||
do \
|
||||
{ \
|
||||
if ((curlResult) != CURLE_OK) \
|
||||
{ \
|
||||
return Error{curlResult}; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
using curl_write_callback_t = size_t(*)(char*, size_t, size_t, void*);
|
||||
using curl_read_callback_t = curl_write_callback_t;
|
||||
|
||||
class SList
|
||||
{
|
||||
private:
|
||||
curl_slist* next_ = nullptr;
|
||||
public:
|
||||
SList() MIJIN_NOEXCEPT = default;
|
||||
SList(const SList&) = delete;
|
||||
SList(SList&& other) noexcept : next_(std::exchange(other.next_, nullptr)) {}
|
||||
~SList() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (next_ != nullptr)
|
||||
{
|
||||
curl_slist_free_all(next_);
|
||||
}
|
||||
}
|
||||
|
||||
SList& operator=(const SList&) = delete;
|
||||
SList& operator=(SList&& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (&other == this)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (next_ != nullptr)
|
||||
{
|
||||
curl_slist_free_all(next_);
|
||||
}
|
||||
next_ = std::exchange(other.next_, nullptr);
|
||||
return *this;
|
||||
}
|
||||
auto operator<=>(const SList&) const noexcept = default;
|
||||
|
||||
void append(const char* str) MIJIN_NOEXCEPT
|
||||
{
|
||||
next_ = curl_slist_append(next_, str);
|
||||
}
|
||||
|
||||
friend class CurlEasy;
|
||||
};
|
||||
|
||||
class CurlEasy
|
||||
{
|
||||
private:
|
||||
CURL* handle_ = nullptr;
|
||||
SList headers_;
|
||||
public:
|
||||
CurlEasy() MIJIN_NOEXCEPT = default;
|
||||
CurlEasy(const CurlEasy&) = delete;
|
||||
CurlEasy(CurlEasy&& other) MIJIN_NOEXCEPT : handle_(std::exchange(other.handle_, nullptr)), headers_(std::move(other.headers_)) {}
|
||||
~CurlEasy() MIJIN_NOEXCEPT
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
CurlEasy& operator=(const CurlEasy&) = delete;
|
||||
CurlEasy& operator=(CurlEasy&& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (this == &other)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
reset();
|
||||
handle_ = std::exchange(other.handle_, nullptr);
|
||||
headers_ = std::move(other.headers_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool init() MIJIN_NOEXCEPT
|
||||
{
|
||||
reset();
|
||||
handle_ = curl_easy_init();
|
||||
if (handle_ == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (handle_)
|
||||
{
|
||||
curl_easy_cleanup(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Error setURL(const char* url) MIJIN_THROWS
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_URL, url);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setURL(const std::string& url) MIJIN_NOEXCEPT
|
||||
{
|
||||
return setURL(url.c_str());
|
||||
}
|
||||
|
||||
Error setURL(const mijin::URL& url) MIJIN_NOEXCEPT
|
||||
{
|
||||
return setURL(url.getBase().c_str());
|
||||
}
|
||||
|
||||
Error setSSLVerifyPeer(bool verify) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYPEER, verify);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setSSLVerifyHost(bool verify) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYHOST, verify);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setWriteFunction(curl_write_callback_t callback) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, callback);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setWriteData(void* data) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_WRITEDATA, data);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setPostFields(const char* data, bool copy = false) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, copy ? CURLOPT_COPYPOSTFIELDS : CURLOPT_POSTFIELDS, data);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setUpload(bool upload) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_UPLOAD, upload ? 1 : 0);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setInFileSize(curl_off_t size) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_INFILESIZE_LARGE, size);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setReadFunction(curl_read_callback_t callback) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_READFUNCTION, callback);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setReadData(const void* data) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_READDATA, data);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setNoBody(bool nobody) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_NOBODY, nobody ? 1 : 0);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setCustomRequest(bool customRequest) MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, customRequest ? 1 : 0);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error setHeaders(SList headers) MIJIN_NOEXCEPT
|
||||
{
|
||||
headers_ = std::move(headers);
|
||||
const CURLcode result = curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, headers_.next_);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Error perform() MIJIN_NOEXCEPT
|
||||
{
|
||||
const CURLcode result = curl_easy_perform(handle_);
|
||||
return {result};
|
||||
}
|
||||
|
||||
Result<int> getHTTPVersion() MIJIN_NOEXCEPT
|
||||
{
|
||||
return getInfo<int, long>(CURLINFO_HTTP_VERSION);
|
||||
}
|
||||
|
||||
Result<int> getStatus() MIJIN_NOEXCEPT
|
||||
{
|
||||
return getInfo<int, long>(CURLINFO_RESPONSE_CODE);
|
||||
}
|
||||
|
||||
Result<std::multimap<std::string, std::string>> getResponseHeaders() MIJIN_NOEXCEPT
|
||||
{
|
||||
// TODO: how to detect errors?
|
||||
std::multimap<std::string, std::string> result;
|
||||
curl_header* header = nullptr;
|
||||
curl_header* prevHeader = nullptr;
|
||||
|
||||
while ((header = curl_easy_nextheader(handle_, CURLH_HEADER, 0, prevHeader)) != nullptr)
|
||||
{
|
||||
result.emplace(header->name, header->value);
|
||||
prevHeader = header;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
private:
|
||||
template<typename TResult, typename TInfo = TResult>
|
||||
Result<TResult> getInfo(CURLINFO info) MIJIN_NOEXCEPT
|
||||
{
|
||||
TInfo value = 0;
|
||||
const CURLcode result = curl_easy_getinfo(handle_, info, &value);
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
return Error{result};
|
||||
}
|
||||
return static_cast<TResult>(value);
|
||||
}
|
||||
};
|
||||
} // namespace curl
|
||||
|
||||
#endif // !defined(MIJIN_NET_CURL_WRAPPERS_HPP_INCLUDED)
|
||||
27
source/mijin/net/detail/net_common.hpp
Normal file
27
source/mijin/net/detail/net_common.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../detect.hpp"
|
||||
#include "../../internal/common.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <WinSock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include "../util/winundef.hpp"
|
||||
#endif // MIJIN_TARGET_OS
|
||||
|
||||
namespace mijin::detail
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
bool initWSA() MIJIN_NOEXCEPT;
|
||||
StreamError translateWSAError() MIJIN_NOEXCEPT;
|
||||
StreamError translateWinError(DWORD error) MIJIN_NOEXCEPT;
|
||||
StreamError translateWinError() MIJIN_NOEXCEPT;
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
}// namespace mijin::detail
|
||||
299
source/mijin/net/http.cpp
Normal file
299
source/mijin/net/http.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
|
||||
#include "./http.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "../util/iterators.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
#if defined(MIJIN_ENABLE_OPENSSL)
|
||||
#include "./ssl.hpp"
|
||||
#endif
|
||||
|
||||
#define MIJIN_HTTP_WRITE(text) \
|
||||
do \
|
||||
{ \
|
||||
if (const StreamError error = co_await base_->c_writeText(text); error != StreamError::SUCCESS) \
|
||||
{ \
|
||||
co_return error; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
#define MIJIN_HTTP_CHECKREAD(read) \
|
||||
do \
|
||||
{ \
|
||||
if (const StreamError error = co_await read; error != StreamError::SUCCESS) \
|
||||
{ \
|
||||
co_return error; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
#define MIJIN_HTTP_READLINE(text) MIJIN_HTTP_CHECKREAD(base_->c_readLine(text)); text = trim(text)
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
inline constexpr std::size_t CONTENT_LENGTH_LIMIT = 100 << 20; // 100MiB
|
||||
bool parseHTTPVersion(std::string_view version, HTTPVersion& outVersion) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::vector<std::string_view> parts = split(version, ".");
|
||||
if (parts.size() != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return toNumber(parts[0], outVersion.major) && toNumber(parts[1], outVersion.minor);
|
||||
}
|
||||
}
|
||||
|
||||
Task<StreamResult<HTTPResponse>> HTTPStream::c_request(HTTPRequest request) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (const StreamError error = co_await c_writeRequest(request); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
co_return co_await c_readResponse();
|
||||
}
|
||||
|
||||
Task<StreamError> HTTPStream::c_writeRequest(const mijin::HTTPRequest& request) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::map<std::string, std::string> moreHeaders;
|
||||
if (!request.body.empty())
|
||||
{
|
||||
auto itLength = request.headers.find("content-length");
|
||||
if (itLength == request.headers.end())
|
||||
{
|
||||
moreHeaders.emplace("content-length", std::to_string(request.body.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::size_t headerValue = 0;
|
||||
if (!toNumber(itLength->second, headerValue) || headerValue != request.body.size())
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MIJIN_HTTP_WRITE(std::format("{} {} HTTP/{}.{}\n", request.method, request.address, request.version.major, request.version.minor));
|
||||
for (const auto& [key, value] : moreHeaders)
|
||||
{
|
||||
MIJIN_HTTP_WRITE(std::format("{}: {}\n", key, value));
|
||||
}
|
||||
for (const auto& [key, value] : request.headers)
|
||||
{
|
||||
MIJIN_HTTP_WRITE(std::format("{}: {}\n", key, value));
|
||||
}
|
||||
|
||||
MIJIN_HTTP_WRITE("\n");
|
||||
if (!request.body.empty())
|
||||
{
|
||||
MIJIN_HTTP_WRITE(request.body);
|
||||
}
|
||||
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
Task<StreamResult<HTTPResponse>> HTTPStream::c_readResponse() MIJIN_NOEXCEPT
|
||||
{
|
||||
std::string line;
|
||||
MIJIN_HTTP_READLINE(line);
|
||||
|
||||
std::vector<std::string_view> parts = split(line, " ", {.limitParts = 3});
|
||||
if (parts.size() != 3)
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
if (!parts[0].starts_with("HTTP/"))
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
HTTPResponse response;
|
||||
if (!parseHTTPVersion(parts[0].substr(5), response.version)
|
||||
|| !toNumber(parts[1], response.status))
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
response.statusMessage = parts[2];
|
||||
|
||||
decltype(response.headers)::iterator lastHeader;
|
||||
while (true)
|
||||
{
|
||||
MIJIN_HTTP_READLINE(line);
|
||||
if (line.empty()) {
|
||||
break;
|
||||
}
|
||||
if (line[0] == ' ' || line[0] == '\t')
|
||||
{
|
||||
// continuation
|
||||
if (lastHeader == response.headers.end())
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
lastHeader->second.push_back(' ');
|
||||
lastHeader->second.append(trim(line));
|
||||
}
|
||||
parts = split(line, ":", {.limitParts = 2});
|
||||
if (parts.size() != 2)
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
lastHeader = response.headers.emplace(toLower(trim(parts[0])), trim(parts[1]));
|
||||
}
|
||||
|
||||
auto itContentLength = response.headers.find("content-length");
|
||||
if (itContentLength != response.headers.end())
|
||||
{
|
||||
std::size_t contentLength = 0;
|
||||
if (!toNumber(itContentLength->second, contentLength))
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
if (contentLength > CONTENT_LENGTH_LIMIT)
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
response.body.resize(contentLength);
|
||||
MIJIN_HTTP_CHECKREAD(base_->c_readRaw(response.body));
|
||||
}
|
||||
|
||||
co_return response;
|
||||
}
|
||||
|
||||
Task<StreamResult<HTTPResponse>> HTTPClient::c_request(ip_address_t address, std::uint16_t port, bool https,
|
||||
HTTPRequest request) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::string hostname;
|
||||
if (auto it = request.headers.find("host"); it != request.headers.end())
|
||||
{
|
||||
hostname = it->second;
|
||||
}
|
||||
if (const StreamError error = createSocket(address, hostname, port, https); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
if (!request.headers.contains("connection"))
|
||||
{
|
||||
request.headers.emplace("connection", "keep-alive");
|
||||
}
|
||||
StreamResult<HTTPResponse> response = co_await stream_->c_request(request);
|
||||
if (response.isError())
|
||||
{
|
||||
disconnect();
|
||||
if (const StreamError error = createSocket(address, hostname, port, https); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
response = co_await stream_->c_request(request);
|
||||
}
|
||||
co_return response;
|
||||
}
|
||||
|
||||
Task<StreamResult<HTTPResponse>> HTTPClient::c_request(const URL& url, HTTPRequest request) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (url.getHost().empty())
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
std::uint16_t port = url.getPort();
|
||||
bool https = false;
|
||||
if (equalsIgnoreCase(url.getScheme(), "http"))
|
||||
{
|
||||
port = (port != 0) ? port : 80;
|
||||
}
|
||||
else if (equalsIgnoreCase(url.getScheme(), "https"))
|
||||
{
|
||||
port = (port != 0) ? port : 443;
|
||||
https = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
Optional<ip_address_t> ipAddress = ipAddressFromString(url.getHost());
|
||||
if (ipAddress.empty())
|
||||
{
|
||||
StreamResult<std::vector<ip_address_t>> addresses = co_await c_resolveHostname(url.getHost());
|
||||
if (addresses.isError())
|
||||
{
|
||||
co_return addresses.getError();
|
||||
}
|
||||
else if (addresses->empty())
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
// TODO: try all addresses
|
||||
ipAddress = addresses->front();
|
||||
}
|
||||
|
||||
if (!request.headers.contains("host"))
|
||||
{
|
||||
request.headers.emplace("host", url.getHost());
|
||||
}
|
||||
request.address = url.getPathQueryFragment();
|
||||
if (request.address.empty())
|
||||
{
|
||||
request.address = "/";
|
||||
}
|
||||
|
||||
co_return co_await c_request(*ipAddress, port, https, std::move(request));
|
||||
}
|
||||
|
||||
void HTTPClient::disconnect() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (socket_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
stream_.destroy();
|
||||
socket_ = nullptr;
|
||||
}
|
||||
|
||||
StreamError HTTPClient::createSocket(ip_address_t address, const std::string& hostname, std::uint16_t port, bool https) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (socket_ != nullptr && address == lastIP_ && port == lastPort_ && https == lastWasHttps_)
|
||||
{
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
disconnect();
|
||||
|
||||
std::unique_ptr<TCPSocket> newSocket = std::make_unique<TCPSocket>();
|
||||
if (const StreamError error = newSocket->open(address, port); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
socket_ = std::move(newSocket);
|
||||
if (!https)
|
||||
{
|
||||
sslStream_.reset();
|
||||
stream_.construct(socket_->getStream());
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(MIJIN_ENABLE_OPENSSL)
|
||||
std::unique_ptr<SSLStream> sslStream = std::make_unique<SSLStream>();
|
||||
if (const StreamError error = sslStream->open(socket_->getStream(), hostname); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
sslStream_ = std::move(sslStream);
|
||||
stream_.construct(*sslStream_);
|
||||
#else
|
||||
(void) hostname;
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
lastIP_ = address;
|
||||
lastPort_ = port;
|
||||
lastWasHttps_ = https;
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
#undef MIJIN_HTTP_WRITE
|
||||
#undef MIJIN_HTTP_CHECKREAD
|
||||
#undef MIJIN_HTTP_READLINE
|
||||
93
source/mijin/net/http.hpp
Normal file
93
source/mijin/net/http.hpp
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_HTTP_HPP_INCLUDED)
|
||||
#define MIJIN_NET_HTTP_HPP_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "./socket.hpp"
|
||||
#include "./url.hpp"
|
||||
#include "../container/boxed_object.hpp"
|
||||
#include "../container/typeless_buffer.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../io/stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace http_method
|
||||
{
|
||||
inline const std::string GET = "GET";
|
||||
inline const std::string POST = "POST";
|
||||
inline const std::string HEAD = "HEAD";
|
||||
inline const std::string PUT = "PUT";
|
||||
inline const std::string DELETE = "DELETE";
|
||||
}
|
||||
|
||||
struct HTTPVersion
|
||||
{
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
};
|
||||
|
||||
struct HTTPRequest
|
||||
{
|
||||
HTTPVersion version = {1, 1};
|
||||
std::string address;
|
||||
std::string method = "GET";
|
||||
std::multimap<std::string, std::string> headers;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
struct HTTPRequestOptions
|
||||
{
|
||||
std::string method = "GET";
|
||||
std::multimap<std::string, std::string> headers;
|
||||
TypelessBuffer body;
|
||||
};
|
||||
|
||||
struct HTTPResponse
|
||||
{
|
||||
HTTPVersion version;
|
||||
unsigned status;
|
||||
std::string statusMessage;
|
||||
std::multimap<std::string, std::string> headers;
|
||||
TypelessBuffer body;
|
||||
};
|
||||
|
||||
class HTTPStream
|
||||
{
|
||||
private:
|
||||
Stream* base_;
|
||||
public:
|
||||
HTTPStream(Stream& base) MIJIN_NOEXCEPT : base_(&base)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Invalid parameter for base.");
|
||||
}
|
||||
Task<StreamResult<HTTPResponse>> c_request(HTTPRequest request) MIJIN_NOEXCEPT;
|
||||
private:
|
||||
Task<StreamError> c_writeRequest(const HTTPRequest& request) MIJIN_NOEXCEPT;
|
||||
Task<StreamResult<HTTPResponse>> c_readResponse() MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
class HTTPClient
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<Socket> socket_;
|
||||
std::unique_ptr<Stream> sslStream_;
|
||||
mijin::BoxedObject<HTTPStream> stream_;
|
||||
ip_address_t lastIP_;
|
||||
std::uint16_t lastPort_ = 0;
|
||||
bool lastWasHttps_ = false;
|
||||
public:
|
||||
~HTTPClient() MIJIN_NOEXCEPT { disconnect(); }
|
||||
Task<StreamResult<HTTPResponse>> c_request(ip_address_t address, std::uint16_t port, bool https, HTTPRequest request) MIJIN_NOEXCEPT;
|
||||
Task<StreamResult<HTTPResponse>> c_request(const URL& url, HTTPRequest request = {}) MIJIN_NOEXCEPT;
|
||||
void disconnect() MIJIN_NOEXCEPT;
|
||||
private:
|
||||
StreamError createSocket(ip_address_t address, const std::string& hostname, std::uint16_t port, bool https) MIJIN_NOEXCEPT;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_NET_HTTP_HPP_INCLUDED)
|
||||
300
source/mijin/net/ip.cpp
Normal file
300
source/mijin/net/ip.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
|
||||
#include "./ip.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "../detect.hpp"
|
||||
#include "../util/string.hpp"
|
||||
#include "./detail/net_common.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
#if !defined(_GNU_SOURCE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
|
||||
#endif // MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
struct AddrInfoContext
|
||||
{
|
||||
gaicb item;
|
||||
gaicb* list = &item;
|
||||
};
|
||||
using os_resolve_handle_t = AddrInfoContext;
|
||||
|
||||
StreamError translateGAIError(int error)
|
||||
{
|
||||
(void) error; // TODO
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
StreamError osBeginResolve(const std::string& hostname, os_resolve_handle_t& handle) MIJIN_NOEXCEPT
|
||||
{
|
||||
handle.item = {.ar_name = hostname.c_str()};
|
||||
|
||||
const int result = getaddrinfo_a(GAI_NOWAIT, &handle.list, 1, nullptr);
|
||||
if (result != 0)
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
bool osResolveDone(os_resolve_handle_t& handle) MIJIN_NOEXCEPT
|
||||
{
|
||||
return gai_error(&handle.item) != EAI_INPROGRESS;
|
||||
}
|
||||
|
||||
StreamResult<std::vector<ip_address_t>> osResolveResult(os_resolve_handle_t& handle) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (const int error = gai_error(&handle.item); error != 0)
|
||||
{
|
||||
if (handle.item.ar_result != nullptr)
|
||||
{
|
||||
freeaddrinfo(handle.item.ar_result);
|
||||
}
|
||||
return translateGAIError(error);
|
||||
}
|
||||
if (handle.item.ar_result == nullptr)
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
std::vector<ip_address_t> resultAddresses;
|
||||
for (addrinfo* result = handle.item.ar_result; result != nullptr; result = result->ai_next)
|
||||
{
|
||||
if (result->ai_protocol != IPPROTO_TCP)
|
||||
{
|
||||
// we actually just care about TCP, right?
|
||||
continue;
|
||||
}
|
||||
switch (result->ai_family)
|
||||
{
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
#error "TODO: swap byte order of the address"
|
||||
#endif
|
||||
case AF_INET:
|
||||
{
|
||||
sockaddr_in& addr = *reinterpret_cast<sockaddr_in*>(result->ai_addr);
|
||||
resultAddresses.emplace_back(std::bit_cast<IPv4Address>(addr.sin_addr));
|
||||
break;
|
||||
}
|
||||
case AF_INET6:
|
||||
{
|
||||
sockaddr_in6& addr = *reinterpret_cast<sockaddr_in6*>(result->ai_addr);
|
||||
IPv6Address addr6 = std::bit_cast<IPv6Address>(addr.sin6_addr);
|
||||
for (std::uint16_t& hextet : addr6.hextets)
|
||||
{
|
||||
hextet = ntohs(hextet);
|
||||
}
|
||||
resultAddresses.emplace_back(addr6);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(handle.item.ar_result);
|
||||
return resultAddresses;
|
||||
}
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
struct WSAQueryContext
|
||||
{
|
||||
// WSA stuff
|
||||
OVERLAPPED overlapped = {};
|
||||
PADDRINFOEX results;
|
||||
HANDLE cancelHandle = nullptr;
|
||||
|
||||
// my stuff
|
||||
StreamResult<std::vector<ip_address_t>> result;
|
||||
};
|
||||
using os_resolve_handle_t = WSAQueryContext;
|
||||
|
||||
void WINAPI getAddrComplete(DWORD error, DWORD bytes, LPOVERLAPPED overlapped) MIJIN_NOEXCEPT
|
||||
{
|
||||
(void) bytes;
|
||||
|
||||
WSAQueryContext& queryContext = *CONTAINING_RECORD(overlapped, WSAQueryContext, overlapped);
|
||||
if (error != ERROR_SUCCESS)
|
||||
{
|
||||
queryContext.result = detail::translateWinError(error);
|
||||
}
|
||||
std::vector<ip_address_t> resultAddresses;
|
||||
for (PADDRINFOEX result = queryContext.results; result != nullptr; result = result->ai_next)
|
||||
{
|
||||
switch (result->ai_family)
|
||||
{
|
||||
case AF_INET:
|
||||
{
|
||||
sockaddr_in& addr = *reinterpret_cast<sockaddr_in*>(result->ai_addr);
|
||||
resultAddresses.emplace_back(std::bit_cast<IPv4Address>(addr.sin_addr));
|
||||
}
|
||||
break;
|
||||
case AF_INET6:
|
||||
{
|
||||
sockaddr_in6& addr = *reinterpret_cast<sockaddr_in6*>(result->ai_addr);
|
||||
IPv6Address addr6 = std::bit_cast<IPv6Address>(addr.sin6_addr);
|
||||
for (std::uint16_t& hextet : addr6.hextets)
|
||||
{
|
||||
hextet = ntohs(hextet);
|
||||
}
|
||||
resultAddresses.emplace_back(addr6);
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (queryContext.results != nullptr)
|
||||
{
|
||||
FreeAddrInfoEx(queryContext.results);
|
||||
}
|
||||
queryContext.result = std::move(resultAddresses);
|
||||
}
|
||||
|
||||
StreamError osBeginResolve(const std::string& hostname, os_resolve_handle_t& queryContext) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (!detail::initWSA())
|
||||
{
|
||||
return detail::translateWSAError();
|
||||
}
|
||||
ADDRINFOEX hints = {.ai_family = AF_UNSPEC};
|
||||
|
||||
std::wstring hostnameW(hostname.begin(), hostname.end());
|
||||
const int error = GetAddrInfoEx(
|
||||
/* pName = */ hostnameW.c_str(),
|
||||
/* pServiceName = */ nullptr,
|
||||
/* dwNameSpace = */ NS_DNS,
|
||||
/* lpNspId = */ nullptr,
|
||||
/* hints = */ &hints,
|
||||
/* ppResult = */ &queryContext.results,
|
||||
/* timeout = */ nullptr,
|
||||
/* lpOverlapped = */ &queryContext.overlapped,
|
||||
/* lpCompletionRoutine = */ &getAddrComplete,
|
||||
/* lpNameHandle = */ nullptr
|
||||
);
|
||||
if (error != WSA_IO_PENDING)
|
||||
{
|
||||
getAddrComplete(error, 0, &queryContext.overlapped);
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
bool osResolveDone(os_resolve_handle_t& queryContext) MIJIN_NOEXCEPT
|
||||
{
|
||||
return !queryContext.result.isEmpty();
|
||||
}
|
||||
|
||||
StreamResult<std::vector<ip_address_t>> osResolveResult(os_resolve_handle_t& queryContext) MIJIN_NOEXCEPT
|
||||
{
|
||||
return queryContext.result;
|
||||
}
|
||||
#endif // MIJIN_TARGET_OS
|
||||
}
|
||||
|
||||
std::string IPv4Address::toString() const
|
||||
{
|
||||
return std::format("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]);
|
||||
}
|
||||
|
||||
std::string IPv6Address::toString() const
|
||||
{
|
||||
return std::format("{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", hextets[0], hextets[1], hextets[2], hextets[3], hextets[4],
|
||||
hextets[5], hextets[6], hextets[7]);
|
||||
}
|
||||
|
||||
Optional<IPv4Address> IPv4Address::fromString(std::string_view stringView) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::vector<std::string_view> parts = split(stringView, ".", {.limitParts = 4});
|
||||
if (parts.size() != 4) {
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
IPv4Address address;
|
||||
for (int idx = 0; idx < 4; ++idx)
|
||||
{
|
||||
if (!toNumber(parts[idx], address.octets[idx]))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
Optional<IPv6Address> IPv6Address::fromString(std::string_view stringView) MIJIN_NOEXCEPT
|
||||
{
|
||||
// very specific edge case
|
||||
if (stringView.contains(":::"))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> parts = split(stringView, "::", {.ignoreEmpty = false});
|
||||
if (parts.size() > 2)
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
if (parts.size() == 1)
|
||||
{
|
||||
parts.emplace_back("");
|
||||
}
|
||||
|
||||
std::vector<std::string_view> partsLeft = split(parts[0], ":");
|
||||
std::vector<std::string_view> partsRight = split(parts[1], ":");
|
||||
|
||||
std::erase_if(partsLeft, std::mem_fn(&std::string_view::empty));
|
||||
std::erase_if(partsRight, std::mem_fn(&std::string_view::empty));
|
||||
|
||||
if (partsLeft.size() + partsRight.size() > 8)
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
IPv6Address address = {};
|
||||
unsigned hextet = 0;
|
||||
for (std::string_view part : partsLeft)
|
||||
{
|
||||
if (!toNumber(part, address.hextets[hextet], /* base = */ 16))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
++hextet;
|
||||
}
|
||||
for (; hextet < (8 - partsRight.size()); ++hextet)
|
||||
{
|
||||
address.hextets[hextet] = 0;
|
||||
}
|
||||
for (std::string_view part : partsRight)
|
||||
{
|
||||
if (!toNumber(part, address.hextets[hextet], /* base = */ 16))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
++hextet;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
Task<StreamResult<std::vector<ip_address_t>>> c_resolveHostname(std::string hostname) MIJIN_NOEXCEPT
|
||||
{
|
||||
os_resolve_handle_t resolveHandle;
|
||||
if (StreamError error = osBeginResolve(hostname, resolveHandle); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
while (!osResolveDone(resolveHandle))
|
||||
{
|
||||
co_await c_suspend();
|
||||
}
|
||||
co_return osResolveResult(resolveHandle);
|
||||
}
|
||||
}
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma clang diagnostic pop
|
||||
#endif // MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
80
source/mijin/net/ip.hpp
Normal file
80
source/mijin/net/ip.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_IP_HPP_INCLUDED)
|
||||
#define MIJIN_NET_IP_HPP_INCLUDED 1
|
||||
|
||||
#include <array>
|
||||
#include <variant>
|
||||
#include "../async/coroutine.hpp"
|
||||
#include "../container/optional.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../io/stream.hpp" // TODO: rename Stream{Error,Result} to IO{*}
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
struct IPv4Address
|
||||
{
|
||||
std::array<std::uint8_t, 4> octets;
|
||||
|
||||
auto operator<=>(const IPv4Address&) const MIJIN_NOEXCEPT = default;
|
||||
|
||||
[[nodiscard]] std::string toString() const;
|
||||
|
||||
[[nodiscard]]
|
||||
static Optional<IPv4Address> fromString(std::string_view stringView) MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
struct IPv6Address
|
||||
{
|
||||
std::array<std::uint16_t, 8> hextets;
|
||||
|
||||
auto operator<=>(const IPv6Address&) const MIJIN_NOEXCEPT = default;
|
||||
|
||||
[[nodiscard]] std::string toString() const;
|
||||
|
||||
[[nodiscard]]
|
||||
static Optional<IPv6Address> fromString(std::string_view stringView) MIJIN_NOEXCEPT;
|
||||
};
|
||||
using ip_address_t = std::variant<IPv4Address, IPv6Address>;
|
||||
|
||||
[[nodiscard]]
|
||||
inline std::string ipAddressToString(const ip_address_t& address) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (address.valueless_by_exception())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return std::visit([](const auto& addr) { return addr.toString(); }, address);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
inline Optional<ip_address_t> ipAddressFromString(std::string_view stringView) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (Optional<IPv4Address> ipv4Address = IPv4Address::fromString(stringView); !ipv4Address.empty())
|
||||
{
|
||||
return ip_address_t(*ipv4Address);
|
||||
}
|
||||
if (Optional<IPv6Address> ipv6Address = IPv6Address::fromString(stringView); !ipv6Address.empty())
|
||||
{
|
||||
return ip_address_t(*ipv6Address);
|
||||
}
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Task<StreamResult<std::vector<ip_address_t>>> c_resolveHostname(std::string hostname) MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]]
|
||||
inline Task<StreamResult<std::vector<ip_address_t>>> c_resolveHostname(std::string_view hostname) MIJIN_NOEXCEPT
|
||||
{
|
||||
return c_resolveHostname(std::string(hostname.begin(), hostname.end()));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
inline Task<StreamResult<std::vector<ip_address_t>>> c_resolveHostname(const char* hostname) MIJIN_NOEXCEPT
|
||||
{
|
||||
return c_resolveHostname(std::string(hostname));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_NET_IP_HPP_INCLUDED)
|
||||
442
source/mijin/net/openssl_wrappers.hpp
Normal file
442
source/mijin/net/openssl_wrappers.hpp
Normal file
@@ -0,0 +1,442 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED)
|
||||
#define MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED 1
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../types/result.hpp"
|
||||
|
||||
namespace ossl
|
||||
{
|
||||
struct ErrorFrame
|
||||
{
|
||||
std::string message;
|
||||
std::string file;
|
||||
std::string function;
|
||||
std::string data;
|
||||
unsigned long numeric = 0;
|
||||
int line = 0;
|
||||
int flags = 0;
|
||||
};
|
||||
|
||||
struct [[nodiscard]] Error
|
||||
{
|
||||
int sslError = SSL_ERROR_NONE;
|
||||
std::vector<ErrorFrame> frames;
|
||||
|
||||
[[nodiscard]]
|
||||
bool isSuccess() const MIJIN_NOEXCEPT { return sslError == SSL_ERROR_NONE; }
|
||||
|
||||
static inline Error current(int sslError = -1) MIJIN_NOEXCEPT;
|
||||
static inline Error current(SSL* handle, int result) MIJIN_NOEXCEPT { return current(SSL_get_error(handle, result)); }
|
||||
};
|
||||
template<typename TSuccess>
|
||||
using Result = mijin::ResultBase<TSuccess, Error>;
|
||||
|
||||
// callback typedefs
|
||||
using verify_callback_t = int (*) (int, X509_STORE_CTX *);
|
||||
|
||||
template<typename TActual, typename THandle>
|
||||
class Base
|
||||
{
|
||||
protected:
|
||||
using base_t = Base<TActual, THandle>;
|
||||
|
||||
THandle handle_ = nullptr;
|
||||
protected:
|
||||
explicit Base(THandle handle) MIJIN_NOEXCEPT : handle_(handle) {}
|
||||
public:
|
||||
Base() MIJIN_NOEXCEPT = default;
|
||||
Base(const Base& other) MIJIN_NOEXCEPT : handle_(other.handle_)
|
||||
{
|
||||
if (handle_)
|
||||
{
|
||||
TActual::upReferences(handle_);
|
||||
}
|
||||
}
|
||||
Base(Base&& other) MIJIN_NOEXCEPT : handle_(std::exchange(other.handle_, {})) {}
|
||||
|
||||
~Base() MIJIN_NOEXCEPT
|
||||
{
|
||||
static_cast<TActual&>(*this).free();
|
||||
}
|
||||
|
||||
TActual& operator=(const Base& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (this == &other)
|
||||
{
|
||||
return static_cast<TActual&>(*this);
|
||||
}
|
||||
static_cast<TActual&>(*this).free();
|
||||
handle_ = other.handle_;
|
||||
if (handle_)
|
||||
{
|
||||
TActual::upReferences(handle_);
|
||||
}
|
||||
return static_cast<TActual&>(*this);
|
||||
}
|
||||
|
||||
TActual& operator=(Base&& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (this == &other)
|
||||
{
|
||||
return static_cast<TActual&>(*this);
|
||||
}
|
||||
static_cast<TActual&>(*this).free();
|
||||
handle_ = std::exchange(other.handle_, {});
|
||||
return static_cast<TActual&>(*this);
|
||||
}
|
||||
auto operator<=>(const Base&) const MIJIN_NOEXCEPT = default;
|
||||
operator bool() const MIJIN_NOEXCEPT { return static_cast<bool>(handle_); }
|
||||
bool operator!() const MIJIN_NOEXCEPT { return !static_cast<bool>(handle_); }
|
||||
|
||||
[[nodiscard]]
|
||||
THandle getHandle() const MIJIN_NOEXCEPT { return handle_; }
|
||||
|
||||
[[nodiscard]]
|
||||
THandle releaseHandle() MIJIN_NOEXCEPT { return std::exchange(handle_, nullptr); }
|
||||
};
|
||||
|
||||
class X509Store : public Base<X509Store, X509_STORE*>
|
||||
{
|
||||
public:
|
||||
using Base::Base;
|
||||
Error create() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(handle_ == nullptr, "X509 Store already created.");
|
||||
ERR_clear_error();
|
||||
handle_ = X509_STORE_new();
|
||||
if (handle_ == nullptr)
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void free() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (handle_ != nullptr)
|
||||
{
|
||||
X509_STORE_free(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Error loadFile(const char* file) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (!X509_STORE_load_file(handle_, file))
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void upReferences(X509_STORE* handle) MIJIN_NOEXCEPT
|
||||
{
|
||||
X509_STORE_up_ref(handle);
|
||||
}
|
||||
};
|
||||
|
||||
class Context : public Base<Context, SSL_CTX*>
|
||||
{
|
||||
public:
|
||||
Error create(const SSL_METHOD* method) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(handle_ == nullptr, "Context already created.");
|
||||
ERR_clear_error();
|
||||
handle_ = SSL_CTX_new(method);
|
||||
if (handle_ == nullptr)
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void free() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (handle_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SSL_CTX_free(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
|
||||
void setVerify(int mode, verify_callback_t callback = nullptr) const MIJIN_NOEXCEPT
|
||||
{
|
||||
SSL_CTX_set_verify(handle_, mode, callback);
|
||||
}
|
||||
|
||||
void setCertStore(X509Store store) const MIJIN_NOEXCEPT
|
||||
{
|
||||
SSL_CTX_set_cert_store(handle_, store.releaseHandle());
|
||||
}
|
||||
|
||||
Error setMinProtoVersion(int version) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (!SSL_CTX_set_min_proto_version(handle_, version))
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void upReferences(SSL_CTX* handle) MIJIN_NOEXCEPT
|
||||
{
|
||||
SSL_CTX_up_ref(handle);
|
||||
}
|
||||
};
|
||||
|
||||
class Bio : public Base<Bio, BIO*>
|
||||
{
|
||||
public:
|
||||
Error createPair(Bio& otherBio, std::size_t writeBuf = 0, std::size_t otherWriteBuf = 0) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(handle_ == nullptr, "Ssl already created.");
|
||||
MIJIN_ASSERT(otherBio.handle_ == nullptr, "Ssl already created.");
|
||||
ERR_clear_error();
|
||||
if (!BIO_new_bio_pair(&handle_, writeBuf, &otherBio.handle_, otherWriteBuf))
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void free() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (handle_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BIO_free_all(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t ctrlPending() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return BIO_ctrl_pending(handle_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t ctrlWPending() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return BIO_ctrl_wpending(handle_);
|
||||
}
|
||||
|
||||
Result<int> write(const void* data, int length) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
const int result = BIO_write(handle_, data, length);
|
||||
if (result <= 0)
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<int> read(void* data, int length) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
const int result = BIO_read(handle_, data, length);
|
||||
if (result <= 0)
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int getReadRequest() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return BIO_get_read_request(handle_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int getWriteGuarantee() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return BIO_get_write_guarantee(handle_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int getWritePending() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return BIO_wpending(handle_);
|
||||
}
|
||||
|
||||
Error flush() const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (!BIO_flush(handle_))
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void upReferences(BIO* handle) MIJIN_NOEXCEPT
|
||||
{
|
||||
BIO_up_ref(handle);
|
||||
}
|
||||
};
|
||||
|
||||
class Ssl : public Base<Ssl, SSL*>
|
||||
{
|
||||
public:
|
||||
Error create(const Context& context) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(handle_ == nullptr, "Ssl already created.");
|
||||
ERR_clear_error();
|
||||
handle_ = SSL_new(context.getHandle());
|
||||
if (handle_ == nullptr)
|
||||
{
|
||||
return Error::current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void free() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (handle_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SSL_free(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
|
||||
void setBio(Bio readBio, Bio writeBio) const MIJIN_NOEXCEPT
|
||||
{
|
||||
SSL_set_bio(handle_, readBio.releaseHandle(), writeBio.releaseHandle());
|
||||
}
|
||||
|
||||
void setBio(Bio&& bio) const MIJIN_NOEXCEPT
|
||||
{
|
||||
BIO* bioHandle = bio.releaseHandle();
|
||||
SSL_set_bio(handle_, bioHandle, bioHandle);
|
||||
}
|
||||
|
||||
Error setTLSExtHostname(const char* hostname) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (const int result = SSL_set_tlsext_host_name(handle_, hostname); result != 1)
|
||||
{
|
||||
return Error::current(handle_, result);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Error setHost(const char* hostname) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (const int result = SSL_set1_host(handle_, hostname); result != 1)
|
||||
{
|
||||
return Error::current(handle_, result);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Error connect() const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (const int result = SSL_connect(handle_); result != 1)
|
||||
{
|
||||
return Error::current(handle_, result);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Error shutdown() const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (const int result = SSL_shutdown(handle_); result != 1)
|
||||
{
|
||||
if (result == 0)
|
||||
{
|
||||
return Error{.sslError = SSL_ERROR_WANT_WRITE}; // TODO?
|
||||
}
|
||||
return Error::current(handle_, result);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
long getVerifyResult() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return SSL_get_verify_result(handle_);
|
||||
}
|
||||
|
||||
Result<int> write(const void* data, int length) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
const int result = SSL_write(handle_, data, length);
|
||||
if (result <= 0)
|
||||
{
|
||||
return Error::current(handle_, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<int> read(void* data, int length) const MIJIN_NOEXCEPT
|
||||
{
|
||||
ERR_clear_error();
|
||||
const int result = SSL_read(handle_, data, length);
|
||||
if (result <= 0)
|
||||
{
|
||||
return Error::current(handle_, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int pending() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return SSL_pending(handle_);
|
||||
}
|
||||
|
||||
static void upReferences(SSL* handle) MIJIN_NOEXCEPT
|
||||
{
|
||||
SSL_up_ref(handle);
|
||||
}
|
||||
};
|
||||
|
||||
Error Error::current(int sslError_) MIJIN_NOEXCEPT
|
||||
{
|
||||
Error error = {
|
||||
.sslError = sslError_
|
||||
};
|
||||
const char* file = nullptr;
|
||||
int line = 0;
|
||||
const char* func = nullptr;
|
||||
const char* data = nullptr;
|
||||
int flags = 0;
|
||||
|
||||
while (const unsigned long numeric = ERR_get_error_all(&file, &line, &func, &data, &flags))
|
||||
{
|
||||
error.frames.push_back({
|
||||
.message = ERR_error_string(numeric, nullptr),
|
||||
.file = file != nullptr ? file : "",
|
||||
.function = func != nullptr ? func : "",
|
||||
.data = data != nullptr ? data : "",
|
||||
.line = line,
|
||||
.flags = flags
|
||||
});
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED)
|
||||
183
source/mijin/net/request.cpp
Normal file
183
source/mijin/net/request.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
#include "./request.hpp"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "./curl_wrappers.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool gCurlInited = false;
|
||||
std::mutex gCurlInitMutex;
|
||||
|
||||
class CURLGuard
|
||||
{
|
||||
public:
|
||||
~CURLGuard() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (gCurlInited)
|
||||
{
|
||||
curl_global_cleanup();
|
||||
}
|
||||
}
|
||||
} gCURLGuard [[maybe_unused]];
|
||||
|
||||
struct ReadData
|
||||
{
|
||||
mijin::TypelessBuffer* buffer;
|
||||
std::size_t pos;
|
||||
};
|
||||
|
||||
bool initCURL() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (gCurlInited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const std::unique_lock initLock(gCurlInitMutex);
|
||||
if (gCurlInited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
gCurlInited = (curl_global_init(0) == CURLE_OK);
|
||||
return gCurlInited;
|
||||
}
|
||||
|
||||
std::size_t writeCallback(char* ptr, std::size_t /* size */, std::size_t nmemb, void* userdata) noexcept
|
||||
{
|
||||
TypelessBuffer& body = *static_cast<TypelessBuffer*>(userdata);
|
||||
body.append(std::span<const char>(ptr, nmemb));
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
std::size_t readCallback(char* ptr, std::size_t size, std::size_t nmemb, void* userdata) noexcept
|
||||
{
|
||||
ReadData& data = *static_cast<ReadData*>(userdata);
|
||||
const std::size_t bytesToRead = std::min(size * nmemb, data.buffer->byteSize() - data.pos);
|
||||
std::memcpy(ptr, static_cast<std::byte*>(data.buffer->data()) + data.pos, bytesToRead);
|
||||
data.pos += bytesToRead;
|
||||
|
||||
return bytesToRead;
|
||||
}
|
||||
}
|
||||
|
||||
Task<StreamResult<HTTPResponse>> c_request(const URL& url, HTTPRequestOptions options) MIJIN_NOEXCEPT
|
||||
{
|
||||
ReadData readData;
|
||||
|
||||
if (!initCURL())
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
curl::CurlEasy easy;
|
||||
if (!easy.init())
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
curl::SList requestHeaders;
|
||||
for (const auto& [name, value] : options.headers)
|
||||
{
|
||||
requestHeaders.append(std::format("{}: {}", name, value).c_str());
|
||||
}
|
||||
|
||||
#define HANDLE_CURL_RESULT(result) \
|
||||
if (const curl::Error error = (result); !error.isSuccess()) \
|
||||
{ \
|
||||
co_return StreamError::UNKNOWN_ERROR; \
|
||||
}
|
||||
HANDLE_CURL_RESULT(easy.setHeaders(std::move(requestHeaders)))
|
||||
|
||||
if (options.method == http_method::POST)
|
||||
{
|
||||
HANDLE_CURL_RESULT(easy.setPostFields(options.body.makeSpan<const char>().data()))
|
||||
}
|
||||
else if (options.method == http_method::PUT)
|
||||
{
|
||||
HANDLE_CURL_RESULT(easy.setUpload(true))
|
||||
HANDLE_CURL_RESULT(easy.setInFileSize(options.body.byteSize()));
|
||||
HANDLE_CURL_RESULT(easy.setReadFunction(&readCallback));
|
||||
readData.buffer = &options.body;
|
||||
readData.pos = 0;
|
||||
HANDLE_CURL_RESULT(easy.setReadData(&readData));
|
||||
}
|
||||
else if (options.method == http_method::HEAD)
|
||||
{
|
||||
HANDLE_CURL_RESULT(easy.setNoBody(true))
|
||||
}
|
||||
else if (options.method != http_method::GET)
|
||||
{
|
||||
// different option, let's do our best
|
||||
HANDLE_CURL_RESULT(easy.setCustomRequest(true))
|
||||
if (!options.body.empty())
|
||||
{
|
||||
HANDLE_CURL_RESULT(easy.setUpload(true))
|
||||
HANDLE_CURL_RESULT(easy.setInFileSize(options.body.byteSize()));
|
||||
HANDLE_CURL_RESULT(easy.setReadFunction(&readCallback));
|
||||
readData.buffer = &options.body;
|
||||
readData.pos = 0;
|
||||
HANDLE_CURL_RESULT(easy.setReadData(&readData));
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE_CURL_RESULT(easy.setURL(url))
|
||||
HANDLE_CURL_RESULT(easy.setWriteFunction(&writeCallback))
|
||||
TypelessBuffer body;
|
||||
HANDLE_CURL_RESULT(easy.setWriteData(&body))
|
||||
HANDLE_CURL_RESULT(easy.perform())
|
||||
#undef HANDLE_CURL_RESULT
|
||||
|
||||
HTTPResponse response = {
|
||||
.version = {},
|
||||
.body = std::move(body)
|
||||
};
|
||||
if (const curl::Result<int> httpVersion = easy.getHTTPVersion(); httpVersion.isSuccess())
|
||||
{
|
||||
switch (*httpVersion)
|
||||
{
|
||||
case CURL_HTTP_VERSION_1_0:
|
||||
response.version = {1, 0};
|
||||
break;
|
||||
case CURL_HTTP_VERSION_1_1:
|
||||
response.version = {1, 1};
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2_0:
|
||||
response.version = {2, 0};
|
||||
break;
|
||||
case CURL_HTTP_VERSION_3:
|
||||
response.version = {3, 0};
|
||||
break;
|
||||
default:
|
||||
MIJIN_ERROR("Unknown CURL http version returned.");
|
||||
response.version = {1, 0};
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
if (const curl::Result<int> status = easy.getStatus(); status.isSuccess())
|
||||
{
|
||||
response.status = *status;
|
||||
}
|
||||
else
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (curl::Result<std::multimap<std::string, std::string>> headers = easy.getResponseHeaders(); headers.isSuccess())
|
||||
{
|
||||
response.headers = std::move(*headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
co_return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
co_return response;
|
||||
}
|
||||
} // namespace mijin
|
||||
15
source/mijin/net/request.hpp
Normal file
15
source/mijin/net/request.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_REQUST_HPP_INCLUDED)
|
||||
#define MIJIN_NET_REQUST_HPP_INCLUDED 1
|
||||
|
||||
#include "./http.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
Task<StreamResult<HTTPResponse>> c_request(const URL& url, HTTPRequestOptions options = {}) MIJIN_NOEXCEPT;
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_NET_REQUST_HPP_INCLUDED)
|
||||
485
source/mijin/net/socket.cpp
Normal file
485
source/mijin/net/socket.cpp
Normal file
@@ -0,0 +1,485 @@
|
||||
|
||||
#include "./socket.hpp"
|
||||
|
||||
#include "./detail/net_common.hpp"
|
||||
#include "../detect.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/variant.hpp"
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
|
||||
#endif // MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
inline constexpr int LISTEN_BACKLOG = 3;
|
||||
StreamError translateErrno() MIJIN_NOEXCEPT
|
||||
{
|
||||
switch (errno)
|
||||
{
|
||||
case EIO:
|
||||
return StreamError::IO_ERROR;
|
||||
case ECONNREFUSED:
|
||||
return StreamError::CONNECTION_REFUSED;
|
||||
default:
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int readFlags(const ReadOptions& options)
|
||||
{
|
||||
return (options.partial ? 0 : MSG_WAITALL)
|
||||
| (options.peek ? MSG_PEEK : 0);
|
||||
}
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
const int SOCKOPT_ONE = 1;
|
||||
|
||||
bool appendSocketFlags(int handle, int flags) MIJIN_NOEXCEPT
|
||||
{
|
||||
const int currentFlags = fcntl(handle, F_GETFL);
|
||||
if (currentFlags < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return fcntl(handle, F_SETFL, currentFlags | flags) >= 0;
|
||||
}
|
||||
|
||||
bool removeSocketFlags(int handle, int flags) MIJIN_NOEXCEPT
|
||||
{
|
||||
const int currentFlags = fcntl(handle, F_GETFL);
|
||||
if (currentFlags < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return fcntl(handle, F_SETFL, currentFlags & ~flags) >= 0;
|
||||
}
|
||||
|
||||
long osRecv(int socket, std::span<std::uint8_t> buffer, int flags) MIJIN_NOEXCEPT
|
||||
{
|
||||
return static_cast<long>(recv(socket, buffer.data(), buffer.size(), flags));
|
||||
}
|
||||
|
||||
long osSend(int socket, std::span<const std::uint8_t> buffer, int flags) MIJIN_NOEXCEPT
|
||||
{
|
||||
return static_cast<long>(send(socket, buffer.data(), buffer.size(), flags));
|
||||
}
|
||||
|
||||
int osCreateSocket(int domain, int type, int protocol) MIJIN_NOEXCEPT
|
||||
{
|
||||
return socket(domain, type, protocol);
|
||||
}
|
||||
|
||||
int osCloseSocket(int socket) MIJIN_NOEXCEPT
|
||||
{
|
||||
return ::close(socket);
|
||||
}
|
||||
|
||||
bool osIsSocketValid(int socket) MIJIN_NOEXCEPT
|
||||
{
|
||||
return socket >= 0;
|
||||
}
|
||||
|
||||
bool osSetSocketNonBlocking(int socket, bool blocking) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (blocking)
|
||||
{
|
||||
return appendSocketFlags(socket, O_NONBLOCK);
|
||||
}
|
||||
else
|
||||
{
|
||||
return removeSocketFlags(socket, O_NONBLOCK);
|
||||
}
|
||||
}
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
using in_addr_t = ULONG;
|
||||
|
||||
const char SOCKOPT_ONE = 1;
|
||||
thread_local bool gWsaInited = false;
|
||||
|
||||
class WSAGuard
|
||||
{
|
||||
public:
|
||||
~WSAGuard() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (gWsaInited)
|
||||
{
|
||||
WSACleanup();
|
||||
}
|
||||
}
|
||||
} thread_local [[maybe_unused]] gWsaGuard;
|
||||
|
||||
long osRecv(SOCKET socket, std::span<std::uint8_t> buffer, int flags) MIJIN_NOEXCEPT
|
||||
{
|
||||
return recv(socket, reinterpret_cast<char*>(buffer.data()), static_cast<int>(buffer.size()), flags);
|
||||
}
|
||||
|
||||
long osSend(SOCKET socket, std::span<const std::uint8_t> buffer, int flags) MIJIN_NOEXCEPT
|
||||
{
|
||||
return send(socket, reinterpret_cast<const char*>(buffer.data()), static_cast<int>(buffer.size()), flags);
|
||||
}
|
||||
|
||||
SOCKET osCreateSocket(int addressFamily, int type, int protocol) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (!detail::initWSA())
|
||||
{
|
||||
return INVALID_SOCKET_HANDLE;
|
||||
}
|
||||
return socket(addressFamily, type, protocol);
|
||||
}
|
||||
|
||||
int osCloseSocket(SOCKET socket) MIJIN_NOEXCEPT
|
||||
{
|
||||
return closesocket(socket);
|
||||
}
|
||||
|
||||
bool osIsSocketValid(SOCKET socket) MIJIN_NOEXCEPT
|
||||
{
|
||||
return socket != INVALID_SOCKET;
|
||||
}
|
||||
|
||||
bool osSetSocketNonBlocking(SOCKET socket, bool blocking) MIJIN_NOEXCEPT
|
||||
{
|
||||
u_long value = blocking ? 0 : 1;
|
||||
return ioctlsocket(socket, FIONBIO, &value) == NO_ERROR;
|
||||
}
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
bool initWSA() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (gWsaInited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gWsaInited = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
StreamError translateWSAError() MIJIN_NOEXCEPT
|
||||
{
|
||||
// TODO
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
StreamError translateWinError(DWORD error) MIJIN_NOEXCEPT
|
||||
{
|
||||
// TODO
|
||||
(void) error;
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
StreamError translateWinError() MIJIN_NOEXCEPT
|
||||
{
|
||||
return translateWinError(GetLastError());
|
||||
}
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
}// namespace impl
|
||||
|
||||
StreamError TCPStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
MIJIN_ASSERT(isOpen(), "Socket is not open.");
|
||||
setNoblock(options.noBlock);
|
||||
|
||||
const long bytesRead = osRecv(handle_, buffer, readFlags(options));
|
||||
if (bytesRead < 0)
|
||||
{
|
||||
if (!options.noBlock || errno != EAGAIN)
|
||||
{
|
||||
return translateErrno();
|
||||
}
|
||||
if (outBytesRead != nullptr)
|
||||
{
|
||||
*outBytesRead = 0;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
if (outBytesRead != nullptr)
|
||||
{
|
||||
*outBytesRead = static_cast<std::size_t>(bytesRead);
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError TCPStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
MIJIN_ASSERT(isOpen(), "Socket is not open.");
|
||||
setNoblock(false);
|
||||
|
||||
if (osSend(handle_, buffer, 0) < 0)
|
||||
{
|
||||
return translateErrno();
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> TCPStream::c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
MIJIN_ASSERT(isOpen(), "Socket is not open.");
|
||||
setNoblock(true);
|
||||
|
||||
if (buffer.empty())
|
||||
{
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
while(true)
|
||||
{
|
||||
const long bytesRead = osRecv(handle_, buffer, readFlags(options));
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
if (outBytesRead != nullptr) {
|
||||
*outBytesRead = static_cast<std::size_t>(bytesRead);
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
co_return StreamError::CONNECTION_CLOSED;
|
||||
}
|
||||
if (errno != EAGAIN)
|
||||
{
|
||||
co_return translateErrno();
|
||||
}
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
Task<StreamError> TCPStream::c_writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
MIJIN_ASSERT(isOpen(), "Socket is not open.");
|
||||
|
||||
if (buffer.empty())
|
||||
{
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
setNoblock(true);
|
||||
|
||||
while (true)
|
||||
{
|
||||
const long bytesSent = osSend(handle_, buffer, 0);
|
||||
if (bytesSent == static_cast<long>(buffer.size()))
|
||||
{
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
else if (bytesSent == 0)
|
||||
{
|
||||
co_return StreamError::CONNECTION_CLOSED;
|
||||
}
|
||||
else if (errno != EAGAIN)
|
||||
{
|
||||
co_return translateErrno();
|
||||
}
|
||||
co_await c_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPStream::setNoblock(bool async)
|
||||
{
|
||||
if (async == async_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
async_ = async;
|
||||
|
||||
osSetSocketNonBlocking(handle_, async);
|
||||
}
|
||||
|
||||
std::size_t TCPStream::tell()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
StreamError TCPStream::seek(std::intptr_t /* pos */, SeekMode /* seekMode */)
|
||||
{
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void TCPStream::flush()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool TCPStream::isAtEnd()
|
||||
{
|
||||
return !isOpen();
|
||||
}
|
||||
|
||||
StreamFeatures TCPStream::getFeatures()
|
||||
{
|
||||
return {
|
||||
.read = true,
|
||||
.write = true,
|
||||
.tell = false,
|
||||
.seek = false,
|
||||
.async = true,
|
||||
.readOptions = {
|
||||
.partial = true,
|
||||
.peek = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
StreamError TCPStream::open(ip_address_t address, std::uint16_t port) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(!isOpen(), "Socket is already open.");
|
||||
|
||||
handle_ = osCreateSocket(AF_INET, SOCK_STREAM, 0);
|
||||
if (!osIsSocketValid(handle_))
|
||||
{
|
||||
return translateErrno();
|
||||
}
|
||||
|
||||
const bool connected = std::visit(Visitor{
|
||||
[&](const IPv4Address& address4)
|
||||
{
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
#error "TODO: swap byte order of the address"
|
||||
#endif
|
||||
sockaddr_in connectAddress =
|
||||
{
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
.sin_addr = std::bit_cast<in_addr>(address4.octets)
|
||||
};
|
||||
return connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in)) == 0;
|
||||
},
|
||||
[&](IPv6Address address6)
|
||||
{
|
||||
for (std::uint16_t& hextet : address6.hextets) {
|
||||
hextet = htons(hextet);
|
||||
}
|
||||
sockaddr_in6 connectAddress =
|
||||
{
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_port = htons(port),
|
||||
.sin6_addr = std::bit_cast<in6_addr>(address6)
|
||||
};
|
||||
return connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in6)) == 0;
|
||||
}
|
||||
}, address);
|
||||
if (!connected)
|
||||
{
|
||||
osCloseSocket(handle_);
|
||||
handle_ = INVALID_SOCKET_HANDLE;
|
||||
return translateErrno();
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
void TCPStream::close() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(isOpen(), "Socket is not open.");
|
||||
osCloseSocket(handle_);
|
||||
handle_ = INVALID_SOCKET_HANDLE;
|
||||
}
|
||||
|
||||
TCPStream& TCPSocket::getStream() MIJIN_NOEXCEPT
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
StreamError TCPServerSocket::setup(ip_address_t address, std::uint16_t port) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(!isListening(), "Socket is already listening.");
|
||||
|
||||
handle_ = osCreateSocket(AF_INET, SOCK_STREAM, 0);
|
||||
if (!osIsSocketValid(handle_))
|
||||
{
|
||||
return translateErrno();
|
||||
}
|
||||
if (setsockopt(handle_, SOL_SOCKET, SO_REUSEADDR, &SOCKOPT_ONE, sizeof(int)) != 0)
|
||||
{
|
||||
close();
|
||||
return translateErrno();
|
||||
}
|
||||
|
||||
const bool bound = std::visit(Visitor{
|
||||
[&](const IPv4Address& address4)
|
||||
{
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
#error "TODO: swap byte order of the address"
|
||||
#endif
|
||||
sockaddr_in bindAddress =
|
||||
{
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
.sin_addr = std::bit_cast<in_addr>(address4.octets)
|
||||
};
|
||||
return bind(handle_, reinterpret_cast<sockaddr*>(&bindAddress), sizeof(sockaddr_in)) == 0;
|
||||
},
|
||||
[&](IPv6Address address6)
|
||||
{
|
||||
for (std::uint16_t& hextet : address6.hextets) {
|
||||
hextet = htons(hextet);
|
||||
}
|
||||
sockaddr_in6 bindAddress =
|
||||
{
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_port = htons(port),
|
||||
.sin6_addr = std::bit_cast<in6_addr>(address6)
|
||||
};
|
||||
return bind(handle_, reinterpret_cast<sockaddr*>(&bindAddress), sizeof(sockaddr_in6)) == 0;
|
||||
}
|
||||
}, address);
|
||||
if (!bound
|
||||
|| (listen(handle_, LISTEN_BACKLOG) < 0)
|
||||
|| !osSetSocketNonBlocking(handle_, true))
|
||||
{
|
||||
close();
|
||||
return translateErrno();
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
void TCPServerSocket::close() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(isListening(), "Socket is not listening.");
|
||||
|
||||
osCloseSocket(handle_);
|
||||
handle_ = INVALID_SOCKET_HANDLE;
|
||||
}
|
||||
|
||||
Task<StreamResult<std::unique_ptr<TCPSocket>>> TCPServerSocket::c_waitForConnection() MIJIN_NOEXCEPT
|
||||
{
|
||||
while (isListening())
|
||||
{
|
||||
sockaddr_in client = {};
|
||||
socklen_t LENGTH = sizeof(sockaddr_in);
|
||||
const socket_handle_t newSocket = accept(handle_, reinterpret_cast<sockaddr*>(&client), &LENGTH);
|
||||
if (!osIsSocketValid(newSocket))
|
||||
{
|
||||
if (errno != EAGAIN)
|
||||
{
|
||||
co_return translateErrno();
|
||||
}
|
||||
co_await c_suspend();
|
||||
continue;
|
||||
}
|
||||
std::unique_ptr<TCPSocket> socket = std::make_unique<TCPSocket>();
|
||||
socket->stream_.handle_ = newSocket;
|
||||
co_return socket;
|
||||
}
|
||||
co_return StreamError::CONNECTION_CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma clang diagnostic pop
|
||||
#endif // MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
114
source/mijin/net/socket.hpp
Normal file
114
source/mijin/net/socket.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_SOCKET_HPP_INCLUDED)
|
||||
#define MIJIN_NET_SOCKET_HPP_INCLUDED 1
|
||||
|
||||
#include "./ip.hpp"
|
||||
#include "../detect.hpp"
|
||||
#include "../async/coroutine.hpp"
|
||||
#include "../container/optional.hpp"
|
||||
#include "../io/stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
using socket_handle_t = std::uintptr_t;
|
||||
|
||||
inline constexpr socket_handle_t INVALID_SOCKET_HANDLE = static_cast<socket_handle_t>(-1);
|
||||
#else
|
||||
using socket_handle_t = int;
|
||||
|
||||
inline constexpr socket_handle_t INVALID_SOCKET_HANDLE = -1;
|
||||
#endif
|
||||
|
||||
class Socket
|
||||
{
|
||||
protected:
|
||||
Socket() MIJIN_NOEXCEPT = default;
|
||||
Socket(const Socket&) MIJIN_NOEXCEPT = default;
|
||||
Socket(Socket&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
Socket& operator=(const Socket&) MIJIN_NOEXCEPT = default;
|
||||
Socket& operator=(Socket&&) MIJIN_NOEXCEPT = default;
|
||||
public:
|
||||
virtual ~Socket() MIJIN_NOEXCEPT = default;
|
||||
|
||||
virtual void close() MIJIN_NOEXCEPT = 0;
|
||||
virtual Stream& getStream() MIJIN_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
class TCPStream : public Stream
|
||||
{
|
||||
private:
|
||||
socket_handle_t handle_ = INVALID_SOCKET_HANDLE;
|
||||
bool async_ = false;
|
||||
public:
|
||||
StreamError readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead) override;
|
||||
StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
|
||||
mijin::Task<StreamError> c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t *outBytesRead) override;
|
||||
mijin::Task<StreamError> c_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;
|
||||
|
||||
StreamError open(ip_address_t address, std::uint16_t port) MIJIN_NOEXCEPT;
|
||||
void close() MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] bool isOpen() const MIJIN_NOEXCEPT { return handle_ != INVALID_SOCKET_HANDLE; }
|
||||
private:
|
||||
void setNoblock(bool async);
|
||||
|
||||
friend class TCPServerSocket;
|
||||
};
|
||||
|
||||
class TCPSocket : public Socket
|
||||
{
|
||||
private:
|
||||
TCPStream stream_;
|
||||
public:
|
||||
TCPStream& getStream() MIJIN_NOEXCEPT override;
|
||||
|
||||
StreamError open(ip_address_t address, std::uint16_t port) MIJIN_NOEXCEPT { return stream_.open(address, port); }
|
||||
StreamError open(std::string_view addressText, std::uint16_t port) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (Optional<ip_address_t> address = ipAddressFromString(addressText); !address.empty())
|
||||
{
|
||||
return open(*address, port);
|
||||
}
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
void close() MIJIN_NOEXCEPT override { stream_.close(); }
|
||||
[[nodiscard]] bool isOpen() const MIJIN_NOEXCEPT { return stream_.isOpen(); }
|
||||
|
||||
friend class TCPServerSocket;
|
||||
};
|
||||
|
||||
class TCPServerSocket
|
||||
{
|
||||
private:
|
||||
socket_handle_t handle_ = INVALID_SOCKET_HANDLE;
|
||||
public:
|
||||
StreamError setup(ip_address_t address, std::uint16_t port) MIJIN_NOEXCEPT;
|
||||
StreamError setup(std::string_view addressText, std::uint16_t port) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (Optional<ip_address_t> address = ipAddressFromString(addressText); !address.empty())
|
||||
{
|
||||
return setup(*address, port);
|
||||
}
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
void close() MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] bool isListening() const MIJIN_NOEXCEPT { return handle_ != INVALID_SOCKET_HANDLE; }
|
||||
|
||||
Task<StreamResult<std::unique_ptr<TCPSocket>>> c_waitForConnection() MIJIN_NOEXCEPT;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_NET_SOCKET_HPP_INCLUDED)
|
||||
298
source/mijin/net/ssl.cpp
Normal file
298
source/mijin/net/ssl.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
|
||||
#include "./ssl.hpp"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
inline constexpr int BIO_BUFFER_SIZE = 4096;
|
||||
ossl::Result<ossl::Context*> getSSLContext() MIJIN_NOEXCEPT
|
||||
{
|
||||
static ossl::Context context;
|
||||
static std::mutex contextMutex;
|
||||
|
||||
if (!context)
|
||||
{
|
||||
const std::unique_lock contextLock(contextMutex);
|
||||
|
||||
if (context)
|
||||
{
|
||||
return &context;
|
||||
}
|
||||
|
||||
ossl::Context newContext;
|
||||
if (const ossl::Error error = newContext.create(SSLv23_client_method()); !error.isSuccess())
|
||||
{
|
||||
return error;
|
||||
}
|
||||
newContext.setVerify(SSL_VERIFY_PEER);
|
||||
|
||||
ossl::X509Store store;
|
||||
if (const ossl::Error error = store.create(); !error.isSuccess())
|
||||
{
|
||||
return error;
|
||||
}
|
||||
if (const ossl::Error error = store.loadFile("/etc/ssl/cert.pem"); !error.isSuccess())
|
||||
{
|
||||
return error;
|
||||
}
|
||||
newContext.setCertStore(std::move(store));
|
||||
if (const ossl::Error error = newContext.setMinProtoVersion(TLS1_2_VERSION); !error.isSuccess())
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
context = std::move(newContext);
|
||||
}
|
||||
|
||||
return &context;
|
||||
}
|
||||
}
|
||||
|
||||
StreamError SSLStream::open(Stream& base, const std::string& hostname) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(base_ == nullptr, "SSL stream is already open.");
|
||||
|
||||
const ossl::Result<ossl::Context*> contextResult = getSSLContext();
|
||||
if (contextResult.isError())
|
||||
{
|
||||
// TODO: convert/print error
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
ossl::Context& context = *contextResult.getValue();
|
||||
|
||||
ossl::Ssl ssl;
|
||||
if (const ossl::Error error = ssl.create(context); !error.isSuccess())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
ossl::Bio externalBio;
|
||||
ossl::Bio internalBio;
|
||||
if (const ossl::Error error = internalBio.createPair(externalBio, BIO_BUFFER_SIZE, BIO_BUFFER_SIZE); !error.isSuccess())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
ssl.setBio(std::move(internalBio));
|
||||
|
||||
if (const ossl::Error error = ssl.setTLSExtHostname(hostname.c_str()); !error.isSuccess())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (const ossl::Error error = ssl.setHost(hostname.c_str()); !error.isSuccess())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// these need to be initialized for connecting
|
||||
ssl_ = std::move(ssl);
|
||||
externalBio_ = std::move(externalBio);
|
||||
base_ = &base;
|
||||
|
||||
if (const ossl::Error error = runIOLoop(&ossl::Ssl::connect, true); !error.isSuccess())
|
||||
{
|
||||
ssl_.free();
|
||||
externalBio.free();
|
||||
base_ = nullptr;
|
||||
return StreamError::UNKNOWN_ERROR; // TODO: translate
|
||||
}
|
||||
|
||||
if (ssl_.getVerifyResult() != X509_V_OK)
|
||||
{
|
||||
ssl_.free();
|
||||
externalBio.free();
|
||||
base_ = nullptr;
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
void SSLStream::close() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "SSL stream is not open.");
|
||||
base_ = nullptr;
|
||||
(void) runIOLoop(&ossl::Ssl::shutdown, true);
|
||||
ssl_.free();
|
||||
externalBio_.free();
|
||||
}
|
||||
|
||||
StreamError SSLStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
if (buffer.empty())
|
||||
{
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
std::size_t bytesToWrite = buffer.size();
|
||||
while (bytesToWrite > 0)
|
||||
{
|
||||
const ossl::Result<int> result = runIOLoop(&ossl::Ssl::write, true, buffer.data() + buffer.size() - bytesToWrite,
|
||||
static_cast<int>(bytesToWrite));
|
||||
if (result.isError())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
bytesToWrite -= result.getValue();
|
||||
|
||||
if (const StreamError error = bioToBase(); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError SSLStream::readRaw(std::span<std::uint8_t> buffer, const mijin::ReadOptions& options,
|
||||
std::size_t* outBytesRead)
|
||||
{
|
||||
if (buffer.empty())
|
||||
{
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
if (const StreamError error = baseToBio(); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!options.partial && options.noBlock && static_cast<std::size_t>(ssl_.pending()) < buffer.size())
|
||||
{
|
||||
return StreamError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
std::size_t bytesToRead = buffer.size();
|
||||
if (options.partial)
|
||||
{
|
||||
bytesToRead = std::min<std::size_t>(bytesToRead, ssl_.pending());
|
||||
}
|
||||
while (bytesToRead > 0)
|
||||
{
|
||||
const ossl::Result<int> result = runIOLoop(&ossl::Ssl::read, !options.noBlock,
|
||||
buffer.data() + buffer.size() - bytesToRead,
|
||||
static_cast<int>(bytesToRead));
|
||||
if (result.isError())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
bytesToRead -= result.getValue();
|
||||
}
|
||||
|
||||
// TODO: options and outBytesRead
|
||||
(void) options;
|
||||
if (outBytesRead != nullptr)
|
||||
{
|
||||
*outBytesRead = buffer.size();
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
std::size_t SSLStream::tell()
|
||||
{
|
||||
MIJIN_ERROR("SSLStream does not support tell().");
|
||||
return 0;
|
||||
}
|
||||
|
||||
StreamError SSLStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
{
|
||||
MIJIN_ERROR("SSLStream does not support tell().");
|
||||
(void) pos;
|
||||
(void) seekMode;
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void SSLStream::flush()
|
||||
{
|
||||
base_->flush();
|
||||
}
|
||||
|
||||
bool SSLStream::isAtEnd()
|
||||
{
|
||||
return base_->isAtEnd();
|
||||
}
|
||||
|
||||
StreamFeatures SSLStream::getFeatures()
|
||||
{
|
||||
return {
|
||||
.read = true,
|
||||
.write = true,
|
||||
.tell = false,
|
||||
.seek = false,
|
||||
.readOptions = {
|
||||
.partial = true,
|
||||
.peek = true,
|
||||
.noBlock = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
StreamError SSLStream::bioToBase() MIJIN_NOEXCEPT
|
||||
{
|
||||
std::array<std::uint8_t, BIO_BUFFER_SIZE> buffer;
|
||||
std::size_t bytes = std::min(externalBio_.ctrlPending(), buffer.size());
|
||||
while (bytes > 0)
|
||||
{
|
||||
const ossl::Result<int> result = externalBio_.read(buffer.data(), static_cast<int>(bytes));
|
||||
if (result.isError())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
if (const StreamError error = base_->writeRaw(buffer.data(), result); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
bytes = externalBio_.ctrlPending();
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError SSLStream::baseToBio() MIJIN_NOEXCEPT
|
||||
{
|
||||
std::array<std::uint8_t, BIO_BUFFER_SIZE> buffer;
|
||||
std::size_t toRead = externalBio_.getWriteGuarantee();
|
||||
|
||||
while (true)
|
||||
{
|
||||
std::size_t bytes = 0;
|
||||
std::size_t maxBytes = std::min(toRead, buffer.size());
|
||||
|
||||
if (maxBytes == 0)
|
||||
{
|
||||
// buffer is full
|
||||
break;
|
||||
}
|
||||
|
||||
if (const StreamError error = base_->readRaw(buffer.data(), maxBytes, {.partial = true, .noBlock = true},
|
||||
&bytes); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
if (bytes == 0)
|
||||
{
|
||||
// nothing more to read
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
const ossl::Result<int> result = externalBio_.write(buffer.data(), static_cast<int>(bytes));
|
||||
if (result.isError())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
MIJIN_ASSERT(result.getValue() == static_cast<int>(bytes), "BIO reported more bytes in buffer than it actually accepted?");
|
||||
toRead -= bytes;
|
||||
}
|
||||
|
||||
if (const ossl::Error error = externalBio_.flush(); !error.isSuccess())
|
||||
{
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
} // namespace shiken
|
||||
100
source/mijin/net/ssl.hpp
Normal file
100
source/mijin/net/ssl.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_SSL_HPP_INCLUDED)
|
||||
#define MIJIN_NET_SSL_HPP_INCLUDED 1
|
||||
|
||||
#if !defined(MIJIN_ENABLE_OPENSSL)
|
||||
#error "SSL support not enabled. Set MIJIN_ENABLE_OPENSSL to True in your SCons environment settings."
|
||||
#endif // !MIJIN_ENABLE_OPENSSL
|
||||
|
||||
#include <memory>
|
||||
#include "./openssl_wrappers.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../io/stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
class SSLStream : public Stream
|
||||
{
|
||||
private:
|
||||
Stream* base_ = nullptr;
|
||||
ossl::Ssl ssl_;
|
||||
ossl::Bio externalBio_;
|
||||
public:
|
||||
~SSLStream() MIJIN_NOEXCEPT override
|
||||
{
|
||||
if (base_ != nullptr)
|
||||
{
|
||||
close();
|
||||
}
|
||||
}
|
||||
StreamError open(Stream& base, const std::string& hostname) MIJIN_NOEXCEPT;
|
||||
void close() MIJIN_NOEXCEPT;
|
||||
|
||||
StreamError readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead) override;
|
||||
StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
|
||||
std::size_t tell() override;
|
||||
StreamError seek(std::intptr_t pos, SeekMode seekMode) override;
|
||||
void flush() override;
|
||||
bool isAtEnd() override;
|
||||
StreamFeatures getFeatures() override;
|
||||
private:
|
||||
StreamError bioToBase() MIJIN_NOEXCEPT;
|
||||
StreamError baseToBio() MIJIN_NOEXCEPT;
|
||||
|
||||
|
||||
template<typename TFunc, typename... TArgs>
|
||||
auto runIOLoop(TFunc&& func, bool block, TArgs&&... args) -> std::decay_t<std::invoke_result_t<TFunc, ossl::Ssl&, TArgs...>>
|
||||
{
|
||||
using result_t = std::decay_t<std::invoke_result_t<TFunc, ossl::Ssl&, TArgs...>>;
|
||||
const std::size_t maxTries = block ? std::numeric_limits<std::size_t>::max() : 10;
|
||||
|
||||
ossl::Error error;
|
||||
for (std::size_t tryNum = 0; tryNum < maxTries; ++tryNum)
|
||||
{
|
||||
auto result = std::invoke(std::forward<TFunc>(func), ssl_, std::forward<TArgs>(args)...);
|
||||
if constexpr (std::is_same_v<result_t, ossl::Error>)
|
||||
{
|
||||
if (error.isSuccess())
|
||||
{
|
||||
return error;
|
||||
}
|
||||
error = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// assume result type
|
||||
if (result.isSuccess())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
error = result.getError();
|
||||
}
|
||||
switch (error.sslError)
|
||||
{
|
||||
case SSL_ERROR_WANT_READ:
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
if(const StreamError streamError = baseToBio(); streamError != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
if (const StreamError streamError = bioToBase(); streamError != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
// mijin::Task<StreamError> c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) override;
|
||||
// mijin::Task<StreamError> c_writeRaw(std::span<const std::uint8_t> buffer) override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_NET_SSL_HPP_INCLUDED)
|
||||
|
||||
172
source/mijin/net/url.hpp
Normal file
172
source/mijin/net/url.hpp
Normal file
@@ -0,0 +1,172 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_URL_HPP_INCLUDED)
|
||||
#define MIJIN_NET_URL_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TChar, typename TTraits = std::char_traits<TChar>, typename TAllocator = std::allocator<TChar>>
|
||||
class URLBase
|
||||
{
|
||||
public:
|
||||
using string_t = std::basic_string<TChar, TTraits, TAllocator>;
|
||||
using string_view_t = std::basic_string_view<TChar, TTraits>;
|
||||
private:
|
||||
string_t base_;
|
||||
string_view_t scheme_;
|
||||
string_view_t userinfo_;
|
||||
string_view_t host_;
|
||||
string_view_t path_;
|
||||
string_view_t query_;
|
||||
string_view_t fragment_;
|
||||
string_view_t pathQueryFragment_;
|
||||
std::uint16_t port_ = 0;
|
||||
public:
|
||||
constexpr URLBase() MIJIN_NOEXCEPT = default;
|
||||
constexpr URLBase(const URLBase&) = default;
|
||||
constexpr URLBase(URLBase&&) MIJIN_NOEXCEPT = default;
|
||||
constexpr URLBase(string_t base) MIJIN_NOEXCEPT : base_(std::move(base)) { parse(); }
|
||||
constexpr URLBase(string_view_t base) : URLBase(string_t(base.begin(), base.end())) {}
|
||||
constexpr URLBase(const TChar* base) : URLBase(string_t(base)) {}
|
||||
|
||||
constexpr URLBase& operator=(const URLBase&) = default;
|
||||
constexpr URLBase& operator=(URLBase&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
[[nodiscard]] constexpr bool isValid() const MIJIN_NOEXCEPT { return !base_.empty(); }
|
||||
[[nodiscard]] constexpr const string_t& getBase() const MIJIN_NOEXCEPT { return base_; }
|
||||
[[nodiscard]] constexpr string_view_t getScheme() const MIJIN_NOEXCEPT { return scheme_; }
|
||||
[[nodiscard]] constexpr string_view_t getUserInfo() const MIJIN_NOEXCEPT { return userinfo_; }
|
||||
[[nodiscard]] constexpr string_view_t getHost() const MIJIN_NOEXCEPT { return host_; }
|
||||
[[nodiscard]] constexpr string_view_t getPath() const MIJIN_NOEXCEPT { return path_; }
|
||||
[[nodiscard]] constexpr string_view_t getQuery() const MIJIN_NOEXCEPT { return query_; }
|
||||
[[nodiscard]] constexpr string_view_t getFragment() const MIJIN_NOEXCEPT { return fragment_; }
|
||||
[[nodiscard]] constexpr string_view_t getPathQueryFragment() const MIJIN_NOEXCEPT { return pathQueryFragment_; }
|
||||
[[nodiscard]] constexpr std::uint16_t getPort() const MIJIN_NOEXCEPT { return port_; }
|
||||
|
||||
|
||||
constexpr void clear() MIJIN_NOEXCEPT;
|
||||
private:
|
||||
constexpr void parse() MIJIN_NOEXCEPT;
|
||||
constexpr bool parseAuthority(string_view_t authority) MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
using URL = URLBase<char>;
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
constexpr void URLBase<TChar, TTraits, TAllocator>::clear() MIJIN_NOEXCEPT
|
||||
{
|
||||
base_.clear();
|
||||
scheme_ = {};
|
||||
userinfo_ = {};
|
||||
host_ = {};
|
||||
path_ = {};
|
||||
query_ = {};
|
||||
fragment_ = {};
|
||||
port_ = 0;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
constexpr void URLBase<TChar, TTraits, TAllocator>::parse() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (base_.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string_view_t toParse = base_;
|
||||
typename string_view_t::size_type pos = toParse.find(TChar(':'));
|
||||
if (pos == string_t::npos)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
scheme_ = toParse.substr(0, pos);
|
||||
toParse = toParse.substr(pos + 1);
|
||||
|
||||
const TChar DOUBLE_SLASH[] = {TChar('/'), TChar('/'), TChar(0)};
|
||||
if (!toParse.starts_with(DOUBLE_SLASH))
|
||||
{
|
||||
userinfo_ = host_ = {};
|
||||
port_ = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
toParse = toParse.substr(2); // skip the slashes
|
||||
pos = toParse.find(TChar('/'));
|
||||
if (!parseAuthority(toParse.substr(0, pos)))
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
if (pos == string_view_t::npos)
|
||||
{
|
||||
path_ = query_ = fragment_ = pathQueryFragment_ = {};
|
||||
return;
|
||||
}
|
||||
toParse = toParse.substr(pos);
|
||||
}
|
||||
pathQueryFragment_ = toParse;
|
||||
pos = toParse.find(TChar('#'));
|
||||
if (pos == string_view_t::npos)
|
||||
{
|
||||
fragment_ = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
fragment_ = toParse.substr(pos + 1);
|
||||
toParse = toParse.substr(0, pos);
|
||||
}
|
||||
pos = toParse.find(TChar('?'));
|
||||
if (pos == string_view_t::npos)
|
||||
{
|
||||
query_ = {};
|
||||
path_ = toParse;
|
||||
}
|
||||
else
|
||||
{
|
||||
query_ = toParse.substr(pos + 1);
|
||||
path_ = toParse.substr(0, pos);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
constexpr bool URLBase<TChar, TTraits, TAllocator>::parseAuthority(string_view_t authority) MIJIN_NOEXCEPT
|
||||
{
|
||||
string_view_t toParse = authority;
|
||||
typename string_view_t::size_type pos = toParse.find(TChar('@'));
|
||||
if (pos == string_view_t::npos)
|
||||
{
|
||||
userinfo_ = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
userinfo_ = toParse.substr(0, pos);
|
||||
toParse = toParse.substr(pos + 1);
|
||||
}
|
||||
pos = toParse.find(TChar(':')); // TODO: IPv6
|
||||
if (pos == string_view_t::npos)
|
||||
{
|
||||
port_ = 0;
|
||||
host_ = toParse;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!toNumber(toParse.substr(pos + 1), port_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
host_ = toParse.substr(0, pos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_NET_URL_HPP_INCLUDED)
|
||||
154
source/mijin/platform/folders.cpp
Normal file
154
source/mijin/platform/folders.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
|
||||
#include "./folders.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <ranges>
|
||||
|
||||
#include "../detect.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
# include <Shlobj.h>
|
||||
#endif
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
fs::path getKnownFolder(KnownFolder folder) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
switch (folder)
|
||||
{
|
||||
case KnownFolder::USER_HOME:
|
||||
if (const char* val = std::getenv("HOME"); val != nullptr)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
return "/";
|
||||
case KnownFolder::TEMP:
|
||||
for (const char* varname : {"TMPDIR", "TEMP", "TMP"})
|
||||
{
|
||||
if (const char* val = std::getenv(varname); val != nullptr)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return "/tmp";
|
||||
case KnownFolder::CACHE_ROOT:
|
||||
if (const char* val = std::getenv("XDG_CACHE_HOME"); val != nullptr)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
return getKnownFolder(KnownFolder::USER_HOME) / ".cache";
|
||||
case KnownFolder::USER_CONFIG_ROOT:
|
||||
if (const char* val = std::getenv("XDG_CONFIG_HOME"); val != nullptr)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
return getKnownFolder(KnownFolder::USER_HOME) / ".config";
|
||||
case KnownFolder::USER_DATA_ROOT:
|
||||
if (const char* val = std::getenv("XDG_DATA_HOME"); val != nullptr)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
return getKnownFolder(KnownFolder::USER_HOME) / ".local/share";
|
||||
}
|
||||
MIJIN_ERROR("Invalid value passed to getKnownFolder().");
|
||||
return {};
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
// TODO: this is 100% untested on Windows
|
||||
|
||||
auto getKnownFolderPath = [](REFKNOWNFOLDERID rfid) -> fs::path
|
||||
{
|
||||
WCHAR* path = nullptr;
|
||||
if (!SUCCEEDED(SHGetKnownFolderPath(rfid, KF_FLAG_DEFAULT, nullptr, &path)))
|
||||
{
|
||||
CoTaskMemFree(path);
|
||||
return {};
|
||||
}
|
||||
fs::path result(path);
|
||||
CoTaskMemFree(path);
|
||||
return result;
|
||||
};
|
||||
|
||||
switch (folder)
|
||||
{
|
||||
case KnownFolder::USER_HOME:
|
||||
if (const fs::path path = getKnownFolderPath(FOLDERID_Profile); !path.empty())
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return "C:\\";
|
||||
case KnownFolder::TEMP:
|
||||
case KnownFolder::CACHE_ROOT:
|
||||
for (const char* varname : {"TMPDIR", "TEMP", "TMP"})
|
||||
{
|
||||
if (const char* val = std::getenv(varname); val != nullptr)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return getKnownFolder(KnownFolder::USER_DATA_ROOT) / "Temp";
|
||||
case KnownFolder::USER_CONFIG_ROOT:
|
||||
case KnownFolder::USER_DATA_ROOT:
|
||||
if (const fs::path path = getKnownFolderPath(FOLDERID_LocalAppData); !path.empty())
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return getKnownFolder(KnownFolder::USER_HOME) / "AppData" / "Local";
|
||||
}
|
||||
MIJIN_ERROR("Invalid value passed to getKnownFolder().");
|
||||
return {};
|
||||
#else
|
||||
MIJIN_ERROR("Known folders not implemented for this platform.");
|
||||
return {}; // don't know :/
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<fs::path> getAllConfigFolders(IncludeUser includeUser, IncludeSystem includeSystem) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
if (includeUser)
|
||||
{
|
||||
result.push_back(getKnownFolder(KnownFolder::USER_CONFIG_ROOT));
|
||||
}
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
if (includeSystem)
|
||||
{
|
||||
if (const char* val = std::getenv("XDG_CONFIG_DIRS"); val != nullptr)
|
||||
{
|
||||
for (auto part : std::ranges::split_view(std::string_view(val), ':'))
|
||||
{
|
||||
result.emplace_back(part.begin(), part.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void) includeSystem;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<fs::path> getAllDataFolders(IncludeUser includeUser, IncludeSystem includeSystem) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
if (includeUser)
|
||||
{
|
||||
result.push_back(getKnownFolder(KnownFolder::USER_DATA_ROOT));
|
||||
}
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
if (includeSystem)
|
||||
{
|
||||
if (const char* val = std::getenv("XDG_DATA_DIRS"); val != nullptr)
|
||||
{
|
||||
for (auto part : std::ranges::split_view(std::string_view(val), ':'))
|
||||
{
|
||||
result.emplace_back(part.begin(), part.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void) includeSystem;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
}
|
||||
40
source/mijin/platform/folders.hpp
Normal file
40
source/mijin/platform/folders.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_PLATFORM_FOLDERS_HPP_INCLUDED)
|
||||
#define MIJIN_PLATFORM_FOLDERS_HPP_INCLUDED 1
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/flag.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
enum class KnownFolder
|
||||
{
|
||||
USER_HOME,
|
||||
TEMP,
|
||||
CACHE_ROOT,
|
||||
USER_CONFIG_ROOT,
|
||||
USER_DATA_ROOT
|
||||
};
|
||||
|
||||
MIJIN_DEFINE_FLAG(IncludeUser);
|
||||
MIJIN_DEFINE_FLAG(IncludeSystem);
|
||||
|
||||
[[nodiscard]]
|
||||
fs::path getKnownFolder(KnownFolder folder) MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]]
|
||||
std::vector<fs::path> getAllConfigFolders(IncludeUser includeUser = IncludeUser::YES,
|
||||
IncludeSystem includeSystem = IncludeSystem::YES) MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]]
|
||||
std::vector<fs::path> getAllDataFolders(IncludeUser includeUser = IncludeUser::YES,
|
||||
IncludeSystem includeSystem = IncludeSystem::YES) MIJIN_NOEXCEPT;
|
||||
}
|
||||
|
||||
#endif // defined(MIJIN_PLATFORM_FOLDERS_HPP_INCLUDED)
|
||||
@@ -8,9 +8,6 @@
|
||||
#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
|
||||
{
|
||||
|
||||
@@ -52,9 +49,9 @@ static std::shared_mutex& getGlobalNamesMutex()
|
||||
return mutex;
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string_view, std::size_t>& getLocalNameCache()
|
||||
static std::unordered_map<std::string, std::size_t>& getLocalNameCache()
|
||||
{
|
||||
static std::unordered_map<std::string_view, std::size_t> cache;
|
||||
static std::unordered_map<std::string, std::size_t> cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
@@ -78,14 +75,14 @@ static std::size_t getOrCreateStringID(std::string_view string)
|
||||
{
|
||||
if (getLocalNames()[idx] == string)
|
||||
{
|
||||
getLocalNameCache()[std::string(string)] = idx;
|
||||
getLocalNameCache().emplace(std::string(string), idx);
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. copy global state and check local again
|
||||
{
|
||||
std::shared_lock lock(getGlobalNamesMutex());
|
||||
const std::shared_lock lock(getGlobalNamesMutex());
|
||||
copyNamesToLocal();
|
||||
}
|
||||
|
||||
@@ -93,17 +90,17 @@ static std::size_t getOrCreateStringID(std::string_view string)
|
||||
{
|
||||
if (getLocalNames()[idx] == string)
|
||||
{
|
||||
getLocalNameCache()[std::string(string)] = idx;
|
||||
getLocalNameCache().emplace(std::string(string), idx);
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. insert a new ID
|
||||
std::unique_lock lock(getGlobalNamesMutex());
|
||||
const std::unique_lock lock(getGlobalNamesMutex());
|
||||
const std::size_t idx = getGlobalNames().size();
|
||||
getGlobalNames().emplace_back(string);
|
||||
copyNamesToLocal();
|
||||
getLocalNameCache()[std::string(string)] = idx;
|
||||
getLocalNameCache().emplace(std::string(string), idx);
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#if __has_include(<fmt/format.h>)
|
||||
# include <fmt/format.h>
|
||||
#endif
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -34,6 +38,8 @@ public:
|
||||
|
||||
Name& operator=(const Name&) = default;
|
||||
auto operator<=>(const Name&) const = default;
|
||||
constexpr operator bool() const MIJIN_NOEXCEPT { return id_ != std::numeric_limits<std::size_t>::max(); }
|
||||
constexpr bool operator!() const MIJIN_NOEXCEPT { return !static_cast<bool>(*this); }
|
||||
|
||||
[[nodiscard]] std::string_view stringView() const;
|
||||
[[nodiscard]] const char* c_str() const {
|
||||
@@ -49,15 +55,24 @@ public:
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<mijin::Name> : hash<std::size_t>
|
||||
struct std::hash<mijin::Name> : hash<std::size_t>
|
||||
{
|
||||
std::size_t operator()(mijin::Name name) const noexcept {
|
||||
std::size_t operator()(mijin::Name name) const MIJIN_NOEXCEPT {
|
||||
return hash<std::size_t>::operator()(name.getID());
|
||||
}
|
||||
};
|
||||
|
||||
#if __has_include(<fmt/format.h>)
|
||||
template<>
|
||||
struct fmt::formatter<mijin::Name> : fmt::formatter<std::string_view>
|
||||
{
|
||||
template<typename TContext>
|
||||
auto format(mijin::Name name, TContext& ctx) const -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::formatter<std::string_view>::format(name.stringView(), ctx);
|
||||
}
|
||||
};
|
||||
#endif // __has_include(<fmt/format.h>)
|
||||
|
||||
#endif // !defined(MIJIN_TYPES_NAME_HPP_INCLUDED)
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
@@ -30,37 +32,45 @@ private:
|
||||
|
||||
std::variant<Empty, TSuccess, TError> state_;
|
||||
public:
|
||||
ResultBase() = default;
|
||||
ResultBase(const ResultBase&) = default;
|
||||
ResultBase(ResultBase&&) = default;
|
||||
ResultBase(TSuccess successValue) noexcept : state_(std::move(successValue)) {}
|
||||
ResultBase(TError errorValue) noexcept : state_(std::move(errorValue)) {}
|
||||
ResultBase() MIJIN_NOEXCEPT = default;
|
||||
ResultBase(const ResultBase&) MIJIN_NOEXCEPT = default;
|
||||
ResultBase(ResultBase&&) MIJIN_NOEXCEPT = default;
|
||||
ResultBase(TSuccess successValue) MIJIN_NOEXCEPT : state_(std::move(successValue)) {}
|
||||
ResultBase(TError errorValue) MIJIN_NOEXCEPT : state_(std::move(errorValue)) {}
|
||||
|
||||
ResultBase& operator=(const ResultBase&) = default;
|
||||
ResultBase& operator=(ResultBase&&) = default;
|
||||
|
||||
bool operator==(const ResultBase& other) const noexcept { return state_ == other.state_; }
|
||||
bool operator!=(const ResultBase& other) const noexcept { return state_ != other.state_; }
|
||||
operator bool() const noexcept { return isSuccess(); }
|
||||
bool operator!() const noexcept { return !isSuccess(); }
|
||||
TSuccess& operator*() noexcept { return getValue(); }
|
||||
const TSuccess& operator*() const noexcept { return getValue(); }
|
||||
TSuccess* operator->() noexcept { return &getValue(); }
|
||||
const TSuccess* operator->() const noexcept { return &getValue(); }
|
||||
bool operator==(const ResultBase& other) const MIJIN_NOEXCEPT { return state_ == other.state_; }
|
||||
bool operator!=(const ResultBase& other) const MIJIN_NOEXCEPT { return state_ != other.state_; }
|
||||
operator bool() const MIJIN_NOEXCEPT { return isSuccess(); }
|
||||
bool operator!() const MIJIN_NOEXCEPT { return !isSuccess(); }
|
||||
TSuccess& operator*() MIJIN_NOEXCEPT { return getValue(); }
|
||||
const TSuccess& operator*() const MIJIN_NOEXCEPT { return getValue(); }
|
||||
TSuccess* operator->() MIJIN_NOEXCEPT { return &getValue(); }
|
||||
const TSuccess* operator->() const MIJIN_NOEXCEPT { return &getValue(); }
|
||||
|
||||
[[nodiscard]] bool isEmpty() const noexcept { return std::holds_alternative<Empty>(state_); }
|
||||
[[nodiscard]] bool isSuccess() const noexcept { return std::holds_alternative<TSuccess>(state_); }
|
||||
[[nodiscard]] bool isError() const noexcept { return std::holds_alternative<TError>(state_); }
|
||||
[[nodiscard]] bool isEmpty() const MIJIN_NOEXCEPT { return std::holds_alternative<Empty>(state_); }
|
||||
[[nodiscard]] bool isSuccess() const MIJIN_NOEXCEPT { return std::holds_alternative<TSuccess>(state_); }
|
||||
[[nodiscard]] bool isError() const MIJIN_NOEXCEPT { return std::holds_alternative<TError>(state_); }
|
||||
|
||||
[[nodiscard]] TSuccess& getValue() noexcept { return std::get<TSuccess>(state_); }
|
||||
[[nodiscard]] TError& getError() noexcept { return std::get<TError>(state_); }
|
||||
[[nodiscard]] const TSuccess& getValue() const noexcept { return std::get<TSuccess>(state_); }
|
||||
[[nodiscard]] const TError& getError() const noexcept { return std::get<TError>(state_); }
|
||||
[[nodiscard]] TSuccess& getValue() MIJIN_NOEXCEPT { return std::get<TSuccess>(state_); }
|
||||
[[nodiscard]] TError& getError() MIJIN_NOEXCEPT { return std::get<TError>(state_); }
|
||||
[[nodiscard]] const TSuccess& getValue() const MIJIN_NOEXCEPT { return std::get<TSuccess>(state_); }
|
||||
[[nodiscard]] const TError& getError() const MIJIN_NOEXCEPT { return std::get<TError>(state_); }
|
||||
};
|
||||
|
||||
struct ResultError
|
||||
{
|
||||
std::string message;
|
||||
|
||||
ResultError() : message("Unknown error") {}
|
||||
ResultError(const ResultError&) = default;
|
||||
ResultError(ResultError&&) MIJIN_NOEXCEPT = default;
|
||||
ResultError(std::string msg) MIJIN_NOEXCEPT : message(std::move(msg)) {}
|
||||
|
||||
ResultError& operator=(const ResultError&) = default;
|
||||
ResultError& operator=(ResultError&&) MIJIN_NOEXCEPT = default;
|
||||
};
|
||||
|
||||
template<typename TSuccess>
|
||||
|
||||
348
source/mijin/types/script_value.hpp
Normal file
348
source/mijin/types/script_value.hpp
Normal file
@@ -0,0 +1,348 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef MIJIN_TYPES_SCRIPT_VALUE_HPP_INCLUDED
|
||||
#define MIJIN_TYPES_SCRIPT_VALUE_HPP_INCLUDED 1
|
||||
|
||||
#include <charconv>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "../container/map_view.hpp"
|
||||
#include "../container/optional.hpp"
|
||||
#include "../container/vector_map.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
#include "../util/variant.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
|
||||
template<typename TConcrete, typename... TAdditional>
|
||||
class ScriptValue;
|
||||
|
||||
template<typename TScriptValue>
|
||||
struct ArrayValue
|
||||
{
|
||||
std::vector<TScriptValue> values;
|
||||
|
||||
auto operator<=>(const ArrayValue&) const MIJIN_NOEXCEPT = default;
|
||||
};
|
||||
|
||||
template<typename TScriptValue>
|
||||
struct MapValue
|
||||
{
|
||||
VectorMap<std::string, TScriptValue> values;
|
||||
|
||||
auto operator<=>(const MapValue&) const MIJIN_NOEXCEPT = default;
|
||||
};
|
||||
|
||||
struct UndefinedValue
|
||||
{
|
||||
auto operator<=>(const UndefinedValue&) const = default;
|
||||
};
|
||||
|
||||
inline constexpr UndefinedValue UNDEFINED_VALUE;
|
||||
|
||||
using script_int_t = long long;
|
||||
using script_float_t = double;
|
||||
|
||||
template<typename TConcrete, typename... TAdditional>
|
||||
using script_value_base_t = std::variant<
|
||||
UndefinedValue,
|
||||
bool,
|
||||
script_int_t,
|
||||
script_float_t,
|
||||
std::string,
|
||||
ArrayValue<TConcrete>,
|
||||
MapValue<TConcrete>,
|
||||
TAdditional...
|
||||
>;
|
||||
|
||||
template<typename TConcrete, typename... TAdditional>
|
||||
class ScriptValue
|
||||
{
|
||||
public:
|
||||
using base_t = script_value_base_t<TConcrete, TAdditional...>;
|
||||
using concrete_t = TConcrete;
|
||||
using array_t = ArrayValue<TConcrete>;
|
||||
using map_t = MapValue<TConcrete>;
|
||||
|
||||
template<typename T>
|
||||
static constexpr bool can_hold_type_v = variant_contains_v<std::remove_cv_t<T>, base_t> || std::is_arithmetic_v<T>;
|
||||
private:
|
||||
base_t base_ = UndefinedValue();
|
||||
public:
|
||||
ScriptValue() MIJIN_NOEXCEPT = default;
|
||||
ScriptValue(const ScriptValue&) MIJIN_NOEXCEPT = default;
|
||||
ScriptValue(ScriptValue&&) MIJIN_NOEXCEPT = default;
|
||||
template<typename TValue>
|
||||
ScriptValue(TValue&& value);
|
||||
|
||||
ScriptValue& operator=(const ScriptValue&) = default;
|
||||
ScriptValue& operator=(ScriptValue&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
// MSVC doesn't like this :/ (internal compiler error)
|
||||
// std::partial_ordering operator<=>(const ScriptValue& other) const = default;
|
||||
bool operator==(const ScriptValue& other) const MIJIN_NOEXCEPT { return base_ == other.base_; }
|
||||
bool operator!=(const ScriptValue& other) const MIJIN_NOEXCEPT { return base_ != other.base_; }
|
||||
|
||||
[[nodiscard]]
|
||||
bool isUndefined() const noexcept
|
||||
{
|
||||
return std::holds_alternative<UndefinedValue>(base_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isBool() const noexcept
|
||||
{
|
||||
return std::holds_alternative<bool>(base_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isInteger() const noexcept
|
||||
{
|
||||
return std::holds_alternative<script_int_t>(base_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isFloat() const noexcept
|
||||
{
|
||||
return std::holds_alternative<script_float_t>(base_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isString() const noexcept
|
||||
{
|
||||
return std::holds_alternative<std::string>(base_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isArray() const noexcept
|
||||
{
|
||||
return std::holds_alternative<array_t>(base_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isMap() const noexcept
|
||||
{
|
||||
return std::holds_alternative<map_t>(base_);
|
||||
}
|
||||
|
||||
template<typename TFunction>
|
||||
auto visit(TFunction&& function) const
|
||||
{
|
||||
return std::visit(function, base_);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Optional<script_int_t> toInt() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return visit([&](auto&& value) -> Optional<script_int_t>
|
||||
{
|
||||
using type_t = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_arithmetic_v<type_t>)
|
||||
{
|
||||
return static_cast<script_int_t>(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, std::string>)
|
||||
{
|
||||
script_int_t result = 0;
|
||||
const std::from_chars_result fromCharsResult = std::from_chars(&*value.begin(), &*value.end(), result);
|
||||
if (fromCharsResult.ec != std::errc{})
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Optional<script_float_t> toFloat() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return visit([&](auto&& value) -> Optional<script_float_t>
|
||||
{
|
||||
using type_t = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_arithmetic_v<type_t>)
|
||||
{
|
||||
return static_cast<script_float_t>(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, std::string>)
|
||||
{
|
||||
script_float_t result = 0;
|
||||
const std::from_chars_result fromCharsResult = std::from_chars(&*value.begin(), &*value.end(), result);
|
||||
if (fromCharsResult.ec != std::errc{})
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Optional<std::string> toString() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return visit([](auto&& value) -> Optional<std::string>
|
||||
{
|
||||
using type_t = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<type_t, UndefinedValue>)
|
||||
{
|
||||
return std::string("");
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, bool>)
|
||||
{
|
||||
return std::string(value ? "true" : "false");
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, script_int_t> || std::is_same_v<type_t, script_float_t>)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, std::string>)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]]
|
||||
Optional<std::decay_t<T>> to() const MIJIN_NOEXCEPT
|
||||
{
|
||||
using type_t = std::decay_t<T>;
|
||||
if constexpr (std::is_same_v<type_t, script_int_t>)
|
||||
{
|
||||
return toInt();
|
||||
}
|
||||
else if constexpr (std::is_integral_v<type_t>)
|
||||
{
|
||||
return toInt().then([](script_int_t val) -> Optional<std::decay_t<T>> { return static_cast<type_t>(val); });
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, script_float_t>)
|
||||
{
|
||||
return toFloat();
|
||||
}
|
||||
else if constexpr (std::is_floating_point_v<type_t>)
|
||||
{
|
||||
return toFloat().then([](script_float_t val) -> Optional<std::decay_t<T>> { return static_cast<type_t>(val); });
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, std::string>)
|
||||
{
|
||||
return toString();
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, TConcrete>)
|
||||
{
|
||||
return *static_cast<copy_cv_t<std::remove_reference_t<decltype(*this)>, TConcrete>*>(this);
|
||||
}
|
||||
else if constexpr (std::is_same_v<type_t, array_t> || std::is_same_v<type_t, map_t>
|
||||
|| is_any_type_v<T, TAdditional...>)
|
||||
{
|
||||
if (std::holds_alternative<T>(base_))
|
||||
{
|
||||
return std::get<T>(base_);
|
||||
}
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(mijin::always_false_v<T>, "Cannot convert to this type.");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Optional<const T&> toRef() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (std::holds_alternative<T>(base_))
|
||||
{
|
||||
return std::get<T>(base_);
|
||||
}
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
concrete_t& operator[](std::size_t index) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isArray(), "Called operator[size_t] on a non-array value.");
|
||||
return std::get<array_t>(base_).values[index];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const concrete_t& operator[](std::size_t index) const MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isArray(), "Called operator[size_t] on a non-array value.");
|
||||
return std::get<array_t>(base_).values[index];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
concrete_t& operator[](const std::string& key)
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isMap(), "Called operator[string] on a non-map value.");
|
||||
return std::get<map_t>(base_).values.at(key);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const concrete_t& operator[](const std::string& key) const
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isMap(), "Called operator[string] on a non-map value.");
|
||||
return std::get<map_t>(base_).values.at(key);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::span<concrete_t> arrayView() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isArray(), "Called iterateArray() on a non-array value.");
|
||||
return std::get<array_t>(base_).values;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::span<const concrete_t> arrayView() const MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isArray(), "Called iterateArray() on a non-array value.");
|
||||
return std::get<array_t>(base_).values;
|
||||
}
|
||||
|
||||
MapView<std::string, concrete_t> mapView() MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isMap(), "Called iterateMap() on a non-map value.");
|
||||
return std::get<map_t>(base_).values;
|
||||
}
|
||||
|
||||
auto mapView() const MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(isMap(), "Called iterateMap() on a non-map value.");
|
||||
return MapView(std::get<map_t>(base_).values);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
template<typename TConcrete, typename... TAdditional>
|
||||
template<typename TValue>
|
||||
ScriptValue<TConcrete, TAdditional...>::ScriptValue(TValue&& value) : base_(std::forward<TValue>(value))
|
||||
{
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // MIJIN_TYPES_SCRIPT_VALUE_HPP_INCLUDED
|
||||
83
source/mijin/types/typedef.hpp
Normal file
83
source/mijin/types/typedef.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_TYPES_TYPEDEF_HPP_INCLUDED)
|
||||
#define MIJIN_TYPES_TYPEDEF_HPP_INCLUDED 1
|
||||
|
||||
#include <utility>
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
#define MIJIN_TYPEDEF(name_, base_) \
|
||||
namespace impl { struct name_ ## _tag; } \
|
||||
using name_ = mijin::TypeDef<base_, impl::name_ ## _tag>
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename TBase, typename TTag>
|
||||
class TypeDef
|
||||
{
|
||||
private:
|
||||
TBase value;
|
||||
public:
|
||||
TypeDef() = default;
|
||||
TypeDef(const TypeDef&) = default;
|
||||
TypeDef(TypeDef&&) = default;
|
||||
|
||||
template<typename... TArgs>
|
||||
constexpr TypeDef(TArgs&&... args) : value(std::forward<TArgs>(args)...) {}
|
||||
|
||||
TypeDef& operator=(const TypeDef&) = default;
|
||||
TypeDef& operator=(TypeDef&&) = default;
|
||||
|
||||
template<typename TArg>
|
||||
constexpr TypeDef& operator=(TArg&& arg)
|
||||
{
|
||||
value = std::forward<TArg>(arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit constexpr operator const TBase&() const MIJIN_NOEXCEPT { return value; }
|
||||
explicit constexpr operator TBase&() MIJIN_NOEXCEPT { return value; }
|
||||
auto operator<=>(const TypeDef&) const MIJIN_NOEXCEPT = default;
|
||||
};
|
||||
|
||||
template<typename TBase, typename TTag> requires (!std::is_fundamental_v<TBase>)
|
||||
class TypeDef<TBase, TTag> : public TBase
|
||||
{
|
||||
public:
|
||||
TypeDef() = default;
|
||||
TypeDef(const TypeDef&) = default;
|
||||
TypeDef(TypeDef&&) = default;
|
||||
template<typename... TArgs>
|
||||
constexpr TypeDef(TArgs&&... args) : TBase(std::forward<TArgs>(args)...) {}
|
||||
|
||||
TypeDef& operator=(const TypeDef&) = default;
|
||||
TypeDef& operator=(TypeDef&&) = default;
|
||||
|
||||
template<typename TArg>
|
||||
constexpr TypeDef& operator=(TArg&& arg)
|
||||
{
|
||||
TBase::operator=(std::forward<TArg>(arg));
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
} // namespace mijin
|
||||
#endif // !defined(MIJIN_TYPES_TYPEDEF_HPP_INCLUDED)
|
||||
@@ -4,10 +4,12 @@
|
||||
#if !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_ALIGN_HPP_INCLUDED 1
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename T>
|
||||
constexpr T alignUp(T value, T alignTo) noexcept
|
||||
constexpr T alignUp(T value, T alignTo) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (value % alignTo != 0)
|
||||
{
|
||||
@@ -16,6 +18,7 @@ constexpr T alignUp(T value, T alignTo) noexcept
|
||||
return value;
|
||||
}
|
||||
|
||||
#define MIJIN_STRIDEOF(T) mijin::alignUp(sizeof(T), alignof(T))
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
namespace mijin
|
||||
@@ -32,8 +31,9 @@ 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);
|
||||
[[nodiscard]] bool get(std::size_t index) const
|
||||
{
|
||||
MIJIN_ASSERT(index < numBits, "BitArray: index out of range.");
|
||||
return (bytes[index / 8] & (1 << (index % 8)));
|
||||
}
|
||||
void set(std::size_t index, bool value)
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
#define MIJIN_UTIL_BITFLAGS_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <compare>
|
||||
#include <cstddef>
|
||||
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
@@ -25,6 +28,8 @@ namespace mijin
|
||||
template<typename TBits>
|
||||
struct BitFlags
|
||||
{
|
||||
using bits_t = TBits;
|
||||
|
||||
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);
|
||||
@@ -66,7 +71,7 @@ struct BitFlags
|
||||
return copy;
|
||||
}
|
||||
|
||||
constexpr operator bool() const {
|
||||
explicit 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;
|
||||
@@ -78,15 +83,50 @@ struct BitFlags
|
||||
constexpr bool operator!() const {
|
||||
return !static_cast<bool>(*this);
|
||||
}
|
||||
|
||||
auto operator<=>(const BitFlags&) const noexcept = default;
|
||||
private:
|
||||
constexpr TBits* asBits() { return static_cast<TBits*>(this); }
|
||||
constexpr const TBits* asBits() const { return static_cast<const TBits*>(this); }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
constexpr bool is_bitflags_v = std::is_base_of_v<BitFlags<T>, T>;
|
||||
|
||||
template<typename T>
|
||||
concept BitFlagsType = is_bitflags_v<T>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<std::integral TInt, BitFlagsType T>
|
||||
TInt bitFlagsToInt(const BitFlags<T>& flags) noexcept
|
||||
{
|
||||
static constexpr std::size_t BYTES = std::min(sizeof(T), sizeof(TInt));
|
||||
TInt result = 0;
|
||||
|
||||
for (std::size_t off = 0; off < BYTES; ++off)
|
||||
{
|
||||
result |= static_cast<TInt>(*(std::bit_cast<const std::byte*>(&flags) + off)) << (off * 8);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<BitFlagsType T, std::integral TInt>
|
||||
T bitFlagsFromInt(TInt intVal) noexcept
|
||||
{
|
||||
static constexpr std::size_t BYTES = std::min(sizeof(T), sizeof(TInt));
|
||||
T result = {};
|
||||
|
||||
for (std::size_t off = 0; off < BYTES; ++off)
|
||||
{
|
||||
(*(std::bit_cast<std::byte*>(&result) + off)) = static_cast<std::byte>(intVal >> (off * 8));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED)
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#if !defined(MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED 1
|
||||
|
||||
#define MIJIN_CONCAT2(a, b) a ## b
|
||||
#define MIJIN_CONCAT(a, b) MIJIN_CONCAT2(a, b)
|
||||
#define MIJIN_CONCAT_DETAIL(a, b) a ## b
|
||||
#define MIJIN_CONCAT(a, b) MIJIN_CONCAT_DETAIL(a, b)
|
||||
#define MIJIN_CONCAT3(a, b, c) MIJIN_CONCAT(a, MIJIN_CONCAT(b, c))
|
||||
|
||||
#endif // defined(MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED)
|
||||
77
source/mijin/util/exception.hpp
Normal file
77
source/mijin/util/exception.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef MIJIN_UTIL_EXCEPTION_HPP_INCLUDED
|
||||
#define MIJIN_UTIL_EXCEPTION_HPP_INCLUDED 1
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../debug/stacktrace.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
class Exception : public std::runtime_error
|
||||
{
|
||||
private:
|
||||
Result<Stacktrace> stacktrace_;
|
||||
std::exception_ptr cause_;
|
||||
public:
|
||||
Exception(const std::string& what) : std::runtime_error(what), stacktrace_(captureStacktrace(1)), cause_(std::current_exception()) {}
|
||||
Exception(const char* what) : Exception(std::string(what)) {}
|
||||
|
||||
[[nodiscard]]
|
||||
const Result<Stacktrace>& getStacktrace() const noexcept { return stacktrace_; }
|
||||
|
||||
[[nodiscard]]
|
||||
const std::exception_ptr& getCause() const noexcept { return cause_; }
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename TCondition, typename TException = std::runtime_error, typename... TExceptionArgs>
|
||||
inline decltype(auto) ensure(TCondition&& condition, TExceptionArgs&&... args)
|
||||
{
|
||||
if (!static_cast<bool>(std::forward<TCondition>(condition)))
|
||||
{
|
||||
throw TException(std::forward<TExceptionArgs>(args)...);
|
||||
}
|
||||
return std::forward<TCondition>(condition);
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
void walkExceptionCause(const std::exception_ptr& cause, TFunc func)
|
||||
{
|
||||
if (cause)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(cause);
|
||||
}
|
||||
catch(Exception& exc)
|
||||
{
|
||||
func(exc);
|
||||
walkExceptionCause(exc.getCause(), std::move(func));
|
||||
}
|
||||
catch(std::exception& exc)
|
||||
{
|
||||
func(exc);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
func(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mijin
|
||||
#endif // MIJIN_UTIL_EXCEPTION_HPP_INCLUDED
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstdint>
|
||||
#include "./traits.hpp"
|
||||
#include "./types.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -25,9 +26,9 @@ private: \
|
||||
public: \
|
||||
constexpr name() = default; \
|
||||
constexpr name(const name&) = default; \
|
||||
constexpr name(Proxy_ proxy) \
|
||||
constexpr name(Proxy_ proxy) MIJIN_NOEXCEPT \
|
||||
: Flag(proxy.value) {} \
|
||||
constexpr explicit name(bool value) noexcept \
|
||||
constexpr explicit name(bool value) MIJIN_NOEXCEPT \
|
||||
: Flag(value) {} \
|
||||
name& operator=(const name&) = default; \
|
||||
static constexpr Proxy_ YES{1}; \
|
||||
@@ -48,23 +49,23 @@ struct Flag
|
||||
|
||||
Flag() = default;
|
||||
Flag(const Flag&) = default;
|
||||
explicit constexpr Flag(uint8_t value) noexcept : value(value) {}
|
||||
explicit constexpr Flag(uint8_t value) MIJIN_NOEXCEPT : value(value) {}
|
||||
|
||||
Flag& operator=(const Flag&) = default;
|
||||
|
||||
constexpr bool operator ==(const Flag& other) const noexcept
|
||||
constexpr bool operator ==(const Flag& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return value == other.value;
|
||||
}
|
||||
constexpr bool operator !=(const Flag& other) const noexcept
|
||||
constexpr bool operator !=(const Flag& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return value != other.value;
|
||||
}
|
||||
constexpr bool operator !() const noexcept
|
||||
constexpr bool operator !() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !value;
|
||||
}
|
||||
constexpr operator bool() const noexcept
|
||||
constexpr operator bool() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return value != 0;
|
||||
}
|
||||
@@ -89,7 +90,7 @@ 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
|
||||
constexpr void set(TFirst value) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
@@ -100,7 +101,7 @@ public:
|
||||
base_t::data_ &= ~BIT;
|
||||
}
|
||||
}
|
||||
constexpr bool get(TFirst) noexcept
|
||||
constexpr bool get(TFirst) MIJIN_NOEXCEPT
|
||||
{
|
||||
return (base_t::data_ & BIT) != 0;
|
||||
}
|
||||
@@ -134,19 +135,19 @@ public:
|
||||
public:
|
||||
FlagSet& operator=(const FlagSet&) = default;
|
||||
template<FlagType T> requires is_any_type_v<T, TFlags...>
|
||||
FlagSet& operator=(T flag) noexcept
|
||||
FlagSet& operator=(T flag) MIJIN_NOEXCEPT
|
||||
{
|
||||
reset(flag);
|
||||
return *this;
|
||||
}
|
||||
template<FlagType T> requires is_any_type_v<T, TFlags...>
|
||||
FlagSet& operator|=(T flag) noexcept
|
||||
FlagSet& operator|=(T flag) MIJIN_NOEXCEPT
|
||||
{
|
||||
set(flag);
|
||||
return *this;
|
||||
}
|
||||
template<FlagType T> requires is_any_type_v<T, TFlags...>
|
||||
FlagSet& operator&=(T flag) noexcept
|
||||
FlagSet& operator&=(T flag) MIJIN_NOEXCEPT
|
||||
{
|
||||
unset(flag);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include "../container/optional.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -96,50 +98,50 @@ struct EnumeratingIterator
|
||||
TIdx idx;
|
||||
TIterator base;
|
||||
|
||||
EnumeratingIterator(TIdx idx_, TIterator base_) noexcept : idx(idx_), base(base_) {}
|
||||
EnumeratingIterator(const EnumeratingIterator&) noexcept = default;
|
||||
EnumeratingIterator(TIdx idx_, TIterator base_) MIJIN_NOEXCEPT : idx(idx_), base(base_) {}
|
||||
EnumeratingIterator(const EnumeratingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
EnumeratingIterator& operator=(const EnumeratingIterator&) noexcept = default;
|
||||
EnumeratingIterator& operator=(const EnumeratingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
auto operator*() const noexcept
|
||||
auto operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::tie(idx, *base);
|
||||
}
|
||||
|
||||
EnumeratingIterator& operator++() noexcept
|
||||
EnumeratingIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
++idx;
|
||||
++base;
|
||||
return *this;
|
||||
}
|
||||
|
||||
EnumeratingIterator operator++(int) noexcept
|
||||
EnumeratingIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
EnumeratingIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
EnumeratingIterator& operator--() noexcept
|
||||
EnumeratingIterator& operator--() MIJIN_NOEXCEPT
|
||||
{
|
||||
--idx;
|
||||
--base;
|
||||
return *this;
|
||||
}
|
||||
|
||||
EnumeratingIterator operator--(int) noexcept
|
||||
EnumeratingIterator operator--(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
EnumeratingIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const EnumeratingIterator& other) const noexcept
|
||||
bool operator==(const EnumeratingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return base == other.base; // note: ignoring idx so we don't have to find it out for end()
|
||||
}
|
||||
|
||||
bool operator!=(const EnumeratingIterator& other) const noexcept
|
||||
bool operator!=(const EnumeratingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return base != other.base; // note: ignoring idx so we don't have to find it out for end()
|
||||
}
|
||||
@@ -152,12 +154,12 @@ struct Enumeratable : RangeAdapter
|
||||
{
|
||||
RangeRef<TIterable> base;
|
||||
|
||||
auto begin() const noexcept
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return EnumeratingIterator(TIdx(0), base.begin());
|
||||
}
|
||||
|
||||
auto end() const noexcept
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return EnumeratingIterator(TIdx(0), base.end());
|
||||
}
|
||||
@@ -169,6 +171,86 @@ Enumeratable<TIdx, TIterable> enumerate(TIterable&& iterable)
|
||||
return {.base = {std::forward<TIterable>(iterable)}};
|
||||
}
|
||||
|
||||
template<typename TFirstIterator, typename TSecondIterator>
|
||||
struct ZippingIterator
|
||||
{
|
||||
TFirstIterator first;
|
||||
TSecondIterator second;
|
||||
|
||||
ZippingIterator(TFirstIterator first_, TSecondIterator second_) MIJIN_NOEXCEPT : first(first_), second(second_) {}
|
||||
ZippingIterator(const ZippingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
ZippingIterator& operator=(const ZippingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
decltype(auto) operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::tie(*first, *second);
|
||||
}
|
||||
|
||||
ZippingIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
++first;
|
||||
++second;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ZippingIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
ZippingIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
ZippingIterator& operator--() MIJIN_NOEXCEPT
|
||||
{
|
||||
--first;
|
||||
--second;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ZippingIterator operator--(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
ZippingIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const ZippingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return first == other.first || second == other.second; // note: this uses or so reaching the end on one range also ends the zipped one.
|
||||
}
|
||||
|
||||
bool operator!=(const ZippingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
template<typename TFirstIterator, typename TSecondIterator>
|
||||
ZippingIterator(TFirstIterator, TSecondIterator) -> ZippingIterator<TFirstIterator, TSecondIterator>;
|
||||
|
||||
template<typename TFirst, typename TSecond>
|
||||
struct ZippingRange : RangeAdapter
|
||||
{
|
||||
RangeRef<TFirst> first;
|
||||
RangeRef<TSecond> second;
|
||||
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return ZippingIterator(first.begin(), second.begin());
|
||||
}
|
||||
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return ZippingIterator(first.end(), second.end());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TFirst, typename TSecond>
|
||||
ZippingRange<TFirst, TSecond> zip(TFirst&& first, TSecond&& second)
|
||||
{
|
||||
return {.first = {std::forward<TFirst>(first)}, .second = {std::forward<TSecond>(second)}};
|
||||
}
|
||||
|
||||
template<typename TIterator>
|
||||
struct ReplacingIterator
|
||||
{
|
||||
@@ -182,12 +264,12 @@ struct ReplacingIterator
|
||||
value_type what;
|
||||
value_type with;
|
||||
|
||||
ReplacingIterator(TIterator base_, value_type what_, value_type with_) noexcept : base(base_), what(what_), with(with_) {}
|
||||
ReplacingIterator(const ReplacingIterator&) noexcept = default;
|
||||
ReplacingIterator(TIterator base_, value_type what_, value_type with_) MIJIN_NOEXCEPT : base(base_), what(what_), with(with_) {}
|
||||
ReplacingIterator(const ReplacingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
ReplacingIterator& operator=(const ReplacingIterator&) noexcept = default;
|
||||
ReplacingIterator& operator=(const ReplacingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
pointer operator->() const noexcept
|
||||
pointer operator->() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (*base == what) {
|
||||
return &with;
|
||||
@@ -195,7 +277,7 @@ struct ReplacingIterator
|
||||
return &*base;
|
||||
}
|
||||
|
||||
reference operator*() const noexcept
|
||||
reference operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (*base == what) {
|
||||
return with;
|
||||
@@ -203,38 +285,38 @@ struct ReplacingIterator
|
||||
return *base;
|
||||
}
|
||||
|
||||
ReplacingIterator& operator++() noexcept
|
||||
ReplacingIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
++base;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReplacingIterator operator++(int) noexcept
|
||||
ReplacingIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
ReplacingIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
ReplacingIterator& operator--() noexcept
|
||||
ReplacingIterator& operator--() MIJIN_NOEXCEPT
|
||||
{
|
||||
--base;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReplacingIterator operator--(int) noexcept
|
||||
ReplacingIterator operator--(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
ReplacingIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const ReplacingIterator& other) const noexcept
|
||||
bool operator==(const ReplacingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return what == other.what && with == other.with && base == other.base;
|
||||
}
|
||||
|
||||
bool operator!=(const ReplacingIterator& other) const noexcept
|
||||
bool operator!=(const ReplacingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -249,12 +331,12 @@ struct ReplacingRange : RangeAdapter
|
||||
value_type what;
|
||||
value_type with;
|
||||
|
||||
auto begin() const noexcept
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return ReplacingIterator(base.begin(), what, with);
|
||||
}
|
||||
|
||||
auto end() const noexcept
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return ReplacingIterator(base.end(), what, with);
|
||||
}
|
||||
@@ -288,14 +370,14 @@ struct MappingIterator
|
||||
TFunctor functor;
|
||||
mutable Optional<value_type> result;
|
||||
|
||||
MappingIterator(TIterator base_, TFunctor functor_) noexcept : base(base_), functor(std::move(functor_)) {}
|
||||
MappingIterator(const MappingIterator&) noexcept = default;
|
||||
MappingIterator(MappingIterator&&) noexcept = default;
|
||||
MappingIterator(TIterator base_, TFunctor functor_) MIJIN_NOEXCEPT : base(base_), functor(std::move(functor_)) {}
|
||||
MappingIterator(const MappingIterator&) MIJIN_NOEXCEPT = default;
|
||||
MappingIterator(MappingIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
MappingIterator& operator=(const MappingIterator&) noexcept = default;
|
||||
MappingIterator& operator=(MappingIterator&&) noexcept = default;
|
||||
MappingIterator& operator=(const MappingIterator&) MIJIN_NOEXCEPT = default;
|
||||
MappingIterator& operator=(MappingIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
reference operator*() const noexcept
|
||||
reference operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (result.empty()) {
|
||||
result = std::invoke(functor, *base);
|
||||
@@ -303,40 +385,40 @@ struct MappingIterator
|
||||
return *result;
|
||||
}
|
||||
|
||||
MappingIterator& operator++() noexcept
|
||||
MappingIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
++base;
|
||||
result.reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappingIterator operator++(int) noexcept
|
||||
MappingIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
MappingIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
MappingIterator& operator--() noexcept
|
||||
MappingIterator& operator--() MIJIN_NOEXCEPT
|
||||
{
|
||||
--base;
|
||||
result.reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappingIterator operator--(int) noexcept
|
||||
MappingIterator operator--(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
MappingIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const MappingIterator& other) const noexcept
|
||||
bool operator==(const MappingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return base == other.base; // TODO: compare functor? -> doesn't always work and may be useless
|
||||
}
|
||||
|
||||
bool operator!=(const MappingIterator& other) const noexcept
|
||||
bool operator!=(const MappingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -346,22 +428,23 @@ template<typename TIterable, typename TFunctor>
|
||||
struct MappingRange : RangeAdapter
|
||||
{
|
||||
// using value_type = typename std::iterator_traits<decltype(std::begin(std::declval<TIterable>()))>::value_type;
|
||||
using value_type = typename std::iterator_traits<decltype(std::declval<RangeRef<TIterable>>().begin())>::value_type;
|
||||
using in_value_type = typename std::iterator_traits<decltype(std::declval<RangeRef<TIterable>>().begin())>::value_type;
|
||||
using value_type = std::invoke_result_t<TFunctor, in_value_type>;
|
||||
|
||||
RangeRef<TIterable> base;
|
||||
TFunctor functor;
|
||||
|
||||
auto begin() const noexcept
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return MappingIterator(base.begin(), functor);
|
||||
}
|
||||
|
||||
auto end() const noexcept
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return MappingIterator(base.end(), functor);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
[[nodiscard]] bool empty() const MIJIN_NOEXCEPT {
|
||||
return base.begin() == base.end();
|
||||
}
|
||||
};
|
||||
@@ -392,7 +475,7 @@ struct OptionalMappingIterator
|
||||
TFunctor functor;
|
||||
mutable optional_type result; // must be mutable so dereferencing can stay const, not nice :/
|
||||
|
||||
OptionalMappingIterator(TIterator base_, TIterator end_, TFunctor functor_) noexcept : base(base_), end(end_), functor(std::move(functor_)) {
|
||||
OptionalMappingIterator(TIterator base_, TIterator end_, TFunctor functor_) MIJIN_NOEXCEPT : base(base_), end(end_), functor(std::move(functor_)) {
|
||||
if (base != end)
|
||||
{
|
||||
result = functor(*base);
|
||||
@@ -401,18 +484,18 @@ struct OptionalMappingIterator
|
||||
}
|
||||
}
|
||||
}
|
||||
OptionalMappingIterator(const OptionalMappingIterator&) noexcept = default;
|
||||
OptionalMappingIterator(OptionalMappingIterator&&) noexcept = default;
|
||||
OptionalMappingIterator(const OptionalMappingIterator&) MIJIN_NOEXCEPT = default;
|
||||
OptionalMappingIterator(OptionalMappingIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
OptionalMappingIterator& operator=(const OptionalMappingIterator&) noexcept = default;
|
||||
OptionalMappingIterator& operator=(OptionalMappingIterator&&) noexcept = default;
|
||||
OptionalMappingIterator& operator=(const OptionalMappingIterator&) MIJIN_NOEXCEPT = default;
|
||||
OptionalMappingIterator& operator=(OptionalMappingIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
reference operator*() const noexcept
|
||||
reference operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return *result;
|
||||
}
|
||||
|
||||
OptionalMappingIterator& operator++() noexcept
|
||||
OptionalMappingIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
do
|
||||
{
|
||||
@@ -422,19 +505,19 @@ struct OptionalMappingIterator
|
||||
return *this;
|
||||
}
|
||||
|
||||
OptionalMappingIterator operator++(int) noexcept
|
||||
OptionalMappingIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
OptionalMappingIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const OptionalMappingIterator& other) const noexcept
|
||||
bool operator==(const OptionalMappingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return base == other.base && end == other.end; // TODO: compare functor? -> doesn't always work and may be useless
|
||||
}
|
||||
|
||||
bool operator!=(const OptionalMappingIterator& other) const noexcept
|
||||
bool operator!=(const OptionalMappingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -449,12 +532,12 @@ struct OptionalMappingRange : RangeAdapter
|
||||
RangeRef<TIterable> base;
|
||||
TFunctor functor;
|
||||
|
||||
auto begin() const noexcept
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return OptionalMappingIterator(base.begin(), base.end(), functor);
|
||||
}
|
||||
|
||||
auto end() const noexcept
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return OptionalMappingIterator(base.end(), base.end(), functor);
|
||||
}
|
||||
@@ -481,23 +564,23 @@ struct FilteringIterator
|
||||
TIterator end;
|
||||
TPredicate predicate;
|
||||
|
||||
FilteringIterator(TIterator base_, TIterator end_, TPredicate predicate_) noexcept : base(base_), end(end_), predicate(std::move(predicate_)) {
|
||||
FilteringIterator(TIterator base_, TIterator end_, TPredicate predicate_) MIJIN_NOEXCEPT : base(base_), end(end_), predicate(std::move(predicate_)) {
|
||||
if (base != end && !predicate(*base)) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
FilteringIterator(const FilteringIterator&) noexcept = default;
|
||||
FilteringIterator(FilteringIterator&&) noexcept = default;
|
||||
FilteringIterator(const FilteringIterator&) MIJIN_NOEXCEPT = default;
|
||||
FilteringIterator(FilteringIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
FilteringIterator& operator=(const FilteringIterator&) noexcept = default;
|
||||
FilteringIterator& operator=(FilteringIterator&&) noexcept = default;
|
||||
FilteringIterator& operator=(const FilteringIterator&) MIJIN_NOEXCEPT = default;
|
||||
FilteringIterator& operator=(FilteringIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
reference operator*() const noexcept
|
||||
reference operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return *base;
|
||||
}
|
||||
|
||||
FilteringIterator& operator++() noexcept
|
||||
FilteringIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
do
|
||||
{
|
||||
@@ -506,19 +589,19 @@ struct FilteringIterator
|
||||
return *this;
|
||||
}
|
||||
|
||||
FilteringIterator operator++(int) noexcept
|
||||
FilteringIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
FilteringIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const FilteringIterator& other) const noexcept
|
||||
bool operator==(const FilteringIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return base == other.base && end == other.end; // TODO: compare predicate?
|
||||
}
|
||||
|
||||
bool operator!=(const FilteringIterator& other) const noexcept
|
||||
bool operator!=(const FilteringIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -533,12 +616,12 @@ struct FilteringRange : RangeAdapter
|
||||
RangeRef<TIterable> base;
|
||||
TPredicate predicate;
|
||||
|
||||
auto begin() const noexcept
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return FilteringIterator(base.begin(), base.end(), predicate);
|
||||
}
|
||||
|
||||
auto end() const noexcept
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return FilteringIterator(base.end(), base.end(), predicate);
|
||||
}
|
||||
@@ -557,9 +640,9 @@ template<typename TFirstIterator, typename TSecondIterator>
|
||||
struct ChainingIterator
|
||||
{
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = typename std::iterator_traits<TFirstIterator>::value_type;
|
||||
using pointer = std::add_const_t<value_type>*;
|
||||
using reference = std::add_const_t<value_type>&;
|
||||
using value_type = std::remove_reference_t<typename std::iterator_traits<TFirstIterator>::reference>;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using iterator_category = std::bidirectional_iterator_tag; // TODO?
|
||||
|
||||
TFirstIterator firstBase;
|
||||
@@ -567,13 +650,13 @@ struct ChainingIterator
|
||||
TSecondIterator secondBase;
|
||||
TSecondIterator secondBegin;
|
||||
|
||||
ChainingIterator(TFirstIterator firstBase_, TFirstIterator firstEnd_, TSecondIterator secondBase_, TSecondIterator secondBegin_) noexcept
|
||||
ChainingIterator(TFirstIterator firstBase_, TFirstIterator firstEnd_, TSecondIterator secondBase_, TSecondIterator secondBegin_) MIJIN_NOEXCEPT
|
||||
: firstBase(firstBase_), firstEnd(firstEnd_), secondBase(secondBase_), secondBegin(secondBegin_) {}
|
||||
ChainingIterator(const ChainingIterator&) noexcept = default;
|
||||
ChainingIterator(const ChainingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
ChainingIterator& operator=(const ChainingIterator&) noexcept = default;
|
||||
ChainingIterator& operator=(const ChainingIterator&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
pointer operator->() const noexcept
|
||||
pointer operator->() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (firstBase == firstEnd)
|
||||
{
|
||||
@@ -582,7 +665,7 @@ struct ChainingIterator
|
||||
return &*firstBase;
|
||||
}
|
||||
|
||||
reference operator*() const noexcept
|
||||
reference operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (firstBase == firstEnd)
|
||||
{
|
||||
@@ -591,7 +674,7 @@ struct ChainingIterator
|
||||
return *firstBase;
|
||||
}
|
||||
|
||||
ChainingIterator& operator++() noexcept
|
||||
ChainingIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (firstBase == firstEnd) {
|
||||
++secondBase;
|
||||
@@ -602,14 +685,14 @@ struct ChainingIterator
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChainingIterator operator++(int) noexcept
|
||||
ChainingIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
ChainingIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
ChainingIterator& operator--() noexcept
|
||||
ChainingIterator& operator--() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (secondBase == secondBegin) {
|
||||
--firstBase;
|
||||
@@ -620,19 +703,19 @@ struct ChainingIterator
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChainingIterator operator--(int) noexcept
|
||||
ChainingIterator operator--(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
ChainingIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const ChainingIterator& other) const noexcept
|
||||
bool operator==(const ChainingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return firstBase == other.firstBase && secondBase == other.secondBase; // should be enough
|
||||
}
|
||||
|
||||
bool operator!=(const ChainingIterator& other) const noexcept
|
||||
bool operator!=(const ChainingIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -646,12 +729,12 @@ struct ChainedRange : RangeAdapter
|
||||
RangeRef<TFirstRange> first;
|
||||
RangeRef<TSecondRange> second;
|
||||
|
||||
auto begin() const noexcept
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return ChainingIterator(first.begin(), first.end(), second.begin(), second.begin());
|
||||
}
|
||||
|
||||
auto end() const noexcept
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return ChainingIterator(first.end(), first.end(), second.end(), second.begin());
|
||||
}
|
||||
@@ -684,23 +767,23 @@ struct TypeFilteringIterator
|
||||
TIterator base;
|
||||
TIterator end;
|
||||
|
||||
TypeFilteringIterator(TIterator base_, TIterator end_) noexcept : base(base_), end(end_) {
|
||||
TypeFilteringIterator(TIterator base_, TIterator end_) MIJIN_NOEXCEPT : base(base_), end(end_) {
|
||||
if (base != end && !isCastable()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
TypeFilteringIterator(const TypeFilteringIterator&) noexcept = default;
|
||||
TypeFilteringIterator(TypeFilteringIterator&&) noexcept = default;
|
||||
TypeFilteringIterator(const TypeFilteringIterator&) MIJIN_NOEXCEPT = default;
|
||||
TypeFilteringIterator(TypeFilteringIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
TypeFilteringIterator& operator=(const TypeFilteringIterator&) noexcept = default;
|
||||
TypeFilteringIterator& operator=(TypeFilteringIterator&&) noexcept = default;
|
||||
TypeFilteringIterator& operator=(const TypeFilteringIterator&) MIJIN_NOEXCEPT = default;
|
||||
TypeFilteringIterator& operator=(TypeFilteringIterator&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
reference operator*() const noexcept
|
||||
reference operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return static_cast<reference>(*base);
|
||||
}
|
||||
|
||||
TypeFilteringIterator& operator++() noexcept
|
||||
TypeFilteringIterator& operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
do
|
||||
{
|
||||
@@ -709,19 +792,19 @@ struct TypeFilteringIterator
|
||||
return *this;
|
||||
}
|
||||
|
||||
TypeFilteringIterator operator++(int) noexcept
|
||||
TypeFilteringIterator operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
FilteringIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const TypeFilteringIterator& other) const noexcept
|
||||
bool operator==(const TypeFilteringIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return base == other.base && end == other.end;
|
||||
}
|
||||
|
||||
bool operator!=(const TypeFilteringIterator& other) const noexcept
|
||||
bool operator!=(const TypeFilteringIterator& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -747,12 +830,12 @@ struct TypeFilteringRange : RangeAdapter
|
||||
|
||||
RangeRef<TIterable> iterable;
|
||||
|
||||
auto begin() const noexcept
|
||||
auto begin() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return TypeFilteringIterator<TType, decltype(iterable.begin())>(iterable.begin(), iterable.end());
|
||||
}
|
||||
|
||||
auto end() const noexcept
|
||||
auto end() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return TypeFilteringIterator<TType, decltype(iterable.begin())>(iterable.end(), iterable.end());
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
#include "os.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include "../detect.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
#include <mutex>
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
// TODO
|
||||
#include <array>
|
||||
#include <windows.h>
|
||||
#include "../util/winundef.hpp"
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@@ -32,6 +35,13 @@ namespace mijin
|
||||
// internal variables
|
||||
//
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
namespace
|
||||
{
|
||||
std::mutex gDlErrorMutex; // dlerror may not be thread-safe
|
||||
}
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
|
||||
//
|
||||
// internal functions
|
||||
//
|
||||
@@ -40,32 +50,85 @@ namespace mijin
|
||||
// public functions
|
||||
//
|
||||
|
||||
LibraryHandle openSharedLibrary(std::string_view libraryFile) noexcept
|
||||
Result<LibraryHandle> openSharedLibrary(std::string_view libraryFile) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
const std::unique_lock dlErrorLock(gDlErrorMutex);
|
||||
dlerror(); // NOLINT(concurrency-mt-unsafe) we take care of that
|
||||
|
||||
const fs::path libraryPath = fs::absolute(libraryFile);
|
||||
return {.data = dlopen(libraryPath.c_str(), RTLD_NOW)};
|
||||
void* ptr = dlopen(libraryPath.c_str(), RTLD_NOW);
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
return ResultError(dlerror()); // NOLINT(concurrency-mt-unsafe) we take care of that
|
||||
}
|
||||
return LibraryHandle{.data = dlopen(libraryPath.c_str(), RTLD_NOW)};
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
// TODO
|
||||
(void) libraryFile;
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
void* loadSymbolFromLibrary(const LibraryHandle library, const char* symbolName) noexcept
|
||||
bool closeSharedLibrary(const LibraryHandle library) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
return dlclose(library.data) == 0;
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
(void) library;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* loadSymbolFromLibrary(const LibraryHandle library, const char* symbolName) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
return dlsym(library.data, symbolName);
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
(void) library;
|
||||
(void) symbolName;
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void setCurrentThreadName(const char* threadName) noexcept
|
||||
void setCurrentThreadName(const char* threadName) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
pthread_setname_np(pthread_self(), threadName);
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
(void) threadName;
|
||||
#endif
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) noexcept
|
||||
std::string getExecutablePath() MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
return fs::canonical("/proc/self/exe").string();
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
// TODO: this is completely untested
|
||||
std::array<char, 1024> buffer;
|
||||
if (FAILED(GetModuleFileNameA(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()))))
|
||||
{
|
||||
std::vector<char> buffer2;
|
||||
buffer2.resize(1024);
|
||||
do
|
||||
{
|
||||
if (buffer2.size() >= 10240)
|
||||
{
|
||||
MIJIN_ERROR("Something is wrong.");
|
||||
return "";
|
||||
}
|
||||
buffer2.resize(buffer2.size() + 1024);
|
||||
}
|
||||
while (FAILED(GetModuleFileNameA(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()))));
|
||||
return buffer2.data();
|
||||
}
|
||||
return buffer.data();
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
return "lib" + std::string(libraryName) + ".so";
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include "../detect.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../types/result.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -20,6 +23,15 @@ namespace mijin
|
||||
// public constants
|
||||
//
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
inline const std::string_view LIBRARY_EXT = "so";
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
inline const std::string_view LIBRARY_EXT = "dll";
|
||||
#else
|
||||
[[deprecated("OS not supported.")]]
|
||||
inline const int LIBRARY_EXT = 0;
|
||||
#endif
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
@@ -28,20 +40,77 @@ struct LibraryHandle
|
||||
{
|
||||
void* data = nullptr;
|
||||
|
||||
constexpr operator bool() const noexcept { return data != nullptr; }
|
||||
constexpr bool operator!() const noexcept { return data == nullptr; }
|
||||
constexpr operator bool() const MIJIN_NOEXCEPT { return data != nullptr; }
|
||||
constexpr bool operator!() const MIJIN_NOEXCEPT { return data == nullptr; }
|
||||
};
|
||||
|
||||
class SharedLibrary
|
||||
{
|
||||
private:
|
||||
LibraryHandle mHandle;
|
||||
public:
|
||||
SharedLibrary() MIJIN_NOEXCEPT = default;
|
||||
SharedLibrary(const SharedLibrary&) = delete;
|
||||
SharedLibrary(SharedLibrary&& other) MIJIN_NOEXCEPT : mHandle(std::exchange(other.mHandle, {})) {}
|
||||
inline ~SharedLibrary() MIJIN_NOEXCEPT;
|
||||
|
||||
SharedLibrary& operator=(const SharedLibrary&) = delete;
|
||||
SharedLibrary& operator=(SharedLibrary&& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
mHandle = std::exchange(other.mHandle, {});
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr operator bool() const MIJIN_NOEXCEPT { return static_cast<bool>(mHandle); }
|
||||
constexpr bool operator!() const MIJIN_NOEXCEPT { return !mHandle; }
|
||||
|
||||
[[nodiscard]] inline Result<bool> open(std::string_view libraryFile) MIJIN_NOEXCEPT;
|
||||
inline bool close() MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] inline void* loadSymbol(const char* symbolName) MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
[[nodiscard]] LibraryHandle openSharedLibrary(std::string_view libraryFile) noexcept;
|
||||
[[nodiscard]] void* loadSymbolFromLibrary(const LibraryHandle library, const char* symbolName) noexcept;
|
||||
void setCurrentThreadName(const char* threadName) noexcept;
|
||||
[[nodiscard]] Result<LibraryHandle> openSharedLibrary(std::string_view libraryFile) MIJIN_NOEXCEPT;
|
||||
bool closeSharedLibrary(const LibraryHandle library) MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] void* loadSymbolFromLibrary(const LibraryHandle library, const char* symbolName) MIJIN_NOEXCEPT;
|
||||
void setCurrentThreadName(const char* threadName) MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] std::string getExecutablePath() MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) noexcept;
|
||||
[[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) MIJIN_NOEXCEPT;
|
||||
|
||||
SharedLibrary::~SharedLibrary() MIJIN_NOEXCEPT
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
Result<bool> SharedLibrary::open(std::string_view libraryFile) MIJIN_NOEXCEPT
|
||||
{
|
||||
close();
|
||||
Result<LibraryHandle> openResult = openSharedLibrary(libraryFile);
|
||||
if (openResult.isSuccess())
|
||||
{
|
||||
mHandle = openResult.getValue();
|
||||
return true;
|
||||
}
|
||||
return ResultError(openResult.getError());
|
||||
}
|
||||
|
||||
bool SharedLibrary::close() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (mHandle)
|
||||
{
|
||||
return closeSharedLibrary(std::exchange(mHandle, {}));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void* SharedLibrary::loadSymbol(const char* symbolName) MIJIN_NOEXCEPT
|
||||
{
|
||||
return loadSymbolFromLibrary(mHandle, symbolName);
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_OS_HPP_INCLUDED)
|
||||
|
||||
109
source/mijin/util/property.hpp
Normal file
109
source/mijin/util/property.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef MIJIN_UTIL_PROPERTY_HPP_INCLUDED
|
||||
#define MIJIN_UTIL_PROPERTY_HPP_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename T, typename TGet = const T&, typename TSet = T>
|
||||
class PropertyStorage
|
||||
{
|
||||
public:
|
||||
using value_t = T;
|
||||
using get_t = TGet;
|
||||
using set_t = TSet;
|
||||
|
||||
virtual ~PropertyStorage() MIJIN_NOEXCEPT = default;
|
||||
|
||||
[[nodiscard]] virtual TGet getValue() = 0;
|
||||
virtual void setValue(TSet value) = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class SimplePropertyStorage : public PropertyStorage<T>
|
||||
{
|
||||
private:
|
||||
T value_;
|
||||
public:
|
||||
SimplePropertyStorage() MIJIN_NOEXCEPT = default;
|
||||
SimplePropertyStorage(const SimplePropertyStorage& other) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(T(other.value_))) = default;
|
||||
SimplePropertyStorage(SimplePropertyStorage&& other) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(T(std::move(other.value_)))) = default;
|
||||
explicit SimplePropertyStorage(T value) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(T(std::move(value)))) : value_(std::move(value)) {}
|
||||
|
||||
SimplePropertyStorage& operator=(const SimplePropertyStorage& other) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(value_ = other.value_)) = default;
|
||||
SimplePropertyStorage& operator=(SimplePropertyStorage&& other) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(value_ = std::move(other.value_))) = default;
|
||||
|
||||
const T& getValue() MIJIN_NOEXCEPT override { return value_; }
|
||||
void setValue(T value) override { value_ = std::move(value); }
|
||||
};
|
||||
|
||||
template<typename T, typename TGet = const T&, typename TSet = T>
|
||||
class Property
|
||||
{
|
||||
private:
|
||||
using storage_t = PropertyStorage<T, TGet, TSet>;
|
||||
using storage_ptr_t = std::unique_ptr<storage_t>;
|
||||
|
||||
storage_ptr_t storage_;
|
||||
public:
|
||||
class PropertyProxy
|
||||
{
|
||||
private:
|
||||
Property* base_;
|
||||
public:
|
||||
explicit PropertyProxy(Property* base) MIJIN_NOEXCEPT : base_(base) {}
|
||||
|
||||
operator TGet() MIJIN_NOEXCEPT { return base_->get(); }
|
||||
PropertyProxy& operator=(TSet value) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(std::declval<T&>() = std::move(value)))
|
||||
{
|
||||
base_->set(std::move(value));
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
Property() MIJIN_NOEXCEPT = default;
|
||||
explicit Property(storage_ptr_t storage) MIJIN_NOEXCEPT : storage_(std::move(storage)) {}
|
||||
Property(Property&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
Property& operator=(Property&&) MIJIN_NOEXCEPT = default;
|
||||
PropertyProxy operator*() MIJIN_NOEXCEPT { return PropertyProxy(this); }
|
||||
std::remove_reference_t<TGet>* operator->() const MIJIN_NOEXCEPT { return &get(); }
|
||||
|
||||
[[nodiscard]] TGet get() const MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(storage_ != nullptr, "Cannot get value from an unset property.");
|
||||
return storage_->getValue();
|
||||
}
|
||||
void set(TSet value) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(std::declval<T&>() = std::move(value)))
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(storage_ != nullptr, "Cannot set value of an unset property.");
|
||||
storage_->setValue(std::move<TSet>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TStorage>
|
||||
Property(std::unique_ptr<TStorage>) -> Property<typename TStorage::value_t, typename TStorage::get_t, typename TStorage::set_t>;
|
||||
|
||||
template<typename T>
|
||||
Property<T> makeSimpleProperty(T value) MIJIN_CONDITIONAL_NOEXCEPT(noexcept(std::declval<T&>() = std::move(value)))
|
||||
{
|
||||
return Property(std::make_unique<SimplePropertyStorage<T>>(std::move(value)));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MIJIN_UTIL_PROPERTY_HPP_INCLUDED
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include "./common_macros.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
#define MIJIN_SCOPE_EXIT_NAMED(name) \
|
||||
const mijin::ScopeExitGuard name = [&]()
|
||||
@@ -19,20 +20,20 @@ private:
|
||||
mutable std::function<void(void)> func; // variable is declared const to make clang-tidy happy, but we still want to be able to reset()
|
||||
public:
|
||||
template<typename TFunc>
|
||||
inline ScopeExitGuard(TFunc&& func_) noexcept : func(std::forward<TFunc>(func_)) {}
|
||||
ScopeExitGuard(const ScopeExitGuard&) noexcept = delete;
|
||||
ScopeExitGuard(ScopeExitGuard&&) noexcept = delete;
|
||||
inline ~ScopeExitGuard() noexcept
|
||||
inline ScopeExitGuard(TFunc&& func_) MIJIN_NOEXCEPT : func(std::forward<TFunc>(func_)) {}
|
||||
ScopeExitGuard(const ScopeExitGuard&) = delete;
|
||||
ScopeExitGuard(ScopeExitGuard&&) = delete;
|
||||
inline ~ScopeExitGuard() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (func) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
ScopeExitGuard& operator=(const ScopeExitGuard&) noexcept = delete;
|
||||
ScopeExitGuard& operator=(ScopeExitGuard&&) noexcept = delete;
|
||||
ScopeExitGuard& operator=(const ScopeExitGuard&) = delete;
|
||||
ScopeExitGuard& operator=(ScopeExitGuard&&) = delete;
|
||||
|
||||
inline void reset() const noexcept
|
||||
inline void reset() const MIJIN_NOEXCEPT
|
||||
{
|
||||
func = {};
|
||||
}
|
||||
|
||||
@@ -4,9 +4,19 @@
|
||||
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "./iterators.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -19,16 +29,29 @@ namespace mijin
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public traits
|
||||
//
|
||||
|
||||
template<typename TString>
|
||||
using char_type_t = decltype(std::string_view(std::declval<TString>()))::value_type;
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
struct SplitOptions
|
||||
{
|
||||
std::size_t limitParts = std::numeric_limits<std::size_t>::max();
|
||||
bool ignoreEmpty = true;
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template <typename TRange, typename TValue = typename TRange::value_type>
|
||||
std::string join(const TRange& elements, const char* const delimiter)
|
||||
[[nodiscard]] std::string join(const TRange& elements, const char* const delimiter)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
auto first = std::begin(elements);
|
||||
@@ -47,13 +70,330 @@ std::string join(const TRange& elements, const char* const delimiter)
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template<typename TChar, typename TTraits, typename TOutIterator>
|
||||
void splitImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> separator,
|
||||
const SplitOptions& options,
|
||||
TOutIterator outIterator,
|
||||
std::size_t& numEmitted)
|
||||
{
|
||||
using sv_t = std::basic_string_view<TChar, TTraits>;
|
||||
|
||||
MIJIN_ASSERT(options.limitParts > 0, "Cannot split to zero parts.");
|
||||
MIJIN_ASSERT(!separator.empty(), "Separator cannot be empty.");
|
||||
|
||||
if (separator.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto emit = [&](std::string_view result)
|
||||
{
|
||||
*outIterator = result;
|
||||
++outIterator;
|
||||
++numEmitted;
|
||||
};
|
||||
|
||||
if (options.limitParts <= 1)
|
||||
{
|
||||
emit(stringView);
|
||||
return;
|
||||
}
|
||||
auto start = stringView.begin();
|
||||
auto pos = start;
|
||||
const auto end = stringView.end();
|
||||
|
||||
auto seperatorFound = [&]()
|
||||
{
|
||||
return pos <= (end - separator.size()) && sv_t(pos, pos + separator.size()) == separator;
|
||||
};
|
||||
|
||||
while (pos != end)
|
||||
{
|
||||
if (seperatorFound())
|
||||
{
|
||||
if (!options.ignoreEmpty || pos != start)
|
||||
{
|
||||
emit(std::string_view(start, pos));
|
||||
}
|
||||
start = pos = (pos + separator.size());
|
||||
if (numEmitted == options.limitParts - 1)
|
||||
{
|
||||
if (options.ignoreEmpty)
|
||||
{
|
||||
while (seperatorFound())
|
||||
{
|
||||
pos += separator.size();
|
||||
}
|
||||
}
|
||||
emit(std::string_view(pos, end));
|
||||
return;
|
||||
}
|
||||
if (!options.ignoreEmpty && pos == end)
|
||||
{
|
||||
// skipped a separator at the very end, add an empty entry
|
||||
emit("");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
if (start != end)
|
||||
{
|
||||
emit(std::string_view(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::vector<std::basic_string_view<TChar, TTraits>> splitImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> separator,
|
||||
const SplitOptions& options)
|
||||
{
|
||||
std::vector<std::basic_string_view<TChar, TTraits>> result;
|
||||
std::size_t numEmitted = 0;
|
||||
splitImpl(stringView, separator, options, std::back_inserter(result), numEmitted);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template<std::size_t count, typename TChar, typename TTraits>
|
||||
std::array<std::basic_string_view<TChar, TTraits>, count> splitFixedImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> separator,
|
||||
SplitOptions options,
|
||||
std::size_t* outNumResults)
|
||||
{
|
||||
options.limitParts = count;
|
||||
|
||||
std::array<std::basic_string_view<TChar, TTraits>, count> result;
|
||||
std::size_t numEmitted = 0;
|
||||
splitImpl(stringView, separator, options, result.begin(), numEmitted);
|
||||
if (outNumResults != nullptr)
|
||||
{
|
||||
*outNumResults = numEmitted;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraitsA, typename TTraitsB>
|
||||
bool equalsIgnoreCaseImpl(std::basic_string_view<TChar, TTraitsA> stringA, std::basic_string_view<TChar, TTraitsB> stringB) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (stringA.size() != stringB.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto [charA, charB] : zip(stringA, stringB))
|
||||
{
|
||||
if (std::tolower(charA) != std::tolower(charB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimPrefixImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim)
|
||||
{
|
||||
stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size()));
|
||||
return stringView;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimSuffixImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim)
|
||||
{
|
||||
stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size()));
|
||||
return stringView;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim)
|
||||
{
|
||||
return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim);
|
||||
}
|
||||
|
||||
template<typename TChar>
|
||||
static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
|
||||
|
||||
template<typename TChar>
|
||||
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
|
||||
= {DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
|
||||
}
|
||||
|
||||
template<typename TLeft, typename TRight>
|
||||
[[nodiscard]] auto split(TLeft&& stringView, TRight&& separator, const SplitOptions& options = {})
|
||||
{
|
||||
return detail::splitImpl(std::basic_string_view(std::forward<TLeft>(stringView)),
|
||||
std::basic_string_view(std::forward<TRight>(separator)), options);
|
||||
}
|
||||
|
||||
template<std::size_t count, typename TLeft, typename TRight>
|
||||
[[nodiscard]] auto splitFixed(TLeft&& stringView, TRight&& separator, SplitOptions options = {}, std::size_t* outNumResults = nullptr)
|
||||
{
|
||||
return detail::splitFixedImpl<count>(std::basic_string_view(std::forward<TLeft>(stringView)),
|
||||
std::basic_string_view(std::forward<TRight>(separator)), options, outNumResults);
|
||||
}
|
||||
|
||||
template<typename TString, typename TChars>
|
||||
[[nodiscard]]
|
||||
auto trimPrefix(TString&& string, TChars&& chars)
|
||||
{
|
||||
return detail::trimPrefixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
|
||||
}
|
||||
|
||||
template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trimPrefix(TString&& string)
|
||||
{
|
||||
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TString, typename TChars>
|
||||
[[nodiscard]]
|
||||
auto trimSuffix(TString&& string, TChars&& chars)
|
||||
{
|
||||
return detail::trimSuffixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
|
||||
}
|
||||
|
||||
template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trimSuffix(TString&& string)
|
||||
{
|
||||
return trimSuffix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TString, typename TChars>
|
||||
[[nodiscard]]
|
||||
auto trim(TString&& string, TChars&& chars)
|
||||
{
|
||||
return detail::trimImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
|
||||
}
|
||||
|
||||
template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trim(TString&& string)
|
||||
{
|
||||
return trim(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TLeft, typename TRight>
|
||||
[[nodiscard]] bool equalsIgnoreCase(TLeft&& left, TRight&& right) MIJIN_NOEXCEPT
|
||||
{
|
||||
return detail::equalsIgnoreCaseImpl(std::string_view(left), std::string_view(right));
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
constexpr void makeLower(std::basic_string<TChar, TTraits, TAllocator>& string)
|
||||
{
|
||||
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
|
||||
{
|
||||
return std::tolower<TChar>(chr, locale);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
constexpr void makeUpper(std::basic_string<TChar, TTraits, TAllocator>& string)
|
||||
{
|
||||
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
|
||||
{
|
||||
return std::toupper<TChar>(chr, locale);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
[[nodiscard]]
|
||||
constexpr auto toLower(TArgs&&... args)
|
||||
{
|
||||
std::basic_string string(std::forward<TArgs>(args)...);
|
||||
makeLower(string);
|
||||
return string;
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
[[nodiscard]]
|
||||
constexpr auto toUpper(TArgs&&... args)
|
||||
{
|
||||
std::basic_string string(std::forward<TArgs>(args)...);
|
||||
makeUpper(string);
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
template<std::integral TNumber>
|
||||
[[nodiscard]]
|
||||
constexpr bool toNumber(std::string_view stringView, TNumber& outNumber, int base = 10) MIJIN_NOEXCEPT
|
||||
{
|
||||
const char* start = &*stringView.begin();
|
||||
const char* end = start + stringView.size();
|
||||
const std::from_chars_result res = std::from_chars(start, end, outNumber, base);
|
||||
return res.ec == std::errc{} && res.ptr == end;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, std::integral TNumber>
|
||||
[[nodiscard]]
|
||||
constexpr bool toNumber(std::basic_string_view<TChar, TTraits> stringView, TNumber& outNumber, int base = 10) MIJIN_NOEXCEPT requires (!std::is_same_v<TChar, char>)
|
||||
{
|
||||
std::string asString;
|
||||
asString.resize(stringView.size());
|
||||
// should only contain number symbols, so just cast down to char
|
||||
std::transform(stringView.begin(), stringView.end(), asString.begin(), [](TChar chr) { return static_cast<char>(chr); });
|
||||
return toNumber(asString, outNumber, base);
|
||||
}
|
||||
|
||||
template<std::floating_point TNumber>
|
||||
[[nodiscard]]
|
||||
constexpr bool toNumber(std::string_view stringView, TNumber& outNumber, std::chars_format fmt = std::chars_format::general) MIJIN_NOEXCEPT
|
||||
{
|
||||
const char* start = &*stringView.begin();
|
||||
const char* end = start + stringView.size();
|
||||
const std::from_chars_result res = std::from_chars(start, end, outNumber, fmt);
|
||||
return res.ec == std::errc{} && res.ptr == end;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, std::floating_point TNumber>
|
||||
[[nodiscard]]
|
||||
constexpr bool toNumber(std::basic_string_view<TChar, TTraits> stringView, TNumber& outNumber, std::chars_format fmt = std::chars_format::general) MIJIN_NOEXCEPT requires (!std::is_same_v<TChar, char>)
|
||||
{
|
||||
std::string asString;
|
||||
asString.resize(stringView.size());
|
||||
// should only contain number symbols, so just cast down to char
|
||||
std::transform(stringView.begin(), stringView.end(), asString.begin(), [](TChar chr) { return static_cast<char>(chr); });
|
||||
return toNumber(asString, outNumber, fmt);
|
||||
}
|
||||
|
||||
template<typename TChar>
|
||||
[[nodiscard]]
|
||||
constexpr bool isDecimalChar(TChar chr) noexcept
|
||||
{
|
||||
return (chr >= TChar('0') && chr <= TChar('9'));
|
||||
}
|
||||
|
||||
template<typename TChar>
|
||||
[[nodiscard]]
|
||||
constexpr bool isHexadecimalChar(TChar chr) noexcept
|
||||
{
|
||||
return isDecimalChar(chr)
|
||||
|| (chr >= TChar('A') && chr <= TChar('F'))
|
||||
|| (chr >= TChar('a') && chr <= TChar('f'));
|
||||
}
|
||||
|
||||
namespace pipe
|
||||
{
|
||||
struct Join
|
||||
{
|
||||
const char* delimiter;
|
||||
|
||||
explicit Join(const char* delimiter_) noexcept : delimiter(delimiter_) {}
|
||||
explicit Join(const char* delimiter_) MIJIN_NOEXCEPT : delimiter(delimiter_) {}
|
||||
};
|
||||
|
||||
template<typename TIterable>
|
||||
@@ -62,7 +402,6 @@ auto operator|(TIterable&& iterable, const Join& joiner)
|
||||
return join(std::forward<TIterable>(iterable), joiner.delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
|
||||
|
||||
@@ -112,6 +112,42 @@ struct is_type_member<TElement, TCollection<Ts...>>
|
||||
template<typename TElement, typename TCollection>
|
||||
constexpr bool is_type_member_v = is_type_member<TElement, TCollection>::value;
|
||||
|
||||
template<typename TFrom, typename TTo>
|
||||
using copy_const_t = std::conditional_t<std::is_const_v<TFrom>, std::add_const_t<TTo>, std::remove_const_t<TTo>>;
|
||||
|
||||
template<typename TFrom, typename TTo>
|
||||
using copy_volatile_t = std::conditional_t<std::is_volatile_v<TFrom>, std::add_volatile_t<TTo>, std::remove_volatile_t<TTo>>;
|
||||
|
||||
template<typename TFrom, typename TTo>
|
||||
using copy_cv_t = copy_const_t<TFrom, copy_volatile_t<TFrom, TTo>>;
|
||||
|
||||
template<typename TActual, typename TDelay>
|
||||
using delay_type_t = TActual;
|
||||
|
||||
|
||||
template<std::size_t I, typename TArg, typename... TArgs>
|
||||
struct TypeAtHelper
|
||||
{
|
||||
using type_t = typename TypeAtHelper<I-1, TArgs...>::type_t;
|
||||
};
|
||||
|
||||
template<typename TArg, typename... TArgs>
|
||||
struct TypeAtHelper<0, TArg, TArgs...>
|
||||
{
|
||||
using type_t = TArg;
|
||||
};
|
||||
template<std::size_t I, typename... TArgs>
|
||||
using type_at_t = TypeAtHelper<I, TArgs...>::type_t;
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
struct is_template_instance : std::false_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename... TArgs>
|
||||
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
32
source/mijin/util/variant.hpp
Normal file
32
source/mijin/util/variant.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_UTIL_VARIANT_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_VARIANT_HPP_INCLUDED 1
|
||||
|
||||
#include <variant>
|
||||
#include "./traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public traits
|
||||
//
|
||||
|
||||
template<typename TSearch, typename TVariant>
|
||||
inline constexpr bool variant_contains_v = false;
|
||||
|
||||
template<typename TSearch, typename... TVariantTypes>
|
||||
inline constexpr bool variant_contains_v<TSearch, std::variant<TVariantTypes...>> = mijin::is_any_type_v<TSearch, TVariantTypes...>;
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
template<typename... TCallable>
|
||||
struct Visitor : TCallable ...
|
||||
{
|
||||
using TCallable::operator()...;
|
||||
};
|
||||
}
|
||||
#endif // !defined(MIJIN_UTIL_VARIANT_HPP_INCLUDED)
|
||||
69
source/mijin/util/winerr.hpp
Normal file
69
source/mijin/util/winerr.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_UTIL_WINERR_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_WINERR_HPP_INCLUDED 1
|
||||
|
||||
#include <format>
|
||||
|
||||
#include <windows.h>
|
||||
#include "./winundef.hpp"
|
||||
|
||||
#include "./exception.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
class HRException : public Exception
|
||||
{
|
||||
private:
|
||||
HRESULT hResult_;
|
||||
public:
|
||||
explicit inline HRException(HRESULT hResult);
|
||||
inline HRException(std::string_view message, HRESULT hResult);
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
[[nodiscard]]
|
||||
inline std::string getHResultMessage(HRESULT hResult)
|
||||
{
|
||||
if (hResult == S_OK)
|
||||
{
|
||||
return "Success (0x0)";
|
||||
}
|
||||
return std::format("Unknown error (0x{:x}", hResult);
|
||||
}
|
||||
|
||||
inline HRESULT ensureHR(HRESULT hResult, std::string_view message = {})
|
||||
{
|
||||
if (!SUCCEEDED(hResult))
|
||||
{
|
||||
if (message.empty())
|
||||
{
|
||||
throw HRException(hResult);
|
||||
}
|
||||
throw HRException(message, hResult);
|
||||
}
|
||||
return hResult;
|
||||
}
|
||||
|
||||
HRException::HRException(HRESULT hResult) : Exception(getHResultMessage(hResult)), hResult_(hResult)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HRException::HRException(std::string_view message, HRESULT hResult)
|
||||
: Exception(std::format("{} {}", message, getHResultMessage(hResult))), hResult_(hResult)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
#endif // !defined(MIJIN_UTIL_WINERR_HPP_INCLUDED)
|
||||
24
source/mijin/util/winundef.hpp
Normal file
24
source/mijin/util/winundef.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
#if defined(NEAR)
|
||||
#undef NEAR
|
||||
#endif
|
||||
|
||||
#if defined(FAR)
|
||||
#undef FAR
|
||||
#endif
|
||||
|
||||
#if defined(ERROR)
|
||||
#undef ERROR
|
||||
#endif
|
||||
|
||||
#if defined(IGNORE)
|
||||
#undef IGNORE
|
||||
#endif
|
||||
|
||||
#if defined(ABSOLUTE)
|
||||
#undef ABSOLUTE
|
||||
#endif
|
||||
|
||||
#if defined(RELATIVE)
|
||||
#undef RELATIVE
|
||||
#endif
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
#include "./filesystem.hpp"
|
||||
|
||||
#include "../platform/folders.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
@@ -28,23 +30,16 @@ namespace mijin
|
||||
// public functions
|
||||
//
|
||||
|
||||
std::vector<fs::path> OSFileSystemAdapter::getRoots()
|
||||
{
|
||||
return {
|
||||
"/" // TODO: other OSs
|
||||
};
|
||||
}
|
||||
|
||||
fs::path OSFileSystemAdapter::getHomeFolder()
|
||||
{
|
||||
return "/home/mewin"; // very TODO
|
||||
return getKnownFolder(KnownFolder::USER_HOME);
|
||||
}
|
||||
|
||||
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);
|
||||
const fs::directory_iterator iterator(folder, fs::directory_options::skip_permission_denied, err);
|
||||
if (err) {
|
||||
return {}; // TODO: propagate?
|
||||
}
|
||||
@@ -57,18 +52,24 @@ std::vector<FileInfo> OSFileSystemAdapter::listFiles(const fs::path& folder)
|
||||
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());
|
||||
if (info.isFolder)
|
||||
{
|
||||
std::error_code errorCode;
|
||||
fs::directory_iterator dirIt(info.path, errorCode);
|
||||
if (errorCode != std::error_code{})
|
||||
{
|
||||
info.size = std::distance(dirIt, fs::directory_iterator());
|
||||
}
|
||||
catch(std::runtime_error&) {
|
||||
else
|
||||
{
|
||||
info.size = 0;
|
||||
}
|
||||
}
|
||||
else if (!info.isSpecial)
|
||||
{
|
||||
info.size = entry.file_size(err);
|
||||
if (err) {
|
||||
if (err)
|
||||
{
|
||||
info.size = 0;
|
||||
}
|
||||
}
|
||||
@@ -89,10 +90,12 @@ FileInfo OSFileSystemAdapter::getFileInfo(const fs::path& file)
|
||||
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 {
|
||||
MIJIN_TRY
|
||||
{
|
||||
info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator());
|
||||
}
|
||||
catch(std::runtime_error&) {
|
||||
MIJIN_CATCH(std::runtime_error&)
|
||||
{
|
||||
info.size = 0;
|
||||
}
|
||||
}
|
||||
@@ -107,6 +110,11 @@ FileInfo OSFileSystemAdapter::getFileInfo(const fs::path& file)
|
||||
return info;
|
||||
}
|
||||
|
||||
Optional<fs::path> OSFileSystemAdapter::getNativePath(const fs::path& file)
|
||||
{
|
||||
return file;
|
||||
}
|
||||
|
||||
StreamError OSFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
|
||||
{
|
||||
const std::string pathStr = path.string();
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../container/optional.hpp"
|
||||
#include "../io/stream.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/hash.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -40,25 +43,65 @@ struct FileInfo
|
||||
bool isHidden : 1 = false;
|
||||
};
|
||||
|
||||
// basically just a thin wrapper around adapter + path, for utility
|
||||
class PathReference
|
||||
{
|
||||
private:
|
||||
class FileSystemAdapter* adapter_ = nullptr;
|
||||
fs::path path_ = {};
|
||||
public:
|
||||
PathReference() = default;
|
||||
PathReference(const PathReference&) = default;
|
||||
PathReference(PathReference&&) = default;
|
||||
PathReference(class FileSystemAdapter* adapter, fs::path path) MIJIN_NOEXCEPT : adapter_(adapter), path_(std::move(path)) {}
|
||||
|
||||
PathReference& operator=(const PathReference&) = default;
|
||||
PathReference& operator=(PathReference&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
auto operator<=>(const PathReference& other) const MIJIN_NOEXCEPT = default;
|
||||
|
||||
[[nodiscard]] bool empty() const MIJIN_NOEXCEPT { return adapter_ == nullptr; }
|
||||
[[nodiscard]] class FileSystemAdapter* getAdapter() const noexcept { return adapter_; }
|
||||
[[nodiscard]] const fs::path& getPath() const MIJIN_NOEXCEPT { return path_; }
|
||||
[[nodiscard]] inline FileInfo getInfo() const;
|
||||
[[nodiscard]] inline Optional<fs::path> getNativePath() const;
|
||||
[[nodiscard]] inline StreamError open(FileOpenMode mode, std::unique_ptr<Stream>& outStream) const;
|
||||
|
||||
[[nodiscard]]
|
||||
PathReference operator/(const fs::path& more) const
|
||||
{
|
||||
return PathReference(adapter_, path_ / more);
|
||||
}
|
||||
};
|
||||
|
||||
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 Optional<fs::path> getNativePath(const fs::path& /* file */) { return NULL_OPTIONAL; }
|
||||
[[nodiscard]] virtual StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) = 0;
|
||||
virtual void getAllPaths(const fs::path& path, std::vector<PathReference>& outPaths) { outPaths.push_back(getPath(path)); }
|
||||
|
||||
[[nodiscard]] PathReference getPath(fs::path path) MIJIN_NOEXCEPT { return PathReference(this, std::move(path)); }
|
||||
[[nodiscard]] std::vector<PathReference> getAllPaths(const fs::path& path)
|
||||
{
|
||||
std::vector<PathReference> paths;
|
||||
getAllPaths(path, paths);
|
||||
return paths;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
Optional<fs::path> getNativePath(const fs::path& file) override;
|
||||
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
|
||||
|
||||
static OSFileSystemAdapter& getInstance();
|
||||
@@ -68,6 +111,21 @@ public:
|
||||
// public functions
|
||||
//
|
||||
|
||||
inline FileInfo PathReference::getInfo() const
|
||||
{
|
||||
return adapter_->getFileInfo(path_);
|
||||
}
|
||||
|
||||
Optional<fs::path> PathReference::getNativePath() const
|
||||
{
|
||||
return adapter_->getNativePath(path_);
|
||||
}
|
||||
|
||||
inline StreamError PathReference::open(FileOpenMode mode, std::unique_ptr<Stream>& outStream) const
|
||||
{
|
||||
return adapter_->open(path_, mode, outStream);
|
||||
}
|
||||
|
||||
inline std::string formatFileType(const FileInfo& info)
|
||||
{
|
||||
if (info.isFolder) {
|
||||
@@ -100,6 +158,42 @@ inline std::string formatFileSize(std::size_t sizeInBytes)
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
namespace vfs_pipe
|
||||
{
|
||||
template<typename TReal, typename TAdapter = FileSystemAdapter>
|
||||
struct Builder
|
||||
{
|
||||
using adapter_t = TAdapter;
|
||||
|
||||
operator std::unique_ptr<FileSystemAdapter>()
|
||||
{
|
||||
return static_cast<TReal&>(*this).build();
|
||||
}
|
||||
};
|
||||
|
||||
struct OSBuilder : Builder<OSBuilder, OSFileSystemAdapter>
|
||||
{
|
||||
std::unique_ptr<OSFileSystemAdapter> build()
|
||||
{
|
||||
return std::make_unique<OSFileSystemAdapter>();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
inline OSBuilder os() noexcept { return {}; }
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
template<>
|
||||
struct std::hash<mijin::PathReference>
|
||||
{
|
||||
std::size_t operator()(const mijin::PathReference& pathRef) const MIJIN_NOEXCEPT
|
||||
{
|
||||
std::size_t hash = 0;
|
||||
mijin::hashCombine(hash, pathRef.getAdapter());
|
||||
mijin::hashCombine(hash, pathRef.getPath());
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED)
|
||||
|
||||
179
source/mijin/virtual_filesystem/mapping.hpp
Normal file
179
source/mijin/virtual_filesystem/mapping.hpp
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_VIRTUAL_FILESYSTEM_MAPPING_HPP_INCLUDED)
|
||||
#define MIJIN_VIRTUAL_FILESYSTEM_MAPPING_HPP_INCLUDED 1
|
||||
|
||||
#include <algorithm>
|
||||
#include "./filesystem.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename TWrapped>
|
||||
class MappingFileSystemAdapter : public FileSystemAdapter
|
||||
{
|
||||
private:
|
||||
TWrapped wrapped_;
|
||||
fs::path root_;
|
||||
public:
|
||||
template<typename... TArgs>
|
||||
explicit MappingFileSystemAdapter(fs::path root, TArgs&&... args)
|
||||
: wrapped_(std::forward<TArgs>(args)...), root_(std::move(root)) {}
|
||||
MappingFileSystemAdapter(const MappingFileSystemAdapter&) = default;
|
||||
MappingFileSystemAdapter(MappingFileSystemAdapter&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
MappingFileSystemAdapter& operator=(const MappingFileSystemAdapter&) = default;
|
||||
MappingFileSystemAdapter& operator=(MappingFileSystemAdapter&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
fs::path getHomeFolder() override;
|
||||
std::vector<FileInfo> listFiles(const fs::path& folder) override;
|
||||
FileInfo getFileInfo(const fs::path& file) override;
|
||||
Optional<fs::path> getNativePath(const fs::path& file) override;
|
||||
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
|
||||
private:
|
||||
bool adjustPath(fs::path& path) const MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename TWrapped>
|
||||
fs::path MappingFileSystemAdapter<TWrapped>::getHomeFolder()
|
||||
{
|
||||
return "/";
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
std::vector<FileInfo> MappingFileSystemAdapter<TWrapped>::listFiles(const fs::path& folder)
|
||||
{
|
||||
fs::path adjusted = folder;
|
||||
if (!adjustPath(adjusted))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<FileInfo> result;
|
||||
result = wrapped_.listFiles(adjusted);
|
||||
for (FileInfo& fileInfo : result)
|
||||
{
|
||||
if (fileInfo.path.is_absolute())
|
||||
{
|
||||
fileInfo.path = fs::relative(fileInfo.path, "/");
|
||||
}
|
||||
fileInfo.path = root_ / fileInfo.path;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
FileInfo MappingFileSystemAdapter<TWrapped>::getFileInfo(const fs::path& file)
|
||||
{
|
||||
fs::path adjusted = file;
|
||||
if (!adjustPath(adjusted))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return wrapped_.getFileInfo(adjusted);
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
Optional<fs::path> MappingFileSystemAdapter<TWrapped>::getNativePath(const fs::path& file)
|
||||
{
|
||||
fs::path adjusted = file;
|
||||
if (!adjustPath(adjusted))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return wrapped_.getNativePath(adjusted);
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
StreamError MappingFileSystemAdapter<TWrapped>::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
|
||||
{
|
||||
fs::path adjusted = path;
|
||||
if (!adjustPath(adjusted))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return wrapped_.open(adjusted, mode, outStream);
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
bool MappingFileSystemAdapter<TWrapped>::adjustPath(fs::path& path) const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (path.empty() || *path.generic_string().c_str() != '/')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// checks whether path starts with root
|
||||
auto [itRoot, itPath] = std::mismatch(root_.begin(), root_.end(), path.begin(), path.end());
|
||||
if (itRoot != root_.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code error;
|
||||
fs::path wrapped = fs::relative(path, root_, error);
|
||||
|
||||
if (wrapped.empty() || error)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
path = std::move(wrapped);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace vfs_pipe
|
||||
{
|
||||
template<typename TBase>
|
||||
struct MappingBuilder : Builder<MappingBuilder<TBase>, MappingFileSystemAdapter<typename TBase::adapter_t>>
|
||||
{
|
||||
fs::path root;
|
||||
TBase base;
|
||||
|
||||
std::unique_ptr<MappingFileSystemAdapter<typename TBase::adapter_t>> build()
|
||||
{
|
||||
return std::make_unique<MappingFileSystemAdapter<typename TBase::adapter_t>>(root, std::move(*base.build()));
|
||||
}
|
||||
};
|
||||
|
||||
struct MappingOptions
|
||||
{
|
||||
fs::path root;
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
inline MappingOptions map_to(fs::path root) noexcept
|
||||
{
|
||||
return {.root = std::move(root) };
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
[[nodiscard]]
|
||||
MappingBuilder<TBase> operator|(TBase other, MappingOptions options) noexcept
|
||||
{
|
||||
return {.root = std::move(options.root), .base = std::move(other) };
|
||||
}
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_MAPPING_HPP_INCLUDED)
|
||||
@@ -5,6 +5,7 @@
|
||||
#define MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED 1
|
||||
|
||||
#include "./filesystem.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -32,28 +33,24 @@ public:
|
||||
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(RelativeFileSystemAdapter&&) MIJIN_NOEXCEPT = default;
|
||||
|
||||
RelativeFileSystemAdapter& operator=(const RelativeFileSystemAdapter&) = default;
|
||||
RelativeFileSystemAdapter& operator=(RelativeFileSystemAdapter&&) noexcept = default;
|
||||
RelativeFileSystemAdapter& operator=(RelativeFileSystemAdapter&&) MIJIN_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;
|
||||
Optional<fs::path> getNativePath(const fs::path& file) override;
|
||||
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
|
||||
private:
|
||||
fs::path appendPath(const fs::path& other) const MIJIN_NOEXCEPT;
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename TWrapped>
|
||||
std::vector<fs::path> RelativeFileSystemAdapter<TWrapped>::getRoots()
|
||||
{
|
||||
return { root_ };
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
fs::path RelativeFileSystemAdapter<TWrapped>::getHomeFolder()
|
||||
{
|
||||
@@ -63,7 +60,9 @@ fs::path RelativeFileSystemAdapter<TWrapped>::getHomeFolder()
|
||||
template<typename TWrapped>
|
||||
std::vector<FileInfo> RelativeFileSystemAdapter<TWrapped>::listFiles(const fs::path& folder)
|
||||
{
|
||||
std::vector<FileInfo> result = wrapped_.listFiles(root_ / folder);
|
||||
std::vector<FileInfo> result;
|
||||
|
||||
result = wrapped_.listFiles(appendPath(folder));
|
||||
for (FileInfo& fileInfo : result) {
|
||||
fileInfo.path = "/" / fileInfo.path.lexically_relative(root_);
|
||||
}
|
||||
@@ -73,15 +72,66 @@ std::vector<FileInfo> RelativeFileSystemAdapter<TWrapped>::listFiles(const fs::p
|
||||
template<typename TWrapped>
|
||||
FileInfo RelativeFileSystemAdapter<TWrapped>::getFileInfo(const fs::path& file)
|
||||
{
|
||||
return wrapped_.getFileInfo(root_ / file);
|
||||
return wrapped_.getFileInfo(appendPath(file));
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
Optional<fs::path> RelativeFileSystemAdapter<TWrapped>::getNativePath(const fs::path& file)
|
||||
{
|
||||
return wrapped_.getNativePath(appendPath(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);
|
||||
return wrapped_.open(appendPath(path), mode, outStream);
|
||||
}
|
||||
|
||||
template<typename TWrapped>
|
||||
fs::path RelativeFileSystemAdapter<TWrapped>::appendPath(const fs::path& other) const MIJIN_NOEXCEPT
|
||||
{
|
||||
fs::path combinedPath = root_;
|
||||
if (other.is_absolute()) {
|
||||
combinedPath += other;
|
||||
}
|
||||
else {
|
||||
combinedPath /= other;
|
||||
}
|
||||
return combinedPath;
|
||||
}
|
||||
|
||||
namespace vfs_pipe
|
||||
{
|
||||
template<typename TBase>
|
||||
struct RelativeBuilder : Builder<RelativeBuilder<TBase>, RelativeFileSystemAdapter<typename TBase::adapter_t>>
|
||||
{
|
||||
fs::path root;
|
||||
TBase base;
|
||||
|
||||
std::unique_ptr<RelativeFileSystemAdapter<typename TBase::adapter_t>> build()
|
||||
{
|
||||
return std::make_unique<RelativeFileSystemAdapter<typename TBase::adapter_t>>(root, std::move(*base.build()));
|
||||
}
|
||||
};
|
||||
|
||||
struct RelativeOptions
|
||||
{
|
||||
fs::path root;
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
inline RelativeOptions relative_to(fs::path root) noexcept
|
||||
{
|
||||
return {.root = std::move(root) };
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
[[nodiscard]]
|
||||
RelativeBuilder<TBase> operator|(TBase other, RelativeOptions options) noexcept
|
||||
{
|
||||
return {.root = std::move(options.root), .base = std::move(other) };
|
||||
}
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED)
|
||||
|
||||
@@ -30,24 +30,6 @@ namespace mijin
|
||||
// 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()) {
|
||||
@@ -68,7 +50,7 @@ std::vector<FileInfo> StackedFileSystemAdapter::listFiles(const fs::path& folder
|
||||
{
|
||||
return existing.path == file.path;
|
||||
});
|
||||
if (it != files.end()) {
|
||||
if (it == files.end()) {
|
||||
files.push_back(file);
|
||||
}
|
||||
}
|
||||
@@ -90,16 +72,52 @@ FileInfo StackedFileSystemAdapter::getFileInfo(const fs::path& file)
|
||||
return {};
|
||||
}
|
||||
|
||||
StreamError StackedFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
|
||||
Optional<fs::path> StackedFileSystemAdapter::getNativePath(const fs::path& file)
|
||||
{
|
||||
for (auto& adapter : adapters_)
|
||||
{
|
||||
FileInfo fileInfo = adapter->getFileInfo(path);
|
||||
if (fileInfo.exists) {
|
||||
Optional<fs::path> result = adapter->getNativePath(file);
|
||||
if (!result.empty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
StreamError StackedFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
|
||||
{
|
||||
// try to open existing files first
|
||||
for (auto& adapter : adapters_)
|
||||
{
|
||||
const FileInfo fileInfo = adapter->getFileInfo(path);
|
||||
if (fileInfo.exists)
|
||||
{
|
||||
return adapter->open(path, mode, outStream);
|
||||
}
|
||||
}
|
||||
|
||||
// if that doesn't work we attempt to write, try creating it
|
||||
if (mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE)
|
||||
{
|
||||
for (auto& adapter : adapters_)
|
||||
{
|
||||
const StreamError error = adapter->open(path, mode, outStream);
|
||||
if (error == StreamError::SUCCESS)
|
||||
{
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
void StackedFileSystemAdapter::getAllPaths(const fs::path& path, std::vector<PathReference>& outPaths)
|
||||
{
|
||||
for (auto& adapter : adapters_)
|
||||
{
|
||||
adapter->getAllPaths(path, outPaths);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
@@ -28,11 +28,13 @@ 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;
|
||||
Optional<fs::path> getNativePath(const fs::path& file) override;
|
||||
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
|
||||
void getAllPaths(const fs::path &path, std::vector<PathReference> &outPaths) override;
|
||||
using FileSystemAdapter::getAllPaths;
|
||||
|
||||
inline void addAdapter(std::unique_ptr<FileSystemAdapter>&& adapter) {
|
||||
adapters_.push_back(std::move(adapter));
|
||||
|
||||
Reference in New Issue
Block a user