├── docs ├── Compile-timeTypeSignatures.pdf └── XOffsetDatastructure_CppCon2024.pdf ├── .gitmodules ├── schemas ├── vector_test.xds.yaml ├── compaction_test.xds.yaml ├── player.xds.yaml ├── map_set_test.xds.yaml ├── alignment_test.xds.yaml ├── basic_types.xds.yaml ├── modify_test.xds.yaml ├── serialization_test.xds.yaml ├── nested_test.xds.yaml ├── test_types.xds.yaml ├── game_data.xds.yaml └── README.md ├── .gitignore ├── LICENSE ├── examples ├── helloworld.cpp ├── CMakeLists.txt └── demo.cpp ├── tests ├── test_basic_types.cpp ├── test_const_support.cpp ├── test_msvc_compat.cpp ├── test_vector.cpp ├── run_all_tests.cpp ├── test_nested.cpp ├── test_map_set.cpp ├── test_compaction.cpp ├── test_type_signature.cpp ├── README.md ├── test_alignment.cpp ├── CMakeLists.txt ├── test_modify.cpp ├── test_serialization.cpp ├── test_platform.cpp └── test_comprehensive.cpp ├── README.md ├── generated ├── vector_test.hpp ├── player.hpp ├── alignment_test.hpp ├── map_set_test.hpp ├── compaction_test.hpp ├── basic_types.hpp ├── modify_test.hpp ├── serialization_test.hpp ├── game_data.hpp ├── test_types.hpp └── nested_test.hpp ├── CMakeLists.txt ├── .github └── workflows │ └── ci.yml └── tools └── xds_generator.py /docs/Compile-timeTypeSignatures.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximicpp/XOffsetDatastructure/HEAD/docs/Compile-timeTypeSignatures.pdf -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/boost"] 2 | path = external/boost 3 | url = https://github.com/boostorg/boost.git 4 | branch = boost-1.81.0 5 | -------------------------------------------------------------------------------- /docs/XOffsetDatastructure_CppCon2024.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximicpp/XOffsetDatastructure/HEAD/docs/XOffsetDatastructure_CppCon2024.pdf -------------------------------------------------------------------------------- /schemas/vector_test.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: VectorTest 5 | type: struct 6 | fields: 7 | - name: intVector 8 | type: XVector 9 | - name: floatVector 10 | type: XVector 11 | - name: stringVector 12 | type: XVector 13 | -------------------------------------------------------------------------------- /schemas/compaction_test.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: MemoryTestType 5 | type: struct 6 | fields: 7 | - name: value 8 | type: int 9 | default: 0 10 | - name: data 11 | type: XVector 12 | - name: strings 13 | type: XVector 14 | -------------------------------------------------------------------------------- /schemas/player.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: Player 5 | type: struct 6 | fields: 7 | - name: id 8 | type: int 9 | default: 0 10 | - name: level 11 | type: int 12 | default: 0 13 | - name: name 14 | type: XString 15 | - name: items 16 | type: XVector 17 | -------------------------------------------------------------------------------- /schemas/map_set_test.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: MapSetTest 5 | type: struct 6 | fields: 7 | - name: intSet 8 | type: XSet 9 | - name: stringSet 10 | type: XSet 11 | - name: intMap 12 | type: XMap 13 | - name: stringMap 14 | type: XMap 15 | -------------------------------------------------------------------------------- /schemas/alignment_test.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: AlignedStruct 5 | type: struct 6 | fields: 7 | - name: a 8 | type: char 9 | default: '\0' 10 | - name: b 11 | type: int 12 | default: 0 13 | - name: c 14 | type: double 15 | default: 0.0 16 | - name: name 17 | type: XString 18 | -------------------------------------------------------------------------------- /schemas/basic_types.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: BasicTypes 5 | type: struct 6 | fields: 7 | - name: mInt 8 | type: int 9 | default: 0 10 | - name: mFloat 11 | type: float 12 | default: 0.0 13 | - name: mDouble 14 | type: double 15 | default: 0.0 16 | - name: mChar 17 | type: char 18 | default: '\0' 19 | - name: mBool 20 | type: bool 21 | default: false 22 | - name: mInt64 23 | type: int64_t 24 | default: 0 25 | -------------------------------------------------------------------------------- /schemas/modify_test.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: ModifyTestData 5 | type: struct 6 | fields: 7 | - name: counter 8 | type: int 9 | default: 0 10 | - name: ratio 11 | type: float 12 | default: 0.0 13 | - name: active 14 | type: bool 15 | default: false 16 | - name: numbers 17 | type: XVector 18 | - name: names 19 | type: XVector 20 | - name: scores 21 | type: XMap 22 | - name: tags 23 | type: XSet 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build/ 3 | build-ci-test/ 4 | cmake-build-*/ 5 | 6 | # Compiled files 7 | *.exe 8 | *.obj 9 | *.o 10 | *.so 11 | *.dylib 12 | *.dll 13 | *.lib 14 | *.a 15 | *.pdb 16 | *.ilk 17 | 18 | # CMake generated files 19 | CMakeCache.txt 20 | CMakeFiles/ 21 | cmake_install.cmake 22 | CTestTestfile.cmake 23 | *.vcxproj 24 | *.vcxproj.filters 25 | *.sln 26 | 27 | # IDE and editor files 28 | .vscode/ 29 | .vs/ 30 | .idea/ 31 | *.swp 32 | *.swo 33 | *~ 34 | .DS_Store 35 | 36 | # Temporary files 37 | temp_* 38 | *.tmp 39 | *.bak 40 | *.log 41 | 42 | # Test outputs 43 | *.out 44 | test_output/ 45 | -------------------------------------------------------------------------------- /schemas/serialization_test.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: SimpleData 5 | type: struct 6 | fields: 7 | - name: id 8 | type: int 9 | default: 0 10 | - name: value 11 | type: float 12 | default: 0.0 13 | - name: name 14 | type: XString 15 | 16 | - name: ComplexData 17 | type: struct 18 | fields: 19 | - name: title 20 | type: XString 21 | - name: items 22 | type: XVector 23 | - name: tags 24 | type: XSet 25 | - name: metadata 26 | type: XMap 27 | -------------------------------------------------------------------------------- /schemas/nested_test.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: InnerObject 5 | type: struct 6 | fields: 7 | - name: id 8 | type: int 9 | default: 0 10 | - name: name 11 | type: XString 12 | - name: data 13 | type: XVector 14 | 15 | - name: MiddleObject 16 | type: struct 17 | fields: 18 | - name: name 19 | type: XString 20 | - name: inner 21 | type: InnerObject 22 | - name: values 23 | type: XVector 24 | 25 | - name: OuterObject 26 | type: struct 27 | fields: 28 | - name: title 29 | type: XString 30 | - name: middle 31 | type: MiddleObject 32 | - name: innerList 33 | type: XVector 34 | -------------------------------------------------------------------------------- /schemas/test_types.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: TestTypeInner 5 | type: struct 6 | fields: 7 | - name: mInt 8 | type: int 9 | default: 0 10 | - name: mVector 11 | type: XVector 12 | 13 | - name: TestType 14 | type: struct 15 | fields: 16 | - name: mInt 17 | type: int 18 | default: 0 19 | - name: mFloat 20 | type: float 21 | default: 0.0 22 | - name: mVector 23 | type: XVector 24 | - name: mStringVector 25 | type: XVector 26 | - name: TestTypeInnerObj 27 | type: TestTypeInner 28 | - name: mXXTypeVector 29 | type: XVector 30 | - name: mComplexMap 31 | type: XMap 32 | - name: mStringSet 33 | type: XSet 34 | - name: mSet 35 | type: XSet 36 | - name: mString 37 | type: XString 38 | 39 | codegen: 40 | output_dir: "generated" 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ximi 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 | -------------------------------------------------------------------------------- /schemas/game_data.xds.yaml: -------------------------------------------------------------------------------- 1 | schema_version: "1.0" 2 | 3 | types: 4 | - name: Item 5 | type: struct 6 | fields: 7 | - name: item_id 8 | type: int 9 | default: 0 10 | - name: item_type 11 | type: int 12 | default: 0 13 | description: "0=weapon, 1=armor, 2=potion, 3=material" 14 | - name: quantity 15 | type: int 16 | default: 0 17 | - name: name 18 | type: XString 19 | 20 | - name: GameData 21 | type: struct 22 | fields: 23 | - name: player_id 24 | type: int 25 | default: 0 26 | - name: level 27 | type: int 28 | default: 0 29 | - name: health 30 | type: float 31 | default: 0.0 32 | - name: player_name 33 | type: XString 34 | - name: items 35 | type: XVector 36 | description: "Inventory items with type info" 37 | - name: achievements 38 | type: XSet 39 | description: "Achievement IDs" 40 | - name: quest_progress 41 | type: XMap 42 | description: "Quest name -> progress %" 43 | -------------------------------------------------------------------------------- /examples/helloworld.cpp: -------------------------------------------------------------------------------- 1 | // XOffsetDatastructure2 Hello World Example 2 | 3 | #include 4 | #include "../xoffsetdatastructure2.hpp" 5 | #include "../generated/player.hpp" 6 | 7 | using namespace XOffsetDatastructure2; 8 | 9 | int main() { 10 | // Create buffer 11 | XBuffer xbuf(4096); 12 | 13 | // Create player 14 | auto* player = xbuf.make_root("Hero"); 15 | player->name = xbuf.create("Alice"); 16 | player->id = 1001; 17 | player->level = 10; 18 | player->items.push_back(101); 19 | player->items.push_back(102); 20 | player->items.push_back(103); 21 | 22 | std::cout << "Created player: " << player->name.c_str() 23 | << " (ID " << player->id << ", level " << player->level 24 | << ", " << player->items.size() << " items)\n"; 25 | 26 | // Serialize 27 | auto data = xbuf.save_to_string(); 28 | std::cout << "Serialized to " << data.size() << " bytes\n"; 29 | 30 | // Deserialize 31 | XBuffer loaded = XBuffer::load_from_string(data); 32 | auto [loaded_player, found] = loaded.find_root("Hero"); 33 | std::cout << "Loaded player: " << loaded_player->name.c_str() 34 | << " (level " << loaded_player->level << ")\n"; 35 | 36 | // Check memory 37 | auto stats = xbuf.stats(); 38 | std::cout << "Memory: " << stats.used_size << "/" << stats.total_size 39 | << " bytes (" << static_cast(stats.usage_percent()) << "%)\n"; 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Ensure C++20 standard 2 | set(CMAKE_CXX_STANDARD 20) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | 5 | # Hello World Demo 6 | add_executable(helloworld helloworld.cpp) 7 | target_include_directories(helloworld PRIVATE 8 | ${CMAKE_SOURCE_DIR} 9 | ${BOOST_INCLUDE_DIRS} 10 | ${CMAKE_SOURCE_DIR}/generated 11 | ) 12 | set_target_properties(helloworld 13 | PROPERTIES 14 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 15 | ) 16 | if(Python3_FOUND) 17 | add_dependencies(helloworld generate_schemas) 18 | endif() 19 | set_ios_bundle_id(helloworld) 20 | 21 | # Comprehensive Demo 22 | add_executable(demo demo.cpp) 23 | target_include_directories(demo PRIVATE 24 | ${CMAKE_SOURCE_DIR} 25 | ${BOOST_INCLUDE_DIRS} 26 | ${CMAKE_SOURCE_DIR}/generated 27 | ) 28 | set_target_properties(demo 29 | PROPERTIES 30 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 31 | ) 32 | if(Python3_FOUND) 33 | add_dependencies(demo generate_schemas) 34 | endif() 35 | set_ios_bundle_id(demo) 36 | 37 | # macOS specific: disable RPATH and use system libraries 38 | if(APPLE) 39 | set_target_properties(helloworld PROPERTIES 40 | SKIP_BUILD_RPATH TRUE 41 | BUILD_WITH_INSTALL_RPATH FALSE 42 | INSTALL_RPATH "" 43 | ) 44 | set_target_properties(demo PROPERTIES 45 | SKIP_BUILD_RPATH TRUE 46 | BUILD_WITH_INSTALL_RPATH FALSE 47 | INSTALL_RPATH "" 48 | ) 49 | target_link_options(helloworld PRIVATE 50 | -Wl,-rpath,/usr/lib 51 | -Wl,-rpath,/usr/local/lib 52 | ) 53 | target_link_options(demo PRIVATE 54 | -Wl,-rpath,/usr/lib 55 | -Wl,-rpath,/usr/local/lib 56 | ) 57 | endif() 58 | -------------------------------------------------------------------------------- /tests/test_basic_types.cpp: -------------------------------------------------------------------------------- 1 | // Test basic POD types 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | #include "basic_types.hpp" 7 | 8 | using namespace XOffsetDatastructure2; 9 | 10 | bool test_basic_types() { 11 | std::cout << "\nTesting basic types...\n"; 12 | 13 | // Create buffer 14 | XBuffer xbuf(1024); 15 | 16 | // Create and initialize object 17 | auto* obj = xbuf.make_root("BasicTypes"); 18 | obj->mInt = 42; 19 | obj->mFloat = 3.14f; 20 | obj->mDouble = 2.71828; 21 | obj->mChar = 'A'; 22 | obj->mBool = true; 23 | obj->mInt64 = 9223372036854775807LL; 24 | 25 | // Verify 26 | assert(obj->mInt == 42); 27 | assert(obj->mFloat == 3.14f); 28 | assert(obj->mDouble == 2.71828); 29 | assert(obj->mChar == 'A'); 30 | assert(obj->mBool == true); 31 | assert(obj->mInt64 == 9223372036854775807LL); 32 | 33 | // Test persistence 34 | auto* buffer = xbuf.get_buffer(); 35 | XBuffer loaded_buf(buffer->data(), buffer->size()); 36 | auto* loaded_obj = loaded_buf.find_root("BasicTypes").first; 37 | 38 | assert(loaded_obj->mInt == 42); 39 | assert(loaded_obj->mFloat == 3.14f); 40 | assert(loaded_obj->mDouble == 2.71828); 41 | assert(loaded_obj->mChar == 'A'); 42 | assert(loaded_obj->mBool == true); 43 | assert(loaded_obj->mInt64 == 9223372036854775807LL); 44 | 45 | std::cout << "All tests passed\n"; 46 | return true; 47 | } 48 | 49 | int main() { 50 | try { 51 | return test_basic_types() ? 0 : 1; 52 | } catch (const std::exception& e) { 53 | std::cerr << "Error: " << e.what() << "\n"; 54 | return 1; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/test_const_support.cpp: -------------------------------------------------------------------------------- 1 | #include "../xoffsetdatastructure2.hpp" 2 | #include 3 | #include 4 | 5 | using namespace XTypeSignature; 6 | 7 | // Test struct with const fields 8 | struct ConstFieldsExample { 9 | const int32_t id; // const field 10 | float value; // non-const field 11 | const double ratio; // const field 12 | }; 13 | 14 | int main() { 15 | std::cout << "=== Testing const T Support ===" << std::endl; 16 | std::cout << std::endl; 17 | 18 | // Test 1: const int32_t should have same signature as int32_t 19 | std::cout << "[TEST 1] const int32_t" << std::endl; 20 | auto sig_int32 = get_XTypeSignature(); 21 | auto sig_const_int32 = get_XTypeSignature(); 22 | 23 | std::cout << "int32_t signature: "; 24 | sig_int32.print(); 25 | std::cout << std::endl; 26 | 27 | std::cout << "const int32_t signature: "; 28 | sig_const_int32.print(); 29 | std::cout << std::endl; 30 | 31 | assert(sig_int32 == sig_const_int32); 32 | std::cout << "[PASS] const int32_t has same signature as int32_t" << std::endl; 33 | std::cout << std::endl; 34 | 35 | // Test 2: Struct with const fields should be reflectable 36 | std::cout << "[TEST 2] Struct with const fields" << std::endl; 37 | auto sig_const_fields = get_XTypeSignature(); 38 | 39 | std::cout << "ConstFieldsExample signature: "; 40 | sig_const_fields.print(); 41 | std::cout << std::endl; 42 | 43 | // Expected: struct[s:16,a:8]{@0:i32[s:4,a:4],@4:f32[s:4,a:4],@8:f64[s:8,a:8]} 44 | std::cout << "[PASS] Struct with const fields is reflectable" << std::endl; 45 | std::cout << std::endl; 46 | 47 | // Verify sizes 48 | static_assert(sizeof(ConstFieldsExample) == 16); 49 | static_assert(alignof(ConstFieldsExample) == 8); 50 | std::cout << "[PASS] Size and alignment verification" << std::endl; 51 | std::cout << std::endl; 52 | 53 | std::cout << "[SUCCESS] All const T support tests passed!" << std::endl; 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Introduction 2 | XOffsetDatastructure is a serialization library designed to reduce or even eliminate the performance consumption of serialization and deserialization by utilizing zero-encoding and zero-decoding. It is also a collection of high-performance data structures designed for efficient read and in-place/non-in-place write, with performance comparable to STL. 3 | 4 | ### Release Branches 5 | 6 | This repository maintains two parallel implementations: 7 | 8 | #### [release/v2.0-practical](https://github.com/ximicpp/XOffsetDatastructure/tree/release/v2.0-practical) - **Recommended** 9 | - **Reflection**: Uses [Boost.PFR](https://github.com/boostorg/pfr) for compile-time reflection 10 | - **Compatibility**: C++20 and above 11 | - **Stability**: Production-ready, thoroughly tested 12 | - **Use Case**: Recommended for production environments 13 | 14 | #### [release/v2.0-cpp26](https://github.com/ximicpp/XOffsetDatastructure/tree/release/v2.0-cpp26) - Experimental 15 | - **Reflection**: Uses C++26 native reflection (`std::meta`) 16 | - **Compatibility**: Requires C++26 (experimental compiler support) 17 | - **Stability**: Experimental, cutting-edge technology 18 | - **Use Case**: For exploring future C++ capabilities 19 | 20 | > **Which version should I use?** 21 | > For most users, we recommend **release/v2.0-practical**. It provides all features with proven stability and broad compiler support. 22 | 23 | ### CppCon 2024 24 | [CppCon 2024: Using Modern C++ to Build XOffsetDatastructure: A Zero-Encoding and Zero-Decoding High-Performance Serialization Library](https://github.com/CppCon/CppCon2024/blob/main/Presentations/Using_Modern_Cpp_to_Build_XOffsetDatastructure.pdf) 25 | 26 | ### CppCon 2025 27 | [CppCon 2025: Cross-platform XOffsetDatastructure: Ensuring Zero-encoding/Zero-decoding Serialization Compatibility Through Compile-time Type Signatures](https://github.com/ximicpp/XOffsetDatastructure/blob/main/docs/Compile-timeTypeSignatures.pdf) 28 | 29 | ### PS: 30 | Benchmark code and results: see the tag [“v1.0.0: CppCon 2024 Milestone Release (Latest)”](https://github.com/ximicpp/XOffsetDatastructure/releases/tag/v1.0.0) -------------------------------------------------------------------------------- /tests/test_msvc_compat.cpp: -------------------------------------------------------------------------------- 1 | // Test MSVC compatibility 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | #include "../generated/game_data.hpp" 7 | 8 | using namespace XOffsetDatastructure2; 9 | 10 | // MSVC Issue: Struct with skipped members in init list 11 | struct GameLikeStruct { 12 | template 13 | GameLikeStruct(Allocator allocator) 14 | : name(allocator), items(allocator), int_set(allocator), string_map(allocator) {} 15 | 16 | int id{0}; // Not in init list 17 | int level{0}; // Not in init list 18 | float health{0.0f}; // Not in init list 19 | XString name; // First in init list 20 | XVector items; 21 | XSet int_set; 22 | XMap string_map; 23 | }; 24 | 25 | bool test_init_list_compatibility() { 26 | std::cout << "\nTesting init list compatibility...\n"; 27 | 28 | XBuffer xbuf(2048); 29 | 30 | // Create struct with skipped members 31 | std::cout << " create object... "; 32 | auto* obj = xbuf.make_root("game_like"); 33 | std::cout << "ok\n"; 34 | 35 | // Verify default values 36 | std::cout << " verify defaults... "; 37 | assert(obj->id == 0); 38 | assert(obj->level == 0); 39 | assert(obj->health == 0.0f); 40 | std::cout << "ok\n"; 41 | 42 | // Set all fields 43 | std::cout << " set fields... "; 44 | obj->id = 999; 45 | obj->level = 50; 46 | obj->health = 75.5f; 47 | obj->name = XString("TestName", xbuf.allocator()); 48 | assert(obj->name == "TestName"); 49 | std::cout << "ok\n"; 50 | 51 | // Add to containers 52 | std::cout << " add to containers... "; 53 | obj->items.push_back(1); 54 | obj->int_set.insert(100); 55 | XString key("key1", xbuf.allocator()); 56 | obj->string_map.emplace(key, 200); 57 | assert(obj->items.size() == 1); 58 | assert(obj->int_set.size() == 1); 59 | assert(obj->string_map.size() == 1); 60 | std::cout << "ok\n"; 61 | 62 | std::cout << "All tests passed\n"; 63 | return true; 64 | } 65 | 66 | int main() { 67 | try { 68 | bool all_passed = test_init_list_compatibility(); 69 | 70 | if (all_passed) { 71 | std::cout << "\nAll MSVC compatibility tests passed\n"; 72 | } else { 73 | std::cout << "\nSome tests failed\n"; 74 | } 75 | 76 | return all_passed ? 0 : 1; 77 | 78 | } catch (const std::exception& e) { 79 | std::cerr << "Exception: " << e.what() << "\n"; 80 | return 1; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/test_vector.cpp: -------------------------------------------------------------------------------- 1 | // Test XVector operations 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | 7 | using namespace XOffsetDatastructure2; 8 | 9 | struct alignas(BASIC_ALIGNMENT) VectorTest { 10 | template 11 | VectorTest(Allocator allocator) 12 | : intVector(allocator), 13 | floatVector(allocator), 14 | stringVector(allocator) {} 15 | 16 | XVector intVector; 17 | XVector floatVector; 18 | XVector stringVector; 19 | }; 20 | 21 | bool test_vector_operations() { 22 | std::cout << "\nTesting vector operations...\n"; 23 | 24 | XBuffer xbuf(4096); 25 | auto* obj = xbuf.make_root("VectorTest"); 26 | 27 | // push_back 28 | std::cout << " push_back... "; 29 | for (int i = 0; i < 100; ++i) { 30 | obj->intVector.push_back(i); 31 | obj->floatVector.push_back(i * 1.5f); 32 | } 33 | assert(obj->intVector.size() == 100); 34 | assert(obj->floatVector.size() == 100); 35 | std::cout << "ok\n"; 36 | 37 | // Element access 38 | std::cout << " element access... "; 39 | assert(obj->intVector[0] == 0); 40 | assert(obj->intVector[50] == 50); 41 | assert(obj->intVector[99] == 99); 42 | assert(obj->floatVector[10] == 15.0f); 43 | std::cout << "ok\n"; 44 | 45 | // String vector 46 | std::cout << " string vector... "; 47 | for (int i = 0; i < 20; ++i) { 48 | std::string str = "String_" + std::to_string(i); 49 | obj->stringVector.emplace_back(XString(str.c_str(), xbuf.allocator())); 50 | } 51 | assert(obj->stringVector.size() == 20); 52 | assert(obj->stringVector[0] == "String_0"); 53 | assert(obj->stringVector[19] == "String_19"); 54 | std::cout << "ok\n"; 55 | 56 | // Iteration 57 | std::cout << " iteration... "; 58 | int sum = 0; 59 | for (const auto& val : obj->intVector) { 60 | sum += val; 61 | } 62 | assert(sum == 4950); 63 | std::cout << "ok\n"; 64 | 65 | // Clear 66 | std::cout << " clear... "; 67 | obj->intVector.clear(); 68 | assert(obj->intVector.empty()); 69 | assert(obj->intVector.size() == 0); 70 | std::cout << "ok\n"; 71 | 72 | // Persistence 73 | std::cout << " persistence... "; 74 | auto* buffer = xbuf.get_buffer(); 75 | XBuffer loaded_buf(buffer->data(), buffer->size()); 76 | auto* loaded_obj = loaded_buf.find_root("VectorTest").first; 77 | assert(loaded_obj->floatVector.size() == 100); 78 | assert(loaded_obj->stringVector.size() == 20); 79 | assert(loaded_obj->stringVector[5] == "String_5"); 80 | assert(loaded_obj->stringVector[19] == "String_19"); 81 | std::cout << "ok\n"; 82 | 83 | std::cout << "All tests passed\n"; 84 | return true; 85 | } 86 | 87 | int main() { 88 | try { 89 | return test_vector_operations() ? 0 : 1; 90 | } catch (const std::exception& e) { 91 | std::cerr << "Error: " << e.what() << "\n"; 92 | return 1; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /generated/vector_test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_VECTOR_TEST_HPP_ 2 | #define GENERATED_VECTOR_TEST_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) VectorTest { 13 | // Default constructor 14 | template 15 | VectorTest(Allocator allocator) : intVector(allocator), floatVector(allocator), stringVector(allocator) {} 16 | 17 | XVector intVector; 18 | XVector floatVector; 19 | XVector stringVector; 20 | }; 21 | 22 | // ============================================================================ 23 | // Reflection Hint Types - Used for compile-time type analysis 24 | // ============================================================================ 25 | // These are aggregate versions of runtime types that satisfy boost::pfr 26 | // requirements for reflection. They must have identical memory layout 27 | // to their runtime counterparts. 28 | // ============================================================================ 29 | 30 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) VectorTestReflectionHint { 31 | XVector intVector; 32 | XVector floatVector; 33 | XVector stringVector; 34 | 35 | // Field names metadata for XTypeSignature 36 | static constexpr std::string_view _field_names[] = { 37 | "intVector", 38 | "floatVector", 39 | "stringVector", 40 | }; 41 | }; 42 | 43 | // ============================================================================ 44 | // Compile-Time Validation 45 | // ============================================================================ 46 | 47 | // Compile-time validation for VectorTest 48 | 49 | // 1. Type Safety Check 50 | // Type safety verification uses Boost.PFR for recursive member checking. 51 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 52 | "Type safety error for VectorTestReflectionHint"); 53 | 54 | // 2. Size and Alignment Check 55 | static_assert(sizeof(VectorTest) == sizeof(VectorTestReflectionHint), 56 | "Size mismatch: VectorTest runtime and reflection types must have identical size"); 57 | static_assert(alignof(VectorTest) == alignof(VectorTestReflectionHint), 58 | "Alignment mismatch: VectorTest runtime and reflection types must have identical alignment"); 59 | 60 | // 3. Type Signature Check 61 | // Type signature verification uses unified Boost.PFR implementation 62 | // All compilers use lightweight tuple_element and tuple_size_v APIs 63 | static_assert(XTypeSignature::get_XTypeSignature() == 64 | "struct[s:96,a:8]{@0[intVector]:vector[s:32,a:8],@32[floatVector]:v" 65 | "ector[s:32,a:8],@64[stringVector]:vector[s:32,a:8]}" 67 | , "Type signature mismatch for VectorTestReflectionHint"); 68 | 69 | #endif // GENERATED_VECTOR_TEST_HPP_ 70 | -------------------------------------------------------------------------------- /tests/run_all_tests.cpp: -------------------------------------------------------------------------------- 1 | // ============================================================================ 2 | // Test Runner - Run All Tests 3 | // Purpose: Execute all test cases and report results 4 | // ============================================================================ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Test function declarations 12 | bool test_basic_types(); 13 | bool test_vector_operations(); 14 | bool test_map_set_operations(); 15 | bool test_nested_structures(); 16 | bool test_memory_compaction(); 17 | 18 | struct TestCase { 19 | std::string name; 20 | std::function test_func; 21 | }; 22 | 23 | int main() { 24 | std::cout << "\n"; 25 | std::cout << "========================================================================\n"; 26 | std::cout << " XOffsetDatastructure2 Test Suite\n"; 27 | std::cout << "========================================================================\n"; 28 | 29 | std::vector tests = { 30 | {"Basic Types", test_basic_types}, 31 | {"Vector Operations", test_vector_operations}, 32 | {"Map and Set Operations", test_map_set_operations}, 33 | {"Nested Structures", test_nested_structures}, 34 | {"Memory Compaction", test_memory_compaction} 35 | }; 36 | 37 | int passed = 0; 38 | int failed = 0; 39 | std::vector failed_tests; 40 | 41 | for (const auto& test : tests) { 42 | std::cout << "\nRunning: " << test.name << "\n"; 43 | std::cout << std::string(70, '=') << "\n"; 44 | 45 | try { 46 | if (test.test_func()) { 47 | passed++; 48 | std::cout << "\n[SUCCESS] " << test.name << " passed!\n"; 49 | } else { 50 | failed++; 51 | failed_tests.push_back(test.name); 52 | std::cout << "\n[FAILURE] " << test.name << " failed!\n"; 53 | } 54 | } catch (const std::exception& e) { 55 | failed++; 56 | failed_tests.push_back(test.name); 57 | std::cout << "\n[EXCEPTION] " << test.name << " threw exception: " 58 | << e.what() << "\n"; 59 | } 60 | } 61 | 62 | // Summary 63 | std::cout << "\n"; 64 | std::cout << "========================================================================\n"; 65 | std::cout << " TEST SUMMARY\n"; 66 | std::cout << "========================================================================\n"; 67 | std::cout << "Total Tests: " << tests.size() << "\n"; 68 | std::cout << "Passed: " << passed << "\n"; 69 | std::cout << "Failed: " << failed << "\n"; 70 | 71 | if (failed > 0) { 72 | std::cout << "\nFailed Tests:\n"; 73 | for (const auto& name : failed_tests) { 74 | std::cout << " - " << name << "\n"; 75 | } 76 | } 77 | 78 | std::cout << "\nResult: " << (failed == 0 ? "[ALL TESTS PASSED]" : "[SOME TESTS FAILED]") << "\n"; 79 | std::cout << "========================================================================\n\n"; 80 | 81 | return failed == 0 ? 0 : 1; 82 | } 83 | -------------------------------------------------------------------------------- /generated/player.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_PLAYER_HPP_ 2 | #define GENERATED_PLAYER_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) Player { 13 | // Default constructor 14 | template 15 | Player(Allocator allocator) : name(allocator), items(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | Player(Allocator allocator, int id_val, int level_val, const char* name_val) 20 | : id(id_val) 21 | , level(level_val) 22 | , name(name_val, allocator) 23 | , items(allocator) 24 | {} 25 | 26 | int id{0}; 27 | int level{0}; 28 | XString name; 29 | XVector items; 30 | }; 31 | 32 | // ============================================================================ 33 | // Reflection Hint Types - Used for compile-time type analysis 34 | // ============================================================================ 35 | // These are aggregate versions of runtime types that satisfy boost::pfr 36 | // requirements for reflection. They must have identical memory layout 37 | // to their runtime counterparts. 38 | // ============================================================================ 39 | 40 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) PlayerReflectionHint { 41 | int32_t id; 42 | int32_t level; 43 | XString name; 44 | XVector items; 45 | 46 | // Field names metadata for XTypeSignature 47 | static constexpr std::string_view _field_names[] = { 48 | "id", 49 | "level", 50 | "name", 51 | "items", 52 | }; 53 | }; 54 | 55 | // ============================================================================ 56 | // Compile-Time Validation 57 | // ============================================================================ 58 | 59 | // Compile-time validation for Player 60 | 61 | // 1. Type Safety Check 62 | // Type safety verification uses Boost.PFR for recursive member checking. 63 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 64 | "Type safety error for PlayerReflectionHint"); 65 | 66 | // 2. Size and Alignment Check 67 | static_assert(sizeof(Player) == sizeof(PlayerReflectionHint), 68 | "Size mismatch: Player runtime and reflection types must have identical size"); 69 | static_assert(alignof(Player) == alignof(PlayerReflectionHint), 70 | "Alignment mismatch: Player runtime and reflection types must have identical alignment"); 71 | 72 | // 3. Type Signature Check 73 | // Type signature verification uses unified Boost.PFR implementation 74 | // All compilers use lightweight tuple_element and tuple_size_v APIs 75 | static_assert(XTypeSignature::get_XTypeSignature() == 76 | "struct[s:72,a:8]{@0[id]:i32[s:4,a:4],@4[level]:i32[s:4,a:4],@8[name]:string[s:32" 77 | ",a:8],@40[items]:vector[s:32,a:8]}" 78 | , "Type signature mismatch for PlayerReflectionHint"); 79 | 80 | #endif // GENERATED_PLAYER_HPP_ 81 | -------------------------------------------------------------------------------- /generated/alignment_test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_ALIGNMENT_TEST_HPP_ 2 | #define GENERATED_ALIGNMENT_TEST_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) AlignedStruct { 13 | // Default constructor 14 | template 15 | AlignedStruct(Allocator allocator) : name(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | AlignedStruct(Allocator allocator, char a_val, int b_val, double c_val, const char* name_val) 20 | : a(a_val) 21 | , b(b_val) 22 | , c(c_val) 23 | , name(name_val, allocator) 24 | {} 25 | 26 | char a{'\0'}; 27 | int b{0}; 28 | double c{0.0}; 29 | XString name; 30 | }; 31 | 32 | // ============================================================================ 33 | // Reflection Hint Types - Used for compile-time type analysis 34 | // ============================================================================ 35 | // These are aggregate versions of runtime types that satisfy boost::pfr 36 | // requirements for reflection. They must have identical memory layout 37 | // to their runtime counterparts. 38 | // ============================================================================ 39 | 40 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) AlignedStructReflectionHint { 41 | char a; 42 | int32_t b; 43 | double c; 44 | XString name; 45 | 46 | // Field names metadata for XTypeSignature 47 | static constexpr std::string_view _field_names[] = { 48 | "a", 49 | "b", 50 | "c", 51 | "name", 52 | }; 53 | }; 54 | 55 | // ============================================================================ 56 | // Compile-Time Validation 57 | // ============================================================================ 58 | 59 | // Compile-time validation for AlignedStruct 60 | 61 | // 1. Type Safety Check 62 | // Type safety verification uses Boost.PFR for recursive member checking. 63 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 64 | "Type safety error for AlignedStructReflectionHint"); 65 | 66 | // 2. Size and Alignment Check 67 | static_assert(sizeof(AlignedStruct) == sizeof(AlignedStructReflectionHint), 68 | "Size mismatch: AlignedStruct runtime and reflection types must have identical size"); 69 | static_assert(alignof(AlignedStruct) == alignof(AlignedStructReflectionHint), 70 | "Alignment mismatch: AlignedStruct runtime and reflection types must have identical alignment"); 71 | 72 | // 3. Type Signature Check 73 | // Type signature verification uses unified Boost.PFR implementation 74 | // All compilers use lightweight tuple_element and tuple_size_v APIs 75 | static_assert(XTypeSignature::get_XTypeSignature() == 76 | "struct[s:48,a:8]{@0[a]:char[s:1,a:1],@4[b]:i32[s:4,a:4],@8[c]:f64[s:8,a:8],@16[n" 77 | "ame]:string[s:32,a:8]}" 78 | , "Type signature mismatch for AlignedStructReflectionHint"); 79 | 80 | #endif // GENERATED_ALIGNMENT_TEST_HPP_ 81 | -------------------------------------------------------------------------------- /generated/map_set_test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_MAP_SET_TEST_HPP_ 2 | #define GENERATED_MAP_SET_TEST_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) MapSetTest { 13 | // Default constructor 14 | template 15 | MapSetTest(Allocator allocator) : intSet(allocator), stringSet(allocator), intMap(allocator), stringMap(allocator) {} 16 | 17 | XSet intSet; 18 | XSet stringSet; 19 | XMap intMap; 20 | XMap stringMap; 21 | }; 22 | 23 | // ============================================================================ 24 | // Reflection Hint Types - Used for compile-time type analysis 25 | // ============================================================================ 26 | // These are aggregate versions of runtime types that satisfy boost::pfr 27 | // requirements for reflection. They must have identical memory layout 28 | // to their runtime counterparts. 29 | // ============================================================================ 30 | 31 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) MapSetTestReflectionHint { 32 | XSet intSet; 33 | XSet stringSet; 34 | XMap intMap; 35 | XMap stringMap; 36 | 37 | // Field names metadata for XTypeSignature 38 | static constexpr std::string_view _field_names[] = { 39 | "intSet", 40 | "stringSet", 41 | "intMap", 42 | "stringMap", 43 | }; 44 | }; 45 | 46 | // ============================================================================ 47 | // Compile-Time Validation 48 | // ============================================================================ 49 | 50 | // Compile-time validation for MapSetTest 51 | 52 | // 1. Type Safety Check 53 | // Type safety verification uses Boost.PFR for recursive member checking. 54 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 55 | "Type safety error for MapSetTestReflectionHint"); 56 | 57 | // 2. Size and Alignment Check 58 | static_assert(sizeof(MapSetTest) == sizeof(MapSetTestReflectionHint), 59 | "Size mismatch: MapSetTest runtime and reflection types must have identical size"); 60 | static_assert(alignof(MapSetTest) == alignof(MapSetTestReflectionHint), 61 | "Alignment mismatch: MapSetTest runtime and reflection types must have identical alignment"); 62 | 63 | // 3. Type Signature Check 64 | // Type signature verification uses unified Boost.PFR implementation 65 | // All compilers use lightweight tuple_element and tuple_size_v APIs 66 | static_assert(XTypeSignature::get_XTypeSignature() == 67 | "struct[s:128,a:8]{@0[intSet]:set[s:32,a:8],@32[stringSet]:set[s:32" 68 | ",a:8],@64[intMap]:map[s:32,a:8]" 69 | ",@96[stringMap]:map[s:32,a:8]}" 70 | , "Type signature mismatch for MapSetTestReflectionHint"); 71 | 72 | #endif // GENERATED_MAP_SET_TEST_HPP_ 73 | -------------------------------------------------------------------------------- /generated/compaction_test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_COMPACTION_TEST_HPP_ 2 | #define GENERATED_COMPACTION_TEST_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) MemoryTestType { 13 | // Default constructor 14 | template 15 | MemoryTestType(Allocator allocator) : data(allocator), strings(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | MemoryTestType(Allocator allocator, int value_val) 20 | : value(value_val) 21 | , data(allocator) 22 | , strings(allocator) 23 | {} 24 | 25 | int value{0}; 26 | XVector data; 27 | XVector strings; 28 | }; 29 | 30 | // ============================================================================ 31 | // Reflection Hint Types - Used for compile-time type analysis 32 | // ============================================================================ 33 | // These are aggregate versions of runtime types that satisfy boost::pfr 34 | // requirements for reflection. They must have identical memory layout 35 | // to their runtime counterparts. 36 | // ============================================================================ 37 | 38 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) MemoryTestTypeReflectionHint { 39 | int32_t value; 40 | XVector data; 41 | XVector strings; 42 | 43 | // Field names metadata for XTypeSignature 44 | static constexpr std::string_view _field_names[] = { 45 | "value", 46 | "data", 47 | "strings", 48 | }; 49 | }; 50 | 51 | // ============================================================================ 52 | // Compile-Time Validation 53 | // ============================================================================ 54 | 55 | // Compile-time validation for MemoryTestType 56 | 57 | // 1. Type Safety Check 58 | // Type safety verification uses Boost.PFR for recursive member checking. 59 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 60 | "Type safety error for MemoryTestTypeReflectionHint"); 61 | 62 | // 2. Size and Alignment Check 63 | static_assert(sizeof(MemoryTestType) == sizeof(MemoryTestTypeReflectionHint), 64 | "Size mismatch: MemoryTestType runtime and reflection types must have identical size"); 65 | static_assert(alignof(MemoryTestType) == alignof(MemoryTestTypeReflectionHint), 66 | "Alignment mismatch: MemoryTestType runtime and reflection types must have identical alignment"); 67 | 68 | // 3. Type Signature Check 69 | // Type signature verification uses unified Boost.PFR implementation 70 | // All compilers use lightweight tuple_element and tuple_size_v APIs 71 | static_assert(XTypeSignature::get_XTypeSignature() == 72 | "struct[s:72,a:8]{@0[value]:i32[s:4,a:4],@8[data]:vector[s:32,a:8]," 73 | "@40[strings]:vector[s:32,a:8]}" 74 | , "Type signature mismatch for MemoryTestTypeReflectionHint"); 75 | 76 | #endif // GENERATED_COMPACTION_TEST_HPP_ 77 | -------------------------------------------------------------------------------- /generated/basic_types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_BASIC_TYPES_HPP_ 2 | #define GENERATED_BASIC_TYPES_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) BasicTypes { 13 | // Default constructor 14 | template 15 | BasicTypes(Allocator allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | BasicTypes(Allocator allocator, int mInt_val, float mFloat_val, double mDouble_val, char mChar_val, bool mBool_val, int64_t mInt64_val) 20 | : mInt(mInt_val) 21 | , mFloat(mFloat_val) 22 | , mDouble(mDouble_val) 23 | , mChar(mChar_val) 24 | , mBool(mBool_val) 25 | , mInt64(mInt64_val) 26 | {} 27 | 28 | int mInt{0}; 29 | float mFloat{0.0f}; 30 | double mDouble{0.0}; 31 | char mChar{'\0'}; 32 | bool mBool{false}; 33 | int64_t mInt64{0}; 34 | }; 35 | 36 | // ============================================================================ 37 | // Reflection Hint Types - Used for compile-time type analysis 38 | // ============================================================================ 39 | // These are aggregate versions of runtime types that satisfy boost::pfr 40 | // requirements for reflection. They must have identical memory layout 41 | // to their runtime counterparts. 42 | // ============================================================================ 43 | 44 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) BasicTypesReflectionHint { 45 | int32_t mInt; 46 | float mFloat; 47 | double mDouble; 48 | char mChar; 49 | bool mBool; 50 | int64_t mInt64; 51 | 52 | // Field names metadata for XTypeSignature 53 | static constexpr std::string_view _field_names[] = { 54 | "mInt", 55 | "mFloat", 56 | "mDouble", 57 | "mChar", 58 | "mBool", 59 | "mInt64", 60 | }; 61 | }; 62 | 63 | // ============================================================================ 64 | // Compile-Time Validation 65 | // ============================================================================ 66 | 67 | // Compile-time validation for BasicTypes 68 | 69 | // 1. Type Safety Check 70 | // Type safety verification uses Boost.PFR for recursive member checking. 71 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 72 | "Type safety error for BasicTypesReflectionHint"); 73 | 74 | // 2. Size and Alignment Check 75 | static_assert(sizeof(BasicTypes) == sizeof(BasicTypesReflectionHint), 76 | "Size mismatch: BasicTypes runtime and reflection types must have identical size"); 77 | static_assert(alignof(BasicTypes) == alignof(BasicTypesReflectionHint), 78 | "Alignment mismatch: BasicTypes runtime and reflection types must have identical alignment"); 79 | 80 | // 3. Type Signature Check 81 | // Type signature verification uses unified Boost.PFR implementation 82 | // All compilers use lightweight tuple_element and tuple_size_v APIs 83 | static_assert(XTypeSignature::get_XTypeSignature() == 84 | "struct[s:32,a:8]{@0[mInt]:i32[s:4,a:4],@4[mFloat]:f32[s:4,a:4],@8[mDouble]:f64[s" 85 | ":8,a:8],@16[mChar]:char[s:1,a:1],@17[mBool]:bool[s:1,a:1],@24[mInt64]:i64[s:8,a:" 86 | "8]}" 87 | , "Type signature mismatch for BasicTypesReflectionHint"); 88 | 89 | #endif // GENERATED_BASIC_TYPES_HPP_ 90 | -------------------------------------------------------------------------------- /tests/test_nested.cpp: -------------------------------------------------------------------------------- 1 | // Test nested structures 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | 7 | using namespace XOffsetDatastructure2; 8 | 9 | struct alignas(BASIC_ALIGNMENT) InnerObject { 10 | template 11 | InnerObject(Allocator allocator) : name(allocator), data(allocator) {} 12 | 13 | int id; 14 | XString name; 15 | XVector data; 16 | }; 17 | 18 | struct alignas(BASIC_ALIGNMENT) MiddleObject { 19 | template 20 | MiddleObject(Allocator allocator) 21 | : name(allocator), inner(allocator), values(allocator) {} 22 | 23 | XString name; 24 | InnerObject inner; 25 | XVector values; 26 | }; 27 | 28 | struct alignas(BASIC_ALIGNMENT) OuterObject { 29 | template 30 | OuterObject(Allocator allocator) 31 | : title(allocator), middle(allocator), innerList(allocator) {} 32 | 33 | XString title; 34 | MiddleObject middle; 35 | XVector innerList; 36 | }; 37 | 38 | bool test_nested_structures() { 39 | std::cout << "\nTesting nested structures...\n"; 40 | 41 | XBuffer xbuf(8192); 42 | auto* obj = xbuf.make_root("Nested"); 43 | 44 | // Initialize nested structure 45 | std::cout << " initialize... "; 46 | obj->title = XString("OuterTitle", xbuf.allocator()); 47 | obj->middle.name = XString("MiddleName", xbuf.allocator()); 48 | obj->middle.inner.id = 42; 49 | for (int i = 0; i < 10; ++i) { 50 | obj->middle.inner.data.push_back(i * 2); 51 | } 52 | std::cout << "ok\n"; 53 | 54 | // Verify nested data 55 | std::cout << " verify data... "; 56 | assert(obj->title == "OuterTitle"); 57 | assert(obj->middle.name == "MiddleName"); 58 | assert(obj->middle.inner.id == 42); 59 | assert(obj->middle.inner.data.size() == 10); 60 | assert(obj->middle.inner.data[5] == 10); 61 | std::cout << "ok\n"; 62 | 63 | // Vector of nested objects 64 | std::cout << " vector of objects... "; 65 | for (int i = 0; i < 5; ++i) { 66 | obj->innerList.emplace_back(xbuf.allocator()); 67 | obj->innerList.back().id = i * 100; 68 | for (int j = 0; j < i + 1; ++j) { 69 | obj->innerList.back().data.push_back(j); 70 | } 71 | } 72 | assert(obj->innerList.size() == 5); 73 | assert(obj->innerList[0].data.size() == 1); 74 | assert(obj->innerList[4].data.size() == 5); 75 | assert(obj->innerList[3].id == 300); 76 | std::cout << "ok\n"; 77 | 78 | // Deep access 79 | std::cout << " deep access... "; 80 | std::size_t total_elements = 0; 81 | for (const auto& inner : obj->innerList) { 82 | total_elements += inner.data.size(); 83 | } 84 | assert(total_elements == 15); // 1+2+3+4+5 85 | std::cout << "ok\n"; 86 | 87 | // Persistence 88 | std::cout << " persistence... "; 89 | auto* buffer = xbuf.get_buffer(); 90 | XBuffer loaded_buf(buffer->data(), buffer->size()); 91 | auto* loaded = loaded_buf.find_root("Nested").first; 92 | 93 | assert(loaded->title == "OuterTitle"); 94 | assert(loaded->middle.name == "MiddleName"); 95 | assert(loaded->middle.inner.id == 42); 96 | assert(loaded->innerList.size() == 5); 97 | assert(loaded->innerList[2].data.size() == 3); 98 | std::cout << "ok\n"; 99 | 100 | std::cout << "All tests passed\n"; 101 | return true; 102 | } 103 | 104 | int main() { 105 | try { 106 | return test_nested_structures() ? 0 : 1; 107 | } catch (const std::exception& e) { 108 | std::cerr << "Exception: " << e.what() << "\n"; 109 | return 1; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /generated/modify_test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_MODIFY_TEST_HPP_ 2 | #define GENERATED_MODIFY_TEST_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) ModifyTestData { 13 | // Default constructor 14 | template 15 | ModifyTestData(Allocator allocator) : numbers(allocator), names(allocator), scores(allocator), tags(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | ModifyTestData(Allocator allocator, int counter_val, float ratio_val, bool active_val) 20 | : counter(counter_val) 21 | , ratio(ratio_val) 22 | , active(active_val) 23 | , numbers(allocator) 24 | , names(allocator) 25 | , scores(allocator) 26 | , tags(allocator) 27 | {} 28 | 29 | int counter{0}; 30 | float ratio{0.0f}; 31 | bool active{false}; 32 | XVector numbers; 33 | XVector names; 34 | XMap scores; 35 | XSet tags; 36 | }; 37 | 38 | // ============================================================================ 39 | // Reflection Hint Types - Used for compile-time type analysis 40 | // ============================================================================ 41 | // These are aggregate versions of runtime types that satisfy boost::pfr 42 | // requirements for reflection. They must have identical memory layout 43 | // to their runtime counterparts. 44 | // ============================================================================ 45 | 46 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) ModifyTestDataReflectionHint { 47 | int32_t counter; 48 | float ratio; 49 | bool active; 50 | XVector numbers; 51 | XVector names; 52 | XMap scores; 53 | XSet tags; 54 | 55 | // Field names metadata for XTypeSignature 56 | static constexpr std::string_view _field_names[] = { 57 | "counter", 58 | "ratio", 59 | "active", 60 | "numbers", 61 | "names", 62 | "scores", 63 | "tags", 64 | }; 65 | }; 66 | 67 | // ============================================================================ 68 | // Compile-Time Validation 69 | // ============================================================================ 70 | 71 | // Compile-time validation for ModifyTestData 72 | 73 | // 1. Type Safety Check 74 | // Type safety verification uses Boost.PFR for recursive member checking. 75 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 76 | "Type safety error for ModifyTestDataReflectionHint"); 77 | 78 | // 2. Size and Alignment Check 79 | static_assert(sizeof(ModifyTestData) == sizeof(ModifyTestDataReflectionHint), 80 | "Size mismatch: ModifyTestData runtime and reflection types must have identical size"); 81 | static_assert(alignof(ModifyTestData) == alignof(ModifyTestDataReflectionHint), 82 | "Alignment mismatch: ModifyTestData runtime and reflection types must have identical alignment"); 83 | 84 | // 3. Type Signature Check 85 | // Type signature verification uses unified Boost.PFR implementation 86 | // All compilers use lightweight tuple_element and tuple_size_v APIs 87 | static_assert(XTypeSignature::get_XTypeSignature() == 88 | "struct[s:144,a:8]{@0[counter]:i32[s:4,a:4],@4[ratio]:f32[s:4,a:4],@8[active]:boo" 89 | "l[s:1,a:1],@16[numbers]:vector[s:32,a:8],@48[names]:vector[s:32,a:" 90 | "8],@80[scores]:map[s:32,a:8],@1" 91 | "12[tags]:set[s:32,a:8]}" 92 | , "Type signature mismatch for ModifyTestDataReflectionHint"); 93 | 94 | #endif // GENERATED_MODIFY_TEST_HPP_ 95 | -------------------------------------------------------------------------------- /tests/test_map_set.cpp: -------------------------------------------------------------------------------- 1 | // Test XMap and XSet containers 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | 7 | using namespace XOffsetDatastructure2; 8 | 9 | struct alignas(BASIC_ALIGNMENT) MapSetTest { 10 | template 11 | MapSetTest(Allocator allocator) 12 | : intSet(allocator), 13 | stringSet(allocator), 14 | intMap(allocator), 15 | stringMap(allocator) {} 16 | 17 | XSet intSet; 18 | XSet stringSet; 19 | XMap intMap; 20 | XMap stringMap; 21 | }; 22 | 23 | bool test_map_set_operations() { 24 | std::cout << "\nTesting map and set operations...\n"; 25 | 26 | XBuffer xbuf(4096); 27 | auto* obj = xbuf.make_root("MapSetTest"); 28 | 29 | // Set insertion 30 | std::cout << " set insertion... "; 31 | for (int i = 0; i < 50; ++i) { 32 | obj->intSet.insert(i); 33 | } 34 | // Insert duplicates (should be ignored) 35 | for (int i = 0; i < 25; ++i) { 36 | obj->intSet.insert(i); 37 | } 38 | assert(obj->intSet.size() == 50); 39 | std::cout << "ok\n"; 40 | 41 | // Set find 42 | std::cout << " set find... "; 43 | assert(obj->intSet.find(25) != obj->intSet.end()); 44 | assert(obj->intSet.find(100) == obj->intSet.end()); 45 | std::cout << "ok\n"; 46 | 47 | // String set 48 | std::cout << " string set... "; 49 | for (int i = 0; i < 10; ++i) { 50 | std::string str = "Item_" + std::to_string(i); 51 | obj->stringSet.emplace(XString(str.c_str(), xbuf.allocator())); 52 | } 53 | assert(obj->stringSet.size() == 10); 54 | std::cout << "ok\n"; 55 | 56 | // Map insertion 57 | std::cout << " map insertion... "; 58 | for (int i = 0; i < 20; ++i) { 59 | std::string value = "Value_" + std::to_string(i); 60 | XString xvalue = XString(value.c_str(), xbuf.allocator()); 61 | obj->intMap.emplace(i, xvalue); 62 | } 63 | assert(obj->intMap.size() == 20); 64 | std::cout << "ok\n"; 65 | 66 | // Map access 67 | std::cout << " map access... "; 68 | auto it = obj->intMap.find(10); 69 | assert(it != obj->intMap.end()); 70 | assert(it->second == "Value_10"); 71 | std::cout << "ok\n"; 72 | 73 | // String key map 74 | std::cout << " string key map... "; 75 | for (int i = 0; i < 15; ++i) { 76 | std::string key = "Key_" + std::to_string(i); 77 | XString xkey = XString(key.c_str(), xbuf.allocator()); 78 | obj->stringMap.emplace(xkey, i * 10); 79 | } 80 | assert(obj->stringMap.size() == 15); 81 | 82 | XString search_key = XString("Key_5", xbuf.allocator()); 83 | auto it2 = obj->stringMap.find(search_key); 84 | assert(it2 != obj->stringMap.end()); 85 | assert(it2->second == 50); 86 | std::cout << "ok\n"; 87 | 88 | // Map iteration 89 | std::cout << " iteration... "; 90 | int count = 0; 91 | for (const auto& pair : obj->intMap) { 92 | count++; 93 | } 94 | assert(count == 20); 95 | std::cout << "ok\n"; 96 | 97 | // Persistence 98 | std::cout << " persistence... "; 99 | auto* buffer = xbuf.get_buffer(); 100 | XBuffer loaded_buf(buffer->data(), buffer->size()); 101 | auto* loaded_obj = loaded_buf.find_root("MapSetTest").first; 102 | assert(loaded_obj->intSet.size() == 50); 103 | assert(loaded_obj->intMap.size() == 20); 104 | assert(loaded_obj->stringMap.size() == 15); 105 | std::cout << "ok\n"; 106 | 107 | std::cout << "All tests passed\n"; 108 | return true; 109 | } 110 | 111 | int main() { 112 | try { 113 | return test_map_set_operations() ? 0 : 1; 114 | } catch (const std::exception& e) { 115 | std::cerr << "Error: " << e.what() << "\n"; 116 | return 1; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/test_compaction.cpp: -------------------------------------------------------------------------------- 1 | // Test memory statistics 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | 7 | using namespace XOffsetDatastructure2; 8 | 9 | struct alignas(BASIC_ALIGNMENT) MemoryTestType { 10 | template 11 | MemoryTestType(Allocator allocator) 12 | : data(allocator), strings(allocator) {} 13 | 14 | int value; 15 | XVector data; 16 | XVector strings; 17 | }; 18 | 19 | bool test_memory_stats() { 20 | std::cout << "\nTesting memory statistics...\n"; 21 | 22 | // Initial buffer stats 23 | std::cout << " initial stats... "; 24 | XBuffer xbuf(4096); 25 | auto stats1 = XBufferVisualizer::get_memory_stats(xbuf); 26 | assert(stats1.total_size == 4096); 27 | assert(stats1.free_size > 0); 28 | assert(stats1.used_size > 0); // Has management overhead 29 | assert(stats1.usage_percent() < 10.0); // Very low usage 30 | std::cout << "ok (used: " << stats1.used_size << "/" << stats1.total_size 31 | << ", " << stats1.usage_percent() << "%)\n"; 32 | 33 | // After object creation 34 | std::cout << " after object creation... "; 35 | auto* obj = xbuf.make_root("MemTest"); 36 | obj->value = 42; 37 | auto stats2 = XBufferVisualizer::get_memory_stats(xbuf); 38 | assert(stats2.used_size > stats1.used_size); 39 | assert(stats2.free_size < stats1.free_size); 40 | std::cout << "ok (" << stats2.usage_percent() << "%)\n"; 41 | 42 | // After adding data 43 | std::cout << " after adding data... "; 44 | for (int i = 0; i < 100; ++i) { 45 | obj->data.push_back(i); 46 | } 47 | auto stats3 = XBufferVisualizer::get_memory_stats(xbuf); 48 | assert(stats3.used_size > stats2.used_size); 49 | std::cout << "ok (" << stats3.usage_percent() << "%)\n"; 50 | 51 | // After adding strings 52 | std::cout << " after adding strings... "; 53 | for (int i = 0; i < 20; ++i) { 54 | std::string str = "String_" + std::to_string(i); 55 | obj->strings.emplace_back(XString(str.c_str(), xbuf.allocator())); 56 | } 57 | auto stats4 = XBufferVisualizer::get_memory_stats(xbuf); 58 | assert(stats4.used_size > stats3.used_size); 59 | std::cout << "ok (" << stats4.usage_percent() << "%)\n"; 60 | 61 | // Buffer growth 62 | std::cout << " buffer growth... "; 63 | std::size_t old_size = xbuf.get_size(); 64 | xbuf.grow(4096); 65 | assert(xbuf.get_size() == old_size + 4096); 66 | auto stats5 = XBufferVisualizer::get_memory_stats(xbuf); 67 | assert(stats5.total_size == old_size + 4096); 68 | assert(stats5.free_size > stats4.free_size); 69 | assert(stats5.usage_percent() < stats4.usage_percent()); 70 | std::cout << "ok (total: " << stats5.total_size << ", " 71 | << stats5.usage_percent() << "%)\n"; 72 | 73 | // Shrink to fit 74 | std::cout << " shrink to fit... "; 75 | xbuf.shrink_to_fit(); 76 | auto stats6 = XBufferVisualizer::get_memory_stats(xbuf); 77 | assert(stats6.total_size < stats5.total_size); 78 | assert(stats6.usage_percent() > stats5.usage_percent()); 79 | std::cout << "ok (total: " << stats6.total_size << ", " 80 | << stats6.usage_percent() << "%)\n"; 81 | 82 | // Verify data integrity after shrink 83 | std::cout << " verify after shrink... "; 84 | auto [found_obj, found] = xbuf.find_root("MemTest"); 85 | assert(found); 86 | assert(found_obj->value == 42); 87 | assert(found_obj->data.size() == 100); 88 | assert(found_obj->strings.size() == 20); 89 | assert(found_obj->data[50] == 50); 90 | assert(found_obj->strings[10] == "String_10"); 91 | std::cout << "ok\n"; 92 | 93 | std::cout << "All tests passed\n"; 94 | return true; 95 | } 96 | 97 | int main() { 98 | try { 99 | return test_memory_stats() ? 0 : 1; 100 | } catch (const std::exception& e) { 101 | std::cerr << "Exception: " << e.what() << "\n"; 102 | return 1; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /schemas/README.md: -------------------------------------------------------------------------------- 1 | # XOffsetDatastructure2 Schema Definitions 2 | 3 | This directory contains YAML schema definitions for all data types used in the project. 4 | 5 | ## Schema Files 6 | 7 | | Schema File | Description | Generated Types | 8 | |------------|-------------|-----------------| 9 | | `test_types.xds.yaml` | Comprehensive test types with nested structures | TestTypeInner, TestType | 10 | | `basic_types.xds.yaml` | Basic POD types testing | BasicTypes | 11 | | `vector_test.xds.yaml` | XVector container tests | VectorTest | 12 | | `map_set_test.xds.yaml` | XMap and XSet container tests | MapSetTest | 13 | | `nested_test.xds.yaml` | Nested structure hierarchy | InnerObject, MiddleObject, OuterObject | 14 | | `modify_test.xds.yaml` | Data modification tests | ModifyTestData | 15 | | `compaction_test.xds.yaml` | Memory compaction tests | MemoryTestType | 16 | | `serialization_test.xds.yaml` | Serialization tests | SimpleData, ComplexData | 17 | | `alignment_test.xds.yaml` | Memory alignment tests | AlignedStruct | 18 | | `player.xds.yaml` | Player data structure (example) | Player | 19 | | `game_data.xds.yaml` | Game data with inventory (example) | Item, GameData | 20 | 21 | ## Schema Format 22 | 23 | Each schema file follows this format: 24 | 25 | ```yaml 26 | schema_version: "1.0" 27 | 28 | types: 29 | - name: TypeName 30 | type: struct 31 | fields: 32 | - name: fieldName 33 | type: fieldType 34 | default: defaultValue # Optional 35 | description: "field description" # Optional 36 | ``` 37 | 38 | ### Supported Field Types 39 | 40 | #### Basic Types 41 | - `int`, `float`, `double`, `bool`, `char` 42 | - `long long` 43 | - `int32_t`, `int64_t`, `uint32_t`, `uint64_t` 44 | 45 | #### XOffsetDatastructure Types 46 | - `XString` - Offset-based string 47 | - `XVector` - Offset-based vector 48 | - `XSet` - Offset-based set 49 | - `XMap` - Offset-based map 50 | 51 | #### Custom Types 52 | - Any type defined in the same schema file 53 | - Nested structures are supported 54 | 55 | ## Code Generation 56 | 57 | Schemas are automatically converted to C++ header files during the build process. 58 | 59 | ### Build Integration 60 | 61 | ```bash 62 | # Generate all schemas 63 | cd build 64 | make generate_schemas 65 | 66 | # Generated headers appear in: build/generated/*.hpp 67 | ``` 68 | 69 | ### CMake Configuration 70 | 71 | The schemas are configured in the main `CMakeLists.txt`: 72 | 73 | ```cmake 74 | set(SCHEMA_FILES 75 | ${CMAKE_SOURCE_DIR}/schemas/test_types.xds.yaml 76 | ${CMAKE_SOURCE_DIR}/schemas/basic_types.xds.yaml 77 | # ... more schemas 78 | ) 79 | ``` 80 | 81 | ## Generated Code Structure 82 | 83 | Each schema generates two types: 84 | 85 | 1. **Runtime Type**: Used for actual data storage 86 | - Has allocator constructor 87 | - Contains XOffset containers 88 | 89 | 2. **ReflectionHint Type**: Used for compile-time reflection 90 | - Aggregate type for boost::pfr 91 | - Converts runtime types to basic types 92 | 93 | ### Example 94 | 95 | Schema: 96 | ```yaml 97 | types: 98 | - name: Player 99 | type: struct 100 | fields: 101 | - name: id 102 | type: int 103 | default: 0 104 | - name: name 105 | type: XString 106 | ``` 107 | 108 | Generated: 109 | ```cpp 110 | // Runtime type 111 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) Player { 112 | template 113 | Player(Allocator allocator) : name(allocator) {} 114 | int id{0}; 115 | XString name; 116 | }; 117 | 118 | // Reflection type 119 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) PlayerReflectionHint { 120 | int32_t id; 121 | XString name; 122 | }; 123 | ``` 124 | 125 | ## Usage in Code 126 | 127 | ```cpp 128 | #include "xoffsetdatastructure2.hpp" 129 | #include "player.hpp" // Generated from player.xds.yaml 130 | 131 | XBufferExt xbuf(4096); 132 | auto* player = xbuf.make("Player1"); 133 | player->id = 100; 134 | player->name.assign("Alice"); 135 | ``` 136 | 137 | ## Adding New Schemas 138 | 139 | 1. Create a new `.xds.yaml` file in `schemas/` directory 140 | 2. Define your types following the schema format 141 | 3. Add the schema to `CMakeLists.txt` in the `SCHEMA_FILES` list 142 | 4. Run `make generate_schemas` to generate the header file 143 | 5. Include the generated header in your code 144 | 145 | ## Tools 146 | 147 | - **Generator**: `tools/xds_generator.py` 148 | - **Documentation**: `docs/DSL_GUIDE.md` 149 | - **Migration Guide**: `docs/GENERATED_TYPES.md` 150 | -------------------------------------------------------------------------------- /tests/test_type_signature.cpp: -------------------------------------------------------------------------------- 1 | // ============================================================================ 2 | // Test: Type Signature Verification 3 | // Purpose: Verify compile-time type signature generation and validation 4 | // ============================================================================ 5 | 6 | #include 7 | #include 8 | #include "../xoffsetdatastructure2.hpp" 9 | 10 | using namespace XOffsetDatastructure2; 11 | 12 | // Simple test structure 13 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) SimpleStruct { 14 | int32_t value; 15 | float ratio; 16 | }; 17 | 18 | // Structure with containers (runtime version) 19 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) ContainerStruct { 20 | template 21 | ContainerStruct(Allocator allocator) : data(allocator), name(allocator) {} 22 | 23 | int32_t id; 24 | XVector data; 25 | XString name; 26 | }; 27 | 28 | // Reflection hint for ContainerStruct 29 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) ContainerStructReflectionHint { 30 | int32_t id; 31 | XVector data; 32 | XString name; 33 | }; 34 | 35 | bool test_simple_signature() { 36 | std::cout << "\n[TEST] Simple Type Signature\n"; 37 | std::cout << std::string(50, '-') << "\n"; 38 | 39 | constexpr auto sig = XTypeSignature::get_XTypeSignature(); 40 | 41 | std::cout << "SimpleStruct signature: "; 42 | sig.print(); 43 | std::cout << "\n"; 44 | 45 | // Verify expected signature 46 | // All compilers now use unified Boost.PFR implementation 47 | constexpr auto expected = XTypeSignature::CompileString{"struct[s:8,a:8]{@0:i32[s:4,a:4],@4:f32[s:4,a:4]}"}; 48 | static_assert(sig == expected, "SimpleStruct signature mismatch"); 49 | 50 | std::cout << "[PASS] Signature matches expected value\n"; 51 | return true; 52 | } 53 | 54 | bool test_container_signature() { 55 | std::cout << "\n[TEST] Container Type Signature\n"; 56 | std::cout << std::string(50, '-') << "\n"; 57 | 58 | constexpr auto sig = XTypeSignature::get_XTypeSignature(); 59 | 60 | std::cout << "ContainerStructReflectionHint signature: "; 61 | sig.print(); 62 | std::cout << "\n"; 63 | 64 | // Verify layout match 65 | static_assert(sizeof(ContainerStruct) == sizeof(ContainerStructReflectionHint), 66 | "Size mismatch between runtime and reflection types"); 67 | static_assert(alignof(ContainerStruct) == alignof(ContainerStructReflectionHint), 68 | "Alignment mismatch between runtime and reflection types"); 69 | 70 | std::cout << "Runtime size: " << sizeof(ContainerStruct) << " bytes\n"; 71 | std::cout << "Reflection size: " << sizeof(ContainerStructReflectionHint) << " bytes\n"; 72 | std::cout << "[PASS] Layout validation successful\n"; 73 | 74 | return true; 75 | } 76 | 77 | bool test_nested_signature() { 78 | std::cout << "\n[TEST] Nested Structure Signature\n"; 79 | std::cout << std::string(50, '-') << "\n"; 80 | 81 | // Nested structure 82 | struct Inner { 83 | int32_t x; 84 | float y; 85 | }; 86 | 87 | struct Outer { 88 | Inner inner; 89 | int32_t z; 90 | }; 91 | 92 | constexpr auto inner_sig = XTypeSignature::get_XTypeSignature(); 93 | constexpr auto outer_sig = XTypeSignature::get_XTypeSignature(); 94 | 95 | std::cout << "Inner signature: "; 96 | inner_sig.print(); 97 | std::cout << "\n"; 98 | 99 | std::cout << "Outer signature: "; 100 | outer_sig.print(); 101 | std::cout << "\n"; 102 | 103 | std::cout << "[PASS] Nested structure signatures generated\n"; 104 | return true; 105 | } 106 | 107 | int main() { 108 | std::cout << "\n=== Type Signature Verification Tests ===\n"; 109 | 110 | try { 111 | bool all_passed = true; 112 | 113 | all_passed &= test_simple_signature(); 114 | all_passed &= test_container_signature(); 115 | all_passed &= test_nested_signature(); 116 | 117 | if (all_passed) { 118 | std::cout << "\n[SUCCESS] All type signature tests passed!\n\n"; 119 | return 0; 120 | } else { 121 | std::cout << "\n[FAILURE] Some tests failed!\n\n"; 122 | return 1; 123 | } 124 | } catch (const std::exception& e) { 125 | std::cerr << "[ERROR] Exception: " << e.what() << "\n"; 126 | return 1; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # XOffsetDatastructure2 Tests 2 | 3 | This directory contains comprehensive test cases for the XOffsetDatastructure2 library. 4 | 5 | ## Test Files 6 | 7 | ### 1. test_basic_types.cpp 8 | **Purpose:** Test basic POD (Plain Old Data) types 9 | - int, float, double, char, bool, long long 10 | - Persistence and data integrity 11 | - Memory serialization/deserialization 12 | 13 | **Run:** 14 | ```bash 15 | cd build 16 | ./test_basic_types 17 | ``` 18 | 19 | ### 2. test_vector.cpp 20 | **Purpose:** Test XVector container operations 21 | - push_back, element access, iteration 22 | - String vector operations 23 | - Clear and empty operations 24 | - Persistence across buffer serialization 25 | 26 | **Run:** 27 | ```bash 28 | cd build 29 | ./test_vector 30 | ``` 31 | 32 | ### 3. test_map_set.cpp 33 | **Purpose:** Test XMap and XSet containers 34 | - Set insertion and uniqueness 35 | - Map key-value operations 36 | - String keys and values 37 | - Find operations 38 | - Iteration and persistence 39 | 40 | **Run:** 41 | ```bash 42 | cd build 43 | ./test_map_set 44 | ``` 45 | 46 | ### 4. test_nested.cpp 47 | **Purpose:** Test nested object structures 48 | - Multi-level object hierarchies 49 | - Nested containers 50 | - Deep access patterns 51 | - Complex data structure persistence 52 | 53 | **Run:** 54 | ```bash 55 | cd build 56 | ./test_nested 57 | ``` 58 | 59 | ### 5. test_compaction.cpp 60 | **Purpose:** Test memory compaction functionality 61 | - Memory usage before/after compaction 62 | - Data integrity after compaction 63 | - Size reduction verification 64 | - Migration function testing 65 | 66 | **Run:** 67 | ```bash 68 | cd build 69 | ./test_compaction 70 | ``` 71 | 72 | ### 6. run_all_tests.cpp 73 | **Purpose:** Run all tests in sequence and report results 74 | - Executes all test cases 75 | - Summary report with pass/fail status 76 | - Exception handling 77 | 78 | **Note:** This requires linking all test object files together. 79 | 80 | ## Building Tests 81 | 82 | ### Add to CMakeLists.txt 83 | 84 | Add the following to your `CMakeLists.txt`: 85 | 86 | ```cmake 87 | # Tests 88 | enable_testing() 89 | 90 | add_executable(test_basic_types tests/test_basic_types.cpp) 91 | target_link_libraries(test_basic_types ${Boost_LIBRARIES}) 92 | 93 | add_executable(test_vector tests/test_vector.cpp) 94 | target_link_libraries(test_vector ${Boost_LIBRARIES}) 95 | 96 | add_executable(test_map_set tests/test_map_set.cpp) 97 | target_link_libraries(test_map_set ${Boost_LIBRARIES}) 98 | 99 | add_executable(test_nested tests/test_nested.cpp) 100 | target_link_libraries(test_nested ${Boost_LIBRARIES}) 101 | 102 | add_executable(test_compaction tests/test_compaction.cpp) 103 | target_link_libraries(test_compaction ${Boost_LIBRARIES}) 104 | 105 | # Add tests 106 | add_test(NAME BasicTypes COMMAND test_basic_types) 107 | add_test(NAME VectorOps COMMAND test_vector) 108 | add_test(NAME MapSetOps COMMAND test_map_set) 109 | add_test(NAME NestedStructures COMMAND test_nested) 110 | add_test(NAME MemoryCompaction COMMAND test_compaction) 111 | ``` 112 | 113 | ### Build and Run 114 | 115 | ```bash 116 | # Configure 117 | cmake -B build -DOFFSET_DATA_STRUCTURE_2_CUSTOM_CONTAINER_GROWTH_FACTOR=0 118 | 119 | # Build 120 | cmake --build build --config Release 121 | 122 | # Run individual tests 123 | cd build 124 | ./test_basic_types 125 | ./test_vector 126 | ./test_map_set 127 | ./test_nested 128 | ./test_compaction 129 | 130 | # Or run all tests via CTest 131 | ctest --verbose 132 | ``` 133 | 134 | ## Test Coverage 135 | 136 | The test suite covers: 137 | 138 | 1. **Basic Types** - POD types and simple data 139 | 2. **Containers** - Vector, Map, Set operations 140 | 3. **Strings** - XString handling and persistence 141 | 4. **Nested Structures** - Complex object hierarchies 142 | 5. **Memory Management** - Buffer growth and compaction 143 | 6. **Persistence** - Serialization and deserialization 144 | 7. **Data Integrity** - Verification after operations 145 | 146 | ## Expected Output 147 | 148 | Each test should output: 149 | ``` 150 | [TEST] Test Name 151 | -------------------------------------------------- 152 | Test 1: Description... [OK] 153 | Test 2: Description... [OK] 154 | ... 155 | [PASS] All tests passed! 156 | ``` 157 | 158 | ## Troubleshooting 159 | 160 | ### Assertion Failures 161 | If a test fails with an assertion error, it indicates a data integrity issue. Check: 162 | - Memory buffer size 163 | - Allocator usage 164 | - Data persistence logic 165 | 166 | ### Exceptions 167 | Common exceptions: 168 | - `interprocess_exception`: Memory allocation or initialization error 169 | - `bad_alloc`: Insufficient memory 170 | - Check buffer size and growth settings 171 | 172 | ### Build Errors 173 | If tests don't compile: 174 | - Ensure `xoffsetdatastructure2.hpp` is in the parent directory 175 | - Check Boost library paths 176 | - Verify C++17 or later compiler support 177 | 178 | ## Contributing 179 | 180 | To add new tests: 181 | 1. Create `test_feature.cpp` in this directory 182 | 2. Follow the existing test structure 183 | 3. Add to CMakeLists.txt 184 | 4. Document in this README 185 | -------------------------------------------------------------------------------- /examples/demo.cpp: -------------------------------------------------------------------------------- 1 | // XOffsetDatastructure2 Demo 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | #include "../generated/game_data.hpp" 7 | 8 | using namespace XOffsetDatastructure2; 9 | 10 | void demo_basic_usage() { 11 | std::cout << "\n--- Basic Container Operations ---\n\n"; 12 | 13 | XBuffer xbuf(4096); 14 | auto* game = xbuf.make_root("player_save"); 15 | 16 | game->player_name = xbuf.create("Hero"); 17 | game->player_id = 12345; 18 | game->level = 42; 19 | game->health = 100.0f; 20 | 21 | std::cout << "Player: " << game->player_name.c_str() 22 | << " (ID " << game->player_id << ", level " << game->level 23 | << ", HP " << game->health << ")\n\n"; 24 | 25 | // Add items (XVector) 26 | const char* types[] = {"Potion", "Weapon", "Armor"}; 27 | for (int i = 0; i < 5; i++) { 28 | // Efficient: pass allocator directly to emplace_back 29 | game->items.emplace_back(xbuf.allocator(), 30 | i + 1, i % 3, (i + 1) * 10, ("Item_" + std::to_string(i+1)).c_str()); 31 | } 32 | std::cout << "Inventory (" << game->items.size() << " items):\n"; 33 | for (const auto& item : game->items) { 34 | std::cout << " " << item.name.c_str() << " x" << item.quantity 35 | << " (" << types[item.item_type] << ")\n"; 36 | } 37 | 38 | // Track achievements (XSet) 39 | for (int id : {1, 5, 10, 25, 50}) 40 | game->achievements.insert(id); 41 | std::cout << "\nAchievements (" << game->achievements.size() << " unlocked): "; 42 | for (int id : game->achievements) 43 | std::cout << id << " "; 44 | std::cout << "\n"; 45 | 46 | // Quest progress (XMap) 47 | game->quest_progress[xbuf.create("Main Quest")] = 75; 48 | game->quest_progress[xbuf.create("Side Quest")] = 100; 49 | std::cout << "\nQuests:\n"; 50 | for (const auto& [quest, progress] : game->quest_progress) 51 | std::cout << " " << quest.c_str() << ": " << progress << "%\n"; 52 | } 53 | 54 | void demo_memory_management() { 55 | std::cout << "\n--- Memory Management ---\n\n"; 56 | 57 | XBuffer xbuf(1024); 58 | auto stats = xbuf.stats(); 59 | std::cout << "Initial: " << stats.used_size << "/" << stats.total_size 60 | << " bytes (" << static_cast(stats.usage_percent()) << "%)\n"; 61 | 62 | auto* game = xbuf.make_root("game"); 63 | game->player_name = xbuf.create("Player1"); 64 | for (int i = 0; i < 100; i++) 65 | game->achievements.insert(i); 66 | 67 | stats = xbuf.stats(); 68 | std::cout << "After adding data: " << stats.used_size << "/" << stats.total_size 69 | << " bytes (" << static_cast(stats.usage_percent()) << "%)\n"; 70 | 71 | xbuf.grow(4096); 72 | stats = xbuf.stats(); 73 | std::cout << "After grow: " << stats.used_size << "/" << stats.total_size 74 | << " bytes (" << static_cast(stats.usage_percent()) << "%)\n"; 75 | 76 | xbuf.shrink_to_fit(); 77 | stats = xbuf.stats(); 78 | std::cout << "After shrink: " << stats.used_size << "/" << stats.total_size 79 | << " bytes (" << static_cast(stats.usage_percent()) << "%)\n"; 80 | } 81 | 82 | void demo_serialization() { 83 | std::cout << "\n--- Serialization (zero-encoding/decoding) ---\n\n"; 84 | 85 | XBuffer src_buf(2048); 86 | auto* src = src_buf.make_root("save"); 87 | src->player_name = src_buf.create("SavedHero"); 88 | src->player_id = 99999; 89 | src->level = 99; 90 | src->health = 100.0f; 91 | 92 | for (int i = 0; i < 3; i++) { 93 | // Efficient: pass allocator directly to emplace_back 94 | src->items.emplace_back(src_buf.allocator(), 95 | i, 0, 1, ("Item_" + std::to_string(i)).c_str()); 96 | } 97 | 98 | std::cout << "Original: " << src->player_name.c_str() 99 | << " (level " << src->level << ", " << src->items.size() << " items)\n"; 100 | 101 | std::string data = src_buf.save_to_string(); 102 | std::cout << "Saved to " << data.size() << " bytes\n"; 103 | 104 | XBuffer dst_buf = XBuffer::load_from_string(data); 105 | auto* dst = dst_buf.find_root("save").first; 106 | 107 | std::cout << "Loaded: " << dst->player_name.c_str() 108 | << " (level " << dst->level << ", " << dst->items.size() << " items)\n"; 109 | 110 | bool ok = (std::string(dst->player_name.c_str()) == "SavedHero" && 111 | dst->player_id == 99999 && dst->level == 99 && dst->items.size() == 3); 112 | std::cout << "Data integrity: " << (ok ? "OK" : "FAIL") << "\n"; 113 | } 114 | 115 | int main() { 116 | try { 117 | demo_basic_usage(); 118 | demo_memory_management(); 119 | demo_serialization(); 120 | return 0; 121 | } catch (const std::exception& e) { 122 | std::cerr << "Error: " << e.what() << "\n"; 123 | return 1; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /generated/serialization_test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_SERIALIZATION_TEST_HPP_ 2 | #define GENERATED_SERIALIZATION_TEST_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) SimpleData { 13 | // Default constructor 14 | template 15 | SimpleData(Allocator allocator) : name(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | SimpleData(Allocator allocator, int id_val, float value_val, const char* name_val) 20 | : id(id_val) 21 | , value(value_val) 22 | , name(name_val, allocator) 23 | {} 24 | 25 | int id{0}; 26 | float value{0.0f}; 27 | XString name; 28 | }; 29 | 30 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) ComplexData { 31 | // Default constructor 32 | template 33 | ComplexData(Allocator allocator) : title(allocator), items(allocator), tags(allocator), metadata(allocator) {} 34 | 35 | XString title; 36 | XVector items; 37 | XSet tags; 38 | XMap metadata; 39 | }; 40 | 41 | // ============================================================================ 42 | // Reflection Hint Types - Used for compile-time type analysis 43 | // ============================================================================ 44 | // These are aggregate versions of runtime types that satisfy boost::pfr 45 | // requirements for reflection. They must have identical memory layout 46 | // to their runtime counterparts. 47 | // ============================================================================ 48 | 49 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) SimpleDataReflectionHint { 50 | int32_t id; 51 | float value; 52 | XString name; 53 | 54 | // Field names metadata for XTypeSignature 55 | static constexpr std::string_view _field_names[] = { 56 | "id", 57 | "value", 58 | "name", 59 | }; 60 | }; 61 | 62 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) ComplexDataReflectionHint { 63 | XString title; 64 | XVector items; 65 | XSet tags; 66 | XMap metadata; 67 | 68 | // Field names metadata for XTypeSignature 69 | static constexpr std::string_view _field_names[] = { 70 | "title", 71 | "items", 72 | "tags", 73 | "metadata", 74 | }; 75 | }; 76 | 77 | // ============================================================================ 78 | // Compile-Time Validation 79 | // ============================================================================ 80 | 81 | // Compile-time validation for SimpleData 82 | 83 | // 1. Type Safety Check 84 | // Type safety verification uses Boost.PFR for recursive member checking. 85 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 86 | "Type safety error for SimpleDataReflectionHint"); 87 | 88 | // 2. Size and Alignment Check 89 | static_assert(sizeof(SimpleData) == sizeof(SimpleDataReflectionHint), 90 | "Size mismatch: SimpleData runtime and reflection types must have identical size"); 91 | static_assert(alignof(SimpleData) == alignof(SimpleDataReflectionHint), 92 | "Alignment mismatch: SimpleData runtime and reflection types must have identical alignment"); 93 | 94 | // 3. Type Signature Check 95 | // Type signature verification uses unified Boost.PFR implementation 96 | // All compilers use lightweight tuple_element and tuple_size_v APIs 97 | static_assert(XTypeSignature::get_XTypeSignature() == "struct[s:40,a:8]{@0[id]:i32[s:4,a:4],@4[value]:f32[s:4,a:4],@8[name]:string[s:32,a:8]}", 98 | "Type signature mismatch for SimpleDataReflectionHint"); 99 | 100 | // Compile-time validation for ComplexData 101 | 102 | // 1. Type Safety Check 103 | // Type safety verification uses Boost.PFR for recursive member checking. 104 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 105 | "Type safety error for ComplexDataReflectionHint"); 106 | 107 | // 2. Size and Alignment Check 108 | static_assert(sizeof(ComplexData) == sizeof(ComplexDataReflectionHint), 109 | "Size mismatch: ComplexData runtime and reflection types must have identical size"); 110 | static_assert(alignof(ComplexData) == alignof(ComplexDataReflectionHint), 111 | "Alignment mismatch: ComplexData runtime and reflection types must have identical alignment"); 112 | 113 | // 3. Type Signature Check 114 | // Type signature verification uses unified Boost.PFR implementation 115 | // All compilers use lightweight tuple_element and tuple_size_v APIs 116 | static_assert(XTypeSignature::get_XTypeSignature() == 117 | "struct[s:128,a:8]{@0[title]:string[s:32,a:8],@32[items]:vector[s:32,a:8],@64[tags]:set[s:32,a:8],@96[metadata]:map[s:32,a:8]}" 120 | , "Type signature mismatch for ComplexDataReflectionHint"); 121 | 122 | #endif // GENERATED_SERIALIZATION_TEST_HPP_ 123 | -------------------------------------------------------------------------------- /tests/test_alignment.cpp: -------------------------------------------------------------------------------- 1 | // Test alignment verification 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | 7 | using namespace XOffsetDatastructure2; 8 | 9 | // Test structures with and without alignment 10 | struct alignas(BASIC_ALIGNMENT) AlignedStruct { 11 | template 12 | AlignedStruct(Allocator allocator) : name(allocator) {} 13 | 14 | char a; // 1 byte 15 | int b; // 4 bytes 16 | double c; // 8 bytes 17 | XString name; 18 | }; 19 | 20 | struct UnalignedStruct { 21 | template 22 | UnalignedStruct(Allocator allocator) : name(allocator) {} 23 | 24 | char a; // 1 byte 25 | int b; // 4 bytes 26 | double c; // 8 bytes 27 | float value; // 4 bytes 28 | XString name; 29 | }; 30 | 31 | bool test_alignment_specification() { 32 | std::cout << "\nTesting alignment specification...\n"; 33 | 34 | std::cout << " BASIC_ALIGNMENT: " << BASIC_ALIGNMENT << " bytes... "; 35 | assert(BASIC_ALIGNMENT == 8); 36 | std::cout << "ok\n"; 37 | 38 | std::cout << " AlignedStruct alignment... "; 39 | std::size_t aligned_alignment = alignof(AlignedStruct); 40 | assert(aligned_alignment >= BASIC_ALIGNMENT); 41 | std::cout << "ok (" << aligned_alignment << " bytes)\n"; 42 | 43 | std::cout << " UnalignedStruct alignment... "; 44 | std::size_t unaligned_alignment = alignof(UnalignedStruct); 45 | std::cout << "ok (" << unaligned_alignment << " bytes)\n"; 46 | 47 | std::cout << " verify aligned >= unaligned... "; 48 | assert(aligned_alignment >= unaligned_alignment); 49 | std::cout << "ok\n"; 50 | 51 | std::cout << "All tests passed\n"; 52 | return true; 53 | } 54 | 55 | bool test_aligned_allocation() { 56 | std::cout << "\nTesting aligned allocation...\n"; 57 | 58 | XBuffer xbuf(8192); 59 | 60 | std::cout << " allocate... "; 61 | auto* aligned = xbuf.make_root("Aligned"); 62 | aligned->a = 1; 63 | aligned->b = 2; 64 | aligned->c = 3.14; 65 | aligned->name = XString("AlignedData", xbuf.allocator()); 66 | std::cout << "ok\n"; 67 | 68 | std::cout << " check address... "; 69 | std::uintptr_t addr = reinterpret_cast(aligned); 70 | bool is_aligned = (addr % BASIC_ALIGNMENT) == 0; 71 | std::cout << "ok (0x" << std::hex << addr << std::dec << ", " << (is_aligned ? "aligned" : "unaligned") << ")\n"; 72 | 73 | std::cout << " verify integrity... "; 74 | assert(aligned->a == 1); 75 | assert(aligned->b == 2); 76 | assert(aligned->c == 3.14); 77 | assert(aligned->name == "AlignedData"); 78 | std::cout << "ok\n"; 79 | 80 | std::cout << "All tests passed\n"; 81 | return true; 82 | } 83 | 84 | bool test_serialization_with_alignment() { 85 | std::cout << "\nTesting serialization with alignment...\n"; 86 | 87 | std::cout << " create and serialize... "; 88 | XBuffer xbuf(4096); 89 | auto* data = xbuf.make_root("Data"); 90 | data->a = 1; 91 | data->b = 2; 92 | data->c = 2.718; 93 | data->name = XString("TestData", xbuf.allocator()); 94 | 95 | std::string serialized = xbuf.save_to_string(); 96 | std::cout << "ok\n"; 97 | 98 | std::cout << " deserialize and verify... "; 99 | XBuffer loaded = XBuffer::load_from_string(serialized); 100 | auto [loaded_data, found] = loaded.find_root("Data"); 101 | assert(found); 102 | assert(loaded_data->a == 1); 103 | assert(loaded_data->b == 2); 104 | assert(loaded_data->c == 2.718); 105 | assert(loaded_data->name == "TestData"); 106 | std::cout << "ok\n"; 107 | 108 | std::cout << "All tests passed\n"; 109 | return true; 110 | } 111 | 112 | bool test_mixed_alignment() { 113 | std::cout << "\nTesting mixed alignment...\n"; 114 | 115 | XBuffer xbuf(8192); 116 | 117 | std::cout << " create objects... "; 118 | auto* aligned = xbuf.make_root("Aligned"); 119 | aligned->a = 1; 120 | aligned->b = 2; 121 | aligned->c = 1.0; 122 | aligned->name = XString("Aligned", xbuf.allocator()); 123 | 124 | auto* unaligned = xbuf.make_root("Unaligned"); 125 | unaligned->a = 10; 126 | unaligned->b = 20; 127 | unaligned->c = 2.0; 128 | unaligned->value = 3.14f; 129 | unaligned->name = XString("Unaligned", xbuf.allocator()); 130 | std::cout << "ok\n"; 131 | 132 | std::cout << " verify... "; 133 | assert(aligned->b == 2); 134 | assert(unaligned->b == 20); 135 | std::cout << "ok\n"; 136 | 137 | std::cout << " serialize/deserialize... "; 138 | std::string serialized = xbuf.save_to_string(); 139 | XBuffer loaded = XBuffer::load_from_string(serialized); 140 | 141 | auto [a, fa] = loaded.find_root("Aligned"); 142 | auto [u, fu] = loaded.find_root("Unaligned"); 143 | 144 | assert(fa && fu); 145 | assert(a->name == "Aligned"); 146 | assert(u->name == "Unaligned"); 147 | std::cout << "ok\n"; 148 | 149 | std::cout << "All tests passed\n"; 150 | return true; 151 | } 152 | 153 | int main() { 154 | bool all_passed = true; 155 | 156 | all_passed &= test_alignment_specification(); 157 | all_passed &= test_aligned_allocation(); 158 | all_passed &= test_serialization_with_alignment(); 159 | all_passed &= test_mixed_alignment(); 160 | 161 | if (all_passed) { 162 | std::cout << "\nAll alignment tests passed\n"; 163 | } else { 164 | std::cout << "\nSome tests failed\n"; 165 | } 166 | 167 | return all_passed ? 0 : 1; 168 | } 169 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set C++ standard to C++20 2 | set(CMAKE_CXX_STANDARD 20) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | 5 | # Enable testing 6 | enable_testing() 7 | 8 | # Helper function for macOS RPATH settings 9 | function(configure_macos_target target_name) 10 | if(APPLE) 11 | set_target_properties(${target_name} PROPERTIES 12 | SKIP_BUILD_RPATH TRUE 13 | BUILD_WITH_INSTALL_RPATH FALSE 14 | INSTALL_RPATH "" 15 | ) 16 | target_link_options(${target_name} PRIVATE 17 | -Wl,-rpath,/usr/lib 18 | -Wl,-rpath,/usr/local/lib 19 | ) 20 | endif() 21 | # Set iOS bundle identifier if building for iOS 22 | set_ios_bundle_id(${target_name}) 23 | endfunction() 24 | 25 | # Test executables 26 | add_executable(test_basic_types test_basic_types.cpp) 27 | target_include_directories(test_basic_types PRIVATE 28 | ${BOOST_INCLUDE_DIRS} 29 | ${CMAKE_SOURCE_DIR} 30 | ${CMAKE_SOURCE_DIR}/generated 31 | ) 32 | set_target_properties(test_basic_types PROPERTIES 33 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 34 | ) 35 | configure_macos_target(test_basic_types) 36 | if(Python3_FOUND) 37 | add_dependencies(test_basic_types generate_schemas) 38 | endif() 39 | 40 | add_executable(test_vector test_vector.cpp) 41 | target_include_directories(test_vector PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 42 | set_target_properties(test_vector PROPERTIES 43 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 44 | ) 45 | configure_macos_target(test_vector) 46 | 47 | add_executable(test_map_set test_map_set.cpp) 48 | target_include_directories(test_map_set PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 49 | set_target_properties(test_map_set PROPERTIES 50 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 51 | ) 52 | configure_macos_target(test_map_set) 53 | 54 | add_executable(test_nested test_nested.cpp) 55 | target_include_directories(test_nested PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 56 | set_target_properties(test_nested PROPERTIES 57 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 58 | ) 59 | configure_macos_target(test_nested) 60 | 61 | add_executable(test_compaction test_compaction.cpp) 62 | target_include_directories(test_compaction PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 63 | set_target_properties(test_compaction PROPERTIES 64 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 65 | ) 66 | configure_macos_target(test_compaction) 67 | 68 | add_executable(test_modify test_modify.cpp) 69 | target_include_directories(test_modify PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 70 | set_target_properties(test_modify PROPERTIES 71 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 72 | ) 73 | configure_macos_target(test_modify) 74 | 75 | add_executable(test_comprehensive test_comprehensive.cpp) 76 | target_include_directories(test_comprehensive PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 77 | set_target_properties(test_comprehensive PROPERTIES 78 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 79 | ) 80 | configure_macos_target(test_comprehensive) 81 | 82 | add_executable(test_serialization test_serialization.cpp) 83 | target_include_directories(test_serialization PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 84 | set_target_properties(test_serialization PROPERTIES 85 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 86 | ) 87 | configure_macos_target(test_serialization) 88 | 89 | add_executable(test_alignment test_alignment.cpp) 90 | target_include_directories(test_alignment PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 91 | set_target_properties(test_alignment PROPERTIES 92 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 93 | ) 94 | configure_macos_target(test_alignment) 95 | 96 | # Type signature verification test 97 | add_executable(test_type_signature test_type_signature.cpp) 98 | target_include_directories(test_type_signature PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 99 | set_target_properties(test_type_signature PROPERTIES 100 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 101 | ) 102 | configure_macos_target(test_type_signature) 103 | 104 | # Platform requirements test (64-bit, little-endian) 105 | add_executable(test_platform test_platform.cpp) 106 | target_include_directories(test_platform PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 107 | set_target_properties(test_platform PROPERTIES 108 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 109 | ) 110 | configure_macos_target(test_platform) 111 | 112 | # MSVC compatibility test (comprehensive test suite for MSVC-specific issues) 113 | add_executable(test_msvc_compat test_msvc_compat.cpp) 114 | target_include_directories(test_msvc_compat PRIVATE 115 | ${BOOST_INCLUDE_DIRS} 116 | ${CMAKE_SOURCE_DIR} 117 | ${CMAKE_SOURCE_DIR}/generated 118 | ) 119 | set_target_properties(test_msvc_compat PROPERTIES 120 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 121 | ) 122 | configure_macos_target(test_msvc_compat) 123 | if(Python3_FOUND) 124 | add_dependencies(test_msvc_compat generate_schemas) 125 | endif() 126 | 127 | # Add CTest tests 128 | add_test(NAME BasicTypes COMMAND test_basic_types) 129 | add_test(NAME VectorOps COMMAND test_vector) 130 | add_test(NAME MapSetOps COMMAND test_map_set) 131 | add_test(NAME NestedStructures COMMAND test_nested) 132 | add_test(NAME MemoryStats COMMAND test_compaction) 133 | add_test(NAME DataModification COMMAND test_modify) 134 | add_test(NAME Comprehensive COMMAND test_comprehensive) 135 | add_test(NAME Serialization COMMAND test_serialization) 136 | add_test(NAME Alignment COMMAND test_alignment) 137 | add_test(NAME TypeSignature COMMAND test_type_signature) 138 | add_test(NAME Platform COMMAND test_platform) 139 | add_test(NAME MSVCCompat COMMAND test_msvc_compat) 140 | 141 | # const T support test 142 | add_executable(test_const_support test_const_support.cpp) 143 | target_include_directories(test_const_support PRIVATE ${BOOST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) 144 | set_target_properties(test_const_support PROPERTIES 145 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 146 | ) 147 | configure_macos_target(test_const_support) 148 | add_test(NAME ConstSupport COMMAND test_const_support) 149 | 150 | -------------------------------------------------------------------------------- /generated/game_data.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_GAME_DATA_HPP_ 2 | #define GENERATED_GAME_DATA_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) Item { 13 | // Default constructor 14 | template 15 | Item(Allocator allocator) : name(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | Item(Allocator allocator, int item_id_val, int item_type_val, int quantity_val, const char* name_val) 20 | : item_id(item_id_val) 21 | , item_type(item_type_val) 22 | , quantity(quantity_val) 23 | , name(name_val, allocator) 24 | {} 25 | 26 | int item_id{0}; 27 | int item_type{0}; 28 | int quantity{0}; 29 | XString name; 30 | }; 31 | 32 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) GameData { 33 | // Default constructor 34 | template 35 | GameData(Allocator allocator) : player_name(allocator), items(allocator), achievements(allocator), quest_progress(allocator) {} 36 | 37 | // Full constructor for emplace_back 38 | template 39 | GameData(Allocator allocator, int player_id_val, int level_val, float health_val, const char* player_name_val) 40 | : player_id(player_id_val) 41 | , level(level_val) 42 | , health(health_val) 43 | , player_name(player_name_val, allocator) 44 | , items(allocator) 45 | , achievements(allocator) 46 | , quest_progress(allocator) 47 | {} 48 | 49 | int player_id{0}; 50 | int level{0}; 51 | float health{0.0f}; 52 | XString player_name; 53 | XVector items; 54 | XSet achievements; 55 | XMap quest_progress; 56 | }; 57 | 58 | // ============================================================================ 59 | // Reflection Hint Types - Used for compile-time type analysis 60 | // ============================================================================ 61 | // These are aggregate versions of runtime types that satisfy boost::pfr 62 | // requirements for reflection. They must have identical memory layout 63 | // to their runtime counterparts. 64 | // ============================================================================ 65 | 66 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) ItemReflectionHint { 67 | int32_t item_id; 68 | int32_t item_type; 69 | int32_t quantity; 70 | XString name; 71 | 72 | // Field names metadata for XTypeSignature 73 | static constexpr std::string_view _field_names[] = { 74 | "item_id", 75 | "item_type", 76 | "quantity", 77 | "name", 78 | }; 79 | }; 80 | 81 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) GameDataReflectionHint { 82 | int32_t player_id; 83 | int32_t level; 84 | float health; 85 | XString player_name; 86 | XVector items; 87 | XSet achievements; 88 | XMap quest_progress; 89 | 90 | // Field names metadata for XTypeSignature 91 | static constexpr std::string_view _field_names[] = { 92 | "player_id", 93 | "level", 94 | "health", 95 | "player_name", 96 | "items", 97 | "achievements", 98 | "quest_progress", 99 | }; 100 | }; 101 | 102 | // ============================================================================ 103 | // Compile-Time Validation 104 | // ============================================================================ 105 | 106 | // Compile-time validation for Item 107 | 108 | // 1. Type Safety Check 109 | // Type safety verification uses Boost.PFR for recursive member checking. 110 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 111 | "Type safety error for ItemReflectionHint"); 112 | 113 | // 2. Size and Alignment Check 114 | static_assert(sizeof(Item) == sizeof(ItemReflectionHint), 115 | "Size mismatch: Item runtime and reflection types must have identical size"); 116 | static_assert(alignof(Item) == alignof(ItemReflectionHint), 117 | "Alignment mismatch: Item runtime and reflection types must have identical alignment"); 118 | 119 | // 3. Type Signature Check 120 | // Type signature verification uses unified Boost.PFR implementation 121 | // All compilers use lightweight tuple_element and tuple_size_v APIs 122 | static_assert(XTypeSignature::get_XTypeSignature() == 123 | "struct[s:48,a:8]{@0[item_id]:i32[s:4,a:4],@4[item_type]:i32[s:4,a:4],@8[quantity" 124 | "]:i32[s:4,a:4],@16[name]:string[s:32,a:8]}" 125 | , "Type signature mismatch for ItemReflectionHint"); 126 | 127 | // Compile-time validation for GameData 128 | 129 | // 1. Type Safety Check 130 | // Type safety verification uses Boost.PFR for recursive member checking. 131 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 132 | "Type safety error for GameDataReflectionHint"); 133 | 134 | // 2. Size and Alignment Check 135 | static_assert(sizeof(GameData) == sizeof(GameDataReflectionHint), 136 | "Size mismatch: GameData runtime and reflection types must have identical size"); 137 | static_assert(alignof(GameData) == alignof(GameDataReflectionHint), 138 | "Alignment mismatch: GameData runtime and reflection types must have identical alignment"); 139 | 140 | // 3. Type Signature Check 141 | // Type signature verification uses unified Boost.PFR implementation 142 | // All compilers use lightweight tuple_element and tuple_size_v APIs 143 | static_assert(XTypeSignature::get_XTypeSignature() == 144 | "struct[s:144,a:8]{@0[player_id]:i32[s:4,a:4],@4[level]:i32[s:4,a:4],@8[health]:f" 145 | "32[s:4,a:4],@16[player_name]:string[s:32,a:8],@48[items]:vector[s:32,a:8],@80[achievements]:set[s:32,a:8],@112[quest_progress]:map[s:32,a:8]}" 149 | , "Type signature mismatch for GameDataReflectionHint"); 150 | 151 | #endif // GENERATED_GAME_DATA_HPP_ 152 | -------------------------------------------------------------------------------- /tests/test_modify.cpp: -------------------------------------------------------------------------------- 1 | // Test data modification 2 | 3 | #include 4 | #include 5 | #include "../xoffsetdatastructure2.hpp" 6 | 7 | using namespace XOffsetDatastructure2; 8 | 9 | // Test data structure 10 | struct alignas(BASIC_ALIGNMENT) ModifyTestData { 11 | template 12 | ModifyTestData(Allocator allocator) 13 | : numbers(allocator), names(allocator), scores(allocator), tags(allocator) {} 14 | 15 | // Basic types 16 | int counter; 17 | float ratio; 18 | bool active; 19 | 20 | // Containers 21 | XVector numbers; 22 | XVector names; 23 | XMap scores; 24 | XSet tags; 25 | }; 26 | 27 | bool test_modify_basic_types() { 28 | std::cout << "\nTesting basic type modification...\n"; 29 | 30 | XBuffer xbuf(8192); 31 | auto* data = xbuf.make_root("ModifyTest"); 32 | 33 | // Initialize values 34 | std::cout << " initialize... "; 35 | data->counter = 100; 36 | data->ratio = 1.5f; 37 | data->active = true; 38 | assert(data->counter == 100); 39 | assert(data->ratio == 1.5f); 40 | assert(data->active == true); 41 | std::cout << "ok\n"; 42 | 43 | // Modify int 44 | std::cout << " modify int... "; 45 | data->counter += 50; 46 | assert(data->counter == 150); 47 | data->counter *= 2; 48 | assert(data->counter == 300); 49 | data->counter--; 50 | assert(data->counter == 299); 51 | std::cout << "ok\n"; 52 | 53 | // Modify float 54 | std::cout << " modify float... "; 55 | data->ratio += 0.5f; 56 | assert(data->ratio > 1.99f && data->ratio < 2.01f); 57 | data->ratio *= 2.0f; 58 | assert(data->ratio > 3.99f && data->ratio < 4.01f); 59 | std::cout << "ok\n"; 60 | 61 | // Toggle bool 62 | std::cout << " toggle bool... "; 63 | data->active = false; 64 | assert(data->active == false); 65 | data->active = !data->active; 66 | assert(data->active == true); 67 | std::cout << "ok\n"; 68 | 69 | // Verify persistence after modification 70 | std::cout << " verify persistence... "; 71 | auto [found_data, found] = xbuf.find_root("ModifyTest"); 72 | assert(found); 73 | assert(found_data->counter == 299); 74 | assert(found_data->ratio > 3.99f && found_data->ratio < 4.01f); 75 | assert(found_data->active == true); 76 | std::cout << "ok\n"; 77 | 78 | std::cout << "All tests passed\n"; 79 | return true; 80 | } 81 | 82 | bool test_modify_mixed_operations() { 83 | std::cout << "\nTesting mixed modifications...\n"; 84 | 85 | XBuffer xbuf(16384); 86 | auto* data = xbuf.make_root("ModifyTest"); 87 | 88 | // Initialize all fields 89 | std::cout << " initialize... "; 90 | data->counter = 0; 91 | data->ratio = 1.0f; 92 | data->active = true; 93 | 94 | for (int i = 0; i < 3; ++i) { 95 | data->numbers.push_back(i); 96 | } 97 | 98 | data->names.emplace_back("Alice", xbuf.allocator()); 99 | data->names.emplace_back("Bob", xbuf.allocator()); 100 | 101 | data->scores.emplace(XString("Alice", xbuf.allocator()), 80); 102 | data->scores.emplace(XString("Bob", xbuf.allocator()), 85); 103 | 104 | data->tags.insert(1); 105 | data->tags.insert(2); 106 | std::cout << "ok\n"; 107 | 108 | // Modify all types simultaneously 109 | std::cout << " modify all... "; 110 | data->counter = 100; 111 | data->ratio = 2.5f; 112 | data->active = false; 113 | 114 | data->numbers[0] = 999; 115 | data->names[0] = XString("Alicia", xbuf.allocator()); 116 | data->scores[XString("Alice", xbuf.allocator())] = 95; 117 | data->tags.insert(3); 118 | 119 | assert(data->counter == 100); 120 | assert(data->numbers[0] == 999); 121 | assert(data->names[0] == "Alicia"); 122 | assert(data->scores[XString("Alice", xbuf.allocator())] == 95); 123 | assert(data->tags.find(3) != data->tags.end()); 124 | std::cout << "ok\n"; 125 | 126 | // Verify via find 127 | std::cout << " verify via find... "; 128 | auto [found_data, found] = xbuf.find_root("ModifyTest"); 129 | assert(found); 130 | assert(found_data->counter == 100); 131 | assert(found_data->ratio > 2.49f && found_data->ratio < 2.51f); 132 | assert(found_data->active == false); 133 | assert(found_data->numbers[0] == 999); 134 | assert(found_data->names[0] == "Alicia"); 135 | std::cout << "ok\n"; 136 | 137 | // Complex batch modifications 138 | std::cout << " batch modify... "; 139 | // Modify vector 140 | for (auto& num : data->numbers) { 141 | num *= 2; 142 | } 143 | // Modify map 144 | for (auto& pair : data->scores) { 145 | pair.second -= 10; 146 | } 147 | // Add to set 148 | for (int i = 10; i < 15; ++i) { 149 | data->tags.insert(i); 150 | } 151 | 152 | assert(data->numbers[0] == 1998); 153 | assert(data->scores[XString("Alice", xbuf.allocator())] == 85); 154 | assert(data->tags.size() == 8); // 1,2,3,10,11,12,13,14 155 | std::cout << "ok\n"; 156 | 157 | // Serialize and verify 158 | std::cout << " serialize... "; 159 | std::vector buffer(*xbuf.get_buffer()); 160 | 161 | XBuffer new_xbuf(buffer); 162 | auto [new_data, new_found] = new_xbuf.find_root("ModifyTest"); 163 | assert(new_found); 164 | assert(new_data->counter == 100); 165 | assert(new_data->numbers[0] == 1998); 166 | assert(new_data->names[0] == "Alicia"); 167 | assert(new_data->scores[XString("Alice", xbuf.allocator())] == 85); 168 | assert(new_data->tags.size() == 8); 169 | std::cout << "ok\n"; 170 | 171 | std::cout << "All tests passed\n"; 172 | return true; 173 | } 174 | 175 | int main() { 176 | try { 177 | bool all_passed = true; 178 | 179 | all_passed &= test_modify_basic_types(); 180 | all_passed &= test_modify_mixed_operations(); 181 | 182 | if (all_passed) { 183 | std::cout << "\nAll modification tests passed\n"; 184 | return 0; 185 | } else { 186 | std::cout << "\nSome tests failed\n"; 187 | return 1; 188 | } 189 | } catch (const std::exception& e) { 190 | std::cerr << "Exception: " << e.what() << "\n"; 191 | return 1; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /generated/test_types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_TEST_TYPES_HPP_ 2 | #define GENERATED_TEST_TYPES_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) TestTypeInner { 13 | // Default constructor 14 | template 15 | TestTypeInner(Allocator allocator) : mVector(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | TestTypeInner(Allocator allocator, int mInt_val) 20 | : mInt(mInt_val) 21 | , mVector(allocator) 22 | {} 23 | 24 | int mInt{0}; 25 | XVector mVector; 26 | }; 27 | 28 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) TestType { 29 | // Default constructor 30 | template 31 | TestType(Allocator allocator) : mVector(allocator), mStringVector(allocator), TestTypeInnerObj(allocator), mXXTypeVector(allocator), mComplexMap(allocator), mStringSet(allocator), mSet(allocator), mString(allocator) {} 32 | 33 | // Full constructor for emplace_back 34 | template 35 | TestType(Allocator allocator, int mInt_val, float mFloat_val, const char* mString_val) 36 | : mInt(mInt_val) 37 | , mFloat(mFloat_val) 38 | , mVector(allocator) 39 | , mStringVector(allocator) 40 | , TestTypeInnerObj(allocator) 41 | , mXXTypeVector(allocator) 42 | , mComplexMap(allocator) 43 | , mStringSet(allocator) 44 | , mSet(allocator) 45 | , mString(mString_val, allocator) 46 | {} 47 | 48 | int mInt{0}; 49 | float mFloat{0.0f}; 50 | XVector mVector; 51 | XVector mStringVector; 52 | TestTypeInner TestTypeInnerObj; 53 | XVector mXXTypeVector; 54 | XMap mComplexMap; 55 | XSet mStringSet; 56 | XSet mSet; 57 | XString mString; 58 | }; 59 | 60 | // ============================================================================ 61 | // Reflection Hint Types - Used for compile-time type analysis 62 | // ============================================================================ 63 | // These are aggregate versions of runtime types that satisfy boost::pfr 64 | // requirements for reflection. They must have identical memory layout 65 | // to their runtime counterparts. 66 | // ============================================================================ 67 | 68 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) TestTypeInnerReflectionHint { 69 | int32_t mInt; 70 | XVector mVector; 71 | 72 | // Field names metadata for XTypeSignature 73 | static constexpr std::string_view _field_names[] = { 74 | "mInt", 75 | "mVector", 76 | }; 77 | }; 78 | 79 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) TestTypeReflectionHint { 80 | int32_t mInt; 81 | float mFloat; 82 | XVector mVector; 83 | XVector mStringVector; 84 | TestTypeInnerReflectionHint TestTypeInnerObj; 85 | XVector mXXTypeVector; 86 | XMap mComplexMap; 87 | XSet mStringSet; 88 | XSet mSet; 89 | XString mString; 90 | 91 | // Field names metadata for XTypeSignature 92 | static constexpr std::string_view _field_names[] = { 93 | "mInt", 94 | "mFloat", 95 | "mVector", 96 | "mStringVector", 97 | "TestTypeInnerObj", 98 | "mXXTypeVector", 99 | "mComplexMap", 100 | "mStringSet", 101 | "mSet", 102 | "mString", 103 | }; 104 | }; 105 | 106 | // ============================================================================ 107 | // Compile-Time Validation 108 | // ============================================================================ 109 | 110 | // Compile-time validation for TestTypeInner 111 | 112 | // 1. Type Safety Check 113 | // Type safety verification uses Boost.PFR for recursive member checking. 114 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 115 | "Type safety error for TestTypeInnerReflectionHint"); 116 | 117 | // 2. Size and Alignment Check 118 | static_assert(sizeof(TestTypeInner) == sizeof(TestTypeInnerReflectionHint), 119 | "Size mismatch: TestTypeInner runtime and reflection types must have identical size"); 120 | static_assert(alignof(TestTypeInner) == alignof(TestTypeInnerReflectionHint), 121 | "Alignment mismatch: TestTypeInner runtime and reflection types must have identical alignment"); 122 | 123 | // 3. Type Signature Check 124 | // Type signature verification uses unified Boost.PFR implementation 125 | // All compilers use lightweight tuple_element and tuple_size_v APIs 126 | static_assert(XTypeSignature::get_XTypeSignature() == "struct[s:40,a:8]{@0[mInt]:i32[s:4,a:4],@8[mVector]:vector[s:32,a:8]}", 127 | "Type signature mismatch for TestTypeInnerReflectionHint"); 128 | 129 | // Compile-time validation for TestType 130 | 131 | // 1. Type Safety Check 132 | // Type safety verification uses Boost.PFR for recursive member checking. 133 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 134 | "Type safety error for TestTypeReflectionHint"); 135 | 136 | // 2. Size and Alignment Check 137 | static_assert(sizeof(TestType) == sizeof(TestTypeReflectionHint), 138 | "Size mismatch: TestType runtime and reflection types must have identical size"); 139 | static_assert(alignof(TestType) == alignof(TestTypeReflectionHint), 140 | "Alignment mismatch: TestType runtime and reflection types must have identical alignment"); 141 | 142 | // 3. Type Signature Check 143 | // Type signature verification uses unified Boost.PFR implementation 144 | // All compilers use lightweight tuple_element and tuple_size_v APIs 145 | static_assert(XTypeSignature::get_XTypeSignature() == 146 | "struct[s:272,a:8]{@0[mInt]:i32[s:4,a:4],@4[mFloat]:f32[s:4,a:4],@8[mVector]:vect" 147 | "or[s:32,a:8],@40[mStringVector]:vector[s:32,a:8]" 148 | ",@72[TestTypeInnerObj]:struct[s:40,a:8]{@0[mInt]:i32[s:4,a:4],@8[mVector]:vector" 149 | "[s:32,a:8]},@112[mXXTypeVector]:vector[s:32,a:8]}>,@144[mComplex" 151 | "Map]:map[s:32,a:8]}>,@176[mStringSet]:set[s:32,a:8],@208[mSet]:set[s:32,a:8],@240[mString]:string[s:32,a:8]}" 154 | , "Type signature mismatch for TestTypeReflectionHint"); 155 | 156 | #endif // GENERATED_TEST_TYPES_HPP_ 157 | -------------------------------------------------------------------------------- /generated/nested_test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENERATED_NESTED_TEST_HPP_ 2 | #define GENERATED_NESTED_TEST_HPP_ 3 | 4 | #include "xoffsetdatastructure2.hpp" 5 | 6 | using namespace XOffsetDatastructure2; 7 | 8 | // ============================================================================ 9 | // Runtime Types - Used for actual data storage 10 | // ============================================================================ 11 | 12 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) InnerObject { 13 | // Default constructor 14 | template 15 | InnerObject(Allocator allocator) : name(allocator), data(allocator) {} 16 | 17 | // Full constructor for emplace_back 18 | template 19 | InnerObject(Allocator allocator, int id_val, const char* name_val) 20 | : id(id_val) 21 | , name(name_val, allocator) 22 | , data(allocator) 23 | {} 24 | 25 | int id{0}; 26 | XString name; 27 | XVector data; 28 | }; 29 | 30 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) MiddleObject { 31 | // Default constructor 32 | template 33 | MiddleObject(Allocator allocator) : name(allocator), inner(allocator), values(allocator) {} 34 | 35 | XString name; 36 | InnerObject inner; 37 | XVector values; 38 | }; 39 | 40 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) OuterObject { 41 | // Default constructor 42 | template 43 | OuterObject(Allocator allocator) : title(allocator), middle(allocator), innerList(allocator) {} 44 | 45 | XString title; 46 | MiddleObject middle; 47 | XVector innerList; 48 | }; 49 | 50 | // ============================================================================ 51 | // Reflection Hint Types - Used for compile-time type analysis 52 | // ============================================================================ 53 | // These are aggregate versions of runtime types that satisfy boost::pfr 54 | // requirements for reflection. They must have identical memory layout 55 | // to their runtime counterparts. 56 | // ============================================================================ 57 | 58 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) InnerObjectReflectionHint { 59 | int32_t id; 60 | XString name; 61 | XVector data; 62 | 63 | // Field names metadata for XTypeSignature 64 | static constexpr std::string_view _field_names[] = { 65 | "id", 66 | "name", 67 | "data", 68 | }; 69 | }; 70 | 71 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) MiddleObjectReflectionHint { 72 | XString name; 73 | InnerObjectReflectionHint inner; 74 | XVector values; 75 | 76 | // Field names metadata for XTypeSignature 77 | static constexpr std::string_view _field_names[] = { 78 | "name", 79 | "inner", 80 | "values", 81 | }; 82 | }; 83 | 84 | struct alignas(XTypeSignature::BASIC_ALIGNMENT) OuterObjectReflectionHint { 85 | XString title; 86 | MiddleObjectReflectionHint middle; 87 | XVector innerList; 88 | 89 | // Field names metadata for XTypeSignature 90 | static constexpr std::string_view _field_names[] = { 91 | "title", 92 | "middle", 93 | "innerList", 94 | }; 95 | }; 96 | 97 | // ============================================================================ 98 | // Compile-Time Validation 99 | // ============================================================================ 100 | 101 | // Compile-time validation for InnerObject 102 | 103 | // 1. Type Safety Check 104 | // Type safety verification uses Boost.PFR for recursive member checking. 105 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 106 | "Type safety error for InnerObjectReflectionHint"); 107 | 108 | // 2. Size and Alignment Check 109 | static_assert(sizeof(InnerObject) == sizeof(InnerObjectReflectionHint), 110 | "Size mismatch: InnerObject runtime and reflection types must have identical size"); 111 | static_assert(alignof(InnerObject) == alignof(InnerObjectReflectionHint), 112 | "Alignment mismatch: InnerObject runtime and reflection types must have identical alignment"); 113 | 114 | // 3. Type Signature Check 115 | // Type signature verification uses unified Boost.PFR implementation 116 | // All compilers use lightweight tuple_element and tuple_size_v APIs 117 | static_assert(XTypeSignature::get_XTypeSignature() == 118 | "struct[s:72,a:8]{@0[id]:i32[s:4,a:4],@8[name]:string[s:32,a:8],@40[data]:vector[" 119 | "s:32,a:8]}" 120 | , "Type signature mismatch for InnerObjectReflectionHint"); 121 | 122 | // Compile-time validation for MiddleObject 123 | 124 | // 1. Type Safety Check 125 | // Type safety verification uses Boost.PFR for recursive member checking. 126 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 127 | "Type safety error for MiddleObjectReflectionHint"); 128 | 129 | // 2. Size and Alignment Check 130 | static_assert(sizeof(MiddleObject) == sizeof(MiddleObjectReflectionHint), 131 | "Size mismatch: MiddleObject runtime and reflection types must have identical size"); 132 | static_assert(alignof(MiddleObject) == alignof(MiddleObjectReflectionHint), 133 | "Alignment mismatch: MiddleObject runtime and reflection types must have identical alignment"); 134 | 135 | // 3. Type Signature Check 136 | // Type signature verification uses unified Boost.PFR implementation 137 | // All compilers use lightweight tuple_element and tuple_size_v APIs 138 | static_assert(XTypeSignature::get_XTypeSignature() == 139 | "struct[s:136,a:8]{@0[name]:string[s:32,a:8],@32[inner]:struct[s:72,a:8]{@0[id]:i" 140 | "32[s:4,a:4],@8[name]:string[s:32,a:8],@40[data]:vector[s:32,a:8]}," 141 | "@104[values]:vector[s:32,a:8]}" 142 | , "Type signature mismatch for MiddleObjectReflectionHint"); 143 | 144 | // Compile-time validation for OuterObject 145 | 146 | // 1. Type Safety Check 147 | // Type safety verification uses Boost.PFR for recursive member checking. 148 | static_assert(XOffsetDatastructure2::is_xbuffer_safe::value, 149 | "Type safety error for OuterObjectReflectionHint"); 150 | 151 | // 2. Size and Alignment Check 152 | static_assert(sizeof(OuterObject) == sizeof(OuterObjectReflectionHint), 153 | "Size mismatch: OuterObject runtime and reflection types must have identical size"); 154 | static_assert(alignof(OuterObject) == alignof(OuterObjectReflectionHint), 155 | "Alignment mismatch: OuterObject runtime and reflection types must have identical alignment"); 156 | 157 | // 3. Type Signature Check 158 | // Type signature verification uses unified Boost.PFR implementation 159 | // All compilers use lightweight tuple_element and tuple_size_v APIs 160 | static_assert(XTypeSignature::get_XTypeSignature() == 161 | "struct[s:200,a:8]{@0[title]:string[s:32,a:8],@32[middle]:struct[s:136,a:8]{@0[na" 162 | "me]:string[s:32,a:8],@32[inner]:struct[s:72,a:8]{@0[id]:i32[s:4,a:4],@8[name]:st" 163 | "ring[s:32,a:8],@40[data]:vector[s:32,a:8]},@104[values]:vector[s:3" 164 | "2,a:8]},@168[innerList]:vector[s:32,a:8]}>" 166 | "}" 167 | , "Type signature mismatch for OuterObjectReflectionHint"); 168 | 169 | #endif // GENERATED_NESTED_TEST_HPP_ 170 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(XOffsetDatastructure) 3 | 4 | # Set C++ standard to C++20 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | # MSVC specific: Enable correct __cplusplus macro value 9 | if(MSVC) 10 | add_compile_options(/Zc:__cplusplus) 11 | endif() 12 | 13 | # macOS and iOS specific settings 14 | if(APPLE) 15 | # Use the system C++ library 16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 17 | 18 | # Set RPATH settings globally 19 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 20 | set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) 21 | set(CMAKE_INSTALL_RPATH "@executable_path;@loader_path") 22 | 23 | # iOS specific settings 24 | if(CMAKE_SYSTEM_NAME STREQUAL "iOS") 25 | # Set code signing identity (use automatic for CI) 26 | set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "") 27 | set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") 28 | set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO") 29 | 30 | # Set development team (empty for unsigned builds) 31 | set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "") 32 | 33 | message(STATUS "Configuring for iOS platform") 34 | else() 35 | # Ensure we use the correct deployment target for macOS 36 | if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) 37 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum macOS deployment version" FORCE) 38 | endif() 39 | endif() 40 | endif() 41 | 42 | # Helper function to set iOS bundle identifier 43 | function(set_ios_bundle_id target) 44 | if(CMAKE_SYSTEM_NAME STREQUAL "iOS") 45 | set_target_properties(${target} PROPERTIES 46 | MACOSX_BUNDLE TRUE 47 | MACOSX_BUNDLE_BUNDLE_NAME ${target} 48 | MACOSX_BUNDLE_BUNDLE_VERSION "1.0.0" 49 | MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0" 50 | XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.xoffsetdatastructure.${target}" 51 | ) 52 | endif() 53 | endfunction() 54 | 55 | # Enable testing 56 | enable_testing() 57 | 58 | # Find Python for code generation 59 | # On Windows, prefer the Python from environment if set 60 | if(WIN32 AND DEFINED ENV{Python3_EXECUTABLE}) 61 | set(Python3_EXECUTABLE $ENV{Python3_EXECUTABLE} CACHE FILEPATH "Path to Python3 executable") 62 | message(STATUS "Using Python from environment: ${Python3_EXECUTABLE}") 63 | elseif(WIN32 AND DEFINED ENV{pythonLocation}) 64 | # GitHub Actions sets pythonLocation 65 | set(Python3_EXECUTABLE "$ENV{pythonLocation}/python.exe" CACHE FILEPATH "Path to Python3 executable") 66 | message(STATUS "Using Python from GitHub Actions: ${Python3_EXECUTABLE}") 67 | endif() 68 | 69 | find_package(Python3 COMPONENTS Interpreter REQUIRED) 70 | message(STATUS "Python3 executable: ${Python3_EXECUTABLE}") 71 | message(STATUS "Python3 version: ${Python3_VERSION}") 72 | 73 | # Set Boost include directories 74 | set(BOOST_INCLUDE_DIRS 75 | ${CMAKE_SOURCE_DIR}/external/boost 76 | ${CMAKE_SOURCE_DIR}/external/boost/libs/any/include 77 | ${CMAKE_SOURCE_DIR}/external/boost/libs/container/include 78 | ${CMAKE_SOURCE_DIR}/external/boost/libs/interprocess/include 79 | ${CMAKE_SOURCE_DIR}/external/boost/libs/type_index/include 80 | ${CMAKE_SOURCE_DIR}/external/boost/libs/core/include 81 | ${CMAKE_SOURCE_DIR}/external/boost/libs/config/include 82 | ${CMAKE_SOURCE_DIR}/external/boost/libs/throw_exception/include 83 | ${CMAKE_SOURCE_DIR}/external/boost/libs/assert/include 84 | ${CMAKE_SOURCE_DIR}/external/boost/libs/static_assert/include 85 | ${CMAKE_SOURCE_DIR}/external/boost/libs/container_hash/include 86 | ${CMAKE_SOURCE_DIR}/external/boost/libs/pfr/include 87 | ${CMAKE_SOURCE_DIR}/external/boost/libs/move/include 88 | ${CMAKE_SOURCE_DIR}/external/boost/libs/type_traits/include 89 | ${CMAKE_SOURCE_DIR}/external/boost/libs/winapi/include 90 | ${CMAKE_SOURCE_DIR}/external/boost/libs/intrusive/include 91 | ${CMAKE_SOURCE_DIR}/external/boost/libs/predef/include 92 | ) 93 | 94 | # Schema code generation 95 | # Option to skip schema generation if headers are pre-generated 96 | option(SKIP_SCHEMA_GENERATION "Skip schema code generation (use pre-generated headers)" OFF) 97 | 98 | if(SKIP_SCHEMA_GENERATION) 99 | # Create an empty target for dependencies, assuming headers are pre-generated 100 | add_custom_target(generate_schemas) 101 | message(STATUS "Schema code generation SKIPPED - using pre-generated headers") 102 | message(STATUS " Generated headers should exist in: ${CMAKE_SOURCE_DIR}/generated/") 103 | 104 | # Verify that generated headers exist 105 | set(EXPECTED_HEADERS 106 | alignment_test.hpp 107 | basic_types.hpp 108 | compaction_test.hpp 109 | game_data.hpp 110 | map_set_test.hpp 111 | modify_test.hpp 112 | nested_test.hpp 113 | player.hpp 114 | serialization_test.hpp 115 | test_types.hpp 116 | vector_test.hpp 117 | ) 118 | 119 | set(MISSING_HEADERS "") 120 | foreach(HEADER ${EXPECTED_HEADERS}) 121 | if(NOT EXISTS "${CMAKE_SOURCE_DIR}/generated/${HEADER}") 122 | list(APPEND MISSING_HEADERS ${HEADER}) 123 | endif() 124 | endforeach() 125 | 126 | if(MISSING_HEADERS) 127 | message(WARNING "Some generated headers are missing:") 128 | foreach(HEADER ${MISSING_HEADERS}) 129 | message(WARNING " - ${HEADER}") 130 | endforeach() 131 | message(WARNING "Please run schema generation manually before building.") 132 | endif() 133 | 134 | elseif(Python3_FOUND) 135 | # Check if PyYAML is available (only once, not per schema) 136 | execute_process( 137 | COMMAND ${Python3_EXECUTABLE} -c "import yaml" 138 | RESULT_VARIABLE PYYAML_CHECK 139 | OUTPUT_QUIET 140 | ERROR_QUIET 141 | ) 142 | 143 | if(NOT PYYAML_CHECK EQUAL 0) 144 | message(WARNING "PyYAML not found for Python ${Python3_EXECUTABLE}.") 145 | message(WARNING " Schema code generation will fail unless headers are pre-generated.") 146 | message(WARNING " Please run: pip3 install pyyaml") 147 | message(WARNING " Or use -DSKIP_SCHEMA_GENERATION=ON and pre-generate headers.") 148 | endif() 149 | 150 | set(SCHEMA_FILES 151 | ${CMAKE_SOURCE_DIR}/schemas/test_types.xds.yaml 152 | ${CMAKE_SOURCE_DIR}/schemas/basic_types.xds.yaml 153 | ${CMAKE_SOURCE_DIR}/schemas/vector_test.xds.yaml 154 | ${CMAKE_SOURCE_DIR}/schemas/map_set_test.xds.yaml 155 | ${CMAKE_SOURCE_DIR}/schemas/nested_test.xds.yaml 156 | ${CMAKE_SOURCE_DIR}/schemas/modify_test.xds.yaml 157 | ${CMAKE_SOURCE_DIR}/schemas/compaction_test.xds.yaml 158 | ${CMAKE_SOURCE_DIR}/schemas/serialization_test.xds.yaml 159 | ${CMAKE_SOURCE_DIR}/schemas/alignment_test.xds.yaml 160 | ${CMAKE_SOURCE_DIR}/schemas/player.xds.yaml 161 | ${CMAKE_SOURCE_DIR}/schemas/game_data.xds.yaml 162 | ) 163 | 164 | # Generate code from schemas 165 | set(GENERATED_HEADERS "") 166 | foreach(SCHEMA_FILE ${SCHEMA_FILES}) 167 | get_filename_component(SCHEMA_NAME ${SCHEMA_FILE} NAME_WE) 168 | string(REPLACE ".xds" "" SCHEMA_NAME ${SCHEMA_NAME}) 169 | set(OUTPUT_FILE "${CMAKE_SOURCE_DIR}/generated/${SCHEMA_NAME}.hpp") 170 | list(APPEND GENERATED_HEADERS ${OUTPUT_FILE}) 171 | 172 | add_custom_command( 173 | OUTPUT ${OUTPUT_FILE} 174 | COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_SOURCE_DIR}/generated 175 | COMMAND ${Python3_EXECUTABLE} 176 | ${CMAKE_SOURCE_DIR}/tools/xds_generator.py 177 | ${SCHEMA_FILE} 178 | -o ${CMAKE_SOURCE_DIR}/generated/ 179 | DEPENDS ${SCHEMA_FILE} ${CMAKE_SOURCE_DIR}/tools/xds_generator.py 180 | COMMENT "Generating ${SCHEMA_NAME}.hpp from schema" 181 | VERBATIM 182 | ) 183 | endforeach() 184 | 185 | add_custom_target(generate_schemas ALL DEPENDS ${GENERATED_HEADERS}) 186 | 187 | message(STATUS "Schema code generation enabled") 188 | message(STATUS " Python: ${Python3_EXECUTABLE}") 189 | message(STATUS " Schemas: ${SCHEMA_FILES}") 190 | else() 191 | # Create empty target even if Python not found 192 | add_custom_target(generate_schemas) 193 | message(WARNING "Python3 not found - schema code generation disabled") 194 | message(WARNING "Please ensure generated headers exist in ${CMAKE_SOURCE_DIR}/generated/") 195 | endif() 196 | 197 | # Add examples subdirectory 198 | add_subdirectory(examples) 199 | 200 | # Add tests subdirectory 201 | add_subdirectory(tests) -------------------------------------------------------------------------------- /tests/test_serialization.cpp: -------------------------------------------------------------------------------- 1 | // Test serialization and deserialization 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../xoffsetdatastructure2.hpp" 7 | 8 | using namespace XOffsetDatastructure2; 9 | 10 | // Test structures 11 | struct alignas(BASIC_ALIGNMENT) SimpleData { 12 | template 13 | SimpleData(Allocator allocator) : name(allocator) {} 14 | 15 | int id; 16 | float value; 17 | XString name; 18 | }; 19 | 20 | struct alignas(BASIC_ALIGNMENT) ComplexData { 21 | template 22 | ComplexData(Allocator allocator) 23 | : title(allocator), items(allocator), tags(allocator), metadata(allocator) {} 24 | 25 | XString title; 26 | XVector items; 27 | XSet tags; 28 | XMap metadata; 29 | }; 30 | 31 | bool test_simple_serialization() { 32 | std::cout << "\nTesting simple serialization...\n"; 33 | 34 | // Create and populate 35 | std::cout << " create and populate... "; 36 | XBuffer xbuf(4096); 37 | auto* data = xbuf.make_root("TestData"); 38 | data->id = 42; 39 | data->value = 3.14f; 40 | std::cout << "ok\n"; 41 | 42 | // Set string field 43 | std::cout << " set string... "; 44 | data->name = XString("Hello", xbuf.allocator()); 45 | std::cout << "ok\n"; 46 | 47 | // Serialize 48 | std::cout << " serialize... "; 49 | std::string serialized = xbuf.save_to_string(); 50 | assert(serialized.size() > 0); 51 | std::cout << "ok (" << serialized.size() << " bytes)\n"; 52 | 53 | // Deserialize 54 | std::cout << " deserialize... "; 55 | XBuffer loaded = XBuffer::load_from_string(serialized); 56 | auto [loaded_data, found] = loaded.find_root("TestData"); 57 | assert(found); 58 | std::cout << "ok\n"; 59 | 60 | // Verify data 61 | std::cout << " verify integrity... "; 62 | assert(loaded_data->id == 42); 63 | assert(loaded_data->value == 3.14f); 64 | assert(loaded_data->name == "Hello"); 65 | std::cout << "ok\n"; 66 | 67 | std::cout << "All tests passed\n"; 68 | return true; 69 | } 70 | 71 | bool test_complex_serialization() { 72 | std::cout << "\nTesting complex serialization...\n"; 73 | 74 | // Create and populate complex structure 75 | std::cout << " create complex structure... "; 76 | XBuffer xbuf(8192); 77 | auto* data = xbuf.make_root("Complex"); 78 | data->title = XString("Test Title", xbuf.allocator()); 79 | 80 | for (int i = 0; i < 10; ++i) { 81 | data->items.push_back(i * 10); 82 | } 83 | 84 | data->tags.insert(1); 85 | data->tags.insert(5); 86 | data->tags.insert(10); 87 | 88 | data->metadata.emplace(XString("version", xbuf.allocator()), 1); 89 | data->metadata.emplace(XString("count", xbuf.allocator()), 100); 90 | std::cout << "ok\n"; 91 | 92 | // Serialize 93 | std::cout << " serialize... "; 94 | std::string serialized = xbuf.save_to_string(); 95 | std::cout << "ok (" << serialized.size() << " bytes)\n"; 96 | 97 | // Deserialize 98 | std::cout << " deserialize... "; 99 | XBuffer loaded = XBuffer::load_from_string(serialized); 100 | auto [loaded_data, found] = loaded.find_root("Complex"); 101 | assert(found); 102 | std::cout << "ok\n"; 103 | 104 | // Verify all data 105 | std::cout << " verify... "; 106 | assert(loaded_data->title == "Test Title"); 107 | assert(loaded_data->items.size() == 10); 108 | assert(loaded_data->items[0] == 0); 109 | assert(loaded_data->items[9] == 90); 110 | assert(loaded_data->tags.size() == 3); 111 | assert(loaded_data->tags.count(5) == 1); 112 | assert(loaded_data->metadata.size() == 2); 113 | assert(loaded_data->metadata[XString("version", xbuf.allocator())] == 1); 114 | std::cout << "ok\n"; 115 | 116 | std::cout << "All tests passed\n"; 117 | return true; 118 | } 119 | 120 | bool test_multiple_objects() { 121 | std::cout << "\nTesting multiple objects...\n"; 122 | 123 | // Create multiple objects 124 | std::cout << " create objects... "; 125 | XBuffer xbuf(8192); 126 | 127 | auto* obj1 = xbuf.make_root("Object1"); 128 | obj1->id = 1; 129 | obj1->value = 1.1f; 130 | obj1->name = XString("First", xbuf.allocator()); 131 | 132 | auto* obj2 = xbuf.make_root("Object2"); 133 | obj2->id = 2; 134 | obj2->value = 2.2f; 135 | obj2->name = XString("Second", xbuf.allocator()); 136 | 137 | auto* obj3 = xbuf.make_root("Object3"); 138 | obj3->id = 3; 139 | obj3->value = 3.3f; 140 | obj3->name = XString("Third", xbuf.allocator()); 141 | std::cout << "ok\n"; 142 | 143 | // Serialize 144 | std::cout << " serialize... "; 145 | std::string serialized = xbuf.save_to_string(); 146 | std::cout << "ok\n"; 147 | 148 | // Deserialize 149 | std::cout << " deserialize... "; 150 | XBuffer loaded = XBuffer::load_from_string(serialized); 151 | 152 | auto [loaded1, found1] = loaded.find_root("Object1"); 153 | auto [loaded2, found2] = loaded.find_root("Object2"); 154 | auto [loaded3, found3] = loaded.find_root("Object3"); 155 | 156 | assert(found1 && found2 && found3); 157 | std::cout << "ok\n"; 158 | 159 | // Verify all objects 160 | std::cout << " verify... "; 161 | assert(loaded1->id == 1 && loaded1->name == "First"); 162 | assert(loaded2->id == 2 && loaded2->name == "Second"); 163 | assert(loaded3->id == 3 && loaded3->name == "Third"); 164 | std::cout << "ok\n"; 165 | 166 | std::cout << "All tests passed\n"; 167 | return true; 168 | } 169 | 170 | bool test_empty_buffer() { 171 | std::cout << "\nTesting empty buffer...\n"; 172 | 173 | // Create empty buffer 174 | std::cout << " create... "; 175 | XBuffer xbuf(1024); 176 | std::cout << "ok\n"; 177 | 178 | // Serialize empty buffer 179 | std::cout << " serialize... "; 180 | std::string serialized = xbuf.save_to_string(); 181 | assert(serialized.size() == 1024); 182 | std::cout << "ok\n"; 183 | 184 | // Deserialize 185 | std::cout << " deserialize... "; 186 | XBuffer loaded = XBuffer::load_from_string(serialized); 187 | assert(loaded.get_size() == 1024); 188 | std::cout << "ok\n"; 189 | 190 | std::cout << "All tests passed\n"; 191 | return true; 192 | } 193 | 194 | bool test_roundtrip() { 195 | std::cout << "\nTesting multiple roundtrips...\n"; 196 | 197 | // Create initial data 198 | std::cout << " create data... "; 199 | XBuffer xbuf1(4096); 200 | auto* data = xbuf1.make_root("Roundtrip"); 201 | data->id = 999; 202 | data->value = 9.99f; 203 | data->name = XString("Original", xbuf1.allocator()); 204 | std::cout << "ok\n"; 205 | 206 | // First roundtrip 207 | std::cout << " roundtrip 1... "; 208 | std::string s1 = xbuf1.save_to_string(); 209 | XBuffer xbuf2 = XBuffer::load_from_string(s1); 210 | auto [data2, f2] = xbuf2.find_root("Roundtrip"); 211 | assert(f2 && data2->id == 999); 212 | std::cout << "ok\n"; 213 | 214 | // Second roundtrip 215 | std::cout << " roundtrip 2... "; 216 | std::string s2 = xbuf2.save_to_string(); 217 | XBuffer xbuf3 = XBuffer::load_from_string(s2); 218 | auto [data3, f3] = xbuf3.find_root("Roundtrip"); 219 | assert(f3 && data3->id == 999); 220 | std::cout << "ok\n"; 221 | 222 | // Third roundtrip 223 | std::cout << " roundtrip 3... "; 224 | std::string s3 = xbuf3.save_to_string(); 225 | XBuffer xbuf4 = XBuffer::load_from_string(s3); 226 | auto [data4, f4] = xbuf4.find_root("Roundtrip"); 227 | assert(f4 && data4->id == 999); 228 | assert(data4->value == 9.99f); 229 | assert(data4->name == "Original"); 230 | std::cout << "ok\n"; 231 | 232 | // Verify serialized data is identical 233 | std::cout << " verify stability... "; 234 | assert(s1.size() == s2.size()); 235 | assert(s2.size() == s3.size()); 236 | std::cout << "ok\n"; 237 | 238 | std::cout << "All tests passed\n"; 239 | return true; 240 | } 241 | 242 | int main() { 243 | bool all_passed = true; 244 | 245 | all_passed &= test_simple_serialization(); 246 | all_passed &= test_complex_serialization(); 247 | all_passed &= test_multiple_objects(); 248 | all_passed &= test_empty_buffer(); 249 | all_passed &= test_roundtrip(); 250 | 251 | if (all_passed) { 252 | std::cout << "\nAll serialization tests passed\n"; 253 | } else { 254 | std::cout << "\nSome tests failed\n"; 255 | } 256 | 257 | return all_passed ? 0 : 1; 258 | } 259 | -------------------------------------------------------------------------------- /tests/test_platform.cpp: -------------------------------------------------------------------------------- 1 | // ============================================================================ 2 | // Test: Platform Requirements 3 | // Purpose: Verify platform meets requirements (64-bit, little-endian) 4 | // ============================================================================ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../xoffsetdatastructure2.hpp" 11 | 12 | using namespace XOffsetDatastructure2; 13 | 14 | // ============================================================================ 15 | // Platform Detection 16 | // ============================================================================ 17 | 18 | bool is_64bit_platform() { 19 | return sizeof(void*) == 8; 20 | } 21 | 22 | bool is_little_endian() { 23 | uint32_t test = 0x01020304; 24 | uint8_t* bytes = reinterpret_cast(&test); 25 | return bytes[0] == 0x04; // Little-endian: LSB first 26 | } 27 | 28 | const char* get_endianness_name() { 29 | return is_little_endian() ? "Little-Endian" : "Big-Endian"; 30 | } 31 | 32 | // ============================================================================ 33 | // Type Size Verification 34 | // ============================================================================ 35 | 36 | bool verify_type_sizes() { 37 | std::cout << " Verifying type sizes...\n"; 38 | 39 | bool all_correct = true; 40 | 41 | // Basic types 42 | if (sizeof(char) != 1) { 43 | std::cout << " [FAIL] sizeof(char) = " << sizeof(char) << ", expected 1\n"; 44 | all_correct = false; 45 | } 46 | 47 | if (sizeof(int) != 4) { 48 | std::cout << " [FAIL] sizeof(int) = " << sizeof(int) << ", expected 4\n"; 49 | all_correct = false; 50 | } 51 | 52 | if (sizeof(float) != 4) { 53 | std::cout << " [FAIL] sizeof(float) = " << sizeof(float) << ", expected 4\n"; 54 | all_correct = false; 55 | } 56 | 57 | if (sizeof(double) != 8) { 58 | std::cout << " [FAIL] sizeof(double) = " << sizeof(double) << ", expected 8\n"; 59 | all_correct = false; 60 | } 61 | 62 | if (sizeof(long long) != 8) { 63 | std::cout << " [FAIL] sizeof(long long) = " << sizeof(long long) << ", expected 8\n"; 64 | all_correct = false; 65 | } 66 | 67 | if (sizeof(void*) != 8) { 68 | std::cout << " [FAIL] sizeof(void*) = " << sizeof(void*) << ", expected 8\n"; 69 | all_correct = false; 70 | } 71 | 72 | // Container types 73 | if (sizeof(XString) != 32) { 74 | std::cout << " [FAIL] sizeof(XString) = " << sizeof(XString) << ", expected 32\n"; 75 | all_correct = false; 76 | } 77 | 78 | if (all_correct) { 79 | std::cout << " [OK] All type sizes correct\n"; 80 | } 81 | 82 | return all_correct; 83 | } 84 | 85 | // ============================================================================ 86 | // Endianness Verification 87 | // ============================================================================ 88 | 89 | bool verify_endianness() { 90 | std::cout << " Verifying endianness...\n"; 91 | 92 | // Test 1: uint32_t byte order 93 | uint32_t test_u32 = 0x12345678; 94 | uint8_t* bytes_u32 = reinterpret_cast(&test_u32); 95 | 96 | if (bytes_u32[0] != 0x78 || bytes_u32[1] != 0x56 || 97 | bytes_u32[2] != 0x34 || bytes_u32[3] != 0x12) { 98 | std::cout << " [FAIL] uint32_t byte order incorrect\n"; 99 | std::cout << " Expected: 78 56 34 12\n"; 100 | std::cout << " Got: " << std::hex 101 | << (int)bytes_u32[0] << " " << (int)bytes_u32[1] << " " 102 | << (int)bytes_u32[2] << " " << (int)bytes_u32[3] << std::dec << "\n"; 103 | return false; 104 | } 105 | 106 | // Test 2: double byte order 107 | double test_double = 1.0; 108 | uint8_t* bytes_double = reinterpret_cast(&test_double); 109 | // IEEE 754 double 1.0 in little-endian: 00 00 00 00 00 00 F0 3F 110 | if (bytes_double[7] != 0x3F || bytes_double[6] != 0xF0) { 111 | std::cout << " [FAIL] double byte order incorrect\n"; 112 | return false; 113 | } 114 | 115 | std::cout << " [OK] Endianness is little-endian\n"; 116 | return true; 117 | } 118 | 119 | // ============================================================================ 120 | // Alignment Verification 121 | // ============================================================================ 122 | 123 | bool verify_alignment() { 124 | std::cout << " Verifying struct alignment...\n"; 125 | 126 | struct TestStruct { 127 | char c; 128 | int i; 129 | double d; 130 | }; 131 | 132 | // Check natural alignment 133 | if (alignof(int) != 4) { 134 | std::cout << " [FAIL] alignof(int) = " << alignof(int) << ", expected 4\n"; 135 | return false; 136 | } 137 | 138 | if (alignof(double) != 8) { 139 | std::cout << " [FAIL] alignof(double) = " << alignof(double) << ", expected 8\n"; 140 | return false; 141 | } 142 | 143 | if (alignof(void*) != 8) { 144 | std::cout << " [FAIL] alignof(void*) = " << alignof(void*) << ", expected 8\n"; 145 | return false; 146 | } 147 | 148 | std::cout << " [OK] Alignment requirements met\n"; 149 | return true; 150 | } 151 | 152 | // ============================================================================ 153 | // Binary Compatibility Test 154 | // ============================================================================ 155 | 156 | bool test_binary_compatibility() { 157 | std::cout << " Testing binary data compatibility...\n"; 158 | 159 | // Create buffer with known data 160 | XBuffer xbuf(1024); 161 | 162 | // Write known values - use vector to avoid array construction issues 163 | typedef XOffsetDatastructure2::XVector IntVector; 164 | auto* int_vec = xbuf.make_root("test_ints"); 165 | int_vec->push_back(0x12345678); 166 | int_vec->push_back(0xABCDEF00); 167 | int_vec->push_back(0x11223344); 168 | int_vec->push_back(0x55667788); 169 | 170 | int* int_array = int_vec->data(); 171 | 172 | // Verify byte order in buffer 173 | auto* buffer = xbuf.get_buffer(); 174 | const uint8_t* data = reinterpret_cast(buffer->data()); 175 | 176 | // Find our test data in the buffer 177 | bool found = false; 178 | for (size_t i = 0; i < buffer->size() - 16; i++) { 179 | if (data[i] == 0x78 && data[i+1] == 0x56 && 180 | data[i+2] == 0x34 && data[i+3] == 0x12) { 181 | found = true; 182 | 183 | // Verify all four integers 184 | const uint8_t expected[] = { 185 | 0x78, 0x56, 0x34, 0x12, // 0x12345678 186 | 0x00, 0xEF, 0xCD, 0xAB, // 0xABCDEF00 187 | 0x44, 0x33, 0x22, 0x11, // 0x11223344 188 | 0x88, 0x77, 0x66, 0x55 // 0x55667788 189 | }; 190 | 191 | bool all_match = true; 192 | for (int j = 0; j < 16; j++) { 193 | if (data[i + j] != expected[j]) { 194 | all_match = false; 195 | break; 196 | } 197 | } 198 | 199 | if (!all_match) { 200 | std::cout << " [FAIL] Binary data layout incorrect\n"; 201 | return false; 202 | } 203 | break; 204 | } 205 | } 206 | 207 | if (!found) { 208 | std::cout << " [FAIL] Could not find test data in buffer\n"; 209 | return false; 210 | } 211 | 212 | std::cout << " [OK] Binary compatibility verified\n"; 213 | return true; 214 | } 215 | 216 | // ============================================================================ 217 | // Cross-buffer Compatibility Test 218 | // ============================================================================ 219 | 220 | bool test_cross_buffer_compatibility() { 221 | std::cout << " Testing cross-buffer data transfer...\n"; 222 | 223 | // Create source buffer with vector 224 | XBuffer src_buf(1024); 225 | typedef XOffsetDatastructure2::XVector IntVector; 226 | auto* src_vec = src_buf.make_root("numbers"); 227 | for (int i = 0; i < 5; i++) { 228 | src_vec->push_back(i * 1000 + i); // 0, 1001, 2002, 3003, 4004 229 | } 230 | 231 | // Serialize to binary 232 | auto* src_buffer = src_buf.get_buffer(); 233 | std::vector binary_data(src_buffer->begin(), src_buffer->end()); 234 | 235 | // Load into new buffer 236 | XBuffer dst_buf(binary_data); 237 | auto* dst_vec = dst_buf.find_root("numbers").first; 238 | 239 | if (!dst_vec) { 240 | std::cout << " [FAIL] Failed to find data in destination buffer\n"; 241 | return false; 242 | } 243 | 244 | // Verify data integrity 245 | for (size_t i = 0; i < dst_vec->size(); i++) { 246 | if ((*dst_vec)[i] != static_cast(i * 1000 + i)) { 247 | std::cout << " [FAIL] Data mismatch at index " << i 248 | << ": expected " << (i * 1000 + i) 249 | << ", got " << (*dst_vec)[i] << "\n"; 250 | return false; 251 | } 252 | } 253 | 254 | std::cout << " [OK] Cross-buffer compatibility verified\n"; 255 | return true; 256 | } 257 | 258 | // ============================================================================ 259 | // Main Test Function 260 | // ============================================================================ 261 | 262 | bool test_platform_requirements() { 263 | std::cout << "\n[TEST] Platform Requirements Verification\n"; 264 | std::cout << std::string(50, '-') << "\n"; 265 | 266 | // Display platform information 267 | std::cout << "Platform Information:\n"; 268 | std::cout << " Architecture: " << (is_64bit_platform() ? "64-bit" : "32-bit") << "\n"; 269 | std::cout << " Endianness: " << get_endianness_name() << "\n"; 270 | std::cout << " Pointer size: " << sizeof(void*) << " bytes\n"; 271 | std::cout << "\n"; 272 | 273 | // Check requirements 274 | std::cout << "Checking Requirements:\n"; 275 | 276 | bool is_64bit = is_64bit_platform(); 277 | bool is_le = is_little_endian(); 278 | 279 | std::cout << " 64-bit architecture: " << (is_64bit ? "[OK] YES" : "[FAIL] NO") << "\n"; 280 | std::cout << " Little-endian: " << (is_le ? "[OK] YES" : "[FAIL] NO") << "\n"; 281 | std::cout << "\n"; 282 | 283 | // If requirements not met, fail with clear message 284 | if (!is_64bit) { 285 | std::cout << "[FAIL] XOffsetDatastructure2 requires 64-bit architecture\n"; 286 | std::cout << " Current platform is " << (sizeof(void*) * 8) << "-bit\n"; 287 | std::cout << " This library is designed for 64-bit systems only.\n"; 288 | return false; 289 | } 290 | 291 | if (!is_le) { 292 | std::cout << "[FAIL] XOffsetDatastructure2 requires little-endian architecture\n"; 293 | std::cout << " Current platform is big-endian\n"; 294 | std::cout << " This library stores data in little-endian format.\n"; 295 | std::cout << " Using it on big-endian systems will cause data corruption.\n"; 296 | return false; 297 | } 298 | 299 | // Run verification tests 300 | std::cout << "Running Verification Tests:\n"; 301 | 302 | if (!verify_type_sizes()) { 303 | std::cout << "[FAIL] Type size verification failed\n"; 304 | return false; 305 | } 306 | 307 | if (!verify_endianness()) { 308 | std::cout << "[FAIL] Endianness verification failed\n"; 309 | return false; 310 | } 311 | 312 | if (!verify_alignment()) { 313 | std::cout << "[FAIL] Alignment verification failed\n"; 314 | return false; 315 | } 316 | 317 | if (!test_binary_compatibility()) { 318 | std::cout << "[FAIL] Binary compatibility test failed\n"; 319 | return false; 320 | } 321 | 322 | if (!test_cross_buffer_compatibility()) { 323 | std::cout << "[FAIL] Cross-buffer compatibility test failed\n"; 324 | return false; 325 | } 326 | 327 | std::cout << "\n"; 328 | std::cout << "[PASS] Platform meets all requirements!\n"; 329 | std::cout << " Your system is compatible with XOffsetDatastructure2.\n"; 330 | std::cout << " - 64-bit architecture\n"; 331 | std::cout << " - Little-endian byte order\n"; 332 | std::cout << " - Correct type sizes and alignment\n"; 333 | std::cout << " - Binary data compatibility verified\n"; 334 | 335 | return true; 336 | } 337 | 338 | int main() { 339 | try { 340 | return test_platform_requirements() ? 0 : 1; 341 | } catch (const std::exception& e) { 342 | std::cerr << "[FAIL] Exception: " << e.what() << "\n"; 343 | return 1; 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /tests/test_comprehensive.cpp: -------------------------------------------------------------------------------- 1 | // ============================================================================ 2 | // Test: Comprehensive Integration Test 3 | // Purpose: Full integration test covering all features with realistic data 4 | // ============================================================================ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "../xoffsetdatastructure2.hpp" 10 | 11 | using namespace XOffsetDatastructure2; 12 | 13 | // Comprehensive test data structure 14 | struct alignas(BASIC_ALIGNMENT) ComprehensiveTestType { 15 | template 16 | ComprehensiveTestType(Allocator allocator) 17 | : mVector(allocator), 18 | mStringVector(allocator), 19 | mComplexMap(allocator), 20 | mStringSet(allocator), 21 | mSet(allocator), 22 | mString(allocator), 23 | TestTypeInnerObj(allocator), 24 | mXXTypeVector(allocator) {} 25 | 26 | int mInt{ 0 }; 27 | float mFloat{ 0.f }; 28 | XVector mVector; 29 | XVector mStringVector; 30 | 31 | class TestTypeInner { 32 | public: 33 | template 34 | TestTypeInner(Allocator allocator) : mVector(allocator) {} 35 | int mInt{ 0 }; 36 | XVector mVector; 37 | } TestTypeInnerObj; 38 | 39 | XVector mXXTypeVector; 40 | XMap mComplexMap; 41 | XSet mStringSet; 42 | XSet mSet; 43 | XString mString; 44 | }; 45 | 46 | // ============================================================================ 47 | // Stage 1: Fill Basic Fields 48 | // ============================================================================ 49 | void fillBasicFields(ComprehensiveTestType* obj, XBuffer& xbuf) { 50 | obj->mInt = 123; 51 | obj->mFloat = 3.14f; 52 | obj->mString = XString("abcdefghijklmnopqrstuvwxyz", xbuf.allocator()); 53 | 54 | obj->mVector.push_back(1); 55 | obj->mVector.push_back(3); 56 | obj->mSet.insert(3); 57 | obj->mSet.insert(4); 58 | 59 | obj->TestTypeInnerObj.mInt = 246; 60 | obj->TestTypeInnerObj.mVector.push_back(23); 61 | obj->TestTypeInnerObj.mVector.push_back(21); 62 | } 63 | 64 | // ============================================================================ 65 | // Stage 2: Fill Small Containers 66 | // ============================================================================ 67 | void fillSmallContainers(ComprehensiveTestType* obj, XBuffer& xbuf) { 68 | for (auto i = 0; i < 1; ++i) { 69 | obj->mVector.push_back(i); 70 | obj->TestTypeInnerObj.mVector.push_back(64 + i); 71 | } 72 | 73 | for (auto i = 0; i < 6; ++i) { 74 | obj->mVector.push_back(1024 + i); 75 | obj->mSet.insert(1024 - i); 76 | } 77 | 78 | for (auto i = 0; i < 10; ++i) { 79 | obj->mStringVector.emplace_back(XString("abcdefghijklmnopqrstuvwxyz", xbuf.allocator())); 80 | } 81 | 82 | for (auto i = 0; i < 13; ++i) { 83 | obj->mStringSet.emplace(XString("stringinset", xbuf.allocator())); 84 | obj->mSet.insert(i); 85 | } 86 | } 87 | 88 | // ============================================================================ 89 | // Stage 3: Fill Large Nested Data 90 | // ============================================================================ 91 | void fillLargeNestedData(ComprehensiveTestType* obj, XBuffer& xbuf) { 92 | // Complex map with nested vectors 93 | for (auto i = 0; i < 7; ++i) { 94 | std::string key = "stringinset" + std::to_string(i); 95 | XString xkey = XString(key.c_str(), xbuf.allocator()); 96 | auto [it, inserted] = obj->mComplexMap.try_emplace(xkey, xbuf.allocator()); 97 | auto& vec = it->second.mVector; 98 | for (int j = 0; j < 100; ++j) { 99 | vec.insert(vec.end(), {7, 8, 9, 10, 11, 12}); 100 | } 101 | } 102 | 103 | // Vector of objects 104 | for (auto i = 0; i < 6; ++i) { 105 | obj->mXXTypeVector.emplace_back(xbuf.allocator()); 106 | obj->mXXTypeVector.back().mInt = i; 107 | auto& vec = obj->mXXTypeVector.back().mVector; 108 | vec = {1, 2, 3, 4, 5, 6}; 109 | for (auto j = 0; j < 12; ++j) { 110 | vec.push_back(j); 111 | } 112 | } 113 | } 114 | 115 | // ============================================================================ 116 | // Validation Functions 117 | // ============================================================================ 118 | bool validateBasicFields(ComprehensiveTestType* obj) { 119 | if (obj->mInt != 123) { 120 | std::cerr << "[X] mInt validation failed\n"; 121 | return false; 122 | } 123 | if (std::abs(obj->mFloat - 3.14f) > 0.01f) { 124 | std::cerr << "[X] mFloat validation failed\n"; 125 | return false; 126 | } 127 | if (obj->mString != "abcdefghijklmnopqrstuvwxyz") { 128 | std::cerr << "[X] mString validation failed\n"; 129 | return false; 130 | } 131 | if (obj->TestTypeInnerObj.mInt != 246) { 132 | std::cerr << "[X] TestTypeInnerObj.mInt validation failed\n"; 133 | return false; 134 | } 135 | return true; 136 | } 137 | 138 | bool validateContainerSizes(ComprehensiveTestType* obj) { 139 | // Vector should have: 1,3,0,1024,1025,1026,1027,1028,1029 140 | if (obj->mVector.size() != 9) { 141 | std::cerr << "[X] mVector size validation failed: " << obj->mVector.size() << "\n"; 142 | return false; 143 | } 144 | if (obj->mStringVector.size() != 10) { 145 | std::cerr << "[X] mStringVector size validation failed\n"; 146 | return false; 147 | } 148 | if (obj->mComplexMap.size() != 7) { 149 | std::cerr << "[X] mComplexMap size validation failed\n"; 150 | return false; 151 | } 152 | if (obj->mXXTypeVector.size() != 6) { 153 | std::cerr << "[X] mXXTypeVector size validation failed\n"; 154 | return false; 155 | } 156 | // Set should have unique values 157 | if (obj->mSet.size() < 13) { 158 | std::cerr << "[X] mSet size validation failed\n"; 159 | return false; 160 | } 161 | return true; 162 | } 163 | 164 | bool validateNestedData(ComprehensiveTestType* obj) { 165 | // Check complex map entries 166 | for (int i = 0; i < 7; ++i) { 167 | std::string key = "stringinset" + std::to_string(i); 168 | auto it = obj->mComplexMap.find(XString(key.c_str(), obj->mComplexMap.get_allocator())); 169 | if (it == obj->mComplexMap.end()) { 170 | std::cerr << "[X] mComplexMap key not found: " << key << "\n"; 171 | return false; 172 | } 173 | // Each entry should have 100 * 6 = 600 elements 174 | if (it->second.mVector.size() != 600) { 175 | std::cerr << "[X] mComplexMap[" << key << "].mVector size validation failed\n"; 176 | return false; 177 | } 178 | } 179 | 180 | // Check vector of objects 181 | for (size_t i = 0; i < obj->mXXTypeVector.size(); ++i) { 182 | if (obj->mXXTypeVector[i].mInt != (int)i) { 183 | std::cerr << "[X] mXXTypeVector[" << i << "].mInt validation failed\n"; 184 | return false; 185 | } 186 | // Each should have 6 + 12 = 18 elements 187 | if (obj->mXXTypeVector[i].mVector.size() != 18) { 188 | std::cerr << "[X] mXXTypeVector[" << i << "].mVector size validation failed\n"; 189 | return false; 190 | } 191 | } 192 | 193 | return true; 194 | } 195 | 196 | // ============================================================================ 197 | // Test Functions 198 | // ============================================================================ 199 | bool test_comprehensive_creation() { 200 | std::cout << "\n[TEST] Comprehensive Data Creation\n"; 201 | std::cout << std::string(50, '-') << "\n"; 202 | 203 | // Test 1: Create buffer and object 204 | std::cout << "Test 1: Create initial buffer... "; 205 | XBuffer xbuf(4096); 206 | auto* obj = xbuf.make_root("CompTest"); 207 | assert(obj != nullptr); 208 | std::cout << "[OK]\n"; 209 | 210 | // Test 2: Fill basic fields 211 | std::cout << "Test 2: Fill basic fields... "; 212 | fillBasicFields(obj, xbuf); 213 | assert(validateBasicFields(obj)); 214 | std::cout << "[OK]\n"; 215 | 216 | // Test 3: Fill small containers 217 | std::cout << "Test 3: Fill small containers... "; 218 | fillSmallContainers(obj, xbuf); 219 | std::cout << "[OK]\n"; 220 | 221 | // Test 4: Grow buffer 222 | std::cout << "Test 4: Grow buffer for large data... "; 223 | xbuf.grow(1024 * 32); 224 | obj = xbuf.find_or_make_root("CompTest"); 225 | assert(obj != nullptr); 226 | std::cout << "[OK]\n"; 227 | 228 | // Test 5: Fill large nested data 229 | std::cout << "Test 5: Fill large nested data... "; 230 | fillLargeNestedData(obj, xbuf); 231 | assert(validateContainerSizes(obj)); 232 | assert(validateNestedData(obj)); 233 | std::cout << "[OK]\n"; 234 | 235 | // Test 6: Memory stats 236 | std::cout << "Test 6: Verify memory usage... "; 237 | auto stats = XBufferVisualizer::get_memory_stats(xbuf); 238 | std::cout << "\n Total: " << stats.total_size << " bytes\n"; 239 | std::cout << " Used: " << stats.used_size << " bytes (" 240 | << stats.usage_percent() << "%)\n"; 241 | assert(stats.used_size > 0); 242 | assert(stats.used_size < stats.total_size); 243 | std::cout << "[OK]\n"; 244 | 245 | std::cout << "[PASS] Comprehensive data creation test passed!\n"; 246 | return true; 247 | } 248 | 249 | bool test_comprehensive_serialization() { 250 | std::cout << "\n[TEST] Comprehensive Serialization\n"; 251 | std::cout << std::string(50, '-') << "\n"; 252 | 253 | // Test 1: Create and fill data 254 | std::cout << "Test 1: Create comprehensive data... "; 255 | XBuffer xbuf(4096); 256 | auto* obj = xbuf.make_root("CompTest"); 257 | fillBasicFields(obj, xbuf); 258 | fillSmallContainers(obj, xbuf); 259 | xbuf.grow(1024 * 32); 260 | obj = xbuf.find_or_make_root("CompTest"); 261 | fillLargeNestedData(obj, xbuf); 262 | std::cout << "[OK]\n"; 263 | 264 | // Test 2: Serialize to memory 265 | std::cout << "Test 2: Serialize to memory... "; 266 | std::vector buffer(*xbuf.get_buffer()); 267 | assert(buffer.size() > 0); 268 | std::cout << "[OK] (" << buffer.size() << " bytes)\n"; 269 | 270 | // Test 3: Deserialize from memory 271 | std::cout << "Test 3: Deserialize from memory... "; 272 | XBuffer new_xbuf(buffer); 273 | auto [new_obj, found] = new_xbuf.find_root("CompTest"); 274 | assert(found); 275 | assert(new_obj != nullptr); 276 | std::cout << "[OK]\n"; 277 | 278 | // Test 4: Validate deserialized basic fields 279 | std::cout << "Test 4: Validate basic fields... "; 280 | assert(validateBasicFields(new_obj)); 281 | std::cout << "[OK]\n"; 282 | 283 | // Test 5: Validate deserialized container sizes 284 | std::cout << "Test 5: Validate container sizes... "; 285 | assert(validateContainerSizes(new_obj)); 286 | std::cout << "[OK]\n"; 287 | 288 | // Test 6: Validate deserialized nested data 289 | std::cout << "Test 6: Validate nested data... "; 290 | assert(validateNestedData(new_obj)); 291 | std::cout << "[OK]\n"; 292 | 293 | std::cout << "[PASS] Comprehensive serialization test passed!\n"; 294 | return true; 295 | } 296 | 297 | bool test_comprehensive_modification() { 298 | std::cout << "\n[TEST] Comprehensive Modification\n"; 299 | std::cout << std::string(50, '-') << "\n"; 300 | 301 | // Test 1: Create initial data 302 | std::cout << "Test 1: Create initial data... "; 303 | XBuffer xbuf(4096); 304 | auto* obj = xbuf.make_root("CompTest"); 305 | fillBasicFields(obj, xbuf); 306 | fillSmallContainers(obj, xbuf); 307 | std::cout << "[OK]\n"; 308 | 309 | // Test 2: Modify basic fields 310 | std::cout << "Test 2: Modify basic fields... "; 311 | obj->mInt = 999; 312 | obj->mFloat = 2.718f; 313 | assert(obj->mInt == 999); 314 | assert(std::abs(obj->mFloat - 2.718f) < 0.01f); 315 | std::cout << "[OK]\n"; 316 | 317 | // Test 3: Modify vector 318 | std::cout << "Test 3: Modify vector elements... "; 319 | obj->mVector[0] = 777; 320 | assert(obj->mVector[0] == 777); 321 | obj->mVector.push_back(9999); 322 | assert(obj->mVector.back() == 9999); 323 | std::cout << "[OK]\n"; 324 | 325 | // Test 4: Modify nested object 326 | std::cout << "Test 4: Modify nested object... "; 327 | obj->TestTypeInnerObj.mInt = 555; 328 | assert(obj->TestTypeInnerObj.mInt == 555); 329 | std::cout << "[OK]\n"; 330 | 331 | // Test 5: Serialize and verify modifications 332 | std::cout << "Test 5: Serialize and verify... "; 333 | std::vector buffer(*xbuf.get_buffer()); 334 | XBuffer new_xbuf(buffer); 335 | auto [new_obj, found] = new_xbuf.find_root("CompTest"); 336 | assert(found); 337 | assert(new_obj->mInt == 999); 338 | assert(new_obj->mVector[0] == 777); 339 | assert(new_obj->TestTypeInnerObj.mInt == 555); 340 | std::cout << "[OK]\n"; 341 | 342 | std::cout << "[PASS] Comprehensive modification test passed!\n"; 343 | return true; 344 | } 345 | 346 | bool test_comprehensive_memory_operations() { 347 | std::cout << "\n[TEST] Comprehensive Memory Operations\n"; 348 | std::cout << std::string(50, '-') << "\n"; 349 | 350 | // Test 1: Initial buffer 351 | std::cout << "Test 1: Create initial buffer... "; 352 | XBuffer xbuf(4096); 353 | auto stats1 = XBufferVisualizer::get_memory_stats(xbuf); 354 | std::cout << "[OK]\n Initial: " << stats1.total_size << " bytes\n"; 355 | 356 | // Test 2: Add data 357 | std::cout << "Test 2: Add comprehensive data... "; 358 | auto* obj = xbuf.make_root("CompTest"); 359 | fillBasicFields(obj, xbuf); 360 | fillSmallContainers(obj, xbuf); 361 | auto stats2 = XBufferVisualizer::get_memory_stats(xbuf); 362 | assert(stats2.used_size > stats1.used_size); 363 | std::cout << "[OK]\n Used: " << stats2.used_size << " bytes (" 364 | << stats2.usage_percent() << "%)\n"; 365 | 366 | // Test 3: Grow buffer 367 | std::cout << "Test 3: Grow buffer... "; 368 | std::size_t old_size = xbuf.get_size(); 369 | xbuf.grow(1024 * 32); 370 | assert(xbuf.get_size() == old_size + 1024 * 32); 371 | auto stats3 = XBufferVisualizer::get_memory_stats(xbuf); 372 | std::cout << "[OK]\n After grow: " << stats3.total_size << " bytes (" 373 | << stats3.usage_percent() << "%)\n"; 374 | 375 | // Test 4: Add more data 376 | std::cout << "Test 4: Add large nested data... "; 377 | obj = xbuf.find_or_make_root("CompTest"); 378 | fillLargeNestedData(obj, xbuf); 379 | auto stats4 = XBufferVisualizer::get_memory_stats(xbuf); 380 | std::cout << "[OK]\n Final used: " << stats4.used_size << " bytes (" 381 | << stats4.usage_percent() << "%)\n"; 382 | 383 | // Test 5: Shrink to fit 384 | std::cout << "Test 5: Shrink to fit... "; 385 | xbuf.shrink_to_fit(); 386 | auto stats5 = XBufferVisualizer::get_memory_stats(xbuf); 387 | assert(stats5.total_size < stats4.total_size); 388 | std::cout << "[OK]\n After shrink: " << stats5.total_size << " bytes (" 389 | << stats5.usage_percent() << "%)\n"; 390 | 391 | // Test 6: Verify data after shrink 392 | std::cout << "Test 6: Verify data integrity... "; 393 | auto [found_obj, found] = xbuf.find_root("CompTest"); 394 | assert(found); 395 | assert(validateBasicFields(found_obj)); 396 | assert(validateContainerSizes(found_obj)); 397 | assert(validateNestedData(found_obj)); 398 | std::cout << "[OK]\n"; 399 | 400 | std::cout << "[PASS] Comprehensive memory operations test passed!\n"; 401 | return true; 402 | } 403 | 404 | // ============================================================================ 405 | // Main 406 | // ============================================================================ 407 | int main() { 408 | try { 409 | bool all_passed = true; 410 | 411 | all_passed &= test_comprehensive_creation(); 412 | all_passed &= test_comprehensive_serialization(); 413 | all_passed &= test_comprehensive_modification(); 414 | all_passed &= test_comprehensive_memory_operations(); 415 | 416 | if (all_passed) { 417 | std::cout << "\n[SUCCESS] All comprehensive tests passed!\n"; 418 | return 0; 419 | } else { 420 | std::cout << "\n[FAILURE] Some comprehensive tests failed!\n"; 421 | return 1; 422 | } 423 | } catch (const std::exception& e) { 424 | std::cerr << "[FAIL] Exception: " << e.what() << "\n"; 425 | return 1; 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Cross-Platform CI 2 | 3 | on: 4 | push: 5 | branches: [ main, develop, next_practical, release/v2.0-practical ] 6 | pull_request: 7 | branches: [ main, develop, next_practical, release/v2.0-practical ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | # ============================================================================ 12 | # macOS Testing 13 | # ============================================================================ 14 | test-macos: 15 | name: macOS (Clang) 16 | runs-on: macos-latest 17 | strategy: 18 | matrix: 19 | build_type: [Debug, Release] 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | with: 25 | submodules: recursive 26 | 27 | - name: Setup Python 28 | uses: actions/setup-python@v4 29 | with: 30 | python-version: '3.11' 31 | 32 | - name: Install dependencies 33 | run: | 34 | brew install cmake 35 | python3 -m pip install --upgrade pip 36 | python3 -m pip install pyyaml 37 | python3 -c "import yaml; print('PyYAML installed for:', __import__('sys').executable)" 38 | 39 | - name: Pre-generate schema headers 40 | run: | 41 | echo "Pre-generating schema headers before CMake build..." 42 | mkdir -p generated 43 | for schema in schemas/*.xds.yaml; do 44 | python3 tools/xds_generator.py "$schema" -o generated/ 45 | done 46 | echo "Generated headers:" 47 | ls -la generated/ 48 | 49 | - name: Configure CMake 50 | run: | 51 | mkdir build 52 | cd build 53 | cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ 54 | -DPython3_EXECUTABLE=$(which python3) \ 55 | -DSKIP_SCHEMA_GENERATION=ON \ 56 | .. 57 | 58 | - name: Build 59 | run: cmake --build build --config ${{ matrix.build_type }} -j$(sysctl -n hw.ncpu) 60 | 61 | - name: Run Tests 62 | run: | 63 | cd build 64 | ctest -C ${{ matrix.build_type }} --output-on-failure -V 65 | 66 | - name: Run MSVC Compatibility Test 67 | run: ./build/bin/test_msvc_compat 68 | 69 | - name: Run Demo 70 | run: ./build/bin/demo 71 | 72 | - name: Upload test results 73 | if: always() 74 | uses: actions/upload-artifact@v4 75 | with: 76 | name: test-results-macos-${{ matrix.build_type }} 77 | path: build/Testing/ 78 | 79 | # ============================================================================ 80 | # Linux Testing 81 | # ============================================================================ 82 | test-linux: 83 | name: Linux (GCC/Clang) 84 | runs-on: ubuntu-latest 85 | strategy: 86 | matrix: 87 | compiler: [gcc, clang] 88 | build_type: [Debug, Release] 89 | include: 90 | - compiler: gcc 91 | cc: gcc-11 92 | cxx: g++-11 93 | - compiler: clang 94 | cc: clang-14 95 | cxx: clang++-14 96 | 97 | steps: 98 | - name: Checkout code 99 | uses: actions/checkout@v4 100 | with: 101 | submodules: recursive 102 | 103 | - name: Setup Python 104 | uses: actions/setup-python@v4 105 | with: 106 | python-version: '3.11' 107 | 108 | - name: Install dependencies 109 | run: | 110 | sudo apt-get update 111 | sudo apt-get install -y cmake python3-pip ${{ matrix.cc }} ${{ matrix.cxx }} 112 | pip3 install pyyaml 113 | 114 | - name: Pre-generate schema headers 115 | run: | 116 | echo "Pre-generating schema headers before CMake build..." 117 | mkdir -p generated 118 | for schema in schemas/*.xds.yaml; do 119 | python3 tools/xds_generator.py "$schema" -o generated/ 120 | done 121 | echo "Generated headers:" 122 | ls -la generated/ 123 | 124 | - name: Configure CMake 125 | env: 126 | CC: ${{ matrix.cc }} 127 | CXX: ${{ matrix.cxx }} 128 | run: | 129 | mkdir build 130 | cd build 131 | cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ 132 | -DSKIP_SCHEMA_GENERATION=ON \ 133 | .. 134 | 135 | - name: Build 136 | run: cmake --build build --config ${{ matrix.build_type }} -j$(nproc) 137 | 138 | - name: Run Tests 139 | run: | 140 | cd build 141 | ctest -C ${{ matrix.build_type }} --output-on-failure -V 142 | 143 | - name: Run MSVC Compatibility Test 144 | run: ./build/bin/test_msvc_compat 145 | 146 | - name: Run Demo 147 | run: ./build/bin/demo 148 | 149 | - name: Upload test results 150 | if: always() 151 | uses: actions/upload-artifact@v4 152 | with: 153 | name: test-results-linux-${{ matrix.compiler }}-${{ matrix.build_type }} 154 | path: build/Testing/ 155 | 156 | # ============================================================================ 157 | # Windows Testing (MSVC) 158 | # ============================================================================ 159 | test-windows-msvc: 160 | name: Windows (MSVC) 161 | runs-on: windows-latest 162 | strategy: 163 | matrix: 164 | build_type: [Debug, Release] 165 | arch: [x64] 166 | 167 | steps: 168 | - name: Checkout code 169 | uses: actions/checkout@v4 170 | with: 171 | submodules: recursive 172 | 173 | - name: Setup Python 174 | uses: actions/setup-python@v4 175 | with: 176 | python-version: '3.11' 177 | 178 | - name: Install PyYAML 179 | run: | 180 | python --version 181 | python -m pip --version 182 | python -m pip install --upgrade pip 183 | python -m pip install pyyaml 184 | python -c "import yaml; print('PyYAML version:', yaml.__version__)" 185 | 186 | - name: Pre-generate schema headers 187 | run: | 188 | echo "Pre-generating schema headers before CMake build..." 189 | if (-not (Test-Path "generated")) { 190 | New-Item -ItemType Directory -Path "generated" 191 | } 192 | Get-ChildItem -Path "schemas" -Filter "*.xds.yaml" | ForEach-Object { 193 | python tools/xds_generator.py $_.FullName -o generated/ 194 | } 195 | echo "" 196 | echo "Generated headers:" 197 | Get-ChildItem -Path "generated" -Filter "*.hpp" | ForEach-Object { 198 | Write-Host " $_" 199 | # Touch the file to make it newer than dependencies 200 | (Get-Item $_).LastWriteTime = Get-Date 201 | } 202 | echo "" 203 | echo "Verification: All headers exist and are up-to-date" 204 | 205 | - name: Configure CMake 206 | run: | 207 | mkdir build 208 | cd build 209 | cmake -G "Visual Studio 17 2022" -A ${{ matrix.arch }} -DSKIP_SCHEMA_GENERATION=ON .. 210 | 211 | - name: Build 212 | run: cmake --build build --config ${{ matrix.build_type }} -j $env:NUMBER_OF_PROCESSORS 213 | 214 | - name: Run Tests 215 | run: | 216 | cd build 217 | ctest -C ${{ matrix.build_type }} --output-on-failure -V 218 | 219 | - name: Run MSVC Compatibility Test 220 | run: .\build\bin\${{ matrix.build_type }}\test_msvc_compat.exe 221 | 222 | - name: Run Demo 223 | run: .\build\bin\${{ matrix.build_type }}\demo.exe 224 | 225 | - name: Test with Type Signature Validation (Experimental) 226 | continue-on-error: true 227 | run: | 228 | mkdir build-sig-validation 229 | cd build-sig-validation 230 | cmake -G "Visual Studio 17 2022" -A ${{ matrix.arch }} -DENABLE_MSVC_TYPE_SIGNATURE_VALIDATION=ON .. 231 | cmake --build . --config ${{ matrix.build_type }} 232 | if ($LASTEXITCODE -eq 0) { 233 | Write-Host "✅ MSVC type signature validation: ENABLED and WORKING!" 234 | .\bin\${{ matrix.build_type }}\test_msvc_compat.exe 235 | } else { 236 | Write-Host "⚠️ MSVC type signature validation: FAILED (as expected)" 237 | } 238 | 239 | - name: Upload test results 240 | if: always() 241 | uses: actions/upload-artifact@v4 242 | with: 243 | name: test-results-windows-msvc-${{ matrix.build_type }} 244 | path: build/Testing/ 245 | 246 | # ============================================================================ 247 | # Android Testing (NDK) 248 | # ============================================================================ 249 | test-android: 250 | name: Android (NDK) 251 | runs-on: ubuntu-latest 252 | strategy: 253 | fail-fast: false # Don't cancel other jobs if one fails 254 | matrix: 255 | abi: [arm64-v8a, x86_64] # Only 64-bit architectures (library requirement) 256 | api_level: [30] 257 | 258 | steps: 259 | - name: Checkout code 260 | uses: actions/checkout@v4 261 | with: 262 | submodules: recursive 263 | 264 | - name: Setup Python 265 | uses: actions/setup-python@v4 266 | with: 267 | python-version: '3.11' 268 | 269 | - name: Install dependencies 270 | run: | 271 | sudo apt-get update 272 | sudo apt-get install -y cmake python3-pip ninja-build 273 | pip3 install pyyaml 274 | 275 | - name: Pre-generate schema headers 276 | run: | 277 | echo "Pre-generating schema headers before CMake build..." 278 | mkdir -p generated 279 | for schema in schemas/*.xds.yaml; do 280 | python3 tools/xds_generator.py "$schema" -o generated/ 281 | done 282 | echo "Generated headers:" 283 | ls -la generated/ 284 | 285 | - name: Setup Android NDK 286 | uses: nttld/setup-ndk@v1 287 | with: 288 | ndk-version: r25c 289 | add-to-path: true 290 | 291 | - name: Configure CMake for Android 292 | run: | 293 | mkdir build-android-${{ matrix.abi }} 294 | cd build-android-${{ matrix.abi }} 295 | cmake -G Ninja \ 296 | -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ 297 | -DANDROID_ABI=${{ matrix.abi }} \ 298 | -DANDROID_PLATFORM=android-${{ matrix.api_level }} \ 299 | -DANDROID_STL=c++_static \ 300 | -DCMAKE_BUILD_TYPE=Release \ 301 | -DSKIP_SCHEMA_GENERATION=ON \ 302 | .. 303 | 304 | - name: Build for Android 305 | run: cmake --build build-android-${{ matrix.abi }} --config Release -j$(nproc) 306 | 307 | - name: List built binaries 308 | run: | 309 | echo "Built binaries for ${{ matrix.abi }}:" 310 | find build-android-${{ matrix.abi }}/bin -type f -executable 311 | 312 | - name: Setup Android Emulator (x86_64 only) 313 | if: matrix.abi == 'x86_64' 314 | uses: reactivecircus/android-emulator-runner@v2 315 | with: 316 | api-level: ${{ matrix.api_level }} 317 | arch: x86_64 318 | script: | 319 | adb push build-android-${{ matrix.abi }}/bin/test_msvc_compat /data/local/tmp/ 320 | adb push build-android-${{ matrix.abi }}/bin/demo /data/local/tmp/ 321 | adb shell "chmod +x /data/local/tmp/test_msvc_compat /data/local/tmp/demo" 322 | adb shell "cd /data/local/tmp && ./test_msvc_compat" 323 | adb shell "cd /data/local/tmp && ./demo" 324 | 325 | - name: Upload Android binaries 326 | uses: actions/upload-artifact@v4 327 | with: 328 | name: android-binaries-${{ matrix.abi }} 329 | path: build-android-${{ matrix.abi }}/bin/ 330 | 331 | # ============================================================================ 332 | # iOS Testing 333 | # ============================================================================ 334 | test-ios: 335 | name: iOS (Xcode) 336 | runs-on: macos-latest 337 | strategy: 338 | matrix: 339 | platform: [iOS, iOS-simulator] 340 | include: 341 | - platform: iOS 342 | sdk: iphoneos 343 | arch: arm64 344 | - platform: iOS-simulator 345 | sdk: iphonesimulator 346 | arch: x86_64 347 | 348 | steps: 349 | - name: Checkout code 350 | uses: actions/checkout@v4 351 | with: 352 | submodules: recursive 353 | 354 | - name: Setup Python 355 | uses: actions/setup-python@v4 356 | with: 357 | python-version: '3.11' 358 | 359 | - name: Install dependencies 360 | run: | 361 | brew install cmake 362 | pip3 install pyyaml 363 | 364 | - name: Pre-generate schema headers 365 | run: | 366 | echo "Pre-generating schema headers before Xcode build..." 367 | mkdir -p generated 368 | for schema in schemas/*.xds.yaml; do 369 | python3 tools/xds_generator.py "$schema" -o generated/ 370 | done 371 | echo "Generated headers:" 372 | ls -la generated/ 373 | 374 | - name: Select Xcode version 375 | run: | 376 | # List available Xcode versions 377 | ls /Applications/ | grep Xcode || true 378 | # Use the default Xcode (usually latest) 379 | sudo xcode-select -s /Applications/Xcode.app/Contents/Developer 380 | xcodebuild -version 381 | 382 | - name: Configure CMake for iOS 383 | run: | 384 | mkdir build-${{ matrix.platform }} 385 | cd build-${{ matrix.platform }} 386 | cmake -G Xcode \ 387 | -DCMAKE_SYSTEM_NAME=iOS \ 388 | -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \ 389 | -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \ 390 | -DCMAKE_OSX_SYSROOT=${{ matrix.sdk }} \ 391 | -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \ 392 | -DCMAKE_IOS_INSTALL_COMBINED=YES \ 393 | -DSKIP_SCHEMA_GENERATION=ON \ 394 | .. 395 | 396 | - name: Build for iOS 397 | run: | 398 | cmake --build build-${{ matrix.platform }} --config Release -- -sdk ${{ matrix.sdk }} -arch ${{ matrix.arch }} 399 | 400 | - name: List built binaries 401 | run: | 402 | echo "Built binaries for ${{ matrix.platform }}:" 403 | find build-${{ matrix.platform }} -name "*.app" -o -name "test_*" -o -name "demo" 404 | 405 | - name: Run tests on iOS Simulator 406 | if: matrix.platform == 'iOS-simulator' 407 | continue-on-error: true 408 | run: | 409 | echo "=== iOS Simulator Test Execution ===" 410 | 411 | # Find simulator 412 | echo "Step 1: Finding iPhone simulator..." 413 | SIMULATOR_ID=$(xcrun simctl list devices available 2>&1 | grep "iPhone" | grep -v "unavailable" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/') 414 | 415 | if [ -z "$SIMULATOR_ID" ]; then 416 | echo "❌ No iPhone simulator found" 417 | exit 0 418 | fi 419 | 420 | SIMULATOR_NAME=$(xcrun simctl list devices 2>&1 | grep "$SIMULATOR_ID" | sed -E 's/^[[:space:]]+//' | sed -E 's/ \([A-F0-9-]+\).*//') 421 | echo "✓ Found simulator: $SIMULATOR_NAME ($SIMULATOR_ID)" 422 | echo "" 423 | 424 | # Boot simulator with timeout 425 | echo "Step 2: Booting simulator..." 426 | if xcrun simctl boot "$SIMULATOR_ID" 2>&1; then 427 | echo "✓ Simulator booted successfully" 428 | else 429 | echo "⚠ Simulator already booted or boot failed (continuing anyway)" 430 | fi 431 | 432 | echo "Waiting 3 seconds for simulator to be ready..." 433 | sleep 3 434 | echo "" 435 | 436 | # Find executables using targeted search 437 | echo "Step 3: Searching for test executables..." 438 | 439 | # Xcode builds with specific configuration paths 440 | POSSIBLE_PATHS=( 441 | "build-${{ matrix.platform }}/Release-iphonesimulator" 442 | "build-${{ matrix.platform }}/tests/Release-iphonesimulator" 443 | "build-${{ matrix.platform }}/examples/Release-iphonesimulator" 444 | "build-${{ matrix.platform }}/bin/Release" 445 | "build-${{ matrix.platform }}/bin" 446 | ) 447 | 448 | echo "Checking build output locations..." 449 | for path in "${POSSIBLE_PATHS[@]}"; do 450 | if [ -d "$path" ]; then 451 | echo " Found directory: $path" 452 | ls "$path" 2>/dev/null | head -10 || true 453 | fi 454 | done 455 | echo "" 456 | 457 | # Search for executables (both standalone and inside .app bundles) 458 | echo "Step 4: Looking for test_msvc_compat and demo..." 459 | TEST_EXEC="" 460 | DEMO_EXEC="" 461 | 462 | for path in "${POSSIBLE_PATHS[@]}"; do 463 | # Check for standalone executables first 464 | if [ -f "$path/test_msvc_compat" ]; then 465 | TEST_EXEC="$path/test_msvc_compat" 466 | echo " ✓ Found test_msvc_compat: $TEST_EXEC" 467 | fi 468 | if [ -f "$path/demo" ]; then 469 | DEMO_EXEC="$path/demo" 470 | echo " ✓ Found demo: $DEMO_EXEC" 471 | fi 472 | 473 | # Check inside .app bundles (iOS typical structure) 474 | if [ -z "$TEST_EXEC" ] && [ -f "$path/test_msvc_compat.app/test_msvc_compat" ]; then 475 | TEST_EXEC="$path/test_msvc_compat.app/test_msvc_compat" 476 | echo " ✓ Found test_msvc_compat in .app bundle: $TEST_EXEC" 477 | fi 478 | if [ -z "$DEMO_EXEC" ] && [ -f "$path/demo.app/demo" ]; then 479 | DEMO_EXEC="$path/demo.app/demo" 480 | echo " ✓ Found demo in .app bundle: $DEMO_EXEC" 481 | fi 482 | done 483 | 484 | if [ -z "$TEST_EXEC" ] && [ -z "$DEMO_EXEC" ]; then 485 | echo " ⚠ No executables found in standard locations or .app bundles" 486 | echo "" 487 | echo "Performing deep search..." 488 | find "build-${{ matrix.platform }}" -type f \( -name "test_msvc_compat" -o -name "demo" \) 2>/dev/null | head -5 | while read -r file; do 489 | echo " Found: $file" 490 | done || echo " No executables found" 491 | fi 492 | echo "" 493 | 494 | # Run tests 495 | echo "Step 5: Running tests on simulator..." 496 | TESTS_RUN=0 497 | 498 | if [ -n "$TEST_EXEC" ] && [ -f "$TEST_EXEC" ]; then 499 | echo "Running test_msvc_compat..." 500 | if xcrun simctl spawn "$SIMULATOR_ID" "$TEST_EXEC" 2>&1; then 501 | echo " ✓ test_msvc_compat completed successfully" 502 | TESTS_RUN=$((TESTS_RUN + 1)) 503 | else 504 | echo " ✗ test_msvc_compat failed" 505 | fi 506 | echo "" 507 | fi 508 | 509 | if [ -n "$DEMO_EXEC" ] && [ -f "$DEMO_EXEC" ]; then 510 | echo "Running demo..." 511 | if xcrun simctl spawn "$SIMULATOR_ID" "$DEMO_EXEC" 2>&1; then 512 | echo " ✓ demo completed successfully" 513 | TESTS_RUN=$((TESTS_RUN + 1)) 514 | else 515 | echo " ✗ demo failed" 516 | fi 517 | echo "" 518 | fi 519 | 520 | # Summary 521 | echo "Step 6: Cleanup..." 522 | xcrun simctl shutdown "$SIMULATOR_ID" 2>&1 || true 523 | echo "" 524 | 525 | echo "=== Summary ===" 526 | echo "Simulator: $SIMULATOR_NAME" 527 | echo "Tests run: $TESTS_RUN" 528 | if [ $TESTS_RUN -gt 0 ]; then 529 | echo "✅ iOS Simulator tests completed" 530 | else 531 | echo "⚠️ No tests were executed (executables not found or not configured for iOS)" 532 | echo "" 533 | echo "Note: iOS builds may require additional configuration to create" 534 | echo "standalone executables instead of .app bundles." 535 | fi 536 | 537 | - name: Upload iOS binaries 538 | uses: actions/upload-artifact@v4 539 | with: 540 | name: ios-binaries-${{ matrix.platform }} 541 | path: build-${{ matrix.platform }}/bin/ 542 | 543 | # ============================================================================ 544 | # Binary Compatibility Test (Cross-Platform) 545 | # ============================================================================ 546 | test-binary-compatibility: 547 | name: Binary Compatibility 548 | needs: [test-macos, test-linux, test-windows-msvc] 549 | runs-on: ubuntu-latest 550 | 551 | steps: 552 | - name: Download all artifacts 553 | uses: actions/download-artifact@v4 554 | 555 | - name: Check binary compatibility 556 | run: | 557 | echo "======================================" 558 | echo "Binary Compatibility Report" 559 | echo "======================================" 560 | 561 | # List all downloaded artifacts 562 | ls -la 563 | 564 | echo "" 565 | echo "✅ All platforms built successfully!" 566 | echo "" 567 | echo "Note: Binary compatibility requires running" 568 | echo "serialization tests with data exchange between platforms." 569 | 570 | # ============================================================================ 571 | # Code Quality Checks 572 | # ============================================================================ 573 | code-quality: 574 | name: Code Quality 575 | runs-on: ubuntu-latest 576 | 577 | steps: 578 | - name: Checkout code 579 | uses: actions/checkout@v4 580 | with: 581 | submodules: recursive 582 | 583 | - name: Setup Python 584 | uses: actions/setup-python@v4 585 | with: 586 | python-version: '3.11' 587 | 588 | - name: Install dependencies 589 | run: | 590 | pip3 install pyyaml 591 | 592 | - name: Validate YAML schemas 593 | run: | 594 | echo "Validating YAML schemas..." 595 | python3 -c " 596 | import yaml 597 | from pathlib import Path 598 | 599 | schemas = Path('schemas').glob('*.yaml') 600 | for schema in schemas: 601 | print(f'Validating {schema}...') 602 | with open(schema) as f: 603 | yaml.safe_load(f) 604 | print(f' ✅ {schema} is valid') 605 | " 606 | 607 | - name: Check generated files are up-to-date 608 | run: | 609 | python3 tools/xds_generator.py schemas/game_data.xds.yaml -o generated/ 610 | if git diff --exit-code generated/; then 611 | echo "✅ Generated files are up-to-date" 612 | else 613 | echo "❌ Generated files are out of date. Please run the generator." 614 | exit 1 615 | fi 616 | 617 | # ============================================================================ 618 | # Performance Benchmarks 619 | # ============================================================================ 620 | benchmarks: 621 | name: Performance Benchmarks 622 | runs-on: ubuntu-latest 623 | 624 | steps: 625 | - name: Checkout code 626 | uses: actions/checkout@v4 627 | with: 628 | submodules: recursive 629 | 630 | - name: Setup Python 631 | uses: actions/setup-python@v4 632 | with: 633 | python-version: '3.11' 634 | 635 | - name: Install dependencies 636 | run: | 637 | sudo apt-get update 638 | sudo apt-get install -y cmake python3-pip 639 | pip3 install pyyaml 640 | 641 | - name: Pre-generate schema headers 642 | run: | 643 | echo "Pre-generating schema headers before CMake build..." 644 | mkdir -p generated 645 | for schema in schemas/*.xds.yaml; do 646 | python3 tools/xds_generator.py "$schema" -o generated/ 647 | done 648 | echo "Generated headers:" 649 | ls -la generated/ 650 | 651 | - name: Build with optimizations 652 | run: | 653 | mkdir build-bench 654 | cd build-bench 655 | cmake -DCMAKE_BUILD_TYPE=Release \ 656 | -DCMAKE_CXX_FLAGS="-O3 -march=native" \ 657 | -DSKIP_SCHEMA_GENERATION=ON \ 658 | .. 659 | cmake --build . --config Release -j$(nproc) 660 | 661 | - name: Run benchmarks 662 | run: | 663 | cd build-bench/bin 664 | echo "Running performance benchmarks..." 665 | time ./demo 666 | 667 | echo "" 668 | echo "Memory usage test..." 669 | /usr/bin/time -v ./test_compaction 2>&1 | grep -E "Maximum resident|User time|System time" 670 | 671 | # ============================================================================ 672 | # Summary 673 | # ============================================================================ 674 | test-summary: 675 | name: Test Summary 676 | needs: [test-macos, test-linux, test-windows-msvc, test-android, test-ios] 677 | runs-on: ubuntu-latest 678 | if: always() 679 | 680 | steps: 681 | - name: Summary 682 | run: | 683 | echo "======================================" 684 | echo "XOffsetDatastructure2 CI Summary" 685 | echo "======================================" 686 | echo "" 687 | echo "Platform Test Results:" 688 | echo " - macOS: ${{ needs.test-macos.result }}" 689 | echo " - Linux: ${{ needs.test-linux.result }}" 690 | echo " - Windows: ${{ needs.test-windows-msvc.result }}" 691 | echo " - Android: ${{ needs.test-android.result }}" 692 | echo " - iOS: ${{ needs.test-ios.result }}" 693 | echo "" 694 | 695 | if [ "${{ needs.test-macos.result }}" == "success" ] && \ 696 | [ "${{ needs.test-linux.result }}" == "success" ] && \ 697 | [ "${{ needs.test-windows-msvc.result }}" == "success" ] && \ 698 | [ "${{ needs.test-android.result }}" == "success" ] && \ 699 | [ "${{ needs.test-ios.result }}" == "success" ]; then 700 | echo "✅ ALL PLATFORMS PASSED!" 701 | exit 0 702 | else 703 | echo "❌ SOME PLATFORMS FAILED" 704 | exit 1 705 | fi 706 | -------------------------------------------------------------------------------- /tools/xds_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | XOffsetDatastructure2 Schema Generator 4 | Generates C++ types from YAML schema definitions 5 | """ 6 | 7 | import yaml 8 | import sys 9 | import os 10 | from pathlib import Path 11 | from typing import List, Dict, Any, Tuple 12 | from dataclasses import dataclass 13 | 14 | @dataclass 15 | class Field: 16 | name: str 17 | type: str 18 | default: Any = None 19 | description: str = "" 20 | 21 | @dataclass 22 | class StructDef: 23 | name: str 24 | fields: List[Field] 25 | kind: str = "struct" 26 | 27 | class TypeAnalyzer: 28 | """Analyze types and generate proper C++ representations""" 29 | 30 | BASIC_TYPES = {'int', 'float', 'double', 'bool', 'char', 'long long', 'int32_t', 'int64_t', 'uint32_t', 'uint64_t'} 31 | XOFFSET_TYPES = {'XString', 'XVector', 'XSet', 'XMap'} 32 | 33 | @staticmethod 34 | def is_basic_type(type_str: str) -> bool: 35 | """Check if type is a basic POD type""" 36 | return type_str in TypeAnalyzer.BASIC_TYPES 37 | 38 | @staticmethod 39 | def is_xoffset_container(type_str: str) -> bool: 40 | """Check if type is XOffsetDatastructure container""" 41 | for xtype in TypeAnalyzer.XOFFSET_TYPES: 42 | if type_str.startswith(xtype): 43 | return True 44 | return False 45 | 46 | @staticmethod 47 | def needs_allocator(type_str: str, struct_names: set = None) -> bool: 48 | """Check if type needs allocator in constructor""" 49 | if TypeAnalyzer.is_xoffset_container(type_str): 50 | return True 51 | # Custom struct types also need allocator 52 | if struct_names and type_str in struct_names: 53 | return True 54 | return False 55 | 56 | @staticmethod 57 | def get_reflection_type(type_str: str, struct_names: set) -> str: 58 | """Convert runtime type to reflection hint type""" 59 | # Basic types stay the same, but use explicit int32_t etc 60 | if type_str == 'int': 61 | return 'int32_t' 62 | elif type_str == 'float': 63 | return 'float' 64 | elif type_str == 'double': 65 | return 'double' 66 | elif type_str == 'bool': 67 | return 'bool' 68 | 69 | # User-defined struct types get ReflectionHint suffix 70 | if type_str in struct_names: 71 | return f"{type_str}ReflectionHint" 72 | 73 | # Handle XVector, XMap, etc. 74 | if type_str.startswith('XVector<'): 75 | inner = type_str[8:-1] # Extract T from XVector 76 | inner_reflection = TypeAnalyzer.get_reflection_type(inner, struct_names) 77 | return f"XVector<{inner_reflection}>" 78 | 79 | if type_str.startswith('XMap<'): 80 | inner = type_str[5:-1] # Extract K,V from XMap 81 | parts = TypeAnalyzer.split_template_args(inner) 82 | if len(parts) == 2: 83 | key_reflection = TypeAnalyzer.get_reflection_type(parts[0], struct_names) 84 | val_reflection = TypeAnalyzer.get_reflection_type(parts[1], struct_names) 85 | return f"XMap<{key_reflection}, {val_reflection}>" 86 | 87 | if type_str.startswith('XSet<'): 88 | inner = type_str[5:-1] # Extract T from XSet 89 | inner_reflection = TypeAnalyzer.get_reflection_type(inner, struct_names) 90 | return f"XSet<{inner_reflection}>" 91 | 92 | # XString stays the same 93 | if type_str == 'XString': 94 | return 'XString' 95 | 96 | return type_str 97 | 98 | @staticmethod 99 | def split_template_args(args_str: str) -> List[str]: 100 | """Split template arguments handling nested templates""" 101 | args = [] 102 | current = "" 103 | depth = 0 104 | 105 | for char in args_str: 106 | if char == '<': 107 | depth += 1 108 | current += char 109 | elif char == '>': 110 | depth -= 1 111 | current += char 112 | elif char == ',' and depth == 0: 113 | args.append(current.strip()) 114 | current = "" 115 | else: 116 | current += char 117 | 118 | if current.strip(): 119 | args.append(current.strip()) 120 | 121 | return args 122 | 123 | class TypeSignatureCalculator: 124 | """Calculate expected type signature strings""" 125 | 126 | # Constants 127 | REFLECTION_SUFFIX = 'ReflectionHint' 128 | CONTAINER_SIZE = 32 # Size of XVector, XSet, XMap, XString 129 | BASIC_ALIGNMENT = 8 # Default alignment for structs and containers 130 | DEFAULT_POINTER_SIZE = 8 # Default size for unknown types 131 | 132 | # Type sizes and alignments (64-bit platform) 133 | TYPE_INFO = { 134 | 'int': (4, 4, 'i32'), 135 | 'int32_t': (4, 4, 'i32'), 136 | 'int64_t': (8, 8, 'i64'), 137 | 'uint32_t': (4, 4, 'u32'), 138 | 'uint64_t': (8, 8, 'u64'), 139 | 'float': (4, 4, 'f32'), 140 | 'double': (8, 8, 'f64'), 141 | 'bool': (1, 1, 'bool'), 142 | 'char': (1, 1, 'char'), 143 | 'long long': (8, 8, 'i64'), 144 | 'XString': (32, 8, 'string'), 145 | } 146 | 147 | @staticmethod 148 | def align_to(size: int, alignment: int) -> int: 149 | """Align size to the specified alignment boundary""" 150 | return (size + alignment - 1) & ~(alignment - 1) 151 | 152 | @staticmethod 153 | def get_type_signature(type_str: str, struct_map: Dict[str, 'StructDef']) -> str: 154 | """Generate type signature for a type""" 155 | # Strip ReflectionHint suffix to get original type name 156 | original_type = type_str 157 | if type_str.endswith(TypeSignatureCalculator.REFLECTION_SUFFIX): 158 | original_type = type_str[:-len(TypeSignatureCalculator.REFLECTION_SUFFIX)] 159 | 160 | # Handle basic types 161 | if type_str in TypeSignatureCalculator.TYPE_INFO: 162 | size, align, sig = TypeSignatureCalculator.TYPE_INFO[type_str] 163 | return f"{sig}[s:{size},a:{align}]" 164 | 165 | # Handle XVector 166 | if type_str.startswith('XVector<'): 167 | inner = type_str[8:-1] 168 | inner_sig = TypeSignatureCalculator.get_type_signature(inner, struct_map) 169 | return f"vector[s:{TypeSignatureCalculator.CONTAINER_SIZE},a:{TypeSignatureCalculator.BASIC_ALIGNMENT}]<{inner_sig}>" 170 | 171 | # Handle XSet 172 | if type_str.startswith('XSet<'): 173 | inner = type_str[5:-1] 174 | inner_sig = TypeSignatureCalculator.get_type_signature(inner, struct_map) 175 | return f"set[s:{TypeSignatureCalculator.CONTAINER_SIZE},a:{TypeSignatureCalculator.BASIC_ALIGNMENT}]<{inner_sig}>" 176 | 177 | # Handle XMap 178 | if type_str.startswith('XMap<'): 179 | inner = type_str[5:-1] 180 | parts = TypeAnalyzer.split_template_args(inner) 181 | if len(parts) == 2: 182 | key_sig = TypeSignatureCalculator.get_type_signature(parts[0], struct_map) 183 | val_sig = TypeSignatureCalculator.get_type_signature(parts[1], struct_map) 184 | return f"map[s:{TypeSignatureCalculator.CONTAINER_SIZE},a:{TypeSignatureCalculator.BASIC_ALIGNMENT}]<{key_sig},{val_sig}>" 185 | 186 | # Handle custom struct types (check original name without ReflectionHint) 187 | if original_type in struct_map: 188 | return TypeSignatureCalculator.get_struct_signature(struct_map[original_type], struct_map) 189 | 190 | return f"unknown[{type_str}]" 191 | 192 | @staticmethod 193 | def calculate_field_offset(fields: List[Field], index: int, struct_map: Dict[str, 'StructDef']) -> int: 194 | """Calculate field offset with proper alignment""" 195 | if index == 0: 196 | return 0 197 | 198 | offset = 0 199 | for i in range(index): 200 | field = fields[i] 201 | field_size = TypeSignatureCalculator.get_type_size(field.type, struct_map) 202 | field_align = TypeSignatureCalculator.get_type_align(field.type, struct_map) 203 | 204 | # Align current offset 205 | offset = TypeSignatureCalculator.align_to(offset, field_align) 206 | offset += field_size 207 | 208 | # Align for current field 209 | current_align = TypeSignatureCalculator.get_type_align(fields[index].type, struct_map) 210 | offset = TypeSignatureCalculator.align_to(offset, current_align) 211 | 212 | return offset 213 | 214 | @staticmethod 215 | def get_type_size(type_str: str, struct_map: Dict[str, 'StructDef']) -> int: 216 | """Get size of a type""" 217 | if type_str in TypeSignatureCalculator.TYPE_INFO: 218 | return TypeSignatureCalculator.TYPE_INFO[type_str][0] 219 | if type_str.startswith(('XVector<', 'XSet<', 'XMap<')): 220 | return TypeSignatureCalculator.CONTAINER_SIZE 221 | if type_str == 'XString': 222 | return TypeSignatureCalculator.CONTAINER_SIZE 223 | if type_str in struct_map: 224 | return TypeSignatureCalculator.calculate_struct_size(struct_map[type_str], struct_map) 225 | return TypeSignatureCalculator.DEFAULT_POINTER_SIZE 226 | 227 | @staticmethod 228 | def get_type_align(type_str: str, struct_map: Dict[str, 'StructDef']) -> int: 229 | """Get alignment of a type""" 230 | if type_str in TypeSignatureCalculator.TYPE_INFO: 231 | return TypeSignatureCalculator.TYPE_INFO[type_str][1] 232 | if type_str.startswith(('XVector<', 'XSet<', 'XMap<')): 233 | return TypeSignatureCalculator.BASIC_ALIGNMENT 234 | if type_str == 'XString': 235 | return TypeSignatureCalculator.BASIC_ALIGNMENT 236 | if type_str in struct_map: 237 | return TypeSignatureCalculator.BASIC_ALIGNMENT 238 | return TypeSignatureCalculator.BASIC_ALIGNMENT 239 | 240 | @staticmethod 241 | def calculate_struct_size(struct: 'StructDef', struct_map: Dict[str, 'StructDef']) -> int: 242 | """Calculate total struct size with alignment""" 243 | if not struct.fields: 244 | return TypeSignatureCalculator.BASIC_ALIGNMENT # Minimum size for alignment 245 | 246 | size = 0 247 | max_align = TypeSignatureCalculator.BASIC_ALIGNMENT 248 | 249 | for field in struct.fields: 250 | field_size = TypeSignatureCalculator.get_type_size(field.type, struct_map) 251 | field_align = TypeSignatureCalculator.get_type_align(field.type, struct_map) 252 | max_align = max(max_align, field_align) 253 | 254 | # Align current position 255 | size = TypeSignatureCalculator.align_to(size, field_align) 256 | size += field_size 257 | 258 | # Final padding to struct alignment 259 | size = TypeSignatureCalculator.align_to(size, max_align) 260 | return size 261 | 262 | @staticmethod 263 | def get_struct_signature(struct: 'StructDef', struct_map: Dict[str, 'StructDef']) -> str: 264 | """Generate complete struct signature""" 265 | size = TypeSignatureCalculator.calculate_struct_size(struct, struct_map) 266 | align = TypeSignatureCalculator.BASIC_ALIGNMENT 267 | 268 | # Create struct_names set once instead of recreating it in the loop 269 | struct_names = {s.name for s in struct_map.values()} 270 | 271 | field_sigs = [] 272 | for i, field in enumerate(struct.fields): 273 | offset = TypeSignatureCalculator.calculate_field_offset(struct.fields, i, struct_map) 274 | # Use reflection types for signature 275 | field_type = TypeAnalyzer.get_reflection_type(field.type, struct_names) 276 | field_sig = TypeSignatureCalculator.get_type_signature(field_type, struct_map) 277 | # Include field name in signature: @offset[name]:type 278 | field_sigs.append(f"@{offset}[{field.name}]:{field_sig}") 279 | 280 | fields_str = ",".join(field_sigs) 281 | return f"struct[s:{size},a:{align}]{{{fields_str}}}" 282 | 283 | class CodeGenerator: 284 | """Generate C++ code from schema""" 285 | 286 | def __init__(self, structs: List[StructDef], config: Dict[str, Any]): 287 | self.structs = structs 288 | self.config = config 289 | self.struct_names = {s.name for s in structs} 290 | self.struct_map = {s.name: s for s in structs} 291 | 292 | def _get_field_param(self, field: Field) -> str: 293 | """Get constructor parameter for a field (returns None if field doesn't need parameter)""" 294 | if field.type == 'XString': 295 | return f"const char* {field.name}_val" 296 | elif TypeAnalyzer.is_xoffset_container(field.type): 297 | return None # Containers don't need parameters in full constructor 298 | elif field.type in self.struct_names: 299 | return None # Nested structs don't need parameters in full constructor 300 | else: 301 | return f"{field.type} {field.name}_val" 302 | 303 | def _get_field_initializer(self, field: Field) -> str: 304 | """Get initializer expression for a field""" 305 | if field.type == 'XString': 306 | return f"{field.name}({field.name}_val, allocator)" 307 | elif TypeAnalyzer.is_xoffset_container(field.type): 308 | return f"{field.name}(allocator)" 309 | elif field.type in self.struct_names: 310 | return f"{field.name}(allocator)" 311 | else: 312 | return f"{field.name}({field.name}_val)" 313 | 314 | def _format_default_value(self, field: Field) -> str: 315 | """Format field default value as C++ initializer syntax""" 316 | if field.default is None: 317 | return "" 318 | 319 | if isinstance(field.default, bool): 320 | return "{true}" if field.default else "{false}" 321 | 322 | if isinstance(field.default, str): 323 | if field.type == 'char' and len(field.default) <= 3: 324 | if not field.default.startswith("'"): 325 | return f"{{'{field.default}'}}" 326 | else: 327 | return f"{{{field.default}}}" 328 | 329 | if isinstance(field.default, (int, float)): 330 | if field.type == 'float': 331 | return f"{{{field.default}f}}" 332 | else: 333 | return f"{{{field.default}}}" 334 | 335 | return "" 336 | 337 | def generate_runtime_type(self, struct: StructDef) -> str: 338 | """Generate runtime type with allocator constructor""" 339 | lines = [] 340 | 341 | # Generate struct header with alignment 342 | lines.append(f"struct alignas(XTypeSignature::BASIC_ALIGNMENT) {struct.name} {{") 343 | 344 | # ==================================================================== 345 | # Constructor 1: Default constructor (allocator only) 346 | # ==================================================================== 347 | allocator_fields = [f.name for f in struct.fields if TypeAnalyzer.needs_allocator(f.type, self.struct_names)] 348 | 349 | lines.append("\t// Default constructor") 350 | if allocator_fields: 351 | lines.append("\ttemplate ") 352 | ctor_init = ", ".join([f"{name}(allocator)" for name in allocator_fields]) 353 | lines.append(f"\t{struct.name}(Allocator allocator) : {ctor_init} {{}}") 354 | else: 355 | lines.append("\ttemplate ") 356 | lines.append(f"\t{struct.name}(Allocator allocator) {{}}") 357 | 358 | lines.append("") 359 | 360 | # ==================================================================== 361 | # Constructor 2: Full constructor for emplace_back (if there are fields) 362 | # ==================================================================== 363 | # Check if we have any non-container fields 364 | non_container_fields = [ 365 | f for f in struct.fields 366 | if not TypeAnalyzer.is_xoffset_container(f.type) and f.type not in self.struct_names 367 | ] 368 | 369 | if non_container_fields: 370 | lines.append("\t// Full constructor for emplace_back") 371 | lines.append("\ttemplate ") 372 | 373 | # Build parameter list using helper method 374 | params = ["Allocator allocator"] 375 | for field in struct.fields: 376 | param = self._get_field_param(field) 377 | if param: 378 | params.append(param) 379 | 380 | # Constructor signature 381 | lines.append(f"\t{struct.name}({', '.join(params)})") 382 | 383 | # Build initializer list using helper method 384 | init_list = [] 385 | for field in struct.fields: 386 | init_list.append(self._get_field_initializer(field)) 387 | 388 | if init_list: 389 | lines.append(f"\t\t: {init_list[0]}") 390 | for init in init_list[1:]: 391 | lines.append(f"\t\t, {init}") 392 | lines.append("\t{}") 393 | lines.append("") 394 | 395 | # Generate fields with formatted default values 396 | for field in struct.fields: 397 | default_val = self._format_default_value(field) 398 | lines.append(f"\t{field.type} {field.name}{default_val};") 399 | 400 | lines.append("};") 401 | lines.append("") 402 | 403 | return "\n".join(lines) 404 | 405 | def generate_reflection_hint_type(self, struct: StructDef) -> str: 406 | """Generate reflection hint type (aggregate) for boost::pfr""" 407 | lines = [] 408 | 409 | # Generate struct header with alignment 410 | lines.append(f"struct alignas(XTypeSignature::BASIC_ALIGNMENT) {struct.name}ReflectionHint {{") 411 | 412 | # Generate fields with reflection types 413 | for field in struct.fields: 414 | reflection_type = TypeAnalyzer.get_reflection_type(field.type, self.struct_names) 415 | lines.append(f"\t{reflection_type} {field.name};") 416 | 417 | # Inject field names for enhanced reflection (field reordering detection) 418 | lines.append("") 419 | lines.append("\t// Field names metadata for XTypeSignature") 420 | lines.append("\tstatic constexpr std::string_view _field_names[] = {") 421 | for field in struct.fields: 422 | lines.append(f'\t\t"{field.name}",') 423 | lines.append("\t};") 424 | 425 | lines.append("};") 426 | lines.append("") 427 | 428 | return "\n".join(lines) 429 | 430 | def generate_type_signature_comment(self, struct: StructDef) -> str: 431 | """Generate expected type signature as a comment for reference""" 432 | lines = [] 433 | lines.append(f"// Expected type signature for {struct.name}ReflectionHint:") 434 | lines.append(f"// This signature is computed at compile-time by XTypeSignature::get_XTypeSignature<>") 435 | lines.append(f"// Format: struct[s:,a:]{{@:,...}}") 436 | lines.append(f"//") 437 | lines.append(f"// You can verify the actual signature by uncommenting this line:") 438 | lines.append(f"// #pragma message(XTypeSignature::get_XTypeSignature<{struct.name}ReflectionHint>().value)") 439 | lines.append("") 440 | return "\n".join(lines) 441 | 442 | def generate_validation(self, struct: StructDef) -> str: 443 | """Generate compile-time validation code""" 444 | lines = [] 445 | 446 | # Add comment explaining the validation 447 | lines.append(f"// Compile-time validation for {struct.name}") 448 | lines.append("") 449 | 450 | # Type safety validation (NEW) - Disabled on MSVC due to Boost.PFR issues 451 | lines.append("// 1. Type Safety Check") 452 | lines.append("// Type safety verification uses Boost.PFR for recursive member checking.") 453 | lines.append(f"static_assert(XOffsetDatastructure2::is_xbuffer_safe<{struct.name}ReflectionHint>::value,") 454 | lines.append(f' "Type safety error for {struct.name}ReflectionHint");') 455 | lines.append("") 456 | 457 | # Size and alignment validation (always enabled) 458 | lines.append("// 2. Size and Alignment Check") 459 | lines.append(f"static_assert(sizeof({struct.name}) == sizeof({struct.name}ReflectionHint),") 460 | lines.append(f' "Size mismatch: {struct.name} runtime and reflection types must have identical size");') 461 | lines.append(f"static_assert(alignof({struct.name}) == alignof({struct.name}ReflectionHint),") 462 | lines.append(f' "Alignment mismatch: {struct.name} runtime and reflection types must have identical alignment");') 463 | lines.append("") 464 | 465 | # Type signature validation (unified implementation) 466 | lines.append("// 3. Type Signature Check") 467 | lines.append("// Type signature verification uses unified Boost.PFR implementation") 468 | lines.append("// All compilers use lightweight tuple_element and tuple_size_v APIs") 469 | 470 | # Calculate expected type signature 471 | expected_sig = TypeSignatureCalculator.get_struct_signature(struct, self.struct_map) 472 | 473 | # Split long signatures for readability (>100 chars) 474 | if len(expected_sig) > 100: 475 | # Format multi-line signature 476 | lines.append(f"static_assert(XTypeSignature::get_XTypeSignature<{struct.name}ReflectionHint>() ==") 477 | 478 | # Simple chunking to avoid regex issues 479 | chunk_size = 80 480 | for i in range(0, len(expected_sig), chunk_size): 481 | chunk = expected_sig[i:i+chunk_size] 482 | lines.append(f' "{chunk}"') 483 | 484 | lines.append(f' , "Type signature mismatch for {struct.name}ReflectionHint");') 485 | else: 486 | # Single line signature 487 | lines.append(f"static_assert(XTypeSignature::get_XTypeSignature<{struct.name}ReflectionHint>() == \"{expected_sig}\",") 488 | lines.append(f' "Type signature mismatch for {struct.name}ReflectionHint");') 489 | 490 | lines.append("") 491 | 492 | return "\n".join(lines) 493 | 494 | def generate_header(self, output_name: str) -> str: 495 | """Generate complete C++ header file""" 496 | lines = [] 497 | 498 | # Header guard 499 | guard_name = f"GENERATED_{output_name.upper().replace('.', '_')}_" 500 | lines.append(f"#ifndef {guard_name}") 501 | lines.append(f"#define {guard_name}") 502 | lines.append("") 503 | 504 | # Includes 505 | lines.append('#include "xoffsetdatastructure2.hpp"') 506 | lines.append("") 507 | lines.append("using namespace XOffsetDatastructure2;") 508 | lines.append("") 509 | 510 | # Add comment about reflection hint types 511 | lines.append("// ============================================================================") 512 | lines.append("// Runtime Types - Used for actual data storage") 513 | lines.append("// ============================================================================") 514 | lines.append("") 515 | 516 | # Generate runtime types 517 | for struct in self.structs: 518 | lines.append(self.generate_runtime_type(struct)) 519 | 520 | # Add comment 521 | lines.append("// ============================================================================") 522 | lines.append("// Reflection Hint Types - Used for compile-time type analysis") 523 | lines.append("// ============================================================================") 524 | lines.append("// These are aggregate versions of runtime types that satisfy boost::pfr") 525 | lines.append("// requirements for reflection. They must have identical memory layout") 526 | lines.append("// to their runtime counterparts.") 527 | lines.append("// ============================================================================") 528 | lines.append("") 529 | 530 | # Generate reflection hint types 531 | for struct in self.structs: 532 | lines.append(self.generate_reflection_hint_type(struct)) 533 | 534 | # MSVC field registry is no longer needed - using unified Boost.PFR approach 535 | # The lightweight tuple_element and tuple_size_v APIs work on all platforms 536 | 537 | # Add validation section 538 | lines.append("// ============================================================================") 539 | lines.append("// Compile-Time Validation") 540 | lines.append("// ============================================================================") 541 | lines.append("") 542 | 543 | for struct in self.structs: 544 | lines.append(self.generate_validation(struct)) 545 | 546 | # Close header guard 547 | lines.append(f"#endif // {guard_name}") 548 | lines.append("") 549 | 550 | return "\n".join(lines) 551 | 552 | def parse_yaml_schema(yaml_file: Path) -> Tuple[List[StructDef], Dict[str, Any]]: 553 | """Parse YAML schema file""" 554 | with open(yaml_file, 'r', encoding='utf-8') as f: 555 | data = yaml.safe_load(f) 556 | 557 | structs = [] 558 | 559 | for type_def in data.get('types', []): 560 | fields = [] 561 | for field_def in type_def.get('fields', []): 562 | field = Field( 563 | name=field_def['name'], 564 | type=field_def['type'], 565 | default=field_def.get('default'), 566 | description=field_def.get('description', '') 567 | ) 568 | fields.append(field) 569 | 570 | struct = StructDef( 571 | name=type_def['name'], 572 | fields=fields, 573 | kind=type_def.get('type', type_def.get('kind', 'struct')) # Support both 'type' and 'kind' 574 | ) 575 | structs.append(struct) 576 | 577 | config = data.get('codegen', {}) 578 | return structs, config 579 | 580 | def main(): 581 | if len(sys.argv) < 2: 582 | print("Usage: xds_generator.py [-o output_dir]") 583 | print("Example: xds_generator.py test_types.xds.yaml -o generated/") 584 | sys.exit(1) 585 | 586 | # Parse arguments 587 | yaml_file = Path(sys.argv[1]) 588 | output_dir = Path("generated") 589 | 590 | if len(sys.argv) >= 4 and sys.argv[2] == '-o': 591 | output_dir = Path(sys.argv[3]) 592 | 593 | if not yaml_file.exists(): 594 | print(f"Error: Schema file not found: {yaml_file}") 595 | sys.exit(1) 596 | 597 | # Create output directory 598 | output_dir.mkdir(parents=True, exist_ok=True) 599 | 600 | # Parse schema 601 | print(f"Parsing schema: {yaml_file}") 602 | structs, config = parse_yaml_schema(yaml_file) 603 | print(f" Found {len(structs)} type(s): {', '.join(s.name for s in structs)}") 604 | 605 | # Generate code 606 | generator = CodeGenerator(structs, config) 607 | output_name = yaml_file.stem.replace('.xds', '') + '.hpp' 608 | output_path = output_dir / output_name 609 | 610 | print(f"Generating code: {output_path}") 611 | code = generator.generate_header(output_name) 612 | 613 | # Write output 614 | with open(output_path, 'w', encoding='utf-8') as f: 615 | f.write(code) 616 | 617 | print(f"[OK] Successfully generated {output_path}") 618 | print(f" Runtime types: {', '.join(s.name for s in structs)}") 619 | print(f" Reflection types: {', '.join(s.name + 'ReflectionHint' for s in structs)}") 620 | 621 | if __name__ == '__main__': 622 | main() --------------------------------------------------------------------------------