From 061c58ef41edbf8beaddc1798a1ed3c160a18929 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sat, 21 Jun 2025 08:07:57 +0200 Subject: [PATCH] Added more stuff like Logging, DynamicPointer, utilities for using STL allocators and NotNullable. --- SModule | 1 + source/mijin/internal/common.hpp | 1 + source/mijin/internal/exception.hpp | 2 + source/mijin/internal/version_support.hpp | 13 ++ source/mijin/logging/formatting.hpp | 176 ++++++++++++++++++++++ source/mijin/logging/logger.cpp | 13 ++ source/mijin/logging/logger.hpp | 173 +++++++++++++++++++++ source/mijin/logging/stdio_sink.hpp | 40 +++++ source/mijin/memory/dynamic_pointer.hpp | 176 ++++++++++++++++++++++ source/mijin/memory/memutil.hpp | 85 +++++++++++ source/mijin/memory/virtual_allocator.hpp | 56 +++++++ source/mijin/util/annot.hpp | 146 ++++++++++++++++++ source/mijin/util/concepts.hpp | 27 ++++ source/mijin/util/os.cpp | 39 +++++ source/mijin/util/os.hpp | 4 + source/mijin/util/traits.hpp | 19 ++- 16 files changed, 970 insertions(+), 1 deletion(-) create mode 100644 source/mijin/internal/version_support.hpp create mode 100644 source/mijin/logging/formatting.hpp create mode 100644 source/mijin/logging/logger.cpp create mode 100644 source/mijin/logging/logger.hpp create mode 100644 source/mijin/logging/stdio_sink.hpp create mode 100644 source/mijin/memory/dynamic_pointer.hpp create mode 100644 source/mijin/memory/memutil.hpp create mode 100644 source/mijin/memory/virtual_allocator.hpp create mode 100644 source/mijin/util/annot.hpp diff --git a/SModule b/SModule index 845eb17..1ba6ce8 100644 --- a/SModule +++ b/SModule @@ -10,6 +10,7 @@ mijin_sources = Split(""" source/mijin/debug/symbol_info.cpp source/mijin/io/process.cpp source/mijin/io/stream.cpp + source/mijin/logging/logger.cpp source/mijin/net/http.cpp source/mijin/net/ip.cpp source/mijin/net/socket.cpp diff --git a/source/mijin/internal/common.hpp b/source/mijin/internal/common.hpp index 57e2266..474aca7 100644 --- a/source/mijin/internal/common.hpp +++ b/source/mijin/internal/common.hpp @@ -2,3 +2,4 @@ #pragma once #include "./exception.hpp" +#include "./version_support.hpp" diff --git a/source/mijin/internal/exception.hpp b/source/mijin/internal/exception.hpp index cbccf00..d781d93 100644 --- a/source/mijin/internal/exception.hpp +++ b/source/mijin/internal/exception.hpp @@ -23,10 +23,12 @@ #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 diff --git a/source/mijin/internal/version_support.hpp b/source/mijin/internal/version_support.hpp new file mode 100644 index 0000000..730f57c --- /dev/null +++ b/source/mijin/internal/version_support.hpp @@ -0,0 +1,13 @@ + +#pragma once + +#if !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED) +#define MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED 1 + +#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) diff --git a/source/mijin/logging/formatting.hpp b/source/mijin/logging/formatting.hpp new file mode 100644 index 0000000..53a4e01 --- /dev/null +++ b/source/mijin/logging/formatting.hpp @@ -0,0 +1,176 @@ + +#pragma once + +#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED) +#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1 + +#include +#include +#include +#include "./logger.hpp" +#include "../memory/dynamic_pointer.hpp" +#include "../memory/memutil.hpp" +#include "../memory/virtual_allocator.hpp" +#include "../util/annot.hpp" +#include "../util/concepts.hpp" + +namespace mijin +{ +template TAllocator = std::allocator> +class LogFormatter +{ +public: + using string_t = std::basic_string, TAllocator>; + + virtual ~LogFormatter() noexcept = default; + + virtual void format(const LogMessage& message, string_t& outFormatted) noexcept = 0; +}; + +template TAllocator = std::allocator> +class SimpleLogFormatter : public LogFormatter +{ +public: + using typename LogFormatter::string_t; +private: + string_t mFormat; +public: + explicit SimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)) {} + + void format(const LogMessage& message, string_t& outFormatted) noexcept override; +}; + +template typename TAllocator = std::allocator, + deleter_type>> TDeleter = AllocatorDeleter>>>> + requires(allocator_type>) +class FormattingLogSink : public LogSink +{ +public: + using allocator_t = TAllocator; + using formatter_t = LogFormatter; + using formatter_deleter_t = TDeleter; + using formatter_ptr_t = DynamicPointer; + using string_t = formatter_t::string_t; +private: + not_null_t mFormatter; + string_t mBuffer; +public: + explicit FormattingLogSink(not_null_t formatter, TAllocator allocator = {}) + MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) + : mFormatter(std::move(formatter)), mBuffer(std::move(allocator)) + {} + + virtual void handleMessageFormatted(const LogMessage& message, const char* formatted) MIJIN_NOEXCEPT = 0; + + void handleMessage(const LogMessage& message) noexcept override + { + mBuffer.clear(); + mFormatter->format(message, mBuffer); + handleMessageFormatted(message, mBuffer.c_str()); + } +}; + +template TAllocator> +void SimpleLogFormatter::format(const LogMessage& message, string_t& outFormatted) noexcept +{ + for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos) + { + if (*pos == '{') + { + ++pos; + if (*pos == '{') + { + // double { + outFormatted += '{'; + continue; + } + const auto argStart = pos; + static const std::string_view endChars = ":}"; + pos = std::find_first_of(pos, mFormat.end(), endChars.begin(), endChars.end()); + MIJIN_ASSERT(pos != mFormat.end(), "Invalid format."); + + const std::string_view argName(argStart, pos); + std::string argFormat; + if (*pos == ':') + { + const auto formatStart = pos; + pos = std::find(pos, mFormat.end(), '}'); + MIJIN_ASSERT(pos != mFormat.end(), "Invalid format."); + argFormat = std::string_view(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; + + // if there is no format, just directly print the value + if (argFormat.empty()) + { + if constexpr (std::is_arithmetic_v) + { + std::format_to(std::back_inserter(outFormatted), "{}", value); + } + else + { + outFormatted += value; + } + return; + } + // first copy the format string + braces into the buffer + const auto formatStart = outFormatted.size(); + outFormatted += '{'; + outFormatted += argFormat; + outFormatted += '}'; + const auto formatEnd = outFormatted.size(); + auto format = std::string_view(outFormatted).substr(formatStart, formatEnd - formatStart); + + // then append the formatted text + std::vformat_to(std::back_inserter(outFormatted), format, std::make_format_args(value)); + + // and then remove the format from the buffer again + outFormatted.erase(formatStart, formatEnd - formatStart); + }; + + if (argName == "text") + { + formatInline(message.text); + } + else if (argName == "channel") + { + formatInline(message.channel->name); + } + else if (argName == "file") + { + formatInline(message.sourceLocation.file_name()); + } + else if (argName == "function") + { + formatInline(message.sourceLocation.function_name()); + } + else if (argName == "line") + { + formatInline(message.sourceLocation.line()); + } + else if (argName == "column") + { + formatInline(message.sourceLocation.column()); + } + else if (argName == "level") + { + formatInline(message.level->name); + } + else + { + MIJIN_ERROR("Invalid format argument name."); + } + } + else + { + outFormatted += *pos; + } + } +} +} // namespace mijin + +#endif // !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED) diff --git a/source/mijin/logging/logger.cpp b/source/mijin/logging/logger.cpp new file mode 100644 index 0000000..570cea1 --- /dev/null +++ b/source/mijin/logging/logger.cpp @@ -0,0 +1,13 @@ + +#include "logger.hpp" + +namespace mijin +{ + +// +// public constants +// + +MIJIN_DEFINE_LOG_CHANNEL(GENERAL) + +} // namespace mijin diff --git a/source/mijin/logging/logger.hpp b/source/mijin/logging/logger.hpp new file mode 100644 index 0000000..ea2b7d9 --- /dev/null +++ b/source/mijin/logging/logger.hpp @@ -0,0 +1,173 @@ + +#pragma once + +#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED) +#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include +#include "../internal/common.hpp" +#include "../util/annot.hpp" + +namespace mijin +{ +struct LogLevel +{ + const char* name; + int value; + + explicit operator int() const MIJIN_NOEXCEPT { return value; } + + auto operator<=>(const LogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; } +}; + +struct LogChannel +{ + const char* name; +}; + +struct LogMessage +{ + const char* text; + const LogChannel* channel; + const LogLevel* level; + std::source_location sourceLocation; +}; + +class LogSink +{ +public: + virtual ~LogSink() noexcept = default; + + virtual void handleMessage(const LogMessage& message) MIJIN_NOEXCEPT = 0; +}; + +template typename TAllocator = std::allocator> +class Logger +{ +private: + std::vector> mSinks; +public: + explicit Logger(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) + : mSinks(std::move(allocator)) + {} + + Logger(const Logger&) = default; + + Logger(Logger&&) = default; + + Logger& operator=(const Logger&) = default; + + Logger& operator=(Logger&&) = default; + + void addSink(LogSink& sink) + { + mSinks.push_back(&sink); + } + + void postMessage(const LogMessage& message) const MIJIN_NOEXCEPT + { + for (LogSink* sink: mSinks) + { + sink->handleMessage(message); + } + } + + void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation, const char* msg) const MIJIN_NOEXCEPT + { + postMessage({ + .text = msg, + .channel = &channel, + .level = &level, + .sourceLocation = std::move(sourceLocation) + }); + } + + template + void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation, + std::format_string fmt, TArgs&& ... args) const + MIJIN_NOEXCEPT_IF(noexcept(std::declval>().allocate(1))) + { + std::basic_string, TAllocator> buffer(TAllocator(mSinks.get_allocator())); + std::format_to(std::back_inserter(buffer), fmt, std::forward(args)...); + log(level, channel, std::move(sourceLocation), buffer.c_str()); + } +}; + +#define MIJIN_DECLARE_LOG_CHANNEL(cnlName) \ +namespace mijin_log_channel \ +{ \ + extern const ::mijin::LogChannel cnlName; \ +} + +#define MIJIN_DEFINE_LOG_CHANNEL(cnlName) \ +namespace mijin_log_channel \ +{ \ +const ::mijin::LogChannel cnlName { \ + .name = #cnlName \ +}; \ +} + +#define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) \ +namespace mijin_log_level \ +{ \ + inline constexpr ::mijin::LogLevel lvlName{ \ + .name = #lvlName, \ + .value = lvlValue \ + }; \ +} + +MIJIN_DECLARE_LOG_CHANNEL(GENERAL) +MIJIN_DEFINE_LOG_LEVEL(DEBUG, -1000) +MIJIN_DEFINE_LOG_LEVEL(VERBOSE, -500) +MIJIN_DEFINE_LOG_LEVEL(INFO, 0) +MIJIN_DEFINE_LOG_LEVEL(WARNING, 500) +MIJIN_DEFINE_LOG_LEVEL(ERROR, 1000) + +#if defined(MIJIN_MIN_LOGLEVEL) +inline constexpr int MIN_LOG_LEVEL = static_cast(MIJIN_MIN_LOGLEVEL); +#elif defined(MIJIN_DEBUG) +inline constexpr int MIN_LOG_LEVEL = mijin_log_level::DEBUG.value; +#else +inline constexpr int MIN_LOG_LEVEL = mijin_log_level::VERBOSE.value; +#endif + +#define MIJIN_IMPORT_LOG_DEFAULTS \ +namespace mijin_log_channel \ +{ \ + using ::mijin::mijin_log_channel::GENERAL; \ +} \ + \ +namespace mijin_log_level \ +{ \ + using ::mijin::mijin_log_level::DEBUG; \ + using ::mijin::mijin_log_level::VERBOSE; \ + using ::mijin::mijin_log_level::INFO; \ + using ::mijin::mijin_log_level::WARNING; \ + using ::mijin::mijin_log_level::ERROR; \ +} + +#define MIJIN_LOG_ALWAYS(level, channel, ...) mijin__getLogger__().log( \ + mijin_log_level::level, mijin_log_channel::channel, std::source_location::current(), __VA_ARGS__ \ +) +#define MIJIN_LOG(level, channel, ...) \ +if constexpr (mijin_log_level::level.value < mijin::MIN_LOG_LEVEL) {} \ +else MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__) + +#define MIJIN_SET_CLASS_LOGGER(loggerExpr) \ +const auto& mijin__getLogger__() const noexcept \ +{ \ + return loggerExpr; \ +} +#define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \ +auto mijin__getLogger__ = [&]() -> const auto& \ +{ \ + return loggerExpr; \ +}; +} // namespace mijin + + +#endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED) diff --git a/source/mijin/logging/stdio_sink.hpp b/source/mijin/logging/stdio_sink.hpp new file mode 100644 index 0000000..e06ec7c --- /dev/null +++ b/source/mijin/logging/stdio_sink.hpp @@ -0,0 +1,40 @@ + +#pragma once + +#if !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED) +#define MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED 1 + +#include "./formatting.hpp" + +namespace mijin +{ +template typename TAllocator = std::allocator, + deleter_type>> TDeleter = AllocatorDeleter>>>> + requires(allocator_type>) +class StdioSink : public FormattingLogSink +{ +public: + using base_t = FormattingLogSink; + using typename base_t::formatter_ptr_t; + + explicit StdioSink(not_null_t formatter, TAllocator allocator = {}) + MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) + : base_t(std::move(formatter), std::move(allocator)) {} + + void handleMessageFormatted(const LogMessage& message, const char* formatted) MIJIN_NOEXCEPT override + { + if (*message.level >= mijin_log_level::WARNING) + { + std::fputs(formatted, stderr); + std::fputc('\n', stderr); + } + else + { + std::puts(formatted); + } + } +}; +} // namespace mijin + + +#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED) diff --git a/source/mijin/memory/dynamic_pointer.hpp b/source/mijin/memory/dynamic_pointer.hpp new file mode 100644 index 0000000..1edd64a --- /dev/null +++ b/source/mijin/memory/dynamic_pointer.hpp @@ -0,0 +1,176 @@ + +#pragma once + +#if !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED) +#define MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED 1 + +#include +#include +#include + +#include "../internal/common.hpp" +#include "../memory/memutil.hpp" +#include "../util/concepts.hpp" +#include "../util/flag.hpp" + +namespace mijin +{ +MIJIN_DEFINE_FLAG(Owning); + +template TDeleter = std::default_delete> +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) + : mData(std::bit_cast(ptr) | (owning ? 1 : 0)), mDeleter(std::move(deleter)) + { + MIJIN_ASSERT((std::bit_cast(ptr) & 1) == 0, "Invalid address, DynamicPointer requires addresses to be divisible by two."); + } + template requires (std::is_constructible_v) + constexpr DynamicPointer(DynamicPointer&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_convertible_v)) + : 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 requires(std::is_assignable_v) + DynamicPointer& operator=(DynamicPointer&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v)) + { + if (this != &other) + { + reset(); + mData = std::exchange(other.mData, 0); + mDeleter = std::move(other.mDeleter); + } + return *this; + } + + template requires(std::equality_comparable_with) + auto operator<=>(const DynamicPointer& 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(mData & ~1); + } + + constexpr void reset(pointer ptr, Owning owning) MIJIN_NOEXCEPT + { + if (isOwning()) + { + mDeleter(get()); + } + mData = std::bit_cast(ptr) | (owning ? 1 : 0); + } + + constexpr void reset() MIJIN_NOEXCEPT + { + reset(nullptr, Owning::NO); + } + + [[nodiscard]] + pointer release() MIJIN_NOEXCEPT + { + return std::bit_cast(std::exchange(mData, 0) & ~1); + } + + template TOtherDeleter> + friend class DynamicPointer; +}; + +template +bool operator==(std::nullptr_t, const DynamicPointer& pointer) MIJIN_NOEXCEPT +{ + return pointer == nullptr; +} + +template +bool operator!=(std::nullptr_t, const DynamicPointer& pointer) MIJIN_NOEXCEPT +{ + return pointer != nullptr; +} + +template +DynamicPointer> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) +{ + return DynamicPointer>(new T(std::forward(args)...), Owning::YES); +} + +template TAllocator, typename... TArgs> +DynamicPointer> makeDynamicWithAllocator(TAllocator allocator, TArgs&&... args) + MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v && std::is_nothrow_move_constructible_v)) +{ + T* obj = allocator.allocate(1); + if (obj != nullptr) + { + ::new(obj) T(std::forward(args)...); + } + return DynamicPointer>(obj, Owning::YES, AllocatorDeleter(std::move(allocator))); +} +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED) diff --git a/source/mijin/memory/memutil.hpp b/source/mijin/memory/memutil.hpp new file mode 100644 index 0000000..3291194 --- /dev/null +++ b/source/mijin/memory/memutil.hpp @@ -0,0 +1,85 @@ + +#pragma once + +#if !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED) +#define MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED 1 + +#include +#include "../internal/common.hpp" + +namespace mijin +{ +template +class AllocatorDeleter +{ +public: + using value_type = std::allocator_traits::value_type; + using pointer = std::allocator_traits::pointer; + +private: + [[no_unique_address]] TAllocator allocator_; + +public: + explicit AllocatorDeleter(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : allocator_(std::move(allocator)) {} + + template requires (std::is_constructible_v) + AllocatorDeleter(const AllocatorDeleter& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : allocator_(other.allocator_) {} + + template requires (std::is_constructible_v) + AllocatorDeleter(AllocatorDeleter&& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : allocator_(std::move(other.allocator_)) {} + + template requires (std::is_assignable_v) + AllocatorDeleter& operator=(const AllocatorDeleter& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v)) + { + if (this != static_cast(&other)) + { + allocator_ = other.allocator_; + } + return *this; + } + + template requires (std::is_assignable_v) + AllocatorDeleter& operator=(AllocatorDeleter&& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v)) + { + if (this != static_cast(&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 + friend class AllocatorDeleter; +}; + +template +class AllocatorDeleter> +{ +public: + AllocatorDeleter() noexcept = default; + template + AllocatorDeleter(const AllocatorDeleter>&) noexcept {} + + template + AllocatorDeleter& operator=(const AllocatorDeleter>&) noexcept { return *this; } + + void operator()(T* ptr) const MIJIN_NOEXCEPT + { + delete ptr; + } +}; +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED) diff --git a/source/mijin/memory/virtual_allocator.hpp b/source/mijin/memory/virtual_allocator.hpp new file mode 100644 index 0000000..a0a6ef2 --- /dev/null +++ b/source/mijin/memory/virtual_allocator.hpp @@ -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 +class VirtualAllocator +{ +public: + virtual ~VirtualAllocator() noexcept = default; + + [[nodiscard]] + virtual owner_t allocate(std::size_t count) noexcept; + virtual void deallocate(owner_t ptr, std::size_t count) noexcept; +}; + +template +class WrappedVirtualAllocator : public VirtualAllocator +{ +private: + [[no_unique_address]] TImpl mImpl; +public: + explicit constexpr WrappedVirtualAllocator(TImpl impl = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : 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 allocate(std::size_t count) noexcept override + { + return mImpl.allocate(count); + } + + void deallocate(owner_t ptr, std::size_t count) noexcept override + { + mImpl.deallocate(ptr, count); + } +}; + +template +WrappedVirtualAllocator makeVirtualAllocator(T allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) +{ + return WrappedVirtualAllocator(std::move(allocator)); +} +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED) diff --git a/source/mijin/util/annot.hpp b/source/mijin/util/annot.hpp new file mode 100644 index 0000000..822a53e --- /dev/null +++ b/source/mijin/util/annot.hpp @@ -0,0 +1,146 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED) +#define MIJIN_UTIL_ANNOT_HPP_INCLUDED 1 + +#include +#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() + #define MIJIN_USE_GSL 1 +#else + #define MIJIN_USE_GSL 0 +#endif +#endif // !defined(MIJIN_USE_GSL) + +#include +#include "./concepts.hpp" + +#if MIJIN_USE_GSL + #include +#endif + +namespace mijin +{ +template requires(!std::is_same_v) && requires(T t) { t == nullptr; } +class NotNullable +{ +private: + T base_; +public: + template requires(std::is_same_v && std::is_copy_constructible_v) + constexpr NotNullable(U base) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) + : base_(base) + { + MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr."); + } + template requires(!std::is_same_v + && (!std::is_same_v && sizeof...(TArgs) == 0) + && std::is_constructible_v) + constexpr NotNullable(TArg&& arg, TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : base_(std::forward(arg), std::forward(args)...) + { + MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr."); + } + constexpr NotNullable(T&& base) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + requires(std::is_move_constructible_v) + : base_(std::move(base)) + { + MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr."); + } + constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + requires(std::is_move_constructible_v) + : 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."); + + constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) + requires(std::is_copy_constructible_v) + { + 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) + requires(std::is_move_assignable_v) + { + 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 TOther> + bool operator==(const NotNullable& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() == std::declval())) + { + return base_ == other.base_; + } + + template TOther> + bool operator!=(const NotNullable& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() != std::declval())) + { + return base_ != other.base_; + } + + template TOther> requires(!std::is_same_v) + bool operator==(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() == std::declval())) + { + return base_ == other; + } + + template TOther> requires(!std::is_same_v) + bool operator!=(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() != std::declval())) + { + 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_; } +}; + +#if MIJIN_USE_GSL +template +using owner_t = gsl::owner; +#else +template +using owner_t = T; +#endif + +template +using not_null_t = NotNullable; +} + +#endif // !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED) diff --git a/source/mijin/util/concepts.hpp b/source/mijin/util/concepts.hpp index e40c4a1..a985a12 100644 --- a/source/mijin/util/concepts.hpp +++ b/source/mijin/util/concepts.hpp @@ -5,6 +5,7 @@ #define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1 #include +#include "./traits.hpp" namespace mijin { @@ -39,6 +40,32 @@ concept pointer_type = std::is_pointer_v; template concept reference_type = std::is_reference_v; +namespace impl +{ +template +using pointer_t = typename T::pointer; +} + +template +concept allocator_type = requires(T alloc, typename T::value_type value, detect_or_t pointer, int count) +{ + typename T::value_type; + { alloc.allocate(count) } -> std::same_as; + { alloc.deallocate(pointer, count) } -> std::same_as; +} && !std::is_const_v && !std::is_volatile_v; + +template +concept allocator_type_for = allocator_type && std::is_same_v; + +template typename T> +concept allocator_tmpl = allocator_type>; + +template +concept deleter_type = requires(T deleter, TData* ptr) +{ + deleter(ptr); +}; + // // public functions // diff --git a/source/mijin/util/os.cpp b/source/mijin/util/os.cpp index 1d7b7ce..ec0b0a8 100644 --- a/source/mijin/util/os.cpp +++ b/source/mijin/util/os.cpp @@ -5,11 +5,14 @@ #include "../debug/assert.hpp" #if MIJIN_TARGET_OS == MIJIN_OS_LINUX + #include + #include #include #include #include #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS #include + #include #include #include "../util/winundef.hpp" #endif @@ -139,4 +142,40 @@ std::string getExecutablePath() MIJIN_NOEXCEPT #endif } +void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT +{ +#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS + return _aligned_alloc(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(newPtr) % alignment) == 0) + { + return newPtr; + } + // bad luck, have to copy a second time + void* newPtr2 = std::aligned_alloc(alignment, size); + std::memcpy(newPtr2, newPtr, size); + std::free(newPtr); + return newPtr2; +#endif +} + +void alignedFree(void* ptr) +{ +#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS + _aligned_free(ptr); +#else + std::free(ptr); +#endif +} + } // namespace mijin diff --git a/source/mijin/util/os.hpp b/source/mijin/util/os.hpp index b071d66..0d10dff 100644 --- a/source/mijin/util/os.hpp +++ b/source/mijin/util/os.hpp @@ -81,6 +81,10 @@ void setCurrentThreadName(const char* threadName) MIJIN_NOEXCEPT; [[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) MIJIN_NOEXCEPT; +[[nodiscard]] void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT; +[[nodiscard]] void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT; +void alignedFree(void* ptr); + SharedLibrary::~SharedLibrary() MIJIN_NOEXCEPT { close(); diff --git a/source/mijin/util/traits.hpp b/source/mijin/util/traits.hpp index 72a166d..cb550f0 100644 --- a/source/mijin/util/traits.hpp +++ b/source/mijin/util/traits.hpp @@ -146,7 +146,24 @@ template typename TTemplate, typename... TArgs> struct is_template_instance> : std::true_type {}; template typename TTemplate, typename TType> -constexpr bool is_template_instance_v = is_template_instance::value; +constexpr bool is_template_instance_v = is_template_instance::value; + +template typename TOper, typename... TArgs> +struct detect_or +{ + using type = TDefault; + static constexpr bool detected = false; +}; + +template typename TOper, typename... TArgs> + requires requires { typename TOper; } +struct detect_or +{ + using type = TOper; + static constexpr bool detected = true; +}; +template typename TOper, typename... TArgs> +using detect_or_t = detect_or::type; // // public functions