├── .clang-format ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── TestHelper.cpp ├── TestHelper.h ├── Zmeya.h ├── ZmeyaTest01.cpp ├── ZmeyaTest02.cpp ├── ZmeyaTest03.cpp ├── ZmeyaTest04.cpp ├── ZmeyaTest05.cpp ├── ZmeyaTest06.cpp ├── ZmeyaTest07.cpp ├── ZmeyaTest08.cpp ├── ZmeyaTest09.cpp ├── ZmeyaTest10.cpp ├── ZmeyaTest11.cpp ├── appveyor.yml ├── build.cmd ├── gen19.cmd └── gen22.cmd /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | 3 | ColumnLimit: 140 4 | PointerAlignment: Left 5 | BreakBeforeBraces: Allman 6 | BreakConstructorInitializers: BeforeComma 7 | BreakInheritanceList: BeforeComma 8 | IndentWidth: 4 9 | TabWidth: 4 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-unix: 7 | strategy: 8 | matrix: 9 | os: [ubuntu, macos] 10 | compiler: [g++, clang++] 11 | defines: [standard] 12 | exclude: 13 | - os: macos 14 | compiler: g++ 15 | name: ${{matrix.os}} ${{matrix.compiler}} 16 | runs-on: ${{matrix.os}}-latest 17 | steps: 18 | - uses: actions/checkout@v1 19 | - name: Update submodules 20 | run: git submodule update --init --recursive 21 | - name: CMake Configure 22 | run: mkdir build && cd build && cmake .. 23 | - name: Build 24 | run: cd build && cmake --build . --config Release 25 | - name: Run unit tests 26 | run: cd build && ./ZmeyaTest 27 | build-windows: 28 | runs-on: windows-latest 29 | strategy: 30 | matrix: 31 | arch: [Win32, x64] 32 | defines: [standard] 33 | steps: 34 | - uses: actions/checkout@v1 35 | - name: Update submodules 36 | run: git submodule update --init --recursive 37 | - name: CMake Configure 38 | run: mkdir build && cd build && cmake .. 39 | - name: Build 40 | run: cd build && cmake --build . --config Release 41 | - name: Run unit tests 42 | run: build/Release/ZmeyaTest.exe 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build2019 2 | build2022 3 | build 4 | *.log 5 | *.zm 6 | CoverageReport-* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/googletest"] 2 | path = extern/googletest 3 | url = https://github.com/google/googletest.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | set(PROJ_NAME ZmeyaTest) 3 | project(${PROJ_NAME}) 4 | 5 | set (CMAKE_CXX_STANDARD 17) 6 | 7 | add_definitions(-DZMEYA_ENABLE_SERIALIZE_SUPPORT) 8 | 9 | set(ZMEYA_SOURCES 10 | TestHelper.h 11 | TestHelper.cpp 12 | ZmeyaTest01.cpp 13 | ZmeyaTest02.cpp 14 | ZmeyaTest03.cpp 15 | ZmeyaTest04.cpp 16 | ZmeyaTest05.cpp 17 | ZmeyaTest06.cpp 18 | ZmeyaTest07.cpp 19 | ZmeyaTest08.cpp 20 | ZmeyaTest09.cpp 21 | ZmeyaTest10.cpp 22 | ZmeyaTest11.cpp 23 | Zmeya.h 24 | ) 25 | 26 | add_executable(${PROJ_NAME} ${ZMEYA_SOURCES}) 27 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJ_NAME}) 28 | 29 | if(MSVC) 30 | target_compile_options(${PROJ_NAME} PRIVATE /W4 /WX) 31 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 32 | else() 33 | target_compile_options(${PROJ_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) 34 | endif() 35 | 36 | # force shared crt 37 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 38 | 39 | # add gtest 40 | add_subdirectory("${PROJECT_SOURCE_DIR}/extern/googletest" "extern/googletest") 41 | target_link_libraries(${PROJ_NAME} gtest_main) 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2025 Sergey Makeev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zmeya 2 | 3 | [![Actions Status](https://github.com/SergeyMakeev/Zmeya/workflows/build/badge.svg)](https://github.com/SergeyMakeev/Zmeya/actions) 4 | [![codecov](https://codecov.io/gh/SergeyMakeev/Zmeya/graph/badge.svg?token=V07QJQX2NT)](https://codecov.io/gh/SergeyMakeev/Zmeya) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/qllqgshfy9cjme2q?svg=true)](https://ci.appveyor.com/project/SergeyMakeev/zmeya) 6 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 7 | 8 | Zmeya is a header-only C++ binary serialization library designed for games and performance-critical applications. 9 | Zmeya is not even a serialization library in the usual sense but rather a set of STL-like containers that entirely agnostic for their memory location and movable. As long as you use Zmeya data structures + other trivially_copyable, everything just works, and there is no deserialization cost. You can memory-map a serialized file and immediately start using your data. There are no pointers fixup, nor any other parsing/decoding needed. You can also serialize your data and send it over the network, there is no deserialization cost on the receiver side, or you can even use Zmeya for interprocess communication. 10 | 11 | # Features 12 | 13 | - Cross-platform compatible 14 | - Single header library (~550 lines of code for deserialization and extra 750 lines of code with serialization support enabled) 15 | - No code generation required: no IDL or metadata, just use your types directly 16 | - No macros 17 | - Heavily optimized for performance 18 | - No dependencies 19 | - Zmeya pointers are always 32-bits (configurable) regardless of the target platform pointer size 20 | 21 | Zmeya library offering the following memory movable types 22 | - `Pointer` 23 | - `Array` 24 | - `String` 25 | - `HashSet` 26 | - `HashMap` 27 | 28 | # Usage 29 | 30 | Include the header file, and you are all set. 31 | 32 | # Usage example 33 | 34 | Here is a simple usage example. 35 | 36 | ```cpp 37 | #include "Zmeya.h" 38 | 39 | struct Test 40 | { 41 | uint32_t someVar; 42 | zm::String name; 43 | zm::Pointer ptr; 44 | zm::Array arr; 45 | zm::HashMap hashMap; 46 | }; 47 | 48 | int main() 49 | { 50 | // load binary file to memory (using fread, mmap, etc) 51 | // no parsing/decoding needed 52 | const Test* test = (const Test*)loadBytesFromDisk("binaryFile.zm"); 53 | 54 | // use your loaded data 55 | printf("%s\n", test->name.c_str()); 56 | for(const zm::String& str : test->arr) 57 | { 58 | printf("%s\n",str.c_str()); 59 | } 60 | printf("key = %3.2f\n", test->hashMap.find("key", 0.0f)); 61 | return 0; 62 | } 63 | ``` 64 | 65 | You can always find more usage examples looking into unit test files. They are organized in a way to covers all Zmeya features and shows common usage patterns. 66 | 67 | # How it works 68 | 69 | Zmeya movable containers' key idea is to use self-relative pointers instead of using “absolute” pointers provided by C++ by default. 70 | The idea is pretty simple; instead of using the absolute address, we are using offset relative to the pointer's memory address. 71 | i.e., `target_address = uintptr_t(this) + offset` 72 | 73 | Which is perfectly representable by one of x86/ARM addressing modes `addr = reg+reg` 74 | Here is an example of generated assembly code 75 | https://godbolt.org/z/aTTW9E7o9 76 | https://godbolt.org/z/xEqTYe44j 77 | 78 | 79 | One of the problems of such offset-based addressing is the representation of the null pointer. The null pointer can't be safely represented like an offset since the absolute address 0 is always outside of the mapped region. So we decided to use offset 0 (pointer to self) as a special magic value that encodes null pointer. 80 | 81 | ```cpp 82 | #include 83 | 84 | template 85 | struct OffsetPtr { 86 | int32_t offset; 87 | T* get() const noexcept { 88 | return reinterpret_cast(uintptr_t(this) + offset); 89 | } 90 | }; 91 | 92 | template 93 | struct Ptr { 94 | T* ptr; 95 | T* get() const noexcept { 96 | return ptr; 97 | } 98 | }; 99 | 100 | int test1(const OffsetPtr& ptr) { 101 | return *ptr.get(); 102 | } 103 | 104 | int test2(const Ptr& ptr) { 105 | return *ptr.get(); 106 | } 107 | ``` 108 | 109 | ```asm 110 | test1(OffsetPtr const&): 111 | movsx rax, DWORD PTR [rdi] 112 | mov eax, DWORD PTR [rax+rdi] 113 | ret 114 | 115 | test2(Ptr const&): 116 | mov rax, QWORD PTR [rdi] 117 | mov eax, DWORD PTR [rax] 118 | ret 119 | ``` 120 | 121 | So there is no extra overhead from using such pointers in comparison with traditional pointers. 122 | 123 | All other Zmeya containers are pretty much based on the same principles. 124 | i.e. 125 | `zm::String` is a self-relative pointer to `const char*` 126 | `zm::Array` is a self-relative pointer to data + size 127 | `zm::HashSet` is made using two arrays (buckets and values) 128 | etc... 129 | 130 | The only requirement is that we have to have all the data tightly packed in a single memory region or binary blob. 131 | Zmeya provides a convenient mechanism to build such a binary blob called `zm::BlobBuilder`. 132 | Blob builder is capable of convert all the standard STL containers to appropriate Zmeya movable containers. Blob builder also provides a mechanism to convert all the inner types (e.g., `std::vector`) to Zmeya compatible type. And by default, Zmeya offers convertors/template specializations for all commonly used cases. 133 | 134 | # References 135 | 136 | Boost::offset_ptr 137 | https://www.boost.org/doc/libs/1_75_0/doc/html/interprocess/offset_ptr.html 138 | 139 | Handmade Hero forum thread 140 | https://hero.handmade.network/forums/code-discussion/t/487-serialization_techniques_with_memory_pooling 141 | 142 | 143 | Relative Pointers article by Ginger Bill 144 | https://www.gingerbill.org/article/2020/05/17/relative-pointers/ 145 | 146 | "The Blob and I" by Niklas Gray 147 | https://bitsquid.blogspot.com/2010/02/blob-and-i.html 148 | 149 | FlatBuffers by Google 150 | https://google.github.io/flatbuffers/ 151 | 152 | Physics Optimization Strategies by Sergiy Migdalskiy (slides 56-68) 153 | http://media.steampowered.com/apps/valve/2015/Migdalskiy_Sergiy_Physics_Optimization_Strategies.pdf 154 | 155 | https://youtu.be/Nsf2_Au6KxU?t=1542 156 | 157 | 158 | Relative Pointers by Jonathan Blow 159 | https://www.youtube.com/watch?v=Z0tsNFZLxSU 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /TestHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | 3 | namespace utils 4 | { 5 | 6 | std::vector copyBytes(zm::Span from) 7 | { 8 | std::vector res; 9 | res.resize(from.size); 10 | for (size_t i = 0; i < from.size; i++) 11 | { 12 | res[i] = from.data[i]; 13 | } 14 | return res; 15 | } 16 | 17 | } // namespace Utils -------------------------------------------------------------------------------- /TestHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Zmeya.h" 3 | 4 | namespace utils 5 | { 6 | std::vector copyBytes(zm::Span from); 7 | } 8 | -------------------------------------------------------------------------------- /Zmeya.h: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021-2025 Sergey Makeev 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | #pragma once 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | // If this is not defined, the built-in hash will be used instead of the user-provided hash 37 | // ZMEYA_EXTERNAL_HASH 38 | // 39 | // 40 | // Override built-in memory allocation functionbs 41 | // ZMEYA_ALLOC 42 | // ZMEYA_FREE 43 | // 44 | // 45 | // This macro is only necessary if you want to create serializable data. 46 | // If you only need to read Zmeya blobs, you don't need it. 47 | // ZMEYA_ENABLE_SERIALIZE_SUPPORT 48 | // 49 | // 50 | // To override NODISCARD 51 | // ZMEYA_NODISCARD 52 | // 53 | // 54 | // To override FALLTHROUGH 55 | // ZMEYA_FALLTHROUGH 56 | // 57 | // 58 | // To override ASSERT 59 | // ZMEYA_ASSERT 60 | // 61 | // 62 | 63 | #if !defined(ZMEYA_ALLOC) || !defined(ZMEYA_FREE) 64 | #if defined(_WIN32) 65 | // Windows 66 | #include 67 | #define ZMEYA_ALLOC(sizeInBytes, alignment) _mm_malloc(sizeInBytes, alignment) 68 | #define ZMEYA_FREE(ptr) _mm_free(ptr) 69 | #elif defined(__ANDROID__) 70 | // Android 71 | #include 72 | #define ZMEYA_ALLOC(sizeInBytes, alignment) memalign(alignment, sizeInBytes); 73 | #define ZMEYA_FREE(ptr) free(ptr) 74 | #else 75 | // Posix 76 | #include 77 | inline void* alloc_aligned_posix(size_t sizeInBytes, size_t alignment) 78 | { 79 | void* ptr = nullptr; 80 | if (posix_memalign(&ptr, alignment, sizeInBytes) != 0) 81 | { 82 | return nullptr; 83 | } 84 | return ptr; 85 | } 86 | #define ZMEYA_ALLOC(sizeInBytes, alignment) alloc_aligned_posix(sizeInBytes, alignment) 87 | #define ZMEYA_FREE(ptr) free(ptr) 88 | #endif 89 | #endif 90 | 91 | #ifndef ZMEYA_ASSERT 92 | #define ZMEYA_ASSERT(cond) assert(cond) 93 | #endif 94 | 95 | #ifndef ZMEYA_NODISCARD 96 | #if __cplusplus >= 201703L 97 | #define ZMEYA_NODISCARD [[nodiscard]] 98 | #else 99 | #define ZMEYA_NODISCARD 100 | #endif 101 | #endif 102 | 103 | #ifndef ZMEYA_FALLTHROUGH 104 | #if __cplusplus >= 201703L 105 | #define ZMEYA_FALLTHROUGH [[fallthrough]] 106 | #else 107 | #define ZMEYA_FALLTHROUGH 108 | #endif 109 | #endif 110 | 111 | #define ZMEYA_MAX_ALIGN (64) 112 | 113 | #ifdef _DEBUG 114 | #define ZMEYA_VALIDATE_HASH_DUPLICATES 115 | #endif 116 | 117 | namespace zm 118 | { 119 | 120 | #define ZMEYA_MURMURHASH_MAGIC64A 0xc6a4a7935bd1e995LLU 121 | 122 | inline uint64_t murmur_hash_process64a(const char* key, uint32_t len, uint64_t seed) 123 | { 124 | const uint64_t m = ZMEYA_MURMURHASH_MAGIC64A; 125 | const int r = 47; 126 | 127 | uint64_t h = seed ^ (len * m); 128 | 129 | const uint64_t* data = (const uint64_t*)key; 130 | const uint64_t* end = data + (len / 8); 131 | 132 | while (data != end) 133 | { 134 | uint64_t k = *data++; 135 | 136 | k *= m; 137 | k ^= k >> r; 138 | k *= m; 139 | 140 | h ^= k; 141 | h *= m; 142 | } 143 | 144 | const unsigned char* data2 = (const unsigned char*)data; 145 | 146 | switch (len & 7) 147 | { 148 | case 7: 149 | h ^= (uint64_t)((uint64_t)data2[6] << (uint64_t)48); 150 | ZMEYA_FALLTHROUGH; 151 | case 6: 152 | h ^= (uint64_t)((uint64_t)data2[5] << (uint64_t)40); 153 | ZMEYA_FALLTHROUGH; 154 | case 5: 155 | h ^= (uint64_t)((uint64_t)data2[4] << (uint64_t)32); 156 | ZMEYA_FALLTHROUGH; 157 | case 4: 158 | h ^= (uint64_t)((uint64_t)data2[3] << (uint64_t)24); 159 | ZMEYA_FALLTHROUGH; 160 | case 3: 161 | h ^= (uint64_t)((uint64_t)data2[2] << (uint64_t)16); 162 | ZMEYA_FALLTHROUGH; 163 | case 2: 164 | h ^= (uint64_t)((uint64_t)data2[1] << (uint64_t)8); 165 | ZMEYA_FALLTHROUGH; 166 | case 1: 167 | h ^= (uint64_t)((uint64_t)data2[0]); 168 | h *= m; 169 | }; 170 | 171 | h ^= h >> r; 172 | h *= m; 173 | h ^= h >> r; 174 | return h; 175 | } 176 | 177 | #undef ZMEYA_MURMURHASH_MAGIC64A 178 | 179 | #ifndef ZMEYA_EXTERNAL_HASH 180 | // 181 | // Functions and types to interact with the application 182 | // Feel free to replace them to your engine specific functions 183 | // 184 | namespace HashUtils 185 | { 186 | 187 | // hasher 188 | template ZMEYA_NODISCARD inline size_t hasher(const T& v) { return std::hash{}(v); } 189 | 190 | // string hasher 191 | ZMEYA_NODISCARD inline size_t hashString(const char* str) 192 | { 193 | size_t len = std::strlen(str); 194 | uint64_t hash = zm::murmur_hash_process64a(str, uint32_t(len), 13061979); 195 | return size_t(hash); 196 | } 197 | } // namespace HashUtils 198 | #endif 199 | 200 | // absolute offset/difference type 201 | using offset_t = std::uintptr_t; 202 | using diff_t = std::ptrdiff_t; 203 | // relative offset type 204 | using roffset_t = int32_t; 205 | 206 | ZMEYA_NODISCARD inline offset_t toAbsolute(offset_t base, roffset_t offset) 207 | { 208 | offset_t res = base + diff_t(offset); 209 | return res; 210 | } 211 | 212 | ZMEYA_NODISCARD inline uintptr_t toAbsoluteAddr(uintptr_t base, roffset_t offset) 213 | { 214 | uintptr_t res = base + ptrdiff_t(offset); 215 | return res; 216 | } 217 | 218 | #ifdef ZMEYA_ENABLE_SERIALIZE_SUPPORT 219 | template class BlobPtr; 220 | #endif 221 | 222 | /* 223 | Pointer - self-relative pointer relative to its own memory address 224 | */ 225 | template class Pointer 226 | { 227 | // addr = this + offset 228 | // offset(0) = this = nullptr (here is the limitation, pointer can't point to itself) 229 | // this extra offset fits well into the x86/ARM addressing modes 230 | // see for details https://godbolt.org/z/aTTW9E7o9 231 | roffset_t relativeOffset; 232 | 233 | bool isEqual(const Pointer& other) const noexcept { return get() == other.get(); } 234 | 235 | ZMEYA_NODISCARD T* getUnsafe() const noexcept 236 | { 237 | uintptr_t self = uintptr_t(this); 238 | // dereferencing a NULL pointer is undefined behavior, so we can skip nullptr check 239 | ZMEYA_ASSERT(relativeOffset != 0); 240 | uintptr_t addr = toAbsoluteAddr(self, relativeOffset); 241 | return reinterpret_cast(addr); 242 | } 243 | 244 | public: 245 | Pointer() noexcept = default; 246 | // Pointer(const Pointer&) = delete; 247 | // Pointer& operator=(const Pointer&) = delete; 248 | 249 | ZMEYA_NODISCARD T* get() const noexcept 250 | { 251 | uintptr_t self = uintptr_t(this); 252 | uintptr_t addr = (relativeOffset == 0) ? uintptr_t(0) : toAbsoluteAddr(self, relativeOffset); 253 | return reinterpret_cast(addr); 254 | } 255 | 256 | #ifdef ZMEYA_ENABLE_SERIALIZE_SUPPORT 257 | Pointer& operator=(const BlobPtr& other); 258 | #endif 259 | 260 | // Note: implicit conversion operator 261 | // operator const T*() const noexcept { return get(); } 262 | // operator T*() noexcept { return get(); } 263 | 264 | ZMEYA_NODISCARD T* operator->() const noexcept { return getUnsafe(); } 265 | 266 | ZMEYA_NODISCARD T& operator*() const noexcept { return *(getUnsafe()); } 267 | 268 | ZMEYA_NODISCARD bool operator==(const Pointer& other) const noexcept { return isEqual(other); } 269 | ZMEYA_NODISCARD bool operator!=(const Pointer& other) const noexcept { return !isEqual(other); } 270 | 271 | operator bool() const noexcept { return relativeOffset != 0; } 272 | ZMEYA_NODISCARD bool operator==(std::nullptr_t) const noexcept { return relativeOffset == 0; } 273 | ZMEYA_NODISCARD bool operator!=(std::nullptr_t) const noexcept { return relativeOffset != 0; } 274 | 275 | friend class BlobBuilder; 276 | }; 277 | 278 | /* 279 | String 280 | */ 281 | class String 282 | { 283 | Pointer data; 284 | 285 | public: 286 | String() noexcept = default; 287 | // String(const String&) = delete; 288 | // String& operator=(const String&) = delete; 289 | 290 | bool isEqual(const char* s2) const noexcept 291 | { 292 | const char* s1 = c_str(); 293 | return (std::strcmp(s1, s2) == 0); 294 | } 295 | 296 | ZMEYA_NODISCARD const char* c_str() const noexcept 297 | { 298 | const char* v = data.get(); 299 | if (v != nullptr) 300 | { 301 | return v; 302 | } 303 | return ""; 304 | } 305 | 306 | ZMEYA_NODISCARD bool empty() const noexcept { return data.get() != nullptr; } 307 | ZMEYA_NODISCARD bool operator==(const String& other) const noexcept 308 | { 309 | // both strings can point to the same memory (fast-path) 310 | if (other.c_str() == c_str()) 311 | { 312 | return true; 313 | } 314 | return isEqual(other.c_str()); 315 | } 316 | ZMEYA_NODISCARD bool operator!=(const String& other) const noexcept 317 | { 318 | // both strings can point to the same memory (fast-path) 319 | if (other.c_str() == c_str()) 320 | { 321 | return false; 322 | } 323 | return !isEqual(other.c_str()); 324 | } 325 | 326 | friend class BlobBuilder; 327 | }; 328 | 329 | ZMEYA_NODISCARD inline bool operator==(const String& left, const char* const right) noexcept { return left.isEqual(right); } 330 | ZMEYA_NODISCARD inline bool operator!=(const String& left, const char* const right) noexcept { return !left.isEqual(right); } 331 | 332 | ZMEYA_NODISCARD inline bool operator==(const char* const left, const String& right) noexcept { return right.isEqual(left); } 333 | ZMEYA_NODISCARD inline bool operator!=(const char* const left, const String& right) noexcept { return !right.isEqual(left); } 334 | 335 | ZMEYA_NODISCARD inline bool operator==(const String& left, const std::string& right) noexcept { return left.isEqual(right.c_str()); } 336 | ZMEYA_NODISCARD inline bool operator!=(const String& left, const std::string& right) noexcept { return !left.isEqual(right.c_str()); } 337 | 338 | ZMEYA_NODISCARD inline bool operator==(const std::string& left, const String& right) noexcept { return right.isEqual(left.c_str()); } 339 | ZMEYA_NODISCARD inline bool operator!=(const std::string& left, const String& right) noexcept { return !right.isEqual(left.c_str()); } 340 | 341 | /* 342 | Array 343 | */ 344 | template class Array 345 | { 346 | roffset_t relativeOffset; 347 | uint32_t numElements; 348 | 349 | private: 350 | ZMEYA_NODISCARD const T* getConstData() const noexcept 351 | { 352 | uintptr_t addr = toAbsoluteAddr(uintptr_t(this), relativeOffset); 353 | return reinterpret_cast(addr); 354 | } 355 | 356 | ZMEYA_NODISCARD T* getData() const noexcept { return const_cast(getConstData()); } 357 | 358 | public: 359 | Array() noexcept = default; 360 | // Array(const Array&) = delete; 361 | // Array& operator=(const Array&) = delete; 362 | 363 | ZMEYA_NODISCARD size_t size() const noexcept { return size_t(numElements); } 364 | 365 | ZMEYA_NODISCARD const T& operator[](const size_t index) const noexcept 366 | { 367 | const T* data = getConstData(); 368 | return data[index]; 369 | } 370 | 371 | #ifdef ZMEYA_ENABLE_SERIALIZE_SUPPORT 372 | 373 | ZMEYA_NODISCARD T* get_element_ptr_unsafe_can_be_relocated(const size_t index) noexcept 374 | { 375 | T* data = getData(); 376 | return data + index; 377 | } 378 | 379 | ZMEYA_NODISCARD T* get_raw_ptr_unsafe_can_be_relocated() noexcept { return getData(); } 380 | #endif 381 | 382 | ZMEYA_NODISCARD const T* at(const size_t index) const 383 | { 384 | ZMEYA_ASSERT(index < size()); 385 | const T* data = getConstData(); 386 | return data[index]; 387 | } 388 | 389 | ZMEYA_NODISCARD const T* data() const noexcept { return getConstData(); } 390 | 391 | ZMEYA_NODISCARD const T* begin() const noexcept 392 | { 393 | const T* data = getConstData(); 394 | return data; 395 | }; 396 | 397 | ZMEYA_NODISCARD const T* end() const noexcept 398 | { 399 | const T* data = getConstData(); 400 | return data + size(); 401 | }; 402 | 403 | ZMEYA_NODISCARD bool empty() const noexcept { return size() == 0; } 404 | 405 | friend class BlobBuilder; 406 | }; 407 | 408 | /* 409 | 410 | Hash adapters 411 | 412 | */ 413 | 414 | // (key) generic adapter 415 | template struct HashKeyAdapterGeneric 416 | { 417 | typedef Item ItemType; 418 | static size_t hash(const ItemType& item) { return HashUtils::hasher(item); } 419 | static bool eq(const ItemType& a, const ItemType& b) { return a == b; } 420 | }; 421 | 422 | // (key) adapter for std::string 423 | struct HashKeyAdapterStdString 424 | { 425 | typedef std::string ItemType; 426 | static size_t hash(const ItemType& item) { return HashUtils::hashString(item.c_str()); } 427 | static bool eq(const ItemType& a, const ItemType& b) { return a == b; } 428 | }; 429 | 430 | // (key,value) generic adapter 431 | template struct HashKeyValueAdapterGeneric 432 | { 433 | typedef Item ItemType; 434 | static size_t hash(const ItemType& item) { return HashUtils::hasher(item.first); } 435 | static bool eq(const ItemType& a, const ItemType& b) { return a.first == b.first; } 436 | }; 437 | 438 | // (key,value) adapter for std::string 439 | template struct HashKeyValueAdapterStdString 440 | { 441 | typedef std::pair ItemType; 442 | static size_t hash(const ItemType& item) { return HashUtils::hashString(item.first.c_str()); } 443 | static bool eq(const ItemType& a, const ItemType& b) { return a.first == b.first; } 444 | }; 445 | 446 | // (key) adapter for String and null-terminated c strings >> SearchAdapterCStrToString 447 | // used only for search 448 | struct HashKeyAdapterCStr 449 | { 450 | typedef const char* ItemType; 451 | static size_t hash(const ItemType& item) { return HashUtils::hashString(item); } 452 | static bool eq(const String& a, const ItemType& b) { return a == b; } 453 | }; 454 | 455 | /* 456 | HashSet 457 | */ 458 | template class HashSet 459 | { 460 | public: 461 | typedef Key Item; 462 | struct Bucket 463 | { 464 | uint32_t beginIndex; 465 | uint32_t endIndex; 466 | }; 467 | Array buckets; 468 | Array items; 469 | 470 | template ZMEYA_NODISCARD bool containsImpl(const Key2& key) const noexcept 471 | { 472 | size_t numBuckets = buckets.size(); 473 | if (numBuckets == 0) 474 | { 475 | return false; 476 | } 477 | size_t hashMod = numBuckets; 478 | size_t hash = Adapter::hash(key); 479 | size_t bucketIndex = hash % hashMod; 480 | const Bucket& bucket = buckets[bucketIndex]; 481 | for (size_t i = bucket.beginIndex; i < bucket.endIndex; i++) 482 | { 483 | const Key& item = items[i]; 484 | if (Adapter::eq(item, key)) 485 | { 486 | return true; 487 | } 488 | } 489 | return false; 490 | } 491 | 492 | public: 493 | HashSet() noexcept = default; 494 | // HashSet(const HashSet&) = delete; 495 | // HashSet& operator=(const HashSet&) = delete; 496 | 497 | ZMEYA_NODISCARD size_t size() const noexcept { return items.size(); } 498 | 499 | ZMEYA_NODISCARD bool empty() const noexcept { return items.empty(); } 500 | 501 | ZMEYA_NODISCARD const Item* begin() const noexcept { return items.begin(); } 502 | 503 | ZMEYA_NODISCARD const Item* end() const noexcept { return items.end(); } 504 | 505 | ZMEYA_NODISCARD bool contains(const char* key) const noexcept 506 | { 507 | static_assert(std::is_same::value, "To use this function, the key type must be Zmeya::String"); 508 | return containsImpl(key); 509 | } 510 | 511 | ZMEYA_NODISCARD bool contains(const Key& key) const noexcept { return containsImpl>(key); } 512 | friend class BlobBuilder; 513 | }; 514 | 515 | /* 516 | Pair 517 | */ 518 | template struct Pair 519 | { 520 | T1 first; 521 | T2 second; 522 | 523 | Pair() noexcept = default; 524 | 525 | Pair(const T1& t1, const T2& t2) noexcept 526 | : first(t1) 527 | , second(t2) 528 | { 529 | } 530 | }; 531 | 532 | /* 533 | HashMap 534 | */ 535 | template class HashMap 536 | { 537 | typedef Pair Item; 538 | struct Bucket 539 | { 540 | uint32_t beginIndex; 541 | uint32_t endIndex; 542 | }; 543 | Array buckets; 544 | Array items; 545 | 546 | template ZMEYA_NODISCARD const Value* findImpl(const Key2& key) const noexcept 547 | { 548 | size_t numBuckets = buckets.size(); 549 | if (numBuckets == 0) 550 | { 551 | return nullptr; 552 | } 553 | size_t hashMod = numBuckets; 554 | size_t hash = Adapter::hash(key); 555 | size_t bucketIndex = hash % hashMod; 556 | const Bucket& bucket = buckets[bucketIndex]; 557 | for (size_t i = bucket.beginIndex; i < bucket.endIndex; i++) 558 | { 559 | const Item& item = items[i]; 560 | if (Adapter::eq(item.first, key)) 561 | { 562 | return &item.second; 563 | } 564 | } 565 | return nullptr; 566 | } 567 | 568 | public: 569 | HashMap() noexcept = default; 570 | // HashMap(const HashMap&) = delete; 571 | // HashMap& operator=(const HashMap&) = delete; 572 | 573 | ZMEYA_NODISCARD size_t size() const noexcept { return items.size(); } 574 | 575 | ZMEYA_NODISCARD bool empty() const noexcept { return items.empty(); } 576 | 577 | ZMEYA_NODISCARD const Item* begin() const noexcept { return items.begin(); } 578 | 579 | ZMEYA_NODISCARD const Item* end() const noexcept { return items.end(); } 580 | 581 | ZMEYA_NODISCARD bool contains(const Key& key) const noexcept { return find(key) != nullptr; } 582 | 583 | ZMEYA_NODISCARD Value* find(const Key& key) noexcept 584 | { 585 | typedef HashKeyAdapterGeneric Adapter; 586 | 587 | const Value* res = findImpl(key); 588 | return res ? const_cast(res) : nullptr; 589 | } 590 | ZMEYA_NODISCARD const Value* find(const Key& key) const noexcept 591 | { 592 | typedef HashKeyAdapterGeneric Adapter; 593 | 594 | const Value* res = findImpl(key); 595 | return res; 596 | } 597 | 598 | ZMEYA_NODISCARD const Value& find(const Key& key, const Value& valueIfNotFound) const noexcept 599 | { 600 | typedef HashKeyAdapterGeneric Adapter; 601 | 602 | const Value* res = findImpl(key); 603 | if (res) 604 | { 605 | return *res; 606 | } 607 | return valueIfNotFound; 608 | } 609 | 610 | ZMEYA_NODISCARD bool contains(const char* key) const noexcept 611 | { 612 | static_assert(std::is_same::value, "To use this function, the key type must be Zmeya::String"); 613 | return find(key) != nullptr; 614 | } 615 | 616 | ZMEYA_NODISCARD Value* find(const char* key) noexcept 617 | { 618 | static_assert(std::is_same::value, "To use this function, the key type must be Zmeya::String"); 619 | typedef HashKeyAdapterCStr Adapter; 620 | 621 | const Value* res = findImpl(key); 622 | return res ? const_cast(res) : nullptr; 623 | } 624 | ZMEYA_NODISCARD const Value* find(const char* key) const noexcept 625 | { 626 | static_assert(std::is_same::value, "To use this function, the key type must be Zmeya::String"); 627 | typedef HashKeyAdapterCStr Adapter; 628 | 629 | const Value* res = findImpl(key); 630 | return res; 631 | } 632 | 633 | ZMEYA_NODISCARD const Value& find(const char* key, const Value& valueIfNotFound) const noexcept 634 | { 635 | static_assert(std::is_same::value, "To use this function, the key type must be Zmeya::String"); 636 | typedef HashKeyAdapterCStr Adapter; 637 | 638 | const Value* res = findImpl(key); 639 | if (res) 640 | { 641 | return *res; 642 | } 643 | return valueIfNotFound; 644 | } 645 | 646 | ZMEYA_NODISCARD const char* find(const Key& key, const char* valueIfNotFound) const noexcept 647 | { 648 | static_assert(std::is_same::value, "To use this function, the value type must be Zmeya::String"); 649 | typedef HashKeyAdapterGeneric Adapter; 650 | 651 | const Value* res = findImpl(key); 652 | if (res) 653 | { 654 | return res->c_str(); 655 | } 656 | return valueIfNotFound; 657 | } 658 | 659 | ZMEYA_NODISCARD const char* find(const char* key, const char* valueIfNotFound) const noexcept 660 | { 661 | static_assert(std::is_same::value, "To use this function, the key type must be Zmeya::String"); 662 | static_assert(std::is_same::value, "To use this function, the value type must be Zmeya::String"); 663 | typedef HashKeyAdapterCStr Adapter; 664 | 665 | const Value* res = findImpl(key); 666 | if (res) 667 | { 668 | return res->c_str(); 669 | } 670 | return valueIfNotFound; 671 | } 672 | 673 | friend class BlobBuilder; 674 | }; 675 | 676 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 677 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 678 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 679 | #ifdef ZMEYA_ENABLE_SERIALIZE_SUPPORT 680 | 681 | ZMEYA_NODISCARD inline diff_t diff(offset_t a, offset_t b) noexcept 682 | { 683 | diff_t res = a - b; 684 | return res; 685 | } 686 | 687 | ZMEYA_NODISCARD inline offset_t diffAddr(uintptr_t a, uintptr_t b) 688 | { 689 | ZMEYA_ASSERT(a >= b); 690 | uintptr_t res = a - b; 691 | ZMEYA_ASSERT(res <= uintptr_t(std::numeric_limits::max())); 692 | return offset_t(res); 693 | } 694 | 695 | ZMEYA_NODISCARD inline roffset_t toRelativeOffset(diff_t v) 696 | { 697 | ZMEYA_ASSERT(v >= diff_t(std::numeric_limits::min())); 698 | ZMEYA_ASSERT(v <= diff_t(std::numeric_limits::max())); 699 | return roffset_t(v); 700 | } 701 | 702 | constexpr bool inline isPowerOfTwo(size_t v) { return v && ((v & (v - 1)) == 0); } 703 | 704 | class BlobBuilder; 705 | 706 | /* 707 | This is a non-serializable pointer to blob internal memory 708 | Note: blob is able to relocate its own memory that's is why we cannot use 709 | standard pointers or references 710 | */ 711 | template class BlobPtr 712 | { 713 | std::weak_ptr blob; 714 | offset_t absoluteOffset = 0; 715 | 716 | bool isEqual(const BlobPtr& other) const 717 | { 718 | if (blob.lock() != other.blob.lock()) 719 | { 720 | return false; 721 | } 722 | return absoluteOffset == other.absoluteOffset; 723 | } 724 | 725 | public: 726 | explicit BlobPtr(std::weak_ptr&& _blob, offset_t _absoluteOffset) 727 | : blob(std::move(_blob)) 728 | , absoluteOffset(_absoluteOffset) 729 | { 730 | } 731 | 732 | BlobPtr() = default; 733 | 734 | BlobPtr(BlobPtr&&) = default; 735 | BlobPtr& operator=(BlobPtr&&) = default; 736 | BlobPtr(const BlobPtr&) = default; 737 | BlobPtr& operator=(const BlobPtr&) = default; 738 | 739 | template BlobPtr(const BlobPtr& other) 740 | { 741 | static_assert(std::is_convertible::value, "Uncompatible types"); 742 | blob = other.blob; 743 | absoluteOffset = other.absoluteOffset; 744 | } 745 | template friend class BlobPtr; 746 | 747 | offset_t getAbsoluteOffset() const { return absoluteOffset; } 748 | 749 | ZMEYA_NODISCARD T* get() const; 750 | 751 | T* operator->() const { return get(); } 752 | T& operator*() const { return *(get()); } 753 | 754 | operator bool() const { return get() != nullptr; } 755 | bool operator==(const BlobPtr& other) const { return isEqual(other); } 756 | bool operator!=(const BlobPtr& other) const { return !isEqual(other); } 757 | 758 | template friend class Pointer; 759 | }; 760 | 761 | /* 762 | Blob allocator - aligned allocator for internal Blob usage 763 | */ 764 | template class BlobBuilderAllocator : public std::allocator 765 | { 766 | public: 767 | typedef size_t size_type; 768 | typedef T* pointer; 769 | typedef const T* const_pointer; 770 | 771 | template struct rebind 772 | { 773 | typedef BlobBuilderAllocator<_Tp1, Alignment> other; 774 | }; 775 | 776 | pointer allocate(size_type n) 777 | { 778 | const size_t alignment = Alignment; 779 | void* const pv = ZMEYA_ALLOC(n * sizeof(T), alignment); 780 | return static_cast(pv); 781 | } 782 | 783 | void deallocate(pointer p, size_type) 784 | { 785 | // 786 | ZMEYA_FREE(p); 787 | } 788 | 789 | BlobBuilderAllocator() 790 | : std::allocator() 791 | { 792 | } 793 | BlobBuilderAllocator(const BlobBuilderAllocator& a) 794 | : std::allocator(a) 795 | { 796 | } 797 | template 798 | BlobBuilderAllocator(const BlobBuilderAllocator& a) 799 | : std::allocator(a) 800 | { 801 | } 802 | }; 803 | 804 | /* 805 | 806 | Span 807 | 808 | */ 809 | template struct Span 810 | { 811 | T* data = nullptr; 812 | size_t size = 0; 813 | 814 | Span() = default; 815 | Span(T* _data, size_t _size) 816 | : data(_data) 817 | , size(_size) 818 | { 819 | } 820 | }; 821 | 822 | template std::weak_ptr weak_from(T* p) 823 | { 824 | std::shared_ptr shared = p->shared_from_this(); 825 | return shared; 826 | } 827 | 828 | /* 829 | Blob - a binary blob of data that is able to store POD types and special 830 | "movable" data structures Note: Zmeya containers can be freely moved in 831 | memory and deserialize from raw bytes without any extra work. 832 | */ 833 | class BlobBuilder : public std::enable_shared_from_this 834 | { 835 | std::vector> data; 836 | 837 | private: 838 | ZMEYA_NODISCARD const char* get(offset_t absoluteOffset) const 839 | { 840 | ZMEYA_ASSERT(absoluteOffset < data.size()); 841 | return &data[absoluteOffset]; 842 | } 843 | 844 | template ZMEYA_NODISCARD BlobPtr getBlobPtr(const T* p) const 845 | { 846 | ZMEYA_ASSERT(containsPointer(p)); 847 | offset_t absoluteOffset = diffAddr(uintptr_t(p), uintptr_t(data.data())); 848 | return BlobPtr(weak_from(this), absoluteOffset); 849 | } 850 | 851 | struct PrivateToken 852 | { 853 | }; 854 | 855 | public: 856 | BlobBuilder() = delete; 857 | 858 | BlobBuilder(size_t initialSizeInBytes, PrivateToken) 859 | { 860 | static_assert(std::is_trivially_copyable>::value, "Pointer is_trivially_copyable check failed"); 861 | static_assert(std::is_trivially_copyable>::value, "Array is_trivially_copyable check failed"); 862 | static_assert(std::is_trivially_copyable>::value, "HashSet is_trivially_copyable check failed"); 863 | static_assert(std::is_trivially_copyable>::value, "Pair is_trivially_copyable check failed"); 864 | static_assert(std::is_trivially_copyable>::value, "HashMap is_trivially_copyable check failed"); 865 | static_assert(std::is_trivially_copyable::value, "String is_trivially_copyable check failed"); 866 | 867 | data.reserve(initialSizeInBytes); 868 | } 869 | 870 | ~BlobBuilder() = default; 871 | 872 | bool containsPointer(const void* p) const { return (!data.empty() && (p >= &data.front() && p <= &data.back())); } 873 | 874 | BlobPtr allocate(size_t numBytes, size_t alignment) 875 | { 876 | ZMEYA_ASSERT(isPowerOfTwo(alignment)); 877 | ZMEYA_ASSERT(alignment < ZMEYA_MAX_ALIGN); 878 | // 879 | size_t cursor = data.size(); 880 | 881 | // padding / alignment 882 | size_t off = cursor & (alignment - 1); 883 | size_t padding = 0; 884 | if (off != 0) 885 | { 886 | padding = alignment - off; 887 | } 888 | size_t absoluteOffset = cursor + padding; 889 | size_t numBytesToAllocate = numBytes + padding; 890 | 891 | // Allocate more memory 892 | // Note: new memory is filled with zeroes 893 | // Zmeya containers rely on this behavior and we want to have all the padding zeroed as well 894 | data.resize(data.size() + numBytesToAllocate, char(0)); 895 | 896 | // check alignment 897 | ZMEYA_ASSERT((uintptr_t(&data[absoluteOffset]) & (alignment - 1)) == 0); 898 | ZMEYA_ASSERT(absoluteOffset < size_t(std::numeric_limits::max())); 899 | return BlobPtr(weak_from(this), offset_t(absoluteOffset)); 900 | } 901 | 902 | template void placementCtor(void* ptr, _Valty&&... _Val) 903 | { 904 | ::new (const_cast(static_cast(ptr))) T(std::forward<_Valty>(_Val)...); 905 | } 906 | 907 | template BlobPtr allocate(_Valty&&... _Val) 908 | { 909 | // compile time checks 910 | static_assert(std::is_trivially_copyable::value, "Only trivially copyable types allowed"); 911 | constexpr size_t alignOfT = std::alignment_of::value; 912 | static_assert(isPowerOfTwo(alignOfT), "Non power of two alignment not supported"); 913 | static_assert(alignOfT < ZMEYA_MAX_ALIGN, "Unsupported alignment"); 914 | constexpr size_t sizeOfT = sizeof(T); 915 | 916 | BlobPtr ptr = allocate(sizeOfT, alignOfT); 917 | 918 | placementCtor(ptr.get(), std::forward<_Valty>(_Val)...); 919 | 920 | return BlobPtr(weak_from(this), ptr.getAbsoluteOffset()); 921 | } 922 | 923 | template T* getDirectMemoryAccessUnsafe(offset_t absoluteOffset) 924 | { 925 | const char* p = get(absoluteOffset); 926 | return const_cast(reinterpret_cast(p)); 927 | } 928 | 929 | template void setArrayOffset(const BlobPtr>& dst, offset_t absoluteOffset) 930 | { 931 | dst->relativeOffset = toRelativeOffset(diff(absoluteOffset, dst.getAbsoluteOffset())); 932 | } 933 | 934 | template offset_t resizeArrayWithoutInitialization(Array& _dst, size_t numElements) 935 | { 936 | constexpr size_t alignOfT = std::alignment_of::value; 937 | constexpr size_t sizeOfT = sizeof(T); 938 | static_assert((sizeOfT % alignOfT) == 0, "The size must be a multiple of the alignment"); 939 | 940 | BlobPtr> dst = getBlobPtr(&_dst); 941 | // An array can be assigned/resized only once (non empty array detected) 942 | ZMEYA_ASSERT(dst->relativeOffset == 0 && dst->numElements == 0); 943 | 944 | BlobPtr arrData = allocate(sizeOfT * numElements, alignOfT); 945 | ZMEYA_ASSERT(numElements < size_t(std::numeric_limits::max())); 946 | dst->numElements = uint32_t(numElements); 947 | setArrayOffset(dst, arrData.getAbsoluteOffset()); 948 | return arrData.getAbsoluteOffset(); 949 | } 950 | 951 | // get writeable pointer to array element 952 | template ZMEYA_NODISCARD BlobPtr getArrayElement(Array& arr, const size_t index) const noexcept 953 | { 954 | T* rawElementPtr = arr.getData() + index; 955 | BlobPtr element = getBlobPtr(rawElementPtr); 956 | return element; 957 | } 958 | 959 | // resize array (using copy constructor) 960 | template offset_t resizeArray(Array& _dst, size_t numElements, const T& emptyElement) 961 | { 962 | BlobPtr> dst = getBlobPtr(&_dst); 963 | offset_t absoluteOffset = resizeArrayWithoutInitialization(_dst, numElements); 964 | T* current = getDirectMemoryAccessUnsafe(absoluteOffset); 965 | for (size_t i = 0; i < numElements; i++) 966 | { 967 | // call copy ctor 968 | placementCtor(current, emptyElement); 969 | current++; 970 | } 971 | return absoluteOffset; 972 | } 973 | 974 | // resize array (using default constructor) 975 | template offset_t resizeArray(Array& _dst, size_t numElements) 976 | { 977 | BlobPtr> dst = getBlobPtr(&_dst); 978 | offset_t absoluteOffset = resizeArrayWithoutInitialization(_dst, numElements); 979 | T* current = getDirectMemoryAccessUnsafe(absoluteOffset); 980 | for (size_t i = 0; i < numElements; i++) 981 | { 982 | // default ctor 983 | placementCtor(current); 984 | current++; 985 | } 986 | return absoluteOffset; 987 | } 988 | 989 | // copyTo array fast (without using convertor) 990 | template offset_t copyToArrayFast(BlobPtr> dst, const T* begin, size_t numElements) 991 | { 992 | static_assert(std::is_trivially_copyable::value, "Only trivially copyable types allowed"); 993 | offset_t absoluteOffset = resizeArrayWithoutInitialization(*dst, numElements); 994 | T* arrData = getDirectMemoryAccessUnsafe(absoluteOffset); 995 | std::memcpy(arrData, begin, sizeof(T) * numElements); 996 | return absoluteOffset; 997 | } 998 | 999 | // copyTo array fast (without using convertor) 1000 | template offset_t copyToArrayFast(Array& _dst, const T* begin, size_t numElements) 1001 | { 1002 | BlobPtr> dst = getBlobPtr(&_dst); 1003 | return copyToArrayFast(dst, begin, numElements); 1004 | } 1005 | 1006 | // copyTo array from range 1007 | template 1008 | offset_t copyToArray(BlobPtr> dst, const Iter begin, const Iter end, int64_t size, ConvertorFunc convertorFunc) 1009 | { 1010 | size_t numElements = (size >= 0) ? size_t(size) : std::distance(begin, end); 1011 | resizeArray(*dst, numElements); 1012 | 1013 | BlobPtr firstElement = getBlobPtr(dst->data()); 1014 | offset_t absoluteOffset = firstElement.getAbsoluteOffset(); 1015 | offset_t currentIndex = 0; 1016 | for (Iter cur = begin; cur != end; ++cur) 1017 | { 1018 | offset_t currentItemAbsoluteOffset = absoluteOffset + sizeof(T) * currentIndex; 1019 | convertorFunc(this, currentItemAbsoluteOffset, *cur); 1020 | currentIndex++; 1021 | } 1022 | return absoluteOffset; 1023 | } 1024 | 1025 | // copyTo array from range 1026 | template 1027 | offset_t copyToArray(Array& _dst, const Iter begin, const Iter end, int64_t size, ConvertorFunc convertorFunc) 1028 | { 1029 | BlobPtr> dst = getBlobPtr(&_dst); 1030 | return copyToArray(dst, begin, end, size, convertorFunc); 1031 | } 1032 | 1033 | // copyTo hash container 1034 | template 1035 | void copyToHash(HashType& _dst, Iter begin, Iter end, int64_t size, ConvertorFunc convertorFunc) 1036 | { 1037 | // Note: this bucketing method relies on the fact that the input data set is (already) unique 1038 | size_t numElements = (size >= 0) ? size_t(size) : std::distance(begin, end); 1039 | ZMEYA_ASSERT(numElements > 0); 1040 | size_t numBuckets = numElements * 2; 1041 | ZMEYA_ASSERT(numBuckets < size_t(std::numeric_limits::max())); 1042 | size_t hashMod = numBuckets; 1043 | 1044 | BlobPtr dst = getBlobPtr(&_dst); 1045 | // allocate buckets & items 1046 | resizeArray(dst->buckets, numBuckets); 1047 | 1048 | // 1-st pass count the number of elements per bucket (beginIndex / endIndex) 1049 | typename HashType::Bucket* buckets = dst->buckets.get_raw_ptr_unsafe_can_be_relocated(); 1050 | for (Iter cur = begin; cur != end; ++cur) 1051 | { 1052 | const auto& current = *cur; 1053 | size_t hash = ItemSrcAdapter::hash(current); 1054 | size_t bucketIndex = hash % hashMod; 1055 | buckets[bucketIndex].beginIndex++; // temporary use beginIndex to store the number of items 1056 | } 1057 | 1058 | size_t beginIndex = 0; 1059 | for (size_t bucketIndex = 0; bucketIndex < numBuckets; bucketIndex++) 1060 | { 1061 | typename HashType::Bucket& bucket = buckets[bucketIndex]; 1062 | size_t numElementsInBucket = bucket.beginIndex; 1063 | bucket.beginIndex = uint32_t(beginIndex); 1064 | bucket.endIndex = bucket.beginIndex; 1065 | beginIndex += numElementsInBucket; 1066 | } 1067 | 1068 | // note: at this point this pointer is no longer valid (resize array can move data) 1069 | buckets = nullptr; 1070 | 1071 | // 2-st pass copy items 1072 | offset_t absoluteOffset = resizeArrayWithoutInitialization(dst->items, numElements); 1073 | for (Iter cur = begin; cur != end; ++cur) 1074 | { 1075 | const auto& current = *cur; 1076 | size_t hash = ItemSrcAdapter::hash(current); 1077 | size_t bucketIndex = hash % hashMod; 1078 | typename HashType::Bucket* bucket = dst->buckets.get_element_ptr_unsafe_can_be_relocated(bucketIndex); 1079 | uint32_t elementIndex = bucket->endIndex; 1080 | offset_t currentItemAbsoluteOffset = absoluteOffset + sizeof(typename ItemDstAdapter::ItemType) * offset_t(elementIndex); 1081 | convertorFunc(this, currentItemAbsoluteOffset, *cur); 1082 | 1083 | // Note: convertorFunc can allocate additional memory -> reallocate storage 1084 | // Hence, we need to reacquire the pointer 1085 | bucket = dst->buckets.get_element_ptr_unsafe_can_be_relocated(bucketIndex); 1086 | #ifdef ZMEYA_VALIDATE_HASH_DUPLICATES 1087 | const typename ItemDstAdapter::ItemType* lastItem = 1088 | getDirectMemoryAccessUnsafe(currentItemAbsoluteOffset); 1089 | size_t newItemHash = ItemDstAdapter::hash(*lastItem); 1090 | // inconsistent hashing! hash(srcItem) != hash(dstItem) 1091 | ZMEYA_ASSERT(hash == newItemHash); 1092 | for (uint32_t testElementIndex = bucket->beginIndex; testElementIndex < bucket->endIndex; testElementIndex++) 1093 | { 1094 | offset_t testItemAbsoluteOffset = absoluteOffset + sizeof(typename ItemDstAdapter::ItemType) * offset_t(testElementIndex); 1095 | const typename ItemDstAdapter::ItemType* testItem = 1096 | getDirectMemoryAccessUnsafe(testItemAbsoluteOffset); 1097 | ZMEYA_ASSERT(!ItemDstAdapter::eq(*testItem, *lastItem)); 1098 | } 1099 | #endif 1100 | bucket->endIndex++; 1101 | } 1102 | } 1103 | 1104 | // assignTo pointer 1105 | template static void assignTo(Pointer& dst, std::nullptr_t) { dst.relativeOffset = 0; } 1106 | 1107 | // assignTo pointer from absolute offset 1108 | template void assignTo(Pointer& _dst, offset_t targetAbsoluteOffset) 1109 | { 1110 | BlobPtr> dst = getBlobPtr(&_dst); 1111 | roffset_t relativeOffset = toRelativeOffset(diff(targetAbsoluteOffset, dst.getAbsoluteOffset())); 1112 | ZMEYA_ASSERT(relativeOffset != 0); 1113 | dst->relativeOffset = relativeOffset; 1114 | } 1115 | 1116 | // copyTo pointer from BlobPtr 1117 | template void assignTo(Pointer& dst, const BlobPtr& src) { assignTo(dst, src.getAbsoluteOffset()); } 1118 | 1119 | // copyTo pointer from RawPointer 1120 | template void assignTo(Pointer& dst, const T* _src) 1121 | { 1122 | const BlobPtr src = getBlobPtr(_src); 1123 | assignTo(dst, src); 1124 | } 1125 | 1126 | // copyTo pointer from reference 1127 | template void assignTo(Pointer& dst, const T& src) { assignTo(dst, &src); } 1128 | 1129 | // copyTo array from std::vector 1130 | template void copyTo(Array& dst, const std::vector& src) 1131 | { 1132 | ZMEYA_ASSERT(src.size() > 0); 1133 | copyToArrayFast(dst, src.data(), src.size()); 1134 | } 1135 | 1136 | // specialization for vector of vectors 1137 | template 1138 | void copyTo(Array>& dst, const std::vector, TAllocator1>& src) 1139 | { 1140 | ZMEYA_ASSERT(src.size() > 0); 1141 | copyToArray(dst, src.begin(), src.end(), src.size(), 1142 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const std::vector& src) 1143 | { 1144 | Array* dst = blobBuilder->getDirectMemoryAccessUnsafe>(dstAbsoluteOffset); 1145 | blobBuilder->copyTo(*dst, src); 1146 | }); 1147 | } 1148 | 1149 | // specialization for vector of strings 1150 | template void copyTo(Array& dst, const std::vector& src) 1151 | { 1152 | ZMEYA_ASSERT(src.size() > 0); 1153 | copyToArray(dst, src.begin(), src.end(), src.size(), 1154 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const T& src) 1155 | { 1156 | String* dst = blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1157 | blobBuilder->copyTo(*dst, src); 1158 | }); 1159 | } 1160 | 1161 | // copyTo array from std::initializer_list 1162 | template void copyTo(Array& dst, std::initializer_list list) 1163 | { 1164 | ZMEYA_ASSERT(list.size() > 0); 1165 | copyToArrayFast(dst, list.begin(), list.size()); 1166 | } 1167 | 1168 | // copyTo array from std::initializer_list 1169 | template void copyTo(BlobPtr> dst, std::initializer_list list) 1170 | { 1171 | ZMEYA_ASSERT(list.size() > 0); 1172 | copyToArrayFast(dst, list.begin(), list.size()); 1173 | } 1174 | 1175 | // specialization for std::initializer_list 1176 | void copyTo(Array& dst, std::initializer_list list) 1177 | { 1178 | ZMEYA_ASSERT(list.size() > 0); 1179 | copyToArray(dst, list.begin(), list.end(), list.size(), 1180 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const char* const& src) 1181 | { 1182 | String* dst = blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1183 | blobBuilder->copyTo(*dst, src); 1184 | }); 1185 | } 1186 | 1187 | // specialization for std::initializer_list 1188 | void copyTo(BlobPtr> dst, std::initializer_list list) 1189 | { 1190 | ZMEYA_ASSERT(list.size() > 0); 1191 | copyToArray(dst, list.begin(), list.end(), list.size(), 1192 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const char* const& src) 1193 | { 1194 | String* dst = blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1195 | blobBuilder->copyTo(*dst, src); 1196 | }); 1197 | } 1198 | 1199 | // copyTo array from std::array 1200 | template void copyTo(Array& dst, const std::array& src) 1201 | { 1202 | ZMEYA_ASSERT(src.size() > 0); 1203 | copyToArrayFast(dst, src.data(), src.size()); 1204 | } 1205 | 1206 | // specialization for array of strings 1207 | template void copyTo(Array& dst, const std::array& src) 1208 | { 1209 | ZMEYA_ASSERT(src.size() > 0); 1210 | copyToArray(dst, src.begin(), src.end(), src.size(), 1211 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const T& src) 1212 | { 1213 | String* dst = blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1214 | blobBuilder->copyTo(*dst, src); 1215 | }); 1216 | } 1217 | 1218 | // copyTo hash set from std::unordered_set 1219 | template 1220 | void copyTo(HashSet& dst, const std::unordered_set& src) 1221 | { 1222 | typedef HashKeyAdapterGeneric DstItemAdapter; 1223 | typedef HashKeyAdapterGeneric SrcItemAdapter; 1224 | 1225 | copyToHash( 1226 | dst, src.begin(), src.end(), src.size(), 1227 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1228 | { 1229 | typename DstItemAdapter::ItemType* dstElem = 1230 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1231 | *dstElem = srcElem; 1232 | }); 1233 | } 1234 | 1235 | // copyTo hash set from std::unordered_set (specialization for string key) 1236 | template 1237 | void copyTo(HashSet& dst, const std::unordered_set& src) 1238 | { 1239 | typedef HashKeyAdapterStdString SrcItemAdapter; 1240 | typedef HashKeyAdapterGeneric DstItemAdapter; 1241 | 1242 | copyToHash( 1243 | dst, src.begin(), src.end(), src.size(), 1244 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1245 | { 1246 | typename DstItemAdapter::ItemType* dstElem = 1247 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1248 | blobBuilder->copyTo(*dstElem, srcElem); 1249 | }); 1250 | } 1251 | 1252 | // copyTo hash set from std::initializer_list 1253 | template void copyTo(HashSet& dst, std::initializer_list list) 1254 | { 1255 | typedef HashKeyAdapterGeneric SrcItemAdapter; 1256 | typedef HashKeyAdapterGeneric DstItemAdapter; 1257 | 1258 | copyToHash( 1259 | dst, list.begin(), list.end(), list.size(), 1260 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1261 | { 1262 | typename DstItemAdapter::ItemType* dstElem = 1263 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1264 | *dstElem = srcElem; 1265 | }); 1266 | } 1267 | 1268 | // copyTo hash set from std::initializer_list (specialization for string key) 1269 | void copyTo(HashSet& dst, std::initializer_list list) 1270 | { 1271 | typedef HashKeyAdapterStdString SrcItemAdapter; 1272 | typedef HashKeyAdapterGeneric DstItemAdapter; 1273 | 1274 | copyToHash( 1275 | dst, list.begin(), list.end(), list.size(), 1276 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1277 | { 1278 | typename DstItemAdapter::ItemType* dstElem = 1279 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1280 | blobBuilder->copyTo(*dstElem, srcElem); 1281 | }); 1282 | } 1283 | 1284 | // copyTo hash map from std::unordered_map 1285 | template 1286 | void copyTo(HashMap& dst, const std::unordered_map& src) 1287 | { 1288 | typedef HashKeyValueAdapterGeneric> SrcItemAdapter; 1289 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1290 | 1291 | copyToHash( 1292 | dst, src.begin(), src.end(), src.size(), 1293 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1294 | { 1295 | typename DstItemAdapter::ItemType* dstElem = 1296 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1297 | dstElem->first = srcElem.first; 1298 | dstElem->second = srcElem.second; 1299 | }); 1300 | } 1301 | 1302 | // copyTo hash set from std::unordered_map (specialization for string key) 1303 | template 1304 | void copyTo(HashMap& dst, const std::unordered_map& src) 1305 | { 1306 | typedef HashKeyValueAdapterStdString SrcItemAdapter; 1307 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1308 | 1309 | copyToHash( 1310 | dst, src.begin(), src.end(), src.size(), 1311 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1312 | { 1313 | typename DstItemAdapter::ItemType* dstElem = 1314 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1315 | dstElem->second = srcElem.second; 1316 | blobBuilder->copyTo(dstElem->first, srcElem.first); 1317 | }); 1318 | } 1319 | 1320 | // copyTo hash set from std::unordered_map (specialization for string value) 1321 | template 1322 | void copyTo(HashMap& dst, const std::unordered_map& src) 1323 | { 1324 | typedef HashKeyValueAdapterGeneric> SrcItemAdapter; 1325 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1326 | 1327 | copyToHash( 1328 | dst, src.begin(), src.end(), src.size(), 1329 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1330 | { 1331 | typename DstItemAdapter::ItemType* dstElem = 1332 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1333 | dstElem->first = srcElem.first; 1334 | blobBuilder->copyTo(dstElem->second, srcElem.second); 1335 | }); 1336 | } 1337 | 1338 | // copyTo hash set from std::unordered_map (specialization for string key/value) 1339 | template 1340 | void copyTo(HashMap& dst, const std::unordered_map& src) 1341 | { 1342 | typedef HashKeyValueAdapterStdString SrcItemAdapter; 1343 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1344 | 1345 | copyToHash( 1346 | dst, src.begin(), src.end(), src.size(), 1347 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1348 | { 1349 | typename DstItemAdapter::ItemType* dstElem = 1350 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1351 | blobBuilder->copyTo(dstElem->first, srcElem.first); 1352 | 1353 | dstElem = blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1354 | blobBuilder->copyTo(dstElem->second, srcElem.second); 1355 | }); 1356 | } 1357 | 1358 | // copyTo hash map from std::initializer_list 1359 | template void copyTo(HashMap& dst, std::initializer_list> list) 1360 | { 1361 | typedef HashKeyValueAdapterGeneric> SrcItemAdapter; 1362 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1363 | 1364 | copyToHash( 1365 | dst, list.begin(), list.end(), list.size(), 1366 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1367 | { 1368 | typename DstItemAdapter::ItemType* dstElem = 1369 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1370 | dstElem->first = srcElem.first; 1371 | dstElem->second = srcElem.second; 1372 | }); 1373 | } 1374 | 1375 | // copyTo hash map from std::initializer_list (specialization for string key) 1376 | template void copyTo(HashMap& dst, std::initializer_list> list) 1377 | { 1378 | typedef HashKeyValueAdapterStdString SrcItemAdapter; 1379 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1380 | 1381 | copyToHash( 1382 | dst, list.begin(), list.end(), list.size(), 1383 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1384 | { 1385 | typename DstItemAdapter::ItemType* dstElem = 1386 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1387 | dstElem->second = srcElem.second; 1388 | blobBuilder->copyTo(dstElem->first, srcElem.first); 1389 | }); 1390 | } 1391 | 1392 | // copyTo hash map from std::initializer_list (specialization for string value) 1393 | template void copyTo(HashMap& dst, std::initializer_list> list) 1394 | { 1395 | typedef HashKeyValueAdapterGeneric> SrcItemAdapter; 1396 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1397 | 1398 | copyToHash( 1399 | dst, list.begin(), list.end(), list.size(), 1400 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1401 | { 1402 | typename DstItemAdapter::ItemType* dstElem = 1403 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1404 | dstElem->first = srcElem.first; 1405 | blobBuilder->copyTo(dstElem->second, srcElem.second); 1406 | }); 1407 | } 1408 | 1409 | // copyTo hash map from std::initializer_list (specialization for string key) 1410 | void copyTo(HashMap& dst, std::initializer_list> list) 1411 | { 1412 | typedef HashKeyValueAdapterStdString SrcItemAdapter; 1413 | typedef HashKeyValueAdapterGeneric> DstItemAdapter; 1414 | 1415 | copyToHash( 1416 | dst, list.begin(), list.end(), list.size(), 1417 | [](BlobBuilder* blobBuilder, offset_t dstAbsoluteOffset, const typename SrcItemAdapter::ItemType& srcElem) 1418 | { 1419 | typename DstItemAdapter::ItemType* dstElem = 1420 | blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1421 | blobBuilder->copyTo(dstElem->first, srcElem.first); 1422 | 1423 | dstElem = blobBuilder->getDirectMemoryAccessUnsafe(dstAbsoluteOffset); 1424 | blobBuilder->copyTo(dstElem->second, srcElem.second); 1425 | }); 1426 | } 1427 | 1428 | // copyTo string from const char* and size 1429 | void copyTo(BlobPtr dst, const char* src, size_t len) 1430 | { 1431 | ZMEYA_ASSERT(src != nullptr && len > 0); 1432 | BlobPtr stringData = allocate(src[0]); 1433 | if (len > 0) 1434 | { 1435 | for (size_t i = 1; i < len; i++) 1436 | { 1437 | allocate(src[i]); 1438 | } 1439 | allocate('\0'); 1440 | } 1441 | assignTo(dst->data, stringData); 1442 | } 1443 | 1444 | void copyTo(String& _dst, const char* src, size_t len) 1445 | { 1446 | BlobPtr dst(getBlobPtr(&_dst)); 1447 | copyTo(dst, src, len); 1448 | } 1449 | 1450 | // copyTo string from std::string 1451 | void copyTo(String& dst, const std::string& src) { copyTo(dst, src.c_str(), src.size()); } 1452 | 1453 | // copyTo string from null teminated c-string 1454 | void copyTo(String& _dst, const char* src) 1455 | { 1456 | ZMEYA_ASSERT(src != nullptr); 1457 | size_t len = std::strlen(src); 1458 | copyTo(_dst, src, len); 1459 | } 1460 | 1461 | void referTo(BlobPtr dst, const String& src) 1462 | { 1463 | BlobPtr stringData = getBlobPtr(src.c_str()); 1464 | assignTo(dst->data, stringData); 1465 | } 1466 | 1467 | // referTo another String (it is not a copy, the destination string will refer to the same data) 1468 | void referTo(String& dst, const String& src) 1469 | { 1470 | BlobPtr stringData = getBlobPtr(src.c_str()); 1471 | assignTo(dst.data, stringData); 1472 | } 1473 | 1474 | // referTo another Array (it is not a copy, the destination string will refer to the same data) 1475 | template void referTo(Array& _dst, const Array& src) 1476 | { 1477 | BlobPtr> dst = getBlobPtr(&_dst); 1478 | BlobPtr arrData = getBlobPtr(src.data()); 1479 | dst->numElements = uint32_t(src.size()); 1480 | setArrayOffset(dst, arrData.getAbsoluteOffset()); 1481 | } 1482 | 1483 | // referTo another HashSet (it is not a copy, the destination string will refer to the same data) 1484 | template void referTo(HashSet& dst, const HashSet& src) 1485 | { 1486 | referTo(dst.buckets, src.buckets); 1487 | referTo(dst.items, src.items); 1488 | } 1489 | 1490 | // referTo another HashMap (it is not a copy, the destination string will refer to the same data) 1491 | template void referTo(HashMap& dst, const HashMap& src) 1492 | { 1493 | referTo(dst.buckets, src.buckets); 1494 | referTo(dst.items, src.items); 1495 | } 1496 | 1497 | Span finalize(size_t desiredSizeShouldBeMultipleOf = 4) 1498 | { 1499 | size_t numPaddingBytes = desiredSizeShouldBeMultipleOf - (data.size() % desiredSizeShouldBeMultipleOf); 1500 | allocate(numPaddingBytes, 1); 1501 | 1502 | ZMEYA_ASSERT((data.size() % desiredSizeShouldBeMultipleOf) == 0); 1503 | return Span(data.data(), data.size()); 1504 | } 1505 | 1506 | ZMEYA_NODISCARD static std::shared_ptr create(size_t initialSizeInBytes = 2048) 1507 | { 1508 | BlobBuilderAllocator allocator; 1509 | return std::allocate_shared(allocator, initialSizeInBytes, PrivateToken{}); 1510 | } 1511 | 1512 | template friend class BlobPtr; 1513 | }; 1514 | 1515 | template ZMEYA_NODISCARD T* BlobPtr::get() const 1516 | { 1517 | std::shared_ptr p = blob.lock(); 1518 | if (!p) 1519 | { 1520 | return nullptr; 1521 | } 1522 | return reinterpret_cast(const_cast(p->get(absoluteOffset))); 1523 | } 1524 | 1525 | template Pointer& Pointer::operator=(const BlobPtr& other) 1526 | { 1527 | Pointer& self = *this; 1528 | std::shared_ptr p = other.blob.lock(); 1529 | BlobBuilder* blobBuilder = const_cast(p.get()); 1530 | if (!blobBuilder) 1531 | { 1532 | BlobBuilder::assignTo(self, nullptr); 1533 | } 1534 | else 1535 | { 1536 | blobBuilder->assignTo(self, other); 1537 | } 1538 | return self; 1539 | } 1540 | 1541 | #endif 1542 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1543 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1544 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1545 | 1546 | } // namespace zm 1547 | 1548 | namespace std 1549 | { 1550 | template <> struct hash 1551 | { 1552 | size_t operator()(zm::String const& s) const noexcept 1553 | { 1554 | const char* str = s.c_str(); 1555 | return zm::HashUtils::hashString(str); 1556 | } 1557 | }; 1558 | } // namespace std 1559 | -------------------------------------------------------------------------------- /ZmeyaTest01.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | /* 4 | namespace Memory 5 | { 6 | 7 | size_t mallocCount = 0; 8 | size_t freeCount = 0; 9 | 10 | struct Header 11 | { 12 | void* p; 13 | size_t size; 14 | size_t magic; 15 | }; 16 | 17 | const size_t kMinValidAlignment = 4; 18 | 19 | void* malloc(size_t bytesCount, size_t alignment) 20 | { 21 | mallocCount++; 22 | if (alignment < kMinValidAlignment) 23 | { 24 | alignment = kMinValidAlignment; 25 | } 26 | void* p; 27 | void** p2; 28 | size_t offset = alignment - 1 + sizeof(Header); 29 | if ((p = (void*)std::malloc(bytesCount + offset)) == NULL) 30 | { 31 | return NULL; 32 | } 33 | p2 = (void**)(((size_t)(p) + offset) & ~(alignment - 1)); 34 | 35 | Header* h = reinterpret_cast(reinterpret_cast(p2) - sizeof(Header)); 36 | h->p = p; 37 | h->size = bytesCount; 38 | h->magic = size_t(0x13061979); 39 | return p2; 40 | } 41 | 42 | void mfree(void* p) 43 | { 44 | freeCount++; 45 | if (!p) 46 | { 47 | return; 48 | } 49 | Header* h = reinterpret_cast(reinterpret_cast(p) - sizeof(Header)); 50 | ASSERT_EQ(h->magic, size_t(0x13061979)); 51 | std::free(h->p); 52 | } 53 | 54 | } // namespace Memory 55 | 56 | #define ZMEYA_ALLOC(sizeInBytes, alignment) Memory::malloc(sizeInBytes, alignment) 57 | #define ZMEYA_FREE(ptr) Memory::mfree(ptr) 58 | */ 59 | 60 | #include "TestHelper.h" 61 | #include "Zmeya.h" 62 | 63 | struct SimpleTestRoot 64 | { 65 | float a; 66 | uint32_t b; 67 | uint16_t c; 68 | int8_t d; 69 | uint32_t arr[32]; 70 | }; 71 | 72 | static void validate(const SimpleTestRoot* root) 73 | { 74 | EXPECT_FLOAT_EQ(root->a, 13.0f); 75 | EXPECT_EQ(root->b, 1979u); 76 | EXPECT_EQ(root->c, 6); 77 | EXPECT_EQ(root->d, -9); 78 | for (size_t i = 0; i < 32; i++) 79 | { 80 | EXPECT_EQ(root->arr[i], uint32_t(i + 3)); 81 | } 82 | } 83 | 84 | TEST(ZmeyaTestSuite, SimpleTest) 85 | { 86 | std::vector bytesCopy; 87 | { 88 | // create blob 89 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 90 | 91 | // allocate structure 92 | zm::BlobPtr root = blobBuilder->allocate(); 93 | 94 | // fill with data 95 | root->a = 13.0f; 96 | root->b = 1979; 97 | root->c = 6; 98 | root->d = -9; 99 | for (size_t i = 0; i < 32; i++) 100 | { 101 | root->arr[i] = uint32_t(i + 3); 102 | } 103 | 104 | validate(root.get()); 105 | 106 | // finalize blob 107 | zm::Span bytes = blobBuilder->finalize(); 108 | 109 | // copy resulting bytes 110 | bytesCopy = utils::copyBytes(bytes); 111 | 112 | // fill original memory with 0xff 113 | std::memset(bytes.data, 0xFF, bytes.size); 114 | } 115 | 116 | // "deserialize" and test results 117 | const SimpleTestRoot* rootCopy = (const SimpleTestRoot*)(bytesCopy.data()); 118 | validate(rootCopy); 119 | } 120 | 121 | struct Desc 122 | { 123 | zm::String name; 124 | float v1; 125 | uint32_t v2; 126 | }; 127 | 128 | struct TestRoot 129 | { 130 | zm::Array arr; 131 | }; 132 | 133 | TEST(ZmeyaTestSuite, SimpleTest2) 134 | { 135 | const std::vector names = {"apple", "banana", "orange", "castle", "dragon", "flower", "guitar", 136 | "hockey", "island", "jungle", "kingdom", "library", "monster", "notable", 137 | "oceanic", "painter", "quarter", "rescue", "seventh", "trivial", "umbrella", 138 | "village", "warrior", "xenial", "yonder", "zephyr"}; 139 | 140 | /* 141 | Memory::mallocCount = 0; 142 | Memory::freeCount = 0; 143 | EXPECT_EQ(Memory::mallocCount, size_t(0)); 144 | EXPECT_EQ(Memory::freeCount, size_t(0)); 145 | */ 146 | 147 | std::vector blob; 148 | { 149 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 150 | zm::BlobPtr root = blobBuilder->allocate(); 151 | 152 | blobBuilder->resizeArray(root->arr, names.size()); 153 | EXPECT_EQ(root->arr.size(), names.size()); 154 | 155 | for (size_t i = 0; i < names.size(); i++) 156 | { 157 | zm::BlobPtr desc = blobBuilder->getArrayElement(root->arr, i); 158 | blobBuilder->copyTo(desc->name, names[i].c_str()); 159 | const char* s1 = root->arr[i].name.c_str(); 160 | const char* s2 = names[i].c_str(); 161 | EXPECT_STREQ(s1, s2); 162 | desc->v1 = (float)(i); 163 | desc->v2 = (uint32_t)(i); 164 | } 165 | zm::Span bytes = blobBuilder->finalize(); 166 | blob = std::vector(bytes.data, bytes.data + bytes.size); 167 | } 168 | 169 | /* 170 | EXPECT_GT(Memory::mallocCount, size_t(0)); 171 | EXPECT_GT(Memory::freeCount, size_t(0)); 172 | EXPECT_EQ(Memory::mallocCount, Memory::freeCount); 173 | */ 174 | 175 | // validate 176 | const TestRoot* rootCopy = (const TestRoot*)(blob.data()); 177 | EXPECT_EQ(rootCopy->arr.size(), names.size()); 178 | 179 | for (size_t i = 0; i < names.size(); i++) 180 | { 181 | const Desc& desc = rootCopy->arr[i]; 182 | EXPECT_STREQ(desc.name.c_str(), names[i].c_str()); 183 | EXPECT_FLOAT_EQ(desc.v1, (float)(i)); 184 | EXPECT_EQ(desc.v2, (uint32_t)(i)); 185 | } 186 | 187 | /* 188 | EXPECT_EQ(Memory::mallocCount, Memory::freeCount); 189 | */ 190 | } -------------------------------------------------------------------------------- /ZmeyaTest02.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct PointerTestNode 6 | { 7 | int32_t payload; 8 | zm::Pointer other; 9 | }; 10 | 11 | struct PointerTestRoot 12 | { 13 | zm::Pointer left; 14 | zm::Pointer right; 15 | }; 16 | 17 | static void validate(const PointerTestRoot* root) 18 | { 19 | EXPECT_NE(root->left, nullptr); 20 | EXPECT_NE(root->right, nullptr); 21 | EXPECT_NE(root->left->other, nullptr); 22 | EXPECT_NE(root->right->other, nullptr); 23 | 24 | EXPECT_EQ(root->left->payload, -13); 25 | EXPECT_EQ(root->right->payload, 13); 26 | 27 | EXPECT_EQ(root->left->other->payload, 13); 28 | EXPECT_EQ(root->right->other->payload, -13); 29 | 30 | EXPECT_EQ(root->right->other, root->left); 31 | EXPECT_EQ(root->left->other, root->right); 32 | } 33 | 34 | TEST(ZmeyaTestSuite, PointerTest) 35 | { 36 | std::vector bytesCopy; 37 | { 38 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 39 | 40 | zm::BlobPtr root = blobBuilder->allocate(); 41 | zm::BlobPtr nodeLeft = blobBuilder->allocate(); 42 | zm::BlobPtr nodeRight = blobBuilder->allocate(); 43 | 44 | root->left = nodeLeft; 45 | root->right = nodeRight; 46 | 47 | nodeLeft->payload = -13; 48 | nodeLeft->other = nodeRight; 49 | 50 | nodeRight->payload = 13; 51 | nodeRight->other = nodeLeft; 52 | 53 | validate(root.get()); 54 | 55 | zm::Span bytes = blobBuilder->finalize(16); 56 | // check (optional) blob size alignment 57 | EXPECT_TRUE((bytes.size % 16) == 0); 58 | 59 | bytesCopy = utils::copyBytes(bytes); 60 | std::memset(bytes.data, 0xFF, bytes.size); 61 | } 62 | 63 | const PointerTestRoot* rootCopy = (const PointerTestRoot*)(bytesCopy.data()); 64 | validate(rootCopy); 65 | } 66 | -------------------------------------------------------------------------------- /ZmeyaTest03.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct Payload 6 | { 7 | float a; 8 | uint32_t b; 9 | 10 | Payload() = default; 11 | Payload(float _a, uint32_t _b) 12 | : a(_a) 13 | , b(_b) 14 | { 15 | } 16 | }; 17 | 18 | struct ArrayTestRoot 19 | { 20 | zm::Array arr1; 21 | zm::Array arr2; 22 | zm::Array arr3; 23 | zm::Array> arr4; 24 | zm::Array> arr5; 25 | zm::Array> arr6; 26 | }; 27 | 28 | static void validate(const ArrayTestRoot* root) 29 | { 30 | EXPECT_EQ(root->arr1.size(), std::size_t(2)); 31 | const Payload& item0 = root->arr1[0]; 32 | EXPECT_FLOAT_EQ(item0.a, 1.3f); 33 | EXPECT_EQ(item0.b, 13u); 34 | const Payload& item1 = root->arr1[1]; 35 | EXPECT_FLOAT_EQ(item1.a, 2.7f); 36 | EXPECT_EQ(item1.b, 27u); 37 | 38 | EXPECT_EQ(root->arr2.size(), std::size_t(6)); 39 | EXPECT_EQ(root->arr2[0], 2); 40 | EXPECT_EQ(root->arr2[1], 4); 41 | EXPECT_EQ(root->arr2[2], 6); 42 | EXPECT_EQ(root->arr2[3], 10); 43 | EXPECT_EQ(root->arr2[4], 14); 44 | EXPECT_EQ(root->arr2[5], 32); 45 | 46 | EXPECT_EQ(root->arr3.size(), std::size_t(4)); 47 | EXPECT_FLOAT_EQ(root->arr3[0], 67.0f); 48 | EXPECT_FLOAT_EQ(root->arr3[1], 82.0f); 49 | EXPECT_FLOAT_EQ(root->arr3[2], 11.0f); 50 | EXPECT_FLOAT_EQ(root->arr3[3], 54.0f); 51 | 52 | EXPECT_EQ(root->arr4.size(), std::size_t(4)); 53 | EXPECT_EQ(root->arr4[0].size(), std::size_t(2)); 54 | EXPECT_FLOAT_EQ(root->arr4[0][0], 1.2f); 55 | EXPECT_FLOAT_EQ(root->arr4[0][1], 2.3f); 56 | EXPECT_EQ(root->arr4[1].size(), std::size_t(3)); 57 | EXPECT_FLOAT_EQ(root->arr4[1][0], 7.1f); 58 | EXPECT_FLOAT_EQ(root->arr4[1][1], 8.8f); 59 | EXPECT_FLOAT_EQ(root->arr4[1][2], 3.2f); 60 | EXPECT_EQ(root->arr4[2].size(), std::size_t(4)); 61 | EXPECT_FLOAT_EQ(root->arr4[2][0], 16.0f); 62 | EXPECT_FLOAT_EQ(root->arr4[2][1], 12.0f); 63 | EXPECT_FLOAT_EQ(root->arr4[2][2], 99.5f); 64 | EXPECT_FLOAT_EQ(root->arr4[2][3], -143.0f); 65 | EXPECT_EQ(root->arr4[3].size(), std::size_t(1)); 66 | EXPECT_FLOAT_EQ(root->arr4[3][0], -1.0f); 67 | 68 | EXPECT_EQ(root->arr5.size(), std::size_t(793)); 69 | for (size_t i = 0; i < root->arr5.size(); i++) 70 | { 71 | EXPECT_NE(root->arr5[i], nullptr); 72 | float a = 1.3f + float(i) * 0.4f; 73 | uint32_t b = uint32_t(i) + 3; 74 | EXPECT_FLOAT_EQ(root->arr5[i]->a, a); 75 | EXPECT_EQ(root->arr5[i]->b, b); 76 | } 77 | 78 | EXPECT_EQ(root->arr6.size(), std::size_t(3)); 79 | EXPECT_EQ(root->arr6[0].size(), std::size_t(2)); 80 | EXPECT_EQ(root->arr6[1].size(), std::size_t(5)); 81 | EXPECT_EQ(root->arr6[2].size(), std::size_t(4)); 82 | 83 | EXPECT_EQ(root->arr6[0][0], uint32_t(1)); 84 | EXPECT_EQ(root->arr6[0][1], uint32_t(2)); 85 | 86 | EXPECT_EQ(root->arr6[1][0], uint32_t(2)); 87 | EXPECT_EQ(root->arr6[1][1], uint32_t(7)); 88 | EXPECT_EQ(root->arr6[1][2], uint32_t(11)); 89 | EXPECT_EQ(root->arr6[1][3], uint32_t(9)); 90 | EXPECT_EQ(root->arr6[1][4], uint32_t(141)); 91 | 92 | EXPECT_EQ(root->arr6[2][0], uint32_t(15)); 93 | EXPECT_EQ(root->arr6[2][1], uint32_t(9)); 94 | EXPECT_EQ(root->arr6[2][2], uint32_t(33)); 95 | EXPECT_EQ(root->arr6[2][3], uint32_t(7)); 96 | } 97 | 98 | TEST(ZmeyaTestSuite, ArrayTest) 99 | { 100 | std::vector bytesCopy; 101 | { 102 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 103 | zm::BlobPtr root = blobBuilder->allocate(); 104 | 105 | // assign from std::vector 106 | std::vector vec = {{1.3f, 13}, {2.7f, 27}}; 107 | blobBuilder->copyTo(root->arr1, vec); 108 | 109 | // assign from std::initializer_list 110 | blobBuilder->copyTo(root->arr2, {2, 4, 6, 10, 14, 32}); 111 | 112 | // assign from std::array 113 | std::array arr{{67.0f, 82.0f, 11.0f, 54.0f}}; 114 | blobBuilder->copyTo(root->arr3, arr); 115 | 116 | // resize array 117 | blobBuilder->resizeArray(root->arr4, 4); 118 | // assign array elements (sub-arrays) 119 | 120 | blobBuilder->copyTo(blobBuilder->getArrayElement(root->arr4, 0), {1.2f, 2.3f}); 121 | blobBuilder->copyTo(blobBuilder->getArrayElement(root->arr4, 1), {7.1f, 8.8f, 3.2f}); 122 | blobBuilder->copyTo(blobBuilder->getArrayElement(root->arr4, 2), {16.0f, 12.0f, 99.5f, -143.0f}); 123 | blobBuilder->copyTo(blobBuilder->getArrayElement(root->arr4, 3), {-1.0f}); 124 | 125 | // resize array 126 | blobBuilder->resizeArray(root->arr5, 793); 127 | for (size_t i = 0; i < root->arr5.size(); i++) 128 | { 129 | zm::BlobPtr payload = blobBuilder->allocate(1.3f + float(i) * 0.4f, uint32_t(i) + 3); 130 | *blobBuilder->getArrayElement(root->arr5, i) = payload; 131 | } 132 | 133 | std::vector> vec2 = {{1, 2}, {2, 7, 11, 9, 141}, {15, 9, 33, 7}}; 134 | blobBuilder->copyTo(root->arr6, vec2); 135 | 136 | validate(root.get()); 137 | 138 | zm::Span bytes = blobBuilder->finalize(); 139 | bytesCopy = utils::copyBytes(bytes); 140 | std::memset(bytes.data, 0xFF, bytes.size); 141 | } 142 | 143 | const ArrayTestRoot* rootCopy = (const ArrayTestRoot*)(bytesCopy.data()); 144 | validate(rootCopy); 145 | } 146 | -------------------------------------------------------------------------------- /ZmeyaTest04.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct ListTestNode 6 | { 7 | uint32_t payload; 8 | zm::Pointer prev; 9 | zm::Pointer next; 10 | }; 11 | 12 | struct ListTestRoot 13 | { 14 | uint32_t numNodes; 15 | zm::Pointer root; 16 | }; 17 | 18 | static void validate(const ListTestRoot* root) 19 | { 20 | uint32_t count = 0; 21 | uint32_t numNodes = root->numNodes; 22 | const ListTestNode* currentNode = root->root.get(); 23 | for (;;) 24 | { 25 | if (currentNode == nullptr) 26 | { 27 | break; 28 | } 29 | 30 | EXPECT_EQ(currentNode->payload, 13 + count); 31 | if (count > 0) 32 | { 33 | EXPECT_TRUE(currentNode->prev != nullptr); 34 | } 35 | if (currentNode->prev) 36 | { 37 | EXPECT_EQ(currentNode->prev->payload, 13 + count - 1); 38 | } 39 | if (currentNode->next) 40 | { 41 | EXPECT_EQ(currentNode->next->payload, 13 + count + 1); 42 | } 43 | 44 | count++; 45 | currentNode = currentNode->next.get(); 46 | } 47 | EXPECT_EQ(count, numNodes); 48 | } 49 | 50 | TEST(ZmeyaTestSuite, ListTest) 51 | { 52 | std::vector bytesCopy; 53 | { 54 | // immediatly reserve 4Mb 55 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(4 * 1024 * 1024); 56 | zm::BlobPtr root = blobBuilder->allocate(); 57 | 58 | #ifdef _DEBUG 59 | uint32_t numNodes = 3000; 60 | #else 61 | uint32_t numNodes = 1000000; 62 | #endif 63 | 64 | root->numNodes = numNodes; 65 | zm::BlobPtr prevNode; 66 | for (uint32_t i = 0; i < numNodes; i++) 67 | { 68 | zm::BlobPtr node = blobBuilder->allocate(); 69 | node->payload = 13 + i; 70 | node->prev = prevNode; 71 | if (prevNode) 72 | { 73 | prevNode->next = node; 74 | } 75 | else 76 | { 77 | EXPECT_TRUE(root->root == nullptr); 78 | root->root = node; 79 | } 80 | prevNode = node; 81 | } 82 | 83 | validate(root.get()); 84 | 85 | zm::Span bytes = blobBuilder->finalize(); 86 | 87 | bytesCopy = utils::copyBytes(bytes); 88 | std::memset(bytes.data, 0xFF, bytes.size); 89 | } 90 | 91 | const ListTestRoot* rootCopy = (const ListTestRoot*)(bytesCopy.data()); 92 | validate(rootCopy); 93 | } 94 | -------------------------------------------------------------------------------- /ZmeyaTest05.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct StringTestRoot 6 | { 7 | zm::String str1; 8 | zm::String str2; 9 | zm::String str3; 10 | zm::String str4; 11 | zm::String str5; 12 | zm::String str6; 13 | zm::Array strArr1; 14 | zm::Array strArr2; 15 | zm::Array strArr3; 16 | zm::Array strArr4; 17 | }; 18 | 19 | static void validate(const StringTestRoot* root) 20 | { 21 | EXPECT_STREQ(root->str1.c_str(), "Hello World - This is a very long test string. Expected 1000000 instances"); 22 | EXPECT_STREQ(root->str2.c_str(), "Hello World 2"); 23 | EXPECT_STREQ(root->str3.c_str(), "Hello W"); 24 | EXPECT_STREQ(root->str4.c_str(), "Hello World - This is a very long test string. Expected 1000000 instances"); 25 | EXPECT_STREQ(root->str5.c_str(), "Hello World 2"); 26 | EXPECT_STREQ(root->str6.c_str(), ""); 27 | 28 | EXPECT_TRUE(root->str1 == root->str1); 29 | EXPECT_TRUE(root->str2 == root->str5); 30 | EXPECT_TRUE(root->str1 == root->str4); 31 | EXPECT_FALSE(root->str1 == root->str2); 32 | EXPECT_FALSE(root->str1 == root->str3); 33 | EXPECT_FALSE(root->str1 == root->str5); 34 | 35 | EXPECT_EQ(root->strArr1.size(), std::size_t(4)); 36 | EXPECT_EQ(root->strArr1[0], "first"); 37 | EXPECT_EQ(root->strArr1[1], "second"); 38 | EXPECT_EQ(root->strArr1[2], "third"); 39 | EXPECT_EQ(root->strArr1[3], "fourth"); 40 | 41 | EXPECT_EQ(root->strArr2.size(), std::size_t(3)); 42 | EXPECT_EQ(root->strArr2[0], "one"); 43 | EXPECT_EQ(root->strArr2[1], "two"); 44 | EXPECT_EQ(root->strArr2[2], "three"); 45 | 46 | EXPECT_EQ(root->strArr3.size(), std::size_t(2)); 47 | EXPECT_EQ(root->strArr3[0], "hello"); 48 | EXPECT_EQ(root->strArr3[1], "world"); 49 | 50 | EXPECT_EQ(root->strArr4.size(), std::size_t(1000000)); 51 | for (const zm::String& s : root->strArr4) 52 | { 53 | EXPECT_EQ(s, root->str1); 54 | } 55 | } 56 | 57 | TEST(ZmeyaTestSuite, StringTest) 58 | { 59 | std::vector bytesCopy; 60 | { 61 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 62 | zm::BlobPtr root = blobBuilder->allocate(); 63 | 64 | // assign from null-terminated c string (74 bytes long) 65 | blobBuilder->copyTo(root->str1, "Hello World - This is a very long test string. Expected 1000000 instances"); 66 | 67 | // assign from std::string 68 | std::string testStr("Hello World 2"); 69 | blobBuilder->copyTo(root->str2, testStr); 70 | 71 | // assign range 72 | blobBuilder->copyTo(root->str3, "Hello World 3", 7); 73 | 74 | // assign existing string (do not introduce extra copy!) 75 | blobBuilder->referTo(root->str4, root->str1); 76 | 77 | blobBuilder->copyTo(root->str5, "Hello World 2"); 78 | 79 | std::vector arr1 = {"first", "second", "third", "fourth"}; 80 | blobBuilder->copyTo(root->strArr1, arr1); 81 | blobBuilder->copyTo(root->strArr2, {"one", "two", "three"}); 82 | 83 | std::array arr3 = {{"hello", "world"}}; 84 | blobBuilder->copyTo(root->strArr3, arr3); 85 | 86 | // 1,000,000 strings 74 bytes each 87 | // since we are assigning already existing string the string body will not be copied (only offset will be stored) 88 | size_t numStrings = 1000000; 89 | blobBuilder->resizeArray(root->strArr4, numStrings); 90 | for (size_t i = 0; i < root->strArr4.size(); i++) 91 | { 92 | blobBuilder->referTo(blobBuilder->getArrayElement(root->strArr4, i), root->str1); 93 | } 94 | 95 | validate(root.get()); 96 | 97 | zm::Span bytes = blobBuilder->finalize(); 98 | EXPECT_LE(bytes.size, std::size_t(4000500)); 99 | 100 | bytesCopy = utils::copyBytes(bytes); 101 | std::memset(bytes.data, 0xFF, bytes.size); 102 | } 103 | 104 | const StringTestRoot* rootCopy = (const StringTestRoot*)(bytesCopy.data()); 105 | 106 | validate(rootCopy); 107 | } 108 | -------------------------------------------------------------------------------- /ZmeyaTest06.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct HashSetTestRoot 6 | { 7 | zm::HashSet set1; 8 | zm::HashSet set2; 9 | zm::HashSet set3; 10 | zm::HashSet strSet1; 11 | zm::HashSet strSet2; 12 | }; 13 | 14 | static void validate(const HashSetTestRoot* root) 15 | { 16 | EXPECT_EQ(root->set1.size(), std::size_t(5)); 17 | EXPECT_TRUE(root->set1.contains(99)); 18 | EXPECT_TRUE(root->set1.contains(5)); 19 | EXPECT_TRUE(root->set1.contains(7)); 20 | EXPECT_TRUE(root->set1.contains(3)); 21 | EXPECT_TRUE(root->set1.contains(11)); 22 | EXPECT_FALSE(root->set1.contains(1)); 23 | EXPECT_FALSE(root->set1.contains(6)); 24 | EXPECT_FALSE(root->set1.contains(15)); 25 | EXPECT_FALSE(root->set1.contains(88)); 26 | 27 | EXPECT_EQ(root->set2.size(), std::size_t(7)); 28 | EXPECT_TRUE(root->set2.contains(1)); 29 | EXPECT_TRUE(root->set2.contains(2)); 30 | EXPECT_TRUE(root->set2.contains(3)); 31 | EXPECT_TRUE(root->set2.contains(4)); 32 | EXPECT_TRUE(root->set2.contains(0)); 33 | EXPECT_TRUE(root->set2.contains(99)); 34 | EXPECT_TRUE(root->set2.contains(6)); 35 | EXPECT_FALSE(root->set2.contains(7)); 36 | EXPECT_FALSE(root->set2.contains(-2)); 37 | EXPECT_FALSE(root->set2.contains(11)); 38 | 39 | EXPECT_EQ(root->set3.size(), std::size_t(0)); 40 | EXPECT_FALSE(root->set3.contains(1)); 41 | EXPECT_FALSE(root->set3.contains(2)); 42 | EXPECT_FALSE(root->set3.contains(99)); 43 | EXPECT_FALSE(root->set3.contains(120)); 44 | 45 | EXPECT_EQ(root->strSet1.size(), std::size_t(6)); 46 | EXPECT_FALSE(root->strSet1.contains("zero")); 47 | EXPECT_TRUE(root->strSet1.contains("one")); 48 | EXPECT_TRUE(root->strSet1.contains("two")); 49 | EXPECT_TRUE(root->strSet1.contains("three")); 50 | EXPECT_TRUE(root->strSet1.contains("four")); 51 | EXPECT_TRUE(root->strSet1.contains("123456")); 52 | EXPECT_TRUE(root->strSet1.contains("1234567")); 53 | EXPECT_FALSE(root->strSet1.contains("five")); 54 | EXPECT_FALSE(root->strSet1.contains("six")); 55 | EXPECT_FALSE(root->strSet1.contains("seven")); 56 | 57 | EXPECT_EQ(root->strSet2.size(), std::size_t(5)); 58 | EXPECT_TRUE(root->strSet2.contains("five")); 59 | EXPECT_TRUE(root->strSet2.contains("six")); 60 | EXPECT_TRUE(root->strSet2.contains("seven")); 61 | EXPECT_TRUE(root->strSet2.contains("eight")); 62 | EXPECT_TRUE(root->strSet2.contains("this-is-a-very-very-long-key-to-test-hasher")); 63 | EXPECT_FALSE(root->strSet2.contains("one")); 64 | EXPECT_FALSE(root->strSet2.contains("two")); 65 | EXPECT_FALSE(root->strSet2.contains("three")); 66 | EXPECT_FALSE(root->strSet2.contains("four")); 67 | } 68 | 69 | TEST(ZmeyaTestSuite, HashSetTest) 70 | { 71 | std::vector bytesCopy; 72 | { 73 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 74 | zm::BlobPtr root = blobBuilder->allocate(); 75 | 76 | // assign from std::unordered_set 77 | std::unordered_set testSet1 = {5, 7, 3, 11, 99}; 78 | blobBuilder->copyTo(root->set1, testSet1); 79 | 80 | // assign from std::initializer_list 81 | blobBuilder->copyTo(root->set2, {1, 2, 3, 4, 0, 99, 6}); 82 | 83 | // assign from std::unordered_set of strings 84 | std::unordered_set strSet1 = {"one", "two", "three", "four", "123456", "1234567"}; 85 | blobBuilder->copyTo(root->strSet1, strSet1); 86 | 87 | // assign from std::initializer_list of strings 88 | blobBuilder->copyTo(root->strSet2, {"five", "six", "seven", "eight", "this-is-a-very-very-long-key-to-test-hasher"}); 89 | 90 | validate(root.get()); 91 | 92 | zm::Span bytes = blobBuilder->finalize(); 93 | bytesCopy = utils::copyBytes(bytes); 94 | std::memset(bytes.data, 0xFF, bytes.size); 95 | } 96 | 97 | const HashSetTestRoot* rootCopy = (const HashSetTestRoot*)(bytesCopy.data()); 98 | 99 | validate(rootCopy); 100 | } 101 | -------------------------------------------------------------------------------- /ZmeyaTest07.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct HashMapTestRoot 6 | { 7 | zm::HashMap hashMap1; 8 | zm::HashMap hashMap2; 9 | zm::HashMap hashMap3; 10 | zm::HashMap strHashMap1; 11 | zm::HashMap strHashMap2; 12 | zm::HashMap strHashMap3; 13 | zm::HashMap strHashMap4; 14 | zm::HashMap strHashMap5; 15 | zm::HashMap strHashMap6; 16 | }; 17 | 18 | static void validate(const HashMapTestRoot* root) 19 | { 20 | EXPECT_EQ(root->hashMap1.size(), std::size_t(5)); 21 | EXPECT_FLOAT_EQ(root->hashMap1.find(3, -1.0f), 7.0f); 22 | EXPECT_FLOAT_EQ(root->hashMap1.find(4, -1.0f), 17.0f); 23 | EXPECT_FLOAT_EQ(root->hashMap1.find(9, -1.0f), 79.0f); 24 | EXPECT_FLOAT_EQ(root->hashMap1.find(11, -1.0f), 13.0f); 25 | EXPECT_FLOAT_EQ(root->hashMap1.find(12, -1.0f), -1.0f); 26 | EXPECT_FLOAT_EQ(root->hashMap1.find(77, 13.0f), 13.0f); 27 | EXPECT_EQ(root->hashMap1.find(99), nullptr); 28 | EXPECT_NE(root->hashMap1.find(3), nullptr); 29 | EXPECT_EQ(root->hashMap1.contains(99), false); 30 | EXPECT_EQ(root->hashMap1.contains(3), true); 31 | 32 | EXPECT_EQ(root->hashMap2.size(), std::size_t(3)); 33 | EXPECT_FLOAT_EQ(root->hashMap2.find(1, 0.0f), -1.0f); 34 | EXPECT_FLOAT_EQ(root->hashMap2.find(2, 0.0f), -2.0f); 35 | EXPECT_FLOAT_EQ(root->hashMap2.find(3, 0.0f), -3.0f); 36 | EXPECT_FLOAT_EQ(root->hashMap2.find(4, 0.0f), 0.0f); 37 | EXPECT_FLOAT_EQ(root->hashMap2.find(5, 0.0f), 0.0f); 38 | EXPECT_FLOAT_EQ(root->hashMap2.find(6, 0.0f), 0.0f); 39 | 40 | EXPECT_EQ(root->hashMap3.size(), std::size_t(0)); 41 | EXPECT_FLOAT_EQ(root->hashMap3.find(1, 0.0f), 0.0f); 42 | EXPECT_FLOAT_EQ(root->hashMap3.find(2, 0.0f), 0.0f); 43 | EXPECT_FLOAT_EQ(root->hashMap3.find(3, 0.0f), 0.0f); 44 | EXPECT_FALSE(root->hashMap3.contains(4)); 45 | EXPECT_FALSE(root->hashMap3.contains(5)); 46 | EXPECT_FALSE(root->hashMap3.contains(6)); 47 | 48 | EXPECT_EQ(root->strHashMap1.size(), std::size_t(3)); 49 | EXPECT_FLOAT_EQ(root->strHashMap1.find("one", 0.0f), 1.0f); 50 | EXPECT_FLOAT_EQ(root->strHashMap1.find("two", 0.0f), 2.0f); 51 | EXPECT_FLOAT_EQ(root->strHashMap1.find("three", 0.0f), 3.0f); 52 | EXPECT_FLOAT_EQ(root->strHashMap1.find("five", 0.0f), 0.0f); 53 | 54 | EXPECT_EQ(root->strHashMap2.size(), std::size_t(2)); 55 | EXPECT_FLOAT_EQ(root->strHashMap2.find("five", 0.0f), -5.0f); 56 | EXPECT_FLOAT_EQ(root->strHashMap2.find("six", 0.0f), -6.0f); 57 | EXPECT_FLOAT_EQ(root->strHashMap2.find("seven", 0.0f), 0.0f); 58 | 59 | EXPECT_EQ(root->strHashMap3.size(), std::size_t(5)); 60 | EXPECT_STREQ(root->strHashMap3.find(1, ""), "one"); 61 | EXPECT_STREQ(root->strHashMap3.find(2, ""), "two"); 62 | EXPECT_STREQ(root->strHashMap3.find(3, ""), "three"); 63 | EXPECT_STREQ(root->strHashMap3.find(5, ""), "five"); 64 | EXPECT_STREQ(root->strHashMap3.find(10, ""), "ten"); 65 | EXPECT_STREQ(root->strHashMap3.find(13, nullptr), nullptr); 66 | 67 | EXPECT_EQ(root->strHashMap4.size(), std::size_t(2)); 68 | EXPECT_STREQ(root->strHashMap4.find(5, ""), "five"); 69 | EXPECT_STREQ(root->strHashMap4.find(7, ""), "seven"); 70 | EXPECT_STREQ(root->strHashMap4.find(6, nullptr), nullptr); 71 | 72 | EXPECT_EQ(root->strHashMap5.size(), std::size_t(5)); 73 | EXPECT_STREQ(root->strHashMap5.find("1", ""), "one"); 74 | EXPECT_STREQ(root->strHashMap5.find("2", ""), "two"); 75 | EXPECT_STREQ(root->strHashMap5.find("3", ""), "three"); 76 | EXPECT_STREQ(root->strHashMap5.find("5", ""), "five"); 77 | EXPECT_STREQ(root->strHashMap5.find("10", ""), "ten"); 78 | EXPECT_STREQ(root->strHashMap5.find("13", nullptr), nullptr); 79 | 80 | EXPECT_EQ(root->strHashMap6.size(), std::size_t(2)); 81 | EXPECT_STREQ(root->strHashMap6.find("5", ""), "five"); 82 | EXPECT_STREQ(root->strHashMap6.find("7", ""), "seven"); 83 | EXPECT_STREQ(root->strHashMap6.find("6", nullptr), nullptr); 84 | } 85 | 86 | TEST(ZmeyaTestSuite, HashMapTest) 87 | { 88 | std::vector bytesCopy; 89 | { 90 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 91 | zm::BlobPtr root = blobBuilder->allocate(); 92 | 93 | // assign from std::unordered_map 94 | std::unordered_map testMap = {{3, 7.0f}, {4, 17.0f}, {9, 79.0f}, {11, 13.0f}, {77, 13.0f}}; 95 | blobBuilder->copyTo(root->hashMap1, testMap); 96 | 97 | // assign from std::initializer_list 98 | blobBuilder->copyTo(root->hashMap2, {{1, -1.0f}, {2, -2.0f}, {3, -3.0f}}); 99 | 100 | // assign from std::unordered_map (string key) 101 | std::unordered_map strMap1 = {{"one", 1.0f}, {"two", 2.0f}, {"three", 3.0f}}; 102 | blobBuilder->copyTo(root->strHashMap1, strMap1); 103 | 104 | // assign from std::initializer_list (string key) 105 | blobBuilder->copyTo(root->strHashMap2, {{"five", -5.0f}, {"six", -6.0f}}); 106 | 107 | // assign from std::unordered_map (string value) 108 | std::unordered_map strMap3 = {{1, "one"}, {2, "two"}, {3, "three"}, {5, "five"}, {10, "ten"}}; 109 | blobBuilder->copyTo(root->strHashMap3, strMap3); 110 | 111 | // assign from std::initializer_list (string value) 112 | blobBuilder->copyTo(root->strHashMap4, {{5, "five"}, {7, "seven"}}); 113 | 114 | // assign from std::unordered_map (string key+value) 115 | std::unordered_map strMap5 = {{"1", "one"}, {"2", "two"}, {"3", "three"}, {"5", "five"}, {"10", "ten"}}; 116 | blobBuilder->copyTo(root->strHashMap5, strMap5); 117 | 118 | // assign from std::initializer_list (string key+value) 119 | blobBuilder->copyTo(root->strHashMap6, {{"5", "five"}, {"7", "seven"}}); 120 | 121 | validate(root.get()); 122 | 123 | zm::Span bytes = blobBuilder->finalize(); 124 | bytesCopy = utils::copyBytes(bytes); 125 | std::memset(bytes.data, 0xFF, bytes.size); 126 | } 127 | 128 | const HashMapTestRoot* rootCopy = (const HashMapTestRoot*)(bytesCopy.data()); 129 | 130 | validate(rootCopy); 131 | } 132 | -------------------------------------------------------------------------------- /ZmeyaTest08.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct IteratorsTestRoot 6 | { 7 | zm::Array arr; 8 | zm::HashSet set; 9 | zm::HashMap map; 10 | }; 11 | 12 | static void validate(const IteratorsTestRoot* root) 13 | { 14 | std::vector temp; 15 | 16 | EXPECT_EQ(root->arr.size(), std::size_t(11)); 17 | temp.clear(); 18 | temp.resize(root->arr.size(), 0); 19 | for (const int& val : root->arr) 20 | { 21 | temp[val] += 1; 22 | } 23 | for (size_t i = 0; i < temp.size(); i++) 24 | { 25 | EXPECT_EQ(temp[i], 1); 26 | } 27 | 28 | EXPECT_EQ(root->set.size(), std::size_t(6)); 29 | temp.clear(); 30 | temp.resize(root->set.size(), 0); 31 | for (const int& val : root->set) 32 | { 33 | temp[val] += 1; 34 | } 35 | for (size_t i = 0; i < temp.size(); i++) 36 | { 37 | EXPECT_EQ(temp[i], 1); 38 | } 39 | 40 | EXPECT_EQ(root->map.size(), std::size_t(3)); 41 | temp.clear(); 42 | temp.resize(root->map.size() * 2, 0); 43 | for (const zm::Pair& val : root->map) 44 | { 45 | temp[val.first] += 1; 46 | temp[val.second] += 1; 47 | } 48 | for (size_t i = 0; i < temp.size(); i++) 49 | { 50 | EXPECT_EQ(temp[i], 1); 51 | } 52 | } 53 | 54 | TEST(ZmeyaTestSuite, IteratorsTest) 55 | { 56 | std::vector bytesCopy; 57 | { 58 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 59 | zm::BlobPtr root = blobBuilder->allocate(); 60 | 61 | blobBuilder->copyTo(root->arr, {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}); 62 | blobBuilder->copyTo(root->set, {0, 1, 4, 3, 5, 2}); 63 | blobBuilder->copyTo(root->map, {{0, 1}, {3, 2}, {4, 5}}); 64 | 65 | validate(root.get()); 66 | 67 | zm::Span bytes = blobBuilder->finalize(); 68 | 69 | bytesCopy = utils::copyBytes(bytes); 70 | std::memset(bytes.data, 0xFF, bytes.size); 71 | } 72 | 73 | const IteratorsTestRoot* rootCopy = (const IteratorsTestRoot*)(bytesCopy.data()); 74 | 75 | validate(rootCopy); 76 | } 77 | -------------------------------------------------------------------------------- /ZmeyaTest09.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct Vec2 6 | { 7 | float x; 8 | float y; 9 | 10 | Vec2() = default; 11 | Vec2(float _x, float _y) 12 | : x(_x) 13 | , y(_y) 14 | { 15 | } 16 | }; 17 | 18 | struct Node 19 | { 20 | zm::String name; 21 | }; 22 | 23 | // as long as inheritance = aggregation it is supported (but beware of different compilers paddings!) 24 | struct Object : public Node 25 | { 26 | zm::Pointer parent; 27 | Vec2 position; 28 | }; 29 | 30 | struct SimpleFileTestRoot 31 | { 32 | uint32_t magic; 33 | zm::Array objects; 34 | zm::HashSet hashSet; 35 | zm::HashMap hashMap; 36 | }; 37 | 38 | static void validate(const SimpleFileTestRoot* root) 39 | { 40 | EXPECT_EQ(root->magic, 0x59454D5Au); 41 | 42 | EXPECT_EQ(root->objects.size(), std::size_t(6)); 43 | EXPECT_EQ(root->objects[0].name, "root"); 44 | EXPECT_EQ(root->objects[1].name, "test1"); 45 | EXPECT_EQ(root->objects[2].name, "floor"); 46 | EXPECT_EQ(root->objects[3].name, "window"); 47 | EXPECT_EQ(root->objects[4].name, "arrow"); 48 | EXPECT_EQ(root->objects[5].name, "door"); 49 | 50 | for (size_t i = 0; i < root->objects.size(); i++) 51 | { 52 | const Object& object = root->objects[i]; 53 | EXPECT_FLOAT_EQ(object.position.x, float(i)); 54 | EXPECT_FLOAT_EQ(object.position.y, float(i + 4)); 55 | if (i == 0) 56 | { 57 | EXPECT_TRUE(object.parent == nullptr); 58 | } 59 | else 60 | { 61 | EXPECT_TRUE(object.parent.get() == &root->objects[i - 1]); 62 | } 63 | } 64 | 65 | EXPECT_EQ(root->hashSet.size(), std::size_t(3)); 66 | EXPECT_TRUE(root->hashSet.contains("one")); 67 | EXPECT_TRUE(root->hashSet.contains("two")); 68 | EXPECT_TRUE(root->hashSet.contains("three")); 69 | 70 | EXPECT_EQ(root->hashMap.size(), std::size_t(3)); 71 | EXPECT_FLOAT_EQ(root->hashMap.find("1", 0.0f), 1.0f); 72 | EXPECT_FLOAT_EQ(root->hashMap.find("2", 0.0f), 2.0f); 73 | EXPECT_FLOAT_EQ(root->hashMap.find("3", 0.0f), 3.0f); 74 | } 75 | 76 | static void generateTestFile(const char* fileName) 77 | { 78 | std::vector bytesCopy; 79 | { 80 | std::vector objectNames = {"root", "test1", "floor", "window", "arrow", "door"}; 81 | 82 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 83 | zm::BlobPtr root = blobBuilder->allocate(); 84 | root->magic = 0x59454D5A; 85 | blobBuilder->resizeArray(root->objects, 6); 86 | for (size_t i = 0; i < root->objects.size(); i++) 87 | { 88 | zm::BlobPtr object = blobBuilder->getArrayElement(root->objects, i); 89 | blobBuilder->copyTo(object->name, objectNames[i]); 90 | object->position = Vec2(float(i), float(i + 4)); 91 | 92 | if (i > 0) 93 | { 94 | const Object& parentObject = root->objects[i - 1]; 95 | blobBuilder->assignTo(object->parent, parentObject); 96 | } 97 | } 98 | 99 | blobBuilder->copyTo(root->hashSet, {"one", "two", "three"}); 100 | blobBuilder->copyTo(root->hashMap, {{"1", 1.0f}, {"2", 2.0f}, {"3", 3.0f}}); 101 | 102 | validate(root.get()); 103 | 104 | zm::Span bytes = blobBuilder->finalize(32); 105 | EXPECT_TRUE((bytes.size % 32) == 0); 106 | 107 | bytesCopy = utils::copyBytes(bytes); 108 | std::memset(bytes.data, 0xFF, bytes.size); 109 | } 110 | 111 | const SimpleFileTestRoot* rootCopy = (const SimpleFileTestRoot*)(bytesCopy.data()); 112 | validate(rootCopy); 113 | 114 | FILE* file = fopen(fileName, "wb"); 115 | ASSERT_TRUE(file != nullptr); 116 | fwrite(bytesCopy.data(), bytesCopy.size(), 1, file); 117 | fclose(file); 118 | } 119 | 120 | TEST(ZmeyaTestSuite, SimpleFileTest) 121 | { 122 | const char* fileName = "test.zm"; 123 | generateTestFile(fileName); 124 | 125 | std::vector content; 126 | 127 | // read file 128 | FILE* file = fopen(fileName, "rb"); 129 | ASSERT_TRUE(file != nullptr); 130 | fseek(file, 0L, SEEK_END); 131 | long fileSize = ftell(file); 132 | fseek(file, 0L, SEEK_SET); 133 | content.resize(fileSize); 134 | fread(content.data(), fileSize, 1, file); 135 | fclose(file); 136 | 137 | const SimpleFileTestRoot* fileRoot = (const SimpleFileTestRoot*)(content.data()); 138 | validate(fileRoot); 139 | } 140 | -------------------------------------------------------------------------------- /ZmeyaTest10.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | #if _WIN32 6 | #include 7 | #endif 8 | 9 | /* 10 | 11 | This test is a nightmare for any serialization system (not for Zmeya) 12 | a ton of different objects, everything is linked by pointers, 13 | plus inheritance and different hash containers. 14 | 15 | */ 16 | 17 | enum class NodeType : uint32_t 18 | { 19 | NodeType1 = 1, 20 | NodeType2 = 2, 21 | Leaf = 3, 22 | }; 23 | 24 | struct MMapTestNode 25 | { 26 | zm::String name; 27 | NodeType nodeType; 28 | zm::Array> children; 29 | }; 30 | 31 | struct MMapTestRoot 32 | { 33 | uint32_t magic; 34 | zm::String desc; 35 | zm::HashMap hashMap; 36 | zm::Array> roots; 37 | }; 38 | 39 | struct MMapTestLeafNode : public MMapTestNode 40 | { 41 | uint32_t payload; 42 | zm::Pointer parent; 43 | }; 44 | 45 | struct MMapTestNode1 : public MMapTestNode 46 | { 47 | zm::String str1; 48 | uint32_t idx; 49 | zm::Pointer root; 50 | }; 51 | 52 | struct MMapTestNode2 : public MMapTestNode 53 | { 54 | zm::String str1; 55 | zm::HashSet hashSet; 56 | }; 57 | 58 | static void validateChildren(const MMapTestNode* parent, size_t count, size_t startIndex) 59 | { 60 | EXPECT_EQ(parent->children.size(), count); 61 | 62 | for (size_t i = 0; i < count; i++) 63 | { 64 | const MMapTestNode* nodeBase = parent->children[i].get(); 65 | EXPECT_EQ(nodeBase->nodeType, NodeType::Leaf); 66 | std::string expectedName = "leaf_" + std::to_string(startIndex + i); 67 | EXPECT_EQ(nodeBase->name, expectedName); 68 | ZMEYA_ASSERT(nodeBase->nodeType == NodeType::Leaf); 69 | 70 | const MMapTestLeafNode* node = reinterpret_cast(nodeBase); 71 | EXPECT_EQ(node->payload, uint32_t(count + startIndex * 13)); 72 | EXPECT_EQ(node->parent.get(), parent); 73 | } 74 | } 75 | 76 | static void validateNode1(const MMapTestNode* nodeBase, size_t index) 77 | { 78 | EXPECT_EQ(nodeBase->nodeType, NodeType::NodeType1); 79 | std::string expectedName = "node_" + std::to_string(index); 80 | EXPECT_EQ(nodeBase->name, expectedName); 81 | ZMEYA_ASSERT(nodeBase->nodeType == NodeType::NodeType1); 82 | 83 | const MMapTestNode1* node = reinterpret_cast(nodeBase); 84 | EXPECT_EQ(node->str1, "Zmyea test file. This is supposed to be a long enough string. I think it is long enough now."); 85 | EXPECT_EQ(node->idx, uint32_t(index)); 86 | size_t numChildrenNodes = 1 + (index % 6); 87 | validateChildren(node, numChildrenNodes, index); 88 | } 89 | static void validateNode2(const MMapTestNode* nodeBase, size_t index) 90 | { 91 | EXPECT_EQ(nodeBase->nodeType, NodeType::NodeType2); 92 | std::string expectedName = "item_" + std::to_string(index); 93 | EXPECT_EQ(nodeBase->name, expectedName); 94 | ZMEYA_ASSERT(nodeBase->nodeType == NodeType::NodeType2); 95 | 96 | const MMapTestNode2* node = reinterpret_cast(nodeBase); 97 | EXPECT_EQ(node->str1, "Zmyea test file. This is supposed to be a long enough string. I think it is long enough now."); 98 | 99 | EXPECT_EQ(node->hashSet.size(), std::size_t(3)); 100 | EXPECT_TRUE(node->hashSet.contains(int32_t(index + 1))); 101 | EXPECT_TRUE(node->hashSet.contains(int32_t(index + 2))); 102 | EXPECT_TRUE(node->hashSet.contains(int32_t(index + 3))); 103 | size_t numChildrenNodes = 2; 104 | validateChildren(node, numChildrenNodes, index); 105 | } 106 | 107 | static void validate(const MMapTestRoot* root) 108 | { 109 | EXPECT_EQ(root->magic, 0x59454D5Au); 110 | EXPECT_EQ(root->desc, "Zmyea test file. This is supposed to be a long enough string. I think it is long enough now."); 111 | 112 | EXPECT_EQ(root->hashMap.size(), std::size_t(6)); 113 | EXPECT_FLOAT_EQ(root->hashMap.find("one", 0.0f), 1.0f); 114 | EXPECT_FLOAT_EQ(root->hashMap.find("two", 0.0f), 2.0f); 115 | EXPECT_FLOAT_EQ(root->hashMap.find("three", 0.0f), 3.0f); 116 | EXPECT_FLOAT_EQ(root->hashMap.find("four", 0.0f), 4.0f); 117 | EXPECT_FLOAT_EQ(root->hashMap.find("five", 0.0f), 5.0f); 118 | EXPECT_FLOAT_EQ(root->hashMap.find("six", 0.0f), 6.0f); 119 | 120 | EXPECT_EQ(root->roots.size(), std::size_t(512)); 121 | for (size_t i = 0; i < root->roots.size(); i++) 122 | { 123 | const zm::Pointer& rootNode = root->roots[i]; 124 | if ((i & 1) == 0) 125 | { 126 | validateNode1(rootNode.get(), i); 127 | } 128 | else 129 | { 130 | validateNode2(rootNode.get(), i); 131 | } 132 | } 133 | } 134 | 135 | void createChildren(zm::BlobBuilder* blobBuilder, const zm::BlobPtr& parent, size_t count, size_t startIndex) 136 | { 137 | for (size_t i = 0; i < count; i++) 138 | { 139 | zm::BlobPtr node = blobBuilder->allocate(); 140 | node->nodeType = NodeType::Leaf; 141 | blobBuilder->copyTo(node->name, "leaf_" + std::to_string(startIndex + i)); 142 | node->payload = uint32_t(count + startIndex * 13); 143 | node->parent = parent; 144 | zm::BlobPtr> ch = blobBuilder->getArrayElement(parent->children, i); 145 | *ch = node; 146 | } 147 | } 148 | 149 | zm::BlobPtr allocateNode1(zm::BlobBuilder* blobBuilder, const zm::BlobPtr& root, size_t index) 150 | { 151 | zm::BlobPtr node = blobBuilder->allocate(); 152 | node->nodeType = NodeType::NodeType1; 153 | blobBuilder->copyTo(node->name, "node_" + std::to_string(index)); 154 | blobBuilder->referTo(node->str1, root->desc); 155 | node->idx = uint32_t(index); 156 | size_t numChildrenNodes = 1 + (index % 6); 157 | blobBuilder->resizeArray(node->children, numChildrenNodes); 158 | createChildren(blobBuilder, node, numChildrenNodes, index); 159 | return node; 160 | } 161 | 162 | zm::BlobPtr allocateNode2(zm::BlobBuilder* blobBuilder, const zm::BlobPtr& root, size_t index) 163 | { 164 | zm::BlobPtr node = blobBuilder->allocate(); 165 | node->nodeType = NodeType::NodeType2; 166 | blobBuilder->copyTo(node->name, "item_" + std::to_string(index)); 167 | blobBuilder->referTo(node->str1, root->desc); 168 | blobBuilder->copyTo(node->hashSet, {int32_t(index + 1), int32_t(index + 2), int32_t(index + 3)}); 169 | size_t numChildrenNodes = 2; 170 | blobBuilder->resizeArray(node->children, numChildrenNodes); 171 | createChildren(blobBuilder, node, numChildrenNodes, index); 172 | return node; 173 | } 174 | 175 | static void generateTestFile(const char* fileName) 176 | { 177 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 178 | zm::BlobPtr root = blobBuilder->allocate(); 179 | root->magic = 0x59454D5A; 180 | blobBuilder->copyTo(root->desc, "Zmyea test file. This is supposed to be a long enough string. I think it is long enough now."); 181 | blobBuilder->copyTo(root->hashMap, {{"one", 1.0f}, {"two", 2.0f}, {"three", 3.0f}, {"four", 4.0f}, {"five", 5.0f}, {"six", 6.0f}}); 182 | size_t numRoots = 512; 183 | blobBuilder->resizeArray(root->roots, numRoots); 184 | for (size_t i = 0; i < 512; i++) 185 | { 186 | zm::BlobPtr rootNode; 187 | if ((i & 1) == 0) 188 | { 189 | rootNode = allocateNode1(blobBuilder.get(), root, i); 190 | } 191 | else 192 | { 193 | rootNode = allocateNode2(blobBuilder.get(), root, i); 194 | } 195 | zm::BlobPtr> rt = blobBuilder->getArrayElement(root->roots, i); 196 | *rt = rootNode; 197 | } 198 | 199 | validate(root.get()); 200 | 201 | zm::Span bytes = blobBuilder->finalize(32); 202 | EXPECT_TRUE((bytes.size % 32) == std::size_t(0)); 203 | 204 | FILE* file = fopen(fileName, "wb"); 205 | ASSERT_TRUE(file != nullptr); 206 | fwrite(bytes.data, bytes.size, 1, file); 207 | fclose(file); 208 | } 209 | 210 | TEST(ZmeyaTestSuite, MMapTest) 211 | { 212 | const char* fileName = "mmaptest.zm"; 213 | generateTestFile(fileName); 214 | 215 | #if _WIN32 216 | // use memory mapped file view to access the data 217 | HANDLE hFile = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 218 | ASSERT_TRUE(hFile != INVALID_HANDLE_VALUE); 219 | 220 | LARGE_INTEGER fileSizeInBytes; 221 | BOOL res = GetFileSizeEx(hFile, &fileSizeInBytes); 222 | ASSERT_TRUE(res); 223 | 224 | HANDLE hMapping = CreateFileMapping(hFile, 0, PAGE_READONLY | SEC_COMMIT, fileSizeInBytes.HighPart, fileSizeInBytes.LowPart, 0); 225 | ASSERT_TRUE(hMapping != NULL); 226 | 227 | const MMapTestRoot* fileRoot = (const MMapTestRoot*)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, size_t(fileSizeInBytes.QuadPart)); 228 | ASSERT_TRUE(fileRoot != nullptr); 229 | 230 | validate(fileRoot); 231 | 232 | UnmapViewOfFile(fileRoot); 233 | CloseHandle(hMapping); 234 | CloseHandle(hFile); 235 | #endif 236 | } 237 | -------------------------------------------------------------------------------- /ZmeyaTest11.cpp: -------------------------------------------------------------------------------- 1 | #include "TestHelper.h" 2 | #include "Zmeya.h" 3 | #include "gtest/gtest.h" 4 | 5 | struct ReferToTestNode 6 | { 7 | zm::String str; 8 | zm::Array arr; 9 | zm::HashSet hashSet; 10 | zm::HashMap hashMap; 11 | }; 12 | 13 | struct ReferToTestRoot 14 | { 15 | zm::String str; 16 | zm::Array arr; 17 | zm::HashSet hashSet; 18 | zm::HashMap hashMap; 19 | 20 | zm::Array nodes; 21 | }; 22 | 23 | template static void validateNode(const T* node) 24 | { 25 | EXPECT_STREQ(node->str.c_str(), "This is supposed to be a long enough string. I think it is long enough now."); 26 | EXPECT_EQ(node->arr.size(), std::size_t(10)); 27 | EXPECT_EQ(node->arr[0], 1); 28 | EXPECT_EQ(node->arr[1], 2); 29 | EXPECT_EQ(node->arr[2], 5); 30 | EXPECT_EQ(node->arr[3], 8); 31 | EXPECT_EQ(node->arr[4], 13); 32 | EXPECT_EQ(node->arr[5], 99); 33 | EXPECT_EQ(node->arr[6], 7); 34 | EXPECT_EQ(node->arr[7], 160); 35 | EXPECT_EQ(node->arr[8], 293); 36 | EXPECT_EQ(node->arr[9], 890); 37 | 38 | EXPECT_EQ(node->hashSet.size(), std::size_t(6)); 39 | EXPECT_TRUE(node->hashSet.contains(1)); 40 | EXPECT_TRUE(node->hashSet.contains(5)); 41 | EXPECT_TRUE(node->hashSet.contains(15)); 42 | EXPECT_TRUE(node->hashSet.contains(23)); 43 | EXPECT_TRUE(node->hashSet.contains(38)); 44 | EXPECT_TRUE(node->hashSet.contains(31)); 45 | EXPECT_FALSE(node->hashSet.contains(32)); 46 | 47 | EXPECT_EQ(node->hashMap.size(), std::size_t(4)); 48 | EXPECT_FLOAT_EQ(node->hashMap.find("one", -1.0f), 1.0f); 49 | EXPECT_FLOAT_EQ(node->hashMap.find("two", -1.0f), 2.0f); 50 | EXPECT_FLOAT_EQ(node->hashMap.find("three", -1.0f), 3.0f); 51 | EXPECT_FLOAT_EQ(node->hashMap.find("four", -1.0f), 4.0f); 52 | } 53 | 54 | static void validate(const ReferToTestRoot* root) 55 | { 56 | validateNode(root); 57 | EXPECT_EQ(root->nodes.size(), std::size_t(10000)); 58 | for (size_t i = 0; i < root->nodes.size(); i++) 59 | { 60 | validateNode(&root->nodes[i]); 61 | } 62 | } 63 | 64 | TEST(ZmeyaTestSuite, ReferToTest) 65 | { 66 | std::vector bytesCopy; 67 | { 68 | // create blob 69 | std::shared_ptr blobBuilder = zm::BlobBuilder::create(1); 70 | 71 | // allocate structure 72 | zm::BlobPtr root = blobBuilder->allocate(); 73 | blobBuilder->copyTo(root->str, "This is supposed to be a long enough string. I think it is long enough now."); 74 | blobBuilder->copyTo(root->arr, {1, 2, 5, 8, 13, 99, 7, 160, 293, 890}); 75 | blobBuilder->copyTo(root->hashSet, {1, 5, 15, 23, 38, 31}); 76 | blobBuilder->copyTo(root->hashMap, {{"one", 1.0f}, {"two", 2.0f}, {"three", 3.0f}, {"four", 4.0f}}); 77 | 78 | float f1 = root->hashMap.find("one", -1.0f); 79 | EXPECT_FLOAT_EQ(f1, 1.0f); 80 | 81 | float f2 = root->hashMap.find("two", -1.0f); 82 | EXPECT_FLOAT_EQ(f2, 2.0f); 83 | 84 | float f3 = root->hashMap.find("three", -1.0f); 85 | EXPECT_FLOAT_EQ(f3, 3.0f); 86 | 87 | float f4 = root->hashMap.find("four", -1.0f); 88 | EXPECT_FLOAT_EQ(f4, 4.0f); 89 | 90 | 91 | size_t nodesCount = 10000; 92 | blobBuilder->resizeArray(root->nodes, nodesCount); 93 | for (size_t i = 0; i < nodesCount; i++) 94 | { 95 | zm::BlobPtr node = blobBuilder->getArrayElement(root->nodes, i); 96 | blobBuilder->referTo(node->str, root->str); 97 | blobBuilder->referTo(node->arr, root->arr); 98 | blobBuilder->referTo(node->hashSet, root->hashSet); 99 | blobBuilder->referTo(node->hashMap, root->hashMap); 100 | } 101 | 102 | validate(root.get()); 103 | 104 | zm::Span bytes = blobBuilder->finalize(); 105 | EXPECT_LE(bytes.size, std::size_t(450000)); 106 | bytesCopy = utils::copyBytes(bytes); 107 | std::memset(bytes.data, 0xFF, bytes.size); 108 | } 109 | 110 | const ReferToTestRoot* rootCopy = (const ReferToTestRoot*)(bytesCopy.data()); 111 | validate(rootCopy); 112 | } 113 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{branch}-ci-{build}" 2 | image: Visual Studio 2017 3 | 4 | build: msvc 5 | platform: x86 6 | configuration: Debug 7 | 8 | install: 9 | - choco install opencppcoverage codecov 10 | - set PATH=C:\Program Files\OpenCppCoverage;%PATH% 11 | 12 | build_script: 13 | - git submodule update --init --recursive 14 | - mkdir build 15 | - cd build 16 | - cmake .. 17 | - cd .. 18 | - cmake --build .\build\ --config Debug 19 | - OpenCppCoverage.exe --export_type=cobertura:cobertura.xml --sources Zmeya*.* --excluded_sources *googletest* --modules *.exe -- .\build\Debug\ZmeyaTest.exe 20 | - codecov -f cobertura.xml --root %APPVEYOR_BUILD_FOLDER% -t 11f29925-c727-4836-945c-a210c2756e2f 21 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set builddir=build 3 | if not exist %builddir% goto GENERATE 4 | del %builddir% /S /Q 5 | :GENERATE 6 | mkdir %builddir% 7 | cd %builddir% 8 | cmake .. 9 | cd .. 10 | cmake --build .\build\ --config Debug 11 | OpenCppCoverage.exe --sources Zmeya*.* --excluded_sources *googletest* --modules *.exe -- .\build\Debug\ZmeyaTest.exe 12 | -------------------------------------------------------------------------------- /gen19.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set builddir=build2019 3 | if not exist %builddir% goto GENERATE 4 | del %builddir% /S /Q 5 | :GENERATE 6 | mkdir %builddir% 7 | cd %builddir% 8 | cmake -G "Visual Studio 16 2019" ../ 9 | cd .. 10 | 11 | -------------------------------------------------------------------------------- /gen22.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set builddir=build2022 3 | if not exist %builddir% goto GENERATE 4 | del %builddir% /S /Q 5 | :GENERATE 6 | mkdir %builddir% 7 | cd %builddir% 8 | cmake -G "Visual Studio 17 2022" ../ 9 | cd .. 10 | 11 | --------------------------------------------------------------------------------