├── .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