Compare commits
225 Commits
5eb7864928
...
feature/pr
| Author | SHA1 | Date | |
|---|---|---|---|
| eda08e509e | |||
| 1c7f043e6f | |||
|
|
9ae424e968 | ||
|
|
5a111df9ea | ||
|
|
32ccaad00a | ||
|
|
10d9b4c98f | ||
|
|
cc20702249 | ||
|
|
7d6fcc60fc | ||
| 3891c0f8ce | |||
| 7da2f7b7f4 | |||
| bd06118b29 | |||
|
|
4d19752964 | ||
|
|
0e988a4d9e | ||
|
|
a95885880f | ||
|
|
d76e64c062 | ||
|
|
e704c082b7 | ||
|
|
b44d6feb97 | ||
|
|
e91184ec82 | ||
|
|
51092bb4cb | ||
|
|
02e99bbc82 | ||
|
|
ad627b7c70 | ||
|
|
4a9a60c7f5 | ||
|
|
d8b03893b3 | ||
|
|
7939d458b3 | ||
|
|
b5546067a8 | ||
| 9e572061da | |||
| d0be5f7739 | |||
| e6556c6f90 | |||
| addae96c91 | |||
| cf860392a5 | |||
| 8ad34427f3 | |||
| 59780ed6de | |||
| 888b0a16f7 | |||
| d29a025ec5 | |||
| 40c0d888e6 | |||
| bf53622b19 | |||
| 6090d5fc74 | |||
| a1d7a63aba | |||
| 1d32530be7 | |||
| 973b62a348 | |||
| 86f3790ce1 | |||
| 1cbd435fbf | |||
| 573c431dbd | |||
| 36a908ab8a | |||
| a956560183 | |||
| 2fc03e4050 | |||
| 94e94f02b6 | |||
| 7284d3d15f | |||
| 2b368c1d4f | |||
| 9c4765dbaf | |||
| 05bc3d5147 | |||
| 232a01eb28 | |||
| c9c4eff130 | |||
| 8a9df15dd0 | |||
| 465a97ded5 | |||
| b91eb34789 | |||
| 061c58ef41 | |||
| 17bd408d3c | |||
| 45623e5273 | |||
| 48fd006819 | |||
| d7f968db3a | |||
| e35f5a35f8 | |||
|
|
91d53805b5 | ||
|
|
8bad5e4346 | ||
|
|
b007768790 | ||
|
|
f6776d233d | ||
| 719505ac05 | |||
| 6dcd95b9f3 | |||
|
|
a64bfde6af | ||
| 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
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Patrick Wuttke
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
33
LibConf
33
LibConf
@@ -1,33 +0,0 @@
|
||||
|
||||
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/util/os.cpp
|
||||
source/mijin/types/name.cpp
|
||||
source/mijin/virtual_filesystem/filesystem.cpp
|
||||
source/mijin/virtual_filesystem/stacked.cpp
|
||||
""")
|
||||
|
||||
lib_libbacktrace = env.Cook('libbacktrace')
|
||||
|
||||
lib_mijin = env.UnityStaticLibrary(
|
||||
target = env['LIB_DIR'] + '/mijin',
|
||||
source = mijin_sources,
|
||||
dependencies = [lib_libbacktrace]
|
||||
)
|
||||
|
||||
LIB_CONFIG = {
|
||||
'CPPPATH': [env.Dir('source')],
|
||||
'CPPDEFINES': [],
|
||||
'DEPENDENCIES': [lib_mijin]
|
||||
}
|
||||
|
||||
if env['BUILD_TYPE'] == 'debug':
|
||||
LIB_CONFIG['CPPDEFINES'].extend(['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1'])
|
||||
|
||||
Return('LIB_CONFIG')
|
||||
59
SModule
Normal file
59
SModule
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
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/memory.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]
|
||||
}
|
||||
}
|
||||
50
source/mijin/async/boxed_signal.hpp
Normal file
50
source/mijin/async/boxed_signal.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)
|
||||
#define MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED 1
|
||||
|
||||
#include "./signal.hpp"
|
||||
#include "../container/boxed_object.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
|
||||
template<template<typename> typename TAllocator, typename... TArgs>
|
||||
class BaseBoxedSignal : private BoxedObject<BaseSignal<TAllocator, TArgs...>>
|
||||
{
|
||||
private:
|
||||
using base_t = BoxedObject<BaseSignal<TAllocator, TArgs...>>;
|
||||
public:
|
||||
using base_t::construct;
|
||||
using base_t::destroy;
|
||||
using base_t::moveTo;
|
||||
|
||||
MIJIN_BOXED_PROXY_FUNC(connect)
|
||||
MIJIN_BOXED_PROXY_FUNC(disconnect)
|
||||
MIJIN_BOXED_PROXY_FUNC(emit)
|
||||
};
|
||||
|
||||
template<typename... TArgs>
|
||||
using BoxedSignal = BaseBoxedSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)
|
||||
@@ -26,191 +26,15 @@ namespace mijin
|
||||
|
||||
namespace impl
|
||||
{
|
||||
thread_local TaskLoop::StoredTask* gCurrentTask = nullptr;
|
||||
thread_local std::shared_ptr<TaskSharedState> gCurrentTaskState;
|
||||
}
|
||||
|
||||
//
|
||||
// internal functions
|
||||
//
|
||||
|
||||
void MultiThreadedTaskLoop::managerThread(std::stop_token stopToken) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
setCurrentThreadName("Task Manager");
|
||||
|
||||
while (!stopToken.stop_requested())
|
||||
{
|
||||
// first clear out any parked tasks that are actually finished
|
||||
auto it = std::remove_if(parkedTasks_.begin(), parkedTasks_.end(), [](StoredTask& task) {
|
||||
return !task.task || task.task->status() == TaskStatus::FINISHED;
|
||||
});
|
||||
parkedTasks_.erase(it, parkedTasks_.end());
|
||||
|
||||
// then try to push any task from the buffer into the queue, if possible
|
||||
for (auto it = parkedTasks_.begin(); it != parkedTasks_.end();)
|
||||
{
|
||||
if (!it->task->canResume())
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (readyTasks_.tryPushMaybeMove(*it)) {
|
||||
it = parkedTasks_.erase(it);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// then clear the incoming task queue
|
||||
while (true)
|
||||
{
|
||||
std::optional<StoredTask> task = queuedTasks_.tryPop();
|
||||
if (!task.has_value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// try to directly move it into the next queue
|
||||
if (readyTasks_.tryPushMaybeMove(*task)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise park it
|
||||
parkedTasks_.push_back(std::move(*task));
|
||||
}
|
||||
|
||||
// next collect tasks returning from the worker threads
|
||||
while (true)
|
||||
{
|
||||
std::optional<StoredTask> task = returningTasks_.tryPop();
|
||||
if (!task.has_value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (task->task == nullptr || task->task->status() == TaskStatus::FINISHED) {
|
||||
continue; // task has been transferred or finished
|
||||
}
|
||||
|
||||
if (task->task->canResume() && readyTasks_.tryPushMaybeMove(*task)) {
|
||||
continue; // instantly resume, no questions asked
|
||||
}
|
||||
|
||||
// otherwise park it for future processing
|
||||
parkedTasks_.push_back(std::move(*task));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::workerThread(std::stop_token stopToken, std::size_t workerId) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
currentLoopStorage() = this; // forever (on this thread)
|
||||
|
||||
std::array<char, 16> threadName;
|
||||
(void) std::snprintf(threadName.data(), 16, "Task Worker %lu", static_cast<unsigned long>(workerId));
|
||||
setCurrentThreadName(threadName.data());
|
||||
|
||||
while (!stopToken.stop_requested())
|
||||
{
|
||||
// try to fetch a task to run
|
||||
std::optional<StoredTask> task = readyTasks_.tryPop();
|
||||
if (!task.has_value())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// run it
|
||||
impl::gCurrentTask = &*task;
|
||||
tickTask(*task);
|
||||
impl::gCurrentTask = nullptr;
|
||||
|
||||
// and give it back
|
||||
returningTasks_.push(std::move(*task));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
|
||||
{
|
||||
assertCorrectThread();
|
||||
|
||||
if (&otherLoop == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
MIJIN_ASSERT_FATAL(currentTask_ != tasks_.end(), "Trying to call transferCurrentTask() while not running a task!");
|
||||
|
||||
// now start the transfer, first disown the task
|
||||
StoredTask storedTask = std::move(*currentTask_);
|
||||
currentTask_->task = nullptr; // just to be sure
|
||||
|
||||
// then send it over to the other loop
|
||||
otherLoop.addStoredTask(std::move(storedTask));
|
||||
}
|
||||
|
||||
void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
|
||||
{
|
||||
storedTask.task->setLoop(this);
|
||||
if (threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id())
|
||||
{
|
||||
// same thread, just copy it over
|
||||
if (currentLoopStorage() != nullptr) {
|
||||
// currently running, can't append to tasks_ directly
|
||||
newTasks_.push_back(std::move(storedTask));
|
||||
}
|
||||
else {
|
||||
tasks_.push_back(std::move(storedTask));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// other thread, better be safe
|
||||
queuedTasks_.push(std::move(storedTask));
|
||||
}
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
|
||||
{
|
||||
if (&otherLoop == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
MIJIN_ASSERT_FATAL(currentTask_ != nullptr, "Trying to call transferCurrentTask() while not running a task!");
|
||||
|
||||
// now start the transfer, first disown the task
|
||||
StoredTask storedTask = std::move(*impl::gCurrentTask);
|
||||
impl::gCurrentTask->task = nullptr; // just to be sure
|
||||
|
||||
// then send it over to the other loop
|
||||
otherLoop.addStoredTask(std::move(storedTask));
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
|
||||
{
|
||||
storedTask.task->setLoop(this);
|
||||
|
||||
// just assume we are not on the manager thread, as that wouldn't make sense
|
||||
queuedTasks_.push(std::move(storedTask));
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::start(std::size_t numWorkerThreads)
|
||||
{
|
||||
managerThread_ = std::jthread([this](std::stop_token stopToken) { managerThread(std::move(stopToken)); });
|
||||
workerThreads_.reserve(numWorkerThreads);
|
||||
for (std::size_t workerId = 0; workerId < numWorkerThreads; ++workerId) {
|
||||
workerThreads_.emplace_back([this, workerId](std::stop_token stopToken) { workerThread(std::move(stopToken), workerId); });
|
||||
}
|
||||
}
|
||||
|
||||
void MultiThreadedTaskLoop::stop()
|
||||
{
|
||||
workerThreads_.clear(); // will also set the stop token
|
||||
managerThread_ = {}; // this too
|
||||
}
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
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)
|
||||
@@ -4,12 +4,15 @@
|
||||
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
|
||||
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
|
||||
|
||||
#include <optional>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include "./signal.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../container/optional.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -25,62 +28,135 @@ namespace mijin
|
||||
//
|
||||
// public types
|
||||
//
|
||||
template<typename TValue>
|
||||
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false>
|
||||
class Future;
|
||||
|
||||
// TODO: add support for mutexes and waiting for futures
|
||||
namespace impl
|
||||
{
|
||||
template<typename TValue>
|
||||
template<typename TValue, bool exceptions>
|
||||
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(); }
|
||||
[[nodiscard]]
|
||||
bool hasValue() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !value.empty();
|
||||
}
|
||||
|
||||
};
|
||||
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<>
|
||||
struct FutureStorage<void>
|
||||
struct FutureStorage<void, false>
|
||||
{
|
||||
bool isSet = false;
|
||||
|
||||
[[nodiscard]]
|
||||
bool hasValue() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return isSet;
|
||||
}
|
||||
void setValue() MIJIN_NOEXCEPT
|
||||
{
|
||||
isSet = true;
|
||||
}
|
||||
void getValue() MIJIN_NOEXCEPT {}
|
||||
};
|
||||
|
||||
#if MIJIN_ENABLE_EXCEPTIONS
|
||||
template<typename TValue>
|
||||
struct FutureStorage<TValue, true>
|
||||
{
|
||||
Optional<TValue> value;
|
||||
std::exception_ptr exception;
|
||||
|
||||
[[nodiscard]]
|
||||
bool hasValue() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (exception) {
|
||||
return true;
|
||||
}
|
||||
return !value.empty();
|
||||
}
|
||||
|
||||
void setException(std::exception_ptr exc) MIJIN_NOEXCEPT
|
||||
{
|
||||
exception = std::move(exc);
|
||||
}
|
||||
void setValue(TValue value_) MIJIN_NOEXCEPT { value = std::move(value_); }
|
||||
[[nodiscard]] TValue& getValue()
|
||||
{
|
||||
if (exception) {
|
||||
std::rethrow_exception(exception);
|
||||
}
|
||||
return value.get();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FutureStorage<void, true>
|
||||
{
|
||||
bool isSet = false;
|
||||
std::exception_ptr exception;
|
||||
|
||||
[[nodiscard]]
|
||||
bool hasValue() const MIJIN_NOEXCEPT
|
||||
{
|
||||
if (exception) {
|
||||
return true;
|
||||
}
|
||||
return isSet;
|
||||
}
|
||||
void setException(std::exception_ptr exc) MIJIN_NOEXCEPT
|
||||
{
|
||||
exception = std::move(exc);
|
||||
}
|
||||
void setValue() MIJIN_NOEXCEPT
|
||||
{
|
||||
isSet = true;
|
||||
}
|
||||
void getValue()
|
||||
{
|
||||
if (exception) {
|
||||
std::rethrow_exception(exception);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
} // namespace impl
|
||||
|
||||
template<typename TValue>
|
||||
template<typename TValue, template<typename> typename TAllocator, bool exceptions>
|
||||
class Future
|
||||
{
|
||||
private:
|
||||
impl::FutureStorage<TValue> value_;
|
||||
bool isSet_ = false;
|
||||
impl::FutureStorage<TValue, exceptions> value_;
|
||||
public:
|
||||
Future() = default;
|
||||
Future(const Future&) = delete;
|
||||
Future(Future&&) noexcept = default;
|
||||
Future(Future&&) MIJIN_NOEXCEPT = default;
|
||||
explicit Future(TAllocator<void> allocator) : sigSet(std::move(allocator)) {}
|
||||
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_IF(!exceptions)
|
||||
{
|
||||
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
|
||||
if constexpr(std::is_same_v<TValue, void>) {
|
||||
MIJIN_ASSERT(ready(), "Attempting to get from future that is not ready.");
|
||||
if constexpr(std::is_same_v<TValue, void>)
|
||||
{
|
||||
value_.getValue(); // in case of exceptions
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -88,10 +164,12 @@ public: // access
|
||||
}
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr decltype(auto) get() const noexcept
|
||||
constexpr decltype(auto) get() const MIJIN_NOEXCEPT_IF(!exceptions)
|
||||
{
|
||||
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
|
||||
if constexpr(std::is_same_v<TValue, void>) {
|
||||
MIJIN_ASSERT(ready(), "Attempting to get from future that is not ready.");
|
||||
if constexpr(std::is_same_v<TValue, void>)
|
||||
{
|
||||
value_.getValue(); // in case of exceptions
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -99,24 +177,24 @@ public: // access
|
||||
}
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr bool ready() const noexcept
|
||||
constexpr bool ready() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return isSet_;
|
||||
return value_.hasValue();
|
||||
}
|
||||
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!");
|
||||
MIJIN_ASSERT(!ready(), "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;
|
||||
if constexpr (std::is_same_v<TValue, void>) {
|
||||
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
|
||||
if constexpr (std::is_same_v<TValue, void>)
|
||||
{
|
||||
value_.setValue();
|
||||
sigSet.emit();
|
||||
}
|
||||
else {
|
||||
@@ -124,17 +202,79 @@ public: // modification
|
||||
MIJIN_ERROR("Attempting to call set(void) on future with value.");
|
||||
}
|
||||
}
|
||||
constexpr void setException(std::exception_ptr exception) requires (exceptions)
|
||||
{
|
||||
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
|
||||
if constexpr (exceptions)
|
||||
{
|
||||
value_.setException(std::move(exception));
|
||||
}
|
||||
}
|
||||
public: // signals
|
||||
Signal<> sigSet;
|
||||
BaseSignal<TAllocator> sigSet;
|
||||
};
|
||||
|
||||
template<typename TValue>
|
||||
using FuturePtr = std::shared_ptr<Future<TValue>>;
|
||||
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false>
|
||||
using FuturePtr = std::shared_ptr<Future<TValue, TAllocator, exceptions>>;
|
||||
|
||||
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
using ExceptFuture = Future<TValue, TAllocator, true>;
|
||||
|
||||
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
using ExceptFuturePtr = std::shared_ptr<Future<TValue, TAllocator, true>>;
|
||||
|
||||
template<typename T>
|
||||
struct is_future : std::false_type {};
|
||||
|
||||
template<typename TValue, template<typename> typename TAllocator, bool exceptions>
|
||||
struct is_future<Future<TValue, TAllocator, exceptions>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
inline constexpr bool is_future_t = is_future<T>::value;
|
||||
|
||||
template<typename T>
|
||||
concept FutureType = is_future<T>::value;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<typename... TResult>
|
||||
struct MultiFutureHelper
|
||||
{
|
||||
template<std::size_t... indices>
|
||||
static bool allReady(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
|
||||
{
|
||||
return (std::get<indices>(futures)->ready() && ...);
|
||||
}
|
||||
|
||||
template<std::size_t... indices>
|
||||
static std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::make_tuple(std::move(std::get<indices>(futures)->get())...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
constexpr FuturePtr<T> makeSharedFuture(TAllocator<Future<T>> allocator = {}) MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::allocate_shared<Future<T>>(std::move(allocator));
|
||||
}
|
||||
|
||||
template<typename... TResult>
|
||||
constexpr bool allReady(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
|
||||
{
|
||||
return impl::MultiFutureHelper<TResult...>::allReady(futures, std::index_sequence_for<TResult...>());
|
||||
}
|
||||
|
||||
template<typename... TResult>
|
||||
constexpr std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
|
||||
{
|
||||
return impl::MultiFutureHelper<TResult...>::getAll(futures, std::index_sequence_for<TResult...>());
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/bitarray.hpp"
|
||||
|
||||
namespace mijin
|
||||
@@ -25,21 +26,76 @@ 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>>;
|
||||
static constexpr std::size_t BUFFER_SIZE = bufferSize;
|
||||
private:
|
||||
std::array<TMessage, bufferSize> messages;
|
||||
mijin::BitArray<bufferSize, true> messageReady;
|
||||
std::atomic_uint writePos = 0;
|
||||
std::atomic_uint readPos = 0;
|
||||
std::array<TMessage, bufferSize> messages_;
|
||||
mijin::BitArray<bufferSize, true> messageReady_;
|
||||
std::atomic_uint writePos_ = 0;
|
||||
std::atomic_uint readPos_ = 0;
|
||||
public:
|
||||
MessageQueue() = default;
|
||||
MessageQueue(const MessageQueue&) = delete;
|
||||
MessageQueue(MessageQueue&&) noexcept = delete;
|
||||
MessageQueue(MessageQueue&&) = delete;
|
||||
explicit MessageQueue(const std::array<TMessage, bufferSize>& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<TMessage>)
|
||||
: messages_(messages) {}
|
||||
explicit MessageQueue(std::array<TMessage, bufferSize>&& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TMessage>)
|
||||
: messages_(std::move(messages)) {}
|
||||
|
||||
MessageQueue& operator=(const MessageQueue&) = delete;
|
||||
MessageQueue& operator=(MessageQueue&&) noexcept = delete;
|
||||
MessageQueue& operator=(MessageQueue&&) = delete;
|
||||
|
||||
[[nodiscard]] bool tryPushMaybeMove(TMessage& message);
|
||||
[[nodiscard]] bool tryPush(TMessage message) {
|
||||
@@ -48,6 +104,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>
|
||||
@@ -64,22 +123,22 @@ struct TaskMessageQueue
|
||||
template<typename TMessage, std::size_t bufferSize>
|
||||
bool MessageQueue<TMessage, bufferSize>::tryPushMaybeMove(TMessage& message)
|
||||
{
|
||||
unsigned oldWritePos = writePos.load(std::memory_order_relaxed);
|
||||
unsigned oldWritePos = writePos_.load(std::memory_order_relaxed);
|
||||
unsigned newWritePos = 0;
|
||||
do
|
||||
{
|
||||
newWritePos = (oldWritePos + 1) % bufferSize;
|
||||
if (newWritePos == readPos) {
|
||||
if (newWritePos == readPos_) {
|
||||
return false;
|
||||
}
|
||||
} while (!writePos.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
|
||||
} while (!writePos_.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
|
||||
|
||||
while (messageReady.get(oldWritePos)) {
|
||||
while (messageReady_.get(oldWritePos)) {
|
||||
std::this_thread::yield(); // someone is still reading, wait...
|
||||
}
|
||||
|
||||
messages[oldWritePos] = std::move(message);
|
||||
messageReady.set(oldWritePos, true);
|
||||
messages_[oldWritePos] = std::move(message);
|
||||
messageReady_.set(oldWritePos, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -95,22 +154,22 @@ void MessageQueue<TMessage, bufferSize>::push(TMessage message)
|
||||
template<typename TMessage, std::size_t bufferSize>
|
||||
std::optional<TMessage> MessageQueue<TMessage, bufferSize>::tryPop()
|
||||
{
|
||||
unsigned oldReadPos = readPos.load(std::memory_order_relaxed);
|
||||
unsigned oldReadPos = readPos_.load(std::memory_order_relaxed);
|
||||
unsigned newReadPos = 0;
|
||||
do
|
||||
{
|
||||
if (oldReadPos == writePos) {
|
||||
if (oldReadPos == writePos_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
newReadPos = (oldReadPos + 1) % bufferSize;
|
||||
} while (!readPos.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed));
|
||||
} while (!readPos_.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed));
|
||||
|
||||
while (!messageReady.get(oldReadPos)) {
|
||||
while (!messageReady_.get(oldReadPos)) {
|
||||
std::this_thread::yield(); // no harm in busy-waiting here, should be fast
|
||||
};
|
||||
|
||||
TMessage message = std::move(messages[oldReadPos]);
|
||||
messageReady.set(oldReadPos, false);
|
||||
TMessage message = std::move(messages_[oldReadPos]);
|
||||
messageReady_.set(oldReadPos, false);
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,18 +24,21 @@ 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
|
||||
//
|
||||
|
||||
MIJIN_DEFINE_FLAG(Oneshot);
|
||||
|
||||
template<typename... TArgs>
|
||||
class Signal
|
||||
template<template<typename> typename TAllocator, typename... TArgs>
|
||||
class BaseSignal
|
||||
{
|
||||
public:
|
||||
using handler_t = std::function<void(TArgs...)>;
|
||||
using token_t = std::uint32_t;
|
||||
using handler_t = std::function<void(TArgs...)>; // TODO: write a custom function wrapper with allocator support
|
||||
using token_t = signal_token_t;
|
||||
private:
|
||||
struct RegisteredHandler
|
||||
{
|
||||
@@ -42,34 +47,41 @@ private:
|
||||
token_t token;
|
||||
Oneshot oneshot = Oneshot::NO;
|
||||
};
|
||||
using handler_vector_t = std::vector<RegisteredHandler>;
|
||||
using handler_vector_t = std::vector<RegisteredHandler, TAllocator<RegisteredHandler>>;
|
||||
private:
|
||||
handler_vector_t handlers_;
|
||||
token_t nextToken = 1;
|
||||
std::mutex handlersMutex_;
|
||||
public:
|
||||
Signal() = default;
|
||||
Signal(const Signal&) = delete;
|
||||
Signal(Signal&&) noexcept = default;
|
||||
explicit BaseSignal(TAllocator<void> allocator = {}) : handlers_(TAllocator<RegisteredHandler>(std::move(allocator))) {}
|
||||
BaseSignal(const BaseSignal&) = delete;
|
||||
BaseSignal(BaseSignal&&) MIJIN_NOEXCEPT = default;
|
||||
public:
|
||||
Signal& operator=(const Signal&) = delete;
|
||||
Signal& operator=(Signal&&) noexcept = default;
|
||||
BaseSignal& operator=(const BaseSignal&) = delete;
|
||||
BaseSignal& operator=(BaseSignal&&) 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;
|
||||
template<typename TObject, typename TWeak = void>
|
||||
inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...) const, 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;
|
||||
};
|
||||
|
||||
template<typename... TArgs>
|
||||
using Signal = BaseSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename... TArgs>
|
||||
template<template<typename> typename TAllocator, 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 BaseSignal<TAllocator, TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -84,9 +96,9 @@ inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::we
|
||||
return nextToken++;
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
template<template<typename> typename TAllocator, 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 BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -104,8 +116,28 @@ inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)
|
||||
return nextToken++;
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
inline void Signal<TArgs...>::disconnect(token_t token) noexcept
|
||||
template<template<typename> typename TAllocator, typename... TArgs>
|
||||
template<typename TObject, typename TWeak>
|
||||
inline auto BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
auto callable = [object = &object, handler](TArgs... args)
|
||||
{
|
||||
std::invoke(handler, object, std::forward<TArgs>(args)...);
|
||||
};
|
||||
handlers_.push_back({
|
||||
.callable = std::move(callable),
|
||||
.referenced = std::move(referenced),
|
||||
.token = nextToken,
|
||||
.oneshot = oneshot
|
||||
});
|
||||
|
||||
return nextToken++;
|
||||
}
|
||||
|
||||
template<template<typename> typename TAllocator, typename... TArgs>
|
||||
inline void BaseSignal<TAllocator, TArgs...>::disconnect(token_t token) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -116,8 +148,9 @@ inline void Signal<TArgs...>::disconnect(token_t token) noexcept
|
||||
handlers_.erase(it, handlers_.end());
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
inline void Signal<TArgs...>::emit(TArgs&&... args) noexcept
|
||||
template<template<typename> typename TAllocator, typename... TArgs>
|
||||
template<typename... TArgs2>
|
||||
inline void BaseSignal<TAllocator, TArgs...>::emit(TArgs2&&... args) MIJIN_NOEXCEPT
|
||||
{
|
||||
std::lock_guard lock(handlersMutex_);
|
||||
|
||||
@@ -132,7 +165,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
|
||||
@@ -16,8 +16,26 @@ namespace mijin
|
||||
//
|
||||
|
||||
#if !defined(MIJIN_BOXED_OBJECT_DEBUG)
|
||||
#if defined(MIJIN_DEBUG)
|
||||
#define MIJIN_BOXED_OBJECT_DEBUG MIJIN_DEBUG
|
||||
#else
|
||||
#define MIJIN_BOXED_OBJECT_DEBUG 0
|
||||
#endif
|
||||
#endif // !defined(MIJIN_BOXED_OBJECT_DEBUG)
|
||||
|
||||
#define MIJIN_BOXED_PROXY_FUNC(funcname) \
|
||||
template<typename... TFuncArgs> \
|
||||
decltype(auto) funcname(TFuncArgs&&... args) \
|
||||
{ \
|
||||
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
|
||||
}
|
||||
|
||||
#define MIJIN_BOXED_PROXY_FUNC_CONST(funcname) \
|
||||
template<typename... TFuncArgs> \
|
||||
decltype(auto) funcname(TFuncArgs&&... args) const \
|
||||
{ \
|
||||
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
|
||||
}
|
||||
|
||||
//
|
||||
// public constants
|
||||
@@ -39,8 +57,8 @@ private:
|
||||
bool constructed = false;
|
||||
#endif
|
||||
public:
|
||||
BoxedObject() noexcept : placeholder_() {};
|
||||
explicit BoxedObject(T object)
|
||||
constexpr BoxedObject() MIJIN_NOEXCEPT : placeholder_() {};
|
||||
explicit constexpr BoxedObject(T object) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
: constructed(true)
|
||||
#endif
|
||||
@@ -51,68 +69,68 @@ public:
|
||||
BoxedObject(BoxedObject&&) = delete;
|
||||
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
~BoxedObject()
|
||||
constexpr ~BoxedObject() noexcept
|
||||
{
|
||||
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
|
||||
|
||||
BoxedObject& operator=(const BoxedObject&) = delete;
|
||||
BoxedObject& operator=(BoxedObject&&) = delete;
|
||||
|
||||
T& operator*() noexcept { return get(); }
|
||||
const T& operator*() const noexcept { return get(); }
|
||||
T* operator->() noexcept { return &get(); }
|
||||
const T* operator->() const noexcept { return &get(); }
|
||||
constexpr T& operator*() noexcept { return get(); }
|
||||
constexpr const T& operator*() const noexcept { return get(); }
|
||||
constexpr T* operator->() noexcept { return &get(); }
|
||||
constexpr const T* operator->() const noexcept { return &get(); }
|
||||
|
||||
template<typename... TArgs>
|
||||
void construct(TArgs&&... args)
|
||||
constexpr void construct(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
|
||||
{
|
||||
#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)...);
|
||||
}
|
||||
|
||||
void destroy()
|
||||
constexpr void destroy() MIJIN_NOEXCEPT
|
||||
{
|
||||
#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_);
|
||||
}
|
||||
|
||||
void copyTo(BoxedObject& other) const
|
||||
constexpr void copyTo(BoxedObject& other) const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
|
||||
{
|
||||
#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_);
|
||||
}
|
||||
|
||||
void moveTo(BoxedObject& other)
|
||||
constexpr void moveTo(BoxedObject& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
#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();
|
||||
}
|
||||
|
||||
[[nodiscard]] T& get()
|
||||
[[nodiscard]] constexpr T& get() MIJIN_NOEXCEPT
|
||||
{
|
||||
#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_;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& get() const
|
||||
[[nodiscard]] constexpr const T& get() const MIJIN_NOEXCEPT
|
||||
{
|
||||
#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_;
|
||||
}
|
||||
@@ -131,38 +149,38 @@ private:
|
||||
BoxedObject<T>* end_ = nullptr;
|
||||
#endif
|
||||
public:
|
||||
BoxedObjectIterator() = default;
|
||||
explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* end)
|
||||
BoxedObjectIterator() noexcept = default;
|
||||
explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* end) MIJIN_NOEXCEPT
|
||||
: box_(box)
|
||||
#if MIJIN_CHECKED_ITERATORS
|
||||
, start_(start), end_(end)
|
||||
#endif
|
||||
{}
|
||||
BoxedObjectIterator(const BoxedObjectIterator&) = default;
|
||||
BoxedObjectIterator(BoxedObjectIterator&&) noexcept = default;
|
||||
constexpr BoxedObjectIterator(const BoxedObjectIterator&) noexcept = default;
|
||||
constexpr BoxedObjectIterator(BoxedObjectIterator&&) noexcept = default;
|
||||
|
||||
BoxedObjectIterator& operator=(const BoxedObjectIterator&) = default;
|
||||
BoxedObjectIterator& operator=(BoxedObjectIterator&&) noexcept = default;
|
||||
BoxedObjectIterator& operator+=(difference_type diff);
|
||||
BoxedObjectIterator& operator-=(difference_type diff) { return (*this += -diff); }
|
||||
constexpr BoxedObjectIterator& operator=(const BoxedObjectIterator&) noexcept = default;
|
||||
constexpr BoxedObjectIterator& operator=(BoxedObjectIterator&&) noexcept = default;
|
||||
constexpr BoxedObjectIterator& operator+=(difference_type diff) MIJIN_NOEXCEPT;
|
||||
constexpr BoxedObjectIterator& operator-=(difference_type diff) MIJIN_NOEXCEPT { return (*this += -diff); }
|
||||
|
||||
constexpr auto operator<=>(const BoxedObjectIterator& other) const noexcept = default;
|
||||
|
||||
[[nodiscard]] T& operator*() const;
|
||||
[[nodiscard]] T* operator->() const;
|
||||
BoxedObjectIterator& operator++();
|
||||
BoxedObjectIterator operator++(int);
|
||||
BoxedObjectIterator& operator--();
|
||||
BoxedObjectIterator operator--(int);
|
||||
[[nodiscard]] constexpr T& operator*() const MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] constexpr T* operator->() const MIJIN_NOEXCEPT;
|
||||
constexpr BoxedObjectIterator& operator++() MIJIN_NOEXCEPT;
|
||||
constexpr BoxedObjectIterator operator++(int) MIJIN_NOEXCEPT;
|
||||
constexpr BoxedObjectIterator& operator--() MIJIN_NOEXCEPT;
|
||||
constexpr BoxedObjectIterator operator--(int) MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]] difference_type operator-(const BoxedObjectIterator& other) const { return box_ - other.box_; }
|
||||
[[nodiscard]] BoxedObjectIterator operator+(difference_type diff) const;
|
||||
[[nodiscard]] BoxedObjectIterator operator-(difference_type diff) const { return (*this + -diff); }
|
||||
[[nodiscard]] constexpr difference_type operator-(const BoxedObjectIterator& other) const MIJIN_NOEXCEPT { return box_ - other.box_; }
|
||||
[[nodiscard]] constexpr BoxedObjectIterator operator+(difference_type diff) const MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] constexpr BoxedObjectIterator operator-(difference_type diff) const MIJIN_NOEXCEPT { return (*this + -diff); }
|
||||
|
||||
[[nodiscard]] T& operator[](difference_type diff) const { return *(*this + diff); }
|
||||
[[nodiscard]] T& operator[](difference_type diff) const MIJIN_NOEXCEPT { return *(*this + diff); }
|
||||
};
|
||||
template<typename T>
|
||||
inline BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) {
|
||||
constexpr BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) MIJIN_NOEXCEPT {
|
||||
return iter + diff;
|
||||
}
|
||||
static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
|
||||
@@ -172,7 +190,7 @@ static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
|
||||
//
|
||||
|
||||
template<typename T>
|
||||
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff)
|
||||
constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_CHECKED_ITERATORS
|
||||
MIJIN_ASSERT(diff <= (end_ - box_) && diff >= (box_ - start_), "BoxedObjectIterator::operator+=(): Attempt to iterate out of range.");
|
||||
@@ -182,7 +200,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff)
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& BoxedObjectIterator<T>::operator*() const
|
||||
constexpr T& BoxedObjectIterator<T>::operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_CHECKED_ITERATORS
|
||||
MIJIN_ASSERT(box_ >= start_ && box_ < end_, "BoxedObjectIterator::operator->(): Attempt to dereference out-of-range iterator.");
|
||||
@@ -191,7 +209,7 @@ T& BoxedObjectIterator<T>::operator*() const
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++()
|
||||
constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++() MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_CHECKED_ITERATORS
|
||||
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end.");
|
||||
@@ -200,7 +218,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++()
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int)
|
||||
constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_CHECKED_ITERATORS
|
||||
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(int): Attempt to iterator past the end.");
|
||||
@@ -211,7 +229,7 @@ BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int)
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--()
|
||||
constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--() MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_CHECKED_ITERATORS
|
||||
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start.");
|
||||
@@ -220,7 +238,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--()
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int)
|
||||
constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_CHECKED_ITERATORS
|
||||
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(int): Attempt to iterator past start.");
|
||||
@@ -231,7 +249,7 @@ BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int)
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const
|
||||
constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const MIJIN_NOEXCEPT
|
||||
{
|
||||
BoxedObjectIterator copy(*this);
|
||||
copy += diff;
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
196
source/mijin/container/memory_view.hpp
Normal file
196
source/mijin/container/memory_view.hpp
Normal file
@@ -0,0 +1,196 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)
|
||||
#define MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public defines
|
||||
//
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename T>
|
||||
concept MemoryViewable = requires(const T& object)
|
||||
{
|
||||
{ object.data() } -> std::convertible_to<const void*>;
|
||||
{ object.byteSize() } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept RWMemoryViewable = MemoryViewable<T> && requires(T& object)
|
||||
{
|
||||
{ object.data() } -> std::convertible_to<void*>;
|
||||
};
|
||||
|
||||
template<typename TConcrete>
|
||||
class MixinMemoryView
|
||||
{
|
||||
public:
|
||||
static constexpr bool WRITABLE = requires(TConcrete& object) { { object.data() } -> std::convertible_to<void*>; };
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]]
|
||||
auto makeSpan();
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]]
|
||||
auto makeSpan() const;
|
||||
|
||||
template<typename TChar = char, typename TTraits = std::char_traits<TChar>>
|
||||
[[nodiscard]]
|
||||
std::basic_string_view<TChar, TTraits> makeStringView() const ;
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]]
|
||||
auto& dataAt(std::size_t offset) MIJIN_NOEXCEPT;
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]]
|
||||
auto& dataAt(std::size_t offset) const MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]]
|
||||
auto bytes() MIJIN_NOEXCEPT
|
||||
{
|
||||
using return_t = mijin::copy_const_t<std::remove_pointer_t<decltype(static_cast<TConcrete*>(this)->data())>, std::byte>;
|
||||
return static_cast<return_t*>(static_cast<TConcrete*>(this)->data());
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto bytes() const MIJIN_NOEXCEPT
|
||||
{
|
||||
using return_t = mijin::copy_const_t<std::remove_pointer_t<decltype(static_cast<const TConcrete*>(this)->data())>, std::byte>;
|
||||
return static_cast<return_t*>(static_cast<const TConcrete*>(this)->data());
|
||||
}
|
||||
private:
|
||||
std::size_t byteSizeImpl() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return static_cast<const TConcrete*>(this)->byteSize();
|
||||
}
|
||||
};
|
||||
|
||||
class MemoryView : public MixinMemoryView<MemoryView>
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
private:
|
||||
void* data_ = nullptr;
|
||||
std::size_t byteSize_ = 0;
|
||||
public:
|
||||
MemoryView() noexcept = default;
|
||||
MemoryView(const MemoryView&) = default;
|
||||
MemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {}
|
||||
template<typename T> requires(!std::is_const_v<T>)
|
||||
MemoryView(std::span<T> span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {}
|
||||
template<RWMemoryViewable T>
|
||||
MemoryView(T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {}
|
||||
|
||||
MemoryView& operator=(const MemoryView&) = default;
|
||||
|
||||
[[nodiscard]]
|
||||
void* data() const MIJIN_NOEXCEPT { return data_; }
|
||||
|
||||
[[nodiscard]]
|
||||
size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; }
|
||||
};
|
||||
|
||||
class ConstMemoryView : public MixinMemoryView<ConstMemoryView>
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
private:
|
||||
const void* data_;
|
||||
std::size_t byteSize_;
|
||||
public:
|
||||
ConstMemoryView() noexcept = default;
|
||||
ConstMemoryView(const ConstMemoryView&) = default;
|
||||
ConstMemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {}
|
||||
template<typename T>
|
||||
ConstMemoryView(std::span<T> span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {}
|
||||
template<MemoryViewable T>
|
||||
ConstMemoryView(const T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {}
|
||||
|
||||
ConstMemoryView& operator=(const ConstMemoryView& other) MIJIN_NOEXCEPT = default;
|
||||
|
||||
[[nodiscard]]
|
||||
const void* data() const MIJIN_NOEXCEPT { return data_; }
|
||||
|
||||
[[nodiscard]]
|
||||
size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; }
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<typename TConcrete>
|
||||
template<typename T>
|
||||
auto MixinMemoryView<TConcrete>::makeSpan()
|
||||
{
|
||||
MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
|
||||
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
|
||||
return std::span<return_t>{
|
||||
std::bit_cast<return_t*>(bytes()),
|
||||
std::bit_cast<return_t*>(bytes() + byteSizeImpl())
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TConcrete>
|
||||
template<typename T>
|
||||
auto MixinMemoryView<TConcrete>::makeSpan() const
|
||||
{
|
||||
MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
|
||||
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
|
||||
return std::span<return_t>{
|
||||
std::bit_cast<return_t*>(bytes()),
|
||||
std::bit_cast<return_t*>(bytes() + byteSizeImpl())
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TConcrete>
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> MixinMemoryView<TConcrete>::makeStringView() const
|
||||
{
|
||||
MIJIN_ASSERT(byteSizeImpl() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type.");
|
||||
return {std::bit_cast<const TChar*>(bytes()), byteSizeImpl() / sizeof(TChar)};
|
||||
}
|
||||
|
||||
template<typename TConcrete>
|
||||
template<typename T>
|
||||
auto& MixinMemoryView<TConcrete>::dataAt(std::size_t offset) MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
|
||||
MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range.");
|
||||
|
||||
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
|
||||
return *std::bit_cast<return_t*>(bytes().data() + offset);
|
||||
}
|
||||
|
||||
template<typename TConcrete>
|
||||
template<typename T>
|
||||
auto& MixinMemoryView<TConcrete>::dataAt(std::size_t offset) const MIJIN_NOEXCEPT
|
||||
{
|
||||
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
|
||||
MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range.");
|
||||
|
||||
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
|
||||
return *std::bit_cast<return_t*>(bytes().data() + offset);
|
||||
}
|
||||
} // 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,10 +4,15 @@
|
||||
#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 "./memory_view.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -24,19 +29,32 @@ namespace mijin
|
||||
// public types
|
||||
//
|
||||
|
||||
template<typename T>
|
||||
template<typename T, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
class BufferView;
|
||||
|
||||
class TypelessBuffer
|
||||
template<template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
||||
class BaseTypelessBuffer : public MixinMemoryView<BaseTypelessBuffer<TAllocator>>
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
private:
|
||||
std::vector<std::byte> bytes_;
|
||||
std::vector<std::byte, TAllocator<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(); }
|
||||
BaseTypelessBuffer() noexcept = default;
|
||||
BaseTypelessBuffer(const BaseTypelessBuffer&) = default;
|
||||
BaseTypelessBuffer(BaseTypelessBuffer&&) = default;
|
||||
explicit BaseTypelessBuffer(TAllocator<std::byte> allocator) : bytes_(std::move(allocator)) {}
|
||||
|
||||
BaseTypelessBuffer& operator=(const BaseTypelessBuffer&) = default;
|
||||
BaseTypelessBuffer& operator=(BaseTypelessBuffer&&) = default;
|
||||
|
||||
auto operator<=>(const BaseTypelessBuffer&) 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); }
|
||||
|
||||
@@ -44,13 +62,12 @@ public:
|
||||
[[nodiscard]] BufferView<T> makeBufferView() { return BufferView<T>(this); }
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::span<T> makeSpan();
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::span<const T> makeSpan() const;
|
||||
void append(std::span<const T> data);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using TypelessBuffer = BaseTypelessBuffer<>;
|
||||
|
||||
template<typename T, template<typename> typename TAllocator>
|
||||
class BufferView
|
||||
{
|
||||
public:
|
||||
@@ -65,10 +82,10 @@ public:
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
private:
|
||||
class TypelessBuffer* buffer_ = nullptr;
|
||||
class BaseTypelessBuffer<TAllocator>* buffer_ = nullptr;
|
||||
public:
|
||||
BufferView() = default;
|
||||
explicit BufferView(class TypelessBuffer* buffer) : buffer_(buffer) {}
|
||||
explicit BufferView(class BaseTypelessBuffer<TAllocator>* buffer) : buffer_(buffer) {}
|
||||
BufferView(const BufferView&) = default;
|
||||
|
||||
BufferView& operator=(const BufferView&) = default;
|
||||
@@ -82,6 +99,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; }
|
||||
@@ -106,24 +129,12 @@ public:
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<template<typename> typename TAllocator>
|
||||
template<typename T>
|
||||
std::span<T> TypelessBuffer::makeSpan()
|
||||
void BaseTypelessBuffer<TAllocator>::append(std::span<const T> data)
|
||||
{
|
||||
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer 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 T>
|
||||
std::span<const T> TypelessBuffer::makeSpan() const
|
||||
{
|
||||
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer 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())
|
||||
};
|
||||
bytes_.resize(bytes_.size() + data.size_bytes());
|
||||
std::memcpy(bytes_.data() + bytes_.size() - data.size_bytes(), data.data(), data.size_bytes());
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
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)
|
||||
301
source/mijin/container/vector_map.hpp
Normal file
301
source/mijin/container/vector_map.hpp
Normal file
@@ -0,0 +1,301 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED)
|
||||
#define MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED 1
|
||||
|
||||
#include <algorithm>
|
||||
#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 = MIJIN_DEFAULT_ALLOCATOR<TKey>, typename TValueAllocator = MIJIN_DEFAULT_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:
|
||||
explicit VectorMap(TKeyAllocator keyAllocator = {})
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_move_constructible_v<TKeyAllocator> && std::is_nothrow_constructible_v<TValueAllocator, const TKeyAllocator&>))
|
||||
: keys_(std::move(keyAllocator)), values_(TValueAllocator(keys_.get_allocator())) {}
|
||||
VectorMap(TKeyAllocator keyAllocator, TValueAllocator valueAllocator)
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TKeyAllocator> && std::is_nothrow_move_constructible_v<TValueAllocator>)
|
||||
: keys_(std::move(keyAllocator)), values_(std::move(valueAllocator)) {}
|
||||
VectorMap(const VectorMap&) = default;
|
||||
VectorMap(VectorMap&&) = default;
|
||||
|
||||
VectorMap& operator=(const VectorMap&) = default;
|
||||
VectorMap& operator=(VectorMap&&) = default;
|
||||
auto operator<=>(const VectorMap& other) const noexcept = default;
|
||||
|
||||
template<typename TIndex>
|
||||
TValue& operator[](const TIndex& key)
|
||||
{
|
||||
auto it = find(key);
|
||||
if (it != end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return emplace(key, TValue()).first->second;
|
||||
}
|
||||
|
||||
template<typename TIndex>
|
||||
const TValue& operator[](const TIndex& key) const
|
||||
{
|
||||
return at(key);
|
||||
}
|
||||
|
||||
TValue& operator[](TKey&& key)
|
||||
{
|
||||
auto it = find(key);
|
||||
if (it != end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return emplace(std::move(key), TValue()).first->second;
|
||||
}
|
||||
|
||||
[[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);
|
||||
}
|
||||
|
||||
template<typename TSearch>
|
||||
[[nodiscard]]
|
||||
iterator find(const TSearch& 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();
|
||||
}
|
||||
|
||||
template<typename TSearch>
|
||||
[[nodiscard]]
|
||||
const_iterator find(const TSearch& 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();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool contains(const TKey& key) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::ranges::contains(keys_, key);
|
||||
}
|
||||
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,8 +66,10 @@ std::abort()
|
||||
|
||||
// TODO: make ignoreAll work (static variables cannot be used in constexpr functions)
|
||||
#define MIJIN_ASSERT(condition, msg) \
|
||||
if (!static_cast<bool>(condition)) \
|
||||
do \
|
||||
{ \
|
||||
if (!static_cast<bool>(condition)) \
|
||||
{ \
|
||||
/* static bool ignoreAll = false; */ \
|
||||
if (true) /*!ignoreAll */ \
|
||||
{ \
|
||||
@@ -71,18 +90,19 @@ 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(...)
|
||||
#define MIJIN_ERROR(...) ((void)0)
|
||||
#define MIJIN_FATAL(...) std::abort()
|
||||
#define MIJIN_ASSERT(...)
|
||||
#define MIJIN_ASSERT_FATAL(...)
|
||||
#define MIJIN_ASSERT(...) ((void)0)
|
||||
#define MIJIN_ASSERT_FATAL(...) ((void)0)
|
||||
#endif // !MIJIN_DEBUG
|
||||
|
||||
//
|
||||
@@ -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,29 @@
|
||||
|
||||
#include "./stacktrace.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <backtrace.h>
|
||||
#include "../detect.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS != MIJIN_OS_WINDOWS && (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>
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include "../util/winundef.hpp"
|
||||
#pragma comment(lib, "dbghelp")
|
||||
#endif
|
||||
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -22,25 +41,37 @@ namespace
|
||||
// internal types
|
||||
//
|
||||
|
||||
#if MIJIN_USE_LIBBACKTRACE
|
||||
struct BacktraceData
|
||||
{
|
||||
std::optional<std::string> error;
|
||||
std::vector<Stackframe> stackframes;
|
||||
};
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
HANDLE gProcessHandle = nullptr;
|
||||
#endif
|
||||
|
||||
//
|
||||
// internal variables
|
||||
//
|
||||
|
||||
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
std::mutex gDbgHelpMutex;
|
||||
bool gDbgHelpInitCalled = false;
|
||||
#endif
|
||||
|
||||
//
|
||||
// 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 +85,60 @@ void backtraceErrorCallback(void* data, const char* msg, int /* errnum */)
|
||||
btData.error = msg;
|
||||
}
|
||||
|
||||
backtrace_state* gBacktraceState = nullptr;
|
||||
thread_local backtrace_state* gBacktraceState = nullptr;
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
void cleanupDbgHelp() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (!SymCleanup(gProcessHandle))
|
||||
{
|
||||
[[maybe_unused]] const DWORD error = GetLastError();
|
||||
MIJIN_ERROR("Error cleaning up DbgHelp.");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool initDbgHelp() MIJIN_NOEXCEPT
|
||||
{
|
||||
if (gDbgHelpInitCalled)
|
||||
{
|
||||
return gProcessHandle != nullptr; // if init was successful, process handle is not null
|
||||
}
|
||||
gDbgHelpInitCalled = true;
|
||||
|
||||
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
|
||||
|
||||
HANDLE hCurrentProcess = GetCurrentProcess();
|
||||
HANDLE hCopy = nullptr;
|
||||
if (!DuplicateHandle(hCurrentProcess, hCurrentProcess, hCurrentProcess, &hCopy, 0, FALSE, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
[[maybe_unused]] const DWORD error = GetLastError();
|
||||
MIJIN_ERROR("Error duplicating process handle.");
|
||||
return false;
|
||||
}
|
||||
if (!SymInitialize(hCopy, nullptr, true))
|
||||
{
|
||||
[[maybe_unused]] const DWORD error = GetLastError();
|
||||
MIJIN_ERROR("Error initializing DbHelp.");
|
||||
return false;
|
||||
}
|
||||
|
||||
[[maybe_unused]] const int result = std::atexit(&cleanupDbgHelp);
|
||||
MIJIN_ASSERT(result == 0, "Error registering DbgHelp cleanup handler.");
|
||||
|
||||
// only copy in the end so we can still figure out if initialization was successful
|
||||
gProcessHandle = hCopy;
|
||||
return true;
|
||||
}
|
||||
#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 +159,126 @@ Result<Stacktrace> captureStacktrace(unsigned skipFrames) noexcept
|
||||
}
|
||||
|
||||
return Stacktrace(std::move(btData.stackframes));
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
if (!initDbgHelp())
|
||||
{
|
||||
return ResultError("error initializing DbgHelp");
|
||||
}
|
||||
|
||||
const HANDLE hThread = GetCurrentThread();
|
||||
|
||||
CONTEXT context;
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
STACKFRAME64 stackFrame = {
|
||||
.AddrPC = {
|
||||
.Offset = context.Rip,
|
||||
.Mode = AddrModeFlat
|
||||
},
|
||||
.AddrFrame = {
|
||||
.Offset = context.Rbp,
|
||||
.Mode = AddrModeFlat
|
||||
},
|
||||
.AddrStack = {
|
||||
.Offset = context.Rsp,
|
||||
.Mode = AddrModeFlat
|
||||
}
|
||||
};
|
||||
|
||||
++skipFrames; // always skip the first frame (the current function)
|
||||
|
||||
// for symbol info
|
||||
DWORD64 displacement64 = 0;
|
||||
static constexpr std::size_t SYMBOL_BUFFER_SIZE = sizeof(SYMBOL_INFO) + (MAX_SYM_NAME * sizeof(char));
|
||||
std::array<std::byte, SYMBOL_BUFFER_SIZE> symbolBuffer alignas(SYMBOL_INFO);
|
||||
SYMBOL_INFO& symbolInfo = *std::bit_cast<SYMBOL_INFO*>(symbolBuffer.data());
|
||||
symbolInfo.SizeOfStruct = sizeof(SYMBOL_BUFFER_SIZE);
|
||||
symbolInfo.MaxNameLen = MAX_SYM_NAME;
|
||||
|
||||
// for file and line info
|
||||
DWORD displacement = 0;
|
||||
IMAGEHLP_LINE64 line64;
|
||||
line64.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
|
||||
std::vector<Stackframe> stackframes;
|
||||
while (StackWalk64(
|
||||
/* MachineType = */ IMAGE_FILE_MACHINE_AMD64,
|
||||
/* hProcess = */ gProcessHandle,
|
||||
/* hThread = */ hThread,
|
||||
/* StackFrame = */ &stackFrame,
|
||||
/* ContextRecord = */ &context,
|
||||
/* ReadMemoryRoutine = */ nullptr,
|
||||
/* FunctionTableAccessRoutine = */ &SymFunctionTableAccess64,
|
||||
/* GetModuleBaseRoutine = */ &SymGetModuleBase64,
|
||||
/* TranslateAddress = */ nullptr
|
||||
))
|
||||
{
|
||||
if (skipFrames > 0)
|
||||
{
|
||||
--skipFrames;
|
||||
continue;
|
||||
}
|
||||
Stackframe& frame = stackframes.emplace_back();
|
||||
const DWORD64 baseAddress = SymGetModuleBase64(gProcessHandle, stackFrame.AddrPC.Offset);
|
||||
const DWORD64 relativeAddress = stackFrame.AddrPC.Offset - baseAddress;
|
||||
|
||||
frame.address = std::bit_cast<void*>(relativeAddress);
|
||||
|
||||
if (SymFromAddr(gProcessHandle, stackFrame.AddrPC.Offset, &displacement64, &symbolInfo))
|
||||
{
|
||||
frame.function = symbolInfo.Name;
|
||||
}
|
||||
|
||||
if (SymGetLineFromAddr64(gProcessHandle, stackFrame.AddrPC.Offset, &displacement, &line64))
|
||||
{
|
||||
frame.filename = line64.FileName;
|
||||
frame.lineNumber = static_cast<int>(line64.LineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
return Stacktrace(std::move(stackframes));
|
||||
#else // MIJIN_USE_LIBBACKTRACE || (MIJIN_TARGET_OS == MIJIN_OS_WINDOWS)
|
||||
(void) skipFrames;
|
||||
return ResultError("not implemented");
|
||||
#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
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
#define MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED 1
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#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 +47,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 +73,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 +88,118 @@ TStream& operator<<(TStream& stream, const Stacktrace& stacktrace)
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
template<typename TChar>
|
||||
struct std::formatter<mijin::Stackframe, TChar>
|
||||
{
|
||||
using char_t = TChar;
|
||||
|
||||
template<typename TContext>
|
||||
constexpr TContext::iterator parse(TContext& ctx)
|
||||
{
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
|
||||
if (it != end && *it != MIJIN_SMART_QUOTE(char_t, '}'))
|
||||
{
|
||||
throw std::format_error("invalid format");
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename TContext>
|
||||
TContext::iterator format(const mijin::Stackframe& stackframe, TContext& ctx) const
|
||||
{
|
||||
auto it = ctx.out();
|
||||
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "[{}] {}:{} in {}"), stackframe.address, stackframe.filename,
|
||||
stackframe.lineNumber, mijin::demangleCPPIdentifier(stackframe.function.c_str()));
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TChar>
|
||||
struct std::formatter<mijin::Stacktrace, TChar>
|
||||
{
|
||||
using char_t = TChar;
|
||||
|
||||
template<class TContext>
|
||||
constexpr TContext::iterator parse(TContext& ctx)
|
||||
{
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
|
||||
if (it != end && *it != MIJIN_SMART_QUOTE(char_t, '}'))
|
||||
{
|
||||
throw std::format_error("invalid format");
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename TContext>
|
||||
TContext::iterator format(const mijin::Stacktrace& stacktrace, TContext& ctx) const
|
||||
{
|
||||
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
|
||||
auto it = ctx.out();
|
||||
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "[{} frames]"), stacktrace.getFrames().size());
|
||||
for (const auto& [idx, frame] : mijin::enumerate(stacktrace.getFrames()))
|
||||
{
|
||||
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "\n #{:<{}} at {}"), idx, numDigits, frame);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
#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
|
||||
|
||||
@@ -61,6 +61,26 @@ namespace mijin
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_RTTI)
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC
|
||||
#if defined(__GXX_RTTI)
|
||||
#define MIJIN_RTTI 1
|
||||
#else
|
||||
#define MIJIN_RTTI 0
|
||||
#endif
|
||||
#elif MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#define MIJIN_RTTI (__has_feature(cxx_rtti))
|
||||
#elif MIJIN_COMPILER == MIJIN_COMPILER_MSVC
|
||||
#if defined(_CPPRTTI)
|
||||
#define MIJIN_RTTI 1
|
||||
#else
|
||||
#define MIJIN_RTTI 0
|
||||
#endif
|
||||
#else
|
||||
#define MIJIN_RTTI 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
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
|
||||
7
source/mijin/internal/common.hpp
Normal file
7
source/mijin/internal/common.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./config.hpp"
|
||||
#include "./helpers.hpp"
|
||||
#include "./exception.hpp"
|
||||
#include "./version_support.hpp"
|
||||
22
source/mijin/internal/config.hpp
Normal file
22
source/mijin/internal/config.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_INTERNAL_CONFIG_HPP_INCLUDED)
|
||||
#define MIJIN_INTERNAL_CONFIG_HPP_INCLUDED 1
|
||||
|
||||
#define MIJIN_QUOTED_ACTUAL(x) #x
|
||||
#define MIJIN_QUOTED(x) MIJIN_QUOTED_ACTUAL(x)
|
||||
|
||||
#if defined(MIJIN_CONFIG_HEADER)
|
||||
#include MIJIN_QUOTED(MIJIN_CONFIG_HEADER)
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_DEFAULT_ALLOCATOR)
|
||||
#define MIJIN_DEFAULT_ALLOCATOR std::allocator
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_DEFAULT_CHAR_TYPE)
|
||||
#define MIJIN_DEFAULT_CHAR_TYPE char
|
||||
#endif
|
||||
|
||||
#endif // !defined(MIJIN_INTERNAL_CONFIG_HPP_INCLUDED)
|
||||
45
source/mijin/internal/exception.hpp
Normal file
45
source/mijin/internal/exception.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
#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_NOEXCEPT_IF(x)
|
||||
#define MIJIN_THROWS
|
||||
#define MIJIN_CONDITIONAL_NOEXCEPT(...)
|
||||
#else
|
||||
#define MIJIN_NOEXCEPT noexcept
|
||||
#define MIJIN_NOEXCEPT_IF(x) noexcept(x)
|
||||
#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)
|
||||
57
source/mijin/internal/helpers.hpp
Normal file
57
source/mijin/internal/helpers.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_INTERNAL_HELPERS_HPP_INCLUDED)
|
||||
#define MIJIN_INTERNAL_HELPERS_HPP_INCLUDED 1
|
||||
|
||||
#include <type_traits>
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
#define MIJIN_IDENTITY(what) what
|
||||
#define MIJIN_NULLIFY(what)
|
||||
|
||||
#define MIJIN_SMART_QUOTE(chr_type, text) \
|
||||
[]<typename TChar__>(TChar__) consteval \
|
||||
{ \
|
||||
if constexpr (std::is_same_v<TChar__, char>) \
|
||||
{ \
|
||||
return text; \
|
||||
} \
|
||||
else if constexpr (std::is_same_v<TChar__, wchar_t>) \
|
||||
{ \
|
||||
return L ## text; \
|
||||
} \
|
||||
else if constexpr (std::is_same_v<TChar__, char8_t>) \
|
||||
{ \
|
||||
return u8 ## text; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
static_assert(::mijin::always_false_v<TChar__>, "Invalid char type."); \
|
||||
} \
|
||||
}(chr_type())
|
||||
|
||||
#define MIJIN_SMART_STRINGIFY(chr_type, text) MIJIN_SMART_QUOTE(chr_type, #text)
|
||||
|
||||
#define MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, prefix_a, prefix_b, prefix_c, set_args) \
|
||||
prefix_a prefix_b(char) prefix_c \
|
||||
using C ## type_name = Base ## type_name <set_args(char)>; \
|
||||
\
|
||||
prefix_a prefix_b(wchar_t) prefix_c \
|
||||
using W ## type_name = Base ## type_name <set_args(wchar_t)>; \
|
||||
\
|
||||
prefix_a prefix_b(char8_t) prefix_c \
|
||||
using U ## type_name = Base ## type_name <set_args(char8_t)>; \
|
||||
\
|
||||
using type_name = Base ## type_name<>;
|
||||
|
||||
#define MIJIN_DEFINE_CHAR_VERSIONS_TMPL(type_name, remaining_args, set_args) \
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, template<, remaining_args, >, set_args)
|
||||
|
||||
#define MIJIN_DEFINE_CHAR_VERSIONS_CUSTOM(type_name, set_args) \
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, , MIJIN_NULLIFY, , set_args)
|
||||
|
||||
#define MIJIN_DEFINE_CHAR_VERSIONS(type_name) \
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_CUSTOM(type_name, MIJIN_IDENTITY)
|
||||
|
||||
#endif // !defined(MIJIN_INTERNAL_HELPERS_HPP_INCLUDED)
|
||||
19
source/mijin/internal/version_support.hpp
Normal file
19
source/mijin/internal/version_support.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED)
|
||||
#define MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED 1
|
||||
|
||||
#include "../detect.hpp"
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma clang diagnostic ignored "-Wc++26-extensions"
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_deleted_function)
|
||||
#define MIJIN_DELETE(reason) = delete(reason)
|
||||
#else
|
||||
#define MIJIN_DELETE(reason) = delete
|
||||
#endif
|
||||
|
||||
#endif // !defined(MIJIN_INTERNAL_VERSION_SUPPORT_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,11 +168,17 @@ std::string shellEscape(const std::string& arg) noexcept
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string makeShellCommand(const std::vector<std::string>& args) noexcept
|
||||
std::string makeShellCommand(std::span<std::string_view> args) MIJIN_NOEXCEPT
|
||||
{
|
||||
(void) args;
|
||||
MIJIN_ERROR("TBD");
|
||||
return {};
|
||||
#if 0
|
||||
using namespace mijin::pipe;
|
||||
return args
|
||||
| Map(&shellEscape)
|
||||
| Join(" ");
|
||||
#endif
|
||||
}
|
||||
} // namespace mijin
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_OSX
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
#if !defined(MIJIN_IO_PROCESS_HPP_INCLUDED)
|
||||
#define MIJIN_IO_PROCESS_HPP_INCLUDED 1
|
||||
|
||||
#include <vector>
|
||||
#include <span>
|
||||
#include "./stream.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -21,12 +22,12 @@ public:
|
||||
inline StreamError open(const std::string& command, FileOpenMode mode_) {
|
||||
return open(command.c_str(), mode_);
|
||||
}
|
||||
inline StreamError open(const std::vector<std::string>& args, FileOpenMode mode_);
|
||||
inline StreamError open(std::span<std::string_view> args, FileOpenMode mode_);
|
||||
int close();
|
||||
[[nodiscard]] inline bool isOpen() const { return handle != nullptr; }
|
||||
|
||||
// Stream overrides
|
||||
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
|
||||
StreamError 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,13 +36,19 @@ 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(std::string_view arg) MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] std::string makeShellCommand(std::span<std::string_view> args) MIJIN_NOEXCEPT;
|
||||
|
||||
StreamError ProcessStream::open(const std::vector<std::string>& args, FileOpenMode mode_)
|
||||
StreamError ProcessStream::open(std::span<std::string_view> args, FileOpenMode mode_)
|
||||
{
|
||||
return open(makeShellCommand(args), mode_);
|
||||
}
|
||||
|
||||
template<typename... TTypes>
|
||||
std::array<std::string_view, sizeof...(TTypes)> makeSVList(TTypes&&... args)
|
||||
{
|
||||
return std::array{std::string_view(std::forward<TTypes>(args))...};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MIJIN_IO_PROCESS_HPP_INCLUDED
|
||||
|
||||
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,217 @@ 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());
|
||||
}
|
||||
|
||||
StreamError Stream::readZString(std::string& outString)
|
||||
{
|
||||
char chr = '\0';
|
||||
std::string result;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (isAtEnd())
|
||||
{
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
if (StreamError error = read(chr); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
if (chr == '\0')
|
||||
{
|
||||
outString = std::move(result);
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
result.push_back(chr);
|
||||
}
|
||||
}
|
||||
|
||||
StreamError Stream::writeZString(std::string_view str)
|
||||
{
|
||||
static const char ZERO = '\0';
|
||||
|
||||
if (StreamError error = writeRaw(str.data(), str.size() * sizeof(char)); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
return write(ZERO);
|
||||
}
|
||||
|
||||
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::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;
|
||||
}
|
||||
|
||||
StreamError Stream::copyTo(Stream& other)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().read, "Stream must support reading.");
|
||||
MIJIN_ASSERT(other.getFeatures().write, "Other stream must support writing.");
|
||||
|
||||
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;
|
||||
}
|
||||
if (const StreamError error = other.writeRaw(chunk.data(), bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
FileStream::~FileStream()
|
||||
{
|
||||
if (handle) {
|
||||
@@ -89,6 +339,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 +352,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 +410,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 +430,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 +442,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;
|
||||
}
|
||||
|
||||
@@ -204,10 +464,14 @@ StreamFeatures FileStream::getFeatures()
|
||||
if (handle)
|
||||
{
|
||||
return {
|
||||
.read = (mode == FileOpenMode::READ),
|
||||
.read = (mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE),
|
||||
.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 +479,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 +510,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 +534,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 +563,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 +573,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,212 @@ 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);
|
||||
}
|
||||
|
||||
template<template<typename> typename TAllocator>
|
||||
StreamError readRaw(BaseTypelessBuffer<TAllocator>& 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);
|
||||
}
|
||||
|
||||
template<template<typename> typename TAllocator>
|
||||
mijin::Task<StreamError> c_readRaw(BaseTypelessBuffer<TAllocator>& 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)
|
||||
{
|
||||
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);
|
||||
|
||||
StreamError readZString(std::string& outString);
|
||||
StreamError writeZString(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 copyTo(Stream& otherStream);
|
||||
|
||||
template<template<typename> typename TAllocator>
|
||||
StreamError readRest(BaseTypelessBuffer<TAllocator>& outBuffer);
|
||||
|
||||
template<template<typename> typename TAllocator>
|
||||
mijin::Task<StreamError> c_readRest(BaseTypelessBuffer<TAllocator>& outBuffer);
|
||||
|
||||
StreamError readLine(std::string& outString);
|
||||
mijin::Task<StreamError> c_readLine(std::string& outString);
|
||||
|
||||
template<typename TChar = char, typename TTraits = std::char_traits<TChar>, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR<char>>
|
||||
StreamError readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString);
|
||||
|
||||
template<typename TChar = char, typename TTraits = std::char_traits<TChar>, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR<char>>
|
||||
mijin::Task<StreamError> c_readAsString(std::basic_string<TChar, TTraits, TAllocator>& 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 +320,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 +337,254 @@ 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<template<typename> typename TAllocator>
|
||||
StreamError Stream::readRest(BaseTypelessBuffer<TAllocator>& 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.template makeSpan<std::byte>();
|
||||
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
template<template<typename> typename TAllocator>
|
||||
mijin::Task<StreamError> Stream::c_readRest(BaseTypelessBuffer<TAllocator>& 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.template makeSpan<std::byte>();
|
||||
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
StreamError Stream::readAsString(std::basic_string<TChar, TTraits, TAllocator>& 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, typename TTraits, typename TAllocator>
|
||||
mijin::Task<StreamError> Stream::c_readAsString(std::basic_string<TChar, TTraits, TAllocator>& 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 Exception(errorName(error));
|
||||
}
|
||||
|
||||
inline void throwOnError(mijin::StreamError error, std::string message)
|
||||
{
|
||||
if (error == mijin::StreamError::SUCCESS) {
|
||||
return;
|
||||
}
|
||||
throw Exception(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)
|
||||
|
||||
67
source/mijin/logging/debug_output_sink.hpp
Normal file
67
source/mijin/logging/debug_output_sink.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_DEBUG_OUTPUT_SINK_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_DEBUG_OUTPUT_SINK_HPP_INCLUDED 1
|
||||
|
||||
#include "./formatting.hpp"
|
||||
#include "../detect.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
#pragma comment(lib, "Kernel32.lib")
|
||||
|
||||
extern "C" void OutputDebugStringA(const char* lpOutputString);
|
||||
extern "C" void OutputDebugStringW(const wchar_t* lpOutputString);
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
|
||||
requires(allocator_type<TAllocator<TChar>>)
|
||||
class BaseDebugOutputSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
|
||||
{
|
||||
public:
|
||||
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
|
||||
using typename base_t::char_t;
|
||||
using typename base_t::allocator_t;
|
||||
using typename base_t::formatter_ptr_t;
|
||||
using typename base_t::message_t;
|
||||
public:
|
||||
explicit BaseDebugOutputSink(formatter_ptr_t formatter, allocator_t allocator = {})
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
|
||||
: base_t(std::move(formatter), std::move(allocator)) {}
|
||||
|
||||
void handleMessageFormatted(const message_t&, const char_t* formatted) MIJIN_NOEXCEPT override
|
||||
{
|
||||
if constexpr (std::is_same_v<char_t, char>)
|
||||
{
|
||||
OutputDebugStringA(formatted);
|
||||
OutputDebugStringA("\n");
|
||||
}
|
||||
else if constexpr (std::is_same_v<char_t, wchar_t>)
|
||||
{
|
||||
OutputDebugStringW(formatted);
|
||||
OutputDebugStringW(L"\n");
|
||||
}
|
||||
else if constexpr (sizeof(char_t) == sizeof(char))
|
||||
{
|
||||
// char8_t etc.
|
||||
OutputDebugStringA(std::bit_cast<const char*>(formatted));
|
||||
OutputDebugStringA("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<char_t>, "Character type not supported.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(DebugOutputSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
|
||||
|
||||
#undef SINK_SET_ARGS
|
||||
} // namespace mijin
|
||||
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
#endif // !defined(MIJIN_LOGGING_DEBUG_OUTPUT_SINK_HPP_INCLUDED)
|
||||
52
source/mijin/logging/filters.hpp
Normal file
52
source/mijin/logging/filters.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_FILTERS_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_FILTERS_HPP_INCLUDED 1
|
||||
|
||||
#include "./logger.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
|
||||
class BaseLevelFilter : public BaseLogFilter<TChar>
|
||||
{
|
||||
public:
|
||||
using base_t = BaseLogFilter<TChar>;
|
||||
using typename base_t::char_t;
|
||||
using typename base_t::message_t;
|
||||
private:
|
||||
int mMinLevel = 0;
|
||||
int mMaxLevel = 0;
|
||||
public:
|
||||
explicit BaseLevelFilter(int minLevel, int maxLevel = std::numeric_limits<int>::max()) MIJIN_NOEXCEPT
|
||||
: mMinLevel(minLevel), mMaxLevel(maxLevel) {}
|
||||
explicit BaseLevelFilter(const BaseLogLevel<char_t>& minLevel, const BaseLogLevel<char_t>& maxLevel = {nullptr, std::numeric_limits<int>::max()}) MIJIN_NOEXCEPT
|
||||
: mMinLevel(minLevel.value), mMaxLevel(maxLevel.value) {}
|
||||
|
||||
[[nodiscard]]
|
||||
int getMinLevel() const MIJIN_NOEXCEPT { return mMinLevel; }
|
||||
|
||||
[[nodiscard]]
|
||||
int getMaxLevel() const MIJIN_NOEXCEPT { return mMaxLevel; }
|
||||
|
||||
void setMinLevel(int level) MIJIN_NOEXCEPT { mMinLevel = level; }
|
||||
|
||||
void setMinLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinLevel = level.value; }
|
||||
|
||||
void setMaxLevel(int level) MIJIN_NOEXCEPT { mMaxLevel = level; }
|
||||
|
||||
void setMaxLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMaxLevel = level.value; }
|
||||
|
||||
bool shouldShow(const message_t& message) MIJIN_NOEXCEPT override
|
||||
{
|
||||
return message.level->value >= mMinLevel && message.level->value <= mMaxLevel;
|
||||
}
|
||||
};
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS(LevelFilter)
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
|
||||
323
source/mijin/logging/formatting.hpp
Normal file
323
source/mijin/logging/formatting.hpp
Normal file
@@ -0,0 +1,323 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1
|
||||
|
||||
#include <format>
|
||||
#include <variant>
|
||||
#include "./logger.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../memory/dynamic_pointer.hpp"
|
||||
#include "../memory/memutil.hpp"
|
||||
#include "../memory/virtual_allocator.hpp"
|
||||
#include "../util/annot.hpp"
|
||||
#include "../util/ansi_colors.hpp"
|
||||
#include "../util/concepts.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
#define FORMATTER_COMMON_ARGS(chr_type) allocator_type_for<chr_type> TAllocator = MIJIN_DEFAULT_ALLOCATOR<chr_type>
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
|
||||
FORMATTER_COMMON_ARGS(TChar)>
|
||||
class BaseLogFormatter
|
||||
{
|
||||
public:
|
||||
using char_t = TChar;
|
||||
using traits_t = TTraits;
|
||||
using allocator_t = TAllocator;
|
||||
using string_t = std::basic_string<char_t, traits_t, allocator_t>;
|
||||
|
||||
virtual ~BaseLogFormatter() noexcept = default;
|
||||
|
||||
virtual void format(const LogMessage& message, string_t& outFormatted) = 0;
|
||||
};
|
||||
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
|
||||
FORMATTER_COMMON_ARGS(TChar)>
|
||||
class BaseSimpleLogFormatter : public BaseLogFormatter<TChar, TTraits, TAllocator>
|
||||
{
|
||||
public:
|
||||
using char_t = TChar;
|
||||
using traits_t = TTraits;
|
||||
using allocator_t = TAllocator;
|
||||
using base_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
|
||||
using typename base_t::string_t;
|
||||
using string_view_t = std::basic_string_view<char_t, traits_t>;
|
||||
private:
|
||||
string_t mFormat;
|
||||
string_t mFormatBuffer;
|
||||
public:
|
||||
explicit BaseSimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)), mFormatBuffer(mFormat.get_allocator()) {}
|
||||
|
||||
void format(const LogMessage& message, string_t& outFormatted) override;
|
||||
private:
|
||||
void formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted);
|
||||
};
|
||||
|
||||
#define FORMATTER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(LogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS)
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(SimpleLogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS)
|
||||
|
||||
#undef FORMATTER_COMMON_ARGS
|
||||
#undef FORMATTER_SET_ARGS
|
||||
|
||||
#define MIJIN_FORMATTING_SINK_COMMON_ARGS(chr_type) \
|
||||
typename TTraits = std::char_traits<chr_type>, \
|
||||
template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, \
|
||||
deleter_type<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>> TDeleter \
|
||||
= AllocatorDeleter<TAllocator<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>>>
|
||||
|
||||
#define MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT \
|
||||
typename TChar = MIJIN_DEFAULT_CHAR_TYPE, \
|
||||
MIJIN_FORMATTING_SINK_COMMON_ARGS(TChar)
|
||||
|
||||
#define MIJIN_FORMATTING_SINK_TMP_ARG_NAMES TChar, TTraits, TAllocator, TDeleter
|
||||
|
||||
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
|
||||
requires(allocator_type<TAllocator<TChar>>)
|
||||
class BaseFormattingLogSink : public BaseLogSink<TChar>
|
||||
{
|
||||
public:
|
||||
using base_t = BaseLogSink<TChar>;
|
||||
|
||||
using char_t = TChar;
|
||||
using traits_t = TTraits;
|
||||
using allocator_t = TAllocator<TChar>;
|
||||
using formatter_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
|
||||
using formatter_deleter_t = TDeleter;
|
||||
using formatter_ptr_t = DynamicPointer<formatter_t, formatter_deleter_t>;
|
||||
using string_t = formatter_t::string_t;
|
||||
using typename base_t::message_t;
|
||||
private:
|
||||
formatter_ptr_t mFormatter;
|
||||
string_t mBuffer;
|
||||
public:
|
||||
explicit BaseFormattingLogSink(formatter_ptr_t formatter, allocator_t allocator = {})
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
|
||||
: mFormatter(std::move(formatter)), mBuffer(std::move(allocator))
|
||||
{}
|
||||
|
||||
virtual void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT = 0;
|
||||
|
||||
void handleMessage(const message_t& message) noexcept override
|
||||
{
|
||||
mBuffer.clear();
|
||||
mFormatter->format(message, mBuffer);
|
||||
handleMessageFormatted(message, mBuffer.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(FormattingLogSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
|
||||
|
||||
#undef SINK_SET_ARGS
|
||||
|
||||
template<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
|
||||
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::format(const LogMessage& message, string_t& outFormatted)
|
||||
{
|
||||
mFormatBuffer.clear();
|
||||
for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos)
|
||||
{
|
||||
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
|
||||
{
|
||||
++pos;
|
||||
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
|
||||
{
|
||||
// double {
|
||||
outFormatted += MIJIN_SMART_QUOTE(char_t, '{');
|
||||
continue;
|
||||
}
|
||||
const auto argStart = pos;
|
||||
static const string_view_t endChars = MIJIN_SMART_QUOTE(char_t, ":}");
|
||||
pos = std::find_first_of(pos, mFormat.end(), endChars.begin(), endChars.end());
|
||||
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
|
||||
|
||||
const string_view_t argName(argStart, pos);
|
||||
string_view_t argFormat;
|
||||
if (*pos == ':')
|
||||
{
|
||||
const auto formatStart = pos;
|
||||
pos = std::find(pos, mFormat.end(), MIJIN_SMART_QUOTE(char_t, '}'));
|
||||
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
|
||||
argFormat = string_view_t(formatStart, pos);
|
||||
}
|
||||
|
||||
// small utility that uses the provided string buffer for storing the format string
|
||||
auto formatInline = [&](const auto& value)
|
||||
{
|
||||
using type_t = std::decay_t<decltype(value)>;
|
||||
|
||||
// if there is no format, just directly print the value
|
||||
if (argFormat.empty())
|
||||
{
|
||||
if constexpr (is_char_v<type_t>)
|
||||
{
|
||||
convertStringType(string_view_t(&value, 1), outFormatted);
|
||||
}
|
||||
else if constexpr (std::is_arithmetic_v<type_t>)
|
||||
{
|
||||
std::format_to(std::back_inserter(outFormatted), MIJIN_SMART_QUOTE(char_t, "{}"), value);
|
||||
}
|
||||
else if constexpr (is_string_v<type_t> || is_cstring_v<type_t>)
|
||||
{
|
||||
convertStringType(value, outFormatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<type_t>);
|
||||
outFormatted += value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// first copy the format string + braces into the buffer
|
||||
const auto formatStart = mFormatBuffer.size();
|
||||
mFormatBuffer += '{';
|
||||
mFormatBuffer += argFormat;
|
||||
mFormatBuffer += '}';
|
||||
const auto formatEnd = mFormatBuffer.size();
|
||||
|
||||
auto doFormatTo = [](string_t& string, string_view_t format, const auto& value)
|
||||
{
|
||||
if constexpr (std::is_same_v<char_t, char>)
|
||||
{
|
||||
std::vformat_to(std::back_inserter(string), format, std::make_format_args(value));
|
||||
}
|
||||
else if constexpr (std::is_same_v<char_t, wchar_t>)
|
||||
{
|
||||
std::vformat_to(std::back_inserter(string), format, std::make_wformat_args(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<char_t>, "Cannot format this char type.");
|
||||
}
|
||||
};
|
||||
|
||||
auto doFormat = [&](const auto& value)
|
||||
{
|
||||
auto format = string_view_t(mFormatBuffer).substr(formatStart, formatEnd - formatStart);
|
||||
doFormatTo(outFormatted, format, value);
|
||||
};
|
||||
|
||||
if constexpr (is_char_v<type_t> && !std::is_same_v<char_t, type_t>)
|
||||
{
|
||||
static_assert(always_false_v<type_t>, "TODO...");
|
||||
}
|
||||
else if constexpr ((is_string_v<type_t> || is_cstring_v<type_t>) && !std::is_same_v<str_char_type_t<type_t>, char_t>)
|
||||
{
|
||||
// different string type, needs to be converted
|
||||
const auto convertedStart = mFormatBuffer.size();
|
||||
convertStringType(value, mFormatBuffer);
|
||||
const auto convertedEnd = mFormatBuffer.size();
|
||||
|
||||
// then we can format it
|
||||
auto converted = string_view_t(mFormatBuffer).substr(convertedStart, mFormatBuffer.size() - convertedEnd);
|
||||
doFormat(converted);
|
||||
}
|
||||
else
|
||||
{
|
||||
// nothing special
|
||||
doFormat(value);
|
||||
}
|
||||
};
|
||||
|
||||
if (argName == MIJIN_SMART_QUOTE(char_t, "text"))
|
||||
{
|
||||
formatInline(message.text);
|
||||
}
|
||||
else if (argName == MIJIN_SMART_QUOTE(char_t, "channel"))
|
||||
{
|
||||
formatInline(message.channel->name);
|
||||
}
|
||||
else if (argName == MIJIN_SMART_QUOTE(char_t, "file"))
|
||||
{
|
||||
formatInline(message.sourceLocation.file_name());
|
||||
}
|
||||
else if (argName == MIJIN_SMART_QUOTE(char_t, "function"))
|
||||
{
|
||||
formatInline(message.sourceLocation.function_name());
|
||||
}
|
||||
else if (argName == MIJIN_SMART_QUOTE(char_t, "line"))
|
||||
{
|
||||
formatInline(message.sourceLocation.line());
|
||||
}
|
||||
else if (argName == MIJIN_SMART_QUOTE(char_t, "column"))
|
||||
{
|
||||
formatInline(message.sourceLocation.column());
|
||||
}
|
||||
else if (argName == MIJIN_SMART_QUOTE(char_t, "level"))
|
||||
{
|
||||
formatInline(message.level->name);
|
||||
}
|
||||
else if (argName == MIJIN_SMART_QUOTE(char_t, "ansi"))
|
||||
{
|
||||
formatAnsiSequence(message, argFormat.substr(1), outFormatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
MIJIN_ERROR("Invalid format argument name.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outFormatted += *pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
|
||||
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted)
|
||||
{
|
||||
std::size_t numParts = 0;
|
||||
const auto formatParts = splitFixed<4>(ansiName, ",", {}, &numParts);
|
||||
|
||||
outFormatted += MIJIN_SMART_QUOTE(char_t, "\033[");
|
||||
for (std::size_t partIdx = 0; partIdx < numParts; ++partIdx)
|
||||
{
|
||||
const string_view_t& part = formatParts[partIdx];
|
||||
if (partIdx > 0)
|
||||
{
|
||||
outFormatted += MIJIN_SMART_QUOTE(char_t, ',');
|
||||
}
|
||||
if (part == MIJIN_SMART_QUOTE(char_t, "reset"))
|
||||
{
|
||||
outFormatted += BaseAnsiFontEffects<char_t>::RESET;
|
||||
}
|
||||
else if (part == MIJIN_SMART_QUOTE(char_t, "level_color"))
|
||||
{
|
||||
const int levelValue = message.level->value;
|
||||
if (levelValue < MIJIN_LOG_LEVEL_VALUE_VERBOSE)
|
||||
{
|
||||
outFormatted += BaseAnsiFontEffects<char_t>::FG_CYAN;
|
||||
}
|
||||
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_INFO)
|
||||
{
|
||||
outFormatted += BaseAnsiFontEffects<char_t>::FG_WHITE;
|
||||
}
|
||||
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_WARNING)
|
||||
{
|
||||
outFormatted += BaseAnsiFontEffects<char_t>::FG_BRIGHT_WHITE;
|
||||
}
|
||||
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_ERROR)
|
||||
{
|
||||
outFormatted += BaseAnsiFontEffects<char_t>::FG_YELLOW;
|
||||
}
|
||||
else
|
||||
{
|
||||
outFormatted += BaseAnsiFontEffects<char_t>::FG_RED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MIJIN_ERROR("Invalid format ansi font effect name.");
|
||||
}
|
||||
}
|
||||
outFormatted += MIJIN_SMART_QUOTE(char_t, 'm');
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
|
||||
298
source/mijin/logging/logger.hpp
Normal file
298
source/mijin/logging/logger.hpp
Normal file
@@ -0,0 +1,298 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <mutex>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/annot.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
#if !defined(MIJIN_LOG_LEVEL_VALUE_DEBUG)
|
||||
#define MIJIN_LOG_LEVEL_VALUE_DEBUG -1000
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_LOG_LEVEL_VALUE_VERBOSE)
|
||||
#define MIJIN_LOG_LEVEL_VALUE_VERBOSE -500
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_LOG_LEVEL_VALUE_INFO)
|
||||
#define MIJIN_LOG_LEVEL_VALUE_INFO 0
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_LOG_LEVEL_VALUE_WARNING)
|
||||
#define MIJIN_LOG_LEVEL_VALUE_WARNING 500
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_LOG_LEVEL_VALUE_ERROR)
|
||||
#define MIJIN_LOG_LEVEL_VALUE_ERROR 1000
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_FUNCNAME_GET_LOGGER)
|
||||
#define MIJIN_FUNCNAME_GET_LOGGER mijin__getLogger__
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE)
|
||||
#define MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE mijin__getMinLogLevelCompile
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_NSNAME_LOG_LEVEL)
|
||||
#define MIJIN_NSNAME_LOG_LEVEL mijin_log_level
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_NSNAME_LOG_CHANNEL)
|
||||
#define MIJIN_NSNAME_LOG_CHANNEL mijin_log_channel
|
||||
#endif
|
||||
|
||||
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
|
||||
struct BaseLogLevel
|
||||
{
|
||||
using char_t = TChar;
|
||||
|
||||
const char_t* name;
|
||||
int value;
|
||||
|
||||
explicit operator int() const MIJIN_NOEXCEPT { return value; }
|
||||
|
||||
auto operator<=>(const BaseLogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
|
||||
};
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS(LogLevel)
|
||||
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
|
||||
struct BaseLogChannel
|
||||
{
|
||||
using char_t = TChar;
|
||||
|
||||
const char_t* name;
|
||||
};
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS(LogChannel)
|
||||
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
|
||||
struct BaseLogMessage
|
||||
{
|
||||
using char_t = TChar;
|
||||
|
||||
const char_t* text;
|
||||
const BaseLogChannel<char_t>* channel;
|
||||
const BaseLogLevel<char_t>* level;
|
||||
std::source_location sourceLocation;
|
||||
};
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS(LogMessage)
|
||||
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
|
||||
class BaseLogSink
|
||||
{
|
||||
public:
|
||||
using char_t = TChar;
|
||||
using message_t = BaseLogMessage<char_t>;
|
||||
|
||||
virtual ~BaseLogSink() noexcept = default;
|
||||
|
||||
virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS(LogSink)
|
||||
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
|
||||
class BaseLogFilter
|
||||
{
|
||||
public:
|
||||
using char_t = TChar;
|
||||
using message_t = BaseLogMessage<char_t>;
|
||||
|
||||
virtual ~BaseLogFilter() noexcept = default;
|
||||
|
||||
virtual bool shouldShow(const message_t& message) MIJIN_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS(LogFilter)
|
||||
|
||||
#define LOGGER_COMMON_ARGS(chr_type) template<typename T> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>, LOGGER_COMMON_ARGS(TChar)>
|
||||
class BaseLogger
|
||||
{
|
||||
public:
|
||||
using char_t = TChar;
|
||||
using traits_t = TTraits;
|
||||
using allocator_t = TAllocator<char_t>;
|
||||
|
||||
using sink_t = BaseLogSink<char_t>;
|
||||
using filter_t = BaseLogFilter<char_t>;
|
||||
using level_t = BaseLogLevel<char_t>;
|
||||
using channel_t = BaseLogChannel<char_t>;
|
||||
using message_t = BaseLogMessage<char_t>;
|
||||
using string_t = std::basic_string<char_t, traits_t, allocator_t>;
|
||||
private:
|
||||
struct SinkEntry
|
||||
{
|
||||
sink_t* sink;
|
||||
filter_t* filter;
|
||||
};
|
||||
std::vector<SinkEntry, TAllocator<SinkEntry>> mSinks;
|
||||
mutable std::mutex mMutex;
|
||||
public:
|
||||
explicit BaseLogger(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<SinkEntry>, TAllocator<void>&&>))
|
||||
: mSinks(TAllocator<SinkEntry>(std::move(allocator)))
|
||||
{}
|
||||
|
||||
BaseLogger(const BaseLogger&) = default;
|
||||
|
||||
BaseLogger(BaseLogger&&) = default;
|
||||
|
||||
BaseLogger& operator=(const BaseLogger&) = default;
|
||||
|
||||
BaseLogger& operator=(BaseLogger&&) = default;
|
||||
|
||||
void addSink(sink_t& sink)
|
||||
{
|
||||
std::unique_lock _(mMutex);
|
||||
mSinks.push_back({&sink, nullptr});
|
||||
}
|
||||
|
||||
void addSink(sink_t& sink, filter_t& filter)
|
||||
{
|
||||
std::unique_lock _(mMutex);
|
||||
mSinks.push_back({&sink, &filter});
|
||||
}
|
||||
|
||||
void postMessage(const message_t& message) const MIJIN_NOEXCEPT
|
||||
{
|
||||
std::unique_lock _(mMutex);
|
||||
for (const SinkEntry& entry : mSinks)
|
||||
{
|
||||
if (entry.filter != nullptr && !entry.filter->shouldShow(message)) {
|
||||
continue;
|
||||
}
|
||||
entry.sink->handleMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, const char_t* msg) const MIJIN_NOEXCEPT
|
||||
{
|
||||
postMessage({
|
||||
.text = msg,
|
||||
.channel = &channel,
|
||||
.level = &level,
|
||||
.sourceLocation = std::move(sourceLocation)
|
||||
});
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation,
|
||||
std::basic_format_string<char_t, std::type_identity_t<TArgs>...> fmt, TArgs&& ... args) const
|
||||
MIJIN_NOEXCEPT_IF(noexcept(std::declval<allocator_t>().allocate(1)))
|
||||
{
|
||||
string_t buffer(allocator_t(mSinks.get_allocator()));
|
||||
std::format_to(std::back_inserter(buffer), fmt, std::forward<TArgs>(args)...);
|
||||
log(level, channel, std::move(sourceLocation), buffer.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
#define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(Logger, LOGGER_COMMON_ARGS, LOGGER_SET_ARGS)
|
||||
|
||||
#undef LOGGER_COMMON_ARGS
|
||||
#undef LOGGER_SET_ARGS
|
||||
|
||||
#define MIJIN_DECLARE_LOG_CHANNEL_BASE(chr_type, cnlName) \
|
||||
namespace MIJIN_NSNAME_LOG_CHANNEL \
|
||||
{ \
|
||||
extern const ::mijin::BaseLogChannel<chr_type> cnlName; \
|
||||
}
|
||||
#define MIJIN_DECLARE_LOG_CHANNEL(cnlName) MIJIN_DECLARE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName)
|
||||
|
||||
#define MIJIN_DEFINE_LOG_CHANNEL_BASE(chr_type, cnlName) \
|
||||
namespace MIJIN_NSNAME_LOG_CHANNEL \
|
||||
{ \
|
||||
const ::mijin::BaseLogChannel<chr_type> cnlName { \
|
||||
.name = MIJIN_SMART_STRINGIFY(chr_type, cnlName) \
|
||||
}; \
|
||||
}
|
||||
#define MIJIN_DEFINE_LOG_CHANNEL(cnlName) MIJIN_DEFINE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName)
|
||||
|
||||
#define MIJIN_DEFINE_LOG_LEVEL_BASE(chr_type, lvlName, lvlValue) \
|
||||
namespace MIJIN_NSNAME_LOG_LEVEL \
|
||||
{ \
|
||||
inline constexpr ::mijin::BaseLogLevel<chr_type> lvlName{ \
|
||||
.name = MIJIN_SMART_STRINGIFY(chr_type, lvlName), \
|
||||
.value = lvlValue \
|
||||
}; \
|
||||
}
|
||||
#define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) MIJIN_DEFINE_LOG_LEVEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, lvlName, lvlValue)
|
||||
|
||||
#if defined(MIJIN_MIN_LOGLEVEL_COMPILE)
|
||||
inline constexpr int MIN_LOG_LEVEL_COMPILE = static_cast<int>(MIJIN_MIN_LOGLEVEL_COMPILE);
|
||||
#elif defined(MIJIN_DEBUG)
|
||||
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_DEBUG;
|
||||
#else
|
||||
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_VERBOSE;
|
||||
#endif
|
||||
|
||||
#define MIJIN_DEFINE_DEFAULT_LOG_LEVELS \
|
||||
MIJIN_DEFINE_LOG_LEVEL(DEBUG, MIJIN_LOG_LEVEL_VALUE_DEBUG) \
|
||||
MIJIN_DEFINE_LOG_LEVEL(VERBOSE, MIJIN_LOG_LEVEL_VALUE_VERBOSE) \
|
||||
MIJIN_DEFINE_LOG_LEVEL(INFO, MIJIN_LOG_LEVEL_VALUE_INFO) \
|
||||
MIJIN_DEFINE_LOG_LEVEL(WARNING, MIJIN_LOG_LEVEL_VALUE_WARNING) \
|
||||
MIJIN_DEFINE_LOG_LEVEL(ERROR, MIJIN_LOG_LEVEL_VALUE_ERROR)
|
||||
|
||||
#define MIJIN_LOG_LEVEL_OBJECT(level) MIJIN_NSNAME_LOG_LEVEL::level
|
||||
#define MIJIN_LOG_CHANNEL_OBJECT(channel) MIJIN_NSNAME_LOG_CHANNEL::channel
|
||||
|
||||
#define MIJIN_LOG_ALWAYS(level, channel, ...) MIJIN_FUNCNAME_GET_LOGGER().log( \
|
||||
MIJIN_LOG_LEVEL_OBJECT(level), MIJIN_LOG_CHANNEL_OBJECT(channel), std::source_location::current(), __VA_ARGS__ \
|
||||
)
|
||||
#define MIJIN_LOG(level, channel, ...) \
|
||||
if constexpr (MIJIN_LOG_LEVEL_OBJECT(level).value < MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE()) {} \
|
||||
else MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__)
|
||||
|
||||
#define MIJIN_LOG_IF(cond, level, channel, ...) \
|
||||
if constexpr (MIJIN_LOG_LEVEL_OBJECT(level).value < MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE()) {} \
|
||||
else if (cond) MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__)
|
||||
|
||||
#define MIJIN_SET_CLASS_LOGGER(loggerExpr) \
|
||||
const auto& MIJIN_FUNCNAME_GET_LOGGER() const noexcept \
|
||||
{ \
|
||||
return loggerExpr; \
|
||||
}
|
||||
#define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \
|
||||
auto MIJIN_FUNCNAME_GET_LOGGER = [&]() -> const auto& \
|
||||
{ \
|
||||
return loggerExpr; \
|
||||
};
|
||||
#define MIJIN_SET_NS_LOGGER(loggerExpr) \
|
||||
inline const mijin::Logger& MIJIN_FUNCNAME_GET_LOGGER() \
|
||||
{ \
|
||||
return loggerExpr; \
|
||||
}
|
||||
#define MIJIN_SET_CLASS_MIN_LOG_LEVEL_COMPILE(level) \
|
||||
int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT \
|
||||
{ \
|
||||
return MIJIN_LOG_LEVEL_OBJECT(level).value; \
|
||||
}
|
||||
#define MIJIN_SET_SCOPE_MIN_LOG_LEVEL_COMPILE(level) \
|
||||
auto MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE = []() -> int \
|
||||
{ \
|
||||
return MIJIN_LOG_LEVEL_OBJECT(level).value; \
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
inline constexpr int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT
|
||||
{
|
||||
return ::mijin::MIN_LOG_LEVEL_COMPILE;
|
||||
}
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
|
||||
71
source/mijin/logging/stdio_sink.hpp
Normal file
71
source/mijin/logging/stdio_sink.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED 1
|
||||
|
||||
#include "./formatting.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
|
||||
requires(allocator_type<TAllocator<TChar>>)
|
||||
class BaseStdioSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
|
||||
{
|
||||
public:
|
||||
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
|
||||
using typename base_t::char_t;
|
||||
using typename base_t::allocator_t;
|
||||
using typename base_t::formatter_ptr_t;
|
||||
using typename base_t::message_t;
|
||||
private:
|
||||
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
|
||||
public:
|
||||
explicit BaseStdioSink(formatter_ptr_t formatter, allocator_t allocator = {})
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
|
||||
: base_t(std::move(formatter), std::move(allocator)) {}
|
||||
|
||||
[[nodiscard]]
|
||||
int getMinStderrLevel() const MIJIN_NOEXCEPT { return mMinStderrLevel; }
|
||||
|
||||
void setMinStderrLevel(int level) MIJIN_NOEXCEPT { mMinStderrLevel = level; }
|
||||
|
||||
void setMinStderrLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinStderrLevel = level.value; }
|
||||
|
||||
void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT override
|
||||
{
|
||||
FILE* stream = (message.level->value >= mMinStderrLevel) ? stderr : stdout;
|
||||
if constexpr (std::is_same_v<char_t, char>)
|
||||
{
|
||||
std::fputs(formatted, stream);
|
||||
std::fputc('\n', stream);
|
||||
}
|
||||
else if constexpr (std::is_same_v<char_t, wchar_t>)
|
||||
{
|
||||
std::fputws(formatted, stream);
|
||||
std::fputwc(L'\n', stream);
|
||||
}
|
||||
else if constexpr (sizeof(char_t) == sizeof(char))
|
||||
{
|
||||
// char8_t etc.
|
||||
std::fputs(std::bit_cast<const char*>(formatted), stream);
|
||||
std::fputc('\n', stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<char_t>, "Character type not supported.");
|
||||
}
|
||||
std::fflush(stream);
|
||||
}
|
||||
};
|
||||
|
||||
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StdioSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
|
||||
|
||||
#undef SINK_SET_ARGS
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
|
||||
58
source/mijin/logging/stream_sink.hpp
Normal file
58
source/mijin/logging/stream_sink.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED 1
|
||||
|
||||
#include "./formatting.hpp"
|
||||
#include "../io/stream.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
|
||||
requires(allocator_type<TAllocator<TChar>>)
|
||||
class BaseStreamSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
|
||||
{
|
||||
public:
|
||||
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
|
||||
using typename base_t::char_t;
|
||||
using typename base_t::allocator_t;
|
||||
using typename base_t::formatter_ptr_t;
|
||||
using typename base_t::message_t;
|
||||
using stream_ptr_t = DynamicPointer<Stream>;
|
||||
private:
|
||||
stream_ptr_t mStream;
|
||||
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
|
||||
public:
|
||||
explicit BaseStreamSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
|
||||
: base_t(std::move(formatter), std::move(allocator)) {}
|
||||
explicit BaseStreamSink(not_null_t<stream_ptr_t> stream, not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
|
||||
: base_t(std::move(formatter), std::move(allocator)), mStream(std::move(stream)) {}
|
||||
|
||||
void setStream(not_null_t<stream_ptr_t> stream) {
|
||||
mStream = std::move(stream).release();
|
||||
}
|
||||
|
||||
void handleMessageFormatted(const message_t& /* message */, const char_t* formatted) MIJIN_NOEXCEPT override
|
||||
{
|
||||
if (!mStream) {
|
||||
return;
|
||||
}
|
||||
(void) mStream->writeSpan(std::basic_string_view(formatted));
|
||||
(void) mStream->write('\n');
|
||||
mStream->flush();
|
||||
}
|
||||
};
|
||||
|
||||
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
|
||||
|
||||
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StreamSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
|
||||
|
||||
#undef SINK_SET_ARGS
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)
|
||||
192
source/mijin/memory/dynamic_pointer.hpp
Normal file
192
source/mijin/memory/dynamic_pointer.hpp
Normal file
@@ -0,0 +1,192 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)
|
||||
#define MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
#include "../memory/memutil.hpp"
|
||||
#include "../util/concepts.hpp"
|
||||
#include "../util/flag.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
MIJIN_DEFINE_FLAG(Owning);
|
||||
|
||||
template<typename T, deleter_type<T> TDeleter = AllocatorDeleter<MIJIN_DEFAULT_ALLOCATOR<T>>>
|
||||
class DynamicPointer
|
||||
{
|
||||
public:
|
||||
using pointer = T*;
|
||||
using element_type = T;
|
||||
using deleter_t = TDeleter;
|
||||
private:
|
||||
std::uintptr_t mData = 0;
|
||||
[[no_unique_address]] TDeleter mDeleter;
|
||||
public:
|
||||
constexpr DynamicPointer(std::nullptr_t = nullptr) MIJIN_NOEXCEPT {}
|
||||
DynamicPointer(const DynamicPointer&) = delete;
|
||||
constexpr DynamicPointer(pointer ptr, Owning owning, TDeleter deleter = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TDeleter>)
|
||||
: mData(std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0)), mDeleter(std::move(deleter))
|
||||
{
|
||||
MIJIN_ASSERT((std::bit_cast<std::uintptr_t>(ptr) & 1) == 0, "Invalid address, DynamicPointer requires addresses to be divisible by two.");
|
||||
}
|
||||
template<typename TOther, typename TOtherDeleter> requires (std::is_assignable_v<T*&, TOther*> && std::is_constructible_v<TDeleter, TOtherDeleter&&>)
|
||||
constexpr DynamicPointer(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_convertible_v<TOtherDeleter, TDeleter>))
|
||||
: DynamicPointer(other.get(), other.isOwning() ? Owning::YES : Owning::NO, TDeleter(std::move(other.mDeleter)))
|
||||
{
|
||||
other.mData = 0;
|
||||
}
|
||||
constexpr ~DynamicPointer() noexcept
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
DynamicPointer& operator=(const DynamicPointer&) = delete;
|
||||
DynamicPointer& operator=(std::nullptr_t) MIJIN_NOEXCEPT
|
||||
{
|
||||
reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename TOther, typename TOtherDeleter> requires(std::is_assignable_v<TDeleter, TOtherDeleter>)
|
||||
DynamicPointer& operator=(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TDeleter, TOtherDeleter>))
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
reset();
|
||||
mData = std::exchange(other.mData, 0);
|
||||
mDeleter = std::move(other.mDeleter);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// template<typename TOther, typename TOtherDeleter> requires (std::is_base_of_v<TOther, T>)
|
||||
// constexpr operator DynamicPointer<TOther, TOtherDeleter>() && // MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TDeleter>>)
|
||||
// {
|
||||
// const Owning owning = isOwning() ? Owning::YES : Owning::NO;
|
||||
// T* const ptr = release();
|
||||
//
|
||||
// return DynamicPointer<TOther, TOtherDeleter>(static_cast<TOther*>(ptr), owning, TOtherDeleter(std::move(mDeleter)));
|
||||
// }
|
||||
|
||||
template<typename TOther, typename TOtherDeleter> requires(std::equality_comparable_with<T, TOther>)
|
||||
auto operator<=>(const DynamicPointer<TOther, TOtherDeleter>& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
return mData <=> other.mData;
|
||||
}
|
||||
|
||||
constexpr bool operator==(std::nullptr_t) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return empty();
|
||||
}
|
||||
|
||||
constexpr bool operator!=(std::nullptr_t) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !empty();
|
||||
}
|
||||
|
||||
constexpr operator bool() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !empty();
|
||||
}
|
||||
|
||||
constexpr bool operator!() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return empty();
|
||||
}
|
||||
|
||||
constexpr pointer operator->() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return get();
|
||||
}
|
||||
|
||||
constexpr element_type& operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return *get();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool isOwning() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return (mData & 1) == 1;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool empty() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return mData == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr pointer get() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::bit_cast<pointer>(mData & ~1);
|
||||
}
|
||||
|
||||
constexpr void reset(pointer ptr, Owning owning) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (isOwning())
|
||||
{
|
||||
mDeleter(get());
|
||||
}
|
||||
mData = std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0);
|
||||
}
|
||||
|
||||
constexpr void reset() MIJIN_NOEXCEPT
|
||||
{
|
||||
reset(nullptr, Owning::NO);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
pointer release() MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::bit_cast<pointer>(std::exchange(mData, 0) & ~1);
|
||||
}
|
||||
|
||||
template<typename TOther, deleter_type<TOther> TOtherDeleter>
|
||||
friend class DynamicPointer;
|
||||
};
|
||||
|
||||
template<typename T, typename TDeleter>
|
||||
bool operator==(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
|
||||
{
|
||||
return pointer == nullptr;
|
||||
}
|
||||
|
||||
template<typename T, typename TDeleter>
|
||||
bool operator!=(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
|
||||
{
|
||||
return pointer != nullptr;
|
||||
}
|
||||
|
||||
template<typename T, typename... TArgs>
|
||||
DynamicPointer<T> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
|
||||
{
|
||||
return DynamicPointer<T>(new T(std::forward<TArgs>(args)...), Owning::YES);
|
||||
}
|
||||
|
||||
template<typename T, allocator_type_for<T> TAllocator, typename... TArgs>
|
||||
DynamicPointer<T, AllocatorDeleter<TAllocator>> makeDynamicWithAllocator(TAllocator allocator, TArgs&&... args)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...> && std::is_nothrow_move_constructible_v<TAllocator>))
|
||||
{
|
||||
T* obj = allocator.allocate(1);
|
||||
if (obj != nullptr)
|
||||
{
|
||||
::new(obj) T(std::forward<TArgs>(args)...);
|
||||
}
|
||||
return DynamicPointer<T, AllocatorDeleter<TAllocator>>(obj, Owning::YES, AllocatorDeleter<TAllocator>(std::move(allocator)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
DynamicPointer<T> wrapDynamic(T* raw)
|
||||
{
|
||||
return DynamicPointer<T>(raw, Owning::NO);
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)
|
||||
90
source/mijin/memory/memutil.hpp
Normal file
90
source/mijin/memory/memutil.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)
|
||||
#define MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TAllocator>
|
||||
class AllocatorDeleter
|
||||
{
|
||||
public:
|
||||
using value_type = std::allocator_traits<TAllocator>::value_type;
|
||||
using pointer = std::allocator_traits<TAllocator>::pointer;
|
||||
|
||||
private:
|
||||
[[no_unique_address]] TAllocator allocator_;
|
||||
|
||||
public:
|
||||
AllocatorDeleter() = default;
|
||||
explicit AllocatorDeleter(TAllocator allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator>)
|
||||
: allocator_(std::move(allocator)) {}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, const TOtherAllocator&>)
|
||||
AllocatorDeleter(const AllocatorDeleter<TOtherAllocator>& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator>))
|
||||
: allocator_(other.allocator_) {}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, TOtherAllocator&&>)
|
||||
AllocatorDeleter(AllocatorDeleter<TOtherAllocator>&& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator&&>))
|
||||
: allocator_(std::move(other.allocator_)) {}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, const TOtherAllocator&>)
|
||||
AllocatorDeleter& operator=(const AllocatorDeleter<TOtherAllocator>& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, const TOtherAllocator&>))
|
||||
{
|
||||
if (this != static_cast<const void*>(&other))
|
||||
{
|
||||
allocator_ = other.allocator_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, TOtherAllocator&&>)
|
||||
AllocatorDeleter& operator=(AllocatorDeleter<TOtherAllocator>&& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, TOtherAllocator&&>))
|
||||
{
|
||||
if (this != static_cast<const void*>(&other))
|
||||
{
|
||||
allocator_ = std::move(other.allocator_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()(pointer ptr) MIJIN_NOEXCEPT_IF(noexcept(allocator_.deallocate(ptr, sizeof(value_type))))
|
||||
{
|
||||
allocator_.deallocate(ptr, sizeof(value_type));
|
||||
}
|
||||
|
||||
template<typename TOtherAllocator>
|
||||
friend class AllocatorDeleter;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class AllocatorDeleter<std::allocator<T>>
|
||||
{
|
||||
public:
|
||||
AllocatorDeleter() noexcept = default;
|
||||
|
||||
template<typename TOther>
|
||||
AllocatorDeleter(std::allocator<TOther>) noexcept {}
|
||||
|
||||
template<typename TOther>
|
||||
AllocatorDeleter(const AllocatorDeleter<std::allocator<TOther>>&) noexcept {}
|
||||
|
||||
template<typename TOther>
|
||||
AllocatorDeleter& operator=(const AllocatorDeleter<std::allocator<TOther>>&) noexcept { return *this; }
|
||||
|
||||
void operator()(T* ptr) const MIJIN_NOEXCEPT
|
||||
{
|
||||
delete ptr;
|
||||
}
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)
|
||||
486
source/mijin/memory/stack_allocator.hpp
Normal file
486
source/mijin/memory/stack_allocator.hpp
Normal file
@@ -0,0 +1,486 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED)
|
||||
#define MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/align.hpp"
|
||||
#include "../util/concepts.hpp"
|
||||
#include "../util/traits.hpp"
|
||||
|
||||
#if !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
|
||||
#if defined(MIJIN_DEBUG)
|
||||
#define MIJIN_STACK_ALLOCATOR_DEBUG 1
|
||||
#else
|
||||
#define MIJIN_STACK_ALLOCATOR_DEBUG 0
|
||||
#endif
|
||||
#endif // !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
|
||||
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
#include <print>
|
||||
#include <unordered_map>
|
||||
#include "../debug/stacktrace.hpp"
|
||||
#endif
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TValue, typename TStackAllocator>
|
||||
class StlStackAllocator
|
||||
{
|
||||
public:
|
||||
using value_type = TValue;
|
||||
private:
|
||||
TStackAllocator* base_;
|
||||
public:
|
||||
explicit StlStackAllocator(TStackAllocator& base) MIJIN_NOEXCEPT : base_(&base) {}
|
||||
template<typename TOtherValue>
|
||||
StlStackAllocator(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT : base_(other.base_) {}
|
||||
|
||||
template<typename TOtherValue>
|
||||
StlStackAllocator& operator=(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
base_ = other.base_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator<=>(const StlStackAllocator&) const noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
TValue* allocate(std::size_t count) const
|
||||
{
|
||||
void* result = base_->allocate(alignof(TValue), count * sizeof(TValue));
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
++base_->numAllocations_;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
base_->activeAllocations_.emplace(result, captureStacktrace(1));
|
||||
#endif
|
||||
return static_cast<TValue*>(result);
|
||||
}
|
||||
|
||||
void deallocate([[maybe_unused]] TValue* ptr, std::size_t /* count */) const MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
MIJIN_ASSERT(base_->numAllocations_ > 0, "Unbalanced allocations in stack allocators!");
|
||||
--base_->numAllocations_;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
auto it = base_->activeAllocations_.find(ptr);
|
||||
if (it != base_->activeAllocations_.end())
|
||||
{
|
||||
base_->activeAllocations_.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
MIJIN_ERROR("Deallocating invalid pointer from StackAllocator.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename TOtherValue, typename TOtherAllocator>
|
||||
friend class StlStackAllocator;
|
||||
};
|
||||
|
||||
namespace impl
|
||||
{
|
||||
struct StackAllocatorSnapshotData
|
||||
{
|
||||
struct ChunkSnapshot
|
||||
{
|
||||
std::size_t allocated;
|
||||
};
|
||||
std::size_t numChunks;
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
std::size_t numAllocations_ = 0;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
// just for debugging, so we don't care what memory this uses...
|
||||
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
|
||||
#endif
|
||||
ChunkSnapshot chunks[1]; // may be bigger than that
|
||||
};
|
||||
}
|
||||
|
||||
class StackAllocatorSnapshot
|
||||
{
|
||||
private:
|
||||
impl::StackAllocatorSnapshotData* data = nullptr;
|
||||
|
||||
impl::StackAllocatorSnapshotData* operator->() const MIJIN_NOEXCEPT { return data; }
|
||||
|
||||
|
||||
template<std::size_t chunkSize, template<typename> typename TBacking> requires (allocator_tmpl<TBacking>)
|
||||
friend class StackAllocator;
|
||||
};
|
||||
|
||||
template<typename TStackAllocator>
|
||||
class StackAllocatorScope
|
||||
{
|
||||
private:
|
||||
TStackAllocator* allocator_;
|
||||
StackAllocatorSnapshot snapshot_;
|
||||
public:
|
||||
explicit StackAllocatorScope(TStackAllocator& allocator) : allocator_(&allocator), snapshot_(allocator_->createSnapshot()) {}
|
||||
StackAllocatorScope(const StackAllocatorScope&) = delete;
|
||||
StackAllocatorScope(StackAllocatorScope&&) = delete;
|
||||
~StackAllocatorScope() MIJIN_NOEXCEPT
|
||||
{
|
||||
allocator_->restoreSnapshot(snapshot_);
|
||||
}
|
||||
|
||||
StackAllocatorScope& operator=(const StackAllocatorScope&) = delete;
|
||||
StackAllocatorScope& operator=(StackAllocatorScope&&) = delete;
|
||||
};
|
||||
|
||||
template<std::size_t chunkSize = 4096, template<typename> typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl<TBacking>)
|
||||
class StackAllocator
|
||||
{
|
||||
public:
|
||||
using backing_t = TBacking<void>;
|
||||
static constexpr std::size_t ACTUAL_CHUNK_SIZE = chunkSize - sizeof(void*) - sizeof(std::size_t);
|
||||
|
||||
template<typename T>
|
||||
using stl_allocator_t = StlStackAllocator<T, StackAllocator<chunkSize, TBacking>>;
|
||||
private:
|
||||
struct Chunk
|
||||
{
|
||||
std::array<std::byte, ACTUAL_CHUNK_SIZE> data;
|
||||
Chunk* next;
|
||||
std::size_t allocated;
|
||||
};
|
||||
[[no_unique_address]] TBacking<Chunk> backing_;
|
||||
Chunk* firstChunk_ = nullptr;
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
std::size_t numAllocations_ = 0;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
// just for debugging, so we don't care what memory this uses...
|
||||
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
|
||||
#endif
|
||||
public:
|
||||
StackAllocator() MIJIN_NOEXCEPT_IF(std::is_nothrow_default_constructible_v<backing_t>) = default;
|
||||
explicit StackAllocator(backing_t backing) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TBacking<Chunk>, backing_t&&>))
|
||||
: backing_(std::move(backing)) {}
|
||||
StackAllocator(const StackAllocator&) = delete;
|
||||
StackAllocator(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TBacking<Chunk>>)
|
||||
: firstChunk_(std::exchange(other.firstChunk_, nullptr)) {}
|
||||
~StackAllocator() noexcept
|
||||
{
|
||||
Chunk* chunk = firstChunk_;
|
||||
while (chunk != nullptr)
|
||||
{
|
||||
Chunk* nextChunk = firstChunk_->next;
|
||||
backing_.deallocate(chunk, 1);
|
||||
chunk = nextChunk;
|
||||
}
|
||||
}
|
||||
StackAllocator& operator=(const StackAllocator&) = delete;
|
||||
StackAllocator& operator=(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v<TBacking<Chunk>>)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
backing_ = std::move(other.backing_);
|
||||
firstChunk_ = std::exchange(other.firstChunk_, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void* allocate(std::size_t alignment, std::size_t size)
|
||||
{
|
||||
// first check if this can ever fit
|
||||
if (size > ACTUAL_CHUNK_SIZE)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// then try to find space in the current chunks
|
||||
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
|
||||
{
|
||||
const std::size_t remaining = ACTUAL_CHUNK_SIZE - chunk->allocated;
|
||||
if (remaining < size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::byte* start = &chunk->data[chunk->allocated];
|
||||
std::byte* pos = mijin::alignUp(start, alignment);
|
||||
|
||||
const std::ptrdiff_t alignmentBytes = pos - start;
|
||||
const std::size_t combinedSize = size + alignmentBytes;
|
||||
|
||||
if (remaining < combinedSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
chunk->allocated += combinedSize;
|
||||
return pos;
|
||||
}
|
||||
|
||||
// no free space in any chunk? allocate a new one
|
||||
Chunk* newChunk = backing_.allocate(1);
|
||||
if (newChunk == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
initAndAddChunk(newChunk);
|
||||
|
||||
// now try with the new chunk
|
||||
std::byte* start = newChunk->data.data();
|
||||
std::byte* pos = mijin::alignUp(start, alignment);
|
||||
|
||||
const std::ptrdiff_t alignmentBytes = pos - start;
|
||||
const std::size_t combinedSize = size + alignmentBytes;
|
||||
|
||||
// doesn't fit (due to alignment), time to give up
|
||||
if (ACTUAL_CHUNK_SIZE < combinedSize)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
newChunk->allocated = combinedSize;
|
||||
return pos;
|
||||
}
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
MIJIN_ASSERT(numAllocations_ == 0, "Missing deallocation in StackAllocator!");
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
if (!activeAllocations_.empty())
|
||||
{
|
||||
std::println(stderr, "{} active allocations in StackAllocator when resetting!", activeAllocations_.size());
|
||||
for (const auto& [ptr, stack] : activeAllocations_)
|
||||
{
|
||||
if (stack.isError())
|
||||
{
|
||||
std::println(stderr, "at {}, no stacktrace ({})", ptr, stack.getError().message);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println(stderr, "at 0x{}:\n{}", ptr, stack.getValue());
|
||||
}
|
||||
}
|
||||
MIJIN_TRAP();
|
||||
}
|
||||
#endif
|
||||
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
|
||||
{
|
||||
chunk->allocated = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool createChunks(std::size_t count)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Chunk* newChunks = backing_.allocate(count);
|
||||
if (newChunks == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// reverse so the chunks are chained from 0 to count (new chunks are inserted in front)
|
||||
for (std::size_t pos = count; pos > 0; --pos)
|
||||
{
|
||||
initAndAddChunk(&newChunks[pos-1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
stl_allocator_t<T> makeStlAllocator() MIJIN_NOEXCEPT
|
||||
{
|
||||
return stl_allocator_t<T>(*this);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t getNumChunks() const MIJIN_NOEXCEPT
|
||||
{
|
||||
std::size_t num = 0;
|
||||
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
|
||||
{
|
||||
++num;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
StackAllocatorSnapshot createSnapshot()
|
||||
{
|
||||
if (firstChunk_ == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
using impl::StackAllocatorSnapshotData;
|
||||
|
||||
std::size_t numChunks = getNumChunks();
|
||||
std::size_t snapshotSize = calcSnapshotSize(numChunks);
|
||||
Chunk* prevFirst = firstChunk_;
|
||||
StackAllocatorSnapshotData* snapshotData = static_cast<StackAllocatorSnapshotData*>(allocate(alignof(StackAllocatorSnapshotData), snapshotSize));
|
||||
if (snapshotData == nullptr)
|
||||
{
|
||||
// couldn't allocate the snapshot
|
||||
return {};
|
||||
}
|
||||
::new (snapshotData) StackAllocatorSnapshotData;
|
||||
StackAllocatorSnapshot snapshot;
|
||||
snapshot.data = snapshotData;
|
||||
if (firstChunk_ != prevFirst)
|
||||
{
|
||||
// new chunk has been added, adjust the snapshot
|
||||
// the snapshot must be inside the new chunk (but not necessarily at the very beginning, due to alignment)
|
||||
MIJIN_ASSERT(static_cast<const void*>(snapshot.data) == alignUp(firstChunk_->data.data(), alignof(StackAllocatorSnapshot)), "Snapshot not where it was expected.");
|
||||
|
||||
// a chunk might be too small for the snapshot if we grow it (unlikely, but not impossible)
|
||||
if (ACTUAL_CHUNK_SIZE - firstChunk_->allocated < MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot)) [[unlikely]]
|
||||
{
|
||||
firstChunk_->allocated = 0;
|
||||
return {};
|
||||
}
|
||||
|
||||
// looking good, adjust the numbers
|
||||
++numChunks;
|
||||
snapshotSize += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
|
||||
firstChunk_->allocated += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
|
||||
}
|
||||
// now fill out the struct
|
||||
snapshot->numChunks = numChunks;
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
snapshot->numAllocations_ = numAllocations_;
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
// just for debugging, so we don't care what memory this uses...
|
||||
snapshot->activeAllocations_ = activeAllocations_;
|
||||
#endif
|
||||
|
||||
std::size_t pos = 0;
|
||||
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next, ++pos)
|
||||
{
|
||||
snapshot->chunks[pos].allocated = chunk->allocated;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void restoreSnapshot(StackAllocatorSnapshot snapshot) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (snapshot.data == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t numChunks = getNumChunks();
|
||||
MIJIN_ASSERT_FATAL(snapshot->numChunks <= numChunks, "Snapshot contains more chunks than the allocator!");
|
||||
|
||||
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||
MIJIN_ASSERT(snapshot->numAllocations_ >= numAllocations_, "Missing deallocation in StackAllocator!");
|
||||
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||
// TODO: compare and print changes
|
||||
unsigned numMismatches = 0;
|
||||
for (const auto& [ptr, stack] : activeAllocations_)
|
||||
{
|
||||
if (snapshot->activeAllocations_.contains(ptr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
++numMismatches;
|
||||
|
||||
if (stack.isError())
|
||||
{
|
||||
std::println(stderr, "Missing deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println(stderr, "Missing deallocation at 0x{}:\n{}", ptr, stack.getValue());
|
||||
}
|
||||
}
|
||||
#if 0 // deallocating more than expected shouldn't be a problem
|
||||
for (const auto& [ptr, stack] : snapshot->activeAllocations_)
|
||||
{
|
||||
if (activeAllocations_.contains(ptr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
++numMismatches;
|
||||
|
||||
if (stack.isError())
|
||||
{
|
||||
std::println(stderr, "Unexpected deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println(stderr, "Unexpected deallocation at 0x{}:\n{}", ptr, stack.getValue());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (numMismatches > 0)
|
||||
{
|
||||
std::println(stderr, "{} mismatched deallocations when restoring stack allocator snapshot.", numMismatches);
|
||||
MIJIN_TRAP();
|
||||
}
|
||||
#endif
|
||||
// if we allocated new chunks since the snapshot, these are completely empty now
|
||||
const std::size_t emptyChunks = numChunks - snapshot->numChunks;
|
||||
|
||||
Chunk* chunk = firstChunk_;
|
||||
for (std::size_t idx = 0; idx < emptyChunks; ++idx)
|
||||
{
|
||||
chunk->allocated = 0;
|
||||
chunk = chunk->next;
|
||||
}
|
||||
|
||||
// the other values are in the snapshot
|
||||
for (std::size_t idx = 0; idx < snapshot->numChunks; ++idx)
|
||||
{
|
||||
chunk->allocated = snapshot->chunks[idx].allocated;
|
||||
chunk = chunk->next;
|
||||
}
|
||||
MIJIN_ASSERT(chunk == nullptr, "Something didn't add up.");
|
||||
|
||||
// finally free the space for the snapshot itself
|
||||
Chunk* snapshotChunk = findChunk(snapshot.data);
|
||||
MIJIN_ASSERT_FATAL(snapshotChunk != nullptr, "Snapshot not in chunks?");
|
||||
snapshotChunk->allocated -= calcSnapshotSize(snapshot->numChunks); // note: this might miss the alignment bytes of the snapshot, but that should be fine
|
||||
snapshot.data->~StackAllocatorSnapshotData();
|
||||
}
|
||||
private:
|
||||
void initAndAddChunk(Chunk* newChunk) noexcept
|
||||
{
|
||||
::new (newChunk) Chunk();
|
||||
|
||||
// put it in the front
|
||||
newChunk->next = firstChunk_;
|
||||
firstChunk_ = newChunk;
|
||||
}
|
||||
|
||||
bool isInChunk(const void* address, const Chunk& chunk) const MIJIN_NOEXCEPT
|
||||
{
|
||||
const std::byte* asByte = static_cast<const std::byte*>(address);
|
||||
return asByte >= chunk.data.data() && asByte < chunk.data.data() + ACTUAL_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
Chunk* findChunk(const void* address) const MIJIN_NOEXCEPT
|
||||
{
|
||||
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
|
||||
{
|
||||
if (isInChunk(address, *chunk))
|
||||
{
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::size_t calcSnapshotSize(std::size_t numChunks) MIJIN_NOEXCEPT
|
||||
{
|
||||
return sizeof(impl::StackAllocatorSnapshotData) + ((numChunks - 1) * MIJIN_STRIDEOF(impl::StackAllocatorSnapshotData::ChunkSnapshot));
|
||||
}
|
||||
|
||||
template<typename TValue, typename TStackAllocator>
|
||||
friend class StlStackAllocator;
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED)
|
||||
56
source/mijin/memory/virtual_allocator.hpp
Normal file
56
source/mijin/memory/virtual_allocator.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED)
|
||||
#define MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED 1
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/annot.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename T>
|
||||
class VirtualAllocator
|
||||
{
|
||||
public:
|
||||
virtual ~VirtualAllocator() noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual owner_t<T*> allocate(std::size_t count) noexcept;
|
||||
virtual void deallocate(owner_t<T*> ptr, std::size_t count) noexcept;
|
||||
};
|
||||
|
||||
template<typename T, typename TImpl>
|
||||
class WrappedVirtualAllocator : public VirtualAllocator<T>
|
||||
{
|
||||
private:
|
||||
[[no_unique_address]] TImpl mImpl;
|
||||
public:
|
||||
explicit constexpr WrappedVirtualAllocator(TImpl impl = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TImpl>)
|
||||
: mImpl(std::move(impl)) {}
|
||||
constexpr WrappedVirtualAllocator(const WrappedVirtualAllocator&) = default;
|
||||
constexpr WrappedVirtualAllocator(WrappedVirtualAllocator&&) = default;
|
||||
|
||||
WrappedVirtualAllocator& operator=(const WrappedVirtualAllocator&) = default;
|
||||
WrappedVirtualAllocator& operator=(WrappedVirtualAllocator&&) = default;
|
||||
|
||||
[[nodiscard]]
|
||||
owner_t<T*> allocate(std::size_t count) noexcept override
|
||||
{
|
||||
return mImpl.allocate(count);
|
||||
}
|
||||
|
||||
void deallocate(owner_t<T*> ptr, std::size_t count) noexcept override
|
||||
{
|
||||
mImpl.deallocate(ptr, count);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
WrappedVirtualAllocator<typename T::value_type, T> makeVirtualAllocator(T allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
return WrappedVirtualAllocator<typename T::value_type, T>(std::move(allocator));
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_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;
|
||||
BaseTypelessBuffer<std::allocator> body;
|
||||
};
|
||||
|
||||
struct HTTPResponse
|
||||
{
|
||||
HTTPVersion version;
|
||||
unsigned status;
|
||||
std::string statusMessage;
|
||||
std::multimap<std::string, std::string> headers;
|
||||
BaseTypelessBuffer<std::allocator> 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 = MIJIN_DEFAULT_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, TAllocator allocator = {}) : URLBase(string_t(base.begin(), base.end(), std::move(allocator))) {}
|
||||
constexpr URLBase(const TChar* base, TAllocator allocator = {}) : URLBase(string_t(base, std::move(allocator))) {}
|
||||
|
||||
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,40 +32,74 @@ 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_); }
|
||||
};
|
||||
|
||||
namespace impl
|
||||
{
|
||||
struct ResultSuccess {};
|
||||
}
|
||||
|
||||
template<typename TError>
|
||||
class ResultBase<void, TError> : public ResultBase<impl::ResultSuccess, TError>
|
||||
{
|
||||
public:
|
||||
ResultBase() MIJIN_NOEXCEPT : ResultBase<impl::ResultSuccess, TError>(impl::ResultSuccess{}) {}
|
||||
ResultBase(const ResultBase&) MIJIN_NOEXCEPT = default;
|
||||
ResultBase(ResultBase&&) MIJIN_NOEXCEPT = default;
|
||||
ResultBase(TError errorValue) MIJIN_NOEXCEPT : ResultBase<impl::ResultSuccess, TError>(std::move(errorValue)) {}
|
||||
|
||||
ResultBase& operator=(const ResultBase&) = default;
|
||||
ResultBase& operator=(ResultBase&&) = default;
|
||||
|
||||
impl::ResultSuccess& operator*() MIJIN_NOEXCEPT = delete;
|
||||
const impl::ResultSuccess& operator*() const MIJIN_NOEXCEPT = delete;
|
||||
impl::ResultSuccess* operator->() MIJIN_NOEXCEPT = delete;
|
||||
const impl::ResultSuccess* operator->() const MIJIN_NOEXCEPT = delete;
|
||||
|
||||
[[nodiscard]] impl::ResultSuccess& getValue() MIJIN_NOEXCEPT = delete;
|
||||
[[nodiscard]] const impl::ResultSuccess& getValue() const MIJIN_NOEXCEPT = delete;
|
||||
};
|
||||
|
||||
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>
|
||||
template<typename TSuccess = void>
|
||||
using Result = ResultBase<TSuccess, ResultError>;
|
||||
|
||||
//
|
||||
|
||||
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,14 @@
|
||||
#if !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_ALIGN_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#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 +20,13 @@ constexpr T alignUp(T value, T alignTo) noexcept
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* alignUp(T* pointer, std::uintptr_t alignTo) MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::bit_cast<T*>(alignUp(std::bit_cast<std::uintptr_t>(pointer), alignTo));
|
||||
}
|
||||
|
||||
#define MIJIN_STRIDEOF(T) mijin::alignUp(sizeof(T), alignof(T))
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)
|
||||
182
source/mijin/util/annot.hpp
Normal file
182
source/mijin/util/annot.hpp
Normal file
@@ -0,0 +1,182 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_ANNOT_HPP_INCLUDED 1
|
||||
|
||||
#include <utility>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
#if !defined(__has_include)
|
||||
#define __has_include(x) (false)
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_USE_GSL)
|
||||
#if __has_include(<gsl/gsl>)
|
||||
#define MIJIN_USE_GSL 1
|
||||
#else
|
||||
#define MIJIN_USE_GSL 0
|
||||
#endif
|
||||
#endif // !defined(MIJIN_USE_GSL)
|
||||
|
||||
#include <concepts>
|
||||
#include "./concepts.hpp"
|
||||
|
||||
#if MIJIN_USE_GSL
|
||||
#include <gsl/gsl>
|
||||
#endif
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename T>
|
||||
concept nullable_type = !std::is_same_v<T, std::nullptr_t> && requires(T t) { t == nullptr; };
|
||||
|
||||
template<nullable_type T>
|
||||
class NotNullable
|
||||
{
|
||||
private:
|
||||
T base_;
|
||||
public:
|
||||
template<typename U> requires(std::is_same_v<T, U> && std::is_copy_constructible_v<T>)
|
||||
constexpr NotNullable(U base) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
|
||||
: base_(base)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
|
||||
// some compilers apparently need this since they are unable to do proper pattern matching ...
|
||||
NotNullable(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
|
||||
NotNullable(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
|
||||
|
||||
template<typename TOther> requires(std::is_constructible_v<T, const TOther&>)
|
||||
constexpr NotNullable(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, const TOther&>))
|
||||
: base_(other.base_)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
template<typename TOther> requires(std::is_constructible_v<T, TOther&&>)
|
||||
constexpr NotNullable(NotNullable<TOther>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TOther&&>))
|
||||
: base_(std::exchange(other.base_, nullptr))
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
template<typename TArg, typename... TArgs> requires(!std::is_same_v<TArg, std::nullptr_t>
|
||||
&& (!std::is_same_v<TArg, T> && sizeof...(TArgs) == 0)
|
||||
&& std::is_constructible_v<T, TArg&&, TArgs&&...>)
|
||||
constexpr NotNullable(TArg&& arg, TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArg&&, TArgs&&...>))
|
||||
: base_(std::forward<TArg>(arg), std::forward<TArgs>(args)...)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(T&& base) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
requires(std::is_move_constructible_v<T>)
|
||||
: base_(std::move(base))
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
|
||||
requires(std::is_copy_constructible_v<T>)
|
||||
: base_(other.base_)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
requires(std::is_move_constructible_v<T>)
|
||||
: base_(std::exchange(other.base_, nullptr))
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
|
||||
// some compilers apparently need this since they are unable to do proper pattern matching ...
|
||||
NotNullable& operator=(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
|
||||
NotNullable& operator=(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
|
||||
|
||||
constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_assignable_v<T>)
|
||||
requires(std::is_copy_assignable_v<T>)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->base_ = other.base_;
|
||||
}
|
||||
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr NotNullable& operator=(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v<T>)
|
||||
requires(std::is_move_assignable_v<T>)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->base_ = std::exchange(other.base_, nullptr);
|
||||
}
|
||||
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr NotNullable& operator=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
|
||||
template<std::equality_comparable_with<T> TOther>
|
||||
bool operator==(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
|
||||
{
|
||||
return base_ == other.base_;
|
||||
}
|
||||
|
||||
template<std::equality_comparable_with<T> TOther>
|
||||
bool operator!=(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
|
||||
{
|
||||
return base_ != other.base_;
|
||||
}
|
||||
|
||||
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
|
||||
bool operator==(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
|
||||
{
|
||||
return base_ == other;
|
||||
}
|
||||
|
||||
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
|
||||
bool operator!=(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
|
||||
{
|
||||
return base_ != other;
|
||||
}
|
||||
|
||||
bool operator==(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
bool operator!=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
|
||||
constexpr operator const T&() const MIJIN_NOEXCEPT { return get(); }
|
||||
constexpr operator std::nullptr_t() const MIJIN_DELETE("Type is not nullable.");
|
||||
constexpr const T& operator->() const MIJIN_NOEXCEPT { return get(); }
|
||||
constexpr decltype(auto) operator*() const MIJIN_NOEXCEPT_IF(noexcept(*get())) { return *get(); }
|
||||
|
||||
NotNullable& operator++() MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable& operator--() MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable operator++(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable operator--(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable& operator+=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable& operator-=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
void operator[](std::ptrdiff_t) const MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const T& get() const MIJIN_NOEXCEPT { return base_; }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr T release() && MIJIN_NOEXCEPT { return std::exchange(base_, nullptr); }
|
||||
|
||||
template<nullable_type TOther>
|
||||
friend class NotNullable;
|
||||
};
|
||||
|
||||
#if MIJIN_USE_GSL
|
||||
template<mijin::pointer_type T>
|
||||
using owner_t = gsl::owner<T>;
|
||||
#else
|
||||
template<mijin::pointer_type T>
|
||||
using owner_t = T;
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
using not_null_t = NotNullable<T>;
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)
|
||||
55
source/mijin/util/ansi_colors.hpp
Normal file
55
source/mijin/util/ansi_colors.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED 1
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
|
||||
struct BaseAnsiFontEffects
|
||||
{
|
||||
using char_t = TChar;
|
||||
|
||||
static constexpr const char_t* RESET = MIJIN_SMART_QUOTE(char_t, "0");
|
||||
|
||||
static constexpr const char_t* FG_BLACK = MIJIN_SMART_QUOTE(char_t, "30");
|
||||
static constexpr const char_t* FG_RED = MIJIN_SMART_QUOTE(char_t, "31");
|
||||
static constexpr const char_t* FG_GREEN = MIJIN_SMART_QUOTE(char_t, "32");
|
||||
static constexpr const char_t* FG_YELLOW = MIJIN_SMART_QUOTE(char_t, "33");
|
||||
static constexpr const char_t* FG_BLUE = MIJIN_SMART_QUOTE(char_t, "34");
|
||||
static constexpr const char_t* FG_MAGENTA = MIJIN_SMART_QUOTE(char_t, "35");
|
||||
static constexpr const char_t* FG_CYAN = MIJIN_SMART_QUOTE(char_t, "36");
|
||||
static constexpr const char_t* FG_WHITE = MIJIN_SMART_QUOTE(char_t, "37");
|
||||
static constexpr const char_t* FG_BRIGHT_BLACK = MIJIN_SMART_QUOTE(char_t, "90");
|
||||
static constexpr const char_t* FG_BRIGHT_RED = MIJIN_SMART_QUOTE(char_t, "91");
|
||||
static constexpr const char_t* FG_BRIGHT_GREEN = MIJIN_SMART_QUOTE(char_t, "92");
|
||||
static constexpr const char_t* FG_BRIGHT_YELLOW = MIJIN_SMART_QUOTE(char_t, "93");
|
||||
static constexpr const char_t* FG_BRIGHT_BLUE = MIJIN_SMART_QUOTE(char_t, "94");
|
||||
static constexpr const char_t* FG_BRIGHT_MAGENTA = MIJIN_SMART_QUOTE(char_t, "95");
|
||||
static constexpr const char_t* FG_BRIGHT_CYAN = MIJIN_SMART_QUOTE(char_t, "96");
|
||||
static constexpr const char_t* FG_BRIGHT_WHITE = MIJIN_SMART_QUOTE(char_t, "97");
|
||||
|
||||
static constexpr const char_t* BG_BLACK = MIJIN_SMART_QUOTE(char_t, "40");
|
||||
static constexpr const char_t* BG_RED = MIJIN_SMART_QUOTE(char_t, "41");
|
||||
static constexpr const char_t* BG_GREEN = MIJIN_SMART_QUOTE(char_t, "42");
|
||||
static constexpr const char_t* BG_YELLOW = MIJIN_SMART_QUOTE(char_t, "43");
|
||||
static constexpr const char_t* BG_BLUE = MIJIN_SMART_QUOTE(char_t, "44");
|
||||
static constexpr const char_t* BG_MAGENTA = MIJIN_SMART_QUOTE(char_t, "45");
|
||||
static constexpr const char_t* BG_CYAN = MIJIN_SMART_QUOTE(char_t, "46");
|
||||
static constexpr const char_t* BG_WHITE = MIJIN_SMART_QUOTE(char_t, "47");
|
||||
static constexpr const char_t* BG_BRIGHT_BLACK = MIJIN_SMART_QUOTE(char_t, "100");
|
||||
static constexpr const char_t* BG_BRIGHT_RED = MIJIN_SMART_QUOTE(char_t, "101");
|
||||
static constexpr const char_t* BG_BRIGHT_GREEN = MIJIN_SMART_QUOTE(char_t, "102");
|
||||
static constexpr const char_t* BG_BRIGHT_YELLOW = MIJIN_SMART_QUOTE(char_t, "103");
|
||||
static constexpr const char_t* BG_BRIGHT_BLUE = MIJIN_SMART_QUOTE(char_t, "104");
|
||||
static constexpr const char_t* BG_BRIGHT_MAGENTA = MIJIN_SMART_QUOTE(char_t, "105");
|
||||
static constexpr const char_t* BG_BRIGHT_CYAN = MIJIN_SMART_QUOTE(char_t, "106");
|
||||
static constexpr const char_t* BG_BRIGHT_WHITE = MIJIN_SMART_QUOTE(char_t, "107");
|
||||
};
|
||||
MIJIN_DEFINE_CHAR_VERSIONS(AnsiFontEffects)
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED)
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
@@ -32,8 +33,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,9 @@ namespace mijin
|
||||
template<typename TBits>
|
||||
struct BitFlags
|
||||
{
|
||||
using bits_t = TBits;
|
||||
static constexpr bool ENABLE_TO_INT = false;
|
||||
|
||||
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 +72,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 +84,55 @@ 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
|
||||
{
|
||||
// If a BitFlags object contains padding (number of defined bits < number of bits in type), the bits in the padding might not be 0.
|
||||
// In that case bit_casting will produce an incorrect value.
|
||||
// Filling the gaps with padding bits fixes this, but is unfortunately quite error prone :/.
|
||||
static_assert(T::ENABLE_TO_INT, "bitFlagsToInt not enabled for this type. Make sure the number of bits defined is the same as the number of bits in the type and define ENABLE_TO_INT to true.");
|
||||
|
||||
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)
|
||||
@@ -5,6 +5,7 @@
|
||||
#define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1
|
||||
|
||||
#include <type_traits>
|
||||
#include "./traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -39,6 +40,32 @@ concept pointer_type = std::is_pointer_v<T>;
|
||||
template<typename T>
|
||||
concept reference_type = std::is_reference_v<T>;
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<typename T>
|
||||
using pointer_t = typename T::pointer;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
concept allocator_type = requires(T alloc, typename T::value_type value, detect_or_t<typename T::value_type*, impl::pointer_t, T> pointer, int count)
|
||||
{
|
||||
typename T::value_type;
|
||||
{ alloc.allocate(count) } -> std::same_as<decltype(pointer)>;
|
||||
{ alloc.deallocate(pointer, count) } -> std::same_as<void>;
|
||||
} && !std::is_const_v<typename T::value_type> && !std::is_volatile_v<typename T::value_type>;
|
||||
|
||||
template<typename T, typename TOther>
|
||||
concept allocator_type_for = allocator_type<T> && std::is_same_v<typename T::value_type, TOther>;
|
||||
|
||||
template<template<typename> typename T>
|
||||
concept allocator_tmpl = allocator_type<T<int>>;
|
||||
|
||||
template<typename T, typename TData>
|
||||
concept deleter_type = requires(T deleter, TData* ptr)
|
||||
{
|
||||
deleter(ptr);
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
151
source/mijin/util/exception.hpp
Normal file
151
source/mijin/util/exception.hpp
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef MIJIN_UTIL_EXCEPTION_HPP_INCLUDED
|
||||
#define MIJIN_UTIL_EXCEPTION_HPP_INCLUDED 1
|
||||
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "../detect.hpp"
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
void walkException(const std::exception& exc, TFunc func)
|
||||
{
|
||||
func(exc);
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
void walkException(const mijin::Exception& exc, TFunc func)
|
||||
{
|
||||
func(exc);
|
||||
walkExceptionCause(exc.getCause(), func);
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
template<typename TChar>
|
||||
struct std::formatter<mijin::Exception, TChar>
|
||||
{
|
||||
using char_t = TChar;
|
||||
|
||||
template<class TContext>
|
||||
constexpr TContext::iterator parse(TContext& ctx)
|
||||
{
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
|
||||
if (it != end && *it != MIJIN_SMART_QUOTE(char_t, '}'))
|
||||
{
|
||||
throw std::format_error("invalid format");
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename TContext>
|
||||
TContext::iterator format(const mijin::Exception& exception, TContext& ctx) const
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
auto it = ctx.out();
|
||||
bool first = true;
|
||||
mijin::walkException(exception, [&]<typename T>(const T& exc)
|
||||
{
|
||||
if constexpr (!std::is_same_v<T, std::nullptr_t>)
|
||||
{
|
||||
if (!first) {
|
||||
it = std::ranges::copy(MIJIN_SMART_QUOTE(char_t, "\nCaused by:\n"sv), it).out;
|
||||
}
|
||||
first = false;
|
||||
#if MIJIN_RTTI
|
||||
it = std::ranges::copy(std::basic_string_view(typeid(exc).name()), it).out;
|
||||
it = std::ranges::copy(MIJIN_SMART_QUOTE(char_t, ": "sv), it).out;
|
||||
#endif
|
||||
it = std::ranges::copy(std::basic_string_view(exc.what()), it).out;
|
||||
if constexpr (std::is_same_v<T, mijin::Exception>)
|
||||
{
|
||||
if (const mijin::Result<mijin::Stacktrace>& trace = exc.getStacktrace(); trace.isSuccess())
|
||||
{
|
||||
*it = MIJIN_SMART_QUOTE(char_t, '\n');
|
||||
++it;
|
||||
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "{}"), trace.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
it = std::ranges::copy(MIJIN_SMART_QUOTE(char_t, "<unknown exception>"sv), it).out;
|
||||
}
|
||||
});
|
||||
return it;
|
||||
}
|
||||
};
|
||||
#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);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#define MIJIN_UTIL_HASH_HPP_INCLUDED 1
|
||||
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -16,4 +18,21 @@ inline void hashCombine(std::size_t& seed, const T& value, const THasher& hasher
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
struct std::hash<std::tuple<T...>>
|
||||
{
|
||||
std::size_t operator()(const std::tuple<T...>& tuple) const noexcept
|
||||
{
|
||||
return hashImpl(tuple, std::index_sequence_for<T...>());
|
||||
}
|
||||
|
||||
template<std::size_t... indices>
|
||||
std::size_t hashImpl(const std::tuple<T...>& tuple, std::index_sequence<indices...>) const noexcept
|
||||
{
|
||||
std::size_t result = 0;
|
||||
(mijin::hashCombine(result, std::get<indices>(tuple)), ...);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // MIJIN_UTIL_HASH_HPP_INCLUDED
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
42
source/mijin/util/misc.hpp
Normal file
42
source/mijin/util/misc.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_MISC_HPP_INCLUDED 1
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
template<auto V, typename T>
|
||||
constexpr decltype(auto) idValue(T&& value)
|
||||
{
|
||||
return std::forward<T>(value);
|
||||
}
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<typename T, typename... TArgs>
|
||||
struct ConstructArrayHelper
|
||||
{
|
||||
template<std::size_t... I>
|
||||
static constexpr std::array<T, sizeof...(I)> construct(const TArgs&... args, std::index_sequence<I...>)
|
||||
{
|
||||
return {idValue<I>(T(args...))...};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, std::size_t count, typename... TArgs>
|
||||
constexpr std::array<T, count> constructArray(const TArgs&... args)
|
||||
{
|
||||
return impl::ConstructArrayHelper<T, TArgs...>::construct(args..., std::make_index_sequence<count>());
|
||||
}
|
||||
}
|
||||
#endif // !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)
|
||||
@@ -2,13 +2,19 @@
|
||||
#include "os.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include "../detect.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
// TODO
|
||||
#include <array>
|
||||
#include <malloc.h>
|
||||
#include <windows.h>
|
||||
#include "../util/winundef.hpp"
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@@ -32,6 +38,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 +53,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";
|
||||
@@ -76,4 +142,40 @@ void setCurrentThreadName(const char* threadName) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
return _aligned_malloc(size, alignment);
|
||||
#else
|
||||
return std::aligned_alloc(alignment, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
return _aligned_realloc(ptr, size, alignment);
|
||||
#else
|
||||
void* newPtr = std::realloc(ptr, size);
|
||||
if (newPtr == ptr || (std::bit_cast<std::uintptr_t>(newPtr) % alignment) == 0)
|
||||
{
|
||||
return newPtr;
|
||||
}
|
||||
// bad luck, have to copy a second time
|
||||
void* newPtr2 = std::aligned_alloc(alignment, size);
|
||||
std::memcpy(newPtr2, newPtr, size);
|
||||
std::free(newPtr);
|
||||
return newPtr2;
|
||||
#endif
|
||||
}
|
||||
|
||||
void alignedFree(void* ptr)
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
std::free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
@@ -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,81 @@ 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;
|
||||
|
||||
[[nodiscard]] void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
|
||||
void alignedFree(void* ptr);
|
||||
|
||||
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 = {};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,9 @@ namespace mijin
|
||||
// public types
|
||||
//
|
||||
|
||||
struct Type_; // use as typevar
|
||||
struct Any_; // use as placeholder in templates
|
||||
|
||||
template<typename T>
|
||||
struct always_false
|
||||
{
|
||||
@@ -95,11 +98,88 @@ struct map_template {
|
||||
using type_t = TTemplate<TPredicate<T>>;
|
||||
};
|
||||
|
||||
template<typename T, typename... Types>
|
||||
struct is_any_type : std::disjunction<std::is_same<T, Types>...> {};
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
struct is_template_instance : std::false_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename... TArgs>
|
||||
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<template<typename...> typename TTemplate, typename... TArgsTTmpl>
|
||||
struct tmpl_param_comparator
|
||||
{
|
||||
template<typename TTmpl, typename TInstance>
|
||||
static constexpr bool compare_single = std::is_same_v<TTmpl, TInstance> or std::is_same_v<TTmpl, Any_>;
|
||||
|
||||
template<typename T>
|
||||
struct matches : std::false_type {};
|
||||
|
||||
// original (which MSVC didn't like for some reason :/)
|
||||
// template<typename... TArgsInstance>
|
||||
// struct matches<TTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
|
||||
|
||||
template<template<typename...> typename TOtherTemplate, typename... TArgsInstance> requires(is_template_instance_v<TTemplate, TOtherTemplate<TArgsInstance...>>)
|
||||
struct matches<TOtherTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
|
||||
|
||||
template<typename T>
|
||||
using matches_t = matches<T>;
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TTemplate, typename TType>
|
||||
struct match_template_type : std::false_type {};
|
||||
|
||||
template<template<typename...> typename TTemplate, typename TType, typename... TArgs>
|
||||
struct match_template_type<TTemplate<TArgs...>, TType> : impl::tmpl_param_comparator<TTemplate, TArgs...>::template matches_t<TType> {};
|
||||
|
||||
template<typename TTemplate, typename TType>
|
||||
constexpr bool match_template_type_v = match_template_type<TTemplate, TType>::value;
|
||||
|
||||
// similar to std::is_same, but allows placeholders
|
||||
// - is_type_or_impl<some_tmpl<Type_>, some_type> resolves to some_tmpl<some_type>
|
||||
// - is_type_or_impl<some_tmpl<Any_, int>, some_type> checks if some_type is an instance of some_tmpl with int as 2nd parameter
|
||||
template<typename TMatch, typename TConcrete>
|
||||
struct type_matches
|
||||
{
|
||||
using type = std::is_same<TMatch, TConcrete>;
|
||||
};
|
||||
|
||||
template<template<typename> typename TTmplMatch, typename TConcrete>
|
||||
struct type_matches<TTmplMatch<Type_>, TConcrete>
|
||||
{
|
||||
using type = TTmplMatch<TConcrete>;
|
||||
};
|
||||
|
||||
template<template<typename...> typename TTmplMatch, typename TConcrete, typename... TArgs>
|
||||
struct type_matches<TTmplMatch<TArgs...>, TConcrete>
|
||||
{
|
||||
using type = match_template_type<TTmplMatch<TArgs...>, TConcrete>;
|
||||
};
|
||||
|
||||
template<typename TMatch, typename TConcrete>
|
||||
using type_matches_t = type_matches<TMatch, TConcrete>::type;
|
||||
|
||||
template<typename TMatch, typename TConcrete>
|
||||
inline constexpr bool type_matches_v = type_matches_t<TMatch, TConcrete>::value;
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
struct is_any_type : std::disjunction<std::is_same<TConcrete, TMatches>...> {};
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
static constexpr bool is_any_type_v = is_any_type<TConcrete, TMatches...>::value;
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
struct match_any_type : std::disjunction<type_matches_t<TMatches, TConcrete>...> {};
|
||||
|
||||
template<typename TConcrete, typename... TMatches>
|
||||
static constexpr bool match_any_type_v = match_any_type<TConcrete, TMatches...>::value;
|
||||
|
||||
template<typename T, typename... Types>
|
||||
static constexpr bool is_any_type_v = is_any_type<T, Types...>::value;
|
||||
concept union_type = match_any_type_v<T, Types...>;
|
||||
|
||||
template<typename TElement, typename TCollection>
|
||||
struct is_type_member;
|
||||
@@ -112,6 +192,79 @@ 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 T, typename TObject>
|
||||
struct is_member_object_pointer_of : std::false_type {};
|
||||
|
||||
template<typename TMember, typename TObject>
|
||||
struct is_member_object_pointer_of<TMember (TObject::*), TObject> : std::true_type {};
|
||||
|
||||
template<typename T, typename TObject>
|
||||
inline constexpr bool is_member_object_pointer_of_v = is_member_object_pointer_of<T, TObject>::value;
|
||||
|
||||
template<typename T, typename TObject>
|
||||
concept member_object_pointer_of = is_member_object_pointer_of_v<T, TObject>;
|
||||
|
||||
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<typename TDefault, template<typename...> typename TOper, typename... TArgs>
|
||||
struct detect_or
|
||||
{
|
||||
using type = TDefault;
|
||||
static constexpr bool detected = false;
|
||||
};
|
||||
|
||||
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
|
||||
requires requires { typename TOper<TArgs...>; }
|
||||
struct detect_or<TDefault, TOper, TArgs...>
|
||||
{
|
||||
using type = TOper<TArgs...>;
|
||||
static constexpr bool detected = true;
|
||||
};
|
||||
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
|
||||
using detect_or_t = detect_or<TDefault, TOper, TArgs...>::type;
|
||||
|
||||
struct empty_type {};
|
||||
|
||||
template<typename T, bool enable>
|
||||
struct optional_base
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct optional_base<T, false>
|
||||
{
|
||||
using type = empty_type;
|
||||
};
|
||||
|
||||
template<typename T, bool enable>
|
||||
using optional_base_t = optional_base<T, enable>::type;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
@@ -122,6 +275,34 @@ decltype(auto) delayEvaluation(TType&& value)
|
||||
return static_cast<TType&&>(value);
|
||||
}
|
||||
|
||||
#define MIJIN_STATIC_TESTS 1
|
||||
#if MIJIN_STATIC_TESTS
|
||||
namespace test
|
||||
{
|
||||
template<typename T, typename U>
|
||||
struct MyTemplate {};
|
||||
|
||||
static_assert(match_template_type_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
|
||||
static_assert(match_template_type_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
|
||||
static_assert(type_matches_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
|
||||
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
|
||||
static_assert(!type_matches_v<MyTemplate<double, int>, MyTemplate<int, int>>);
|
||||
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, double>>);
|
||||
static_assert(type_matches_v<std::is_pointer<Type_>, void*>);
|
||||
|
||||
static_assert(union_type<int, int>);
|
||||
static_assert(!union_type<int>);
|
||||
static_assert(!union_type<int, double>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<int, int>>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, int>>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, Any_>>);
|
||||
static_assert(union_type<MyTemplate<int, int>, MyTemplate<double, double>, MyTemplate<Any_, Any_>>);
|
||||
static_assert(!union_type<int*, int>);
|
||||
static_assert(union_type<int*, std::is_pointer<Type_>>);
|
||||
static_assert(!union_type<int, std::is_pointer<Type_>>);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)
|
||||
|
||||
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)
|
||||
28
source/mijin/util/winundef.hpp
Normal file
28
source/mijin/util/winundef.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
#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
|
||||
|
||||
#if defined(DEBUG)
|
||||
#undef DEBUG
|
||||
#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;
|
||||
|
||||
@@ -32,6 +35,7 @@ namespace mijin
|
||||
struct FileInfo
|
||||
{
|
||||
fs::path path;
|
||||
/// either file size in bytes, or number of entries if folder
|
||||
std::size_t size = 0;
|
||||
bool exists : 1 = false;
|
||||
bool isFolder : 1 = false;
|
||||
@@ -40,25 +44,66 @@ 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;
|
||||
[[deprecated("Will be removed ASAP, use getKnownFolder(KnownFolder::USER_HOME) from platform/folders.hpp instead.")]]
|
||||
[[nodiscard]] virtual fs::path getHomeFolder() { return {}; } // TODO: get rid of this ...
|
||||
[[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 +113,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 +160,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)
|
||||
160
source/mijin/virtual_filesystem/memory.cpp
Normal file
160
source/mijin/virtual_filesystem/memory.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
|
||||
#include "./memory.hpp"
|
||||
|
||||
#include "../io/stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
std::vector<FileInfo> MemoryFileSystemAdapter::listFiles(const fs::path& folder)
|
||||
{
|
||||
const detail::MemoryFolder* folderObj = findFolder(folder);
|
||||
if (folderObj == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
std::vector<FileInfo> result;
|
||||
result.reserve(folderObj->folders.size() + folderObj->files.size());
|
||||
|
||||
for (const auto& [name, subFolder] : folderObj->folders)
|
||||
{
|
||||
result.push_back(folderInfo(folder / name, subFolder));
|
||||
}
|
||||
|
||||
for (const auto& [name, file] : folderObj->files)
|
||||
{
|
||||
result.push_back(fileInfo(folder / name, file));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FileInfo MemoryFileSystemAdapter::getFileInfo(const fs::path& file)
|
||||
{
|
||||
#if 0 // shouldn't be necessary
|
||||
// empty means root
|
||||
if (file.empty())
|
||||
{
|
||||
return {
|
||||
.path = {},
|
||||
.size = root_.folders.size() + root_.files.size(),
|
||||
.exists = true,
|
||||
.isFolder = true
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
const detail::MemoryFolder* folderObj = findFolder(file.parent_path());
|
||||
if (folderObj == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const std::string filename = file.filename().generic_string();
|
||||
|
||||
if (auto itFolder = folderObj->folders.find(filename); itFolder != folderObj->folders.end())
|
||||
{
|
||||
return folderInfo(file, itFolder->second);
|
||||
}
|
||||
if (auto itFile = folderObj->files.find(filename); itFile != folderObj->files.end())
|
||||
{
|
||||
return fileInfo(file, itFile->second);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
StreamError MemoryFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
|
||||
{
|
||||
if (mode != FileOpenMode::READ)
|
||||
{
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
const detail::MemoryFolder* folderObj = findFolder(path.parent_path());
|
||||
if (folderObj == nullptr)
|
||||
{
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
auto itFile = folderObj->files.find(path.filename().generic_string());
|
||||
if (itFile == folderObj->files.end())
|
||||
{
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
std::unique_ptr<MemoryStream> stream = std::make_unique<MemoryStream>();
|
||||
stream->openRO(itFile->second.data);
|
||||
outStream = std::move(stream);
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
bool MemoryFileSystemAdapter::addFile(const fs::path& path, std::span<const std::uint8_t> data, Overwrite overwrite, CopyData copyData)
|
||||
{
|
||||
detail::MemoryFolder& folder = *findFolder(path.parent_path(), true);
|
||||
std::string filename = path.filename().generic_string();
|
||||
|
||||
if (folder.folders.contains(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!overwrite && folder.files.contains(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copyData)
|
||||
{
|
||||
data = fileData_.emplace_back(data.begin(), data.end());
|
||||
}
|
||||
|
||||
folder.files.emplace(std::move(filename), detail::MemoryFile{.data = data});
|
||||
return true;
|
||||
}
|
||||
|
||||
void MemoryFileSystemAdapter::addFolder(const fs::path& path)
|
||||
{
|
||||
(void) findFolder(path, true);
|
||||
}
|
||||
|
||||
detail::MemoryFolder* MemoryFileSystemAdapter::findFolder(const fs::path& path, bool create) MIJIN_NOEXCEPT
|
||||
{
|
||||
detail::MemoryFolder* folder = &root_;
|
||||
for (const fs::path& part : path)
|
||||
{
|
||||
std::string partname = part.generic_string();
|
||||
auto it = folder->folders.find(partname);
|
||||
if (it == folder->folders.end())
|
||||
{
|
||||
if (!create)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
folder = &folder->folders[std::move(partname)];
|
||||
}
|
||||
else
|
||||
{
|
||||
folder = &it->second;
|
||||
}
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
FileInfo MemoryFileSystemAdapter::folderInfo(const fs::path& path, const detail::MemoryFolder& folder) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return {
|
||||
.path = path,
|
||||
.size = folder.folders.size() + folder.files.size(),
|
||||
.exists = true,
|
||||
.isFolder = true
|
||||
};
|
||||
}
|
||||
|
||||
FileInfo MemoryFileSystemAdapter::fileInfo(const fs::path& path, const detail::MemoryFile& file) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return {
|
||||
.path = path,
|
||||
.size = file.data.size(),
|
||||
.exists = true
|
||||
};
|
||||
}
|
||||
}
|
||||
55
source/mijin/virtual_filesystem/memory.hpp
Normal file
55
source/mijin/virtual_filesystem/memory.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED)
|
||||
#define MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED 1
|
||||
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "../container/vector_map.hpp"
|
||||
#include "../util/flag.hpp"
|
||||
#include "../virtual_filesystem/filesystem.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct MemoryFile
|
||||
{
|
||||
std::span<const std::uint8_t> data;
|
||||
};
|
||||
struct MemoryFolder
|
||||
{
|
||||
VectorMap<std::string, MemoryFile, std::allocator<std::string>, std::allocator<MemoryFile>> files; // TODO: make the FS library allocator aware
|
||||
VectorMap<std::string, MemoryFolder, std::allocator<std::string>, std::allocator<MemoryFolder>> folders;
|
||||
};
|
||||
}
|
||||
|
||||
class MemoryFileSystemAdapter : public FileSystemAdapter
|
||||
{
|
||||
public:
|
||||
MIJIN_DEFINE_FLAG(Overwrite);
|
||||
MIJIN_DEFINE_FLAG(CopyData);
|
||||
private:
|
||||
detail::MemoryFolder root_;
|
||||
std::vector<std::vector<std::uint8_t>> fileData_;
|
||||
public:
|
||||
[[nodiscard]] std::vector<FileInfo> listFiles(const fs::path& folder) override;
|
||||
[[nodiscard]] FileInfo getFileInfo(const fs::path& file) override;
|
||||
[[nodiscard]] StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
|
||||
|
||||
bool addFile(const fs::path& path, std::span<const std::uint8_t> data, Overwrite overwrite = Overwrite::NO, CopyData copyData = CopyData::NO);
|
||||
bool addFile(const fs::path& path, std::span<const std::uint8_t> data, CopyData copyData)
|
||||
{
|
||||
return addFile(path, data, Overwrite::NO, copyData);
|
||||
}
|
||||
void addFolder(const fs::path& path);
|
||||
private:
|
||||
detail::MemoryFolder* findFolder(const fs::path& path, bool create = false) MIJIN_NOEXCEPT;
|
||||
FileInfo folderInfo(const fs::path& path, const detail::MemoryFolder& folder) const MIJIN_NOEXCEPT;
|
||||
FileInfo fileInfo(const fs::path& path, const detail::MemoryFile& file) const MIJIN_NOEXCEPT;
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_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.lexically_normal();
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "./stacked.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include "../detect.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@@ -30,30 +31,24 @@ 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()) {
|
||||
return fs::path();
|
||||
}
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_MSVC
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996) // yeah, we're using a deprecated function here, in order to implement another deprecated function ¯\_(ツ)_/¯
|
||||
#elif MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
return adapters_.front()->getHomeFolder();
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_MSVC
|
||||
#pragma warning(pop)
|
||||
#elif MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<FileInfo> StackedFileSystemAdapter::listFiles(const fs::path& folder)
|
||||
@@ -68,7 +63,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 +85,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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user