| 1 | /**************************************************************************/ |
| 2 | /* multiplayer_synchronizer.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "multiplayer_synchronizer.h" |
| 32 | |
| 33 | #include "core/config/engine.h" |
| 34 | #include "scene/main/multiplayer_api.h" |
| 35 | |
| 36 | Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath &p_path) { |
| 37 | if (p_path.get_name_count() == 0) { |
| 38 | return p_obj; |
| 39 | } |
| 40 | Node *node = Object::cast_to<Node>(p_obj); |
| 41 | ERR_FAIL_COND_V_MSG(!node || !node->has_node(p_path), nullptr, vformat("Node '%s' not found." , p_path)); |
| 42 | return node->get_node(p_path); |
| 43 | } |
| 44 | |
| 45 | void MultiplayerSynchronizer::_stop() { |
| 46 | #ifdef TOOLS_ENABLED |
| 47 | if (Engine::get_singleton()->is_editor_hint()) { |
| 48 | return; |
| 49 | } |
| 50 | #endif |
| 51 | root_node_cache = ObjectID(); |
| 52 | reset(); |
| 53 | Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; |
| 54 | if (node) { |
| 55 | get_multiplayer()->object_configuration_remove(node, this); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | void MultiplayerSynchronizer::_start() { |
| 60 | #ifdef TOOLS_ENABLED |
| 61 | if (Engine::get_singleton()->is_editor_hint()) { |
| 62 | return; |
| 63 | } |
| 64 | #endif |
| 65 | root_node_cache = ObjectID(); |
| 66 | reset(); |
| 67 | Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; |
| 68 | if (node) { |
| 69 | root_node_cache = node->get_instance_id(); |
| 70 | get_multiplayer()->object_configuration_add(node, this); |
| 71 | _update_process(); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | void MultiplayerSynchronizer::_update_process() { |
| 76 | #ifdef TOOLS_ENABLED |
| 77 | if (Engine::get_singleton()->is_editor_hint()) { |
| 78 | return; |
| 79 | } |
| 80 | #endif |
| 81 | Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; |
| 82 | if (!node) { |
| 83 | return; |
| 84 | } |
| 85 | set_process_internal(false); |
| 86 | set_physics_process_internal(false); |
| 87 | if (!visibility_filters.size()) { |
| 88 | return; |
| 89 | } |
| 90 | switch (visibility_update_mode) { |
| 91 | case VISIBILITY_PROCESS_IDLE: |
| 92 | set_process_internal(true); |
| 93 | break; |
| 94 | case VISIBILITY_PROCESS_PHYSICS: |
| 95 | set_physics_process_internal(true); |
| 96 | break; |
| 97 | case VISIBILITY_PROCESS_NONE: |
| 98 | break; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | Node *MultiplayerSynchronizer::get_root_node() { |
| 103 | return root_node_cache.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(root_node_cache)) : nullptr; |
| 104 | } |
| 105 | |
| 106 | void MultiplayerSynchronizer::reset() { |
| 107 | net_id = 0; |
| 108 | last_sync_usec = 0; |
| 109 | last_inbound_sync = 0; |
| 110 | } |
| 111 | |
| 112 | uint32_t MultiplayerSynchronizer::get_net_id() const { |
| 113 | return net_id; |
| 114 | } |
| 115 | |
| 116 | void MultiplayerSynchronizer::set_net_id(uint32_t p_net_id) { |
| 117 | net_id = p_net_id; |
| 118 | } |
| 119 | |
| 120 | bool MultiplayerSynchronizer::update_outbound_sync_time(uint64_t p_usec) { |
| 121 | if (last_sync_usec == p_usec) { |
| 122 | // last_sync_usec has been updated in this frame. |
| 123 | return true; |
| 124 | } |
| 125 | if (p_usec < last_sync_usec + sync_interval_usec) { |
| 126 | // Too soon, should skip this synchronization frame. |
| 127 | return false; |
| 128 | } |
| 129 | last_sync_usec = p_usec; |
| 130 | return true; |
| 131 | } |
| 132 | |
| 133 | bool MultiplayerSynchronizer::update_inbound_sync_time(uint16_t p_network_time) { |
| 134 | if (p_network_time <= last_inbound_sync && last_inbound_sync - p_network_time < 32767) { |
| 135 | return false; |
| 136 | } |
| 137 | last_inbound_sync = p_network_time; |
| 138 | return true; |
| 139 | } |
| 140 | |
| 141 | PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const { |
| 142 | PackedStringArray warnings = Node::get_configuration_warnings(); |
| 143 | |
| 144 | if (root_path.is_empty() || !has_node(root_path)) { |
| 145 | warnings.push_back(RTR("A valid NodePath must be set in the \"Root Path\" property in order for MultiplayerSynchronizer to be able to synchronize properties." )); |
| 146 | } |
| 147 | |
| 148 | return warnings; |
| 149 | } |
| 150 | |
| 151 | Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) { |
| 152 | ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); |
| 153 | r_variant.resize(p_properties.size()); |
| 154 | r_variant_ptrs.resize(r_variant.size()); |
| 155 | int i = 0; |
| 156 | for (const NodePath &prop : p_properties) { |
| 157 | bool valid = false; |
| 158 | const Object *obj = _get_prop_target(p_obj, prop); |
| 159 | ERR_FAIL_COND_V(!obj, FAILED); |
| 160 | r_variant.write[i] = obj->get_indexed(prop.get_subnames(), &valid); |
| 161 | r_variant_ptrs.write[i] = &r_variant[i]; |
| 162 | ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found." , prop)); |
| 163 | i++; |
| 164 | } |
| 165 | return OK; |
| 166 | } |
| 167 | |
| 168 | Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state) { |
| 169 | ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); |
| 170 | int i = 0; |
| 171 | for (const NodePath &prop : p_properties) { |
| 172 | Object *obj = _get_prop_target(p_obj, prop); |
| 173 | ERR_FAIL_COND_V(!obj, FAILED); |
| 174 | obj->set_indexed(prop.get_subnames(), p_state[i]); |
| 175 | i += 1; |
| 176 | } |
| 177 | return OK; |
| 178 | } |
| 179 | |
| 180 | bool MultiplayerSynchronizer::is_visibility_public() const { |
| 181 | return peer_visibility.has(0); |
| 182 | } |
| 183 | |
| 184 | void MultiplayerSynchronizer::set_visibility_public(bool p_visible) { |
| 185 | set_visibility_for(0, p_visible); |
| 186 | } |
| 187 | |
| 188 | bool MultiplayerSynchronizer::is_visible_to(int p_peer) { |
| 189 | if (visibility_filters.size()) { |
| 190 | Variant arg = p_peer; |
| 191 | const Variant *argv[1] = { &arg }; |
| 192 | for (Callable filter : visibility_filters) { |
| 193 | Variant ret; |
| 194 | Callable::CallError err; |
| 195 | filter.callp(argv, 1, ret, err); |
| 196 | ERR_FAIL_COND_V(err.error != Callable::CallError::CALL_OK || ret.get_type() != Variant::BOOL, false); |
| 197 | if (!ret.operator bool()) { |
| 198 | return false; |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | return peer_visibility.has(0) || peer_visibility.has(p_peer); |
| 203 | } |
| 204 | |
| 205 | void MultiplayerSynchronizer::add_visibility_filter(Callable p_callback) { |
| 206 | visibility_filters.insert(p_callback); |
| 207 | _update_process(); |
| 208 | } |
| 209 | |
| 210 | void MultiplayerSynchronizer::remove_visibility_filter(Callable p_callback) { |
| 211 | visibility_filters.erase(p_callback); |
| 212 | _update_process(); |
| 213 | } |
| 214 | |
| 215 | void MultiplayerSynchronizer::set_visibility_for(int p_peer, bool p_visible) { |
| 216 | if (peer_visibility.has(p_peer) == p_visible) { |
| 217 | return; |
| 218 | } |
| 219 | if (p_visible) { |
| 220 | peer_visibility.insert(p_peer); |
| 221 | } else { |
| 222 | peer_visibility.erase(p_peer); |
| 223 | } |
| 224 | update_visibility(p_peer); |
| 225 | } |
| 226 | |
| 227 | bool MultiplayerSynchronizer::get_visibility_for(int p_peer) const { |
| 228 | return peer_visibility.has(p_peer); |
| 229 | } |
| 230 | |
| 231 | void MultiplayerSynchronizer::set_visibility_update_mode(VisibilityUpdateMode p_mode) { |
| 232 | visibility_update_mode = p_mode; |
| 233 | _update_process(); |
| 234 | } |
| 235 | |
| 236 | MultiplayerSynchronizer::VisibilityUpdateMode MultiplayerSynchronizer::get_visibility_update_mode() const { |
| 237 | return visibility_update_mode; |
| 238 | } |
| 239 | |
| 240 | void MultiplayerSynchronizer::_bind_methods() { |
| 241 | ClassDB::bind_method(D_METHOD("set_root_path" , "path" ), &MultiplayerSynchronizer::set_root_path); |
| 242 | ClassDB::bind_method(D_METHOD("get_root_path" ), &MultiplayerSynchronizer::get_root_path); |
| 243 | |
| 244 | ClassDB::bind_method(D_METHOD("set_replication_interval" , "milliseconds" ), &MultiplayerSynchronizer::set_replication_interval); |
| 245 | ClassDB::bind_method(D_METHOD("get_replication_interval" ), &MultiplayerSynchronizer::get_replication_interval); |
| 246 | |
| 247 | ClassDB::bind_method(D_METHOD("set_delta_interval" , "milliseconds" ), &MultiplayerSynchronizer::set_delta_interval); |
| 248 | ClassDB::bind_method(D_METHOD("get_delta_interval" ), &MultiplayerSynchronizer::get_delta_interval); |
| 249 | |
| 250 | ClassDB::bind_method(D_METHOD("set_replication_config" , "config" ), &MultiplayerSynchronizer::set_replication_config); |
| 251 | ClassDB::bind_method(D_METHOD("get_replication_config" ), &MultiplayerSynchronizer::get_replication_config); |
| 252 | |
| 253 | ClassDB::bind_method(D_METHOD("set_visibility_update_mode" , "mode" ), &MultiplayerSynchronizer::set_visibility_update_mode); |
| 254 | ClassDB::bind_method(D_METHOD("get_visibility_update_mode" ), &MultiplayerSynchronizer::get_visibility_update_mode); |
| 255 | ClassDB::bind_method(D_METHOD("update_visibility" , "for_peer" ), &MultiplayerSynchronizer::update_visibility, DEFVAL(0)); |
| 256 | |
| 257 | ClassDB::bind_method(D_METHOD("set_visibility_public" , "visible" ), &MultiplayerSynchronizer::set_visibility_public); |
| 258 | ClassDB::bind_method(D_METHOD("is_visibility_public" ), &MultiplayerSynchronizer::is_visibility_public); |
| 259 | |
| 260 | ClassDB::bind_method(D_METHOD("add_visibility_filter" , "filter" ), &MultiplayerSynchronizer::add_visibility_filter); |
| 261 | ClassDB::bind_method(D_METHOD("remove_visibility_filter" , "filter" ), &MultiplayerSynchronizer::remove_visibility_filter); |
| 262 | ClassDB::bind_method(D_METHOD("set_visibility_for" , "peer" , "visible" ), &MultiplayerSynchronizer::set_visibility_for); |
| 263 | ClassDB::bind_method(D_METHOD("get_visibility_for" , "peer" ), &MultiplayerSynchronizer::get_visibility_for); |
| 264 | |
| 265 | ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path" ), "set_root_path" , "get_root_path" ); |
| 266 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval" , PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s" ), "set_replication_interval" , "get_replication_interval" ); |
| 267 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "delta_interval" , PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s" ), "set_delta_interval" , "get_delta_interval" ); |
| 268 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config" , PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig" , PROPERTY_USAGE_NO_EDITOR), "set_replication_config" , "get_replication_config" ); |
| 269 | ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_update_mode" , PROPERTY_HINT_ENUM, "Idle,Physics,None" ), "set_visibility_update_mode" , "get_visibility_update_mode" ); |
| 270 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "public_visibility" ), "set_visibility_public" , "is_visibility_public" ); |
| 271 | |
| 272 | BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_IDLE); |
| 273 | BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_PHYSICS); |
| 274 | BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_NONE); |
| 275 | |
| 276 | ADD_SIGNAL(MethodInfo("synchronized" )); |
| 277 | ADD_SIGNAL(MethodInfo("delta_synchronized" )); |
| 278 | ADD_SIGNAL(MethodInfo("visibility_changed" , PropertyInfo(Variant::INT, "for_peer" ))); |
| 279 | } |
| 280 | |
| 281 | void MultiplayerSynchronizer::_notification(int p_what) { |
| 282 | #ifdef TOOLS_ENABLED |
| 283 | if (Engine::get_singleton()->is_editor_hint()) { |
| 284 | return; |
| 285 | } |
| 286 | #endif |
| 287 | if (root_path.is_empty()) { |
| 288 | return; |
| 289 | } |
| 290 | |
| 291 | switch (p_what) { |
| 292 | case NOTIFICATION_ENTER_TREE: { |
| 293 | _start(); |
| 294 | } break; |
| 295 | |
| 296 | case NOTIFICATION_EXIT_TREE: { |
| 297 | _stop(); |
| 298 | } break; |
| 299 | |
| 300 | case NOTIFICATION_INTERNAL_PROCESS: |
| 301 | case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { |
| 302 | update_visibility(0); |
| 303 | } break; |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | void MultiplayerSynchronizer::set_replication_interval(double p_interval) { |
| 308 | ERR_FAIL_COND_MSG(p_interval < 0, "Interval must be greater or equal to 0 (where 0 means default)" ); |
| 309 | sync_interval_usec = uint64_t(p_interval * 1000 * 1000); |
| 310 | } |
| 311 | |
| 312 | double MultiplayerSynchronizer::get_replication_interval() const { |
| 313 | return double(sync_interval_usec) / 1000.0 / 1000.0; |
| 314 | } |
| 315 | |
| 316 | void MultiplayerSynchronizer::set_delta_interval(double p_interval) { |
| 317 | ERR_FAIL_COND_MSG(p_interval < 0, "Interval must be greater or equal to 0 (where 0 means default)" ); |
| 318 | delta_interval_usec = uint64_t(p_interval * 1000 * 1000); |
| 319 | } |
| 320 | |
| 321 | double MultiplayerSynchronizer::get_delta_interval() const { |
| 322 | return double(delta_interval_usec) / 1000.0 / 1000.0; |
| 323 | } |
| 324 | |
| 325 | void MultiplayerSynchronizer::set_replication_config(Ref<SceneReplicationConfig> p_config) { |
| 326 | replication_config = p_config; |
| 327 | } |
| 328 | |
| 329 | Ref<SceneReplicationConfig> MultiplayerSynchronizer::get_replication_config() { |
| 330 | return replication_config; |
| 331 | } |
| 332 | |
| 333 | void MultiplayerSynchronizer::update_visibility(int p_for_peer) { |
| 334 | #ifdef TOOLS_ENABLED |
| 335 | if (Engine::get_singleton()->is_editor_hint()) { |
| 336 | return; |
| 337 | } |
| 338 | #endif |
| 339 | Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; |
| 340 | if (node && get_multiplayer()->has_multiplayer_peer() && is_multiplayer_authority()) { |
| 341 | emit_signal(SNAME("visibility_changed" ), p_for_peer); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) { |
| 346 | _stop(); |
| 347 | root_path = p_path; |
| 348 | _start(); |
| 349 | } |
| 350 | |
| 351 | NodePath MultiplayerSynchronizer::get_root_path() const { |
| 352 | return root_path; |
| 353 | } |
| 354 | |
| 355 | void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_recursive) { |
| 356 | Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; |
| 357 | if (!node || get_multiplayer_authority() == p_peer_id) { |
| 358 | Node::set_multiplayer_authority(p_peer_id, p_recursive); |
| 359 | return; |
| 360 | } |
| 361 | |
| 362 | get_multiplayer()->object_configuration_remove(node, this); |
| 363 | Node::set_multiplayer_authority(p_peer_id, p_recursive); |
| 364 | get_multiplayer()->object_configuration_add(node, this); |
| 365 | } |
| 366 | |
| 367 | Error MultiplayerSynchronizer::_watch_changes(uint64_t p_usec) { |
| 368 | ERR_FAIL_COND_V(replication_config.is_null(), FAILED); |
| 369 | const List<NodePath> props = replication_config->get_watch_properties(); |
| 370 | if (props.size() != watchers.size()) { |
| 371 | watchers.resize(props.size()); |
| 372 | } |
| 373 | if (props.size() == 0) { |
| 374 | return OK; |
| 375 | } |
| 376 | Node *node = get_root_node(); |
| 377 | ERR_FAIL_COND_V(!node, FAILED); |
| 378 | int idx = -1; |
| 379 | Watcher *ptr = watchers.ptrw(); |
| 380 | for (const NodePath &prop : props) { |
| 381 | idx++; |
| 382 | bool valid = false; |
| 383 | const Object *obj = _get_prop_target(node, prop); |
| 384 | ERR_CONTINUE_MSG(!obj, vformat("Node not found for property '%s'." , prop)); |
| 385 | Variant v = obj->get(prop.get_concatenated_subnames(), &valid); |
| 386 | ERR_CONTINUE_MSG(!valid, vformat("Property '%s' not found." , prop)); |
| 387 | Watcher &w = ptr[idx]; |
| 388 | if (w.prop != prop) { |
| 389 | w.prop = prop; |
| 390 | w.value = v.duplicate(true); |
| 391 | w.last_change_usec = p_usec; |
| 392 | } else if (!w.value.hash_compare(v)) { |
| 393 | w.value = v.duplicate(true); |
| 394 | w.last_change_usec = p_usec; |
| 395 | } |
| 396 | } |
| 397 | return OK; |
| 398 | } |
| 399 | |
| 400 | List<Variant> MultiplayerSynchronizer::get_delta_state(uint64_t p_cur_usec, uint64_t p_last_usec, uint64_t &r_indexes) { |
| 401 | r_indexes = 0; |
| 402 | List<Variant> out; |
| 403 | |
| 404 | if (last_watch_usec == p_cur_usec) { |
| 405 | // We already watched for changes in this frame. |
| 406 | |
| 407 | } else if (p_cur_usec < p_last_usec + delta_interval_usec) { |
| 408 | // Too soon skip delta synchronization. |
| 409 | return out; |
| 410 | |
| 411 | } else { |
| 412 | // Watch for changes. |
| 413 | Error err = _watch_changes(p_cur_usec); |
| 414 | ERR_FAIL_COND_V(err != OK, out); |
| 415 | last_watch_usec = p_cur_usec; |
| 416 | } |
| 417 | |
| 418 | const Watcher *ptr = watchers.size() ? watchers.ptr() : nullptr; |
| 419 | for (int i = 0; i < watchers.size(); i++) { |
| 420 | const Watcher &w = ptr[i]; |
| 421 | if (w.last_change_usec <= p_last_usec) { |
| 422 | continue; |
| 423 | } |
| 424 | out.push_back(w.value); |
| 425 | r_indexes |= 1ULL << i; |
| 426 | } |
| 427 | return out; |
| 428 | } |
| 429 | |
| 430 | List<NodePath> MultiplayerSynchronizer::get_delta_properties(uint64_t p_indexes) { |
| 431 | List<NodePath> out; |
| 432 | ERR_FAIL_COND_V(replication_config.is_null(), out); |
| 433 | const List<NodePath> watch_props = replication_config->get_watch_properties(); |
| 434 | int idx = 0; |
| 435 | for (const NodePath &prop : watch_props) { |
| 436 | if ((p_indexes & (1ULL << idx++)) == 0) { |
| 437 | continue; |
| 438 | } |
| 439 | out.push_back(prop); |
| 440 | } |
| 441 | return out; |
| 442 | } |
| 443 | |
| 444 | MultiplayerSynchronizer::MultiplayerSynchronizer() { |
| 445 | // Publicly visible by default. |
| 446 | peer_visibility.insert(0); |
| 447 | } |
| 448 | |