├── .clang-format ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── SCsub ├── cmake └── CMakeLists.txt ├── config.py ├── core ├── bit_array.cpp ├── bit_array.h ├── bitcasts.h ├── core.cpp ├── core.h ├── data_buffer.cpp ├── data_buffer.h ├── ensure.cpp ├── ensure.h ├── event_processor.h ├── fp16.h ├── insertion_sort.h ├── json.hpp ├── net_math.h ├── net_utilities.cpp ├── net_utilities.h ├── network_codec.cpp ├── network_codec.h ├── network_interface.cpp ├── network_interface.h ├── network_interface_define.h ├── object_data.cpp ├── object_data.h ├── object_data_storage.cpp ├── object_data_storage.h ├── peer_data.cpp ├── peer_data.h ├── peer_networked_controller.cpp ├── peer_networked_controller.h ├── processor.h ├── quick_sort.h ├── scene_synchronizer_debugger.cpp ├── scene_synchronizer_debugger.h ├── scene_synchronizer_debugger_json_storage.h ├── scheduled_procedure.h ├── snapshot.cpp ├── snapshot.h ├── var_data.cpp └── var_data.h ├── debugger_ui ├── cpplize_debugger.py ├── debugger.py └── readme.md ├── doc_classes ├── DataBuffer.xml ├── GdSceneSynchronizer.xml └── InputNetworkEncoder.xml ├── godot4 ├── gd_data_buffer.cpp ├── gd_data_buffer.h ├── gd_network_interface.cpp ├── gd_network_interface.h ├── gd_scene_synchronizer.cpp └── gd_scene_synchronizer.h ├── icons └── NetworkedController.svg ├── register_types.cpp ├── register_types.h ├── scene_synchronizer.cpp ├── scene_synchronizer.h └── tests ├── local_network.cpp ├── local_network.h ├── local_scene.cpp ├── local_scene.h ├── test_AI_simulation.cpp ├── test_AI_simulation.h ├── test_data_buffer.cpp ├── test_data_buffer.h ├── test_doll_simulation.cpp ├── test_doll_simulation.h ├── test_math_lib.cpp ├── test_math_lib.h ├── test_netsync_bit_array.h ├── test_processor.cpp ├── test_processor.h ├── test_scene_synchronizer.cpp ├── test_scene_synchronizer.h ├── test_simulation.cpp ├── test_simulation.h ├── test_switch_controller.cpp ├── test_switch_controller.h ├── tests.cpp └── tests.h /.clang-format: -------------------------------------------------------------------------------- 1 | # Commented out parameters are those with the same value as base LLVM style. 2 | # We can uncomment them if we want to change their value, or enforce the 3 | # chosen value in case the base style changes (last sync: Clang 13.0). 4 | --- 5 | ### General config, applies to all languages ### 6 | BasedOnStyle: LLVM 7 | AccessModifierOffset: -4 8 | AlignAfterOpenBracket: DontAlign 9 | # AlignArrayOfStructures: None 10 | # AlignConsecutiveMacros: None 11 | # AlignConsecutiveAssignments: None 12 | # AlignConsecutiveBitFields: None 13 | # AlignConsecutiveDeclarations: None 14 | # AlignEscapedNewlines: Right 15 | AlignOperands: DontAlign 16 | AlignTrailingComments: false 17 | # AllowAllArgumentsOnNextLine: true 18 | # AllowAllConstructorInitializersOnNextLine: true 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | # AllowShortEnumsOnASingleLine: true 21 | # AllowShortBlocksOnASingleLine: Never 22 | # AllowShortCaseLabelsOnASingleLine: false 23 | AllowShortFunctionsOnASingleLine: Inline 24 | # AllowShortLambdasOnASingleLine: All 25 | # AllowShortIfStatementsOnASingleLine: Never 26 | # AllowShortLoopsOnASingleLine: false 27 | # AlwaysBreakAfterDefinitionReturnType: None 28 | # AlwaysBreakAfterReturnType: None 29 | # AlwaysBreakBeforeMultilineStrings: false 30 | # AlwaysBreakTemplateDeclarations: MultiLine 31 | # AttributeMacros: 32 | # - __capability 33 | # BinPackArguments: true 34 | # BinPackParameters: true 35 | # BraceWrapping: 36 | # AfterCaseLabel: false 37 | # AfterClass: false 38 | # AfterControlStatement: Never 39 | # AfterEnum: false 40 | # AfterFunction: false 41 | # AfterNamespace: false 42 | # AfterObjCDeclaration: false 43 | # AfterStruct: false 44 | # AfterUnion: false 45 | # AfterExternBlock: false 46 | # BeforeCatch: false 47 | # BeforeElse: false 48 | # BeforeLambdaBody: false 49 | # BeforeWhile: false 50 | # IndentBraces: false 51 | # SplitEmptyFunction: true 52 | # SplitEmptyRecord: true 53 | # SplitEmptyNamespace: true 54 | # BreakBeforeBinaryOperators: None 55 | # BreakBeforeConceptDeclarations: true 56 | # BreakBeforeBraces: Attach 57 | # BreakBeforeInheritanceComma: false 58 | # BreakInheritanceList: BeforeColon 59 | # BreakBeforeTernaryOperators: true 60 | # BreakConstructorInitializersBeforeComma: false 61 | BreakConstructorInitializers: AfterColon 62 | # BreakStringLiterals: true 63 | ColumnLimit: 0 64 | # CommentPragmas: '^ IWYU pragma:' 65 | # CompactNamespaces: false 66 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 67 | ConstructorInitializerIndentWidth: 8 68 | ContinuationIndentWidth: 8 69 | Cpp11BracedListStyle: false 70 | # DeriveLineEnding: true 71 | # DerivePointerAlignment: false 72 | # DisableFormat: false 73 | # EmptyLineAfterAccessModifier: Never 74 | # EmptyLineBeforeAccessModifier: LogicalBlock 75 | # ExperimentalAutoDetectBinPacking: false 76 | # FixNamespaceComments: true 77 | # ForEachMacros: 78 | # - foreach 79 | # - Q_FOREACH 80 | # - BOOST_FOREACH 81 | # IfMacros: 82 | # - KJ_IF_MAYBE 83 | # IncludeBlocks: Preserve 84 | IncludeCategories: 85 | - Regex: '".*"' 86 | Priority: 1 87 | - Regex: '^<.*\.h>' 88 | Priority: 2 89 | - Regex: '^<.*' 90 | Priority: 3 91 | # IncludeIsMainRegex: '(Test)?$' 92 | # IncludeIsMainSourceRegex: '' 93 | # IndentAccessModifiers: false 94 | IndentCaseLabels: true 95 | # IndentCaseBlocks: false 96 | # IndentGotoLabels: true 97 | # IndentPPDirectives: None 98 | # IndentExternBlock: AfterExternBlock 99 | # IndentRequires: false 100 | IndentWidth: 4 101 | # IndentWrappedFunctionNames: false 102 | # InsertTrailingCommas: None 103 | # JavaScriptQuotes: Leave 104 | # JavaScriptWrapImports: true 105 | KeepEmptyLinesAtTheStartOfBlocks: false 106 | # LambdaBodyIndentation: Signature 107 | # MacroBlockBegin: '' 108 | # MacroBlockEnd: '' 109 | # MaxEmptyLinesToKeep: 1 110 | # NamespaceIndentation: None 111 | # PenaltyBreakAssignment: 2 112 | # PenaltyBreakBeforeFirstCallParameter: 19 113 | # PenaltyBreakComment: 300 114 | # PenaltyBreakFirstLessLess: 120 115 | # PenaltyBreakString: 1000 116 | # PenaltyBreakTemplateDeclaration: 10 117 | # PenaltyExcessCharacter: 1000000 118 | # PenaltyReturnTypeOnItsOwnLine: 60 119 | # PenaltyIndentedWhitespace: 0 120 | # PointerAlignment: Right 121 | # PPIndentWidth: -1 122 | # ReferenceAlignment: Pointer 123 | # ReflowComments: true 124 | # ShortNamespaceLines: 1 125 | # SortIncludes: CaseSensitive 126 | # SortJavaStaticImport: Before 127 | # SortUsingDeclarations: true 128 | # SpaceAfterCStyleCast: false 129 | # SpaceAfterLogicalNot: false 130 | # SpaceAfterTemplateKeyword: true 131 | # SpaceBeforeAssignmentOperators: true 132 | # SpaceBeforeCaseColon: false 133 | # SpaceBeforeCpp11BracedList: false 134 | # SpaceBeforeCtorInitializerColon: true 135 | # SpaceBeforeInheritanceColon: true 136 | # SpaceBeforeParens: ControlStatements 137 | # SpaceAroundPointerQualifiers: Default 138 | # SpaceBeforeRangeBasedForLoopColon: true 139 | # SpaceInEmptyParentheses: false 140 | # SpacesBeforeTrailingComments: 1 141 | # SpaceInEmptyBlock: false 142 | # SpaceInEmptyParentheses: false 143 | # SpacesBeforeTrailingComments: 1 144 | # SpacesInAngles: Never 145 | # SpacesInContainerLiterals: true 146 | # SpacesInConditionalStatement: false 147 | # SpacesInContainerLiterals: true 148 | # SpacesInCStyleCastParentheses: false 149 | ## Godot TODO: We'll want to use a min of 1, but we need to see how to fix 150 | ## our comment capitalization at the same time. 151 | SpacesInLineCommentPrefix: 152 | Minimum: 0 153 | Maximum: -1 154 | # SpacesInParentheses: false 155 | # SpacesInSquareBrackets: false 156 | # SpaceBeforeSquareBrackets: false 157 | # BitFieldColonSpacing: Both 158 | # StatementAttributeLikeMacros: 159 | # - Q_EMIT 160 | # StatementMacros: 161 | # - Q_UNUSED 162 | # - QT_REQUIRE_VERSION 163 | TabWidth: 4 164 | # UseCRLF: false 165 | UseTab: Always 166 | # WhitespaceSensitiveMacros: 167 | # - STRINGIZE 168 | # - PP_STRINGIZE 169 | # - BOOST_PP_STRINGIZE 170 | # - NS_SWIFT_NAME 171 | # - CF_SWIFT_NAME 172 | --- 173 | ### C++ specific config ### 174 | Language: Cpp 175 | Standard: c++14 176 | --- 177 | ### ObjC specific config ### 178 | Language: ObjC 179 | # ObjCBinPackProtocolList: Auto 180 | ObjCBlockIndentWidth: 4 181 | # ObjCBreakBeforeNestedBlockParam: true 182 | # ObjCSpaceAfterProperty: false 183 | # ObjCSpaceBeforeProtocolList: true 184 | --- 185 | ### Java specific config ### 186 | Language: Java 187 | # BreakAfterJavaFieldAnnotations: false 188 | JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax'] 189 | ... 190 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__generated__debugger_ui.h 2 | 3 | # Godot-specific ignores 4 | .import/ 5 | .godot/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Mono-specific ignores 10 | .mono/ 11 | data_*/ 12 | 13 | # General c++ generated files 14 | *.lib 15 | *.o 16 | *.obj 17 | *.ox 18 | *.a 19 | *.ax 20 | *.d 21 | *.so 22 | *.os 23 | *.Plo 24 | *.lo 25 | 26 | # for projects that use SCons for building: http://http://www.scons.org/ 27 | .sconf_temp 28 | .sconsign*.dblite 29 | *.pyc 30 | 31 | # vscode 32 | .vscode 33 | *.code-workspace 34 | 35 | # QtCreator 36 | *.cflags 37 | *.config 38 | *.creator 39 | *.creator.user 40 | *.cxxflags 41 | *.files 42 | *.includes 43 | 44 | # CLion 45 | .idea/ 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GodotNetworking 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network Synchronizer 2 | Godot 3.x and 4.0 module to create realtime multiplayer games. It uses Prediction & Rewinding networking model. 3 | 4 | -- 5 | 6 | # Contributors 7 | 8 | 9 | 10 | 11 | 12 | Made with [contrib.rocks](https://contrib.rocks). 13 | -------------------------------------------------------------------------------- /SCsub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from debugger_ui import cpplize_debugger 4 | 5 | cpplize_debugger.create_debugger_header() 6 | 7 | Import("env") 8 | Import("env_modules") 9 | 10 | env_network_synchronizer = env_modules.Clone() 11 | 12 | if "tracy_enable" in env and env["tracy_enable"] == "yes": 13 | env_network_synchronizer.Append(CPPDEFINES=["TRACY_ENABLE"]) 14 | 15 | # TODO ensure debugs are enabled only in debug builds. 16 | env_network_synchronizer.Append(CPPDEFINES=["NS_DEBUG_ENABLED"]) 17 | 18 | env_network_synchronizer.Append(CPPDEFINES=["DISABLE_DEBUG_DATA_BUFFER"]) 19 | 20 | env_network_synchronizer.add_source_files(env.modules_sources, "core/**.cpp") 21 | env_network_synchronizer.add_source_files(env.modules_sources, "godot4/**.cpp") 22 | env_network_synchronizer.add_source_files(env.modules_sources, "*.cpp") 23 | env_network_synchronizer.add_source_files(env.modules_sources, "tests/**.cpp") 24 | -------------------------------------------------------------------------------- /cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(NetworkSynchronizer CXX) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | # When turning this option on, the library will be compiled with debug symbols 9 | option(GENERATE_DEBUG_SYMBOLS "Generate debug symbols" OFF) 10 | 11 | # When turning this option on, the library will be compiled with extra checks and assertions. 12 | option(ENABLE_DEBUG "Enable debug checks" OFF) 13 | 14 | # When turning this option on, the library will be compiled with extra checks and assertions. 15 | option(ENABLE_DEBUG_DATA_BUFFER "Enable debug data buffer." OFF) 16 | 17 | # When turning this option on, you can override the release and debug CPP build flags. 18 | option(OVERRIDE_CXX_FLAGS "Enable this to override the flags used for release and debug. `CMAKE_CXX_FLAGS_DEBUG` `CMAKE_CXX_FLAGS_RELEASE`" OFF) 19 | 20 | # Ability to toggle between the static and DLL versions of the MSVC runtime library 21 | # Windows Store only supports the DLL version 22 | option(USE_STATIC_MSVC_RUNTIME_LIBRARY "Use the static MSVC runtime library" ON) 23 | 24 | # Ability to toggle between the static and DLL versions of the MSVC runtime library 25 | # Windows Store only supports the DLL version 26 | option(BUILD_MSVC_MD "In MSVC build the library using MD." ON) 27 | 28 | # Enables the debugger UI. Allows to debug the data sent and received by any peers to identify bugs. 29 | option(ENABLE_DEBUGGER_UI "Enables the debugger UI." OFF) 30 | 31 | if(ENABLE_DEBUGGER_UI) 32 | # Find Python interpreter used to load the debugger_ui into C++. 33 | find_package(Python3 COMPONENTS Interpreter REQUIRED) 34 | 35 | # Command to execute the Python script 36 | add_custom_command( 37 | OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/../core/__generated__debugger_ui.h 38 | COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../debugger_ui/cpplize_debugger.py ${CMAKE_CURRENT_SOURCE_DIR}/.. 39 | COMMENT "Running Python script to generate the __generated__debugger_ui.h file" 40 | ) 41 | 42 | # Include the generated file as part of the build dependencies 43 | add_custom_target(RunPythonScript ALL 44 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../core/__generated__debugger_ui.h 45 | ) 46 | endif() 47 | 48 | if (MSVC) 49 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise") # Ensure floating point is not fast to make math operations deterministic. 50 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fp:precise") # Ensure floating point is not fast to make math operations deterministic. 51 | 52 | # Disable RTTI 53 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") 54 | 55 | # Set runtime library 56 | if (USE_STATIC_MSVC_RUNTIME_LIBRARY) 57 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 58 | endif() 59 | 60 | # Optionally generate debug symbols 61 | if (GENERATE_DEBUG_SYMBOLS) 62 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") 63 | endif() 64 | 65 | # Set linker flags 66 | if (GENERATE_DEBUG_SYMBOLS) 67 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") 68 | endif() 69 | 70 | if (BUILD_MSVC_MD) 71 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MD") 72 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MD") 73 | endif() 74 | 75 | # Set compiler flags for various configurations 76 | if (NOT OVERRIDE_CXX_FLAGS) 77 | set(CMAKE_CXX_FLAGS_DEBUG "/MDd /Zi /GS /Od /Ob0 /RTC1") 78 | if (BUILD_MSVC_MD) 79 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") 80 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MDd") 81 | endif() 82 | set(CMAKE_CXX_FLAGS_RELEASE "/GS- /Gy /O2 /Oi /Ot") 83 | endif() 84 | set(CMAKE_CXX_FLAGS_DISTRIBUTION "${CMAKE_CXX_FLAGS_RELEASE}") 85 | else() 86 | # Floating point is always precise to ensure determinism across platforms. 87 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-fast-math -frounding-math -fno-trapping-math") 88 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-fast-math -frounding-math -fno-trapping-math") 89 | 90 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffp-model=precise -msse2 -mfpmath=sse") 91 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffp-model=precise -msse2 -mfpmath=sse") 92 | 93 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffp-contract=off") 94 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffp-contract=off") 95 | 96 | # Disable RTTI 97 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") 98 | 99 | if (GENERATE_DEBUG_SYMBOLS) 100 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") 101 | endif() 102 | 103 | if (NOT OVERRIDE_CXX_FLAGS) 104 | set(CMAKE_CXX_FLAGS_DEBUG "-g -O0") 105 | set(CMAKE_CXX_FLAGS_RELEASE "-O2") 106 | endif() 107 | set(CMAKE_CXX_FLAGS_DISTRIBUTION "${CMAKE_CXX_FLAGS_RELEASE}") 108 | endif() 109 | 110 | # Set repository root 111 | set(REPO_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../) 112 | 113 | # Network Synchronizer 114 | set(NS_ROOT ${REPO_ROOT}/NetworkSynchronizer) 115 | set(NS_SRC_FILES 116 | ${NS_ROOT}/scene_synchronizer.h 117 | ${NS_ROOT}/scene_synchronizer.cpp 118 | ${NS_ROOT}/core/insertion_sort.h 119 | ${NS_ROOT}/core/quick_sort.h 120 | ${NS_ROOT}/core/bit_array.cpp 121 | ${NS_ROOT}/core/bit_array.h 122 | ${NS_ROOT}/core/bit_array.cpp 123 | ${NS_ROOT}/core/bitcasts.h 124 | ${NS_ROOT}/core/core.h 125 | ${NS_ROOT}/core/core.cpp 126 | ${NS_ROOT}/core/scheduled_procedure.h 127 | ${NS_ROOT}/core/data_buffer.h 128 | ${NS_ROOT}/core/data_buffer.cpp 129 | ${NS_ROOT}/core/ensure.h 130 | ${NS_ROOT}/core/ensure.cpp 131 | ${NS_ROOT}/core/fp16.h 132 | ${NS_ROOT}/core/json.hpp 133 | ${NS_ROOT}/core/net_math.h 134 | ${NS_ROOT}/core/net_utilities.h 135 | ${NS_ROOT}/core/net_utilities.cpp 136 | ${NS_ROOT}/core/network_codec.h 137 | ${NS_ROOT}/core/network_codec.cpp 138 | ${NS_ROOT}/core/network_interface_define.h 139 | ${NS_ROOT}/core/network_interface.h 140 | ${NS_ROOT}/core/network_interface.cpp 141 | ${NS_ROOT}/core/object_data.h 142 | ${NS_ROOT}/core/object_data.cpp 143 | ${NS_ROOT}/core/object_data_storage.h 144 | ${NS_ROOT}/core/object_data_storage.cpp 145 | ${NS_ROOT}/core/peer_data.h 146 | ${NS_ROOT}/core/peer_data.cpp 147 | ${NS_ROOT}/core/peer_networked_controller.h 148 | ${NS_ROOT}/core/peer_networked_controller.cpp 149 | ${NS_ROOT}/core/processor.h 150 | ${NS_ROOT}/core/scene_synchronizer_debugger.h 151 | ${NS_ROOT}/core/scene_synchronizer_debugger.cpp 152 | ${NS_ROOT}/core/scene_synchronizer_debugger_json_storage.h 153 | ${NS_ROOT}/core/snapshot.h 154 | ${NS_ROOT}/core/snapshot.cpp 155 | ${NS_ROOT}/core/var_data.h 156 | ${NS_ROOT}/core/var_data.cpp 157 | 158 | # TODO disable tests in shipping builds. 159 | # Test 160 | ${NS_ROOT}/tests/local_network.cpp 161 | ${NS_ROOT}/tests/local_network.h 162 | ${NS_ROOT}/tests/local_scene.cpp 163 | ${NS_ROOT}/tests/local_scene.h 164 | ${NS_ROOT}/tests/test_data_buffer.cpp 165 | ${NS_ROOT}/tests/test_data_buffer.h 166 | ${NS_ROOT}/tests/test_doll_simulation.cpp 167 | ${NS_ROOT}/tests/test_doll_simulation.h 168 | ${NS_ROOT}/tests/test_AI_simulation.cpp 169 | ${NS_ROOT}/tests/test_AI_simulation.h 170 | ${NS_ROOT}/tests/test_math_lib.cpp 171 | ${NS_ROOT}/tests/test_math_lib.h 172 | ${NS_ROOT}/tests/test_processor.cpp 173 | ${NS_ROOT}/tests/test_processor.h 174 | ${NS_ROOT}/tests/test_scene_synchronizer.cpp 175 | ${NS_ROOT}/tests/test_scene_synchronizer.h 176 | ${NS_ROOT}/tests/test_simulation.cpp 177 | ${NS_ROOT}/tests/test_simulation.h 178 | ${NS_ROOT}/tests/test_switch_controller.cpp 179 | ${NS_ROOT}/tests/test_switch_controller.h 180 | ${NS_ROOT}/tests/tests.cpp 181 | ${NS_ROOT}/tests/tests.h 182 | ) 183 | 184 | if(ENABLE_DEBUGGER_UI) 185 | add_compile_definitions(UI_DEBUGGER_ENABLED) 186 | list(APPEND NS_SRC_FILES ${NS_ROOT}/core/__generated__debugger_ui.h) 187 | endif() 188 | 189 | # Group source files 190 | source_group(TREE ${NS_ROOT} FILES ${NS_SRC_FILES}) 191 | 192 | # Create NetworkSynchronizer lib 193 | add_library(NetworkSynchronizer ${NS_SRC_FILES}) 194 | 195 | target_include_directories(NetworkSynchronizer PUBLIC ${REPO_ROOT}) 196 | 197 | # Setting enable asserts flag 198 | if (ENABLE_DEBUG) 199 | target_compile_definitions(NetworkSynchronizer PUBLIC NS_DEBUG_ENABLED) 200 | endif() 201 | 202 | if (ENABLE_DEBUG_DATA_BUFFER) 203 | target_compile_definitions(NetworkSynchronizer PUBLIC DEBUG_DATA_BUFFER) 204 | endif() 205 | 206 | if(ENABLE_DEBUGGER_UI) 207 | add_dependencies(NetworkSynchronizer RunPythonScript) 208 | endif() 209 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | def can_build(env, platform): 2 | return True 3 | 4 | 5 | def configure(env): 6 | pass 7 | 8 | 9 | def get_doc_path(): 10 | return "doc_classes" 11 | 12 | 13 | def get_doc_classes(): 14 | return [ 15 | "DataBuffer", 16 | "GdNetworkedController", 17 | "GdSceneSynchronizer", 18 | "InputNetworkEncoder", 19 | ] 20 | 21 | 22 | def is_enabled(): 23 | return True 24 | -------------------------------------------------------------------------------- /core/bit_array.cpp: -------------------------------------------------------------------------------- 1 | #include "bit_array.h" 2 | 3 | #include "ensure.h" 4 | #include // Needed to include `memset` in linux. 5 | #include 6 | #include 7 | #include 8 | 9 | BitArray::BitArray(NS::SceneSynchronizerDebugger &p_debugger): 10 | debugger(&p_debugger) { 11 | } 12 | 13 | BitArray::BitArray(NS::SceneSynchronizerDebugger &p_debugger, std::uint32_t p_initial_size_in_bit): 14 | debugger(&p_debugger) { 15 | resize_in_bits(p_initial_size_in_bit); 16 | } 17 | 18 | BitArray::BitArray(NS::SceneSynchronizerDebugger &p_debugger, const std::vector &p_bytes) : 19 | debugger(&p_debugger), 20 | bytes(p_bytes) { 21 | } 22 | 23 | void BitArray::operator=(const BitArray &p_other) { 24 | debugger = p_other.debugger; 25 | bytes = p_other.bytes; 26 | } 27 | 28 | bool BitArray::resize_in_bytes(int p_bytes_count) { 29 | NS_ENSURE_V_MSG(p_bytes_count >= 0, false, "Bytes count can't be negative"); 30 | bytes.resize(p_bytes_count); 31 | return true; 32 | } 33 | 34 | int BitArray::size_in_bytes() const { 35 | return int(bytes.size()); 36 | } 37 | 38 | bool BitArray::resize_in_bits(int p_bits_count) { 39 | NS_ENSURE_V_MSG(p_bits_count >= 0, false, "Bits count can't be negative"); 40 | const int min_size = int(std::ceil((static_cast(p_bits_count)) / 8.0f)); 41 | bytes.resize(min_size); 42 | return true; 43 | } 44 | 45 | int BitArray::size_in_bits() const { 46 | return int(bytes.size() * 8); 47 | } 48 | 49 | bool BitArray::store_bits(int p_bit_offset, std::uint64_t p_value, int p_bits) { 50 | NS_ENSURE_V_MSG(p_bit_offset >= 0, false, "Offset can't be negative"); 51 | NS_ENSURE_V_MSG(p_bits > 0, false, "The number of bits should be more than 0"); 52 | NS_ENSURE_V_MSG((p_bit_offset + p_bits - 1) < size_in_bits(), false, "The bit array size is `" + std::to_string(size_in_bits()) + "` while you are trying to write `" + std::to_string(p_bits) + "` starting from `" + std::to_string(p_bit_offset) + "`."); 53 | 54 | int bits = p_bits; 55 | int bit_offset = p_bit_offset; 56 | uint64_t val = p_value; 57 | 58 | while (bits > 0) { 59 | const int bits_to_write = std::min(bits, 8 - bit_offset % 8); 60 | const int bits_to_jump = bit_offset % 8; 61 | const int bits_to_skip = 8 - (bits_to_write + bits_to_jump); 62 | const int byte_offset = bit_offset / 8; 63 | 64 | // Clear the bits that we have to write 65 | //const std::uint8_t byte_clear = ~(((0xFF >> bits_to_jump) << (bits_to_jump + bits_to_skip)) >> bits_to_skip); 66 | std::uint8_t byte_clear = 0xFF >> bits_to_jump; 67 | byte_clear = byte_clear << (bits_to_jump + bits_to_skip); 68 | byte_clear = ~(byte_clear >> bits_to_skip); 69 | bytes[byte_offset] &= byte_clear; 70 | 71 | // Now we can continue to write bits 72 | bytes[byte_offset] |= (val & 0xFF) << bits_to_jump; 73 | 74 | bits -= bits_to_write; 75 | bit_offset += bits_to_write; 76 | 77 | val >>= bits_to_write; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | bool BitArray::read_bits(int p_bit_offset, int p_bits, std::uint64_t &r_out) const { 84 | NS_ENSURE_V_MSG(p_bit_offset >= 0, false, "The offset must be always positive."); 85 | NS_ENSURE_V_MSG(p_bits > 0, false, "The number of bits should be more than 0"); 86 | NS_ENSURE_V_MSG((p_bit_offset + p_bits - 1) < size_in_bits(), false, "The bit array size is `" + std::to_string(size_in_bits()) + "` while you are trying to read `" + std::to_string(p_bits) + "` starting from `" + std::to_string(p_bit_offset) + "`."); 87 | 88 | int bits = p_bits; 89 | int bit_offset = p_bit_offset; 90 | uint64_t val = 0; 91 | 92 | const std::uint8_t *bytes_ptr = bytes.data(); 93 | 94 | int val_bits_to_jump = 0; 95 | while (bits > 0) { 96 | const int bits_to_read = std::min(bits, 8 - bit_offset % 8); 97 | const int bits_to_jump = bit_offset % 8; 98 | const int bits_to_skip = 8 - (bits_to_read + bits_to_jump); 99 | const int byte_offset = bit_offset / 8; 100 | 101 | std::uint8_t byte_mask = 0xFF >> bits_to_jump; 102 | byte_mask = byte_mask << (bits_to_skip + bits_to_jump); 103 | byte_mask = byte_mask >> bits_to_skip; 104 | const uint64_t byte_val = static_cast((bytes_ptr[byte_offset] & byte_mask) >> bits_to_jump); 105 | val |= byte_val << val_bits_to_jump; 106 | 107 | bits -= bits_to_read; 108 | bit_offset += bits_to_read; 109 | val_bits_to_jump += bits_to_read; 110 | } 111 | 112 | r_out = val; 113 | return true; 114 | } 115 | 116 | void BitArray::zero() { 117 | if (bytes.size() > 0) { 118 | memset(bytes.data(), 0, sizeof(std::uint8_t) * bytes.size()); 119 | } 120 | } -------------------------------------------------------------------------------- /core/bit_array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace NS { 7 | class SceneSynchronizerDebugger; 8 | } 9 | 10 | class BitArray { 11 | NS::SceneSynchronizerDebugger *debugger = nullptr; 12 | std::vector bytes; 13 | 14 | public: 15 | BitArray() = default; // Use this with care: Not initializing the debugger will cause a crash 16 | BitArray(const BitArray &p_other) = default; 17 | BitArray(NS::SceneSynchronizerDebugger &p_debugger); 18 | BitArray(NS::SceneSynchronizerDebugger &p_debugger, std::uint32_t p_initial_size_in_bit); 19 | BitArray(NS::SceneSynchronizerDebugger &p_debugger, const std::vector &p_bytes); 20 | 21 | void operator=(const BitArray &p_other); 22 | 23 | void set_debugger(NS::SceneSynchronizerDebugger &p_debugger) { 24 | debugger = &p_debugger; 25 | } 26 | 27 | NS::SceneSynchronizerDebugger &get_debugger() const { 28 | return *debugger; 29 | } 30 | 31 | const std::vector &get_bytes() const { 32 | return bytes; 33 | } 34 | 35 | std::vector &get_bytes_mut() { 36 | return bytes; 37 | } 38 | 39 | bool resize_in_bytes(int p_bytes_count); 40 | int size_in_bytes() const; 41 | 42 | bool resize_in_bits(int p_bits_count); 43 | int size_in_bits() const; 44 | 45 | bool store_bits(int p_bit_offset, std::uint64_t p_value, int p_bits); 46 | bool read_bits(int p_bit_offset, int p_bits, std::uint64_t &r_out) const; 47 | 48 | // Puts all the bytes to 0. 49 | void zero(); 50 | }; 51 | -------------------------------------------------------------------------------- /core/bitcasts.h: -------------------------------------------------------------------------------- 1 | // SOURCE: https://github.com/Maratyszcza/FP16 2 | 3 | #pragma once 4 | #ifndef FP16_BITCASTS_H 5 | #define FP16_BITCASTS_H 6 | 7 | #if defined(__cplusplus) && (__cplusplus >= 201103L) 8 | #include 9 | #elif !defined(__OPENCL_VERSION__) 10 | #include 11 | #endif 12 | 13 | #if defined(__INTEL_COMPILER) 14 | #include 15 | #endif 16 | 17 | #if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || defined(_M_ARM64)) 18 | #include 19 | #endif 20 | 21 | 22 | static inline float fp32_from_bits(uint32_t w) { 23 | #if defined(__OPENCL_VERSION__) 24 | return as_float(w); 25 | #elif defined(__CUDA_ARCH__) 26 | return __uint_as_float((unsigned int) w); 27 | #elif defined(__INTEL_COMPILER) 28 | return _castu32_f32(w); 29 | #elif defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || defined(_M_ARM64)) 30 | return _CopyFloatFromInt32((__int32) w); 31 | #else 32 | union { 33 | uint32_t as_bits; 34 | float as_value; 35 | } fp32 = { w }; 36 | return fp32.as_value; 37 | #endif 38 | } 39 | 40 | static inline uint32_t fp32_to_bits(float f) { 41 | #if defined(__OPENCL_VERSION__) 42 | return as_uint(f); 43 | #elif defined(__CUDA_ARCH__) 44 | return (uint32_t) __float_as_uint(f); 45 | #elif defined(__INTEL_COMPILER) 46 | return _castf32_u32(f); 47 | #elif defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || defined(_M_ARM64)) 48 | return (uint32_t) _CopyInt32FromFloat(f); 49 | #else 50 | union { 51 | float as_value; 52 | uint32_t as_bits; 53 | } fp32 = { f }; 54 | return fp32.as_bits; 55 | #endif 56 | } 57 | 58 | static inline double fp64_from_bits(uint64_t w) { 59 | #if defined(__OPENCL_VERSION__) 60 | return as_double(w); 61 | #elif defined(__CUDA_ARCH__) 62 | return __longlong_as_double((long long) w); 63 | #elif defined(__INTEL_COMPILER) 64 | return _castu64_f64(w); 65 | #elif defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || defined(_M_ARM64)) 66 | return _CopyDoubleFromInt64((__int64) w); 67 | #else 68 | union { 69 | uint64_t as_bits; 70 | double as_value; 71 | } fp64 = { w }; 72 | return fp64.as_value; 73 | #endif 74 | } 75 | 76 | static inline uint64_t fp64_to_bits(double f) { 77 | #if defined(__OPENCL_VERSION__) 78 | return as_ulong(f); 79 | #elif defined(__CUDA_ARCH__) 80 | return (uint64_t) __double_as_longlong(f); 81 | #elif defined(__INTEL_COMPILER) 82 | return _castf64_u64(f); 83 | #elif defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || defined(_M_ARM64)) 84 | return (uint64_t) _CopyInt64FromDouble(f); 85 | #else 86 | union { 87 | double as_value; 88 | uint64_t as_bits; 89 | } fp64 = { f }; 90 | return fp64.as_bits; 91 | #endif 92 | } 93 | 94 | #endif /* FP16_BITCASTS_H */ 95 | -------------------------------------------------------------------------------- /core/core.cpp: -------------------------------------------------------------------------------- 1 | #include "core.h" 2 | 3 | #include 4 | 5 | std::string operator+(const char *p_chr, const std::string &p_str) { 6 | std::string tmp = p_chr; 7 | tmp += p_str; 8 | return tmp; 9 | } 10 | 11 | NS_NAMESPACE_BEGIN 12 | const GlobalFrameIndex GlobalFrameIndex::NONE = GlobalFrameIndex{ { std::numeric_limits::max() } }; 13 | const FrameIndex FrameIndex::NONE = FrameIndex{ { std::numeric_limits::max() } }; 14 | const SyncGroupId SyncGroupId::NONE = SyncGroupId{ { std::numeric_limits::max() } }; 15 | const SyncGroupId SyncGroupId::GLOBAL = SyncGroupId{ { 0 } }; 16 | const VarId VarId::NONE = VarId{ { std::numeric_limits::max() } }; 17 | const ScheduledProcedureId ScheduledProcedureId::NONE = ScheduledProcedureId{ { std::numeric_limits::max() } }; 18 | const ObjectLocalId ObjectLocalId::NONE = ObjectLocalId{ { std::numeric_limits::max() } }; 19 | const ObjectNetId ObjectNetId::NONE = ObjectNetId{ { std::numeric_limits::max() } }; 20 | const ObjectHandle ObjectHandle::NONE = ObjectHandle{ { 0 } }; 21 | const SchemeId SchemeId::DEFAULT = SchemeId{ { 0 } }; 22 | 23 | static const char *ProcessPhaseName[PROCESS_PHASE_COUNT] = { 24 | "EARLY PROCESS", 25 | "PRE PROCESS", 26 | "PROCESS", 27 | "POST PROCESS", 28 | "LATE PROCESS" 29 | }; 30 | 31 | const char *get_process_phase_name(ProcessPhase pp) { 32 | return ProcessPhaseName[pp]; 33 | } 34 | 35 | std::string get_log_level_txt(NS::PrintMessageType p_level) { 36 | std::string log_level_str = ""; 37 | if (NS::PrintMessageType::VERBOSE == p_level) { 38 | log_level_str = "[VERBOSE] "; 39 | } else if (NS::PrintMessageType::INFO == p_level) { 40 | log_level_str = "[INFO] "; 41 | } else if (NS::PrintMessageType::WARNING == p_level) { 42 | log_level_str = "[WARNING] "; 43 | } else if (NS::PrintMessageType::ERROR == p_level) { 44 | log_level_str = "[ERROR] "; 45 | } 46 | return log_level_str; 47 | } 48 | 49 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | std::string operator+(const char *p_chr, const std::string &p_str); 7 | 8 | #if __cplusplus > 201703L 9 | // C++20 supports likely and unlikely 10 | #define make_likely(cond) (cond) [[likely]] 11 | #define make_unlikely(cond) (cond) [[unlikely]] 12 | #else 13 | #define make_likely(cond) (cond) 14 | #define make_unlikely(cond) (cond) 15 | #endif 16 | 17 | #define NS_NAMESPACE_BEGIN \ 18 | namespace NS { 19 | 20 | // End the JPH namespace 21 | #define NS_NAMESPACE_END \ 22 | } 23 | 24 | /// Flags used to control when an event is executed. 25 | enum NetEventFlag { 26 | // ~~ Flags ~~ // 27 | EMPTY = 0, 28 | 29 | /// Called at the end of the frame, if the value is different. 30 | /// It's also called when a variable is modified by the 31 | /// `apply_scene_changes` function. 32 | CHANGE = 1 << 0, 33 | 34 | /// Called when the variable is modified by the `NetworkSynchronizer` 35 | /// because not in sync with the server. 36 | SERVER_UPDATE = 1 << 1, 37 | 38 | /// Called when the variable is modified by the `NetworkSynchronizer` 39 | /// because it's preparing the node for the rewinding. 40 | SYNC_RESET = 1 << 2, 41 | 42 | /// Called when the variable is modified during the rewinding phase. 43 | SYNC_REWIND = 1 << 3, 44 | 45 | /// Called at the end of the recovering phase, if the value was modified 46 | /// during the rewinding. 47 | END_SYNC = 1 << 4, 48 | 49 | // ~~ Preconfigured ~~ // 50 | 51 | DEFAULT = CHANGE | END_SYNC, 52 | SYNC = SERVER_UPDATE | SYNC_RESET | SYNC_REWIND, 53 | ALWAYS = CHANGE | SERVER_UPDATE | SYNC_RESET | SYNC_REWIND | END_SYNC 54 | }; 55 | 56 | enum ProcessPhase { 57 | PROCESS_PHASE_EARLY = 0, 58 | PROCESS_PHASE_PRE, 59 | PROCESS_PHASE_PROCESS, 60 | PROCESS_PHASE_POST, 61 | PROCESS_PHASE_LATE, 62 | PROCESS_PHASE_COUNT 63 | }; 64 | 65 | const char *get_process_phase_name(ProcessPhase pp); 66 | 67 | NS_NAMESPACE_BEGIN 68 | enum PrintMessageType : std::uint8_t { 69 | VERBOSE = 0, 70 | INFO = 1, 71 | WARNING = 2, 72 | ERROR = 3, 73 | }; 74 | 75 | std::string get_log_level_txt(NS::PrintMessageType p_level); 76 | 77 | enum class RpcRecipientFetch { 78 | // Send the rpc if the local peer is the authority of the object to the server. 79 | PLAYER_TO_SERVER, 80 | // Send the rpc if the local peer is NOT the authority of the object to the server. 81 | DOLL_TO_SERVER, 82 | // Send the rpc to the server. 83 | ALL_TO_SERVER, 84 | // Send the rpc to the player if local peer is server. 85 | SERVER_TO_PLAYER, 86 | // Send the rpc to the dolls if local peer is server. 87 | SERVER_TO_DOLL, 88 | // Send the rpc to all if local peer is server. 89 | SERVER_TO_ALL, 90 | }; 91 | 92 | enum class RpcAllowedSender { 93 | PLAYER, 94 | DOLL, 95 | SERVER, 96 | ALL, 97 | }; 98 | 99 | template 100 | struct IdMaker { 101 | using IdType = TheIdType; 102 | 103 | TheIdType id; 104 | 105 | bool operator==(const T &p_o) const { 106 | return id == p_o.id; 107 | } 108 | 109 | bool operator!=(const T &p_o) const { 110 | return !(operator==(p_o)); 111 | } 112 | 113 | bool operator<(const T &p_o) const { 114 | return id < p_o.id; 115 | } 116 | 117 | bool operator<=(const T &p_o) const { 118 | return operator<(p_o) || operator==(p_o); 119 | } 120 | 121 | bool operator>=(const T &p_o) const { 122 | return (!operator<(p_o)); 123 | } 124 | 125 | bool operator>(const T &p_o) const { 126 | return (!operator<(p_o)) && operator!=(p_o); 127 | } 128 | 129 | T operator+(const T &p_o) const { 130 | return T{ static_cast(id + p_o.id) }; 131 | } 132 | 133 | T operator+(TheIdType p_id) const { 134 | return T{ static_cast(id + p_id) }; 135 | } 136 | 137 | T &operator+=(const T &p_o) { 138 | id += p_o.id; 139 | return *static_cast(this); 140 | } 141 | 142 | T &operator+=(TheIdType p_id) { 143 | id += p_id; 144 | return *static_cast(this); 145 | } 146 | 147 | T operator-(const T &p_o) const { 148 | return T{ static_cast(id - p_o.id) }; 149 | } 150 | 151 | T operator-(TheIdType p_id) const { 152 | return T{ static_cast(id - p_id) }; 153 | } 154 | 155 | T &operator-=(const T &p_o) { 156 | id -= p_o.id; 157 | return *static_cast(this); 158 | } 159 | 160 | T &operator-=(TheIdType p_id) { 161 | id -= p_id; 162 | return *static_cast(this); 163 | } 164 | 165 | operator std::string() const { 166 | return "`" + std::to_string(id) + "`"; 167 | } 168 | }; 169 | 170 | struct GlobalFrameIndex : public IdMaker { 171 | static const GlobalFrameIndex NONE; 172 | }; 173 | 174 | struct FrameIndex : public IdMaker { 175 | static const FrameIndex NONE; 176 | }; 177 | 178 | struct SyncGroupId : public IdMaker { 179 | static const SyncGroupId NONE; 180 | /// This SyncGroup contains ALL the registered ObjectData. 181 | static const SyncGroupId GLOBAL; 182 | }; 183 | 184 | struct VarId : public IdMaker { 185 | static const VarId NONE; 186 | }; 187 | 188 | struct ScheduledProcedureId : public IdMaker { 189 | static const ScheduledProcedureId NONE; 190 | }; 191 | 192 | struct ObjectNetId : public IdMaker { 193 | static const ObjectNetId NONE; 194 | }; 195 | 196 | struct ObjectLocalId : public IdMaker { 197 | // TODO use `int` instead? 198 | static const ObjectLocalId NONE; 199 | }; 200 | 201 | struct ObjectHandle : public IdMaker { 202 | static const ObjectHandle NONE; 203 | }; 204 | 205 | struct SchemeId : public IdMaker { 206 | static const SchemeId DEFAULT; 207 | }; 208 | 209 | enum class ScheduledProcedurePhase : std::uint8_t { 210 | /// The procedure is called with in this phase only on the server when collecting the arguments. 211 | COLLECTING_ARGUMENTS = 0, 212 | /// This is executed on the client when the procedure is received. In some case this is not executed, so don't count on this too much. 213 | RECEIVED = 1, 214 | /// The scheduled procedure time is over and the execute is triggered. Here the procedure can do its normal job. 215 | EXECUTING = 2, 216 | }; 217 | 218 | template 219 | constexpr const T sign(const T m_v) { 220 | return m_v == 0 ? 0.0f : (m_v < 0 ? -1.0f : +1.0f); 221 | } 222 | 223 | #define NS_ScheduledProcedureFunc std::function 224 | 225 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/ensure.cpp: -------------------------------------------------------------------------------- 1 | #include "ensure.h" 2 | 3 | #include "../scene_synchronizer.h" 4 | 5 | void _ns_print_code_message( 6 | NS::SceneSynchronizerDebugger &p_debugger, 7 | const char *p_function, 8 | const char *p_file, 9 | int p_line, 10 | const std::string &p_error, 11 | const std::string &p_message, 12 | NS::PrintMessageType p_type) { 13 | NS::SceneSynchronizerBase::print_code_message( 14 | &p_debugger, 15 | p_function, 16 | p_file, 17 | p_line, 18 | p_error, 19 | p_message, 20 | p_type); 21 | } 22 | 23 | void _ns_print_code_message( 24 | const char *p_function, 25 | const char *p_file, 26 | int p_line, 27 | const std::string &p_error, 28 | const std::string &p_message, 29 | NS::PrintMessageType p_type) { 30 | NS::SceneSynchronizerBase::print_code_message( 31 | nullptr, 32 | p_function, 33 | p_file, 34 | p_line, 35 | p_error, 36 | p_message, 37 | p_type); 38 | } 39 | 40 | void _ns_print_flush_stdout() { 41 | NS::SceneSynchronizerBase::print_flush_stdout(); 42 | } -------------------------------------------------------------------------------- /core/ensure.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include 5 | 6 | #ifdef __GNUC__ 7 | //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying 8 | #define FUNCTION_STR __FUNCTION__ 9 | #else 10 | #define FUNCTION_STR __FUNCTION__ 11 | #endif 12 | 13 | #ifdef _MSC_VER 14 | /** 15 | * Don't use GENERATE_TRAP() directly, should only be used be the macros below. 16 | */ 17 | #define GENERATE_TRAP() __debugbreak() 18 | #else 19 | /** 20 | * Don't use GENERATE_TRAP() directly, should only be used be the macros below. 21 | */ 22 | #define GENERATE_TRAP() __builtin_trap() 23 | #endif 24 | 25 | // Turn argument to string constant: 26 | // https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing 27 | #ifndef _STR 28 | #define _STR(m_x) #m_x 29 | #define _MKSTR(m_x) _STR(m_x) 30 | #endif 31 | 32 | namespace NS { 33 | class SceneSynchronizerDebugger; 34 | }; 35 | 36 | void _ns_print_code_message( 37 | NS::SceneSynchronizerDebugger &p_debugger, 38 | const char *p_function, 39 | const char *p_file, 40 | int p_line, 41 | const std::string &p_error, 42 | const std::string &p_message, 43 | NS::PrintMessageType p_type); 44 | 45 | void _ns_print_code_message( 46 | const char *p_function, 47 | const char *p_file, 48 | int p_line, 49 | const std::string &p_error, 50 | const std::string &p_message, 51 | NS::PrintMessageType p_type); 52 | 53 | void _ns_print_flush_stdout(); 54 | 55 | /// Ensures `m_cond` is true. 56 | /// If `m_cond` is false the current function returns. 57 | #define NS_ENSURE(m_cond) \ 58 | if make_likely (m_cond) { \ 59 | } else { \ 60 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is false.", "", NS::PrintMessageType::ERROR); \ 61 | return; \ 62 | } 63 | 64 | /// Ensures `m_cond` is true. 65 | /// If `m_cond` is false, prints `m_msg` and the current function returns. 66 | #define NS_ENSURE_MSG(m_cond, m_msg) \ 67 | if make_likely (m_cond) { \ 68 | } else { \ 69 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is false. Returning: " _STR(m_retval), m_msg, NS::PrintMessageType::ERROR); \ 70 | return; \ 71 | } 72 | 73 | /// Ensures `m_cond` is true. 74 | /// If `m_cond` is false, the current function returns `m_retval`. 75 | #define NS_ENSURE_V(m_cond, m_retval) \ 76 | if make_likely (m_cond) { \ 77 | } else { \ 78 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is false. Returning: " _STR(m_retval), "", NS::PrintMessageType::ERROR); \ 79 | return m_retval; \ 80 | } 81 | 82 | /// Ensures `m_cond` is true. 83 | /// If `m_cond` is false, prints `m_msg` and the current function returns `m_retval`. 84 | #define NS_ENSURE_V_MSG(m_cond, m_retval, m_msg) \ 85 | if make_likely (m_cond) { \ 86 | } else { \ 87 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is false. Returning: " _STR(m_retval), m_msg, NS::PrintMessageType::ERROR); \ 88 | return m_retval; \ 89 | } 90 | 91 | /// Ensures no entry 92 | #define NS_ENSURE_NO_ENTRY() \ 93 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "No entry triggered", "", NS::PrintMessageType::ERROR); \ 94 | return; 95 | 96 | /// Ensures no entry with message 97 | #define NS_ENSURE_NO_ENTRY_MSG(m_msg) \ 98 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "No entry. Returning: " _STR(m_retval), m_msg, NS::PrintMessageType::ERROR); \ 99 | return; 100 | 101 | /// Ensures no entry with return value. 102 | #define NS_ENSURE_NO_ENTRY_V(m_retval) \ 103 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "No entry. Returning: " _STR(m_retval), "", NS::PrintMessageType::ERROR); \ 104 | return m_retval; 105 | 106 | /// Ensures no entry with return value and with message. 107 | #define NS_ENSURE_NO_ENTRY_V_MSG(m_retval, m_msg) \ 108 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "No entry. Returning: " _STR(m_retval), m_msg, NS::PrintMessageType::ERROR); \ 109 | return m_retval; 110 | 111 | /// Ensures `m_cond` is true. 112 | /// If `m_cond` is false the current function returns. 113 | #define NS_ENSURE_CONTINUE(m_cond) \ 114 | if make_likely (m_cond) { \ 115 | } else { \ 116 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is false.", "", NS::PrintMessageType::ERROR); \ 117 | continue; \ 118 | } 119 | 120 | /// Ensures `m_cond` is true. 121 | /// If `m_cond` is false the current function returns. 122 | #define NS_ENSURE_CONTINUE_MSG(m_cond, m_msg) \ 123 | if make_likely (m_cond) { \ 124 | } else { \ 125 | _ns_print_code_message(get_debugger(), FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is false.", m_msg, NS::PrintMessageType::ERROR); \ 126 | continue; \ 127 | } 128 | 129 | #define NS_ASSERT_NO_ENTRY() \ 130 | _ns_print_code_message(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Ne entry triggered.", "", NS::PrintMessageType::ERROR); \ 131 | _ns_print_flush_stdout(); \ 132 | GENERATE_TRAP(); 133 | 134 | #define NS_ASSERT_NO_ENTRY_MSG(m_msg) \ 135 | _ns_print_code_message(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Ne entry triggered.", m_msg, NS::PrintMessageType::ERROR); \ 136 | _ns_print_flush_stdout(); \ 137 | GENERATE_TRAP(); 138 | 139 | /// Ensures `m_cond` is true. 140 | /// If `m_cond` is true, the application crashes. 141 | #define NS_ASSERT_COND(m_cond) \ 142 | if make_likely (m_cond) { \ 143 | } else { \ 144 | _ns_print_code_message(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Condition \"" _STR(m_cond) "\" is false", "", NS::PrintMessageType::ERROR); \ 145 | _ns_print_flush_stdout(); \ 146 | GENERATE_TRAP(); \ 147 | } 148 | 149 | /// Ensures `m_cond` is true. 150 | /// If `m_cond` is true, prints `m_msg` and the application crashes. 151 | #define NS_ASSERT_COND_MSG(m_cond, m_msg) \ 152 | if make_likely (m_cond) { \ 153 | } else { \ 154 | _ns_print_code_message(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Condition \"" _STR(m_cond) "\" is false.", m_msg, NS::PrintMessageType::ERROR); \ 155 | _ns_print_flush_stdout(); \ 156 | GENERATE_TRAP(); \ 157 | } -------------------------------------------------------------------------------- /core/event_processor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include "net_utilities.h" 5 | #include 6 | 7 | NS_NAMESPACE_BEGIN 8 | // A simple but yet effective event system. 9 | template 10 | class EventProcessor { 11 | typedef void (func_sub_type)(ARGS...); 12 | typedef std::function func_type; 13 | 14 | public: 15 | class Handler { 16 | int id; 17 | EventProcessor *processor; 18 | 19 | public: 20 | Handler(): 21 | id(-1), 22 | processor(nullptr) { 23 | } 24 | 25 | Handler(int p_id, EventProcessor *p_processor): 26 | id(p_id), 27 | processor(p_processor) { 28 | } 29 | 30 | ~Handler() { 31 | clear(); 32 | } 33 | 34 | bool is_valid() const { 35 | return id != -1; 36 | } 37 | 38 | void clear() { 39 | if (processor) { 40 | processor->unbind(id); 41 | id = -1; 42 | processor = nullptr; 43 | } 44 | } 45 | }; 46 | 47 | struct EventProcessorData { 48 | int id; 49 | Handler *handler = nullptr; 50 | func_type function = nullptr; 51 | 52 | bool operator==(const EventProcessorData &o) const { 53 | return id == o.id; 54 | } 55 | }; 56 | 57 | private: 58 | std::vector binded_functions; 59 | int id_counter = 0; 60 | 61 | private: 62 | void unbind(int id); 63 | 64 | public: 65 | ~EventProcessor(); 66 | /// Bind a function and returns it's handler. 67 | std::unique_ptr bind(func_type p_func); 68 | void broadcast(ARGS... p_args); 69 | void clear(); 70 | int bind_count() const; 71 | }; 72 | 73 | template 74 | void EventProcessor::unbind(int id) { 75 | const EventProcessorData epd{ id, nullptr, nullptr }; 76 | VecFunc::remove_unordered(binded_functions, epd); 77 | } 78 | 79 | template 80 | EventProcessor::~EventProcessor() { 81 | clear(); 82 | } 83 | 84 | template 85 | std::unique_ptr::Handler> EventProcessor::bind(func_type p_func) { 86 | // Make sure this function was not bind already. 87 | std::unique_ptr handler = std::make_unique(Handler(id_counter, this)); 88 | binded_functions.push_back({ id_counter, handler.get(), p_func }); 89 | id_counter += 1; 90 | return std::move(handler); 91 | } 92 | 93 | template 94 | void EventProcessor::clear() { 95 | while (binded_functions.size() > 0) { 96 | auto h = binded_functions.back().handler; 97 | h->clear(); 98 | } 99 | id_counter = 0; 100 | } 101 | 102 | template 103 | void EventProcessor::broadcast(ARGS... p_args) { 104 | for (auto &func_data : binded_functions) { 105 | func_data.function(p_args...); 106 | } 107 | } 108 | 109 | template 110 | int EventProcessor::bind_count() const { 111 | return int(binded_functions.size()); 112 | } 113 | 114 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/insertion_sort.h: -------------------------------------------------------------------------------- 1 | // PORTED FROM JOLT - std::sort is not deterministic across plats. 2 | 3 | // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) 4 | // SPDX-FileCopyrightText: 2022 Jorrit Rouwe 5 | // SPDX-License-Identifier: MIT 6 | 7 | #pragma once 8 | 9 | namespace NS_JOLT { 10 | /// Implementation of the insertion sort algorithm. 11 | template 12 | inline void InsertionSort(Iterator inBegin, Iterator inEnd, Compare inCompare) { 13 | // Empty arrays don't need to be sorted 14 | if (inBegin != inEnd) { 15 | // Start at the second element 16 | for (Iterator i = inBegin + 1; i != inEnd; ++i) { 17 | // Move this element to a temporary value 18 | auto x = std::move(*i); 19 | 20 | // Check if the element goes before inBegin (we can't decrement the iterator before inBegin so this needs to be a separate branch) 21 | if (inCompare(x, *inBegin)) { 22 | // Move all elements to the right to make space for x 23 | Iterator prev; 24 | for (Iterator j = i; j != inBegin; j = prev) { 25 | prev = j - 1; 26 | *j = std::move(*prev); 27 | } 28 | 29 | // Move x to the first place 30 | *inBegin = std::move(x); 31 | } else { 32 | // Move elements to the right as long as they are bigger than x 33 | Iterator j = i; 34 | for (Iterator prev = j - 1; inCompare(x, *prev); j = prev, --prev) 35 | *j = std::move(*prev); 36 | 37 | // Move x into place 38 | *j = std::move(x); 39 | } 40 | } 41 | } 42 | } 43 | 44 | /// Implementation of insertion sort algorithm without comparator. 45 | template 46 | inline void InsertionSort(Iterator inBegin, Iterator inEnd) { 47 | std::less<> compare; 48 | InsertionSort(inBegin, inEnd, compare); 49 | } 50 | } -------------------------------------------------------------------------------- /core/net_math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include 5 | 6 | NS_NAMESPACE_BEGIN 7 | class MathFunc { 8 | public: 9 | // Constants 10 | static constexpr float TAU = 6.28318530718f; // 2π 11 | static constexpr float PI = 3.14159265359f; 12 | static constexpr float HALF_PI = 1.57079632679f; // π/2 13 | static constexpr float TWO_OVER_PI = 0.6366197723675814f; // 2 / π 14 | static constexpr float NEG_INF = -std::numeric_limits::infinity(); 15 | 16 | template 17 | static bool is_equal_approx(T a, T b, T epsilon = std::numeric_limits::epsilon()) { 18 | // Check for exact equality first, required to handle "infinity" values. 19 | if (a == b) { 20 | return true; 21 | } 22 | 23 | // Then check for approximate equality. 24 | return std::abs(a - b) <= epsilon; 25 | } 26 | 27 | template 28 | static bool is_zero_approx(T a, T epsilon = std::numeric_limits::epsilon()) { 29 | return std::abs(a) < epsilon; 30 | } 31 | 32 | template 33 | static T vec2_length_squared(T x, T y) { 34 | return x * x + y * y; 35 | } 36 | 37 | template 38 | static T vec2_length(T x, T y) { 39 | return std::sqrt(x * x + y * y); 40 | } 41 | 42 | template 43 | static T vec3_length_squared(T x, T y, T z) { 44 | return x * x + y * y + z * z; 45 | } 46 | 47 | template 48 | static T vec3_length(T x, T y, T z) { 49 | return std::sqrt(x * x + y * y + z * z); 50 | } 51 | 52 | template 53 | static bool vec2_is_normalized(T x, T y) { 54 | return is_equal_approx(vec2_length(x, y), T(1.0)); 55 | } 56 | 57 | template 58 | static bool vec3_is_normalized(T x, T y, T z) { 59 | return is_equal_approx(vec3_length(x, y, z), T(1.0)); 60 | } 61 | 62 | static float vec2_angle(float x, float y) { 63 | return atan2(y, x); 64 | } 65 | 66 | template 67 | static void vec2_normalize(T &x, T &y) { 68 | T l = x * x + y * y; 69 | if (l != 0) { 70 | l = std::sqrt(l); 71 | x /= l; 72 | y /= l; 73 | } else { 74 | x = 0; 75 | y = 0; 76 | } 77 | } 78 | 79 | template 80 | static void vec3_normalize(T &x, T &y, T &z) { 81 | T l = x * x + y * y + z * z; 82 | if (l != 0) { 83 | l = std::sqrt(l); 84 | x /= l; 85 | y /= l; 86 | z /= l; 87 | } else { 88 | x = 0; 89 | y = 0; 90 | z = 0; 91 | } 92 | } 93 | 94 | template 95 | static inline F lerp(F a, F b, A alpha) { 96 | return a + alpha * (b - a); 97 | } 98 | 99 | template 100 | static inline T clamp(const T value, const T2 min, const T3 max) { 101 | return value < min ? min : (value > max ? max : value); 102 | } 103 | 104 | template 105 | static inline T abs(const T value) { 106 | return value >= T(0.0) ? value : -value; 107 | } 108 | 109 | static inline std::int64_t round_to_nearest(double num_to_round) { 110 | if (num_to_round >= 0.0) { 111 | return static_cast(num_to_round + 0.5); 112 | } else { 113 | return static_cast(num_to_round - 0.5); 114 | } 115 | } 116 | 117 | static inline int round_to_nearest(float num_to_round) { 118 | if (num_to_round >= 0.0f) { 119 | return static_cast(num_to_round + 0.5f); 120 | } else { 121 | return static_cast(num_to_round - 0.5f); 122 | } 123 | } 124 | 125 | // Ported from Jolt - Deterministic across platforms. 126 | static inline float sin(float in_x) { 127 | float s, c; 128 | CrossSinCosInternal(in_x, s, c); 129 | return s; 130 | } 131 | 132 | // Ported from Jolt - Deterministic across platforms. 133 | static inline float cos(float in_x) { 134 | float s, c; 135 | CrossSinCosInternal(in_x, s, c); 136 | return c; 137 | } 138 | 139 | // Ported from Jolt - Deterministic across platforms. 140 | static inline float atan(float v) { 141 | // Make argument positive, remember sign 142 | union { 143 | float f; 144 | uint32_t u; 145 | } tmp; 146 | tmp.f = v; 147 | uint32_t sign = tmp.u & 0x80000000U; 148 | float x = (sign == 0) ? v : -v; 149 | 150 | // If x > tan(π/8) 151 | float threshold1 = 0.4142135623730950f; // tan(π/8) ~ 0.4142135623 152 | bool greater1 = (x > threshold1); 153 | float x1 = (x - 1.0f) / (x + 1.0f); 154 | 155 | // If x > tan(3π/8) 156 | float threshold2 = 2.414213562373095f; // tan(3π/8) ~ 2.4142135624 157 | bool greater2 = (x > threshold2); 158 | // Add a tiny epsilon to avoid div by zero 159 | float x2 = -1.0f / (x + 1e-38f); 160 | 161 | // Apply first condition 162 | float x_sel = greater1 ? x1 : x; 163 | float y_sel = greater1 ? 0.78539816339f : 0.0f; // π/4 = 0.78539816339 164 | 165 | // Apply second condition 166 | float x_final = greater2 ? x2 : x_sel; 167 | float y_final = greater2 ? 1.57079632679f : y_sel; // π/2 = 1.57079632679 168 | 169 | // Polynomial approximation 170 | float z = x_final * x_final; 171 | float add = (((8.05374449538e-2f * z - 1.38776856032e-1f) * z + 1.99777106478e-1f) * z - 3.33329491539e-1f) * z * x_final + x_final; 172 | float result = y_final + add; 173 | 174 | // Put sign back 175 | union { 176 | float f; 177 | uint32_t u; 178 | } s; 179 | s.f = result; 180 | s.u ^= sign; 181 | return s.f; 182 | } 183 | 184 | // Ported from Jolt - Deterministic across platforms. 185 | static inline float atan2(float y, float x) { 186 | // If x=0 187 | if (x == 0.0f) { 188 | if (y > 0.0f) 189 | return 1.57079632679f; // π/2 190 | if (y < 0.0f) 191 | return -1.57079632679f; // -π/2 192 | return 0.0f; // (0,0) 193 | } 194 | 195 | // Sign bits / absolute values 196 | union { 197 | float f; 198 | uint32_t u; 199 | } sign_x, sign_y; 200 | sign_x.f = x; 201 | sign_y.f = y; 202 | uint32_t x_sign = sign_x.u & 0x80000000U; 203 | uint32_t y_sign = sign_y.u & 0x80000000U; 204 | float ax = (x_sign == 0) ? x : -x; 205 | float ay = (y_sign == 0) ? y : -y; 206 | 207 | // Always divide smaller by larger 208 | bool x_is_numer = (ax < ay); 209 | float numer = x_is_numer ? ax : ay; 210 | float denom = x_is_numer ? ay : ax; 211 | 212 | // Base atan of ratio 213 | float ratio = (denom < 1e-38f) ? 0.0f : (numer / denom); 214 | float angle = atan(ratio); 215 | 216 | // If we did x / y instead of y / x => angle = π/2 - angle 217 | if (x_is_numer) 218 | angle = 1.57079632679f - angle; // π/2 219 | 220 | // Map to correct quadrant: 221 | // If x<0 => angle = (y>=0)? (angle+π) : (angle-π) 222 | // Then flip sign if (x_sign ^ y_sign) is set 223 | if (x_sign != 0) 224 | angle = (y_sign == 0) ? (angle + 3.14159265359f) : (angle - 3.14159265359f); 225 | 226 | union { 227 | float f; 228 | uint32_t u; 229 | } ret; 230 | ret.f = angle; 231 | // Flip sign if (x_sign ^ y_sign) is set 232 | ret.u ^= (x_sign ^ y_sign); 233 | return ret.f; 234 | } 235 | 236 | // Function to compute the minimal difference between two angles in radians 237 | static inline float angle_difference(float angle1, float angle2) { 238 | float diff = fmod(angle2 - angle1 + PI, TAU); 239 | if (diff < 0) 240 | diff += TAU; 241 | diff -= PI; 242 | return std::fabs(diff); 243 | } 244 | 245 | // Ported from Jolt - Deterministic across platforms. 246 | static void CrossSinCosInternal(float in_x, float &out_sin, float &out_cos) { 247 | //------------------------------------------------------ 248 | // 1) Normalize angle in [0..2π) 249 | //------------------------------------------------------ 250 | float angle = in_x; 251 | //float angle = std::fmod(in_x, TAU); 252 | //if (angle < 0.0f) 253 | // angle += TAU; // e.g. -5.76 => +0.52368 => 30 deg 254 | 255 | //------------------------------------------------------ 256 | // 2) quadrant = int(angle * 2/π + 0.5) 257 | //------------------------------------------------------ 258 | int quad = static_cast(angle * TWO_OVER_PI + 0.5f); 259 | float fquad = static_cast(quad); 260 | 261 | //------------------------------------------------------ 262 | // 3) Subtract quadrant*(π/2) via cody–waite steps 263 | //------------------------------------------------------ 264 | float x = ((angle - fquad * 1.5703125f) 265 | - fquad * 0.0004837512969970703125f) 266 | - fquad * 7.549789948768648e-8f; 267 | // x now in [-π/4, π/4], if angle was near a quadrant boundary 268 | 269 | float x2 = x * x; 270 | 271 | //------------------------------------------------------ 272 | // 4) Polynomial expansions 273 | //------------------------------------------------------ 274 | // sin_approx 275 | float sin_approx = 276 | ((-1.9515295891e-4f * x2 + 8.3321608736e-3f) * x2 - 1.6666654611e-1f) 277 | * x2 * x 278 | + x; 279 | 280 | // cos_approx 281 | float cos_approx = 282 | (((2.443315711809948e-5f * x2 - 1.388731625493765e-3f) * x2 283 | + 4.166664568298827e-2f) * x2 * x2) 284 | - 0.5f * x2 285 | + 1.0f; 286 | 287 | //------------------------------------------------------ 288 | // 5) quadrant-based sign manipulations 289 | // (no manual sign-flip for negative in_x!) 290 | //------------------------------------------------------ 291 | int quadrant_bits = quad & 3; 292 | float s, c; 293 | switch (quadrant_bits) { 294 | case 0: 295 | s = sin_approx; 296 | c = cos_approx; 297 | break; 298 | case 1: 299 | s = cos_approx; 300 | c = -sin_approx; 301 | break; 302 | case 2: 303 | s = -sin_approx; 304 | c = -cos_approx; 305 | break; 306 | default: // 3 307 | s = -cos_approx; 308 | c = sin_approx; 309 | break; 310 | } 311 | 312 | out_sin = s; 313 | out_cos = c; 314 | } 315 | }; 316 | 317 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/network_codec.cpp: -------------------------------------------------------------------------------- 1 | #include "network_codec.h" 2 | 3 | #include "../scene_synchronizer.h" 4 | #include "data_buffer.h" 5 | #include "var_data.h" 6 | 7 | NS_NAMESPACE_BEGIN 8 | void encode_variable(bool val, DataBuffer &r_buffer) { 9 | r_buffer.add_bool(val); 10 | } 11 | 12 | void decode_variable(bool &val, DataBuffer &p_buffer) { 13 | val = p_buffer.read_bool(); 14 | } 15 | 16 | void encode_variable(std::uint8_t val, DataBuffer &r_buffer) { 17 | r_buffer.add(val); 18 | } 19 | 20 | void decode_variable(std::uint8_t &val, DataBuffer &p_buffer) { 21 | p_buffer.read(val); 22 | } 23 | 24 | void encode_variable(std::uint16_t val, DataBuffer &r_buffer) { 25 | r_buffer.add(val); 26 | } 27 | 28 | void decode_variable(std::uint16_t &val, DataBuffer &p_buffer) { 29 | p_buffer.read(val); 30 | } 31 | 32 | void encode_variable(int val, DataBuffer &r_buffer) { 33 | // TODO optimize 34 | r_buffer.add_int(val, DataBuffer::COMPRESSION_LEVEL_0); 35 | } 36 | 37 | void decode_variable(int &val, DataBuffer &p_buffer) { 38 | // TODO optimize 39 | val = int(p_buffer.read_int(DataBuffer::COMPRESSION_LEVEL_0)); 40 | } 41 | 42 | void encode_variable(ObjectNetId val, DataBuffer &r_buffer) { 43 | r_buffer.add(val.id); 44 | } 45 | 46 | void decode_variable(ObjectNetId &val, DataBuffer &p_buffer) { 47 | p_buffer.read(val.id); 48 | } 49 | 50 | void encode_variable(FrameIndex val, DataBuffer &r_buffer) { 51 | r_buffer.add(val.id); 52 | } 53 | 54 | void decode_variable(FrameIndex &val, DataBuffer &p_buffer) { 55 | p_buffer.read(val.id); 56 | } 57 | 58 | void encode_variable(GlobalFrameIndex val, DataBuffer &r_buffer) { 59 | r_buffer.add(val.id); 60 | } 61 | 62 | void decode_variable(GlobalFrameIndex &val, DataBuffer &p_buffer) { 63 | p_buffer.read(val.id); 64 | } 65 | 66 | void encode_variable(ScheduledProcedureId val, DataBuffer &r_buffer) { 67 | r_buffer.add(val.id); 68 | } 69 | 70 | void decode_variable(ScheduledProcedureId &val, DataBuffer &p_buffer) { 71 | p_buffer.read(val.id); 72 | } 73 | 74 | void encode_variable(float val, DataBuffer &r_buffer) { 75 | r_buffer.add_real(val, DataBuffer::COMPRESSION_LEVEL_1); 76 | } 77 | 78 | void decode_variable(float &val, DataBuffer &p_buffer) { 79 | p_buffer.read_real(val, DataBuffer::COMPRESSION_LEVEL_1); 80 | } 81 | 82 | void encode_variable(double val, DataBuffer &r_buffer) { 83 | r_buffer.add_real(val, DataBuffer::COMPRESSION_LEVEL_0); 84 | } 85 | 86 | void decode_variable(double &val, DataBuffer &p_buffer) { 87 | p_buffer.read_real(val, DataBuffer::COMPRESSION_LEVEL_0); 88 | } 89 | 90 | void encode_variable(const std::vector &val, DataBuffer &r_buffer) { 91 | // TODO optimize? 92 | NS_ASSERT_COND(val.size() < 4294967295); 93 | r_buffer.add_uint(val.size(), DataBuffer::COMPRESSION_LEVEL_1); 94 | for (const auto v : val) { 95 | r_buffer.add_uint(v, DataBuffer::COMPRESSION_LEVEL_3); 96 | } 97 | } 98 | 99 | void decode_variable(std::vector &val, DataBuffer &p_buffer) { 100 | // TODO optimize? 101 | const std::uint64_t size = p_buffer.read_uint(DataBuffer::COMPRESSION_LEVEL_1); 102 | val.resize(size); 103 | for (int i = 0; i < size; i++) { 104 | val[i] = std::uint8_t(p_buffer.read_uint(DataBuffer::COMPRESSION_LEVEL_3)); 105 | } 106 | } 107 | 108 | void encode_variable(const DataBuffer &val, DataBuffer &r_buffer) { 109 | r_buffer.add(val); 110 | } 111 | 112 | void decode_variable(DataBuffer &val, DataBuffer &p_buffer) { 113 | p_buffer.read(val); 114 | } 115 | 116 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/network_codec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include 5 | 6 | NS_NAMESPACE_BEGIN 7 | 8 | void encode_variable(bool val, class DataBuffer &r_buffer); 9 | void decode_variable(bool &val, DataBuffer &p_buffer); 10 | 11 | void encode_variable(std::uint8_t val, DataBuffer &r_buffer); 12 | void decode_variable(std::uint8_t &val, DataBuffer &p_buffer); 13 | 14 | void encode_variable(std::uint16_t val, DataBuffer &r_buffer); 15 | void decode_variable(std::uint16_t &val, DataBuffer &p_buffer); 16 | 17 | void encode_variable(int val, DataBuffer &r_buffer); 18 | void decode_variable(int &val, DataBuffer &p_buffer); 19 | 20 | void encode_variable(ObjectNetId val, DataBuffer &r_buffer); 21 | void decode_variable(ObjectNetId &val, DataBuffer &p_buffer); 22 | 23 | void encode_variable(FrameIndex val, DataBuffer &r_buffer); 24 | void decode_variable(FrameIndex &val, DataBuffer &p_buffer); 25 | 26 | void encode_variable(GlobalFrameIndex val, DataBuffer &r_buffer); 27 | void decode_variable(GlobalFrameIndex &val, DataBuffer &p_buffer); 28 | 29 | void encode_variable(ScheduledProcedureId val, DataBuffer &r_buffer); 30 | void decode_variable(ScheduledProcedureId &val, DataBuffer &p_buffer); 31 | 32 | void encode_variable(float val, DataBuffer &r_buffer); 33 | void decode_variable(float &val, DataBuffer &r_buffer); 34 | 35 | void encode_variable(double val, DataBuffer &r_buffer); 36 | void decode_variable(double &val, DataBuffer &p_buffer); 37 | 38 | void encode_variable(const std::vector &val, DataBuffer &r_buffer); 39 | void decode_variable(std::vector &val, DataBuffer &p_buffer); 40 | 41 | void encode_variable(const DataBuffer &val, DataBuffer &r_buffer); 42 | void decode_variable(DataBuffer &val, DataBuffer &p_buffer); 43 | 44 | template 45 | void encode_variables(DataBuffer &r_buffer) {} 46 | 47 | template 48 | void encode_variables(DataBuffer &r_buffer, const A &p_a, const ARGS &...p_args) { 49 | encode_variable(p_a, r_buffer); 50 | encode_variables(r_buffer, p_args...); 51 | } 52 | 53 | NS_NAMESPACE_END 54 | -------------------------------------------------------------------------------- /core/network_interface.cpp: -------------------------------------------------------------------------------- 1 | #include "network_interface.h" 2 | 3 | #include "../scene_synchronizer.h" 4 | 5 | NS_NAMESPACE_BEGIN 6 | void NetworkInterface::set_scene_synchronizer(SceneSynchronizerBase *p_scene_sync) { 7 | scene_synchronizer = p_scene_sync; 8 | } 9 | 10 | SceneSynchronizerDebugger &NetworkInterface::get_debugger() const { 11 | return debugger; 12 | } 13 | 14 | bool NetworkInterface::validate_rpc_sender_receive(int p_sender_peer, const RPCInfo &p_rpc_info, const ObjectData *p_od) const { 15 | if (p_rpc_info.call_local && p_sender_peer == get_local_peer_id()) { 16 | return true; 17 | } 18 | 19 | return validate_rpc_sender(p_sender_peer, p_rpc_info, p_od); 20 | } 21 | 22 | bool NetworkInterface::validate_rpc_sender(int p_sender_peer, const RPCInfo &p_rpc_info, const ObjectData *p_od) const { 23 | if (p_rpc_info.call_local && p_sender_peer == get_local_peer_id()) { 24 | return true; 25 | } 26 | 27 | switch (p_rpc_info.allowed_sender) { 28 | case RpcAllowedSender::ALL: 29 | // Always true. 30 | return true; 31 | case RpcAllowedSender::DOLL: 32 | if (p_od) { 33 | if (p_od->get_controlled_by_peer() > 0) { 34 | return p_od->get_controlled_by_peer() != p_sender_peer; 35 | } else { 36 | // Always true when the object is not controlled 37 | return true; 38 | } 39 | } else { 40 | // Never allow for rpcs toward to SceneSynchronizer. 41 | return false; 42 | } 43 | case RpcAllowedSender::PLAYER: 44 | if (p_od) { 45 | return p_od->get_controlled_by_peer() == p_sender_peer; 46 | } else { 47 | // Never allow for rpcs toward to SceneSynchronizer. 48 | return false; 49 | } 50 | case RpcAllowedSender::SERVER: 51 | return p_sender_peer == get_server_peer(); 52 | } 53 | 54 | // Please implement all. 55 | NS_ASSERT_NO_ENTRY(); 56 | return false; 57 | } 58 | 59 | void NetworkInterface::rpc_receive(int p_sender_peer, DataBuffer &p_db) { 60 | rpc_last_sender = p_sender_peer; 61 | 62 | p_db.begin_read(get_debugger()); 63 | 64 | bool target_object; 65 | p_db.read(target_object); 66 | 67 | ObjectNetId target_id = ObjectNetId::NONE; 68 | if (target_object) { 69 | p_db.read(target_id.id); 70 | } 71 | 72 | std::uint8_t rpc_id; 73 | p_db.read(rpc_id); 74 | 75 | if (target_id != ObjectNetId::NONE) { 76 | ObjectData *od = scene_synchronizer->get_object_data(target_id); 77 | if (od) { 78 | NS_ENSURE_MSG(rpc_id < od->rpcs_info.size(), "The received rpc of object "+std::to_string(target_id.id)+" contains a broken RPC ID: `" + std::to_string(rpc_id) + "`, the `rpcs_info` size is `" + std::to_string(rpcs_info.size()) + "`."); 79 | // This can't be triggered because the rpc always points to a valid 80 | // function at this point because as soon as the object is deregistered 81 | // the RPCs are deregistered. 82 | NS_ASSERT_COND(od->rpcs_info[rpc_id].func); 83 | NS_ENSURE_MSG(validate_rpc_sender_receive(p_sender_peer, od->rpcs_info[rpc_id], od), "The RPC `"+std::to_string(rpc_id)+"` validation failed for the Object `"+std::to_string(od->get_net_id().id)+"#"+od->get_object_name()+"`, is the peer `"+std::to_string(p_sender_peer)+"` cheating?"); 84 | od->rpcs_info[rpc_id].func(p_db); 85 | } else { 86 | // The rpc was not delivered because the object is not spawned yet, 87 | // Notify the network synchronizer. 88 | scene_synchronizer->notify_undelivered_rpc(target_id, rpc_id, p_sender_peer, p_db); 89 | } 90 | } else { 91 | NS_ENSURE_MSG(rpc_id < rpcs_info.size(), "The received rpc contains a broken RPC ID: `" + std::to_string(rpc_id) + "`, the `rpcs_info` size is `" + std::to_string(rpcs_info.size()) + "`."); 92 | // This can't be triggered because the rpc always points to a valid 93 | // function at this point. 94 | NS_ASSERT_COND(rpcs_info[rpc_id].func); 95 | NS_ENSURE_MSG(validate_rpc_sender_receive(p_sender_peer, rpcs_info[rpc_id], nullptr), "The RPC `"+std::to_string(rpc_id)+"` validation failed for the SceneSynchronizer RPC, is the peer `"+std::to_string(p_sender_peer)+"` cheating?"); 96 | rpcs_info[rpc_id].func(p_db); 97 | } 98 | } 99 | 100 | void NetworkInterface::__fetch_rpc_info_from_object( 101 | ObjectLocalId p_id, 102 | int p_rpc_index, 103 | ObjectNetId &r_net_id, 104 | RPCInfo *&r_rpc_info) const { 105 | r_net_id = ObjectNetId::NONE; 106 | 107 | if (ObjectData *od = scene_synchronizer->get_object_data(p_id)) { 108 | if (od->rpcs_info.size() > p_rpc_index) { 109 | r_net_id = od->get_net_id(); 110 | r_rpc_info = &od->rpcs_info[p_rpc_index]; 111 | } 112 | } 113 | } 114 | 115 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/network_interface_define.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include "data_buffer.h" 5 | #include 6 | 7 | NS_NAMESPACE_BEGIN 8 | struct RPCInfo { 9 | bool is_reliable = false; 10 | bool call_local = false; 11 | RpcAllowedSender allowed_sender; 12 | std::function func; 13 | }; 14 | 15 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/object_data.cpp: -------------------------------------------------------------------------------- 1 | #include "object_data.h" 2 | 3 | #include "core.h" 4 | #include "ensure.h" 5 | #include "object_data_storage.h" 6 | #include "peer_networked_controller.h" 7 | #include "../scene_synchronizer.h" 8 | 9 | NS_NAMESPACE_BEGIN 10 | NameAndVar::NameAndVar(NameAndVar &&p_other) : 11 | name(std::move(p_other.name)), 12 | value(std::move(p_other.value)) { 13 | } 14 | 15 | NameAndVar &NameAndVar::operator=(NameAndVar &&p_other) { 16 | name = std::move(p_other.name); 17 | value = std::move(p_other.value); 18 | return *this; 19 | } 20 | 21 | void NameAndVar::copy(const NameAndVar &p_nav) { 22 | name = p_nav.name; 23 | value.copy(p_nav.value); 24 | } 25 | 26 | NameAndVar NameAndVar::make_copy(const NameAndVar &p_other) { 27 | NameAndVar named_var; 28 | named_var.name = p_other.name; 29 | named_var.value.copy(p_other.value); 30 | return named_var; 31 | } 32 | 33 | VarDescriptor::VarDescriptor( 34 | VarId p_id, 35 | const std::string &p_name, 36 | std::uint8_t p_type, 37 | VarData &&p_val, 38 | NS_VarDataSetFunc p_set_func, 39 | NS_VarDataGetFunc p_get_func, 40 | bool p_skip_rewinding, 41 | bool p_enabled) : 42 | id(p_id), 43 | type(p_type), 44 | set_func(p_set_func), 45 | get_func(p_get_func), 46 | skip_rewinding(p_skip_rewinding), 47 | enabled(p_enabled) { 48 | var.name = p_name; 49 | var.value = std::move(p_val); 50 | NS_ASSERT_COND(id != NS::VarId::NONE); 51 | NS_ASSERT_COND_MSG(set_func, "Please ensure that all the functions have a valid set function."); 52 | NS_ASSERT_COND_MSG(get_func, "Please ensure that all the functions have a valid get function."); 53 | } 54 | 55 | ObjectData::ObjectData(ObjectDataStorage &p_storage) : 56 | storage(p_storage) { 57 | } 58 | 59 | void ObjectData::flush_everything_registered() { 60 | vars.clear(); 61 | for (int process_phase = PROCESS_PHASE_EARLY; process_phase < PROCESS_PHASE_COUNT; ++process_phase) { 62 | functions[process_phase].clear(); 63 | } 64 | scheduled_procedures.clear(); 65 | rpcs_info.clear(); 66 | controller_funcs.collect_input = nullptr; 67 | controller_funcs.are_inputs_different = nullptr; 68 | controller_funcs.process = nullptr; 69 | } 70 | 71 | void ObjectData::set_net_id(ObjectNetId p_id) { 72 | storage.object_set_net_id(*this, p_id); 73 | } 74 | 75 | ObjectNetId ObjectData::get_net_id() const { 76 | return net_id; 77 | } 78 | 79 | ObjectLocalId ObjectData::get_local_id() const { 80 | return local_id; 81 | } 82 | 83 | bool ObjectData::has_registered_process_functions() const { 84 | for (int process_phase = PROCESS_PHASE_EARLY; process_phase < PROCESS_PHASE_COUNT; ++process_phase) { 85 | if (functions[process_phase].size() > 0) { 86 | return true; 87 | } 88 | } 89 | 90 | if (controller_funcs.collect_input) { 91 | return true; 92 | } 93 | 94 | return false; 95 | } 96 | 97 | bool ObjectData::can_trickled_sync() const { 98 | return func_trickled_collect && func_trickled_apply; 99 | } 100 | 101 | void ObjectData::setup_controller( 102 | std::function p_collect_input_func, 103 | std::function p_are_inputs_different_func, 104 | std::function p_process_func) { 105 | controller_funcs.collect_input = p_collect_input_func; 106 | controller_funcs.are_inputs_different = p_are_inputs_different_func; 107 | controller_funcs.process = p_process_func; 108 | } 109 | 110 | bool ObjectData::set_controlled_by_peer(SceneSynchronizerBase &synchronizer, int p_peer) { 111 | if (p_peer == controlled_by_peer) { 112 | return false; 113 | } 114 | 115 | const int old_peer = controlled_by_peer; 116 | controlled_by_peer = p_peer; 117 | storage.notify_set_controlled_by_peer(old_peer, *this); 118 | 119 | if (old_peer > 0) { 120 | PeerNetworkedController *prev_controller = synchronizer.get_controller_for_peer(old_peer); 121 | if (prev_controller) { 122 | prev_controller->notify_controllable_objects_changed(); 123 | } 124 | } 125 | 126 | if (p_peer > 0) { 127 | PeerNetworkedController *controller = synchronizer.get_controller_for_peer(p_peer); 128 | if (controller) { 129 | controller->notify_controllable_objects_changed(); 130 | } 131 | } 132 | 133 | if (synchronizer.get_synchronizer_internal()) { 134 | synchronizer.get_synchronizer_internal()->on_object_data_controller_changed(*this, old_peer); 135 | } 136 | 137 | return true; 138 | } 139 | 140 | int ObjectData::get_controlled_by_peer() const { 141 | return controlled_by_peer; 142 | } 143 | 144 | void ObjectData::set_object_name(const std::string &name, bool p_force_set) { 145 | if (name == object_name && !p_force_set) { 146 | return; 147 | } 148 | bool need_update = object_name.empty(); 149 | object_name = name; 150 | need_update = need_update || object_name.empty(); 151 | if (need_update) { 152 | storage.notify_object_name_unnamed_changed(*this); 153 | } 154 | } 155 | 156 | const std::string &ObjectData::get_object_name() const { 157 | return object_name; 158 | } 159 | 160 | VarId ObjectData::find_variable_id(const std::string &p_var_name) const { 161 | for (const auto &v : vars) { 162 | if (v.var.name == p_var_name) { 163 | return v.id; 164 | } 165 | } 166 | 167 | return VarId::NONE; 168 | } 169 | 170 | ScheduledProcedureId ObjectData::scheduled_procedure_add(NS_ScheduledProcedureFunc p_func) { 171 | ScheduledProcedureId id = ScheduledProcedureId::NONE; 172 | 173 | for (int i = 0; i < scheduled_procedures.size(); i++) { 174 | if (scheduled_procedures[i].func == nullptr) { 175 | scheduled_procedures[i].func = p_func; 176 | id.id = ScheduledProcedureId::IdType(i); 177 | return id; 178 | } 179 | } 180 | 181 | NS_ASSERT_COND(scheduled_procedures.size() < std::numeric_limits::max()); 182 | id.id = ScheduledProcedureId::IdType(scheduled_procedures.size()); 183 | scheduled_procedures.push_back(ScheduledProcedureInfo{ p_func }); 184 | 185 | return id; 186 | } 187 | 188 | bool ObjectData::scheduled_procedure_exist(ScheduledProcedureId p_id) const { 189 | return p_id.id < scheduled_procedures.size() && scheduled_procedures[p_id.id].func != nullptr; 190 | } 191 | 192 | void ObjectData::scheduled_procedure_remove(ScheduledProcedureId p_id) { 193 | scheduled_procedures[p_id.id].func = nullptr; 194 | scheduled_procedures[p_id.id].execute_frame = GlobalFrameIndex{ 0 }; 195 | scheduled_procedures[p_id.id].paused_frame = GlobalFrameIndex{ 0 }; 196 | scheduled_procedures[p_id.id].args = DataBuffer(); 197 | storage.notify_scheduled_procedure_updated(*this, p_id, false); 198 | } 199 | 200 | 201 | void ObjectData::scheduled_procedure_fetch_args(ScheduledProcedureId p_id, const SynchronizerManager &p_sync_manager, SceneSynchronizerDebugger &p_debugger) { 202 | scheduled_procedures[p_id.id].args.begin_write(p_debugger, 0); 203 | scheduled_procedures[p_id.id].func( 204 | p_sync_manager, 205 | app_object_handle, 206 | ScheduledProcedurePhase::COLLECTING_ARGUMENTS, 207 | scheduled_procedures[p_id.id].args); 208 | #ifdef NS_DEBUG_ENABLED 209 | NS_ASSERT_COND(!scheduled_procedures[p_id.id].args.is_buffer_failed()); 210 | #endif 211 | } 212 | 213 | void ObjectData::scheduled_procedure_set_args(ScheduledProcedureId p_id, const DataBuffer &p_args) { 214 | scheduled_procedures[p_id.id].args.copy(p_args); 215 | } 216 | 217 | void ObjectData::scheduled_procedure_reset_to(ScheduledProcedureId p_id, const ScheduledProcedureSnapshot &p_snapshot) { 218 | if (p_snapshot.execute_frame.id != 0 && p_snapshot.paused_frame.id == 0) { 219 | scheduled_procedure_set_args(p_id, p_snapshot.args); 220 | scheduled_procedure_start(p_id, p_snapshot.execute_frame); 221 | } else if (p_snapshot.paused_frame.id != 0) { 222 | scheduled_procedure_pause(p_id, p_snapshot.execute_frame, p_snapshot.paused_frame); 223 | } else { 224 | scheduled_procedure_stop(p_id); 225 | } 226 | } 227 | 228 | void ObjectData::scheduled_procedure_execute(ScheduledProcedureId p_id, ScheduledProcedurePhase p_phase, const SynchronizerManager &p_sync_manager, SceneSynchronizerDebugger &p_debugger) { 229 | #ifdef NS_DEBUG_ENABLED 230 | NS_ASSERT_COND(!scheduled_procedures[p_id.id].args.is_buffer_failed()); 231 | #endif 232 | scheduled_procedures[p_id.id].args.begin_read(p_debugger); 233 | scheduled_procedures[p_id.id].func( 234 | p_sync_manager, 235 | app_object_handle, 236 | p_phase, 237 | scheduled_procedures[p_id.id].args); 238 | } 239 | 240 | void ObjectData::scheduled_procedure_start(ScheduledProcedureId p_id, GlobalFrameIndex p_executes_at_frame) { 241 | scheduled_procedures[p_id.id].execute_frame = p_executes_at_frame; 242 | scheduled_procedures[p_id.id].paused_frame = GlobalFrameIndex{ 0 }; 243 | storage.notify_scheduled_procedure_updated(*this, p_id, true); 244 | } 245 | 246 | void ObjectData::scheduled_procedure_pause(ScheduledProcedureId p_id, GlobalFrameIndex p_current_frame) { 247 | scheduled_procedure_pause(p_id, scheduled_procedures[p_id.id].execute_frame, p_current_frame); 248 | } 249 | 250 | void ObjectData::scheduled_procedure_pause(ScheduledProcedureId p_id, GlobalFrameIndex p_executes_at_frame, GlobalFrameIndex p_current_frame) { 251 | scheduled_procedures[p_id.id].execute_frame = p_executes_at_frame; 252 | scheduled_procedures[p_id.id].paused_frame = p_current_frame; 253 | storage.notify_scheduled_procedure_updated(*this, p_id, false); 254 | } 255 | 256 | void ObjectData::scheduled_procedure_stop(ScheduledProcedureId p_id) { 257 | scheduled_procedures[p_id.id].execute_frame = GlobalFrameIndex{ 0 }; 258 | scheduled_procedures[p_id.id].paused_frame = GlobalFrameIndex{ 0 }; 259 | scheduled_procedures[p_id.id].args.shrink_to(0, 0); 260 | storage.notify_scheduled_procedure_updated(*this, p_id, false); 261 | } 262 | 263 | bool ObjectData::scheduled_procedure_is_inprogress(ScheduledProcedureId p_id) const { 264 | return scheduled_procedures[p_id.id].paused_frame == GlobalFrameIndex{ 0 } 265 | && scheduled_procedures[p_id.id].execute_frame > GlobalFrameIndex{ 0 }; 266 | } 267 | 268 | bool ObjectData::scheduled_procedure_is_paused(ScheduledProcedureId p_id) const { 269 | return scheduled_procedures[p_id.id].paused_frame > GlobalFrameIndex{ 0 }; 270 | } 271 | 272 | std::uint32_t ObjectData::scheduled_procedure_remaining_frames(ScheduledProcedureId p_id, GlobalFrameIndex p_current_frame) const { 273 | if (scheduled_procedures[p_id.id].paused_frame.id > 0) { 274 | if (scheduled_procedures[p_id.id].paused_frame < scheduled_procedures[p_id.id].execute_frame) { 275 | return scheduled_procedures[p_id.id].execute_frame.id - scheduled_procedures[p_id.id].paused_frame.id; 276 | } 277 | } else if (scheduled_procedures[p_id.id].execute_frame.id > 0) { 278 | if (p_current_frame.id < scheduled_procedures[p_id.id].execute_frame.id) { 279 | return scheduled_procedures[p_id.id].execute_frame.id - p_current_frame.id; 280 | } 281 | } 282 | return 0; 283 | } 284 | 285 | bool ObjectData::scheduled_procedure_is_outdated(ScheduledProcedureId p_id, GlobalFrameIndex p_current_frame) const { 286 | if (scheduled_procedures[p_id.id].paused_frame.id > 0) { 287 | if (scheduled_procedures[p_id.id].paused_frame <= scheduled_procedures[p_id.id].execute_frame) { 288 | return false; 289 | } 290 | } else if (scheduled_procedures[p_id.id].execute_frame.id > 0) { 291 | if (p_current_frame.id <= scheduled_procedures[p_id.id].execute_frame.id) { 292 | return false; 293 | } 294 | } 295 | return true; 296 | } 297 | 298 | GlobalFrameIndex ObjectData::scheduled_procedure_get_execute_frame(ScheduledProcedureId p_id) const { 299 | if (scheduled_procedures[p_id.id].paused_frame.id == 0) { 300 | return scheduled_procedures[p_id.id].execute_frame; 301 | } 302 | return GlobalFrameIndex{ 0 }; 303 | } 304 | 305 | const DataBuffer &ObjectData::scheduled_procedure_get_args(ScheduledProcedureId p_id) const { 306 | return scheduled_procedures[p_id.id].args; 307 | } 308 | 309 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/object_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include "data_buffer.h" 5 | #include "network_interface_define.h" 6 | #include "processor.h" 7 | #include "var_data.h" 8 | #include 9 | #include 10 | #include 11 | 12 | NS_NAMESPACE_BEGIN 13 | struct NameAndVar { 14 | std::string name; 15 | VarData value; 16 | 17 | NameAndVar() = default; 18 | 19 | NameAndVar(const NameAndVar &) = delete; 20 | NameAndVar &operator=(const NameAndVar &) = delete; 21 | 22 | NameAndVar(NameAndVar &&p_other); 23 | NameAndVar &operator=(NameAndVar &&p_other); 24 | 25 | void copy(const NameAndVar &p_nav); 26 | static NameAndVar make_copy(const NameAndVar &p_other); 27 | }; 28 | 29 | struct VarDescriptor { 30 | const VarId id; 31 | NameAndVar var; 32 | /// The variable type. 33 | const std::uint8_t type; 34 | NS_VarDataSetFunc set_func = nullptr; 35 | NS_VarDataGetFunc get_func = nullptr; 36 | bool skip_rewinding = false; 37 | bool enabled = false; 38 | std::vector changes_listeners; 39 | 40 | VarDescriptor() = delete; 41 | VarDescriptor( 42 | VarId p_id, 43 | const std::string &p_name, 44 | std::uint8_t p_type, 45 | VarData &&p_val, 46 | NS_VarDataSetFunc p_set_func, 47 | NS_VarDataGetFunc p_get_func, 48 | bool p_skip_rewinding, 49 | bool p_enabled); 50 | }; 51 | 52 | struct ObjectData { 53 | friend class ObjectDataStorage; 54 | 55 | private: 56 | class ObjectDataStorage &storage; 57 | 58 | ObjectData(class ObjectDataStorage &p_storage); 59 | 60 | private: 61 | /// ID used to reference this ObjectData in the networked calls. 62 | /// This id is set by the server and the client may not have it yet. 63 | ObjectNetId net_id = ObjectNetId::NONE; 64 | 65 | /// ID used to reference this ObjectData locally. This id is always set. 66 | ObjectLocalId local_id = ObjectLocalId::NONE; 67 | 68 | // TODO consider uint8 or uint16 69 | int controlled_by_peer = -1; 70 | 71 | std::string object_name; 72 | 73 | /// The scheme_id is used to identify the type of object when the 74 | /// synchronized variables change dynamically based on the represented type. 75 | /// This function is very useful for synchronizing characters, since 76 | /// the class is the same but the synchronized variables change depending on 77 | /// the loaded abilities. 78 | SchemeId scheme_id = SchemeId::DEFAULT; 79 | 80 | public: 81 | struct { 82 | std::function collect_input; 83 | std::function are_inputs_different; 84 | std::function process; 85 | } controller_funcs; 86 | 87 | public: 88 | #ifdef NS_DEBUG_ENABLED 89 | uint64_t debug_object_id = 0; 90 | #endif 91 | // The local application object handle associated to this `NodeData`. 92 | ObjectHandle app_object_handle = ObjectHandle::NONE; 93 | 94 | bool realtime_sync_enabled_on_client = false; 95 | 96 | /// The sync variables of this node. The order of this vector matters 97 | /// because the index is the `VarId`. 98 | std::vector vars; 99 | Processor functions[PROCESS_PHASE_COUNT]; 100 | 101 | struct ScheduledProcedureInfo { 102 | NS_ScheduledProcedureFunc func = nullptr; 103 | GlobalFrameIndex execute_frame = GlobalFrameIndex{ 0 }; 104 | GlobalFrameIndex paused_frame = GlobalFrameIndex{ 0 }; 105 | DataBuffer args; 106 | }; 107 | 108 | private: 109 | std::vector scheduled_procedures; 110 | 111 | public: 112 | std::vector rpcs_info; 113 | 114 | public: 115 | std::function func_trickled_collect; 116 | std::function func_trickled_apply; 117 | 118 | public: 119 | void flush_everything_registered(); 120 | 121 | void set_net_id(ObjectNetId p_id); 122 | ObjectNetId get_net_id() const; 123 | 124 | ObjectLocalId get_local_id() const; 125 | 126 | void set_scheme_id(SchemeId p_scheme_id) { 127 | scheme_id = p_scheme_id; 128 | } 129 | 130 | SchemeId get_scheme_id() const { 131 | return scheme_id; 132 | } 133 | 134 | bool has_registered_process_functions() const; 135 | bool can_trickled_sync() const; 136 | 137 | void setup_controller( 138 | std::function p_collect_input_func = nullptr, 139 | std::function p_are_inputs_different_func = nullptr, 140 | std::function p_process_func = nullptr); 141 | bool set_controlled_by_peer(class SceneSynchronizerBase &Synchronizer, int p_peer); 142 | int get_controlled_by_peer() const; 143 | 144 | void set_object_name(const std::string &name, bool p_force_set = false); 145 | const std::string &get_object_name() const; 146 | 147 | VarId find_variable_id(const std::string &p_var_name) const; 148 | 149 | /// Adds a new scheduled procedure and returns its handle. 150 | ScheduledProcedureId scheduled_procedure_add(NS_ScheduledProcedureFunc p_func); 151 | /// Returns true if the ScheduledProcedureId points to a valid procedure. 152 | bool scheduled_procedure_exist(ScheduledProcedureId p_id) const; 153 | /// Removes a procedure. 154 | void scheduled_procedure_remove(ScheduledProcedureId p_id); 155 | 156 | /// Calls the procedure and initialize the args. This is usually called on the server. 157 | void scheduled_procedure_fetch_args(ScheduledProcedureId p_id, const SynchronizerManager &p_sync_manager, SceneSynchronizerDebugger &p_debugger); 158 | void scheduled_procedure_set_args(ScheduledProcedureId p_id, const DataBuffer &p_args); 159 | void scheduled_procedure_reset_to(ScheduledProcedureId p_id, const struct ScheduledProcedureSnapshot &p_snapshot); 160 | 161 | void scheduled_procedure_execute(ScheduledProcedureId p_id, ScheduledProcedurePhase p_phase, const SynchronizerManager &p_sync_manager, SceneSynchronizerDebugger &p_debugger); 162 | 163 | /// Starts a procedure. Notice this function calls the procedure to initialize the args DataBuffer. 164 | void scheduled_procedure_start(ScheduledProcedureId p_id, GlobalFrameIndex p_executes_at_frame); 165 | /// Pause the procedure. 166 | void scheduled_procedure_pause(ScheduledProcedureId p_id, GlobalFrameIndex p_current_frame); 167 | void scheduled_procedure_pause(ScheduledProcedureId p_id, GlobalFrameIndex p_executes_at_frame, GlobalFrameIndex p_current_frame); 168 | /// Stop the procedure. 169 | void scheduled_procedure_stop(ScheduledProcedureId p_id); 170 | 171 | /// Returns true if the procedure is paused. 172 | bool scheduled_procedure_is_inprogress(ScheduledProcedureId p_id) const; 173 | bool scheduled_procedure_is_paused(ScheduledProcedureId p_id) const; 174 | /// Returns the remaining frames of this procedure according to its status (Playing, Paused, Stop) 175 | std::uint32_t scheduled_procedure_remaining_frames(ScheduledProcedureId p_id, GlobalFrameIndex p_current_frame) const; 176 | /// Returns true when the procedure is outdated 177 | bool scheduled_procedure_is_outdated(ScheduledProcedureId p_id, GlobalFrameIndex p_current_frame) const; 178 | 179 | GlobalFrameIndex scheduled_procedure_get_execute_frame(ScheduledProcedureId p_id) const; 180 | const DataBuffer &scheduled_procedure_get_args(ScheduledProcedureId p_id) const; 181 | 182 | const std::vector &get_scheduled_procedures() const { 183 | return scheduled_procedures; 184 | } 185 | }; 186 | 187 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/object_data_storage.cpp: -------------------------------------------------------------------------------- 1 | #include "object_data_storage.h" 2 | 3 | #include "../scene_synchronizer.h" 4 | #include "ensure.h" 5 | #include "scene_synchronizer_debugger.h" 6 | #include // memset 7 | 8 | NS_NAMESPACE_BEGIN 9 | ObjectDataStorage::ObjectDataStorage(SceneSynchronizerBase &p_sync) : 10 | sync(p_sync) { 11 | } 12 | 13 | ObjectDataStorage::~ObjectDataStorage() { 14 | for (auto od : objects_data) { 15 | if (od) { 16 | delete od; 17 | } 18 | } 19 | 20 | free_local_indices.clear(); 21 | objects_data.clear(); 22 | objects_data_organized_by_netid.clear(); 23 | objects_data_controlled_by_peers.clear(); 24 | } 25 | 26 | SceneSynchronizerDebugger &ObjectDataStorage::get_debugger() const { 27 | return sync.get_debugger(); 28 | } 29 | 30 | ObjectData *ObjectDataStorage::allocate_object_data() { 31 | ObjectData *od = new ObjectData(*this); 32 | 33 | if (free_local_indices.empty()) { 34 | od->local_id.id = ObjectLocalId::IdType(objects_data.size()); 35 | objects_data.push_back(od); 36 | } else { 37 | od->local_id.id = free_local_indices.back().id; 38 | free_local_indices.pop_back(); 39 | NS_ASSERT_COND(objects_data.size() > od->local_id.id); 40 | NS_ASSERT_COND(objects_data[od->local_id.id] == nullptr); 41 | objects_data[od->local_id.id] = od; 42 | } 43 | 44 | NS_ASSERT_COND(objects_data[od->local_id.id] == od); 45 | 46 | return od; 47 | } 48 | 49 | void ObjectDataStorage::deallocate_object_data(ObjectData &p_object_data) { 50 | const ObjectLocalId local_id = p_object_data.local_id; 51 | const ObjectNetId net_id = p_object_data.net_id; 52 | 53 | // The allocate function guarantee the validity of this check. 54 | NS_ASSERT_COND(objects_data[local_id.id] == (&p_object_data)); 55 | objects_data[local_id.id] = nullptr; 56 | 57 | if (objects_data_organized_by_netid.size() > net_id.id) { 58 | NS_ASSERT_COND(objects_data_organized_by_netid[net_id.id] == (&p_object_data)); 59 | objects_data_organized_by_netid[net_id.id] = nullptr; 60 | } 61 | 62 | // Clear the peers array. 63 | const int cbp = p_object_data.controlled_by_peer; 64 | p_object_data.controlled_by_peer = -1; 65 | notify_set_controlled_by_peer(cbp, p_object_data); 66 | 67 | // Remove from unnamed_objects_data if set 68 | VecFunc::remove_unordered(unnamed_objects_data, &p_object_data); 69 | 70 | // Clear the active procedures. 71 | for (int i = int(sorted_active_scheduled_procedures.size()) - 1; i >= 0; i--) { 72 | if (sorted_active_scheduled_procedures[i].get_object_net_id() == p_object_data.get_net_id()) { 73 | VecFunc::remove_at(sorted_active_scheduled_procedures, i); 74 | } 75 | } 76 | 77 | delete (&p_object_data); 78 | 79 | free_local_indices.push_back(local_id); 80 | } 81 | 82 | void ObjectDataStorage::object_set_net_id(ObjectData &p_object_data, ObjectNetId p_new_id) { 83 | if (p_object_data.net_id == p_new_id) { 84 | return; 85 | } 86 | 87 | if (objects_data_organized_by_netid.size() > p_object_data.net_id.id) { 88 | objects_data_organized_by_netid[p_object_data.net_id.id] = nullptr; 89 | } 90 | 91 | p_object_data.net_id = ObjectNetId::NONE; 92 | 93 | if (p_new_id == ObjectNetId::NONE) { 94 | sync.notify_object_data_net_id_changed(p_object_data); 95 | return; 96 | } 97 | 98 | if (objects_data_organized_by_netid.size() > p_new_id.id) { 99 | if (objects_data_organized_by_netid[p_new_id.id] && objects_data_organized_by_netid[p_new_id.id] != (&p_object_data)) { 100 | get_debugger().print(ERROR, "[NET] The object `" + p_object_data.object_name + "` was associated with to a new NetId that was used by `" + objects_data_organized_by_netid[p_new_id.id]->object_name + "`. THIS IS NOT SUPPOSED TO HAPPEN."); 101 | } 102 | } else { 103 | // Expand the array 104 | const std::size_t new_size = p_new_id.id + 1; 105 | const std::size_t old_size = objects_data_organized_by_netid.size(); 106 | objects_data_organized_by_netid.resize(new_size); 107 | // Set the new pointers to nullptr. 108 | memset(objects_data_organized_by_netid.data() + old_size, 0, sizeof(void *) * (new_size - old_size)); 109 | } 110 | 111 | objects_data_organized_by_netid[p_new_id.id] = &p_object_data; 112 | p_object_data.net_id = p_new_id; 113 | sync.notify_object_data_net_id_changed(p_object_data); 114 | } 115 | 116 | ObjectLocalId ObjectDataStorage::find_object_local_id(ObjectHandle p_handle) const { 117 | for (auto od : objects_data) { 118 | if (od && od->app_object_handle == p_handle) { 119 | return od->local_id; 120 | } 121 | } 122 | return ObjectLocalId::NONE; 123 | } 124 | 125 | NS::ObjectData *ObjectDataStorage::get_object_data(ObjectNetId p_net_id, bool p_expected) { 126 | if (p_expected) { 127 | NS_ENSURE_V_MSG(p_net_id.id < objects_data_organized_by_netid.size(), nullptr, "The ObjectData with NetID `" + std::to_string(p_net_id.id) + "` was not found."); 128 | } else { 129 | if (objects_data_organized_by_netid.size() <= p_net_id.id) { 130 | return nullptr; 131 | } 132 | } 133 | 134 | return objects_data_organized_by_netid[p_net_id.id]; 135 | } 136 | 137 | const NS::ObjectData *ObjectDataStorage::get_object_data(ObjectNetId p_net_id, bool p_expected) const { 138 | if (p_expected) { 139 | NS_ENSURE_V_MSG(p_net_id.id < objects_data_organized_by_netid.size(), nullptr, "The ObjectData with NetID `" + std::to_string(p_net_id.id) + "` was not found."); 140 | } else { 141 | if (objects_data_organized_by_netid.size() <= p_net_id.id) { 142 | return nullptr; 143 | } 144 | } 145 | 146 | return objects_data_organized_by_netid[p_net_id.id]; 147 | } 148 | 149 | NS::ObjectData *ObjectDataStorage::get_object_data(ObjectLocalId p_handle, bool p_expected) { 150 | if (p_expected) { 151 | NS_ENSURE_V_MSG(p_handle.id < objects_data.size(), nullptr, "The ObjectData with LocalID `" + std::to_string(p_handle.id) + "` was not found."); 152 | } else { 153 | if (p_handle.id < 0 || objects_data.size() <= p_handle.id) { 154 | return nullptr; 155 | } 156 | } 157 | 158 | return objects_data[p_handle.id]; 159 | } 160 | 161 | const ObjectData *ObjectDataStorage::get_object_data(ObjectLocalId p_handle, bool p_expected) const { 162 | if (p_expected) { 163 | NS_ENSURE_V_MSG(p_handle.id < objects_data.size(), nullptr, "The ObjectData with LocalID `" + std::to_string(p_handle.id) + "` was not found."); 164 | } else { 165 | if (p_handle.id < 0 || objects_data.size() <= p_handle.id) { 166 | return nullptr; 167 | } 168 | } 169 | 170 | return objects_data[p_handle.id]; 171 | } 172 | 173 | void ObjectDataStorage::reserve_net_ids(int p_count) { 174 | objects_data_organized_by_netid.reserve(p_count); 175 | } 176 | 177 | const std::vector &ObjectDataStorage::get_objects_data() const { 178 | return objects_data; 179 | } 180 | 181 | const std::vector &ObjectDataStorage::get_sorted_objects_data() const { 182 | return objects_data_organized_by_netid; 183 | } 184 | 185 | const std::map> &ObjectDataStorage::get_peers_controlled_objects_data() const { 186 | return objects_data_controlled_by_peers; 187 | } 188 | 189 | const std::vector *ObjectDataStorage::get_peer_controlled_objects_data(int p_peer) const { 190 | return MapFunc::get_or_null(objects_data_controlled_by_peers, p_peer); 191 | } 192 | 193 | const std::vector &ObjectDataStorage::get_unnamed_objects_data() const { 194 | return unnamed_objects_data; 195 | } 196 | 197 | ObjectNetId ObjectDataStorage::generate_net_id() const { 198 | ObjectNetId::IdType i = 0; 199 | for (auto od : objects_data_organized_by_netid) { 200 | if (!od) { 201 | // This position is empty, can be used as NetId. 202 | return ObjectNetId{ { i } }; 203 | } 204 | i++; 205 | } 206 | 207 | // Create a new NetId. 208 | return ObjectNetId{ { ObjectNetId::IdType(objects_data_organized_by_netid.size()) } }; 209 | } 210 | 211 | bool ObjectDataStorage::is_empty() const { 212 | for (auto od : objects_data) { 213 | if (od) { 214 | return false; 215 | } 216 | } 217 | 218 | return true; 219 | } 220 | 221 | void ObjectDataStorage::notify_set_controlled_by_peer(int p_old_peer, ObjectData &p_object) { 222 | if (p_old_peer != -1) { 223 | std::vector *objects = MapFunc::get_or_null(objects_data_controlled_by_peers, p_old_peer); 224 | if (objects) { 225 | VecFunc::remove_unordered(*objects, &p_object); 226 | } 227 | } 228 | 229 | if (p_object.get_controlled_by_peer() != -1) { 230 | std::map>::iterator objects_it = NS::MapFunc::insert_if_new( 231 | objects_data_controlled_by_peers, 232 | p_object.get_controlled_by_peer(), 233 | std::vector()); 234 | VecFunc::insert_unique(objects_it->second, &p_object); 235 | } 236 | } 237 | 238 | void ObjectDataStorage::notify_object_name_unnamed_changed(ObjectData &p_object) { 239 | if (p_object.object_name.empty()) { 240 | VecFunc::insert_unique(unnamed_objects_data, &p_object); 241 | } else { 242 | VecFunc::remove_unordered(unnamed_objects_data, &p_object); 243 | } 244 | } 245 | 246 | void ObjectDataStorage::notify_scheduled_procedure_updated(ObjectData &p_object, const ScheduledProcedureId p_procedure_id, bool p_active) { 247 | if (p_active) { 248 | if (!VecFunc::has(sorted_active_scheduled_procedures, ScheduledProcedureHandle(p_object.get_net_id(), p_procedure_id))) { 249 | VecFunc::insert_sorted(sorted_active_scheduled_procedures, ScheduledProcedureHandle(p_object.get_net_id(), p_procedure_id)); 250 | } 251 | } else { 252 | VecFunc::remove(sorted_active_scheduled_procedures, ScheduledProcedureHandle(p_object.get_net_id(), p_procedure_id)); 253 | } 254 | } 255 | 256 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/object_data_storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include "scheduled_procedure.h" 5 | 6 | #include 7 | #include 8 | 9 | NS_NAMESPACE_BEGIN 10 | class ObjectDataStorage { 11 | class SceneSynchronizerBase &sync; 12 | 13 | std::vector free_local_indices; 14 | 15 | // All allocated object data. 16 | std::vector objects_data; 17 | 18 | // All registered objects, that have the NetId assigned, organized per NetId. 19 | std::vector objects_data_organized_by_netid; 20 | 21 | std::map> objects_data_controlled_by_peers; 22 | 23 | std::vector unnamed_objects_data; 24 | 25 | std::vector sorted_active_scheduled_procedures; 26 | 27 | public: 28 | ObjectDataStorage(class SceneSynchronizerBase &p_sync); 29 | ~ObjectDataStorage(); 30 | 31 | class SceneSynchronizerDebugger &get_debugger() const; 32 | 33 | ObjectData *allocate_object_data(); 34 | void deallocate_object_data(ObjectData &p_object_data); 35 | 36 | void object_set_net_id(ObjectData &p_object_data, ObjectNetId p_new_id); 37 | 38 | ObjectLocalId find_object_local_id(ObjectHandle p_handle) const; 39 | 40 | ObjectData *get_object_data(ObjectNetId p_net_id, bool p_expected = true); 41 | const ObjectData *get_object_data(ObjectNetId p_net_id, bool p_expected = true) const; 42 | 43 | ObjectData *get_object_data(ObjectLocalId p_handle, bool p_expected = true); 44 | const ObjectData *get_object_data(ObjectLocalId p_handle, bool p_expected = true) const; 45 | 46 | void reserve_net_ids(int p_count); 47 | 48 | const std::vector &get_objects_data() const; 49 | const std::vector &get_sorted_objects_data() const; 50 | const std::map> &get_peers_controlled_objects_data() const; 51 | const std::vector *get_peer_controlled_objects_data(int p_peer) const; 52 | const std::vector &get_unnamed_objects_data() const; 53 | 54 | ObjectNetId generate_net_id() const; 55 | bool is_empty() const; 56 | 57 | void notify_set_controlled_by_peer(int p_old_peer, ObjectData &p_object); 58 | 59 | void notify_object_name_unnamed_changed(ObjectData &p_object); 60 | 61 | void notify_scheduled_procedure_updated(ObjectData &p_object, const ScheduledProcedureId p_procedure_id, bool p_active); 62 | 63 | const std::vector &get_sorted_active_scheduled_procedures() const { 64 | return sorted_active_scheduled_procedures; 65 | } 66 | }; 67 | 68 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/peer_data.cpp: -------------------------------------------------------------------------------- 1 | #include "peer_data.h" 2 | 3 | #include 4 | #include 5 | 6 | void NS::PeerData::set_latency(float p_latency) { 7 | compressed_latency = (std::uint8_t)std::round(std::clamp(p_latency, 0.f, 1000.0f) / 4.0f); 8 | } 9 | 10 | float NS::PeerData::get_latency() const { 11 | return compressed_latency * 4.0f; 12 | } 13 | 14 | void NS::PeerData::set_out_packet_loss_percentage(float p_packet_loss) { 15 | out_packet_loss_percentage = std::clamp(p_packet_loss, 0.0f, 1.0f); 16 | } 17 | 18 | void NS::PeerData::make_controller(SceneSynchronizerBase &p_scene_synchronizer) { 19 | controller = std::make_unique(p_scene_synchronizer); 20 | } 21 | -------------------------------------------------------------------------------- /core/peer_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include "peer_networked_controller.h" 5 | 6 | NS_NAMESPACE_BEGIN 7 | // These data are used by the server and are never synchronized. 8 | struct PeerAuthorityData { 9 | // Used to know if the peer is enabled. 10 | bool enabled = true; 11 | 12 | // The Sync group this peer is in. 13 | SyncGroupId sync_group_id = SyncGroupId::GLOBAL; 14 | }; 15 | 16 | struct PeerData { 17 | std::unique_ptr controller; 18 | 19 | PeerAuthorityData authority_data; 20 | 21 | private: 22 | /// Get latency (ping): The round trip time a packet takes to go and return back. 23 | std::uint8_t compressed_latency = 0; 24 | 25 | /// Get OUT packetloss in % 26 | float out_packet_loss_percentage = 0.0f; 27 | 28 | /// Current jitter for this connection in milliseconds. 29 | /// Jitter represents the average time divergence of all sent packets. 30 | /// Ex: 31 | /// - If the time between the sending and the reception of packets is always 32 | /// 100ms; the jitter will be 0. 33 | /// - If the time difference is either 150ms or 100ms, the jitter will tend 34 | /// towards 50ms. 35 | float latency_jitter_ms = 0.0f; 36 | 37 | public: 38 | // These constructors were added to avoid the std::map to complain about the 39 | // PeerData inability to be copied. I was unable to figure out why std::move 40 | // stop working and didn't have the time to figure it out. 41 | // TODO consider to fix this --^ 42 | PeerData() = default; 43 | 44 | PeerData(const PeerData &other) : 45 | authority_data(other.authority_data), 46 | compressed_latency(other.compressed_latency), 47 | out_packet_loss_percentage(other.out_packet_loss_percentage), 48 | latency_jitter_ms(other.latency_jitter_ms) { 49 | } 50 | 51 | PeerData &operator=(const PeerData &other) noexcept { 52 | authority_data = other.authority_data; 53 | compressed_latency = other.compressed_latency; 54 | out_packet_loss_percentage = other.out_packet_loss_percentage; 55 | latency_jitter_ms = other.latency_jitter_ms; 56 | return *this; 57 | } 58 | 59 | public: 60 | // In ms 61 | void set_latency(float p_ping); 62 | 63 | // In ms 64 | float get_latency() const; 65 | 66 | void set_compressed_latency(std::uint8_t p_compressed_latency) { 67 | compressed_latency = p_compressed_latency; 68 | } 69 | 70 | std::uint8_t get_compressed_latency() const { 71 | return compressed_latency; 72 | } 73 | 74 | void set_out_packet_loss_percentage(float p_packet_loss); 75 | 76 | float get_out_packet_loss_percentage() const { 77 | return out_packet_loss_percentage; 78 | } 79 | 80 | void set_latency_jitter_ms(float p_jitter_ms) { 81 | latency_jitter_ms = p_jitter_ms; 82 | } 83 | 84 | float get_latency_jitter_ms() const { 85 | return latency_jitter_ms; 86 | } 87 | 88 | void make_controller(SceneSynchronizerBase &p_scene_synchronizer); 89 | 90 | PeerNetworkedController *get_controller() { 91 | return controller.get(); 92 | } 93 | 94 | const PeerNetworkedController *get_controller() const { 95 | return controller.get(); 96 | } 97 | }; 98 | 99 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/processor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include 5 | 6 | NS_NAMESPACE_BEGIN 7 | 8 | typedef int PHandler; 9 | static inline PHandler NullPHandler = -1; 10 | 11 | // A simple but yet effective event system. 12 | template 13 | class Processor { 14 | typedef void(func_sub_type)(ARGS...); 15 | typedef std::function func_type; 16 | 17 | int index_counter = 0; 18 | struct ProcessorData { 19 | PHandler handler; 20 | func_type function; 21 | }; 22 | 23 | std::vector binded_functions; 24 | 25 | public: 26 | /// Bind a function and returns it's handler. 27 | PHandler bind(func_type p_func); 28 | void append(const Processor &p_other, std::vector *p_added_handlers = nullptr); 29 | void unbind(PHandler p_handler); 30 | bool is_bind(PHandler p_handler) const; 31 | void clear(); 32 | void broadcast(ARGS... p_args); 33 | 34 | int find_function(PHandler p_handler) const; 35 | int size() const; 36 | }; 37 | 38 | template 39 | PHandler Processor::bind(func_type p_func) { 40 | // Make sure this function was not bind already. 41 | const PHandler handler = index_counter; 42 | binded_functions.push_back({ handler, p_func }); 43 | index_counter += 1; 44 | return handler; 45 | } 46 | 47 | template 48 | void Processor::append(const Processor &p_other, std::vector *p_added_handlers) { 49 | for (auto &func_data : p_other.binded_functions) { 50 | const PHandler handler = bind(func_data.function); 51 | if (p_added_handlers) { 52 | p_added_handlers->push_back(handler); 53 | } 54 | } 55 | } 56 | 57 | template 58 | void Processor::unbind(PHandler p_handler) { 59 | const int index = find_function(p_handler); 60 | if (index >= 0) { 61 | auto it = binded_functions.begin() + index; 62 | binded_functions.erase(it); 63 | } 64 | } 65 | 66 | template 67 | bool Processor::is_bind(PHandler p_handler) const { 68 | return find_function(p_handler) != NullPHandler; 69 | } 70 | 71 | template 72 | void Processor::clear() { 73 | binded_functions.clear(); 74 | index_counter = 0; 75 | } 76 | 77 | template 78 | void Processor::broadcast(ARGS... p_args) { 79 | for (auto &func_data : binded_functions) { 80 | func_data.function(p_args...); 81 | } 82 | } 83 | 84 | template 85 | int Processor::find_function(PHandler p_handler) const { 86 | int i = 0; 87 | for (auto &func_data : binded_functions) { 88 | if (func_data.handler == p_handler) { 89 | return i; 90 | } 91 | i += 1; 92 | } 93 | return -1; 94 | } 95 | 96 | template 97 | int Processor::size() const { 98 | return int(binded_functions.size()); 99 | } 100 | 101 | NS_NAMESPACE_END 102 | -------------------------------------------------------------------------------- /core/quick_sort.h: -------------------------------------------------------------------------------- 1 | // PORTED FROM JOLT - std::sort is not deterministic across plats. 2 | 3 | // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) 4 | // SPDX-FileCopyrightText: 2022 Jorrit Rouwe 5 | // SPDX-License-Identifier: MIT 6 | 7 | #pragma once 8 | 9 | #include "insertion_sort.h" 10 | 11 | namespace NS_JOLT { 12 | /// Helper function for QuickSort, will move the pivot element to inMiddle. 13 | template 14 | inline void QuickSortMedianOfThree(Iterator inFirst, Iterator inMiddle, Iterator inLast, Compare inCompare) { 15 | // This should be guaranteed because we switch over to insertion sort when there's 32 or less elements 16 | NS_ASSERT_COND(inFirst != inMiddle && inMiddle != inLast); 17 | 18 | if (inCompare(*inMiddle, *inFirst)) 19 | std::swap(*inFirst, *inMiddle); 20 | 21 | if (inCompare(*inLast, *inFirst)) 22 | std::swap(*inFirst, *inLast); 23 | 24 | if (inCompare(*inLast, *inMiddle)) 25 | std::swap(*inMiddle, *inLast); 26 | } 27 | 28 | /// Helper function for QuickSort using the Ninther method, will move the pivot element to inMiddle. 29 | template 30 | inline void QuickSortNinther(Iterator inFirst, Iterator inMiddle, Iterator inLast, Compare inCompare) { 31 | // Divide the range in 8 equal parts (this means there are 9 points) 32 | auto diff = (inLast - inFirst) >> 3; 33 | auto two_diff = diff << 1; 34 | 35 | // Median of first 3 points 36 | Iterator mid1 = inFirst + diff; 37 | QuickSortMedianOfThree(inFirst, mid1, inFirst + two_diff, inCompare); 38 | 39 | // Median of second 3 points 40 | QuickSortMedianOfThree(inMiddle - diff, inMiddle, inMiddle + diff, inCompare); 41 | 42 | // Median of third 3 points 43 | Iterator mid3 = inLast - diff; 44 | QuickSortMedianOfThree(inLast - two_diff, mid3, inLast, inCompare); 45 | 46 | // Determine the median of the 3 medians 47 | QuickSortMedianOfThree(mid1, inMiddle, mid3, inCompare); 48 | } 49 | 50 | /// Implementation of the quick sort algorithm. The STL version implementation is not consistent across platforms. 51 | template 52 | inline void QuickSort(Iterator inBegin, Iterator inEnd, Compare inCompare) { 53 | // Implementation based on https://en.wikipedia.org/wiki/Quicksort using Hoare's partition scheme 54 | 55 | // Loop so that we only need to do 1 recursive call instead of 2. 56 | for (;;) { 57 | // If there's less than 2 elements we're done 58 | auto num_elements = inEnd - inBegin; 59 | if (num_elements < 2) 60 | return; 61 | 62 | // Fall back to insertion sort if there are too few elements 63 | if (num_elements <= 32) { 64 | InsertionSort(inBegin, inEnd, inCompare); 65 | return; 66 | } 67 | 68 | // Determine pivot 69 | Iterator pivot_iterator = inBegin + ((num_elements - 1) >> 1); 70 | QuickSortNinther(inBegin, pivot_iterator, inEnd - 1, inCompare); 71 | 72 | // Left and right iterators 73 | Iterator i = inBegin; 74 | Iterator j = inEnd; 75 | 76 | for (;;) { 77 | // Find the first element that is bigger than the pivot 78 | while (inCompare(*i, *pivot_iterator)) 79 | i++; 80 | 81 | // Find the last element that is smaller than the pivot 82 | do 83 | --j; 84 | while (inCompare(*pivot_iterator, *j)); 85 | 86 | // If the two iterators crossed, we're done 87 | if (i >= j) 88 | break; 89 | 90 | // Swap the elements 91 | std::swap(*i, *j); 92 | 93 | // Note that the first while loop in this function should 94 | // have been do i++ while (...) but since we cannot decrement 95 | // the iterator from inBegin we left that out, so we need to do 96 | // it here. 97 | ++i; 98 | } 99 | 100 | // Include the middle element on the left side 101 | j++; 102 | 103 | // Check which partition is smaller 104 | if (j - inBegin < inEnd - j) { 105 | // Left side is smaller, recurse to left first 106 | QuickSort(inBegin, j, inCompare); 107 | 108 | // Loop again with the right side to avoid a call 109 | inBegin = j; 110 | } else { 111 | // Right side is smaller, recurse to right first 112 | QuickSort(j, inEnd, inCompare); 113 | 114 | // Loop again with the left side to avoid a call 115 | inEnd = j; 116 | } 117 | } 118 | } 119 | 120 | /// Implementation of quick sort algorithm without comparator. 121 | template 122 | inline void QuickSort(Iterator inBegin, Iterator inEnd) { 123 | std::less<> compare; 124 | QuickSort(inBegin, inEnd, compare); 125 | } 126 | } -------------------------------------------------------------------------------- /core/scene_synchronizer_debugger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | 5 | NS_NAMESPACE_BEGIN 6 | class SceneSynchronizerBase; 7 | class NetworkInterface; 8 | 9 | class FileSystem { 10 | public: 11 | virtual ~FileSystem() { 12 | } 13 | 14 | virtual std::string get_base_dir() const = 0; 15 | virtual std::string get_date() const = 0; 16 | virtual std::string get_time() const = 0; 17 | virtual bool make_dir_recursive(const std::string &p_dir_path, bool p_erase_content) const = 0; 18 | 19 | virtual bool store_file_string(const std::string &p_path, const std::string &p_string_file) const { 20 | return store_file_buffer(p_path, reinterpret_cast(p_string_file.c_str()), p_string_file.length()); 21 | } 22 | 23 | virtual bool store_file_buffer(const std::string &p_path, const std::uint8_t *p_src, uint64_t p_length) const = 0; 24 | virtual bool file_exists(const std::string &p_path) const = 0; 25 | }; 26 | 27 | class SceneSynchronizerDebugger { 28 | public: 29 | enum DataBufferDumpMode { 30 | NONE, 31 | WRITE, 32 | READ 33 | }; 34 | 35 | enum FrameEvent : uint32_t { 36 | EMPTY = 0, 37 | CLIENT_DESYNC_DETECTED = 1 << 0, 38 | CLIENT_DESYNC_DETECTED_SOFT = 1 << 1, 39 | }; 40 | 41 | private: 42 | std::string log_prefix = ""; 43 | PrintMessageType log_level = NS::PrintMessageType::ERROR; 44 | 45 | #ifdef NS_DEBUG_ENABLED 46 | bool dump_enabled = false; 47 | bool setup_done = false; 48 | 49 | FileSystem *file_system = nullptr; 50 | 51 | uint32_t log_counter = 0; 52 | std::string main_dump_directory_path; 53 | std::string dump_name; 54 | 55 | // NOTICE: This is created at runtime when the object spawns. 56 | // The reason for this mechanism is to keep the Json header 57 | // inside the CPP and avoid clutter the includes, since that header includes many non wanted includes. 58 | struct SceneSynchronizerDebuggerJsonStorage *frame_dump_storage = nullptr; 59 | 60 | // A small description about what happens on this frame. 61 | FrameEvent frame_dump__frame_events = FrameEvent::EMPTY; 62 | 63 | DataBufferDumpMode frame_dump_data_buffer_dump_mode = NONE; 64 | #endif 65 | 66 | public: 67 | SceneSynchronizerDebugger(); 68 | ~SceneSynchronizerDebugger(); 69 | 70 | SceneSynchronizerDebugger &get_debugger() const; 71 | 72 | void set_file_system(FileSystem *p_file_system); 73 | 74 | FileSystem *get_file_system() const { 75 | #ifdef NS_DEBUG_ENABLED 76 | return file_system; 77 | #else 78 | return nullptr; 79 | #endif 80 | } 81 | 82 | void set_log_prefix(const std::string &p_prefix) { 83 | log_prefix = p_prefix; 84 | } 85 | 86 | const std::string &get_log_prefix() const { 87 | return log_prefix; 88 | } 89 | 90 | void set_log_level(PrintMessageType p_log_level); 91 | PrintMessageType get_log_level() const; 92 | 93 | void set_dump_enabled(bool p_dump_enabled); 94 | bool get_dump_enabled() const; 95 | 96 | void setup_debugger(const std::string &p_dump_name, int p_peer); 97 | 98 | private: 99 | void prepare_dumping(int p_peer); 100 | void setup_debugger_python_ui(); 101 | 102 | public: 103 | void write_dump(int p_peer, uint32_t p_frame_index); 104 | void start_new_frame(); 105 | 106 | void scene_sync_process_start(SceneSynchronizerBase &p_scene_sync); 107 | void scene_sync_process_end(SceneSynchronizerBase &p_scene_sync); 108 | 109 | void databuffer_operation_begin_record(int p_peer, DataBufferDumpMode p_mode); 110 | void databuffer_operation_end_record(); 111 | void databuffer_write(uint32_t p_data_type, uint32_t p_compression_level, int p_new_bit_offset, const char *p_variable); 112 | void databuffer_read(uint32_t p_data_type, uint32_t p_compression_level, int p_new_bit_offset, const char *p_variable); 113 | 114 | void notify_input_sent_to_server(int p_peer, uint32_t p_frame_index, uint32_t p_input_index); 115 | void notify_are_inputs_different_result(int p_peer, uint32_t p_other_frame_index, bool p_is_similar); 116 | 117 | void print( 118 | PrintMessageType p_level, 119 | const std::string &p_message, 120 | const std::string &p_object_name = "GLOBAL", 121 | bool p_force_print_to_log = false); 122 | 123 | void notify_event(FrameEvent p_event); 124 | 125 | void __add_message(const std::string &p_message, const std::string &p_object_name); 126 | }; 127 | 128 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/scene_synchronizer_debugger_json_storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "json.hpp" 4 | 5 | namespace NS { 6 | struct SceneSynchronizerDebuggerJsonStorage { 7 | #ifdef NS_DEBUG_ENABLED 8 | nlohmann::json::object_t frame_dump__begin_state; 9 | 10 | // JSON of dictionary containing nodes info. 11 | nlohmann::json::object_t frame_dump__end_state; 12 | 13 | // The JSON containing the data buffer operations performed by the controllers. 14 | nlohmann::json::object_t frame_dump__node_log; 15 | 16 | // This Array contains all the inputs (stringified) written on the `DataBuffer` from the 17 | // `_controller_process` function 18 | nlohmann::json::array_t frame_dump__data_buffer_writes; 19 | 20 | // This Array contains all the inputs (stringified) read on the `DataBuffer` from the 21 | // `_controller_process` function 22 | nlohmann::json::array_t frame_dump__data_buffer_reads; 23 | 24 | // This JSON contains the comparison (`_are_inputs_different`) fetched by this frame, and 25 | // the result. 26 | nlohmann::json::object_t frame_dump__are_inputs_different_results; 27 | 28 | // The controller name for which the data buffer operations is in progress. 29 | std::string frame_dump__data_buffer_name; 30 | 31 | bool frame_dump__has_warnings = false; 32 | bool frame_dump__has_errors = false; 33 | #endif 34 | }; 35 | }; -------------------------------------------------------------------------------- /core/scheduled_procedure.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | 5 | NS_NAMESPACE_BEGIN 6 | struct ScheduledProcedureHandle { 7 | ObjectNetId object_net_id; 8 | ScheduledProcedureId procedure_id; 9 | 10 | ScheduledProcedureHandle( 11 | ObjectNetId p_object_local_id, 12 | ScheduledProcedureId p_procedure_id) { 13 | object_net_id = p_object_local_id; 14 | procedure_id = p_procedure_id; 15 | } 16 | 17 | bool operator<(const ScheduledProcedureHandle &p_other) const { 18 | if (get_object_net_id() == p_other.get_object_net_id()) { 19 | return get_scheduled_procedure_id() < p_other.get_scheduled_procedure_id(); 20 | } else { 21 | return get_object_net_id() < p_other.get_object_net_id(); 22 | } 23 | } 24 | 25 | bool operator==(const ScheduledProcedureHandle &p_other) const { 26 | return object_net_id == p_other.object_net_id && procedure_id == p_other.procedure_id; 27 | } 28 | 29 | ObjectNetId get_object_net_id() const { 30 | return object_net_id; 31 | } 32 | 33 | ScheduledProcedureId get_scheduled_procedure_id() const { 34 | return procedure_id; 35 | } 36 | }; 37 | 38 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/snapshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include "object_data.h" 5 | #include 6 | #include 7 | 8 | NS_NAMESPACE_BEGIN 9 | class SceneSynchronizerBase; 10 | 11 | struct SimulatedObjectInfo { 12 | ObjectNetId net_id; 13 | int controlled_by_peer; 14 | 15 | SimulatedObjectInfo() = default; 16 | 17 | SimulatedObjectInfo(const ObjectNetId &p_id) : 18 | net_id(p_id), 19 | controlled_by_peer(-1) { 20 | } 21 | 22 | SimulatedObjectInfo(const ObjectNetId &p_id, int p_controlled_by_peer) : 23 | net_id(p_id), 24 | controlled_by_peer(p_controlled_by_peer) { 25 | } 26 | 27 | bool operator==(const SimulatedObjectInfo &p_other) const { 28 | return net_id == p_other.net_id; 29 | } 30 | }; 31 | 32 | struct FrameIndexWithMeta { 33 | /// This is set to true only when the `frame_index` comes from the server. 34 | /// NOTE: This is needed to know when the `frame_index` is taken from a 35 | /// client generated snapshot because of a partial updated snapshot 36 | /// was received. 37 | bool is_server_validated = false; 38 | FrameIndex frame_index = FrameIndex::NONE; 39 | 40 | FrameIndexWithMeta() = default; 41 | FrameIndexWithMeta(const FrameIndexWithMeta &) = default; 42 | 43 | FrameIndexWithMeta(bool p_is_server_validated, FrameIndex p_frame_index): 44 | is_server_validated(p_is_server_validated), 45 | frame_index(p_frame_index) { 46 | } 47 | 48 | FrameIndexWithMeta(FrameIndex p_frame_index): 49 | frame_index(p_frame_index) { 50 | } 51 | }; 52 | 53 | struct ScheduledProcedureSnapshot { 54 | GlobalFrameIndex execute_frame = GlobalFrameIndex{ 0 }; 55 | GlobalFrameIndex paused_frame = GlobalFrameIndex{ 0 }; 56 | DataBuffer args; 57 | 58 | bool operator==(const ScheduledProcedureSnapshot &p_other) const { 59 | return execute_frame == p_other.execute_frame 60 | && paused_frame == p_other.paused_frame 61 | && args == p_other.args; 62 | } 63 | 64 | bool operator!=(const ScheduledProcedureSnapshot &p_other) const { 65 | return !operator==(p_other); 66 | } 67 | 68 | operator std::string() const { 69 | return std::string() + "execute_frame: " + std::to_string(execute_frame.id) 70 | + ", paused_frame: " + std::to_string(paused_frame.id) 71 | + ", args: " + std::to_string(args.size()); 72 | } 73 | }; 74 | 75 | struct ObjectDataSnapshot { 76 | std::vector> vars; 77 | std::vector procedures; 78 | 79 | void copy(const ObjectDataSnapshot &p_other); 80 | void clear(); 81 | }; 82 | 83 | struct Snapshot { 84 | FrameIndex input_id = FrameIndex::NONE; 85 | GlobalFrameIndex global_frame_index = GlobalFrameIndex::NONE; 86 | std::vector simulated_objects; 87 | /// The objects info in a particular frame. The order of this vector 88 | /// matters because the index is the `ObjectNetId`. 89 | std::vector objects; 90 | 91 | /// The executed FrameIndex for the simulating peers. 92 | /// NOTE: Due to the nature of the doll simulation, when comparing the 93 | /// server snapshot with the client snapshot this map is never checked. 94 | /// This map is used by the Doll-controller's reconciliation algorithm. 95 | std::map peers_frames_index; 96 | 97 | bool has_custom_data = false; 98 | 99 | /// Custom variable specified by the user. 100 | /// NOTE: The user can specify a different variable depending on the passed GroupSync. 101 | VarData custom_data; 102 | 103 | public: 104 | operator std::string() const; 105 | 106 | const std::vector> *get_object_vars(ObjectNetId p_id) const; 107 | const std::vector *get_object_procedures(ObjectNetId p_id) const; 108 | 109 | /// Copy the given snapshot. 110 | static Snapshot make_copy(const Snapshot &p_other); 111 | void copy(const Snapshot &p_other); 112 | 113 | static bool compare( 114 | const SceneSynchronizerBase &scene_synchronizer, 115 | const Snapshot &p_snap_A, 116 | const Snapshot &p_snap_B, 117 | const int p_skip_objects_not_controlled_by_peer, 118 | Snapshot *r_no_rewind_recover, 119 | std::vector *r_differences_info 120 | #ifdef NS_DEBUG_ENABLED 121 | , 122 | std::vector *r_different_node_data); 123 | #else 124 | ); 125 | #endif 126 | }; 127 | 128 | struct RollingUpdateSnapshot final : public Snapshot { 129 | /// This is set to true when the server sends only parts of the changed objects. 130 | bool was_partially_updated = false; 131 | bool is_just_updated_simulated_objects = false; 132 | bool is_just_updated_custom_data = false; 133 | /// The list of the updated object vars on the last update. 134 | std::vector just_updated_object_vars; 135 | }; 136 | 137 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/var_data.cpp: -------------------------------------------------------------------------------- 1 | #include "var_data.h" 2 | 3 | #include // Needed to include `memset` in linux. 4 | 5 | NS_NAMESPACE_BEGIN 6 | VarData::VarData() { 7 | type = 0; 8 | memset(&data, 0, sizeof(data)); 9 | } 10 | 11 | VarData::VarData(float x, float y, float z, float w) : 12 | VarData() { 13 | data.vec_f32.x = x; 14 | data.vec_f32.y = y; 15 | data.vec_f32.z = z; 16 | data.vec_f32.w = w; 17 | } 18 | 19 | VarData::VarData(double x, double y, double z, double w) : 20 | VarData() { 21 | data.vec.x = x; 22 | data.vec.y = y; 23 | data.vec.z = z; 24 | data.vec.w = w; 25 | } 26 | 27 | VarData::VarData(VarData &&p_other) : 28 | type(std::move(p_other.type)), 29 | data(std::move(p_other.data)), 30 | shared_buffer(std::move(p_other.shared_buffer)) { 31 | } 32 | 33 | VarData &VarData::operator=(VarData &&p_other) { 34 | type = std::move(p_other.type); 35 | data = std::move(p_other.data); 36 | shared_buffer = std::move(p_other.shared_buffer); 37 | return *this; 38 | } 39 | 40 | VarData VarData::make_copy(const VarData &p_other) { 41 | VarData vd; 42 | vd.copy(p_other); 43 | return vd; 44 | } 45 | 46 | void VarData::copy(const VarData &p_other) { 47 | type = p_other.type; 48 | data = p_other.data; 49 | shared_buffer = p_other.shared_buffer; 50 | } 51 | 52 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /core/var_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.h" 4 | #include 5 | #include 6 | 7 | NS_NAMESPACE_BEGIN 8 | // VarData is a struct that olds the value of a variable. 9 | struct VarData { 10 | /// The type of the data, defined by the user. 11 | std::uint8_t type = 0; 12 | 13 | /// The data. 14 | union { 15 | void *ptr; 16 | 17 | bool boolean; 18 | std::int32_t i32; 19 | std::int64_t i64; 20 | float f32; 21 | double f64; 22 | 23 | struct { 24 | float x; 25 | float y; 26 | float z; 27 | float w; 28 | } vec_f32; 29 | 30 | struct { 31 | double x; 32 | double y; 33 | double z; 34 | double w; 35 | } vec; 36 | 37 | struct { 38 | std::int64_t ix; 39 | std::int64_t iy; 40 | std::int64_t iz; 41 | std::int64_t iw; 42 | } ivec; 43 | 44 | struct { 45 | float x; 46 | float y; 47 | float z; 48 | float w; 49 | } columns_f32[4]; 50 | 51 | struct { 52 | double x; 53 | double y; 54 | double z; 55 | double w; 56 | } columns[4]; 57 | 58 | struct { 59 | float x; 60 | float y; 61 | float z; 62 | float w; 63 | } rows_f32[4]; 64 | 65 | struct { 66 | double x; 67 | double y; 68 | double z; 69 | double w; 70 | } rows[4]; 71 | } data; 72 | 73 | // Eventually shared buffer across many `VarData`. 74 | std::shared_ptr shared_buffer; 75 | 76 | public: 77 | VarData(); 78 | VarData(float x, float y = 0.0f, float z = 0.0f, float w = 0.0f); 79 | VarData(double x, double y = 0.0, double z = 0.0, double w = 0.0); 80 | 81 | VarData(const VarData &p_other) = delete; 82 | VarData &operator=(const VarData &p_other) = delete; 83 | 84 | VarData(VarData &&p_other); 85 | VarData &operator=(VarData &&p_other); 86 | 87 | static VarData make_copy(const VarData &p_other); 88 | void copy(const VarData &p_other); 89 | }; 90 | 91 | class SynchronizerManager; 92 | 93 | NS_NAMESPACE_END 94 | 95 | #define NS_VarDataSetFunc std::function 96 | #define NS_VarDataGetFunc std::function -------------------------------------------------------------------------------- /debugger_ui/cpplize_debugger.py: -------------------------------------------------------------------------------- 1 | from os import listdir 2 | from os.path import isfile, join, isdir, exists 3 | import sys 4 | 5 | def create_debugger_header(source_path): 6 | 7 | f = open(source_path + "/core/__generated__debugger_ui.h", "w", encoding="utf-8") 8 | f.write("#pragma once\n") 9 | f.write("\n") 10 | f.write("/// This is a generated file by `cpplize_debugger.py`, executed by `SCsub`.\n") 11 | f.write("/// \n") 12 | f.write("/// DO NOT EDIT this header.\n") 13 | f.write("/// If you want to modify this python code, you can simply change `debugger.py`\n") 14 | f.write("/// During the next compilation this header will be updated.\n") 15 | f.write("/// \n") 16 | f.write("/// HOWEVER! The file will not be copied into the `bin` folder unless you remove the\n") 17 | f.write("/// existing `bin/debugger.py` first; this algorithm prevents destroying eventual\n") 18 | f.write("/// changes made on that file.\n") 19 | f.write("\n") 20 | f.write("static const char __debugger_ui_code[] = R\"TheCodeRKS(") 21 | 22 | size = 0 23 | with open(source_path + '/debugger_ui/debugger.py', encoding="utf-8") as deb_f: 24 | for l in deb_f.readlines(): 25 | l_utf8 = l.encode('utf-8') 26 | size += len(l_utf8) 27 | f.write(l); 28 | 29 | f.write(" )TheCodeRKS\";\n") 30 | f.write("static unsigned int __debugger_ui_code_size = "+str(size)+";\n") 31 | f.close() 32 | 33 | 34 | if len(sys.argv) < 2: 35 | print("Usage: cpplize_debugger.py ") 36 | sys.exit(1) 37 | 38 | source_path = sys.argv[1] 39 | 40 | create_debugger_header(source_path) 41 | -------------------------------------------------------------------------------- /debugger_ui/readme.md: -------------------------------------------------------------------------------- 1 | # Network debugger UI 2 | 3 | Requirements 4 | - Install python 5 | - Install pip 6 | - Install PySimpleGUI: `pip install pysimplegui` 7 | - Install Tkinter: 8 | 1. Install Tkinter app: 9 | - Fedora: `sudo dnf install python3-tkinter` 10 | - Ubuntu: `sudo apt-get install python3-tk` 11 | - Windows: Nothing to do 12 | 1. Install python library: `pip install tk` 13 | 14 | 15 | # Missing features 16 | 1. At the moment it's not supported multiple client debugging. 17 | -------------------------------------------------------------------------------- /doc_classes/InputNetworkEncoder.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /godot4/gd_data_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/object/class_db.h" 4 | 5 | #include "../core/data_buffer.h" 6 | 7 | class GdDataBuffer : public Object { 8 | GDCLASS(GdDataBuffer, Object); 9 | 10 | public: 11 | enum DataType { 12 | DATA_TYPE_BOOL, 13 | DATA_TYPE_INT, 14 | DATA_TYPE_UINT, 15 | DATA_TYPE_REAL, 16 | DATA_TYPE_POSITIVE_UNIT_REAL, 17 | DATA_TYPE_UNIT_REAL, 18 | DATA_TYPE_VECTOR2, 19 | DATA_TYPE_NORMALIZED_VECTOR2, 20 | DATA_TYPE_VECTOR3, 21 | DATA_TYPE_NORMALIZED_VECTOR3, 22 | DATA_TYPE_BITS, 23 | // The only dynamic sized value. 24 | DATA_TYPE_DATABUFFER 25 | }; 26 | 27 | /// Compression level for the stored input data. 28 | /// 29 | /// Depending on the data type and the compression level used the amount of 30 | /// bits used and loss change. 31 | /// 32 | /// 33 | /// ## Bool 34 | /// Always use 1 bit 35 | /// 36 | /// 37 | /// ## Int 38 | /// COMPRESSION_LEVEL_0: 64 bits are used - Stores integers -9223372036854775808 / 9223372036854775807 39 | /// COMPRESSION_LEVEL_1: 32 bits are used - Stores integers -2147483648 / 2147483647 40 | /// COMPRESSION_LEVEL_2: 16 bits are used - Stores integers -32768 / 32767 41 | /// COMPRESSION_LEVEL_3: 8 bits are used - Stores integers -128 / 127 42 | /// 43 | /// 44 | /// ## Uint 45 | /// COMPRESSION_LEVEL_0: 64 bits are used - Stores integers 18446744073709551615 46 | /// COMPRESSION_LEVEL_1: 32 bits are used - Stores integers 4294967295 47 | /// COMPRESSION_LEVEL_2: 16 bits are used - Stores integers 65535 48 | /// COMPRESSION_LEVEL_3: 8 bits are used - Stores integers 2SS 49 | /// 50 | /// 51 | /// ## Real 52 | /// Precision depends on an integer range 53 | /// COMPRESSION_LEVEL_0: 64 bits are used - Double precision. Up to 16 precision is 0.00000000000000177636 in worst case. Up to 512 precision is 0.00000000000005684342 in worst case. Up to 1024 precision is 0.00000000000011368684 in worst case. 54 | /// COMPRESSION_LEVEL_1: 32 bits are used - Single precision (float). Up to 16 precision is 0.00000095367431640625 in worst case. Up to 512 precision is 0.000030517578125 in worst case. Up to 1024 precision is 0.00006103515625 in worst case. 55 | /// COMPRESSION_LEVEL_2: 16 bits are used - Half precision. Up to 16 precision is 0.0078125 in worst case. Up to 512 precision is 0.25 in worst case. Up to 1024 precision is 0.5. 56 | /// COMPRESSION_LEVEL_3: 8 bits are used - Minifloat: Up to 2 precision is 0.125. Up to 4 precision is 0.25. Up to 8 precision is 0.5. 57 | /// 58 | /// To get the exact precision for the stored number, you need to find the lower power of two relative to the number and divide it by 2^mantissa_bits. 59 | /// To get the mantissa or exponent bits for a specific compression level, you can use the get_mantissa_bits and get_exponent_bits functions. 60 | /// 61 | /// 62 | /// ## Positive unit real 63 | /// COMPRESSION_LEVEL_0: 10 bits are used - Max loss ~0.005% 64 | /// COMPRESSION_LEVEL_1: 8 bits are used - Max loss ~0.020% 65 | /// COMPRESSION_LEVEL_2: 6 bits are used - Max loss ~0.793% 66 | /// COMPRESSION_LEVEL_3: 4 bits are used - Max loss ~3.333% 67 | /// 68 | /// 69 | /// ## Unit real (uses one extra bit for the sign) 70 | /// COMPRESSION_LEVEL_0: 11 bits are used - Max loss ~0.005% 71 | /// COMPRESSION_LEVEL_1: 9 bits are used - Max loss ~0.020% 72 | /// COMPRESSION_LEVEL_2: 7 bits are used - Max loss ~0.793% 73 | /// COMPRESSION_LEVEL_3: 5 bits are used - Max loss ~3.333% 74 | /// 75 | /// 76 | /// ## Vector2 77 | /// COMPRESSION_LEVEL_0: 2 * 64 bits are used - Double precision 78 | /// COMPRESSION_LEVEL_1: 2 * 32 bits are used - Single precision 79 | /// COMPRESSION_LEVEL_2: 2 * 16 bits are used - Half precision 80 | /// COMPRESSION_LEVEL_3: 2 * 8 bits are used - Minifloat 81 | /// 82 | /// For floating point precision, check the Real compression section. 83 | /// 84 | /// 85 | /// ## Normalized Vector2 86 | /// COMPRESSION_LEVEL_0: 12 bits are used - Max loss 0.17° 87 | /// COMPRESSION_LEVEL_1: 11 bits are used - Max loss 0.35° 88 | /// COMPRESSION_LEVEL_2: 10 bits are used - Max loss 0.7° 89 | /// COMPRESSION_LEVEL_3: 9 bits are used - Max loss 1.1° 90 | /// 91 | /// 92 | /// ## Vector3 93 | /// COMPRESSION_LEVEL_0: 3 * 64 bits are used - Double precision 94 | /// COMPRESSION_LEVEL_1: 3 * 32 bits are used - Single precision 95 | /// COMPRESSION_LEVEL_2: 3 * 16 bits are used - Half precision 96 | /// COMPRESSION_LEVEL_3: 3 * 8 bits are used - Minifloat 97 | /// 98 | /// For floating point precision, check the Real compression section. 99 | /// 100 | /// 101 | /// ## Normalized Vector3 102 | /// COMPRESSION_LEVEL_0: 11 * 3 bits are used - Max loss ~0.005% per axis 103 | /// COMPRESSION_LEVEL_1: 9 * 3 bits are used - Max loss ~0.020% per axis 104 | /// COMPRESSION_LEVEL_2: 7 * 3 bits are used - Max loss ~0.793% per axis 105 | /// COMPRESSION_LEVEL_3: 5 * 3 bits are used - Max loss ~3.333% per axis 106 | /// 107 | /// ## Variant 108 | /// It's dynamic sized. It's not possible to compress it. 109 | enum CompressionLevel { 110 | COMPRESSION_LEVEL_0, 111 | COMPRESSION_LEVEL_1, 112 | COMPRESSION_LEVEL_2, 113 | COMPRESSION_LEVEL_3 114 | }; 115 | 116 | public: 117 | NS::DataBuffer *data_buffer = nullptr; 118 | 119 | public: 120 | static void _bind_methods(); 121 | 122 | GdDataBuffer() = default; 123 | 124 | /// Returns the buffer size in bits 125 | int size() const; 126 | 127 | /// Begin write. 128 | void begin_write(int ms); 129 | 130 | /// Begin read. 131 | void begin_read(); 132 | 133 | void dry(); 134 | 135 | // -------------------------------------------------- Specific serialization 136 | 137 | /// Add a boolean to the buffer. 138 | /// Returns the same data. 139 | bool add_bool(bool p_input); 140 | 141 | /// Parse the next data as boolean. 142 | bool read_bool(); 143 | 144 | /// Add the next data as int. 145 | int64_t add_int(int64_t p_input, CompressionLevel p_compression_level); 146 | 147 | /// Parse the next data as int. 148 | int64_t read_int(CompressionLevel p_compression_level); 149 | 150 | /// Add the next data as uint 151 | uint64_t add_uint(uint64_t p_input, CompressionLevel p_compression_level); 152 | 153 | /// Parse the next data as uint. 154 | uint64_t read_uint(CompressionLevel p_compression_level); 155 | 156 | /// Add a real into the buffer. Depending on the compression level is possible 157 | /// to store different range level. 158 | /// The fractional part has a precision of ~0.3% 159 | /// 160 | /// Returns the compressed value so both the client and the peers can use 161 | /// the same data. 162 | double add_real(double p_input, CompressionLevel p_compression_level); 163 | 164 | /// Parse the following data as a real. 165 | double read_real(CompressionLevel p_compression_level); 166 | 167 | /// Add a positive unit real into the buffer. 168 | /// 169 | /// **Note:** Not unitary values lead to unexpected behaviour. 170 | /// 171 | /// Returns the compressed value so both the client and the peers can use 172 | /// the same data. 173 | float add_positive_unit_real(float p_input, CompressionLevel p_compression_level); 174 | 175 | /// Parse the following data as a positive unit real. 176 | float read_positive_unit_real(CompressionLevel p_compression_level); 177 | 178 | /// Add a unit real into the buffer. 179 | /// 180 | /// **Note:** Not unitary values lead to unexpected behaviour. 181 | /// 182 | /// Returns the compressed value so both the client and the peers can use 183 | /// the same data. 184 | float add_unit_real(float p_input, CompressionLevel p_compression_level); 185 | 186 | /// Parse the following data as an unit real. 187 | float read_unit_real(CompressionLevel p_compression_level); 188 | 189 | /// Add a vector2 into the buffer. 190 | /// Note: This kind of vector occupies more space than the normalized verison. 191 | /// Consider use a normalized vector to save bandwidth if possible. 192 | /// 193 | /// Returns the decompressed vector so both the client and the peers can use 194 | /// the same data. 195 | Vector2 add_vector2(Vector2 p_input, CompressionLevel p_compression_level); 196 | 197 | /// Parse next data as vector from the input buffer. 198 | Vector2 read_vector2(CompressionLevel p_compression_level); 199 | 200 | /// Add a normalized vector2 into the buffer. 201 | /// Note: The compression algorithm rely on the fact that this is a 202 | /// normalized vector. The behaviour is unexpected for not normalized vectors. 203 | /// 204 | /// Returns the decompressed vector so both the client and the peers can use 205 | /// the same data. 206 | Vector2 add_normalized_vector2(Vector2 p_input, CompressionLevel p_compression_level); 207 | 208 | /// Parse next data as normalized vector from the input buffer. 209 | Vector2 read_normalized_vector2(CompressionLevel p_compression_level); 210 | 211 | /// Add a vector3 into the buffer. 212 | /// Note: This kind of vector occupies more space than the normalized verison. 213 | /// Consider use a normalized vector to save bandwidth if possible. 214 | /// 215 | /// Returns the decompressed vector so both the client and the peers can use 216 | /// the same data. 217 | Vector3 add_vector3(Vector3 p_input, CompressionLevel p_compression_level); 218 | 219 | /// Parse next data as vector3 from the input buffer. 220 | Vector3 read_vector3(CompressionLevel p_compression_level); 221 | 222 | /// Add a normalized vector3 into the buffer. 223 | /// Note: The compression algorithm rely on the fact that this is a 224 | /// normalized vector. The behaviour is unexpected for not normalized vectors. 225 | /// 226 | /// Returns the decompressed vector so both the client and the peers can use 227 | /// the same data. 228 | Vector3 add_normalized_vector3(Vector3 p_input, CompressionLevel p_compression_level); 229 | 230 | /// Parse next data as normalized vector3 from the input buffer. 231 | Vector3 read_normalized_vector3(CompressionLevel p_compression_level); 232 | 233 | /// Add a variant. This is the only supported dynamic sized value. 234 | Variant add_variant(const Variant &p_input); 235 | 236 | /// Parse the next data as Variant and returns it. 237 | Variant read_variant(); 238 | 239 | /// This function differ from the `add_variant` because when the variant passed 240 | /// equals to the default one: the variant is not encoded into the data buffer 241 | /// and takes no space. 242 | Variant add_optional_variant(const Variant &p_input, const Variant &p_def); 243 | 244 | /// Returns the variant encoded using `add_optinal_variant`. 245 | /// Make sure `p_def` equals to the one passed to `add_optional_variant`. 246 | Variant read_optional_variant(const Variant &p_def); 247 | 248 | /** Skips the amount of bits a type takes. */ 249 | 250 | void skip_bool(); 251 | void skip_int(CompressionLevel p_compression); 252 | void skip_uint(CompressionLevel p_compression); 253 | void skip_real(CompressionLevel p_compression); 254 | void skip_positive_unit_real(CompressionLevel p_compression); 255 | void skip_unit_real(CompressionLevel p_compression); 256 | void skip_vector2(CompressionLevel p_compression); 257 | void skip_normalized_vector2(CompressionLevel p_compression); 258 | void skip_vector3(CompressionLevel p_compression); 259 | void skip_normalized_vector3(CompressionLevel p_compression); 260 | void skip_variant(); 261 | void skip_optional_variant(const Variant &p_def); 262 | 263 | /** Just returns the size of a specific type. */ 264 | 265 | int get_bool_size() const; 266 | int get_int_size(CompressionLevel p_compression) const; 267 | int get_uint_size(CompressionLevel p_compression) const; 268 | int get_real_size(CompressionLevel p_compression) const; 269 | int get_positive_unit_real_size(CompressionLevel p_compression) const; 270 | int get_unit_real_size(CompressionLevel p_compression) const; 271 | int get_vector2_size(CompressionLevel p_compression) const; 272 | int get_normalized_vector2_size(CompressionLevel p_compression) const; 273 | int get_vector3_size(CompressionLevel p_compression) const; 274 | int get_normalized_vector3_size(CompressionLevel p_compression) const; 275 | 276 | /** Read the size and pass to the next parameter. */ 277 | 278 | int read_bool_size(); 279 | int read_int_size(CompressionLevel p_compression); 280 | int read_uint_size(CompressionLevel p_compression); 281 | int read_real_size(CompressionLevel p_compression); 282 | int read_positive_unit_real_size(CompressionLevel p_compression); 283 | int read_unit_real_size(CompressionLevel p_compression); 284 | int read_vector2_size(CompressionLevel p_compression); 285 | int read_normalized_vector2_size(CompressionLevel p_compression); 286 | int read_vector3_size(CompressionLevel p_compression); 287 | int read_normalized_vector3_size(CompressionLevel p_compression); 288 | int read_variant_size(); 289 | int read_optional_variant_size(const Variant &p_def); 290 | int read_buffer_size(); 291 | }; 292 | 293 | VARIANT_ENUM_CAST(GdDataBuffer::DataType) 294 | VARIANT_ENUM_CAST(GdDataBuffer::CompressionLevel) 295 | -------------------------------------------------------------------------------- /godot4/gd_network_interface.cpp: -------------------------------------------------------------------------------- 1 | #include "gd_network_interface.h" 2 | 3 | #include "core/error/error_macros.h" 4 | #include "core/math/aabb.h" 5 | #include "core/math/basis.h" 6 | #include "core/math/math_defs.h" 7 | #include "core/math/projection.h" 8 | #include "core/math/quaternion.h" 9 | #include "core/math/rect2i.h" 10 | #include "core/math/transform_2d.h" 11 | #include "core/math/transform_3d.h" 12 | #include "core/math/vector2.h" 13 | #include "core/math/vector2i.h" 14 | #include "core/math/vector3.h" 15 | #include "core/math/vector3i.h" 16 | #include "core/math/vector4i.h" 17 | #include "core/object/callable_method_pointer.h" 18 | #include "core/string/string_name.h" 19 | #include "core/variant/callable.h" 20 | #include "core/variant/dictionary.h" 21 | #include "core/variant/variant.h" 22 | #include "modules/network_synchronizer/core/var_data.h" 23 | #include "modules/network_synchronizer/godot4/gd_scene_synchronizer.h" 24 | #include "scene/main/multiplayer_api.h" 25 | #include "scene/main/node.h" 26 | #include 27 | #include 28 | #include 29 | 30 | GdNetworkInterface::GdNetworkInterface() { 31 | } 32 | 33 | GdNetworkInterface::~GdNetworkInterface() { 34 | } 35 | 36 | std::string GdNetworkInterface::get_owner_name() const { 37 | return String(owner->get_path()).utf8().ptr(); 38 | } 39 | 40 | void GdNetworkInterface::start_listening_peer_connection( 41 | std::function p_on_peer_connected_callback, 42 | std::function p_on_peer_disconnected_callback) { 43 | on_peer_connected_callback = p_on_peer_connected_callback; 44 | on_peer_disconnected_callback = p_on_peer_disconnected_callback; 45 | 46 | if (!owner->get_multiplayer()->is_connected(SNAME("peer_connected"), callable_mp(this, &GdNetworkInterface::on_peer_connected))) { 47 | owner->get_multiplayer()->connect(SNAME("peer_connected"), callable_mp(this, &GdNetworkInterface::on_peer_connected)); 48 | owner->get_multiplayer()->connect(SNAME("peer_disconnected"), callable_mp(this, &GdNetworkInterface::on_peer_disconnected)); 49 | } 50 | } 51 | 52 | void GdNetworkInterface::on_peer_connected(int p_peer) { 53 | ERR_FAIL_COND_MSG(!on_peer_connected_callback, "The callback `on_peer_connected_callback` is not valid."); 54 | on_peer_connected_callback(p_peer); 55 | } 56 | 57 | void GdNetworkInterface::on_peer_disconnected(int p_peer) { 58 | ERR_FAIL_COND_MSG(!on_peer_disconnected_callback, "The callback `on_peer_connected_callback` is not valid."); 59 | on_peer_disconnected_callback(p_peer); 60 | } 61 | 62 | void GdNetworkInterface::stop_listening_peer_connection() { 63 | if (owner->get_multiplayer()->is_connected(SNAME("peer_connected"), callable_mp(this, &GdNetworkInterface::on_peer_connected))) { 64 | owner->get_multiplayer()->disconnect(SNAME("peer_connected"), callable_mp(this, &GdNetworkInterface::on_peer_connected)); 65 | owner->get_multiplayer()->disconnect(SNAME("peer_disconnected"), callable_mp(this, &GdNetworkInterface::on_peer_disconnected)); 66 | } 67 | on_peer_connected_callback = [](int) {}; 68 | on_peer_disconnected_callback = [](int) {}; 69 | } 70 | 71 | int GdNetworkInterface::get_local_peer_id() const { 72 | if (owner && owner->get_multiplayer().is_valid()) { 73 | return owner->get_multiplayer()->get_unique_id(); 74 | } 75 | return 0; 76 | } 77 | 78 | void GdNetworkInterface::fetch_connected_peers(std::vector &p_connected_peers) const { 79 | p_connected_peers.clear(); 80 | if ( 81 | owner->get_tree() && 82 | owner->get_tree()->get_multiplayer().is_valid()) { 83 | for (auto peer : owner->get_tree()->get_multiplayer()->get_peer_ids()) { 84 | p_connected_peers.push_back(peer); 85 | } 86 | } 87 | } 88 | 89 | bool GdNetworkInterface::is_local_peer_networked() const { 90 | return owner->get_tree() && 91 | owner->get_tree()->get_multiplayer()->get_multiplayer_peer()->get_class_name() != "OfflineMultiplayerPeer"; 92 | } 93 | 94 | bool GdNetworkInterface::is_local_peer_server() const { 95 | if (is_local_peer_networked()) { 96 | return owner->get_tree()->get_multiplayer()->is_server(); 97 | } else { 98 | return false; 99 | } 100 | } 101 | 102 | void NS_GD_Test::test_var_data_conversin() { 103 | // Test Transform 104 | { 105 | Basis b; 106 | b.set_axis_angle(Vector3(1, 0, 0), 0.12); 107 | Vector3 o(1, 2, 3); 108 | 109 | Transform3D initial_transform(b, o); 110 | Variant variant = initial_transform; 111 | 112 | NS::VarData vd; 113 | GdSceneSynchronizer::convert(vd, variant); 114 | 115 | Variant final_variant; 116 | GdSceneSynchronizer::convert(final_variant, vd); 117 | 118 | Transform3D final_transform = final_variant; 119 | 120 | CRASH_COND(!final_transform.is_equal_approx(initial_transform)); 121 | } 122 | 123 | // Test bool 124 | { 125 | Variant from = true; 126 | NS::VarData vd_from; 127 | GdSceneSynchronizer::convert(vd_from, from); 128 | 129 | Variant to; 130 | GdSceneSynchronizer::convert(to, vd_from); 131 | 132 | CRASH_COND(from != to); 133 | 134 | // Test compare. 135 | { 136 | Variant from2 = true; 137 | NS::VarData vd_from2; 138 | GdSceneSynchronizer::convert(vd_from2, from2); 139 | 140 | CRASH_COND(!GdSceneSynchronizer::compare(vd_from, vd_from2)); 141 | NS::VarData vd; 142 | CRASH_COND(GdSceneSynchronizer::compare(vd_from, vd)); 143 | CRASH_COND(GdSceneSynchronizer::compare(vd_from2, vd)); 144 | } 145 | } 146 | 147 | // Test StringName 148 | { 149 | Variant from = StringName("GHUEIAiasfjasdfkadjfak"); 150 | CRASH_COND(from.get_type() != Variant::STRING_NAME); 151 | NS::VarData vd_from; 152 | GdSceneSynchronizer::convert(vd_from, from); 153 | 154 | NS::VarData vd_from_copy; 155 | vd_from_copy.copy(vd_from); 156 | 157 | Variant to; 158 | GdSceneSynchronizer::convert(to, vd_from_copy); 159 | 160 | CRASH_COND(from != to); 161 | CRASH_COND(vd_from.shared_buffer != vd_from_copy.shared_buffer); 162 | } 163 | 164 | // Test NodePath 165 | { 166 | Variant from = NodePath("/root/asdf/fieae"); 167 | CRASH_COND(from.get_type() != Variant::NODE_PATH); 168 | NS::VarData vd_from; 169 | GdSceneSynchronizer::convert(vd_from, from); 170 | 171 | NS::VarData vd_from_copy; 172 | vd_from_copy.copy(vd_from); 173 | 174 | Variant to; 175 | GdSceneSynchronizer::convert(to, vd_from_copy); 176 | 177 | CRASH_COND(from != to); 178 | CRASH_COND(vd_from.shared_buffer != vd_from_copy.shared_buffer); 179 | } 180 | 181 | // Test String 182 | { 183 | Variant from = "GHUEIAiasfjasdfkadjfak"; 184 | CRASH_COND(from.get_type() != Variant::STRING); 185 | NS::VarData vd_from; 186 | GdSceneSynchronizer::convert(vd_from, from); 187 | 188 | NS::VarData vd_from_copy; 189 | vd_from_copy.copy(vd_from); 190 | 191 | Variant to; 192 | GdSceneSynchronizer::convert(to, vd_from_copy); 193 | 194 | CRASH_COND(from != to); 195 | CRASH_COND(vd_from.shared_buffer != vd_from_copy.shared_buffer); 196 | } 197 | 198 | // Test int vector 199 | { 200 | Vector integers; 201 | integers.push_back(1); 202 | integers.push_back(2); 203 | integers.push_back(3); 204 | Variant from = integers; 205 | CRASH_COND(from.get_type() != Variant::PACKED_INT32_ARRAY); 206 | NS::VarData vd_from; 207 | GdSceneSynchronizer::convert(vd_from, from); 208 | 209 | NS::VarData vd_from_copy; 210 | vd_from_copy.copy(vd_from); 211 | 212 | Variant to; 213 | GdSceneSynchronizer::convert(to, vd_from_copy); 214 | 215 | CRASH_COND(from != to); 216 | CRASH_COND(vd_from.shared_buffer != vd_from_copy.shared_buffer); 217 | } 218 | 219 | // Test Array 220 | { 221 | Dictionary dic; 222 | dic["Test"] = "www"; 223 | 224 | Array arr; 225 | arr.push_back(1); 226 | arr.push_back(String("asdf")); 227 | arr.push_back(dic); 228 | 229 | Variant from = arr; 230 | CRASH_COND(from.get_type() != Variant::ARRAY); 231 | NS::VarData vd_from; 232 | GdSceneSynchronizer::convert(vd_from, from); 233 | 234 | NS::VarData vd_from_copy; 235 | vd_from_copy.copy(vd_from); 236 | 237 | Variant to; 238 | GdSceneSynchronizer::convert(to, vd_from_copy); 239 | 240 | CRASH_COND(from != to); 241 | CRASH_COND(vd_from.shared_buffer != vd_from_copy.shared_buffer); 242 | } 243 | 244 | // Test Dictionary 245 | { 246 | Array arr; 247 | arr.push_back(1); 248 | arr.push_back(String("asdf")); 249 | 250 | Dictionary dic; 251 | dic["Test"] = "www"; 252 | dic["Arr"] = arr; 253 | 254 | Variant from = dic; 255 | CRASH_COND(from.get_type() != Variant::DICTIONARY); 256 | NS::VarData vd_from; 257 | GdSceneSynchronizer::convert(vd_from, from); 258 | 259 | NS::VarData vd_from_copy; 260 | vd_from_copy.copy(vd_from); 261 | 262 | Variant to; 263 | GdSceneSynchronizer::convert(to, vd_from_copy); 264 | 265 | CRASH_COND(from != to); 266 | CRASH_COND(vd_from.shared_buffer != vd_from_copy.shared_buffer); 267 | CRASH_COND(!GdSceneSynchronizer::compare(vd_from, vd_from_copy)); 268 | { 269 | NS::VarData vd; 270 | CRASH_COND(GdSceneSynchronizer::compare(vd_from, vd)); 271 | } 272 | } 273 | } 274 | 275 | void GdNetworkInterface::rpc_send(int p_peer_recipient, bool p_reliable, NS::DataBuffer &&p_buffer) { 276 | const std::vector &buffer = p_buffer.get_buffer().get_bytes(); 277 | 278 | // TODO use RPC directly from MultiPlayerPeer that allows to sent raw buffers. This would avoid this conversion to Vector. 279 | Vector gd_buffer; 280 | for (auto b : buffer) { 281 | gd_buffer.push_back(b); 282 | } 283 | 284 | if (p_reliable) { 285 | owner->rpc_id(p_peer_recipient, SNAME("_rpc_net_sync_reliable"), gd_buffer); 286 | } else { 287 | owner->rpc_id(p_peer_recipient, SNAME("_rpc_net_sync_unreliable"), gd_buffer); 288 | } 289 | } 290 | 291 | void GdNetworkInterface::gd_rpc_receive(const Vector &p_gd_buffer) { 292 | NS::DataBuffer db; 293 | db.get_buffer_mut().get_bytes_mut().reserve(p_gd_buffer.size()); 294 | for (auto b : p_gd_buffer) { 295 | db.get_buffer_mut().get_bytes_mut().push_back(b); 296 | } 297 | 298 | db.begin_read(get_debugger()); 299 | rpc_receive( 300 | owner->get_multiplayer()->get_remote_sender_id(), 301 | db); 302 | } 303 | 304 | // Copied from `enet_packet_peer.h` to avoid including it, which is complex due to its dependency with enet. 305 | enum PeerStatistic { 306 | PEER_PACKET_LOSS, 307 | PEER_PACKET_LOSS_VARIANCE, 308 | PEER_PACKET_LOSS_EPOCH, 309 | PEER_ROUND_TRIP_TIME, 310 | PEER_ROUND_TRIP_TIME_VARIANCE, 311 | PEER_LAST_ROUND_TRIP_TIME, 312 | PEER_LAST_ROUND_TRIP_TIME_VARIANCE, 313 | PEER_PACKET_THROTTLE, 314 | PEER_PACKET_THROTTLE_LIMIT, 315 | PEER_PACKET_THROTTLE_COUNTER, 316 | PEER_PACKET_THROTTLE_EPOCH, 317 | PEER_PACKET_THROTTLE_ACCELERATION, 318 | PEER_PACKET_THROTTLE_DECELERATION, 319 | PEER_PACKET_THROTTLE_INTERVAL, 320 | }; 321 | 322 | // Copied from enet.h to avoid including it. 323 | uint64_t ENET_PEER_PACKET_LOSS_SCALE = (1 << 16); 324 | 325 | void GdNetworkInterface::server_update_net_stats(int p_peer, NS::PeerData &r_peer_data) const { 326 | // This function is always called on the server. 327 | NS_ASSERT_COND(is_local_peer_server()); 328 | 329 | ERR_FAIL_COND(!owner); 330 | ERR_FAIL_COND(!owner->get_multiplayer().is_valid()); 331 | ERR_FAIL_COND(!owner->get_multiplayer()->get_multiplayer_peer().is_valid()); 332 | 333 | Ref packet_peer = owner->get_multiplayer()->get_multiplayer_peer(); 334 | Ref enet_peer = packet_peer->call("get_peer", p_peer); 335 | ERR_FAIL_COND(!enet_peer.is_valid()); 336 | 337 | // Using the GDScript bindings to read these so to avoid including `enet_packet_peer.h`, which is complex due to its dependency with enet. 338 | r_peer_data.set_latency(enet_peer->call("get_statistic", PEER_ROUND_TRIP_TIME)); 339 | r_peer_data.set_out_packet_loss_percentage(double(enet_peer->call("get_statistic", PEER_PACKET_LOSS)) / double(ENET_PEER_PACKET_LOSS_SCALE)); 340 | r_peer_data.set_latency_jitter_ms(enet_peer->call("get_statistic", PEER_ROUND_TRIP_TIME_VARIANCE)); 341 | } 342 | -------------------------------------------------------------------------------- /godot4/gd_network_interface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/object/object.h" 4 | #include "modules/network_synchronizer/core/network_interface.h" 5 | 6 | #define FROM_GSTRING(str) std::string(str.utf8().ptr()) 7 | 8 | class GdNetworkInterface final : public NS::NetworkInterface, 9 | public Object { 10 | public: 11 | class Node *owner = nullptr; 12 | std::function on_peer_connected_callback; 13 | std::function on_peer_disconnected_callback; 14 | 15 | public: 16 | GdNetworkInterface(); 17 | virtual ~GdNetworkInterface(); 18 | 19 | public: // ---------------------------------------------------------------- APIs 20 | virtual std::string get_owner_name() const override; 21 | 22 | virtual int get_server_peer() const override { return 1; } 23 | 24 | /// Call this function to start receiving events on peer connection / disconnection. 25 | virtual void start_listening_peer_connection( 26 | std::function p_on_peer_connected_callback, 27 | std::function p_on_peer_disconnected_callback) override; 28 | 29 | /// Emitted when a new peers connects. 30 | void on_peer_connected(int p_peer); 31 | static void __on_peer_connected(GdNetworkInterface *p_ni, int p_peer); 32 | 33 | /// Emitted when a peers disconnects. 34 | void on_peer_disconnected(int p_peer); 35 | 36 | /// Call this function to stop receiving events on peer connection / disconnection. 37 | virtual void stop_listening_peer_connection() override; 38 | 39 | /// Fetch the current client peer_id 40 | virtual int get_local_peer_id() const override; 41 | 42 | /// Fetch the list with all the connected peers. 43 | virtual void fetch_connected_peers(std::vector &p_connected_peers) const override; 44 | 45 | /// Can be used to verify if the local peer is connected to a server. 46 | virtual bool is_local_peer_networked() const override; 47 | 48 | /// Can be used to verify if the local peer is the server. 49 | virtual bool is_local_peer_server() const override; 50 | 51 | virtual void rpc_send(int p_peer_recipient, bool p_reliable, NS::DataBuffer &&p_buffer) override; 52 | void gd_rpc_receive(const Vector &p_args); 53 | 54 | virtual void server_update_net_stats(int p_peer, NS::PeerData &r_peer_data) const override; 55 | }; 56 | 57 | namespace NS_GD_Test { 58 | void test_var_data_conversin(); 59 | }; 60 | -------------------------------------------------------------------------------- /godot4/gd_scene_synchronizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/object/object.h" 4 | #include "modules/network_synchronizer/core/data_buffer.h" 5 | #include "modules/network_synchronizer/core/processor.h" 6 | #include "modules/network_synchronizer/core/var_data.h" 7 | #include "modules/network_synchronizer/godot4/gd_network_interface.h" 8 | #include "modules/network_synchronizer/scene_synchronizer.h" 9 | #include "scene/main/node.h" 10 | 11 | #include "modules/network_synchronizer/core/core.h" 12 | #include "modules/network_synchronizer/core/net_utilities.h" 13 | #include "modules/network_synchronizer/core/scene_synchronizer_debugger.h" 14 | 15 | class GdFileSystem : public NS::FileSystem { 16 | virtual std::string get_base_dir() const override; 17 | virtual std::string get_date() const override; 18 | virtual std::string get_time() const override; 19 | virtual bool make_dir_recursive(const std::string &p_dir_path, bool p_erase_content) const override; 20 | virtual bool store_file_string(const std::string &p_path, const std::string &p_string_file) const override; 21 | virtual bool store_file_buffer(const std::string &p_path, const std::uint8_t *p_src, uint64_t p_length) const override; 22 | virtual bool file_exists(const std::string &p_path) const override; 23 | }; 24 | 25 | class GdSceneSynchronizer : public Node, public NS::SynchronizerManager { 26 | GDCLASS(GdSceneSynchronizer, Node); 27 | 28 | public: 29 | static const uint32_t GLOBAL_SYNC_GROUP_ID; 30 | 31 | public: 32 | static void _bind_methods(); 33 | 34 | GDVIRTUAL0(_update_nodes_relevancy); 35 | 36 | typedef NS::SceneSynchronizer SyncClass; 37 | SyncClass scene_synchronizer; 38 | 39 | // Just used to detect when the low level peer change. 40 | void *low_level_peer = nullptr; 41 | 42 | NS::PHandler event_handler_sync_started = NS::NullPHandler; 43 | NS::PHandler event_handler_sync_paused = NS::NullPHandler; 44 | NS::PHandler event_handler_peer_status_updated = NS::NullPHandler; 45 | NS::PHandler event_handler_state_validated = NS::NullPHandler; 46 | NS::PHandler event_handler_rewind_frame_begin = NS::NullPHandler; 47 | NS::PHandler event_handler_desync_detected = NS::NullPHandler; 48 | 49 | public: 50 | GdSceneSynchronizer(); 51 | ~GdSceneSynchronizer(); 52 | 53 | void _notification(int p_what); 54 | 55 | public: // ---------------------------------------------------------- Properties 56 | void set_netstats_update_interval_sec(float p_delay_in_ms); 57 | float get_netstats_update_interval_sec() const; 58 | 59 | void set_max_fps_acceleration_percentage(double p_acceleration); 60 | double get_max_fps_acceleration_percentage() const; 61 | 62 | void set_max_trickled_nodes_per_update(int p_rate); 63 | int get_max_trickled_nodes_per_update() const; 64 | 65 | void set_frame_confirmation_timespan(real_t p_interval); 66 | real_t get_frame_confirmation_timespan() const; 67 | 68 | void set_nodes_relevancy_update_time(real_t p_time); 69 | real_t get_nodes_relevancy_update_time() const; 70 | 71 | void set_frames_per_seconds(int p_fps); 72 | int get_frames_per_seconds() const; 73 | 74 | public: // ---------------------------------------- Scene Synchronizer Interface 75 | virtual void on_init_synchronizer(bool p_was_generating_ids) override; 76 | virtual void on_uninit_synchronizer() override; 77 | 78 | #ifdef NS_DEBUG_ENABLED 79 | virtual void debug_only_validate_objects() override; 80 | virtual uint64_t debug_only_get_object_id(NS::ObjectHandle p_app_object_handle) const override; 81 | #endif 82 | 83 | virtual void on_add_object_data(NS::ObjectData &p_object_data) override; 84 | 85 | virtual void update_objects_relevancy() override; 86 | 87 | virtual NS::ObjectHandle fetch_app_object(const std::string &p_object_name) override; 88 | virtual std::string get_object_name(NS::ObjectHandle p_app_object_handle) const override; 89 | virtual void setup_synchronizer_for(NS::ObjectHandle p_app_object_handle, NS::ObjectLocalId p_id) override; 90 | 91 | public: // ------------------------------------------------------- RPC Interface 92 | // This funtion is used to sync data betweend the server and the client. 93 | void _rpc_net_sync_reliable(const Vector &p_args); 94 | // This funtion is used to sync data betweend the server and the client. 95 | void _rpc_net_sync_unreliable(const Vector &p_args); 96 | 97 | public: // ---------------------------------------------------------------- APIs 98 | virtual void reset_synchronizer_mode(); 99 | void clear(); 100 | 101 | /// Register a new node and returns its `NodeData`. 102 | NS::ObjectLocalId register_node(Node *p_node); 103 | uint32_t register_node_gdscript(Node *p_node); 104 | void setup_controller( 105 | Node *p_node, 106 | int p_peer, 107 | const Callable &p_collect_input_func, 108 | const Callable &p_are_inputs_different_func, 109 | const Callable &p_process_func); 110 | void unregister_node(Node *p_node); 111 | 112 | /// Returns the node ID. 113 | /// This may return `UINT32_MAX` in various cases: 114 | /// - The node is not registered. 115 | /// - The client doesn't know the ID yet. 116 | uint32_t get_node_id(Node *p_node); 117 | Node *get_node_from_id(uint32_t p_id, bool p_expected = true); 118 | const Node *get_node_from_id_const(uint32_t p_id, bool p_expected = true) const; 119 | 120 | void register_variable(Node *p_node, const StringName &p_variable); 121 | void unregister_variable(Node *p_node, const StringName &p_variable); 122 | 123 | /// Returns the variable ID relative to the `Node`. 124 | /// This may return `UINT32_MAX` in various cases: 125 | /// - The node is not registered. 126 | /// - The variable is not registered. 127 | /// - The client doesn't know the ID yet. 128 | uint32_t get_variable_id(Node *p_node, const StringName &p_variable); 129 | 130 | void set_skip_rewinding(Node *p_node, const StringName &p_variable, bool p_skip_rewinding); 131 | 132 | uint64_t track_variable_changes(Array p_nodes, Array p_vars, const Callable &p_callable, NetEventFlag p_flags = NetEventFlag::DEFAULT); 133 | void untrack_variable_changes(uint64_t p_handle); 134 | 135 | /// You can use the macro `callable_mp()` to register custom C++ function. 136 | uint64_t register_process(Node *p_node, ProcessPhase p_phase, const Callable &p_callable); 137 | void unregister_process(Node *p_node, ProcessPhase p_phase, uint64_t p_handler); 138 | 139 | void setup_simulated_sync( 140 | Node *p_node, 141 | const Callable &p_collect, 142 | const Callable &p_get_size, 143 | const Callable &p_are_equals, 144 | const Callable &p_process); 145 | 146 | /// Setup the trickled sync method for this specific node. 147 | /// The trickled-sync is different from the realtime-sync because the data 148 | /// is streamed and not simulated. 149 | void setup_trickled_sync(Node *p_node, const Callable &p_collect_epoch_func, const Callable &p_apply_epoch_func); 150 | 151 | Array local_controller_get_controlled_nodes() const; 152 | 153 | int get_peer_latency_ms(int p_peer) const; 154 | int get_peer_latency_jitter_ms(int p_peer) const; 155 | float get_peer_packet_loss_percentage(int p_peer) const; 156 | 157 | bool client_is_object_simulating(Node *p_node) const; 158 | bool client_is_object_simulating(NS::ObjectLocalId p_id) const; 159 | bool client_is_object_simulating(NS::ObjectNetId p_id) const; 160 | 161 | /// Creates a realtime sync group containing a list of nodes. 162 | /// The Peers listening to this group will receive the updates only 163 | /// from the nodes within this group. 164 | virtual uint32_t sync_group_create(); 165 | const NS::SyncGroup *sync_group_get(uint32_t p_group_id) const; 166 | 167 | void sync_group_add_node_by_id(uint32_t p_net_id, uint32_t p_group_id, bool p_realtime); 168 | void sync_group_add_node(NS::ObjectData *p_object_data, uint32_t p_group_id, bool p_realtime); 169 | void sync_group_remove_node_by_id(uint32_t p_net_id, uint32_t p_group_id); 170 | void sync_group_remove_node(NS::ObjectData *p_object_data, uint32_t p_group_id); 171 | 172 | /// Use `std::move()` to transfer `p_new_realtime_nodes` and `p_new_trickled_nodes`. 173 | void sync_group_replace_nodes(uint32_t p_group_id, std::vector &&p_new_realtime_nodes, std::vector &&p_new_trickled_nodes); 174 | 175 | void sync_group_remove_all_nodes(uint32_t p_group_id); 176 | void sync_group_move_peer_to(int p_peer_id, uint32_t p_group_id); 177 | uint32_t sync_group_get_peer_group(int p_peer_id) const; 178 | const std::vector *sync_group_get_listening_peers(uint32_t p_group_id) const; 179 | 180 | void sync_group_set_trickled_update_rate_by_id(uint32_t p_node_id, uint32_t p_group_id, real_t p_update_rate); 181 | void sync_group_set_trickled_update_rate(NS::ObjectData *p_object_data, uint32_t p_group_id, real_t p_update_rate); 182 | real_t sync_group_get_trickled_update_rate_by_id(uint32_t p_node_id, uint32_t p_group_id) const; 183 | real_t sync_group_get_trickled_update_rate(const NS::ObjectData *p_object_data, uint32_t p_group_id) const; 184 | 185 | void sync_group_set_user_data(uint32_t p_group_id, uint64_t p_user_ptr); 186 | uint64_t sync_group_get_user_data(uint32_t p_group_id) const; 187 | 188 | bool is_resyncing() const; 189 | bool is_resetting() const; 190 | bool is_rewinding() const; 191 | bool is_end_sync() const; 192 | 193 | void force_state_notify(uint32_t p_sync_group_id); 194 | void force_state_notify_all(); 195 | 196 | void set_enabled(bool p_enable); 197 | 198 | void set_peer_networking_enable(int p_peer, bool p_enable); 199 | bool is_peer_networking_enabled(int p_peer) const; 200 | 201 | /// Returns true if this peer is server. 202 | bool is_server() const; 203 | /// Returns true if this peer is client. 204 | bool is_client() const; 205 | /// Returns true if there is no network. 206 | bool is_no_network() const; 207 | /// Returns true if network is enabled. 208 | bool is_networked() const; 209 | 210 | static void encode(NS::DataBuffer &r_buffer, const NS::VarData &p_val); 211 | static void decode(NS::VarData &r_val, NS::DataBuffer &p_buffer, std::uint8_t p_variable_type); 212 | 213 | static void convert(Variant &r_variant, const NS::VarData &p_vd); 214 | static void convert(NS::VarData &r_vd, const Variant &p_variant); 215 | 216 | static bool compare(const NS::VarData &p_A, const NS::VarData &p_B); 217 | 218 | static std::string stringify(const NS::VarData &p_var_data, bool p_verbose); 219 | }; 220 | 221 | VARIANT_ENUM_CAST(NetEventFlag) 222 | VARIANT_ENUM_CAST(ProcessPhase) 223 | -------------------------------------------------------------------------------- /icons/NetworkedController.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 56 | 59 | 65 | 71 | 77 | 85 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /register_types.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* register_types.cpp */ 3 | /*************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /*************************************************************************/ 8 | /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ 9 | /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /*************************************************************************/ 30 | 31 | /** 32 | @author AndreaCatania 33 | */ 34 | 35 | #include "register_types.h" 36 | 37 | #include "core/config/engine.h" 38 | #include "core/config/project_settings.h" 39 | #include "core/core.h" 40 | #include "core/error/error_macros.h" 41 | #include "core/scene_synchronizer_debugger.h" 42 | #include "godot4/gd_data_buffer.h" 43 | #include "godot4/gd_network_interface.h" 44 | #include "godot4/gd_scene_synchronizer.h" 45 | 46 | #ifdef NS_DEBUG_ENABLED 47 | #include "tests/tests.h" 48 | #endif 49 | 50 | void initialize_network_synchronizer_module(ModuleInitializationLevel p_level) { 51 | if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) { 52 | NS::SceneSynchronizerBase::install_synchronizer( 53 | GdSceneSynchronizer::encode, 54 | GdSceneSynchronizer::decode, 55 | GdSceneSynchronizer::compare, 56 | GdSceneSynchronizer::stringify, 57 | [](const std::string &p_str) { 58 | print_line(p_str.c_str()); 59 | }, 60 | [](const char *p_function, const char *p_file, int p_line, const std::string &p_error, const std::string &p_message, NS::PrintMessageType p_type) { 61 | _err_print_error( 62 | p_function, 63 | p_file, 64 | p_line, 65 | String(p_error.c_str()), 66 | String(p_message.c_str()), 67 | p_type == NS::PrintMessageType::ERROR ? ERR_HANDLER_ERROR : ERR_HANDLER_WARNING); 68 | }, 69 | []() { 70 | _err_flush_stdout(); 71 | }); 72 | 73 | GDREGISTER_CLASS(GdDataBuffer); 74 | GDREGISTER_CLASS(GdSceneSynchronizer); 75 | 76 | memnew(SceneSynchronizerDebugger); 77 | SceneSynchronizerDebugger::singleton()->set_file_system(memnew(GdFileSystem)); 78 | 79 | GLOBAL_DEF("NetworkSynchronizer/debug_server_speedup", false); 80 | GLOBAL_DEF("NetworkSynchronizer/log_debug_rewindings", false); 81 | GLOBAL_DEF("NetworkSynchronizer/log_level", 3); 82 | GLOBAL_DEF("NetworkSynchronizer/log_debug_nodes_relevancy_update", false); 83 | GLOBAL_DEF("NetworkSynchronizer/debugger/dump_enabled", false); 84 | GLOBAL_DEF("NetworkSynchronizer/debugger/dump_classes", Array()); 85 | } else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { 86 | #ifdef NS_DEBUG_ENABLED 87 | List args = OS::get_singleton()->get_cmdline_args(); 88 | if (args.find("--unit-test-netsync")) { 89 | NS_GD_Test::test_var_data_conversin(); 90 | NS_Test::test_all(); 91 | } 92 | #endif 93 | } 94 | } 95 | 96 | void uninitialize_network_synchronizer_module(ModuleInitializationLevel p_level) { 97 | if (p_level != MODULE_INITIALIZATION_LEVEL_SERVERS) { 98 | return; 99 | } 100 | 101 | if (SceneSynchronizerDebugger::singleton()) { 102 | memdelete(SceneSynchronizerDebugger::singleton()->get_file_system()); 103 | memdelete(SceneSynchronizerDebugger::singleton()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /register_types.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* register_types.h */ 3 | /*************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /*************************************************************************/ 8 | /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ 9 | /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /*************************************************************************/ 30 | 31 | /** 32 | @author AndreaCatania 33 | */ 34 | #include "modules/register_module_types.h" 35 | 36 | void initialize_network_synchronizer_module(ModuleInitializationLevel p_level); 37 | void uninitialize_network_synchronizer_module(ModuleInitializationLevel p_level); 38 | -------------------------------------------------------------------------------- /tests/local_network.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/core.h" 4 | #include "../core/data_buffer.h" 5 | #include "../core/network_interface.h" 6 | #include "../core/processor.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | NS_NAMESPACE_BEGIN 14 | class LocalNetworkInterface; 15 | 16 | struct LocalNetworkProps { 17 | // Ping in seconds. 18 | float rtt_seconds = 0.0; 19 | 20 | // From 0.0 to 1.0 21 | float reorder = 0.0; 22 | 23 | // From 0.0 to 1.0 24 | float packet_loss = 0.0; 25 | }; 26 | 27 | struct PendingPacket { 28 | // -1 means unreliable 29 | int reliable_packet_index = -1; 30 | float delay = 0.0; 31 | int peer_recipient = -1; 32 | std::string object_name; 33 | DataBuffer data_buffer; 34 | }; 35 | 36 | class LocalNetwork { 37 | bool is_server = false; 38 | bool is_no_net = false; 39 | int this_peer = 0; 40 | 41 | int peer_counter = 2; 42 | std::map connected_peers; 43 | 44 | std::map registered_objects; 45 | 46 | std::vector> sending_packets; 47 | 48 | public: 49 | LocalNetworkProps *network_properties = nullptr; 50 | 51 | Processor connected_event; 52 | Processor disconnected_event; 53 | 54 | public: 55 | int get_peer() const; 56 | 57 | bool get_is_server() const { 58 | return is_server; 59 | } 60 | 61 | bool get_is_no_net() const { 62 | return is_no_net; 63 | } 64 | 65 | const std::map &get_connected_peers() const; 66 | void start_as_no_net(); 67 | void start_as_server(); 68 | 69 | void start_as_client(LocalNetwork &p_server_network); 70 | 71 | void register_object(LocalNetworkInterface &p_interface); 72 | 73 | void rpc_send(std::string p_object_name, int p_peer_recipient, bool p_reliable, NS::DataBuffer &&p_data_buffer); 74 | 75 | void process(float p_delta); 76 | 77 | private: 78 | void rpc_send_internal(const std::shared_ptr &p_packet); 79 | void rpc_receive_internal(int p_peer_sender, const std::shared_ptr &p_packet); 80 | }; 81 | 82 | class LocalNetworkInterface final : public NS::NetworkInterface { 83 | std::string name; 84 | LocalNetwork *network = nullptr; 85 | 86 | public: 87 | int authoritative_peer_id = 0; 88 | NS::PHandler processor_handler_connected = NS::NullPHandler; 89 | NS::PHandler processor_handler_disconnected = NS::NullPHandler; 90 | 91 | void init(LocalNetwork &p_network, const std::string &p_unique_name, int p_authoritative_peer); 92 | 93 | std::vector &get_rpcs_info() { 94 | return rpcs_info; 95 | } 96 | 97 | virtual std::string get_owner_name() const override { 98 | return name; 99 | } 100 | 101 | virtual int get_server_peer() const override { 102 | return 1; 103 | } 104 | 105 | /// Call this function to start receiving events on peer connection / disconnection. 106 | virtual void start_listening_peer_connection( 107 | std::function p_on_peer_connected_callback, 108 | std::function p_on_peer_disconnected_callback) override; 109 | 110 | /// Call this function to stop receiving events on peer connection / disconnection. 111 | virtual void stop_listening_peer_connection() override; 112 | 113 | /// Fetch the current client peer_id 114 | virtual int get_local_peer_id() const override; 115 | 116 | /// Fetch the list with all the connected peers. 117 | virtual void fetch_connected_peers(std::vector &p_connected_peers) const override; 118 | 119 | /// Can be used to verify if the local peer is connected to a server. 120 | virtual bool is_local_peer_networked() const override; 121 | 122 | /// Can be used to verify if the local peer is the server. 123 | virtual bool is_local_peer_server() const override; 124 | 125 | virtual void rpc_send(int p_peer_recipient, bool p_reliable, NS::DataBuffer &&p_data_buffer) override; 126 | 127 | virtual void server_update_net_stats(int p_peer, PeerData &r_peer_data) const override; 128 | }; 129 | 130 | NS_NAMESPACE_END 131 | 132 | namespace NS_Test { 133 | void test_local_network(); 134 | }; -------------------------------------------------------------------------------- /tests/local_scene.cpp: -------------------------------------------------------------------------------- 1 | #include "local_scene.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | NS_NAMESPACE_BEGIN 8 | LocalSceneObject::~LocalSceneObject() { 9 | const NS::ObjectLocalId id = find_local_id(); 10 | if (get_scene() && get_scene()->scene_sync && id != NS::ObjectLocalId::NONE) { 11 | get_scene()->scene_sync->unregister_app_object(id); 12 | } 13 | } 14 | 15 | NS::ObjectLocalId LocalSceneObject::find_local_id() const { 16 | if (get_scene()) { 17 | return get_scene()->scene_sync->find_object_local_id(get_scene()->scene_sync->to_handle(this)); 18 | } 19 | return NS::ObjectLocalId::NONE; 20 | } 21 | 22 | LocalSceneSynchronizer::LocalSceneSynchronizer(bool p_disable_sub_ticking) : 23 | SceneSynchronizer(true, p_disable_sub_ticking), 24 | LocalSceneObject("LocalSceneSynchronizer") { 25 | } 26 | 27 | LocalSceneSynchronizer::~LocalSceneSynchronizer() { 28 | } 29 | 30 | void (*prev_var_data_encode_func)(NS::DataBuffer &r_buffer, const NS::VarData &p_val) = nullptr; 31 | void (*prev_var_data_decode_func)(NS::VarData &r_val, NS::DataBuffer &p_buffer, std::uint8_t p_variable_type) = nullptr; 32 | bool (*prev_var_data_compare_func)(const VarData &p_A, const VarData &p_B) = nullptr; 33 | std::string (*prev_var_data_stringify_func)(const VarData &p_var_data, bool p_verbose) = nullptr; 34 | 35 | void LocalSceneSynchronizer::install_local_scene_sync() { 36 | // Store the already set functions, so we can restore it again after the tests are done. 37 | prev_var_data_encode_func = SceneSynchronizerBase::var_data_encode_func; 38 | prev_var_data_decode_func = SceneSynchronizerBase::var_data_decode_func; 39 | prev_var_data_compare_func = SceneSynchronizerBase::var_data_compare_func; 40 | prev_var_data_stringify_func = SceneSynchronizerBase::var_data_stringify_func; 41 | 42 | install_synchronizer( 43 | [](NS::DataBuffer &r_buffer, const NS::VarData &p_val) { 44 | r_buffer.add(p_val.type); 45 | if (p_val.type == 3) { 46 | // Shared buffer array of integers 47 | const std::vector &val = *std::static_pointer_cast>(p_val.shared_buffer); 48 | r_buffer.add(int(val.size())); 49 | for (int v : val) { 50 | r_buffer.add(v); 51 | } 52 | } else { 53 | r_buffer.add_bits(reinterpret_cast(&p_val.data), sizeof(p_val.data) * 8); 54 | // The shared buffer must have the type set to 3. 55 | NS_ASSERT_COND(!p_val.shared_buffer); 56 | } 57 | }, 58 | [](NS::VarData &r_val, NS::DataBuffer &p_buffer, std::uint8_t p_variable_type) { 59 | p_buffer.read(r_val.type); 60 | if (r_val.type == 3) { 61 | int size; 62 | p_buffer.read(size); 63 | std::vector array; 64 | for (int i = 0; i < size; i++) { 65 | int v; 66 | p_buffer.read(v); 67 | array.push_back(v); 68 | } 69 | r_val.shared_buffer = std::make_shared>(array); 70 | } else { 71 | p_buffer.read_bits(reinterpret_cast(&r_val.data), sizeof(r_val.data) * 8); 72 | } 73 | }, 74 | [](const NS::VarData &p_A, const NS::VarData &p_B) -> bool { 75 | if (p_A.type == 3 && p_B.type == 3) { 76 | const std::vector &A = *std::static_pointer_cast>(p_A.shared_buffer); 77 | const std::vector &B = *std::static_pointer_cast>(p_B.shared_buffer); 78 | if (A.size() == B.size()) { 79 | for (int i = 0; i < A.size(); i++) { 80 | if (A[i] != B[i]) { 81 | return false; 82 | } 83 | } 84 | return true; 85 | } 86 | return false; 87 | } else { 88 | return memcmp(&p_A.data, &p_B.data, sizeof(p_A.data)) == 0; 89 | } 90 | }, 91 | [](const NS::VarData &p_var_data, bool p_verbose) -> std::string { 92 | if (p_var_data.type == 1) { 93 | return std::to_string(p_var_data.data.f32); 94 | } else if (p_var_data.type == 2) { 95 | return std::string("[" + std::to_string(p_var_data.data.vec.x) + ", " + std::to_string(p_var_data.data.vec.y) + ", " + std::to_string(p_var_data.data.vec.z) + "]"); 96 | } else if (p_var_data.type == 3) { 97 | return std::string("[Array of ints]"); 98 | } else { 99 | return std::string("[No stringify supported for this VarData type: `" + std::to_string(p_var_data.type) + "`]"); 100 | } 101 | }, 102 | print_line_func, 103 | print_code_message_func, 104 | print_flush_stdout_func); 105 | } 106 | 107 | void LocalSceneSynchronizer::uninstall_local_scene_sync() { 108 | // Restore the previously set functions. 109 | install_synchronizer( 110 | prev_var_data_encode_func, 111 | prev_var_data_decode_func, 112 | prev_var_data_compare_func, 113 | prev_var_data_stringify_func, 114 | print_line_func, 115 | print_code_message_func, 116 | print_flush_stdout_func); 117 | 118 | prev_var_data_encode_func = nullptr; 119 | prev_var_data_decode_func = nullptr; 120 | prev_var_data_compare_func = nullptr; 121 | prev_var_data_stringify_func = nullptr; 122 | } 123 | 124 | void LocalSceneSynchronizer::on_scene_entry() { 125 | get_network_interface().init(get_scene()->get_network(), name, authoritative_peer_id); 126 | setup(*this); 127 | register_app_object(to_handle(this)); 128 | } 129 | 130 | void LocalSceneSynchronizer::setup_synchronizer(LocalSceneSynchronizer &p_scene_sync, ObjectLocalId p_id) { 131 | local_id = p_id; 132 | } 133 | 134 | void LocalSceneSynchronizer::on_scene_exit() { 135 | on_app_object_removed(to_handle(this)); 136 | } 137 | 138 | ObjectHandle LocalSceneSynchronizer::fetch_app_object(const std::string &p_object_name) { 139 | LocalSceneObject *lso = get_scene()->fetch_object(p_object_name.c_str()); 140 | if (lso) { 141 | return to_handle(lso); 142 | } 143 | return ObjectHandle::NONE; 144 | } 145 | 146 | uint64_t LocalSceneSynchronizer::debug_only_get_object_id(ObjectHandle p_app_object_handle) const { 147 | // No Object ID. 148 | return 0; 149 | } 150 | 151 | std::string LocalSceneSynchronizer::fetch_object_name(ObjectHandle p_app_object_handle) const { 152 | return from_handle(p_app_object_handle)->name; 153 | } 154 | 155 | void LocalSceneSynchronizer::setup_synchronizer_for(ObjectHandle p_app_object_handle, ObjectLocalId p_id, SchemeId p_scheme_id) { 156 | from_handle(p_app_object_handle)->scheme_id = p_scheme_id; 157 | from_handle(p_app_object_handle)->setup_synchronizer(*get_scene()->scene_sync, p_id); 158 | } 159 | 160 | void LocalSceneSynchronizer::clear_scene() { 161 | scene_owner = nullptr; 162 | } 163 | 164 | LocalScene *LocalSceneObject::get_scene() const { 165 | return scene_owner; 166 | } 167 | 168 | void LocalScene::start_as_no_net() { 169 | network.start_as_no_net(); 170 | } 171 | 172 | void LocalScene::start_as_server() { 173 | network.start_as_server(); 174 | } 175 | 176 | void LocalScene::start_as_client(LocalScene &p_server) { 177 | network.start_as_client(p_server.network); 178 | } 179 | 180 | int LocalScene::get_peer() const { 181 | return network.get_peer(); 182 | } 183 | 184 | int LocalScene::fetch_object_index(const char *p_object_name) const { 185 | int i = 0; 186 | for (auto o : objects) { 187 | if (o->name == p_object_name) { 188 | return i; 189 | } 190 | i++; 191 | } 192 | return -1; 193 | } 194 | 195 | void LocalScene::remove_object(const char *p_object_name) { 196 | const int obj_index = fetch_object_index(p_object_name); 197 | if (obj_index >= 0) { 198 | std::shared_ptr obj = objects[obj_index]; 199 | obj->on_scene_exit(); 200 | obj->scene_owner = nullptr; 201 | VecFunc::remove_at_unordered(objects, obj_index); 202 | } 203 | } 204 | 205 | void LocalScene::process(float p_delta) { 206 | scene_sync->process(p_delta); 207 | // Clear any pending RPC. 208 | // NOTE: The network process is executed after the scene_sync so any pending 209 | // rpc is dispatched right away. When the rpc is sent, it's received 210 | // right away, so it's not needed to process it before the scene_sync. 211 | network.process(p_delta); 212 | } 213 | 214 | void LocalScene::process_only_network(float p_delta) { 215 | network.process(p_delta); 216 | } 217 | 218 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /tests/local_scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/core.h" 4 | #include "../core/network_interface.h" 5 | #include "../core/peer_networked_controller.h" 6 | #include "../scene_synchronizer.h" 7 | #include "local_network.h" 8 | #include 9 | #include 10 | 11 | NS_NAMESPACE_BEGIN 12 | class LocalSceneObject { 13 | friend class LocalSceneSynchronizer; 14 | 15 | const std::size_t type_index; 16 | 17 | protected: 18 | friend class LocalScene; 19 | class LocalScene *scene_owner = nullptr; 20 | SchemeId scheme_id = SchemeId::DEFAULT; 21 | int authoritative_peer_id = 0; 22 | 23 | public: 24 | std::string name; 25 | 26 | static std::size_t fetch_class_type_index(const char *cn) { 27 | return std::hash{}(cn); 28 | } 29 | 30 | LocalSceneObject(const char *p_class_name) : 31 | type_index(fetch_class_type_index(p_class_name)) { 32 | } 33 | 34 | virtual ~LocalSceneObject(); 35 | 36 | std::size_t get_type_index() const { 37 | return type_index; 38 | } 39 | 40 | class LocalScene *get_scene() const; 41 | 42 | virtual void on_scene_entry() { 43 | } 44 | 45 | SchemeId get_scheme_id() const { 46 | return scheme_id; 47 | } 48 | 49 | virtual void setup_synchronizer(class LocalSceneSynchronizer &p_scene_sync, ObjectLocalId p_id) { 50 | } 51 | 52 | virtual void on_scene_exit() { 53 | } 54 | 55 | ObjectLocalId find_local_id() const; 56 | }; 57 | 58 | class LocalSceneSynchronizer : public SceneSynchronizer, public SynchronizerManager, public LocalSceneObject { 59 | public: 60 | ObjectLocalId local_id; 61 | 62 | LocalSceneSynchronizer(bool p_disable_sub_ticking = false); 63 | virtual ~LocalSceneSynchronizer(); 64 | 65 | static void install_local_scene_sync(); 66 | static void uninstall_local_scene_sync(); 67 | 68 | virtual void on_scene_entry() override; 69 | virtual void setup_synchronizer(class LocalSceneSynchronizer &p_scene_sync, ObjectLocalId p_id) override; 70 | virtual void on_scene_exit() override; 71 | 72 | /// NOTE: THIS FUNCTION MUST RETURN THE POINTER THAT POINTS TO `BaseType` SPECIFIED IN `SceneSynchronizer`. 73 | /// If you have a pointer pointing to a parent class, cast it using 74 | /// `static_cast` first, or you will cause a segmentation fault. 75 | virtual ObjectHandle fetch_app_object(const std::string &p_object_name) override; 76 | #ifdef NS_DEBUG_ENABLED 77 | virtual uint64_t debug_only_get_object_id(ObjectHandle p_app_object_handle) const override; 78 | #endif 79 | virtual std::string fetch_object_name(ObjectHandle p_app_object_handle) const override; 80 | virtual void setup_synchronizer_for(ObjectHandle p_app_object_handle, ObjectLocalId p_id, SchemeId p_scheme_id) override; 81 | 82 | std::function &p_partial_update_simulated_objects_info_indices, 85 | VarData &r_custom_data)> snapshot_get_custom_data_func; 86 | 87 | std::function &p_partial_update_objects, 88 | VarData &r_custom_data, 89 | const VarData &p_custom_data_from_server_snapshot)> snapshot_merge_custom_data_for_partial_update_func; 90 | 91 | std::function snapshot_get_custom_data_type_func; 92 | 93 | std::function snapshot_set_custom_data_func; 94 | 95 | virtual bool snapshot_get_custom_data( 96 | const SyncGroup *p_group, 97 | bool p_is_partial_update, 98 | const std::vector &p_partial_update_simulated_objects_info_indices, 99 | VarData &r_custom_data) override { 100 | if (snapshot_get_custom_data_func) { 101 | return snapshot_get_custom_data_func(p_group, p_is_partial_update, p_partial_update_simulated_objects_info_indices, r_custom_data); 102 | } else { 103 | return SynchronizerManager::snapshot_get_custom_data(p_group, p_is_partial_update, p_partial_update_simulated_objects_info_indices, r_custom_data); 104 | } 105 | } 106 | 107 | virtual bool snapshot_merge_custom_data_for_partial_update( 108 | const std::vector &p_partial_update_objects, 109 | VarData &r_custom_data, 110 | const VarData &p_custom_data_from_server_snapshot) override { 111 | if (snapshot_merge_custom_data_for_partial_update_func) { 112 | return snapshot_merge_custom_data_for_partial_update_func(p_partial_update_objects, r_custom_data, p_custom_data_from_server_snapshot); 113 | } else { 114 | return SynchronizerManager::snapshot_merge_custom_data_for_partial_update(p_partial_update_objects, r_custom_data, p_custom_data_from_server_snapshot); 115 | } 116 | } 117 | 118 | virtual std::uint8_t snapshot_get_custom_data_type() const override { 119 | if (snapshot_get_custom_data_type_func) { 120 | return snapshot_get_custom_data_type_func(); 121 | } else { 122 | return SynchronizerManager::snapshot_get_custom_data_type(); 123 | } 124 | } 125 | 126 | virtual void snapshot_set_custom_data(const VarData &r_custom_data) override { 127 | if (snapshot_set_custom_data_func) { 128 | snapshot_set_custom_data_func(r_custom_data); 129 | } 130 | } 131 | 132 | void clear_scene(); 133 | }; 134 | 135 | class LocalSceneSynchronizerNoSubTicks : public LocalSceneSynchronizer { 136 | public: 137 | LocalSceneSynchronizerNoSubTicks() : 138 | LocalSceneSynchronizer(true) { 139 | } 140 | }; 141 | 142 | class LocalScene { 143 | LocalNetwork network; 144 | std::vector> objects; 145 | 146 | public: 147 | LocalSceneSynchronizer *scene_sync = nullptr; 148 | 149 | public: 150 | LocalNetwork &get_network() { 151 | return network; 152 | } 153 | 154 | const LocalNetwork &get_network() const { 155 | return network; 156 | } 157 | 158 | // Start the scene as no network. 159 | void start_as_no_net(); 160 | 161 | // Start the scene as server. 162 | void start_as_server(); 163 | 164 | // Start the scene as client connected to the server. 165 | void start_as_client(LocalScene &p_server); 166 | 167 | int get_peer() const; 168 | 169 | template 170 | T *add_existing_object(const std::shared_ptr &p_object, const std::string &p_object_name, int p_authoritative_peer); 171 | 172 | template 173 | T *add_object(const std::string &p_object_name, int p_authoritative_peer); 174 | 175 | template 176 | T *fetch_object(const char *p_object_name); 177 | 178 | int fetch_object_index(const char *p_object_name) const; 179 | 180 | void remove_object(const char *p_object_name); 181 | 182 | void process(float p_delta); 183 | void process_only_network(float p_delta); 184 | }; 185 | 186 | template 187 | T *LocalScene::add_object(const std::string &p_object_name, int p_authoritative_peer) { 188 | // Make sure the name are unique. 189 | NS_ASSERT_COND(!fetch_object(p_object_name.c_str())); 190 | 191 | std::shared_ptr object = std::make_shared(); 192 | return add_existing_object(object, p_object_name, p_authoritative_peer); 193 | } 194 | 195 | template 196 | T *LocalScene::add_existing_object(const std::shared_ptr &p_object, const std::string &p_object_name, int p_authoritative_peer) { 197 | // Make sure the name are unique. 198 | NS_ASSERT_COND(!fetch_object(p_object_name.c_str())); 199 | 200 | objects.push_back(p_object); 201 | p_object->scene_owner = this; 202 | p_object->name = p_object_name; 203 | p_object->authoritative_peer_id = p_authoritative_peer; 204 | p_object->on_scene_entry(); 205 | return p_object.get(); 206 | } 207 | 208 | template 209 | T *LocalScene::fetch_object(const char *p_object_name) { 210 | for (auto o : objects) { 211 | if (o->name == p_object_name) { 212 | return static_cast(o.get()); 213 | } 214 | } 215 | return nullptr; 216 | } 217 | 218 | 219 | NS_NAMESPACE_END -------------------------------------------------------------------------------- /tests/test_AI_simulation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO convert to DOC-TEST 4 | // I din't that right away because at the moment there are some modules that 5 | // doesn't support it and it's difficult to test it. 6 | 7 | namespace NS_Test { 8 | namespace NS_AI_Test { 9 | void test_AI_simulation(); 10 | }; 11 | }; -------------------------------------------------------------------------------- /tests/test_data_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace NS_Test { 4 | void test_data_buffer(); 5 | }; 6 | -------------------------------------------------------------------------------- /tests/test_doll_simulation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO convert to DOC-TEST 4 | // I din't that right away because at the moment there are some modules that 5 | // doesn't support it and it's difficult to test it. 6 | 7 | namespace NS_Test { 8 | void test_doll_simulation(); 9 | }; 10 | -------------------------------------------------------------------------------- /tests/test_math_lib.cpp: -------------------------------------------------------------------------------- 1 | #include "test_math_lib.h" 2 | 3 | #include "../core/net_math.h" 4 | #include "NetworkSynchronizer/core/ensure.h" 5 | 6 | namespace NS_Test { 7 | Vec3::operator NS::VarData() const { 8 | NS::VarData vd; 9 | vd.data.vec_f32.x = x; 10 | vd.data.vec_f32.y = y; 11 | vd.data.vec_f32.z = z; 12 | return vd; 13 | } 14 | 15 | 16 | Vec3 Vec3::from(const NS::VarData &p_vd) { 17 | Vec3 v; 18 | v.x = p_vd.data.vec_f32.x; 19 | v.y = p_vd.data.vec_f32.y; 20 | v.z = p_vd.data.vec_f32.z; 21 | return v; 22 | } 23 | 24 | Vec3 Vec3::operator+(const Vec3 &p_vd) const { 25 | Vec3 v = *this; 26 | v += p_vd; 27 | return v; 28 | } 29 | 30 | Vec3 &Vec3::operator+=(const Vec3 &p_vd) { 31 | x += p_vd.x; 32 | y += p_vd.y; 33 | z += p_vd.z; 34 | return *this; 35 | } 36 | 37 | Vec3 Vec3::operator-(const Vec3 &p_vd) const { 38 | Vec3 v = *this; 39 | v -= p_vd; 40 | return v; 41 | } 42 | 43 | Vec3 &Vec3::operator-=(const Vec3 &p_vd) { 44 | x -= p_vd.x; 45 | y -= p_vd.y; 46 | z -= p_vd.z; 47 | return *this; 48 | } 49 | 50 | Vec3 Vec3::operator/(const Vec3 &p_vd) const { 51 | Vec3 v = *this; 52 | v /= p_vd; 53 | return v; 54 | } 55 | 56 | Vec3 &Vec3::operator/=(const Vec3 &p_vd) { 57 | x /= p_vd.x; 58 | y /= p_vd.y; 59 | z /= p_vd.z; 60 | return *this; 61 | } 62 | 63 | Vec3 Vec3::operator*(const Vec3 &p_vd) const { 64 | Vec3 v = *this; 65 | v *= p_vd; 66 | return v; 67 | } 68 | 69 | Vec3 &Vec3::operator*=(const Vec3 &p_vd) { 70 | x *= p_vd.x; 71 | y *= p_vd.y; 72 | z *= p_vd.z; 73 | return *this; 74 | } 75 | 76 | Vec3 Vec3::operator/(const float p_val) const { 77 | Vec3 v = *this; 78 | v /= p_val; 79 | return v; 80 | } 81 | 82 | Vec3 &Vec3::operator/=(const float &p_val) { 83 | x /= p_val; 84 | y /= p_val; 85 | z /= p_val; 86 | return *this; 87 | } 88 | 89 | Vec3 Vec3::operator*(const float p_val) const { 90 | Vec3 v = *this; 91 | v *= p_val; 92 | return v; 93 | } 94 | 95 | Vec3 &Vec3::operator*=(const float &p_val) { 96 | x *= p_val; 97 | y *= p_val; 98 | z *= p_val; 99 | return *this; 100 | } 101 | 102 | float Vec3::length() const { 103 | return std::sqrt(x * x + y * y + z * z); 104 | } 105 | 106 | void Vec3::normalize() { 107 | float l = length(); 108 | if (l > 0.0001) { 109 | *this /= l; 110 | } else { 111 | x = 0.0; 112 | y = 0.0; 113 | z = 0.0; 114 | } 115 | } 116 | 117 | Vec3 Vec3::normalized() const { 118 | Vec3 v = *this; 119 | v.normalize(); 120 | return v; 121 | } 122 | 123 | float Vec3::distance_to(const Vec3 &p_v) const { 124 | Vec3 v = *this; 125 | v -= p_v; 126 | return v.length(); 127 | } 128 | 129 | void test_math_trigonometry() { 130 | // We'll test angles from -2π to +2π 131 | constexpr int STEPS = 100000; 132 | constexpr float START_ANGLE = -20.0f * NS::MathFunc::PI; 133 | constexpr float END_ANGLE = 20.0f * NS::MathFunc::PI; 134 | float step_size = (END_ANGLE - START_ANGLE) / STEPS; 135 | 136 | // For atan2 tests, we do y and x in [-2π .. 2π], but let's just re-use the loop 137 | float max_diff_sin = 0.0f, sum_diff_sin = 0.0f; 138 | float max_diff_cos = 0.0f, sum_diff_cos = 0.0f; 139 | float max_diff_at2 = 0.0f, sum_diff_at2 = 0.0f; 140 | int count_at2 = 0; 141 | 142 | // Sin/Cos test 143 | float angle = START_ANGLE; 144 | for (int i = 0; i <= STEPS; ++i) { 145 | float cross_s = NS::MathFunc::sin(angle); 146 | float cross_c = NS::MathFunc::cos(angle); 147 | float std_s = std::sin(angle); 148 | float std_c = std::cos(angle); 149 | 150 | float ds = std::fabs(cross_s - std_s); 151 | float dc = std::fabs(cross_c - std_c); 152 | if (ds > max_diff_sin) 153 | max_diff_sin = ds; 154 | if (dc > max_diff_cos) 155 | max_diff_cos = dc; 156 | sum_diff_sin += ds; 157 | sum_diff_cos += dc; 158 | 159 | angle += step_size; 160 | } 161 | 162 | // ATan2 test 163 | // We'll just do a grid in x,y in [-2, +2], for instance 164 | const int GRID_STEPS = 501; 165 | float grid_min = -2.0f; 166 | float grid_max = 2.0f; 167 | float gx_step = (grid_max - grid_min) / (GRID_STEPS - 1); 168 | 169 | for (int ix = 0; ix < GRID_STEPS; ++ix) { 170 | float px = grid_min + ix * gx_step; 171 | for (int iy = 0; iy < GRID_STEPS; ++iy) { 172 | float py = grid_min + iy * gx_step; 173 | 174 | float cross_at = NS::MathFunc::atan2(py, px); 175 | float std_at = std::atan2(py, px); 176 | float diff = std::fabs(NS::MathFunc::angle_difference(cross_at, std_at)); 177 | 178 | if (diff > max_diff_at2) 179 | max_diff_at2 = diff; 180 | sum_diff_at2 += diff; 181 | 182 | ++count_at2; 183 | } 184 | } 185 | 186 | NS_ASSERT_COND(max_diff_sin < 0.005); 187 | NS_ASSERT_COND(max_diff_cos < 0.005); 188 | NS_ASSERT_COND(max_diff_at2 < 0.0001); 189 | } 190 | 191 | void test_math() { 192 | test_math_trigonometry(); 193 | } 194 | }; //namespace NS_Test -------------------------------------------------------------------------------- /tests/test_math_lib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/var_data.h" 4 | 5 | namespace NS_Test { 6 | 7 | struct Vec3 { 8 | float x = 0.f; 9 | float y = 0.f; 10 | float z = 0.f; 11 | 12 | Vec3() = default; 13 | Vec3(float px, float py, float pz) : 14 | x(px), y(py), z(pz) {} 15 | 16 | operator NS::VarData() const; 17 | static Vec3 from(const NS::VarData &p_vd); 18 | 19 | Vec3 operator+(const Vec3 &p_vd) const; 20 | Vec3 &operator+=(const Vec3 &p_vd); 21 | 22 | Vec3 operator-(const Vec3 &p_vd) const; 23 | Vec3 &operator-=(const Vec3 &p_vd); 24 | 25 | Vec3 operator/(const Vec3 &p_vd) const; 26 | Vec3 &operator/=(const Vec3 &p_vd); 27 | 28 | Vec3 operator*(const Vec3 &p_vd) const; 29 | Vec3 &operator*=(const Vec3 &p_vd); 30 | 31 | Vec3 operator/(const float p_val) const; 32 | Vec3 &operator/=(const float &p_val); 33 | 34 | Vec3 operator*(const float p_val) const; 35 | Vec3 &operator*=(const float &p_val); 36 | 37 | float length() const; 38 | void normalize(); 39 | Vec3 normalized() const; 40 | 41 | float distance_to(const Vec3 &p_v) const; 42 | }; 43 | 44 | void test_math(); 45 | }; //namespace NS_Test 46 | -------------------------------------------------------------------------------- /tests/test_netsync_bit_array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/bit_array.h" 4 | 5 | #include "tests/test_macros.h" 6 | #include 7 | 8 | namespace test_netsync_BitArray { 9 | 10 | TEST_CASE("[NetSync][BitArray] Read and write") { 11 | BitArray array; 12 | int offset = 0; 13 | int bits = {}; 14 | std::uint64_t value = {}; 15 | 16 | SUBCASE("[NetSync][BitArray] One bit") { 17 | bits = 1; 18 | 19 | SUBCASE("[NetSync][BitArray] One") { 20 | value = 0b1; 21 | } 22 | SUBCASE("[NetSync][BitArray] Zero") { 23 | value = 0b0; 24 | } 25 | } 26 | SUBCASE("[NetSync][BitArray] 16 mixed bits") { 27 | bits = 16; 28 | value = 0b1010101010101010; 29 | } 30 | SUBCASE("[NetSync][BitArray] One and 4 zeroes") { 31 | bits = 5; 32 | value = 0b10000; 33 | } 34 | SUBCASE("[NetSync][BitArray] 64 bits") { 35 | bits = 64; 36 | 37 | SUBCASE("[NetSync][BitArray] One") { 38 | value = UINT64_MAX; 39 | } 40 | SUBCASE("[NetSync][BitArray] Zero") { 41 | value = 0; 42 | } 43 | } 44 | SUBCASE("[NetSync][BitArray] One bit with offset") { 45 | bits = 1; 46 | offset = 64; 47 | array.resize_in_bits(offset); 48 | 49 | SUBCASE("[NetSync][BitArray] One") { 50 | array.store_bits(0, UINT64_MAX, 64); 51 | value = 0b0; 52 | } 53 | SUBCASE("[NetSync][BitArray] Zero") { 54 | array.store_bits(0, 0, 64); 55 | value = 0b1; 56 | } 57 | } 58 | 59 | array.resize_in_bits(offset + bits); 60 | array.store_bits(offset, value, bits); 61 | std::uint64_t buffer_val = 0; 62 | CHECK_MESSAGE(array.read_bits(offset, bits, buffer_val), "Reading failed."); 63 | CHECK_MESSAGE((buffer_val == value), "Should read the same value"); 64 | } 65 | 66 | TEST_CASE("[NetSync][BitArray] Constructing from Vector") { 67 | std::vector data; 68 | data.push_back(-1); 69 | data.push_back(0); 70 | data.push_back(1); 71 | 72 | const BitArray array(data); 73 | CHECK_MESSAGE(array.size_in_bits() == data.size() * 8.0, "Number of bits must be equal to size of original data"); 74 | CHECK_MESSAGE(array.size_in_bytes() == data.size(), "Number of bytes must be equal to size of original data"); 75 | for (int i = 0; i < data.size(); ++i) { 76 | std::uint64_t buffer_val = 0; 77 | CHECK_MESSAGE(array.read_bits(i * 8, 8, buffer_val), "Reading should never fail."); 78 | CHECK_MESSAGE(std::uint8_t(buffer_val) == data[i], "Readed bits should be equal to the original"); 79 | } 80 | } 81 | 82 | TEST_CASE("[NetSync][BitArray] Pre-allocation and zeroing") { 83 | constexpr std::uint64_t value = std::numeric_limits::max(); 84 | constexpr int bits = sizeof(value); 85 | 86 | BitArray array(bits); 87 | CHECK_MESSAGE(array.size_in_bits() == bits, "Number of bits must be equal to allocated"); 88 | array.store_bits(0, value, bits); 89 | array.zero(); 90 | std::uint64_t buffer_val = 0; 91 | CHECK_MESSAGE(array.read_bits(0, bits, buffer_val), "Reading should never fail."); 92 | CHECK_MESSAGE(buffer_val == 0, "Should read zero"); 93 | } 94 | } //namespace test_netsync_BitArray 95 | -------------------------------------------------------------------------------- /tests/test_processor.cpp: -------------------------------------------------------------------------------- 1 | #include "test_processor.h" 2 | 3 | #include "../core/ensure.h" 4 | #include "../core/processor.h" 5 | #include "NetworkSynchronizer/core/event_processor.h" 6 | 7 | void test_internal_processor() { 8 | NS::Processor test_event; 9 | NS_ASSERT_COND(test_event.size() == 0); 10 | 11 | int event_execution_counter = 0; 12 | int last_event_arg_a = 0; 13 | int last_event_arg_b = 0; 14 | 15 | // Test the `bind` & `breoadcast` APIs. 16 | NS::PHandler handler_index = test_event.bind([&](int a, int b) { 17 | event_execution_counter++; 18 | last_event_arg_a = a; 19 | last_event_arg_b = b; 20 | }); 21 | 22 | NS_ASSERT_COND(handler_index != NS::NullPHandler); 23 | NS_ASSERT_COND(test_event.is_bind(handler_index)); 24 | NS_ASSERT_COND(test_event.size() == 1); 25 | 26 | test_event.broadcast(1, 2); 27 | 28 | NS_ASSERT_COND_MSG(event_execution_counter == 1, "The event should have called the handler at this point."); 29 | NS_ASSERT_COND_MSG(last_event_arg_a == 1, "The event should have called the handler at this point."); 30 | NS_ASSERT_COND_MSG(last_event_arg_b == 2, "The event should have called the handler at this point."); 31 | 32 | // Test the `unbind` API. 33 | test_event.unbind(handler_index); 34 | NS_ASSERT_COND(!test_event.is_bind(handler_index)); 35 | NS_ASSERT_COND(test_event.size() == 0); 36 | 37 | test_event.broadcast(3, 4); 38 | 39 | NS_ASSERT_COND_MSG(event_execution_counter == 1, "The event should NOT have called the handler at this point."); 40 | NS_ASSERT_COND_MSG(last_event_arg_a == 1, "The event should NOT have called the handler at this point."); 41 | NS_ASSERT_COND_MSG(last_event_arg_b == 2, "The event should NOT have called the handler at this point."); 42 | 43 | // Test the `clear` API. 44 | handler_index = test_event.bind([&](int a, int b) { 45 | event_execution_counter++; 46 | last_event_arg_a = a; 47 | last_event_arg_b = b; 48 | }); 49 | NS_ASSERT_COND(test_event.size() == 1); 50 | NS_ASSERT_COND(test_event.is_bind(handler_index)); 51 | 52 | test_event.broadcast(5, 6); 53 | 54 | NS_ASSERT_COND_MSG(event_execution_counter == 2, "The event should have called the handler at this point."); 55 | NS_ASSERT_COND_MSG(last_event_arg_a == 5, "The event should have called the handler at this point."); 56 | NS_ASSERT_COND_MSG(last_event_arg_b == 6, "The event should have called the handler at this point."); 57 | 58 | test_event.clear(); 59 | NS_ASSERT_COND(test_event.size() == 0); 60 | NS_ASSERT_COND(!test_event.is_bind(handler_index)); 61 | test_event.broadcast(7, 8); 62 | 63 | NS_ASSERT_COND_MSG(event_execution_counter == 2, "The event should NOT have called the handler at this point."); 64 | NS_ASSERT_COND_MSG(last_event_arg_a == 5, "The event should NOT have called the handler at this point."); 65 | NS_ASSERT_COND_MSG(last_event_arg_b == 6, "The event should NOT have called the handler at this point."); 66 | 67 | // Test the `append` API. 68 | { 69 | handler_index = test_event.bind([&](int a, int b) { 70 | event_execution_counter++; 71 | last_event_arg_a = a; 72 | last_event_arg_b = b; 73 | // Check execution order. 74 | NS_ASSERT_COND(event_execution_counter == 2); 75 | }); 76 | NS_ASSERT_COND(test_event.size() == 1); 77 | 78 | NS::Processor test_event_2; 79 | 80 | NS::PHandler handler_index_2 = test_event_2.bind([&](int a, int b) { 81 | event_execution_counter++; 82 | last_event_arg_a = a + 1; 83 | last_event_arg_b = b + 1; 84 | // Check execution order. 85 | NS_ASSERT_COND(event_execution_counter == 1); 86 | }); 87 | NS_ASSERT_COND(test_event_2.size() == 1); 88 | NS_ASSERT_COND(test_event_2.is_bind(handler_index_2)); 89 | 90 | std::vector new_handlers; 91 | test_event_2.append(test_event, &new_handlers); 92 | 93 | // Make sure this function is still bind. 94 | NS_ASSERT_COND(test_event_2.is_bind(handler_index_2)); 95 | 96 | // Make sure the append added all functions. 97 | NS_ASSERT_COND(int(new_handlers.size()) == test_event.size()); 98 | // Make sure the `test_event_2` size equals to `test_event` + the function already registered in `test_event_2`. 99 | NS_ASSERT_COND(test_event_2.size() == (test_event.size() + 1)); 100 | for (auto H : new_handlers) { 101 | NS_ASSERT_COND(test_event_2.is_bind(H)); 102 | } 103 | 104 | event_execution_counter = 0; 105 | test_event_2.broadcast(1, 1); 106 | 107 | NS_ASSERT_COND_MSG(event_execution_counter == 2, "The event should have called two handlers at this point."); 108 | NS_ASSERT_COND_MSG(last_event_arg_a == 1, "The event should have called the two handlers at this point."); 109 | NS_ASSERT_COND_MSG(last_event_arg_b == 1, "The event should have called the two handlers at this point."); 110 | 111 | // Make sure the initial `Event` works just fine. 112 | // But first make sure the CRASH_COND inside the function is not triggered 113 | // by set `event_execution_counter` to 1. 114 | event_execution_counter = 1; 115 | test_event.broadcast(2, 3); 116 | 117 | NS_ASSERT_COND(event_execution_counter == 2); 118 | NS_ASSERT_COND(last_event_arg_a == 2); 119 | NS_ASSERT_COND(last_event_arg_b == 3); 120 | } 121 | 122 | // Test the execution order after using `unbind`. 123 | { 124 | test_event.clear(); 125 | event_execution_counter = 0; 126 | 127 | NS::PHandler h = test_event.bind([&](int a, int b) { 128 | // Under proper considition this doesn't run. 129 | event_execution_counter++; 130 | }); 131 | 132 | test_event.bind([&](int a, int b) { 133 | // So this function shoul be the first one executing. 134 | event_execution_counter++; 135 | NS_ASSERT_COND(event_execution_counter == 1); 136 | }); 137 | 138 | test_event.bind([&](int a, int b) { 139 | // Then this. 140 | event_execution_counter++; 141 | NS_ASSERT_COND(event_execution_counter == 2); 142 | }); 143 | 144 | test_event.bind([&](int a, int b) { 145 | // Then this. 146 | event_execution_counter++; 147 | // Check execution order. 148 | NS_ASSERT_COND(event_execution_counter == 3); 149 | }); 150 | 151 | test_event.unbind(h); 152 | test_event.broadcast(0, 0); 153 | NS_ASSERT_COND(event_execution_counter == 3); 154 | } 155 | 156 | // Test the execution order after using `append`. 157 | { 158 | test_event.clear(); 159 | event_execution_counter = 0; 160 | 161 | NS::PHandler h = test_event.bind([&](int a, int b) { 162 | // Under proper considition this doesn't run. 163 | event_execution_counter++; 164 | }); 165 | 166 | test_event.bind([&](int a, int b) { 167 | // So this function this is the second executing function. 168 | event_execution_counter++; 169 | NS_ASSERT_COND(event_execution_counter == 2); 170 | }); 171 | 172 | test_event.bind([&](int a, int b) { 173 | // Then this. 174 | event_execution_counter++; 175 | NS_ASSERT_COND(event_execution_counter == 3); 176 | }); 177 | 178 | test_event.bind([&](int a, int b) { 179 | // Then this. 180 | event_execution_counter++; 181 | // Check execution order. 182 | NS_ASSERT_COND(event_execution_counter == 4); 183 | }); 184 | 185 | test_event.unbind(h); 186 | 187 | NS::Processor test_event_2; 188 | test_event_2.bind([&](int a, int b) { 189 | // Under proper considition this is the first functon to be executed 190 | event_execution_counter++; 191 | NS_ASSERT_COND(event_execution_counter == 1); 192 | }); 193 | 194 | test_event_2.append(test_event); 195 | test_event_2.broadcast(0, 0); 196 | NS_ASSERT_COND(event_execution_counter == 4); 197 | } 198 | 199 | // Test the lambda added from the same class but different pointer doesn't 200 | // override each other. 201 | { 202 | test_event.clear(); 203 | event_execution_counter = 0; 204 | 205 | struct TestLambda { 206 | int v = 0; 207 | 208 | NS::PHandler add_lambda(NS::Processor &p_processor) { 209 | return p_processor.bind([this](int a, int b) { 210 | v = a + b; 211 | }); 212 | } 213 | }; 214 | 215 | TestLambda test_lambda_1; 216 | test_lambda_1.v = 2; 217 | TestLambda test_lambda_2; 218 | test_lambda_2.v = 3; 219 | 220 | NS::PHandler H_1 = test_lambda_1.add_lambda(test_event); 221 | NS::PHandler H_2 = test_lambda_2.add_lambda(test_event); 222 | 223 | NS_ASSERT_COND(test_event.size() == 2); 224 | NS_ASSERT_COND(test_event.is_bind(H_1)); 225 | NS_ASSERT_COND(test_event.is_bind(H_2)); 226 | NS_ASSERT_COND(H_1 != H_2); 227 | } 228 | } 229 | 230 | void test_event_processor() { 231 | NS::EventProcessor test_event; 232 | 233 | int the_a = 2, the_b = 2; 234 | test_event.broadcast(55, 223); 235 | NS_ASSERT_COND(test_event.bind_count() == 0); 236 | NS_ASSERT_COND(the_a == 2); 237 | NS_ASSERT_COND(the_b == 2); 238 | 239 | { 240 | std::unique_ptr::Handler> event_handler = test_event.bind([&](int a, int b) { 241 | the_a = a; 242 | the_b = b; 243 | }); 244 | NS_ASSERT_COND(test_event.bind_count() == 1); 245 | 246 | NS_ASSERT_COND(the_a == 2); 247 | NS_ASSERT_COND(the_b == 2); 248 | 249 | test_event.broadcast(55, 223); 250 | NS_ASSERT_COND(the_a == 55); 251 | NS_ASSERT_COND(the_b == 223); 252 | } 253 | 254 | test_event.broadcast(43, 32); 255 | NS_ASSERT_COND(test_event.bind_count() == 0); 256 | NS_ASSERT_COND(the_a == 55); 257 | NS_ASSERT_COND(the_b == 223); 258 | 259 | // Test that the clear function is able to correctly destroy all the registered functions. 260 | { 261 | std::unique_ptr::Handler> event_handler = test_event.bind([&](int a, int b) { 262 | the_a = a; 263 | the_b = b; 264 | }); 265 | NS_ASSERT_COND(test_event.bind_count() == 1); 266 | 267 | event_handler->clear(); 268 | NS_ASSERT_COND(the_a == 55); 269 | NS_ASSERT_COND(the_b == 223); 270 | 271 | test_event.broadcast(43, 32); 272 | NS_ASSERT_COND(test_event.bind_count() == 0); 273 | NS_ASSERT_COND(the_a == 55); 274 | NS_ASSERT_COND(the_b == 223); 275 | } 276 | 277 | // Test that the clear function is able to correctly destroy all the registered functions. 278 | { 279 | std::unique_ptr::Handler> event_handler = test_event.bind([&](int a, int b) { 280 | the_a = a; 281 | the_b = b; 282 | }); 283 | NS_ASSERT_COND(test_event.bind_count() == 1); 284 | 285 | test_event.clear(); 286 | NS_ASSERT_COND(event_handler && !event_handler->is_valid()); 287 | 288 | test_event.broadcast(43, 32); 289 | NS_ASSERT_COND(test_event.bind_count() == 0); 290 | NS_ASSERT_COND(the_a == 55); 291 | NS_ASSERT_COND(the_b == 223); 292 | } 293 | 294 | // Test the event override. 295 | { 296 | std::unique_ptr::Handler> event_handler = test_event.bind([&](int a, int b) { 297 | the_a = 0; 298 | the_b = 0; 299 | }); 300 | event_handler = test_event.bind([&](int a, int b) { 301 | the_a = 1; 302 | the_b = 1; 303 | }); 304 | event_handler = test_event.bind([&](int a, int b) { 305 | the_a = a; 306 | the_b = b; 307 | }); 308 | 309 | NS_ASSERT_COND(test_event.bind_count() == 1); 310 | test_event.broadcast(43, 32); 311 | 312 | NS_ASSERT_COND(the_a == 43); 313 | NS_ASSERT_COND(the_b == 32); 314 | } 315 | 316 | // Test multiple functions 317 | { 318 | the_a = 0; 319 | the_b = 0; 320 | std::unique_ptr::Handler> event_handler_0 = test_event.bind([&](int a, int b) { 321 | the_a += a; 322 | the_b += b; 323 | }); 324 | std::unique_ptr::Handler> event_handler_1 = test_event.bind([&](int a, int b) { 325 | the_a += a; 326 | the_b += b; 327 | }); 328 | std::unique_ptr::Handler> event_handler_2 = test_event.bind([&](int a, int b) { 329 | the_a += a; 330 | the_b += b; 331 | }); 332 | 333 | NS_ASSERT_COND(test_event.bind_count() == 3); 334 | test_event.broadcast(2, 2); 335 | 336 | NS_ASSERT_COND(the_a == 6); 337 | NS_ASSERT_COND(the_b == 6); 338 | 339 | event_handler_1 = nullptr; 340 | 341 | NS_ASSERT_COND(test_event.bind_count() == 2); 342 | test_event.broadcast(2, 2); 343 | 344 | NS_ASSERT_COND(the_a == 10); 345 | NS_ASSERT_COND(the_b == 10); 346 | } 347 | } 348 | 349 | void NS_Test::test_processor() { 350 | test_internal_processor(); 351 | test_event_processor(); 352 | } -------------------------------------------------------------------------------- /tests/test_processor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace NS_Test { 4 | void test_processor(); 5 | }; 6 | -------------------------------------------------------------------------------- /tests/test_scene_synchronizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO convert to DOC-TEST 4 | // I din't that right away because at the moment there are some modules that 5 | // doesn't support it and it's difficult to test it. 6 | 7 | namespace NS_Test { 8 | void test_scene_synchronizer(); 9 | }; 10 | -------------------------------------------------------------------------------- /tests/test_simulation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO convert to DOC-TEST 4 | // I din't that right away because at the moment there are some modules that 5 | // doesn't support it and it's difficult to test it. 6 | 7 | namespace NS_Test { 8 | void test_simulation(); 9 | }; 10 | -------------------------------------------------------------------------------- /tests/test_switch_controller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace NS_Test { 4 | void test_switch_controller(); 5 | }; -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | #include "local_scene.h" 4 | #include "test_AI_simulation.h" 5 | #include "test_data_buffer.h" 6 | #include "test_doll_simulation.h" 7 | #include "test_math_lib.h" 8 | #include "test_processor.h" 9 | #include "test_scene_synchronizer.h" 10 | #include "test_simulation.h" 11 | #include "test_switch_controller.h" 12 | 13 | void NS_Test::test_all() { 14 | NS::LocalSceneSynchronizer::install_local_scene_sync(); 15 | 16 | test_math(); 17 | test_data_buffer(); 18 | test_processor(); 19 | test_local_network(); 20 | test_scene_synchronizer(); 21 | test_simulation(); 22 | test_doll_simulation(); 23 | NS_AI_Test::test_AI_simulation(); 24 | test_switch_controller(); 25 | 26 | NS::LocalSceneSynchronizer::uninstall_local_scene_sync(); 27 | } -------------------------------------------------------------------------------- /tests/tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace NS_Test { 4 | void test_all(); 5 | }; 6 | --------------------------------------------------------------------------------