223 Commits

Author SHA1 Message Date
680acdb332 Fixed compilation of boxed_object.hpp in non-debug builds. 2025-07-25 00:51:53 +02:00
3d507ddef7 Finally implemented object pools. 2025-07-25 00:51:32 +02:00
d35140d278 Added missing template parameter. 2025-07-17 23:47:00 +02:00
933a36d992 Added formattable utility. 2025-07-17 10:56:52 +02:00
d3659f11f7 Implemented getCPUTicks() and getCPUTicksPerSecond() for Windows. 2025-07-17 10:56:39 +02:00
d207774868 Added missing include. 2025-07-17 10:56:15 +02:00
54d9cd327a Added undef for VOID. 2025-07-17 07:51:19 +02:00
93ef90aeb8 Added function_traits. 2025-07-16 21:07:50 +02:00
ba6ffa6c42 Added getCPUTicks() and getCPUTicksPerSeconds() functions. 2025-07-16 21:07:50 +02:00
2cf270df84 Added MIJIN_FUNCNAME() macro. 2025-07-16 21:07:50 +02:00
9808fcf50e Removed unused MIJIN_FUNC() macro. 2025-07-16 21:07:50 +02:00
e91924e049 Added FixedArrayOutputIterator and use it for logging without heap allocations. 2025-07-16 21:07:50 +02:00
79f49829d3 Added utility type to convert variant members via std::visit. 2025-07-16 21:07:50 +02:00
042e0d2465 Added explicit constructor for casting between related not-nullable pointers. 2025-07-16 21:07:50 +02:00
018c75a5ed First implementation of custom path type. 2025-07-16 21:07:50 +02:00
33bc48dd58 Added Path type (WIP). 2025-07-16 21:07:50 +02:00
Patrick Wuttke
51092bb4cb Added splitView() and splitLines() to string utility functions. 2025-07-15 18:21:10 +02:00
Patrick Wuttke
02e99bbc82 Made logger thread-safe and added filters. 2025-07-14 17:16:24 +02:00
Patrick Wuttke
ad627b7c70 Added NotNullable::release() function. 2025-07-14 17:16:09 +02:00
Patrick Wuttke
4a9a60c7f5 Replaced default deleter used in DynamicPointer so it fits better with other functions. 2025-07-14 17:15:25 +02:00
Patrick Wuttke
d8b03893b3 Merge branch 'master' of https://git.mewin.de/mewin/mijin2 2025-07-14 10:08:04 +02:00
Patrick Wuttke
7939d458b3 Added Stream functions for reading and writing 0-terminated strings. 2025-07-14 10:07:57 +02:00
Patrick Wuttke
b5546067a8 Added missing include. 2025-07-14 10:07:37 +02:00
9e572061da Added DEBUG to macros being undefed. 2025-07-11 14:11:14 +02:00
d0be5f7739 Fixed use-after-move in TaskLoop::addTaskImpl() and some minor compilation problems. 2025-07-10 02:16:29 +02:00
e6556c6f90 Fixed convertCharType when converting from wchar_t to char. 2025-07-09 00:57:45 +02:00
addae96c91 Made Stream::readAsString() and Stream::c_readAsString() work with arbitrary std::basic_string instances. 2025-07-09 00:57:19 +02:00
cf860392a5 Added missing copy assignment operator for ConstMemoryView. 2025-07-08 00:35:12 +02:00
8ad34427f3 Made TypelessBuffer allocator-aware and split some of its functionality into MemoryView type. 2025-07-07 00:12:34 +02:00
59780ed6de Fixed compilation of MemoryFileSystem if MIJIN_DEFAULT_ALLOCATOR is not copy-constructible. 2025-07-06 12:37:45 +02:00
888b0a16f7 Fixed use of MIJIN_CONFIG_HEADER. 2025-07-06 12:37:06 +02:00
d29a025ec5 Properly constexpred and noexcepted BoxedObject. 2025-07-03 09:10:36 +02:00
40c0d888e6 Added std::hash for tuples, because why not. 2025-07-02 23:10:30 +02:00
bf53622b19 Added missing construction and destruction of StackAllocatorSnapshotData, which is only required when MIJIN_STACK_ALLOCATOR_DEBUG is 2. 2025-07-02 16:40:46 +02:00
6090d5fc74 Implemented stacktrace collection for Windows. 2025-07-02 16:39:26 +02:00
a1d7a63aba Added flusing to the stdio log sink. 2025-07-02 16:39:02 +02:00
1d32530be7 Added allReady() and getAll() functions for futures and c_allDone() with tasks for tasks. 2025-07-01 18:19:38 +02:00
973b62a348 Added some missing headers. 2025-06-28 02:23:23 +02:00
86f3790ce1 Added stack allocator snapshots. 2025-06-26 16:55:33 +02:00
1cbd435fbf Added missing Signal constructor taking const-qualified member functions. 2025-06-25 00:45:40 +02:00
573c431dbd Lots of windows fixes and some more improvements. 2025-06-24 15:21:01 +02:00
36a908ab8a Added StackAllocator type. 2025-06-23 00:24:03 +02:00
a956560183 Made coroutines allocator-aware (let's hope this really works). 2025-06-23 00:23:50 +02:00
2fc03e4050 Made Signal allocator-aware. 2025-06-23 00:21:46 +02:00
94e94f02b6 Modified assertion macros so they evaluate to an empty instruction instead of nothing in non-debug builds. 2025-06-23 00:21:24 +02:00
7284d3d15f Added STL formatters for Stacktrace and Stackframe. 2025-06-23 00:20:17 +02:00
2b368c1d4f Added converting constructor for AllocatorDeleter. 2025-06-23 00:19:30 +02:00
9c4765dbaf Added converting constructors for NotNullable. 2025-06-23 00:18:47 +02:00
05bc3d5147 Added optional_base trait. 2025-06-23 00:17:30 +02:00
232a01eb28 Added alignUp() variant for pointers. 2025-06-23 00:16:52 +02:00
c9c4eff130 Added (very limited) support for ansi coloring to log formatter. 2025-06-21 17:01:37 +02:00
8a9df15dd0 CLang advertises C++-26 features but warns us when we use them :/. 2025-06-21 15:07:12 +02:00
465a97ded5 Removed logger.cpp again. 2025-06-21 15:06:39 +02:00
b91eb34789 Massively overengineered logger to support different character types. 2025-06-21 14:16:19 +02:00
061c58ef41 Added more stuff like Logging, DynamicPointer, utilities for using STL allocators and NotNullable. 2025-06-21 08:07:57 +02:00
17bd408d3c Fixed test and CLang compilation. 2025-05-20 21:15:51 +02:00
45623e5273 Fixed GCC compilation. 2025-05-17 11:14:03 +02:00
48fd006819 Added MemoryFileSystemAdapter. 2025-03-28 11:44:28 +01:00
d7f968db3a Deprecated FileSystemAdapter::getHomeFolder(). 2025-03-28 11:44:15 +01:00
e35f5a35f8 Some improvements to VectorMap interface. Added moving operator[], contains and [[nodiscard]] to find(). 2025-03-28 11:43:32 +01:00
Patrick Wuttke
91d53805b5 Semi-disabled bitFlagsToInt() again, since it is quite error-prone. 2025-03-27 15:07:45 +01:00
Patrick Wuttke
8bad5e4346 Fixed stars- and endsWithIgnoreCase. 2025-03-27 15:07:17 +01:00
Patrick Wuttke
b007768790 Merge branch 'master' of https://git.mewin.de/mewin/mijin2 2025-03-27 12:18:57 +01:00
Patrick Wuttke
f6776d233d Added startsWithIgnoreCase() and endsWithIgnoreCase() functions. 2025-03-27 12:18:52 +01:00
719505ac05 Removed old LibConf file. 2025-03-18 17:19:38 +01:00
6dcd95b9f3 Added license. 2025-03-18 17:19:38 +01:00
Patrick Wuttke
a64bfde6af Added findIgnoreCase() utility function. 2025-03-18 15:07:55 +01:00
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
103 changed files with 13353 additions and 1045 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
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Patrick Wuttke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

33
LibConf
View File

@@ -1,33 +0,0 @@
Import('env')
mijin_sources = Split("""
source/mijin/async/coroutine.cpp
source/mijin/debug/stacktrace.cpp
source/mijin/debug/symbol_info.cpp
source/mijin/io/process.cpp
source/mijin/io/stream.cpp
source/mijin/util/os.cpp
source/mijin/types/name.cpp
source/mijin/virtual_filesystem/filesystem.cpp
source/mijin/virtual_filesystem/stacked.cpp
""")
lib_libbacktrace = env.Cook('libbacktrace')
lib_mijin = env.UnityStaticLibrary(
target = env['LIB_DIR'] + '/mijin',
source = mijin_sources,
dependencies = [lib_libbacktrace]
)
LIB_CONFIG = {
'CPPPATH': [env.Dir('source')],
'CPPDEFINES': [],
'DEPENDENCIES': [lib_mijin]
}
if env['BUILD_TYPE'] == 'debug':
LIB_CONFIG['CPPDEFINES'].extend(['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1'])
Return('LIB_CONFIG')

60
SModule Normal file
View File

@@ -0,0 +1,60 @@
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/types/path.cpp
source/mijin/virtual_filesystem/filesystem.cpp
source/mijin/virtual_filesystem/memory.cpp
source/mijin/virtual_filesystem/stacked.cpp
""")
with open('dependencies.json', 'r') as f:
dependencies = env.DepsFromJson(json.load(f))
cppdefines = []
if env['BUILD_TYPE'] == 'debug':
cppdefines += ['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1']
# SSL libs
if env.get('MIJIN_ENABLE_OPENSSL'):
cppdefines.append('MIJIN_ENABLE_OPENSSL=1')
mijin_sources.extend(Split("""
source/mijin/net/ssl.cpp
"""))
# CURL libs
if env.get('MIJIN_ENABLE_CURL'):
cppdefines.append('MIJIN_ENABLE_CURL=1')
mijin_sources.extend(Split("""
source/mijin/net/request.cpp
"""))
lib_mijin = env.UnityStaticLibrary(
target = env['LIB_DIR'] + '/mijin',
source = mijin_sources,
dependencies = dependencies,
CPPDEFINES = list(env['CPPDEFINES']) + cppdefines
)
LIB_CONFIG = {
'CPPPATH': [env.Dir('source')],
'CPPDEFINES': cppdefines,
'LIBS': [lib_mijin]
}
Return('LIB_CONFIG')

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

@@ -0,0 +1,50 @@
#pragma once
#if !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)
#define MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED 1
#include "./signal.hpp"
#include "../container/boxed_object.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<template<typename> typename TAllocator, typename... TArgs>
class BaseBoxedSignal : private BoxedObject<BaseSignal<TAllocator, TArgs...>>
{
private:
using base_t = BoxedObject<BaseSignal<TAllocator, TArgs...>>;
public:
using base_t::construct;
using base_t::destroy;
using base_t::moveTo;
MIJIN_BOXED_PROXY_FUNC(connect)
MIJIN_BOXED_PROXY_FUNC(disconnect)
MIJIN_BOXED_PROXY_FUNC(emit)
};
template<typename... TArgs>
using BoxedSignal = BaseBoxedSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)

View File

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

File diff suppressed because it is too large Load Diff

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

@@ -4,12 +4,14 @@
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED) #if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1 #define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
#include <optional>
#include <memory> #include <memory>
#include <optional>
#include <tuple>
#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
{ {
@@ -25,7 +27,7 @@ namespace mijin
// //
// public types // public types
// //
template<typename TValue> template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
class Future; class Future;
// TODO: add support for mutexes and waiting for futures // TODO: add support for mutexes and waiting for futures
@@ -34,21 +36,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>
@@ -56,28 +58,29 @@ struct FutureStorage<void>
}; };
} // namespace impl } // namespace impl
template<typename TValue> template<typename TValue, template<typename> typename TAllocator>
class Future class Future
{ {
private: private:
impl::FutureStorage<TValue> value_; [[no_unique_address]] impl::FutureStorage<TValue> value_;
bool isSet_ = false; bool isSet_ = false;
public: public:
Future() = default; Future() = default;
Future(const Future&) = delete; Future(const Future&) = delete;
Future(Future&&) noexcept = default; Future(Future&&) MIJIN_NOEXCEPT = default;
explicit Future(TAllocator<void> allocator) : sigSet(std::move(allocator)) {}
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 +91,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 +102,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;
@@ -125,16 +128,52 @@ public: // modification
} }
} }
public: // signals public: // signals
Signal<> sigSet; BaseSignal<TAllocator> sigSet;
}; };
template<typename TValue> template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
using FuturePtr = std::shared_ptr<Future<TValue>>; using FuturePtr = std::shared_ptr<Future<TValue, TAllocator>>;
// //
// public functions // public functions
// //
namespace impl
{
template<typename... TResult>
struct MultiFutureHelper
{
template<std::size_t... indices>
static bool allReady(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
{
return (std::get<indices>(futures)->ready() && ...);
}
template<std::size_t... indices>
static std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
{
return std::make_tuple(std::move(std::get<indices>(futures)->get())...);
}
};
}
template<typename T, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
constexpr FuturePtr<T> makeSharedFuture(TAllocator<Future<T>> allocator = {}) MIJIN_NOEXCEPT
{
return std::allocate_shared<Future<T>>(std::move(allocator));
}
template<typename... TResult>
constexpr bool allReady(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
{
return impl::MultiFutureHelper<TResult...>::allReady(futures, std::index_sequence_for<TResult...>());
}
template<typename... TResult>
constexpr std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
{
return impl::MultiFutureHelper<TResult...>::getAll(futures, std::index_sequence_for<TResult...>());
}
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED) #endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)

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,21 +26,76 @@ 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>>;
static constexpr std::size_t BUFFER_SIZE = bufferSize;
private: private:
std::array<TMessage, bufferSize> messages; std::array<TMessage, bufferSize> messages_;
mijin::BitArray<bufferSize, true> messageReady; mijin::BitArray<bufferSize, true> messageReady_;
std::atomic_uint writePos = 0; std::atomic_uint writePos_ = 0;
std::atomic_uint readPos = 0; std::atomic_uint readPos_ = 0;
public: public:
MessageQueue() = default; MessageQueue() = default;
MessageQueue(const MessageQueue&) = delete; MessageQueue(const MessageQueue&) = delete;
MessageQueue(MessageQueue&&) noexcept = delete; MessageQueue(MessageQueue&&) = delete;
explicit MessageQueue(const std::array<TMessage, bufferSize>& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<TMessage>)
: messages_(messages) {}
explicit MessageQueue(std::array<TMessage, bufferSize>&& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TMessage>)
: messages_(std::move(messages)) {}
MessageQueue& operator=(const MessageQueue&) = delete; MessageQueue& operator=(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 +104,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>
@@ -64,22 +123,22 @@ struct TaskMessageQueue
template<typename TMessage, std::size_t bufferSize> template<typename TMessage, std::size_t bufferSize>
bool MessageQueue<TMessage, bufferSize>::tryPushMaybeMove(TMessage& message) bool MessageQueue<TMessage, bufferSize>::tryPushMaybeMove(TMessage& message)
{ {
unsigned oldWritePos = writePos.load(std::memory_order_relaxed); unsigned oldWritePos = writePos_.load(std::memory_order_relaxed);
unsigned newWritePos = 0; unsigned newWritePos = 0;
do do
{ {
newWritePos = (oldWritePos + 1) % bufferSize; newWritePos = (oldWritePos + 1) % bufferSize;
if (newWritePos == readPos) { if (newWritePos == readPos_) {
return false; return false;
} }
} while (!writePos.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed)); } while (!writePos_.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
while (messageReady.get(oldWritePos)) { while (messageReady_.get(oldWritePos)) {
std::this_thread::yield(); // someone is still reading, wait... std::this_thread::yield(); // someone is still reading, wait...
} }
messages[oldWritePos] = std::move(message); messages_[oldWritePos] = std::move(message);
messageReady.set(oldWritePos, true); messageReady_.set(oldWritePos, true);
return true; return true;
} }
@@ -95,22 +154,22 @@ void MessageQueue<TMessage, bufferSize>::push(TMessage message)
template<typename TMessage, std::size_t bufferSize> template<typename TMessage, std::size_t bufferSize>
std::optional<TMessage> MessageQueue<TMessage, bufferSize>::tryPop() std::optional<TMessage> MessageQueue<TMessage, bufferSize>::tryPop()
{ {
unsigned oldReadPos = readPos.load(std::memory_order_relaxed); unsigned oldReadPos = readPos_.load(std::memory_order_relaxed);
unsigned newReadPos = 0; unsigned newReadPos = 0;
do do
{ {
if (oldReadPos == writePos) { if (oldReadPos == writePos_) {
return std::nullopt; return std::nullopt;
} }
newReadPos = (oldReadPos + 1) % bufferSize; newReadPos = (oldReadPos + 1) % bufferSize;
} while (!readPos.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed)); } while (!readPos_.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed));
while (!messageReady.get(oldReadPos)) { while (!messageReady_.get(oldReadPos)) {
std::this_thread::yield(); // no harm in busy-waiting here, should be fast std::this_thread::yield(); // no harm in busy-waiting here, should be fast
}; };
TMessage message = std::move(messages[oldReadPos]); TMessage message = std::move(messages_[oldReadPos]);
messageReady.set(oldReadPos, false); messageReady_.set(oldReadPos, false);
return message; return message;
} }

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,18 +24,21 @@ 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
// //
MIJIN_DEFINE_FLAG(Oneshot); MIJIN_DEFINE_FLAG(Oneshot);
template<typename... TArgs> template<template<typename> typename TAllocator, typename... TArgs>
class Signal class BaseSignal
{ {
public: public:
using handler_t = std::function<void(TArgs...)>; using handler_t = std::function<void(TArgs...)>; // TODO: write a custom function wrapper with allocator support
using token_t = std::uint32_t; using token_t = signal_token_t;
private: private:
struct RegisteredHandler struct RegisteredHandler
{ {
@@ -42,34 +47,41 @@ private:
token_t token; token_t token;
Oneshot oneshot = Oneshot::NO; Oneshot oneshot = Oneshot::NO;
}; };
using handler_vector_t = std::vector<RegisteredHandler>; using handler_vector_t = std::vector<RegisteredHandler, TAllocator<RegisteredHandler>>;
private: private:
handler_vector_t handlers_; handler_vector_t handlers_;
token_t nextToken = 1; token_t nextToken = 1;
std::mutex handlersMutex_; std::mutex handlersMutex_;
public: public:
Signal() = default; explicit BaseSignal(TAllocator<void> allocator = {}) : handlers_(TAllocator<RegisteredHandler>(std::move(allocator))) {}
Signal(const Signal&) = delete; BaseSignal(const BaseSignal&) = delete;
Signal(Signal&&) noexcept = default; BaseSignal(BaseSignal&&) MIJIN_NOEXCEPT = default;
public: public:
Signal& operator=(const Signal&) = delete; BaseSignal& operator=(const BaseSignal&) = delete;
Signal& operator=(Signal&&) noexcept = default; BaseSignal& operator=(BaseSignal&&) 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; template<typename TObject, typename TWeak = void>
inline void emit(TArgs&&... args) noexcept; inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
inline void disconnect(token_t token) MIJIN_NOEXCEPT;
template<typename... TArgs2>
inline void emit(TArgs2&&... args) MIJIN_NOEXCEPT;
}; };
template<typename... TArgs>
using Signal = BaseSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
// //
// public functions // public functions
// //
template<typename... TArgs> template<template<typename> typename TAllocator, 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 BaseSignal<TAllocator, 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_);
@@ -84,9 +96,9 @@ inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::we
return nextToken++; return nextToken++;
} }
template<typename... TArgs> template<template<typename> typename TAllocator, 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 BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
{ {
std::lock_guard lock(handlersMutex_); std::lock_guard lock(handlersMutex_);
@@ -104,8 +116,28 @@ inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)
return nextToken++; return nextToken++;
} }
template<typename... TArgs> template<template<typename> typename TAllocator, typename... TArgs>
inline void Signal<TArgs...>::disconnect(token_t token) noexcept template<typename TObject, typename TWeak>
inline auto BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
{
std::lock_guard lock(handlersMutex_);
auto callable = [object = &object, handler](TArgs... args)
{
std::invoke(handler, object, std::forward<TArgs>(args)...);
};
handlers_.push_back({
.callable = std::move(callable),
.referenced = std::move(referenced),
.token = nextToken,
.oneshot = oneshot
});
return nextToken++;
}
template<template<typename> typename TAllocator, typename... TArgs>
inline void BaseSignal<TAllocator, TArgs...>::disconnect(token_t token) MIJIN_NOEXCEPT
{ {
std::lock_guard lock(handlersMutex_); std::lock_guard lock(handlersMutex_);
@@ -116,8 +148,9 @@ inline void Signal<TArgs...>::disconnect(token_t token) noexcept
handlers_.erase(it, handlers_.end()); handlers_.erase(it, handlers_.end());
} }
template<typename... TArgs> template<template<typename> typename TAllocator, typename... TArgs>
inline void Signal<TArgs...>::emit(TArgs&&... args) noexcept template<typename... TArgs2>
inline void BaseSignal<TAllocator, TArgs...>::emit(TArgs2&&... args) MIJIN_NOEXCEPT
{ {
std::lock_guard lock(handlersMutex_); std::lock_guard lock(handlersMutex_);
@@ -132,7 +165,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

@@ -16,8 +16,26 @@ namespace mijin
// //
#if !defined(MIJIN_BOXED_OBJECT_DEBUG) #if !defined(MIJIN_BOXED_OBJECT_DEBUG)
#if defined(MIJIN_DEBUG)
#define MIJIN_BOXED_OBJECT_DEBUG MIJIN_DEBUG #define MIJIN_BOXED_OBJECT_DEBUG MIJIN_DEBUG
#else
#define MIJIN_BOXED_OBJECT_DEBUG 0
#endif #endif
#endif // !defined(MIJIN_BOXED_OBJECT_DEBUG)
#define MIJIN_BOXED_PROXY_FUNC(funcname) \
template<typename... TFuncArgs> \
decltype(auto) funcname(TFuncArgs&&... args) \
{ \
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
}
#define MIJIN_BOXED_PROXY_FUNC_CONST(funcname) \
template<typename... TFuncArgs> \
decltype(auto) funcname(TFuncArgs&&... args) const \
{ \
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
}
// //
// public constants // public constants
@@ -39,8 +57,8 @@ private:
bool constructed = false; bool constructed = false;
#endif #endif
public: public:
BoxedObject() noexcept : placeholder_() {}; constexpr BoxedObject() MIJIN_NOEXCEPT : placeholder_() {};
explicit BoxedObject(T object) explicit constexpr BoxedObject(T object) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
#if MIJIN_BOXED_OBJECT_DEBUG #if MIJIN_BOXED_OBJECT_DEBUG
: constructed(true) : constructed(true)
#endif #endif
@@ -50,69 +68,69 @@ public:
BoxedObject(const BoxedObject&) = delete; BoxedObject(const BoxedObject&) = delete;
BoxedObject(BoxedObject&&) = delete; BoxedObject(BoxedObject&&) = delete;
#if MIJIN_BOXED_OBJECT_DEBUG constexpr ~BoxedObject() noexcept
~BoxedObject()
{ {
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!") #if MIJIN_BOXED_OBJECT_DEBUG
} MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!");
#endif #endif
}
BoxedObject& operator=(const BoxedObject&) = delete; BoxedObject& operator=(const BoxedObject&) = delete;
BoxedObject& operator=(BoxedObject&&) = delete; BoxedObject& operator=(BoxedObject&&) = delete;
T& operator*() noexcept { return get(); } constexpr T& operator*() noexcept { return get(); }
const T& operator*() const noexcept { return get(); } constexpr const T& operator*() const noexcept { return get(); }
T* operator->() noexcept { return &get(); } constexpr T* operator->() noexcept { return &get(); }
const T* operator->() const noexcept { return &get(); } constexpr const T* operator->() const noexcept { return &get(); }
template<typename... TArgs> template<typename... TArgs>
void construct(TArgs&&... args) constexpr void construct(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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)...);
} }
void destroy() constexpr void destroy() MIJIN_NOEXCEPT
{ {
#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_);
} }
void copyTo(BoxedObject& other) const constexpr void copyTo(BoxedObject& other) const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
{ {
#if MIJIN_BOXED_OBJECT_DEBUG #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_);
} }
void moveTo(BoxedObject& other) constexpr void moveTo(BoxedObject& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
{ {
#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();
} }
[[nodiscard]] T& get() [[nodiscard]] constexpr T& get() MIJIN_NOEXCEPT
{ {
#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_;
} }
[[nodiscard]] const T& get() const [[nodiscard]] constexpr const T& get() const MIJIN_NOEXCEPT
{ {
#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_;
} }
@@ -131,38 +149,38 @@ private:
BoxedObject<T>* end_ = nullptr; BoxedObject<T>* end_ = nullptr;
#endif #endif
public: public:
BoxedObjectIterator() = default; BoxedObjectIterator() noexcept = default;
explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* end) explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* end) MIJIN_NOEXCEPT
: box_(box) : box_(box)
#if MIJIN_CHECKED_ITERATORS #if MIJIN_CHECKED_ITERATORS
, start_(start), end_(end) , start_(start), end_(end)
#endif #endif
{} {}
BoxedObjectIterator(const BoxedObjectIterator&) = default; constexpr BoxedObjectIterator(const BoxedObjectIterator&) noexcept = default;
BoxedObjectIterator(BoxedObjectIterator&&) noexcept = default; constexpr BoxedObjectIterator(BoxedObjectIterator&&) noexcept = default;
BoxedObjectIterator& operator=(const BoxedObjectIterator&) = default; constexpr BoxedObjectIterator& operator=(const BoxedObjectIterator&) noexcept = default;
BoxedObjectIterator& operator=(BoxedObjectIterator&&) noexcept = default; constexpr BoxedObjectIterator& operator=(BoxedObjectIterator&&) noexcept = default;
BoxedObjectIterator& operator+=(difference_type diff); constexpr BoxedObjectIterator& operator+=(difference_type diff) MIJIN_NOEXCEPT;
BoxedObjectIterator& operator-=(difference_type diff) { return (*this += -diff); } constexpr BoxedObjectIterator& operator-=(difference_type diff) MIJIN_NOEXCEPT { return (*this += -diff); }
constexpr auto operator<=>(const BoxedObjectIterator& other) const noexcept = default; constexpr auto operator<=>(const BoxedObjectIterator& other) const noexcept = default;
[[nodiscard]] T& operator*() const; [[nodiscard]] constexpr T& operator*() const MIJIN_NOEXCEPT;
[[nodiscard]] T* operator->() const; [[nodiscard]] constexpr T* operator->() const MIJIN_NOEXCEPT;
BoxedObjectIterator& operator++(); constexpr BoxedObjectIterator& operator++() MIJIN_NOEXCEPT;
BoxedObjectIterator operator++(int); constexpr BoxedObjectIterator operator++(int) MIJIN_NOEXCEPT;
BoxedObjectIterator& operator--(); constexpr BoxedObjectIterator& operator--() MIJIN_NOEXCEPT;
BoxedObjectIterator operator--(int); constexpr BoxedObjectIterator operator--(int) MIJIN_NOEXCEPT;
[[nodiscard]] difference_type operator-(const BoxedObjectIterator& other) const { return box_ - other.box_; } [[nodiscard]] constexpr difference_type operator-(const BoxedObjectIterator& other) const MIJIN_NOEXCEPT { return box_ - other.box_; }
[[nodiscard]] BoxedObjectIterator operator+(difference_type diff) const; [[nodiscard]] constexpr BoxedObjectIterator operator+(difference_type diff) const MIJIN_NOEXCEPT;
[[nodiscard]] BoxedObjectIterator operator-(difference_type diff) const { return (*this + -diff); } [[nodiscard]] constexpr BoxedObjectIterator operator-(difference_type diff) const MIJIN_NOEXCEPT { return (*this + -diff); }
[[nodiscard]] T& operator[](difference_type diff) const { return *(*this + diff); } [[nodiscard]] T& operator[](difference_type diff) const MIJIN_NOEXCEPT { return *(*this + diff); }
}; };
template<typename T> template<typename T>
inline BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) { constexpr BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) MIJIN_NOEXCEPT {
return iter + diff; return iter + diff;
} }
static_assert(std::random_access_iterator<BoxedObjectIterator<int>>); static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
@@ -172,7 +190,7 @@ static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
// //
template<typename T> template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff) constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff) MIJIN_NOEXCEPT
{ {
#if MIJIN_CHECKED_ITERATORS #if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(diff <= (end_ - box_) && diff >= (box_ - start_), "BoxedObjectIterator::operator+=(): Attempt to iterate out of range."); MIJIN_ASSERT(diff <= (end_ - box_) && diff >= (box_ - start_), "BoxedObjectIterator::operator+=(): Attempt to iterate out of range.");
@@ -182,7 +200,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff)
} }
template<typename T> template<typename T>
T& BoxedObjectIterator<T>::operator*() const constexpr T& BoxedObjectIterator<T>::operator*() const MIJIN_NOEXCEPT
{ {
#if MIJIN_CHECKED_ITERATORS #if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ >= start_ && box_ < end_, "BoxedObjectIterator::operator->(): Attempt to dereference out-of-range iterator."); MIJIN_ASSERT(box_ >= start_ && box_ < end_, "BoxedObjectIterator::operator->(): Attempt to dereference out-of-range iterator.");
@@ -191,7 +209,7 @@ T& BoxedObjectIterator<T>::operator*() const
} }
template<typename T> template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++() constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++() MIJIN_NOEXCEPT
{ {
#if MIJIN_CHECKED_ITERATORS #if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end."); MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end.");
@@ -200,7 +218,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++()
} }
template<typename T> template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int) constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int) MIJIN_NOEXCEPT
{ {
#if MIJIN_CHECKED_ITERATORS #if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(int): Attempt to iterator past the end."); MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(int): Attempt to iterator past the end.");
@@ -211,7 +229,7 @@ BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int)
} }
template<typename T> template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--() constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--() MIJIN_NOEXCEPT
{ {
#if MIJIN_CHECKED_ITERATORS #if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start."); MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start.");
@@ -220,7 +238,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--()
} }
template<typename T> template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int) constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int) MIJIN_NOEXCEPT
{ {
#if MIJIN_CHECKED_ITERATORS #if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(int): Attempt to iterator past start."); MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(int): Attempt to iterator past start.");
@@ -231,7 +249,7 @@ BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int)
} }
template<typename T> template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const MIJIN_NOEXCEPT
{ {
BoxedObjectIterator copy(*this); BoxedObjectIterator copy(*this);
copy += diff; copy += diff;

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,196 @@
#pragma once
#if !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)
#define MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED 1
#include <bit>
#include <span>
#include <string_view>
#include "../debug/assert.hpp"
#include "../internal/common.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<typename T>
concept MemoryViewable = requires(const T& object)
{
{ object.data() } -> std::convertible_to<const void*>;
{ object.byteSize() } -> std::convertible_to<std::size_t>;
};
template<typename T>
concept RWMemoryViewable = MemoryViewable<T> && requires(T& object)
{
{ object.data() } -> std::convertible_to<void*>;
};
template<typename TConcrete>
class MixinMemoryView
{
public:
static constexpr bool WRITABLE = requires(TConcrete& object) { { object.data() } -> std::convertible_to<void*>; };
template<typename T>
[[nodiscard]]
auto makeSpan();
template<typename T>
[[nodiscard]]
auto makeSpan() const;
template<typename TChar = char, typename TTraits = std::char_traits<TChar>>
[[nodiscard]]
std::basic_string_view<TChar, TTraits> makeStringView() const ;
template<typename T>
[[nodiscard]]
auto& dataAt(std::size_t offset) MIJIN_NOEXCEPT;
template<typename T>
[[nodiscard]]
auto& dataAt(std::size_t offset) const MIJIN_NOEXCEPT;
[[nodiscard]]
auto bytes() MIJIN_NOEXCEPT
{
using return_t = mijin::copy_const_t<std::remove_pointer_t<decltype(static_cast<TConcrete*>(this)->data())>, std::byte>;
return static_cast<return_t*>(static_cast<TConcrete*>(this)->data());
}
[[nodiscard]]
auto bytes() const MIJIN_NOEXCEPT
{
using return_t = mijin::copy_const_t<std::remove_pointer_t<decltype(static_cast<const TConcrete*>(this)->data())>, std::byte>;
return static_cast<return_t*>(static_cast<const TConcrete*>(this)->data());
}
private:
std::size_t byteSizeImpl() const MIJIN_NOEXCEPT
{
return static_cast<const TConcrete*>(this)->byteSize();
}
};
class MemoryView : public MixinMemoryView<MemoryView>
{
public:
using size_type = std::size_t;
private:
void* data_ = nullptr;
std::size_t byteSize_ = 0;
public:
MemoryView() noexcept = default;
MemoryView(const MemoryView&) = default;
MemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {}
template<typename T> requires(!std::is_const_v<T>)
MemoryView(std::span<T> span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {}
template<RWMemoryViewable T>
MemoryView(T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {}
MemoryView& operator=(const MemoryView&) = default;
[[nodiscard]]
void* data() const MIJIN_NOEXCEPT { return data_; }
[[nodiscard]]
size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; }
};
class ConstMemoryView : public MixinMemoryView<ConstMemoryView>
{
public:
using size_type = std::size_t;
private:
const void* data_;
std::size_t byteSize_;
public:
ConstMemoryView() noexcept = default;
ConstMemoryView(const ConstMemoryView&) = default;
ConstMemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {}
template<typename T>
ConstMemoryView(std::span<T> span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {}
template<MemoryViewable T>
ConstMemoryView(const T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {}
ConstMemoryView& operator=(const ConstMemoryView& other) MIJIN_NOEXCEPT = default;
[[nodiscard]]
const void* data() const MIJIN_NOEXCEPT { return data_; }
[[nodiscard]]
size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; }
};
//
// public functions
//
template<typename TConcrete>
template<typename T>
auto MixinMemoryView<TConcrete>::makeSpan()
{
MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return std::span<return_t>{
std::bit_cast<return_t*>(bytes()),
std::bit_cast<return_t*>(bytes() + byteSizeImpl())
};
}
template<typename TConcrete>
template<typename T>
auto MixinMemoryView<TConcrete>::makeSpan() const
{
MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return std::span<return_t>{
std::bit_cast<return_t*>(bytes()),
std::bit_cast<return_t*>(bytes() + byteSizeImpl())
};
}
template<typename TConcrete>
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> MixinMemoryView<TConcrete>::makeStringView() const
{
MIJIN_ASSERT(byteSizeImpl() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type.");
return {std::bit_cast<const TChar*>(bytes()), byteSizeImpl() / sizeof(TChar)};
}
template<typename TConcrete>
template<typename T>
auto& MixinMemoryView<TConcrete>::dataAt(std::size_t offset) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return *std::bit_cast<return_t*>(bytes().data() + offset);
}
template<typename TConcrete>
template<typename T>
auto& MixinMemoryView<TConcrete>::dataAt(std::size_t offset) const MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return *std::bit_cast<return_t*>(bytes().data() + offset);
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)

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,10 +4,15 @@
#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 "./memory_view.hpp"
#include "../debug/assert.hpp" #include "../debug/assert.hpp"
#include "../internal/common.hpp"
namespace mijin namespace mijin
{ {
@@ -24,33 +29,45 @@ namespace mijin
// public types // public types
// //
template<typename T> template<typename T, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
class BufferView; class BufferView;
class TypelessBuffer template<template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
class BaseTypelessBuffer : public MixinMemoryView<BaseTypelessBuffer<TAllocator>>
{ {
public: public:
using size_type = std::size_t; using size_type = std::size_t;
private: private:
std::vector<std::byte> bytes_; std::vector<std::byte, TAllocator<std::byte>> bytes_;
public: public:
[[nodiscard]] void* data() { return bytes_.data(); } BaseTypelessBuffer() noexcept = default;
[[nodiscard]] const void* data() const { return bytes_.data(); } BaseTypelessBuffer(const BaseTypelessBuffer&) = default;
[[nodiscard]] size_type byteSize() const { return bytes_.size(); } BaseTypelessBuffer(BaseTypelessBuffer&&) = default;
explicit BaseTypelessBuffer(TAllocator<std::byte> allocator) : bytes_(std::move(allocator)) {}
BaseTypelessBuffer& operator=(const BaseTypelessBuffer&) = default;
BaseTypelessBuffer& operator=(BaseTypelessBuffer&&) = default;
auto operator<=>(const BaseTypelessBuffer&) const noexcept = default;
[[nodiscard]] void* data() noexcept { return bytes_.data(); }
[[nodiscard]] const void* data() const noexcept { return bytes_.data(); }
[[nodiscard]] size_type byteSize() const noexcept { return bytes_.size(); }
[[nodiscard]] size_type byteCapacity() const noexcept { return bytes_.capacity(); }
[[nodiscard]] bool empty() const noexcept { return bytes_.empty(); }
void resize(size_type numBytes) { bytes_.resize(numBytes); } void resize(size_type numBytes) { bytes_.resize(numBytes); }
void reserve(size_type numBytes) { bytes_.reserve(numBytes); } void reserve(size_type numBytes) { bytes_.reserve(numBytes); }
template<typename T> template<typename T>
[[nodiscard]] BufferView<T> makeBufferView() { return BufferView<T>(this); } [[nodiscard]] BufferView<T> makeBufferView() { return BufferView<T>(this); }
template<typename T> template<typename T>
[[nodiscard]] std::span<T> makeSpan(); void append(std::span<const T> data);
template<typename T>
[[nodiscard]] std::span<const T> makeSpan() const;
}; };
template<typename T> using TypelessBuffer = BaseTypelessBuffer<>;
template<typename T, template<typename> typename TAllocator>
class BufferView class BufferView
{ {
public: public:
@@ -65,10 +82,10 @@ public:
using iterator = T*; using iterator = T*;
using const_iterator = const T*; using const_iterator = const T*;
private: private:
class TypelessBuffer* buffer_ = nullptr; class BaseTypelessBuffer<TAllocator>* buffer_ = nullptr;
public: public:
BufferView() = default; BufferView() = default;
explicit BufferView(class TypelessBuffer* buffer) : buffer_(buffer) {} explicit BufferView(class BaseTypelessBuffer<TAllocator>* buffer) : buffer_(buffer) {}
BufferView(const BufferView&) = default; BufferView(const BufferView&) = default;
BufferView& operator=(const BufferView&) = default; BufferView& operator=(const BufferView&) = default;
@@ -82,6 +99,12 @@ public:
MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer."); 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; }
@@ -106,24 +129,12 @@ public:
// public functions // public functions
// //
template<template<typename> typename TAllocator>
template<typename T> template<typename T>
std::span<T> TypelessBuffer::makeSpan() void BaseTypelessBuffer<TAllocator>::append(std::span<const T> data)
{ {
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type."); bytes_.resize(bytes_.size() + data.size_bytes());
return { std::memcpy(bytes_.data() + bytes_.size() - data.size_bytes(), data.data(), data.size_bytes());
std::bit_cast<T*>(bytes_.data()),
std::bit_cast<T*>(bytes_.data() + bytes_.size())
};
}
template<typename T>
std::span<const T> TypelessBuffer::makeSpan() const
{
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
return {
std::bit_cast<const T*>(bytes_.data()),
std::bit_cast<const T*>(bytes_.data() + bytes_.size())
};
} }
} // namespace mijin } // namespace mijin

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,300 @@
#pragma once
#if !defined(MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED)
#define MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED 1
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "./boxed_object.hpp"
#include "./optional.hpp"
#include "../internal/common.hpp"
namespace mijin
{
template<typename TKey, typename TValue>
struct VectorMapIterator
{
public:
using difference_type = std::ptrdiff_t;
private:
Optional<std::pair<TKey&, TValue&>> ref_;
public:
VectorMapIterator(TKey* key, TValue* value)
{
ref_.emplace(std::tie(*key, *value));
}
VectorMapIterator(const VectorMapIterator& other) MIJIN_NOEXCEPT : VectorMapIterator(&other.ref_->first, &other.ref_->second) {}
~VectorMapIterator() noexcept
{
ref_.reset();
}
VectorMapIterator& operator=(const VectorMapIterator& other) MIJIN_NOEXCEPT
{
if (this != &other)
{
ref_.reset();
ref_.emplace(std::tie(other.ref_->first, other.ref_->second));
}
return *this;
}
bool operator==(const VectorMapIterator& other) const MIJIN_NOEXCEPT { return &ref_->first == &other.ref_->first; }
bool operator!=(const VectorMapIterator& other) const MIJIN_NOEXCEPT { return &ref_->first != &other.ref_->first; }
std::pair<TKey&, TValue&> operator*() const MIJIN_NOEXCEPT
{
return *ref_;
}
const std::pair<TKey&, TValue&>* operator->() const MIJIN_NOEXCEPT
{
return &*ref_;
}
VectorMapIterator& operator++() MIJIN_NOEXCEPT
{
TKey* oldKey = &ref_->first;
TValue* oldValue = &ref_->second;
ref_.reset();
ref_.emplace(std::tie(*(++oldKey), *(++oldValue)));
return *this;
}
VectorMapIterator operator++(int) const MIJIN_NOEXCEPT
{
VectorMapIterator copy(*this);
++(*this);
return copy;
}
VectorMapIterator& operator--() MIJIN_NOEXCEPT
{
TKey* oldKey = &ref_->first;
TValue* oldValue = &ref_->second;
ref_.reset();
ref_.emplace(std::tie(*(--oldKey), *(--oldValue)));
return *this;
}
VectorMapIterator operator--(int) const MIJIN_NOEXCEPT
{
VectorMapIterator copy(*this);
--(*this);
return copy;
}
VectorMapIterator operator+(difference_type diff) const MIJIN_NOEXCEPT
{
return VectorMapIterator(&ref_->first + diff, &ref_->second + diff);
}
VectorMapIterator operator-(difference_type diff) const MIJIN_NOEXCEPT
{
return VectorMapIterator(&ref_->first - diff, &ref_->second - diff);
}
difference_type operator-(const VectorMapIterator& other) const MIJIN_NOEXCEPT
{
return &ref_->first - &other.ref_->first;
}
};
template<typename TKey, typename TValue, typename TKeyAllocator = MIJIN_DEFAULT_ALLOCATOR<TKey>, typename TValueAllocator = MIJIN_DEFAULT_ALLOCATOR<TValue>>
class VectorMap
{
public:
using key_type = TKey;
using mapped_type = TValue;
using value_type = std::pair<const TKey, TValue>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = VectorMapIterator<const TKey, TValue>;
using const_iterator = VectorMapIterator<const TKey, const TValue>;
private:
std::vector<TKey, TKeyAllocator> keys_;
std::vector<TValue, TValueAllocator> values_;
public:
explicit VectorMap(TKeyAllocator keyAllocator = {})
MIJIN_NOEXCEPT_IF((std::is_nothrow_move_constructible_v<TKeyAllocator> && std::is_nothrow_constructible_v<TValueAllocator, const TKeyAllocator&>))
: keys_(std::move(keyAllocator)), values_(TValueAllocator(keys_.get_allocator())) {}
VectorMap(TKeyAllocator keyAllocator, TValueAllocator valueAllocator)
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TKeyAllocator> && std::is_nothrow_move_constructible_v<TValueAllocator>)
: keys_(std::move(keyAllocator)), values_(std::move(valueAllocator)) {}
VectorMap(const VectorMap&) = default;
VectorMap(VectorMap&&) = default;
VectorMap& operator=(const VectorMap&) = default;
VectorMap& operator=(VectorMap&&) = default;
auto operator<=>(const VectorMap& other) const noexcept = default;
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);
}
TValue& operator[](TKey&& key)
{
auto it = find(key);
if (it != end())
{
return it->second;
}
return emplace(std::move(key), TValue()).first->second;
}
[[nodiscard]]
iterator begin() MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
[[nodiscard]]
const_iterator begin() const MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
[[nodiscard]]
const_iterator cbegin() const MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
[[nodiscard]]
iterator end() MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
[[nodiscard]]
const_iterator end() const MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
[[nodiscard]]
const_iterator cend() const MIJIN_NOEXCEPT { return {keys_.data() + keys_.size(), values_.data() + values_.size()}; }
[[nodiscard]]
TValue& at(const TKey& key)
{
auto it = find(key);
if (it == end())
{
throw std::out_of_range("key not found in map");
}
return it->second;
}
[[nodiscard]]
const TValue& at(const TKey& key) const
{
auto it = find(key);
if (it == end())
{
throw std::out_of_range("key not found in map");
}
return it->second;
}
[[nodiscard]]
bool empty() const MIJIN_NOEXCEPT { return keys_.empty(); }
[[nodiscard]]
size_type size() const MIJIN_NOEXCEPT { return keys_.size(); }
[[nodiscard]]
size_type max_size() const MIJIN_NOEXCEPT { return std::min(keys_.max_size(), values_.max_size()); }
void reserve(std::size_t size)
{
keys_.reserve(size);
values_.reserve(size);
}
void clear()
{
keys_.clear();
values_.clear();
}
template<typename... TArgs>
std::pair<iterator, bool> emplace(TArgs&&... args)
{
std::pair<TKey, TValue> asPair(std::forward<TArgs>(args)...);
auto it = find(asPair.first);
if (it != end())
{
return std::make_pair(std::move(it), false);
}
keys_.push_back(std::move(asPair.first));
values_.push_back(std::move(asPair.second));
return std::make_pair(iterator(&keys_.back(), &values_.back()), true);
}
iterator erase(iterator pos) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &pos->first - &keys_[0];
return eraseImpl(idx);
}
iterator erase(const_iterator pos) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &pos->first - &keys_[0];
return eraseImpl(idx);
}
iterator erase(iterator first, iterator last) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &first->first - &keys_[0];
const std::ptrdiff_t count = last - first;
return eraseImpl(idx, count);
}
iterator erase(const_iterator first, const_iterator last) MIJIN_NOEXCEPT
{
const std::ptrdiff_t idx = &first->first - &keys_[0];
const std::ptrdiff_t count = last - first;
return eraseImpl(idx, count);
}
template<std::equality_comparable_with<TKey> T>
[[nodiscard]]
iterator find(const T& keyValue) MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{
if (keys_[idx] == keyValue)
{
return iterator(&keys_[idx], &values_[idx]);
}
}
return end();
}
template<std::equality_comparable_with<TKey> T>
[[nodiscard]]
const_iterator find(const T& keyValue) const MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{
if (keys_[idx] == keyValue)
{
return const_iterator(&keys_[idx], &values_[idx]);
}
}
return end();
}
template<std::equality_comparable_with<TKey> T>
[[nodiscard]]
bool contains(const T& keyValue) const MIJIN_NOEXCEPT
{
return std::ranges::contains(keys_, keyValue);
}
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,34 +8,49 @@
#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__
#else // _WIN32 #else // _WIN32
#include <csignal> #include <unistd.h>
#define MIJIN_TRAP() (void) std::raise(SIGTRAP) #define MIJIN_TRAP() \
#define MIJIN_FUNC() "" // TODO: __PRETTY_FUNCTION__ is not working for some reason -.- { \
const pid_t tid = gettid(); \
const pid_t pid = getpid(); \
__asm__ __volatile__ \
( \
"syscall" \
: \
: "D"(pid), "S"(tid), "d"(5), "a"(0xea) \
: "rcx", "r11", "memory" \
); \
}
#endif // !_WIN32 #endif // !_WIN32
namespace mijin namespace mijin
{ {
// //
// public defines // public defines
// //
#if MIJIN_DEBUG #if MIJIN_DEBUG
#define MIJIN_RAISE_ERROR(msg, source_loc) \ #define MIJIN_RAISE_ERROR(msg, source_loc) \
switch (mijin::handleError(msg, source_loc)) \ switch (mijin::handleError(msg, source_loc)) \
{ \ { \
case mijin::ErrorHandling::CONTINUE: \ case mijin::ErrorHandling::CONTINUE: \
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,41 +63,44 @@ 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(...) ((void)0)
#define MIJIN_FATAL(...) std::abort() #define MIJIN_FATAL(...) std::abort()
#define MIJIN_ASSERT(...) #define MIJIN_ASSERT(...) ((void)0)
#define MIJIN_ASSERT_FATAL(...) #define MIJIN_ASSERT_FATAL(...) ((void)0)
#endif // !MIJIN_DEBUG #endif // !MIJIN_DEBUG
// //
@@ -93,6 +111,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 +130,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 +180,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 +196,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,29 @@
#include "./stacktrace.hpp" #include "./stacktrace.hpp"
#include <cstdint>
#include <optional> #include <optional>
#include <string> #include <string>
#include <backtrace.h> #include "../detect.hpp"
#include "../util/string.hpp"
#if MIJIN_TARGET_OS != MIJIN_OS_WINDOWS && (MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC)
#define MIJIN_USE_LIBBACKTRACE 1
#else
#define MIJIN_USE_LIBBACKTRACE 0
#endif
#if MIJIN_USE_LIBBACKTRACE
#include <backtrace.h>
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
#include <array>
#include <cstddef>
#include <mutex>
#include <Windows.h>
#include <DbgHelp.h>
#include "../util/winundef.hpp"
#pragma comment(lib, "dbghelp")
#endif
namespace mijin namespace mijin
{ {
@@ -22,25 +41,37 @@ namespace
// internal types // internal types
// //
#if MIJIN_USE_LIBBACKTRACE
struct BacktraceData struct BacktraceData
{ {
std::optional<std::string> error; std::optional<std::string> error;
std::vector<Stackframe> stackframes; std::vector<Stackframe> stackframes;
}; };
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
HANDLE gProcessHandle = nullptr;
#endif
// //
// internal variables // internal variables
// //
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
std::mutex gDbgHelpMutex;
bool gDbgHelpInitCalled = false;
#endif
// //
// 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 +85,60 @@ 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;
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
void cleanupDbgHelp() MIJIN_NOEXCEPT
{
if (!SymCleanup(gProcessHandle))
{
[[maybe_unused]] const DWORD error = GetLastError();
MIJIN_ERROR("Error cleaning up DbgHelp.");
}
}
[[nodiscard]]
bool initDbgHelp() MIJIN_NOEXCEPT
{
if (gDbgHelpInitCalled)
{
return gProcessHandle != nullptr; // if init was successful, process handle is not null
}
gDbgHelpInitCalled = true;
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
HANDLE hCurrentProcess = GetCurrentProcess();
HANDLE hCopy = nullptr;
if (!DuplicateHandle(hCurrentProcess, hCurrentProcess, hCurrentProcess, &hCopy, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
[[maybe_unused]] const DWORD error = GetLastError();
MIJIN_ERROR("Error duplicating process handle.");
return false;
}
if (!SymInitialize(hCopy, nullptr, true))
{
[[maybe_unused]] const DWORD error = GetLastError();
MIJIN_ERROR("Error initializing DbHelp.");
return false;
}
const int result = std::atexit(&cleanupDbgHelp);
MIJIN_ASSERT(result == 0, "Error registering DbgHelp cleanup handler.");
// only copy in the end so we can still figure out if initialization was successful
gProcessHandle = hCopy;
return true;
}
#endif // MIJIN_USE_LIBBACKTRACE
} // namespace } // 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 +159,126 @@ Result<Stacktrace> captureStacktrace(unsigned skipFrames) noexcept
} }
return Stacktrace(std::move(btData.stackframes)); return Stacktrace(std::move(btData.stackframes));
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
if (!initDbgHelp())
{
return ResultError("error initializing DbgHelp");
}
const HANDLE hThread = GetCurrentThread();
CONTEXT context;
RtlCaptureContext(&context);
STACKFRAME64 stackFrame = {
.AddrPC = {
.Offset = context.Rip,
.Mode = AddrModeFlat
},
.AddrFrame = {
.Offset = context.Rbp,
.Mode = AddrModeFlat
},
.AddrStack = {
.Offset = context.Rsp,
.Mode = AddrModeFlat
}
};
++skipFrames; // always skip the first frame (the current function)
// for symbol info
DWORD64 displacement64 = 0;
static constexpr std::size_t SYMBOL_BUFFER_SIZE = sizeof(SYMBOL_INFO) + (MAX_SYM_NAME * sizeof(char));
std::array<std::byte, SYMBOL_BUFFER_SIZE> symbolBuffer alignas(SYMBOL_INFO);
SYMBOL_INFO& symbolInfo = *std::bit_cast<SYMBOL_INFO*>(symbolBuffer.data());
symbolInfo.SizeOfStruct = sizeof(SYMBOL_BUFFER_SIZE);
symbolInfo.MaxNameLen = MAX_SYM_NAME;
// for file and line info
DWORD displacement = 0;
IMAGEHLP_LINE64 line64;
line64.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
std::vector<Stackframe> stackframes;
while (StackWalk64(
/* MachineType = */ IMAGE_FILE_MACHINE_AMD64,
/* hProcess = */ gProcessHandle,
/* hThread = */ hThread,
/* StackFrame = */ &stackFrame,
/* ContextRecord = */ &context,
/* ReadMemoryRoutine = */ nullptr,
/* FunctionTableAccessRoutine = */ &SymFunctionTableAccess64,
/* GetModuleBaseRoutine = */ &SymGetModuleBase64,
/* TranslateAddress = */ nullptr
))
{
if (skipFrames > 0)
{
--skipFrames;
continue;
}
Stackframe& frame = stackframes.emplace_back();
const DWORD64 baseAddress = SymGetModuleBase64(gProcessHandle, stackFrame.AddrPC.Offset);
const DWORD64 relativeAddress = stackFrame.AddrPC.Offset - baseAddress;
frame.address = std::bit_cast<void*>(relativeAddress);
if (SymFromAddr(gProcessHandle, stackFrame.AddrPC.Offset, &displacement64, &symbolInfo))
{
frame.function = symbolInfo.Name;
}
if (SymGetLineFromAddr64(gProcessHandle, stackFrame.AddrPC.Offset, &displacement, &line64))
{
frame.filename = line64.FileName;
frame.lineNumber = static_cast<int>(line64.LineNumber);
}
}
return Stacktrace(std::move(stackframes));
#else // MIJIN_USE_LIBBACKTRACE || (MIJIN_TARGET_OS == MIJIN_OS_WINDOWS)
(void) skipFrames;
return ResultError("not implemented");
#endif // MIJIN_USE_LIBBACKTRACE
} }
const Optional<Stacktrace>& getExceptionStacktrace() MIJIN_NOEXCEPT
{
return gCurrentExceptionStackTrace;
}
} // namespace mijin } // 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

@@ -5,9 +5,14 @@
#define MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED 1 #define MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED 1
#include <cmath> #include <cmath>
#include <format>
#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 +47,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 +73,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 +88,118 @@ TStream& operator<<(TStream& stream, const Stacktrace& stacktrace)
} // namespace mijin } // namespace mijin
template<typename TChar>
struct std::formatter<mijin::Stackframe, TChar>
{
using char_t = TChar;
template<typename TContext>
constexpr TContext::iterator parse(TContext& ctx)
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != MIJIN_SMART_QUOTE(char_t, '}'))
{
throw std::format_error("invalid format");
}
return it;
}
template<typename TContext>
TContext::iterator format(const mijin::Stackframe& stackframe, TContext& ctx) const
{
auto it = ctx.out();
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "[{}] {}:{} in {}"), stackframe.address, stackframe.filename,
stackframe.lineNumber, mijin::demangleCPPIdentifier(stackframe.function.c_str()));
return it;
}
};
template<typename TChar>
struct std::formatter<mijin::Stacktrace, TChar>
{
using char_t = TChar;
template<class TContext>
constexpr TContext::iterator parse(TContext& ctx)
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != MIJIN_SMART_QUOTE(char_t, '}'))
{
throw std::format_error("invalid format");
}
return it;
}
template<typename TContext>
TContext::iterator format(const mijin::Stacktrace& stacktrace, TContext& ctx) const
{
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
auto it = ctx.out();
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "[{} frames]"), stacktrace.getFrames().size());
for (const auto& [idx, frame] : mijin::enumerate(stacktrace.getFrames()))
{
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "\n #{:<{}} at {}"), idx, numDigits, frame);
}
return it;
}
};
#if __has_include(<fmt/format.h>)
template<>
struct fmt::formatter<mijin::Stackframe>
{
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != '}') FMT_THROW(format_error("invalid format"));
return it;
}
template<typename TContext>
auto format(const mijin::Stackframe& stackframe, TContext& ctx) const -> decltype(ctx.out())
{
auto it = ctx.out();
it = fmt::format_to(it, "[{}] {}:{} in {}", stackframe.address, stackframe.filename,
stackframe.lineNumber, mijin::demangleCPPIdentifier(stackframe.function.c_str()));
return it;
}
};
template<>
struct fmt::formatter<mijin::Stacktrace>
{
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != '}') FMT_THROW(format_error("invalid format"));
return it;
}
template<typename TContext>
auto format(const mijin::Stacktrace& stacktrace, TContext& ctx) const -> decltype(ctx.out())
{
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
auto it = ctx.out();
it = fmt::format_to(it, "[{} frames]", stacktrace.getFrames().size());
for (const auto& [idx, frame] : mijin::enumerate(stacktrace.getFrames()))
{
it = fmt::format_to(it, "\n #{:<{}} at {}", idx, numDigits, frame);
}
return it;
}
};
#endif // __has_include(<fmt/format.h>)
#endif // !defined(MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED) #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,7 @@
#pragma once
#include "./config.hpp"
#include "./helpers.hpp"
#include "./exception.hpp"
#include "./version_support.hpp"

View File

@@ -0,0 +1,22 @@
#pragma once
#if !defined(MIJIN_INTERNAL_CONFIG_HPP_INCLUDED)
#define MIJIN_INTERNAL_CONFIG_HPP_INCLUDED 1
#define MIJIN_QUOTED_ACTUAL(x) #x
#define MIJIN_QUOTED(x) MIJIN_QUOTED_ACTUAL(x)
#if defined(MIJIN_CONFIG_HEADER)
#include MIJIN_QUOTED(MIJIN_CONFIG_HEADER)
#endif
#if !defined(MIJIN_DEFAULT_ALLOCATOR)
#define MIJIN_DEFAULT_ALLOCATOR std::allocator
#endif
#if !defined(MIJIN_DEFAULT_CHAR_TYPE)
#define MIJIN_DEFAULT_CHAR_TYPE char
#endif
#endif // !defined(MIJIN_INTERNAL_CONFIG_HPP_INCLUDED)

View File

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

View File

@@ -0,0 +1,57 @@
#pragma once
#if !defined(MIJIN_INTERNAL_HELPERS_HPP_INCLUDED)
#define MIJIN_INTERNAL_HELPERS_HPP_INCLUDED 1
#include <type_traits>
#include "../util/traits.hpp"
#define MIJIN_IDENTITY(what) what
#define MIJIN_NULLIFY(what)
#define MIJIN_SMART_QUOTE(chr_type, text) \
[]<typename TChar__>(TChar__) consteval \
{ \
if constexpr (std::is_same_v<TChar__, char>) \
{ \
return text; \
} \
else if constexpr (std::is_same_v<TChar__, wchar_t>) \
{ \
return L ## text; \
} \
else if constexpr (std::is_same_v<TChar__, char8_t>) \
{ \
return u8 ## text; \
} \
else \
{ \
static_assert(::mijin::always_false_v<TChar__>, "Invalid char type."); \
} \
}(chr_type())
#define MIJIN_SMART_STRINGIFY(chr_type, text) MIJIN_SMART_QUOTE(chr_type, #text)
#define MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, prefix_a, prefix_b, prefix_c, set_args) \
prefix_a prefix_b(char) prefix_c \
using C ## type_name = Base ## type_name <set_args(char)>; \
\
prefix_a prefix_b(wchar_t) prefix_c \
using W ## type_name = Base ## type_name <set_args(wchar_t)>; \
\
prefix_a prefix_b(char8_t) prefix_c \
using U ## type_name = Base ## type_name <set_args(char8_t)>; \
\
using type_name = Base ## type_name<>;
#define MIJIN_DEFINE_CHAR_VERSIONS_TMPL(type_name, remaining_args, set_args) \
MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, template<, remaining_args, >, set_args)
#define MIJIN_DEFINE_CHAR_VERSIONS_CUSTOM(type_name, set_args) \
MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, , MIJIN_NULLIFY, , set_args)
#define MIJIN_DEFINE_CHAR_VERSIONS(type_name) \
MIJIN_DEFINE_CHAR_VERSIONS_CUSTOM(type_name, MIJIN_IDENTITY)
#endif // !defined(MIJIN_INTERNAL_HELPERS_HPP_INCLUDED)

View File

@@ -0,0 +1,19 @@
#pragma once
#if !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED)
#define MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED 1
#include "../detect.hpp"
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma clang diagnostic ignored "-Wc++26-extensions"
#endif
#if defined(__cpp_deleted_function)
#define MIJIN_DELETE(reason) = delete(reason)
#else
#define MIJIN_DELETE(reason) = delete
#endif
#endif // !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED)

197
source/mijin/io/archive.hpp Normal file
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,194 @@ 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());
} }
StreamError Stream::readZString(std::string& outString)
{
char chr = '\0';
std::string result;
while (true)
{
if (isAtEnd())
{
return StreamError::IO_ERROR;
}
if (StreamError error = read(chr); error != StreamError::SUCCESS)
{
return error;
}
if (chr == '\0')
{
outString = std::move(result);
return StreamError::SUCCESS;
}
result.push_back(chr);
}
}
StreamError Stream::writeZString(std::string_view str)
{
static const char ZERO = '\0';
if (StreamError error = writeRaw(str.data(), str.size() * sizeof(char)); error != StreamError::SUCCESS)
{
return error;
}
return write(ZERO);
}
mijin::Task<StreamError> Stream::c_readBinaryString(std::string& outString)
{
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
StreamError error = co_await c_read(length);
if (error != StreamError::SUCCESS) {
co_return error;
}
std::string result;
result.resize(length);
error = co_await c_readSpan(result.begin(), result.end());
if (error != StreamError::SUCCESS) {
co_return error;
}
outString = std::move(result);
co_return StreamError::SUCCESS;
}
mijin::Task<StreamError> Stream::c_writeBinaryString(std::string_view str)
{
MIJIN_ASSERT(str.length() <= std::numeric_limits<std::uint32_t>::max(), "Binary string is too long.");
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
StreamError error = co_await c_write(length);
if (error != StreamError::SUCCESS) {
co_return error;
}
co_return co_await c_writeSpan(str.begin(), str.end());
}
StreamError Stream::getTotalLength(std::size_t& outLength)
{
const StreamFeatures features = getFeatures();
if (!features.tell || !features.seek) {
return StreamError::NOT_SUPPORTED;
}
const std::size_t origPos = tell();
if (const StreamError error = seek(0, SeekMode::RELATIVE_TO_END); error != StreamError::SUCCESS)
{
return error;
}
outLength = tell();
if (const StreamError error = seek(static_cast<std::intptr_t>(origPos)); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
StreamError Stream::readLine(std::string& outString)
{
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
static constexpr std::size_t BUFFER_SIZE = 4096;
std::array<char, BUFFER_SIZE> buffer;
outString.clear();
bool done = false;
while (!done)
{
// read into the buffer
std::size_t bytesRead = 0;
if (const StreamError error = readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
// try to find a \n
auto begin = buffer.begin(); // NOLINT(readability-qualified-auto)
auto end = buffer.begin() + bytesRead; // NOLINT(readability-qualified-auto)
auto newline = std::find(begin, end, '\n'); // NOLINT(readability-qualified-auto)
if (newline != end)
{
// found the end
outString.append(begin, newline);
end = newline + 1;
done = true;
}
else
{
outString.append(begin, end);
}
// read again, this time to skip
if (const StreamError error = readSpan(begin, end); error != StreamError::SUCCESS)
{
return error;
}
if (isAtEnd())
{
done = true;
}
}
return StreamError::SUCCESS;
}
mijin::Task<StreamError> Stream::c_readLine(std::string& outString)
{
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
static constexpr std::size_t BUFFER_SIZE = 4096;
std::array<char, BUFFER_SIZE> buffer;
outString.clear();
bool done = false;
while(!done)
{
// read into the buffer
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
// try to find a \n
auto begin = buffer.begin(); // NOLINT(readability-qualified-auto)
auto end = buffer.begin() + bytesRead; // NOLINT(readability-qualified-auto)
auto newline = std::find(begin, end, '\n'); // NOLINT(readability-qualified-auto)
if (newline != end)
{
// found the end
outString.append(begin, newline);
end = newline + 1;
done = true;
}
else
{
outString.append(begin, end);
}
// read again, this time to skip
if (StreamError error = co_await c_readSpan(begin, end); error != StreamError::SUCCESS)
{
co_return error;
}
}
co_return StreamError::SUCCESS;
}
FileStream::~FileStream() FileStream::~FileStream()
{ {
if (handle) { if (handle) {
@@ -89,6 +316,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 +329,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 +387,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 +407,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 +419,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 +444,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 +456,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 +487,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 +511,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 +540,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 +550,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,210 @@ 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);
}
template<template<typename> typename TAllocator>
StreamError readRaw(BaseTypelessBuffer<TAllocator>& buffer, const ReadOptions& options = {})
{
return readRaw(buffer.data(), buffer.byteSize(), options);
}
template<std::ranges::contiguous_range TRange>
mijin::Task<StreamError> c_readRaw(TRange& range, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return c_readRaw(&*range.begin(), bytes, options, outBytesRead);
}
template<template<typename> typename TAllocator>
mijin::Task<StreamError> c_readRaw(BaseTypelessBuffer<TAllocator>& buffer, const ReadOptions& options = {})
{
return c_readRaw(buffer.data(), buffer.byteSize(), options);
}
StreamError writeRaw(const void* data, std::size_t bytes)
{ {
const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data); 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)
{ {
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);
StreamError readZString(std::string& outString);
StreamError writeZString(std::string_view str);
mijin::Task<StreamError> c_readBinaryString(std::string& outString);
mijin::Task<StreamError> c_writeBinaryString(std::string_view str);
[[deprecated("Use readBinaryString() or readAsString() instead.")]]
inline StreamError readString(std::string& outString) { return readBinaryString(outString); }
[[deprecated("Use writeBinaryString() or writeText() instead.")]]
inline StreamError writeString(std::string_view str) { return writeBinaryString(str); }
StreamError getTotalLength(std::size_t& outLength);
template<template<typename> typename TAllocator>
StreamError readRest(BaseTypelessBuffer<TAllocator>& outBuffer);
template<template<typename> typename TAllocator>
mijin::Task<StreamError> c_readRest(BaseTypelessBuffer<TAllocator>& outBuffer);
StreamError readLine(std::string& outString);
mijin::Task<StreamError> c_readLine(std::string& outString);
template<typename TChar = char, typename TTraits = std::char_traits<TChar>, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR<char>>
StreamError readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString);
template<typename TChar = char, typename TTraits = std::char_traits<TChar>, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR<char>>
mijin::Task<StreamError> c_readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString);
StreamError writeText(std::string_view str)
{
return writeSpan(str);
}
mijin::Task<StreamError> c_writeText(std::string_view str)
{
return c_writeSpan(str);
}
}; };
class FileStream : public Stream class FileStream : public Stream
@@ -142,7 +318,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 +335,254 @@ 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<template<typename> typename TAllocator>
StreamError Stream::readRest(BaseTypelessBuffer<TAllocator>& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (const StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (const StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<std::byte, CHUNK_SIZE> chunk = {};
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (const StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
const std::span<std::byte> bufferBytes = outBuffer.template makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
return StreamError::SUCCESS;
}
template<template<typename> typename TAllocator>
mijin::Task<StreamError> Stream::c_readRest(BaseTypelessBuffer<TAllocator>& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (StreamError error = co_await c_readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
co_return error;
}
co_return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<std::byte, CHUNK_SIZE> chunk = {};
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
std::span<std::byte> bufferBytes = outBuffer.template makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
co_return StreamError::SUCCESS;
}
template<typename TChar, typename TTraits, typename TAllocator>
StreamError Stream::readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString)
{
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outString.resize(length);
if (StreamError error = readRaw(outString.data(), length); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<TChar, CHUNK_SIZE> chunk;
outString.clear();
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
outString.append(chunk.data(), chunk.data() + bytesRead);
}
return StreamError::SUCCESS;
}
template<typename TChar, typename TTraits, typename TAllocator>
mijin::Task<StreamError> Stream::c_readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString)
{
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outString.resize(length);
if (StreamError error = co_await c_readRaw(outString.data(), length); error != StreamError::SUCCESS)
{
co_return error;
}
co_return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<TChar, CHUNK_SIZE> chunk;
outString.clear();
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(chunk, true, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
outString.append(chunk.data(), chunk.data() + bytesRead);
}
co_return StreamError::SUCCESS;
}
inline const char* errorName(StreamError error) MIJIN_NOEXCEPT
{
switch (error)
{
case StreamError::SUCCESS:
return "success";
case StreamError::IO_ERROR:
return "IO error";
case StreamError::NOT_SUPPORTED:
return "not supported";
case StreamError::CONNECTION_CLOSED:
return "connection closed";
case StreamError::PROTOCOL_ERROR:
return "protocol error";
case StreamError::WOULD_BLOCK:
return "would block";
case StreamError::UNKNOWN_ERROR:
return "unknown error";
case StreamError::CONNECTION_REFUSED:
return "connection refused";
}
return "<invalid error>";
}
#if MIJIN_ENABLE_EXCEPTIONS // these functions don't make sense with exceptions disabled
inline void throwOnError(mijin::StreamError error)
{
if (error == mijin::StreamError::SUCCESS) {
return;
}
throw 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,52 @@
#pragma once
#if !defined(MIJIN_LOGGING_FILTERS_HPP_INCLUDED)
#define MIJIN_LOGGING_FILTERS_HPP_INCLUDED 1
#include "./logger.hpp"
namespace mijin
{
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLevelFilter : public BaseLogFilter<TChar>
{
public:
using base_t = BaseLogFilter<TChar>;
using typename base_t::char_t;
using typename base_t::message_t;
private:
int mMinLevel = 0;
int mMaxLevel = 0;
public:
explicit BaseLevelFilter(int minLevel, int maxLevel = std::numeric_limits<int>::max()) MIJIN_NOEXCEPT
: mMinLevel(minLevel), mMaxLevel(maxLevel) {}
explicit BaseLevelFilter(const BaseLogLevel<char_t>& minLevel, const BaseLogLevel<char_t>& maxLevel = {nullptr, std::numeric_limits<int>::max()}) MIJIN_NOEXCEPT
: mMinLevel(minLevel.value), mMaxLevel(maxLevel.value) {}
[[nodiscard]]
int getMinLevel() const MIJIN_NOEXCEPT { return mMinLevel; }
[[nodiscard]]
int getMaxLevel() const MIJIN_NOEXCEPT { return mMaxLevel; }
void setMinLevel(int level) MIJIN_NOEXCEPT { mMinLevel = level; }
void setMinLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinLevel = level.value; }
void setMaxLevel(int level) MIJIN_NOEXCEPT { mMaxLevel = level; }
void setMaxLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMaxLevel = level.value; }
bool shouldShow(const message_t& message) MIJIN_NOEXCEPT override
{
return message.level->value >= mMinLevel && message.level->value <= mMaxLevel;
}
};
MIJIN_DEFINE_CHAR_VERSIONS(LevelFilter)
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)

View File

@@ -0,0 +1,323 @@
#pragma once
#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1
#include <format>
#include <variant>
#include "./logger.hpp"
#include "../internal/common.hpp"
#include "../memory/dynamic_pointer.hpp"
#include "../memory/memutil.hpp"
#include "../memory/virtual_allocator.hpp"
#include "../util/annot.hpp"
#include "../util/ansi_colors.hpp"
#include "../util/concepts.hpp"
#include "../util/string.hpp"
namespace mijin
{
#define FORMATTER_COMMON_ARGS(chr_type) allocator_type_for<chr_type> TAllocator = MIJIN_DEFAULT_ALLOCATOR<chr_type>
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
FORMATTER_COMMON_ARGS(TChar)>
class BaseLogFormatter
{
public:
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator;
using string_t = std::basic_string<char_t, traits_t, allocator_t>;
virtual ~BaseLogFormatter() noexcept = default;
virtual void format(const LogMessage& message, string_t& outFormatted) = 0;
};
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
FORMATTER_COMMON_ARGS(TChar)>
class BaseSimpleLogFormatter : public BaseLogFormatter<TChar, TTraits, TAllocator>
{
public:
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator;
using base_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
using typename base_t::string_t;
using string_view_t = std::basic_string_view<char_t, traits_t>;
private:
string_t mFormat;
string_t mFormatBuffer;
public:
explicit BaseSimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)), mFormatBuffer(mFormat.get_allocator()) {}
void format(const LogMessage& message, string_t& outFormatted) override;
private:
void formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted);
};
#define FORMATTER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(LogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS)
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(SimpleLogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS)
#undef FORMATTER_COMMON_ARGS
#undef FORMATTER_SET_ARGS
#define MIJIN_FORMATTING_SINK_COMMON_ARGS(chr_type) \
typename TTraits = std::char_traits<chr_type>, \
template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, \
deleter_type<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>> TDeleter \
= AllocatorDeleter<TAllocator<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>>>
#define MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT \
typename TChar = MIJIN_DEFAULT_CHAR_TYPE, \
MIJIN_FORMATTING_SINK_COMMON_ARGS(TChar)
#define MIJIN_FORMATTING_SINK_TMP_ARG_NAMES TChar, TTraits, TAllocator, TDeleter
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseFormattingLogSink : public BaseLogSink<TChar>
{
public:
using base_t = BaseLogSink<TChar>;
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator<TChar>;
using formatter_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
using formatter_deleter_t = TDeleter;
using formatter_ptr_t = DynamicPointer<formatter_t, formatter_deleter_t>;
using string_t = formatter_t::string_t;
using typename base_t::message_t;
private:
not_null_t<formatter_ptr_t> mFormatter;
string_t mBuffer;
public:
explicit BaseFormattingLogSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: mFormatter(std::move(formatter)), mBuffer(std::move(allocator))
{}
virtual void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT = 0;
void handleMessage(const message_t& message) noexcept override
{
mBuffer.clear();
mFormatter->format(message, mBuffer);
handleMessageFormatted(message, mBuffer.c_str());
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(FormattingLogSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
template<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::format(const LogMessage& message, string_t& outFormatted)
{
mFormatBuffer.clear();
for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos)
{
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
{
++pos;
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
{
// double {
outFormatted += MIJIN_SMART_QUOTE(char_t, '{');
continue;
}
const auto argStart = pos;
static const string_view_t endChars = MIJIN_SMART_QUOTE(char_t, ":}");
pos = std::find_first_of(pos, mFormat.end(), endChars.begin(), endChars.end());
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
const string_view_t argName(argStart, pos);
string_view_t argFormat;
if (*pos == ':')
{
const auto formatStart = pos;
pos = std::find(pos, mFormat.end(), MIJIN_SMART_QUOTE(char_t, '}'));
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
argFormat = string_view_t(formatStart, pos);
}
// small utility that uses the provided string buffer for storing the format string
auto formatInline = [&](const auto& value)
{
using type_t = std::decay_t<decltype(value)>;
// if there is no format, just directly print the value
if (argFormat.empty())
{
if constexpr (is_char_v<type_t>)
{
convertStringType(string_view_t(&value, 1), outFormatted);
}
else if constexpr (std::is_arithmetic_v<type_t>)
{
std::format_to(std::back_inserter(outFormatted), MIJIN_SMART_QUOTE(char_t, "{}"), value);
}
else if constexpr (is_string_v<type_t> || is_cstring_v<type_t>)
{
convertStringType(value, outFormatted);
}
else
{
static_assert(always_false_v<type_t>);
outFormatted += value;
}
return;
}
// first copy the format string + braces into the buffer
const auto formatStart = mFormatBuffer.size();
mFormatBuffer += '{';
mFormatBuffer += argFormat;
mFormatBuffer += '}';
const auto formatEnd = mFormatBuffer.size();
auto doFormatTo = [](string_t& string, string_view_t format, const auto& value)
{
if constexpr (std::is_same_v<char_t, char>)
{
std::vformat_to(std::back_inserter(string), format, std::make_format_args(value));
}
else if constexpr (std::is_same_v<char_t, wchar_t>)
{
std::vformat_to(std::back_inserter(string), format, std::make_wformat_args(value));
}
else
{
static_assert(always_false_v<char_t>, "Cannot format this char type.");
}
};
auto doFormat = [&](const auto& value)
{
auto format = string_view_t(mFormatBuffer).substr(formatStart, formatEnd - formatStart);
doFormatTo(outFormatted, format, value);
};
if constexpr (is_char_v<type_t> && !std::is_same_v<char_t, type_t>)
{
static_assert(always_false_v<type_t>, "TODO...");
}
else if constexpr ((is_string_v<type_t> || is_cstring_v<type_t>) && !std::is_same_v<str_char_type_t<type_t>, char_t>)
{
// different string type, needs to be converted
const auto convertedStart = mFormatBuffer.size();
convertStringType(value, mFormatBuffer);
const auto convertedEnd = mFormatBuffer.size();
// then we can format it
auto converted = string_view_t(mFormatBuffer).substr(convertedStart, mFormatBuffer.size() - convertedEnd);
doFormat(converted);
}
else
{
// nothing special
doFormat(value);
}
};
if (argName == MIJIN_SMART_QUOTE(char_t, "text"))
{
formatInline(message.text);
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "channel"))
{
formatInline(message.channel->name);
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "file"))
{
formatInline(message.sourceLocation.file_name());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "function"))
{
formatInline(message.sourceLocation.function_name());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "line"))
{
formatInline(message.sourceLocation.line());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "column"))
{
formatInline(message.sourceLocation.column());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "level"))
{
formatInline(message.level->name);
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "ansi"))
{
formatAnsiSequence(message, argFormat.substr(1), outFormatted);
}
else
{
MIJIN_ERROR("Invalid format argument name.");
}
}
else
{
outFormatted += *pos;
}
}
}
template<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted)
{
std::size_t numParts = 0;
const auto formatParts = splitFixed<4>(ansiName, ",", {}, &numParts);
outFormatted += MIJIN_SMART_QUOTE(char_t, "\033[");
for (std::size_t partIdx = 0; partIdx < numParts; ++partIdx)
{
const string_view_t& part = formatParts[partIdx];
if (partIdx > 0)
{
outFormatted += MIJIN_SMART_QUOTE(char_t, ',');
}
if (part == MIJIN_SMART_QUOTE(char_t, "reset"))
{
outFormatted += BaseAnsiFontEffects<char_t>::RESET;
}
else if (part == MIJIN_SMART_QUOTE(char_t, "level_color"))
{
const int levelValue = message.level->value;
if (levelValue < MIJIN_LOG_LEVEL_VALUE_VERBOSE)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_CYAN;
}
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_INFO)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_WHITE;
}
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_WARNING)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_BRIGHT_WHITE;
}
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_ERROR)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_YELLOW;
}
else
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_RED;
}
}
else
{
MIJIN_ERROR("Invalid format ansi font effect name.");
}
}
outFormatted += MIJIN_SMART_QUOTE(char_t, 'm');
}
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)

View File

@@ -0,0 +1,303 @@
#pragma once
#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1
#include <cstdint>
#include <format>
#include <mutex>
#include <source_location>
#include <string>
#include <vector>
#include "../internal/common.hpp"
#include "../util/annot.hpp"
#include "../util/iterators.hpp"
namespace mijin
{
#if !defined(MIJIN_LOG_LEVEL_VALUE_DEBUG)
#define MIJIN_LOG_LEVEL_VALUE_DEBUG -1000
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_VERBOSE)
#define MIJIN_LOG_LEVEL_VALUE_VERBOSE -500
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_INFO)
#define MIJIN_LOG_LEVEL_VALUE_INFO 0
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_WARNING)
#define MIJIN_LOG_LEVEL_VALUE_WARNING 500
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_ERROR)
#define MIJIN_LOG_LEVEL_VALUE_ERROR 1000
#endif
#if !defined(MIJIN_FUNCNAME_GET_LOGGER)
#define MIJIN_FUNCNAME_GET_LOGGER mijin__getLogger__
#endif
#if !defined(MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE)
#define MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE mijin__getMinLogLevelCompile
#endif
#if !defined(MIJIN_NSNAME_LOG_LEVEL)
#define MIJIN_NSNAME_LOG_LEVEL mijin_log_level
#endif
#if !defined(MIJIN_NSNAME_LOG_CHANNEL)
#define MIJIN_NSNAME_LOG_CHANNEL mijin_log_channel
#endif
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogLevel
{
using char_t = TChar;
const char_t* name;
int value;
explicit operator int() const MIJIN_NOEXCEPT { return value; }
auto operator<=>(const BaseLogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
};
MIJIN_DEFINE_CHAR_VERSIONS(LogLevel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogChannel
{
using char_t = TChar;
const char_t* name;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogChannel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogMessage
{
using char_t = TChar;
const char_t* text;
const BaseLogChannel<char_t>* channel;
const BaseLogLevel<char_t>* level;
std::source_location sourceLocation;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogMessage)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogSink
{
public:
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual ~BaseLogSink() noexcept = default;
virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogSink)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogFilter
{
public:
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual ~BaseLogFilter() noexcept = default;
virtual bool shouldShow(const message_t& message) MIJIN_NOEXCEPT = 0;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogFilter)
#define LOGGER_COMMON_ARGS(chr_type) template<typename T> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>, LOGGER_COMMON_ARGS(TChar)>
class BaseLogger
{
public:
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator<char_t>;
using sink_t = BaseLogSink<char_t>;
using filter_t = BaseLogFilter<char_t>;
using level_t = BaseLogLevel<char_t>;
using channel_t = BaseLogChannel<char_t>;
using message_t = BaseLogMessage<char_t>;
using string_t = std::basic_string<char_t, traits_t, allocator_t>;
private:
struct SinkEntry
{
sink_t* sink;
filter_t* filter;
};
std::vector<SinkEntry, TAllocator<SinkEntry>> mSinks;
mutable std::mutex mMutex;
public:
explicit BaseLogger(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<SinkEntry>, TAllocator<void>&&>))
: mSinks(TAllocator<SinkEntry>(std::move(allocator)))
{}
BaseLogger(const BaseLogger&) = default;
BaseLogger(BaseLogger&&) = default;
BaseLogger& operator=(const BaseLogger&) = default;
BaseLogger& operator=(BaseLogger&&) = default;
void addSink(sink_t& sink)
{
std::unique_lock _(mMutex);
mSinks.push_back({&sink, nullptr});
}
void addSink(sink_t& sink, filter_t& filter)
{
std::unique_lock _(mMutex);
mSinks.push_back({&sink, &filter});
}
void postMessage(const message_t& message) const MIJIN_NOEXCEPT
{
std::unique_lock _(mMutex);
for (const SinkEntry& entry : mSinks)
{
if (entry.filter != nullptr && !entry.filter->shouldShow(message)) {
continue;
}
entry.sink->handleMessage(message);
}
}
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, const char_t* msg) const MIJIN_NOEXCEPT
{
postMessage({
.text = msg,
.channel = &channel,
.level = &level,
.sourceLocation = std::move(sourceLocation)
});
}
template<typename... TArgs>
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation,
std::basic_format_string<char_t, std::type_identity_t<TArgs>...> fmt, TArgs&& ... args) const MIJIN_NOEXCEPT
{
// TODO: make the logger use a traits struct to make this adjustable
static constexpr std::size_t BUFFER_SIZE = 256;
std::array<char_t, BUFFER_SIZE> buffer;
// first try to write into a buffer on the stack
FixedArrayOutputIterator itAfter = std::format_to(FixedArrayOutputIterator(buffer), fmt, std::forward<TArgs>(args)...);
*itAfter = '\0';
++itAfter;
if (!itAfter.didOverflow())
{
log(level, channel, std::move(sourceLocation), buffer.data());
return;
}
// if that didn't work, allocate more space
const std::size_t newBufferSize = itAfter.getCounter();
char_t* newBuffer = static_cast<char_t*>(alloca(newBufferSize * sizeof(char_t)));
const std::format_to_n_result result = std::format_to_n(newBuffer, newBufferSize - 1, fmt, std::forward<TArgs>(args)...);
*result.out = '\0';
log(level, channel, std::move(sourceLocation), newBuffer);
}
};
#define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(Logger, LOGGER_COMMON_ARGS, LOGGER_SET_ARGS)
#undef LOGGER_COMMON_ARGS
#undef LOGGER_SET_ARGS
#define MIJIN_DECLARE_LOG_CHANNEL_BASE(chr_type, cnlName) \
namespace MIJIN_NSNAME_LOG_CHANNEL \
{ \
extern const ::mijin::BaseLogChannel<chr_type> cnlName; \
}
#define MIJIN_DECLARE_LOG_CHANNEL(cnlName) MIJIN_DECLARE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName)
#define MIJIN_DEFINE_LOG_CHANNEL_BASE(chr_type, cnlName) \
namespace MIJIN_NSNAME_LOG_CHANNEL \
{ \
const ::mijin::BaseLogChannel<chr_type> cnlName { \
.name = MIJIN_SMART_STRINGIFY(chr_type, cnlName) \
}; \
}
#define MIJIN_DEFINE_LOG_CHANNEL(cnlName) MIJIN_DEFINE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName)
#define MIJIN_DEFINE_LOG_LEVEL_BASE(chr_type, lvlName, lvlValue) \
namespace MIJIN_NSNAME_LOG_LEVEL \
{ \
inline constexpr ::mijin::BaseLogLevel<chr_type> lvlName{ \
.name = MIJIN_SMART_STRINGIFY(chr_type, lvlName), \
.value = lvlValue \
}; \
}
#define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) MIJIN_DEFINE_LOG_LEVEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, lvlName, lvlValue)
#if defined(MIJIN_MIN_LOGLEVEL_COMPILE)
inline constexpr int MIN_LOG_LEVEL_COMPILE = static_cast<int>(MIJIN_MIN_LOGLEVEL_COMPILE);
#elif defined(MIJIN_DEBUG)
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_DEBUG;
#else
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_VERBOSE;
#endif
#define MIJIN_DEFINE_DEFAULT_LOG_LEVELS \
MIJIN_DEFINE_LOG_LEVEL(DEBUG, MIJIN_LOG_LEVEL_VALUE_DEBUG) \
MIJIN_DEFINE_LOG_LEVEL(VERBOSE, MIJIN_LOG_LEVEL_VALUE_VERBOSE) \
MIJIN_DEFINE_LOG_LEVEL(INFO, MIJIN_LOG_LEVEL_VALUE_INFO) \
MIJIN_DEFINE_LOG_LEVEL(WARNING, MIJIN_LOG_LEVEL_VALUE_WARNING) \
MIJIN_DEFINE_LOG_LEVEL(ERROR, MIJIN_LOG_LEVEL_VALUE_ERROR)
#define MIJIN_LOG_LEVEL_OBJECT(level) MIJIN_NSNAME_LOG_LEVEL::level
#define MIJIN_LOG_CHANNEL_OBJECT(channel) MIJIN_NSNAME_LOG_CHANNEL::channel
#define MIJIN_LOG_ALWAYS(level, channel, ...) MIJIN_FUNCNAME_GET_LOGGER().log( \
MIJIN_LOG_LEVEL_OBJECT(level), MIJIN_LOG_CHANNEL_OBJECT(channel), std::source_location::current(), __VA_ARGS__ \
)
#define MIJIN_LOG(level, channel, ...) \
if constexpr (MIJIN_LOG_LEVEL_OBJECT(level).value < MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE()) {} \
else MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__)
#define MIJIN_SET_CLASS_LOGGER(loggerExpr) \
const auto& MIJIN_FUNCNAME_GET_LOGGER() const noexcept \
{ \
return loggerExpr; \
}
#define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \
auto MIJIN_FUNCNAME_GET_LOGGER = [&]() -> const auto& \
{ \
return loggerExpr; \
};
#define MIJIN_SET_CLASS_MIN_LOG_LEVEL_COMPILE(level) \
int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT \
{ \
return MIJIN_LOG_LEVEL_OBJECT(level).value; \
}
#define MIJIN_SET_SCOPE_MIN_LOG_LEVEL_COMPILE(level) \
auto MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE = []() -> int \
{ \
return MIJIN_LOG_LEVEL_OBJECT(level).value; \
};
} // namespace mijin
inline constexpr int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT
{
return ::mijin::MIN_LOG_LEVEL_COMPILE;
}
#endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)

View File

@@ -0,0 +1,71 @@
#pragma once
#if !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
#define MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../util/traits.hpp"
namespace mijin
{
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseStdioSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
{
public:
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
using typename base_t::char_t;
using typename base_t::allocator_t;
using typename base_t::formatter_ptr_t;
using typename base_t::message_t;
private:
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
public:
explicit BaseStdioSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)) {}
[[nodiscard]]
int getMinStderrLevel() const MIJIN_NOEXCEPT { return mMinStderrLevel; }
void setMinStderrLevel(int level) MIJIN_NOEXCEPT { mMinStderrLevel = level; }
void setMinStderrLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinStderrLevel = level.value; }
void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT override
{
FILE* stream = (message.level->value >= mMinStderrLevel) ? stderr : stdout;
if constexpr (std::is_same_v<char_t, char>)
{
std::fputs(formatted, stream);
std::fputc('\n', stream);
}
else if constexpr (std::is_same_v<char_t, wchar_t>)
{
std::fputws(formatted, stream);
std::fputwc(L'\n', stream);
}
else if constexpr (sizeof(char_t) == sizeof(char))
{
// char8_t etc.
std::fputs(std::bit_cast<const char*>(formatted), stream);
std::fputc('\n', stream);
}
else
{
static_assert(always_false_v<char_t>, "Character type not supported.");
}
std::fflush(stream);
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StdioSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)

View File

@@ -0,0 +1,58 @@
#pragma once
#if !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)
#define MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../io/stream.hpp"
#include "../util/traits.hpp"
namespace mijin
{
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseStreamSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
{
public:
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
using typename base_t::char_t;
using typename base_t::allocator_t;
using typename base_t::formatter_ptr_t;
using typename base_t::message_t;
using stream_ptr_t = DynamicPointer<Stream>;
private:
stream_ptr_t mStream;
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
public:
explicit BaseStreamSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)) {}
explicit BaseStreamSink(not_null_t<stream_ptr_t> stream, not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)), mStream(std::move(stream)) {}
void setStream(not_null_t<stream_ptr_t> stream) {
mStream = std::move(stream).release();
}
void handleMessageFormatted(const message_t& /* message */, const char_t* formatted) MIJIN_NOEXCEPT override
{
if (!mStream) {
return;
}
(void) mStream->writeSpan(std::basic_string_view(formatted));
(void) mStream->write('\n');
mStream->flush();
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StreamSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)

View File

@@ -0,0 +1,176 @@
#pragma once
#if !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)
#define MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED 1
#include <bit>
#include <cstdint>
#include <utility>
#include "../internal/common.hpp"
#include "../memory/memutil.hpp"
#include "../util/concepts.hpp"
#include "../util/flag.hpp"
namespace mijin
{
MIJIN_DEFINE_FLAG(Owning);
template<typename T, deleter_type<T> TDeleter = AllocatorDeleter<MIJIN_DEFAULT_ALLOCATOR<T>>>
class DynamicPointer
{
public:
using pointer = T*;
using element_type = T;
using deleter_t = TDeleter;
private:
std::uintptr_t mData = 0;
[[no_unique_address]] TDeleter mDeleter;
public:
constexpr DynamicPointer(std::nullptr_t = nullptr) MIJIN_NOEXCEPT {}
DynamicPointer(const DynamicPointer&) = delete;
constexpr DynamicPointer(pointer ptr, Owning owning, TDeleter deleter = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TDeleter>)
: mData(std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0)), mDeleter(std::move(deleter))
{
MIJIN_ASSERT((std::bit_cast<std::uintptr_t>(ptr) & 1) == 0, "Invalid address, DynamicPointer requires addresses to be divisible by two.");
}
template<typename TOther, typename TOtherDeleter> requires (std::is_constructible_v<TDeleter, TOtherDeleter&&>)
constexpr DynamicPointer(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_convertible_v<TOtherDeleter, TDeleter>))
: mData(std::exchange(other.mData, 0)), mDeleter(std::move(other.mDeleter)) {
MIJIN_ASSERT(other.mData == 0, "");
}
constexpr ~DynamicPointer() noexcept
{
reset();
}
DynamicPointer& operator=(const DynamicPointer&) = delete;
DynamicPointer& operator=(std::nullptr_t) MIJIN_NOEXCEPT
{
reset();
return *this;
}
template<typename TOther, typename TOtherDeleter> requires(std::is_assignable_v<TDeleter, TOtherDeleter>)
DynamicPointer& operator=(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TDeleter, TOtherDeleter>))
{
if (this != &other)
{
reset();
mData = std::exchange(other.mData, 0);
mDeleter = std::move(other.mDeleter);
}
return *this;
}
template<typename TOther, typename TOtherDeleter> requires(std::equality_comparable_with<T, TOther>)
auto operator<=>(const DynamicPointer<TOther, TOtherDeleter>& other) MIJIN_NOEXCEPT
{
return mData <=> other.mData;
}
constexpr bool operator==(std::nullptr_t) const MIJIN_NOEXCEPT
{
return empty();
}
constexpr bool operator!=(std::nullptr_t) const MIJIN_NOEXCEPT
{
return !empty();
}
constexpr operator bool() const MIJIN_NOEXCEPT
{
return !empty();
}
constexpr bool operator!() const MIJIN_NOEXCEPT
{
return empty();
}
constexpr pointer operator->() const MIJIN_NOEXCEPT
{
return get();
}
constexpr element_type& operator*() const MIJIN_NOEXCEPT
{
return *get();
}
[[nodiscard]]
constexpr bool isOwning() const MIJIN_NOEXCEPT
{
return (mData & 1) == 1;
}
[[nodiscard]]
constexpr bool empty() const MIJIN_NOEXCEPT
{
return mData == 0;
}
[[nodiscard]]
constexpr pointer get() const MIJIN_NOEXCEPT
{
return std::bit_cast<pointer>(mData & ~1);
}
constexpr void reset(pointer ptr, Owning owning) MIJIN_NOEXCEPT
{
if (isOwning())
{
mDeleter(get());
}
mData = std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0);
}
constexpr void reset() MIJIN_NOEXCEPT
{
reset(nullptr, Owning::NO);
}
[[nodiscard]]
pointer release() MIJIN_NOEXCEPT
{
return std::bit_cast<pointer>(std::exchange(mData, 0) & ~1);
}
template<typename TOther, deleter_type<TOther> TOtherDeleter>
friend class DynamicPointer;
};
template<typename T, typename TDeleter>
bool operator==(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
{
return pointer == nullptr;
}
template<typename T, typename TDeleter>
bool operator!=(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
{
return pointer != nullptr;
}
template<typename T, typename... TArgs>
DynamicPointer<T, std::default_delete<T>> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
{
return DynamicPointer<T, std::default_delete<T>>(new T(std::forward<TArgs>(args)...), Owning::YES);
}
template<typename T, allocator_type_for<T> TAllocator, typename... TArgs>
DynamicPointer<T, AllocatorDeleter<TAllocator>> makeDynamicWithAllocator(TAllocator allocator, TArgs&&... args)
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...> && std::is_nothrow_move_constructible_v<TAllocator>))
{
T* obj = allocator.allocate(1);
if (obj != nullptr)
{
::new(obj) T(std::forward<TArgs>(args)...);
}
return DynamicPointer<T, AllocatorDeleter<TAllocator>>(obj, Owning::YES, AllocatorDeleter<TAllocator>(std::move(allocator)));
}
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)

View File

@@ -0,0 +1,90 @@
#pragma once
#if !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)
#define MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED 1
#include <memory>
#include "../internal/common.hpp"
namespace mijin
{
template<typename TAllocator>
class AllocatorDeleter
{
public:
using value_type = std::allocator_traits<TAllocator>::value_type;
using pointer = std::allocator_traits<TAllocator>::pointer;
private:
[[no_unique_address]] TAllocator allocator_;
public:
AllocatorDeleter() = default;
explicit AllocatorDeleter(TAllocator allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator>)
: allocator_(std::move(allocator)) {}
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, const TOtherAllocator&>)
AllocatorDeleter(const AllocatorDeleter<TOtherAllocator>& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator>))
: allocator_(other.allocator_) {}
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, TOtherAllocator&&>)
AllocatorDeleter(AllocatorDeleter<TOtherAllocator>&& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator&&>))
: allocator_(std::move(other.allocator_)) {}
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, const TOtherAllocator&>)
AllocatorDeleter& operator=(const AllocatorDeleter<TOtherAllocator>& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, const TOtherAllocator&>))
{
if (this != static_cast<const void*>(&other))
{
allocator_ = other.allocator_;
}
return *this;
}
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, TOtherAllocator&&>)
AllocatorDeleter& operator=(AllocatorDeleter<TOtherAllocator>&& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, TOtherAllocator&&>))
{
if (this != static_cast<const void*>(&other))
{
allocator_ = std::move(other.allocator_);
}
return *this;
}
void operator()(pointer ptr) MIJIN_NOEXCEPT_IF(noexcept(allocator_.deallocate(ptr, sizeof(value_type))))
{
allocator_.deallocate(ptr, sizeof(value_type));
}
template<typename TOtherAllocator>
friend class AllocatorDeleter;
};
template<typename T>
class AllocatorDeleter<std::allocator<T>>
{
public:
AllocatorDeleter() noexcept = default;
template<typename TOther>
AllocatorDeleter(std::allocator<TOther>) noexcept {}
template<typename TOther>
AllocatorDeleter(const AllocatorDeleter<std::allocator<TOther>>&) noexcept {}
template<typename TOther>
AllocatorDeleter& operator=(const AllocatorDeleter<std::allocator<TOther>>&) noexcept { return *this; }
void operator()(T* ptr) const MIJIN_NOEXCEPT
{
delete ptr;
}
};
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)

View File

@@ -7,56 +7,317 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <limits> #include "../internal/common.hpp"
#include <memory>
#include <vector>
#include "../debug/assert.hpp" #include "../debug/assert.hpp"
#include "../util/align.hpp"
namespace mijin namespace mijin
{ {
template<typename TObject, std::size_t OBJECTS_PER_PAGE = 1024> namespace impl
class ObjectPool
{ {
private: struct ObjectPoolGap
struct GapInfo {
{ std::uint16_t nextGapOffset;
std::uint32_t objectCount; std::uint16_t remainingObjects;
std::uint32_t nextGap;
};
static constexpr std::size_t ALIGN = std::max(alignof(TObject), alignof(GapInfo));
struct alignas(ALIGN) ObjectData ObjectPoolGap* nextGap() noexcept {
{ return nextGapOffset == 0 ? nullptr : reinterpret_cast<ObjectPoolGap*>(reinterpret_cast<std::byte*>(this) + nextGapOffset);
std::array<std::byte, sizeof(TObject)> bytes; }
};
struct Page
{
std::uint32_t freeOffset = 0;
std::array<ObjectData, OBJECTS_PER_PAGE> data;
};
std::vector<std::unique_ptr<Page>> pages;
public:
[[nodiscard]] TObject* allocate(std::size_t count = 1) noexcept;
void free(TObject* object, std::size_t count = 1) noexcept;
}; };
template<typename TObject, std::size_t OBJECTS_PER_PAGE> template<typename TObject, std::size_t OBJECTS_PER_PAGE>
TObject* ObjectPool<TObject, OBJECTS_PER_PAGE>::allocate(std::size_t count) noexcept struct ObjectPoolPage
{ {
MIJIN_ASSERT(count <= OBJECTS_PER_PAGE, "Cannot allocate more than OBJECTS_PER_PAGE elements at once!"); ObjectPoolGap* firstGap;
// first try to find a free spot in the existing pages ObjectPoolPage* nextPage = nullptr;
std::array<std::byte, OBJECTS_PER_PAGE * MIJIN_STRIDEOF(TObject)> data;
for (std::unique_ptr<Page>& page : pages) ObjectPoolPage() noexcept
{ {
std::uint32_t offset = page->freeOffset; firstGap = reinterpret_cast<ObjectPoolGap*>(data.data());
while (offset < OBJECTS_PER_PAGE) firstGap->nextGapOffset = 0;
firstGap->remainingObjects = OBJECTS_PER_PAGE;
}
};
}
template<typename TObject, std::size_t OBJECTS_PER_PAGE>
class ObjectPoolIterator
{
public:
using value_type = TObject;
using difference_type = std::ptrdiff_t;
private:
using gap_t = impl::ObjectPoolGap;
using page_t = impl::ObjectPoolPage<TObject, OBJECTS_PER_PAGE>;
page_t* currentPage_ = nullptr;
gap_t* currentGap_ = nullptr;
std::uint16_t currentObject_ = 0;
explicit ObjectPoolIterator(page_t& firstPage) noexcept
{
seekNextOnPage(firstPage);
}
public:
ObjectPoolIterator() noexcept = default;
ObjectPoolIterator(const ObjectPoolIterator&) noexcept = default;
ObjectPoolIterator& operator=(const ObjectPoolIterator&) noexcept = default;
auto operator<=>(const ObjectPoolIterator&) const noexcept = default;
TObject& operator*() const noexcept { return getObject(); }
TObject* operator->() const noexcept { return &getObject(); }
ObjectPoolIterator& operator++() noexcept
{
seekNext();
return *this;
}
ObjectPoolIterator operator++(int) const noexcept
{
ObjectPoolIterator copy(*this);
++(*this);
return copy;
}
private:
TObject& getObject() const noexcept
{
MIJIN_ASSERT(currentGap_ != nullptr, "Attempting to dereference an invalid iterator.");
return *(reinterpret_cast<TObject*>(currentGap_) + currentGap_->remainingObjects + currentObject_);
}
bool seekNextOnPage(page_t& firstPage) noexcept
{
for (page_t* page = &firstPage; page != nullptr; page = page->nextPage)
{ {
GapInfo& gapInfo = *std::bit_cast<GapInfo*>(&page->data[offset]); if (page->firstGap != nullptr && seekNextInGap(*page->firstGap))
{
currentPage_ = page;
return true;
}
}
return false;
}
bool seekNextInGap(gap_t& firstGap) noexcept
{
for (gap_t* gap = &firstGap; gap != nullptr; gap = gap->nextGap())
{
if (gap->remainingObjects < OBJECTS_PER_PAGE)
{
currentGap_ = gap;
currentObject_ = 0;
return true;
}
}
return false;
}
void seekNext() noexcept
{
if (++currentObject_ < OBJECTS_PER_PAGE - currentGap_->remainingObjects)
{
return;
}
if (currentGap_->nextGapOffset != 0 && seekNextInGap(*currentGap_->nextGap())) {
return;
}
if (currentPage_->nextPage != nullptr && seekNextOnPage(*currentPage_->nextPage)) {
return;
}
currentPage_ = nullptr;
currentGap_ = nullptr;
currentObject_ = 0;
}
template<typename TObject2, std::size_t OBJECTS_PER_PAGE2, template<typename> typename TAllocator>
friend class ObjectPool;
};
template<typename TObject, std::size_t OBJECTS_PER_PAGE = 1024, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
class ObjectPool
{
public:
using iterator = ObjectPoolIterator<TObject, OBJECTS_PER_PAGE>;
using const_iterator = iterator;
private:
using gap_t = impl::ObjectPoolGap;
using page_t = impl::ObjectPoolPage<TObject, OBJECTS_PER_PAGE>;
static_assert(sizeof(gap_t) <= sizeof(TObject));
[[no_unique_address]] TAllocator<page_t> allocator_;
page_t* firstPage = nullptr;
public:
explicit ObjectPool(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<page_t>, TAllocator<void>&&>))
: allocator_(std::move(allocator))
{
}
ObjectPool(const ObjectPool&) = delete;
ObjectPool(ObjectPool&&) = default;
ObjectPool& operator=(const ObjectPool&) = delete;
ObjectPool& operator=(ObjectPool&&) = default;
[[nodiscard]]
TObject* allocate(std::size_t count = 1) noexcept
{
MIJIN_ASSERT(count <= OBJECTS_PER_PAGE, "Cannot allocate that many objects at once.");
// first try to find a free spot in the existing pages
for (page_t* page = firstPage; page != nullptr; page = page->nextPage)
{
if (TObject* result = allocateFromPage(*page, count); result != nullptr) {
return result;
}
}
// nothing found in the existing pages, allocate a new one and return memory from there
page_t* newPage = ::new (allocator_.allocate(1)) page_t;
if (firstPage == nullptr)
{
firstPage = newPage;
}
else
{
page_t* lastPage = firstPage;
for (; lastPage->nextPage != nullptr; lastPage = lastPage->nextPage);
lastPage->nextPage = newPage;
}
return allocateFromPage(*newPage, count);
}
void deallocate(TObject* object, std::size_t count = 1) noexcept
{
std::byte* const objectPtr = reinterpret_cast<std::byte*>(object); // for easier comparison
for (page_t* page = firstPage; page != nullptr; page = page->nextPage)
{
// first find the page it's in
std::byte* pageStart = page->data.data();
std::byte* pageEnd = pageStart + (OBJECTS_PER_PAGE * sizeof(TObject));
if (objectPtr >= pageStart && objectPtr <= pageEnd)
{
// then the corresponding gap
if (page->firstGap == nullptr)
{
// everything is used, create a new gap
gap_t* newGap = reinterpret_cast<gap_t*>(objectPtr);
newGap->remainingObjects = count;
newGap->nextGapOffset = 0;
page->firstGap = newGap;
return;
}
for (gap_t* gap = page->firstGap; gap != nullptr; gap = gap->nextGap())
{
std::byte* gapStart = reinterpret_cast<std::byte*>(gap);
std::byte* gapEnd = gap->nextGapOffset == 0 ? pageEnd : gapStart + gap->nextGapOffset;
if (objectPtr > gapStart && objectPtr < gapEnd)
{
if (objectPtr + (count * sizeof(TObject)) == gapEnd)
{
// deallocating right from the end -> just increase the gap remainingObjects
gap->remainingObjects += count;
mergeGaps(gap);
}
else
{
// deallocating from the middle -> create a new gap
gap_t* newGap = reinterpret_cast<gap_t*>(objectPtr);
newGap->remainingObjects = count;
newGap->nextGapOffset = gap->nextGapOffset == 0 ? 0 : (gapEnd - reinterpret_cast<std::byte*>(newGap));
gap->nextGapOffset -= objectPtr - gapStart;
mergeGaps(newGap);
}
return;
}
}
}
} }
} }
}
template<typename... TArgs>
[[nodiscard]]
TObject* create(TArgs&&... args) noexcept
{
TObject* result = allocate();
if (result == nullptr) {
return nullptr;
}
return ::new (result) TObject(std::forward<TArgs>(args)...);
}
[[nodiscard]]
TObject* createMultiple(std::size_t count = 1) noexcept
{
TObject* result = allocate(count);
if (result == nullptr) {
return nullptr;
}
return ::new (result) TObject[count];
}
void destroy(TObject* ptr, std::size_t count = 1) noexcept
{
for (std::size_t idx = 0; idx < count; ++idx) {
ptr[idx].~TObject();
}
deallocate(ptr, count);
}
[[nodiscard]]
iterator begin() const noexcept { return firstPage != nullptr ? iterator(*firstPage) : iterator(); }
[[nodiscard]]
iterator end() const noexcept { return {}; }
private:
TObject* allocateFromPage(page_t& page, std::size_t count)
{
gap_t* previousGap = nullptr;
for (gap_t* gap = page.firstGap; gap != nullptr; previousGap = gap, gap = gap->nextGap())
{
if (gap->remainingObjects == count)
{
// exactly the correct size
if (previousGap != nullptr) {
previousGap->nextGapOffset += gap->nextGapOffset;
}
else {
page.firstGap = gap->nextGap();
}
return reinterpret_cast<TObject*>(gap);
}
else if (gap->remainingObjects > count)
{
// still enough space
TObject* memory = reinterpret_cast<TObject*>(gap) + gap->remainingObjects - count;
gap->remainingObjects -= count;
return reinterpret_cast<TObject*>(memory);
}
}
return nullptr;
}
void mergeGaps(gap_t* start) noexcept
{
while (start->nextGapOffset != 0 && start->remainingObjects == start->nextGapOffset * sizeof(TObject))
{
gap_t& next = *start->nextGap();
start->remainingObjects += start->nextGap()->remainingObjects;
if (next.nextGapOffset == 0) {
start->nextGapOffset = 0;
}
else {
start->nextGapOffset += next.nextGapOffset;
}
}
}
};
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_MEMORY_OBJECT_POOL_HPP_INCLUDED) #endif // !defined(MIJIN_MEMORY_OBJECT_POOL_HPP_INCLUDED)

View File

@@ -0,0 +1,486 @@
#pragma once
#if !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED)
#define MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED 1
#include <memory>
#include <utility>
#include "../debug/assert.hpp"
#include "../internal/common.hpp"
#include "../util/align.hpp"
#include "../util/concepts.hpp"
#include "../util/traits.hpp"
#if !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
#if defined(MIJIN_DEBUG)
#define MIJIN_STACK_ALLOCATOR_DEBUG 1
#else
#define MIJIN_STACK_ALLOCATOR_DEBUG 0
#endif
#endif // !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
#if MIJIN_STACK_ALLOCATOR_DEBUG > 1
#include <print>
#include <unordered_map>
#include "../debug/stacktrace.hpp"
#endif
namespace mijin
{
template<typename TValue, typename TStackAllocator>
class StlStackAllocator
{
public:
using value_type = TValue;
private:
TStackAllocator* base_;
public:
explicit StlStackAllocator(TStackAllocator& base) MIJIN_NOEXCEPT : base_(&base) {}
template<typename TOtherValue>
StlStackAllocator(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT : base_(other.base_) {}
template<typename TOtherValue>
StlStackAllocator& operator=(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT
{
base_ = other.base_;
return *this;
}
auto operator<=>(const StlStackAllocator&) const noexcept = default;
[[nodiscard]]
TValue* allocate(std::size_t count) const
{
void* result = base_->allocate(alignof(TValue), count * sizeof(TValue));
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
++base_->numAllocations_;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
base_->activeAllocations_.emplace(result, captureStacktrace(1));
#endif
return static_cast<TValue*>(result);
}
void deallocate([[maybe_unused]] TValue* ptr, std::size_t /* count */) const MIJIN_NOEXCEPT
{
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
MIJIN_ASSERT(base_->numAllocations_ > 0, "Unbalanced allocations in stack allocators!");
--base_->numAllocations_;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
auto it = base_->activeAllocations_.find(ptr);
if (it != base_->activeAllocations_.end())
{
base_->activeAllocations_.erase(it);
}
else
{
MIJIN_ERROR("Deallocating invalid pointer from StackAllocator.");
}
#endif
}
template<typename TOtherValue, typename TOtherAllocator>
friend class StlStackAllocator;
};
namespace impl
{
struct StackAllocatorSnapshotData
{
struct ChunkSnapshot
{
std::size_t allocated;
};
std::size_t numChunks;
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
std::size_t numAllocations_ = 0;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// just for debugging, so we don't care what memory this uses...
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
#endif
ChunkSnapshot chunks[1]; // may be bigger than that
};
}
class StackAllocatorSnapshot
{
private:
impl::StackAllocatorSnapshotData* data = nullptr;
impl::StackAllocatorSnapshotData* operator->() const MIJIN_NOEXCEPT { return data; }
template<std::size_t chunkSize, template<typename> typename TBacking> requires (allocator_tmpl<TBacking>)
friend class StackAllocator;
};
template<typename TStackAllocator>
class StackAllocatorScope
{
private:
TStackAllocator* allocator_;
StackAllocatorSnapshot snapshot_;
public:
explicit StackAllocatorScope(TStackAllocator& allocator) : allocator_(&allocator), snapshot_(allocator_->createSnapshot()) {}
StackAllocatorScope(const StackAllocatorScope&) = delete;
StackAllocatorScope(StackAllocatorScope&&) = delete;
~StackAllocatorScope() MIJIN_NOEXCEPT
{
allocator_->restoreSnapshot(snapshot_);
}
StackAllocatorScope& operator=(const StackAllocatorScope&) = delete;
StackAllocatorScope& operator=(StackAllocatorScope&&) = delete;
};
template<std::size_t chunkSize = 4096, template<typename> typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl<TBacking>)
class StackAllocator
{
public:
using backing_t = TBacking<void>;
static constexpr std::size_t ACTUAL_CHUNK_SIZE = chunkSize - sizeof(void*) - sizeof(std::size_t);
template<typename T>
using stl_allocator_t = StlStackAllocator<T, StackAllocator<chunkSize, TBacking>>;
private:
struct Chunk
{
std::array<std::byte, ACTUAL_CHUNK_SIZE> data;
Chunk* next;
std::size_t allocated;
};
[[no_unique_address]] TBacking<Chunk> backing_;
Chunk* firstChunk_ = nullptr;
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
std::size_t numAllocations_ = 0;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// just for debugging, so we don't care what memory this uses...
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
#endif
public:
StackAllocator() MIJIN_NOEXCEPT_IF(std::is_nothrow_default_constructible_v<backing_t>) = default;
explicit StackAllocator(backing_t backing) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TBacking<Chunk>, backing_t&&>))
: backing_(std::move(backing)) {}
StackAllocator(const StackAllocator&) = delete;
StackAllocator(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TBacking<Chunk>>)
: firstChunk_(std::exchange(other.firstChunk_, nullptr)) {}
~StackAllocator() noexcept
{
Chunk* chunk = firstChunk_;
while (chunk != nullptr)
{
Chunk* nextChunk = firstChunk_->next;
backing_.deallocate(chunk, 1);
chunk = nextChunk;
}
}
StackAllocator& operator=(const StackAllocator&) = delete;
StackAllocator& operator=(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v<TBacking<Chunk>>)
{
if (this != &other)
{
backing_ = std::move(other.backing_);
firstChunk_ = std::exchange(other.firstChunk_, nullptr);
}
return *this;
}
void* allocate(std::size_t alignment, std::size_t size)
{
// first check if this can ever fit
if (size > ACTUAL_CHUNK_SIZE)
{
return nullptr;
}
// then try to find space in the current chunks
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
const std::size_t remaining = ACTUAL_CHUNK_SIZE - chunk->allocated;
if (remaining < size)
{
continue;
}
std::byte* start = &chunk->data[chunk->allocated];
std::byte* pos = mijin::alignUp(start, alignment);
const std::ptrdiff_t alignmentBytes = pos - start;
const std::size_t combinedSize = size + alignmentBytes;
if (remaining < combinedSize)
{
continue;
}
chunk->allocated += combinedSize;
return pos;
}
// no free space in any chunk? allocate a new one
Chunk* newChunk = backing_.allocate(1);
if (newChunk == nullptr)
{
return nullptr;
}
initAndAddChunk(newChunk);
// now try with the new chunk
std::byte* start = newChunk->data.data();
std::byte* pos = mijin::alignUp(start, alignment);
const std::ptrdiff_t alignmentBytes = pos - start;
const std::size_t combinedSize = size + alignmentBytes;
// doesn't fit (due to alignment), time to give up
if (ACTUAL_CHUNK_SIZE < combinedSize)
{
return nullptr;
}
newChunk->allocated = combinedSize;
return pos;
}
void reset() noexcept
{
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
MIJIN_ASSERT(numAllocations_ == 0, "Missing deallocation in StackAllocator!");
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
if (!activeAllocations_.empty())
{
std::println(stderr, "{} active allocations in StackAllocator when resetting!", activeAllocations_.size());
for (const auto& [ptr, stack] : activeAllocations_)
{
if (stack.isError())
{
std::println(stderr, "at {}, no stacktrace ({})", ptr, stack.getError().message);
}
else
{
std::println(stderr, "at 0x{}:\n{}", ptr, stack.getValue());
}
}
MIJIN_TRAP();
}
#endif
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
chunk->allocated = 0;
}
}
bool createChunks(std::size_t count)
{
if (count == 0)
{
return true;
}
Chunk* newChunks = backing_.allocate(count);
if (newChunks == nullptr)
{
return false;
}
// reverse so the chunks are chained from 0 to count (new chunks are inserted in front)
for (std::size_t pos = count; pos > 0; --pos)
{
initAndAddChunk(&newChunks[pos-1]);
}
return true;
}
template<typename T>
stl_allocator_t<T> makeStlAllocator() MIJIN_NOEXCEPT
{
return stl_allocator_t<T>(*this);
}
[[nodiscard]]
std::size_t getNumChunks() const MIJIN_NOEXCEPT
{
std::size_t num = 0;
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
++num;
}
return num;
}
[[nodiscard]]
StackAllocatorSnapshot createSnapshot()
{
if (firstChunk_ == nullptr)
{
return {};
}
using impl::StackAllocatorSnapshotData;
std::size_t numChunks = getNumChunks();
std::size_t snapshotSize = calcSnapshotSize(numChunks);
Chunk* prevFirst = firstChunk_;
StackAllocatorSnapshotData* snapshotData = static_cast<StackAllocatorSnapshotData*>(allocate(alignof(StackAllocatorSnapshotData), snapshotSize));
if (snapshotData == nullptr)
{
// couldn't allocate the snapshot
return {};
}
::new (snapshotData) StackAllocatorSnapshotData;
StackAllocatorSnapshot snapshot;
snapshot.data = snapshotData;
if (firstChunk_ != prevFirst)
{
// new chunk has been added, adjust the snapshot
// the snapshot must be inside the new chunk (but not necessarily at the very beginning, due to alignment)
MIJIN_ASSERT(static_cast<const void*>(snapshot.data) == alignUp(firstChunk_->data.data(), alignof(StackAllocatorSnapshot)), "Snapshot not where it was expected.");
// a chunk might be too small for the snapshot if we grow it (unlikely, but not impossible)
if (ACTUAL_CHUNK_SIZE - firstChunk_->allocated < MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot)) [[unlikely]]
{
firstChunk_->allocated = 0;
return {};
}
// looking good, adjust the numbers
++numChunks;
snapshotSize += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
firstChunk_->allocated += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
}
// now fill out the struct
snapshot->numChunks = numChunks;
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
snapshot->numAllocations_ = numAllocations_;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// just for debugging, so we don't care what memory this uses...
snapshot->activeAllocations_ = activeAllocations_;
#endif
std::size_t pos = 0;
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next, ++pos)
{
snapshot->chunks[pos].allocated = chunk->allocated;
}
return snapshot;
}
void restoreSnapshot(StackAllocatorSnapshot snapshot) MIJIN_NOEXCEPT
{
if (snapshot.data == nullptr)
{
return;
}
const std::size_t numChunks = getNumChunks();
MIJIN_ASSERT_FATAL(snapshot->numChunks <= numChunks, "Snapshot contains more chunks than the allocator!");
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
MIJIN_ASSERT(snapshot->numAllocations_ >= numAllocations_, "Missing deallocation in StackAllocator!");
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// TODO: compare and print changes
unsigned numMismatches = 0;
for (const auto& [ptr, stack] : activeAllocations_)
{
if (snapshot->activeAllocations_.contains(ptr))
{
continue;
}
++numMismatches;
if (stack.isError())
{
std::println(stderr, "Missing deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
}
else
{
std::println(stderr, "Missing deallocation at 0x{}:\n{}", ptr, stack.getValue());
}
}
#if 0 // deallocating more than expected shouldn't be a problem
for (const auto& [ptr, stack] : snapshot->activeAllocations_)
{
if (activeAllocations_.contains(ptr))
{
continue;
}
++numMismatches;
if (stack.isError())
{
std::println(stderr, "Unexpected deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
}
else
{
std::println(stderr, "Unexpected deallocation at 0x{}:\n{}", ptr, stack.getValue());
}
}
#endif
if (numMismatches > 0)
{
std::println(stderr, "{} mismatched deallocations when restoring stack allocator snapshot.", numMismatches);
MIJIN_TRAP();
}
#endif
// if we allocated new chunks since the snapshot, these are completely empty now
const std::size_t emptyChunks = numChunks - snapshot->numChunks;
Chunk* chunk = firstChunk_;
for (std::size_t idx = 0; idx < emptyChunks; ++idx)
{
chunk->allocated = 0;
chunk = chunk->next;
}
// the other values are in the snapshot
for (std::size_t idx = 0; idx < snapshot->numChunks; ++idx)
{
chunk->allocated = snapshot->chunks[idx].allocated;
chunk = chunk->next;
}
MIJIN_ASSERT(chunk == nullptr, "Something didn't add up.");
// finally free the space for the snapshot itself
Chunk* snapshotChunk = findChunk(snapshot.data);
MIJIN_ASSERT_FATAL(snapshotChunk != nullptr, "Snapshot not in chunks?");
snapshotChunk->allocated -= calcSnapshotSize(snapshot->numChunks); // note: this might miss the alignment bytes of the snapshot, but that should be fine
snapshot.data->~StackAllocatorSnapshotData();
}
private:
void initAndAddChunk(Chunk* newChunk) noexcept
{
::new (newChunk) Chunk();
// put it in the front
newChunk->next = firstChunk_;
firstChunk_ = newChunk;
}
bool isInChunk(const void* address, const Chunk& chunk) const MIJIN_NOEXCEPT
{
const std::byte* asByte = static_cast<const std::byte*>(address);
return asByte >= chunk.data.data() && asByte < chunk.data.data() + ACTUAL_CHUNK_SIZE;
}
Chunk* findChunk(const void* address) const MIJIN_NOEXCEPT
{
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
if (isInChunk(address, *chunk))
{
return chunk;
}
}
return nullptr;
}
static std::size_t calcSnapshotSize(std::size_t numChunks) MIJIN_NOEXCEPT
{
return sizeof(impl::StackAllocatorSnapshotData) + ((numChunks - 1) * MIJIN_STRIDEOF(impl::StackAllocatorSnapshotData::ChunkSnapshot));
}
template<typename TValue, typename TStackAllocator>
friend class StlStackAllocator;
};
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED)

View File

@@ -0,0 +1,56 @@
#pragma once
#if !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED)
#define MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED 1
#include "../internal/common.hpp"
#include "../util/annot.hpp"
namespace mijin
{
template<typename T>
class VirtualAllocator
{
public:
virtual ~VirtualAllocator() noexcept = default;
[[nodiscard]]
virtual owner_t<T*> allocate(std::size_t count) noexcept;
virtual void deallocate(owner_t<T*> ptr, std::size_t count) noexcept;
};
template<typename T, typename TImpl>
class WrappedVirtualAllocator : public VirtualAllocator<T>
{
private:
[[no_unique_address]] TImpl mImpl;
public:
explicit constexpr WrappedVirtualAllocator(TImpl impl = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TImpl>)
: mImpl(std::move(impl)) {}
constexpr WrappedVirtualAllocator(const WrappedVirtualAllocator&) = default;
constexpr WrappedVirtualAllocator(WrappedVirtualAllocator&&) = default;
WrappedVirtualAllocator& operator=(const WrappedVirtualAllocator&) = default;
WrappedVirtualAllocator& operator=(WrappedVirtualAllocator&&) = default;
[[nodiscard]]
owner_t<T*> allocate(std::size_t count) noexcept override
{
return mImpl.allocate(count);
}
void deallocate(owner_t<T*> ptr, std::size_t count) noexcept override
{
mImpl.deallocate(ptr, count);
}
};
template<typename T>
WrappedVirtualAllocator<typename T::value_type, T> makeVirtualAllocator(T allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
{
return WrappedVirtualAllocator<typename T::value_type, T>(std::move(allocator));
}
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED)

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;
BaseTypelessBuffer<std::allocator> body;
};
struct HTTPResponse
{
HTTPVersion version;
unsigned status;
std::string statusMessage;
std::multimap<std::string, std::string> headers;
BaseTypelessBuffer<std::allocator> body;
};
class HTTPStream
{
private:
Stream* base_;
public:
HTTPStream(Stream& base) MIJIN_NOEXCEPT : base_(&base)
{
MIJIN_ASSERT(base_ != nullptr, "Invalid parameter for base.");
}
Task<StreamResult<HTTPResponse>> c_request(HTTPRequest request) MIJIN_NOEXCEPT;
private:
Task<StreamError> c_writeRequest(const HTTPRequest& request) MIJIN_NOEXCEPT;
Task<StreamResult<HTTPResponse>> c_readResponse() MIJIN_NOEXCEPT;
};
class HTTPClient
{
private:
std::unique_ptr<Socket> socket_;
std::unique_ptr<Stream> sslStream_;
mijin::BoxedObject<HTTPStream> stream_;
ip_address_t lastIP_;
std::uint16_t lastPort_ = 0;
bool lastWasHttps_ = false;
public:
~HTTPClient() MIJIN_NOEXCEPT { disconnect(); }
Task<StreamResult<HTTPResponse>> c_request(ip_address_t address, std::uint16_t port, bool https, HTTPRequest request) MIJIN_NOEXCEPT;
Task<StreamResult<HTTPResponse>> c_request(const URL& url, HTTPRequest request = {}) MIJIN_NOEXCEPT;
void disconnect() MIJIN_NOEXCEPT;
private:
StreamError createSocket(ip_address_t address, const std::string& hostname, std::uint16_t port, bool https) MIJIN_NOEXCEPT;
};
}
#endif // !defined(MIJIN_NET_HTTP_HPP_INCLUDED)

300
source/mijin/net/ip.cpp Normal file
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 = MIJIN_DEFAULT_ALLOCATOR<TChar>>
class URLBase
{
public:
using string_t = std::basic_string<TChar, TTraits, TAllocator>;
using string_view_t = std::basic_string_view<TChar, TTraits>;
private:
string_t base_;
string_view_t scheme_;
string_view_t userinfo_;
string_view_t host_;
string_view_t path_;
string_view_t query_;
string_view_t fragment_;
string_view_t pathQueryFragment_;
std::uint16_t port_ = 0;
public:
constexpr URLBase() MIJIN_NOEXCEPT = default;
constexpr URLBase(const URLBase&) = default;
constexpr URLBase(URLBase&&) MIJIN_NOEXCEPT = default;
constexpr URLBase(string_t base) MIJIN_NOEXCEPT : base_(std::move(base)) { parse(); }
constexpr URLBase(string_view_t base, TAllocator allocator = {}) : URLBase(string_t(base.begin(), base.end(), std::move(allocator))) {}
constexpr URLBase(const TChar* base, TAllocator allocator = {}) : URLBase(string_t(base, std::move(allocator))) {}
constexpr URLBase& operator=(const URLBase&) = default;
constexpr URLBase& operator=(URLBase&&) MIJIN_NOEXCEPT = default;
[[nodiscard]] constexpr bool isValid() const MIJIN_NOEXCEPT { return !base_.empty(); }
[[nodiscard]] constexpr const string_t& getBase() const MIJIN_NOEXCEPT { return base_; }
[[nodiscard]] constexpr string_view_t getScheme() const MIJIN_NOEXCEPT { return scheme_; }
[[nodiscard]] constexpr string_view_t getUserInfo() const MIJIN_NOEXCEPT { return userinfo_; }
[[nodiscard]] constexpr string_view_t getHost() const MIJIN_NOEXCEPT { return host_; }
[[nodiscard]] constexpr string_view_t getPath() const MIJIN_NOEXCEPT { return path_; }
[[nodiscard]] constexpr string_view_t getQuery() const MIJIN_NOEXCEPT { return query_; }
[[nodiscard]] constexpr string_view_t getFragment() const MIJIN_NOEXCEPT { return fragment_; }
[[nodiscard]] constexpr string_view_t getPathQueryFragment() const MIJIN_NOEXCEPT { return pathQueryFragment_; }
[[nodiscard]] constexpr std::uint16_t getPort() const MIJIN_NOEXCEPT { return port_; }
constexpr void clear() MIJIN_NOEXCEPT;
private:
constexpr void parse() MIJIN_NOEXCEPT;
constexpr bool parseAuthority(string_view_t authority) MIJIN_NOEXCEPT;
};
using URL = URLBase<char>;
template<typename TChar, typename TTraits, typename TAllocator>
constexpr void URLBase<TChar, TTraits, TAllocator>::clear() MIJIN_NOEXCEPT
{
base_.clear();
scheme_ = {};
userinfo_ = {};
host_ = {};
path_ = {};
query_ = {};
fragment_ = {};
port_ = 0;
}
template<typename TChar, typename TTraits, typename TAllocator>
constexpr void URLBase<TChar, TTraits, TAllocator>::parse() MIJIN_NOEXCEPT
{
if (base_.empty())
{
return;
}
string_view_t toParse = base_;
typename string_view_t::size_type pos = toParse.find(TChar(':'));
if (pos == string_t::npos)
{
clear();
return;
}
scheme_ = toParse.substr(0, pos);
toParse = toParse.substr(pos + 1);
const TChar DOUBLE_SLASH[] = {TChar('/'), TChar('/'), TChar(0)};
if (!toParse.starts_with(DOUBLE_SLASH))
{
userinfo_ = host_ = {};
port_ = 0;
}
else
{
toParse = toParse.substr(2); // skip the slashes
pos = toParse.find(TChar('/'));
if (!parseAuthority(toParse.substr(0, pos)))
{
clear();
return;
}
if (pos == string_view_t::npos)
{
path_ = query_ = fragment_ = pathQueryFragment_ = {};
return;
}
toParse = toParse.substr(pos);
}
pathQueryFragment_ = toParse;
pos = toParse.find(TChar('#'));
if (pos == string_view_t::npos)
{
fragment_ = {};
}
else
{
fragment_ = toParse.substr(pos + 1);
toParse = toParse.substr(0, pos);
}
pos = toParse.find(TChar('?'));
if (pos == string_view_t::npos)
{
query_ = {};
path_ = toParse;
}
else
{
query_ = toParse.substr(pos + 1);
path_ = toParse.substr(0, pos);
}
}
template<typename TChar, typename TTraits, typename TAllocator>
constexpr bool URLBase<TChar, TTraits, TAllocator>::parseAuthority(string_view_t authority) MIJIN_NOEXCEPT
{
string_view_t toParse = authority;
typename string_view_t::size_type pos = toParse.find(TChar('@'));
if (pos == string_view_t::npos)
{
userinfo_ = {};
}
else
{
userinfo_ = toParse.substr(0, pos);
toParse = toParse.substr(pos + 1);
}
pos = toParse.find(TChar(':')); // TODO: IPv6
if (pos == string_view_t::npos)
{
port_ = 0;
host_ = toParse;
}
else
{
if (!toNumber(toParse.substr(pos + 1), port_))
{
return false;
}
host_ = toParse.substr(0, pos);
}
return true;
}
}
#endif // !defined(MIJIN_NET_URL_HPP_INCLUDED)

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

@@ -0,0 +1,40 @@
#include "./path.hpp"
#include <filesystem>
namespace fs = std::filesystem;
namespace mijin
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
//
// internal variables
//
//
// internal functions
//
//
// public functions
//
NativePath getWorkingDirectory()
{
return fs::current_path().generic_string();
}
} // namespace mijin

511
source/mijin/types/path.hpp Normal file
View File

@@ -0,0 +1,511 @@
#pragma once
#if !defined(MIJIN_TYPES_PATH_HPP_INCLUDED)
#define MIJIN_TYPES_PATH_HPP_INCLUDED 1
#include "../debug/assert.hpp"
#include "../internal/common.hpp"
#include "../util/traits.hpp"
#include <ranges>
#include <string>
#include <type_traits>
namespace mijin
{
template<typename T>
concept BasePathType = requires(const T& path)
{
typename T::traits_t;
typename T::char_t;
typename T::char_traits_t;
typename T::allocator_t;
typename T::string_t;
typename T::string_view_t;
typename T::size_type;
typename T::difference_type;
typename T::path_view_t;
{ T::SEPARATOR } -> std::convertible_to<typename T::char_t>;
{ path.stringView() } -> std::convertible_to<typename T::string_view_t>;
};
template<typename T>
concept PathType = BasePathType<T> && std::is_same_v<typename T::char_t, char>;
template<typename T>
concept WPathType = BasePathType<T> && std::is_same_v<typename T::char_t, wchar_t>;
template<typename TChar, TChar separator = TChar('/')>
struct DefaultPathTraits
{
using char_t = TChar;
using char_traits_t = std::char_traits<TChar>;
using allocator_t = std::allocator<TChar>;
using string_t = std::basic_string<char_t, char_traits_t, allocator_t>;
using string_view_t = std::basic_string_view<char_t, char_traits_t>;
using size_type = string_view_t::size_type;
using difference_type = string_view_t::difference_type;
static constexpr char_t SEPARATOR = separator;
static constexpr bool isAbsolute(string_view_t path) MIJIN_NOEXCEPT
{
return !path.empty() && path[0] == SEPARATOR;
}
};
template<typename TChar = char, typename TTraits = DefaultPathTraits<TChar>>
class BasePathView;
template<typename TConcrete, typename TChar = char, typename TTraits = DefaultPathTraits<TChar>>
class MixinPath;
namespace impl
{
template<typename TChar, typename TTraits, TChar separator>
class BasePathIterator
{
private:
std::string_view full_;
std::string_view::iterator pos_;
std::string_view::iterator next_;
BasePathIterator(std::string_view full, std::string_view::iterator pos) MIJIN_NOEXCEPT : full_(full), pos_(pos) {
if (pos != full_.end() && *pos == separator) {
++pos;
}
findNext();
}
public:
BasePathIterator() MIJIN_NOEXCEPT : pos_(full_.end()), next_(full_.end()) {}
BasePathIterator(const BasePathIterator&) noexcept = default;
BasePathIterator& operator=(const BasePathIterator&) noexcept = default;
bool operator==(const BasePathIterator& other) MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ == other.pos_; }
bool operator!=(const BasePathIterator& other) MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ != other.pos_; }
bool operator<(const BasePathIterator& other) MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ < other.pos_; }
bool operator<=(const BasePathIterator& other) MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ <= other.pos_; }
bool operator>(const BasePathIterator& other) MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ > other.pos_; }
bool operator>=(const BasePathIterator& other) MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ >= other.pos_; }
std::string_view operator*() const MIJIN_NOEXCEPT
{
MIJIN_ASSERT(pos_ != full_.end(), "Dereferencing an invalid iterator.");
return {pos_, next_};
}
BasePathIterator& operator++() MIJIN_NOEXCEPT
{
MIJIN_ASSERT(pos_ != full_.end(), "Iterating past end.");
if (next_ == full_.end()) {
pos_ = full_.end();
}
else
{
pos_ = std::next(next_);
findNext();
}
return *this;
}
BasePathIterator operator++(int) const MIJIN_NOEXCEPT
{
BasePathIterator copy(*this);
++copy;
return copy;
}
// TODO
// BasePathIterator& operator--() MIJIN_NOEXCEPT
// {
// MIJIN_ASSERT(pos_ != full_.begin(), "Iterating past begin.");
// next_ = std::prev(pos_);
// pos_ = std::find(std::reverse_iterator(next_), std::reverse_iterator(full_.begin()), separator).base();
// }
private:
void findNext()
{
next_ = std::find(pos_, full_.end(), separator);
}
template<typename TConcrete, typename TCharOther, typename TTraitsOther>
friend class ::mijin::MixinPath;
};
}
template<typename TConcrete, typename TChar, typename TTraits>
class MixinPath
{
public:
using traits_t = TTraits;
using char_t = typename traits_t::char_t;
using char_traits_t = typename traits_t::char_traits_t;
using allocator_t = typename traits_t::allocator_t;
using string_t = typename traits_t::string_t;
using string_view_t = typename traits_t::string_view_t;
using size_type = typename traits_t::size_type;
using difference_type = typename traits_t::difference_type;
using path_view_t = BasePathView<TChar, traits_t>;
using iterator = impl::BasePathIterator<char_t, char_traits_t, traits_t::SEPARATOR>;
using const_iterator = iterator;
static constexpr char_t SEPARATOR = traits_t::SEPARATOR;
constexpr bool operator==(string_view_t stringView) const MIJIN_NOEXCEPT { return stringView() == stringView; }
constexpr bool operator==(const TChar* cString) const MIJIN_NOEXCEPT { return stringView() == cString; }
[[nodiscard]]
constexpr string_view_t getName() const MIJIN_NOEXCEPT;
[[nodiscard]]
constexpr path_view_t getParent() const MIJIN_NOEXCEPT;
[[nodiscard]]
constexpr string_view_t stringView() const MIJIN_NOEXCEPT
{
return static_cast<const TConcrete*>(this)->stringViewImpl();
}
[[nodiscard]]
constexpr bool empty() const MIJIN_NOEXCEPT { return stringView().empty(); }
[[nodiscard]]
constexpr size_type size() const MIJIN_NOEXCEPT { return stringView().size(); }
[[nodiscard]]
constexpr bool isAbsolute() const MIJIN_NOEXCEPT
{
return traits_t::isAbsolute(stringView());
}
[[nodiscard]]
iterator begin() const MIJIN_NOEXCEPT { return iterator(stringView(), stringView().begin()); }
[[nodiscard]]
iterator end() const MIJIN_NOEXCEPT { return iterator(stringView(), stringView().end()); }
private:
template<typename TConcreteOther, typename TCharOther, typename TTraitsOther>
friend class MixinPath;
};
template<typename TConcreteA, typename TConcreteB, typename TChar, typename TTraits>
constexpr bool operator==(const MixinPath<TConcreteA, TChar, TTraits>& pathA, const MixinPath<TConcreteB, TChar, TTraits>& pathB) MIJIN_NOEXCEPT
{
return pathA.stringView() == pathB.stringView();
}
template<typename TChar = char, typename TTraits = DefaultPathTraits<TChar>>
class BasePath : public MixinPath<BasePath<TChar, TTraits>, TChar, TTraits>
{
public:
using mixin_t = MixinPath<BasePath<TChar, TTraits>, TChar, TTraits>;
using typename mixin_t::traits_t;
using typename mixin_t::char_t;
using typename mixin_t::char_traits_t;
using typename mixin_t::allocator_t;
using typename mixin_t::string_t;
using typename mixin_t::string_view_t;
using typename mixin_t::difference_type;
using typename mixin_t::path_view_t;
static constexpr char_t SEPARATOR = mixin_t::SEPARATOR;
private:
struct NoSimplify {};
string_t storage_;
constexpr BasePath(string_t storage, NoSimplify) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible<string_t>()) : storage_(std::move(storage)) {}
public:
constexpr BasePath() = default;
constexpr BasePath(const BasePath&) = default;
constexpr BasePath(BasePath&&) = default;
constexpr BasePath(string_t string) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible<string_t>()) : storage_(std::move(string)) {
simplify();
}
constexpr BasePath(const char_t* cString, allocator_t allocator = {}) : BasePath(string_t(cString, std::move(allocator))) {}
constexpr BasePath(string_view_t stringView, allocator_t allocator = {}) : BasePath(string_t(stringView, std::move(allocator))) {}
template<typename TConcreteOther> requires (!std::is_same_v<BasePath<TChar, TTraits>, TConcreteOther>)
explicit constexpr BasePath(const MixinPath<TConcreteOther, TChar, TTraits> other, allocator_t allocator = {}) : BasePath(string_t(other.stringView(), std::move(allocator)), NoSimplify()) {}
constexpr BasePath& operator=(const BasePath&) = default;
constexpr BasePath& operator=(BasePath&&) = default;
constexpr auto operator<=>(const BasePath&) const noexcept = default;
using mixin_t::operator==;
template<typename TConcreteOther>
BasePath& operator/=(const MixinPath<TConcreteOther, char_t, traits_t>& other);
BasePath& operator/=(string_view_t more);
template<typename TConcreteOther>
BasePath operator/(const MixinPath<TConcreteOther, char_t, traits_t>& other) const;
BasePath operator/(string_view_t more) const;
[[nodiscard]]
constexpr const string_t& string() const MIJIN_NOEXCEPT
{
return storage_;
}
[[nodiscard]]
constexpr string_view_t stringViewImpl() const MIJIN_NOEXCEPT
{
return storage_;
}
private:
constexpr void simplify() MIJIN_NOEXCEPT;
};
using Path = BasePath<char>;
using WPath = BasePath<wchar_t>;
using NativePath = Path; // TODO
template<typename TChar, typename TTraits>
class BasePathView : public MixinPath<BasePathView<TChar, TTraits>, TChar, TTraits>
{
public:
using mixin_t = MixinPath<BasePathView<TChar, TTraits>, TChar, TTraits>;
using typename mixin_t::char_t;
using typename mixin_t::string_view_t;
private:
string_view_t view_;
public:
constexpr BasePathView() noexcept = default;
constexpr BasePathView(const BasePathView&) noexcept = default;
explicit constexpr BasePathView(string_view_t view) MIJIN_NOEXCEPT : view_(view) {}
template<typename TIterator>
constexpr BasePathView(const TIterator& begin, const TIterator& end) MIJIN_NOEXCEPT : view_(begin, end) {}
template<typename TConcreteOther> requires (!std::is_same_v<BasePathView<TChar, TTraits>, TConcreteOther>)
constexpr BasePathView(const MixinPath<TConcreteOther>& other) MIJIN_NOEXCEPT : view_(other.stringView()) {}
constexpr BasePathView& operator=(const BasePathView&) noexcept = default;
constexpr auto operator<=>(const BasePathView&) const noexcept = default;
using mixin_t::operator==;
[[nodiscard]]
constexpr string_view_t stringViewImpl() const MIJIN_NOEXCEPT
{
return view_;
}
};
using PathView = BasePathView<char>;
using WPathView = BasePathView<wchar_t>;
using NativePathView = PathView; // TODO
template<typename TConcrete, typename TChar, typename TTraits>
constexpr auto MixinPath<TConcrete, TChar, TTraits>::getName() const MIJIN_NOEXCEPT -> string_view_t
{
const string_view_t strView = stringView();
auto it = std::ranges::find(std::ranges::reverse_view(strView), SEPARATOR).base();
return {it, strView.end()};
}
template<typename TConcrete, typename TChar, typename TTraits>
constexpr auto MixinPath<TConcrete, TChar, TTraits>::getParent() const MIJIN_NOEXCEPT -> path_view_t
{
const string_view_t strView = stringView();
auto it = std::ranges::find(std::ranges::reverse_view(strView), SEPARATOR).base();
if (it == strView.begin()) {
return {};
}
if (std::prev(it) == strView.begin()) {
return {strView.begin(), it}; // edge case, return "/" instead of nothing
}
return {strView.begin(), std::prev(it)};
}
template<typename TChar, typename TTraits>
template<typename TConcreteOther>
auto BasePath<TChar, TTraits>::operator/=(const MixinPath<TConcreteOther, char_t, traits_t>& other) -> BasePath&
{
if (other.isAbsolute())
{
storage_ = other.stringView();
}
else if (!other.empty())
{
if (other.stringView()[0] != SEPARATOR)
{
storage_.reserve(storage_.size() + other.size() + 1);
storage_.push_back(SEPARATOR);
}
storage_.append_range(other.stringView());
}
return *this;
}
template<typename TChar, typename TTraits>
auto BasePath<TChar, TTraits>::operator/=(string_view_t more) -> BasePath&
{
operator/=(path_view_t(more));
simplify();
return *this;
}
template<typename TChar, typename TTraits>
template<typename TConcreteOther>
auto BasePath<TChar, TTraits>::operator/(const MixinPath<TConcreteOther, char_t, traits_t>& other) const -> BasePath
{
if (other.isAbsolute() || other.empty())
{
return BasePath(string_t(other.stringView(), storage_.get_allocator()), NoSimplify());
}
const bool addSeparator = other.stringView()[0] != SEPARATOR;
string_t combined(storage_.get_allocator());
combined.reserve(storage_.size() + other.stringView().size() + (addSeparator ? 1 : 0));
combined.append_range(storage_);
if (addSeparator) {
combined.push_back(SEPARATOR);
}
combined.append_range(other.stringView());
return BasePath(std::move(combined), NoSimplify());
}
template<typename TChar, typename TTraits>
auto BasePath<TChar, TTraits>::operator/(string_view_t other) const -> BasePath
{
BasePath combined = (*this / path_view_t(other));
combined.simplify();
return combined;
}
template<typename TChar, typename TTraits>
constexpr void BasePath<TChar, TTraits>::simplify() MIJIN_NOEXCEPT
{
// step 1: remove double slashes
difference_type moveBy = 0;
for (auto it = std::next(storage_.begin()); it < storage_.end(); ++it)
{
const bool doubleSlash = (*it == SEPARATOR && *std::prev(it) == SEPARATOR); // check this before moving the current character, as that might create a double slash itself
if (moveBy > 0) {
*std::prev(it, moveBy) = *it;
}
if (doubleSlash) {
++moveBy;
}
}
// step 1.5: remove trailing slash (but only if it's not the only remaining char)
if (moveBy < static_cast<difference_type>(storage_.size() - 1) && storage_[storage_.size() - moveBy - 1] == SEPARATOR) {
++moveBy;
}
storage_.resize(storage_.size() - moveBy);
// step 2: get rid of any "/.." together with the preceeding segment
moveBy = 0;
for (auto it = std::next(storage_.begin(), 2); it < storage_.end(); ++it)
{
if (moveBy > 0)
{
*std::prev(it, moveBy) = *it;
}
if (*std::prev(it, moveBy) == TChar('.') && *std::prev(it, moveBy + 1) == TChar('.') && *std::prev(it, moveBy + 2) == SEPARATOR
&& (std::next(it) == storage_.end() || *std::next(it) == SEPARATOR))
{
if (std::prev(it, moveBy + 2) == storage_.begin())
{
// leading "/.." -> just remove it
moveBy += 3;
continue;
}
// find the start of the preceeding segment
for (auto itStart = std::prev(it, moveBy + 3);; --itStart)
{
if (*std::prev(itStart) == SEPARATOR || std::prev(itStart) == storage_.begin())
{
// /path/with/../double/dot
// itStart --A A
// it -------------|
// remove everything from itStart to it + two slashes
moveBy += std::distance(itStart, std::prev(it, moveBy)) + 2; // skip it all
break;
}
}
}
}
storage_.resize(storage_.size() - moveBy);
// step 3: eliminate any segments that are just "."
moveBy = 0;
if (storage_.size() == 1) {
return; // just stop it here
}
for (auto it = storage_.begin(); it < storage_.end(); ++it)
{
const bool atStart = (it == storage_.begin());
const bool atEnd = (std::next(it) == storage_.end());
const bool emptyEle = (*it == TChar('.') // char is a dot
&& (atStart || *std::prev(it, moveBy + 1) == SEPARATOR) // previous is a slash or doesn't exist
&& (atEnd || *std::next(it) == SEPARATOR)); // next is a slash or doesn't exist
if (moveBy > 0) {
*std::prev(it, moveBy) = *it;
}
if (emptyEle) {
moveBy += 2;
if (!atEnd) {
++it; // skip the next one
}
}
}
storage_.resize(storage_.size() - moveBy);
}
template<typename TChar, TChar separator = TChar('/'), typename TTraits = std::char_traits<TChar>>
constexpr bool verifyPathString(std::basic_string_view<TChar, TTraits> stringView) MIJIN_NOEXCEPT
{
for (auto it = std::next(stringView.begin()); it < stringView.end(); ++it)
{
if(*it == separator && *std::prev(it) == separator) {
return false; // double slash
}
if (it != std::next(stringView.begin())
&& *it == TChar('.') && *std::prev(it) == TChar('.') && *std::prev(it, 2) == separator
&& (std::next(it) == stringView.end() || *std::next(it) == separator)) {
return false; // "/.."
}
const bool atStart = (it == stringView.begin());
const bool atEnd = (std::next(it) == stringView.end());
if(*it == TChar('.') // char is a dot
&& (atStart || *std::prev(it) == separator) // previous is a slash or doesn't exist
&& (atEnd || *std::next(it) == separator)) // next is a slash or doesn't exist
{
return false; // "/."
}
}
return true;
}
consteval PathView operator""_pv(const char* cString, std::size_t len)
{
if (!verifyPathString(std::string_view(cString, len))) {
throw "Invalid path string.";
}
return PathView(std::string_view(cString, len));
}
consteval WPathView operator""_pv(const wchar_t* cString, std::size_t len)
{
if (!verifyPathString(std::wstring_view(cString, len))) {
throw "Invalid path string.";
}
return WPathView(std::wstring_view(cString, len));
}
[[nodiscard]]
NativePath getWorkingDirectory();
} // namespace mijin
template<mijin::BasePathType TPath>
struct std::hash<TPath>
{
std::size_t operator()(const TPath& path) const MIJIN_NOEXCEPT
{
return std::hash<std::string_view>()(path.stringView());
}
};
#endif // !defined(MIJIN_TYPES_PATH_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,14 @@
#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 <bit>
#include <cstdint>
#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 +20,13 @@ constexpr T alignUp(T value, T alignTo) noexcept
return value; return value;
} }
template<typename T>
T* alignUp(T* pointer, std::uintptr_t alignTo) MIJIN_NOEXCEPT
{
return std::bit_cast<T*>(alignUp(std::bit_cast<std::uintptr_t>(pointer), alignTo));
}
#define MIJIN_STRIDEOF(T) mijin::alignUp(sizeof(T), alignof(T))
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED) #endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)

192
source/mijin/util/annot.hpp Normal file
View File

@@ -0,0 +1,192 @@
#pragma once
#if !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)
#define MIJIN_UTIL_ANNOT_HPP_INCLUDED 1
#include <utility>
#include "../internal/common.hpp"
#include "../debug/assert.hpp"
#if !defined(__has_include)
#define __has_include(x) (false)
#endif
#if !defined(MIJIN_USE_GSL)
#if __has_include(<gsl/gsl>)
#define MIJIN_USE_GSL 1
#else
#define MIJIN_USE_GSL 0
#endif
#endif // !defined(MIJIN_USE_GSL)
#include <concepts>
#include "./concepts.hpp"
#if MIJIN_USE_GSL
#include <gsl/gsl>
#endif
namespace mijin
{
template<typename T>
concept nullable_type = !std::is_same_v<T, std::nullptr_t> && requires(T t) { t == nullptr; };
template<nullable_type T>
class NotNullable
{
public:
using wrapped_t = T;
using element_type = std::remove_reference_t<decltype(*std::declval<const wrapped_t>())>;
using pointer = element_type*;
private:
T base_;
public:
template<typename U> requires(std::is_same_v<T, U> && std::is_copy_constructible_v<T>)
constexpr NotNullable(U base) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
: base_(base)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
// some compilers apparently need this since they are unable to do proper pattern matching ...
NotNullable(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
NotNullable(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
template<typename TOther> requires(std::is_constructible_v<T, const TOther&>)
constexpr NotNullable(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, const TOther&>))
: base_(other.base_)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
template<typename TOther> requires(std::is_base_of_v<typename NotNullable<TOther>::element_type, element_type> && std::is_constructible_v<T, pointer>)
explicit constexpr NotNullable(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, pointer>))
: base_(static_cast<pointer>(&*other))
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
template<typename TOther> requires(std::is_constructible_v<T, TOther&&>)
constexpr NotNullable(NotNullable<TOther>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TOther&&>))
: base_(std::exchange(other.base_, nullptr))
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
template<typename TArg, typename... TArgs> requires(!std::is_same_v<TArg, std::nullptr_t>
&& (!std::is_same_v<TArg, T> && sizeof...(TArgs) == 0)
&& std::is_constructible_v<T, TArg&&, TArgs&&...>)
constexpr NotNullable(TArg&& arg, TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArg&&, TArgs&&...>))
: base_(std::forward<TArg>(arg), std::forward<TArgs>(args)...)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(T&& base) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
requires(std::is_move_constructible_v<T>)
: base_(std::move(base))
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
requires(std::is_copy_constructible_v<T>)
: base_(other.base_)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
requires(std::is_move_constructible_v<T>)
: base_(std::exchange(other.base_, nullptr))
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
// some compilers apparently need this since they are unable to do proper pattern matching ...
NotNullable& operator=(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
NotNullable& operator=(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_assignable_v<T>)
requires(std::is_copy_assignable_v<T>)
{
if (this != &other)
{
this->base_ = other.base_;
}
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
return *this;
}
constexpr NotNullable& operator=(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v<T>)
requires(std::is_move_assignable_v<T>)
{
if (this != &other)
{
this->base_ = std::exchange(other.base_, nullptr);
}
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
return *this;
}
constexpr NotNullable& operator=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
template<std::equality_comparable_with<T> TOther>
bool operator==(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
{
return base_ == other.base_;
}
template<std::equality_comparable_with<T> TOther>
bool operator!=(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
{
return base_ != other.base_;
}
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
bool operator==(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
{
return base_ == other;
}
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
bool operator!=(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
{
return base_ != other;
}
bool operator==(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
bool operator!=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
constexpr operator const T&() const MIJIN_NOEXCEPT { return get(); }
constexpr operator std::nullptr_t() const MIJIN_DELETE("Type is not nullable.");
constexpr const T& operator->() const MIJIN_NOEXCEPT { return get(); }
constexpr decltype(auto) operator*() const MIJIN_NOEXCEPT_IF(noexcept(*get())) { return *get(); }
NotNullable& operator++() MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable& operator--() MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable operator++(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable operator--(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable& operator+=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable& operator-=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
void operator[](std::ptrdiff_t) const MIJIN_DELETE("Operator disabled for non-nullable types.");
[[nodiscard]]
constexpr const T& get() const MIJIN_NOEXCEPT { return base_; }
[[nodiscard]]
constexpr T release() && MIJIN_NOEXCEPT { return std::exchange(base_, nullptr); }
template<nullable_type TOther>
friend class NotNullable;
};
#if MIJIN_USE_GSL
template<mijin::pointer_type T>
using owner_t = gsl::owner<T>;
#else
template<mijin::pointer_type T>
using owner_t = T;
#endif
template<typename T>
using not_null_t = NotNullable<T>;
}
#endif // !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)

View File

@@ -0,0 +1,55 @@
#pragma once
#if !defined(MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED)
#define MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED 1
#include "../internal/common.hpp"
namespace mijin
{
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseAnsiFontEffects
{
using char_t = TChar;
static constexpr const char_t* RESET = MIJIN_SMART_QUOTE(char_t, "0");
static constexpr const char_t* FG_BLACK = MIJIN_SMART_QUOTE(char_t, "30");
static constexpr const char_t* FG_RED = MIJIN_SMART_QUOTE(char_t, "31");
static constexpr const char_t* FG_GREEN = MIJIN_SMART_QUOTE(char_t, "32");
static constexpr const char_t* FG_YELLOW = MIJIN_SMART_QUOTE(char_t, "33");
static constexpr const char_t* FG_BLUE = MIJIN_SMART_QUOTE(char_t, "34");
static constexpr const char_t* FG_MAGENTA = MIJIN_SMART_QUOTE(char_t, "35");
static constexpr const char_t* FG_CYAN = MIJIN_SMART_QUOTE(char_t, "36");
static constexpr const char_t* FG_WHITE = MIJIN_SMART_QUOTE(char_t, "37");
static constexpr const char_t* FG_BRIGHT_BLACK = MIJIN_SMART_QUOTE(char_t, "90");
static constexpr const char_t* FG_BRIGHT_RED = MIJIN_SMART_QUOTE(char_t, "91");
static constexpr const char_t* FG_BRIGHT_GREEN = MIJIN_SMART_QUOTE(char_t, "92");
static constexpr const char_t* FG_BRIGHT_YELLOW = MIJIN_SMART_QUOTE(char_t, "93");
static constexpr const char_t* FG_BRIGHT_BLUE = MIJIN_SMART_QUOTE(char_t, "94");
static constexpr const char_t* FG_BRIGHT_MAGENTA = MIJIN_SMART_QUOTE(char_t, "95");
static constexpr const char_t* FG_BRIGHT_CYAN = MIJIN_SMART_QUOTE(char_t, "96");
static constexpr const char_t* FG_BRIGHT_WHITE = MIJIN_SMART_QUOTE(char_t, "97");
static constexpr const char_t* BG_BLACK = MIJIN_SMART_QUOTE(char_t, "40");
static constexpr const char_t* BG_RED = MIJIN_SMART_QUOTE(char_t, "41");
static constexpr const char_t* BG_GREEN = MIJIN_SMART_QUOTE(char_t, "42");
static constexpr const char_t* BG_YELLOW = MIJIN_SMART_QUOTE(char_t, "43");
static constexpr const char_t* BG_BLUE = MIJIN_SMART_QUOTE(char_t, "44");
static constexpr const char_t* BG_MAGENTA = MIJIN_SMART_QUOTE(char_t, "45");
static constexpr const char_t* BG_CYAN = MIJIN_SMART_QUOTE(char_t, "46");
static constexpr const char_t* BG_WHITE = MIJIN_SMART_QUOTE(char_t, "47");
static constexpr const char_t* BG_BRIGHT_BLACK = MIJIN_SMART_QUOTE(char_t, "100");
static constexpr const char_t* BG_BRIGHT_RED = MIJIN_SMART_QUOTE(char_t, "101");
static constexpr const char_t* BG_BRIGHT_GREEN = MIJIN_SMART_QUOTE(char_t, "102");
static constexpr const char_t* BG_BRIGHT_YELLOW = MIJIN_SMART_QUOTE(char_t, "103");
static constexpr const char_t* BG_BRIGHT_BLUE = MIJIN_SMART_QUOTE(char_t, "104");
static constexpr const char_t* BG_BRIGHT_MAGENTA = MIJIN_SMART_QUOTE(char_t, "105");
static constexpr const char_t* BG_BRIGHT_CYAN = MIJIN_SMART_QUOTE(char_t, "106");
static constexpr const char_t* BG_BRIGHT_WHITE = MIJIN_SMART_QUOTE(char_t, "107");
};
MIJIN_DEFINE_CHAR_VERSIONS(AnsiFontEffects)
}
#endif // !defined(MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED)

View File

@@ -6,9 +6,10 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cassert>
#include <cstddef> #include <cstddef>
#include "../debug/assert.hpp"
namespace mijin namespace mijin
{ {
@@ -32,8 +33,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,9 @@ namespace mijin
template<typename TBits> template<typename TBits>
struct BitFlags struct BitFlags
{ {
using bits_t = TBits;
static constexpr bool ENABLE_TO_INT = false;
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 +72,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 +84,55 @@ 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
{
// If a BitFlags object contains padding (number of defined bits < number of bits in type), the bits in the padding might not be 0.
// In that case bit_casting will produce an incorrect value.
// Filling the gaps with padding bits fixes this, but is unfortunately quite error prone :/.
static_assert(T::ENABLE_TO_INT, "bitFlagsToInt not enabled for this type. Make sure the number of bits defined is the same as the number of bits in the type and define ENABLE_TO_INT to true.");
static constexpr std::size_t BYTES = std::min(sizeof(T), sizeof(TInt));
TInt result = 0;
for (std::size_t off = 0; off < BYTES; ++off)
{
result |= static_cast<TInt>(*(std::bit_cast<const std::byte*>(&flags) + off)) << (off * 8);
}
return result;
}
template<BitFlagsType T, std::integral TInt>
T bitFlagsFromInt(TInt intVal) noexcept
{
static constexpr std::size_t BYTES = std::min(sizeof(T), sizeof(TInt));
T result = {};
for (std::size_t off = 0; off < BYTES; ++off)
{
(*(std::bit_cast<std::byte*>(&result) + off)) = static_cast<std::byte>(intVal >> (off * 8));
}
return result;
}
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED) #endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED)

View File

@@ -4,7 +4,18 @@
#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 #include "../detect.hpp"
#define MIJIN_CONCAT(a, b) MIJIN_CONCAT2(a, b)
#define MIJIN_CONCAT_DETAIL(a, b) a ## b
#define MIJIN_CONCAT(a, b) MIJIN_CONCAT_DETAIL(a, b)
#define MIJIN_CONCAT3(a, b, c) MIJIN_CONCAT(a, MIJIN_CONCAT(b, c))
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#define MIJIN_FUNCNAME() __PRETTY_FUNCTION__
#elif MIJIN_COMPILER == MIJIN_COMPILER_MSVC
#define MIJIN_FUNCNAME() __FUNCSIG__
#else
#define MIJIN_FUNCNAME() __func__
#endif
#endif // defined(MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED) #endif // defined(MIJIN_UTIL_COMMON_MACROS_HPP_INCLUDED)

View File

@@ -5,6 +5,7 @@
#define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1 #define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1
#include <type_traits> #include <type_traits>
#include "./traits.hpp"
namespace mijin namespace mijin
{ {
@@ -39,6 +40,32 @@ concept pointer_type = std::is_pointer_v<T>;
template<typename T> template<typename T>
concept reference_type = std::is_reference_v<T>; concept reference_type = std::is_reference_v<T>;
namespace impl
{
template<typename T>
using pointer_t = typename T::pointer;
}
template<typename T>
concept allocator_type = requires(T alloc, typename T::value_type value, detect_or_t<typename T::value_type*, impl::pointer_t, T> pointer, int count)
{
typename T::value_type;
{ alloc.allocate(count) } -> std::same_as<decltype(pointer)>;
{ alloc.deallocate(pointer, count) } -> std::same_as<void>;
} && !std::is_const_v<typename T::value_type> && !std::is_volatile_v<typename T::value_type>;
template<typename T, typename TOther>
concept allocator_type_for = allocator_type<T> && std::is_same_v<typename T::value_type, TOther>;
template<template<typename> typename T>
concept allocator_tmpl = allocator_type<T<int>>;
template<typename T, typename TData>
concept deleter_type = requires(T deleter, TData* ptr)
{
deleter(ptr);
};
// //
// public functions // public functions
// //

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

@@ -0,0 +1,122 @@
#pragma once
#ifndef MIJIN_UTIL_FORMATTABLE_HPP_INCLUDED
#define MIJIN_UTIL_FORMATTABLE_HPP_INCLUDED 1
#include <format>
#include "../container/boxed_object.hpp"
namespace mijin
{
//
// public concepts
//
template<typename T>
concept parse_result_type = requires
{
typename T::parse_result_t;
};
template<typename T, typename TParseContext>
concept parseable_by_type = parse_result_type<T> &&
requires(const T& object, TParseContext& parseContext)
{
{ T::parseFormat(parseContext) } -> std::convertible_to<std::pair<typename TParseContext::iterator, typename T::parse_result_t>>;
};
template<typename T, typename TFmtContext>
concept simple_formattable_to_type = requires(const T& object, TFmtContext& formatContext)
{
{ object.format(formatContext) } -> std::convertible_to<typename TFmtContext::iterator>;
};
template<typename T, typename TFmtContext, typename TParseContext>
concept complex_formattable_to_type = parseable_by_type<T, TParseContext> &&
requires(const T& object, TParseContext& parseContext, TFmtContext& formatContext, T::parse_result_t& parseResult)
{
{ object.format(formatContext, parseResult) } -> std::convertible_to<typename TFmtContext::iterator>;
};
template<typename T, typename TFmtContext, typename TParseContext>
concept formattable_to_type = simple_formattable_to_type<T, TFmtContext> || complex_formattable_to_type<T, TFmtContext, TParseContext>;
template<typename T>
concept cformattable_type = formattable_to_type<T, std::format_context, std::format_parse_context>;
template<typename T>
concept wformattable_type = formattable_to_type<T, std::wformat_context, std::wformat_parse_context>;
template<typename T>
concept formattable_type = cformattable_type<T> && wformattable_type<T>;
template<typename T>
concept any_formattable_type = cformattable_type<T> || wformattable_type<T>;
//
// internal types
//
namespace impl
{
template<typename T>
struct ParseResult
{
};
template<parse_result_type T>
struct ParseResult<T>
{
BoxedObject<typename T::parse_result_t> value;
};
}
} // namespace mijin
template<mijin::any_formattable_type T>
struct std::formatter<T>
{
[[no_unique_address]] [[maybe_unused]] mijin::impl::ParseResult<T> parseResult;
template<typename TContext>
constexpr TContext::iterator parse(TContext& ctx)
{
if constexpr (mijin::parse_result_type<T>)
{
static_assert(mijin::parseable_by_type<T, TContext>, "Type does not support parsing by this context.");
auto [it, result] = T::parseFormat(ctx);
parseResult.value.construct(std::move(result));
return it;
}
else
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != '}')
{
throw std::format_error("invalid format");
}
return it;
}
}
template<typename TContext>
TContext::iterator format(const T& object, TContext& ctx) const
{
if constexpr (mijin::parse_result_type<T>)
{
auto it = object.format(ctx, std::move(*parseResult.value));
parseResult.destroy();
return it;
}
else
{
static_assert(mijin::simple_formattable_to_type<T, TContext>, "Type does not support formatting to this context.");
return object.format(ctx);
}
}
};
#endif // MIJIN_UTIL_FORMATTABLE_HPP_INCLUDED

View File

@@ -6,6 +6,8 @@
#define MIJIN_UTIL_HASH_HPP_INCLUDED 1 #define MIJIN_UTIL_HASH_HPP_INCLUDED 1
#include <functional> #include <functional>
#include <tuple>
#include <utility>
namespace mijin namespace mijin
{ {
@@ -16,4 +18,21 @@ inline void hashCombine(std::size_t& seed, const T& value, const THasher& hasher
} }
} }
template<typename... T>
struct std::hash<std::tuple<T...>>
{
std::size_t operator()(const std::tuple<T...>& tuple) const noexcept
{
return hashImpl(tuple, std::index_sequence_for<T...>());
}
template<std::size_t... indices>
std::size_t hashImpl(const std::tuple<T...>& tuple, std::index_sequence<indices...>) const noexcept
{
std::size_t result = 0;
(mijin::hashCombine(result, std::get<indices>(tuple)), ...);
return result;
}
};
#endif // MIJIN_UTIL_HASH_HPP_INCLUDED #endif // MIJIN_UTIL_HASH_HPP_INCLUDED

View File

@@ -3,14 +3,18 @@
#if !defined(MIJIN_UTIL_ITERATORS_HPP_INCLUDED) #if !defined(MIJIN_UTIL_ITERATORS_HPP_INCLUDED)
#define MIJIN_UTIL_ITERATORS_HPP_INCLUDED 1 #define MIJIN_UTIL_ITERATORS_HPP_INCLUDED 1
#include <array>
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <optional> #include <optional>
#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"
#include "../util/annot.hpp"
namespace mijin namespace mijin
{ {
@@ -96,50 +100,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 +156,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 +173,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 +266,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 +279,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 +287,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 +333,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 +372,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 +387,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 +430,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 +477,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 +486,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 +507,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 +534,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 +566,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 +591,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 +618,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 +642,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 +652,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 +667,7 @@ struct ChainingIterator
return &*firstBase; return &*firstBase;
} }
reference operator*() const noexcept reference operator*() const MIJIN_NOEXCEPT
{ {
if (firstBase == firstEnd) if (firstBase == firstEnd)
{ {
@@ -591,7 +676,7 @@ struct ChainingIterator
return *firstBase; return *firstBase;
} }
ChainingIterator& operator++() noexcept ChainingIterator& operator++() MIJIN_NOEXCEPT
{ {
if (firstBase == firstEnd) { if (firstBase == firstEnd) {
++secondBase; ++secondBase;
@@ -602,14 +687,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 +705,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 +731,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 +769,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 +794,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 +832,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());
} }
@@ -772,6 +857,64 @@ TAs collect(TIterable&& iterable)
return TAs(iterable.begin(), iterable.end()); return TAs(iterable.begin(), iterable.end());
} }
template<typename TEle, std::size_t numEles>
class FixedArrayOutputIterator
{
public:
using array_t = std::array<TEle, numEles>;
using difference_type = std::ptrdiff_t;
using value_type = TEle;
using pointer = value_type*;
using reference = value_type&;
static constexpr std::size_t NUM_ELES = numEles;
private:
not_null_t<array_t*> array_;
std::size_t counter_ = 0;
public:
constexpr explicit FixedArrayOutputIterator(array_t& array) MIJIN_NOEXCEPT : array_(&array) {}
constexpr explicit FixedArrayOutputIterator(array_t& array, array_t::iterator pos) MIJIN_NOEXCEPT
: array_(&array), counter_(static_cast<std::size_t>(pos - array.begin())) {}
constexpr FixedArrayOutputIterator(const FixedArrayOutputIterator&) noexcept = default;
constexpr FixedArrayOutputIterator& operator=(const FixedArrayOutputIterator&) noexcept = default;
[[nodiscard]]
constexpr std::size_t getCounter() const MIJIN_NOEXCEPT
{
return counter_;
}
[[nodiscard]]
constexpr bool didOverflow() const MIJIN_NOEXCEPT
{
return counter_ >= NUM_ELES;
}
constexpr reference operator*() const MIJIN_NOEXCEPT
{
static TEle dummy_; // returned when we're at the end of the array
if (counter_ < NUM_ELES) {
return array_->at(counter_);
}
return dummy_;
}
constexpr FixedArrayOutputIterator& operator++() MIJIN_NOEXCEPT
{
++counter_;
return *this;
}
constexpr FixedArrayOutputIterator operator++(int) const MIJIN_NOEXCEPT
{
FixedArrayOutputIterator copy(*this);
++copy;
return copy;
}
};
static_assert(std::output_iterator<FixedArrayOutputIterator<int, 1>, int>);
namespace pipe namespace pipe
{ {
template<typename T> template<typename T>
@@ -890,7 +1033,7 @@ auto operator|(TIterable&& iterable, Xth<idx>)
{ {
return map(std::forward<TIterable>(iterable), [](auto&& element) { return std::get<idx>(element); } ); return map(std::forward<TIterable>(iterable), [](auto&& element) { return std::get<idx>(element); } );
} }
} } // namespace pipe
} // namespace mijin } // namespace mijin
#endif // MIJIN_UTIL_ITERATORS_HPP_INCLUDED #endif // MIJIN_UTIL_ITERATORS_HPP_INCLUDED

View File

@@ -0,0 +1,42 @@
#pragma once
#if !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)
#define MIJIN_UTIL_MISC_HPP_INCLUDED 1
#include <array>
#include <utility>
namespace mijin
{
//
// public functions
//
template<auto V, typename T>
constexpr decltype(auto) idValue(T&& value)
{
return std::forward<T>(value);
}
namespace impl
{
template<typename T, typename... TArgs>
struct ConstructArrayHelper
{
template<std::size_t... I>
static constexpr std::array<T, sizeof...(I)> construct(const TArgs&... args, std::index_sequence<I...>)
{
return {idValue<I>(T(args...))...};
}
};
}
template<typename T, std::size_t count, typename... TArgs>
constexpr std::array<T, count> constructArray(const TArgs&... args)
{
return impl::ConstructArrayHelper<T, TArgs...>::construct(args..., std::make_index_sequence<count>());
}
}
#endif // !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)

View File

@@ -2,13 +2,20 @@
#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 <bit>
#include <cstring>
#include <mutex>
#include <dlfcn.h> #include <dlfcn.h>
#include <pthread.h> #include <pthread.h>
#include <time.h>
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
// TODO #include <array>
#include <malloc.h>
#include <windows.h>
#include "../util/winundef.hpp"
#endif #endif
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -32,6 +39,30 @@ namespace mijin
// internal variables // internal variables
// //
namespace
{
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
std::mutex gDlErrorMutex; // dlerror may not be thread-safe
const std::uint64_t gCPUClockResolution = []()
{
struct timespec time;
[[maybe_unused]] const int result = clock_getres(CLOCK_PROCESS_CPUTIME_ID, &time);
MIJIN_ASSERT(result == 0, "Error getting cputime resolution.");
MIJIN_ASSERT(time.tv_sec == 0, "What kind of cpu clock is this?");
return static_cast<std::uint64_t>(time.tv_nsec);
}();
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
const std::uint64_t gCPUTickFrequency = []()
{
LARGE_INTEGER ticks;
[[maybe_unused]] const BOOL result = QueryPerformanceFrequency(&ticks);
MIJIN_ASSERT(result, "Error getting cpu frequency.");
return static_cast<std::uint64_t>(ticks.QuadPart);
}();
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
}
// //
// internal functions // internal functions
// //
@@ -40,32 +71,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";
@@ -76,4 +160,63 @@ void setCurrentThreadName(const char* threadName) noexcept
#endif #endif
} }
void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
{
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
return _aligned_malloc(size, alignment);
#else
return std::aligned_alloc(alignment, size);
#endif
}
void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
{
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
return _aligned_realloc(ptr, size, alignment);
#else
void* newPtr = std::realloc(ptr, size);
if (newPtr == ptr || (std::bit_cast<std::uintptr_t>(newPtr) % alignment) == 0)
{
return newPtr;
}
// bad luck, have to copy a second time
void* newPtr2 = std::aligned_alloc(alignment, size);
std::memcpy(newPtr2, newPtr, size);
std::free(newPtr);
return newPtr2;
#endif
}
void alignedFree(void* ptr)
{
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
_aligned_free(ptr);
#else
std::free(ptr);
#endif
}
std::uint64_t getCPUTicks() MIJIN_NOEXCEPT
{
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
struct timespec time;
[[maybe_unused]] const int result = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time);
MIJIN_ASSERT(result == 0, "Error getting cpu time.");
return (static_cast<std::uint64_t>(time.tv_sec) * 1e9 + time.tv_nsec) / gCPUClockResolution;
#else
LARGE_INTEGER ticks;
[[maybe_unused]] const BOOL result = QueryPerformanceCounter(&ticks);
MIJIN_ASSERT(result, "Error getting cpu time.");
return static_cast<std::uint64_t>(ticks.QuadPart);
#endif
}
std::uint64_t getCPUTicksPerSecond() MIJIN_NOEXCEPT
{
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
return 1e9 / gCPUClockResolution;
#else
return gCPUTickFrequency;
#endif
}
} // namespace mijin } // namespace mijin

View File

@@ -5,9 +5,13 @@
#define MIJIN_UTIL_OS_HPP_INCLUDED 1 #define MIJIN_UTIL_OS_HPP_INCLUDED 1
#include <csignal> #include <csignal>
#include <cstdint>
#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 +24,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 +41,84 @@ 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;
[[nodiscard]] void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
[[nodiscard]] void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
void alignedFree(void* ptr);
[[nodiscard]] std::uint64_t getCPUTicks() MIJIN_NOEXCEPT;
[[nodiscard]] std::uint64_t getCPUTicksPerSecond() 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 = {};
} }

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,9 @@ namespace mijin
// public types // public types
// //
struct Type_; // use as typevar
struct Any_; // use as placeholder in templates
template<typename T> template<typename T>
struct always_false struct always_false
{ {
@@ -95,11 +98,88 @@ struct map_template {
using type_t = TTemplate<TPredicate<T>>; using type_t = TTemplate<TPredicate<T>>;
}; };
template<typename T, typename... Types> template<template<typename...> typename TTemplate, typename TType>
struct is_any_type : std::disjunction<std::is_same<T, Types>...> {}; struct is_template_instance : std::false_type {};
template<template<typename...> typename TTemplate, typename... TArgs>
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
template<template<typename...> typename TTemplate, typename TType>
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
namespace impl
{
template<template<typename...> typename TTemplate, typename... TArgsTTmpl>
struct tmpl_param_comparator
{
template<typename TTmpl, typename TInstance>
static constexpr bool compare_single = std::is_same_v<TTmpl, TInstance> or std::is_same_v<TTmpl, Any_>;
template<typename T>
struct matches : std::false_type {};
// original (which MSVC didn't like for some reason :/)
// template<typename... TArgsInstance>
// struct matches<TTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
template<template<typename...> typename TOtherTemplate, typename... TArgsInstance> requires(is_template_instance_v<TTemplate, TOtherTemplate<TArgsInstance...>>)
struct matches<TOtherTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
template<typename T>
using matches_t = matches<T>;
};
}
template<typename TTemplate, typename TType>
struct match_template_type : std::false_type {};
template<template<typename...> typename TTemplate, typename TType, typename... TArgs>
struct match_template_type<TTemplate<TArgs...>, TType> : impl::tmpl_param_comparator<TTemplate, TArgs...>::template matches_t<TType> {};
template<typename TTemplate, typename TType>
constexpr bool match_template_type_v = match_template_type<TTemplate, TType>::value;
// similar to std::is_same, but allows placeholders
// - is_type_or_impl<some_tmpl<Type_>, some_type> resolves to some_tmpl<some_type>
// - is_type_or_impl<some_tmpl<Any_, int>, some_type> checks if some_type is an instance of some_tmpl with int as 2nd parameter
template<typename TMatch, typename TConcrete>
struct type_matches
{
using type = std::is_same<TMatch, TConcrete>;
};
template<template<typename> typename TTmplMatch, typename TConcrete>
struct type_matches<TTmplMatch<Type_>, TConcrete>
{
using type = TTmplMatch<TConcrete>;
};
template<template<typename...> typename TTmplMatch, typename TConcrete, typename... TArgs>
struct type_matches<TTmplMatch<TArgs...>, TConcrete>
{
using type = match_template_type<TTmplMatch<TArgs...>, TConcrete>;
};
template<typename TMatch, typename TConcrete>
using type_matches_t = type_matches<TMatch, TConcrete>::type;
template<typename TMatch, typename TConcrete>
inline constexpr bool type_matches_v = type_matches_t<TMatch, TConcrete>::value;
template<typename TConcrete, typename... TMatches>
struct is_any_type : std::disjunction<std::is_same<TConcrete, TMatches>...> {};
template<typename TConcrete, typename... TMatches>
static constexpr bool is_any_type_v = is_any_type<TConcrete, TMatches...>::value;
template<typename TConcrete, typename... TMatches>
struct match_any_type : std::disjunction<type_matches_t<TMatches, TConcrete>...> {};
template<typename TConcrete, typename... TMatches>
static constexpr bool match_any_type_v = match_any_type<TConcrete, TMatches...>::value;
template<typename T, typename... Types> template<typename T, typename... Types>
static constexpr bool is_any_type_v = is_any_type<T, Types...>::value; concept union_type = match_any_type_v<T, Types...>;
template<typename TElement, typename TCollection> template<typename TElement, typename TCollection>
struct is_type_member; struct is_type_member;
@@ -112,6 +192,103 @@ 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<typename TDefault, template<typename...> typename TOper, typename... TArgs>
struct detect_or
{
using type = TDefault;
static constexpr bool detected = false;
};
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
requires requires { typename TOper<TArgs...>; }
struct detect_or<TDefault, TOper, TArgs...>
{
using type = TOper<TArgs...>;
static constexpr bool detected = true;
};
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
using detect_or_t = detect_or<TDefault, TOper, TArgs...>::type;
struct empty_type {};
template<typename T, bool enable>
struct optional_base
{
using type = T;
};
template<typename T>
struct optional_base<T, false>
{
using type = empty_type;
};
template<typename T, bool enable>
using optional_base_t = optional_base<T, enable>::type;
namespace impl
{
template<typename TFunc>
struct function_traits_base {};
template<typename TResult, typename... TParams>
struct function_traits_base<TResult (*)(TParams...)>
{
using result_t = TResult;
using params_t = std::tuple<TParams...>;
};
template<typename TResult, typename TType, typename... TParams>
struct function_traits_base<TResult (TType::*)(TParams...)>
{
using result_t = TResult;
using params_t = std::tuple<TType*, TParams...>;
};
template<typename TResult, typename TType, typename... TParams>
struct function_traits_base<TResult (TType::*)(TParams...) const>
{
using result_t = TResult;
using params_t = std::tuple<const TType*, TParams...>;
};
}
template<typename TFunc>
struct function_traits : impl::function_traits_base<TFunc>
{
static constexpr std::size_t NUM_PARAMS = std::tuple_size_v<typename impl::function_traits_base<TFunc>::params_t>;
template<std::size_t pos>
using param_t = std::tuple_element_t<pos, typename impl::function_traits_base<TFunc>::params_t>;
};
// //
// public functions // public functions
// //
@@ -122,6 +299,34 @@ decltype(auto) delayEvaluation(TType&& value)
return static_cast<TType&&>(value); return static_cast<TType&&>(value);
} }
#define MIJIN_STATIC_TESTS 1
#if MIJIN_STATIC_TESTS
namespace test
{
template<typename T, typename U>
struct MyTemplate {};
static_assert(match_template_type_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
static_assert(match_template_type_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
static_assert(type_matches_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
static_assert(!type_matches_v<MyTemplate<double, int>, MyTemplate<int, int>>);
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, double>>);
static_assert(type_matches_v<std::is_pointer<Type_>, void*>);
static_assert(union_type<int, int>);
static_assert(!union_type<int>);
static_assert(!union_type<int, double>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<int, int>>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, int>>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, Any_>>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<double, double>, MyTemplate<Any_, Any_>>);
static_assert(!union_type<int*, int>);
static_assert(union_type<int*, std::is_pointer<Type_>>);
static_assert(!union_type<int, std::is_pointer<Type_>>);
}
#endif
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED) #endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)

View File

@@ -0,0 +1,44 @@
#pragma once
#if !defined(MIJIN_UTIL_VARIANT_HPP_INCLUDED)
#define MIJIN_UTIL_VARIANT_HPP_INCLUDED 1
#include <utility>
#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()...;
};
template<typename TRet>
struct CastTo
{
template<typename T>
TRet operator()(T&& arg) const
{
return static_cast<TRet>(std::forward<T>(arg));
}
};
}
#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,32 @@
#if defined(NEAR)
#undef NEAR
#endif
#if defined(FAR)
#undef FAR
#endif
#if defined(ERROR)
#undef ERROR
#endif
#if defined(IGNORE)
#undef IGNORE
#endif
#if defined(ABSOLUTE)
#undef ABSOLUTE
#endif
#if defined(RELATIVE)
#undef RELATIVE
#endif
#if defined(DEBUG)
#undef DEBUG
#endif
#if defined(VOID)
#undef VOID
#endif

View File

@@ -1,6 +1,12 @@
#include "./filesystem.hpp" #include "./filesystem.hpp"
#include "../platform/folders.hpp"
#include <filesystem>
namespace fs = std::filesystem;
namespace mijin namespace mijin
{ {
@@ -24,94 +30,91 @@ namespace mijin
// internal functions // internal functions
// //
namespace
{
void doGetFileInfo(const fs::path& stlPath, FileInfo& outInfo)
{
std::error_code err;
outInfo.isFolder = fs::is_directory(stlPath, err);
outInfo.isSymlink = fs::is_symlink(stlPath, err);
outInfo.isSpecial = !outInfo.isFolder && !fs::is_regular_file(stlPath, err);
outInfo.isHidden = stlPath.c_str()[0] == '.'; // at least for Linux
if (outInfo.isFolder)
{
const fs::directory_iterator dirIt(stlPath, err);
if (err != std::error_code{})
{
outInfo.size = std::distance(dirIt, fs::directory_iterator());
}
else
{
outInfo.size = 0;
}
}
else if (!outInfo.isSpecial)
{
outInfo.size = fs::file_size(stlPath, err);
if (err)
{
outInfo.size = 0;
}
}
}
}
// //
// public functions // public functions
// //
std::vector<fs::path> OSFileSystemAdapter::getRoots() std::vector<FolderEntry> OSFileSystemAdapter::listFiles(PathView folder)
{ {
return { std::vector<FolderEntry> entries;
"/" // TODO: other OSs
};
}
fs::path OSFileSystemAdapter::getHomeFolder()
{
return "/home/mewin"; // very TODO
}
std::vector<FileInfo> OSFileSystemAdapter::listFiles(const fs::path& folder)
{
std::vector<FileInfo> entries;
std::error_code err; std::error_code err;
fs::directory_iterator iterator(folder, fs::directory_options::skip_permission_denied, err);
const fs::path stlFolder(folder.stringView());
const fs::directory_iterator iterator(stlFolder, fs::directory_options::skip_permission_denied, err);
if (err) { if (err) {
return {}; // TODO: propagate? return {}; // TODO: propagate?
} }
for (const fs::directory_entry& entry : iterator) for (const fs::directory_entry& stlEntry : iterator)
{ {
FileInfo& info = entries.emplace_back(); FolderEntry& entry = entries.emplace_back();
info.path = entry.path(); entry.path = stlEntry.path().generic_string();
info.exists = true; entry.info.exists = true;
info.isFolder = entry.is_directory(err); doGetFileInfo(stlEntry.path(), entry.info);
info.isSymlink = entry.is_symlink(err);
info.isSpecial = !info.isFolder && !entry.is_regular_file(err);
info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux
if (info.isFolder) {
try {
info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator());
}
catch(std::runtime_error&) {
info.size = 0;
}
}
else if (!info.isSpecial)
{
info.size = entry.file_size(err);
if (err) {
info.size = 0;
}
}
} }
return entries; return entries;
} }
FileInfo OSFileSystemAdapter::getFileInfo(const fs::path& file) FileInfo OSFileSystemAdapter::getFileInfo(PathView file)
{ {
const fs::path stlFile(file.stringView());
FileInfo info = {}; FileInfo info = {};
std::error_code err; std::error_code err;
info.path = file; info.exists = fs::exists(stlFile, err);
info.exists = fs::exists(file, err);
if (info.exists) if (info.exists)
{ {
info.isFolder = fs::is_directory(file, err); doGetFileInfo(fs::path(file.stringView()), info);
info.isSymlink = fs::is_symlink(file, err);
info.isSpecial = !info.isFolder && !fs::is_regular_file(file, err);
info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux
if (info.isFolder) {
try {
info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator());
}
catch(std::runtime_error&) {
info.size = 0;
}
}
else if (!info.isSpecial)
{
info.size = fs::file_size(file, err);
if (err) {
info.size = 0;
}
}
} }
return info; return info;
} }
StreamError OSFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) Optional<NativePath> OSFileSystemAdapter::getNativePath(PathView file)
{ {
const std::string pathStr = path.string(); return NativePath(file);
}
StreamError OSFileSystemAdapter::open(PathView path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{
const PathView::string_view_t pathSv = path.stringView();
char* pathStr = static_cast<char*>(alloca(pathSv.size() + 1));
std::memcpy(pathStr, pathSv.data(), pathSv.size());
pathStr[pathSv.size()] = '\0';
auto stream = std::make_unique<FileStream>(); auto stream = std::make_unique<FileStream>();
const StreamError error = stream->open(pathStr.c_str(), mode); const StreamError error = stream->open(pathStr, mode);
if (error != StreamError::SUCCESS) { if (error != StreamError::SUCCESS) {
return error; return error;
} }

View File

@@ -6,13 +6,14 @@
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <filesystem>
#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"
namespace fs = std::filesystem; #include "../types/path.hpp"
#include "../util/hash.hpp"
namespace mijin namespace mijin
{ {
@@ -31,7 +32,7 @@ namespace mijin
struct FileInfo struct FileInfo
{ {
fs::path path; /// either file size in bytes, or number of entries if folder
std::size_t size = 0; std::size_t size = 0;
bool exists : 1 = false; bool exists : 1 = false;
bool isFolder : 1 = false; bool isFolder : 1 = false;
@@ -40,26 +41,70 @@ struct FileInfo
bool isHidden : 1 = false; bool isHidden : 1 = false;
}; };
struct FolderEntry
{
Path path;
FileInfo info;
};
// basically just a thin wrapper around adapter + path, for utility
class PathReference
{
private:
class FileSystemAdapter* adapter_ = nullptr;
Path path_ = {};
public:
PathReference() = default;
PathReference(const PathReference&) = default;
PathReference(PathReference&&) = default;
PathReference(class FileSystemAdapter* adapter, 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 Path& getPath() const MIJIN_NOEXCEPT { return path_; }
[[nodiscard]] inline FileInfo getInfo() const;
[[nodiscard]] inline Optional<NativePath> getNativePath() const;
[[nodiscard]] inline StreamError open(FileOpenMode mode, std::unique_ptr<Stream>& outStream) const;
[[nodiscard]]
PathReference operator/(const 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 std::vector<FolderEntry> listFiles(PathView folder) = 0;
[[nodiscard]] virtual fs::path getHomeFolder() = 0; [[nodiscard]] virtual FileInfo getFileInfo(PathView file) = 0;
[[nodiscard]] virtual std::vector<FileInfo> listFiles(const fs::path& folder) = 0; [[nodiscard]] virtual Optional<NativePath> getNativePath(PathView /* file */) { return NULL_OPTIONAL; }
[[nodiscard]] virtual FileInfo getFileInfo(const fs::path& file) = 0; [[nodiscard]] virtual StreamError open(PathView 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(PathView path, std::vector<PathReference>& outPaths) { outPaths.push_back(getPath(Path(path))); }
[[nodiscard]] PathReference getPath(Path path) MIJIN_NOEXCEPT { return PathReference(this, std::move(path)); }
[[nodiscard]] std::vector<PathReference> getAllPaths(PathView 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; std::vector<FolderEntry> listFiles(PathView folder) override;
fs::path getHomeFolder() override; FileInfo getFileInfo(PathView file) override;
std::vector<FileInfo> listFiles(const fs::path& folder) override; Optional<NativePath> getNativePath(PathView file) override;
FileInfo getFileInfo(const fs::path& file) override; StreamError open(PathView, 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 +113,21 @@ public:
// public functions // public functions
// //
inline FileInfo PathReference::getInfo() const
{
return adapter_->getFileInfo(path_);
}
Optional<NativePath> 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 +160,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

@@ -0,0 +1,158 @@
#include "./memory.hpp"
#include "../io/stream.hpp"
namespace mijin
{
std::vector<FolderEntry> MemoryFileSystemAdapter::listFiles(PathView folder)
{
const detail::MemoryFolder* folderObj = findFolder(folder);
if (folderObj == nullptr)
{
return {};
}
std::vector<FolderEntry> result;
result.reserve(folderObj->folders.size() + folderObj->files.size());
const Path folderPath(folder);
for (const auto& [name, subFolder] : folderObj->folders)
{
result.emplace_back(folderPath / name, folderInfo(subFolder));
}
for (const auto& [name, file] : folderObj->files)
{
result.emplace_back(folderPath / name, fileInfo(file));
}
return result;
}
FileInfo MemoryFileSystemAdapter::getFileInfo(PathView file)
{
#if 0 // shouldn't be necessary
// empty means root
if (file.empty())
{
return {
.path = {},
.size = root_.folders.size() + root_.files.size(),
.exists = true,
.isFolder = true
};
}
#endif
const detail::MemoryFolder* folderObj = findFolder(file.getParent());
if (folderObj == nullptr)
{
return {};
}
const std::string_view filename = file.getName();
if (auto itFolder = folderObj->folders.find(filename); itFolder != folderObj->folders.end())
{
return folderInfo(itFolder->second);
}
if (auto itFile = folderObj->files.find(filename); itFile != folderObj->files.end())
{
return fileInfo(itFile->second);
}
return {};
}
StreamError MemoryFileSystemAdapter::open(PathView path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{
if (mode != FileOpenMode::READ)
{
return StreamError::IO_ERROR;
}
const detail::MemoryFolder* folderObj = findFolder(path.getParent());
if (folderObj == nullptr)
{
return StreamError::IO_ERROR;
}
auto itFile = folderObj->files.find(path.getName());
if (itFile == folderObj->files.end())
{
return StreamError::IO_ERROR;
}
std::unique_ptr<MemoryStream> stream = std::make_unique<MemoryStream>();
stream->openRO(itFile->second.data);
outStream = std::move(stream);
return StreamError::SUCCESS;
}
bool MemoryFileSystemAdapter::addFile(PathView path, std::span<const std::uint8_t> data, Overwrite overwrite, CopyData copyData)
{
detail::MemoryFolder& folder = *findFolder(path.getParent(), true);
const std::string_view filename = path.getName();
if (folder.folders.contains(filename))
{
return false;
}
if (!overwrite && folder.files.contains(filename))
{
return false;
}
if (copyData)
{
data = fileData_.emplace_back(data.begin(), data.end());
}
folder.files.emplace(filename, detail::MemoryFile{.data = data});
return true;
}
void MemoryFileSystemAdapter::addFolder(PathView path)
{
(void) findFolder(path, true);
}
detail::MemoryFolder* MemoryFileSystemAdapter::findFolder(PathView path, bool create) MIJIN_NOEXCEPT
{
detail::MemoryFolder* folder = &root_;
for (const std::string_view part : path)
{
auto it = folder->folders.find(part);
if (it == folder->folders.end())
{
if (!create)
{
return nullptr;
}
folder = &folder->folders[std::string(part)];
}
else
{
folder = &it->second;
}
}
return folder;
}
FileInfo MemoryFileSystemAdapter::folderInfo(const detail::MemoryFolder& folder) const MIJIN_NOEXCEPT
{
return {
.size = folder.folders.size() + folder.files.size(),
.exists = true,
.isFolder = true
};
}
FileInfo MemoryFileSystemAdapter::fileInfo(const detail::MemoryFile& file) const MIJIN_NOEXCEPT
{
return {
.size = file.data.size(),
.exists = true
};
}
}

View File

@@ -0,0 +1,55 @@
#pragma once
#if !defined(MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED)
#define MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED 1
#include <variant>
#include <vector>
#include "../container/vector_map.hpp"
#include "../util/flag.hpp"
#include "../virtual_filesystem/filesystem.hpp"
namespace mijin
{
namespace detail
{
struct MemoryFile
{
std::span<const std::uint8_t> data;
};
struct MemoryFolder
{
VectorMap<std::string, MemoryFile, std::allocator<std::string>, std::allocator<MemoryFile>> files; // TODO: make the FS library allocator aware
VectorMap<std::string, MemoryFolder, std::allocator<std::string>, std::allocator<MemoryFolder>> folders;
};
}
class MemoryFileSystemAdapter : public FileSystemAdapter
{
public:
MIJIN_DEFINE_FLAG(Overwrite);
MIJIN_DEFINE_FLAG(CopyData);
private:
detail::MemoryFolder root_;
std::vector<std::vector<std::uint8_t>> fileData_;
public:
[[nodiscard]] std::vector<FolderEntry> listFiles(PathView folder) override;
[[nodiscard]] FileInfo getFileInfo(PathView file) override;
[[nodiscard]] StreamError open(PathView path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
bool addFile(PathView path, std::span<const std::uint8_t> data, Overwrite overwrite = Overwrite::NO, CopyData copyData = CopyData::NO);
bool addFile(PathView path, std::span<const std::uint8_t> data, CopyData copyData)
{
return addFile(path, data, Overwrite::NO, copyData);
}
void addFolder(PathView path);
private:
detail::MemoryFolder* findFolder(PathView path, bool create = false) MIJIN_NOEXCEPT;
FileInfo folderInfo(const detail::MemoryFolder& folder) const MIJIN_NOEXCEPT;
FileInfo fileInfo(const detail::MemoryFile& file) const MIJIN_NOEXCEPT;
};
} // namespace mijin
#endif // !defined(MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED)

Some files were not shown because too many files have changed in this diff Show More