156 Commits

Author SHA1 Message Date
cd66b76a8f Added bitFlagsToInt() and bitFlagsFromInt() methods for converting bit flags to and from integer types. 2025-03-13 09:46:29 +01:00
ede7477ffa Fixed compilation warnings due to unused results in release versions. 2025-03-13 09:46:29 +01:00
75e77c53e4 Some more FS fixes. 2025-03-10 09:56:32 +01:00
c5b9c02342 Fixed MappingFileSystemAdapter::adjustPath() on Windows. 2025-03-07 14:28:23 +01:00
1c0c928856 Fixed known folders detection for Windows (at least compilation). 2025-03-03 17:26:29 +01:00
ce26cda739 Import getAllPaths() to StackedFileSystemAdapter so it is also availabe there. 2025-03-02 20:06:46 +01:00
b6657189d3 Added getAllPaths() method to get combined results from stacked adapters. 2025-03-02 20:02:03 +01:00
3d0f5b9a3f Remove errornously added .idea folder. 2025-03-02 19:58:15 +01:00
6407c5ca09 Added MappingFileSystemAdapter and adjusted some more stuff in the VFS module. 2025-03-02 18:41:04 +01:00
8f2cee4968 Added functions for detecting known folders per-platform. Not tested on Windows yet, but who cares? 2025-03-02 17:19:18 +01:00
ba23cb0c70 Made the lib work with -fno-exceptions (at least for now). 2025-03-02 14:35:37 +01:00
21b3b2c03a Allow calling Signal::emit() with arguments that aren't exactly the signal handlers types. 2025-02-14 22:39:01 +01:00
55fb360dfa Fixed Windows version of getExecutablePath(). 2025-01-22 11:54:13 +01:00
6d2a57485e Added dataAt() to TypelessBuffer and elementSize() to UntypedVector. 2025-01-22 11:47:31 +01:00
461d3ec694 Added getExecutablePath(). Still need to check if the Windows version works, but who cares? 2025-01-19 21:24:38 +01:00
75398b89d5 Added MemoryView. 2025-01-18 18:38:37 +01:00
43e772bb89 Added UntypedVector type. 2024-12-25 01:56:45 +01:00
3c7b7212b9 Added path concatination operator for PathReference. 2024-12-25 01:56:36 +01:00
28ae64169d Possibly fixed some crashes when reading in a coroutine. 2024-12-21 22:30:10 +01:00
0d9769f2b7 Added missing include. 2024-12-21 22:29:50 +01:00
36a9b60e0b Added constant index operator to VectorMap. 2024-12-18 14:50:01 +01:00
48aeb32ee5 Added walkExceptionCause helper function. 2024-12-16 20:53:17 +01:00
aa804a2a99 Replaced noexcept with MIJIN_NOEXCEPT macro and added PODWrapper for simplifying serializing POD types. 2024-12-13 22:20:22 +01:00
781bad3c8e Fixed TCPServerSocket::isListening() on Windows. 2024-12-13 10:08:46 +01:00
41618310e4 Added 'connection refused' stream error. 2024-12-13 10:01:33 +01:00
713d5ca02d Removed unnecessary ServerSocket type. 2024-12-13 10:01:01 +01:00
7ffc9df3fc Added spaceship operator for BitFlags type. 2024-12-10 23:06:44 +01:00
f232728f69 Fixed clang compilation and added support for making assertions and erros throw exceptions for easier testing. 2024-12-06 18:36:49 +01:00
34adf3f006 Added constructors so the VectorMap is default-, copy- and move-constructible. 2024-12-06 18:05:42 +01:00
5596ab16a7 Added VectorMap type (for having a map-like interface using std::vector that supports incomplete types). 2024-12-06 17:59:37 +01:00
49791c4fd0 Added toNumber() variants for floating-point types. 2024-12-04 01:08:53 +01:00
5bd19369ce Fixed wrong type used for storing the width. 2024-11-30 15:22:07 +01:00
81f4c33508 Fixed windows compilation. 2024-11-29 09:48:30 +01:00
5f0e6e9726 Added STLStream that wraps STL stream types for usage with functions that expect a Mijin stream. 2024-11-29 01:37:50 +01:00
f28cc04aa1 Replaced another instance of result_of_t with invoke_result_t. 2024-11-23 15:56:24 +01:00
e747d6d3f7 Limit CURL version to anything before 8.10.0 for now. 2024-11-20 20:19:55 +01:00
4be8f387dc Fixed ScriptValue::mapView() for const ScriptValues. 2024-11-20 20:19:29 +01:00
71fc1686bb Added MIJIN_CONCAT3 macro. 2024-11-17 13:10:49 +01:00
aa63f35902 Added is_template_instance traits. 2024-11-17 13:09:31 +01:00
98cf879541 Fixed ScriptValue arrayView() and mapView() functions. 2024-11-17 13:09:17 +01:00
d52cc6af6e Added value_t type to optional. 2024-11-12 23:32:00 +01:00
2af1b5edd5 Made Future::set() only callable on futures with void result to prevent futures that are ready but have no result. 2024-11-12 23:31:43 +01:00
7dcdf2df22 Fixed ScriptValue::to() for int and float types. 2024-11-10 19:08:40 +01:00
017bba89e4 Fixed type_at_t trait. 2024-11-09 23:33:15 +01:00
009113e566 Added delay_type_t and type_at_t traits. 2024-11-09 21:35:59 +01:00
741ad4603f Fixed value_type of MappingRanges. 2024-10-31 23:38:54 +01:00
57a9b6a518 Added ScriptValue::toRef(), spaceship operators for array and map values and fixed return type of index operators. 2024-10-31 23:38:38 +01:00
85373e5293 Added header for converting HRESULTs to exceptions. 2024-10-25 09:41:26 +02:00
c36fc2d6ed Fixed compilation with MSVC. 2024-10-25 09:40:53 +02:00
6f7d518ca7 Added more defines to winundef.hpp 2024-10-25 09:40:37 +02:00
1f493d3b57 Added missing include. 2024-10-25 00:21:48 +02:00
44aee31747 Added some utilities to ScriptValue for reading from arrays and maps. 2024-10-25 00:08:36 +02:00
c31dbbd093 Added utility functions to check type of script values. 2024-10-24 22:21:22 +02:00
161fdb6c93 Added throwOnError() for StreamResults. 2024-10-24 18:30:03 +02:00
c4e3576bc7 Added missing string_view include. 2024-10-24 12:26:54 +02:00
0d00dec8c7 Made the request stuff work. 2024-10-23 23:55:28 +02:00
77d46d986c Added exception type that automatically captures stacktrace and "parent" exception when constructed. 2024-10-12 23:05:11 +02:00
4735fc10f8 Fixed readLine() getting stuck at the end of the file. 2024-09-19 15:21:05 +02:00
6ebcb96444 Fixed assertion when manually closing a filestream. 2024-09-19 14:10:11 +02:00
24033dbfcc Added isDecimalChar() and isHexadecimalChar(). 2024-09-18 09:46:52 +02:00
aff59724fc Added splitFixed() function to avoid heap-allocation when the number of (expected) splits is known at compile-time. 2024-09-17 09:49:36 +02:00
8e117e0f47 Added wrapper to allow passing Mijin streams to functions that expect an STL stream. 2024-09-15 14:45:30 +02:00
a015ad54a7 Added warning about windows.h. 2024-09-12 14:51:18 +02:00
92d7b5552f WIP: CURL/request implementation. 2024-09-11 08:57:43 +02:00
4e93898a56 Added required includes to variant util header. 2024-09-11 08:57:20 +02:00
3942c7e074 Made bitflags bool cast operator explicit. 2024-09-11 08:56:08 +02:00
3d4ae9678f Removed some of the unused exception stuff. 2024-09-11 08:54:52 +02:00
9ba097fc2f Added support for completely disabling noexcept using MIJIN_TEST_NO_NOEXCEPT (for testing). 2024-08-29 00:01:23 +02:00
a43f92fb58 More SSL stuff (still doesn't work :/). 2024-08-27 19:52:08 +02:00
0be34a845a Added wrapper for openssl types. 2024-08-22 00:30:38 +02:00
f761f2fb07 SSLStream (WIP) 2024-08-21 09:35:49 +02:00
0acadf994d Fixed Windows ip resolve. 2024-08-20 17:05:04 +02:00
f5ceb25a44 Added ip.cpp to LibConf. 2024-08-20 14:18:15 +02:00
04a28e220c Added name resolution code for linux. 2024-08-20 13:54:52 +02:00
a755de5c5f Added clang format file. 2024-08-20 13:54:42 +02:00
8a611bf4f3 Split IP stuff into separate source and WIP implementation of name resolution. 2024-08-20 12:07:25 +02:00
05f0e1474a Fixed URLs with non-default char types. 2024-08-20 00:35:19 +02:00
03f255a7d0 Added HTTPClient type. 2024-08-20 00:28:12 +02:00
99f5987f4b Added URL type. 2024-08-20 00:28:01 +02:00
8002e1f1f8 Fixed Linux socket implementation again. 2024-08-19 18:42:31 +02:00
df260808b9 Implemented/fixed Windows/MSVC support for sockets. 2024-08-19 18:35:55 +02:00
35e7131780 Added some basic HTTP and ip address parsing. 2024-08-18 23:06:09 +02:00
03c899f17e Updated string split method to accept limitParts and ignoreEmpty options. 2024-08-18 17:24:06 +02:00
d508ccfe2b Fixed initialization of sockaddr_in. 2024-08-18 13:43:14 +02:00
6d111904d4 Added clang-tidy config and cleaned up the code a little. 2024-08-18 13:30:40 +02:00
9f011952c2 Fixed a bunch of clang-tidy warnings. 2024-08-18 13:13:17 +02:00
d73fa25ed8 Silenced some weird (very likely false-positive) clang-tidy warnings. 2024-08-18 12:24:37 +02:00
66e319fb0d Added missing <exception> include. 2024-08-18 09:58:49 +02:00
822abb7b30 Fixed some issues with compilation in CLang. 2024-08-18 09:10:26 +02:00
acb5d5b04e Added utilty header for variant stuff (Visitor type and variant_contains_v). 2024-08-17 22:55:58 +02:00
def91ac1bf Added split() and trim() string utilty functions. 2024-08-17 22:55:29 +02:00
9337ad7ddb Added support for map values and can_hold_type_v utility trait to ScriptValue. 2024-08-17 18:10:29 +02:00
bb05bb0ae5 Explictly construct result_t to fix conflicting return types. 2024-08-17 18:08:01 +02:00
2e54844989 Use steady_clock instead of high_resolution_clock for sleeping. 2024-08-17 18:07:21 +02:00
f0d0ee17ea Implemented tcp sockets (only IPv4) and asynchronous IO (for sockets). 2024-08-16 21:29:33 +02:00
da348c1f40 Added missing <stdexcept> header. 2024-08-15 10:03:16 +02:00
efedca0d3c Added SModule and dependencies.json for S++ 2.0 support. 2024-08-14 23:29:46 +02:00
9ef450df33 Added equalsIgnoreCase() function. 2024-08-04 11:28:51 +02:00
ea97d3af48 Removed deducing this-version again as the world (clang) isn't ready for it yet. 2024-08-03 20:49:31 +02:00
af51973b2a Added a variant of throwOnError that takes an additional error message. 2024-08-03 15:55:57 +02:00
686dff506d Made Optional::then() use deducing this so it supports const and non-const optionals. 2024-08-03 15:55:26 +02:00
7658e8fbda Added copy_const_t, copy_volatile_t and copy_cv_t type traits. 2024-07-31 22:19:19 +02:00
2942149cb5 Added option to add custom types ScriptValue. 2024-07-31 22:18:51 +02:00
cdcf99237b Made TypelessBuffer functions noexcept. 2024-07-31 22:17:47 +02:00
f6f77f6dc1 Return result with more error information when opening a shared library fails. 2024-07-29 21:48:04 +02:00
a9a85aecdf Added SharedLibrary wrapper for library handles. 2024-07-27 11:58:54 +02:00
232e2a3e28 Added closeSharedLibrary(). 2024-07-27 11:32:56 +02:00
8b55786e77 Added LIBRARY_EXT constant. 2024-07-27 11:14:44 +02:00
e82c697d2e Added getNativePath() method to get the OS version of a virtual path. 2024-07-27 11:04:38 +02:00
9b4425c495 Added ScriptValue type as a generic variant type for scripts and similar situations. 2024-07-26 23:28:39 +02:00
3a8602edcc Added ensure() utility to throw an exception if a condition isn't fulfilled. 2024-07-26 23:28:03 +02:00
543d77859d Added operator->() and made get() and operator*() const. 2024-07-26 23:27:24 +02:00
b40814e58b Added missing include. 2024-07-26 23:26:42 +02:00
0a4f1d2ce4 Added property type. 2024-07-26 18:51:16 +02:00
b66893dda7 Added Optionl::then() for monad functionality. 2024-07-26 18:51:08 +02:00
f35ee5f038 Added throwOnError utility function. 2024-07-23 20:16:24 +02:00
83a46cae15 Fixed other functions in RelativeFileSystemAdapter. 2024-07-23 20:16:12 +02:00
3b7396c4d6 Fixed enumeration of files. 2024-07-23 19:55:40 +02:00
7aa1edcea0 Added MIJIN_STRIDEOF() utilty macro. 2024-06-28 18:21:29 +02:00
251393ae48 Fixed new nodiscard attribute. 2024-06-27 19:18:43 +02:00
eccaa177ca Added nodiscard attribute to TaskBase. 2024-06-27 18:13:26 +02:00
b1fa99428e Fixed Windows/MSVC compilation. 2024-06-26 10:05:32 +02:00
b10f250945 Added simple (albeit in many cases suboptiomal) c_sleep() method for coroutines. 2024-06-23 12:19:57 +02:00
e586698fb6 Added bits_t member to BitFlags and is_bitflags_v helper constant. 2024-06-10 23:11:54 +02:00
c214398fac Deprecated readString() method so it is always clear if you want to read a binary encoded string or the file as string. Also added possibility to open a memory stream from any span. 2024-06-10 23:11:25 +02:00
463f4ca19c Fixed some compiler errors. 2024-05-17 20:29:46 +02:00
80310f3c5c Added some constructors and an empty() check to path references. 2023-12-29 23:32:14 +01:00
1d14c1f3ff Added comparator and hash function for path references. 2023-12-23 12:10:07 +01:00
83d3ce9f1f Use copies of strings for the cache instead of string_views. Apparently my assumption was wrong. 2023-12-19 22:41:40 +01:00
de07cf91bc Added bool conversion for Name. 2023-12-18 17:53:52 +01:00
faf4489b92 Added isLocked() to task mutex. 2023-12-18 17:53:40 +01:00
2668e69ae1 Added signal_token_t to make it easier to store tokens. 2023-12-17 02:36:24 +01:00
bb4d31747a Added function to convert StrideSpan to regular span, if possible. 2023-12-16 10:25:58 +01:00
8ce77cf8bf Fixed tangent generation and added Vector4 type. 2023-12-15 02:20:50 +01:00
b692425f83 Added TaskMutex header. 2023-12-10 18:42:20 +01:00
1d8ef0bac8 Added some basic geometry functionality (only tangent generation for now). 2023-12-07 17:16:08 +01:00
b89f19ed98 Use inline assembly for MIJIN_TRAP() so the debugger stops at the exact location. Only works for x64, but that's currently my only target. 2023-12-07 17:15:42 +01:00
0e877f4769 Added getActiveTasks() function to count how many tasks are running or suspended. 2023-12-07 17:14:45 +01:00
5e236dae00 Added TypeDef. 2023-12-02 10:59:31 +01:00
6cac3a655b Fixed StrideSpan operator[] for const spans. 2023-11-26 22:42:33 +01:00
27b163f4db Added libfmt support for Name. 2023-11-26 15:13:06 +01:00
c44350269a Added code to capture stacktrace whenever an exception is thrown. 2023-11-26 11:49:23 +01:00
adbb4fd0d1 Disabled coroutine debug info by default as it is fucking expensive. 2023-11-24 22:01:08 +01:00
45845fa31b Fixed crashes when capturing stacktrace from multiple threads. 2023-11-24 22:00:47 +01:00
4f7e8ae39f Fixed StrideSpan::size() for default-constructed spans and added empty check + operatators. 2023-11-24 13:08:39 +01:00
ce7a7b15c7 Added utilty functions to read an entire file. 2023-11-19 23:45:35 +01:00
0e90cabb7e Added PathReference utility type. 2023-11-19 23:45:14 +01:00
60707421c8 Fixed chaining iterators for const ranges. 2023-11-19 20:04:46 +01:00
ba8c1ebe1e Moved the creation stack to the shared state so it is retrievable, added comparison operators to TaskHandle and added getCurrentTask() function to retrieve the handle of the current task. 2023-11-19 20:04:19 +01:00
803f1463dc Made the message queue iterable. 2023-11-19 20:03:24 +01:00
065181fc69 Also cancel sub-tasks (those that are awaited by this one) when cancelling a task. 2023-11-18 22:20:47 +01:00
d98e14285b Fixed CPPDEFINES only being set for dependant projects but not mijin itself. 2023-11-18 22:19:52 +01:00
4dfc116830 Added zip() functionality for combining iterators. 2023-11-16 23:39:49 +01:00
a92148aac1 Added libfmt formatters for stacktrace. 2023-11-16 23:39:33 +01:00
55486b49dc Fixed optional move constructor for references. 2023-11-16 23:39:19 +01:00
5aecd20c56 Use our optional type for storing results of the future. 2023-11-16 23:38:53 +01:00
78 changed files with 8014 additions and 563 deletions

66
.clang-format Normal file
View 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
View 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
LibConf
View File

@@ -7,27 +7,36 @@ mijin_sources = Split("""
source/mijin/debug/symbol_info.cpp source/mijin/debug/symbol_info.cpp
source/mijin/io/process.cpp source/mijin/io/process.cpp
source/mijin/io/stream.cpp source/mijin/io/stream.cpp
source/mijin/net/http.cpp
source/mijin/net/ip.cpp
source/mijin/net/socket.cpp
source/mijin/util/os.cpp source/mijin/util/os.cpp
source/mijin/types/name.cpp source/mijin/types/name.cpp
source/mijin/virtual_filesystem/filesystem.cpp source/mijin/virtual_filesystem/filesystem.cpp
source/mijin/virtual_filesystem/stacked.cpp source/mijin/virtual_filesystem/stacked.cpp
""") """)
lib_libbacktrace = env.Cook('libbacktrace') dependencies = []
if env['COMPILER_FAMILY'] in ('gcc', 'clang'):
lib_libbacktrace = env.Cook('libbacktrace')
dependencies.append(lib_libbacktrace)
cppdefines = []
if env['BUILD_TYPE'] == 'debug':
cppdefines += ['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1']
lib_mijin = env.UnityStaticLibrary( lib_mijin = env.UnityStaticLibrary(
target = env['LIB_DIR'] + '/mijin', target = env['LIB_DIR'] + '/mijin',
source = mijin_sources, source = mijin_sources,
dependencies = [lib_libbacktrace] dependencies = dependencies,
CPPDEFINES = list(env['CPPDEFINES']) + cppdefines
) )
LIB_CONFIG = { LIB_CONFIG = {
'CPPPATH': [env.Dir('source')], 'CPPPATH': [env.Dir('source')],
'CPPDEFINES': [], 'CPPDEFINES': cppdefines,
'DEPENDENCIES': [lib_mijin] 'DEPENDENCIES': [lib_mijin]
} }
if env['BUILD_TYPE'] == 'debug':
LIB_CONFIG['CPPDEFINES'].extend(['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1'])
Return('LIB_CONFIG') Return('LIB_CONFIG')

58
SModule Normal file
View File

@@ -0,0 +1,58 @@
import json
Import('env')
mijin_sources = Split("""
source/mijin/async/coroutine.cpp
source/mijin/debug/stacktrace.cpp
source/mijin/debug/symbol_info.cpp
source/mijin/io/process.cpp
source/mijin/io/stream.cpp
source/mijin/net/http.cpp
source/mijin/net/ip.cpp
source/mijin/net/socket.cpp
source/mijin/platform/folders.cpp
source/mijin/util/os.cpp
source/mijin/types/name.cpp
source/mijin/virtual_filesystem/filesystem.cpp
source/mijin/virtual_filesystem/stacked.cpp
""")
with open('dependencies.json', 'r') as f:
dependencies = env.DepsFromJson(json.load(f))
cppdefines = []
if env['BUILD_TYPE'] == 'debug':
cppdefines += ['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1']
# SSL libs
if env.get('MIJIN_ENABLE_OPENSSL'):
cppdefines.append('MIJIN_ENABLE_OPENSSL=1')
mijin_sources.extend(Split("""
source/mijin/net/ssl.cpp
"""))
# CURL libs
if env.get('MIJIN_ENABLE_CURL'):
cppdefines.append('MIJIN_ENABLE_CURL=1')
mijin_sources.extend(Split("""
source/mijin/net/request.cpp
"""))
lib_mijin = env.UnityStaticLibrary(
target = env['LIB_DIR'] + '/mijin',
source = mijin_sources,
dependencies = dependencies,
CPPDEFINES = list(env['CPPDEFINES']) + cppdefines
)
LIB_CONFIG = {
'CPPPATH': [env.Dir('source')],
'CPPDEFINES': cppdefines,
'LIBS': [lib_mijin]
}
Return('LIB_CONFIG')

19
dependencies.json Normal file
View 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]
}
}

View File

@@ -40,10 +40,10 @@ void MultiThreadedTaskLoop::managerThread(std::stop_token stopToken) // NOLINT(p
while (!stopToken.stop_requested()) while (!stopToken.stop_requested())
{ {
// first clear out any parked tasks that are actually finished // first clear out any parked tasks that are actually finished
auto it = std::remove_if(parkedTasks_.begin(), parkedTasks_.end(), [](StoredTask& task) { auto itRem = std::remove_if(parkedTasks_.begin(), parkedTasks_.end(), [](StoredTask& task) {
return !task.task || task.task->status() == TaskStatus::FINISHED; return !task.task || task.task->status() == TaskStatus::FINISHED;
}); });
parkedTasks_.erase(it, parkedTasks_.end()); parkedTasks_.erase(itRem, parkedTasks_.end());
// then try to push any task from the buffer into the queue, if possible // then try to push any task from the buffer into the queue, if possible
for (auto it = parkedTasks_.begin(); it != parkedTasks_.end();) for (auto it = parkedTasks_.begin(); it != parkedTasks_.end();)
@@ -135,7 +135,7 @@ void MultiThreadedTaskLoop::workerThread(std::stop_token stopToken, std::size_t
// public functions // public functions
// //
void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT
{ {
assertCorrectThread(); assertCorrectThread();
@@ -153,7 +153,7 @@ void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
otherLoop.addStoredTask(std::move(storedTask)); otherLoop.addStoredTask(std::move(storedTask));
} }
void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT
{ {
storedTask.task->setLoop(this); storedTask.task->setLoop(this);
if (threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id()) if (threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id())
@@ -174,13 +174,27 @@ void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept
} }
} }
void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept std::size_t SimpleTaskLoop::getActiveTasks() const MIJIN_NOEXCEPT
{
std::size_t sum = 0;
for (const StoredTask& task : mijin::chain(tasks_, newTasks_))
{
const TaskStatus status = task.task ? task.task->status() : TaskStatus::FINISHED;
if (status == TaskStatus::SUSPENDED || status == TaskStatus::RUNNING)
{
++sum;
}
}
return sum;
}
void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT
{ {
if (&otherLoop == this) { if (&otherLoop == this) {
return; return;
} }
MIJIN_ASSERT_FATAL(currentTask_ != nullptr, "Trying to call transferCurrentTask() while not running a task!"); MIJIN_ASSERT_FATAL(impl::gCurrentTask != nullptr, "Trying to call transferCurrentTask() while not running a task!");
// now start the transfer, first disown the task // now start the transfer, first disown the task
StoredTask storedTask = std::move(*impl::gCurrentTask); StoredTask storedTask = std::move(*impl::gCurrentTask);
@@ -190,7 +204,7 @@ void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept
otherLoop.addStoredTask(std::move(storedTask)); otherLoop.addStoredTask(std::move(storedTask));
} }
void MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) noexcept void MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT
{ {
storedTask.task->setLoop(this); storedTask.task->setLoop(this);

View File

@@ -4,8 +4,14 @@
#ifndef MIJIN_ASYNC_COROUTINE_HPP_INCLUDED #ifndef MIJIN_ASYNC_COROUTINE_HPP_INCLUDED
#define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1 #define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1
#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO)
# define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0 // Capture stack each time a coroutine is started. Warning, expensive! // TODO: maybe implement a lighter version only storing the return address?
#endif
#include <any> #include <any>
#include <coroutine> #include <coroutine>
#include <exception>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <tuple> #include <tuple>
@@ -13,8 +19,25 @@
#include "./future.hpp" #include "./future.hpp"
#include "./message_queue.hpp" #include "./message_queue.hpp"
#include "../container/optional.hpp" #include "../container/optional.hpp"
#include "../internal/common.hpp"
#include "../util/flag.hpp" #include "../util/flag.hpp"
#include "../util/iterators.hpp"
#include "../util/traits.hpp" #include "../util/traits.hpp"
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
#include "../debug/stacktrace.hpp"
#endif
#if !defined(MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING)
# define MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING MIJIN_ENABLE_EXCEPTIONS
#elif !__cpp_exceptions
# error "Coroutine exception handling enabled, but exceptions are disabled."
#endif
#if !defined(MIJIN_COROUTINE_ENABLE_CANCEL)
# define MIJIN_COROUTINE_ENABLE_CANCEL MIJIN_ENABLE_EXCEPTIONS
#elif !__cpp_exceptions
# error "Cancelling tasks requires exceptions to be anbled."
#endif
namespace mijin namespace mijin
{ {
@@ -23,10 +46,6 @@ namespace mijin
// public defines // public defines
// //
#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO)
#define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0
#endif
// //
// public types // public types
// //
@@ -49,13 +68,54 @@ class TaskLoop;
template<typename TResult = void> template<typename TResult = void>
class TaskBase; class TaskBase;
#if MIJIN_COROUTINE_ENABLE_CANCEL
struct TaskCancelled : std::exception {}; struct TaskCancelled : std::exception {};
#endif
namespace impl namespace impl
{ {
inline void throwIfCancelled(); inline void throwIfCancelled();
} // namespace impl } // namespace impl
class TaskHandle
{
private:
std::weak_ptr<struct TaskSharedState> state_;
public:
TaskHandle() = default;
explicit TaskHandle(std::weak_ptr<TaskSharedState> state) MIJIN_NOEXCEPT : state_(std::move(state)) {}
TaskHandle(const TaskHandle&) = default;
TaskHandle(TaskHandle&&) = default;
TaskHandle& operator=(const TaskHandle&) = default;
TaskHandle& operator=(TaskHandle&&) = default;
bool operator==(const TaskHandle& other) const MIJIN_NOEXCEPT {
return !state_.owner_before(other.state_) && !other.state_.owner_before(state_);
}
bool operator!=(const TaskHandle& other) const MIJIN_NOEXCEPT {
return !(*this == other);
}
[[nodiscard]] bool isValid() const MIJIN_NOEXCEPT
{
return !state_.expired();
}
inline void cancel() const MIJIN_NOEXCEPT;
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
inline Optional<Stacktrace> getCreationStack() const MIJIN_NOEXCEPT;
#endif
};
struct TaskSharedState
{
std::atomic_bool cancelled_ = false;
TaskHandle subTask;
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
Stacktrace creationStack_;
#endif
};
template<typename T> template<typename T>
struct TaskState struct TaskState
{ {
@@ -65,11 +125,11 @@ struct TaskState
TaskState() = default; TaskState() = default;
TaskState(const TaskState&) = default; TaskState(const TaskState&) = default;
TaskState(TaskState&&) noexcept = default; TaskState(TaskState&&) MIJIN_NOEXCEPT = default;
inline TaskState(T _value, TaskStatus _status) noexcept : value(std::move(_value)), status(_status) {} inline TaskState(T _value, TaskStatus _status) MIJIN_NOEXCEPT : value(std::move(_value)), status(_status) {}
inline TaskState(std::exception_ptr _exception) noexcept : exception(std::move(_exception)), status(TaskStatus::FINISHED) {} inline TaskState(std::exception_ptr _exception) MIJIN_NOEXCEPT : exception(std::move(_exception)), status(TaskStatus::FINISHED) {}
TaskState& operator=(const TaskState&) = default; TaskState& operator=(const TaskState&) = default;
TaskState& operator=(TaskState&&) noexcept = default; TaskState& operator=(TaskState&&) MIJIN_NOEXCEPT = default;
}; };
template<> template<>
@@ -80,11 +140,11 @@ struct TaskState<void>
TaskState() = default; TaskState() = default;
TaskState(const TaskState&) = default; TaskState(const TaskState&) = default;
TaskState(TaskState&&) noexcept = default; TaskState(TaskState&&) MIJIN_NOEXCEPT = default;
inline TaskState(TaskStatus _status) noexcept : status(_status) {} inline TaskState(TaskStatus _status) MIJIN_NOEXCEPT : status(_status) {}
inline TaskState(std::exception_ptr _exception) noexcept : exception(std::move(_exception)), status(TaskStatus::FINISHED) {} inline TaskState(std::exception_ptr _exception) MIJIN_NOEXCEPT : exception(std::move(_exception)), status(TaskStatus::FINISHED) {}
TaskState& operator=(const TaskState&) = default; TaskState& operator=(const TaskState&) = default;
TaskState& operator=(TaskState&&) noexcept = default; TaskState& operator=(TaskState&&) MIJIN_NOEXCEPT = default;
}; };
namespace impl namespace impl
@@ -93,15 +153,15 @@ template<typename TReturn, typename TPromise>
struct TaskReturn struct TaskReturn
{ {
template<typename... TArgs> template<typename... TArgs>
constexpr void return_value(TArgs&&... args) noexcept { constexpr void return_value(TArgs&&... args) MIJIN_NOEXCEPT {
(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::forward<TArgs>(args)...), TaskStatus::FINISHED); (static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::forward<TArgs>(args)...), TaskStatus::FINISHED);
} }
constexpr void return_value(TReturn value) noexcept { constexpr void return_value(TReturn value) MIJIN_NOEXCEPT {
(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::move(value)), TaskStatus::FINISHED); (static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::move(value)), TaskStatus::FINISHED);
} }
constexpr void unhandled_exception() noexcept { constexpr void unhandled_exception() MIJIN_NOEXCEPT {
(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(std::current_exception()); (static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(std::current_exception());
} }
}; };
@@ -109,11 +169,11 @@ struct TaskReturn
template<typename TPromise> template<typename TPromise>
struct TaskReturn<void, TPromise> struct TaskReturn<void, TPromise>
{ {
constexpr void return_void() noexcept { constexpr void return_void() MIJIN_NOEXCEPT {
static_cast<TPromise&>(*this).state_.status = TaskStatus::FINISHED; static_cast<TPromise&>(*this).state_.status = TaskStatus::FINISHED;
} }
constexpr void unhandled_exception() noexcept { constexpr void unhandled_exception() MIJIN_NOEXCEPT {
(static_cast<TPromise&>(*this).state_) = TaskState<void>(std::current_exception()); (static_cast<TPromise&>(*this).state_) = TaskState<void>(std::current_exception());
} }
}; };
@@ -124,8 +184,8 @@ struct TaskAwaitableFuture
{ {
FuturePtr<TValue> future; FuturePtr<TValue> future;
[[nodiscard]] constexpr bool await_ready() const noexcept { return future->ready(); } [[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return future->ready(); }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
constexpr TValue await_resume() const constexpr TValue await_resume() const
{ {
impl::throwIfCancelled(); impl::throwIfCancelled();
@@ -143,8 +203,8 @@ struct TaskAwaitableSignal
{ {
std::shared_ptr<std::tuple<TArgs...>> data; std::shared_ptr<std::tuple<TArgs...>> data;
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; } [[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
inline auto& await_resume() const inline auto& await_resume() const
{ {
impl::throwIfCancelled(); impl::throwIfCancelled();
@@ -157,8 +217,8 @@ struct TaskAwaitableSignal<TSingleArg>
{ {
std::shared_ptr<TSingleArg> data; std::shared_ptr<TSingleArg> data;
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; } [[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
constexpr auto& await_resume() const constexpr auto& await_resume() const
{ {
impl::throwIfCancelled(); impl::throwIfCancelled();
@@ -169,8 +229,8 @@ struct TaskAwaitableSignal<TSingleArg>
template<> template<>
struct TaskAwaitableSignal<> struct TaskAwaitableSignal<>
{ {
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; } [[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
inline void await_resume() const { inline void await_resume() const {
impl::throwIfCancelled(); impl::throwIfCancelled();
} }
@@ -178,8 +238,8 @@ struct TaskAwaitableSignal<>
struct TaskAwaitableSuspend struct TaskAwaitableSuspend
{ {
[[nodiscard]] constexpr bool await_ready() const noexcept { return false; } [[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
inline void await_resume() const { inline void await_resume() const {
impl::throwIfCancelled(); impl::throwIfCancelled();
} }
@@ -193,24 +253,25 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
using result_t = typename TTraits::result_t; using result_t = typename TTraits::result_t;
TaskState<result_t> state_; TaskState<result_t> state_;
std::shared_ptr<TaskSharedState> sharedState_ = std::make_shared<TaskSharedState>();
TaskLoop* loop_ = nullptr; TaskLoop* loop_ = nullptr;
constexpr task_t get_return_object() noexcept { return task_t(handle_t::from_promise(*this)); } constexpr task_t get_return_object() MIJIN_NOEXCEPT { return task_t(handle_t::from_promise(*this)); }
constexpr std::suspend_always initial_suspend() noexcept { return {}; } constexpr TaskAwaitableSuspend initial_suspend() MIJIN_NOEXCEPT { return {}; }
constexpr std::suspend_always final_suspend() noexcept { return {}; } constexpr std::suspend_always final_suspend() noexcept { return {}; } // note: this must always be noexcept, no matter what
// template<typename TValue> // template<typename TValue>
// constexpr std::suspend_always yield_value(TValue value) noexcept { // constexpr std::suspend_always yield_value(TValue value) MIJIN_NOEXCEPT {
// *state_ = TaskState<result_t>(std::move(value), TaskStatus::YIELDED); // *state_ = TaskState<result_t>(std::move(value), TaskStatus::YIELDED);
// return {}; // return {};
// } // }
// TODO: implement yielding (can't use futures for this) // TODO: implement yielding (can't use futures for this)
// constexpr void unhandled_exception() noexcept {} // constexpr void unhandled_exception() MIJIN_NOEXCEPT {}
template<typename TValue> template<typename TValue>
auto await_transform(FuturePtr<TValue> future) noexcept auto await_transform(FuturePtr<TValue> future) MIJIN_NOEXCEPT
{ {
MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!"); MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!");
TaskAwaitableFuture<TValue> awaitable{future}; TaskAwaitableFuture<TValue> awaitable{future};
@@ -226,15 +287,15 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
} }
template<typename TResultOther> template<typename TResultOther>
auto await_transform(TaskBase<TResultOther> task) noexcept auto await_transform(TaskBase<TResultOther> task) MIJIN_NOEXCEPT
{ {
MIJIN_ASSERT(loop_ != nullptr, "Cannot await another task outside of a loop!"); MIJIN_ASSERT(loop_ != nullptr, "Cannot await another task outside of a loop!"); // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult)
auto future = delayEvaluation<TResultOther>(loop_)->addTask(std::move(task)); // hackidyhack: delay evaluation of the type of loop_ as it is only forward-declared here auto future = delayEvaluation<TResultOther>(loop_)->addTask(std::move(task), &sharedState_->subTask); // hackidyhack: delay evaluation of the type of loop_ as it is only forward-declared here
return await_transform(future); return await_transform(future);
} }
template<typename TFirstArg, typename TSecondArg, typename... TArgs> template<typename TFirstArg, typename TSecondArg, typename... TArgs>
auto await_transform(Signal<TFirstArg, TSecondArg, TArgs...>& signal) noexcept auto await_transform(Signal<TFirstArg, TSecondArg, TArgs...>& signal) MIJIN_NOEXCEPT
{ {
auto data = std::make_shared<std::tuple<TFirstArg, TSecondArg, TArgs...>>(); auto data = std::make_shared<std::tuple<TFirstArg, TSecondArg, TArgs...>>();
signal.connect([this, data](TFirstArg arg0, TSecondArg arg1, TArgs... args) mutable signal.connect([this, data](TFirstArg arg0, TSecondArg arg1, TArgs... args) mutable
@@ -248,7 +309,7 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
} }
template<typename TFirstArg> template<typename TFirstArg>
auto await_transform(Signal<TFirstArg>& signal) noexcept auto await_transform(Signal<TFirstArg>& signal) MIJIN_NOEXCEPT
{ {
auto data = std::make_shared<TFirstArg>(); auto data = std::make_shared<TFirstArg>();
signal.connect([this, data](TFirstArg arg0) mutable signal.connect([this, data](TFirstArg arg0) mutable
@@ -261,7 +322,7 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
return awaitable; return awaitable;
} }
auto await_transform(Signal<>& signal) noexcept auto await_transform(Signal<>& signal) MIJIN_NOEXCEPT
{ {
signal.connect([this]() signal.connect([this]()
{ {
@@ -272,17 +333,17 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
return awaitable; return awaitable;
} }
std::suspend_always await_transform(std::suspend_always) noexcept std::suspend_always await_transform(std::suspend_always) MIJIN_NOEXCEPT
{ {
state_.status = TaskStatus::SUSPENDED; state_.status = TaskStatus::SUSPENDED;
return std::suspend_always(); return std::suspend_always();
} }
std::suspend_never await_transform(std::suspend_never) noexcept { std::suspend_never await_transform(std::suspend_never) MIJIN_NOEXCEPT {
return std::suspend_never(); return std::suspend_never();
} }
TaskAwaitableSuspend await_transform(TaskAwaitableSuspend) noexcept TaskAwaitableSuspend await_transform(TaskAwaitableSuspend) MIJIN_NOEXCEPT
{ {
state_.status = TaskStatus::SUSPENDED; state_.status = TaskStatus::SUSPENDED;
return TaskAwaitableSuspend(); return TaskAwaitableSuspend();
@@ -290,7 +351,7 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
}; };
template<typename TResult> template<typename TResult>
class TaskBase class [[nodiscard("Tasks should either we awaited or added to a loop.")]] TaskBase
{ {
public: public:
using task_t = TaskBase; using task_t = TaskBase;
@@ -306,22 +367,37 @@ public:
private: private:
handle_t handle_; handle_t handle_;
public: public:
constexpr explicit TaskBase(handle_t handle) noexcept : handle_(handle) {} constexpr explicit TaskBase(handle_t handle) MIJIN_NOEXCEPT : handle_(handle) {
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
if (Result<Stacktrace> stacktrace = captureStacktrace(2); stacktrace.isSuccess())
{
handle_.promise().sharedState_->creationStack_ = *stacktrace;
}
#endif
}
TaskBase(const TaskBase&) = delete; TaskBase(const TaskBase&) = delete;
TaskBase(TaskBase&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {} TaskBase(TaskBase&& other) MIJIN_NOEXCEPT : handle_(std::exchange(other.handle_, nullptr))
~TaskBase() noexcept; {}
~TaskBase() MIJIN_NOEXCEPT;
public: public:
TaskBase& operator=(const TaskBase&) = default; TaskBase& operator=(const TaskBase&) = delete;
TaskBase& operator=(TaskBase&& other) noexcept = default; TaskBase& operator=(TaskBase&& other) MIJIN_NOEXCEPT
{
if (handle_) {
handle_.destroy();
}
handle_ = std::exchange(other.handle_, nullptr);
return *this;
}
[[nodiscard]] [[nodiscard]]
constexpr bool operator==(const TaskBase& other) const noexcept { return handle_ == other.handle_; } constexpr bool operator==(const TaskBase& other) const MIJIN_NOEXCEPT { return handle_ == other.handle_; }
[[nodiscard]] [[nodiscard]]
constexpr bool operator!=(const TaskBase& other) const noexcept { return handle_ != other.handle_; } constexpr bool operator!=(const TaskBase& other) const MIJIN_NOEXCEPT { return handle_ != other.handle_; }
public: public:
[[nodiscard]] [[nodiscard]]
constexpr TaskState<TResult>& state() noexcept constexpr TaskState<TResult>& state() MIJIN_NOEXCEPT
{ {
return handle_.promise().state_; return handle_.promise().state_;
} }
@@ -331,15 +407,19 @@ public:
handle_.resume(); handle_.resume();
return state(); return state();
} }
constexpr std::shared_ptr<TaskSharedState>& sharedState() MIJIN_NOEXCEPT
{
return handle_.promise().sharedState_;
}
private: private:
[[nodiscard]] [[nodiscard]]
constexpr handle_t handle() const noexcept { return handle_; } constexpr handle_t handle() const MIJIN_NOEXCEPT { return handle_; }
[[nodiscard]] [[nodiscard]]
constexpr TaskLoop* getLoop() noexcept constexpr TaskLoop* getLoop() MIJIN_NOEXCEPT
{ {
return handle_.promise().loop_; return handle_.promise().loop_;
} }
constexpr void setLoop(TaskLoop* loop) noexcept constexpr void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT
{ {
// MIJIN_ASSERT(handle_.promise().loop_ == nullptr // MIJIN_ASSERT(handle_.promise().loop_ == nullptr
// || handle_.promise().loop_ == loop // || handle_.promise().loop_ == loop
@@ -358,13 +438,14 @@ class WrappedTaskBase
public: public:
virtual ~WrappedTaskBase() = default; virtual ~WrappedTaskBase() = default;
public: public:
virtual TaskStatus status() noexcept = 0; virtual TaskStatus status() MIJIN_NOEXCEPT = 0;
virtual std::exception_ptr exception() noexcept = 0; virtual std::exception_ptr exception() MIJIN_NOEXCEPT = 0;
// virtual std::any result() noexcept = 0; // virtual std::any result() MIJIN_NOEXCEPT = 0;
virtual void resume() = 0; virtual void resume() = 0;
virtual void* raw() noexcept = 0; virtual void* raw() MIJIN_NOEXCEPT = 0;
virtual std::coroutine_handle<> handle() noexcept = 0; virtual std::coroutine_handle<> handle() MIJIN_NOEXCEPT = 0;
virtual void setLoop(TaskLoop* loop) noexcept = 0; virtual void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT = 0;
virtual std::shared_ptr<TaskSharedState>& sharedState() MIJIN_NOEXCEPT = 0;
[[nodiscard]] inline bool canResume() { [[nodiscard]] inline bool canResume() {
const TaskStatus stat = status(); const TaskStatus stat = status();
@@ -378,16 +459,16 @@ class WrappedTask : public WrappedTaskBase
private: private:
TTask task_; TTask task_;
public: public:
constexpr explicit WrappedTask(TTask&& task) noexcept : task_(std::move(task)) {} constexpr explicit WrappedTask(TTask&& task) MIJIN_NOEXCEPT : task_(std::move(task)) {}
WrappedTask(const WrappedTask&) = delete; WrappedTask(const WrappedTask&) = delete;
WrappedTask(WrappedTask&&) noexcept = default; WrappedTask(WrappedTask&&) MIJIN_NOEXCEPT = default;
public: public:
WrappedTask& operator=(const WrappedTask&) = delete; WrappedTask& operator=(const WrappedTask&) = delete;
WrappedTask& operator=(WrappedTask&&) noexcept = default; WrappedTask& operator=(WrappedTask&&) MIJIN_NOEXCEPT = default;
public: public:
TaskStatus status() noexcept override { return task_.state().status; } TaskStatus status() MIJIN_NOEXCEPT override { return task_.state().status; }
std::exception_ptr exception() noexcept override { return task_.state().exception; } std::exception_ptr exception() MIJIN_NOEXCEPT override { return task_.state().exception; }
// std::any result() noexcept override // std::any result() MIJIN_NOEXCEPT
// { // {
// if constexpr (std::is_same_v<typename TTask::result_t, void>) { // if constexpr (std::is_same_v<typename TTask::result_t, void>) {
// return {}; // return {};
@@ -397,45 +478,14 @@ public:
// } // }
// } // }
void resume() override { task_.resume(); } void resume() override { task_.resume(); }
void* raw() noexcept override { return &task_; } void* raw() MIJIN_NOEXCEPT override { return &task_; }
std::coroutine_handle<> handle() noexcept override { return task_.handle(); } std::coroutine_handle<> handle() MIJIN_NOEXCEPT override { return task_.handle(); }
void setLoop(TaskLoop* loop) noexcept override { task_.setLoop(loop); } void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT override { task_.setLoop(loop); }
}; virtual std::shared_ptr<TaskSharedState>& sharedState() MIJIN_NOEXCEPT override { return task_.sharedState(); }
struct TaskSharedState
{
std::atomic_bool cancelled_ = false;
};
class TaskHandle
{
private:
std::weak_ptr<TaskSharedState> state_;
public:
TaskHandle() = default;
explicit TaskHandle(std::weak_ptr<TaskSharedState> state) noexcept : state_(std::move(state)) {}
TaskHandle(const TaskHandle&) = default;
TaskHandle(TaskHandle&&) = default;
TaskHandle& operator=(const TaskHandle&) = default;
TaskHandle& operator=(TaskHandle&&) = default;
[[nodiscard]] bool isValid() const noexcept
{
return !state_.expired();
}
void cancel() const noexcept
{
if (std::shared_ptr<TaskSharedState> state = state_.lock())
{
state->cancelled_ = true;
}
}
}; };
template<typename TTask> template<typename TTask>
std::unique_ptr<WrappedTask<TTask>> wrapTask(TTask&& task) noexcept std::unique_ptr<WrappedTask<TTask>> wrapTask(TTask&& task) MIJIN_NOEXCEPT
{ {
return std::make_unique<WrappedTask<TTask>>(std::forward<TTask>(task)); return std::make_unique<WrappedTask<TTask>>(std::forward<TTask>(task));
} }
@@ -450,7 +500,6 @@ public:
using wrapped_task_base_ptr_t = std::unique_ptr<wrapped_task_t>; using wrapped_task_base_ptr_t = std::unique_ptr<wrapped_task_t>;
struct StoredTask struct StoredTask
{ {
std::shared_ptr<TaskSharedState> sharedState;
wrapped_task_base_ptr_t task; wrapped_task_base_ptr_t task;
std::function<void(StoredTask&)> setFuture; std::function<void(StoredTask&)> setFuture;
std::any resultData; std::any resultData;
@@ -465,29 +514,29 @@ protected:
exception_handler_t uncaughtExceptionHandler_; exception_handler_t uncaughtExceptionHandler_;
public: public:
TaskLoop() = default; TaskLoop() MIJIN_NOEXCEPT = default;
TaskLoop(const TaskLoop&) = delete; TaskLoop(const TaskLoop&) = delete;
TaskLoop(TaskLoop&&) = delete; TaskLoop(TaskLoop&&) = delete;
virtual ~TaskLoop() = default; virtual ~TaskLoop() MIJIN_NOEXCEPT = default;
TaskLoop& operator=(const TaskLoop&) = delete; TaskLoop& operator=(const TaskLoop&) = delete;
TaskLoop& operator=(TaskLoop&&) = delete; TaskLoop& operator=(TaskLoop&&) = delete;
void setUncaughtExceptionHandler(exception_handler_t handler) noexcept { uncaughtExceptionHandler_ = std::move(handler); } void setUncaughtExceptionHandler(exception_handler_t handler) MIJIN_NOEXCEPT { uncaughtExceptionHandler_ = std::move(handler); }
template<typename TResult> template<typename TResult>
inline FuturePtr<TResult> addTask(TaskBase<TResult> task, TaskHandle* outHandle = nullptr) noexcept; inline FuturePtr<TResult> addTask(TaskBase<TResult> task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT;
virtual void transferCurrentTask(TaskLoop& otherLoop) noexcept = 0; virtual void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT = 0;
virtual void addStoredTask(StoredTask&& storedTask) noexcept = 0; virtual void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT = 0;
[[nodiscard]] static TaskLoop& current() noexcept; [[nodiscard]] static TaskLoop& current() MIJIN_NOEXCEPT;
protected: protected:
inline TaskStatus tickTask(StoredTask& task); inline TaskStatus tickTask(StoredTask& task);
protected: protected:
static inline TaskLoop*& currentLoopStorage() noexcept; static inline TaskLoop*& currentLoopStorage() MIJIN_NOEXCEPT;
template<typename TResult> template<typename TResult>
static inline void setFutureHelper(StoredTask& storedTask) noexcept; static inline void setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT;
}; };
template<typename TResult = void> template<typename TResult = void>
@@ -503,13 +552,17 @@ private:
std::thread::id threadId_; std::thread::id threadId_;
public: // TaskLoop implementation public: // TaskLoop implementation
void transferCurrentTask(TaskLoop& otherLoop) noexcept override; void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override;
void addStoredTask(StoredTask&& storedTask) noexcept override; void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT override;
public: // public interface public: // public interface
[[nodiscard]] constexpr bool empty() const noexcept { return tasks_.empty() && newTasks_.empty(); } [[nodiscard]] constexpr bool empty() const MIJIN_NOEXCEPT { return tasks_.empty() && newTasks_.empty(); }
[[nodiscard]] constexpr std::size_t getNumTasks() const MIJIN_NOEXCEPT { return tasks_.size() + newTasks_.size(); }
[[nodiscard]] std::size_t getActiveTasks() const MIJIN_NOEXCEPT;
inline CanContinue tick(); inline CanContinue tick();
inline void runUntilDone(IgnoreWaiting ignoreWaiting = IgnoreWaiting::NO); inline void runUntilDone(IgnoreWaiting ignoreWaiting = IgnoreWaiting::NO);
inline void cancelAllTasks() MIJIN_NOEXCEPT;
[[nodiscard]] inline std::vector<TaskHandle> getAllTasks() const MIJIN_NOEXCEPT;
private: private:
inline void assertCorrectThread() { MIJIN_ASSERT(threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id(), "Unsafe to TaskLoop from different thread!"); } inline void assertCorrectThread() { MIJIN_ASSERT(threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id(), "Unsafe to TaskLoop from different thread!"); }
}; };
@@ -525,8 +578,8 @@ private:
std::vector<std::jthread> workerThreads_; std::vector<std::jthread> workerThreads_;
public: // TaskLoop implementation public: // TaskLoop implementation
void transferCurrentTask(TaskLoop& otherLoop) noexcept override; void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override;
void addStoredTask(StoredTask&& storedTask) noexcept override; void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT override;
public: // public interface public: // public interface
void start(std::size_t numWorkerThreads); void start(std::size_t numWorkerThreads);
@@ -546,15 +599,37 @@ extern thread_local TaskLoop::StoredTask* gCurrentTask;
inline void throwIfCancelled() inline void throwIfCancelled()
{ {
if (gCurrentTask->sharedState->cancelled_) #if MIJIN_COROUTINE_ENABLE_CANCEL
if (gCurrentTask->task->sharedState()->cancelled_)
{ {
throw TaskCancelled(); throw TaskCancelled();
} }
#endif
} }
} }
void TaskHandle::cancel() const MIJIN_NOEXCEPT
{
if (std::shared_ptr<TaskSharedState> state = state_.lock())
{
state->cancelled_ = true;
state->subTask.cancel();
}
}
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
Optional<Stacktrace> TaskHandle::getCreationStack() const MIJIN_NOEXCEPT
{
if (std::shared_ptr<TaskSharedState> state = state_.lock())
{
return state->creationStack_;
}
return NULL_OPTIONAL;
}
#endif // MIJIN_COROUTINE_ENABLE_DEBUG_INFO
template<typename TResult> template<typename TResult>
TaskBase<TResult>::~TaskBase() noexcept TaskBase<TResult>::~TaskBase() MIJIN_NOEXCEPT
{ {
if (handle_) if (handle_)
{ {
@@ -563,23 +638,21 @@ TaskBase<TResult>::~TaskBase() noexcept
} }
template<typename TResult> template<typename TResult>
inline FuturePtr<TResult> TaskLoop::addTask(TaskBase<TResult> task, TaskHandle* outHandle) noexcept inline FuturePtr<TResult> TaskLoop::addTask(TaskBase<TResult> task, TaskHandle* outHandle) MIJIN_NOEXCEPT
{ {
MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!"); MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!");
task.setLoop(this); task.setLoop(this);
auto sharedState = std::make_shared<TaskSharedState>();
auto future = std::make_shared<Future<TResult>>(); auto future = std::make_shared<Future<TResult>>();
auto setFuture = &setFutureHelper<TResult>; auto setFuture = &setFutureHelper<TResult>;
if (outHandle != nullptr) if (outHandle != nullptr)
{ {
*outHandle = TaskHandle(sharedState); *outHandle = TaskHandle(task.sharedState());
} }
// add tasks to a seperate vector first as we might be running another task right now // add tasks to a seperate vector first as we might be running another task right now
addStoredTask(StoredTask{ addStoredTask(StoredTask{
.sharedState = std::move(sharedState),
.task = wrapTask(std::move(task)), .task = wrapTask(std::move(task)),
.setFuture = setFuture, .setFuture = setFuture,
.resultData = future .resultData = future
@@ -600,13 +673,16 @@ inline TaskStatus TaskLoop::tickTask(StoredTask& task)
while (status == TaskStatus::RUNNING); while (status == TaskStatus::RUNNING);
impl::gCurrentTask = nullptr; impl::gCurrentTask = nullptr;
#if MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING
if (task.task && task.task->exception()) if (task.task && task.task->exception())
{ {
try try
{ {
std::rethrow_exception(task.task->exception()); std::rethrow_exception(task.task->exception());
} }
#if MIJIN_COROUTINE_ENABLE_CANCEL
catch(TaskCancelled&) {} // ignore those catch(TaskCancelled&) {} // ignore those
#endif
catch(...) catch(...)
{ {
if (uncaughtExceptionHandler_) if (uncaughtExceptionHandler_)
@@ -622,6 +698,7 @@ inline TaskStatus TaskLoop::tickTask(StoredTask& task)
// TODO: handle the exception somehow, others may be waiting // TODO: handle the exception somehow, others may be waiting
return TaskStatus::FINISHED; return TaskStatus::FINISHED;
} }
#endif // MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING
if (status == TaskStatus::YIELDED || status == TaskStatus::FINISHED) if (status == TaskStatus::YIELDED || status == TaskStatus::FINISHED)
{ {
task.setFuture(task); task.setFuture(task);
@@ -629,20 +706,20 @@ inline TaskStatus TaskLoop::tickTask(StoredTask& task)
return status; return status;
} }
/* static */ inline auto TaskLoop::current() noexcept -> TaskLoop& /* static */ inline auto TaskLoop::current() MIJIN_NOEXCEPT -> TaskLoop&
{ {
MIJIN_ASSERT(currentLoopStorage() != nullptr, "Attempting to fetch current loop while no coroutine is running!"); MIJIN_ASSERT(currentLoopStorage() != nullptr, "Attempting to fetch current loop while no coroutine is running!");
return *currentLoopStorage(); return *currentLoopStorage();
} }
/* static */ auto TaskLoop::currentLoopStorage() noexcept -> TaskLoop*& /* static */ auto TaskLoop::currentLoopStorage() MIJIN_NOEXCEPT -> TaskLoop*&
{ {
static thread_local TaskLoop* storage = nullptr; static thread_local TaskLoop* storage = nullptr;
return storage; return storage;
} }
template<typename TResult> template<typename TResult>
/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) noexcept /* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT
{ {
TaskBase<TResult>& task = *static_cast<TaskBase<TResult>*>(storedTask.task->raw()); TaskBase<TResult>& task = *static_cast<TaskBase<TResult>*>(storedTask.task->raw());
auto future = std::any_cast<FuturePtr<TResult>>(storedTask.resultData); auto future = std::any_cast<FuturePtr<TResult>>(storedTask.resultData);
@@ -703,6 +780,11 @@ inline auto SimpleTaskLoop::tick() -> CanContinue
{ {
StoredTask& task = *currentTask_; StoredTask& task = *currentTask_;
TaskStatus status = task.task->status(); TaskStatus status = task.task->status();
if (status == TaskStatus::WAITING && task.task->sharedState()->cancelled_)
{
// always continue a cancelled task, even if it was still waiting for a result
status = TaskStatus::SUSPENDED;
}
if (status != TaskStatus::SUSPENDED && status != TaskStatus::YIELDED) if (status != TaskStatus::SUSPENDED && status != TaskStatus::YIELDED)
{ {
MIJIN_ASSERT(status == TaskStatus::WAITING, "Task with invalid status in task list!"); MIJIN_ASSERT(status == TaskStatus::WAITING, "Task with invalid status in task list!");
@@ -740,6 +822,29 @@ inline void SimpleTaskLoop::runUntilDone(IgnoreWaiting ignoreWaiting)
} }
} }
inline void SimpleTaskLoop::cancelAllTasks() MIJIN_NOEXCEPT
{
for (StoredTask& task : mijin::chain(tasks_, newTasks_))
{
task.task->sharedState()->cancelled_ = true;
}
for (StoredTask& task : queuedTasks_)
{
// just discard it
(void) task;
}
}
inline std::vector<TaskHandle> SimpleTaskLoop::getAllTasks() const MIJIN_NOEXCEPT
{
std::vector<TaskHandle> result;
for (const StoredTask& task : mijin::chain(tasks_, newTasks_))
{
result.emplace_back(task.task->sharedState());
}
return result;
}
// utility stuff // utility stuff
inline TaskAwaitableSuspend c_suspend() { inline TaskAwaitableSuspend c_suspend() {
@@ -764,8 +869,11 @@ Task<> c_allDone(const TCollection<FuturePtr<TType>, TTemplateArgs...>& futures)
} while (!allDone); } while (!allDone);
} }
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO [[nodiscard]] inline TaskHandle getCurrentTask() MIJIN_NOEXCEPT
#endif {
MIJIN_ASSERT(impl::gCurrentTask != nullptr, "Attempt to call getCurrentTask() outside of task.");
return TaskHandle(impl::gCurrentTask->task->sharedState());
}
} }
#endif // MIJIN_ASYNC_COROUTINE_HPP_INCLUDED #endif // MIJIN_ASYNC_COROUTINE_HPP_INCLUDED

View 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)

View File

@@ -8,8 +8,9 @@
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include "./signal.hpp" #include "./signal.hpp"
#include "../debug/assert.hpp"
#include "../container/optional.hpp" #include "../container/optional.hpp"
#include "../debug/assert.hpp"
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -34,21 +35,21 @@ namespace impl
template<typename TValue> template<typename TValue>
struct FutureStorage struct FutureStorage
{ {
std::optional<TValue> value; Optional<TValue> value;
void setValue(TValue value_) noexcept { value = std::move(value_); } void setValue(TValue value_) MIJIN_NOEXCEPT { value = std::move(value_); }
[[nodiscard]] TValue& getValue() noexcept { return value.value(); } [[nodiscard]] TValue& getValue() MIJIN_NOEXCEPT { return value.get(); }
}; };
template<typename TValue> // template<typename TValue>
struct FutureStorage<TValue&> // struct FutureStorage<TValue&>
{ // {
std::optional<TValue*> value; // Optional<TValue*> value;
//
void setValue(TValue& value_) noexcept { value = &value_; } // void setValue(TValue& value_) MIJIN_NOEXCEPT { value = &value_; }
[[nodiscard]] TValue& getValue() const noexcept { return *value.value(); } // [[nodiscard]] TValue& getValue() const MIJIN_NOEXCEPT { return *value.get(); }
}; // };
template<> template<>
struct FutureStorage<void> struct FutureStorage<void>
@@ -65,19 +66,19 @@ private:
public: public:
Future() = default; Future() = default;
Future(const Future&) = delete; Future(const Future&) = delete;
Future(Future&&) noexcept = default; Future(Future&&) MIJIN_NOEXCEPT = default;
public: public:
Future& operator=(const Future&) = delete; Future& operator=(const Future&) = delete;
Future& operator=(Future&&) noexcept = default; Future& operator=(Future&&) MIJIN_NOEXCEPT = default;
[[nodiscard]] [[nodiscard]]
constexpr explicit operator bool() const noexcept { return ready(); } constexpr explicit operator bool() const MIJIN_NOEXCEPT { return ready(); }
[[nodiscard]] [[nodiscard]]
constexpr bool operator!() const noexcept { return !ready(); } constexpr bool operator!() const MIJIN_NOEXCEPT { return !ready(); }
public: // access public: // access
[[nodiscard]] [[nodiscard]]
constexpr decltype(auto) get() noexcept constexpr decltype(auto) get() MIJIN_NOEXCEPT
{ {
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready."); MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) { if constexpr(std::is_same_v<TValue, void>) {
@@ -88,7 +89,7 @@ public: // access
} }
} }
[[nodiscard]] [[nodiscard]]
constexpr decltype(auto) get() const noexcept constexpr decltype(auto) get() const MIJIN_NOEXCEPT
{ {
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready."); MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) { if constexpr(std::is_same_v<TValue, void>) {
@@ -99,20 +100,20 @@ public: // access
} }
} }
[[nodiscard]] [[nodiscard]]
constexpr bool ready() const noexcept constexpr bool ready() const MIJIN_NOEXCEPT
{ {
return isSet_; return isSet_;
} }
public: // modification public: // modification
template<typename TArg> requires (!std::is_same_v<TValue, void>) 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(!isSet_, "Trying to set a future twice!");
value_.setValue(std::move(value)); value_.setValue(std::move(value));
isSet_ = true; isSet_ = true;
sigSet.emit(); 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!"); MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
isSet_ = true; isSet_ = true;

View File

@@ -8,6 +8,7 @@
#include <atomic> #include <atomic>
#include <optional> #include <optional>
#include <thread> #include <thread>
#include "../internal/common.hpp"
#include "../util/bitarray.hpp" #include "../util/bitarray.hpp"
namespace mijin namespace mijin
@@ -25,9 +26,59 @@ namespace mijin
// public types // 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> template<typename TMessage, std::size_t bufferSize = 32>
class MessageQueue class MessageQueue
{ {
public:
using message_t = TMessage;
using iterator_t = MessageQueueIterator<MessageQueue<TMessage, bufferSize>>;
private: private:
std::array<TMessage, bufferSize> messages; std::array<TMessage, bufferSize> messages;
mijin::BitArray<bufferSize, true> messageReady; mijin::BitArray<bufferSize, true> messageReady;
@@ -36,10 +87,10 @@ private:
public: public:
MessageQueue() = default; MessageQueue() = default;
MessageQueue(const MessageQueue&) = delete; MessageQueue(const MessageQueue&) = delete;
MessageQueue(MessageQueue&&) noexcept = delete; MessageQueue(MessageQueue&&) = delete;
MessageQueue& operator=(const MessageQueue&) = delete; MessageQueue& operator=(const MessageQueue&) = delete;
MessageQueue& operator=(MessageQueue&&) noexcept = delete; MessageQueue& operator=(MessageQueue&&) = delete;
[[nodiscard]] bool tryPushMaybeMove(TMessage& message); [[nodiscard]] bool tryPushMaybeMove(TMessage& message);
[[nodiscard]] bool tryPush(TMessage message) { [[nodiscard]] bool tryPush(TMessage message) {
@@ -48,6 +99,9 @@ public:
void push(TMessage message); void push(TMessage message);
[[nodiscard]] std::optional<TMessage> tryPop(); [[nodiscard]] std::optional<TMessage> tryPop();
[[nodiscard]] TMessage wait(); [[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> template<typename TRequest, typename TResponse, std::size_t requestBufferSize = 32, std::size_t responseBufferSize = 32>

View File

@@ -4,11 +4,13 @@
#if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED) #if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED)
#define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1 #define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1
#include <algorithm>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include "../internal/common.hpp"
#include "../util/flag.hpp" #include "../util/flag.hpp"
namespace mijin namespace mijin
@@ -22,6 +24,9 @@ namespace mijin
// public constants // 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 // public types
// //
@@ -33,7 +38,7 @@ class Signal
{ {
public: public:
using handler_t = std::function<void(TArgs...)>; using handler_t = std::function<void(TArgs...)>;
using token_t = std::uint32_t; using token_t = signal_token_t;
private: private:
struct RegisteredHandler struct RegisteredHandler
{ {
@@ -50,17 +55,19 @@ private:
public: public:
Signal() = default; Signal() = default;
Signal(const Signal&) = delete; Signal(const Signal&) = delete;
Signal(Signal&&) noexcept = default; Signal(Signal&&) MIJIN_NOEXCEPT = default;
public: public:
Signal& operator=(const Signal&) = delete; Signal& operator=(const Signal&) = delete;
Signal& operator=(Signal&&) noexcept = default; Signal& operator=(Signal&&) MIJIN_NOEXCEPT = default;
public: public:
template<typename THandler, typename TWeak = void> 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> 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 token_t connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
inline void disconnect(token_t token) noexcept; inline void disconnect(token_t token) MIJIN_NOEXCEPT;
inline void emit(TArgs&&... args) noexcept;
template<typename... TArgs2>
inline void emit(TArgs2&&... args) MIJIN_NOEXCEPT;
}; };
// //
@@ -69,7 +76,7 @@ public:
template<typename... TArgs> template<typename... TArgs>
template<typename THandler, typename TWeak> template<typename THandler, typename TWeak>
inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) noexcept -> token_t inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
{ {
std::lock_guard lock(handlersMutex_); std::lock_guard lock(handlersMutex_);
@@ -86,7 +93,7 @@ inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::we
template<typename... TArgs> template<typename... TArgs>
template<typename TObject, typename TWeak> template<typename TObject, typename TWeak>
inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) noexcept -> token_t inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
{ {
std::lock_guard lock(handlersMutex_); std::lock_guard lock(handlersMutex_);
@@ -105,7 +112,7 @@ inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)
} }
template<typename... TArgs> template<typename... TArgs>
inline void Signal<TArgs...>::disconnect(token_t token) noexcept inline void Signal<TArgs...>::disconnect(token_t token) MIJIN_NOEXCEPT
{ {
std::lock_guard lock(handlersMutex_); std::lock_guard lock(handlersMutex_);
@@ -117,7 +124,8 @@ inline void Signal<TArgs...>::disconnect(token_t token) noexcept
} }
template<typename... TArgs> template<typename... TArgs>
inline void Signal<TArgs...>::emit(TArgs&&... args) noexcept template<typename... TArgs2>
inline void Signal<TArgs...>::emit(TArgs2&&... args) MIJIN_NOEXCEPT
{ {
std::lock_guard lock(handlersMutex_); std::lock_guard lock(handlersMutex_);
@@ -132,7 +140,7 @@ inline void Signal<TArgs...>::emit(TArgs&&... args) noexcept
// invoke all handlers // invoke all handlers
for (RegisteredHandler& handler : handlers_) for (RegisteredHandler& handler : handlers_)
{ {
handler.callable(std::forward<TArgs>(args)...); handler.callable(std::forward<TArgs2>(args)...);
} }
// remove any oneshot // remove any oneshot

View 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

View File

@@ -53,7 +53,7 @@ public:
#if MIJIN_BOXED_OBJECT_DEBUG #if MIJIN_BOXED_OBJECT_DEBUG
~BoxedObject() ~BoxedObject()
{ {
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!") MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!");
} }
#endif #endif
@@ -69,7 +69,7 @@ public:
void construct(TArgs&&... args) void construct(TArgs&&... args)
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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; constructed = true;
#endif #endif
std::construct_at(&object_, std::forward<TArgs>(args)...); std::construct_at(&object_, std::forward<TArgs>(args)...);
@@ -78,7 +78,7 @@ public:
void destroy() void destroy()
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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; constructed = false;
#endif #endif
std::destroy_at(&object_); std::destroy_at(&object_);
@@ -87,7 +87,7 @@ public:
void copyTo(BoxedObject& other) const void copyTo(BoxedObject& other) const
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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 #endif
other.construct(object_); other.construct(object_);
} }
@@ -95,7 +95,7 @@ public:
void moveTo(BoxedObject& other) void moveTo(BoxedObject& other)
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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 #endif
other.construct(std::move(object_)); other.construct(std::move(object_));
destroy(); destroy();
@@ -104,7 +104,7 @@ public:
[[nodiscard]] T& get() [[nodiscard]] T& get()
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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 #endif
return object_; return object_;
} }
@@ -112,7 +112,7 @@ public:
[[nodiscard]] const T& get() const [[nodiscard]] const T& get() const
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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 #endif
return object_; return object_;
} }

View File

@@ -81,6 +81,9 @@ public:
template<typename TMap> template<typename TMap>
MapView(TMap& map) -> MapView<typename TMap::key_type, typename TMap::mapped_type, 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 // public functions
// //

View File

@@ -0,0 +1,84 @@
#pragma once
#if !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)
#define MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED 1
#include <bit>
#include <cstddef>
#include <span>
#include "../debug/assert.hpp"
#include "../util/traits.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename TBytes>
class MemoryViewBase
{
public:
using size_type = std::size_t;
private:
std::span<TBytes> bytes_;
public:
MemoryViewBase() noexcept = default;
MemoryViewBase(const MemoryViewBase&) noexcept = default;
MemoryViewBase(copy_const_t<TBytes, void>* data, std::size_t size) noexcept : bytes_(static_cast<TBytes*>(data), size) {}
MemoryViewBase& operator=(const MemoryViewBase&) noexcept = default;
[[nodiscard]] void* data() noexcept { return bytes_.data(); }
[[nodiscard]] const void* data() const noexcept { return bytes_.data(); }
[[nodiscard]] size_type byteSize() const noexcept { return bytes_.size(); }
[[nodiscard]] bool empty() const noexcept { return bytes_.empty(); }
template<typename T>
[[nodiscard]] std::span<T> makeSpan();
template<typename T>
[[nodiscard]] std::span<const T> makeSpan() const;
};
using MemoryView = MemoryViewBase<std::byte>;
using ConstMemoryView = MemoryViewBase<const std::byte>;
//
// public functions
//
template<typename TBytes>
template<typename T>
std::span<T> MemoryViewBase<TBytes>::makeSpan()
{
static_assert(!std::is_const_v<TBytes>, "Cannot create writable spans from const memory views.");
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type.");
return {
std::bit_cast<T*>(bytes_.data()),
std::bit_cast<T*>(bytes_.data() + bytes_.size())
};
}
template<typename TBytes>
template<typename T>
std::span<const T> MemoryViewBase<TBytes>::makeSpan() const
{
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type.");
return {
std::bit_cast<const T*>(bytes_.data()),
std::bit_cast<const T*>(bytes_.data() + bytes_.size())
};
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)

View File

@@ -5,9 +5,11 @@
#define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1 #define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1
#include <cstdint> #include <cstdint>
#include <functional>
#include <utility> #include <utility>
#include "../debug/assert.hpp" #include "../debug/assert.hpp"
#include "../util/concepts.hpp" #include "../util/concepts.hpp"
#include "../util/traits.hpp"
namespace mijin namespace mijin
{ {
@@ -33,7 +35,7 @@ struct OptionalStorage
std::uint8_t used = 0; std::uint8_t used = 0;
[[nodiscard]] [[nodiscard]]
constexpr bool empty() const noexcept { return !used; } constexpr bool empty() const noexcept { return !used; } // NOLINT(clang-analyzer-core.uninitialized.UndefReturn)
template<typename... TArgs> template<typename... TArgs>
constexpr void emplace(TArgs&&... args) noexcept constexpr void emplace(TArgs&&... args) noexcept
@@ -108,8 +110,10 @@ static constexpr NullOptional NULL_OPTIONAL;
template<typename TValue> template<typename TValue>
class Optional class Optional
{ {
public:
using value_t = TValue;
private: private:
impl::OptionalStorage<TValue> storage_; impl::OptionalStorage<TValue> storage_ = {};
public: public:
constexpr Optional() = default; constexpr Optional() = default;
constexpr Optional(NullOptional) noexcept {} constexpr Optional(NullOptional) noexcept {}
@@ -154,6 +158,56 @@ public:
[[nodiscard]] inline const std::remove_reference_t<TValue>& get() const noexcept; [[nodiscard]] inline const std::remove_reference_t<TValue>& get() const noexcept;
[[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); } [[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); }
inline void reset() noexcept; 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 (other)
{ {
emplace(std::move(other.get())); if constexpr (!std::is_reference_v<TValue>) {
emplace(std::move(other.get()));
}
else {
emplace(other.get());
}
other.reset(); other.reset();
} }
} }

View File

@@ -4,6 +4,7 @@
#if !defined(MIJIN_CONTAINER_STRIDE_SPAN_HPP_INCLUDED) #if !defined(MIJIN_CONTAINER_STRIDE_SPAN_HPP_INCLUDED)
#define MIJIN_CONTAINER_STRIDE_SPAN_HPP_INCLUDED 1 #define MIJIN_CONTAINER_STRIDE_SPAN_HPP_INCLUDED 1
#include <bit>
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
#include <type_traits> #include <type_traits>
@@ -117,10 +118,15 @@ public:
StrideSpan& operator=(const StrideSpan&) = default; StrideSpan& operator=(const StrideSpan&) = default;
auto operator<=>(const StrideSpan&) const noexcept = 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); reference operator[](size_type index);
const_reference operator[](size_type index) const; 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]] iterator begin() { return StrideSpanIterator<T>(begin_, strideBytes_, begin_, end_); }
[[nodiscard]] const_iterator begin() const { return StrideSpanIterator<const 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 // 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> template<typename T>
StrideSpanIterator<T>& StrideSpanIterator<T>::operator+=(difference_type diff) 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 auto StrideSpan<T>::operator[](size_type index) const -> const_reference
{ {
MIJIN_ASSERT(index < size(), "Attempting to access StrideSpan out of bounds."); MIJIN_ASSERT(index < size(), "Attempting to access StrideSpan out of bounds.");
return byteoffset(begin_, index * strideBytes_); return *byteoffset(begin_, index * strideBytes_);
} }
} // namespace mijin } // namespace mijin

View File

@@ -4,8 +4,11 @@
#if !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED) #if !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)
#define MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED 1 #define MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED 1
#include <bit>
#include <cstddef> #include <cstddef>
#include <cstring>
#include <span> #include <span>
#include <string_view>
#include <vector> #include <vector>
#include "../debug/assert.hpp" #include "../debug/assert.hpp"
@@ -34,9 +37,13 @@ public:
private: private:
std::vector<std::byte> bytes_; std::vector<std::byte> bytes_;
public: public:
[[nodiscard]] void* data() { return bytes_.data(); } auto operator<=>(const TypelessBuffer&) const noexcept = default;
[[nodiscard]] const void* data() const { return bytes_.data(); }
[[nodiscard]] size_type byteSize() const { return bytes_.size(); } [[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 resize(size_type numBytes) { bytes_.resize(numBytes); }
void reserve(size_type numBytes) { bytes_.reserve(numBytes); } void reserve(size_type numBytes) { bytes_.reserve(numBytes); }
@@ -48,6 +55,18 @@ public:
template<typename T> template<typename T>
[[nodiscard]] std::span<const T> makeSpan() const; [[nodiscard]] std::span<const T> makeSpan() const;
template<typename TChar = char, typename TTraits = std::char_traits<TChar>>
[[nodiscard]] std::basic_string_view<TChar, TTraits> makeStringView() const ;
template<typename T>
void append(std::span<const T> data);
template<typename T>
[[nodiscard]] T& dataAt(size_type offset) MIJIN_NOEXCEPT;
template<typename T>
[[nodiscard]] const T& dataAt(size_type offset) const MIJIN_NOEXCEPT;
}; };
template<typename T> template<typename T>
@@ -82,6 +101,12 @@ public:
MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer."); MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer.");
buffer_->reserve(numElements * sizeof(T)); 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 iterator begin() { return buffer_ ? static_cast<T*>(buffer_->data()) : nullptr; }
[[nodiscard]] inline const_iterator begin() const { return buffer_ ? static_cast<T*>(buffer_->data()) : nullptr; } [[nodiscard]] inline const_iterator begin() const { return buffer_ ? static_cast<T*>(buffer_->data()) : nullptr; }
@@ -125,6 +150,38 @@ std::span<const T> TypelessBuffer::makeSpan() const
std::bit_cast<const T*>(bytes_.data() + bytes_.size()) std::bit_cast<const T*>(bytes_.data() + bytes_.size())
}; };
} }
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> TypelessBuffer::makeStringView() const
{
MIJIN_ASSERT(bytes_.size() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type.");
return {std::bit_cast<const TChar*>(bytes_.data()), bytes_.size() / sizeof(TChar)};
}
template<typename T>
void TypelessBuffer::append(std::span<const T> data)
{
bytes_.resize(bytes_.size() + data.size_bytes());
std::memcpy(bytes_.data() + bytes_.size() - data.size_bytes(), data.data(), data.size_bytes());
}
template<typename T>
T& TypelessBuffer::dataAt(size_type offset) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range.");
return *std::bit_cast<T*>(bytes_.data() + offset);
}
template<typename T>
const T& TypelessBuffer::dataAt(size_type offset) const MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range.");
return *std::bit_cast<const T*>(bytes_.data() + offset);
}
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED) #endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)

View 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)

View File

@@ -0,0 +1,272 @@
#pragma once
#if !defined(MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED)
#define MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED 1
#include <stdexcept>
#include <vector>
#include "./boxed_object.hpp"
#include "./optional.hpp"
#include "../internal/common.hpp"
namespace mijin
{
template<typename TKey, typename TValue>
struct VectorMapIterator
{
public:
using difference_type = std::ptrdiff_t;
private:
Optional<std::pair<TKey&, TValue&>> ref_;
public:
VectorMapIterator(TKey* key, TValue* value)
{
ref_.emplace(std::tie(*key, *value));
}
VectorMapIterator(const VectorMapIterator& other) MIJIN_NOEXCEPT : VectorMapIterator(&other.ref_->first, &other.ref_->second) {}
~VectorMapIterator() noexcept
{
ref_.reset();
}
VectorMapIterator& operator=(const VectorMapIterator& other) MIJIN_NOEXCEPT
{
if (this != &other)
{
ref_.reset();
ref_.emplace(std::tie(other.ref_->first, other.ref_->second));
}
return *this;
}
bool operator==(const VectorMapIterator& other) const MIJIN_NOEXCEPT { return &ref_->first == &other.ref_->first; }
bool operator!=(const VectorMapIterator& other) const MIJIN_NOEXCEPT { return &ref_->first != &other.ref_->first; }
std::pair<TKey&, TValue&> operator*() const MIJIN_NOEXCEPT
{
return *ref_;
}
const std::pair<TKey&, TValue&>* operator->() const MIJIN_NOEXCEPT
{
return &*ref_;
}
VectorMapIterator& operator++() MIJIN_NOEXCEPT
{
TKey* oldKey = &ref_->first;
TValue* oldValue = &ref_->second;
ref_.reset();
ref_.emplace(std::tie(*(++oldKey), *(++oldValue)));
return *this;
}
VectorMapIterator operator++(int) const MIJIN_NOEXCEPT
{
VectorMapIterator copy(*this);
++(*this);
return copy;
}
VectorMapIterator& operator--() MIJIN_NOEXCEPT
{
TKey* oldKey = &ref_->first;
TValue* oldValue = &ref_->second;
ref_.reset();
ref_.emplace(std::tie(*(--oldKey), *(--oldValue)));
return *this;
}
VectorMapIterator operator--(int) const MIJIN_NOEXCEPT
{
VectorMapIterator copy(*this);
--(*this);
return copy;
}
VectorMapIterator operator+(difference_type diff) const MIJIN_NOEXCEPT
{
return VectorMapIterator(&ref_->first + diff, &ref_->second + diff);
}
VectorMapIterator operator-(difference_type diff) const MIJIN_NOEXCEPT
{
return VectorMapIterator(&ref_->first - diff, &ref_->second - diff);
}
difference_type operator-(const VectorMapIterator& other) const MIJIN_NOEXCEPT
{
return &ref_->first - &other.ref_->first;
}
};
template<typename TKey, typename TValue, typename TKeyAllocator = std::allocator<TKey>, typename TValueAllocator = std::allocator<TValue>>
class VectorMap
{
public:
using key_type = TKey;
using mapped_type = TValue;
using value_type = std::pair<const TKey, TValue>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = VectorMapIterator<const TKey, TValue>;
using const_iterator = VectorMapIterator<const TKey, const TValue>;
private:
std::vector<TKey, TKeyAllocator> keys_;
std::vector<TValue, TValueAllocator> values_;
public:
VectorMap() noexcept = default;
VectorMap(const VectorMap&) = default;
VectorMap(VectorMap&&) MIJIN_NOEXCEPT = default;
VectorMap& operator=(const VectorMap&) = default;
VectorMap& operator=(VectorMap&&) MIJIN_NOEXCEPT = default;
auto operator<=>(const VectorMap& other) const noexcept = default;
TValue& operator[](const TKey& key)
{
auto it = find(key);
if (it != end())
{
return it->second;
}
return emplace(key, TValue()).first->second;
}
const TValue& operator[](const TKey& key) const
{
return at(key);
}
[[nodiscard]]
iterator begin() MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
[[nodiscard]]
const_iterator begin() const MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
[[nodiscard]]
const_iterator cbegin() const MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
[[nodiscard]]
iterator end() MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
[[nodiscard]]
const_iterator end() const MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
[[nodiscard]]
const_iterator cend() const MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
[[nodiscard]]
TValue& at(const TKey& key)
{
auto it = find(key);
if (it == end())
{
throw std::out_of_range("key not found in map");
}
return it->second;
}
[[nodiscard]]
const TValue& at(const TKey& key) const
{
auto it = find(key);
if (it == end())
{
throw std::out_of_range("key not found in map");
}
return it->second;
}
[[nodiscard]]
bool empty() const MIJIN_NOEXCEPT { return keys_.empty(); }
[[nodiscard]]
size_type size() const MIJIN_NOEXCEPT { return keys_.size(); }
[[nodiscard]]
size_type max_size() const MIJIN_NOEXCEPT { return std::min(keys_.max_size(), values_.max_size()); }
void reserve(std::size_t size)
{
keys_.reserve(size);
values_.reserve(size);
}
void clear()
{
keys_.clear();
values_.clear();
}
template<typename... TArgs>
std::pair<iterator, bool> emplace(TArgs&&... args)
{
std::pair<TKey, TValue> asPair(std::forward<TArgs>(args)...);
auto it = find(asPair.first);
if (it != end())
{
return std::make_pair(std::move(it), false);
}
keys_.push_back(std::move(asPair.first));
values_.push_back(std::move(asPair.second));
return std::make_pair(iterator(&keys_.back(), &values_.back()), true);
}
iterator erase(iterator pos) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &pos->first - &keys_[0];
return eraseImpl(idx);
}
iterator erase(const_iterator pos) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &pos->first - &keys_[0];
return eraseImpl(idx);
}
iterator erase(iterator first, iterator last) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &first->first - &keys_[0];
const std::ptrdiff_t count = last - first;
return eraseImpl(idx, count);
}
iterator erase(const_iterator first, const_iterator last) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &first->first - &keys_[0];
const std::ptrdiff_t count = last - first;
return eraseImpl(idx, count);
}
iterator find(const TKey& key) MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{
if (keys_[idx] == key)
{
return iterator(&keys_[idx], &values_[idx]);
}
}
return end();
}
const_iterator find(const TKey& key) const MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{
if (keys_[idx] == key)
{
return const_iterator(&keys_[idx], &values_[idx]);
}
}
return end();
}
private:
iterator eraseImpl(std::ptrdiff_t idx, std::ptrdiff_t count = 1) MIJIN_NOEXCEPT
{
auto itKey = keys_.begin() + idx;
auto itValue = values_.begin() + idx;
itKey = keys_.erase(itKey, itKey + count);
itValue = values_.erase(itValue, itValue + count);
return itKey == keys_.end() ? end() : iterator(&*itKey, &*itValue); // cannot dereference the iterators if the last element was removed
}
};
} // namespace mijin
#endif // MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED

View File

@@ -8,20 +8,37 @@
#include <cstdlib> #include <cstdlib>
#include <source_location> #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 #ifdef _WIN32
#pragma comment(lib, "kernel32") #pragma comment(lib, "kernel32")
extern "C" __declspec(dllimport) void __stdcall DebugBreak(); extern "C" __declspec(dllimport) void __stdcall DebugBreak();
#define MIJIN_TRAP() DebugBreak() #define MIJIN_TRAP() DebugBreak()
#define MIJIN_FUNC() __FUNCSIG__ #define MIJIN_FUNC() __FUNCSIG__
#else // _WIN32 #else // _WIN32
#include <csignal> #include <unistd.h>
#define MIJIN_TRAP() (void) std::raise(SIGTRAP) #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 -.- #define MIJIN_FUNC() "" // TODO: __PRETTY_FUNCTION__ is not working for some reason -.-
#endif // !_WIN32 #endif // !_WIN32
namespace mijin namespace mijin
{ {
// //
// public defines // public defines
// //
@@ -35,7 +52,7 @@ switch (mijin::handleError(msg, source_loc)) \
break; \ break; \
case mijin::ErrorHandling::TRAP: \ case mijin::ErrorHandling::TRAP: \
MIJIN_TRAP(); \ MIJIN_TRAP(); \
[[fallthrough]]; \ break; \
default: /* ABORT */ \ default: /* ABORT */ \
std::abort(); \ std::abort(); \
} }
@@ -48,35 +65,38 @@ MIJIN_ERROR(msg) \
std::abort() std::abort()
// TODO: make ignoreAll work (static variables cannot be used in constexpr functions) // TODO: make ignoreAll work (static variables cannot be used in constexpr functions)
#define MIJIN_ASSERT(condition, msg) \ #define MIJIN_ASSERT(condition, msg) \
if (!static_cast<bool>(condition)) \ do \
{ \ { \
/* static bool ignoreAll = false; */ \ if (!static_cast<bool>(condition)) \
if (true) /*!ignoreAll */ \ { \
{ \ /* static bool ignoreAll = false; */ \
const mijin::AssertionResult assertion_result__ = mijin::handleAssert( \ if (true) /*!ignoreAll */ \
#condition, msg, std::source_location::current()); \ { \
switch (assertion_result__) \ const mijin::AssertionResult assertion_result__ = mijin::handleAssert( \
{ \ #condition, msg, std::source_location::current()); \
case mijin::AssertionResult::ABORT: \ switch (assertion_result__) \
std::abort(); \ { \
break; \ case mijin::AssertionResult::ABORT: \
case mijin::AssertionResult::IGNORE: \ std::abort(); \
/*break*/; \ break; \
case mijin::AssertionResult::IGNORE_ALL: \ case mijin::AssertionResult::IGNORE: \
/* ignoreAll = true; */ \ /*break*/; \
break; \ case mijin::AssertionResult::IGNORE_ALL: \
default: /* ERROR */ \ /* ignoreAll = true; */ \
MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \ break; \
break; \ default: /* ERROR */ \
} \ MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \
} \ break; \
} } \
} \
} \
} while(false)
#define MIJIN_ASSERT_FATAL(condition, msg) \ #define MIJIN_ASSERT_FATAL(condition, msg) \
if (!static_cast<bool>(condition)) \ if (!static_cast<bool>(condition)) \
{ \ { \
MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \ MIJIN_FATAL("Debug assertion failed: " #condition "\nMessage: " msg); \
} }
#else // MIJIN_DEBUG #else // MIJIN_DEBUG
#define MIJIN_ERROR(...) #define MIJIN_ERROR(...)
@@ -93,6 +113,10 @@ if (!static_cast<bool>(condition)) \
// public types // public types
// //
#if defined(ERROR)
#error "Someone included windows.h! Include mijin/util/winundef.h."
#endif
enum class AssertionResult enum class AssertionResult
{ {
ABORT = -1, // call std::abort() ABORT = -1, // call std::abort()
@@ -108,6 +132,49 @@ enum class ErrorHandling
ABORT = 2 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 // public functions
// //
@@ -115,9 +182,14 @@ enum class ErrorHandling
#ifdef MIJIN_USE_CUSTOM_ASSERTION_HANDLER #ifdef MIJIN_USE_CUSTOM_ASSERTION_HANDLER
AssertionResult handleAssert(const char* condition, AssertionResult handleAssert(const char* condition,
const char* message, const std::source_location& location); 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 #else
constexpr AssertionResult handleAssert(const char* /* condition */, constexpr AssertionResult handleAssert(const char* /* condition */, const char* /* message */,
const char* /* message */, const std::source_location& /* location */) const std::source_location& /* location */)
{ {
return AssertionResult::ERROR; return AssertionResult::ERROR;
} }
@@ -126,13 +198,17 @@ constexpr AssertionResult handleAssert(const char* /* condition */,
#ifdef MIJIN_USE_CUSTOM_ERROR_HANDLER #ifdef MIJIN_USE_CUSTOM_ERROR_HANDLER
ErrorHandling handleError(const char* message, const std::source_location& location); ErrorHandling handleError(const char* message, const std::source_location& location);
#else #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::puts(message);
std::printf("Function: %s\n", location.function_name()); std::printf("Function: %s\n", location.function_name());
std::printf("Location: %s:%d:%d\n", location.file_name(), location.line(), location.column()); std::printf("Location: %s:%d:%d\n", location.file_name(), location.line(), location.column());
(void) std::fflush(stdout); (void) std::fflush(stdout);
return ErrorHandling::TRAP; return ErrorHandling::TRAP;
#endif
} }
#endif #endif

View File

@@ -1,10 +1,20 @@
#include "./stacktrace.hpp" #include "./stacktrace.hpp"
#include <cstdint>
#include <optional> #include <optional>
#include <string> #include <string>
#include "../detect.hpp"
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC
#define MIJIN_USE_LIBBACKTRACE 1
#else
#define MIJIN_USE_LIBBACKTRACE 0
#endif
#if MIJIN_USE_LIBBACKTRACE
#include <backtrace.h> #include <backtrace.h>
#endif
namespace mijin namespace mijin
{ {
@@ -32,15 +42,18 @@ struct BacktraceData
// internal variables // internal variables
// //
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
// //
// internal functions // internal functions
// //
#if MIJIN_USE_LIBBACKTRACE
int backtraceFullCallback(void* data, std::uintptr_t programCounter, const char* filename, int lineno, const char* function) int backtraceFullCallback(void* data, std::uintptr_t programCounter, const char* filename, int lineno, const char* function)
{ {
BacktraceData& btData = *static_cast<BacktraceData*>(data); BacktraceData& btData = *static_cast<BacktraceData*>(data);
btData.stackframes.push_back({ btData.stackframes.push_back({
.address = reinterpret_cast<void*>(programCounter), .address = reinterpret_cast<void*>(programCounter), // NOLINT(performance-no-int-to-ptr)
.filename = filename ? filename : "<unknown>", .filename = filename ? filename : "<unknown>",
.function = function ? function : "<unknown>", .function = function ? function : "<unknown>",
.lineNumber = lineno .lineNumber = lineno
@@ -54,15 +67,17 @@ void backtraceErrorCallback(void* data, const char* msg, int /* errnum */)
btData.error = msg; btData.error = msg;
} }
backtrace_state* gBacktraceState = nullptr; thread_local backtrace_state* gBacktraceState = nullptr;
#endif // MIJIN_USE_LIBBACKTRACE
} // namespace } // namespace
// //
// public functions // public functions
// //
Result<Stacktrace> captureStacktrace(unsigned skipFrames) noexcept Result<Stacktrace> captureStacktrace(unsigned skipFrames) MIJIN_NOEXCEPT
{ {
#if MIJIN_USE_LIBBACKTRACE
BacktraceData btData; BacktraceData btData;
if (gBacktraceState == nullptr) if (gBacktraceState == nullptr)
{ {
@@ -83,6 +98,48 @@ Result<Stacktrace> captureStacktrace(unsigned skipFrames) noexcept
} }
return Stacktrace(std::move(btData.stackframes)); return Stacktrace(std::move(btData.stackframes));
#else // MIJIN_USE_LIBBACKTRACE
(void) skipFrames;
return {}; // TODO
#endif // MIJIN_USE_LIBBACKTRACE
} }
const Optional<Stacktrace>& getExceptionStacktrace() MIJIN_NOEXCEPT
{
return gCurrentExceptionStackTrace;
}
} // namespace mijin } // 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

View File

@@ -7,7 +7,11 @@
#include <cmath> #include <cmath>
#include <iomanip> #include <iomanip>
#include <vector> #include <vector>
#if __has_include(<fmt/format.h>)
# include <fmt/format.h>
#endif
#include "./symbol_info.hpp" #include "./symbol_info.hpp"
#include "../internal/common.hpp"
#include "../types/result.hpp" #include "../types/result.hpp"
#include "../util/iterators.hpp" #include "../util/iterators.hpp"
@@ -42,19 +46,20 @@ public:
Stacktrace() = default; Stacktrace() = default;
Stacktrace(const Stacktrace&) = default; Stacktrace(const Stacktrace&) = default;
Stacktrace(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=(const Stacktrace&) = default;
Stacktrace& operator=(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 // 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> template<typename TStream>
TStream& operator<<(TStream& stream, const Stackframe& stackframe) TStream& operator<<(TStream& stream, const Stackframe& stackframe)
@@ -67,7 +72,7 @@ TStream& operator<<(TStream& stream, const Stackframe& stackframe)
template<typename TStream> template<typename TStream>
TStream& operator<<(TStream& stream, const Stacktrace& stacktrace) 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 std::ios::fmtflags oldFlags = stream.flags();
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size()))); const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
stream << std::left; stream << std::left;
@@ -82,4 +87,56 @@ TStream& operator<<(TStream& stream, const Stacktrace& stacktrace)
} // namespace mijin } // namespace mijin
#if __has_include(<fmt/format.h>)
template<>
struct fmt::formatter<mijin::Stackframe>
{
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != '}') FMT_THROW(format_error("invalid format"));
return it;
}
template<typename TContext>
auto format(const mijin::Stackframe& stackframe, TContext& ctx) const -> decltype(ctx.out())
{
auto it = ctx.out();
it = fmt::format_to(it, "[{}] {}:{} in {}", stackframe.address, stackframe.filename,
stackframe.lineNumber, mijin::demangleCPPIdentifier(stackframe.function.c_str()));
return it;
}
};
template<>
struct fmt::formatter<mijin::Stacktrace>
{
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != '}') FMT_THROW(format_error("invalid format"));
return it;
}
template<typename TContext>
auto format(const mijin::Stacktrace& stacktrace, TContext& ctx) const -> decltype(ctx.out())
{
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
auto it = ctx.out();
it = fmt::format_to(it, "[{} frames]", stacktrace.getFrames().size());
for (const auto& [idx, frame] : mijin::enumerate(stacktrace.getFrames()))
{
it = fmt::format_to(it, "\n #{:<{}} at {}", idx, numDigits, frame);
}
return it;
}
};
#endif // __has_include(<fmt/format.h>)
#endif // !defined(MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED) #endif // !defined(MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED)

View File

@@ -62,7 +62,7 @@ const char* lookupFunctionName(const void* function)
#else #else
std::string name = "<unknown>"; std::string name = "<unknown>";
#endif #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 #if MIJIN_COMPILER == MIJIN_COMPILER_GCC

View 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

View 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

View 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

View File

@@ -0,0 +1,4 @@
#pragma once
#include "./exception.hpp"

View File

@@ -0,0 +1,43 @@
#pragma once
#if !defined(MIJIN_INTERNAL_EXCEPTION_HPP_INCLUDED)
#define MIJIN_INTERNAL_EXCEPTION_HPP_INCLUDED 1
#if !defined(MIJIN_ENABLE_EXCEPTIONS)
# define MIJIN_ENABLE_EXCEPTIONS (__cpp_exceptions)
#endif
#define MIJIN_CATCH_EXCEPTIONS (__cpp_exceptions)
#if !defined(MIJIN_THROWING_ASSERTS)
# define MIJIN_THROWING_ASSERTS 0
#endif
#if MIJIN_THROWING_ASSERTS && !defined(MIJIN_TEST_NO_NOEXCEPT)
#define MIJIN_TEST_NO_NOEXCEPT
#endif
#if 0 // TODO: what? MIJIN_WITH_EXCEPTIONS
#error "Maybe someday"
#else
#if defined(MIJIN_TEST_NO_NOEXCEPT) // only use for testing
#define MIJIN_NOEXCEPT
#define MIJIN_THROWS
#define MIJIN_CONDITIONAL_NOEXCEPT(...)
#else
#define MIJIN_NOEXCEPT noexcept
#define MIJIN_THROWS noexcept
#define MIJIN_CONDITIONAL_NOEXCEPT(...) noexcept(__VA_ARGS__)
#endif
#endif
#if MIJIN_CATCH_EXCEPTIONS
# define MIJIN_TRY try
# define MIJIN_CATCH catch
#else
# define MIJIN_TRY
# define MIJIN_CATCH(...) if constexpr(false)
#endif
#endif // !defined(MIJIN_INTERNAL_EXCEPTION_HPP_INCLUDED)

197
source/mijin/io/archive.hpp Normal file
View 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)

View File

@@ -3,9 +3,11 @@
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
#include "../util/iterators.hpp" #include "../detect.hpp"
#include "../debug/assert.hpp"
#include "../util/string.hpp" #include "../util/string.hpp"
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_OSX
namespace mijin namespace mijin
{ {
ProcessStream::~ProcessStream() ProcessStream::~ProcessStream()
@@ -32,10 +34,10 @@ StreamError ProcessStream::open(const char* command, FileOpenMode mode_)
modeStr = "rw"; modeStr = "rw";
break; break;
default: default:
assert(!"Unsupported mode for ProcessStream::open()"); MIJIN_ERROR("Unsupported mode for ProcessStream::open()");
return StreamError::NOT_SUPPORTED; 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) { if (!handle) {
return StreamError::IO_ERROR; return StreamError::IO_ERROR;
} }
@@ -45,18 +47,18 @@ StreamError ProcessStream::open(const char* command, FileOpenMode mode_)
int ProcessStream::close() int ProcessStream::close()
{ {
assert(handle); MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened.");
const int result = pclose(handle); const int result = pclose(handle);
handle = nullptr; handle = nullptr;
return result; 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); MIJIN_ASSERT(handle != nullptr, "Process stream has not been openend.");
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE); 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); 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)) { if (std::ferror(handle)) {
return StreamError::IO_ERROR; return StreamError::IO_ERROR;
} }
if (!partial && readBytes < buffer.size()) { if (!options.partial && readBytes < buffer.size()) {
return StreamError::IO_ERROR; return StreamError::IO_ERROR;
} }
if (outBytesRead != nullptr) 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) StreamError ProcessStream::writeRaw(std::span<const std::uint8_t> buffer)
{ {
assert(handle); MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened.");
assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE); 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); const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
if (written != buffer.size() || std::ferror(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() 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? return std::ftell(handle); // TODO: does this work?
} }
@@ -106,22 +108,19 @@ StreamError ProcessStream::seek(std::intptr_t pos, SeekMode seekMode)
void ProcessStream::flush() void ProcessStream::flush()
{ {
const int result = std::fflush(handle); const int result = std::fflush(handle);
assert(result == 0); MIJIN_ASSERT(result == 0, "Error flushing process stream.");
} }
bool ProcessStream::isAtEnd() bool ProcessStream::isAtEnd()
{ {
assert(handle); MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened.");
if (bufferedChar >= 0) { if (bufferedChar >= 0) {
return false; return false;
} }
bufferedChar = std::fgetc(handle); bufferedChar = std::fgetc(handle);
if (std::feof(handle)) { return static_cast<bool>(std::feof(handle));
return true;
}
return false;
} }
StreamFeatures ProcessStream::getFeatures() StreamFeatures ProcessStream::getFeatures()
@@ -132,13 +131,16 @@ StreamFeatures ProcessStream::getFeatures()
.read = (mode == FileOpenMode::READ), .read = (mode == FileOpenMode::READ),
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE), .write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE),
.tell = true, .tell = true,
.seek = false .seek = false,
.readOptions = {
.partial = true
}
}; };
} }
return {}; return {};
} }
std::string shellEscape(const std::string& arg) noexcept std::string shellEscape(const std::string& arg) MIJIN_NOEXCEPT
{ {
std::ostringstream oss; std::ostringstream oss;
const bool requiresQuotes = std::any_of(arg.begin(), arg.end(), [&](const char chr) { return std::isspace(chr); }); 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 '$': case '$':
oss << '\\'; oss << '\\';
break; break;
default: break;
} }
oss << chr; oss << chr;
} }
@@ -165,7 +168,7 @@ std::string shellEscape(const std::string& arg) noexcept
return oss.str(); return oss.str();
} }
std::string makeShellCommand(const std::vector<std::string>& args) noexcept std::string makeShellCommand(const std::vector<std::string>& args) MIJIN_NOEXCEPT
{ {
using namespace mijin::pipe; using namespace mijin::pipe;
return args return args
@@ -173,3 +176,4 @@ std::string makeShellCommand(const std::vector<std::string>& args) noexcept
| Join(" "); | Join(" ");
} }
} // namespace mijin } // namespace mijin
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_OSX

View File

@@ -5,6 +5,7 @@
#include <vector> #include <vector>
#include "./stream.hpp" #include "./stream.hpp"
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -26,7 +27,7 @@ public:
[[nodiscard]] inline bool isOpen() const { return handle != nullptr; } [[nodiscard]] inline bool isOpen() const { return handle != nullptr; }
// Stream overrides // 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; StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override; std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override; StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
@@ -35,8 +36,8 @@ public:
StreamFeatures getFeatures() override; StreamFeatures getFeatures() override;
}; };
[[nodiscard]] std::string shellEscape(const std::string& arg) noexcept; [[nodiscard]] std::string shellEscape(const std::string& arg) MIJIN_NOEXCEPT;
[[nodiscard]] std::string makeShellCommand(const std::vector<std::string>& args) noexcept; [[nodiscard]] std::string makeShellCommand(const std::vector<std::string>& args) MIJIN_NOEXCEPT;
StreamError ProcessStream::open(const std::vector<std::string>& args, FileOpenMode mode_) StreamError ProcessStream::open(const std::vector<std::string>& args, FileOpenMode mode_)
{ {

View 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)

View File

@@ -2,7 +2,7 @@
#include "./stream.hpp" #include "./stream.hpp"
#include <algorithm> #include <algorithm>
#include <cassert> #include <array>
#include <limits> #include <limits>
namespace mijin namespace mijin
@@ -34,7 +34,57 @@ namespace mijin
void Stream::flush() {} 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) std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
StreamError error = read(length); StreamError error = read(length);
@@ -52,17 +102,228 @@ StreamError Stream::readString(std::string& outString)
return StreamError::SUCCESS; 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()); 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) { if (error != StreamError::SUCCESS) {
return error; return error;
} }
return writeSpan(str.begin(), str.end()); return writeSpan(str.begin(), str.end());
} }
mijin::Task<StreamError> Stream::c_readBinaryString(std::string& outString)
{
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
StreamError error = co_await c_read(length);
if (error != StreamError::SUCCESS) {
co_return error;
}
std::string result;
result.resize(length);
error = co_await c_readSpan(result.begin(), result.end());
if (error != StreamError::SUCCESS) {
co_return error;
}
outString = std::move(result);
co_return StreamError::SUCCESS;
}
mijin::Task<StreamError> Stream::c_writeBinaryString(std::string_view str)
{
MIJIN_ASSERT(str.length() <= std::numeric_limits<std::uint32_t>::max(), "Binary string is too long.");
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
StreamError error = co_await c_write(length);
if (error != StreamError::SUCCESS) {
co_return error;
}
co_return co_await c_writeSpan(str.begin(), str.end());
}
StreamError Stream::getTotalLength(std::size_t& outLength)
{
const StreamFeatures features = getFeatures();
if (!features.tell || !features.seek) {
return StreamError::NOT_SUPPORTED;
}
const std::size_t origPos = tell();
if (const StreamError error = seek(0, SeekMode::RELATIVE_TO_END); error != StreamError::SUCCESS)
{
return error;
}
outLength = tell();
if (const StreamError error = seek(static_cast<std::intptr_t>(origPos)); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
StreamError Stream::readRest(TypelessBuffer& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (const StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (const StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<std::byte, CHUNK_SIZE> chunk = {};
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (const StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
const std::span<std::byte> bufferBytes = outBuffer.makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
return StreamError::SUCCESS;
}
mijin::Task<StreamError> Stream::c_readRest(TypelessBuffer& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (StreamError error = co_await c_readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
co_return error;
}
co_return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<std::byte, CHUNK_SIZE> chunk = {};
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
std::span<std::byte> bufferBytes = outBuffer.makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
co_return StreamError::SUCCESS;
}
StreamError Stream::readLine(std::string& outString)
{
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
static constexpr std::size_t BUFFER_SIZE = 4096;
std::array<char, BUFFER_SIZE> buffer;
outString.clear();
bool done = false;
while (!done)
{
// read into the buffer
std::size_t bytesRead = 0;
if (const StreamError error = readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
// try to find a \n
auto begin = buffer.begin(); // NOLINT(readability-qualified-auto)
auto end = buffer.begin() + bytesRead; // NOLINT(readability-qualified-auto)
auto newline = std::find(begin, end, '\n'); // NOLINT(readability-qualified-auto)
if (newline != end)
{
// found the end
outString.append(begin, newline);
end = newline + 1;
done = true;
}
else
{
outString.append(begin, end);
}
// read again, this time to skip
if (const StreamError error = readSpan(begin, end); error != StreamError::SUCCESS)
{
return error;
}
if (isAtEnd())
{
done = true;
}
}
return StreamError::SUCCESS;
}
mijin::Task<StreamError> Stream::c_readLine(std::string& outString)
{
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
static constexpr std::size_t BUFFER_SIZE = 4096;
std::array<char, BUFFER_SIZE> buffer;
outString.clear();
bool done = false;
while(!done)
{
// read into the buffer
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
// try to find a \n
auto begin = buffer.begin(); // NOLINT(readability-qualified-auto)
auto end = buffer.begin() + bytesRead; // NOLINT(readability-qualified-auto)
auto newline = std::find(begin, end, '\n'); // NOLINT(readability-qualified-auto)
if (newline != end)
{
// found the end
outString.append(begin, newline);
end = newline + 1;
done = true;
}
else
{
outString.append(begin, end);
}
// read again, this time to skip
if (StreamError error = co_await c_readSpan(begin, end); error != StreamError::SUCCESS)
{
co_return error;
}
}
co_return StreamError::SUCCESS;
}
FileStream::~FileStream() FileStream::~FileStream()
{ {
if (handle) { if (handle) {
@@ -89,6 +350,9 @@ StreamError FileStream::open(const char* path, FileOpenMode mode_)
case FileOpenMode::READ_WRITE: case FileOpenMode::READ_WRITE:
modeStr = "r+b"; modeStr = "r+b";
break; break;
default:
MIJIN_FATAL("Invalid value for mode.");
return StreamError::UNKNOWN_ERROR;
} }
handle = std::fopen(path, modeStr); // NOLINT(cppcoreguidelines-owning-memory) handle = std::fopen(path, modeStr); // NOLINT(cppcoreguidelines-owning-memory)
if (!handle && mode_ == FileOpenMode::READ_WRITE) { if (!handle && mode_ == FileOpenMode::READ_WRITE) {
@@ -99,43 +363,52 @@ StreamError FileStream::open(const char* path, FileOpenMode mode_)
} }
int result = std::fseek(handle, 0, SEEK_END); int result = std::fseek(handle, 0, SEEK_END);
assert(result == 0); MIJIN_ASSERT(result == 0, "fseek failed.");
length = std::ftell(handle); length = std::ftell(handle);
result = std::fseek(handle, 0, SEEK_SET); result = std::fseek(handle, 0, SEEK_SET);
assert(result == 0); MIJIN_ASSERT(result == 0, "fseek failed.");
return StreamError::SUCCESS; return StreamError::SUCCESS;
} }
void FileStream::close() void FileStream::close()
{ {
assert(handle); MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory) [[maybe_unused]] const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory)
assert(result == 0); 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); MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE); 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); const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle);
if (std::ferror(handle)) { if (std::ferror(handle)) {
return StreamError::IO_ERROR; return StreamError::IO_ERROR;
} }
if (!partial && readBytes < buffer.size()) { if (!options.partial && readBytes < buffer.size()) {
return StreamError::IO_ERROR; return StreamError::IO_ERROR;
} }
if (outBytesRead != nullptr) { if (outBytesRead != nullptr) {
*outBytesRead = readBytes; *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; return StreamError::SUCCESS;
} }
StreamError FileStream::writeRaw(std::span<const std::uint8_t> buffer) StreamError FileStream::writeRaw(std::span<const std::uint8_t> buffer)
{ {
assert(handle); MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE); 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); const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
if (written != buffer.size() || std::ferror(handle)) { if (written != buffer.size() || std::ferror(handle)) {
@@ -148,15 +421,13 @@ StreamError FileStream::writeRaw(std::span<const std::uint8_t> buffer)
std::size_t FileStream::tell() std::size_t FileStream::tell()
{ {
assert(handle); MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
return std::ftell(handle); return std::ftell(handle);
} }
StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode) 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) int origin; // NOLINT(cppcoreguidelines-init-variables)
switch (seekMode) switch (seekMode)
{ {
@@ -170,7 +441,7 @@ StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
origin = SEEK_END; origin = SEEK_END;
break; break;
default: default:
assert(!"Invalid value passed as seekMode!"); MIJIN_ERROR("Invalid value passed as seekMode!");
return StreamError::UNKNOWN_ERROR; return StreamError::UNKNOWN_ERROR;
} }
const int result = std::fseek(handle, static_cast<long>(pos), origin); const int result = std::fseek(handle, static_cast<long>(pos), origin);
@@ -182,20 +453,20 @@ StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
void FileStream::flush() void FileStream::flush()
{ {
const int result = std::fflush(handle); [[maybe_unused]] const int result = std::fflush(handle);
assert(result == 0); MIJIN_ASSERT(result == 0, "fflush failed.");
} }
bool FileStream::isAtEnd() bool FileStream::isAtEnd()
{ {
assert(handle); MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
(void) std::fgetc(handle); (void) std::fgetc(handle);
if (std::feof(handle)) { if (std::feof(handle)) {
return true; return true;
} }
const int result = std::fseek(handle, -1, SEEK_CUR); [[maybe_unused]] const int result = std::fseek(handle, -1, SEEK_CUR);
assert(result == 0); MIJIN_ASSERT(result == 0, "fseek failed.");
return false; return false;
} }
@@ -207,7 +478,11 @@ StreamFeatures FileStream::getFeatures()
.read = (mode == FileOpenMode::READ), .read = (mode == FileOpenMode::READ),
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE), .write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE),
.tell = true, .tell = true,
.seek = true .seek = true,
.readOptions = {
.partial = true,
.peek = true
}
}; };
} }
return {}; return {};
@@ -215,30 +490,30 @@ StreamFeatures FileStream::getFeatures()
void MemoryStream::openRW(std::span<std::uint8_t> data) void MemoryStream::openRW(std::span<std::uint8_t> data)
{ {
assert(!isOpen()); MIJIN_ASSERT(!isOpen(), "MemoryStream is already open.");
data_ = data; data_ = data;
pos_ = 0; pos_ = 0;
canWrite_ = true; canWrite_ = true;
} }
void MemoryStream::openRO(std::span<const std::uint8_t> data) void MemoryStream::openROImpl(const void* data, std::size_t bytes)
{ {
assert(!isOpen()); MIJIN_ASSERT(!isOpen(), "MemoryStream is already open.");
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 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; pos_ = 0;
canWrite_ = false; canWrite_ = false;
} }
void MemoryStream::close() void MemoryStream::close()
{ {
assert(isOpen()); MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
data_ = {}; 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()); MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
if (!partial && availableBytes() < buffer.size()) { if (!options.partial && availableBytes() < buffer.size()) {
return StreamError::IO_ERROR; // TODO: need more errors? return StreamError::IO_ERROR; // TODO: need more errors?
} }
const std::size_t numBytes = std::min(buffer.size(), availableBytes()); const std::size_t numBytes = std::min(buffer.size(), availableBytes());
@@ -246,14 +521,19 @@ StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, bool partial,
if (outBytesRead) { if (outBytesRead) {
*outBytesRead = numBytes; *outBytesRead = numBytes;
} }
pos_ += numBytes; if (!options.peek) {
pos_ += numBytes;
}
return StreamError::SUCCESS; return StreamError::SUCCESS;
} }
StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer) StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer)
{ {
assert(isOpen()); MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
assert(canWrite_);
if (!canWrite_) {
return StreamError::NOT_SUPPORTED;
}
if (availableBytes() < buffer.size()) { if (availableBytes() < buffer.size()) {
return StreamError::IO_ERROR; return StreamError::IO_ERROR;
@@ -265,13 +545,13 @@ StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer)
std::size_t MemoryStream::tell() std::size_t MemoryStream::tell()
{ {
assert(isOpen()); MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
return pos_; return pos_;
} }
StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode) StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode)
{ {
assert(isOpen()); MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
std::intptr_t newPos = -1; std::intptr_t newPos = -1;
switch (seekMode) switch (seekMode)
{ {
@@ -294,7 +574,7 @@ StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode)
bool MemoryStream::isAtEnd() bool MemoryStream::isAtEnd()
{ {
assert(isOpen()); MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
return pos_ == data_.size(); return pos_ == data_.size();
} }
@@ -304,7 +584,10 @@ StreamFeatures MemoryStream::getFeatures()
.read = true, .read = true,
.write = canWrite_, .write = canWrite_,
.tell = true, .tell = true,
.seek = true .seek = true,
.readOptions = {
.peek = true
}
}; };
} }

View File

@@ -4,11 +4,17 @@
#if !defined(MIJIN_IO_STREAM_HPP_INCLUDED) #if !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
#define MIJIN_IO_STREAM_HPP_INCLUDED 1 #define MIJIN_IO_STREAM_HPP_INCLUDED 1
#include <cassert>
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <ranges>
#include <span> #include <span>
#include <stdexcept>
#include <string> #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 namespace mijin
{ {
@@ -32,12 +38,22 @@ enum class SeekMode
RELATIVE_TO_END RELATIVE_TO_END
}; };
struct ReadOptions
{
bool partial : 1 = false;
bool peek : 1 = false;
bool noBlock : 1 = false;
};
struct StreamFeatures struct StreamFeatures
{ {
bool read : 1 = false; bool read : 1 = false;
bool write : 1 = false; bool write : 1 = false;
bool tell : 1 = false; bool tell : 1 = false;
bool seek : 1 = false; bool seek : 1 = false;
bool async : 1 = false;
ReadOptions readOptions = {};
}; };
enum class FileOpenMode enum class FileOpenMode
@@ -50,10 +66,14 @@ enum class FileOpenMode
enum class [[nodiscard]] StreamError enum class [[nodiscard]] StreamError
{ {
SUCCESS, SUCCESS = 0,
IO_ERROR, IO_ERROR = 1,
NOT_SUPPORTED, NOT_SUPPORTED = 2,
UNKNOWN_ERROR CONNECTION_CLOSED = 3,
PROTOCOL_ERROR = 4,
WOULD_BLOCK = 5,
CONNECTION_REFUSED = 6,
UNKNOWN_ERROR = -1
}; };
class Stream class Stream
@@ -62,7 +82,12 @@ public:
virtual ~Stream() = default; virtual ~Stream() = default;
public: 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 StreamError writeRaw(std::span<const std::uint8_t> buffer) = 0;
virtual std::size_t tell() = 0; virtual std::size_t tell() = 0;
virtual StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) = 0; virtual StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) = 0;
@@ -70,59 +95,201 @@ public:
virtual bool isAtEnd() = 0; virtual bool isAtEnd() = 0;
virtual StreamFeatures getFeatures() = 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); std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
return readRaw(std::span(ptr, ptr + bytes), partial, outBytesRead); return readRaw(std::span(ptr, ptr + bytes), options, outBytesRead);
} }
inline StreamError writeRaw(const void* data, std::size_t bytes) [[deprecated("Partial parameter has been replaced with options.")]]
StreamError readRaw(void* outData, std::size_t bytes, bool partial, std::size_t* outBytesRead = nullptr)
{
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
return readRaw(std::span(ptr, ptr + bytes), {.partial = partial}, outBytesRead);
}
mijin::Task<StreamError> c_readRaw(void* outData, std::size_t bytes, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
co_return co_await c_readRaw(std::span(ptr, ptr + bytes), options, outBytesRead);
}
template<std::ranges::contiguous_range TRange>
StreamError readRaw(TRange& range, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return readRaw(&*range.begin(), bytes, options, outBytesRead);
}
template<std::ranges::contiguous_range TRange>
[[deprecated("Partial parameter has been replaced with options.")]]
StreamError readRaw(TRange& range, bool partial, std::size_t* outBytesRead = nullptr)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return readRaw(&*range.begin(), bytes, {.partial = partial}, outBytesRead);
}
StreamError readRaw(TypelessBuffer& buffer, const ReadOptions& options = {})
{
return readRaw(buffer.data(), buffer.byteSize(), options);
}
template<std::ranges::contiguous_range TRange>
mijin::Task<StreamError> c_readRaw(TRange& range, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return c_readRaw(&*range.begin(), bytes, options, outBytesRead);
}
mijin::Task<StreamError> c_readRaw(TypelessBuffer& buffer, const ReadOptions& options = {})
{
return c_readRaw(buffer.data(), buffer.byteSize(), options);
}
StreamError writeRaw(const void* data, std::size_t bytes)
{ {
const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data); const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data);
return writeRaw(std::span(ptr, ptr + bytes)); return writeRaw(std::span(ptr, ptr + bytes));
} }
template<typename T> mijin::Task<StreamError> c_writeRaw(const void* data, std::size_t bytes)
inline StreamError read(T& value)
{ {
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> 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); 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> 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)); 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> template<typename T>
inline StreamError write(const T& value) StreamError write(const T& value) requires(std::is_trivial_v<T>)
{ {
return writeRaw(&value, sizeof(T)); return writeRaw(&value, sizeof(T));
} }
template<typename 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); auto asSpan = std::span(values);
return writeRaw(asSpan.data(), asSpan.size_bytes()); 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> 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))); return writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end)));
} }
StreamError readString(std::string& outString); template<typename TItBegin, typename TItEnd>
StreamError writeString(std::string_view str); mijin::Task<StreamError> c_writeSpan(TItBegin&& begin, TItEnd&& end)
{
return c_writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end)));
}
StreamError readBinaryString(std::string& outString);
StreamError writeBinaryString(std::string_view str);
mijin::Task<StreamError> c_readBinaryString(std::string& outString);
mijin::Task<StreamError> c_writeBinaryString(std::string_view str);
[[deprecated("Use readBinaryString() or readAsString() instead.")]]
inline StreamError readString(std::string& outString) { return readBinaryString(outString); }
[[deprecated("Use writeBinaryString() or writeText() instead.")]]
inline StreamError writeString(std::string_view str) { return writeBinaryString(str); }
StreamError getTotalLength(std::size_t& outLength);
StreamError readRest(TypelessBuffer& outBuffer);
mijin::Task<StreamError> c_readRest(TypelessBuffer& outBuffer);
StreamError readLine(std::string& outString);
mijin::Task<StreamError> c_readLine(std::string& outString);
template<typename TChar = char>
StreamError readAsString(std::basic_string<TChar>& outString);
template<typename TChar = char>
mijin::Task<StreamError> c_readAsString(std::basic_string<TChar>& outString);
StreamError writeText(std::string_view str)
{
return writeSpan(str);
}
mijin::Task<StreamError> c_writeText(std::string_view str)
{
return c_writeSpan(str);
}
}; };
class FileStream : public Stream class FileStream : public Stream
@@ -142,7 +309,7 @@ public:
[[nodiscard]] inline bool isOpen() const { return handle != nullptr; } [[nodiscard]] inline bool isOpen() const { return handle != nullptr; }
// Stream overrides // 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; StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override; std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override; StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
@@ -159,27 +326,182 @@ private:
bool canWrite_ = false; bool canWrite_ = false;
public: public:
void openRW(std::span<std::uint8_t> data); 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(); void close();
[[nodiscard]] inline bool isOpen() const { return data_.data() != nullptr; } [[nodiscard]] inline bool isOpen() const { return data_.data() != nullptr; }
[[nodiscard]] inline std::size_t availableBytes() const { [[nodiscard]] inline std::size_t availableBytes() const
assert(isOpen()); {
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
return data_.size() - pos_; return data_.size() - pos_;
} }
// Stream overrides // 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; StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override; std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override; StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
bool isAtEnd() override; bool isAtEnd() override;
StreamFeatures getFeatures() override; StreamFeatures getFeatures() override;
private:
void openROImpl(const void* data, std::size_t bytes);
}; };
template<typename TSuccess>
using StreamResult = ResultBase<TSuccess, StreamError>;
// //
// public functions // public functions
// //
template<typename TChar>
StreamError Stream::readAsString(std::basic_string<TChar>& outString)
{
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outString.resize(length);
if (StreamError error = readRaw(outString.data(), length); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<TChar, CHUNK_SIZE> chunk;
outString.clear();
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
outString.append(chunk.data(), chunk.data() + bytesRead);
}
return StreamError::SUCCESS;
}
template<typename TChar>
mijin::Task<StreamError> Stream::c_readAsString(std::basic_string<TChar>& outString)
{
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outString.resize(length);
if (StreamError error = co_await c_readRaw(outString.data(), length); error != StreamError::SUCCESS)
{
co_return error;
}
co_return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<TChar, CHUNK_SIZE> chunk;
outString.clear();
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(chunk, true, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
outString.append(chunk.data(), chunk.data() + bytesRead);
}
co_return StreamError::SUCCESS;
}
inline const char* errorName(StreamError error) MIJIN_NOEXCEPT
{
switch (error)
{
case StreamError::SUCCESS:
return "success";
case StreamError::IO_ERROR:
return "IO error";
case StreamError::NOT_SUPPORTED:
return "not supported";
case StreamError::CONNECTION_CLOSED:
return "connection closed";
case StreamError::PROTOCOL_ERROR:
return "protocol error";
case StreamError::WOULD_BLOCK:
return "would block";
case StreamError::UNKNOWN_ERROR:
return "unknown error";
case StreamError::CONNECTION_REFUSED:
return "connection refused";
}
return "<invalid error>";
}
#if MIJIN_ENABLE_EXCEPTIONS // these functions don't make sense with exceptions disabled
inline void throwOnError(mijin::StreamError error)
{
if (error == mijin::StreamError::SUCCESS) {
return;
}
throw std::runtime_error(errorName(error));
}
inline void throwOnError(mijin::StreamError error, std::string message)
{
if (error == mijin::StreamError::SUCCESS) {
return;
}
throw std::runtime_error(message + ": " + errorName(error));
}
template<typename TSuccess>
inline decltype(auto) throwOnError(StreamResult<TSuccess>&& result)
{
if (result.isError())
{
throw Exception(errorName(result.getError()));
}
else if (!result.isSuccess())
{
throw Exception("result is empty");
}
return *result;
}
template<typename TSuccess>
inline decltype(auto) throwOnError(StreamResult<TSuccess>&& result, const std::string& message)
{
if (result.isError())
{
throw Exception(message + ": " + errorName(result.getError()));
}
else if (!result.isSuccess())
{
throw Exception(message + ": result is empty");
}
return *result;
}
#endif // MIJIN_ENABLE_EXCEPTIONS
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_IO_STREAM_HPP_INCLUDED) #endif // !defined(MIJIN_IO_STREAM_HPP_INCLUDED)

View 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)

View 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
View 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
View File

@@ -0,0 +1,93 @@
#pragma once
#if !defined(MIJIN_NET_HTTP_HPP_INCLUDED)
#define MIJIN_NET_HTTP_HPP_INCLUDED 1
#include <memory>
#include <string>
#include <map>
#include "./socket.hpp"
#include "./url.hpp"
#include "../container/boxed_object.hpp"
#include "../container/typeless_buffer.hpp"
#include "../internal/common.hpp"
#include "../io/stream.hpp"
namespace mijin
{
namespace http_method
{
inline const std::string GET = "GET";
inline const std::string POST = "POST";
inline const std::string HEAD = "HEAD";
inline const std::string PUT = "PUT";
inline const std::string DELETE = "DELETE";
}
struct HTTPVersion
{
unsigned major;
unsigned minor;
};
struct HTTPRequest
{
HTTPVersion version = {1, 1};
std::string address;
std::string method = "GET";
std::multimap<std::string, std::string> headers;
std::string body;
};
struct HTTPRequestOptions
{
std::string method = "GET";
std::multimap<std::string, std::string> headers;
TypelessBuffer body;
};
struct HTTPResponse
{
HTTPVersion version;
unsigned status;
std::string statusMessage;
std::multimap<std::string, std::string> headers;
TypelessBuffer body;
};
class HTTPStream
{
private:
Stream* base_;
public:
HTTPStream(Stream& base) MIJIN_NOEXCEPT : base_(&base)
{
MIJIN_ASSERT(base_ != nullptr, "Invalid parameter for base.");
}
Task<StreamResult<HTTPResponse>> c_request(HTTPRequest request) MIJIN_NOEXCEPT;
private:
Task<StreamError> c_writeRequest(const HTTPRequest& request) MIJIN_NOEXCEPT;
Task<StreamResult<HTTPResponse>> c_readResponse() MIJIN_NOEXCEPT;
};
class HTTPClient
{
private:
std::unique_ptr<Socket> socket_;
std::unique_ptr<Stream> sslStream_;
mijin::BoxedObject<HTTPStream> stream_;
ip_address_t lastIP_;
std::uint16_t lastPort_ = 0;
bool lastWasHttps_ = false;
public:
~HTTPClient() MIJIN_NOEXCEPT { disconnect(); }
Task<StreamResult<HTTPResponse>> c_request(ip_address_t address, std::uint16_t port, bool https, HTTPRequest request) MIJIN_NOEXCEPT;
Task<StreamResult<HTTPResponse>> c_request(const URL& url, HTTPRequest request = {}) MIJIN_NOEXCEPT;
void disconnect() MIJIN_NOEXCEPT;
private:
StreamError createSocket(ip_address_t address, const std::string& hostname, std::uint16_t port, bool https) MIJIN_NOEXCEPT;
};
}
#endif // !defined(MIJIN_NET_HTTP_HPP_INCLUDED)

300
source/mijin/net/ip.cpp Normal file
View 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
View 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)

View 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)

View 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

View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,172 @@
#pragma once
#if !defined(MIJIN_NET_URL_HPP_INCLUDED)
#define MIJIN_NET_URL_HPP_INCLUDED 1
#include <cstdint>
#include <string>
#include "../internal/common.hpp"
#include "../util/string.hpp"
namespace mijin
{
template<typename TChar, typename TTraits = std::char_traits<TChar>, typename TAllocator = std::allocator<TChar>>
class URLBase
{
public:
using string_t = std::basic_string<TChar, TTraits, TAllocator>;
using string_view_t = std::basic_string_view<TChar, TTraits>;
private:
string_t base_;
string_view_t scheme_;
string_view_t userinfo_;
string_view_t host_;
string_view_t path_;
string_view_t query_;
string_view_t fragment_;
string_view_t pathQueryFragment_;
std::uint16_t port_ = 0;
public:
constexpr URLBase() MIJIN_NOEXCEPT = default;
constexpr URLBase(const URLBase&) = default;
constexpr URLBase(URLBase&&) MIJIN_NOEXCEPT = default;
constexpr URLBase(string_t base) MIJIN_NOEXCEPT : base_(std::move(base)) { parse(); }
constexpr URLBase(string_view_t base) : URLBase(string_t(base.begin(), base.end())) {}
constexpr URLBase(const TChar* base) : URLBase(string_t(base)) {}
constexpr URLBase& operator=(const URLBase&) = default;
constexpr URLBase& operator=(URLBase&&) MIJIN_NOEXCEPT = default;
[[nodiscard]] constexpr bool isValid() const MIJIN_NOEXCEPT { return !base_.empty(); }
[[nodiscard]] constexpr const string_t& getBase() const MIJIN_NOEXCEPT { return base_; }
[[nodiscard]] constexpr string_view_t getScheme() const MIJIN_NOEXCEPT { return scheme_; }
[[nodiscard]] constexpr string_view_t getUserInfo() const MIJIN_NOEXCEPT { return userinfo_; }
[[nodiscard]] constexpr string_view_t getHost() const MIJIN_NOEXCEPT { return host_; }
[[nodiscard]] constexpr string_view_t getPath() const MIJIN_NOEXCEPT { return path_; }
[[nodiscard]] constexpr string_view_t getQuery() const MIJIN_NOEXCEPT { return query_; }
[[nodiscard]] constexpr string_view_t getFragment() const MIJIN_NOEXCEPT { return fragment_; }
[[nodiscard]] constexpr string_view_t getPathQueryFragment() const MIJIN_NOEXCEPT { return pathQueryFragment_; }
[[nodiscard]] constexpr std::uint16_t getPort() const MIJIN_NOEXCEPT { return port_; }
constexpr void clear() MIJIN_NOEXCEPT;
private:
constexpr void parse() MIJIN_NOEXCEPT;
constexpr bool parseAuthority(string_view_t authority) MIJIN_NOEXCEPT;
};
using URL = URLBase<char>;
template<typename TChar, typename TTraits, typename TAllocator>
constexpr void URLBase<TChar, TTraits, TAllocator>::clear() MIJIN_NOEXCEPT
{
base_.clear();
scheme_ = {};
userinfo_ = {};
host_ = {};
path_ = {};
query_ = {};
fragment_ = {};
port_ = 0;
}
template<typename TChar, typename TTraits, typename TAllocator>
constexpr void URLBase<TChar, TTraits, TAllocator>::parse() MIJIN_NOEXCEPT
{
if (base_.empty())
{
return;
}
string_view_t toParse = base_;
typename string_view_t::size_type pos = toParse.find(TChar(':'));
if (pos == string_t::npos)
{
clear();
return;
}
scheme_ = toParse.substr(0, pos);
toParse = toParse.substr(pos + 1);
const TChar DOUBLE_SLASH[] = {TChar('/'), TChar('/'), TChar(0)};
if (!toParse.starts_with(DOUBLE_SLASH))
{
userinfo_ = host_ = {};
port_ = 0;
}
else
{
toParse = toParse.substr(2); // skip the slashes
pos = toParse.find(TChar('/'));
if (!parseAuthority(toParse.substr(0, pos)))
{
clear();
return;
}
if (pos == string_view_t::npos)
{
path_ = query_ = fragment_ = pathQueryFragment_ = {};
return;
}
toParse = toParse.substr(pos);
}
pathQueryFragment_ = toParse;
pos = toParse.find(TChar('#'));
if (pos == string_view_t::npos)
{
fragment_ = {};
}
else
{
fragment_ = toParse.substr(pos + 1);
toParse = toParse.substr(0, pos);
}
pos = toParse.find(TChar('?'));
if (pos == string_view_t::npos)
{
query_ = {};
path_ = toParse;
}
else
{
query_ = toParse.substr(pos + 1);
path_ = toParse.substr(0, pos);
}
}
template<typename TChar, typename TTraits, typename TAllocator>
constexpr bool URLBase<TChar, TTraits, TAllocator>::parseAuthority(string_view_t authority) MIJIN_NOEXCEPT
{
string_view_t toParse = authority;
typename string_view_t::size_type pos = toParse.find(TChar('@'));
if (pos == string_view_t::npos)
{
userinfo_ = {};
}
else
{
userinfo_ = toParse.substr(0, pos);
toParse = toParse.substr(pos + 1);
}
pos = toParse.find(TChar(':')); // TODO: IPv6
if (pos == string_view_t::npos)
{
port_ = 0;
host_ = toParse;
}
else
{
if (!toNumber(toParse.substr(pos + 1), port_))
{
return false;
}
host_ = toParse.substr(0, pos);
}
return true;
}
}
#endif // !defined(MIJIN_NET_URL_HPP_INCLUDED)

View 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;
}
}

View 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)

View File

@@ -8,9 +8,6 @@
#include <string> #include <string>
#include <vector> #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 namespace mijin
{ {
@@ -52,9 +49,9 @@ static std::shared_mutex& getGlobalNamesMutex()
return mutex; 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; return cache;
} }
@@ -78,14 +75,14 @@ static std::size_t getOrCreateStringID(std::string_view string)
{ {
if (getLocalNames()[idx] == string) if (getLocalNames()[idx] == string)
{ {
getLocalNameCache()[std::string(string)] = idx; getLocalNameCache().emplace(std::string(string), idx);
return idx; return idx;
} }
} }
// 3. copy global state and check local again // 3. copy global state and check local again
{ {
std::shared_lock lock(getGlobalNamesMutex()); const std::shared_lock lock(getGlobalNamesMutex());
copyNamesToLocal(); copyNamesToLocal();
} }
@@ -93,17 +90,17 @@ static std::size_t getOrCreateStringID(std::string_view string)
{ {
if (getLocalNames()[idx] == string) if (getLocalNames()[idx] == string)
{ {
getLocalNameCache()[std::string(string)] = idx; getLocalNameCache().emplace(std::string(string), idx);
return idx; return idx;
} }
} }
// 4. insert a new ID // 4. insert a new ID
std::unique_lock lock(getGlobalNamesMutex()); const std::unique_lock lock(getGlobalNamesMutex());
const std::size_t idx = getGlobalNames().size(); const std::size_t idx = getGlobalNames().size();
getGlobalNames().emplace_back(string); getGlobalNames().emplace_back(string);
copyNamesToLocal(); copyNamesToLocal();
getLocalNameCache()[std::string(string)] = idx; getLocalNameCache().emplace(std::string(string), idx);
return idx; return idx;
} }

View File

@@ -6,6 +6,10 @@
#include <limits> #include <limits>
#include <string_view> #include <string_view>
#if __has_include(<fmt/format.h>)
# include <fmt/format.h>
#endif
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -31,9 +35,11 @@ public:
Name(const Name&) = default; Name(const Name&) = default;
Name(std::string_view string); Name(std::string_view string);
Name(const char* cStr) : Name(std::string_view(cStr)) {} Name(const char* cStr) : Name(std::string_view(cStr)) {}
Name& operator=(const Name&) = default; Name& operator=(const Name&) = default;
auto operator<=>(const Name&) const = 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]] std::string_view stringView() const;
[[nodiscard]] const char* c_str() const { [[nodiscard]] const char* c_str() const {
@@ -49,15 +55,24 @@ public:
} // namespace mijin } // namespace mijin
namespace std
{
template<> 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()); 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) #endif // !defined(MIJIN_TYPES_NAME_HPP_INCLUDED)

View File

@@ -7,6 +7,8 @@
#include <string> #include <string>
#include <variant> #include <variant>
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -30,37 +32,45 @@ private:
std::variant<Empty, TSuccess, TError> state_; std::variant<Empty, TSuccess, TError> state_;
public: public:
ResultBase() = default; ResultBase() MIJIN_NOEXCEPT = default;
ResultBase(const ResultBase&) = default; ResultBase(const ResultBase&) MIJIN_NOEXCEPT = default;
ResultBase(ResultBase&&) = default; ResultBase(ResultBase&&) MIJIN_NOEXCEPT = default;
ResultBase(TSuccess successValue) noexcept : state_(std::move(successValue)) {} ResultBase(TSuccess successValue) MIJIN_NOEXCEPT : state_(std::move(successValue)) {}
ResultBase(TError errorValue) noexcept : state_(std::move(errorValue)) {} ResultBase(TError errorValue) MIJIN_NOEXCEPT : state_(std::move(errorValue)) {}
ResultBase& operator=(const ResultBase&) = default; ResultBase& operator=(const ResultBase&) = default;
ResultBase& operator=(ResultBase&&) = default; ResultBase& operator=(ResultBase&&) = default;
bool operator==(const ResultBase& other) const noexcept { return state_ == other.state_; } bool operator==(const ResultBase& other) const MIJIN_NOEXCEPT { return state_ == other.state_; }
bool operator!=(const ResultBase& other) const noexcept { return state_ != other.state_; } bool operator!=(const ResultBase& other) const MIJIN_NOEXCEPT { return state_ != other.state_; }
operator bool() const noexcept { return isSuccess(); } operator bool() const MIJIN_NOEXCEPT { return isSuccess(); }
bool operator!() const noexcept { return !isSuccess(); } bool operator!() const MIJIN_NOEXCEPT { return !isSuccess(); }
TSuccess& operator*() noexcept { return getValue(); } TSuccess& operator*() MIJIN_NOEXCEPT { return getValue(); }
const TSuccess& operator*() const noexcept { return getValue(); } const TSuccess& operator*() const MIJIN_NOEXCEPT { return getValue(); }
TSuccess* operator->() noexcept { return &getValue(); } TSuccess* operator->() MIJIN_NOEXCEPT { return &getValue(); }
const TSuccess* operator->() const noexcept { return &getValue(); } const TSuccess* operator->() const MIJIN_NOEXCEPT { return &getValue(); }
[[nodiscard]] bool isEmpty() const noexcept { return std::holds_alternative<Empty>(state_); } [[nodiscard]] bool isEmpty() const MIJIN_NOEXCEPT { return std::holds_alternative<Empty>(state_); }
[[nodiscard]] bool isSuccess() const noexcept { return std::holds_alternative<TSuccess>(state_); } [[nodiscard]] bool isSuccess() const MIJIN_NOEXCEPT { return std::holds_alternative<TSuccess>(state_); }
[[nodiscard]] bool isError() const noexcept { return std::holds_alternative<TError>(state_); } [[nodiscard]] bool isError() const MIJIN_NOEXCEPT { return std::holds_alternative<TError>(state_); }
[[nodiscard]] TSuccess& getValue() noexcept { return std::get<TSuccess>(state_); } [[nodiscard]] TSuccess& getValue() MIJIN_NOEXCEPT { return std::get<TSuccess>(state_); }
[[nodiscard]] TError& getError() noexcept { return std::get<TError>(state_); } [[nodiscard]] TError& getError() MIJIN_NOEXCEPT { return std::get<TError>(state_); }
[[nodiscard]] const TSuccess& getValue() const noexcept { return std::get<TSuccess>(state_); } [[nodiscard]] const TSuccess& getValue() const MIJIN_NOEXCEPT { return std::get<TSuccess>(state_); }
[[nodiscard]] const TError& getError() const noexcept { return std::get<TError>(state_); } [[nodiscard]] const TError& getError() const MIJIN_NOEXCEPT { return std::get<TError>(state_); }
}; };
struct ResultError struct ResultError
{ {
std::string message; 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>

View 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

View 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)

View File

@@ -4,10 +4,12 @@
#if !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED) #if !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)
#define MIJIN_UTIL_ALIGN_HPP_INCLUDED 1 #define MIJIN_UTIL_ALIGN_HPP_INCLUDED 1
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
template<typename T> template<typename T>
constexpr T alignUp(T value, T alignTo) noexcept constexpr T alignUp(T value, T alignTo) MIJIN_NOEXCEPT
{ {
if (value % alignTo != 0) if (value % alignTo != 0)
{ {
@@ -16,6 +18,7 @@ constexpr T alignUp(T value, T alignTo) noexcept
return value; return value;
} }
#define MIJIN_STRIDEOF(T) mijin::alignUp(sizeof(T), alignof(T))
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED) #endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)

View File

@@ -6,7 +6,6 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cassert>
#include <cstddef> #include <cstddef>
namespace mijin namespace mijin
@@ -32,8 +31,9 @@ private:
using byte_type = std::conditional_t<threadSafe, std::atomic_uint8_t, std::uint8_t>; using byte_type = std::conditional_t<threadSafe, std::atomic_uint8_t, std::uint8_t>;
std::array<byte_type, (numBits + 7) / 8> bytes; std::array<byte_type, (numBits + 7) / 8> bytes;
public: public:
[[nodiscard]] bool get(std::size_t index) const { [[nodiscard]] bool get(std::size_t index) const
assert(index < numBits); {
MIJIN_ASSERT(index < numBits, "BitArray: index out of range.");
return (bytes[index / 8] & (1 << (index % 8))); return (bytes[index / 8] & (1 << (index % 8)));
} }
void set(std::size_t index, bool value) void set(std::size_t index, bool value)

View File

@@ -5,8 +5,11 @@
#define MIJIN_UTIL_BITFLAGS_HPP_INCLUDED 1 #define MIJIN_UTIL_BITFLAGS_HPP_INCLUDED 1
#include <bit> #include <bit>
#include <compare>
#include <cstddef> #include <cstddef>
#include "../util/traits.hpp"
namespace mijin namespace mijin
{ {
@@ -25,6 +28,8 @@ namespace mijin
template<typename TBits> template<typename TBits>
struct BitFlags struct BitFlags
{ {
using bits_t = TBits;
constexpr TBits& operator |=(const BitFlags& other) { constexpr TBits& operator |=(const BitFlags& other) {
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) { 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); *(std::bit_cast<std::byte*>(asBits()) + idx) |= *(std::bit_cast<const std::byte*>(other.asBits()) + idx);
@@ -66,7 +71,7 @@ struct BitFlags
return copy; return copy;
} }
constexpr operator bool() const { explicit constexpr operator bool() const {
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) { for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
if (*(std::bit_cast<const std::byte*>(asBits()) + idx) != std::byte(0)) { if (*(std::bit_cast<const std::byte*>(asBits()) + idx) != std::byte(0)) {
return true; return true;
@@ -78,15 +83,50 @@ struct BitFlags
constexpr bool operator!() const { constexpr bool operator!() const {
return !static_cast<bool>(*this); return !static_cast<bool>(*this);
} }
auto operator<=>(const BitFlags&) const noexcept = default;
private: private:
constexpr TBits* asBits() { return static_cast<TBits*>(this); } constexpr TBits* asBits() { return static_cast<TBits*>(this); }
constexpr const TBits* asBits() const { return static_cast<const 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 // public functions
// //
template<std::integral TInt, BitFlagsType T>
TInt bitFlagsToInt(const BitFlags<T>& flags) noexcept
{
static constexpr std::size_t BYTES = std::min(sizeof(T), sizeof(TInt));
TInt result = 0;
for (std::size_t off = 0; off < BYTES; ++off)
{
result |= static_cast<TInt>(*(std::bit_cast<const std::byte*>(&flags) + off)) << (off * 8);
}
return result;
}
template<BitFlagsType T, std::integral TInt>
T bitFlagsFromInt(TInt intVal) noexcept
{
static constexpr std::size_t BYTES = std::min(sizeof(T), sizeof(TInt));
T result = {};
for (std::size_t off = 0; off < BYTES; ++off)
{
(*(std::bit_cast<std::byte*>(&result) + off)) = static_cast<std::byte>(intVal >> (off * 8));
}
return result;
}
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED) #endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED)

View File

@@ -4,7 +4,8 @@
#if !defined(MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED) #if !defined(MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED)
#define MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED 1 #define MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED 1
#define MIJIN_CONCAT2(a, b) a ## b #define MIJIN_CONCAT_DETAIL(a, b) a ## b
#define MIJIN_CONCAT(a, b) MIJIN_CONCAT2(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) #endif // defined(MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED)

View File

@@ -0,0 +1,77 @@
#pragma once
#ifndef MIJIN_UTIL_EXCEPTION_HPP_INCLUDED
#define MIJIN_UTIL_EXCEPTION_HPP_INCLUDED 1
#include <stdexcept>
#include "../debug/stacktrace.hpp"
namespace mijin
{
//
// public defines
//
//
// public types
//
class Exception : public std::runtime_error
{
private:
Result<Stacktrace> stacktrace_;
std::exception_ptr cause_;
public:
Exception(const std::string& what) : std::runtime_error(what), stacktrace_(captureStacktrace(1)), cause_(std::current_exception()) {}
Exception(const char* what) : Exception(std::string(what)) {}
[[nodiscard]]
const Result<Stacktrace>& getStacktrace() const noexcept { return stacktrace_; }
[[nodiscard]]
const std::exception_ptr& getCause() const noexcept { return cause_; }
};
//
// public functions
//
template<typename TCondition, typename TException = std::runtime_error, typename... TExceptionArgs>
inline decltype(auto) ensure(TCondition&& condition, TExceptionArgs&&... args)
{
if (!static_cast<bool>(std::forward<TCondition>(condition)))
{
throw TException(std::forward<TExceptionArgs>(args)...);
}
return std::forward<TCondition>(condition);
}
template<typename TFunc>
void walkExceptionCause(const std::exception_ptr& cause, TFunc func)
{
if (cause)
{
try
{
std::rethrow_exception(cause);
}
catch(Exception& exc)
{
func(exc);
walkExceptionCause(exc.getCause(), std::move(func));
}
catch(std::exception& exc)
{
func(exc);
}
catch(...)
{
func(nullptr);
}
}
}
} // namespace mijin
#endif // MIJIN_UTIL_EXCEPTION_HPP_INCLUDED

View File

@@ -7,6 +7,7 @@
#include <cstdint> #include <cstdint>
#include "./traits.hpp" #include "./traits.hpp"
#include "./types.hpp" #include "./types.hpp"
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -15,23 +16,23 @@ namespace mijin
// public defines // public defines
// //
#define MIJIN_DEFINE_FLAG(name) \ #define MIJIN_DEFINE_FLAG(name) \
struct name : mijin::Flag \ struct name : mijin::Flag \
{ \ { \
private: \ private: \
struct Proxy_ { \ struct Proxy_ { \
uint8_t value; \ uint8_t value; \
}; \ }; \
public: \ public: \
constexpr name() = default; \ constexpr name() = default; \
constexpr name(const name&) = default; \ constexpr name(const name&) = default; \
constexpr name(Proxy_ proxy) \ constexpr name(Proxy_ proxy) MIJIN_NOEXCEPT \
: Flag(proxy.value) {} \ : Flag(proxy.value) {} \
constexpr explicit name(bool value) noexcept \ constexpr explicit name(bool value) MIJIN_NOEXCEPT \
: Flag(value) {} \ : Flag(value) {} \
name& operator=(const name&) = default; \ name& operator=(const name&) = default; \
static constexpr Proxy_ YES{1}; \ static constexpr Proxy_ YES{1}; \
static constexpr Proxy_ NO{0}; \ static constexpr Proxy_ NO{0}; \
} }
// //
@@ -48,23 +49,23 @@ struct Flag
Flag() = default; Flag() = default;
Flag(const 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; 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; 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; return value != other.value;
} }
constexpr bool operator !() const noexcept constexpr bool operator !() const MIJIN_NOEXCEPT
{ {
return !value; return !value;
} }
constexpr operator bool() const noexcept constexpr operator bool() const MIJIN_NOEXCEPT
{ {
return value != 0; return value != 0;
} }
@@ -89,7 +90,7 @@ private:
using base_t = FlagSetStorage<offset + 1, TMore...>; using base_t = FlagSetStorage<offset + 1, TMore...>;
static constexpr typename base_t::data_t BIT = (1 << offset); static constexpr typename base_t::data_t BIT = (1 << offset);
public: public:
constexpr void set(TFirst value) noexcept constexpr void set(TFirst value) MIJIN_NOEXCEPT
{ {
if (value) if (value)
{ {
@@ -100,7 +101,7 @@ public:
base_t::data_ &= ~BIT; base_t::data_ &= ~BIT;
} }
} }
constexpr bool get(TFirst) noexcept constexpr bool get(TFirst) MIJIN_NOEXCEPT
{ {
return (base_t::data_ & BIT) != 0; return (base_t::data_ & BIT) != 0;
} }
@@ -134,19 +135,19 @@ public:
public: public:
FlagSet& operator=(const FlagSet&) = default; FlagSet& operator=(const FlagSet&) = default;
template<FlagType T> requires is_any_type_v<T, TFlags...> template<FlagType T> requires is_any_type_v<T, TFlags...>
FlagSet& operator=(T flag) noexcept FlagSet& operator=(T flag) MIJIN_NOEXCEPT
{ {
reset(flag); reset(flag);
return *this; return *this;
} }
template<FlagType T> requires is_any_type_v<T, TFlags...> template<FlagType T> requires is_any_type_v<T, TFlags...>
FlagSet& operator|=(T flag) noexcept FlagSet& operator|=(T flag) MIJIN_NOEXCEPT
{ {
set(flag); set(flag);
return *this; return *this;
} }
template<FlagType T> requires is_any_type_v<T, TFlags...> template<FlagType T> requires is_any_type_v<T, TFlags...>
FlagSet& operator&=(T flag) noexcept FlagSet& operator&=(T flag) MIJIN_NOEXCEPT
{ {
unset(flag); unset(flag);
} }

View File

@@ -9,8 +9,10 @@
#include <span> #include <span>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
#include <type_traits>
#include <variant> #include <variant>
#include "../container/optional.hpp" #include "../container/optional.hpp"
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -96,50 +98,50 @@ struct EnumeratingIterator
TIdx idx; TIdx idx;
TIterator base; TIterator base;
EnumeratingIterator(TIdx idx_, TIterator base_) noexcept : idx(idx_), base(base_) {} EnumeratingIterator(TIdx idx_, TIterator base_) MIJIN_NOEXCEPT : idx(idx_), base(base_) {}
EnumeratingIterator(const EnumeratingIterator&) noexcept = default; 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); return std::tie(idx, *base);
} }
EnumeratingIterator& operator++() noexcept EnumeratingIterator& operator++() MIJIN_NOEXCEPT
{ {
++idx; ++idx;
++base; ++base;
return *this; return *this;
} }
EnumeratingIterator operator++(int) noexcept EnumeratingIterator operator++(int) MIJIN_NOEXCEPT
{ {
EnumeratingIterator copy(*this); EnumeratingIterator copy(*this);
++(*this); ++(*this);
return copy; return copy;
} }
EnumeratingIterator& operator--() noexcept EnumeratingIterator& operator--() MIJIN_NOEXCEPT
{ {
--idx; --idx;
--base; --base;
return *this; return *this;
} }
EnumeratingIterator operator--(int) noexcept EnumeratingIterator operator--(int) MIJIN_NOEXCEPT
{ {
EnumeratingIterator copy(*this); EnumeratingIterator copy(*this);
--(*this); --(*this);
return copy; 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() 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() 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; RangeRef<TIterable> base;
auto begin() const noexcept auto begin() const MIJIN_NOEXCEPT
{ {
return EnumeratingIterator(TIdx(0), base.begin()); return EnumeratingIterator(TIdx(0), base.begin());
} }
auto end() const noexcept auto end() const MIJIN_NOEXCEPT
{ {
return EnumeratingIterator(TIdx(0), base.end()); return EnumeratingIterator(TIdx(0), base.end());
} }
@@ -169,6 +171,86 @@ Enumeratable<TIdx, TIterable> enumerate(TIterable&& iterable)
return {.base = {std::forward<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> template<typename TIterator>
struct ReplacingIterator struct ReplacingIterator
{ {
@@ -182,12 +264,12 @@ struct ReplacingIterator
value_type what; value_type what;
value_type with; value_type with;
ReplacingIterator(TIterator base_, value_type what_, value_type with_) noexcept : base(base_), what(what_), with(with_) {} ReplacingIterator(TIterator base_, value_type what_, value_type with_) MIJIN_NOEXCEPT : base(base_), what(what_), with(with_) {}
ReplacingIterator(const ReplacingIterator&) noexcept = default; 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) { if (*base == what) {
return &with; return &with;
@@ -195,7 +277,7 @@ struct ReplacingIterator
return &*base; return &*base;
} }
reference operator*() const noexcept reference operator*() const MIJIN_NOEXCEPT
{ {
if (*base == what) { if (*base == what) {
return with; return with;
@@ -203,38 +285,38 @@ struct ReplacingIterator
return *base; return *base;
} }
ReplacingIterator& operator++() noexcept ReplacingIterator& operator++() MIJIN_NOEXCEPT
{ {
++base; ++base;
return *this; return *this;
} }
ReplacingIterator operator++(int) noexcept ReplacingIterator operator++(int) MIJIN_NOEXCEPT
{ {
ReplacingIterator copy(*this); ReplacingIterator copy(*this);
++(*this); ++(*this);
return copy; return copy;
} }
ReplacingIterator& operator--() noexcept ReplacingIterator& operator--() MIJIN_NOEXCEPT
{ {
--base; --base;
return *this; return *this;
} }
ReplacingIterator operator--(int) noexcept ReplacingIterator operator--(int) MIJIN_NOEXCEPT
{ {
ReplacingIterator copy(*this); ReplacingIterator copy(*this);
--(*this); --(*this);
return copy; 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; 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); return !(*this == other);
} }
@@ -249,12 +331,12 @@ struct ReplacingRange : RangeAdapter
value_type what; value_type what;
value_type with; value_type with;
auto begin() const noexcept auto begin() const MIJIN_NOEXCEPT
{ {
return ReplacingIterator(base.begin(), what, with); return ReplacingIterator(base.begin(), what, with);
} }
auto end() const noexcept auto end() const MIJIN_NOEXCEPT
{ {
return ReplacingIterator(base.end(), what, with); return ReplacingIterator(base.end(), what, with);
} }
@@ -288,14 +370,14 @@ struct MappingIterator
TFunctor functor; TFunctor functor;
mutable Optional<value_type> result; mutable Optional<value_type> result;
MappingIterator(TIterator base_, TFunctor functor_) noexcept : base(base_), functor(std::move(functor_)) {} MappingIterator(TIterator base_, TFunctor functor_) MIJIN_NOEXCEPT : base(base_), functor(std::move(functor_)) {}
MappingIterator(const MappingIterator&) noexcept = default; MappingIterator(const MappingIterator&) MIJIN_NOEXCEPT = default;
MappingIterator(MappingIterator&&) noexcept = default; MappingIterator(MappingIterator&&) MIJIN_NOEXCEPT = default;
MappingIterator& operator=(const MappingIterator&) noexcept = default; MappingIterator& operator=(const MappingIterator&) MIJIN_NOEXCEPT = default;
MappingIterator& operator=(MappingIterator&&) noexcept = default; MappingIterator& operator=(MappingIterator&&) MIJIN_NOEXCEPT = default;
reference operator*() const noexcept reference operator*() const MIJIN_NOEXCEPT
{ {
if (result.empty()) { if (result.empty()) {
result = std::invoke(functor, *base); result = std::invoke(functor, *base);
@@ -303,40 +385,40 @@ struct MappingIterator
return *result; return *result;
} }
MappingIterator& operator++() noexcept MappingIterator& operator++() MIJIN_NOEXCEPT
{ {
++base; ++base;
result.reset(); result.reset();
return *this; return *this;
} }
MappingIterator operator++(int) noexcept MappingIterator operator++(int) MIJIN_NOEXCEPT
{ {
MappingIterator copy(*this); MappingIterator copy(*this);
++(*this); ++(*this);
return copy; return copy;
} }
MappingIterator& operator--() noexcept MappingIterator& operator--() MIJIN_NOEXCEPT
{ {
--base; --base;
result.reset(); result.reset();
return *this; return *this;
} }
MappingIterator operator--(int) noexcept MappingIterator operator--(int) MIJIN_NOEXCEPT
{ {
MappingIterator copy(*this); MappingIterator copy(*this);
--(*this); --(*this);
return copy; 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 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); return !(*this == other);
} }
@@ -346,22 +428,23 @@ template<typename TIterable, typename TFunctor>
struct MappingRange : RangeAdapter 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::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; RangeRef<TIterable> base;
TFunctor functor; TFunctor functor;
auto begin() const noexcept auto begin() const MIJIN_NOEXCEPT
{ {
return MappingIterator(base.begin(), functor); return MappingIterator(base.begin(), functor);
} }
auto end() const noexcept auto end() const MIJIN_NOEXCEPT
{ {
return MappingIterator(base.end(), functor); return MappingIterator(base.end(), functor);
} }
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const MIJIN_NOEXCEPT {
return base.begin() == base.end(); return base.begin() == base.end();
} }
}; };
@@ -392,7 +475,7 @@ struct OptionalMappingIterator
TFunctor functor; TFunctor functor;
mutable optional_type result; // must be mutable so dereferencing can stay const, not nice :/ 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) if (base != end)
{ {
result = functor(*base); result = functor(*base);
@@ -401,18 +484,18 @@ struct OptionalMappingIterator
} }
} }
} }
OptionalMappingIterator(const OptionalMappingIterator&) noexcept = default; OptionalMappingIterator(const OptionalMappingIterator&) MIJIN_NOEXCEPT = default;
OptionalMappingIterator(OptionalMappingIterator&&) noexcept = default; OptionalMappingIterator(OptionalMappingIterator&&) MIJIN_NOEXCEPT = default;
OptionalMappingIterator& operator=(const OptionalMappingIterator&) noexcept = default; OptionalMappingIterator& operator=(const OptionalMappingIterator&) MIJIN_NOEXCEPT = default;
OptionalMappingIterator& operator=(OptionalMappingIterator&&) noexcept = default; OptionalMappingIterator& operator=(OptionalMappingIterator&&) MIJIN_NOEXCEPT = default;
reference operator*() const noexcept reference operator*() const MIJIN_NOEXCEPT
{ {
return *result; return *result;
} }
OptionalMappingIterator& operator++() noexcept OptionalMappingIterator& operator++() MIJIN_NOEXCEPT
{ {
do do
{ {
@@ -422,19 +505,19 @@ struct OptionalMappingIterator
return *this; return *this;
} }
OptionalMappingIterator operator++(int) noexcept OptionalMappingIterator operator++(int) MIJIN_NOEXCEPT
{ {
OptionalMappingIterator copy(*this); OptionalMappingIterator copy(*this);
++(*this); ++(*this);
return copy; 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 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); return !(*this == other);
} }
@@ -449,12 +532,12 @@ struct OptionalMappingRange : RangeAdapter
RangeRef<TIterable> base; RangeRef<TIterable> base;
TFunctor functor; TFunctor functor;
auto begin() const noexcept auto begin() const MIJIN_NOEXCEPT
{ {
return OptionalMappingIterator(base.begin(), base.end(), functor); return OptionalMappingIterator(base.begin(), base.end(), functor);
} }
auto end() const noexcept auto end() const MIJIN_NOEXCEPT
{ {
return OptionalMappingIterator(base.end(), base.end(), functor); return OptionalMappingIterator(base.end(), base.end(), functor);
} }
@@ -481,23 +564,23 @@ struct FilteringIterator
TIterator end; TIterator end;
TPredicate predicate; 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)) { if (base != end && !predicate(*base)) {
++(*this); ++(*this);
} }
} }
FilteringIterator(const FilteringIterator&) noexcept = default; FilteringIterator(const FilteringIterator&) MIJIN_NOEXCEPT = default;
FilteringIterator(FilteringIterator&&) noexcept = default; FilteringIterator(FilteringIterator&&) MIJIN_NOEXCEPT = default;
FilteringIterator& operator=(const FilteringIterator&) noexcept = default; FilteringIterator& operator=(const FilteringIterator&) MIJIN_NOEXCEPT = default;
FilteringIterator& operator=(FilteringIterator&&) noexcept = default; FilteringIterator& operator=(FilteringIterator&&) MIJIN_NOEXCEPT = default;
reference operator*() const noexcept reference operator*() const MIJIN_NOEXCEPT
{ {
return *base; return *base;
} }
FilteringIterator& operator++() noexcept FilteringIterator& operator++() MIJIN_NOEXCEPT
{ {
do do
{ {
@@ -506,19 +589,19 @@ struct FilteringIterator
return *this; return *this;
} }
FilteringIterator operator++(int) noexcept FilteringIterator operator++(int) MIJIN_NOEXCEPT
{ {
FilteringIterator copy(*this); FilteringIterator copy(*this);
++(*this); ++(*this);
return copy; 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? 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); return !(*this == other);
} }
@@ -533,12 +616,12 @@ struct FilteringRange : RangeAdapter
RangeRef<TIterable> base; RangeRef<TIterable> base;
TPredicate predicate; TPredicate predicate;
auto begin() const noexcept auto begin() const MIJIN_NOEXCEPT
{ {
return FilteringIterator(base.begin(), base.end(), predicate); return FilteringIterator(base.begin(), base.end(), predicate);
} }
auto end() const noexcept auto end() const MIJIN_NOEXCEPT
{ {
return FilteringIterator(base.end(), base.end(), predicate); return FilteringIterator(base.end(), base.end(), predicate);
} }
@@ -557,9 +640,9 @@ template<typename TFirstIterator, typename TSecondIterator>
struct ChainingIterator struct ChainingIterator
{ {
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using value_type = typename std::iterator_traits<TFirstIterator>::value_type; using value_type = std::remove_reference_t<typename std::iterator_traits<TFirstIterator>::reference>;
using pointer = std::add_const_t<value_type>*; using pointer = value_type*;
using reference = std::add_const_t<value_type>&; using reference = value_type&;
using iterator_category = std::bidirectional_iterator_tag; // TODO? using iterator_category = std::bidirectional_iterator_tag; // TODO?
TFirstIterator firstBase; TFirstIterator firstBase;
@@ -567,13 +650,13 @@ struct ChainingIterator
TSecondIterator secondBase; TSecondIterator secondBase;
TSecondIterator secondBegin; 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_) {} : 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) if (firstBase == firstEnd)
{ {
@@ -582,7 +665,7 @@ struct ChainingIterator
return &*firstBase; return &*firstBase;
} }
reference operator*() const noexcept reference operator*() const MIJIN_NOEXCEPT
{ {
if (firstBase == firstEnd) if (firstBase == firstEnd)
{ {
@@ -591,7 +674,7 @@ struct ChainingIterator
return *firstBase; return *firstBase;
} }
ChainingIterator& operator++() noexcept ChainingIterator& operator++() MIJIN_NOEXCEPT
{ {
if (firstBase == firstEnd) { if (firstBase == firstEnd) {
++secondBase; ++secondBase;
@@ -602,14 +685,14 @@ struct ChainingIterator
return *this; return *this;
} }
ChainingIterator operator++(int) noexcept ChainingIterator operator++(int) MIJIN_NOEXCEPT
{ {
ChainingIterator copy(*this); ChainingIterator copy(*this);
++(*this); ++(*this);
return copy; return copy;
} }
ChainingIterator& operator--() noexcept ChainingIterator& operator--() MIJIN_NOEXCEPT
{ {
if (secondBase == secondBegin) { if (secondBase == secondBegin) {
--firstBase; --firstBase;
@@ -620,19 +703,19 @@ struct ChainingIterator
return *this; return *this;
} }
ChainingIterator operator--(int) noexcept ChainingIterator operator--(int) MIJIN_NOEXCEPT
{ {
ChainingIterator copy(*this); ChainingIterator copy(*this);
--(*this); --(*this);
return copy; 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 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); return !(*this == other);
} }
@@ -646,12 +729,12 @@ struct ChainedRange : RangeAdapter
RangeRef<TFirstRange> first; RangeRef<TFirstRange> first;
RangeRef<TSecondRange> second; RangeRef<TSecondRange> second;
auto begin() const noexcept auto begin() const MIJIN_NOEXCEPT
{ {
return ChainingIterator(first.begin(), first.end(), second.begin(), second.begin()); 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()); return ChainingIterator(first.end(), first.end(), second.end(), second.begin());
} }
@@ -684,23 +767,23 @@ struct TypeFilteringIterator
TIterator base; TIterator base;
TIterator end; 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()) { if (base != end && !isCastable()) {
++(*this); ++(*this);
} }
} }
TypeFilteringIterator(const TypeFilteringIterator&) noexcept = default; TypeFilteringIterator(const TypeFilteringIterator&) MIJIN_NOEXCEPT = default;
TypeFilteringIterator(TypeFilteringIterator&&) noexcept = default; TypeFilteringIterator(TypeFilteringIterator&&) MIJIN_NOEXCEPT = default;
TypeFilteringIterator& operator=(const TypeFilteringIterator&) noexcept = default; TypeFilteringIterator& operator=(const TypeFilteringIterator&) MIJIN_NOEXCEPT = default;
TypeFilteringIterator& operator=(TypeFilteringIterator&&) noexcept = default; TypeFilteringIterator& operator=(TypeFilteringIterator&&) MIJIN_NOEXCEPT = default;
reference operator*() const noexcept reference operator*() const MIJIN_NOEXCEPT
{ {
return static_cast<reference>(*base); return static_cast<reference>(*base);
} }
TypeFilteringIterator& operator++() noexcept TypeFilteringIterator& operator++() MIJIN_NOEXCEPT
{ {
do do
{ {
@@ -709,19 +792,19 @@ struct TypeFilteringIterator
return *this; return *this;
} }
TypeFilteringIterator operator++(int) noexcept TypeFilteringIterator operator++(int) MIJIN_NOEXCEPT
{ {
FilteringIterator copy(*this); FilteringIterator copy(*this);
++(*this); ++(*this);
return copy; return copy;
} }
bool operator==(const TypeFilteringIterator& other) const noexcept bool operator==(const TypeFilteringIterator& other) const MIJIN_NOEXCEPT
{ {
return base == other.base && end == other.end; 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); return !(*this == other);
} }
@@ -747,12 +830,12 @@ struct TypeFilteringRange : RangeAdapter
RangeRef<TIterable> iterable; RangeRef<TIterable> iterable;
auto begin() const noexcept auto begin() const MIJIN_NOEXCEPT
{ {
return TypeFilteringIterator<TType, decltype(iterable.begin())>(iterable.begin(), iterable.end()); 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()); return TypeFilteringIterator<TType, decltype(iterable.begin())>(iterable.end(), iterable.end());
} }

View File

@@ -2,13 +2,16 @@
#include "os.hpp" #include "os.hpp"
#include <filesystem> #include <filesystem>
#include "../detect.hpp" #include "../debug/assert.hpp"
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX #if MIJIN_TARGET_OS == MIJIN_OS_LINUX
#include <mutex>
#include <dlfcn.h> #include <dlfcn.h>
#include <pthread.h> #include <pthread.h>
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
// TODO #include <array>
#include <windows.h>
#include "../util/winundef.hpp"
#endif #endif
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -32,6 +35,13 @@ namespace mijin
// internal variables // 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 // internal functions
// //
@@ -40,32 +50,85 @@ namespace mijin
// public functions // 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 #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); 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 #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
// TODO
(void) libraryFile;
return {};
#endif #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 #if MIJIN_TARGET_OS == MIJIN_OS_LINUX
return dlsym(library.data, symbolName); return dlsym(library.data, symbolName);
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
(void) library;
(void) symbolName;
return nullptr;
#endif #endif
} }
void setCurrentThreadName(const char* threadName) noexcept void setCurrentThreadName(const char* threadName) MIJIN_NOEXCEPT
{ {
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX #if MIJIN_TARGET_OS == MIJIN_OS_LINUX
pthread_setname_np(pthread_self(), threadName); pthread_setname_np(pthread_self(), threadName);
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
(void) threadName;
#endif #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 #if MIJIN_TARGET_OS == MIJIN_OS_LINUX
return "lib" + std::string(libraryName) + ".so"; return "lib" + std::string(libraryName) + ".so";

View File

@@ -7,7 +7,10 @@
#include <csignal> #include <csignal>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <utility>
#include "../detect.hpp" #include "../detect.hpp"
#include "../internal/common.hpp"
#include "../types/result.hpp"
namespace mijin namespace mijin
{ {
@@ -20,6 +23,15 @@ namespace mijin
// public constants // 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 // public types
// //
@@ -28,20 +40,77 @@ struct LibraryHandle
{ {
void* data = nullptr; void* data = nullptr;
constexpr operator bool() const noexcept { return data != nullptr; } constexpr operator bool() const MIJIN_NOEXCEPT { return data != nullptr; }
constexpr bool operator!() const 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 // public functions
// //
[[nodiscard]] LibraryHandle openSharedLibrary(std::string_view libraryFile) noexcept; [[nodiscard]] Result<LibraryHandle> openSharedLibrary(std::string_view libraryFile) MIJIN_NOEXCEPT;
[[nodiscard]] void* loadSymbolFromLibrary(const LibraryHandle library, const char* symbolName) noexcept; bool closeSharedLibrary(const LibraryHandle library) MIJIN_NOEXCEPT;
void setCurrentThreadName(const char* threadName) noexcept; [[nodiscard]] void* loadSymbolFromLibrary(const LibraryHandle library, const char* symbolName) MIJIN_NOEXCEPT;
void setCurrentThreadName(const char* threadName) MIJIN_NOEXCEPT;
[[nodiscard]] std::string getExecutablePath() MIJIN_NOEXCEPT;
[[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) noexcept; [[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) MIJIN_NOEXCEPT;
SharedLibrary::~SharedLibrary() MIJIN_NOEXCEPT
{
close();
}
Result<bool> SharedLibrary::open(std::string_view libraryFile) MIJIN_NOEXCEPT
{
close();
Result<LibraryHandle> openResult = openSharedLibrary(libraryFile);
if (openResult.isSuccess())
{
mHandle = openResult.getValue();
return true;
}
return ResultError(openResult.getError());
}
bool SharedLibrary::close() MIJIN_NOEXCEPT
{
if (mHandle)
{
return closeSharedLibrary(std::exchange(mHandle, {}));
}
return false;
}
void* SharedLibrary::loadSymbol(const char* symbolName) MIJIN_NOEXCEPT
{
return loadSymbolFromLibrary(mHandle, symbolName);
}
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_UTIL_OS_HPP_INCLUDED) #endif // !defined(MIJIN_UTIL_OS_HPP_INCLUDED)

View 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

View File

@@ -5,6 +5,7 @@
#include <functional> #include <functional>
#include "./common_macros.hpp" #include "./common_macros.hpp"
#include "../internal/common.hpp"
#define MIJIN_SCOPE_EXIT_NAMED(name) \ #define MIJIN_SCOPE_EXIT_NAMED(name) \
const mijin::ScopeExitGuard 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() 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: public:
template<typename TFunc> template<typename TFunc>
inline ScopeExitGuard(TFunc&& func_) noexcept : func(std::forward<TFunc>(func_)) {} inline ScopeExitGuard(TFunc&& func_) MIJIN_NOEXCEPT : func(std::forward<TFunc>(func_)) {}
ScopeExitGuard(const ScopeExitGuard&) noexcept = delete; ScopeExitGuard(const ScopeExitGuard&) = delete;
ScopeExitGuard(ScopeExitGuard&&) noexcept = delete; ScopeExitGuard(ScopeExitGuard&&) = delete;
inline ~ScopeExitGuard() noexcept inline ~ScopeExitGuard() MIJIN_NOEXCEPT
{ {
if (func) { if (func) {
func(); func();
} }
} }
ScopeExitGuard& operator=(const ScopeExitGuard&) noexcept = delete; ScopeExitGuard& operator=(const ScopeExitGuard&) = delete;
ScopeExitGuard& operator=(ScopeExitGuard&&) noexcept = delete; ScopeExitGuard& operator=(ScopeExitGuard&&) = delete;
inline void reset() const noexcept inline void reset() const MIJIN_NOEXCEPT
{ {
func = {}; func = {};
} }

View File

@@ -4,9 +4,19 @@
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED) #if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1 #define MIJIN_UTIL_STRING_HPP_INCLUDED 1
#include <algorithm>
#include <array>
#include <charconv>
#include <iterator> #include <iterator>
#include <limits>
#include <locale>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <string_view>
#include <vector>
#include "./iterators.hpp"
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -19,16 +29,29 @@ namespace mijin
// public constants // public constants
// //
//
// public traits
//
template<typename TString>
using char_type_t = decltype(std::string_view(std::declval<TString>()))::value_type;
// //
// public types // public types
// //
struct SplitOptions
{
std::size_t limitParts = std::numeric_limits<std::size_t>::max();
bool ignoreEmpty = true;
};
// //
// public functions // public functions
// //
template <typename TRange, typename TValue = typename TRange::value_type> template <typename TRange, typename TValue = typename TRange::value_type>
std::string join(const TRange& elements, const char* const delimiter) [[nodiscard]] std::string join(const TRange& elements, const char* const delimiter)
{ {
std::ostringstream oss; std::ostringstream oss;
auto first = std::begin(elements); auto first = std::begin(elements);
@@ -47,13 +70,330 @@ std::string join(const TRange& elements, const char* const delimiter)
return oss.str(); return oss.str();
} }
namespace detail
{
template<typename TChar, typename TTraits, typename TOutIterator>
void splitImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> separator,
const SplitOptions& options,
TOutIterator outIterator,
std::size_t& numEmitted)
{
using sv_t = std::basic_string_view<TChar, TTraits>;
MIJIN_ASSERT(options.limitParts > 0, "Cannot split to zero parts.");
MIJIN_ASSERT(!separator.empty(), "Separator cannot be empty.");
if (separator.empty())
{
return;
}
auto emit = [&](std::string_view result)
{
*outIterator = result;
++outIterator;
++numEmitted;
};
if (options.limitParts <= 1)
{
emit(stringView);
return;
}
auto start = stringView.begin();
auto pos = start;
const auto end = stringView.end();
auto seperatorFound = [&]()
{
return pos <= (end - separator.size()) && sv_t(pos, pos + separator.size()) == separator;
};
while (pos != end)
{
if (seperatorFound())
{
if (!options.ignoreEmpty || pos != start)
{
emit(std::string_view(start, pos));
}
start = pos = (pos + separator.size());
if (numEmitted == options.limitParts - 1)
{
if (options.ignoreEmpty)
{
while (seperatorFound())
{
pos += separator.size();
}
}
emit(std::string_view(pos, end));
return;
}
if (!options.ignoreEmpty && pos == end)
{
// skipped a separator at the very end, add an empty entry
emit("");
return;
}
}
else
{
++pos;
}
}
if (start != end)
{
emit(std::string_view(start, end));
}
}
template<typename TChar, typename TTraits>
std::vector<std::basic_string_view<TChar, TTraits>> splitImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> separator,
const SplitOptions& options)
{
std::vector<std::basic_string_view<TChar, TTraits>> result;
std::size_t numEmitted = 0;
splitImpl(stringView, separator, options, std::back_inserter(result), numEmitted);
return result;
}
template<std::size_t count, typename TChar, typename TTraits>
std::array<std::basic_string_view<TChar, TTraits>, count> splitFixedImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> separator,
SplitOptions options,
std::size_t* outNumResults)
{
options.limitParts = count;
std::array<std::basic_string_view<TChar, TTraits>, count> result;
std::size_t numEmitted = 0;
splitImpl(stringView, separator, options, result.begin(), numEmitted);
if (outNumResults != nullptr)
{
*outNumResults = numEmitted;
}
return result;
}
template<typename TChar, typename TTraitsA, typename TTraitsB>
bool equalsIgnoreCaseImpl(std::basic_string_view<TChar, TTraitsA> stringA, std::basic_string_view<TChar, TTraitsB> stringB) MIJIN_NOEXCEPT
{
if (stringA.size() != stringB.size())
{
return false;
}
for (const auto [charA, charB] : zip(stringA, stringB))
{
if (std::tolower(charA) != std::tolower(charB))
{
return false;
}
}
return true;
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimPrefixImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim)
{
stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size()));
return stringView;
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimSuffixImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim)
{
stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size()));
return stringView;
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim)
{
return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim);
}
template<typename TChar>
static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
template<typename TChar>
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
= {DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
}
template<typename TLeft, typename TRight>
[[nodiscard]] auto split(TLeft&& stringView, TRight&& separator, const SplitOptions& options = {})
{
return detail::splitImpl(std::basic_string_view(std::forward<TLeft>(stringView)),
std::basic_string_view(std::forward<TRight>(separator)), options);
}
template<std::size_t count, typename TLeft, typename TRight>
[[nodiscard]] auto splitFixed(TLeft&& stringView, TRight&& separator, SplitOptions options = {}, std::size_t* outNumResults = nullptr)
{
return detail::splitFixedImpl<count>(std::basic_string_view(std::forward<TLeft>(stringView)),
std::basic_string_view(std::forward<TRight>(separator)), options, outNumResults);
}
template<typename TString, typename TChars>
[[nodiscard]]
auto trimPrefix(TString&& string, TChars&& chars)
{
return detail::trimPrefixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
}
template<typename TString>
[[nodiscard]]
auto trimPrefix(TString&& string)
{
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
}
template<typename TString, typename TChars>
[[nodiscard]]
auto trimSuffix(TString&& string, TChars&& chars)
{
return detail::trimSuffixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
}
template<typename TString>
[[nodiscard]]
auto trimSuffix(TString&& string)
{
return trimSuffix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
}
template<typename TString, typename TChars>
[[nodiscard]]
auto trim(TString&& string, TChars&& chars)
{
return detail::trimImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
}
template<typename TString>
[[nodiscard]]
auto trim(TString&& string)
{
return trim(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
}
template<typename TLeft, typename TRight>
[[nodiscard]] bool equalsIgnoreCase(TLeft&& left, TRight&& right) MIJIN_NOEXCEPT
{
return detail::equalsIgnoreCaseImpl(std::string_view(left), std::string_view(right));
}
template<typename TChar, typename TTraits, typename TAllocator>
constexpr void makeLower(std::basic_string<TChar, TTraits, TAllocator>& string)
{
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
{
return std::tolower<TChar>(chr, locale);
});
}
template<typename TChar, typename TTraits, typename TAllocator>
constexpr void makeUpper(std::basic_string<TChar, TTraits, TAllocator>& string)
{
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
{
return std::toupper<TChar>(chr, locale);
});
}
template<typename... TArgs>
[[nodiscard]]
constexpr auto toLower(TArgs&&... args)
{
std::basic_string string(std::forward<TArgs>(args)...);
makeLower(string);
return string;
}
template<typename... TArgs>
[[nodiscard]]
constexpr auto toUpper(TArgs&&... args)
{
std::basic_string string(std::forward<TArgs>(args)...);
makeUpper(string);
return string;
}
template<std::integral TNumber>
[[nodiscard]]
constexpr bool toNumber(std::string_view stringView, TNumber& outNumber, int base = 10) MIJIN_NOEXCEPT
{
const char* start = &*stringView.begin();
const char* end = start + stringView.size();
const std::from_chars_result res = std::from_chars(start, end, outNumber, base);
return res.ec == std::errc{} && res.ptr == end;
}
template<typename TChar, typename TTraits, std::integral TNumber>
[[nodiscard]]
constexpr bool toNumber(std::basic_string_view<TChar, TTraits> stringView, TNumber& outNumber, int base = 10) MIJIN_NOEXCEPT requires (!std::is_same_v<TChar, char>)
{
std::string asString;
asString.resize(stringView.size());
// should only contain number symbols, so just cast down to char
std::transform(stringView.begin(), stringView.end(), asString.begin(), [](TChar chr) { return static_cast<char>(chr); });
return toNumber(asString, outNumber, base);
}
template<std::floating_point TNumber>
[[nodiscard]]
constexpr bool toNumber(std::string_view stringView, TNumber& outNumber, std::chars_format fmt = std::chars_format::general) MIJIN_NOEXCEPT
{
const char* start = &*stringView.begin();
const char* end = start + stringView.size();
const std::from_chars_result res = std::from_chars(start, end, outNumber, fmt);
return res.ec == std::errc{} && res.ptr == end;
}
template<typename TChar, typename TTraits, std::floating_point TNumber>
[[nodiscard]]
constexpr bool toNumber(std::basic_string_view<TChar, TTraits> stringView, TNumber& outNumber, std::chars_format fmt = std::chars_format::general) MIJIN_NOEXCEPT requires (!std::is_same_v<TChar, char>)
{
std::string asString;
asString.resize(stringView.size());
// should only contain number symbols, so just cast down to char
std::transform(stringView.begin(), stringView.end(), asString.begin(), [](TChar chr) { return static_cast<char>(chr); });
return toNumber(asString, outNumber, fmt);
}
template<typename TChar>
[[nodiscard]]
constexpr bool isDecimalChar(TChar chr) noexcept
{
return (chr >= TChar('0') && chr <= TChar('9'));
}
template<typename TChar>
[[nodiscard]]
constexpr bool isHexadecimalChar(TChar chr) noexcept
{
return isDecimalChar(chr)
|| (chr >= TChar('A') && chr <= TChar('F'))
|| (chr >= TChar('a') && chr <= TChar('f'));
}
namespace pipe namespace pipe
{ {
struct Join struct Join
{ {
const char* delimiter; const char* delimiter;
explicit Join(const char* delimiter_) noexcept : delimiter(delimiter_) {} explicit Join(const char* delimiter_) MIJIN_NOEXCEPT : delimiter(delimiter_) {}
}; };
template<typename TIterable> template<typename TIterable>
@@ -62,7 +402,6 @@ auto operator|(TIterable&& iterable, const Join& joiner)
return join(std::forward<TIterable>(iterable), joiner.delimiter); return join(std::forward<TIterable>(iterable), joiner.delimiter);
} }
} }
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED) #endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)

View File

@@ -112,6 +112,42 @@ struct is_type_member<TElement, TCollection<Ts...>>
template<typename TElement, typename TCollection> template<typename TElement, typename TCollection>
constexpr bool is_type_member_v = is_type_member<TElement, TCollection>::value; constexpr bool is_type_member_v = is_type_member<TElement, TCollection>::value;
template<typename TFrom, typename TTo>
using copy_const_t = std::conditional_t<std::is_const_v<TFrom>, std::add_const_t<TTo>, std::remove_const_t<TTo>>;
template<typename TFrom, typename TTo>
using copy_volatile_t = std::conditional_t<std::is_volatile_v<TFrom>, std::add_volatile_t<TTo>, std::remove_volatile_t<TTo>>;
template<typename TFrom, typename TTo>
using copy_cv_t = copy_const_t<TFrom, copy_volatile_t<TFrom, TTo>>;
template<typename TActual, typename TDelay>
using delay_type_t = TActual;
template<std::size_t I, typename TArg, typename... TArgs>
struct TypeAtHelper
{
using type_t = typename TypeAtHelper<I-1, TArgs...>::type_t;
};
template<typename TArg, typename... TArgs>
struct TypeAtHelper<0, TArg, TArgs...>
{
using type_t = TArg;
};
template<std::size_t I, typename... TArgs>
using type_at_t = TypeAtHelper<I, TArgs...>::type_t;
template<template<typename...> typename TTemplate, typename TType>
struct is_template_instance : std::false_type {};
template<template<typename...> typename TTemplate, typename... TArgs>
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
template<template<typename...> typename TTemplate, typename TType>
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
// //
// public functions // public functions
// //

View 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)

View 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)

View File

@@ -0,0 +1,24 @@
#if defined(NEAR)
#undef NEAR
#endif
#if defined(FAR)
#undef FAR
#endif
#if defined(ERROR)
#undef ERROR
#endif
#if defined(IGNORE)
#undef IGNORE
#endif
#if defined(ABSOLUTE)
#undef ABSOLUTE
#endif
#if defined(RELATIVE)
#undef RELATIVE
#endif

View File

@@ -1,6 +1,8 @@
#include "./filesystem.hpp" #include "./filesystem.hpp"
#include "../platform/folders.hpp"
namespace mijin namespace mijin
{ {
@@ -28,23 +30,16 @@ namespace mijin
// public functions // public functions
// //
std::vector<fs::path> OSFileSystemAdapter::getRoots()
{
return {
"/" // TODO: other OSs
};
}
fs::path OSFileSystemAdapter::getHomeFolder() 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> OSFileSystemAdapter::listFiles(const fs::path& folder)
{ {
std::vector<FileInfo> entries; std::vector<FileInfo> entries;
std::error_code err; 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) { if (err) {
return {}; // TODO: propagate? return {}; // TODO: propagate?
} }
@@ -57,18 +52,24 @@ std::vector<FileInfo> OSFileSystemAdapter::listFiles(const fs::path& folder)
info.isSymlink = entry.is_symlink(err); info.isSymlink = entry.is_symlink(err);
info.isSpecial = !info.isFolder && !entry.is_regular_file(err); info.isSpecial = !info.isFolder && !entry.is_regular_file(err);
info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux
if (info.isFolder) { if (info.isFolder)
try { {
info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator()); 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; info.size = 0;
} }
} }
else if (!info.isSpecial) else if (!info.isSpecial)
{ {
info.size = entry.file_size(err); info.size = entry.file_size(err);
if (err) { if (err)
{
info.size = 0; 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.isSpecial = !info.isFolder && !fs::is_regular_file(file, err);
info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux
if (info.isFolder) { if (info.isFolder) {
try { MIJIN_TRY
{
info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator()); info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator());
} }
catch(std::runtime_error&) { MIJIN_CATCH(std::runtime_error&)
{
info.size = 0; info.size = 0;
} }
} }
@@ -107,6 +110,11 @@ FileInfo OSFileSystemAdapter::getFileInfo(const fs::path& file)
return info; 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) StreamError OSFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{ {
const std::string pathStr = path.string(); const std::string pathStr = path.string();

View File

@@ -10,7 +10,10 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
#include "../container/optional.hpp"
#include "../io/stream.hpp" #include "../io/stream.hpp"
#include "../internal/common.hpp"
#include "../util/hash.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -40,25 +43,65 @@ struct FileInfo
bool isHidden : 1 = false; 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 class FileSystemAdapter
{ {
public: public:
virtual ~FileSystemAdapter() = default; virtual ~FileSystemAdapter() = default;
[[nodiscard]] virtual std::vector<fs::path> getRoots() = 0;
[[nodiscard]] virtual fs::path getHomeFolder() = 0; [[nodiscard]] virtual fs::path getHomeFolder() = 0;
[[nodiscard]] virtual std::vector<FileInfo> listFiles(const fs::path& folder) = 0; [[nodiscard]] virtual std::vector<FileInfo> listFiles(const fs::path& folder) = 0;
[[nodiscard]] virtual FileInfo getFileInfo(const fs::path& file) = 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; [[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 class OSFileSystemAdapter : public FileSystemAdapter
{ {
public: public:
std::vector<fs::path> getRoots() override;
fs::path getHomeFolder() override; fs::path getHomeFolder() override;
std::vector<FileInfo> listFiles(const fs::path& folder) override; std::vector<FileInfo> listFiles(const fs::path& folder) override;
FileInfo getFileInfo(const fs::path& file) 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; StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
static OSFileSystemAdapter& getInstance(); static OSFileSystemAdapter& getInstance();
@@ -68,6 +111,21 @@ public:
// public functions // 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) inline std::string formatFileType(const FileInfo& info)
{ {
if (info.isFolder) { if (info.isFolder) {
@@ -100,6 +158,42 @@ inline std::string formatFileSize(std::size_t sizeInBytes)
return oss.str(); 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 } // 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) #endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED)

View 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)

View File

@@ -5,6 +5,7 @@
#define MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED 1 #define MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED 1
#include "./filesystem.hpp" #include "./filesystem.hpp"
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -32,28 +33,24 @@ public:
explicit RelativeFileSystemAdapter(fs::path root, TArgs&&... args) explicit RelativeFileSystemAdapter(fs::path root, TArgs&&... args)
: wrapped_(std::forward<TArgs>(args)...), root_(std::move(root)) {} : wrapped_(std::forward<TArgs>(args)...), root_(std::move(root)) {}
RelativeFileSystemAdapter(const RelativeFileSystemAdapter&) = default; RelativeFileSystemAdapter(const RelativeFileSystemAdapter&) = default;
RelativeFileSystemAdapter(RelativeFileSystemAdapter&&) noexcept = default; RelativeFileSystemAdapter(RelativeFileSystemAdapter&&) MIJIN_NOEXCEPT = default;
RelativeFileSystemAdapter& operator=(const RelativeFileSystemAdapter&) = 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; fs::path getHomeFolder() override;
std::vector<FileInfo> listFiles(const fs::path& folder) override; std::vector<FileInfo> listFiles(const fs::path& folder) override;
FileInfo getFileInfo(const fs::path& file) 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; 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 // public functions
// //
template<typename TWrapped>
std::vector<fs::path> RelativeFileSystemAdapter<TWrapped>::getRoots()
{
return { root_ };
}
template<typename TWrapped> template<typename TWrapped>
fs::path RelativeFileSystemAdapter<TWrapped>::getHomeFolder() fs::path RelativeFileSystemAdapter<TWrapped>::getHomeFolder()
{ {
@@ -63,7 +60,9 @@ fs::path RelativeFileSystemAdapter<TWrapped>::getHomeFolder()
template<typename TWrapped> template<typename TWrapped>
std::vector<FileInfo> RelativeFileSystemAdapter<TWrapped>::listFiles(const fs::path& folder) 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) { for (FileInfo& fileInfo : result) {
fileInfo.path = "/" / fileInfo.path.lexically_relative(root_); fileInfo.path = "/" / fileInfo.path.lexically_relative(root_);
} }
@@ -73,15 +72,66 @@ std::vector<FileInfo> RelativeFileSystemAdapter<TWrapped>::listFiles(const fs::p
template<typename TWrapped> template<typename TWrapped>
FileInfo RelativeFileSystemAdapter<TWrapped>::getFileInfo(const fs::path& file) 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> template<typename TWrapped>
StreamError RelativeFileSystemAdapter<TWrapped>::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) StreamError RelativeFileSystemAdapter<TWrapped>::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{ {
return wrapped_.open(root_ / path, mode, outStream); return wrapped_.open(appendPath(path), mode, outStream);
} }
template<typename TWrapped>
fs::path RelativeFileSystemAdapter<TWrapped>::appendPath(const fs::path& other) const MIJIN_NOEXCEPT
{
fs::path combinedPath = root_;
if (other.is_absolute()) {
combinedPath += other;
}
else {
combinedPath /= other;
}
return combinedPath;
}
namespace vfs_pipe
{
template<typename TBase>
struct RelativeBuilder : Builder<RelativeBuilder<TBase>, RelativeFileSystemAdapter<typename TBase::adapter_t>>
{
fs::path root;
TBase base;
std::unique_ptr<RelativeFileSystemAdapter<typename TBase::adapter_t>> build()
{
return std::make_unique<RelativeFileSystemAdapter<typename TBase::adapter_t>>(root, std::move(*base.build()));
}
};
struct RelativeOptions
{
fs::path root;
};
[[nodiscard]]
inline RelativeOptions relative_to(fs::path root) noexcept
{
return {.root = std::move(root) };
}
template<typename TBase>
[[nodiscard]]
RelativeBuilder<TBase> operator|(TBase other, RelativeOptions options) noexcept
{
return {.root = std::move(options.root), .base = std::move(other) };
}
}
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED) #endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED)

View File

@@ -30,24 +30,6 @@ namespace mijin
// public functions // public functions
// //
std::vector<fs::path> StackedFileSystemAdapter::getRoots()
{
std::vector<fs::path> roots;
for (auto& adapter : adapters_)
{
for (const fs::path& root : adapter->getRoots())
{
auto it = std::find(roots.begin(), roots.end(), root);
if (it == roots.end()) {
roots.push_back(root);
}
}
}
return roots;
}
fs::path StackedFileSystemAdapter::getHomeFolder() fs::path StackedFileSystemAdapter::getHomeFolder()
{ {
if (adapters_.empty()) { if (adapters_.empty()) {
@@ -68,7 +50,7 @@ std::vector<FileInfo> StackedFileSystemAdapter::listFiles(const fs::path& folder
{ {
return existing.path == file.path; return existing.path == file.path;
}); });
if (it != files.end()) { if (it == files.end()) {
files.push_back(file); files.push_back(file);
} }
} }
@@ -90,16 +72,52 @@ FileInfo StackedFileSystemAdapter::getFileInfo(const fs::path& file)
return {}; 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_) for (auto& adapter : adapters_)
{ {
FileInfo fileInfo = adapter->getFileInfo(path); Optional<fs::path> result = adapter->getNativePath(file);
if (fileInfo.exists) { 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); 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; 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 } // namespace mijin

View File

@@ -28,11 +28,13 @@ class StackedFileSystemAdapter : public FileSystemAdapter
private: private:
std::vector<std::unique_ptr<FileSystemAdapter>> adapters_; std::vector<std::unique_ptr<FileSystemAdapter>> adapters_;
public: public:
std::vector<fs::path> getRoots() override;
fs::path getHomeFolder() override; fs::path getHomeFolder() override;
std::vector<FileInfo> listFiles(const fs::path& folder) override; std::vector<FileInfo> listFiles(const fs::path& folder) override;
FileInfo getFileInfo(const fs::path& file) 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; StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
void getAllPaths(const fs::path &path, std::vector<PathReference> &outPaths) override;
using FileSystemAdapter::getAllPaths;
inline void addAdapter(std::unique_ptr<FileSystemAdapter>&& adapter) { inline void addAdapter(std::unique_ptr<FileSystemAdapter>&& adapter) {
adapters_.push_back(std::move(adapter)); adapters_.push_back(std::move(adapter));