diff options
author | crupest <crupest@outlook.com> | 2024-10-06 13:57:39 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2024-10-06 13:57:39 +0800 |
commit | dfe62dcf8bcefc523b466e127c3edc4dc2756629 (patch) | |
tree | 1c751a14ba0da07ca2ff805633f97568060aa4c9 /include/cru/base | |
parent | f51eb955e188858272230a990565931e7403f23b (diff) | |
download | cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.gz cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.bz2 cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.zip |
Rename common to base.
Diffstat (limited to 'include/cru/base')
42 files changed, 3773 insertions, 0 deletions
diff --git a/include/cru/base/Base.h b/include/cru/base/Base.h new file mode 100644 index 00000000..8a6a7634 --- /dev/null +++ b/include/cru/base/Base.h @@ -0,0 +1,112 @@ +#pragma once +#include "PreConfig.h" // IWYU pragma: keep + +#include <cassert> +#include <cstddef> +#include <functional> +#include <memory> + +#ifdef CRU_PLATFORM_WINDOWS +#ifdef CRU_BASE_EXPORT_API +#define CRU_BASE_API __declspec(dllexport) +#else +#define CRU_BASE_API __declspec(dllimport) +#endif +#else +#define CRU_BASE_API +#endif + +#define CRU_UNUSED(entity) static_cast<void>(entity); + +#define CRU__CONCAT(a, b) a##b +#define CRU_MAKE_UNICODE_LITERAL(str) CRU__CONCAT(u, #str) + +#define CRU_DEFAULT_COPY(classname) \ + classname(const classname&) = default; \ + classname& operator=(const classname&) = default; + +#define CRU_DEFAULT_MOVE(classname) \ + classname(classname&&) = default; \ + classname& operator=(classname&&) = default; + +#define CRU_DELETE_COPY(classname) \ + classname(const classname&) = delete; \ + classname& operator=(const classname&) = delete; + +#define CRU_DELETE_MOVE(classname) \ + classname(classname&&) = delete; \ + classname& operator=(classname&&) = delete; + +#define CRU_DEFAULT_DESTRUCTOR(classname) ~classname() = default; + +#define CRU_DEFAULT_CONSTRUCTOR_DESTRUCTOR(classname) \ + classname() = default; \ + ~classname() = default; + +#define CRU_DEFINE_COMPARE_OPERATORS(classname) \ + inline bool operator==(const classname& left, const classname& right) { \ + return left.Compare(right) == 0; \ + } \ + \ + inline bool operator!=(const classname& left, const classname& right) { \ + return left.Compare(right) != 0; \ + } \ + \ + inline bool operator<(const classname& left, const classname& right) { \ + return left.Compare(right) < 0; \ + } \ + \ + inline bool operator<=(const classname& left, const classname& right) { \ + return left.Compare(right) <= 0; \ + } \ + \ + inline bool operator>(const classname& left, const classname& right) { \ + return left.Compare(right) > 0; \ + } \ + \ + inline bool operator>=(const classname& left, const classname& right) { \ + return left.Compare(right) >= 0; \ + } + +namespace cru { +class CRU_BASE_API Object { + public: + Object() = default; + CRU_DEFAULT_COPY(Object) + CRU_DEFAULT_MOVE(Object) + virtual ~Object() = default; +}; + +struct CRU_BASE_API Interface { + Interface() = default; + CRU_DELETE_COPY(Interface) + CRU_DELETE_MOVE(Interface) + virtual ~Interface() = default; +}; + +[[noreturn]] void CRU_BASE_API UnreachableCode(); + +using Index = std::ptrdiff_t; + +inline void Expects(bool condition) { assert(condition); } +inline void Ensures(bool condition) { assert(condition); } +template <typename T> +inline void Expects(const std::shared_ptr<T>& ptr) { + assert(ptr != nullptr); +} +template <typename T> +inline void Ensures(const std::shared_ptr<T>& ptr) { + assert(ptr != nullptr); +} + +// https://www.boost.org/doc/libs/1_54_0/doc/html/hash/reference.html#boost.hash_combine +template <class T> +inline void hash_combine(std::size_t& s, const T& v) { + std::hash<T> h; + s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2); +} + +#define CRU_DEFINE_CLASS_LOG_TAG(tag) \ + private: \ + constexpr static const char16_t* kLogTag = tag; +} // namespace cru diff --git a/include/cru/base/Bitmask.h b/include/cru/base/Bitmask.h new file mode 100644 index 00000000..9b6b8957 --- /dev/null +++ b/include/cru/base/Bitmask.h @@ -0,0 +1,55 @@ +#pragma once + +#include <functional> + +namespace cru { +template <typename Tag, typename TUnderlying = unsigned> +struct Bitmask final { + using Underlying = TUnderlying; + + constexpr Bitmask() : value(0) {} + constexpr explicit Bitmask(TUnderlying value) : value(value) {} + + static constexpr Bitmask FromOffset(int offset) { + return Bitmask(static_cast<TUnderlying>(1u << offset)); + } + + constexpr bool Has(Bitmask rhs) const { return (value & rhs.value) != 0; } + + Bitmask operator|(Bitmask rhs) const { return Bitmask(value | rhs.value); } + Bitmask operator&(Bitmask rhs) const { return Bitmask(value & rhs.value); } + Bitmask operator^(Bitmask rhs) const { return Bitmask(value ^ rhs.value); } + Bitmask operator~() const { return Bitmask(~value); } + Bitmask& operator|=(Bitmask rhs) { + value |= rhs.value; + return *this; + } + Bitmask& operator&=(Bitmask rhs) { + value &= rhs.value; + return *this; + } + Bitmask& operator^=(Bitmask rhs) { + value ^= rhs.value; + return *this; + } + + bool operator==(Bitmask rhs) const { return this->value == rhs.value; } + bool operator!=(Bitmask rhs) const { return this->value != rhs.value; } + + explicit operator TUnderlying() const { return value; } + operator bool() const { return value != 0; } + + TUnderlying value; +}; +} // namespace cru + +namespace std { +template <typename Tag, typename TUnderlying> +struct hash<cru::Bitmask<Tag, TUnderlying>> { + using Bitmask = cru::Bitmask<Tag, TUnderlying>; + + std::size_t operator()(Bitmask bitmask) const { + return std::hash<TUnderlying>{}(bitmask.value); + } +}; +} // namespace std diff --git a/include/cru/base/Buffer.h b/include/cru/base/Buffer.h new file mode 100644 index 00000000..bc2e2a26 --- /dev/null +++ b/include/cru/base/Buffer.h @@ -0,0 +1,161 @@ +#pragma once + +#include "Base.h" + +namespace cru { +class Buffer final { + friend void swap(Buffer& left, Buffer& right) noexcept; + + public: + Buffer(); + explicit Buffer(Index size); + + Buffer(const Buffer& other); + Buffer(Buffer&& other) noexcept; + + Buffer& operator=(const Buffer& other); + Buffer& operator=(Buffer&& other) noexcept; + + ~Buffer(); + + public: + Index GetBufferSize() const { return size_; } + Index GetUsedSize() const { return used_end_ - used_begin_; } + bool IsNull() const { return ptr_ == nullptr; } + bool IsUsedReachEnd() const { return used_end_ == size_; } + + Index GetFrontFree() const { return used_begin_; } + Index GetBackFree() const { return size_ - used_end_; } + + Index GetUsedBegin() const { return used_begin_; } + Index GetUsedEnd() const { return used_end_; } + + std::byte* GetPtr() { return GetPtrAt(0); } + const std::byte* GetPtr() const { return GetPtrAt(0); } + + std::byte* GetPtrAt(Index index) { return ptr_ + index; } + const std::byte* GetPtrAt(Index index) const { return ptr_ + index; } + + std::byte& GetRefAt(Index index) { return *GetPtrAt(index); } + const std::byte& GetRefAt(Index index) const { return *GetPtrAt(index); } + + std::byte* GetUsedBeginPtr() { return GetPtrAt(GetUsedBegin()); } + const std::byte* GetUsedBeginPtr() const { return GetPtrAt(GetUsedBegin()); } + std::byte* GetUsedEndPtr() { return GetPtrAt(GetUsedEnd()); } + const std::byte* GetUsedEndPtr() const { return GetPtrAt(GetUsedEnd()); } + + std::byte GetByteAt(Index index) const { return ptr_[index]; } + void SetByteAt(Index index, std::byte value) { ptr_[index] = value; } + + void AssignBytes(std::byte* src, Index src_size, bool use_memmove = false) { + return AssignBytes(0, src, 0, src_size, use_memmove); + } + void AssignBytes(Index dst_offset, std::byte* src, Index src_size, + bool use_memmove = false) { + return AssignBytes(dst_offset, src, 0, src_size, use_memmove); + } + void AssignBytes(Index dst_offset, std::byte* src, Index src_offset, + Index src_size, bool use_memmove = false); + + /** + * @brief Change the size of the buffer. + * + * Unless new size is the same as current size, the buffer is always released + * and a new one is allocated. If preserve_used is true, the used size and old + * data is copied to the new buffer. If new size is smaller than old used + * size, then exceeded data will be lost. + */ + void ResizeBuffer(Index new_size, bool preserve_used); + + /** + * @brief Append data to the front of used bytes and increase used size. + * @return The actual size of data saved. + * + * If there is no enough space left for new data, the rest space will be + * written and the size of it will be returned, leaving exceeded data not + * saved. + */ + Index PushFront(const std::byte* other, Index other_size, + bool use_memmove = false); + + bool PushBack(std::byte b); + + /** + * @brief Append data to the back of used bytes and increase used size. + * @return The actual size of data saved. + * + * If there is no enough space left for new data, the rest space will be + * written and the size of it will be returned, leaving exceeded data not + * saved. + */ + Index PushBack(const std::byte* other, Index other_size, + bool use_memmove = false); + + void PushBackCount(Index count); + + /** + * @brief Move forward the used-begin ptr. + * @return The actual size moved forward. + * + * If given size is bigger than current used size, the used size will be + * returned and set to 0. + */ + Index PopFront(Index size); + + /** + * @brief Pop front data of used bytes into another buffer. + * @return The actual size popped. + * + * If given size is bigger than current used size, then only current used size + * of bytes will be popped. If given size is smaller than current used size, + * then only given size of bytes will be popped. + */ + Index PopFront(std::byte* buffer, Index size, bool use_memmove = false); + + /** + * @brief Move backward the used-end ptr. + * @return The actual size moved backward. + * + * If given size is bigger than current used size, the used size will be + * returned and set to 0. + */ + Index PopEnd(Index size); + + /** + * @brief Pop back data of used bytes into another buffer. + * @return The actual size popped. + * + * If given size is bigger than current used size, then only current used size + * of bytes will be popped. If given size is smaller than current used size, + * then only given size of bytes will be popped. + */ + Index PopEnd(std::byte* buffer, Index size, bool use_memmove = false); + + operator std::byte*() { return GetPtr(); } + operator const std::byte*() const { return GetPtr(); } + + /** + * @brief Detach internal buffer and return it. + * @param size If not null, size of the buffer is written to it. + * @return The buffer pointer. May be nullptr. + * + * After detach, you are responsible to delete[] it. + */ + std::byte* Detach(Index* size = nullptr); + + private: + void Copy_(const Buffer& other); + void Move_(Buffer&& other) noexcept; + void Delete_() noexcept; + + void AssertValid(); + + private: + std::byte* ptr_; + Index size_; + Index used_begin_; + Index used_end_; +}; + +void swap(Buffer& left, Buffer& right) noexcept; +} // namespace cru diff --git a/include/cru/base/ClonablePtr.h b/include/cru/base/ClonablePtr.h new file mode 100644 index 00000000..a2a88758 --- /dev/null +++ b/include/cru/base/ClonablePtr.h @@ -0,0 +1,216 @@ +#pragma once + +#include <cstddef> +#include <functional> +#include <memory> +#include <type_traits> + +namespace cru { +template <typename TClonable> +class ClonablePtr { + template <typename T> + friend class ClonablePtr; + + public: + using element_type = typename std::unique_ptr<TClonable>::element_type; + using pointer = typename std::unique_ptr<TClonable>::pointer; + + ClonablePtr() = default; + ClonablePtr(std::nullptr_t) noexcept : ptr_(nullptr) {} + explicit ClonablePtr(pointer p) noexcept : ptr_(p) {} + ClonablePtr(std::unique_ptr<element_type>&& p) noexcept + : ptr_(std::move(p)) {} + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr(std::unique_ptr<O>&& p) : ptr_(std::move(p)) {} + ClonablePtr(const ClonablePtr& other) : ptr_(other.ptr_->Clone()) {} + ClonablePtr(ClonablePtr&& other) = default; + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr(const ClonablePtr<O>& other) : ptr_(other.ptr_->Clone()) {} + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr(ClonablePtr<O>&& other) noexcept : ptr_(std::move(other.ptr_)) {} + ClonablePtr& operator=(std::nullptr_t) noexcept { + ptr_ = nullptr; + return *this; + } + ClonablePtr& operator=(std::unique_ptr<element_type>&& other) noexcept { + ptr_ = std::move(other); + return *this; + } + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(std::unique_ptr<O>&& p) noexcept { + ptr_ = std::move(p); + return *this; + } + ClonablePtr& operator=(const ClonablePtr& other) { + if (this != &other) { + ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone()); + } + return *this; + } + ClonablePtr& operator=(ClonablePtr&& other) = default; + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(const ClonablePtr<O>& other) noexcept { + if (this != &other) { + ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone()); + } + return *this; + } + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(ClonablePtr<O>&& other) noexcept { + ptr_ = std::move(other.ptr_); + } + + ~ClonablePtr() = default; + + public: + pointer release() noexcept { return ptr_.release(); } + void reset(pointer p = pointer()) noexcept { ptr_.reset(p); } + void swap(ClonablePtr& other) noexcept { ptr_.swap(other.ptr_); } + + public: + pointer get() const noexcept { return ptr_.get(); } + + operator bool() const noexcept { return ptr_ != nullptr; } + + element_type& operator*() const noexcept { return *ptr_; } + pointer operator->() const noexcept { return ptr_.get(); } + + int Compare(const ClonablePtr& other) const noexcept { + if (ptr_ == other.ptr_) { + return 0; + } else if (ptr_ < other.ptr_) { + return -1; + } else { + return 1; + } + } + + int Compare(nullptr_t) const noexcept { return ptr_ ? 1 : 0; } + + private: + std::unique_ptr<element_type> ptr_; +}; + +template <typename T> +void swap(ClonablePtr<T>& left, ClonablePtr<T>& right) noexcept { + left.swap(right); +} + +template <typename T> +bool operator==(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.Compare(right) == 0; +} + +template <typename T> +bool operator!=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.Compare(right) != 0; +} + +template <typename T> +bool operator<(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.Compare(right) < 0; +} + +template <typename T> +bool operator<=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.Compare(right) <= 0; +} + +template <typename T> +bool operator>(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.Compare(right) > 0; +} + +template <typename T> +bool operator>=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.Compare(right) >= 0; +} + +template <typename T> +bool operator==(const ClonablePtr<T>& left, std::nullptr_t) { + return left.Compare(nullptr) == 0; +} + +template <typename T> +bool operator!=(const ClonablePtr<T>& left, std::nullptr_t) { + return left.Compare(nullptr) != 0; +} + +template <typename T> +bool operator<(const ClonablePtr<T>& left, std::nullptr_t) { + return left.Compare(nullptr) < 0; +} + +template <typename T> +bool operator<=(const ClonablePtr<T>& left, std::nullptr_t) { + return left.Compare(nullptr) <= 0; +} + +template <typename T> +bool operator>(const ClonablePtr<T>& left, std::nullptr_t) { + return left.Compare(nullptr) > 0; +} + +template <typename T> +bool operator>=(const ClonablePtr<T>& left, std::nullptr_t) { + return left.Compare(nullptr) >= 0; +} + +template <typename T> +bool operator==(std::nullptr_t, const ClonablePtr<T>& right) { + return right.Compare(nullptr) == 0; +} + +template <typename T> +bool operator!=(std::nullptr_t, const ClonablePtr<T>& right) { + return right.Compare(nullptr) != 0; +} + +template <typename T> +bool operator<(std::nullptr_t, const ClonablePtr<T>& right) { + return right.Compare(nullptr) > 0; +} + +template <typename T> +bool operator<=(std::nullptr_t, const ClonablePtr<T>& right) { + return right.Compare(nullptr) >= 0; +} + +template <typename T> +bool operator>(std::nullptr_t, const ClonablePtr<T>& right) { + return right.Compare(nullptr) < 0; +} + +template <typename T> +bool operator>=(std::nullptr_t, const ClonablePtr<T>& right) { + return right.Compare(nullptr) <= 0; +} + +} // namespace cru + +namespace std { +template <typename T> +struct hash<cru::ClonablePtr<T>> { + std::size_t operator()(const cru::ClonablePtr<T>& p) const { + return std::hash<typename cru::ClonablePtr<T>::pointer>(p.get()); + } +}; +} // namespace std diff --git a/include/cru/base/Event.h b/include/cru/base/Event.h new file mode 100644 index 00000000..18d2c570 --- /dev/null +++ b/include/cru/base/Event.h @@ -0,0 +1,281 @@ +#pragma once +#include "Base.h" + +#include "SelfResolvable.h" + +#include <algorithm> +#include <cassert> +#include <functional> +#include <memory> +#include <utility> +#include <vector> + +namespace cru { +class EventRevoker; + +namespace details { +template <class> +inline constexpr bool always_false_v = false; + +// Base class of all Event<T...>. +// It erases event args types and provides a +// unified form to create event revoker and +// revoke(remove) handler. +class EventBase : public SelfResolvable<EventBase> { + friend EventRevoker; + + protected: + using EventHandlerToken = long; + + EventBase() {} + CRU_DELETE_COPY(EventBase) + CRU_DEFAULT_MOVE(EventBase) + virtual ~EventBase() = default; + + // Remove the handler with the given token. If the token + // corresponds to no handler (which might have be revoked + // before), then nothing will be done. + virtual void RemoveHandler(EventHandlerToken token) = 0; + + // Create a revoker with the given token. + inline EventRevoker CreateRevoker(EventHandlerToken token); +}; +} // namespace details + +// A non-copyable and movable event revoker. +// Call function call operator to revoke the handler. +class EventRevoker { + friend details::EventBase; + + private: + EventRevoker(ObjectResolver<details::EventBase>&& resolver, + details::EventBase::EventHandlerToken token) + : resolver_(std::move(resolver)), token_(token) {} + + public: + EventRevoker(const EventRevoker& other) = default; + EventRevoker(EventRevoker&& other) = default; + EventRevoker& operator=(const EventRevoker& other) = default; + EventRevoker& operator=(EventRevoker&& other) = default; + ~EventRevoker() = default; + + // Revoke the registered handler. If the event has already + // been destroyed, then nothing will be done. If one of the + // copies calls this, then other copies's calls will have no + // effect. (They have the same token.) + void operator()() const { + if (const auto event = resolver_.Resolve()) { + event->RemoveHandler(token_); + } + } + + private: + ObjectResolver<details::EventBase> resolver_; + details::EventBase::EventHandlerToken token_; +}; + +inline EventRevoker details::EventBase::CreateRevoker(EventHandlerToken token) { + return EventRevoker(CreateResolver(), token); +} + +// int -> int +// Point -> const Point& +// int& -> int& +template <typename TRaw> +using DeducedEventArgs = std::conditional_t< + std::is_lvalue_reference_v<TRaw>, TRaw, + std::conditional_t<std::is_scalar_v<TRaw>, TRaw, const TRaw&>>; + +struct IBaseEvent { + protected: + IBaseEvent() = default; + CRU_DELETE_COPY(IBaseEvent) + CRU_DEFAULT_MOVE(IBaseEvent) + ~IBaseEvent() = default; // Note that user can't destroy a Event via IEvent. + // So destructor should be protected. + + using SpyOnlyHandler = std::function<void()>; + + public: + virtual EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) = 0; +}; + +// Provides an interface of event. +// IEvent only allow to add handler but not to raise the event. You may +// want to create an Event object and expose IEvent only so users won't +// be able to emit the event. +template <typename TEventArgs> +struct IEvent : virtual IBaseEvent { + public: + using EventArgs = DeducedEventArgs<TEventArgs>; + using EventHandler = std::function<void(EventArgs)>; + using ShortCircuitHandler = std::function<bool(EventArgs)>; + + protected: + IEvent() = default; + CRU_DELETE_COPY(IEvent) + CRU_DEFAULT_MOVE(IEvent) + ~IEvent() = default; // Note that user can't destroy a Event via IEvent. So + // destructor should be protected. + + public: + virtual EventRevoker AddHandler(EventHandler handler) = 0; + virtual EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) = 0; + virtual EventRevoker PrependShortCircuitHandler( + ShortCircuitHandler handler) = 0; +}; + +// A non-copyable non-movable Event class. +// It stores a list of event handlers. +template <typename TEventArgs> +class Event : public details::EventBase, public IEvent<TEventArgs> { + using typename IEvent<TEventArgs>::EventArgs; + + using typename IBaseEvent::SpyOnlyHandler; + using typename IEvent<TEventArgs>::EventHandler; + using typename IEvent<TEventArgs>::ShortCircuitHandler; + + private: + struct HandlerData { + HandlerData(EventHandlerToken token, ShortCircuitHandler handler) + : token(token), handler(std::move(handler)) {} + EventHandlerToken token; + ShortCircuitHandler handler; + }; + + public: + Event() = default; + CRU_DELETE_COPY(Event) + CRU_DEFAULT_MOVE(Event) + ~Event() = default; + + EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) override { + return AddShortCircuitHandler([handler = std::move(handler)](EventArgs) { + handler(); + return false; + }); + } + + EventRevoker AddHandler(EventHandler handler) override { + return AddShortCircuitHandler( + [handler = std::move(handler)](EventArgs args) { + handler(args); + return false; + }); + } + + // Handler return true to short circuit following handlers. + EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) override { + const auto token = current_token_++; + this->handler_data_list_.emplace_back(token, std::move(handler)); + return CreateRevoker(token); + } + + // Handler return true to short circuit following handlers. + EventRevoker PrependShortCircuitHandler( + ShortCircuitHandler handler) override { + const auto token = current_token_++; + this->handler_data_list_.emplace(this->handler_data_list_.cbegin(), token, + std::move(handler)); + return CreateRevoker(token); + } + + // This method will make a copy of all handlers. Because user might delete a + // handler in a handler, which may lead to seg fault as the handler is + // deleted while being executed. Thanks to this behavior, all handlers will + // be taken a snapshot when Raise is called, so even if you delete a handler + // during this period, all handlers in the snapshot will be executed. + void Raise(EventArgs args) { + std::vector<ShortCircuitHandler> handlers; + handlers.reserve(this->handler_data_list_.size()); + for (const auto& data : this->handler_data_list_) { + handlers.push_back(data.handler); + } + for (const auto& handler : handlers) { + auto short_circuit = handler(args); + if (short_circuit) return; + } + } + + protected: + void RemoveHandler(EventHandlerToken token) override { + const auto find_result = std::find_if( + this->handler_data_list_.cbegin(), this->handler_data_list_.cend(), + [token](const HandlerData& data) { return data.token == token; }); + if (find_result != this->handler_data_list_.cend()) { + this->handler_data_list_.erase(find_result); + } + } + + private: + std::vector<HandlerData> handler_data_list_; + EventHandlerToken current_token_ = 0; +}; + +namespace details { +struct EventRevokerDestroyer { + void operator()(EventRevoker* p) { + (*p)(); + delete p; + } +}; +} // namespace details + +// A guard class for event revoker. Automatically revoke it when destroyed. +class EventRevokerGuard { + public: + EventRevokerGuard() = default; + explicit EventRevokerGuard(EventRevoker&& revoker) + : revoker_(new EventRevoker(std::move(revoker))) {} + EventRevokerGuard(const EventRevokerGuard& other) = delete; + EventRevokerGuard(EventRevokerGuard&& other) = default; + EventRevokerGuard& operator=(const EventRevokerGuard& other) = delete; + EventRevokerGuard& operator=(EventRevokerGuard&& other) = default; + ~EventRevokerGuard() = default; + + EventRevoker Get() { + // revoker is only null when this is moved + // you shouldn't use a moved instance + assert(revoker_); + return *revoker_; + } + + EventRevoker Release() { return std::move(*revoker_.release()); } + + void Reset() { revoker_.reset(); } + + void Reset(EventRevoker&& revoker) { + revoker_.reset(new EventRevoker(std::move(revoker))); + } + + private: + std::unique_ptr<EventRevoker, details::EventRevokerDestroyer> revoker_; +}; + +class EventRevokerListGuard { + public: + EventRevokerListGuard() = default; + EventRevokerListGuard(const EventRevokerListGuard& other) = delete; + EventRevokerListGuard(EventRevokerListGuard&& other) = default; + EventRevokerListGuard& operator=(const EventRevokerListGuard& other) = delete; + EventRevokerListGuard& operator=(EventRevokerListGuard&& other) = default; + ~EventRevokerListGuard() = default; + + public: + void Add(EventRevoker&& revoker) { + event_revoker_guard_list_.push_back(EventRevokerGuard(std::move(revoker))); + } + + EventRevokerListGuard& operator+=(EventRevoker&& revoker) { + this->Add(std::move(revoker)); + return *this; + } + + void Clear() { event_revoker_guard_list_.clear(); } + + bool IsEmpty() const { return event_revoker_guard_list_.empty(); } + + private: + std::vector<EventRevokerGuard> event_revoker_guard_list_; +}; +} // namespace cru diff --git a/include/cru/base/Event2.h b/include/cru/base/Event2.h new file mode 100644 index 00000000..891b314f --- /dev/null +++ b/include/cru/base/Event2.h @@ -0,0 +1,200 @@ +#pragma once + +#include <cru/base/Bitmask.h> +#include <cru/base/SelfResolvable.h> + +#include <cstddef> +#include <functional> +#include <type_traits> +#include <utility> +#include <vector> + +namespace cru { +class Event2Base { + public: + virtual ~Event2Base() = default; + + virtual void RevokeHandler(int token_value) = 0; +}; + +template <typename TArgument, typename TResult> +class EventContext { + public: + EventContext() : argument_(), result_() {} + explicit EventContext(TArgument argument) + : argument_(std::move(argument)), result_() {} + EventContext(TArgument argument, TResult result) + : argument_(std::move(argument)), result_(std::move(result)) {} + + TArgument& GetArgument() { return argument_; } + const TArgument& GetArgument() const { return argument_; } + + TResult& GetResult() { return result_; } + const TResult& GetResult() const { return result_; } + void SetResult(const TResult& result) { result_ = result; } + void SetResult(TResult&& result) { result_ = std::move(result); } + TResult TakeResult() { return std::move(result_); } + + bool GetStopHandling() const { return stop_handling_; } + void SetStopHandling(bool stop = true) { stop_handling_ = stop; } + + private: + TArgument argument_; + TResult result_; + bool stop_handling_ = false; +}; + +template <typename TArgument, typename TResult> +class Event2; + +template <typename T> +constexpr bool is_event2_v = false; + +template <typename TArgument, typename TResult> +constexpr bool is_event2_v<Event2<TArgument, TResult>> = true; + +class EventHandlerToken { + public: + EventHandlerToken(ObjectResolver<Event2Base> event_resolver, int token_value) + : event_resolver_(std::move(event_resolver)), token_value_(token_value) {} + + ObjectResolver<Event2Base> GetEventResolver() const { + return event_resolver_; + } + int GetTokenValue() const { return token_value_; } + inline void RevokeHandler() const; + + private: + ObjectResolver<Event2Base> event_resolver_; + int token_value_; +}; + +namespace details { +struct Event2BehaviorFlagTag {}; +} // namespace details +using Event2BehaviorFlag = Bitmask<details::Event2BehaviorFlagTag>; + +struct Event2BehaviorFlags { + /** + * @brief Make a copy of handler list before invoking handlers. So the event + * object or its owner can be destroyed during running handlers. + */ + static constexpr Event2BehaviorFlag CopyHandlers = + Event2BehaviorFlag::FromOffset(1); +}; + +template <typename TArgument = std::nullptr_t, + typename TResult = std::nullptr_t> +class Event2 : public Event2Base, + public SelfResolvable<Event2<TArgument, TResult>> { + public: + using HandlerToken = EventHandlerToken; + using Context = EventContext<TArgument, TResult>; + using Handler = std::function<void(Context*)>; + using SpyOnlyHandler = std::function<void()>; + + template <typename TFunc> + static std::enable_if_t<std::invocable<TFunc>, Handler> WrapAsHandler( + TFunc&& handler) { + return Handler([h = std::forward<TFunc>(handler)](Context*) { h(); }); + } + + template <typename TFunc> + static std::enable_if_t<std::invocable<TFunc, Context*>, Handler> + WrapAsHandler(TFunc&& handler) { + return Handler(std::forward<TFunc>(handler)); + } + + private: + struct HandlerData { + int token_value; + Handler handler; + }; + + public: + explicit Event2(Event2BehaviorFlag flags = {}) : flags_(flags) {} + Event2(const Event2&) = delete; + Event2(Event2&&) = delete; + Event2& operator=(const Event2&) = delete; + Event2& operator=(Event2&&) = delete; + ~Event2() override = default; + + public: + template <typename TFunc> + HandlerToken AddHandler(TFunc&& handler) { + auto token = this->current_token_value_++; + auto real_handler = WrapAsHandler(std::forward<TFunc>(handler)); + HandlerData handler_data{token, std::move(real_handler)}; + this->handlers_.push_back(std::move(handler_data)); + return HandlerToken(this->CreateResolver(), token); + } + + void RevokeHandler(int token_value) override { + auto iter = this->handlers_.cbegin(); + auto end = this->handlers_.cend(); + for (; iter != end; ++iter) { + if (iter->token_value == token_value) { + this->handlers_.erase(iter); + break; + } + } + } + + void RevokeHandler(const HandlerToken& token) { + return RevokeHandler(token.GetTokenValue()); + } + + TResult Raise() { + Context context; + RunInContext(&context); + return context.TakeResult(); + } + + TResult Raise(TArgument argument) { + Context context(std::move(argument)); + RunInContext(&context); + return context.TakeResult(); + } + + TResult Raise(TArgument argument, TResult result) { + Context context(std::move(argument), std::move(result)); + RunInContext(&context); + return context.TakeResult(); + } + + private: + void RunInContext(Context* context) { + if (this->flags_ & Event2BehaviorFlags::CopyHandlers) { + std::vector<Handler> handlers_copy; + for (const auto& handler : this->handlers_) { + handlers_copy.push_back(handler.handler); + } + for (const auto& handler : handlers_copy) { + if (context->GetStopHandling()) { + break; + } + handler(context); + } + } else { + for (const auto& handler : this->handlers_) { + if (context->GetStopHandling()) { + break; + } + handler.handler(context); + } + } + } + + private: + int current_token_value_ = 1; + std::vector<HandlerData> handlers_; + Event2BehaviorFlag flags_; +}; + +inline void EventHandlerToken::RevokeHandler() const { + auto event = this->event_resolver_.Resolve(); + if (event) { + event->RevokeHandler(this->token_value_); + } +} +} // namespace cru diff --git a/include/cru/base/Exception.h b/include/cru/base/Exception.h new file mode 100644 index 00000000..609fd2c9 --- /dev/null +++ b/include/cru/base/Exception.h @@ -0,0 +1,60 @@ +#pragma once +#include "String.h" + +#include <exception> +#include <optional> + +namespace cru { +#ifdef _MSC_VER +#pragma warning(disable : 4275) +#endif +class CRU_BASE_API Exception : public std::exception { + public: + explicit Exception(String message = {}, + std::unique_ptr<std::exception> inner = nullptr); + + ~Exception() override; + + public: + String GetMessage() const { return message_; } + + std::exception* GetInner() const noexcept { return inner_.get(); } + + const char* what() const noexcept override; + + protected: + void SetMessage(String message) { message_ = std::move(message); } + + void AppendMessage(StringView additional_message); + void AppendMessage(std::optional<StringView> additional_message); + + private: + String message_; + mutable std::string utf8_message_; + std::unique_ptr<std::exception> inner_; +}; + +class CRU_BASE_API TextEncodeException : public Exception { + public: + using Exception::Exception; +}; + +class ErrnoException : public Exception { + public: + /** + * @brief will retrieve errno automatically. + */ + explicit ErrnoException(String message = {}); + ErrnoException(String message, int errno_code); + + CRU_DELETE_COPY(ErrnoException) + CRU_DELETE_MOVE(ErrnoException) + + ~ErrnoException() override = default; + + int GetErrnoCode() const { return errno_code_; } + + private: + int errno_code_; +}; +} // namespace cru diff --git a/include/cru/base/Format.h b/include/cru/base/Format.h new file mode 100644 index 00000000..d5c5ed99 --- /dev/null +++ b/include/cru/base/Format.h @@ -0,0 +1,154 @@ +#pragma once + +#include "Exception.h" +#include "String.h" + +#include <cassert> +#include <cstdio> +#include <type_traits> +#include <vector> + +namespace cru { +inline String ToString(bool value) { + return value ? String(u"true") : String(u"false"); +} + +template <typename T> +inline constexpr std::nullptr_t kPrintfFormatSpecifierOfType = nullptr; + +#define CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(type, specifier) \ + template <> \ + inline constexpr const char* kPrintfFormatSpecifierOfType<type> = specifier; + +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed char, "%c") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned char, "%c") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed short, "%hd") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned short, "%hu") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed int, "%d") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned int, "%u") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed long, "%ld") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned long, "%lu") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed long long, "%lld") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned long long, "%llu") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(float, "%f") +CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(double, "%f") + +#undef CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE + +template <typename T> +std::enable_if_t< + !std::is_null_pointer_v<decltype(kPrintfFormatSpecifierOfType<T>)>, String> +ToString(T value) { + auto size = std::snprintf(nullptr, 0, kPrintfFormatSpecifierOfType<T>, value); + assert(size > 0); + std::vector<char> buffer(size + 1); + size = std::snprintf(buffer.data(), size + 1, kPrintfFormatSpecifierOfType<T>, + value); + assert(size > 0); + return String::FromUtf8(buffer.data(), size); +} + +template <typename T> +String ToString(const T& value, StringView option) { + CRU_UNUSED(option) + return ToString(value); +} + +inline String ToString(String value) { return value; } + +namespace details { +enum class FormatTokenType { PlaceHolder, Text }; +enum class FormatPlaceHolderType { None, Positioned, Named }; + +struct FormatToken { + static FormatToken Text() { + return FormatToken{FormatTokenType::Text, {}, {}, 0, {}, {}}; + } + + static FormatToken NonePlaceHolder(String option) { + return FormatToken(FormatTokenType::PlaceHolder, {}, + FormatPlaceHolderType::None, 0, {}, std::move(option)); + } + + static FormatToken PositionedPlaceHolder(int position, String option) { + return FormatToken(FormatTokenType::PlaceHolder, {}, + FormatPlaceHolderType::Positioned, position, {}, + std::move(option)); + } + + static FormatToken NamedPlaceHolder(String name, String option) { + return FormatToken(FormatTokenType::PlaceHolder, {}, + FormatPlaceHolderType::Named, 0, std::move(name), + std::move(option)); + } + + FormatToken(FormatTokenType type, String data, + FormatPlaceHolderType place_holder_type, + int place_holder_position, String place_holder_name, + String place_holder_option) + : type(type), + data(std::move(data)), + place_holder_type(place_holder_type), + place_holder_position(place_holder_position), + place_holder_name(std::move(place_holder_name)), + place_holder_option(std::move(place_holder_option)) {} + + CRU_DEFAULT_COPY(FormatToken) + CRU_DEFAULT_MOVE(FormatToken) + + CRU_DEFAULT_DESTRUCTOR(FormatToken) + + FormatTokenType type; + String data; + FormatPlaceHolderType place_holder_type; + int place_holder_position; + String place_holder_name; + String place_holder_option; +}; + +std::vector<FormatToken> CRU_BASE_API ParseToFormatTokenList(StringView str); + +void CRU_BASE_API FormatAppendFromFormatTokenList( + String& current, const std::vector<FormatToken>& format_token_list, + Index index); + +template <typename TA, typename... T> +void FormatAppendFromFormatTokenList( + String& current, const std::vector<FormatToken>& format_token_list, + Index index, TA&& args0, T&&... args) { + for (Index i = index; i < static_cast<Index>(format_token_list.size()); i++) { + const auto& token = format_token_list[i]; + if (token.type == FormatTokenType::PlaceHolder) { + if (token.place_holder_type == FormatPlaceHolderType::None) { + current += ToString(std::forward<TA>(args0), token.place_holder_option); + FormatAppendFromFormatTokenList(current, format_token_list, i + 1, + std::forward<T>(args)...); + + return; + } else { + throw Exception( + u"Currently do not support positional or named place holder."); + } + } else { + current += token.data; + } + } +} +} // namespace details + +template <typename... T> +String Format(StringView format, T&&... args) { + String result; + + details::FormatAppendFromFormatTokenList( + result, details::ParseToFormatTokenList(format), 0, + std::forward<T>(args)...); + + return result; +} + +template <typename... T> +String String::Format(T&&... args) const { + return cru::Format(*this, std::forward<T>(args)...); +} +} // namespace cru diff --git a/include/cru/base/Guard.h b/include/cru/base/Guard.h new file mode 100644 index 00000000..5a9f9c5d --- /dev/null +++ b/include/cru/base/Guard.h @@ -0,0 +1,26 @@ +#pragma once + +#include <functional> + +namespace cru { +struct Guard { + using ExitFunc = std::function<void()>; + + Guard() = default; + explicit Guard(const ExitFunc& f) : on_exit(f) {} + explicit Guard(ExitFunc&& f) : on_exit(std::move(f)) {} + Guard(const Guard&) = delete; + Guard(Guard&&) = default; + Guard& operator=(const Guard&) = delete; + Guard& operator=(Guard&&) = default; + ~Guard() { + if (on_exit) { + on_exit(); + } + } + + void Drop() { on_exit = {}; } + + ExitFunc on_exit; +}; +} // namespace cru diff --git a/include/cru/base/HandlerRegistry.h b/include/cru/base/HandlerRegistry.h new file mode 100644 index 00000000..e405d1fd --- /dev/null +++ b/include/cru/base/HandlerRegistry.h @@ -0,0 +1,87 @@ +#pragma once +#include "Base.h" + +#include <algorithm> +#include <functional> +#include <utility> +#include <vector> + +namespace cru { + +template <typename T> +class HandlerRegistryIterator { + public: + using RawIterator = + typename std::vector<std::pair<int, std::function<T>>>::const_iterator; + + explicit HandlerRegistryIterator(RawIterator raw) : raw_(std::move(raw)) {} + + CRU_DELETE_COPY(HandlerRegistryIterator) + CRU_DELETE_MOVE(HandlerRegistryIterator) + + ~HandlerRegistryIterator() = default; + + const std::function<T>& operator*() const { return raw_->second; } + const std::function<T>* operator->() const { return &raw_->second; } + + HandlerRegistryIterator& operator++() { + ++raw_; + return *this; + } + + HandlerRegistryIterator operator++(int) { + auto c = *this; + this->operator++(); + return c; + } + + bool operator==(const HandlerRegistryIterator<T>& other) const { + return this->raw_ == other.raw_; + } + + bool operator!=(const HandlerRegistryIterator<T>& other) const { + return !this->operator==(other); + } + + private: + RawIterator raw_; +}; + +template <typename T> +class HandlerRegistry final { + public: + HandlerRegistry() = default; + CRU_DEFAULT_COPY(HandlerRegistry) + CRU_DEFAULT_MOVE(HandlerRegistry) + ~HandlerRegistry() = default; + + public: + int AddHandler(std::function<T> handler) { + auto id = current_id_++; + handler_list_.push_back({id, std::move(handler)}); + return id; + } + + void RemoveHandler(int id) { + auto result = std::find_if(handler_list_.cbegin(), handler_list_.cend(), + [id](const std::pair<int, std::function<T>>& d) { + return d.first == id; + }); + if (result != handler_list_.cend()) { + handler_list_.erase(result); + } + } + + HandlerRegistryIterator<T> begin() const { + return HandlerRegistryIterator<T>(handler_list_.begin()); + } + + HandlerRegistryIterator<T> end() const { + return HandlerRegistryIterator<T>(handler_list_.end()); + } + + private: + int current_id_ = 1; + std::vector<std::pair<int, std::function<T>>> handler_list_; +}; +} // namespace cru diff --git a/include/cru/base/PreConfig.h b/include/cru/base/PreConfig.h new file mode 100644 index 00000000..3f26c589 --- /dev/null +++ b/include/cru/base/PreConfig.h @@ -0,0 +1,18 @@ +// IWYU pragma: always_keep + +#pragma once + +#ifdef _MSC_VER +// disable the unnecessary warning about multi-inheritance +#pragma warning(disable : 4250) +// disable dll export template issue warning +#pragma warning(disable : 4251) +#endif + +#ifdef CRU_PLATFORM_WINDOWS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#if defined(CRU_PLATFORM_OSX) || defined(CRU_PLATFORM_LINUX) +#define CRU_PLATFORM_UNIX +#endif diff --git a/include/cru/base/PropertyTree.h b/include/cru/base/PropertyTree.h new file mode 100644 index 00000000..54e185b9 --- /dev/null +++ b/include/cru/base/PropertyTree.h @@ -0,0 +1,63 @@ +#pragma once + +#include "Base.h" +#include "String.h" + +#include <unordered_map> + +namespace cru { +class PropertyTree; + +class CRU_BASE_API PropertySubTreeRef { + public: + static String CombineKey(StringView left, StringView right); + + explicit PropertySubTreeRef(PropertyTree* tree, String path = {}); + + CRU_DEFAULT_COPY(PropertySubTreeRef); + CRU_DEFAULT_MOVE(PropertySubTreeRef); + + CRU_DEFAULT_DESTRUCTOR(PropertySubTreeRef); + + public: + PropertyTree* GetTree() const { return tree_; } + + String GetPath() const { return path_; } + void SetPath(String path) { path_ = std::move(path); } + + PropertySubTreeRef GetParent() const; + PropertySubTreeRef GetChild(const String& key) const; + + String GetValue(const String& key) const; + void SetValue(const String& key, String value); + void DeleteValue(const String& key); + + private: + PropertyTree* tree_; + String path_; +}; + +class CRU_BASE_API PropertyTree { + public: + static String CombineKey(StringView left, StringView right); + + PropertyTree() = default; + explicit PropertyTree(std::unordered_map<String, String> values); + + CRU_DELETE_COPY(PropertyTree); + CRU_DELETE_MOVE(PropertyTree); + + CRU_DEFAULT_DESTRUCTOR(PropertyTree); + + public: + String GetValue(const String& key) const; + void SetValue(const String& key, String value); + void DeleteValue(const String& key); + + PropertySubTreeRef GetSubTreeRef(const String& path); + + private: + std::unordered_map<String, String> values_; +}; + +} // namespace cru diff --git a/include/cru/base/Range.h b/include/cru/base/Range.h new file mode 100644 index 00000000..edc2ec55 --- /dev/null +++ b/include/cru/base/Range.h @@ -0,0 +1,42 @@ +#pragma once +#include "Base.h" + +namespace cru { +struct Range final { + constexpr static Range FromTwoSides(Index start, Index end) { + return Range(start, end - start); + } + + constexpr static Range FromTwoSides(Index start, Index end, Index offset) { + return Range(start + offset, end - start); + } + + constexpr Range() = default; + constexpr Range(const Index position, const Index count = 0) + : position(position), count(count) {} + + Index GetStart() const { return position; } + Index GetEnd() const { return position + count; } + + void ChangeEnd(Index new_end) { count = new_end - position; } + + Range Normalize() const { + auto result = *this; + if (result.count < 0) { + result.position += result.count; + result.count = -result.count; + } + return result; + } + + Range CoerceInto(Index min, Index max) const { + auto coerce = [min, max](Index index) { + return index > max ? max : (index < min ? min : index); + }; + return Range::FromTwoSides(coerce(GetStart()), coerce(GetEnd())); + } + + Index position = 0; + Index count = 0; +}; +} // namespace cru diff --git a/include/cru/base/SelfResolvable.h b/include/cru/base/SelfResolvable.h new file mode 100644 index 00000000..84fa54f6 --- /dev/null +++ b/include/cru/base/SelfResolvable.h @@ -0,0 +1,153 @@ +#pragma once + +#include <cassert> +#include <functional> +#include <memory> +#include <type_traits> + +namespace cru { +template <typename T> +class SelfResolvable; + +template <typename T> +class ObjectResolver { + friend SelfResolvable<T>; + template <typename U> + friend class ObjectResolver; + + private: + template <typename U> + using Accessor_ = std::function<U*(const std::shared_ptr<void*>&)>; + using ThisAccessor_ = Accessor_<T>; + + explicit ObjectResolver(T* o) + : shared_object_ptr_(new void*(o)), + accessor_([](const std::shared_ptr<void*>& ptr) { + return static_cast<T*>(*ptr); + }) {} + explicit ObjectResolver(std::shared_ptr<void*> ptr, ThisAccessor_ accessor) + : shared_object_ptr_(std::move(ptr)), accessor_(std::move(accessor)) {} + + template <typename U> + static ThisAccessor_ CreateAccessor(Accessor_<U> parent_accessor) { + return [parent_accessor = + std::move(parent_accessor)](const std::shared_ptr<void*>& ptr) { + return static_cast<T*>(parent_accessor(ptr)); + }; + } + + public: + template <typename U, + typename = std::enable_if_t<std::is_convertible_v<U*, T*>>> + ObjectResolver(const ObjectResolver<U>& other) + : shared_object_ptr_(other.shared_object_ptr_), + accessor_(CreateAccessor(other.accessor_)) {} + + template <typename U, + typename = std::enable_if_t<std::is_convertible_v<U*, T*>>> + ObjectResolver(ObjectResolver<U>&& other) + : shared_object_ptr_(std::move(other.shared_object_ptr_)), + accessor_(CreateAccessor(std::move(other.accessor_))) {} + + ObjectResolver(const ObjectResolver&) = default; + ObjectResolver& operator=(const ObjectResolver&) = default; + ObjectResolver(ObjectResolver&&) = default; + ObjectResolver& operator=(ObjectResolver&&) = default; + ~ObjectResolver() = default; + + template <typename U, + typename = std::enable_if_t<std::is_convertible_v<U*, T*>>> + ObjectResolver& operator=(const ObjectResolver<U>& other) { + if (this != &other) { + this->shared_object_ptr_ = other.shared_object_ptr_; + this->accessor_ = CreateAccessor(other.accessor_); + } + return *this; + } + + template <typename U, + typename = std::enable_if_t<std::is_convertible_v<U*, T*>>> + ObjectResolver& operator=(ObjectResolver<U>&& other) { + if (this != &other) { + this->shared_object_ptr_ = std::move(other.shared_object_ptr_); + this->accessor_ = CreateAccessor(std::move(other.shared_object_ptr_)); + } + return *this; + } + + bool IsValid() const { return this->shared_object_ptr_ != nullptr; } + + T* Resolve() const { + assert(IsValid()); + return this->accessor_(this->shared_object_ptr_); + } + + /** + * @remarks So this class can be used as a functor. + */ + T* operator()() const { return Resolve(); } + + template <typename U, + typename = std::enable_if_t<std::is_convertible_v<T*, U*>>> + operator ObjectResolver<U>() const { + return ObjectResolver<U>(*this); + } + + private: + void SetResolvedObject(T* o) { + assert(IsValid()); + *this->shared_object_ptr_ = o; + } + + private: + std::shared_ptr<void*> shared_object_ptr_; + std::function<T*(const std::shared_ptr<void*>&)> accessor_; +}; + +/** + * @remarks + * This class is not copyable and movable since subclass is polymorphic and + * copying is then nonsense. However, you can even delete move capability in + * subclass because it may also be nonsense for subclass. The move capability is + * optional. + * + * Whether this class needs to be thread-safe still has to be considered. + */ +template <typename T> +class SelfResolvable { + public: + SelfResolvable() : resolver_(CastToSubClass()) {} + SelfResolvable(const SelfResolvable&) = delete; + SelfResolvable& operator=(const SelfResolvable&) = delete; + + // Resolvers to old object will resolve to new object. + SelfResolvable(SelfResolvable&& other) + : resolver_(std::move(other.resolver_)) { + this->resolver_.SetResolvedObject(CastToSubClass()); + } + + // Old resolvers for this object will resolve to nullptr. + // Other's resolvers will now resolve to this. + SelfResolvable& operator=(SelfResolvable&& other) { + if (this != &other) { + this->resolver_ = std::move(other.resolver_); + this->resolver_.SetResolvedObject(CastToSubClass()); + } + return *this; + } + + virtual ~SelfResolvable() { + if (this->resolver_.IsValid()) { + this->resolver_.SetResolvedObject(nullptr); + } + } + + ObjectResolver<T> CreateResolver() { return resolver_; } + + private: + T* CastToSubClass() { return static_cast<T*>(this); } + + private: + ObjectResolver<T> resolver_; +}; +} // namespace cru diff --git a/include/cru/base/String.h b/include/cru/base/String.h new file mode 100644 index 00000000..21a3db51 --- /dev/null +++ b/include/cru/base/String.h @@ -0,0 +1,492 @@ +#pragma once +#include "Base.h" + +#include "Buffer.h" +#include "Range.h" +#include "StringToNumberConverter.h" +#include "StringUtil.h" + +#include <filesystem> +#include <initializer_list> +#include <iterator> +#include <string> +#include <string_view> +#include <type_traits> +#include <vector> + +namespace cru { +class StringView; + +class CRU_BASE_API String { + public: + using value_type = char16_t; + using size_type = Index; + using difference_type = Index; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = value_type*; + using const_iterator = const value_type*; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + public: + static String FromUtf8(const char* str); + static String FromUtf8(const char* str, Index size); + static String FromUtf8(const std::byte* str, Index size); + static String FromUtf8(std::string_view str) { + return FromUtf8(str.data(), str.size()); + } + static String FromUtf8(const Buffer& buffer); + + static String FromUtf16(const char16_t* str) { return String(str); } + static String FromUtf16(const char16_t* str, Index size) { + return String(str, size); + } + static String FromUtf16(std::u16string_view str) { + return FromUtf16(str.data(), str.size()); + } + + static inline String From(StringView str); + + // Never use this if you don't know what this mean! + static String FromBuffer(pointer buffer, Index size, Index capacity) { + return String{from_buffer_tag{}, buffer, size, capacity}; + } + + static String FromStdPath(const std::filesystem::path& path); + +#ifdef CRU_PLATFORM_WINDOWS + static String FromUtf16(wchar_t* str) { return String(str); } + static String FromUtf16(wchar_t* str, Index size) { + return String(str, size); + } +#endif + + public: + String() = default; + + String(const_pointer str); + String(const_pointer str, size_type size); + + template <Index size> + String(const char16_t (&str)[size]) + : String(reinterpret_cast<const_pointer>(str), size - 1) {} + + template <typename Iter> + String(Iter start, Iter end) { + for (; start != end; start++) { + append(*start); + } + } + + String(size_type size, value_type ch = 0); + + String(std::initializer_list<value_type> l); + + explicit String(StringView str); + +#ifdef CRU_PLATFORM_WINDOWS + String(const wchar_t* str); + String(const wchar_t* str, Index size); + String(const std::wstring& str) : String(str.data(), str.size()) {} +#endif + + String(const String& other); + String(String&& other) noexcept; + + String& operator=(const String& other); + String& operator=(String&& other) noexcept; + + ~String(); + + private: + struct from_buffer_tag {}; + String(from_buffer_tag, pointer buffer, Index size, Index capacity); + + public: + bool empty() const { return this->size_ == 0; } + Index size() const { return this->size_; } + Index length() const { return this->size(); } + Index capacity() const { return this->capacity_; } + pointer data() { return this->buffer_; } + const_pointer data() const { return this->buffer_; } + + void resize(Index new_size); + void reserve(Index new_capacity); + void shrink_to_fit(); + + reference front() { return this->operator[](0); } + const_reference front() const { return this->operator[](0); } + + reference back() { return this->operator[](size_ - 1); } + const_reference back() const { return this->operator[](size_ - 1); } + + const_pointer c_str() const { return buffer_; } + + reference operator[](Index index) { return buffer_[index]; } + const_reference operator[](Index index) const { return buffer_[index]; } + + public: + iterator begin() { return this->buffer_; } + const_iterator begin() const { return this->buffer_; } + const_iterator cbegin() const { return this->buffer_; } + + iterator end() { return this->buffer_ + this->size_; } + const_iterator end() const { return this->buffer_ + this->size_; } + const_iterator cend() const { return this->buffer_ + this->size_; } + + reverse_iterator rbegin() { return reverse_iterator{end()}; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator{end()}; + } + const_reverse_iterator crbegin() const { + return const_reverse_iterator{cend()}; + } + + reverse_iterator rend() { return reverse_iterator{begin()}; } + const_reverse_iterator rend() const { + return const_reverse_iterator{begin()}; + } + const_reverse_iterator crend() const { + return const_reverse_iterator{cbegin()}; + } + + public: + void clear(); + iterator insert(const_iterator pos, value_type value) { + return this->insert(pos, &value, 1); + } + iterator insert(const_iterator pos, const_iterator str, Index size); + iterator insert(const_iterator pos, StringView str); + iterator erase(const_iterator pos) { return this->erase(pos, pos + 1); } + iterator erase(const_iterator start, const_iterator end); + void push_back(value_type value) { this->append(value); } + void pop_back() { this->erase(cend() - 1); } + void append(value_type value) { this->insert(cend(), value); } + void append(const_iterator str, Index size) { + this->insert(cend(), str, size); + } + inline void append(StringView str); + + String substr(size_type start, size_type size = -1) const { + if (size == -1) { + size = this->size_ - start; + } + return String(this->buffer_ + start, size); + } + + String& operator+=(value_type value) { + this->append(value); + return *this; + } + String& operator+=(StringView other); + + public: + operator std::u16string_view() const { + return std::u16string_view(data(), size()); + } + + StringView View() const; + + public: + Index Find(value_type value, Index start = 0) const; + std::vector<String> Split(value_type separator, + bool remove_space_line = false) const; + std::vector<String> SplitToLines(bool remove_space_line = false) const; + + bool StartWith(StringView str) const; + bool EndWith(StringView str) const; + + String& TrimStart(); + String& TrimEnd(); + String& Trim(); + + public: + void AppendCodePoint(CodePoint code_point); + + Utf16CodePointIterator CodePointIterator() const { + return Utf16CodePointIterator(buffer_, size_); + } + + Index IndexFromCodeUnitToCodePoint(Index code_unit_index) const; + Index IndexFromCodePointToCodeUnit(Index code_point_index) const; + Range RangeFromCodeUnitToCodePoint(Range code_unit_range) const; + Range RangeFromCodePointToCodeUnit(Range code_point_range) const; + + template <typename TInteger> + std::enable_if_t<std::is_signed_v<TInteger>, TInteger> ParseToInteger( + Index* processed_characters_count, unsigned flags, int base) const; + + int ParseToInt(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}, int base = 0) const; + long long ParseToLongLong(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}, int base = 0) const; + + float ParseToFloat(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}) const; + double ParseToDouble(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}) const; + std::vector<float> ParseToFloatList(value_type separator = u' ') const; + std::vector<double> ParseToDoubleList(value_type separator = u' ') const; + +#ifdef CRU_PLATFORM_WINDOWS + const wchar_t* WinCStr() const { + return reinterpret_cast<const wchar_t*>(c_str()); + } +#endif + + template <typename... T> + String Format(T&&... args) const; + + std::string ToUtf8() const; + Buffer ToUtf8Buffer(bool end_zero = true) const; + + int Compare(const String& other) const; + int CaseInsensitiveCompare(const String& other) const; + bool CaseInsensitiveEqual(const String& other) const { + return CaseInsensitiveCompare(other) == 0; + } + + private: + static char16_t kEmptyBuffer[1]; + + private: + char16_t* buffer_ = kEmptyBuffer; + Index size_ = 0; // not including trailing '\0' + Index capacity_ = 0; // always 1 smaller than real buffer size +}; + +std::ostream& CRU_BASE_API operator<<(std::ostream& os, const String& value); + +class CRU_BASE_API StringView { + public: + using value_type = char16_t; + using size_type = Index; + using difference_type = Index; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = const value_type*; + using const_iterator = const value_type*; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + StringView() = default; + + constexpr StringView(const_pointer ptr, Index size) + : ptr_(ptr), size_(size) {} + + template <Index size> + constexpr StringView(const value_type (&array)[size]) + : StringView(array, size - 1) {} + + StringView(const String& str) : StringView(str.data(), str.size()) {} + + CRU_DEFAULT_COPY(StringView) + CRU_DEFAULT_MOVE(StringView) + + ~StringView() = default; + + bool empty() const { return size_ == 0; } + Index size() const { return size_; } + const value_type* data() const { return ptr_; } + + public: + iterator begin() { return this->ptr_; } + const_iterator begin() const { return this->ptr_; } + const_iterator cbegin() const { return this->ptr_; } + + iterator end() { return this->ptr_ + this->size_; } + const_iterator end() const { return this->ptr_ + this->size_; } + const_iterator cend() const { return this->ptr_ + this->size_; } + + reverse_iterator rbegin() { return reverse_iterator{end()}; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator{end()}; + } + const_reverse_iterator crbegin() const { + return const_reverse_iterator{cend()}; + } + + reverse_iterator rend() { return reverse_iterator{begin()}; } + const_reverse_iterator rend() const { + return const_reverse_iterator{begin()}; + } + const_reverse_iterator crend() const { + return const_reverse_iterator{cbegin()}; + } + + StringView substr(Index pos); + StringView substr(Index pos, Index size); + + value_type operator[](Index index) const { return ptr_[index]; } + + operator std::u16string_view() const { + return std::u16string_view(data(), size()); + } + + public: + int Compare(const StringView& other) const; + int CaseInsensitiveCompare(const StringView& other) const; + bool CaseInsensitiveEqual(const StringView& other) const { + return CaseInsensitiveCompare(other) == 0; + } + + String ToString() const { return String(ptr_, size_); } + + Utf16CodePointIterator CodePointIterator() const { + return Utf16CodePointIterator(ptr_, size_); + } + + Index Find(value_type value, Index start = 0) const; + std::vector<String> Split(value_type separator, + bool remove_space_line = false) const; + std::vector<String> SplitToLines(bool remove_space_line = false) const; + + bool StartWith(StringView str) const; + bool EndWith(StringView str) const; + + Index IndexFromCodeUnitToCodePoint(Index code_unit_index) const; + Index IndexFromCodePointToCodeUnit(Index code_point_index) const; + Range RangeFromCodeUnitToCodePoint(Range code_unit_range) const; + Range RangeFromCodePointToCodeUnit(Range code_point_range) const; + + template <typename TInteger> + std::enable_if_t<std::is_signed_v<TInteger>, TInteger> ParseToInteger( + Index* processed_characters_count, StringToNumberFlag flags, + int base) const { + auto utf8_string = ToUtf8(); + auto result = StringToIntegerConverter(flags, base) + .Parse(utf8_string.data(), utf8_string.size(), + processed_characters_count); + return result.negate ? -result.value : result.value; + } + + int ParseToInt(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}, int base = 0) const; + long long ParseToLongLong(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}, int base = 0) const; + + float ParseToFloat(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}) const; + double ParseToDouble(Index* processed_characters_count = nullptr, + StringToNumberFlag flags = {}) const; + std::vector<float> ParseToFloatList(value_type separator = u' ') const; + std::vector<double> ParseToDoubleList(value_type separator = u' ') const; + + std::string ToUtf8() const; + Buffer ToUtf8Buffer(bool end_zero = true) const; + + private: + const char16_t* ptr_; + Index size_; +}; + +CRU_DEFINE_COMPARE_OPERATORS(String) + +inline String operator+(const String& left, const String& right) { + String result; + result += left; + result += right; + return result; +} + +inline String operator+(String::value_type left, const String& right) { + String result; + result += left; + result += right; + return result; +} + +inline String operator+(const String& left, String::value_type right) { + String result; + result += left; + result += right; + return result; +} + +CRU_DEFINE_COMPARE_OPERATORS(StringView) + +inline String::iterator String::insert(const_iterator pos, StringView str) { + return insert(pos, str.data(), str.size()); +} + +inline void String::append(StringView str) { + this->append(str.data(), str.size()); +} + +inline String String::From(StringView str) { return str.ToString(); } + +inline String::String(StringView str) : String(str.data(), str.size()) {} + +inline String ToString(StringView value) { return value.ToString(); } + +inline CodePoint Utf16PreviousCodePoint(StringView str, Index current, + Index* previous_position) { + return Utf16PreviousCodePoint(str.data(), str.size(), current, + previous_position); +} + +inline CodePoint Utf16NextCodePoint(StringView str, Index current, + Index* next_position) { + return Utf16NextCodePoint(str.data(), str.size(), current, next_position); +} + +inline bool Utf16IsValidInsertPosition(StringView str, Index position) { + return Utf16IsValidInsertPosition(str.data(), str.size(), position); +} + +// Return position after the character making predicate returns true or 0 if no +// character doing so. +inline Index CRU_BASE_API +Utf16BackwardUntil(StringView str, Index position, + const std::function<bool(CodePoint)>& predicate) { + return Utf16BackwardUntil(str.data(), str.size(), position, predicate); +} +// Return position before the character making predicate returns true or +// str.size() if no character doing so. +inline Index CRU_BASE_API +Utf16ForwardUntil(StringView str, Index position, + const std::function<bool(CodePoint)>& predicate) { + return Utf16ForwardUntil(str.data(), str.size(), position, predicate); +} + +inline Index Utf16PreviousWord(StringView str, Index position, + bool* is_space = nullptr) { + return Utf16PreviousWord(str.data(), str.size(), position, is_space); +} + +inline Index Utf16NextWord(StringView str, Index position, + bool* is_space = nullptr) { + return Utf16NextWord(str.data(), str.size(), position, is_space); +} + +String CRU_BASE_API ToLower(StringView s); +String CRU_BASE_API ToUpper(StringView s); + +template <typename TInteger> +std::enable_if_t<std::is_signed_v<TInteger>, TInteger> String::ParseToInteger( + Index* processed_characters_count, unsigned flags, int base) const { + View().ParseToInteger<TInteger>(processed_characters_count, flags, base); +} + +} // namespace cru + +template <> +struct std::hash<cru::String> { + std::size_t operator()(const cru::String& value) const { + return std::hash<std::u16string_view>{}(std::u16string_view( + reinterpret_cast<const char16_t*>(value.data()), value.size())); + } +}; + +template <> +struct std::hash<cru::StringView> { + std::size_t operator()(const cru::StringView& value) const { + return std::hash<std::u16string_view>{}(std::u16string_view( + reinterpret_cast<const char16_t*>(value.data()), value.size())); + } +}; diff --git a/include/cru/base/StringToNumberConverter.h b/include/cru/base/StringToNumberConverter.h new file mode 100644 index 00000000..758b26c8 --- /dev/null +++ b/include/cru/base/StringToNumberConverter.h @@ -0,0 +1,107 @@ +#pragma once +#include "Base.h" +#include "Bitmask.h" + +#include <cstddef> +#include <ostream> + +namespace cru { +namespace details { +struct StringToNumberFlagTag {}; +} // namespace details + +using StringToNumberFlag = Bitmask<details::StringToNumberFlagTag>; + +struct StringToNumberFlags { + constexpr static StringToNumberFlag kAllowLeadingSpaces = + StringToNumberFlag::FromOffset(0); + constexpr static StringToNumberFlag kAllowTrailingSpaces = + StringToNumberFlag::FromOffset(1); + constexpr static StringToNumberFlag kAllowTrailingJunk = + StringToNumberFlag::FromOffset(2); + constexpr static StringToNumberFlag kAllowLeadingZeroForInteger = + StringToNumberFlag::FromOffset(3); + constexpr static StringToNumberFlag kThrowOnError = + StringToNumberFlag::FromOffset(4); +}; + +template <typename TResult> +struct IStringToNumberConverter : virtual Interface { + virtual TResult Parse(const char* str, Index size, + Index* processed_characters_count) const = 0; + + template <std::size_t Size> + TResult Parse(const char (&str)[Size], + Index* processed_characters_count) const { + return Parse(str, Size - 1, processed_characters_count); + } +}; + +struct CRU_BASE_API StringToIntegerResult { + StringToIntegerResult() = default; + StringToIntegerResult(bool negate, unsigned long long value) + : negate(negate), value(value) {} + + bool negate; + unsigned long long value; +}; + +inline bool CRU_BASE_API operator==(const StringToIntegerResult& left, + const StringToIntegerResult& right) { + return left.negate == right.negate && left.value == right.value; +} + +inline bool CRU_BASE_API operator!=(const StringToIntegerResult& left, + const StringToIntegerResult& right) { + return !(left == right); +} + +inline std::ostream& CRU_BASE_API +operator<<(std::ostream& stream, const StringToIntegerResult& result) { + return stream << "StringToIntegerConverterImplResult(" + << (result.negate ? "-" : "") << result.value << ")"; +} + +/** + * \brief A converter that convert number into long long. + */ +struct CRU_BASE_API StringToIntegerConverter + : IStringToNumberConverter<StringToIntegerResult> { + public: + explicit StringToIntegerConverter(StringToNumberFlag flags, int base = 0) + : flags(flags), base(base) {} + + bool CheckParams() const; + + /** + * \brief Convert string to long long. + * \param str The string to convert. + * \param size The size of the string. + * \param processed_characters_count The number of characters that were + * processed. Or nullptr to not retrieve. + */ + StringToIntegerResult Parse(const char* str, Index size, + Index* processed_characters_count) const override; + using IStringToNumberConverter<StringToIntegerResult>::Parse; + + StringToNumberFlag flags; + /** + * \brief The base of the number used for parse or 0 for auto detect. + * \remarks Base can only be of range [2, 36] or 0. If base is 0, decimal is + * assumed by default ,or if str is started with "0x" or "0X" hexadecimal is + * assumed, or if str is started with a single "0" octoral is assumed, or if + * str is started with "0b" or "0B" binary is assumed. Otherwise it is an + * error. + */ + int base; +}; + +struct CRU_BASE_API StringToFloatConverter { + StringToFloatConverter(StringToNumberFlag flags) : flags(flags) {} + + double Parse(const char* str, Index size, + Index* processed_characters_count) const; + + StringToNumberFlag flags; +}; +} // namespace cru diff --git a/include/cru/base/StringUtil.h b/include/cru/base/StringUtil.h new file mode 100644 index 00000000..8f085283 --- /dev/null +++ b/include/cru/base/StringUtil.h @@ -0,0 +1,226 @@ +#pragma once +#include "Base.h" + +#include <functional> +#include <type_traits> + +namespace cru { +using CodePoint = std::int32_t; +constexpr CodePoint k_invalid_code_point = -1; + +inline bool IsUtf16SurrogatePairCodeUnit(char16_t c) { + return c >= 0xD800 && c <= 0xDFFF; +} + +inline bool IsUtf16SurrogatePairLeading(char16_t c) { + return c >= 0xD800 && c <= 0xDBFF; +} + +inline bool IsUtf16SurrogatePairTrailing(char16_t c) { + return c >= 0xDC00 && c <= 0xDFFF; +} + +CodePoint CRU_BASE_API Utf8NextCodePoint(const char* ptr, Index size, + Index current, Index* next_position); + +CodePoint CRU_BASE_API Utf16NextCodePoint(const char16_t* ptr, Index size, + Index current, Index* next_position); +CodePoint CRU_BASE_API Utf16PreviousCodePoint(const char16_t* ptr, Index size, + Index current, + Index* previous_position); + +template <typename CharType> +using NextCodePointFunctionType = CodePoint (*)(const CharType*, Index, Index, + Index*); + +template <typename CharType, + NextCodePointFunctionType<CharType> NextCodePointFunction> +class CodePointIterator { + public: + using difference_type = Index; + using value_type = CodePoint; + using pointer = void; + using reference = value_type; + using iterator_category = std::forward_iterator_tag; + + public: + struct past_end_tag_t {}; + + explicit CodePointIterator(const CharType* ptr, Index size, Index current = 0) + : ptr_(ptr), size_(size), position_(current) {} + explicit CodePointIterator(const CharType* ptr, Index size, past_end_tag_t) + : ptr_(ptr), size_(size), position_(size) {} + + CRU_DEFAULT_COPY(CodePointIterator) + CRU_DEFAULT_MOVE(CodePointIterator) + + ~CodePointIterator() = default; + + public: + const CharType* GetPtr() const { return ptr_; } + Index GetSize() const { return size_; } + Index GetPosition() const { return position_; } + + bool IsPastEnd() const { return position_ == static_cast<Index>(size_); } + + public: + CodePointIterator begin() const { return *this; } + CodePointIterator end() const { + return CodePointIterator{ptr_, size_, past_end_tag_t{}}; + } + + public: + bool operator==(const CodePointIterator& other) const { + // You should compare iterator that iterate on the same string. + Expects(this->ptr_ == other.ptr_ && this->size_ == other.size_); + return this->position_ == other.position_; + } + bool operator!=(const CodePointIterator& other) const { + return !this->operator==(other); + } + + CodePointIterator& operator++() { + Expects(!IsPastEnd()); + Forward(); + return *this; + } + + CodePointIterator operator++(int) { + Expects(!IsPastEnd()); + CodePointIterator old = *this; + Forward(); + return old; + } + + CodePoint operator*() const { + return NextCodePointFunction(ptr_, size_, position_, &next_position_cache_); + } + + private: + void Forward() { + if (next_position_cache_ > position_) { + position_ = next_position_cache_; + } else { + NextCodePointFunction(ptr_, size_, position_, &position_); + } + } + + private: + const CharType* ptr_; + Index size_; + Index position_; + mutable Index next_position_cache_ = 0; +}; + +using Utf8CodePointIterator = CodePointIterator<char, &Utf8NextCodePoint>; + +using Utf16CodePointIterator = CodePointIterator<char16_t, &Utf16NextCodePoint>; + +namespace details { +template <typename UInt, int number_of_bit, typename ReturnType> +inline std::enable_if_t<std::is_unsigned_v<UInt>, ReturnType> ExtractBits( + UInt n) { + return static_cast<ReturnType>(n & ((1u << number_of_bit) - 1)); +} +} // namespace details + +template <typename CharWriter> +std::enable_if_t<std::is_invocable_v<CharWriter, char>, bool> +Utf8EncodeCodePointAppend(CodePoint code_point, CharWriter&& writer) { + auto write_continue_byte = [&writer](std::uint8_t byte6) { + writer((1u << 7) + (((1u << 6) - 1) & byte6)); + }; + + if (code_point >= 0 && code_point <= 0x007F) { + writer(static_cast<char>(code_point)); + return true; + } else if (code_point >= 0x0080 && code_point <= 0x07FF) { + std::uint32_t unsigned_code_point = code_point; + writer( + static_cast<char>(details::ExtractBits<std::uint32_t, 5, std::uint8_t>( + (unsigned_code_point >> 6)) + + 0b11000000)); + write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>( + unsigned_code_point)); + return true; + } else if (code_point >= 0x0800 && code_point <= 0xFFFF) { + std::uint32_t unsigned_code_point = code_point; + writer( + static_cast<char>(details::ExtractBits<std::uint32_t, 4, std::uint8_t>( + (unsigned_code_point >> (6 * 2))) + + 0b11100000)); + write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>( + unsigned_code_point >> 6)); + write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>( + unsigned_code_point)); + return true; + } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) { + std::uint32_t unsigned_code_point = code_point; + writer( + static_cast<char>(details::ExtractBits<std::uint32_t, 3, std::uint8_t>( + (unsigned_code_point >> (6 * 3))) + + 0b11110000)); + write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>( + unsigned_code_point >> (6 * 2))); + write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>( + unsigned_code_point >> 6)); + write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>( + unsigned_code_point)); + return true; + } else { + return false; + } +} + +template <typename CharWriter> +std::enable_if_t<std::is_invocable_v<CharWriter, char16_t>, bool> +Utf16EncodeCodePointAppend(CodePoint code_point, CharWriter&& writer) { + if ((code_point >= 0 && code_point <= 0xD7FF) || + (code_point >= 0xE000 && code_point <= 0xFFFF)) { + writer(static_cast<char16_t>(code_point)); + return true; + } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) { + std::uint32_t u = code_point - 0x10000; + writer(static_cast<char16_t>( + details::ExtractBits<std::uint32_t, 10, std::uint32_t>(u >> 10) + + 0xD800u)); + writer(static_cast<char16_t>( + details::ExtractBits<std::uint32_t, 10, std::uint32_t>(u) + 0xDC00u)); + return true; + } else { + return false; + } +} + +// If given s is not a valid utf16 string, return value is UD. +bool CRU_BASE_API Utf16IsValidInsertPosition(const char16_t* ptr, Index size, + Index position); + +// Return position after the character making predicate returns true or 0 if no +// character doing so. +Index CRU_BASE_API +Utf16BackwardUntil(const char16_t* ptr, Index size, Index position, + const std::function<bool(CodePoint)>& predicate); +// Return position before the character making predicate returns true or +// str.size() if no character doing so. +Index CRU_BASE_API +Utf16ForwardUntil(const char16_t* ptr, Index size, Index position, + const std::function<bool(CodePoint)>& predicate); + +Index CRU_BASE_API Utf16PreviousWord(const char16_t* ptr, Index size, + Index position, bool* is_space = nullptr); +Index CRU_BASE_API Utf16NextWord(const char16_t* ptr, Index size, + Index position, bool* is_space = nullptr); + +char16_t CRU_BASE_API ToLower(char16_t c); +char16_t CRU_BASE_API ToUpper(char16_t c); + +bool CRU_BASE_API IsWhitespace(char16_t c); +bool CRU_BASE_API IsDigit(char16_t c); + +Utf8CodePointIterator CRU_BASE_API CreateUtf8Iterator(const std::byte* buffer, + Index size); +Utf8CodePointIterator CRU_BASE_API +CreateUtf8Iterator(const std::vector<std::byte>& buffer); + +} // namespace cru diff --git a/include/cru/base/SubProcess.h b/include/cru/base/SubProcess.h new file mode 100644 index 00000000..fbe8ad2b --- /dev/null +++ b/include/cru/base/SubProcess.h @@ -0,0 +1,258 @@ +#pragma once +#include "Base.h" +#include "Exception.h" +#include "String.h" +#include "io/Stream.h" + +#include <chrono> +#include <condition_variable> +#include <mutex> +#include <optional> +#include <unordered_map> +#include <vector> + +namespace cru { +enum class SubProcessStatus { + /** + * @brief The process has not been created and started. + */ + Prepare, + /** + * @brief The process is failed to start. + */ + FailedToStart, + /** + * @brief The process is running now. + */ + Running, + /** + * @brief The process has been exited. + */ + Exited, +}; + +class CRU_BASE_API SubProcessException : public Exception { + public: + using Exception::Exception; +}; + +class CRU_BASE_API SubProcessFailedToStartException + : public SubProcessException { + public: + using SubProcessException::SubProcessException; +}; + +class CRU_BASE_API SubProcessInternalException : public SubProcessException { + public: + using SubProcessException::SubProcessException; +}; + +struct SubProcessStartInfo { + String program; + std::vector<String> arguments; + std::unordered_map<String, String> environments; +}; + +enum class SubProcessExitType { + Unknown, + Normal, + Signal, +}; + +struct SubProcessExitResult { + SubProcessExitType exit_type; + int exit_code; + int exit_signal; + bool has_core_dump; + + bool IsSuccess() const { + return exit_type == SubProcessExitType::Normal && exit_code == 0; + } + + static SubProcessExitResult Unknown() { + return {SubProcessExitType::Unknown, 0, 0, false}; + } + + static SubProcessExitResult Normal(int exit_code) { + return {SubProcessExitType::Normal, exit_code, 0, false}; + } + + static SubProcessExitResult Signal(int exit_signal, bool has_core_dump) { + return {SubProcessExitType::Normal, 0, exit_signal, has_core_dump}; + } +}; + +struct IPlatformSubProcessImpl : virtual Interface { + /** + * @brief Create and start a real process. + * + * If the program can't be created or start, an exception should be + * thrown. + * + * Implementation should fill internal data of the new process and start + * it. + * + * This method will be called only once in first call of `Start` on this + * thread with lock holdDefaultConstructible. + */ + virtual void PlatformCreateProcess(const SubProcessStartInfo& start_info) = 0; + + /** + * @brief Wait for the created process forever and return the exit result + * when process stops. + * + * Implementation should wait for the real process forever, after that, + * fill internal data and returns exit result. + * + * This method will be called only once on another thread after + * `PlatformCreateProcess` returns successfully + */ + virtual SubProcessExitResult PlatformWaitForProcess() = 0; + + /** + * @brief Kill the process immediately. + * + * This method will be called only once on this thread given + * `PlatformCreateProcess` returns successfully. There will be a window + * that the window already exits but the status has not been updated and + * this is called. So handle this gracefully and write to internal data + * carefully. + */ + virtual void PlatformKillProcess() = 0; + + virtual io::Stream* GetStdinStream() = 0; + virtual io::Stream* GetStdoutStream() = 0; + virtual io::Stream* GetStderrStream() = 0; +}; + +/** + * @brief A wrapper platform process. It is one-time, which means it + * starts and exits and can't start again. + * + * TODO: Current implementation has a problem. If the process does not exit for + * a long time, the resource related to it will not be released. It may cause a + * leak. + */ +class PlatformSubProcess : public Object { + CRU_DEFINE_CLASS_LOG_TAG(u"PlatformSubProcess") + + private: + struct State { + explicit State(SubProcessStartInfo start_info, + std::shared_ptr<IPlatformSubProcessImpl> impl) + : start_info(std::move(start_info)), impl(std::move(impl)) {} + + std::mutex mutex; + std::condition_variable condition_variable; + SubProcessStartInfo start_info; + SubProcessExitResult exit_result; + SubProcessStatus status = SubProcessStatus::Prepare; + bool killed = false; + std::shared_ptr<IPlatformSubProcessImpl> impl; + }; + + public: + PlatformSubProcess(SubProcessStartInfo start_info, + std::shared_ptr<IPlatformSubProcessImpl> impl); + + ~PlatformSubProcess() override; + + /** + * @brief Create and start a real process. If the process can't be created or + * start, `SubProcessFailedToStartException` will be thrown. If this function + * is already called once, `SubProcessException` will be thrown. Ensure only + * call this method once. + */ + void Start(); + + /** + * @brief Wait for the process to exit optionally for at most `wait_time`. If + * the process already exits, it will return immediately. If the process has + * not started or failed to start, `SubProcessException` will be thrown. + * Ensure `Start` is called and does not throw before calling this. + * + * @remarks You may wish this returns bool to indicate whether it is timeout + * or the process exits. But no, even if it is timeout, the process may also + * have exited due to task schedule. + */ + void Wait(std::optional<std::chrono::milliseconds> wait_time); + + /** + * @brief kill the process if it is running. If the process already exits, + * nothing will happen. If the process has not started or failed to start, + * `SubProcessException` will be throw. Ensure `Start` is called and does not + * throw before calling this. + */ + void Kill(); + + /** + * @brief Get the status of the process. + * 1. If the process has tried to start, aka `Start` is called, then this + * method will return one of `Running`, `FailedToStart`, `Exited`. + * 2. If it returns `Prepare`, `Start` is not called. + * 3. It does NOT guarantee that this return `Running` and the process is + * actually running. Because there might be a window that the process exits + * already but status is not updated. + */ + SubProcessStatus GetStatus(); + + /** + * @brief Get the exit result. If the process is not started, failed to start + * or running, `SubProcessException` will be thrown. + */ + SubProcessExitResult GetExitResult(); + + io::Stream* GetStdinStream(); + io::Stream* GetStdoutStream(); + io::Stream* GetStderrStream(); + + private: + std::shared_ptr<State> state_; + std::unique_lock<std::mutex> lock_; +}; + +class CRU_BASE_API SubProcess : public Object { + CRU_DEFINE_CLASS_LOG_TAG(u"SubProcess") + + public: + static SubProcess Create( + String program, std::vector<String> arguments = {}, + std::unordered_map<String, String> environments = {}); + + static SubProcessExitResult Call( + String program, std::vector<String> arguments = {}, + std::unordered_map<String, String> environments = {}); + + public: + SubProcess(SubProcessStartInfo start_info); + + CRU_DELETE_COPY(SubProcess) + + SubProcess(SubProcess&& other) = default; + SubProcess& operator=(SubProcess&& other) = default; + + ~SubProcess() override; + + public: + void Wait(std::optional<std::chrono::milliseconds> wait_time = std::nullopt); + void Kill(); + + SubProcessStatus GetStatus(); + SubProcessExitResult GetExitResult(); + + io::Stream* GetStdinStream(); + io::Stream* GetStdoutStream(); + io::Stream* GetStderrStream(); + + void Detach(); + + bool IsValid() const { return platform_process_ != nullptr; } + explicit operator bool() const { return IsValid(); } + + private: + void CheckValid() const; + + private: + std::unique_ptr<PlatformSubProcess> platform_process_; +}; +} // namespace cru diff --git a/include/cru/base/concurrent/ConcurrentQueue.h b/include/cru/base/concurrent/ConcurrentQueue.h new file mode 100644 index 00000000..e311d5f9 --- /dev/null +++ b/include/cru/base/concurrent/ConcurrentQueue.h @@ -0,0 +1,88 @@ +#pragma once +#include <condition_variable> +#include <mutex> +#include <optional> +#include <utility> + +namespace cru::concurrent { +namespace details { +template <typename T> +struct ConcurrentQueueNode { + ConcurrentQueueNode(T&& value, ConcurrentQueueNode* next = nullptr) + : value(std::move(value)), next(next) {} + + T value; + ConcurrentQueueNode* next; +}; +} // namespace details + +template <typename T> +class ConcurrentQueue { + public: + ConcurrentQueue() {} + + ConcurrentQueue(const ConcurrentQueue&) = delete; + ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; + + ~ConcurrentQueue() { + if (head_) { + auto node = head_; + while (node) { + auto next = node->next; + delete node; + node = next; + } + } + } + + public: + void Push(T&& value) { + std::unique_lock<std::mutex> lock(mutex_); + if (head_ == nullptr) { + head_ = tail_ = new details::ConcurrentQueueNode<T>(std::move(value)); + condition_variable_.notify_one(); + } else { + tail_->next = new details::ConcurrentQueueNode<T>(std::move(value)); + tail_ = tail_->next; + } + } + + T Pull() { + std::unique_lock<std::mutex> lock(mutex_); + if (head_ == nullptr) { + condition_variable_.wait(lock); + } + assert(head_ != nullptr); + auto value = std::move(head_->value); + auto next = head_->next; + delete head_; + head_ = next; + if (next == nullptr) { + tail_ = nullptr; + } + return value; + } + + std::optional<T> Poll() { + std::unique_lock<std::mutex> lock(mutex_); + if (head_ == nullptr) { + return std::nullopt; + } + auto value = std::move(head_->value); + auto next = head_->next; + delete head_; + head_ = next; + if (next == nullptr) { + tail_ = nullptr; + } + return value; + } + + private: + details::ConcurrentQueueNode<T>* head_ = nullptr; + details::ConcurrentQueueNode<T>* tail_ = nullptr; + + std::mutex mutex_; + std::condition_variable condition_variable_; +}; +} // namespace cru::concurrent diff --git a/include/cru/base/io/AutoReadStream.h b/include/cru/base/io/AutoReadStream.h new file mode 100644 index 00000000..759d5026 --- /dev/null +++ b/include/cru/base/io/AutoReadStream.h @@ -0,0 +1,72 @@ +#pragma once + +#include "BufferStream.h" +#include "Stream.h" + +#include <mutex> +#include <thread> + +namespace cru::io { +struct AutoReadStreamOptions { + /** + * @brief Will be passed to BufferStreamOptions::block_size. + */ + Index block_size = 0; + + /** + * @brief Will be passed to BufferStreamOptions::total_size_limit. + */ + Index total_size_limit = 0; + + BufferStreamOptions GetBufferStreamOptions() const { + BufferStreamOptions options; + options.block_size = block_size; + options.total_size_limit = total_size_limit; + return options; + } +}; + +/** + * @brief A stream that wraps another stream and auto read it into a buffer in a + * background thread. + */ +class CRU_BASE_API AutoReadStream : public Stream { + public: + /** + * @brief Wrap a stream and auto read it in background. + * @param stream The stream to auto read. + * @param auto_delete Whether to delete the stream object in destructor. + * @param options Options to modify the behavior. + */ + AutoReadStream( + Stream* stream, bool auto_delete, + const AutoReadStreamOptions& options = AutoReadStreamOptions()); + + ~AutoReadStream() override; + + public: + CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE + + void BeginToDrop(bool auto_delete = true); + + protected: + Index DoRead(std::byte* buffer, Index offset, Index size) override; + Index DoWrite(const std::byte* buffer, Index offset, Index size) override; + void DoFlush() override; + + private: + void DoClose(); + + void BackgroundThreadRun(); + + private: + Stream* stream_; + bool auto_delete_; + + Index size_per_read_; + std::unique_ptr<BufferStream> buffer_stream_; + std::mutex buffer_stream_mutex_; + + std::thread background_thread_; +}; +} // namespace cru::io diff --git a/include/cru/base/io/BufferStream.h b/include/cru/base/io/BufferStream.h new file mode 100644 index 00000000..5ebff546 --- /dev/null +++ b/include/cru/base/io/BufferStream.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../Buffer.h" +#include "../Exception.h" +#include "Stream.h" + +#include <condition_variable> +#include <list> +#include <mutex> + +namespace cru::io { +class WriteAfterEofException : public Exception { + public: + using Exception::Exception; + ~WriteAfterEofException() override = default; +}; + +struct BufferStreamOptions { + /** + * Actually I have no ideas about the best value for this. May change it later + * when I get some ideas. + */ + constexpr static Index kDefaultBlockSize = 1024; + + /** + * @brief The size of a single buffer allocated each time new space is needed. + * Use default value if <= 0. + * + * When current buffer is full and there is no space for following data, a new + * buffer will be allocated and appended to the buffer list. Note if sum size + * of all buffers reaches the total_buffer_limit, no more buffer will be + * allocated but wait. + */ + Index block_size = 0; + + /** + * @brief Total size limit of saved data in buffer. No limit if <= 0. + * + * The size will be floor(total_size_limit / block_size). When the buffer is + * filled, it will block and wait for user to read to get free space of buffer + * to continue read. + */ + Index total_size_limit = 0; + + Index GetBlockSizeOrDefault() const { + return block_size <= 0 ? kDefaultBlockSize : block_size; + } + + Index GetMaxBlockCount() const { + return total_size_limit / GetBlockSizeOrDefault(); + } +}; + +/** + * @brief SPSC (Single Producer Single Consumer) buffer stream. + * + * If used by multiple producer or multiple consumer, the behavior is undefined. + */ +class BufferStream : public Stream { + public: + BufferStream(const BufferStreamOptions& options); + ~BufferStream() override; + + CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE + + void SetEof(); + + protected: + Index DoRead(std::byte* buffer, Index offset, Index size) override; + Index DoWrite(const std::byte* buffer, Index offset, Index size) override; + + private: + void DoClose(); + + private: + Index block_size_; + Index max_block_count_; + + std::list<Buffer> buffer_list_; + bool eof_; + + std::mutex mutex_; + std::condition_variable condition_variable_; +}; +} // namespace cru::io diff --git a/include/cru/base/io/CFileStream.h b/include/cru/base/io/CFileStream.h new file mode 100644 index 00000000..0b58bdc9 --- /dev/null +++ b/include/cru/base/io/CFileStream.h @@ -0,0 +1,39 @@ +#pragma once + +#include "Stream.h" + +#include <cstdio> + +namespace cru::io { +class CRU_BASE_API CFileStream : public Stream { + public: + CFileStream(const char* path, const char* mode); + explicit CFileStream(std::FILE* file, bool readable = true, + bool writable = true, bool auto_close = true); + + CRU_DELETE_COPY(CFileStream) + CRU_DELETE_MOVE(CFileStream) + + ~CFileStream() override; + + public: + CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE + + std::FILE* GetHandle() const; + + protected: + Index DoSeek(Index offset, SeekOrigin origin) override; + Index DoTell() override; + void DoRewind() override; + Index DoRead(std::byte* buffer, Index offset, Index size) override; + Index DoWrite(const std::byte* buffer, Index offset, Index size) override; + void DoFlush() override; + + private: + void DoClose(); + + private: + std::FILE* file_; + bool auto_close_; +}; +} // namespace cru::io diff --git a/include/cru/base/io/MemoryStream.h b/include/cru/base/io/MemoryStream.h new file mode 100644 index 00000000..a1f90c3b --- /dev/null +++ b/include/cru/base/io/MemoryStream.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Stream.h" + +#include <functional> + +namespace cru::io { +class CRU_BASE_API MemoryStream : public Stream { + public: + MemoryStream( + std::byte* buffer, Index size, bool read_only = false, + std::function<void(std::byte* buffer, Index size)> release_func = {}); + + ~MemoryStream() override; + + public: + void Close() override; + + std::byte* GetBuffer() const { return buffer_; } + + protected: + Index DoSeek(Index offset, SeekOrigin origin) override; + Index DoGetSize() override { return size_; } + Index DoRead(std::byte* buffer, Index offset, Index size) override; + Index DoWrite(const std::byte* buffer, Index offset, Index size) override; + + private: + void DoClose(); + + private: + std::byte* buffer_; + Index size_; + Index position_; + std::function<void(std::byte* buffer, Index size)> release_func_; +}; +} // namespace cru::io diff --git a/include/cru/base/io/OpenFileFlag.h b/include/cru/base/io/OpenFileFlag.h new file mode 100644 index 00000000..4a5789fb --- /dev/null +++ b/include/cru/base/io/OpenFileFlag.h @@ -0,0 +1,56 @@ +#pragma once + +#include "../Bitmask.h" + +namespace cru::io { +namespace details { +struct OpenFileFlagTag {}; +} // namespace details +using OpenFileFlag = Bitmask<details::OpenFileFlagTag>; + +struct OpenFileFlags { + /** + * \brief for reading + * If the file does not exist, FileNotExistException should be thrown. + */ + static constexpr OpenFileFlag Read{0x1}; + + /** + * \brief for writing + * If the file does not exist and Create is not specified, + * FileNotExistException should be thrown. + */ + static constexpr OpenFileFlag Write{0x2}; + + /** + * \brief when writing, seek to end first + * Only effective for writing. + */ + static constexpr OpenFileFlag Append{0x4}; + + /** + * \brief when writing, truncate the file to empty + * Only effective for writing. + * So the content is erased! Be careful! + */ + static constexpr OpenFileFlag Truncate{0x8}; + + /** + * \brief when writing, if the file does not exist, create one + * Only effective for writing. When file does not exist, FileNotExistException + * will NOT be thrown and a new file will be created. + */ + static constexpr OpenFileFlag Create{0x10}; + + /** + * TODO: ??? + */ + static constexpr OpenFileFlag Exclusive{0x20}; +}; + +/** + * Append, Truncate, Create must be used with Write. + * Append and Truncate must not be used together. + */ +bool CheckOpenFileFlag(OpenFileFlag flags); +} // namespace cru::io diff --git a/include/cru/base/io/ProxyStream.h b/include/cru/base/io/ProxyStream.h new file mode 100644 index 00000000..42ec9dfd --- /dev/null +++ b/include/cru/base/io/ProxyStream.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Stream.h" + +#include <functional> + +namespace cru::io { +struct ProxyStreamHandlers { + std::function<Index(Index offset, Stream::SeekOrigin origin)> seek; + std::function<Index(std::byte* buffer, Index offset, Index size)> read; + std::function<Index(const std::byte* buffer, Index offset, Index size)> write; + std::function<void()> flush; + + /** + * @brief This method will be only called once when `Close` is called or the + * stream is destructed. + */ + std::function<void()> close; +}; + +class ProxyStream : public Stream { + public: + explicit ProxyStream(ProxyStreamHandlers handlers); + + ~ProxyStream() override; + + public: + CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE + + protected: + Index DoSeek(Index offset, SeekOrigin origin) override; + Index DoRead(std::byte* buffer, Index offset, Index size) override; + Index DoWrite(const std::byte* buffer, Index offset, Index size) override; + void DoFlush() override; + + private: + void DoClose(); + + private: + ProxyStreamHandlers handlers_; +}; +} // namespace cru::io diff --git a/include/cru/base/io/Resource.h b/include/cru/base/io/Resource.h new file mode 100644 index 00000000..1d5313a6 --- /dev/null +++ b/include/cru/base/io/Resource.h @@ -0,0 +1,8 @@ +#pragma once +#include "../Base.h" + +#include <filesystem> + +namespace cru::io { +std::filesystem::path CRU_BASE_API GetResourceDir(); +} diff --git a/include/cru/base/io/Stream.h b/include/cru/base/io/Stream.h new file mode 100644 index 00000000..e0b61627 --- /dev/null +++ b/include/cru/base/io/Stream.h @@ -0,0 +1,126 @@ +#pragma once + +#include "../Base.h" + +#include "../Exception.h" +#include "../String.h" + +#include <cstddef> + +namespace cru::io { +class CRU_BASE_API StreamOperationNotSupportedException : public Exception { + public: + explicit StreamOperationNotSupportedException(String operation); + + CRU_DEFAULT_DESTRUCTOR(StreamOperationNotSupportedException) + + public: + String GetOperation() const { return operation_; } + + public: + static void CheckSeek(bool seekable); + static void CheckRead(bool readable); + static void CheckWrite(bool writable); + + private: + String operation_; +}; + +class CRU_BASE_API StreamAlreadyClosedException : public Exception { + public: + StreamAlreadyClosedException(); + + CRU_DEFAULT_DESTRUCTOR(StreamAlreadyClosedException) + + static void Check(bool closed); +}; + +#define CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE \ + void Close() override { DoClose(); } + +#define CRU_STREAM_BEGIN_CLOSE \ + if (GetClosed()) return; \ + CloseGuard close_guard(this); + +/** + * All stream is thread-unsafe by default unless being documented. + */ +class CRU_BASE_API Stream : public Object { + protected: + struct SupportedOperations { + std::optional<bool> can_seek; + std::optional<bool> can_read; + std::optional<bool> can_write; + }; + + struct CloseGuard { + explicit CloseGuard(Stream* stream) : stream(stream) {} + ~CloseGuard() { stream->SetClosed(true); } + Stream* stream; + }; + + protected: + explicit Stream(SupportedOperations supported_operations = {}); + Stream(std::optional<bool> can_seek, std::optional<bool> can_read, + std::optional<bool> can_write); + + public: + enum class SeekOrigin { Current, Begin, End }; + + CRU_DELETE_COPY(Stream) + CRU_DELETE_MOVE(Stream) + + ~Stream() override = default; + + public: + bool CanSeek(); + Index Seek(Index offset, SeekOrigin origin = SeekOrigin::Current); + Index Tell(); + void Rewind(); + Index GetSize(); + + bool CanRead(); + Index Read(std::byte* buffer, Index offset, Index size); + Index Read(std::byte* buffer, Index size); + Index Read(char* buffer, Index offset, Index size); + Index Read(char* buffer, Index size); + + bool CanWrite(); + Index Write(const std::byte* buffer, Index offset, Index size); + Index Write(const std::byte* buffer, Index size); + Index Write(const char* buffer, Index offset, Index size); + Index Write(const char* buffer, Index size); + + void Flush(); + virtual void Close() = 0; + + virtual Buffer ReadToEnd(Index grow_size = 256); + + // Utf8 encoding. + String ReadToEndAsUtf8String(); + + protected: + virtual bool DoCanSeek(); + virtual bool DoCanRead(); + virtual bool DoCanWrite(); + virtual Index DoSeek(Index offset, SeekOrigin origin); + virtual Index DoTell(); + virtual void DoRewind(); + virtual Index DoGetSize(); + virtual Index DoRead(std::byte* buffer, Index offset, Index size); + virtual Index DoWrite(const std::byte* buffer, Index offset, Index size); + virtual void DoFlush(); + + void SetSupportedOperations(SupportedOperations supported_operations) { + supported_operations_ = std::move(supported_operations); + } + + bool GetClosed() { return closed_; } + void SetClosed(bool closed) { closed_ = closed; } + void CheckClosed() { StreamAlreadyClosedException::Check(closed_); } + + private: + std::optional<SupportedOperations> supported_operations_; + bool closed_; +}; +} // namespace cru::io diff --git a/include/cru/base/log/Logger.h b/include/cru/base/log/Logger.h new file mode 100644 index 00000000..25735e14 --- /dev/null +++ b/include/cru/base/log/Logger.h @@ -0,0 +1,88 @@ +#pragma once +#include "../Base.h" + +#include "../Format.h" +#include "../String.h" +#include "../concurrent/ConcurrentQueue.h" + +#include <memory> +#include <mutex> +#include <thread> +#include <vector> + +namespace cru::log { +enum class LogLevel { Debug, Info, Warn, Error }; + +struct CRU_BASE_API LogInfo { + LogInfo(LogLevel level, String tag, String message) + : level(level), tag(std::move(tag)), message(std::move(message)) {} + + CRU_DEFAULT_COPY(LogInfo) + CRU_DEFAULT_MOVE(LogInfo) + + ~LogInfo() = default; + + LogLevel level; + String tag; + String message; +}; + +struct CRU_BASE_API ILogTarget : virtual Interface { + // Write the string s. LogLevel is just a helper. It has no effect on the + // content to write. + virtual void Write(LogLevel level, StringView s) = 0; +}; + +class CRU_BASE_API Logger : public Object { + public: + static Logger* GetInstance(); + + public: + Logger(); + + CRU_DELETE_COPY(Logger) + CRU_DELETE_MOVE(Logger) + + ~Logger() override; + + public: + void AddLogTarget(std::unique_ptr<ILogTarget> source); + void RemoveLogTarget(ILogTarget* source); + + public: + void Log(LogLevel level, String tag, String message) { + Log(LogInfo(level, std::move(tag), std::move(message))); + } + void Log(LogInfo log_info); + + template <typename... Args> + void FormatLog(LogLevel level, String tag, StringView format, + Args&&... args) { + Log(level, std::move(tag), Format(format, std::forward<Args>(args)...)); + } + + private: + concurrent::ConcurrentQueue<LogInfo> log_queue_; + + std::mutex target_list_mutex_; + std::vector<std::unique_ptr<ILogTarget>> target_list_; + + std::thread log_thread_; +}; +} // namespace cru::log + +#define CRU_LOG_DEBUG(...) \ + cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Debug, \ + kLogTag, __VA_ARGS__) + +#define CRU_LOG_INFO(...) \ + cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Info, \ + kLogTag, __VA_ARGS__) + +#define CRU_LOG_WARN(...) \ + cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Warn, \ + kLogTag, __VA_ARGS__) + +#define CRU_LOG_ERROR(...) \ + cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Error, \ + kLogTag, __VA_ARGS__) diff --git a/include/cru/base/log/StdioLogTarget.h b/include/cru/base/log/StdioLogTarget.h new file mode 100644 index 00000000..4123766b --- /dev/null +++ b/include/cru/base/log/StdioLogTarget.h @@ -0,0 +1,17 @@ +#pragma once +#include "Logger.h" + +namespace cru::log { +class StdioLogTarget : public Object, public virtual log::ILogTarget { + public: + explicit StdioLogTarget(); + + CRU_DELETE_COPY(StdioLogTarget) + CRU_DELETE_MOVE(StdioLogTarget) + + ~StdioLogTarget() override; + + public: + void Write(log::LogLevel level, StringView s) override; +}; +} // namespace cru::log diff --git a/include/cru/base/platform/Exception.h b/include/cru/base/platform/Exception.h new file mode 100644 index 00000000..74dd6ad4 --- /dev/null +++ b/include/cru/base/platform/Exception.h @@ -0,0 +1,12 @@ +#pragma once +#include "../Base.h" +#include "../Exception.h" + +namespace cru::platform { +class CRU_BASE_API PlatformException : public Exception { + public: + using Exception::Exception; // inherit constructors + + CRU_DEFAULT_DESTRUCTOR(PlatformException) +}; +} // namespace cru::platform diff --git a/include/cru/base/platform/osx/Convert.h b/include/cru/base/platform/osx/Convert.h new file mode 100644 index 00000000..395cbbae --- /dev/null +++ b/include/cru/base/platform/osx/Convert.h @@ -0,0 +1,18 @@ +#pragma once +#include "../../PreConfig.h" + +#ifndef CRU_PLATFORM_OSX +#error "This file can only be included on osx." +#endif + +#include "../../String.h" + +#include <CoreFoundation/CoreFoundation.h> + +namespace cru::platform::osx { +CFStringRef Convert(const String& string); +String Convert(CFStringRef string); + +CFRange Convert(const Range& range); +Range Convert(const CFRange& range); +} // namespace cru::platform::osx diff --git a/include/cru/base/platform/osx/Exception.h b/include/cru/base/platform/osx/Exception.h new file mode 100644 index 00000000..5ab14ebd --- /dev/null +++ b/include/cru/base/platform/osx/Exception.h @@ -0,0 +1,15 @@ +#pragma once +#include "../../PreConfig.h" + +#ifndef CRU_PLATFORM_OSX +#error "This file can only be included on osx." +#endif + +#include "../Exception.h" + +namespace cru::platform::osx { +class OsxException : public PlatformException { + public: + using PlatformException::PlatformException; +}; +} // namespace cru::platform::osx diff --git a/include/cru/base/platform/unix/PosixSpawnSubProcess.h b/include/cru/base/platform/unix/PosixSpawnSubProcess.h new file mode 100644 index 00000000..ee4e912a --- /dev/null +++ b/include/cru/base/platform/unix/PosixSpawnSubProcess.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../../PreConfig.h" + +#ifndef CRU_PLATFORM_UNIX +#error "This file can only be included on unix." +#endif + +#include "../../Base.h" +#include "../../SubProcess.h" +#include "../../io/AutoReadStream.h" + +#include "UnixFileStream.h" +#include "UnixPipe.h" + +#include <spawn.h> + +namespace cru::platform::unix { +class PosixSpawnSubProcessImpl : public Object, + public virtual IPlatformSubProcessImpl { + CRU_DEFINE_CLASS_LOG_TAG(u"PosixSpawnSubProcess") + + public: + explicit PosixSpawnSubProcessImpl(); + ~PosixSpawnSubProcessImpl(); + + void PlatformCreateProcess(const SubProcessStartInfo& start_info) override; + SubProcessExitResult PlatformWaitForProcess() override; + void PlatformKillProcess() override; + + io::Stream* GetStdinStream() override; + io::Stream* GetStdoutStream() override; + io::Stream* GetStderrStream() override; + + private: + pid_t pid_; + int exit_code_; + + UnixPipe stdin_pipe_; + UnixPipe stdout_pipe_; + UnixPipe stderr_pipe_; + + std::unique_ptr<UnixFileStream> stdin_stream_; + std::unique_ptr<UnixFileStream> stdout_stream_; + std::unique_ptr<UnixFileStream> stderr_stream_; + + std::unique_ptr<io::AutoReadStream> stdout_buffer_stream_; + std::unique_ptr<io::AutoReadStream> stderr_buffer_stream_; +}; +} // namespace cru::platform::unix diff --git a/include/cru/base/platform/unix/UnixFileStream.h b/include/cru/base/platform/unix/UnixFileStream.h new file mode 100644 index 00000000..8021f21a --- /dev/null +++ b/include/cru/base/platform/unix/UnixFileStream.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../../PreConfig.h" + +#ifndef CRU_PLATFORM_UNIX +#error "This file can only be included on unix." +#endif + +#include "../../io/Stream.h" + +namespace cru::platform::unix { +class UnixFileStream : public io::Stream { + private: + static constexpr auto kLogTag = u"cru::platform::unix::UnixFileStream"; + + public: + UnixFileStream(const char* path, int oflag, mode_t mode = 0660); + UnixFileStream(int fd, bool can_seek, bool can_read, bool can_write, + bool auto_close); + ~UnixFileStream() override; + + public: + CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE + + int GetFileDescriptor() const { return file_descriptor_; } + + protected: + Index DoSeek(Index offset, SeekOrigin origin = SeekOrigin::Current) override; + Index DoRead(std::byte* buffer, Index offset, Index size) override; + Index DoWrite(const std::byte* buffer, Index offset, Index size) override; + + private: + void DoClose(); + + private: + int file_descriptor_; // -1 for no file descriptor + bool auto_close_; +}; +} // namespace cru::platform::unix diff --git a/include/cru/base/platform/unix/UnixPipe.h b/include/cru/base/platform/unix/UnixPipe.h new file mode 100644 index 00000000..cf35fb11 --- /dev/null +++ b/include/cru/base/platform/unix/UnixPipe.h @@ -0,0 +1,70 @@ +#pragma once + +#include "../../PreConfig.h" + +#ifndef CRU_PLATFORM_UNIX +#error "This file can only be included on unix." +#endif + +#include "../../Base.h" +#include "../../Bitmask.h" + +namespace cru::platform::unix { +namespace details { +struct UnixPipeFlagTag; +} +using UnixPipeFlag = Bitmask<details::UnixPipeFlagTag>; +struct UnixPipeFlags { + constexpr static auto NonBlock = UnixPipeFlag::FromOffset(1); +}; + +/** + * @brief an unix pipe, commonly for communication of parent process and child + * process. + * + * There are two types of pipes sorted by its usage. For stdin, parent process + * SEND data to child process. For stdout and stderr, parent process RECEIVE + * data from child process. Each pipe has two ends, one for read and the other + * for write. But for send and receive, they are reversed. It is a little + * confused to consider which end should be used by parent and which end should + * be used by child. So this class help you make it clear. You specify SEND or + * RECEIVE, and this class give you a parent used end and a child used end. + * + * This class will only close the end used by parent when it is destructed. It + * is the user's duty to close the one used by child. + */ +class UnixPipe : public Object { + private: + constexpr static auto kLogTag = u"cru::platform::unix::UnixPipe"; + + public: + enum class Usage { + Send, + Receive, + }; + + explicit UnixPipe(Usage usage, bool auto_close, UnixPipeFlag flags = {}); + + CRU_DELETE_COPY(UnixPipe) + CRU_DELETE_MOVE(UnixPipe) + + ~UnixPipe(); + + /** + * @brief aka, the one used by parent process. + */ + int GetSelfFileDescriptor(); + + /** + * @brief aka, the one used by child process. + */ + int GetOtherFileDescriptor(); + + private: + Usage usage_; + bool auto_close_; + UnixPipeFlag flags_; + int read_fd_; + int write_fd_; +}; +} // namespace cru::platform::unix diff --git a/include/cru/base/platform/web/WebException.h b/include/cru/base/platform/web/WebException.h new file mode 100644 index 00000000..d98b8943 --- /dev/null +++ b/include/cru/base/platform/web/WebException.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../../PreConfig.h" + +#ifdef CRU_PLATFORM_EMSCRIPTEN + +#include "../Exception.h" + +namespace cru::platform::web { +class WebException : public PlatformException { + public: + using PlatformException::PlatformException; +}; +} // namespace cru::platform::web + +#endif diff --git a/include/cru/base/platform/win/ComAutoInit.h b/include/cru/base/platform/win/ComAutoInit.h new file mode 100644 index 00000000..569085c8 --- /dev/null +++ b/include/cru/base/platform/win/ComAutoInit.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../../PreConfig.h" +#ifdef CRU_PLATFORM_WINDOWS + +#include "WinPreConfig.h" +#include "cru/base/Base.h" + +namespace cru::platform::win { +class CRU_BASE_API ComAutoInit { + public: + ComAutoInit(); + + CRU_DELETE_COPY(ComAutoInit) + CRU_DELETE_MOVE(ComAutoInit) + + ~ComAutoInit(); +}; +} // namespace cru::platform::win + +#endif diff --git a/include/cru/base/platform/win/DebugLogTarget.h b/include/cru/base/platform/win/DebugLogTarget.h new file mode 100644 index 00000000..8257f637 --- /dev/null +++ b/include/cru/base/platform/win/DebugLogTarget.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../../PreConfig.h" +#ifdef CRU_PLATFORM_WINDOWS + +#include "WinPreConfig.h" + +#include "../../log/Logger.h" + +namespace cru::platform::win { + +class CRU_BASE_API WinDebugLogTarget : public ::cru::log::ILogTarget { + public: + WinDebugLogTarget() = default; + + CRU_DELETE_COPY(WinDebugLogTarget) + CRU_DELETE_MOVE(WinDebugLogTarget) + + ~WinDebugLogTarget() = default; + + void Write(::cru::log::LogLevel level, StringView s) override; +}; +} // namespace cru::platform::win + +#endif diff --git a/include/cru/base/platform/win/Exception.h b/include/cru/base/platform/win/Exception.h new file mode 100644 index 00000000..3e63b191 --- /dev/null +++ b/include/cru/base/platform/win/Exception.h @@ -0,0 +1,56 @@ +#pragma once +#include "../../PreConfig.h" +#ifdef CRU_PLATFORM_WINDOWS + +#include "WinPreConfig.h" + +#include "../Exception.h" + +#include <stdexcept> +#include <string_view> + +namespace cru::platform::win { +class CRU_BASE_API HResultError : public platform::PlatformException { + public: + explicit HResultError(HRESULT h_result); + explicit HResultError(HRESULT h_result, std::string_view message); + + CRU_DEFAULT_COPY(HResultError) + CRU_DEFAULT_MOVE(HResultError) + + ~HResultError() override = default; + + HRESULT GetHResult() const { return h_result_; } + + private: + HRESULT h_result_; +}; + +inline void ThrowIfFailed(const HRESULT h_result) { + if (FAILED(h_result)) throw HResultError(h_result); +} + +inline void ThrowIfFailed(const HRESULT h_result, std::string_view message) { + if (FAILED(h_result)) throw HResultError(h_result, message); +} + +class CRU_BASE_API Win32Error : public platform::PlatformException { + public: + // ::GetLastError is automatically called to get the error code. + // The same as Win32Error(::GetLastError(), message) + explicit Win32Error(String message); + Win32Error(DWORD error_code, String message); + + CRU_DEFAULT_COPY(Win32Error) + CRU_DEFAULT_MOVE(Win32Error) + + ~Win32Error() override = default; + + DWORD GetErrorCode() const { return error_code_; } + + private: + DWORD error_code_; +}; +} // namespace cru::platform::win + +#endif diff --git a/include/cru/base/platform/win/StreamConvert.h b/include/cru/base/platform/win/StreamConvert.h new file mode 100644 index 00000000..3499604a --- /dev/null +++ b/include/cru/base/platform/win/StreamConvert.h @@ -0,0 +1,14 @@ +#pragma once +#include "../../PreConfig.h" + +#ifdef CRU_PLATFORM_WINDOWS + +#include "../../io/Stream.h" + +#include <objidlbase.h> + +namespace cru::platform::win { +CRU_BASE_API IStream* ConvertStreamToComStream(io::Stream* stream); +} + +#endif diff --git a/include/cru/base/platform/win/Win32FileStream.h b/include/cru/base/platform/win/Win32FileStream.h new file mode 100644 index 00000000..06656466 --- /dev/null +++ b/include/cru/base/platform/win/Win32FileStream.h @@ -0,0 +1,56 @@ +#pragma once + +#include "../../PreConfig.h" + +#ifdef CRU_PLATFORM_WINDOWS + +#include "../../String.h" +#include "../../io/OpenFileFlag.h" +#include "../../io/Stream.h" + +namespace cru::platform::win { +namespace details { +class Win32FileStreamPrivate; +} + +class CRU_BASE_API Win32FileStream : public io::Stream { + public: + Win32FileStream(String path, io::OpenFileFlag flags); + + CRU_DELETE_COPY(Win32FileStream) + CRU_DELETE_MOVE(Win32FileStream) + + ~Win32FileStream() override; + + public: + bool CanSeek() override; + Index Seek(Index offset, SeekOrigin origin = SeekOrigin::Current) override; + + bool CanRead() override; + Index Read(std::byte* buffer, Index offset, Index size) override; + using Stream::Read; + + bool CanWrite() override; + Index Write(const std::byte* buffer, Index offset, Index size) override; + using Stream::Write; + + void Close() override; + + String GetPath() const { return path_; } + io::OpenFileFlag GetOpenFileFlags() const { return flags_; } + + details::Win32FileStreamPrivate* GetPrivate_() { return p_; } + + private: + void CheckClosed(); + + private: + String path_; + io::OpenFileFlag flags_; + bool closed_ = false; + + details::Win32FileStreamPrivate* p_; +}; +} // namespace cru::platform::win + +#endif diff --git a/include/cru/base/platform/win/WinPreConfig.h b/include/cru/base/platform/win/WinPreConfig.h new file mode 100644 index 00000000..c2284df3 --- /dev/null +++ b/include/cru/base/platform/win/WinPreConfig.h @@ -0,0 +1,13 @@ +#pragma once +#include "../../PreConfig.h" +#ifdef CRU_PLATFORM_WINDOWS + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#undef CreateWindow +#undef DrawText +#undef CreateFont +#undef CreateEvent + +#endif |