From 2ce653eaccb67b3a8648f249f9f3414524e89d5e Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 6 Aug 2018 17:58:51 +0700 Subject: [PATCH] initial basic utils --- CMakeLists.txt | 13 +- ROADMAP.md | 21 +- headers/enduro2d/base/_base.hpp | 6 +- headers/enduro2d/base/macros.hpp | 20 +- headers/enduro2d/base/stdex.hpp | 527 +++++++++++++++- headers/enduro2d/base/types.hpp | 26 +- headers/enduro2d/math/_math.hpp | 18 +- headers/enduro2d/utils/_all.hpp | 11 + headers/enduro2d/utils/_utils.hpp | 46 ++ headers/enduro2d/utils/buffer.hpp | 51 ++ headers/enduro2d/utils/color.hpp | 101 ++++ headers/enduro2d/utils/color32.hpp | 99 +++ headers/enduro2d/utils/filesystem.hpp | 70 +++ headers/enduro2d/utils/image.hpp | 126 ++++ headers/enduro2d/utils/jobber.hpp | 175 ++++++ headers/enduro2d/utils/streams.hpp | 54 ++ headers/enduro2d/utils/strfmts.hpp | 570 ++++++++++++++++++ headers/enduro2d/utils/strings.hpp | 58 ++ headers/enduro2d/utils/strings.inl | 375 ++++++++++++ headers/enduro2d/utils/time.hpp | 168 ++++++ modules/catch2 | 2 +- modules/utfcpp | 2 +- samples/CMakeLists.txt | 1 + scripts/build_debug.bat | 2 +- scripts/build_debug.sh | 2 +- scripts/build_release.bat | 2 +- scripts/build_release.sh | 2 +- scripts/update_modules.sh | 3 +- sources/enduro2d/utils/buffer.cpp | 141 +++++ sources/enduro2d/utils/color.cpp | 338 +++++++++++ sources/enduro2d/utils/color32.cpp | 327 ++++++++++ sources/enduro2d/utils/filesystem.cpp | 203 +++++++ .../utils/filesystem_impl/files_posix.cpp | 198 ++++++ .../utils/filesystem_impl/files_winapi.cpp | 237 ++++++++ .../utils/filesystem_impl/filesystem_impl.hpp | 44 ++ .../filesystem_impl/filesystem_posix.cpp | 55 ++ .../filesystem_impl/filesystem_winapi.cpp | 55 ++ sources/enduro2d/utils/image.cpp | 323 ++++++++++ .../enduro2d/utils/image_impl/image_impl.hpp | 23 + .../utils/image_impl/image_reader_dds.cpp | 16 + .../utils/image_impl/image_reader_pvr.cpp | 16 + .../utils/image_impl/image_reader_stb.cpp | 87 +++ .../utils/image_impl/image_writer_dds.cpp | 39 ++ .../utils/image_impl/image_writer_pvr.cpp | 40 ++ .../utils/image_impl/image_writer_stb.cpp | 99 +++ sources/enduro2d/utils/jobber.cpp | 108 ++++ sources/enduro2d/utils/streams.cpp | 103 ++++ sources/enduro2d/utils/strings.cpp | 310 ++++++++++ untests/CMakeLists.txt | 3 +- untests/sources/common.hpp | 29 + untests/sources/untests_base/stdex.cpp | 171 ++++++ untests/sources/untests_utils/buffer.cpp | 171 ++++++ untests/sources/untests_utils/color.cpp | 150 +++++ untests/sources/untests_utils/color32.cpp | 136 +++++ untests/sources/untests_utils/filesystem.cpp | 457 ++++++++++++++ untests/sources/untests_utils/image.cpp | 169 ++++++ untests/sources/untests_utils/jobber.cpp | 138 +++++ untests/sources/untests_utils/streams.cpp | 106 ++++ untests/sources/untests_utils/strfmts.cpp | 158 +++++ untests/sources/untests_utils/strings.cpp | 444 ++++++++++++++ untests/sources/untests_utils/time.cpp | 56 ++ 61 files changed, 7431 insertions(+), 70 deletions(-) create mode 100644 headers/enduro2d/utils/buffer.hpp create mode 100644 headers/enduro2d/utils/color.hpp create mode 100644 headers/enduro2d/utils/color32.hpp create mode 100644 headers/enduro2d/utils/filesystem.hpp create mode 100644 headers/enduro2d/utils/image.hpp create mode 100644 headers/enduro2d/utils/jobber.hpp create mode 100644 headers/enduro2d/utils/streams.hpp create mode 100644 headers/enduro2d/utils/strfmts.hpp create mode 100644 headers/enduro2d/utils/strings.hpp create mode 100644 headers/enduro2d/utils/strings.inl create mode 100644 headers/enduro2d/utils/time.hpp create mode 100644 sources/enduro2d/utils/buffer.cpp create mode 100644 sources/enduro2d/utils/color.cpp create mode 100644 sources/enduro2d/utils/color32.cpp create mode 100644 sources/enduro2d/utils/filesystem.cpp create mode 100644 sources/enduro2d/utils/filesystem_impl/files_posix.cpp create mode 100644 sources/enduro2d/utils/filesystem_impl/files_winapi.cpp create mode 100644 sources/enduro2d/utils/filesystem_impl/filesystem_impl.hpp create mode 100644 sources/enduro2d/utils/filesystem_impl/filesystem_posix.cpp create mode 100644 sources/enduro2d/utils/filesystem_impl/filesystem_winapi.cpp create mode 100644 sources/enduro2d/utils/image.cpp create mode 100644 sources/enduro2d/utils/image_impl/image_impl.hpp create mode 100644 sources/enduro2d/utils/image_impl/image_reader_dds.cpp create mode 100644 sources/enduro2d/utils/image_impl/image_reader_pvr.cpp create mode 100644 sources/enduro2d/utils/image_impl/image_reader_stb.cpp create mode 100644 sources/enduro2d/utils/image_impl/image_writer_dds.cpp create mode 100644 sources/enduro2d/utils/image_impl/image_writer_pvr.cpp create mode 100644 sources/enduro2d/utils/image_impl/image_writer_stb.cpp create mode 100644 sources/enduro2d/utils/jobber.cpp create mode 100644 sources/enduro2d/utils/streams.cpp create mode 100644 sources/enduro2d/utils/strings.cpp create mode 100644 untests/sources/untests_base/stdex.cpp create mode 100644 untests/sources/untests_utils/buffer.cpp create mode 100644 untests/sources/untests_utils/color.cpp create mode 100644 untests/sources/untests_utils/color32.cpp create mode 100644 untests/sources/untests_utils/filesystem.cpp create mode 100644 untests/sources/untests_utils/image.cpp create mode 100644 untests/sources/untests_utils/jobber.cpp create mode 100644 untests/sources/untests_utils/streams.cpp create mode 100644 untests/sources/untests_utils/strfmts.cpp create mode 100644 untests/sources/untests_utils/strings.cpp create mode 100644 untests/sources/untests_utils/time.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 80f68aec..b9bf45aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,12 @@ cmake_minimum_required(VERSION 3.9.2 FATAL_ERROR) project(enduro2d) +# +# external packages +# + +find_package(Threads REQUIRED) + # # global defines # @@ -23,12 +29,11 @@ set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}${E2D_RELEASE_FLAGS}) # if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Werror -Weverything) - add_compile_options(-Wno-c++98-compat -Wno-c++98-compat-pedantic) + add_compile_options(-Wall -Wextra -Wpedantic) elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - add_compile_options(-Werror -Wall -Wextra -Wpedantic) + add_compile_options(-Wall -Wextra -Wpedantic) elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_compile_options(/WX /W4) + add_compile_options(/W4) endif() # diff --git a/ROADMAP.md b/ROADMAP.md index f496fcd1..52fc46cd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -16,25 +16,24 @@ ``` - ### `basic utils` - - [ ] `path` + - [x] `path` ``` functions to manipulating paths in a portable way ``` - - [ ] `color, color32` - - [ ] `basic string functions` + - [x] `color, color32` + - [x] `basic string functions` ``` unicode convertions, wildcard patterns, type safe format ``` - - [ ] `timer and time functions` - - [ ] `image, image loaders` + - [x] `timer and time functions` + - [x] `image, image loaders` ``` - basic raw and compressed formats - internal: g8, ga8, rgb8, rgba8, dxt1/3/5 - external: png, jpg, tga, dds + basic true color formats + internal: g8, ga8, rgb8, rgba8 + external: png, jpg, tga ``` - - [ ] `streams and native file system` - - [ ] `thread pool and async tasks` - - [ ] `pseudo-random number generator` + - [x] `streams and native file system` + - [x] `thread pool and async tasks` ## `Milestone II` diff --git a/headers/enduro2d/base/_base.hpp b/headers/enduro2d/base/_base.hpp index fa86c680..a8ba2881 100644 --- a/headers/enduro2d/base/_base.hpp +++ b/headers/enduro2d/base/_base.hpp @@ -16,8 +16,10 @@ #include #include #include +#include #include +#include #include #include #include @@ -25,9 +27,11 @@ #include #include -#include +#include #include +#include #include +#include #include #include #include diff --git a/headers/enduro2d/base/macros.hpp b/headers/enduro2d/base/macros.hpp index 259f63a8..1df1d918 100644 --- a/headers/enduro2d/base/macros.hpp +++ b/headers/enduro2d/base/macros.hpp @@ -30,4 +30,22 @@ // E2D_UNUSED // -#define E2D_UNUSED(expr) (void)(expr) +template < typename T > +void E2D_UNUSED(T&& arg) noexcept { + (void)arg; +} + +template < typename T, typename... Ts > +void E2D_UNUSED(T&& arg, Ts&&... args) noexcept { + E2D_UNUSED(arg); + E2D_UNUSED(std::forward(args)...); +} + +// +// E2D_COUNTOF +// + +template < typename T, std::size_t N > +constexpr std::size_t E2D_COUNTOF(const T(&)[N]) noexcept { + return N; +} diff --git a/headers/enduro2d/base/stdex.hpp b/headers/enduro2d/base/stdex.hpp index 90835826..dc3a4a01 100644 --- a/headers/enduro2d/base/stdex.hpp +++ b/headers/enduro2d/base/stdex.hpp @@ -6,44 +6,517 @@ #pragma once -#include "_base.hpp" +#include "macros.hpp" + +#define E2D_STDEX_NOEXCEPT_RETURN(...) \ + noexcept(noexcept(__VA_ARGS__)) { return __VA_ARGS__; } + +#define E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN(...) \ + noexcept(noexcept(__VA_ARGS__)) -> decltype (__VA_ARGS__) { return __VA_ARGS__; } + +// +// void_t +// + +namespace e2d { namespace stdex +{ + namespace impl + { + template < typename... Args > + struct make_void { + using type = void; + }; + } + + template < typename... Args > + using void_t = typename impl::make_void::type; +}} + +// +// is_reference_wrapper +// namespace e2d { namespace stdex { namespace impl { template < typename T > - struct unique_if { - using unique_single = std::unique_ptr; - }; + struct is_reference_wrapper_impl + : std::false_type {}; - template < typename T > - struct unique_if { - using unique_array_unknown_bound = std::unique_ptr; - }; - - template < typename T, std::size_t N > - struct unique_if { - using unique_array_known_bound = void; - }; - } - - template < typename T, typename... Args > - typename impl::unique_if::unique_single - make_unique(Args&&... args) - { - return std::unique_ptr(new T(std::forward(args)...)); + template < typename U > + struct is_reference_wrapper_impl> + : std::true_type {}; } template < typename T > - typename impl::unique_if::unique_array_unknown_bound - make_unique(std::size_t n) + struct is_reference_wrapper + : impl::is_reference_wrapper_impl> {}; +}} + +// +// invoke +// + +namespace e2d { namespace stdex +{ + namespace impl { - using U = std::remove_extent_t; - return std::unique_ptr(new U[n]()); + // + // invoke_member_object_impl + // + + template + < + typename Base, typename F, typename Derived, + typename std::enable_if_t>::value, int> = 0 + > + constexpr auto invoke_member_object_impl(F Base::* f, Derived&& ref) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + std::forward(ref).*f) + + template + < + typename Base, typename F, typename RefWrap, + typename std::enable_if_t>::value, int> = 0 + > + constexpr auto invoke_member_object_impl(F Base::* f, RefWrap&& ref) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + ref.get().*f) + + template + < + typename Base, typename F, typename Pointer, + typename std::enable_if_t< + !std::is_base_of>::value && + !is_reference_wrapper>::value + , int> = 0 + > + constexpr auto invoke_member_object_impl(F Base::* f, Pointer&& ptr) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + (*std::forward(ptr)).*f) + + // + // invoke_member_function_impl + // + + template + < + typename Base, typename F, typename Derived, typename... Args, + typename std::enable_if_t>::value, int> = 0 + > + constexpr auto invoke_member_function_impl(F Base::* f, Derived&& ref, Args&&... args) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + (std::forward(ref).*f)(std::forward(args)...)) + + template + < + typename Base, typename F, typename RefWrap, typename... Args, + typename std::enable_if_t>::value, int> = 0 + > + constexpr auto invoke_member_function_impl(F Base::* f, RefWrap&& ref, Args&&... args) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + (ref.get().*f)(std::forward(args)...)) + + template + < + typename Base, typename F, typename Pointer, typename... Args, + typename std::enable_if_t< + !std::is_base_of>::value && + !is_reference_wrapper>::value + , int> = 0 + > + constexpr auto invoke_member_function_impl(F Base::* f, Pointer&& ptr, Args&&... args) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + ((*std::forward(ptr)).*f)(std::forward(args)...)) } - template < typename T, typename... Args > - typename impl::unique_if::unique_array_known_bound - make_unique(Args&&...) = delete; + template + < + typename F, typename... Args, + typename std::enable_if_t>::value, int> = 0 + > + constexpr auto invoke(F&& f, Args&&... args) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + std::forward(f)(std::forward(args)...)) + + template + < + typename F, typename T, + typename std::enable_if_t>::value, int> = 0 + > + constexpr auto invoke(F&& f, T&& t) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + impl::invoke_member_object_impl(std::forward(f), std::forward(t))) + + template + < + typename F, typename... Args, + typename std::enable_if_t>::value, int> = 0 + > + constexpr auto invoke(F&& f, Args&&... args) + E2D_STDEX_NOEXCEPT_DECLTYPE_RETURN( + impl::invoke_member_function_impl(std::forward(f), std::forward(args)...)) +}} + +// +// invoke_result +// + +namespace e2d { namespace stdex +{ + namespace impl + { + struct invoke_result_impl_tag {}; + + template < typename Void, typename F, typename... Args > + struct invoke_result_impl {}; + + template < typename F, typename... Args > + struct invoke_result_impl(), std::declval()...))>, F, Args...> { + using type = decltype(stdex::invoke(std::declval(), std::declval()...)); + }; + } + + template < typename F, typename... Args > + struct invoke_result + : impl::invoke_result_impl {}; + + template < typename F, typename... Args > + using invoke_result_t = typename invoke_result::type; +}} + +// +// apply +// + +namespace e2d { namespace stdex +{ + namespace impl + { + template < typename F, typename Tuple, std::size_t... I > + constexpr decltype(auto) apply_impl(F&& f, Tuple&& args, std::index_sequence) + E2D_STDEX_NOEXCEPT_RETURN( + stdex::invoke( + std::forward(f), + std::get(std::forward(args))...)) + } + + template < typename F, typename Tuple > + constexpr decltype(auto) apply(F&& f, Tuple&& args) + E2D_STDEX_NOEXCEPT_RETURN( + impl::apply_impl( + std::forward(f), + std::forward(args), + std::make_index_sequence>::value>())) +}} + +// +// basic_string_view +// + +namespace e2d { namespace stdex +{ + template < typename Char, typename Traits = std::char_traits > + class basic_string_view { + public: + using value_type = Char; + using traits_type = Traits; + + using pointer = const Char*; + using const_pointer = const Char*; + + using reference = const Char&; + using const_reference = const Char&; + + using const_iterator = const_pointer; + using iterator = const_iterator; + + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = const_reverse_iterator; + + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + static const size_type npos = size_type(-1); + + static_assert( + std::is_pod::value, + "value_type must be a POD"); + static_assert( + std::is_same::value, + "traits_type::char_type must be the same type as value_type"); + public: + basic_string_view() noexcept = default; + basic_string_view(const basic_string_view&) noexcept = default; + basic_string_view& operator=(const basic_string_view&) noexcept = default; + + basic_string_view(std::nullptr_t) noexcept = delete; + basic_string_view(std::nullptr_t, size_type) noexcept = delete; + + basic_string_view(const Char* str) noexcept { + E2D_ASSERT(str); + data_ = str; + size_ = Traits::length(str); + } + + basic_string_view(const Char* str, size_type size) noexcept { + E2D_ASSERT(!size || str); + data_ = str; + size_ = size; + } + + template < typename Alloc > + basic_string_view(const std::basic_string& str) noexcept + : data_(str.data()) + , size_(str.size()) {} + + template < typename Alloc > + operator std::basic_string() const { + return {cbegin(), cend()}; + } + + const_iterator begin() const noexcept { + return data_; + } + + const_iterator cbegin() const noexcept { + return data_; + } + + const_iterator end() const noexcept { + return data_ + size_; + } + + const_iterator cend() const noexcept { + return data_ + size_; + } + + const_reverse_iterator rbegin() const noexcept { + return const_reverse_iterator(end()); + } + + const_reverse_iterator crbegin() const noexcept { + return const_reverse_iterator(cend()); + } + + const_reverse_iterator rend() const noexcept { + return const_reverse_iterator(begin()); + } + + const_reverse_iterator crend() const noexcept { + return const_reverse_iterator(cbegin()); + } + + size_type size() const noexcept { + return size_; + } + + size_type length() const noexcept { + return size_; + } + + size_type max_size() const noexcept { + return std::numeric_limits::max(); + } + + bool empty() const noexcept { + return size_ == 0; + } + + const_reference operator[](size_type index) const noexcept { + E2D_ASSERT(index < size_); + return data_[index]; + } + + const_reference at(size_type index) const { + if ( index < size_ ) { + return data_[index]; + } + throw std::out_of_range("basic_string_view::at"); + } + + const_reference front() const noexcept { + E2D_ASSERT(size_ > 0); + return data_[0]; + } + + const_reference back() const noexcept { + E2D_ASSERT(size_ > 0); + return data_[size_ - 1]; + } + + const_pointer data() const noexcept { + return data_; + } + + void swap(basic_string_view& other) noexcept { + std::swap(data_, other.data_); + std::swap(size_, other.size_); + } + + void remove_prefix(size_type n) noexcept { + E2D_ASSERT(n <= size_); + data_ += n; + size_ -= n; + } + + void remove_suffix(size_type n) noexcept { + E2D_ASSERT(n <= size_); + size_ -= n; + } + + int compare(basic_string_view str) const noexcept { + const auto ms = std::min(size(), str.size()); + const auto cr = Traits::compare(data(), str.data(), ms); + return cr == 0 + ? (size() == str.size() + ? 0 + : (size() < str.size() ? -1 : 1)) + : cr; + } + + size_type copy(Char* dst, size_type size, size_type index = 0) const { + if ( index > size_ ) { + throw std::out_of_range("basic_string_view::copy"); + } + size_type ms = std::min(size, size_ - index); + Traits::copy(dst, data_ + index, ms); + return ms; + } + + basic_string_view substr(size_type index = 0, size_type size = npos) const { + if ( index <= size_ ) { + return basic_string_view( + data_ + index, + std::min(size, size_ - index)); + } + throw std::out_of_range("basic_string_view::substr"); + } + private: + const Char* data_ = nullptr; + size_type size_ = 0; + }; + + template < typename Char, typename Traits > + bool operator<( + basic_string_view l, + basic_string_view r) noexcept + { + return l.compare(r) < 0; + } + + template < typename Char, typename Traits > + bool operator<( + basic_string_view l, + const char* r) noexcept + { + return l.compare(basic_string_view(r)) < 0; + } + + template < typename Char, typename Traits > + bool operator<( + basic_string_view l, + const std::basic_string& r) noexcept + { + return l.compare(basic_string_view(r)) < 0; + } + + template < typename Char, typename Traits > + bool operator<( + const char* l, + basic_string_view r) noexcept + { + return basic_string_view(l).compare(r) < 0; + } + + template < typename Char, typename Traits > + bool operator<( + const std::basic_string& l, + basic_string_view r) noexcept + { + return basic_string_view(l).compare(r) < 0; + } + + template < typename Char, typename Traits > + bool operator==( + basic_string_view l, + basic_string_view r) noexcept + { + const auto ls = l.size(); + const auto rs = r.size(); + return ls == rs && Traits::compare(l.data(), r.data(), ls) == 0; + } + + template < typename Char, typename Traits > + bool operator==( + basic_string_view l, + const char* r) noexcept + { + return l.compare(basic_string_view(r)) == 0; + } + + template < typename Char, typename Traits > + bool operator==( + basic_string_view l, + const std::basic_string& r) noexcept + { + return l.compare(basic_string_view(r)) == 0; + } + + template < typename Char, typename Traits > + bool operator==( + const char* l, + basic_string_view r) noexcept + { + return basic_string_view(l).compare(r) == 0; + } + + template < typename Char, typename Traits > + bool operator==( + const std::basic_string& l, + basic_string_view r) noexcept + { + return basic_string_view(l).compare(r) == 0; + } + + template < typename Char, typename Traits > + bool operator!=( + basic_string_view l, + basic_string_view r) noexcept + { + return !(l == r); + } + + template < typename Char, typename Traits > + bool operator!=( + basic_string_view l, + const char* r) noexcept + { + return !(l == r); + } + + template < typename Char, typename Traits > + bool operator!=( + basic_string_view l, + const std::basic_string& r) noexcept + { + return !(l == r); + } + + template < typename Char, typename Traits > + bool operator!=( + const char* l, + basic_string_view r) noexcept + { + return !(l == r); + } + + template < typename Char, typename Traits > + bool operator!=( + const std::basic_string& l, + basic_string_view r) noexcept + { + return !(l == r); + } }} diff --git a/headers/enduro2d/base/types.hpp b/headers/enduro2d/base/types.hpp index e72acd4f..9646a10c 100644 --- a/headers/enduro2d/base/types.hpp +++ b/headers/enduro2d/base/types.hpp @@ -6,7 +6,7 @@ #pragma once -#include "_base.hpp" +#include "stdex.hpp" namespace e2d { @@ -26,28 +26,36 @@ namespace e2d namespace e2d { + class exception + : public std::exception {}; + template < typename Value , std::size_t Size > using array = std::array; template < typename Value - , typename Alloc = std::allocator > - using vector = std::vector; + , typename Allocator = std::allocator > + using vector = std::vector; template < typename Value , typename Hash = std::hash , typename Pred = std::equal_to - , typename Alloc = std::allocator > - using hash_set = std::unordered_set; + , typename Allocator = std::allocator > + using hash_set = std::unordered_set; template < typename Key , typename Value , typename Hash = std::hash , typename Pred = std::equal_to - , typename Alloc = std::allocator> > - using hash_map = std::unordered_map; + , typename Allocator = std::allocator> > + using hash_map = std::unordered_map; template < typename Char - , typename Alloc = std::allocator > - using basic_string = std::basic_string, Alloc>; + , typename Traits = std::char_traits + , typename Allocator = std::allocator > + using basic_string = std::basic_string; + + template < typename Char + , typename Traits = std::char_traits > + using basic_string_view = stdex::basic_string_view; } diff --git a/headers/enduro2d/math/_math.hpp b/headers/enduro2d/math/_math.hpp index 1837fb3b..07c6dd1b 100644 --- a/headers/enduro2d/math/_math.hpp +++ b/headers/enduro2d/math/_math.hpp @@ -425,15 +425,6 @@ namespace e2d { namespace math template < typename T > using make_distance_t = typename make_distance::type; - template < typename T > - std::enable_if_t< - std::is_unsigned::value || std::is_floating_point::value, - make_distance_t> - distance(T l, T r) noexcept { - std::tie(l, r) = minmax(l, r); - return r - l; - } - template < typename T > std::enable_if_t< std::is_integral::value && std::is_signed::value, @@ -445,6 +436,15 @@ namespace e2d { namespace math : abs_to_unsigned(l) + abs_to_unsigned(r); } + template < typename T > + std::enable_if_t< + std::is_unsigned::value || std::is_floating_point::value, + make_distance_t> + distance(T l, T r) noexcept { + std::tie(l, r) = minmax(l, r); + return r - l; + } + // // approximately // diff --git a/headers/enduro2d/utils/_all.hpp b/headers/enduro2d/utils/_all.hpp index c087f288..f4b65d53 100644 --- a/headers/enduro2d/utils/_all.hpp +++ b/headers/enduro2d/utils/_all.hpp @@ -7,3 +7,14 @@ #pragma once #include "_utils.hpp" + +#include "buffer.hpp" +#include "color.hpp" +#include "color32.hpp" +#include "filesystem.hpp" +#include "image.hpp" +#include "jobber.hpp" +#include "streams.hpp" +#include "strfmts.hpp" +#include "strings.hpp" +#include "time.hpp" diff --git a/headers/enduro2d/utils/_utils.hpp b/headers/enduro2d/utils/_utils.hpp index c3d219d3..6c968249 100644 --- a/headers/enduro2d/utils/_utils.hpp +++ b/headers/enduro2d/utils/_utils.hpp @@ -8,3 +8,49 @@ #include "../base/_all.hpp" #include "../math/_all.hpp" + +namespace e2d +{ + class buffer; + class color; + class color32; + class image; + class jobber; + class input_stream; + class output_stream; +} + +namespace e2d +{ + using str = basic_string; + using wstr = basic_string; + using str16 = basic_string; + using str32 = basic_string; + + using str_view = basic_string_view; + using wstr_view = basic_string_view; + using str16_view = basic_string_view; + using str32_view = basic_string_view; + + struct seconds_tag {}; + struct milliseconds_tag {}; + struct microseconds_tag {}; + + template < typename T > + using seconds = unit; + template < typename T > + using milliseconds = unit; + template < typename T > + using microseconds = unit; +} + +namespace e2d +{ + class noncopyable { + protected: + noncopyable() = default; + ~noncopyable() = default; + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; + }; +} diff --git a/headers/enduro2d/utils/buffer.hpp b/headers/enduro2d/utils/buffer.hpp new file mode 100644 index 00000000..cacbe11a --- /dev/null +++ b/headers/enduro2d/utils/buffer.hpp @@ -0,0 +1,51 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +namespace e2d +{ + class buffer final { + public: + buffer() noexcept = default; + ~buffer() noexcept = default; + + buffer(buffer&& other) noexcept; + buffer& operator=(buffer&& other) noexcept; + + buffer(const buffer& other); + buffer& operator=(const buffer& other); + + explicit buffer(std::size_t size); + buffer(const void* src, std::size_t size); + + buffer& fill(u8 ch) noexcept; + buffer& resize(std::size_t nsize); + + buffer& assign(buffer&& other) noexcept; + buffer& assign(const buffer& other); + buffer& assign(const void* src, std::size_t nsize); + + void swap(buffer& other) noexcept; + void clear() noexcept; + bool empty() const noexcept; + + u8* data() noexcept; + const u8* data() const noexcept; + std::size_t size() const noexcept; + private: + using data_t = std::unique_ptr; + data_t data_; + std::size_t size_ = 0; + }; + + void swap(buffer& l, buffer& r) noexcept; + bool operator<(const buffer& l, const buffer& r) noexcept; + bool operator==(const buffer& l, const buffer& r) noexcept; + bool operator!=(const buffer& l, const buffer& r) noexcept; +} diff --git a/headers/enduro2d/utils/color.hpp b/headers/enduro2d/utils/color.hpp new file mode 100644 index 00000000..1c8c1b9c --- /dev/null +++ b/headers/enduro2d/utils/color.hpp @@ -0,0 +1,101 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +namespace e2d +{ + class color final { + public: + f32 r = 1.f; + f32 g = 1.f; + f32 b = 1.f; + f32 a = 1.f; + public: + static const color& clear() noexcept; /// (0; 0; 0; 0) + static const color& black() noexcept; /// (0; 0; 0; 1) + static const color& white() noexcept; /// (1; 1; 1; 1) + static const color& red() noexcept; /// (1; 0; 0; 1) + static const color& green() noexcept; /// (0; 1; 0; 1) + static const color& blue() noexcept; /// (0; 0; 1; 1) + static const color& yellow() noexcept; /// (1; 1; 0; 1) + static const color& magenta() noexcept; /// (1; 0; 1; 1) + static const color& cyan() noexcept; /// (0; 1; 1; 1) + public: + color() noexcept = default; + color(const color& other) noexcept = default; + color& operator=(const color& other) noexcept = default; + + color(f32 r, f32 g, f32 b, f32 a = 1.f) noexcept; + explicit color(const color32& other) noexcept; + + f32* data() noexcept; + const f32* data() const noexcept; + + f32& operator[](std::size_t index) noexcept; + f32 operator[](std::size_t index) const noexcept; + + color& operator+=(f32 v) noexcept; + color& operator-=(f32 v) noexcept; + color& operator*=(f32 v) noexcept; + color& operator/=(f32 v) noexcept; + + color& operator+=(const color& other) noexcept; + color& operator-=(const color& other) noexcept; + color& operator*=(const color& other) noexcept; + color& operator/=(const color& other) noexcept; + }; +} + +namespace e2d +{ + bool operator<(const color& l, const color& r) noexcept; + bool operator==(const color& l, const color& r) noexcept; + bool operator!=(const color& l, const color& r) noexcept; + + color operator+(color l, f32 v) noexcept; + color operator-(color l, f32 v) noexcept; + color operator*(color l, f32 v) noexcept; + color operator/(color l, f32 v) noexcept; + + color operator+(f32 v, const color& r) noexcept; + color operator-(f32 v, const color& r) noexcept; + color operator*(f32 v, const color& r) noexcept; + color operator/(f32 v, const color& r) noexcept; + + color operator+(color l, const color& r) noexcept; + color operator-(color l, const color& r) noexcept; + color operator*(color l, const color& r) noexcept; + color operator/(color l, const color& r) noexcept; +} + +namespace e2d { namespace math +{ + bool approximately(const color& l, const color& r) noexcept; + bool approximately(const color& l, const color& r, f32 precision) noexcept; + + f32 minimum(const color& c) noexcept; + f32 maximum(const color& c) noexcept; + + color minimized(const color& c, const color& cmin) noexcept; + color maximized(const color& c, const color& cmax) noexcept; + color clamped(const color& c, const color& cmin, const color& cmax) noexcept; + color saturated(const color& c) noexcept; + + bool contains( + const color& c, + f32 value, + f32 precision = math::default_precision()) noexcept; + bool contains_nan(const color& c) noexcept; +}} + +namespace e2d { namespace colors +{ + u32 pack_color(const color& c) noexcept; + color unpack_color(u32 argb) noexcept; +}} diff --git a/headers/enduro2d/utils/color32.hpp b/headers/enduro2d/utils/color32.hpp new file mode 100644 index 00000000..e99c1094 --- /dev/null +++ b/headers/enduro2d/utils/color32.hpp @@ -0,0 +1,99 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +namespace e2d +{ + class color32 final { + public: + u8 r = 255; + u8 g = 255; + u8 b = 255; + u8 a = 255; + public: + static const color32& clear() noexcept; /// ( 0; 0; 0; 0 ) + static const color32& black() noexcept; /// ( 0; 0; 0; 255) + static const color32& white() noexcept; /// (255; 255; 255; 255) + static const color32& red() noexcept; /// (255; 0; 0; 255) + static const color32& green() noexcept; /// ( 0; 255; 0; 255) + static const color32& blue() noexcept; /// ( 0; 0; 255; 255) + static const color32& yellow() noexcept; /// (255; 255; 0; 255) + static const color32& magenta() noexcept; /// (255; 0; 255; 255) + static const color32& cyan() noexcept; /// ( 0; 255; 255; 255) + public: + color32() noexcept = default; + color32(const color32& other) noexcept = default; + color32& operator=(const color32& other) noexcept = default; + + color32(u8 r, u8 g, u8 b, u8 a = 255) noexcept; + explicit color32(const color& other) noexcept; + + u8* data() noexcept; + const u8* data() const noexcept; + + u8& operator[](std::size_t index) noexcept; + u8 operator[](std::size_t index) const noexcept; + + color32& operator+=(u8 v) noexcept; + color32& operator-=(u8 v) noexcept; + color32& operator*=(u8 v) noexcept; + color32& operator/=(u8 v) noexcept; + + color32& operator+=(const color32& other) noexcept; + color32& operator-=(const color32& other) noexcept; + color32& operator*=(const color32& other) noexcept; + color32& operator/=(const color32& other) noexcept; + }; +} + +namespace e2d +{ + bool operator<(const color32& l, const color32& r) noexcept; + bool operator==(const color32& l, const color32& r) noexcept; + bool operator!=(const color32& l, const color32& r) noexcept; + + color32 operator+(color32 l, u8 v) noexcept; + color32 operator-(color32 l, u8 v) noexcept; + color32 operator*(color32 l, u8 v) noexcept; + color32 operator/(color32 l, u8 v) noexcept; + + color32 operator+(u8 v, const color32& r) noexcept; + color32 operator-(u8 v, const color32& r) noexcept; + color32 operator*(u8 v, const color32& r) noexcept; + color32 operator/(u8 v, const color32& r) noexcept; + + color32 operator+(color32 l, const color32& r) noexcept; + color32 operator-(color32 l, const color32& r) noexcept; + color32 operator*(color32 l, const color32& r) noexcept; + color32 operator/(color32 l, const color32& r) noexcept; +} + +namespace e2d { namespace math +{ + bool approximately(const color32& l, const color32& r) noexcept; + bool approximately(const color32& l, const color32& r, u8 precision) noexcept; + + u8 minimum(const color32& c) noexcept; + u8 maximum(const color32& c) noexcept; + + color32 minimized(const color32& c, const color32& cmin) noexcept; + color32 maximized(const color32& c, const color32& cmax) noexcept; + color32 clamped(const color32& c, const color32& cmin, const color32& cmax) noexcept; + + bool contains( + const color32& c, + u8 value, + u8 precision = math::default_precision()) noexcept; +}} + +namespace e2d { namespace colors +{ + u32 pack_color32(const color32& c) noexcept; + color32 unpack_color32(u32 argb) noexcept; +}} diff --git a/headers/enduro2d/utils/filesystem.hpp b/headers/enduro2d/utils/filesystem.hpp new file mode 100644 index 00000000..6c414171 --- /dev/null +++ b/headers/enduro2d/utils/filesystem.hpp @@ -0,0 +1,70 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" +#include "streams.hpp" + +namespace e2d +{ + class read_file : public input_stream { + public: + virtual const str& path() const noexcept = 0; + }; + using read_file_uptr = std::unique_ptr; + + class write_file : public output_stream { + public: + virtual const str& path() const noexcept = 0; + }; + using write_file_uptr = std::unique_ptr; +} + +namespace e2d +{ + read_file_uptr make_read_file(str_view path) noexcept; + write_file_uptr make_write_file(str_view path, bool append) noexcept; +} + +namespace e2d { namespace path +{ + str combine(str_view lhs, str_view rhs); + + str remove_filename(str_view path); + str remove_extension(str_view path); + + str replace_filename(str_view path, str_view filename); + str replace_extension(str_view path, str_view extension); + + str stem(str_view path); + str filename(str_view path); + str extension(str_view path); + str parent_path(str_view path); + + bool is_absolute(str_view path) noexcept; + bool is_relative(str_view path) noexcept; +}} + + +namespace e2d { namespace filesystem +{ + bool remove(str_view path); + bool exists(str_view path); + + bool remove_file(str_view path); + bool remove_directory(str_view path); + + bool file_exists(str_view path); + bool directory_exists(str_view path); + + bool create_file(str_view path); + bool create_directory(str_view path); + bool create_directory_recursive(str_view path); + + bool try_read_all(buffer& dst, str_view path) noexcept; + bool try_write_all(const buffer& src, str_view path, bool append) noexcept; +}} diff --git a/headers/enduro2d/utils/image.hpp b/headers/enduro2d/utils/image.hpp new file mode 100644 index 00000000..63992b3b --- /dev/null +++ b/headers/enduro2d/utils/image.hpp @@ -0,0 +1,126 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +#include "buffer.hpp" +#include "color.hpp" +#include "color32.hpp" +#include "streams.hpp" + +namespace e2d +{ + enum class image_file_format : u8 { + dds, + jpg, + png, + pvr, + tga, + + unknown + }; + + enum class image_data_format : u8 { + g8, + ga8, + rgb8, + rgba8, + + dxt1, + dxt3, + dxt5, + + rgb_pvrtc2, + rgb_pvrtc4, + + rgba_pvrtc2, + rgba_pvrtc4, + + unknown + }; + + class bad_image_access final : public exception { + public: + const char* what() const noexcept final { + return "bad image access"; + } + }; + + class bad_image_data_format final : public exception { + public: + const char* what() const noexcept final { + return "bad image data format"; + } + }; + + class image final { + public: + image() noexcept = default; + ~image() noexcept = default; + + image(image&& other) noexcept; + image& operator=(image&& other) noexcept; + + image(const image& other); + image& operator=(const image& other); + + image(const v2u& size, image_data_format format); + image(const v2u& size, image_data_format format, buffer&& data); + image(const v2u& size, image_data_format format, const buffer& data); + + image& assign(image&& other) noexcept; + image& assign(const image& other); + + image& assign(const v2u& size, image_data_format format); + image& assign(const v2u& size, image_data_format format, buffer&& data); + image& assign(const v2u& size, image_data_format format, const buffer& data); + + void swap(image& other) noexcept; + void clear() noexcept; + bool empty() const noexcept; + + color pixel(u32 u, u32 v) const; + color pixel(const v2u& uv) const; + color32 pixel32(u32 u, u32 v) const; + color32 pixel32(const v2u& uv) const; + + const v2u& size() const noexcept; + image_data_format format() const noexcept; + const buffer& data() const noexcept; + private: + buffer data_; + v2u size_; + image_data_format format_ = image_data_format::unknown; + }; + + void swap(image& l, image& r) noexcept; + bool operator<(const image& l, const image& r) noexcept; + bool operator==(const image& l, const image& r) noexcept; + bool operator!=(const image& l, const image& r) noexcept; +} + +namespace e2d { namespace images +{ + bool try_load_image( + image& dst, + const buffer& src) noexcept; + + bool try_load_image( + image& dst, + const input_stream_uptr& src) noexcept; + + bool try_save_image( + const image& src, + image_file_format format, + buffer& dst) noexcept; + + bool try_save_image( + const image& src, + image_file_format format, + const output_stream_uptr& dst) noexcept; +}} diff --git a/headers/enduro2d/utils/jobber.hpp b/headers/enduro2d/utils/jobber.hpp new file mode 100644 index 00000000..b4d8c78e --- /dev/null +++ b/headers/enduro2d/utils/jobber.hpp @@ -0,0 +1,175 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +namespace e2d +{ + class jobber final : private noncopyable { + public: + enum class priority { + lowest, + below_normal, + normal, + above_normal, + highest + }; + public: + explicit jobber(u32 threads); + ~jobber() noexcept; + + template < typename F, typename... Args > + using async_invoke_result_t = stdex::invoke_result_t< + std::decay_t, + std::decay_t...>; + + template < typename F, typename... Args + , typename R = async_invoke_result_t > + std::future async(F&& f, Args&&... args); + + template < typename F, typename... Args + , typename R = async_invoke_result_t > + std::future async(priority priority, F&& f, Args&&... args); + + void pause() noexcept; + void resume() noexcept; + bool is_paused() const noexcept; + + void wait_all() const noexcept; + void active_wait_all() noexcept; + private: + class task; + using task_ptr = std::unique_ptr; + template < typename R, typename F, typename... Args > + class concrete_task; + private: + void push_task_(priority priority, task_ptr task); + task_ptr pop_task_() noexcept; + void shutdown_() noexcept; + void worker_main_() noexcept; + void process_task_(std::unique_lock lock) noexcept; + private: + vector threads_; + vector> tasks_; + std::mutex tasks_mutex_; + std::condition_variable cond_var_; + std::atomic_bool paused_{false}; + std::atomic_bool cancelled_{false}; + std::atomic_size_t active_task_count_{0}; + }; + + class jobber::task : private noncopyable { + public: + virtual ~task() noexcept = default; + virtual void run() noexcept = 0; + }; + + template < typename R, typename F, typename... Args > + class jobber::concrete_task : public task { + F f_; + std::tuple args_; + std::promise promise_; + public: + template < typename U > + concrete_task(U&& u, std::tuple&& args); + void run() noexcept final; + std::future future() noexcept; + }; + + template < typename F, typename... Args > + class jobber::concrete_task : public task { + F f_; + std::tuple args_; + std::promise promise_; + public: + template < typename U > + concrete_task(U&& u, std::tuple&& args); + void run() noexcept final; + std::future future() noexcept; + }; +} + +namespace e2d +{ + // + // async + // + + template < typename F, typename... Args, typename R > + std::future jobber::async(F&& f, Args&&... args) { + return async( + priority::normal, + std::forward(f), + std::forward(args)...); + } + + template < typename F, typename... Args, typename R > + std::future jobber::async(priority priority, F&& f, Args&&... args) { + using task_t = concrete_task< + R, + std::decay_t, + std::decay_t...>; + std::unique_ptr task = std::make_unique( + std::forward(f), + std::make_tuple(std::forward(args)...)); + std::future future = task->future(); + std::lock_guard guard(tasks_mutex_); + push_task_(priority, std::move(task)); + return future; + } + + // + // concrete_task + // + + template < typename R, typename F, typename... Args > + template < typename U > + jobber::concrete_task::concrete_task(U&& u, std::tuple&& args) + : f_(std::forward(u)) + , args_(std::move(args)) {} + + template < typename R, typename F, typename... Args > + void jobber::concrete_task::run() noexcept { + try { + R value = stdex::apply(std::move(f_), std::move(args_)); + promise_.set_value(std::move(value)); + } catch (...) { + promise_.set_exception(std::current_exception()); + } + } + + template < typename R, typename F, typename... Args > + std::future jobber::concrete_task::future() noexcept { + return promise_.get_future(); + } + + // + // concrete_task + // + + template < typename F, typename... Args > + template < typename U > + jobber::concrete_task::concrete_task(U&& u, std::tuple&& args) + : f_(std::forward(u)) + , args_(std::move(args)) {} + + template < typename F, typename... Args > + void jobber::concrete_task::run() noexcept { + try { + stdex::apply(std::move(f_), std::move(args_)); + promise_.set_value(); + } catch (...) { + promise_.set_exception(std::current_exception()); + } + } + + template < typename F, typename... Args > + std::future jobber::concrete_task::future() noexcept { + return promise_.get_future(); + } +} diff --git a/headers/enduro2d/utils/streams.hpp b/headers/enduro2d/utils/streams.hpp new file mode 100644 index 00000000..72ab66c0 --- /dev/null +++ b/headers/enduro2d/utils/streams.hpp @@ -0,0 +1,54 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +namespace e2d +{ + class bad_stream_operation final : public exception { + public: + const char* what() const noexcept final { + return "bad stream operation"; + } + }; + + class input_stream : private noncopyable { + public: + virtual ~input_stream() noexcept = default; + virtual std::size_t read(void* dst, std::size_t size) = 0; + virtual std::size_t seek(std::ptrdiff_t offset, bool relative) = 0; + virtual std::size_t tell() const = 0; + virtual std::size_t length() const = 0; + }; + using input_stream_uptr = std::unique_ptr; + + class output_stream : private noncopyable { + public: + virtual ~output_stream() noexcept = default; + virtual std::size_t write(const void* src, std::size_t size) = 0; + virtual std::size_t seek(std::ptrdiff_t offset, bool relative) = 0; + virtual std::size_t tell() const = 0; + }; + using output_stream_uptr = std::unique_ptr; +} + +namespace e2d +{ + input_stream_uptr make_memory_stream(buffer data) noexcept; +} + +namespace e2d { namespace streams +{ + bool try_read_tail( + buffer& dst, + const input_stream_uptr& stream) noexcept; + + bool try_write_tail( + const buffer& src, + const output_stream_uptr& stream) noexcept; +}} diff --git a/headers/enduro2d/utils/strfmts.hpp b/headers/enduro2d/utils/strfmts.hpp new file mode 100644 index 00000000..371390b7 --- /dev/null +++ b/headers/enduro2d/utils/strfmts.hpp @@ -0,0 +1,570 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +#include "color.hpp" +#include "color32.hpp" +#include "strings.hpp" + +namespace e2d { namespace strings +{ + // + // vec2 + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + vec2 value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1)", + make_format_arg(value_.x, width_), + make_format_arg(value_.y, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + vec2 value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1)", + make_format_arg(value_.x, width_, precision_), + make_format_arg(value_.y, width_, precision_))); + } + }; + + // + // vec3 + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + vec3 value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2)", + make_format_arg(value_.x, width_), + make_format_arg(value_.y, width_), + make_format_arg(value_.z, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + vec3 value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2)", + make_format_arg(value_.x, width_, precision_), + make_format_arg(value_.y, width_, precision_), + make_format_arg(value_.z, width_, precision_))); + } + }; + + // + // vec4 + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + vec4 value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2,%3)", + make_format_arg(value_.x, width_), + make_format_arg(value_.y, width_), + make_format_arg(value_.z, width_), + make_format_arg(value_.w, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + vec4 value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2,%3)", + make_format_arg(value_.x, width_, precision_), + make_format_arg(value_.y, width_, precision_), + make_format_arg(value_.z, width_, precision_), + make_format_arg(value_.w, width_, precision_))); + } + }; + + // + // mat2 + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + mat2 value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1)", + make_format_arg(value_[0], width_), + make_format_arg(value_[1], width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + mat2 value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1)", + make_format_arg(value_[0], width_, precision_), + make_format_arg(value_[1], width_, precision_))); + } + }; + + // + // mat3 + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + mat3 value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2)", + make_format_arg(value_[0], width_), + make_format_arg(value_[1], width_), + make_format_arg(value_[2], width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + mat3 value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2)", + make_format_arg(value_[0], width_, precision_), + make_format_arg(value_[1], width_, precision_), + make_format_arg(value_[2], width_, precision_))); + } + }; + + // + // mat4 + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + mat4 value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2,%3)", + make_format_arg(value_[0], width_), + make_format_arg(value_[1], width_), + make_format_arg(value_[2], width_), + make_format_arg(value_[3], width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + mat4 value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2,%3)", + make_format_arg(value_[0], width_, precision_), + make_format_arg(value_[1], width_, precision_), + make_format_arg(value_[2], width_, precision_), + make_format_arg(value_[3], width_, precision_))); + } + }; + + // + // rad + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + rad value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0rad", + make_format_arg(value_.value, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + rad value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0rad", + make_format_arg(value_.value, width_, precision_))); + } + }; + + // + // deg + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + deg value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0deg", + make_format_arg(value_.value, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + deg value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0deg", + make_format_arg(value_.value, width_, precision_))); + } + }; + + // + // str + // + + template <> + class format_arg { + str value_; + public: + template < typename U > + explicit format_arg(U&& value) + noexcept(noexcept(std::is_nothrow_constructible::value)) + : value_(std::forward(value)) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0", value_.c_str())); + } + }; + + // + // wstr + // + + template <> + class format_arg { + wstr value_; + public: + template < typename U > + explicit format_arg(U&& value) + noexcept(noexcept(std::is_nothrow_constructible::value)) + : value_(std::forward(value)) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0", make_utf8(value_.c_str()))); + } + }; + + // + // str16 + // + + template <> + class format_arg { + str16 value_; + public: + template < typename U > + explicit format_arg(U&& value) + noexcept(noexcept(std::is_nothrow_constructible::value)) + : value_(std::forward(value)) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0", make_utf8(value_.c_str()))); + } + }; + + // + // str32 + // + + template <> + class format_arg { + str32 value_; + public: + template < typename U > + explicit format_arg(U&& value) + noexcept(noexcept(std::is_nothrow_constructible::value)) + : value_(std::forward(value)) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0", make_utf8(value_.c_str()))); + } + }; + + // + // color + // + + template <> + class format_arg { + color value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2,%3)", + make_format_arg(value_.r, width_, precision_), + make_format_arg(value_.g, width_, precision_), + make_format_arg(value_.b, width_, precision_), + make_format_arg(value_.a, width_, precision_))); + } + }; + + // + // color32 + // + + template <> + class format_arg { + color32 value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "(%0,%1,%2,%3)", + make_format_arg(value_.r, width_), + make_format_arg(value_.g, width_), + make_format_arg(value_.b, width_), + make_format_arg(value_.a, width_))); + } + }; + + // + // seconds + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + seconds value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0s", + make_format_arg(value_.value, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + seconds value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0s", + make_format_arg(value_.value, width_, precision_))); + } + }; + + // + // milliseconds + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + milliseconds value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0ms", + make_format_arg(value_.value, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + milliseconds value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0ms", + make_format_arg(value_.value, width_, precision_))); + } + }; + + // + // microseconds + // + + template < typename T > + class format_arg, std::enable_if_t::value>> { + microseconds value_; + u8 width_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0) noexcept + : value_(std::forward(value)), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0us", + make_format_arg(value_.value, width_))); + } + }; + + template < typename T > + class format_arg, std::enable_if_t::value>> { + microseconds value_; + u8 width_; + u8 precision_; + public: + template < typename U > + explicit format_arg(U&& value, u8 width = 0, u8 precision = 6) noexcept + : value_(std::forward(value)), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const { + return math::numeric_cast( + format(dst, size, "%0us", + make_format_arg(value_.value, width_, precision_))); + } + }; +}} diff --git a/headers/enduro2d/utils/strings.hpp b/headers/enduro2d/utils/strings.hpp new file mode 100644 index 00000000..f94fa31d --- /dev/null +++ b/headers/enduro2d/utils/strings.hpp @@ -0,0 +1,58 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +namespace e2d +{ + str make_utf8(str_view src); + str make_utf8(wstr_view src); + str make_utf8(str16_view src); + str make_utf8(str32_view src); + + wstr make_wide(str_view src); + wstr make_wide(wstr_view src); + wstr make_wide(str16_view src); + wstr make_wide(str32_view src); + + str16 make_utf16(str_view src); + str16 make_utf16(wstr_view src); + str16 make_utf16(str16_view src); + str16 make_utf16(str32_view src); + + str32 make_utf32(str_view src); + str32 make_utf32(wstr_view src); + str32 make_utf32(str16_view src); + str32 make_utf32(str32_view src); + + namespace strings + { + class format_error; + class bad_format; + class bad_format_buffer; + class bad_format_argument; + + template < typename T, typename = void > + class format_arg; + + template < typename T, typename... Args > + format_arg> make_format_arg(T&& v, Args&&... args); + + template < typename... Args > + std::size_t format( + char* dst, std::size_t size, + const char* fmt, Args&&... args); + + template < typename... Args > + str rformat(const char* fmt, Args&&... args); + + bool wildcard_match(str_view string, str_view pattern); + } +} + +#include "strings.inl" diff --git a/headers/enduro2d/utils/strings.inl b/headers/enduro2d/utils/strings.inl new file mode 100644 index 00000000..35715f65 --- /dev/null +++ b/headers/enduro2d/utils/strings.inl @@ -0,0 +1,375 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" +#include "strings.hpp" + +namespace e2d { namespace strings +{ + // + // exceptions + // + + class format_error : public exception { + public: + const char* what() const noexcept override { + return "format error"; + } + }; + + class bad_format final : public format_error { + public: + const char* what() const noexcept final { + return "bad format"; + } + }; + + class bad_format_buffer final : public format_error { + public: + const char* what() const noexcept final { + return "bad format buffer"; + } + }; + + class bad_format_argument final : public format_error { + public: + const char* what() const noexcept final { + return "bad format argument"; + } + }; + + // + // arguments + // + + template < typename T, typename... Args > + format_arg> make_format_arg(T&& v, Args&&... args) { + return format_arg>( + std::forward(v), std::forward(args)...); + } + + namespace impl + { + // Inspired by: + // https://github.com/miloyip/itoa-benchmark + + static constexpr char u8toa_lt[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + + inline char* u8toa(u8 value, char* buffer) noexcept { + E2D_ASSERT(buffer); + const i32 d1 = (value / 100) << 1; + const i32 d2 = (value % 100) << 1; + if ( value >= 100 ) { + *buffer++ = u8toa_lt[d1 + 1]; + } + if ( value >= 10 ) { + *buffer++ = u8toa_lt[d2]; + } + *buffer++ = u8toa_lt[d2 + 1]; + return buffer; + } + } + + template < typename T > + class format_arg::value && std::is_signed::value>> + { + T value_; + u8 width_; + public: + explicit format_arg(T value, u8 width = 0) noexcept + : value_(value), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const noexcept { + char format[7] = {0}; + char* b_format = format; + *b_format++ = '%'; + b_format = impl::u8toa(width_, b_format); + *b_format++ = 'j'; + *b_format++ = 'i'; + E2D_ASSERT(b_format < format + sizeof(format)); + return std::snprintf( + dst, size, format, + math::numeric_cast(value_)); + } + }; + + template < typename T > + class format_arg::value && std::is_unsigned::value>> + { + T value_; + u8 width_; + public: + explicit format_arg(T value, u8 width = 0) noexcept + : value_(value), width_(width) {} + + std::ptrdiff_t write(char* dst, size_t size) const noexcept { + char format[7] = {0}; + char* b_format = format; + *b_format++ = '%'; + b_format = impl::u8toa(width_, b_format); + *b_format++ = 'j'; + *b_format++ = 'u'; + E2D_ASSERT(b_format < format + sizeof(format)); + return std::snprintf( + dst, size, format, + math::numeric_cast(value_)); + } + }; + + template < typename T > + class format_arg::value>> + { + T value_; + u8 width_; + u8 precision_; + public: + explicit format_arg(T value, u8 width = 0, u8 precision = 6) noexcept + : value_(value), width_(width), precision_(precision) {} + + std::ptrdiff_t write(char* dst, size_t size) const noexcept { + char format[10] = {0}; + char* b_format = format; + *b_format++ = '%'; + b_format = impl::u8toa(width_, b_format); + *b_format++ = '.'; + b_format = impl::u8toa(precision_, b_format); + *b_format++ = 'L'; + *b_format++ = 'f'; + E2D_ASSERT(b_format < format + sizeof(format)); + return std::snprintf( + dst, size, format, + math::numeric_cast(value_)); + } + }; + + template <> + class format_arg { + bool value_; + public: + explicit format_arg(bool value) noexcept + : value_(value) {} + + std::ptrdiff_t write(char* dst, size_t size) const noexcept { + return std::snprintf( + dst, size, "%s", + value_ ? "true" : "false"); + } + }; + + template <> + class format_arg { + const char* value_; + public: + explicit format_arg(const char* value) noexcept + : value_(value) {} + + std::ptrdiff_t write(char* dst, size_t size) const noexcept { + return std::snprintf(dst, size, "%s", value_); + } + }; + + template <> + class format_arg { + const char* value_; + public: + explicit format_arg(const char* value) noexcept + : value_(value) {} + + std::ptrdiff_t write(char* dst, size_t size) const noexcept { + return std::snprintf(dst, size, "%s", value_); + } + }; + + namespace impl + { + template < typename T > + struct is_arg_impl : std::false_type {}; + template < typename U > + struct is_arg_impl> : std::true_type {}; + template < typename T > + struct is_arg : is_arg_impl> {}; + + template < typename T > + std::enable_if_t>::value, T> + wrap_arg(T&& arg) { + return std::forward(arg); + } + + template < typename T > + std::enable_if_t>::value, format_arg>> + wrap_arg(T&& value) { + return make_format_arg(std::forward(value)); + } + + template < std::size_t N, typename Tuple > + std::enable_if_t::value, std::ptrdiff_t> + write_arg_n(char* dst, std::size_t size, const Tuple& targs) { + return std::get(targs).write(dst, size); + } + + template < std::size_t N, typename Tuple > + std::enable_if_t= std::tuple_size::value, std::ptrdiff_t> + write_arg_n(char* dst, std::size_t size, const Tuple& targs) { + E2D_UNUSED(dst, size, targs); + if ( dst ) { + *dst = '\0'; + } + throw bad_format(); + } + + template < typename Tuple > + std::enable_if_t::value <= 10, std::size_t> + format_impl(char* dst, std::size_t size, const char* format, const Tuple& targs) { + if ( !format ) { + throw bad_format(); + } + if ( !dst != !size ) { + throw bad_format_buffer(); + } + std::size_t result = 0; + const char* const b_dst = dst; + const char* const e_dst = b_dst ? b_dst + size : nullptr; + while ( *format ) { + if ( dst && dst == e_dst - 1 ) { + *dst = '\0'; + throw bad_format_buffer(); + } + if ( *format != '%' ) { + if ( dst ) { + *dst++ = *format; + } + ++result; + ++format; + } else { + const char n_param = *(++format); + if ( !n_param ) { + // "hello%" + if ( dst ) { + *dst = '\0'; + } + throw bad_format(); + } + std::ptrdiff_t write_arg_r = 0; + std::size_t dst_tail_size = dst + ? math::numeric_cast(e_dst - dst) + : 0; + E2D_ASSERT(!dst || dst_tail_size); + switch ( n_param ) { + case '0' : + write_arg_r = write_arg_n<0>(dst, dst_tail_size, targs); + break; + case '1' : + write_arg_r = write_arg_n<1>(dst, dst_tail_size, targs); + break; + case '2' : + write_arg_r = write_arg_n<2>(dst, dst_tail_size, targs); + break; + case '3' : + write_arg_r = write_arg_n<3>(dst, dst_tail_size, targs); + break; + case '4' : + write_arg_r = write_arg_n<4>(dst, dst_tail_size, targs); + break; + case '5' : + write_arg_r = write_arg_n<5>(dst, dst_tail_size, targs); + break; + case '6' : + write_arg_r = write_arg_n<6>(dst, dst_tail_size, targs); + break; + case '7' : + write_arg_r = write_arg_n<7>(dst, dst_tail_size, targs); + break; + case '8' : + write_arg_r = write_arg_n<8>(dst, dst_tail_size, targs); + break; + case '9' : + write_arg_r = write_arg_n<9>(dst, dst_tail_size, targs); + break; + case '%': + // "hel%%lo" -> "hel%lo" + if ( dst ) { + *dst = '%'; + } + write_arg_r = 1; + break; + default: + // "hel%xlo" + if ( dst ) { + *dst = '\0'; + } + throw bad_format(); + } + if ( write_arg_r < 0 ) { + if ( dst ) { + *dst = '\0'; + } + throw bad_format_argument(); + } + const std::size_t write_bytes = + math::abs_to_unsigned(write_arg_r); + if ( size && write_bytes >= dst_tail_size ) { + if ( dst ) { + E2D_ASSERT(b_dst + size == dst + dst_tail_size); + *(dst + dst_tail_size - 1) = '\0'; + } + throw bad_format_buffer(); + } + if ( dst ) { + dst += write_bytes; + } + result += write_bytes; + ++format; + } + } + if ( dst ) { + *dst = '\0'; + } + E2D_ASSERT( + !dst || + result == math::numeric_cast(dst - b_dst)); + return result; + } + } + + template < typename... Args > + std::size_t format( + char* dst, std::size_t size, + const char* fmt, Args&&... args) + { + return impl::format_impl( + dst, size, fmt, + std::make_tuple(impl::wrap_arg(std::forward(args))...)); + } + + template < typename... Args > + str rformat(const char* fmt, Args&&... args) { + auto targs = std::make_tuple( + impl::wrap_arg(std::forward(args))...); + const std::size_t expected_format_size = impl::format_impl( + nullptr, 0, fmt, targs); + vector buffer(expected_format_size + 1, '\0'); + const std::size_t actual_format_size = impl::format_impl( + buffer.data(), buffer.size(), fmt, targs); + E2D_ASSERT(expected_format_size == actual_format_size); + return str(buffer.data(), buffer.data() + actual_format_size); + } +}} diff --git a/headers/enduro2d/utils/time.hpp b/headers/enduro2d/utils/time.hpp new file mode 100644 index 00000000..de83da03 --- /dev/null +++ b/headers/enduro2d/utils/time.hpp @@ -0,0 +1,168 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_utils.hpp" + +namespace e2d { namespace time +{ + template < typename T > + const seconds& second() noexcept { + static seconds second = seconds(T(1)); + return second; + } + + template < typename T > + const seconds& minute() noexcept { + static seconds minute = second() * T(60); + return minute; + } + + template < typename T > + const seconds& hour() noexcept { + static seconds hour = minute() * T(60); + return hour; + } +}} + +namespace e2d +{ + template < typename T > + seconds make_seconds(T v) noexcept { + return make_unit(v); + } + + template < typename T > + milliseconds make_milliseconds(T v) noexcept { + return make_unit(v); + } + + template < typename T > + microseconds make_microseconds(T v) noexcept { + return make_unit(v); + } + + template <> + struct unit_converter { + template < typename T > + milliseconds operator()(const seconds& u) const noexcept { + const i64 seconds_to_milli = 1000; + return make_milliseconds(u.value * seconds_to_milli) + .template cast_to(); + } + }; + + template <> + struct unit_converter { + template < typename T > + microseconds operator()(const seconds& u) const noexcept { + const i64 seconds_to_micro = 1000 * 1000; + return make_microseconds(u.value * seconds_to_micro) + .template cast_to(); + } + }; + + template <> + struct unit_converter { + template < typename T > + seconds operator()(const milliseconds& u) const noexcept { + const f64 milli_to_seconds = 1.0 / 1000.0; + return make_seconds(u.value * milli_to_seconds) + .template cast_to(); + } + }; + + template <> + struct unit_converter { + template < typename T > + microseconds operator()(const milliseconds& u) const noexcept { + const i64 milli_to_micro = 1000; + return make_microseconds(u.value * milli_to_micro) + .template cast_to(); + } + }; + + template <> + struct unit_converter { + template < typename T > + seconds operator()(const microseconds& u) const noexcept { + const f64 micro_to_seconds = 1.0 / 1000.0 / 1000.0; + return make_seconds(u.value * micro_to_seconds) + .template cast_to(); + } + }; + + template <> + struct unit_converter { + template < typename T > + milliseconds operator()(const microseconds& u) const noexcept { + const f64 micro_to_milli = 1.0 / 1000.0; + return make_milliseconds(u.value * micro_to_milli) + .template cast_to(); + } + }; +} + +namespace e2d { namespace time +{ + template < typename T > + std::chrono::duration> + to_chrono(const unit& u) noexcept { + return std::chrono::duration>(u.value); + } + + template < typename T > + std::chrono::duration> + to_chrono(const unit& u) noexcept { + return std::chrono::duration>(u.value); + } + + template < typename T > + std::chrono::duration> + to_chrono(const unit& u) noexcept { + return std::chrono::duration>(u.value); + } + + template < typename T, typename Tag > + seconds to_seconds(const unit& u) noexcept { + return u.template convert_to(); + } + + template < typename T, typename Tag > + milliseconds to_milliseconds(const unit& u) noexcept { + return u.template convert_to(); + } + + template < typename T, typename Tag > + microseconds to_microseconds(const unit& u) noexcept { + return u.template convert_to(); + } +}} + +namespace e2d { namespace time +{ + template < typename TimeTag > + unit now() noexcept { + namespace ch = std::chrono; + const auto n = ch::high_resolution_clock::now(); + const auto m = ch::time_point_cast(n); + const auto c = m.time_since_epoch().count(); + return make_microseconds(c).cast_to().convert_to(); + } + + inline unit now_s() noexcept { + return now(); + } + + inline unit now_ms() noexcept { + return now(); + } + + inline unit now_us() noexcept { + return now(); + } +}} diff --git a/modules/catch2 b/modules/catch2 index c9de7dd1..38e1731f 160000 --- a/modules/catch2 +++ b/modules/catch2 @@ -1 +1 @@ -Subproject commit c9de7dd12d2971c63f9d32ce5459eb98f2fec13d +Subproject commit 38e1731f69349fbb290a8675a4cd09ba30500bb6 diff --git a/modules/utfcpp b/modules/utfcpp index 15375439..e6bde781 160000 --- a/modules/utfcpp +++ b/modules/utfcpp @@ -1 +1 @@ -Subproject commit 1537543999978d3a0464560a9a940d6140c6ba59 +Subproject commit e6bde7819c60c453b720b5de8c7c5ee9ffd9805a diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 52f6f1d6..3ebafcb0 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -8,6 +8,7 @@ function(add_e2d_sample NAME) add_executable(${SAMPLE_NAME} ${SAMPLE_SOURCES}) target_include_directories(${SAMPLE_NAME} PRIVATE "../headers") target_link_libraries(${SAMPLE_NAME} enduro2d) + target_link_libraries(${SAMPLE_NAME} "${CMAKE_THREAD_LIBS_INIT}") endfunction(add_e2d_sample) add_e2d_sample(00) diff --git a/scripts/build_debug.bat b/scripts/build_debug.bat index 61b7bfdd..01f8572f 100644 --- a/scripts/build_debug.bat +++ b/scripts/build_debug.bat @@ -4,7 +4,7 @@ mkdir %BUILD_DIR%\debug || goto :error cd %BUILD_DIR%\debug || goto :error cmake ../.. || goto :error cmake --build . --config Debug || goto :error -ctest || goto :error +ctest --verbose || goto :error cd ..\.. || goto :error goto :EOF diff --git a/scripts/build_debug.sh b/scripts/build_debug.sh index dd63fc19..89f4c936 100755 --- a/scripts/build_debug.sh +++ b/scripts/build_debug.sh @@ -5,5 +5,5 @@ mkdir -p $BUILD_DIR/debug cd $BUILD_DIR/debug cmake -DCMAKE_BUILD_TYPE=Debug ../.. cmake --build . -- -j8 -ctest +ctest --verbose cd ../.. diff --git a/scripts/build_release.bat b/scripts/build_release.bat index 152cb491..b6bf3624 100644 --- a/scripts/build_release.bat +++ b/scripts/build_release.bat @@ -4,7 +4,7 @@ mkdir %BUILD_DIR%\release || goto :error cd %BUILD_DIR%\release || goto :error cmake ../.. || goto :error cmake --build . --config Release || goto :error -ctest || goto :error +ctest --verbose || goto :error cd ..\.. || goto :error goto :EOF diff --git a/scripts/build_release.sh b/scripts/build_release.sh index 76a3fd3f..9ab7e235 100755 --- a/scripts/build_release.sh +++ b/scripts/build_release.sh @@ -5,5 +5,5 @@ mkdir -p $BUILD_DIR/release cd $BUILD_DIR/release cmake -DCMAKE_BUILD_TYPE=Release ../.. cmake --build . -- -j8 -ctest +ctest --verbose cd ../.. diff --git a/scripts/update_modules.sh b/scripts/update_modules.sh index ec42796a..8d2a6978 100755 --- a/scripts/update_modules.sh +++ b/scripts/update_modules.sh @@ -10,7 +10,8 @@ mkdir -p $RDPARTY_DIR git submodule init git submodule update -git submodule foreach git pull +git pull --recurse-submodules +git submodule update --remote --recursive mkdir -p $ROOT_DIR/untests/catch cp -fv $MODULES_DIR/catch2/single_include/catch2/catch.hpp $ROOT_DIR/untests/catch/catch.hpp diff --git a/sources/enduro2d/utils/buffer.cpp b/sources/enduro2d/utils/buffer.cpp new file mode 100644 index 00000000..f572384a --- /dev/null +++ b/sources/enduro2d/utils/buffer.cpp @@ -0,0 +1,141 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include + +namespace e2d +{ + buffer::buffer(buffer&& other) noexcept { + assign(std::move(other)); + } + + buffer& buffer::operator=(buffer&& other) noexcept { + return assign(std::move(other)); + } + + buffer::buffer(const buffer& other) { + assign(other); + } + + buffer& buffer::operator=(const buffer& other) { + return assign(other); + } + + buffer::buffer(std::size_t size) { + resize(size); + } + + buffer::buffer(const void* src, std::size_t size) { + assign(src, size); + } + + buffer& buffer::fill(u8 ch) noexcept { + if ( data_ && size_) { + std::memset(data_.get(), ch, size_); + } + return *this; + } + + buffer& buffer::resize(std::size_t nsize) { + data_t ndata = size_ == nsize + ? std::move(data_) + : (nsize + ? std::make_unique(nsize) + : data_t()); + if ( ndata && data_ && size_ ) { + std::memcpy(ndata.get(), data_.get(), math::min(size_, nsize)); + } + data_.swap(ndata); + size_ = nsize; + return *this; + } + + buffer& buffer::assign(buffer&& other) noexcept { + if ( this != &other ) { + swap(other); + other.clear(); + } + return *this; + } + + buffer& buffer::assign(const buffer& other) { + return this != &other + ? assign(other.data(), other.size()) + : *this; + } + + buffer& buffer::assign(const void* src, std::size_t nsize) { + data_t ndata = size_ == nsize + ? std::move(data_) + : (nsize + ? std::make_unique(nsize) + : data_t()); + if ( ndata && src && nsize ) { + std::memcpy(ndata.get(), src, nsize); + } + data_.swap(ndata); + size_ = nsize; + return *this; + } + + void buffer::swap(buffer& other) noexcept { + using std::swap; + swap(data_, other.data_); + swap(size_, other.size_); + } + + void buffer::clear() noexcept { + data_.reset(); + size_ = 0; + } + + bool buffer::empty() const noexcept { + return !size_; + } + + u8* buffer::data() noexcept { + return data_.get(); + } + + const u8* buffer::data() const noexcept { + return data_.get(); + } + + std::size_t buffer::size() const noexcept { + return size_; + } +} + +namespace e2d +{ + void swap(buffer& l, buffer& r) noexcept { + l.swap(r); + } + + bool operator<(const buffer& l, const buffer& r) noexcept { + const u8* ld = l.data(); + const u8* rd = r.data(); + const std::size_t ls = l.size(); + const std::size_t rs = r.size(); + return + (ls < rs) || + (ls == rs && ls > 0 && std::memcmp(ld, rd, ls) < 0); + } + + bool operator==(const buffer& l, const buffer& r) noexcept { + const u8* ld = l.data(); + const u8* rd = r.data(); + const std::size_t ls = l.size(); + const std::size_t rs = r.size(); + return + (ls == rs) && + (ls == 0 || std::memcmp(ld, rd, ls) == 0); + } + + bool operator!=(const buffer& l, const buffer& r) noexcept { + return !(l == r); + } +} diff --git a/sources/enduro2d/utils/color.cpp b/sources/enduro2d/utils/color.cpp new file mode 100644 index 00000000..e75280f1 --- /dev/null +++ b/sources/enduro2d/utils/color.cpp @@ -0,0 +1,338 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include +#include + +namespace e2d +{ + const color& color::clear() noexcept { + static const color c(0, 0, 0, 0); + return c; + } + + const color& color::black() noexcept { + static const color c(0, 0, 0, 1); + return c; + } + + const color& color::white() noexcept { + static const color c(1, 1, 1, 1); + return c; + } + + const color& color::red() noexcept { + static const color c(1, 0, 0, 1); + return c; + } + + const color& color::green() noexcept { + static const color c(0, 1, 0, 1); + return c; + } + + const color& color::blue() noexcept { + static const color c(0, 0, 1, 1); + return c; + } + + const color& color::yellow() noexcept { + static const color c(1, 1, 0, 1); + return c; + } + + const color& color::magenta() noexcept { + static const color c(1, 0, 1, 1); + return c; + } + + const color& color::cyan() noexcept { + static const color c(0, 1, 1, 1); + return c; + } + + color::color(const color32& other) noexcept + : r(other.r / 255.f) + , g(other.g / 255.f) + , b(other.b / 255.f) + , a(other.a / 255.f) {} + + color::color(f32 nr, f32 ng, f32 nb, f32 na) noexcept + : r(nr) + , g(ng) + , b(nb) + , a(na) {} + + f32* color::data() noexcept { + return &r; + } + + const f32* color::data() const noexcept { + return &r; + } + + f32& color::operator[](std::size_t index) noexcept { + E2D_ASSERT(index < 4); + return data()[index]; + } + + f32 color::operator[](std::size_t index) const noexcept { + E2D_ASSERT(index < 4); + return data()[index]; + } + + color& color::operator+=(f32 v) noexcept { + return *this += color(v,v,v,v); + } + + color& color::operator-=(f32 v) noexcept { + return *this -= color(v,v,v,v); + } + + color& color::operator*=(f32 v) noexcept { + return *this *= color(v,v,v,v); + } + + color& color::operator/=(f32 v) noexcept { + return *this /= color(v,v,v,v); + } + + color& color::operator+=(const color& other) noexcept { + r += other.r; + g += other.g; + b += other.b; + a += other.a; + return *this; + } + + color& color::operator-=(const color& other) noexcept { + r -= other.r; + g -= other.g; + b -= other.b; + a -= other.a; + return *this; + } + + color& color::operator*=(const color& other) noexcept { + r *= other.r; + g *= other.g; + b *= other.b; + a *= other.a; + return *this; + } + + color& color::operator/=(const color& other) noexcept { + E2D_ASSERT(!math::contains(other, 0.f, 0.f)); + r /= other.r; + g /= other.g; + b /= other.b; + a /= other.a; + return *this; + } +} + +namespace e2d +{ + // + // color (<,==,!=) color + // + + bool operator<(const color& l, const color& r) noexcept { + return (l.r < r.r) + || (l.r == r.r && l.g < r.g) + || (l.r == r.r && l.g == r.g && l.b < r.b) + || (l.r == r.r && l.g == r.g && l.b == r.b && l.a < r.a); + } + + bool operator==(const color& l, const color& r) noexcept { + return math::approximately(l.r, r.r) + && math::approximately(l.g, r.g) + && math::approximately(l.b, r.b) + && math::approximately(l.a, r.a); + } + + bool operator!=(const color& l, const color& r) noexcept { + return !(l == r); + } + + // + // color (+,-,*,/) value + // + + color operator+(color l, f32 v) noexcept { + l += v; + return l; + } + + color operator-(color l, f32 v) noexcept { + l -= v; + return l; + } + + color operator*(color l, f32 v) noexcept { + l *= v; + return l; + } + + color operator/(color l, f32 v) noexcept { + l /= v; + return l; + } + + // + // value (+,-,*,/) color + // + + color operator+(f32 v, const color& r) noexcept { + color l(v,v,v,v); + l += r; + return l; + } + + color operator-(f32 v, const color& r) noexcept { + color l(v,v,v,v); + l -= r; + return l; + } + + color operator*(f32 v, const color& r) noexcept { + color l(v,v,v,v); + l *= r; + return l; + } + + color operator/(f32 v, const color& r) noexcept { + color l(v,v,v,v); + l /= r; + return l; + } + + // + // color (+,-,*,/) color + // + + color operator+(color l, const color& r) noexcept { + l += r; + return l; + } + + color operator-(color l, const color& r) noexcept { + l -= r; + return l; + } + + color operator*(color l, const color& r) noexcept { + l *= r; + return l; + } + + color operator/(color l, const color& r) noexcept { + l /= r; + return l; + } +} + +namespace e2d { namespace math +{ + // + // approximately + // + + bool approximately(const color& l, const color& r) noexcept { + return math::approximately(l.r, r.r) + && math::approximately(l.g, r.g) + && math::approximately(l.b, r.b) + && math::approximately(l.a, r.a); + } + + bool approximately(const color& l, const color& r, f32 precision) noexcept { + return math::approximately(l.r, r.r, precision) + && math::approximately(l.g, r.g, precision) + && math::approximately(l.b, r.b, precision) + && math::approximately(l.a, r.a, precision); + } + + // + // minimum/maximum + // + + f32 minimum(const color& c) noexcept { + return math::min(math::min(math::min(c.r, c.g), c.b), c.a); + } + + f32 maximum(const color& c) noexcept { + return math::max(math::max(math::max(c.r, c.g), c.b), c.a); + } + + // + // minimized/maximized/clamped + // + + color minimized(const color& c, const color& cmin) noexcept { + return color( + math::min(c.r, cmin.r), + math::min(c.g, cmin.g), + math::min(c.b, cmin.b), + math::min(c.a, cmin.a)); + } + + color maximized(const color& c, const color& cmax) noexcept { + return color( + math::max(c.r, cmax.r), + math::max(c.g, cmax.g), + math::max(c.b, cmax.b), + math::max(c.a, cmax.a)); + } + + color clamped(const color& c, const color& cmin, const color& cmax) noexcept { + return color( + math::clamp(c.r, cmin.r, cmax.r), + math::clamp(c.g, cmin.g, cmax.g), + math::clamp(c.b, cmin.b, cmax.b), + math::clamp(c.a, cmin.a, cmax.a)); + } + + color saturated(const color& c) noexcept { + return clamped(c, color::clear(), color::white()); + } + + // + // contains + // + + bool contains(const color& c, f32 value, f32 precision) noexcept { + return math::approximately(c.r, value, precision) + || math::approximately(c.g, value, precision) + || math::approximately(c.b, value, precision) + || math::approximately(c.a, value, precision); + } + + bool contains_nan(const color& c) noexcept { + return !math::is_finite(c.r) + || !math::is_finite(c.g) + || !math::is_finite(c.b) + || !math::is_finite(c.a); + } +}} + +namespace e2d { namespace colors +{ + u32 pack_color(const color& c) noexcept { + return + math::numeric_cast(math::round(math::saturate(c.a) * 255.f)) << 24 | + math::numeric_cast(math::round(math::saturate(c.r) * 255.f)) << 16 | + math::numeric_cast(math::round(math::saturate(c.g) * 255.f)) << 8 | + math::numeric_cast(math::round(math::saturate(c.b) * 255.f)) << 0; + } + + color unpack_color(u32 argb) noexcept { + return color( + math::numeric_cast((argb >> 16) & 0xFF) / 255.f, + math::numeric_cast((argb >> 8) & 0xFF) / 255.f, + math::numeric_cast((argb >> 0) & 0xFF) / 255.f, + math::numeric_cast((argb >> 24) & 0xFF) / 255.f); + } +}} diff --git a/sources/enduro2d/utils/color32.cpp b/sources/enduro2d/utils/color32.cpp new file mode 100644 index 00000000..b3c03e68 --- /dev/null +++ b/sources/enduro2d/utils/color32.cpp @@ -0,0 +1,327 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include +#include + +namespace e2d +{ + const color32& color32::clear() noexcept { + static const color32 c(0, 0, 0, 0); + return c; + } + + const color32& color32::black() noexcept { + static const color32 c(0, 0, 0, 255); + return c; + } + + const color32& color32::white() noexcept { + static const color32 c(255, 255, 255, 255); + return c; + } + + const color32& color32::red() noexcept { + static const color32 c(255, 0, 0, 255); + return c; + } + + const color32& color32::green() noexcept { + static const color32 c(0, 255, 0, 255); + return c; + } + + const color32& color32::blue() noexcept { + static const color32 c(0, 0, 255, 255); + return c; + } + + const color32& color32::yellow() noexcept { + static const color32 c(255, 255, 0, 255); + return c; + } + + const color32& color32::magenta() noexcept { + static const color32 c(255, 0, 255, 255); + return c; + } + + const color32& color32::cyan() noexcept { + static const color32 c(0, 255, 255, 255); + return c; + } + + color32::color32(const color& other) noexcept + : r(math::numeric_cast(math::round(math::saturate(other.r) * 255.f))) + , g(math::numeric_cast(math::round(math::saturate(other.g) * 255.f))) + , b(math::numeric_cast(math::round(math::saturate(other.b) * 255.f))) + , a(math::numeric_cast(math::round(math::saturate(other.a) * 255.f))) {} + + color32::color32(u8 nr, u8 ng, u8 nb, u8 na) noexcept + : r(nr) + , g(ng) + , b(nb) + , a(na) {} + + u8* color32::data() noexcept { + return &r; + } + + const u8* color32::data() const noexcept { + return &r; + } + + u8& color32::operator[](std::size_t index) noexcept { + E2D_ASSERT(index < 4); + return data()[index]; + } + + u8 color32::operator[](std::size_t index) const noexcept { + E2D_ASSERT(index < 4); + return data()[index]; + } + + color32& color32::operator+=(u8 v) noexcept { + return *this += color32(v,v,v,v); + } + + color32& color32::operator-=(u8 v) noexcept { + return *this -= color32(v,v,v,v); + } + + color32& color32::operator*=(u8 v) noexcept { + return *this *= color32(v,v,v,v); + } + + color32& color32::operator/=(u8 v) noexcept { + return *this /= color32(v,v,v,v); + } + + color32& color32::operator+=(const color32& other) noexcept { + r += other.r; + g += other.g; + b += other.b; + a += other.a; + return *this; + } + + color32& color32::operator-=(const color32& other) noexcept { + r -= other.r; + g -= other.g; + b -= other.b; + a -= other.a; + return *this; + } + + color32& color32::operator*=(const color32& other) noexcept { + r *= other.r; + g *= other.g; + b *= other.b; + a *= other.a; + return *this; + } + + color32& color32::operator/=(const color32& other) noexcept { + E2D_ASSERT(!math::contains(other, u8(0), u8(0))); + r /= other.r; + g /= other.g; + b /= other.b; + a /= other.a; + return *this; + } +} + +namespace e2d +{ + // + // color32 (<,==,!=) color32 + // + + bool operator<(const color32& l, const color32& r) noexcept { + return (l.r < r.r) + || (l.r == r.r && l.g < r.g) + || (l.r == r.r && l.g == r.g && l.b < r.b) + || (l.r == r.r && l.g == r.g && l.b == r.b && l.a < r.a); + } + + bool operator==(const color32& l, const color32& r) noexcept { + return math::approximately(l.r, r.r) + && math::approximately(l.g, r.g) + && math::approximately(l.b, r.b) + && math::approximately(l.a, r.a); + } + + bool operator!=(const color32& l, const color32& r) noexcept { + return !(l == r); + } + + // + // color32 (+,-,*,/) value + // + + color32 operator+(color32 l, u8 v) noexcept { + l += v; + return l; + } + + color32 operator-(color32 l, u8 v) noexcept { + l -= v; + return l; + } + + color32 operator*(color32 l, u8 v) noexcept { + l *= v; + return l; + } + + color32 operator/(color32 l, u8 v) noexcept { + l /= v; + return l; + } + + // + // value (+,-,*,/) color32 + // + + color32 operator+(u8 v, const color32& r) noexcept { + color32 l(v,v,v,v); + l += r; + return l; + } + + color32 operator-(u8 v, const color32& r) noexcept { + color32 l(v,v,v,v); + l -= r; + return l; + } + + color32 operator*(u8 v, const color32& r) noexcept { + color32 l(v,v,v,v); + l *= r; + return l; + } + + color32 operator/(u8 v, const color32& r) noexcept { + color32 l(v,v,v,v); + l /= r; + return l; + } + + // + // color32 (+,-,*,/) color32 + // + + color32 operator+(color32 l, const color32& r) noexcept { + l += r; + return l; + } + + color32 operator-(color32 l, const color32& r) noexcept { + l -= r; + return l; + } + + color32 operator*(color32 l, const color32& r) noexcept { + l *= r; + return l; + } + + color32 operator/(color32 l, const color32& r) noexcept { + l /= r; + return l; + } +} + +namespace e2d { namespace math +{ + // + // approximately + // + + bool approximately(const color32& l, const color32& r) noexcept { + return math::approximately(l.r, r.r) + && math::approximately(l.g, r.g) + && math::approximately(l.b, r.b) + && math::approximately(l.a, r.a); + } + + bool approximately(const color32& l, const color32& r, u8 precision) noexcept { + return math::approximately(l.r, r.r, precision) + && math::approximately(l.g, r.g, precision) + && math::approximately(l.b, r.b, precision) + && math::approximately(l.a, r.a, precision); + } + + // + // minimum/maximum + // + + u8 minimum(const color32& c) noexcept { + return math::min(math::min(math::min(c.r, c.g), c.b), c.a); + } + + u8 maximum(const color32& c) noexcept { + return math::max(math::max(math::max(c.r, c.g), c.b), c.a); + } + + // + // minimized/maximized/clamped + // + + color32 minimized(const color32& c, const color32& cmin) noexcept { + return color32( + math::min(c.r, cmin.r), + math::min(c.g, cmin.g), + math::min(c.b, cmin.b), + math::min(c.a, cmin.a)); + } + + color32 maximized(const color32& c, const color32& cmax) noexcept { + return color32( + math::max(c.r, cmax.r), + math::max(c.g, cmax.g), + math::max(c.b, cmax.b), + math::max(c.a, cmax.a)); + } + + color32 clamped(const color32& c, const color32& cmin, const color32& cmax) noexcept { + return color32( + math::clamp(c.r, cmin.r, cmax.r), + math::clamp(c.g, cmin.g, cmax.g), + math::clamp(c.b, cmin.b, cmax.b), + math::clamp(c.a, cmin.a, cmax.a)); + } + + // + // contains + // + + bool contains(const color32& c, u8 value, u8 precision) noexcept { + return math::approximately(c.r, value, precision) + || math::approximately(c.g, value, precision) + || math::approximately(c.b, value, precision) + || math::approximately(c.a, value, precision); + } +}} + +namespace e2d { namespace colors +{ + u32 pack_color32(const color32& c) noexcept { + return + math::numeric_cast(c.a) << 24 | + math::numeric_cast(c.r) << 16 | + math::numeric_cast(c.g) << 8 | + math::numeric_cast(c.b) << 0; + } + + color32 unpack_color32(u32 argb) noexcept { + return color32( + math::numeric_cast((argb >> 16) & 0xFF), + math::numeric_cast((argb >> 8) & 0xFF), + math::numeric_cast((argb >> 0) & 0xFF), + math::numeric_cast((argb >> 24) & 0xFF)); + } +}} diff --git a/sources/enduro2d/utils/filesystem.cpp b/sources/enduro2d/utils/filesystem.cpp new file mode 100644 index 00000000..37310db3 --- /dev/null +++ b/sources/enduro2d/utils/filesystem.cpp @@ -0,0 +1,203 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "filesystem_impl/filesystem_impl.hpp" + +namespace +{ + using namespace e2d; + + const str_view dot = "."; + const str_view dot_dot = ".."; + const str_view path_separators = "/\\"; + + bool is_directory_separator(char ch) noexcept { + return path_separators.cend() != std::find( + path_separators.cbegin(), path_separators.cend(), ch); + } + + str str_view_concat(str_view v1, str_view v2) { + str result; + result.reserve(v1.size() + v2.size()); + result.append(v1.cbegin(), v1.cend()); + result.append(v2.cbegin(), v2.cend()); + return result; + } + + str str_view_concat(str_view v1, str_view v2, str_view v3) { + str result; + result.reserve(v1.size() + v2.size() + v3.size()); + result.append(v1.cbegin(), v1.cend()); + result.append(v2.cbegin(), v2.cend()); + result.append(v3.cbegin(), v3.cend()); + return result; + } +} + +namespace e2d +{ + read_file_uptr make_read_file(str_view path) noexcept { + return impl::make_read_file(path); + } + + write_file_uptr make_write_file(str_view path, bool append) noexcept { + return impl::make_write_file(path, append); + } +} + +namespace e2d { namespace path +{ + str combine(str_view lhs, str_view rhs) { + if ( lhs.empty() || is_absolute(rhs) ) { + return rhs; + } else if ( rhs.empty() ) { + return lhs; + } + return is_directory_separator(lhs.back()) + ? str_view_concat(lhs, rhs) + : str_view_concat(lhs, "/", rhs); + } + + str remove_filename(str_view path) { + const str name = filename(path); + return name.empty() + ? path + : path.substr(0, path.length() - name.length()); + } + + str remove_extension(str_view path) { + const str ext = extension(path); + return ext.empty() + ? path + : path.substr(0, path.length() - ext.length()); + } + + str replace_filename(str_view path, str_view filename) { + const str without_name = remove_filename(path); + return str_view_concat(without_name, filename); + } + + str replace_extension(str_view path, str_view extension) { + const str without_ext = remove_extension(path); + return extension.empty() + ? without_ext + : (extension.front() == '.' + ? str_view_concat(without_ext, extension) + : str_view_concat(without_ext, dot, extension)); + } + + str stem(str_view path) { + const str name = filename(path); + if ( name.empty() || name == dot || name == dot_dot ) { + return name; + } + const str::size_type ext_pos = name.rfind('.'); + return ext_pos != str::npos + ? name.substr(0, ext_pos) + : name; + } + + str filename(str_view path) { + const auto sep_e = std::find_if( + path.crbegin(), path.crend(), &is_directory_separator); + const auto sep_d = std::distance(sep_e, path.crend()); + return path.substr(static_cast(sep_d)); + } + + str extension(str_view path) { + const str name = filename(path); + if ( name.empty() || name == dot || name == dot_dot ) { + return str(); + } + const str::size_type ext_pos = name.rfind('.'); + return ext_pos != str::npos + ? name.substr(ext_pos) + : str(); + } + + str parent_path(str_view path) { + const auto sep_e = std::find_if( + path.crbegin(), path.crend(), &is_directory_separator); + if ( sep_e == path.crend() ) { + return str(); + } + const auto sep_b = std::find_if_not( + sep_e, path.crend(), &is_directory_separator); + if ( sep_b == path.crend() ) { + return str(); + } + const auto sep_d = std::distance(sep_b, path.crend()); + return path.substr(0, static_cast(sep_d)); + } + + bool is_absolute(str_view path) noexcept { + return + (path.size() >= 1 && (path[0] == '/' || path[0] == '\\')) || + (path.size() >= 2 && path[1] == ':'); + } + + bool is_relative(str_view path) noexcept { + return !is_absolute(path); + } +}} + +namespace e2d { namespace filesystem +{ + bool remove(str_view path) { + return impl::remove_file(path) + || impl::remove_directory(path); + } + + bool exists(str_view path) { + return impl::file_exists(path) + || impl::directory_exists(path); + } + + bool remove_file(str_view path) { + return impl::remove_file(path); + } + + bool remove_directory(str_view path) { + return impl::remove_directory(path); + } + + bool file_exists(str_view path) { + return impl::file_exists(path); + } + + bool directory_exists(str_view path) { + return impl::directory_exists(path); + } + + bool create_file(str_view path) { + return create_directory_recursive(path::parent_path(path)) + && make_write_file(path, true); + } + + bool create_directory(str_view path) { + return impl::create_directory(path); + } + + bool create_directory_recursive(str_view path) { + if ( path.empty() || directory_exists(path) ) { + return true; + } else if ( !create_directory(path::parent_path(path)) ) { + return false; + } else { + return impl::create_directory(path); + } + } + + bool try_read_all(buffer& dst, str_view path) noexcept { + return streams::try_read_tail( + dst, make_read_file(path)); + } + + bool try_write_all(const buffer& src, str_view path, bool append) noexcept { + return streams::try_write_tail( + src, make_write_file(path, append)); + } +}} diff --git a/sources/enduro2d/utils/filesystem_impl/files_posix.cpp b/sources/enduro2d/utils/filesystem_impl/files_posix.cpp new file mode 100644 index 00000000..7612e5bd --- /dev/null +++ b/sources/enduro2d/utils/filesystem_impl/files_posix.cpp @@ -0,0 +1,198 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "filesystem_impl.hpp" + +#if defined(E2D_FILESYSTEM_MODE) && E2D_FILESYSTEM_MODE == E2D_FILESYSTEM_MODE_POSIX + +#include +#include + +namespace +{ + using namespace e2d; + + class read_file_posix final : public read_file { + public: + read_file_posix(str path) + : path_(std::move(path)) + { + if ( !open_() ) { + throw bad_stream_operation(); + } + } + + ~read_file_posix() noexcept final { + close_(); + } + public: + std::size_t read(void* dst, std::size_t size) final { + E2D_ASSERT(is_opened_()); + const std::ptrdiff_t rread = ::read(handle_, dst, size); + return rread >= 0 + ? math::numeric_cast(rread) + : throw bad_stream_operation(); + } + + std::size_t seek(std::ptrdiff_t offset, bool relative) final { + E2D_ASSERT(is_opened_()); + const std::ptrdiff_t rlseek = ::lseek(handle_, offset, relative ? SEEK_CUR : SEEK_SET); + return rlseek >= 0 + ? math::numeric_cast(rlseek) + : throw bad_stream_operation(); + } + + std::size_t tell() const final { + E2D_ASSERT(is_opened_()); + const std::ptrdiff_t rlseek = ::lseek(handle_, 0, SEEK_CUR); + return rlseek >= 0 + ? math::numeric_cast(rlseek) + : throw bad_stream_operation(); + } + + std::size_t length() const final { + return length_; + } + + const str& path() const noexcept final { + return path_; + } + private: + bool open_() noexcept { + E2D_ASSERT(!is_opened_()); + handle_ = ::open(path_.c_str(), O_RDONLY); + if ( handle_ < 0 ) { + close_(); + return false; + } + if ( ::lseek(handle_, 0, SEEK_END) < 0 ) { + close_(); + return false; + } + const std::ptrdiff_t rlseek = ::lseek(handle_, 0, SEEK_CUR); + if ( rlseek < 0 ) { + close_(); + return false; + } + length_ = math::numeric_cast(rlseek); + if ( ::lseek(handle_, 0, SEEK_SET) < 0 ) { + close_(); + return false; + } + return true; + } + + void close_() noexcept { + if ( handle_ >= 0 ) { + ::close(handle_); + handle_ = -1; + } + } + + bool is_opened_() const noexcept { + return handle_ >= 0; + } + private: + str path_; + std::size_t length_ = 0; + int handle_ = -1; + }; + + class write_file_posix : public write_file { + public: + write_file_posix(str path, bool append) + : path_(std::move(path)) + { + if ( !open_(append) ) { + throw bad_stream_operation(); + } + } + + ~write_file_posix() noexcept final { + close_(); + } + public: + std::size_t write(const void* src, std::size_t size) final { + E2D_ASSERT(is_opened_()); + const std::ptrdiff_t rwrite = ::write(handle_, src, size); + return rwrite >= 0 + ? math::numeric_cast(rwrite) + : throw bad_stream_operation(); + } + + std::size_t seek(std::ptrdiff_t offset, bool relative) final { + E2D_ASSERT(is_opened_()); + const std::ptrdiff_t rlseek = ::lseek(handle_, offset, relative ? SEEK_CUR : SEEK_SET); + return rlseek >= 0 + ? math::numeric_cast(rlseek) + : throw bad_stream_operation(); + } + + std::size_t tell() const final { + E2D_ASSERT(is_opened_()); + const std::ptrdiff_t rlseek = ::lseek(handle_, 0, SEEK_CUR); + return rlseek >= 0 + ? math::numeric_cast(rlseek) + : throw bad_stream_operation(); + } + + const str& path() const noexcept final { + return path_; + } + private: + bool open_(bool append) noexcept { + E2D_ASSERT(!is_opened_()); + handle_ = ::open( + path_.c_str(), + append ? (O_WRONLY|O_CREAT) : (O_WRONLY|O_CREAT|O_TRUNC), + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if ( handle_ < 0 ) { + close_(); + return false; + } + if ( ::lseek(handle_, 0, SEEK_END) < 0 ) { + close_(); + return false; + } + return true; + } + + void close_() noexcept { + if ( handle_ >= 0 ) { + ::close(handle_); + handle_ = -1; + } + } + + bool is_opened_() const noexcept { + return handle_ >= 0; + } + private: + str path_; + int handle_ = -1; + }; +} + +namespace e2d { namespace impl +{ + read_file_uptr make_read_file(str_view path) noexcept { + try { + return std::make_unique(path); + } catch (...) { + return nullptr; + } + } + + write_file_uptr make_write_file(str_view path, bool append) noexcept { + try { + return std::make_unique(path, append); + } catch (...) { + return nullptr; + } + } +}} + +#endif diff --git a/sources/enduro2d/utils/filesystem_impl/files_winapi.cpp b/sources/enduro2d/utils/filesystem_impl/files_winapi.cpp new file mode 100644 index 00000000..4aac92e2 --- /dev/null +++ b/sources/enduro2d/utils/filesystem_impl/files_winapi.cpp @@ -0,0 +1,237 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "filesystem_impl.hpp" + +#if defined(E2D_FILESYSTEM_MODE) && E2D_FILESYSTEM_MODE == E2D_FILESYSTEM_MODE_WINAPI + +#include + +namespace +{ + using namespace e2d; + + class read_file_winapi : public read_file { + public: + read_file_winapi(str path) + : path_(std::move(path)) + { + if ( !open_() ) { + throw bad_stream_operation(); + } + } + + ~read_file_winapi() noexcept final { + close_(); + } + public: + std::size_t read(void* dst, std::size_t size) final { + E2D_ASSERT(is_opened_()); + DWORD read_bytes = 0; + const BOOL rread = ::ReadFile( + handle_, + dst, + math::numeric_cast(size), + &read_bytes, + NULL); + return TRUE == rread + ? math::numeric_cast(read_bytes) + : throw bad_stream_operation(); + } + + std::size_t seek(std::ptrdiff_t offset, bool relative) final { + E2D_ASSERT(is_opened_()); + const DWORD rseek = ::SetFilePointer( + handle_, + math::numeric_cast(offset), + NULL, + relative ? FILE_CURRENT : FILE_BEGIN); + return rseek != INVALID_SET_FILE_POINTER + ? math::numeric_cast(rseek) + : throw bad_stream_operation(); + } + + std::size_t tell() const final { + E2D_ASSERT(is_opened_()); + const DWORD rseek = ::SetFilePointer(handle_, 0, NULL, FILE_CURRENT); + return rseek != INVALID_SET_FILE_POINTER + ? math::numeric_cast(rseek) + : throw bad_stream_operation(); + } + + std::size_t length() const final { + return length_; + } + + const str& path() const noexcept final { + return path_; + } + private: + bool open_() { + E2D_ASSERT(!is_opened_()); + const wstr wide_path = make_wide(path_); + handle_ = ::CreateFileW( + wide_path.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_READONLY, + NULL); + if ( INVALID_HANDLE_VALUE == handle_ ) { + handle_ = ::CreateFileW( + wide_path.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_READONLY, + NULL); + } + if ( INVALID_HANDLE_VALUE == handle_ ) { + close_(); + return false; + } + const DWORD file_size = ::GetFileSize(handle_, NULL); + if ( INVALID_FILE_SIZE == file_size ) { + close_(); + return false; + } + length_ = math::numeric_cast(file_size); + return true; + } + + void close_() noexcept { + if ( INVALID_HANDLE_VALUE != handle_ ) { + ::CloseHandle(handle_); + handle_ = INVALID_HANDLE_VALUE; + } + } + + bool is_opened_() const noexcept { + return INVALID_HANDLE_VALUE != handle_; + } + private: + str path_; + std::size_t length_ = 0; + HANDLE handle_ = INVALID_HANDLE_VALUE; + }; + + class write_file_winapi : public write_file { + public: + write_file_winapi(str path, bool append) + : path_(std::move(path)) + { + if ( !open_(append) ) { + throw bad_stream_operation(); + } + } + + ~write_file_winapi() noexcept final { + close_(); + } + public: + std::size_t write(const void* src, std::size_t size) final { + E2D_ASSERT(is_opened_()); + DWORD write_bytes = 0; + const BOOL rwrite = ::WriteFile( + handle_, + src, + math::numeric_cast(size), + &write_bytes, + NULL); + return TRUE == rwrite + ? math::numeric_cast(write_bytes) + : throw bad_stream_operation(); + } + + std::size_t seek(std::ptrdiff_t offset, bool relative) final { + E2D_ASSERT(is_opened_()); + const DWORD rseek = ::SetFilePointer( + handle_, + math::numeric_cast(offset), + NULL, + relative ? FILE_CURRENT : FILE_BEGIN); + return rseek != INVALID_SET_FILE_POINTER + ? math::numeric_cast(rseek) + : throw bad_stream_operation(); + } + + std::size_t tell() const final { + E2D_ASSERT(is_opened_()); + const DWORD rseek = ::SetFilePointer(handle_, 0, NULL, FILE_CURRENT); + return rseek != INVALID_SET_FILE_POINTER + ? math::numeric_cast(rseek) + : throw bad_stream_operation(); + } + + const str& path() const noexcept final { + return path_; + } + private: + bool open_(bool append) { + E2D_ASSERT(!is_opened_()); + const wstr wide_path = make_wide(path_); + handle_ = ::CreateFileW( + wide_path.c_str(), + GENERIC_WRITE, + FILE_SHARE_WRITE, + NULL, + append ? OPEN_ALWAYS : CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if ( INVALID_HANDLE_VALUE == handle_ ) { + close_(); + return false; + } + if ( append && INVALID_SET_FILE_POINTER == ::SetFilePointer( + handle_, + 0, + NULL, + FILE_END) ) + { + close_(); + return false; + } + return true; + } + + void close_() noexcept { + if ( INVALID_HANDLE_VALUE != handle_ ) { + ::CloseHandle(handle_); + handle_ = INVALID_HANDLE_VALUE; + } + } + + bool is_opened_() const noexcept { + return INVALID_HANDLE_VALUE != handle_; + } + private: + str path_; + HANDLE handle_ = INVALID_HANDLE_VALUE; + }; +} + +namespace e2d { namespace impl +{ + read_file_uptr make_read_file(str_view path) noexcept { + try { + return std::make_unique(path); + } catch (...) { + return nullptr; + } + } + + write_file_uptr make_write_file(str_view path, bool append) noexcept { + try { + return std::make_unique(path, append); + } catch (...) { + return nullptr; + } + } +}} + +#endif diff --git a/sources/enduro2d/utils/filesystem_impl/filesystem_impl.hpp b/sources/enduro2d/utils/filesystem_impl/filesystem_impl.hpp new file mode 100644 index 00000000..a09c9ae7 --- /dev/null +++ b/sources/enduro2d/utils/filesystem_impl/filesystem_impl.hpp @@ -0,0 +1,44 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include +#include + +#define E2D_FILESYSTEM_MODE_POSIX 1 +#define E2D_FILESYSTEM_MODE_WINAPI 2 + +#ifndef E2D_FILESYSTEM_MODE +# if defined(E2D_PLATFORM) && E2D_PLATFORM == E2D_PLATFORM_ANDROID +# define E2D_FILESYSTEM_MODE E2D_FILESYSTEM_MODE_POSIX +# elif defined(E2D_PLATFORM) && E2D_PLATFORM == E2D_PLATFORM_IOS +# define E2D_FILESYSTEM_MODE E2D_FILESYSTEM_MODE_POSIX +# elif defined(E2D_PLATFORM) && E2D_PLATFORM == E2D_PLATFORM_LINUX +# define E2D_FILESYSTEM_MODE E2D_FILESYSTEM_MODE_POSIX +# elif defined(E2D_PLATFORM) && E2D_PLATFORM == E2D_PLATFORM_MACOSX +# define E2D_FILESYSTEM_MODE E2D_FILESYSTEM_MODE_POSIX +# elif defined(E2D_PLATFORM) && E2D_PLATFORM == E2D_PLATFORM_WINDOWS +# define E2D_FILESYSTEM_MODE E2D_FILESYSTEM_MODE_WINAPI +# endif +#endif + +namespace e2d { namespace impl +{ + read_file_uptr make_read_file(str_view path) noexcept; + write_file_uptr make_write_file(str_view path, bool append) noexcept; +}} + +namespace e2d { namespace filesystem { namespace impl +{ + bool remove_file(str_view path); + bool remove_directory(str_view path); + + bool file_exists(str_view path); + bool directory_exists(str_view path); + + bool create_directory(str_view path); +}}} diff --git a/sources/enduro2d/utils/filesystem_impl/filesystem_posix.cpp b/sources/enduro2d/utils/filesystem_impl/filesystem_posix.cpp new file mode 100644 index 00000000..e3957b02 --- /dev/null +++ b/sources/enduro2d/utils/filesystem_impl/filesystem_posix.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "filesystem_impl.hpp" + +#if defined(E2D_FILESYSTEM_MODE) && E2D_FILESYSTEM_MODE == E2D_FILESYSTEM_MODE_POSIX + +#include +#include +#include + +namespace +{ + mode_t default_directory_mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; +} + +namespace e2d { namespace filesystem { namespace impl +{ + bool remove_file(str_view path) { + return + 0 == ::unlink(make_utf8(path).c_str()) + || errno == ENOENT; + } + + bool remove_directory(str_view path) { + return + 0 == ::rmdir(make_utf8(path).c_str()) + || errno == ENOENT; + } + + bool file_exists(str_view path) { + struct stat st; + return + 0 == ::stat(make_utf8(path).c_str(), &st) + && S_ISREG(st.st_mode); + } + + bool directory_exists(str_view path) { + struct stat st; + return + 0 == ::stat(make_utf8(path).c_str(), &st) + && S_ISDIR(st.st_mode); + } + + bool create_directory(str_view path) { + return + 0 == ::mkdir(make_utf8(path).c_str(), default_directory_mode) + || errno == EEXIST; + } +}}} + +#endif diff --git a/sources/enduro2d/utils/filesystem_impl/filesystem_winapi.cpp b/sources/enduro2d/utils/filesystem_impl/filesystem_winapi.cpp new file mode 100644 index 00000000..7493e2c8 --- /dev/null +++ b/sources/enduro2d/utils/filesystem_impl/filesystem_winapi.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* + * This file is part of the "enduro2d" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "filesystem_impl.hpp" + +#if defined(E2D_FILESYSTEM_MODE) && E2D_FILESYSTEM_MODE == E2D_FILESYSTEM_MODE_WINAPI + +#include + +namespace e2d { namespace filesystem { namespace impl +{ + bool remove_file(str_view path) { + const wstr wide_path = make_wide(path); + return + ::DeleteFileW(wide_path.c_str()) + || ::GetLastError() == ERROR_FILE_NOT_FOUND + || ::GetLastError() == ERROR_PATH_NOT_FOUND; + } + + bool remove_directory(str_view path) { + const wstr wide_path = make_wide(path); + return + ::RemoveDirectoryW(wide_path.c_str()) + || ::GetLastError() == ERROR_FILE_NOT_FOUND + || ::GetLastError() == ERROR_PATH_NOT_FOUND; + } + + bool file_exists(str_view path) { + const wstr wide_path = make_wide(path); + DWORD attributes = ::GetFileAttributesW(wide_path.c_str()); + return + attributes != INVALID_FILE_ATTRIBUTES && + !(attributes & FILE_ATTRIBUTE_DIRECTORY); + } + + bool directory_exists(str_view path) { + const wstr wide_path = make_wide(path); + DWORD attributes = ::GetFileAttributesW(wide_path.c_str()); + return + attributes != INVALID_FILE_ATTRIBUTES && + (attributes & FILE_ATTRIBUTE_DIRECTORY); + } + + bool create_directory(str_view path) { + const wstr wide_path = make_wide(path); + return + ::CreateDirectoryW(wide_path.c_str(), nullptr) || + ::GetLastError() == ERROR_ALREADY_EXISTS; + } +}}} + +#endif diff --git a/sources/enduro2d/utils/image.cpp b/sources/enduro2d/utils/image.cpp new file mode 100644 index 00000000..966588c7 --- /dev/null +++ b/sources/enduro2d/utils/image.cpp @@ -0,0 +1,323 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "image_impl/image_impl.hpp" + +namespace +{ + using namespace e2d; + + struct data_format_description { + u32 minimal_size; + u32 bits_per_pixel; + image_data_format format; + bool compressed; + bool must_be_square; + bool must_be_power_of_two; + }; + + const data_format_description data_format_descriptions[] = { + {1, 8, image_data_format::g8, false, false, false}, + {1, 16, image_data_format::ga8, false, false, false}, + {1, 24, image_data_format::rgb8, false, false, false}, + {1, 32, image_data_format::rgba8, false, false, false}, + + {4, 4, image_data_format::dxt1, true, false, true}, + {4, 8, image_data_format::dxt3, true, false, true}, + {4, 8, image_data_format::dxt5, true, false, true}, + + {8, 2, image_data_format::rgb_pvrtc2, true, true, true}, + {8, 4, image_data_format::rgb_pvrtc4, true, true, true}, + {8, 2, image_data_format::rgba_pvrtc2, true, true, true}, + {8, 4, image_data_format::rgba_pvrtc4, true, true, true}, + + {0, 0, image_data_format::unknown, false, false, false} + }; + + const data_format_description& get_data_format_description(image_data_format format) noexcept { + const std::size_t findex = static_cast>(format); + E2D_ASSERT(findex < E2D_COUNTOF(data_format_descriptions)); + const data_format_description& fdesc = data_format_descriptions[findex]; + E2D_ASSERT(fdesc.format == format); + return fdesc; + } + + v2u adjust_image_size(const v2u& size, image_data_format format) noexcept { + const data_format_description& format_desc = get_data_format_description(format); + v2u nsize = math::maximized(size, v2u(format_desc.minimal_size)); + if ( format_desc.must_be_square ) { + nsize = v2u(math::maximum(nsize)); + } + if ( format_desc.must_be_power_of_two ) { + nsize = v2u(math::next_power_of_2(nsize.x), + math::next_power_of_2(nsize.y)); + } + return nsize; + } + + std::size_t buffer_size_for_image(const v2u& size, image_data_format format) noexcept { + const v2u nsize = adjust_image_size(size, format); + const std::size_t bpp = get_data_format_description(format).bits_per_pixel; + const std::size_t bits = nsize.x * nsize.y * bpp; + E2D_ASSERT(bits % 8 == 0); + return bits / 8; + } + + bool check_image_format(const v2u& size, image_data_format format) noexcept { + return size == adjust_image_size(size, format) + && (format != image_data_format::unknown || size == v2u::zero()); + } + + bool check_image_format(const v2u& size, image_data_format format, const buffer& data) noexcept { + return check_image_format(size, format) + && data.size() == buffer_size_for_image(size, format); + } +} + +namespace e2d +{ + image::image(image&& other) noexcept { + assign(std::move(other)); + } + + image& image::operator=(image&& other) noexcept { + return assign(std::move(other)); + } + + image::image(const image& other) { + assign(other); + } + + image& image::operator=(const image& other) { + return assign(other); + } + + image::image(const v2u& size, image_data_format format) { + assign(size, format); + } + + image::image(const v2u& size, image_data_format format, buffer&& data) { + assign(size, format, std::move(data)); + } + + image::image(const v2u& size, image_data_format format, const buffer& data) { + assign(size, format, data); + } + + image& image::assign(image&& other) noexcept { + if ( this != &other ) { + swap(other); + other.clear(); + } + return *this; + } + + image& image::assign(const image& other) { + if ( this != &other ) { + data_.assign(other.data_); + size_ = other.size_; + format_ = other.format_; + } + return *this; + } + + image& image::assign(const v2u& size, image_data_format format) { + if ( !check_image_format(size, format) ) { + throw bad_image_data_format(); + } + data_.resize(buffer_size_for_image(size, format)); + size_ = size; + format_ = format; + return *this; + } + + image& image::assign(const v2u& size, image_data_format format, buffer&& data) { + if ( !check_image_format(size, format, data) ) { + throw bad_image_data_format(); + } + data_.assign(std::move(data)); + size_ = size; + format_ = format; + return *this; + } + + image& image::assign(const v2u& size, image_data_format format, const buffer& data) { + if ( !check_image_format(size, format, data) ) { + throw bad_image_data_format(); + } + data_.assign(data); + size_ = size; + format_ = format; + return *this; + } + + void image::swap(image& other) noexcept { + using std::swap; + swap(data_, other.data_); + swap(size_, other.size_); + swap(format_, other.format_); + } + + void image::clear() noexcept { + data_.clear(); + size_ = v2u::zero(); + format_ = image_data_format::unknown; + } + + bool image::empty() const noexcept { + return data_.empty(); + } + + color image::pixel(u32 u, u32 v) const { + return color(pixel32(u, v)); + } + + color image::pixel(const v2u& uv) const { + return color(pixel32(uv)); + } + + color32 image::pixel32(u32 u, u32 v) const { + const data_format_description& format_desc = + get_data_format_description(format_); + + if ( empty() || u >= size_.x || v >= size_.y ) { + throw bad_image_access(); + } else if ( format_desc.format == image_data_format::unknown || format_desc.compressed ) { + throw bad_image_access(); + } + + const std::size_t bits_per_pixel = format_desc.bits_per_pixel; + const std::size_t bytes_per_pixel = bits_per_pixel / 8; + const std::size_t stride_in_bytes = size_.x * bytes_per_pixel; + E2D_ASSERT(bits_per_pixel % 8 == 0); + + const std::size_t pixel_index = v * stride_in_bytes + u * bytes_per_pixel; + E2D_ASSERT(pixel_index + bytes_per_pixel <= data_.size()); + const u8* const pixel = data_.data() + pixel_index; + + switch ( format_ ) { + case image_data_format::g8: + E2D_ASSERT(bytes_per_pixel == 1); + return color32(pixel[0], + pixel[0], + pixel[0]); + case image_data_format::ga8: + E2D_ASSERT(bytes_per_pixel == 2); + return color32(pixel[0], + pixel[0], + pixel[0], + pixel[1]); + case image_data_format::rgb8: + E2D_ASSERT(bytes_per_pixel == 3); + return color32(pixel[0], + pixel[1], + pixel[2]); + case image_data_format::rgba8: + E2D_ASSERT(bytes_per_pixel == 4); + return color32(pixel[0], + pixel[1], + pixel[2], + pixel[3]); + default: + E2D_ASSERT_MSG(false, "unexpected image data format"); + throw bad_image_access(); + } + } + + color32 image::pixel32(const v2u& uv) const { + return pixel32(uv.x, uv.y); + } + + const v2u& image::size() const noexcept { + return size_; + } + + image_data_format image::format() const noexcept { + return format_; + } + + const buffer& image::data() const noexcept { + return data_; + } +} + +namespace e2d +{ + void swap(image& l, image& r) noexcept { + l.swap(r); + } + + bool operator<(const image& l, const image& r) noexcept { + return l.format() < r.format() + || (l.format() == r.format() && l.size() < r.size()) + || (l.format() == r.format() && l.size() == r.size() && l.data() < r.data()); + } + + bool operator==(const image& l, const image& r) noexcept { + return l.format() == r.format() + && l.size() == r.size() + && l.data() == r.data(); + } + + bool operator!=(const image& l, const image& r) noexcept { + return !(l == r); + } +} + +namespace e2d { namespace images +{ + bool try_load_image( + image& dst, + const buffer& src) noexcept + { + return impl::try_load_image_dds(dst, src) + || impl::try_load_image_pvr(dst, src) + || impl::try_load_image_stb(dst, src); + } + + bool try_load_image( + image& dst, + const input_stream_uptr& src) noexcept + { + buffer file_data; + return streams::try_read_tail(file_data, src) + && try_load_image(dst, file_data); + } + + bool try_save_image( + const image& src, + image_file_format format, + buffer& dst) noexcept + { + switch ( format ) { + case image_file_format::dds: + return impl::try_save_image_dds(src, dst); + case image_file_format::jpg: + return impl::try_save_image_jpg(src, dst); + case image_file_format::png: + return impl::try_save_image_png(src, dst); + case image_file_format::pvr: + return impl::try_save_image_pvr(src, dst); + case image_file_format::tga: + return impl::try_save_image_tga(src, dst); + case image_file_format::unknown: + return false; + default: + E2D_ASSERT_MSG(false, "unexpected image file format"); + return false; + } + } + + bool try_save_image( + const image& src, + image_file_format format, + const output_stream_uptr& dst) noexcept + { + buffer file_data; + return try_save_image(src, format, file_data) + && streams::try_write_tail(file_data, dst); + } +}} diff --git a/sources/enduro2d/utils/image_impl/image_impl.hpp b/sources/enduro2d/utils/image_impl/image_impl.hpp new file mode 100644 index 00000000..a683368c --- /dev/null +++ b/sources/enduro2d/utils/image_impl/image_impl.hpp @@ -0,0 +1,23 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include +#include + +namespace e2d { namespace images { namespace impl +{ + bool try_load_image_dds(image& dst, const buffer& src) noexcept; + bool try_load_image_pvr(image& dst, const buffer& src) noexcept; + bool try_load_image_stb(image& dst, const buffer& src) noexcept; + + bool try_save_image_dds(const image& src, buffer& dst) noexcept; + bool try_save_image_jpg(const image& src, buffer& dst) noexcept; + bool try_save_image_png(const image& src, buffer& dst) noexcept; + bool try_save_image_pvr(const image& src, buffer& dst) noexcept; + bool try_save_image_tga(const image& src, buffer& dst) noexcept; +}}} diff --git a/sources/enduro2d/utils/image_impl/image_reader_dds.cpp b/sources/enduro2d/utils/image_impl/image_reader_dds.cpp new file mode 100644 index 00000000..1006e112 --- /dev/null +++ b/sources/enduro2d/utils/image_impl/image_reader_dds.cpp @@ -0,0 +1,16 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "image_impl.hpp" + +namespace e2d { namespace images { namespace impl +{ + bool try_load_image_dds(image& dst, const buffer& src) noexcept { + //TODO: implme + E2D_UNUSED(dst, src); + return false; + } +}}} diff --git a/sources/enduro2d/utils/image_impl/image_reader_pvr.cpp b/sources/enduro2d/utils/image_impl/image_reader_pvr.cpp new file mode 100644 index 00000000..f3452a2a --- /dev/null +++ b/sources/enduro2d/utils/image_impl/image_reader_pvr.cpp @@ -0,0 +1,16 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "image_impl.hpp" + +namespace e2d { namespace images { namespace impl +{ + bool try_load_image_pvr(image& dst, const buffer& src) noexcept { + //TODO: implme + E2D_UNUSED(dst, src); + return false; + } +}}} diff --git a/sources/enduro2d/utils/image_impl/image_reader_stb.cpp b/sources/enduro2d/utils/image_impl/image_reader_stb.cpp new file mode 100644 index 00000000..da973a0d --- /dev/null +++ b/sources/enduro2d/utils/image_impl/image_reader_stb.cpp @@ -0,0 +1,87 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "image_impl.hpp" + +#define STBI_NO_STDIO +#define STBI_NO_FAILURE_STRINGS + +#define STBI_NO_HDR +#define STBI_NO_LINEAR + +#define STBI_ONLY_PNG +#define STBI_ONLY_TGA +#define STBI_ONLY_JPEG + +#define STBI_FREE(ptr) std::free(ptr) +#define STBI_MALLOC(size) std::malloc(size) +#define STBI_REALLOC(ptr,nsize) std::realloc(ptr, nsize) + +#define STBI_ASSERT(expr) E2D_ASSERT(expr) + +#define STB_IMAGE_IMPLEMENTATION +#include <3rdparty/stb/stb_image.h> + +namespace +{ + using namespace e2d; + + using stbi_img_uptr = std::unique_ptr; + + stbi_img_uptr load_stb_image(const buffer& data, v2u& out_size, u32& out_channels) noexcept { + int img_w = 0, img_h = 0, img_c = 0; + stbi_uc* img = stbi_load_from_memory( + static_cast(data.data()), + math::numeric_cast(data.size()), + &img_w, &img_h, &img_c, 0); + stbi_img_uptr img_ptr(img, stbi_image_free); + if ( img_ptr && img_w > 0 && img_h > 0 && img_c >= 1 && img_c <= 4 ) { + out_size = v2u(math::numeric_cast(img_w), math::numeric_cast(img_h)); + out_channels = math::numeric_cast(img_c); + return img_ptr; + } + return stbi_img_uptr(nullptr, stbi_image_free); + } + + image_data_format image_format_from_stb_channels(u32 channels) noexcept { + switch ( channels ) { + case 1: return image_data_format::g8; + case 2: return image_data_format::ga8; + case 3: return image_data_format::rgb8; + case 4: return image_data_format::rgba8; + default: return image_data_format::unknown; + } + } + + bool image_from_stb_description( + image& dst, const stbi_img_uptr& img, const v2u& img_size, u32 img_channels) noexcept + { + try { + const image_data_format img_format = image_format_from_stb_channels(img_channels); + if ( img && img_size.x > 0 && img_size.y > 0 && img_format != image_data_format::unknown ) { + buffer img_buffer( + img.get(), + math::numeric_cast(img_size.x * img_size.y * img_channels)); + dst.assign(img_size, img_format, std::move(img_buffer)); + return true; + } + } catch (std::exception&) { + // nothing + } + return false; + } +} + +namespace e2d { namespace images { namespace impl +{ + bool try_load_image_stb(image& dst, const buffer& src) noexcept { + v2u img_size; + u32 img_channels = 0; + const stbi_img_uptr img_ptr = load_stb_image(src, img_size, img_channels); + return img_ptr + && image_from_stb_description(dst, img_ptr, img_size, img_channels); + } +}}} diff --git a/sources/enduro2d/utils/image_impl/image_writer_dds.cpp b/sources/enduro2d/utils/image_impl/image_writer_dds.cpp new file mode 100644 index 00000000..9194618e --- /dev/null +++ b/sources/enduro2d/utils/image_impl/image_writer_dds.cpp @@ -0,0 +1,39 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "image_impl.hpp" + +namespace +{ + using namespace e2d; + + bool is_save_image_dds_supported(image_data_format data_format) noexcept { + switch ( data_format ) { + case image_data_format::g8: + case image_data_format::ga8: + case image_data_format::rgb8: + case image_data_format::rgba8: + case image_data_format::dxt1: + case image_data_format::dxt3: + case image_data_format::dxt5: + return true; + default: + return false; + } + } +} + +namespace e2d { namespace images { namespace impl +{ + bool try_save_image_dds(const image& src, buffer& dst) noexcept { + E2D_UNUSED(src, dst); + if ( is_save_image_dds_supported(src.format()) ) { + //TODO: implme + E2D_ASSERT_MSG(false, "implme"); + } + return false; + } +}}} diff --git a/sources/enduro2d/utils/image_impl/image_writer_pvr.cpp b/sources/enduro2d/utils/image_impl/image_writer_pvr.cpp new file mode 100644 index 00000000..f8685d02 --- /dev/null +++ b/sources/enduro2d/utils/image_impl/image_writer_pvr.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "image_impl.hpp" + +namespace +{ + using namespace e2d; + + bool is_save_image_pvr_supported(image_data_format data_format) noexcept { + switch ( data_format ) { + case image_data_format::g8: + case image_data_format::ga8: + case image_data_format::rgb8: + case image_data_format::rgba8: + case image_data_format::rgb_pvrtc2: + case image_data_format::rgb_pvrtc4: + case image_data_format::rgba_pvrtc2: + case image_data_format::rgba_pvrtc4: + return true; + default: + return false; + } + } +} + +namespace e2d { namespace images { namespace impl +{ + bool try_save_image_pvr(const image& src, buffer& dst) noexcept { + E2D_UNUSED(src, dst); + if ( is_save_image_pvr_supported(src.format()) ) { + //TODO: implme + E2D_ASSERT_MSG(false, "implme"); + } + return false; + } +}}} diff --git a/sources/enduro2d/utils/image_impl/image_writer_stb.cpp b/sources/enduro2d/utils/image_impl/image_writer_stb.cpp new file mode 100644 index 00000000..7ba2b2bb --- /dev/null +++ b/sources/enduro2d/utils/image_impl/image_writer_stb.cpp @@ -0,0 +1,99 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "image_impl.hpp" + +#define STBI_WRITE_NO_STDIO + +#define STBIW_FREE(ptr) std::free(ptr) +#define STBIW_MALLOC(size) std::malloc(size) +#define STBIW_REALLOC(ptr,nsize) std::realloc(ptr, nsize) + +#define STBIW_ASSERT(expr) E2D_ASSERT(expr) + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include <3rdparty/stb/stb_image_write.h> + +namespace +{ + using namespace e2d; + + int stb_channels_from_image_format(image_data_format format) noexcept { + switch ( format ) { + case image_data_format::g8: return 1; + case image_data_format::ga8: return 2; + case image_data_format::rgb8: return 3; + case image_data_format::rgba8: return 4; + default: return 0; + } + } + + void stb_write_callback(void *context, void *data, int size) { + E2D_ASSERT(context && data && size > 0); + vector& ctx = *static_cast*>(context); + ctx.insert( + ctx.cend(), + reinterpret_cast(data), + reinterpret_cast(data) + math::numeric_cast(size)); + } +} + +namespace e2d { namespace images { namespace impl +{ + bool try_save_image_jpg(const image& src, buffer& dst) noexcept { + int img_w = math::numeric_cast(src.size().x); + int img_h = math::numeric_cast(src.size().y); + int img_c = stb_channels_from_image_format(src.format()); + try { + vector data; + if ( 0 != stbi_write_jpg_to_func( + stb_write_callback, &data, img_w, img_h, img_c, src.data().data(), 80) ) + { + dst.assign(data.data(), data.size()); + return true; + } + } catch (...) { + // nothing + } + return false; + } + + bool try_save_image_png(const image& src, buffer& dst) noexcept { + int img_w = math::numeric_cast(src.size().x); + int img_h = math::numeric_cast(src.size().y); + int img_c = stb_channels_from_image_format(src.format()); + try { + vector data; + if ( 0 != stbi_write_png_to_func( + stb_write_callback, &data, img_w, img_h, img_c, src.data().data(), img_w * img_c) ) + { + dst.assign(data.data(), data.size()); + return true; + } + } catch (...) { + // nothing + } + return false; + } + + bool try_save_image_tga(const image& src, buffer& dst) noexcept { + int img_w = math::numeric_cast(src.size().x); + int img_h = math::numeric_cast(src.size().y); + int img_c = stb_channels_from_image_format(src.format()); + try { + vector data; + if ( 0 != stbi_write_tga_to_func( + stb_write_callback, &data, img_w, img_h, img_c, src.data().data()) ) + { + dst.assign(data.data(), data.size()); + return true; + } + } catch (...) { + // nothing + } + return false; + } +}}} diff --git a/sources/enduro2d/utils/jobber.cpp b/sources/enduro2d/utils/jobber.cpp new file mode 100644 index 00000000..c309b140 --- /dev/null +++ b/sources/enduro2d/utils/jobber.cpp @@ -0,0 +1,108 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include + +namespace e2d +{ + jobber::jobber(u32 threads) { + try { + threads_.resize(math::max(1u, threads)); + for ( std::thread& thread : threads_ ) { + thread = std::thread(&jobber::worker_main_, this); + } + } catch (...) { + shutdown_(); + throw; + } + } + + jobber::~jobber() noexcept { + shutdown_(); + } + + void jobber::pause() noexcept { + paused_.store(true); + } + + void jobber::resume() noexcept { + paused_.store(false); + { + std::lock_guard guard(tasks_mutex_); + cond_var_.notify_all(); + } + } + + bool jobber::is_paused() const noexcept { + return paused_; + } + + void jobber::wait_all() const noexcept { + while ( active_task_count_ ) { + std::this_thread::yield(); + } + } + + void jobber::active_wait_all() noexcept { + while ( active_task_count_ ) { + std::unique_lock lock(tasks_mutex_); + process_task_(std::move(lock)); + } + } + + void jobber::push_task_(priority priority, task_ptr task) { + tasks_.emplace_back(priority, std::move(task)); + std::push_heap(tasks_.begin(), tasks_.end()); + ++active_task_count_; + cond_var_.notify_one(); + } + + jobber::task_ptr jobber::pop_task_() noexcept { + if ( !tasks_.empty() ) { + std::pop_heap(tasks_.begin(), tasks_.end()); + task_ptr task = std::move(tasks_.back().second); + tasks_.pop_back(); + return task; + } + return nullptr; + } + + void jobber::shutdown_() noexcept { + cancelled_.store(true); + { + std::lock_guard guard(tasks_mutex_); + cond_var_.notify_all(); + } + for ( std::thread& thread : threads_ ) { + if ( thread.joinable() ) { + thread.join(); + } + } + } + + void jobber::worker_main_() noexcept { + for (;;) { + std::unique_lock lock(tasks_mutex_); + cond_var_.wait(lock, [this](){ + return cancelled_ || (!paused_ && !tasks_.empty()); + }); + if ( cancelled_ ) { + break; + } + process_task_(std::move(lock)); + } + } + + void jobber::process_task_(std::unique_lock lock) noexcept { + E2D_ASSERT(lock.owns_lock()); + task_ptr task = pop_task_(); + if ( task ) { + lock.unlock(); + task->run(); + --active_task_count_; + } + } +} diff --git a/sources/enduro2d/utils/streams.cpp b/sources/enduro2d/utils/streams.cpp new file mode 100644 index 00000000..f738b92c --- /dev/null +++ b/sources/enduro2d/utils/streams.cpp @@ -0,0 +1,103 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include +#include + +namespace +{ + using namespace e2d; + + class memory_stream final : public input_stream { + public: + memory_stream(buffer data) noexcept + : data_(std::move(data)) {} + + std::size_t read(void* dst, std::size_t size) final { + const std::size_t read_bytes = dst + ? math::min(size, data_.size() - pos_) + : 0; + if ( read_bytes > 0 ) { + std::memcpy(dst, data_.data() + pos_, read_bytes); + pos_ += read_bytes; + } + return read_bytes; + } + + std::size_t seek(std::ptrdiff_t offset, bool relative) final { + if ( offset < 0 ) { + const std::size_t uoffset = math::abs_to_unsigned(offset); + if ( !relative || uoffset > pos_ ) { + throw bad_stream_operation(); + } else { + pos_ -= uoffset; + return pos_; + } + } else { + const std::size_t uoffset = math::abs_to_unsigned(offset); + const std::size_t available_bytes = relative + ? data_.size() - pos_ + : data_.size(); + if ( uoffset > available_bytes ) { + throw bad_stream_operation(); + } else { + pos_ = uoffset + (data_.size() - available_bytes); + return pos_; + } + } + } + + std::size_t tell() const final { + return pos_; + } + + std::size_t length() const final { + return data_.size(); + } + private: + buffer data_; + std::size_t pos_ = 0; + }; +} + +namespace e2d +{ + input_stream_uptr make_memory_stream(buffer data) noexcept { + try { + return std::make_unique(std::move(data)); + } catch (...) { + return nullptr; + } + } +} + +namespace e2d { namespace streams +{ + bool try_read_tail(buffer& dst, const input_stream_uptr& stream) noexcept { + try { + if ( stream ) { + buffer tail(stream->length() - stream->tell()); + if ( tail.size() == stream->read(tail.data(), tail.size()) ) { + dst.swap(tail); + return true; + } + } + } catch (...) { + // nothing + } + return false; + } + + bool try_write_tail(const buffer& src, const output_stream_uptr& stream) noexcept { + try { + return + stream && + src.size() == stream->write(src.data(), src.size()); + } catch (...) { + return false; + } + } +}} diff --git a/sources/enduro2d/utils/strings.cpp b/sources/enduro2d/utils/strings.cpp new file mode 100644 index 00000000..794e7689 --- /dev/null +++ b/sources/enduro2d/utils/strings.cpp @@ -0,0 +1,310 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include + +#include <3rdparty/utfcpp/utf8.h> + +namespace +{ + using namespace e2d; + + // + // utf8_to_X + // + + str16 utf8_to_16(str_view src) { + str16 dst; + dst.reserve(src.size()); + utf8::utf8to16(src.cbegin(), src.cend(), std::back_inserter(dst)); + dst.shrink_to_fit(); + return dst; + } + + str32 utf8_to_32(str_view src) { + str32 dst; + dst.reserve(src.size()); + utf8::utf8to32(src.cbegin(), src.cend(), std::back_inserter(dst)); + dst.shrink_to_fit(); + return dst; + } + + template < typename WChar = wchar_t > + std::enable_if_t + utf8_to_wide(str_view src) { + wstr dst; + dst.reserve(src.size()); + utf8::utf8to16(src.cbegin(), src.cend(), std::back_inserter(dst)); + dst.shrink_to_fit(); + return dst; + } + + template < typename WChar = wchar_t > + std::enable_if_t + utf8_to_wide(str_view src) { + wstr dst; + dst.reserve(src.size()); + utf8::utf8to32(src.cbegin(), src.cend(), std::back_inserter(dst)); + dst.shrink_to_fit(); + return dst; + } + + // + // utf16_to_X + // + + template < typename Char > + std::enable_if_t + utf16_to_8(basic_string_view src) { + str dst; + dst.reserve(src.size() * 2); + utf8::utf16to8(src.cbegin(), src.cend(), std::back_inserter(dst)); + dst.shrink_to_fit(); + return dst; + } + + template < typename Char > + std::enable_if_t + utf16_to_32(basic_string_view src) { + return utf8_to_32(utf16_to_8(src)); + } + + template < typename Char > + std::enable_if_t + utf16_to_wide(basic_string_view src) { + return utf8_to_wide(utf16_to_8(src)); + } + + // + // utf32_to_X + // + + template < typename Char > + std::enable_if_t + utf32_to_8(basic_string_view src) { + str dst; + dst.reserve(src.size() * 4); + utf8::utf32to8(src.cbegin(), src.cend(), std::back_inserter(dst)); + dst.shrink_to_fit(); + return dst; + } + + template < typename Char > + std::enable_if_t + utf32_to_16(basic_string_view src) { + return utf8_to_16(utf32_to_8(src)); + } + + template < typename Char > + std::enable_if_t + utf32_to_wide(basic_string_view src) { + return utf8_to_wide(utf32_to_8(src)); + } + + // + // wide_to_X + // + + template < typename Char > + std::enable_if_t + wide_to_utf8(basic_string_view src) { + return utf16_to_8(src); + } + + template < typename Char > + std::enable_if_t + wide_to_utf8(basic_string_view src) { + return utf32_to_8(src); + } + + template < typename Char > + std::enable_if_t + wide_to_utf16(basic_string_view src) { + return src.empty() + ? str16() + : str16(src.cbegin(), src.cend()); + } + + template < typename Char > + std::enable_if_t + wide_to_utf16(basic_string_view src) { + return utf32_to_16(src); + } + + template < typename Char > + std::enable_if_t + wide_to_utf32(basic_string_view src) { + return utf16_to_32(src); + } + + template < typename Char > + std::enable_if_t + wide_to_utf32(basic_string_view src) { + return src.empty() + ? str32() + : str32(src.cbegin(), src.cend()); + } +} + +namespace e2d +{ + // + // make_utf8 + // + + str make_utf8(str_view src) { + return src.empty() + ? str() + : str(src.cbegin(), src.cend()); + } + + str make_utf8(wstr_view src) { + return wide_to_utf8(src); + } + + str make_utf8(str16_view src) { + return utf16_to_8(src); + } + + str make_utf8(str32_view src) { + return utf32_to_8(src); + } + + // + // make_wide + // + + wstr make_wide(str_view src) { + return utf8_to_wide(src); + } + + wstr make_wide(wstr_view src) { + return src.empty() + ? wstr() + : wstr(src.cbegin(), src.cend()); + } + + wstr make_wide(str16_view src) { + return utf16_to_wide(src); + } + + wstr make_wide(str32_view src) { + return utf32_to_wide(src); + } + + // + // make_utf16 + // + + str16 make_utf16(str_view src) { + return utf8_to_16(src); + } + + str16 make_utf16(wstr_view src) { + return wide_to_utf16(src); + } + + str16 make_utf16(str16_view src) { + return src.empty() + ? str16() + : str16(src.cbegin(), src.cend()); + } + + str16 make_utf16(str32_view src) { + return utf32_to_16(src); + } + + // + // make_utf32 + // + + str32 make_utf32(str_view src) { + return utf8_to_32(src); + } + + str32 make_utf32(wstr_view src) { + return wide_to_utf32(src); + } + + str32 make_utf32(str16_view src) { + return utf16_to_32(src); + } + + str32 make_utf32(str32_view src) { + return src.empty() + ? str32() + : str32(src.cbegin(), src.cend()); + } +} + +namespace e2d { namespace strings +{ + namespace impl + { + // Inspired by: + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + + using utf8_iter = utf8::iterator; + + static bool wildcard_match_impl( + utf8_iter string_i, utf8_iter string_e, + utf8_iter pattern_i, utf8_iter pattern_e) + { + while ( pattern_i != pattern_e && *pattern_i != '*' ) { + if ( string_i == string_e ) { + break; + } + if ( *pattern_i != *string_i && *pattern_i != '?' ) { + return false; + } + ++string_i; + ++pattern_i; + } + if ( pattern_i == pattern_e ) { + return string_i == string_e; + } + utf8_iter s_mark = string_e; + utf8_iter p_mark = pattern_e; + while ( string_i != string_e ) { + if ( pattern_i != pattern_e ) { + if ( *pattern_i == '*' ) { + if ( ++pattern_i == pattern_e ) { + return true; + } + s_mark = string_i; + p_mark = pattern_i; + ++s_mark; + continue; + } else if ( *pattern_i == *string_i || *pattern_i == '?' ) { + ++string_i; + ++pattern_i; + continue; + } + } + string_i = s_mark; + pattern_i = p_mark; + if ( s_mark != string_e ) { + ++s_mark; + } + } + while ( pattern_i != pattern_e && *pattern_i == '*' ) { + ++pattern_i; + } + return pattern_i == pattern_e; + } + } + + bool wildcard_match(str_view string, str_view pattern) { + using namespace impl; + str_view::const_iterator si = string.cbegin(); + str_view::const_iterator se = string.cend(); + str_view::const_iterator pi = pattern.cbegin(); + str_view::const_iterator pe = pattern.cend(); + return wildcard_match_impl( + utf8_iter(si, si, se), utf8_iter(se, si, se), + utf8_iter(pi, pi, pe), utf8_iter(pe, pi, pe)); + } +}} diff --git a/untests/CMakeLists.txt b/untests/CMakeLists.txt index 15778c68..664d6976 100644 --- a/untests/CMakeLists.txt +++ b/untests/CMakeLists.txt @@ -2,12 +2,13 @@ function(add_e2d_tests NAME) set(TESTS_NAME untests_${NAME}) file(GLOB ${TESTS_NAME}_sources sources/*.* - sources/${TESTS_NAME}/*.*) + sources/${TESTS_NAME}/*.*) set(TESTS_SOURCES ${${TESTS_NAME}_sources}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${TESTS_SOURCES}) add_executable(${TESTS_NAME} ${TESTS_SOURCES}) target_include_directories(${TESTS_NAME} PRIVATE "../headers") target_link_libraries(${TESTS_NAME} enduro2d) + target_link_libraries(${TESTS_NAME} "${CMAKE_THREAD_LIBS_INIT}") add_test(${TESTS_NAME} ${TESTS_NAME}) endfunction(add_e2d_tests) diff --git a/untests/sources/common.hpp b/untests/sources/common.hpp index ed8e6c5e..183b9e9e 100644 --- a/untests/sources/common.hpp +++ b/untests/sources/common.hpp @@ -8,3 +8,32 @@ #include "../catch/catch.hpp" #include + +namespace e2d_untests +{ + using namespace e2d; + + template < typename TimeTag > + class verbose_profiler : noncopyable { + public: + verbose_profiler(const str& desc) + : desc_(desc) { + begin_ = time::now(); + } + + template < typename T > + void done(const T& result) const { + const auto end = time::now(); + std::printf( + "result: %s, time: %s, desc: %s\n", + std::to_string(result).c_str(), + std::to_string((end - begin_).value).c_str(), + desc_.c_str()); + } + private: + str desc_; + unit begin_; + }; + using verbose_profiler_us = verbose_profiler; + using verbose_profiler_ms = verbose_profiler; +} diff --git a/untests/sources/untests_base/stdex.cpp b/untests/sources/untests_base/stdex.cpp new file mode 100644 index 00000000..d45243c7 --- /dev/null +++ b/untests/sources/untests_base/stdex.cpp @@ -0,0 +1,171 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_base.hpp" +using namespace e2d; + +TEST_CASE("stdex") { + { + basic_string_view v0; + REQUIRE(v0.data() == nullptr); + REQUIRE(v0.size() == 0); + REQUIRE(v0.length() == 0); + + const char* s = "hello"; + + basic_string_view v1(s); + REQUIRE(v1.data() == s); + REQUIRE(v1.size() == 5); + REQUIRE(v1.length() == 5); + + basic_string_view v2(s, 4); + REQUIRE(v2.data() == s); + REQUIRE(v2.size() == 4); + REQUIRE(v2.length() == 4); + } + { + const char* s0 = "hell"; + const char* s1 = "world"; + basic_string_view v0(s0); + basic_string_view v1(s1); + v0.swap(v1); + REQUIRE(v0.data() == s1); + REQUIRE(v0.size() == 5); + REQUIRE(v1.data() == s0); + REQUIRE(v1.size() == 4); + } + { + const char* s = "hello"; + basic_string_view v0(s); + REQUIRE(v0.data() == s); + REQUIRE(v0.size() == 5); + REQUIRE(v0.length() == 5); + basic_string_view v1(v0); + REQUIRE(v1.data() == s); + REQUIRE(v1.size() == 5); + REQUIRE(v1.length() == 5); + basic_string_view v2; + v2 = v1; + REQUIRE(v2.data() == s); + REQUIRE(v2.size() == 5); + REQUIRE(v2.length() == 5); + } + { + const char* s = "hello"; + basic_string_view v0(s); + + REQUIRE(v0.begin() == s); + REQUIRE(v0.cbegin() == s); + REQUIRE(v0.rend().base() == s); + REQUIRE(v0.crend().base() == s); + + REQUIRE(v0.rbegin().base() == s + 5); + REQUIRE(v0.crbegin().base() == s + 5); + REQUIRE(v0.end() == s + 5); + REQUIRE(v0.cend() == s + 5); + } + { + basic_string_view v0; + REQUIRE(v0.empty()); + v0 = basic_string_view("world"); + REQUIRE_FALSE(v0.empty()); + } + { + basic_string_view v0("world"); + REQUIRE(v0[0] == 'w'); + REQUIRE(v0[4] == 'd'); + REQUIRE(v0.at(1) == 'o'); + REQUIRE(v0.at(3) == 'l'); + REQUIRE_THROWS_AS(v0.at(5), std::out_of_range); + REQUIRE(v0.front() == 'w'); + REQUIRE(v0.back() == 'd'); + } + { + const char* s0 = "world"; + basic_string_view v0(s0); + v0.remove_prefix(1); + REQUIRE(v0.data() == s0 + 1); + REQUIRE(v0.size() == 4); + v0.remove_suffix(2); + REQUIRE(v0.data() == s0 + 1); + REQUIRE(v0.size() == 2); + } + { + char buf[6] = {0}; + basic_string_view("hello").copy(buf, 5); + REQUIRE(std::strcpy(buf, "hello")); + basic_string_view("world").copy(buf, 3, 1); + REQUIRE(std::memcmp(buf, "orllo\0", 6) == 0); + basic_string_view("hell").copy(buf, 0); + REQUIRE(std::memcmp(buf, "orllo\0", 6) == 0); + + basic_string_view().copy(buf, 0); + REQUIRE(std::memcmp(buf, "orllo\0", 6) == 0); + basic_string_view().copy(buf, 10); + REQUIRE(std::memcmp(buf, "orllo\0", 6) == 0); + basic_string_view("hell").copy(buf, 10, 4); + REQUIRE(std::memcmp(buf, "orllo\0", 6) == 0); + + REQUIRE_THROWS_AS(basic_string_view("hell").copy(buf, 1, 5), std::out_of_range); + REQUIRE(std::memcmp(buf, "orllo\0", 6) == 0); + } + { + const char* s0 = "world"; + basic_string_view v0(s0); + auto v1 = v0.substr(1, 3); + REQUIRE(v1.data() == v0.data() + 1); + REQUIRE(v1.size() == 3); + v1 = basic_string_view(s0).substr(3); + REQUIRE(v1.data() == v0.data() + 3); + REQUIRE(v1.size() == 2); + v1 = basic_string_view(s0).substr(5); + REQUIRE(v1.data() == v0.data() + 5); + REQUIRE(v1.size() == 0); + REQUIRE_THROWS_AS(basic_string_view("hello").substr(6), std::out_of_range); + } + { + using sw = basic_string_view; + + REQUIRE(sw("hello") == sw("hello")); + REQUIRE(sw("hello") != sw("world")); + REQUIRE_FALSE(sw("hello") != sw("hello")); + REQUIRE_FALSE(sw("hello") == sw("world")); + + REQUIRE_FALSE(sw("hello") == sw("hell")); + REQUIRE_FALSE(sw("hell") == sw("hello")); + + REQUIRE(sw("hello") != sw("hell")); + REQUIRE(sw("hell") != sw("hello")); + + REQUIRE_FALSE(sw("abc") < sw("abc")); + REQUIRE(sw("abc") < sw("abcd")); + REQUIRE_FALSE(sw("abcd") < sw("abc")); + REQUIRE(sw("abc") < sw("acc")); + REQUIRE_FALSE(sw("acc") < sw("abc")); + + REQUIRE(sw("world") == "world"); + REQUIRE(("world" == sw("world"))); + REQUIRE_FALSE(sw("world") != "world"); + REQUIRE_FALSE(("world" != sw("world"))); + + REQUIRE(sw("hell") < "hello"); + REQUIRE_FALSE(sw("hello") < "hell"); + + REQUIRE_FALSE("hello" < sw("hell")); + REQUIRE("hell" < sw("hello")); + + REQUIRE(sw("world") == str("world")); + REQUIRE((str("world") == sw("world"))); + REQUIRE_FALSE(sw("world") != str("world")); + REQUIRE_FALSE((str("world") != sw("world"))); + + REQUIRE(sw("hell") < str("hello")); + REQUIRE_FALSE(sw("hello") < str("hell")); + + REQUIRE_FALSE(str("hello") < sw("hell")); + REQUIRE(str("hell") < sw("hello")); + } +} diff --git a/untests/sources/untests_utils/buffer.cpp b/untests/sources/untests_utils/buffer.cpp new file mode 100644 index 00000000..d2b172c1 --- /dev/null +++ b/untests/sources/untests_utils/buffer.cpp @@ -0,0 +1,171 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("buffer") { + { + REQUIRE(buffer().size() == 0); + REQUIRE(buffer().data() == nullptr); + REQUIRE(buffer(10).size() == 10); + REQUIRE(buffer(10).data() != nullptr); + } + { + buffer b("hello", 5); + REQUIRE(b.size() == 5); + REQUIRE(std::memcmp(b.data(), "hello", 5) == 0); + b.clear(); + REQUIRE(b.size() == 0); + REQUIRE(b.data() == nullptr); + } + { + const buffer b0("hello", 5); + buffer b1(b0); + REQUIRE(b0.size() == 5); + REQUIRE(std::memcmp(b0.data(), "hello", 5) == 0); + REQUIRE(b1.size() == 5); + REQUIRE(std::memcmp(b1.data(), "hello", 5) == 0); + REQUIRE(b0.data() != b1.data()); + const u8* b1_data = b1.data(); + const buffer b2("world", 5); + b1 = b2; + REQUIRE(b1_data == b1.data()); + REQUIRE(b1.size() == 5); + REQUIRE(std::memcmp(b1.data(), "world", 5) == 0); + } + { + buffer b0("hello", 5); + buffer b1(std::move(b0)); + REQUIRE(b0.size() == 0); + REQUIRE(b0.data() == nullptr); + REQUIRE(b1.size() == 5); + REQUIRE(std::memcmp(b1.data(), "hello", 5) == 0); + b1 = buffer("hello, world", 12); + REQUIRE(b1.size() == 12); + REQUIRE(std::memcmp(b1.data(), "hello, world", 12) == 0); + } + { + buffer b0("hello", 5); + buffer b1 = b0; + REQUIRE(b0 == b1); + REQUIRE_FALSE(b0 == buffer("world", 5)); + REQUIRE_FALSE(b0 != b1); + REQUIRE(b0 != buffer("world", 5)); + REQUIRE_FALSE(b0 == buffer()); + REQUIRE(b0 != buffer()); + REQUIRE(buffer() == buffer()); + REQUIRE_FALSE(buffer() != buffer()); + } + { + buffer b0("hello", 5); + buffer b1 = b0; + b0.clear(); + REQUIRE(b1.size() == 5); + REQUIRE(std::memcmp(b1.data(), "hello", 5) == 0); + } + { + buffer b0; + REQUIRE((&b0 == &b0.fill('a') && b0.size() == 0)); + b0.resize(5); + REQUIRE_FALSE(std::memcmp(b0.data(), "aaaaa", 5) == 0); + REQUIRE((&b0 == &b0.fill('a') && b0.size() == 5)); + REQUIRE(std::memcmp(b0.data(), "aaaaa", 5) == 0); + REQUIRE((&b0 == &b0.fill('b') && b0.size() == 5)); + REQUIRE(std::memcmp(b0.data(), "bbbbb", 5) == 0); + b0.resize(7); + REQUIRE_FALSE(std::memcmp(b0.data(), "ccccccc", 7) == 0); + b0.fill('c'); + REQUIRE(std::memcmp(b0.data(), "ccccccc", 7) == 0); + b0.resize(7); + REQUIRE(std::memcmp(b0.data(), "ccccccc", 7) == 0); + } + { + buffer b0("hello", 5); + buffer b1; + REQUIRE(b1.empty()); + REQUIRE(&b1 == &b1.assign(std::move(b0))); + REQUIRE_FALSE(b1.empty()); + REQUIRE((b0.empty() && b0.data() == nullptr && b0.size() == 0)); + REQUIRE((std::memcmp(b1.data(), "hello", 5) == 0 && b1.size() == 5)); + REQUIRE(&b1 == &b1.resize(4)); + REQUIRE((std::memcmp(b1.data(), "hell", 4) == 0 && b1.size() == 4)); + REQUIRE(&b1 == &b1.assign("world", 5)); + REQUIRE((std::memcmp(b1.data(), "world", 5) == 0 && b1.size() == 5)); + REQUIRE(&b1 == &b1.assign("hello", 5)); + REQUIRE((std::memcmp(b1.data(), "hello", 5) == 0 && b1.size() == 5)); + REQUIRE(&b1 == &b1.resize(0)); + REQUIRE((b1.data() == nullptr && b1.size() == 0)); + } + { + buffer b1("hello", 5); + b1.assign(b1.data(), 3); + REQUIRE(b1.size() == 3); + REQUIRE(std::memcmp(b1.data(), "hel", 3) == 0); + } + { + REQUIRE_FALSE(buffer() < buffer()); + REQUIRE_FALSE(buffer("aa",2) < buffer("aa",2)); + + REQUIRE(buffer() < buffer("a",1)); + REQUIRE_FALSE(buffer("a",1) < buffer()); + + REQUIRE(buffer("aa",2) < buffer("ab",2)); + REQUIRE_FALSE(buffer("ab",2) < buffer("aa",2)); + + REQUIRE(buffer("aa",2) < buffer("aaa",3)); + REQUIRE(buffer("aa",2) < buffer("abb",3)); + REQUIRE_FALSE(buffer("aaa",3) < buffer("aa",2)); + REQUIRE_FALSE(buffer("abb",3) < buffer("aa",2)); + } + { + REQUIRE(buffer() == buffer()); + REQUIRE_FALSE(buffer() != buffer()); + + REQUIRE_FALSE(buffer() == buffer("hello",5)); + REQUIRE_FALSE(buffer("hello",5) == buffer()); + REQUIRE(buffer("hello",5) == buffer("hello",5)); + REQUIRE_FALSE(buffer("hello",5) != buffer("hello",5)); + + REQUIRE(buffer("hello",5) != buffer("world",5)); + REQUIRE_FALSE(buffer("hello",5) == buffer("world",5)); + + REQUIRE(buffer("hello",5) != buffer("hello, world",12)); + REQUIRE_FALSE(buffer("hello",5) == buffer("hello, world",12)); + } + { + buffer b0("hello", 5); + buffer b1("hello world", 11); + b0.swap(b1); + REQUIRE((std::memcmp(b1.data(), "hello", 5) == 0 && b1.size() == 5)); + REQUIRE((std::memcmp(b0.data(), "hello world", 11) == 0 && b0.size() == 11)); + swap(b0, b1); + REQUIRE((std::memcmp(b0.data(), "hello", 5) == 0 && b0.size() == 5)); + REQUIRE((std::memcmp(b1.data(), "hello world", 11) == 0 && b1.size() == 11)); + } + { + const std::size_t msize = std::numeric_limits::max(); + REQUIRE_THROWS_AS(buffer(msize), std::bad_alloc); + REQUIRE_THROWS_AS(buffer(nullptr, msize), std::bad_alloc); + { + buffer b0; + REQUIRE_THROWS_AS(b0.resize(msize), std::bad_alloc); + REQUIRE((b0.data() == nullptr && b0.size() == 0)); + buffer b1("hello", 5); + REQUIRE_THROWS_AS(b1.resize(msize), std::bad_alloc); + REQUIRE((std::memcmp(b1.data(), "hello", 5) == 0 && b1.size() == 5)); + } + { + buffer b0; + REQUIRE_THROWS_AS(b0.assign(nullptr, msize), std::bad_alloc); + REQUIRE((b0.data() == nullptr && b0.size() == 0)); + + buffer b1("hello", 5); + REQUIRE_THROWS_AS(b1.assign(nullptr, msize), std::bad_alloc); + REQUIRE((std::memcmp(b1.data(), "hello", 5) == 0 && b1.size() == 5)); + } + } +} diff --git a/untests/sources/untests_utils/color.cpp b/untests/sources/untests_utils/color.cpp new file mode 100644 index 00000000..6afeb5d9 --- /dev/null +++ b/untests/sources/untests_utils/color.cpp @@ -0,0 +1,150 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("color") { + { + REQUIRE(color() == color::white()); + REQUIRE(color(color::black()) == color::black()); + REQUIRE(color(color32::yellow()) == color::yellow()); + color c = color::white(); + c = color::red(); + REQUIRE(c == color::red()); + REQUIRE((c = color(0.5f, 0.6f, 0.7f, 0.8f)) == color(0.5f, 0.6f, 0.7f, 0.8f)); + REQUIRE(math::approximately(c[0], 0.5f)); + REQUIRE(math::approximately(c[1], 0.6f)); + REQUIRE(math::approximately(c[2], 0.7f)); + REQUIRE(math::approximately(c[3], 0.8f)); + c[0] = 0.1f; + c[1] = 0.2f; + c[2] = 0.3f; + c[3] = 0.4f; + REQUIRE(math::approximately(c[0], 0.1f)); + REQUIRE(math::approximately(c[1], 0.2f)); + REQUIRE(math::approximately(c[2], 0.3f)); + REQUIRE(math::approximately(c[3], 0.4f)); + } + { + color c = color(0.1f,0.2f,0.3f,0.4f); + c += 0.1f; + REQUIRE(c == color(0.2f,0.3f,0.4f,0.5f)); + c -= -0.3f; + REQUIRE(c == color(0.5f,0.6f,0.7f,0.8f)); + c *= 2.f; + REQUIRE(c == color(1.0f,1.2f,1.4f,1.6f)); + c /= 2.f; + REQUIRE(c == color(0.5f,0.6f,0.7f,0.8f)); + c += color(0.1f,0.2f,0.3f,0.4f); + REQUIRE(c == color(0.6f,0.8f,1.0f,1.2f)); + c -= color(0.1f,0.2f,0.3f,0.4f); + REQUIRE(c == color(0.5f,0.6f,0.7f,0.8f)); + c *= color(0.1f,0.2f,0.3f,0.4f); + REQUIRE(c == color(0.05f,0.12f,0.21f,0.32f)); + c /= color(0.1f,0.2f,0.3f,0.4f); + REQUIRE(c == color(0.5f,0.6f,0.7f,0.8f)); + } + { + REQUIRE(color(0.1f,0.2f,0.3f,0.4f) + 0.1f == color(0.2f,0.3f,0.4f,0.5f)); + REQUIRE(color(0.1f,0.2f,0.3f,0.4f) - 0.1f == color(0.0f,0.1f,0.2f,0.3f)); + REQUIRE(color(0.1f,0.2f,0.3f,0.4f) * 2.f == color(0.2f,0.4f,0.6f,0.8f)); + REQUIRE(color(0.1f,0.2f,0.3f,0.4f) / 2.f == color(0.05f,0.1f,0.15f,0.2f)); + + REQUIRE(0.1f + color(0.1f,0.2f,0.3f,0.4f) == color(0.2f,0.3f,0.4f,0.5f)); + REQUIRE(0.6f - color(0.1f,0.2f,0.3f,0.4f) == color(0.5f,0.4f,0.3f,0.2f)); + REQUIRE(2.0f * color(0.1f,0.2f,0.3f,0.4f) == color(0.2f,0.4f,0.6f,0.8f)); + REQUIRE(0.1f / color(0.1f,0.2f,0.4f,0.5f) == color(1.f,0.5f,0.25f,0.2f)); + + REQUIRE(color(0.1f,0.2f,0.3f,0.4f) + color(0.2f,0.3f,0.4f,0.5f) == color(0.3f,0.5f,0.7f,0.9f)); + REQUIRE(color(0.3f,0.5f,0.7f,0.9f) - color(0.2f,0.3f,0.4f,0.5f) == color(0.1f,0.2f,0.3f,0.4f)); + REQUIRE(color(0.1f,0.2f,0.3f,0.4f) * color(0.2f,0.3f,0.4f,0.5f) == color(0.02f,0.06f,0.12f,0.2f)); + REQUIRE(color(0.1f,0.2f,0.3f,0.4f) / color(0.2f,0.1f,0.6f,0.5f) == color(0.5f,2.0f,0.5f,0.8f)); + } + { + REQUIRE(math::approximately(color::white(), color(1.f,1.f,1.f,1.000001f))); + REQUIRE(math::approximately(color::white(), color(1.f,0.999999f,1.f,1.000001f))); + REQUIRE(math::approximately(color::white(), color(0.9f,1.f,1.1f,1.f), 0.15f)); + + color c0(1,2,3,4); + REQUIRE(math::clamped(c0, color(0,0,0,0), color(0,1,2,3)) == color(0,1,2,3)); + REQUIRE(c0 == color(1,2,3,4)); + REQUIRE(math::clamped(c0, color(2,3,4,5), color(9,9,9,9)) == color(2,3,4,5)); + REQUIRE(c0 == color(1,2,3,4)); + + REQUIRE(math::clamped(c0, color(3,4,5,6), color(6,7,8,9)) == color(3,4,5,6)); + REQUIRE(c0 == color(1,2,3,4)); + REQUIRE(math::clamped(c0, color(2,3,4,5), color(6,7,8,9)) == color(2,3,4,5)); + REQUIRE(c0 == color(1,2,3,4)); + } + { + REQUIRE(math::approximately(math::minimum(color(1,2,3,4)), 1.f)); + REQUIRE(math::approximately(math::minimum(color(2,1,3,4)), 1.f)); + REQUIRE(math::approximately(math::minimum(color(4,3,2,1)), 1.f)); + + REQUIRE(math::approximately(math::maximum(color(1,2,3,4)), 4.f)); + REQUIRE(math::approximately(math::maximum(color(2,1,3,4)), 4.f)); + REQUIRE(math::approximately(math::maximum(color(4,3,2,1)), 4.f)); + } + { + color c0(1,2,3,4); + REQUIRE(math::minimized(c0, color(0,1,2,3)) == color(0,1,2,3)); + REQUIRE(c0 == color(1,2,3,4)); + REQUIRE(math::maximized(c0, color(2,3,4,5)) == color(2,3,4,5)); + REQUIRE(c0 == color(1,2,3,4)); + + REQUIRE(math::maximized(c0, color(3,4,5,6)) == color(3,4,5,6)); + REQUIRE(c0 == color(1,2,3,4)); + REQUIRE(math::minimized(c0, color(2,3,4,5)) == color(1,2,3,4)); + REQUIRE(c0 == color(1,2,3,4)); + } + { + color v0(3,4,5,6); + REQUIRE(math::clamped(v0, color(0,0,0,0), color(2,3,4,5)) == color(2,3,4,5)); + REQUIRE(v0 == color(3,4,5,6)); + REQUIRE(math::clamped(v0, color(3,4,5,6), color(3,4,5,6)) == color(3,4,5,6)); + REQUIRE(v0 == color(3,4,5,6)); + + REQUIRE(math::clamped(v0, color(0,0,0,0), color(0,0,0,0)) == color(0,0,0,0)); + REQUIRE(math::clamped(v0, color(0,0,0,0), color(3,2,1,0)) == color(3,2,1,0)); + REQUIRE(math::clamped(v0, color(0,0,0,0), color(4,3,2,1)) == color(3,3,2,1)); + + REQUIRE(math::clamped(v0, color(4,5,6,7), color(9,9,9,9)) == color(4,5,6,7)); + REQUIRE(math::clamped(v0, color(6,5,4,3), color(9,9,9,9)) == color(6,5,5,6)); + REQUIRE(math::clamped(v0, color(7,6,5,4), color(9,9,9,9)) == color(7,6,5,6)); + + REQUIRE(math::saturated(color(-1,-2,-3,-4)) == color(0,0,0,0)); + REQUIRE(math::saturated(color( 2, 3, 4, 5)) == color(1,1,1,1)); + REQUIRE(math::saturated(color(-1, 3, 4, 5)) == color(0,1,1,1)); + + REQUIRE(math::saturated(color(2,0.6f,2,0.7f)) == color(1,0.6f,1,0.7f)); + REQUIRE(math::saturated(color(0.6f,-2,2,2)) == color(0.6f,0,1,1)); + } + { + { + REQUIRE_FALSE(math::contains_nan(color(0,1,2,3))); + REQUIRE_FALSE(math::contains_nan(color(0.f,1.f,2.f,3.f))); + REQUIRE(math::contains_nan(color(0.f,1.f,2.f,std::numeric_limits::quiet_NaN()))); + REQUIRE(math::contains_nan(color(0.f,1.f,std::numeric_limits::quiet_NaN(),2.f))); + REQUIRE(math::contains_nan(color(std::numeric_limits::infinity(),1.f,2.f,3.f))); + REQUIRE(math::contains_nan(color(1.f,std::numeric_limits::infinity(),2.f,3.f))); + + REQUIRE_FALSE(math::contains(color(0.1f,0.2f,0.3f,0.4f), 0.f)); + REQUIRE(math::contains(color(0.1f,0.2f,0.3f,0.4f), 0.f, 0.45f)); + REQUIRE(math::contains(color(0,1.f,2.f,3.f), 0.f)); + REQUIRE(math::contains(color(1.f,0,2.f,3.f), 0.f)); + REQUIRE(math::contains(color(1.f,2.f,0,3.f), 0.f)); + REQUIRE(math::contains(color(1.f,2.f,3.f,0), 0.f)); + REQUIRE(math::contains(color(0,0,0,0), 0.f)); + } + } + { + REQUIRE(colors::pack_color(color(color32(1,2,3,4))) == 0x04010203); + REQUIRE(colors::pack_color(color(color32(0x12,0x34,0x56,0x78))) == 0x78123456); + REQUIRE(colors::unpack_color(0x04010203) == color(color32(1,2,3,4))); + REQUIRE(colors::unpack_color(0x78123456) == color(color32(0x12,0x34,0x56,0x78))); + } +} diff --git a/untests/sources/untests_utils/color32.cpp b/untests/sources/untests_utils/color32.cpp new file mode 100644 index 00000000..499c6261 --- /dev/null +++ b/untests/sources/untests_utils/color32.cpp @@ -0,0 +1,136 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("color32") { + { + REQUIRE(color32() == color32::white()); + REQUIRE(color32(color32::black()) == color32::black()); + REQUIRE(color32(color::yellow()) == color32::yellow()); + REQUIRE(color32(color(0.9999f,0.9999f,0.9999f,0.9999f)) == color32::white()); + REQUIRE(color32(color(0.0001f,0.0001f,0.0001f,0.0001f)) == color32::clear()); + color32 c = color32::white(); + c = color32::red(); + REQUIRE(c == color32::red()); + REQUIRE((c = color32(10,20,30,40)) == color32(10,20,30,40)); + REQUIRE(c[0] == 10); + REQUIRE(c[1] == 20); + REQUIRE(c[2] == 30); + REQUIRE(c[3] == 40); + c[0] = u8(20); + c[1] = u8(30); + c[2] = u8(40); + c[3] = u8(50); + REQUIRE(c[0] == 20); + REQUIRE(c[1] == 30); + REQUIRE(c[2] == 40); + REQUIRE(c[3] == 50); + } + { + color32 c = color32(10,20,30,40); + c += 40; + REQUIRE(c == color32(50,60,70,80)); + c -= 10; + REQUIRE(c == color32(40,50,60,70)); + c *= 2; + REQUIRE(c == color32(80,100,120,140)); + c /= 2; + REQUIRE(c == color32(40,50,60,70)); + c += color32(10,20,30,40); + REQUIRE(c == color32(50,70,90,110)); + c -= color32(10,20,30,40); + REQUIRE(c == color32(40,50,60,70)); + c *= color32(2,3,4,2); + REQUIRE(c == color32(80,150,240,140)); + c /= color32(2,3,6,7); + REQUIRE(c == color32(40,50,40,20)); + } + { + REQUIRE(color32(10,20,30,40) + 10 == color32(20,30,40,50)); + REQUIRE(color32(10,20,30,40) - 10 == color32(0,10,20,30)); + REQUIRE(color32(10,20,30,40) * 2 == color32(20,40,60,80)); + REQUIRE(color32(10,20,30,40) / 2 == color32(5,10,15,20)); + + REQUIRE(10 + color32(10,20,30,40) == color32(20,30,40,50)); + REQUIRE(60 - color32(10,20,30,40) == color32(50,40,30,20)); + REQUIRE(2 * color32(10,20,30,40) == color32(20,40,60,80)); + REQUIRE(100 / color32(10,20,25,50) == color32(10,5,4,2)); + + REQUIRE(color32(10,20,30,40) + color32(20,30,40,50) == color32(30,50,70,90)); + REQUIRE(color32(10,20,30,40) - color32(1,2,3,4) == color32(9,18,27,36)); + REQUIRE(color32(10,20,30,40) * color32(3,4,2,5) == color32(30,80,60,200)); + REQUIRE(color32(80,150,180,140) / color32(2,3,6,7) == color32(40,50,30,20)); + } + { + REQUIRE(math::approximately(color32::white(), color32(255,255,255,255))); + REQUIRE(math::approximately(color32::white(), color32(255,255,255,255))); + REQUIRE(math::approximately(color32::white(), color32(253,255,255,254), 3)); + + color32 c0(1,2,3,4); + REQUIRE(math::clamped(c0, color32(0,0,0,0), color32(0,1,2,3)) == color32(0,1,2,3)); + REQUIRE(c0 == color32(1,2,3,4)); + REQUIRE(math::clamped(c0, color32(2,3,4,5), color32(9,9,9,9)) == color32(2,3,4,5)); + REQUIRE(c0 == color32(1,2,3,4)); + + REQUIRE(math::clamped(c0, color32(3,4,5,6), color32(6,7,8,9)) == color32(3,4,5,6)); + REQUIRE(c0 == color32(1,2,3,4)); + REQUIRE(math::clamped(c0, color32(2,3,4,5), color32(6,7,8,9)) == color32(2,3,4,5)); + REQUIRE(c0 == color32(1,2,3,4)); + } + { + REQUIRE(math::minimum(color32(1,2,3,4)) == 1); + REQUIRE(math::minimum(color32(2,1,3,4)) == 1); + REQUIRE(math::minimum(color32(4,3,2,1)) == 1); + + REQUIRE(math::maximum(color32(1,2,3,4)) == 4); + REQUIRE(math::maximum(color32(2,1,3,4)) == 4); + REQUIRE(math::maximum(color32(4,3,2,1)) == 4); + } + { + color32 v0(3,4,5,6); + REQUIRE(math::clamped(v0, color32(0,0,0,0), color32(2,3,4,5)) == color32(2,3,4,5)); + REQUIRE(v0 == color32(3,4,5,6)); + REQUIRE(math::clamped(v0, color32(3,4,5,6), color32(3,4,5,6)) == color32(3,4,5,6)); + REQUIRE(v0 == color32(3,4,5,6)); + + REQUIRE(math::clamped(v0, color32(0,0,0,0), color32(0,0,0,0)) == color32(0,0,0,0)); + REQUIRE(math::clamped(v0, color32(0,0,0,0), color32(3,2,1,0)) == color32(3,2,1,0)); + REQUIRE(math::clamped(v0, color32(0,0,0,0), color32(4,3,2,1)) == color32(3,3,2,1)); + + REQUIRE(math::clamped(v0, color32(4,5,6,7), color32(9,9,9,9)) == color32(4,5,6,7)); + REQUIRE(math::clamped(v0, color32(6,5,4,3), color32(9,9,9,9)) == color32(6,5,5,6)); + REQUIRE(math::clamped(v0, color32(7,6,5,4), color32(9,9,9,9)) == color32(7,6,5,6)); + } + { + color32 c0(1,2,3,4); + REQUIRE(math::minimized(c0, color32(0,1,2,3)) == color32(0,1,2,3)); + REQUIRE(c0 == color32(1,2,3,4)); + REQUIRE(math::maximized(c0, color32(2,3,4,5)) == color32(2,3,4,5)); + REQUIRE(c0 == color32(1,2,3,4)); + + REQUIRE(math::maximized(c0, color32(3,4,5,6)) == color32(3,4,5,6)); + REQUIRE(c0 == color32(1,2,3,4)); + REQUIRE(math::minimized(c0, color32(2,3,4,5)) == color32(1,2,3,4)); + REQUIRE(c0 == color32(1,2,3,4)); + } + { + REQUIRE_FALSE(math::contains(color32(1,1,1,1), 0)); + REQUIRE(math::contains(color32(1,1,1,1), 0, 1)); + REQUIRE(math::contains(color32(0,1,1,1), 0)); + REQUIRE(math::contains(color32(1,0,1,1), 0)); + REQUIRE(math::contains(color32(1,1,0,1), 0)); + REQUIRE(math::contains(color32(1,1,1,0), 0)); + REQUIRE(math::contains(color32(0,0,0,0), 0)); + } + { + REQUIRE(colors::pack_color32(color32(1,2,3,4)) == 0x04010203); + REQUIRE(colors::pack_color32(color32(0x12,0x34,0x56,0x78)) == 0x78123456); + REQUIRE(colors::unpack_color32(0x04010203) == color32(1,2,3,4)); + REQUIRE(colors::unpack_color32(0x78123456) == color32(0x12,0x34,0x56,0x78)); + } +} diff --git a/untests/sources/untests_utils/filesystem.cpp b/untests/sources/untests_utils/filesystem.cpp new file mode 100644 index 00000000..1fbea18c --- /dev/null +++ b/untests/sources/untests_utils/filesystem.cpp @@ -0,0 +1,457 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("filesystem") { + SECTION("path") { + { + const vector absolute_paths = { + "/", + "/usr", + "/usr/", + "/usr/local", + + "C:", + "C:\\", + "C:\\windows", + "C:\\windows\\", + "C:\\windows\\system32", + + "\\\\.\\COM1", + "\\\\?\\C:", + "\\\\?\\UNC", + "\\\\server", + }; + const vector relative_paths = { + "", + + "usr", + "usr/", + "usr/local", + + "windows", + "windows\\", + "windows\\system32", + }; + for ( const str& s : absolute_paths ) { + REQUIRE(path::is_absolute(s)); + REQUIRE_FALSE(path::is_relative(s)); + } + for ( const str& s : relative_paths ) { + REQUIRE(path::is_relative(s)); + REQUIRE_FALSE(path::is_absolute(s)); + } + } + { + // [lsh, rhs, result] + using t = std::tuple; + const vector combinations = { + t{"", "", ""}, + + t{".", ".", "./."}, + t{"..", "..", "../.."}, + + t{".", "..", "./.."}, + t{"..", ".", "../."}, + + t{"file", "", "file"}, + t{"", "file", "file"}, + + t{"folder", "file", "folder/file"}, + t{"folder/", "file", "folder/file"}, + t{"folder\\", "file", "folder\\file"}, + + t{"folder", "other/file", "folder/other/file"}, + t{"folder/", "other/file", "folder/other/file"}, + t{"folder\\", "other/file", "folder\\other/file"}, + + t{"folder", "/file", "/file"}, + t{"folder/", "\\file", "\\file"}, + t{"folder\\", "C:\\file", "C:\\file"}, + t{"folder\\", "C:/file", "C:/file"}, + }; + for ( const auto& combination : combinations ) { + str lhs, rhs, res; + std::tie(lhs, rhs, res) = combination; + REQUIRE(path::combine(lhs, rhs) == res); + } + } + { + // [path, stem, filename, extension] + using t = std::tuple; + const vector combinations = { + t{"", "", "", ""}, + + t{".", ".", ".", ""}, + t{"/.", ".", ".", ""}, + t{"\\.", ".", ".", ""}, + t{"folder.jpg/.", ".", ".", ""}, + t{"folder.jpg\\.", ".", ".", ""}, + + t{"..", "..", "..", ""}, + t{"/..", "..", "..", ""}, + t{"\\..", "..", "..", ""}, + t{"folder.jpg/..", "..", "..", ""}, + t{"folder.jpg\\..", "..", "..", ""}, + + t{"/", "", "", ""}, + t{"file.jpg/", "", "", ""}, + + t{"\\", "", "", ""}, + t{"file.jpg\\", "", "", ""}, + + t{"C:", "C:", "C:", ""}, + t{"C:/", "", "", ""}, + t{"C:\\", "", "", ""}, + + t{"file", "file", "file", ""}, + t{"file.png", "file", "file.png", ".png"}, + t{"file.jpg.png", "file.jpg", "file.jpg.png", ".png"}, + + t{"/file", "file", "file", ""}, + t{"/file.png", "file", "file.png", ".png"}, + t{"/file.jpg.png", "file.jpg", "file.jpg.png", ".png"}, + + t{"\\file", "file", "file", ""}, + t{"\\file.png", "file", "file.png", ".png"}, + t{"\\file.jpg.png", "file.jpg", "file.jpg.png", ".png"}, + + t{"folder.jpg/file", "file", "file", ""}, + t{"folder.jpg/file.png", "file", "file.png", ".png"}, + t{"folder.jpg/file.jpg.png", "file.jpg", "file.jpg.png", ".png"}, + + t{"/folder.jpg/file", "file", "file", ""}, + t{"/folder.jpg/file.png", "file", "file.png", ".png"}, + t{"/folder.jpg/file.jpg.png", "file.jpg", "file.jpg.png", ".png"}, + + t{"\\folder.jpg\\file", "file", "file", ""}, + t{"\\folder.jpg\\file.png", "file", "file.png", ".png"}, + t{"\\folder.jpg/file.jpg.png", "file.jpg", "file.jpg.png", ".png"}, + }; + for ( const auto& combination : combinations ) { + const str path = std::get<0>(combination); + REQUIRE(path::stem(path) == std::get<1>(combination)); + REQUIRE(path::filename(path) == std::get<2>(combination)); + REQUIRE(path::extension(path) == std::get<3>(combination)); + } + } + { + // [path, remove_filename, remove_extension] + using t = std::tuple; + const vector combinations = { + t{"", "", ""}, + t{"/", "/", "/"}, + t{"\\", "\\", "\\"}, + + t{".", "", "."}, + t{"/.", "/", "/."}, + t{"\\.", "\\", "\\."}, + + t{"..", "", ".."}, + t{"/..", "/", "/.."}, + t{"\\..", "\\", "\\.."}, + + t{"usr", "", "usr"}, + t{"usr.", "", "usr"}, + t{"usr.png", "", "usr"}, + t{"usr.jpg.png", "", "usr.jpg"}, + + t{"/usr", "/", "/usr"}, + t{"/usr.", "/", "/usr"}, + t{"/usr.png", "/", "/usr"}, + t{"/usr.jpg.png", "/", "/usr.jpg"}, + + t{"\\usr", "\\", "\\usr"}, + t{"\\usr.", "\\", "\\usr"}, + t{"\\usr.png", "\\", "\\usr"}, + t{"\\usr.jpg.png", "\\", "\\usr.jpg"}, + + t{"folder.jpg/usr", "folder.jpg/", "folder.jpg/usr"}, + t{"folder.jpg/usr.", "folder.jpg/", "folder.jpg/usr"}, + t{"folder.jpg/usr.png", "folder.jpg/", "folder.jpg/usr"}, + t{"folder.jpg/usr.jpg.png", "folder.jpg/", "folder.jpg/usr.jpg"}, + + t{"/folder.jpg/usr", "/folder.jpg/", "/folder.jpg/usr"}, + t{"/folder.jpg/usr.", "/folder.jpg/", "/folder.jpg/usr"}, + t{"/folder.jpg/usr.png", "/folder.jpg/", "/folder.jpg/usr"}, + t{"/folder.jpg/usr.jpg.png", "/folder.jpg/", "/folder.jpg/usr.jpg"}, + + t{"\\folder.jpg/usr", "\\folder.jpg/", "\\folder.jpg/usr"}, + t{"\\folder.jpg/usr.", "\\folder.jpg/", "\\folder.jpg/usr"}, + t{"\\folder.jpg/usr.png", "\\folder.jpg/", "\\folder.jpg/usr"}, + t{"\\folder.jpg/usr.jpg.png", "\\folder.jpg/", "\\folder.jpg/usr.jpg"}, + }; + for ( const auto& combination : combinations ) { + const str path = std::get<0>(combination); + REQUIRE(path::remove_filename(path) == std::get<1>(combination)); + REQUIRE(path::remove_extension(path) == std::get<2>(combination)); + } + } + { + // [path, filename, result] + using t = std::tuple; + const vector combinations = { + t{ "", "", "" }, + t{ "/", "", "/" }, + t{ "\\", "", "\\" }, + + t{ "", "file", "file" }, + t{ "/", "file", "/file" }, + t{ "\\", "file", "\\file" }, + + t{ ".", "file", "file" }, + t{ "/.", "file", "/file" }, + t{ "\\.", "file", "\\file" }, + t{ ".", "", "" }, + t{ "/.", "", "/" }, + t{ "\\.", "", "\\" }, + + t{ "..", "file", "file" }, + t{ "/..", "file", "/file" }, + t{ "\\..", "file", "\\file" }, + t{ "..", "", "" }, + t{ "/..", "", "/" }, + t{ "\\..", "", "\\" }, + + t{ "usr", "file", "file" }, + t{ "/usr", "file", "/file" }, + t{ "\\usr", "file", "\\file" }, + t{ "usr", "", "" }, + t{ "/usr", "", "/" }, + t{ "\\usr", "", "\\" }, + + t{ "usr/", "file", "usr/file" }, + t{ "/usr/", "file", "/usr/file" }, + t{ "\\usr/", "file", "\\usr/file" }, + t{ "usr/", "", "usr/" }, + t{ "/usr/", "", "/usr/" }, + t{ "\\usr/", "", "\\usr/" }, + + t{ "folder/usr", "file", "folder/file" }, + t{ "/folder/usr", "file", "/folder/file" }, + t{ "\\folder/usr", "file", "\\folder/file" }, + t{ "folder/usr", "", "folder/" }, + t{ "/folder/usr", "", "/folder/" }, + t{ "\\folder/usr", "", "\\folder/" }, + + t{ "folder/usr/", "file", "folder/usr/file" }, + t{ "/folder/usr/", "file", "/folder/usr/file" }, + t{ "\\folder/usr/", "file", "\\folder/usr/file" }, + t{ "folder/usr/", "", "folder/usr/" }, + t{ "/folder/usr/", "", "/folder/usr/" }, + t{ "\\folder/usr/", "", "\\folder/usr/" }, + }; + for ( const auto& combination : combinations ) { + str path, name, result; + std::tie(path, name, result) = combination; + REQUIRE(path::replace_filename(path, name) == result); + } + } + { + // [path, extension, result] + using t = std::tuple; + const vector combinations = { + t{"", "", ""}, + t{"/", "", "/"}, + t{"\\", "", "\\"}, + t{"C:", "", "C:"}, + t{".", "", "."}, + t{"..", "", ".."}, + + t{".jpg", "png", ".png"}, + t{".jpg", ".png", ".png"}, + + t{".jpg/", "png", ".jpg/.png"}, + t{".jpg/", ".png", ".jpg/.png"}, + + t{"file", "png", "file.png"}, + t{"file.", "png", "file.png"}, + t{"file.jpg", "png", "file.png"}, + + t{"file/", "png", "file/.png"}, + t{"file./", "png", "file./.png"}, + t{"file.jpg/", "png", "file.jpg/.png"}, + + t{"/file", "png", "/file.png"}, + t{"/file.", "png", "/file.png"}, + t{"/file.jpg", "png", "/file.png"}, + + t{"/file/", "png", "/file/.png"}, + t{"/file./", "png", "/file./.png"}, + t{"/file.jpg/", "png", "/file.jpg/.png"}, + + t{"folder.jpg/file", "png", "folder.jpg/file.png"}, + t{"folder.jpg/file.", "png", "folder.jpg/file.png"}, + t{"folder.jpg/file.jpg", "png", "folder.jpg/file.png"}, + }; + for ( const auto& combination : combinations ) { + str path, extension, result; + std::tie(path, extension, result) = combination; + REQUIRE(path::replace_extension(path, extension) == result); + } + } + { + // [path, result] + using t = std::tuple; + const vector combinations = { + t{"usr/local/", "usr/local"}, + t{"usr///local///", "usr///local"}, + + t{"usr/local", "usr"}, + t{"usr///local", "usr"}, + + t{"usr/", "usr"}, + t{"usr///", "usr"}, + + t{"usr", ""}, + t{"", ""}, + + t{"/usr/local/", "/usr/local"}, + t{"/usr/local///", "/usr/local"}, + + t{"/usr/local", "/usr"}, + t{"/usr///local", "/usr"}, + + t{"/usr/", "/usr"}, + t{"/usr///", "/usr"}, + + t{"/usr", ""}, + t{"///usr", ""}, + + t{"/", ""}, + t{"//", ""}, + t{"///", ""}, + + // + // incorrect but expected + // + + t{"C:/windows", "C:"}, + t{"C:/", "C:"}, + t{"C:", ""}, + + t{"\\\\.\\COM", "\\\\."}, + t{"\\\\.", ""}, + + t{"\\\\?\\UNC", "\\\\?"}, + t{"\\\\?", ""}, + + t{"\\\\?\\C:\\", "\\\\?\\C:"}, + t{"\\\\?\\C:", "\\\\?"}, + }; + for ( const auto& combination : combinations ) { + str path, result; + std::tie(path, result) = combination; + REQUIRE(path::parent_path(path) == result); + } + } + } + SECTION("files") { + { + auto f = make_write_file("files_test", false); + REQUIRE(f); + REQUIRE(f->path() == "files_test"); + REQUIRE(f->tell() == 0); + REQUIRE(f->write("hello", 5) == 5); + REQUIRE(f->tell() == 5); + } + { + auto f = make_read_file("files_test"); + REQUIRE(f); + REQUIRE(f->path() == "files_test"); + REQUIRE(f->tell() == 0); + REQUIRE(f->length() == 5); + char buf[5] = {'\0'}; + REQUIRE(f->read(buf, 5) == 5); + REQUIRE(f->tell() == 5); + REQUIRE(std::memcmp(buf, "hello", 5) == 0); + } + { + auto f = make_write_file("files_test", true); + REQUIRE(f); + REQUIRE(f->path() == "files_test"); + REQUIRE(f->tell() == 5); + REQUIRE(f->write("world", 5) == 5); + REQUIRE(f->tell() == 10); + } + { + auto f = make_read_file("files_test"); + REQUIRE(f); + REQUIRE(f->path() == "files_test"); + REQUIRE(f->tell() == 0); + REQUIRE(f->length() == 10); + char buf[10] = {'\0'}; + REQUIRE(f->read(buf, 10) == 10); + REQUIRE(f->tell() == 10); + REQUIRE(std::memcmp(buf, "helloworld", 10) == 0); + } + } + SECTION("filesystem") { + { + const str_view child_dir_name = "test_filesystem_file_name"; + REQUIRE(filesystem::remove_file(child_dir_name)); + REQUIRE_FALSE(filesystem::exists(child_dir_name)); + + REQUIRE(filesystem::create_file(child_dir_name)); + REQUIRE(filesystem::exists(child_dir_name)); + REQUIRE(filesystem::file_exists(child_dir_name)); + + buffer data("hello", 5); + REQUIRE(filesystem::try_write_all(data, child_dir_name, false)); + { + buffer d1; + REQUIRE(filesystem::try_read_all(d1, child_dir_name)); + REQUIRE(data == d1); + } + REQUIRE(filesystem::create_file(child_dir_name)); + REQUIRE(filesystem::file_exists(child_dir_name)); + { + buffer d1; + REQUIRE(filesystem::try_read_all(d1, child_dir_name)); + REQUIRE(data == d1); + } + REQUIRE(filesystem::remove_file(child_dir_name)); + REQUIRE_FALSE(filesystem::exists(child_dir_name)); + } + { + const str child_dir_name = "test_filesystem_child_dir"; + const str parent_dir_path = "test_filesystem_parent_dir"; + const str child_dir_path = path::combine(parent_dir_path, child_dir_name); + REQUIRE(filesystem::remove(child_dir_path)); + REQUIRE(filesystem::remove(parent_dir_path)); + REQUIRE_FALSE(filesystem::exists(child_dir_path)); + REQUIRE_FALSE(filesystem::exists(parent_dir_path)); + REQUIRE_FALSE(filesystem::file_exists(child_dir_path)); + REQUIRE_FALSE(filesystem::file_exists(parent_dir_path)); + REQUIRE_FALSE(filesystem::directory_exists(child_dir_path)); + REQUIRE_FALSE(filesystem::directory_exists(parent_dir_path)); + + REQUIRE_FALSE(filesystem::create_directory(child_dir_path)); + REQUIRE_FALSE(filesystem::exists(child_dir_path)); + + REQUIRE(filesystem::create_directory(parent_dir_path)); + + REQUIRE(filesystem::exists(parent_dir_path)); + REQUIRE(filesystem::directory_exists(parent_dir_path)); + REQUIRE_FALSE(filesystem::file_exists(parent_dir_path)); + + REQUIRE(filesystem::create_directory(child_dir_path)); + + REQUIRE(filesystem::exists(child_dir_path)); + REQUIRE(filesystem::directory_exists(child_dir_path)); + REQUIRE_FALSE(filesystem::file_exists(child_dir_path)); + + REQUIRE_FALSE(filesystem::remove(parent_dir_path)); + + REQUIRE(filesystem::remove_directory(child_dir_path)); + REQUIRE(filesystem::remove_directory(parent_dir_path)); + REQUIRE_FALSE(filesystem::exists(child_dir_path)); + REQUIRE_FALSE(filesystem::exists(parent_dir_path)); + } + } +} diff --git a/untests/sources/untests_utils/image.cpp b/untests/sources/untests_utils/image.cpp new file mode 100644 index 00000000..268f4719 --- /dev/null +++ b/untests/sources/untests_utils/image.cpp @@ -0,0 +1,169 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("images") { + { + image i; + REQUIRE(i.size() == v2u::zero()); + REQUIRE(i.format() == image_data_format::unknown); + REQUIRE(i.data().empty()); + REQUIRE(i.empty()); + } + { + image i(v2u::zero(), image_data_format::unknown); + REQUIRE(i.size() == v2u::zero()); + REQUIRE(i.format() == image_data_format::unknown); + REQUIRE(i.data().empty()); + REQUIRE(i.empty()); + } + { + image i(v2u::zero(), image_data_format::unknown, buffer()); + REQUIRE(i.size() == v2u::zero()); + REQUIRE(i.format() == image_data_format::unknown); + REQUIRE(i.data().empty()); + REQUIRE(i.empty()); + } + { + const buffer b{}; + image i(v2u::zero(), image_data_format::unknown, b); + REQUIRE(i.size() == v2u::zero()); + REQUIRE(i.format() == image_data_format::unknown); + REQUIRE(i.data().empty()); + REQUIRE(i.empty()); + } + { + REQUIRE_THROWS_AS(image(v2u::zero(), image_data_format::g8), bad_image_data_format); + REQUIRE_THROWS_AS(image(v2u::zero(), image_data_format::ga8), bad_image_data_format); + REQUIRE_THROWS_AS(image(v2u::zero(), image_data_format::rgb8), bad_image_data_format); + REQUIRE_THROWS_AS(image(v2u::zero(), image_data_format::rgba8), bad_image_data_format); + + REQUIRE(image(v2u::unit(), image_data_format::g8).data().size() == 1); + REQUIRE(image(v2u::unit(), image_data_format::ga8).data().size() == 2); + REQUIRE(image(v2u::unit(), image_data_format::rgb8).data().size() == 3); + REQUIRE(image(v2u::unit(), image_data_format::rgba8).data().size() == 4); + + REQUIRE_THROWS_AS(image(v2u::unit() * 1u, image_data_format::dxt1), bad_image_data_format); + REQUIRE_THROWS_AS(image(v2u::unit() * 2u, image_data_format::dxt1), bad_image_data_format); + REQUIRE_THROWS_AS(image(v2u::unit() * 3u, image_data_format::dxt1), bad_image_data_format); + REQUIRE(image(v2u::unit() * 4u, image_data_format::dxt1).data().size() == 8); + REQUIRE(image(v2u::unit() * 4u, image_data_format::dxt5).data().size() == 16); + } + { + const u8 img[] = {1,2,3,4,5,6,7,8}; + image i0(v2u(2,2), image_data_format::g8, {img,4}); + image i1(v2u(2,1), image_data_format::ga8, {img,4}); + image i2(v2u(1,2), image_data_format::ga8, {img,4}); + image i3(v2u(2,1), image_data_format::rgb8, {img,6}); + image i4(v2u(1,2), image_data_format::rgb8, {img,6}); + image i5(v2u(2,1), image_data_format::rgba8, {img,8}); + image i6(v2u(1,2), image_data_format::rgba8, {img,8}); + REQUIRE(i0.format() == image_data_format::g8); + REQUIRE(i1.format() == image_data_format::ga8); + REQUIRE(i2.format() == image_data_format::ga8); + REQUIRE(i3.format() == image_data_format::rgb8); + REQUIRE(i4.format() == image_data_format::rgb8); + REQUIRE(i5.format() == image_data_format::rgba8); + REQUIRE(i6.format() == image_data_format::rgba8); + } + { + REQUIRE(filesystem::remove_file("image_save_test.jpg")); + REQUIRE(filesystem::remove_file("image_save_test.png")); + REQUIRE(filesystem::remove_file("image_save_test.tga")); + const u8 img_data[] = {255,0,0, 0,255,0, 0,0,255}; + image img(v2u(3,1), image_data_format::rgb8, buffer(img_data, sizeof(img_data))); + REQUIRE(images::try_save_image( + img, + image_file_format::jpg, + make_write_file("image_save_test.jpg", false))); + REQUIRE(filesystem::file_exists("image_save_test.jpg")); + REQUIRE(images::try_save_image( + img, + image_file_format::png, + make_write_file("image_save_test.png", false))); + REQUIRE(filesystem::file_exists("image_save_test.png")); + REQUIRE(images::try_save_image( + img, + image_file_format::tga, + make_write_file("image_save_test.tga", false))); + REQUIRE(filesystem::file_exists("image_save_test.tga")); + } + { + image img; + REQUIRE(images::try_load_image(img, make_read_file("image_save_test.jpg"))); + REQUIRE(img.size() == v2u(3,1)); + REQUIRE(img.format() == image_data_format::rgb8); + REQUIRE(math::approximately(img.pixel32(0,0), color32::red(), 10u)); + REQUIRE(math::approximately(img.pixel32(1,0), color32::green(), 10u)); + REQUIRE(math::approximately(img.pixel32(2,0), color32::blue(), 10u)); + + REQUIRE(images::try_load_image(img, make_read_file("image_save_test.png"))); + REQUIRE(img.size() == v2u(3,1)); + REQUIRE(img.format() == image_data_format::rgb8); + REQUIRE(math::approximately(img.pixel32(0,0), color32::red(), 0u)); + REQUIRE(math::approximately(img.pixel32(1,0), color32::green(), 0u)); + REQUIRE(math::approximately(img.pixel32(2,0), color32::blue(), 0u)); + + REQUIRE(images::try_load_image(img, make_read_file("image_save_test.tga"))); + REQUIRE(img.size() == v2u(3,1)); + REQUIRE(img.format() == image_data_format::rgb8); + REQUIRE(math::approximately(img.pixel32(0,0), color32::red(), 0u)); + REQUIRE(math::approximately(img.pixel32(1,0), color32::green(), 0u)); + REQUIRE(math::approximately(img.pixel32(2,0), color32::blue(), 0u)); + } + { + REQUIRE(filesystem::remove_file("image_save_test.jpg")); + REQUIRE(filesystem::remove_file("image_save_test.png")); + REQUIRE(filesystem::remove_file("image_save_test.tga")); + const u8 img_data[] = {255,0,0, 0,255,0, 0,0,255}; + image img(v2u(3,1), image_data_format::rgb8, buffer(img_data, sizeof(img_data))); + buffer buf; + REQUIRE(images::try_save_image( + img, + image_file_format::jpg, + buf)); + REQUIRE(filesystem::try_write_all(buf, "image_save_test.jpg", false)); + REQUIRE(images::try_save_image( + img, + image_file_format::png, + buf)); + REQUIRE(filesystem::try_write_all(buf, "image_save_test.png", false)); + REQUIRE(images::try_save_image( + img, + image_file_format::tga, + buf)); + REQUIRE(filesystem::try_write_all(buf, "image_save_test.tga", false)); + } + { + image img; + buffer buf; + REQUIRE(filesystem::try_read_all(buf, "image_save_test.jpg")); + REQUIRE(images::try_load_image(img, buf)); + REQUIRE(img.size() == v2u(3,1)); + REQUIRE(img.format() == image_data_format::rgb8); + REQUIRE(math::approximately(img.pixel32(0,0), color32::red(), 10)); + REQUIRE(math::approximately(img.pixel32(1,0), color32::green(), 10)); + REQUIRE(math::approximately(img.pixel32(2,0), color32::blue(), 10)); + + REQUIRE(filesystem::try_read_all(buf, "image_save_test.png")); + REQUIRE(images::try_load_image(img, buf)); + REQUIRE(img.size() == v2u(3,1)); + REQUIRE(img.format() == image_data_format::rgb8); + REQUIRE(math::approximately(img.pixel32(0,0), color32::red(), 0)); + REQUIRE(math::approximately(img.pixel32(1,0), color32::green(), 0)); + REQUIRE(math::approximately(img.pixel32(2,0), color32::blue(), 0)); + + REQUIRE(filesystem::try_read_all(buf, "image_save_test.tga")); + REQUIRE(images::try_load_image(img, buf)); + REQUIRE(img.size() == v2u(3,1)); + REQUIRE(img.format() == image_data_format::rgb8); + REQUIRE(math::approximately(img.pixel32(0,0), color32::red(), 0)); + REQUIRE(math::approximately(img.pixel32(1,0), color32::green(), 0)); + REQUIRE(math::approximately(img.pixel32(2,0), color32::blue(), 0)); + } +} diff --git a/untests/sources/untests_utils/jobber.cpp b/untests/sources/untests_utils/jobber.cpp new file mode 100644 index 00000000..75778500 --- /dev/null +++ b/untests/sources/untests_utils/jobber.cpp @@ -0,0 +1,138 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("jobber") { + { + jobber j(1); + auto pv0 = j.async([](){ + throw exception(); + }); + REQUIRE_THROWS_AS(pv0.get(), exception); + } + { + i32 v5 = 5; + + jobber j(1); + auto pv0 = j.async([](i32 v){ + REQUIRE(v == 5); + throw exception(); + }, v5); + REQUIRE_THROWS_AS(pv0.get(), exception); + + auto pv1 = j.async([](int& v){ + REQUIRE(v == 5); + return v != 5 + ? 0 + : throw exception(); + }, std::ref(v5)); + REQUIRE_THROWS_AS(pv1.get(), exception); + + auto pv3 = j.async([](int& v){ + v = 4; + return v; + }, std::ref(v5)); + REQUIRE(pv3.get() == v5); + REQUIRE(v5 == 4); + } + { + jobber j(1); + auto p0 = j.async([](rad angle){ + return math::sin(angle); + }, math::pi()); + auto p1 = j.async([](rad angle){ + return math::cos(angle); + }, math::two_pi()); + REQUIRE(math::approximately(p0.get(), 0.f)); + REQUIRE(math::approximately(p1.get(), 1.f)); + } + { + jobber j(1); + j.pause(); + jobber::priority max_priority = jobber::priority::highest; + j.async([](){ + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + }); + for ( std::size_t i = 0; i < 10; ++i ) { + jobber::priority p = static_cast( + i % static_cast(jobber::priority::highest)); + j.async(p, [&max_priority](jobber::priority priority) { + REQUIRE(priority <= max_priority); + max_priority = priority; + }, p); + } + j.resume(); + j.wait_all(); + } + { + jobber j(2); + jobber g(2); + + std::vector> jp(50); + for ( std::size_t i = 0; i < jp.size(); ++i ) { + jp[i] = j.async([&g](){ + std::vector> gp(50); + for ( std::size_t ii = 0; ii < gp.size(); ++ii ) { + gp[ii] = g.async([](rad angle){ + return math::sin(angle); + }, make_rad(ii).cast_to()); + } + return std::accumulate(gp.begin(), gp.end(), 0.f, + [](f32 r, std::future& f){ + return r + f.get(); + }); + }); + } + f32 r0 = std::accumulate(jp.begin(), jp.end(), 0.f, + [](f32 r, std::future& f){ + return r + f.get(); + }); + f32 r1 = 0.f; + for ( std::size_t i = 0; i < 50; ++i ) { + r1 += math::sin(make_rad(i).cast_to()); + } + REQUIRE(math::approximately(r0, r1 * 50.f)); + } + SECTION("performance") { + std::printf("-= jobber::performance tests =-\n"); + #if defined(E2D_BUILD_MODE) && E2D_BUILD_MODE == E2D_BUILD_MODE_DEBUG + const std::size_t task_n = 100'000; + #else + const std::size_t task_n = 1'000'000; + #endif + const auto big_task = [](std::size_t b, std::size_t n){ + f32 result = 0.f; + for ( std::size_t i = 0; i < n; ++i ) { + result += math::sin(make_rad(b + i).cast_to()); + result += math::cos(make_rad(b + i).cast_to()); + } + return result; + }; + const auto multi_task = [&](u32 threads, std::size_t tasks){ + e2d_untests::verbose_profiler_ms p( + str("cores: ") + std::to_string(threads) + + str(", tasks: ") + std::to_string(tasks)); + jobber j(threads); + std::vector> rs(tasks); + for ( std::size_t i = 0; i < tasks; ++i ) { + const std::size_t n = task_n / tasks; + rs[i] = j.async(big_task, i * n, n); + } + f32 result = std::accumulate(rs.begin(), rs.end(), 0.f, + [](f32 r, std::future& f){ + return r + f.get(); + }); + p.done(result); + }; + for ( u32 i = 1; i <= 4; ++i ) { + multi_task(i, 10); + multi_task(i, 100); + multi_task(i, 1000); + } + } +} diff --git a/untests/sources/untests_utils/streams.cpp b/untests/sources/untests_utils/streams.cpp new file mode 100644 index 00000000..053b95ba --- /dev/null +++ b/untests/sources/untests_utils/streams.cpp @@ -0,0 +1,106 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("streams") { + buffer hello_data("hello", 5); + { + input_stream_uptr s = make_memory_stream(hello_data); + REQUIRE_THROWS_AS(s->seek(-10, false), bad_stream_operation); + REQUIRE(s->tell() == 0); + REQUIRE_THROWS_AS(s->seek(10, false), bad_stream_operation); + REQUIRE_THROWS_AS( + s->seek(std::numeric_limits::min(), false), + bad_stream_operation); + REQUIRE_THROWS_AS( + s->seek(std::numeric_limits::max(), false), + bad_stream_operation); + REQUIRE(s->tell() == 0); + REQUIRE((s->seek(5, false) == 5 && s->tell() == 5)); + REQUIRE((s->seek(3, false) == 3 && s->tell() == 3)); + REQUIRE((s->seek(0, false) == 0 && s->tell() == 0)); + } + { + input_stream_uptr s = make_memory_stream(hello_data); + REQUIRE((s->seek(3, false) == 3 && s->tell() == 3)); + REQUIRE_THROWS_AS(s->seek(3, true), bad_stream_operation); + REQUIRE(s->tell() == 3); + REQUIRE((s->seek(2, true) == 5 && s->tell() == 5)); + REQUIRE_THROWS_AS(s->seek(-6, true), bad_stream_operation); + REQUIRE(s->tell() == 5); + REQUIRE((s->seek(-5, true) == 0 && s->tell() == 0)); + } + { + input_stream_uptr s = make_memory_stream(hello_data); + REQUIRE((s->seek(3, false) == 3 && s->tell() == 3)); + REQUIRE_THROWS_AS(s->seek(-4, true), bad_stream_operation); + REQUIRE(s->tell() == 3); + REQUIRE((s->seek(-3, true) == 0 && s->tell() == 0)); + REQUIRE_THROWS_AS(s->seek(6, true), bad_stream_operation); + REQUIRE(s->tell() == 0); + REQUIRE((s->seek(5, true) == 5 && s->tell() == 5)); + } + { + input_stream_uptr s = make_memory_stream(hello_data); + REQUIRE(s->tell() == 0); + REQUIRE(s->length() == 5); + char buf[5] = {'\0'}; + REQUIRE(s->read(buf, 5) == 5); + REQUIRE(std::memcmp(buf, "hello", 5) == 0); + REQUIRE(s->tell() == 5); + REQUIRE(s->length() == 5); + } + { + input_stream_uptr s = make_memory_stream(hello_data); + char buf[10] = {'\0'}; + { + REQUIRE(s->read(buf, 0) == 0); + REQUIRE(std::memcmp(buf, "\0\0\0\0\0\0\0\0\0\0", 10) == 0); + REQUIRE(s->tell() == 0); + REQUIRE(s->length() == 5); + } + { + REQUIRE(s->read(buf, 10) == 5); + REQUIRE(std::memcmp(buf, "hello\0\0\0\0\0", 10) == 0); + REQUIRE(s->tell() == 5); + REQUIRE(s->length() == 5); + } + { + REQUIRE(s->seek(2, false) == 2); + REQUIRE(s->read(buf, 10) == 3); + REQUIRE(std::memcmp(buf, "llolo\0\0\0\0\0", 10) == 0); + REQUIRE(s->tell() == 5); + REQUIRE(s->length() == 5); + } + { + REQUIRE(s->seek(0, false) == 0); + REQUIRE(s->read(buf, 3) == 3); + REQUIRE(std::memcmp(buf, "hello\0\0\0\0\0", 10) == 0); + REQUIRE(s->tell() == 3); + REQUIRE(s->length() == 5); + } + } + { + input_stream_uptr s = make_memory_stream(hello_data); + char buf[10] = {'\0'}; + { + REQUIRE(s->read(buf, 1) == 1); + REQUIRE(std::memcmp(buf, "h\0\0\0\0\0\0\0\0\0", 10) == 0); + REQUIRE(s->tell() == 1); + REQUIRE(s->length() == 5); + } + { + buffer b; + REQUIRE(streams::try_read_tail(b, s)); + REQUIRE(b.size() == 4); + REQUIRE(std::memcmp(b.data(), "ello", 4) == 0); + REQUIRE(s->tell() == 5); + REQUIRE(s->length() == 5); + } + } +} diff --git a/untests/sources/untests_utils/strfmts.cpp b/untests/sources/untests_utils/strfmts.cpp new file mode 100644 index 00000000..fe43adea --- /dev/null +++ b/untests/sources/untests_utils/strfmts.cpp @@ -0,0 +1,158 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("strfmts") { + { + char buf[2]; + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", v2i::zero()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", v3i::zero()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", v4i::zero()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", m2i::identity()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", m3i::identity()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", m4i::identity()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_rad(10)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_deg(10)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", v2f::zero()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", v3f::zero()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", v4f::zero()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", m2f::identity()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", m3f::identity()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", m4f::identity()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_rad(10.f)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_deg(10.0)), strings::bad_format_buffer); + } + { + char buf[2]; + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", color::white()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", color32::white()), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_seconds(0)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_milliseconds(0)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_microseconds(0)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_seconds(0.f)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_milliseconds(0.f)), strings::bad_format_buffer); + REQUIRE_THROWS_AS( + strings::format(buf, sizeof(buf), "%0", make_microseconds(0.f)), strings::bad_format_buffer); + } + { + REQUIRE(strings::rformat( + "%0", + v2u(1,2)) == "(1,2)"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(v3u(1,2,3), 3)) == "( 1, 2, 3)"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(v4i(1,2,3,4), 2)) == "( 1, 2, 3, 4)"); + + REQUIRE(strings::rformat( + "%0", + v2f(1.f,2.f)) == "(1.000000,2.000000)"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(v3d(1,2,3), 5, 2)) == "( 1.00, 2.00, 3.00)"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(v4f(1,2,3,4),0,1)) == "(1.0,2.0,3.0,4.0)"); + } + { + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(m2i(1,2,3,4), 3)) == + "(( 1, 2),( 3, 4))"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(m3i(1,2,3,4,5,6,7,8,9), 1)) == + "((1,2,3),(4,5,6),(7,8,9))"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(m4i(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), 2)) == + "(( 1, 2, 3, 4),( 5, 6, 7, 8),( 9,10,11,12),(13,14,15,16))"); + + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(m2f(1,2,3,4), 5, 2)) == + "(( 1.00, 2.00),( 3.00, 4.00))"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(m3f(1,2,3,4,5,6,7,8,9), 4, 1)) == + "(( 1.0, 2.0, 3.0),( 4.0, 5.0, 6.0),( 7.0, 8.0, 9.0))"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(m4f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), 2,0)) == + "(( 1, 2, 3, 4),( 5, 6, 7, 8),( 9,10,11,12),(13,14,15,16))"); + } + { + REQUIRE(strings::rformat("%0", make_rad(2)) == "2rad"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(make_rad(2.f), 5, 2)) == + " 2.00rad"); + + REQUIRE(strings::rformat("%0", make_deg(3)) == "3deg"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(make_deg(2.0), 5, 2)) == + " 2.00deg"); + } + { + str utf8 = make_utf8("hello"); + wstr wide = make_wide("hello"); + str16 utf16 = make_utf16("hello"); + str32 utf32 = make_utf32("hello"); + + REQUIRE(strings::rformat("%0", utf8) == utf8); + REQUIRE(strings::rformat("%0", wide) == utf8); + REQUIRE(strings::rformat("%0", utf16) == utf8); + REQUIRE(strings::rformat("%0", utf32) == utf8); + } + { + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(color(1.f,2.f,3.f,4.f), 0, 2)) == + "(1.00,2.00,3.00,4.00)"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(color32(1,2,3,4), 2)) == "( 1, 2, 3, 4)"); + } + { + REQUIRE(strings::rformat("%0", make_seconds(2)) == "2s"); + REQUIRE(strings::rformat("%0", make_milliseconds(3)) == "3ms"); + REQUIRE(strings::rformat("%0", make_microseconds(4)) == "4us"); + + REQUIRE(strings::rformat("%0", make_seconds(2.f)) == "2.000000s"); + REQUIRE(strings::rformat("%0", make_milliseconds(3.0)) == "3.000000ms"); + REQUIRE(strings::rformat( + "%0", + strings::make_format_arg(make_microseconds(4.f), 5, 2)) == + " 4.00us"); + } +} diff --git a/untests/sources/untests_utils/strings.cpp b/untests/sources/untests_utils/strings.cpp new file mode 100644 index 00000000..9177cfc8 --- /dev/null +++ b/untests/sources/untests_utils/strings.cpp @@ -0,0 +1,444 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("strings") { + { + REQUIRE(make_utf8("hello") == "hello"); + REQUIRE(make_utf8(L"hello") == "hello"); + REQUIRE(make_utf8(u"hello") == "hello"); + REQUIRE(make_utf8(U"hello") == "hello"); + + REQUIRE(make_wide("hello") == L"hello"); + REQUIRE(make_wide(L"hello") == L"hello"); + REQUIRE(make_wide(u"hello") == L"hello"); + REQUIRE(make_wide(U"hello") == L"hello"); + + REQUIRE(make_utf16("hello") == u"hello"); + REQUIRE(make_utf16(L"hello") == u"hello"); + REQUIRE(make_utf16(u"hello") == u"hello"); + REQUIRE(make_utf16(U"hello") == u"hello"); + + REQUIRE(make_utf32("hello") == U"hello"); + REQUIRE(make_utf32(L"hello") == U"hello"); + REQUIRE(make_utf32(u"hello") == U"hello"); + REQUIRE(make_utf32(U"hello") == U"hello"); + } + { + const char* null_utf8 = nullptr; + const wchar_t* null_wide = nullptr; + const char16_t* null_utf16 = nullptr; + const char32_t* null_utf32 = nullptr; + + REQUIRE(make_utf8(str_view(null_utf8, 0)) == make_utf8(u"")); + REQUIRE(make_utf8(wstr_view(null_wide, 0)) == make_utf8(L"")); + REQUIRE(make_utf8(str16_view(null_utf16, 0)) == make_utf8(u"")); + REQUIRE(make_utf8(str32_view(null_utf32, 0)) == make_utf8(U"")); + + REQUIRE(make_wide(str_view(null_utf8, 0)) == make_wide(u"")); + REQUIRE(make_wide(wstr_view(null_wide, 0)) == make_wide(L"")); + REQUIRE(make_wide(str16_view(null_utf16, 0)) == make_wide(u"")); + REQUIRE(make_wide(str32_view(null_utf32, 0)) == make_wide(U"")); + + REQUIRE(make_utf16(str_view(null_utf8, 0)) == make_utf16(u"")); + REQUIRE(make_utf16(wstr_view(null_wide, 0)) == make_utf16(L"")); + REQUIRE(make_utf16(str16_view(null_utf16, 0)) == make_utf16(u"")); + REQUIRE(make_utf16(str32_view(null_utf32, 0)) == make_utf16(U"")); + + REQUIRE(make_utf32(str_view(null_utf8, 0)) == make_utf32(u"")); + REQUIRE(make_utf32(wstr_view(null_wide, 0)) == make_utf32(L"")); + REQUIRE(make_utf32(str16_view(null_utf16, 0)) == make_utf32(u"")); + REQUIRE(make_utf32(str32_view(null_utf32, 0)) == make_utf32(U"")); + } + { + using strings::wildcard_match; + + char invalid_utf[] = "\xe6\x97\xa5\xd1\x88\xfa"; + REQUIRE_THROWS_AS(wildcard_match(invalid_utf, "???"), std::exception); + + const auto mark_string = [](const char* str) -> str_view { + static char string_buf[1024] = {0}; + std::memset(string_buf, '*', sizeof(string_buf)); + const std::size_t str_len = std::strlen(str); + E2D_ASSERT(str_len < sizeof(string_buf)); + std::memcpy(string_buf, str, str_len); + return str_view(string_buf, str_len); + }; + + const auto mark_pattern = [](const char* str) -> str_view { + static char pattern_buf[1024] = {0}; + std::memset(pattern_buf, '*', sizeof(pattern_buf)); + const std::size_t str_len = std::strlen(str); + E2D_ASSERT(str_len < sizeof(pattern_buf)); + std::memcpy(pattern_buf, str, str_len); + return str_view(pattern_buf, str_len); + }; + + // 你好! + REQUIRE(wildcard_match(u8"\u4F60\u597D!", u8"\u4F60\u597D!") == true); + REQUIRE(wildcard_match(u8"\u4F60\u597D!", u8"?\u597D!") == true); + REQUIRE(wildcard_match(u8"\u4F60\u597D!", u8"\u4F60?!") == true); + + REQUIRE(wildcard_match( + // 你好你好你好你好世界世界世界世界世界世界世界世界彡ಠ + mark_string("\u4F60\u597D\u4F60\u597D\u4F60\u597D\u4F60\u597D\u4E16\u754C\u4E16\u754C\u4E16\u754C\u4E16\u754C\u4E16\u754C\u4E16\u754C\u4E16\u754C\u4E16\u754C\u5F61\u0CA0"), + // 你好你好你好你好*世界世界彡*ಠ + mark_pattern("\u4F60\u597D\u4F60\u597D\u4F60\u597D\u4F60\u597D*\u4E16\u754C\u4E16\u754C\u5F61*\u0CA0")) == true); + + REQUIRE(wildcard_match("", "") == true); + REQUIRE(wildcard_match("a", "") == false); + REQUIRE(wildcard_match("", "*") == true); + REQUIRE(wildcard_match("", "?") == false); + + const char* null_utf8 = nullptr; + str_view null_view = {null_utf8, 0}; + REQUIRE(wildcard_match(null_view, null_view) == true); + REQUIRE(wildcard_match("a", null_view) == false); + REQUIRE(wildcard_match(null_view, "*") == true); + REQUIRE(wildcard_match(null_view, "?") == false); + + // tests source: + // http://developforperformance.com/MatchingWildcards_AnImprovedAlgorithmForBigData.html + + REQUIRE(wildcard_match(mark_string("abc"), mark_pattern("ab*d")) == false); + + REQUIRE(wildcard_match(mark_string("abcccd"), mark_pattern("*ccd")) == true); + REQUIRE(wildcard_match(mark_string("mississipissippi"), mark_pattern("*issip*ss*")) == true); + REQUIRE(wildcard_match(mark_string("xxxx*zzzzzzzzy*f"), mark_pattern("xxxx*zzy*fffff")) == false); + REQUIRE(wildcard_match(mark_string("xxxx*zzzzzzzzy*f"), mark_pattern("xxx*zzy*f")) == true); + REQUIRE(wildcard_match(mark_string("xxxxzzzzzzzzyf"), mark_pattern("xxxx*zzy*fffff")) == false); + REQUIRE(wildcard_match(mark_string("xxxxzzzzzzzzyf"), mark_pattern("xxxx*zzy*f")) == true); + REQUIRE(wildcard_match(mark_string("xyxyxyzyxyz"), mark_pattern("xy*z*xyz")) == true); + REQUIRE(wildcard_match(mark_string("mississippi"), mark_pattern("*sip*")) == true); + REQUIRE(wildcard_match(mark_string("xyxyxyxyz"), mark_pattern("xy*xyz")) == true); + REQUIRE(wildcard_match(mark_string("mississippi"), mark_pattern("mi*sip*")) == true); + REQUIRE(wildcard_match(mark_string("ababac"), mark_pattern("*abac*")) == true); + REQUIRE(wildcard_match(mark_string("ababac"), mark_pattern("*abac*")) == true); + REQUIRE(wildcard_match(mark_string("aaazz"), mark_pattern("a*zz*")) == true); + REQUIRE(wildcard_match(mark_string("a12b12"), mark_pattern("*12*23")) == false); + REQUIRE(wildcard_match(mark_string("a12b12"), mark_pattern("a12b")) == false); + REQUIRE(wildcard_match(mark_string("a12b12"), mark_pattern("*12*12*")) == true); + + REQUIRE(wildcard_match(mark_string("caaab"), mark_pattern("*a?b")) == true); + + REQUIRE(wildcard_match(mark_string("*"), mark_pattern("*")) == true); + REQUIRE(wildcard_match(mark_string("a*abab"), mark_pattern("a*b")) == true); + REQUIRE(wildcard_match(mark_string("a*r"), mark_pattern("a*")) == true); + REQUIRE(wildcard_match(mark_string("a*ar"), mark_pattern("a*aar")) == false); + + REQUIRE(wildcard_match(mark_string("XYXYXYZYXYz"), mark_pattern("XY*Z*XYz")) == true); + REQUIRE(wildcard_match(mark_string("missisSIPpi"), mark_pattern("*SIP*")) == true); + REQUIRE(wildcard_match(mark_string("mississipPI"), mark_pattern("*issip*PI")) == true); + REQUIRE(wildcard_match(mark_string("xyxyxyxyz"), mark_pattern("xy*xyz")) == true); + REQUIRE(wildcard_match(mark_string("miSsissippi"), mark_pattern("mi*sip*")) == true); + REQUIRE(wildcard_match(mark_string("miSsissippi"), mark_pattern("mi*Sip*")) == false); + REQUIRE(wildcard_match(mark_string("abAbac"), mark_pattern("*Abac*")) == true); + REQUIRE(wildcard_match(mark_string("abAbac"), mark_pattern("*Abac*")) == true); + REQUIRE(wildcard_match(mark_string("aAazz"), mark_pattern("a*zz*")) == true); + REQUIRE(wildcard_match(mark_string("A12b12"), mark_pattern("*12*23")) == false); + REQUIRE(wildcard_match(mark_string("a12B12"), mark_pattern("*12*12*")) == true); + REQUIRE(wildcard_match(mark_string("oWn"), mark_pattern("*oWn*")) == true); + + REQUIRE(wildcard_match(mark_string("bLah"), mark_pattern("bLah")) == true); + REQUIRE(wildcard_match(mark_string("bLah"), mark_pattern("bLaH")) == false); + + REQUIRE(wildcard_match(mark_string("a"), mark_pattern("*?")) == true); + REQUIRE(wildcard_match(mark_string("ab"), mark_pattern("*?")) == true); + REQUIRE(wildcard_match(mark_string("abc"), mark_pattern("*?")) == true); + + REQUIRE(wildcard_match(mark_string("a"), mark_pattern("??")) == false); + REQUIRE(wildcard_match(mark_string("ab"), mark_pattern("?*?")) == true); + REQUIRE(wildcard_match(mark_string("ab"), mark_pattern("*?*?*")) == true); + REQUIRE(wildcard_match(mark_string("abc"), mark_pattern("?**?*?")) == true); + REQUIRE(wildcard_match(mark_string("abc"), mark_pattern("?**?*&?")) == false); + REQUIRE(wildcard_match(mark_string("abcd"), mark_pattern("?b*??")) == true); + REQUIRE(wildcard_match(mark_string("abcd"), mark_pattern("?a*??")) == false); + REQUIRE(wildcard_match(mark_string("abcd"), mark_pattern("?**?c?")) == true); + REQUIRE(wildcard_match(mark_string("abcd"), mark_pattern("?**?d?")) == false); + REQUIRE(wildcard_match(mark_string("abcde"), mark_pattern("?*b*?*d*?")) == true); + + REQUIRE(wildcard_match(mark_string("bLah"), mark_pattern("bL?h")) == true); + REQUIRE(wildcard_match(mark_string("bLaaa"), mark_pattern("bLa?")) == false); + REQUIRE(wildcard_match(mark_string("bLah"), mark_pattern("bLa?")) == true); + REQUIRE(wildcard_match(mark_string("bLaH"), mark_pattern("?Lah")) == false); + REQUIRE(wildcard_match(mark_string("bLaH"), mark_pattern("?LaH")) == true); + + REQUIRE(wildcard_match( + mark_string("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"), + mark_pattern("a*a*a*a*a*a*aa*aaa*a*a*b")) == true); + REQUIRE(wildcard_match( + mark_string("abababababababababababababababababababaacacacacacacacadaeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab"), + mark_pattern("*a*b*ba*ca*a*aa*aaa*fa*ga*b*")) == true); + REQUIRE(wildcard_match( + mark_string("abababababababababababababababababababaacacacacacacacadaeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab"), + mark_pattern("*a*b*ba*ca*a*x*aaa*fa*ga*b*")) == false); + REQUIRE(wildcard_match( + mark_string("abababababababababababababababababababaacacacacacacacadaeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab"), + mark_pattern("*a*b*ba*ca*aaaa*fa*ga*gggg*b*")) == false); + REQUIRE(wildcard_match( + mark_string("abababababababababababababababababababaacacacacacacacadaeafagahaiajakalaaaaaaaaaaaaaaaaaffafagaagggagaaaaaaaab"), + mark_pattern("*a*b*ba*ca*aaaa*fa*ga*ggg*b*")) == true); + REQUIRE(wildcard_match( + mark_string("aaabbaabbaab"), + mark_pattern("*aabbaa*a*")) == true); + REQUIRE(wildcard_match( + mark_string("a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*"), + mark_pattern("a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*")) == true); + REQUIRE(wildcard_match( + mark_string("aaaaaaaaaaaaaaaaa"), + mark_pattern("*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*")) == true); + REQUIRE(wildcard_match( + mark_string("aaaaaaaaaaaaaaaa"), + mark_pattern("*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*")) == false); + REQUIRE(wildcard_match( + mark_string("abc*abcd*abcde*abcdef*abcdefg*abcdefgh*abcdefghi*abcdefghij*abcdefghijk*abcdefghijkl*abcdefghijklm*abcdefghijklmn"), + mark_pattern("abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*")) == false); + REQUIRE(wildcard_match( + mark_string("abc*abcd*abcde*abcdef*abcdefg*abcdefgh*abcdefghi*abcdefghij*abcdefghijk*abcdefghijkl*abcdefghijklm*abcdefghijklmn"), + mark_pattern("abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*")) == true); + REQUIRE(wildcard_match( + mark_string("abc*abcd*abcd*abc*abcd"), + mark_pattern("abc*abc*abc*abc*abc")) == false); + REQUIRE(wildcard_match( + mark_string("abc*abcd*abcd*abc*abcd*abcd*abc*abcd*abc*abc*abcd"), + mark_pattern("abc*abc*abc*abc*abc*abc*abc*abc*abc*abc*abcd")) == true); + REQUIRE(wildcard_match(mark_string("abc"), mark_pattern("********a********b********c********")) == true); + REQUIRE(wildcard_match(mark_string("********a********b********c********"), mark_pattern("abc")) == false); + REQUIRE(wildcard_match(mark_string("abc"), mark_pattern("********a********b********b********")) == false); + REQUIRE(wildcard_match(mark_string("*abc*"), mark_pattern("***a*b*c***")) == true); + } + { + char buf[6]; + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), nullptr), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, 0, "hello"), strings::bad_format_buffer); + REQUIRE_THROWS_AS(strings::format(nullptr, sizeof(buf), "hello"), strings::bad_format_buffer); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "helloE"), strings::bad_format_buffer); + REQUIRE_NOTHROW(strings::format(buf, sizeof(buf), "hello")); + REQUIRE_NOTHROW(strings::format(nullptr, 0, "hello")); + + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%hell"), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "he%ll"), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "hell%"), strings::bad_format); + + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%10%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "hell%10%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%10%hell"), strings::bad_format); + + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%x%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "hell%y%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%z%hell"), strings::bad_format); + } + { + REQUIRE_THROWS_AS(strings::rformat(nullptr), strings::bad_format); + + REQUIRE_THROWS_AS(strings::rformat("%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::rformat("%hell"), strings::bad_format); + REQUIRE_THROWS_AS(strings::rformat("he%ll"), strings::bad_format); + REQUIRE_THROWS_AS(strings::rformat("hell%"), strings::bad_format); + + REQUIRE_THROWS_AS(strings::rformat("%10%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::rformat("hell%10%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::rformat("%10%hell"), strings::bad_format); + + REQUIRE_THROWS_AS(strings::rformat("%x%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::rformat("hell%y%"), strings::bad_format); + REQUIRE_THROWS_AS(strings::rformat("%z%hell"), strings::bad_format); + } + { + char buf[1]; + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE(strings::format(buf, sizeof(buf), "") == 0); + REQUIRE(str(buf) == str("")); + } + { + char buf[6]; + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE(strings::format(buf, sizeof(buf), "hello") == 5); + REQUIRE(strings::format(nullptr, 0, "hello") == 5); + REQUIRE(str(buf) == str("hello")); + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE(strings::format(buf, sizeof(buf), "hell%%") == 5); + REQUIRE(strings::format(nullptr, 0, "hell%%") == 5); + REQUIRE(str(buf) == str("hell%")); + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE(strings::format(buf, sizeof(buf), "%%hell") == 5); + REQUIRE(strings::format(nullptr, 0, "%%hell") == 5); + REQUIRE(str(buf) == str("%hell")); + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE(strings::format(buf, sizeof(buf), "he%%ll") == 5); + REQUIRE(strings::format(nullptr, 0, "he%%ll") == 5); + REQUIRE(str(buf) == str("he%ll")); + } + { + char buf[5]; + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "hello"), strings::bad_format_buffer); + REQUIRE(str(buf) == str("hell")); + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "he%"), strings::bad_format); + REQUIRE(str(buf) == str("he")); + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "he%99%"), strings::bad_format); + REQUIRE(str(buf) == str("he")); + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "he%x%"), strings::bad_format); + REQUIRE(str(buf) == str("he")); + + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "he%0%", 1234), strings::bad_format_buffer); + REQUIRE(str(buf) == str("he12")); + } + { + char buf[10]; + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%0%", "hello world"), strings::bad_format_buffer); + REQUIRE(str(buf) == str("hello wor")); + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "test%0%", "hello world"), strings::bad_format_buffer); + REQUIRE(str(buf) == str("testhello")); + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "%0%test", "hello world"), strings::bad_format_buffer); + REQUIRE(str(buf) == str("hello wor")); + std::memset(buf, 0xAB, sizeof(buf)); + REQUIRE_THROWS_AS(strings::format(buf, sizeof(buf), "te%0%st", "hello world"), strings::bad_format_buffer); + REQUIRE(str(buf) == str("tehello w")); + } + { + REQUIRE(strings::rformat("%0 %1 %2", "hello", "world", 5) == str("hello world 5")); + REQUIRE(strings::rformat("%1 %0 %2", "hello", "world", 5) == str("world hello 5")); + REQUIRE(strings::rformat("%2 %1 %0", "hello", "world", 5) == str("5 world hello")); + REQUIRE(strings::rformat("%0 %0 %1", "hello", "world", 5) == str("hello hello world")); + REQUIRE(strings::rformat("%2 %1 %1", "hello", "world", 5) == str("5 world world")); + + REQUIRE( + strings::rformat( + "%0 %2 %1 %4 %3 %6 %7 %5 %8 %9", + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) == + str("0 2 1 4 3 6 7 5 8 9")); + } + { + REQUIRE(strings::rformat("%0", strings::make_format_arg(-5, 3)) == " -5"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(-5, 4)) == " -5"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(21, 1)) == "21"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(21, 2)) == "21"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(42, 3)) == " 42"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(42u, 3)) == " 42"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(1.23f)) == "1.230000"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(1.23f,0)) == "1.230000"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(1.23f,0,2)) == "1.23"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(1.23f,5,2)) == " 1.23"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(true)) == "true"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(false)) == "false"); + + const char* s0 = "hello"; + char s1[64]; + std::strcpy(s1, "world"); + REQUIRE(strings::rformat("%0", s0) == "hello"); + REQUIRE(strings::rformat("%0", s1) == "world"); + } + { + REQUIRE( + strings::rformat("%0", std::numeric_limits::max()) == + std::to_string(std::numeric_limits::max())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::min()) == + std::to_string(std::numeric_limits::min())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::max()) == + std::to_string(std::numeric_limits::max())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::min()) == + std::to_string(std::numeric_limits::min())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::max()) == + std::to_string(std::numeric_limits::max())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::max()) == + std::to_string(std::numeric_limits::max())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::min()) == + std::to_string(std::numeric_limits::min())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::max()) == + std::to_string(std::numeric_limits::max())); + REQUIRE( + strings::rformat("%0", std::numeric_limits::min()) == + std::to_string(std::numeric_limits::min())); + } + SECTION("performance") { + std::printf("-= strings::performance tests =-\n"); + #if defined(E2D_BUILD_MODE) && E2D_BUILD_MODE == E2D_BUILD_MODE_DEBUG + const std::size_t task_n = 100'000; + #else + const std::size_t task_n = 1'000'000; + #endif + { + std::size_t result = 0; + e2d_untests::verbose_profiler_ms p("format(int, int)"); + for ( std::size_t i = 0; i < task_n; ++i ) { + char buffer[128]; + result += strings::format(buffer, sizeof(buffer), "hello %0 world %1 !", 1000, 123); + } + p.done(result); + } + { + std::ptrdiff_t result = 0; + e2d_untests::verbose_profiler_ms p("snprintf(int, int)"); + for ( std::size_t i = 0; i < task_n; ++i ) { + char buffer[128]; + result += std::snprintf(buffer, sizeof(buffer), "hello %i world %i !", 1000, 123); + } + p.done(result); + } + { + std::ptrdiff_t result = 0; + e2d_untests::verbose_profiler_ms p("format(float, float)"); + for ( std::size_t i = 0; i < task_n; ++i ) { + char buffer[128]; + result += strings::format(buffer, sizeof(buffer), "hello %0 world %1 !", 1000.f, 123.f); + } + p.done(result); + } + { + std::ptrdiff_t result = 0; + e2d_untests::verbose_profiler_ms p("snprintf(float, float)"); + for ( std::size_t i = 0; i < task_n; ++i ) { + char buffer[128]; + result += std::snprintf(buffer, sizeof(buffer), "hello %f world %f !", 1000.0, 123.0); + } + p.done(result); + } + { + std::ptrdiff_t result = 0; + e2d_untests::verbose_profiler_ms p("format(const char*)"); + for ( std::size_t i = 0; i < task_n; ++i ) { + char buffer[128]; + result += strings::format(buffer, sizeof(buffer), "hello %0 world %1 !", "foo", "bar"); + } + p.done(result); + } + { + std::ptrdiff_t result = 0; + e2d_untests::verbose_profiler_ms p("snprintf(const char*)"); + for ( std::size_t i = 0; i < task_n; ++i ) { + char buffer[128]; + result += std::snprintf(buffer, sizeof(buffer), "hello %s world %s !", "foo", "bar"); + } + p.done(result); + } + } +} diff --git a/untests/sources/untests_utils/time.cpp b/untests/sources/untests_utils/time.cpp new file mode 100644 index 00000000..0d2b9ae6 --- /dev/null +++ b/untests/sources/untests_utils/time.cpp @@ -0,0 +1,56 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_utils.hpp" +using namespace e2d; + +TEST_CASE("time") { + { + REQUIRE(time::second().value == 1); + REQUIRE(time::minute().value == 60); + REQUIRE(time::hour().value == 60 * 60); + } + { + REQUIRE(make_seconds(1).convert_to().value == 1000); + REQUIRE(make_seconds(1).convert_to().value == 1000000); + + REQUIRE(make_milliseconds(1000).convert_to().value == 1); + REQUIRE(make_milliseconds(1000).convert_to().value == 1000000); + + REQUIRE(make_microseconds(1000000).convert_to().value == 1); + REQUIRE(make_microseconds(1000000).convert_to().value == 1000); + } + { + namespace ch = std::chrono; + + REQUIRE(time::to_chrono(make_seconds(2)) == ch::seconds(2)); + REQUIRE(time::to_chrono(make_milliseconds(3)) == ch::milliseconds(3)); + REQUIRE(time::to_chrono(make_microseconds(4)) == ch::microseconds(4)); + + REQUIRE(time::to_chrono(make_seconds(2)) == ch::seconds(2)); + REQUIRE(time::to_chrono(make_milliseconds(3)) == ch::milliseconds(3)); + REQUIRE(time::to_chrono(make_microseconds(4)) == ch::microseconds(4)); + } + { + REQUIRE(time::to_seconds(make_seconds(2)).value == 2); + REQUIRE(time::to_milliseconds(make_seconds(2)).value == 2000); + REQUIRE(time::to_microseconds(make_seconds(2)).value == 2000000); + + REQUIRE(time::to_seconds(make_milliseconds(2000)).value == 2); + REQUIRE(time::to_milliseconds(make_milliseconds(2000)).value == 2000); + REQUIRE(time::to_microseconds(make_milliseconds(2000)).value == 2000000); + + REQUIRE(time::to_seconds(make_microseconds(2000000)).value == 2); + REQUIRE(time::to_milliseconds(make_microseconds(2000000)).value == 2000); + REQUIRE(time::to_microseconds(make_microseconds(2000000)).value == 2000000); + } + { + auto b = time::now(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto e = time::now(); + REQUIRE(e > b); + } +}