From 6adee8f1b0ce07aab45182d55e7a7696501797e9 Mon Sep 17 00:00:00 2001 From: Relintai Date: Sun, 20 Mar 2022 23:30:30 +0100 Subject: [PATCH] Added the NetworkedController & SceneRewinder module from https://github.com/godotengine/godot/pull/37200. It's disabled for now, it needs to be ported. --- modules/network_synchronizer/SCsub | 8 + modules/network_synchronizer/bit_array.cpp | 134 + modules/network_synchronizer/bit_array.h | 69 + .../class_docs/DataBuffer.xml | 324 ++ .../class_docs/Interpolator.xml | 62 + .../class_docs/NetworkedController.xml | 160 + .../class_docs/SceneDiff.xml | 13 + .../class_docs/SceneSynchronizer.xml | 278 ++ modules/network_synchronizer/config.py | 18 + modules/network_synchronizer/data_buffer.cpp | 942 +++++ modules/network_synchronizer/data_buffer.h | 342 ++ modules/network_synchronizer/interpolator.cpp | 415 +++ modules/network_synchronizer/interpolator.h | 104 + .../network_synchronizer/net_utilities.cpp | 91 + modules/network_synchronizer/net_utilities.h | 342 ++ .../networked_controller.cpp | 1571 +++++++++ .../networked_controller.h | 526 +++ .../network_synchronizer/register_types.cpp | 55 + modules/network_synchronizer/register_types.h | 36 + modules/network_synchronizer/scene_diff.cpp | 165 + modules/network_synchronizer/scene_diff.h | 71 + .../scene_synchronizer.cpp | 3052 +++++++++++++++++ .../network_synchronizer/scene_synchronizer.h | 464 +++ .../tests/test_bit_array.h | 120 + .../tests/test_data_buffer.h | 550 +++ .../tests/test_interpolator.h | 131 + 26 files changed, 10043 insertions(+) create mode 100644 modules/network_synchronizer/SCsub create mode 100644 modules/network_synchronizer/bit_array.cpp create mode 100644 modules/network_synchronizer/bit_array.h create mode 100644 modules/network_synchronizer/class_docs/DataBuffer.xml create mode 100644 modules/network_synchronizer/class_docs/Interpolator.xml create mode 100644 modules/network_synchronizer/class_docs/NetworkedController.xml create mode 100644 modules/network_synchronizer/class_docs/SceneDiff.xml create mode 100644 modules/network_synchronizer/class_docs/SceneSynchronizer.xml create mode 100644 modules/network_synchronizer/config.py create mode 100644 modules/network_synchronizer/data_buffer.cpp create mode 100644 modules/network_synchronizer/data_buffer.h create mode 100644 modules/network_synchronizer/interpolator.cpp create mode 100644 modules/network_synchronizer/interpolator.h create mode 100644 modules/network_synchronizer/net_utilities.cpp create mode 100644 modules/network_synchronizer/net_utilities.h create mode 100644 modules/network_synchronizer/networked_controller.cpp create mode 100644 modules/network_synchronizer/networked_controller.h create mode 100644 modules/network_synchronizer/register_types.cpp create mode 100644 modules/network_synchronizer/register_types.h create mode 100644 modules/network_synchronizer/scene_diff.cpp create mode 100644 modules/network_synchronizer/scene_diff.h create mode 100644 modules/network_synchronizer/scene_synchronizer.cpp create mode 100644 modules/network_synchronizer/scene_synchronizer.h create mode 100644 modules/network_synchronizer/tests/test_bit_array.h create mode 100644 modules/network_synchronizer/tests/test_data_buffer.h create mode 100644 modules/network_synchronizer/tests/test_interpolator.h diff --git a/modules/network_synchronizer/SCsub b/modules/network_synchronizer/SCsub new file mode 100644 index 000000000..257431408 --- /dev/null +++ b/modules/network_synchronizer/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_network_synchronizer = env_modules.Clone() + +env_network_synchronizer.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/network_synchronizer/bit_array.cpp b/modules/network_synchronizer/bit_array.cpp new file mode 100644 index 000000000..f22a1da0c --- /dev/null +++ b/modules/network_synchronizer/bit_array.cpp @@ -0,0 +1,134 @@ +/*************************************************************************/ +/* bit_array.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "bit_array.h" + +#include "core/math/math_funcs.h" +#include "core/string/ustring.h" + +BitArray::BitArray(uint32_t p_initial_size_in_bit) { + resize_in_bits(p_initial_size_in_bit); +} + +BitArray::BitArray(const Vector &p_bytes) : + bytes(p_bytes) { +} + +void BitArray::resize_in_bytes(int p_bytes) { + ERR_FAIL_COND_MSG(p_bytes < 0, "Bytes count can't be negative"); + bytes.resize(p_bytes); +} + +int BitArray::size_in_bytes() const { + return bytes.size(); +} + +void BitArray::resize_in_bits(int p_bits) { + ERR_FAIL_COND_MSG(p_bits < 0, "Bits count can't be negative"); + const int min_size = Math::ceil((static_cast(p_bits)) / 8); + bytes.resize(min_size); +} + +int BitArray::size_in_bits() const { + return bytes.size() * 8; +} + +void BitArray::store_bits(int p_bit_offset, uint64_t p_value, int p_bits) { + ERR_FAIL_COND_MSG(p_bit_offset < 0, "Offset can't be negative"); + ERR_FAIL_COND_MSG(p_bits <= 0, "The number of bits should be more than 0"); + ERR_FAIL_INDEX_MSG(p_bit_offset + p_bits - 1, size_in_bits(), "The bit array size is `" + itos(size_in_bits()) + "` while you are trying to write `" + itos(p_bits) + "` starting from `" + itos(p_bit_offset) + "`."); + + int bits = p_bits; + int bit_offset = p_bit_offset; + uint64_t val = p_value; + + while (bits > 0) { + const int bits_to_write = MIN(bits, 8 - bit_offset % 8); + const int bits_to_jump = bit_offset % 8; + const int bits_to_skip = 8 - (bits_to_write + bits_to_jump); + const int byte_offset = bit_offset / 8; + + // Clear the bits that we have to write + //const uint8_t byte_clear = ~(((0xFF >> bits_to_jump) << (bits_to_jump + bits_to_skip)) >> bits_to_skip); + uint8_t byte_clear = 0xFF >> bits_to_jump; + byte_clear = byte_clear << (bits_to_jump + bits_to_skip); + byte_clear = ~(byte_clear >> bits_to_skip); + bytes.write[byte_offset] &= byte_clear; + + // Now we can continue to write bits + bytes.write[byte_offset] |= (val & 0xFF) << bits_to_jump; + + bits -= bits_to_write; + bit_offset += bits_to_write; + + val >>= bits_to_write; + } +} + +uint64_t BitArray::read_bits(int p_bit_offset, int p_bits) const { + ERR_FAIL_COND_V_MSG(p_bits <= 0, 0, "The number of bits should be more than 0"); + ERR_FAIL_INDEX_V_MSG(p_bit_offset + p_bits - 1, size_in_bits(), 0, "The bit array size is `" + itos(size_in_bits()) + "` while you are trying to read `" + itos(p_bits) + "` starting from `" + itos(p_bit_offset) + "`."); + + int bits = p_bits; + int bit_offset = p_bit_offset; + uint64_t val = 0; + + const uint8_t *bytes_ptr = bytes.ptr(); + + int val_bits_to_jump = 0; + while (bits > 0) { + const int bits_to_read = MIN(bits, 8 - bit_offset % 8); + const int bits_to_jump = bit_offset % 8; + const int bits_to_skip = 8 - (bits_to_read + bits_to_jump); + const int byte_offset = bit_offset / 8; + + uint8_t byte_mask = 0xFF >> bits_to_jump; + byte_mask = byte_mask << (bits_to_skip + bits_to_jump); + byte_mask = byte_mask >> bits_to_skip; + const uint64_t byte_val = static_cast((bytes_ptr[byte_offset] & byte_mask) >> bits_to_jump); + val |= byte_val << val_bits_to_jump; + + bits -= bits_to_read; + bit_offset += bits_to_read; + val_bits_to_jump += bits_to_read; + } + + return val; +} + +void BitArray::zero() { + if (bytes.size() > 0) { + memset(bytes.ptrw(), 0, sizeof(uint8_t) * bytes.size()); + } +} diff --git a/modules/network_synchronizer/bit_array.h b/modules/network_synchronizer/bit_array.h new file mode 100644 index 000000000..f812936eb --- /dev/null +++ b/modules/network_synchronizer/bit_array.h @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* bit_array.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "core/vector.h" + +#ifndef BITARRAY_H +#define BITARRAY_H + +class BitArray { + Vector bytes; + +public: + BitArray() = default; + BitArray(uint32_t p_initial_size_in_bit); + BitArray(const Vector &p_bytes); + + const Vector &get_bytes() const { + return bytes; + } + + Vector &get_bytes_mut() { + return bytes; + } + + void resize_in_bytes(int p_bits); + int size_in_bytes() const; + + void resize_in_bits(int p_bits); + int size_in_bits() const; + + void store_bits(int p_bit_offset, uint64_t p_value, int p_bits); + uint64_t read_bits(int p_bit_offset, int p_bits) const; + + // Puts all the bytes to 0. + void zero(); +}; + +#endif diff --git a/modules/network_synchronizer/class_docs/DataBuffer.xml b/modules/network_synchronizer/class_docs/DataBuffer.xml new file mode 100644 index 000000000..554564cdf --- /dev/null +++ b/modules/network_synchronizer/class_docs/DataBuffer.xml @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/network_synchronizer/class_docs/Interpolator.xml b/modules/network_synchronizer/class_docs/Interpolator.xml new file mode 100644 index 000000000..5020ff0b0 --- /dev/null +++ b/modules/network_synchronizer/class_docs/Interpolator.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/network_synchronizer/class_docs/NetworkedController.xml b/modules/network_synchronizer/class_docs/NetworkedController.xml new file mode 100644 index 000000000..67a075a40 --- /dev/null +++ b/modules/network_synchronizer/class_docs/NetworkedController.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/network_synchronizer/class_docs/SceneDiff.xml b/modules/network_synchronizer/class_docs/SceneDiff.xml new file mode 100644 index 000000000..877c08a6b --- /dev/null +++ b/modules/network_synchronizer/class_docs/SceneDiff.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/modules/network_synchronizer/class_docs/SceneSynchronizer.xml b/modules/network_synchronizer/class_docs/SceneSynchronizer.xml new file mode 100644 index 000000000..353f89ec1 --- /dev/null +++ b/modules/network_synchronizer/class_docs/SceneSynchronizer.xml @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/network_synchronizer/config.py b/modules/network_synchronizer/config.py new file mode 100644 index 000000000..249c3051f --- /dev/null +++ b/modules/network_synchronizer/config.py @@ -0,0 +1,18 @@ +def can_build(env, platform): + return False + + +def configure(env): + pass + + +def get_doc_classes(): + return [] + + +def get_doc_path(): + return "" + + +def is_enabled(): + return False diff --git a/modules/network_synchronizer/data_buffer.cpp b/modules/network_synchronizer/data_buffer.cpp new file mode 100644 index 000000000..b5fd14f30 --- /dev/null +++ b/modules/network_synchronizer/data_buffer.cpp @@ -0,0 +1,942 @@ +/*************************************************************************/ +/* data_buffer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "data_buffer.h" + +#include "core/io/marshalls.h" + +// TODO improve the allocation mechanism. + +void DataBuffer::_bind_methods() { + BIND_ENUM_CONSTANT(DATA_TYPE_BOOL); + BIND_ENUM_CONSTANT(DATA_TYPE_INT); + BIND_ENUM_CONSTANT(DATA_TYPE_REAL); + BIND_ENUM_CONSTANT(DATA_TYPE_UNIT_REAL); + BIND_ENUM_CONSTANT(DATA_TYPE_VECTOR2); + BIND_ENUM_CONSTANT(DATA_TYPE_NORMALIZED_VECTOR2); + BIND_ENUM_CONSTANT(DATA_TYPE_VECTOR3); + BIND_ENUM_CONSTANT(DATA_TYPE_NORMALIZED_VECTOR3); + + BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_0); + BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_1); + BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_2); + BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_3); + + ClassDB::bind_method(D_METHOD("size"), &DataBuffer::size); + + ClassDB::bind_method(D_METHOD("add_bool", "value"), &DataBuffer::add_bool); + ClassDB::bind_method(D_METHOD("add_int", "value", "compression_level"), &DataBuffer::add_int, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_real", "value", "compression_level"), &DataBuffer::add_real, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_positive_unit_real", "value", "compression_level"), &DataBuffer::add_positive_unit_real, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_unit_real", "value", "compression_level"), &DataBuffer::add_unit_real, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_vector2", "value", "compression_level"), &DataBuffer::add_vector2, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_normalized_vector2", "value", "compression_level"), &DataBuffer::add_normalized_vector2, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_vector3", "value", "compression_level"), &DataBuffer::add_vector3, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_normalized_vector3", "value", "compression_level"), &DataBuffer::add_normalized_vector3, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("add_variant", "value"), &DataBuffer::add_variant); + + ClassDB::bind_method(D_METHOD("read_bool"), &DataBuffer::read_bool); + ClassDB::bind_method(D_METHOD("read_int", "compression_level"), &DataBuffer::read_int, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_real", "compression_level"), &DataBuffer::read_real, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_unit_real", "compression_level"), &DataBuffer::read_unit_real, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_vector2", "compression_level"), &DataBuffer::read_vector2, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_normalized_vector2", "compression_level"), &DataBuffer::read_normalized_vector2, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_vector3", "compression_level"), &DataBuffer::read_vector3, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_normalized_vector3", "compression_level"), &DataBuffer::read_normalized_vector3, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_variant"), &DataBuffer::read_variant); + + ClassDB::bind_method(D_METHOD("skip_bool"), &DataBuffer::skip_bool); + ClassDB::bind_method(D_METHOD("skip_int", "compression_level"), &DataBuffer::skip_int, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("skip_real", "compression_level"), &DataBuffer::skip_real, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("skip_unit_real", "compression_level"), &DataBuffer::skip_unit_real, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("skip_vector2", "compression_level"), &DataBuffer::skip_vector2, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("skip_normalized_vector2", "compression_level"), &DataBuffer::skip_normalized_vector2, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("skip_vector3", "compression_level"), &DataBuffer::skip_vector3, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("skip_normalized_vector3", "compression_level"), &DataBuffer::skip_normalized_vector3, DEFVAL(COMPRESSION_LEVEL_1)); + + ClassDB::bind_method(D_METHOD("get_bool_size"), &DataBuffer::get_bool_size); + ClassDB::bind_method(D_METHOD("get_int_size", "compression_level"), &DataBuffer::get_int_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("get_real_size", "compression_level"), &DataBuffer::get_real_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("get_unit_real_size", "compression_level"), &DataBuffer::get_unit_real_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("get_vector2_size", "compression_level"), &DataBuffer::get_vector2_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("get_normalized_vector2_size", "compression_level"), &DataBuffer::get_normalized_vector2_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("get_vector3_size", "compression_level"), &DataBuffer::get_vector3_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("get_normalized_vector3_size", "compression_level"), &DataBuffer::get_normalized_vector3_size, DEFVAL(COMPRESSION_LEVEL_1)); + + ClassDB::bind_method(D_METHOD("read_bool_size"), &DataBuffer::read_bool_size); + ClassDB::bind_method(D_METHOD("read_int_size", "compression_level"), &DataBuffer::read_int_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_real_size", "compression_level"), &DataBuffer::read_real_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_unit_real_size", "compression_level"), &DataBuffer::read_unit_real_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_vector2_size", "compression_level"), &DataBuffer::read_vector2_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_normalized_vector2_size", "compression_level"), &DataBuffer::read_normalized_vector2_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_vector3_size", "compression_level"), &DataBuffer::read_vector3_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_normalized_vector3_size", "compression_level"), &DataBuffer::read_normalized_vector3_size, DEFVAL(COMPRESSION_LEVEL_1)); + ClassDB::bind_method(D_METHOD("read_variant_size"), &DataBuffer::read_variant_size); + + ClassDB::bind_method(D_METHOD("begin_read"), &DataBuffer::begin_read); + ClassDB::bind_method(D_METHOD("begin_write", "meta_size"), &DataBuffer::begin_write); + ClassDB::bind_method(D_METHOD("dry"), &DataBuffer::dry); +} + +DataBuffer::DataBuffer(const DataBuffer &p_other) : + Object(), + metadata_size(p_other.metadata_size), + bit_offset(p_other.bit_offset), + bit_size(p_other.bit_size), + is_reading(p_other.is_reading), + buffer(p_other.buffer) {} + +DataBuffer::DataBuffer(const BitArray &p_buffer) : + Object(), + bit_size(p_buffer.size_in_bits()), + is_reading(true), + buffer(p_buffer) {} + +void DataBuffer::begin_write(int p_metadata_size) { + CRASH_COND_MSG(p_metadata_size < 0, "Metadata size can't be negative"); + metadata_size = p_metadata_size; + bit_size = 0; + bit_offset = 0; + is_reading = false; +} + +void DataBuffer::dry() { + buffer.resize_in_bits(metadata_size + bit_size); +} + +void DataBuffer::seek(int p_bits) { + ERR_FAIL_INDEX(p_bits, metadata_size + bit_size + 1); + bit_offset = p_bits; +} + +void DataBuffer::shrink_to(int p_metadata_bit_size, int p_bit_size) { + CRASH_COND_MSG(p_metadata_bit_size < 0, "Metadata size can't be negative"); + ERR_FAIL_COND_MSG(p_bit_size < 0, "Bit size can't be negative"); + ERR_FAIL_COND_MSG(buffer.size_in_bits() < (p_metadata_bit_size + p_bit_size), "The buffer is smaller than the new given size."); + metadata_size = p_metadata_bit_size; + bit_size = p_bit_size; +} + +int DataBuffer::get_metadata_size() const { + return metadata_size; +} + +int DataBuffer::size() const { + return bit_size; +} + +int DataBuffer::total_size() const { + return bit_size + metadata_size; +} + +int DataBuffer::get_bit_offset() const { + return bit_offset; +} + +void DataBuffer::skip(int p_bits) { + ERR_FAIL_COND((metadata_size + bit_size) < (bit_offset + p_bits)); + bit_offset += p_bits; +} + +void DataBuffer::begin_read() { + bit_offset = 0; + is_reading = true; +} + +bool DataBuffer::add_bool(bool p_input) { + ERR_FAIL_COND_V(is_reading == true, p_input); + + const int bits = get_bit_taken(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0); + + make_room_in_bits(bits); + buffer.store_bits(bit_offset, p_input, bits); + bit_offset += bits; + +#ifdef DEBUG_ENABLED + // Can't never happen because the buffer size is correctly handled. + CRASH_COND((metadata_size + bit_size) > buffer.size_in_bits() && bit_offset > buffer.size_in_bits()); +#endif + + return p_input; +} + +bool DataBuffer::read_bool() { + ERR_FAIL_COND_V(is_reading == false, false); + + const int bits = get_bit_taken(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0); + const bool d = buffer.read_bits(bit_offset, bits); + bit_offset += bits; + return d; +} + +int64_t DataBuffer::add_int(int64_t p_input, CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == true, p_input); + + const int bits = get_bit_taken(DATA_TYPE_INT, p_compression_level); + + int64_t value = p_input; + + // Clamp the value to the max that the bit can store. + if (bits == 8) { + value = CLAMP(value, INT8_MIN, INT8_MAX); + } else if (bits == 16) { + value = CLAMP(value, INT16_MIN, INT16_MAX); + } else if (bits == 32) { + value = CLAMP(value, INT32_MIN, INT32_MAX); + } else { + // Nothing to do here + } + + make_room_in_bits(bits); + buffer.store_bits(bit_offset, value, bits); + bit_offset += bits; + +#ifdef DEBUG_ENABLED + // Can't never happen because the buffer size is correctly handled. + CRASH_COND((metadata_size + bit_size) > buffer.size_in_bits() && bit_offset > buffer.size_in_bits()); +#endif + + if (bits == 8) { + return static_cast(value); + } else if (bits == 16) { + return static_cast(value); + } else if (bits == 32) { + return static_cast(value); + } else { + return value; + } +} + +int64_t DataBuffer::read_int(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, 0); + + const int bits = get_bit_taken(DATA_TYPE_INT, p_compression_level); + + const uint64_t value = buffer.read_bits(bit_offset, bits); + bit_offset += bits; + + if (bits == 8) { + return static_cast(value); + } else if (bits == 16) { + return static_cast(value); + } else if (bits == 32) { + return static_cast(value); + } else { + return static_cast(value); + } +} + +double DataBuffer::add_real(double p_input, CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == true, p_input); + + // Clamp the input value according to the compression level + // Minifloat (compression level 0) have a special bias + const int exponent_bits = get_exponent_bits(p_compression_level); + const int mantissa_bits = get_mantissa_bits(p_compression_level); + const double bias = p_compression_level == COMPRESSION_LEVEL_3 ? Math::pow(2.0, exponent_bits) - 3 : Math::pow(2.0, exponent_bits - 1) - 1; + const double max_value = (2.0 - Math::pow(2.0, -(mantissa_bits - 1))) * Math::pow(2.0, bias); + const double clamped_input = CLAMP(p_input, -max_value, max_value); + + // Split number according to IEEE 754 binary format. + // Mantissa floating point value represented in range (-1;-0.5], [0.5; 1). + int exponent; + double mantissa = frexp(clamped_input, &exponent); + + // Extract sign. + const bool sign = mantissa < 0; + mantissa = Math::abs(mantissa); + + // Round mantissa into the specified number of bits (like float -> double conversion). + double mantissa_scale = Math::pow(2.0, mantissa_bits); + if (exponent <= 0) { + // Subnormal value, apply exponent to mantissa and reduce power of scale by one. + mantissa *= Math::pow(2.0, exponent); + exponent = 0; + mantissa_scale /= 2.0; + } + mantissa = Math::round(mantissa * mantissa_scale) / mantissa_scale; // Round to specified number of bits. + if (mantissa < 0.5 && mantissa != 0) { + // Check underflow, extract exponent from mantissa. + exponent += ilogb(mantissa) + 1; + mantissa /= Math::pow(2.0, exponent); + } else if (mantissa == 1) { + // Check overflow, increment the exponent. + ++exponent; + mantissa = 0.5; + } + // Convert the mantissa to an integer that represents the offset index (IEE 754 floating point representation) to send over network safely. + const uint64_t integer_mantissa = exponent <= 0 ? mantissa * mantissa_scale * Math::pow(2.0, exponent) : (mantissa - 0.5) * mantissa_scale; + + make_room_in_bits(mantissa_bits + exponent_bits); + buffer.store_bits(bit_offset, sign, 1); + bit_offset += 1; + buffer.store_bits(bit_offset, integer_mantissa, mantissa_bits - 1); + bit_offset += mantissa_bits - 1; + // Send unsigned value (just shift it by bias) to avoid sign issues. + buffer.store_bits(bit_offset, exponent + bias, exponent_bits); + bit_offset += exponent_bits; + + return ldexp(sign ? -mantissa : mantissa, exponent); +} + +double DataBuffer::read_real(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, 0.0); + + const bool sign = buffer.read_bits(bit_offset, 1); + bit_offset += 1; + + const int mantissa_bits = get_mantissa_bits(p_compression_level); + const uint64_t integer_mantissa = buffer.read_bits(bit_offset, mantissa_bits - 1); + bit_offset += mantissa_bits - 1; + + const int exponent_bits = get_exponent_bits(p_compression_level); + const double bias = p_compression_level == COMPRESSION_LEVEL_3 ? Math::pow(2.0, exponent_bits) - 3 : Math::pow(2.0, exponent_bits - 1) - 1; + int exponent = static_cast(buffer.read_bits(bit_offset, exponent_bits)) - static_cast(bias); + bit_offset += exponent_bits; + + // Convert integer mantissa into the floating point representation + // When the index of the mantissa and exponent are 0, then this is a special case and the mantissa is 0. + const double mantissa_scale = Math::pow(2.0, exponent <= 0 ? mantissa_bits - 1 : mantissa_bits); + const double mantissa = exponent <= 0 ? integer_mantissa / mantissa_scale / Math::pow(2.0, exponent) : integer_mantissa / mantissa_scale + 0.5; + + return ldexp(sign ? -mantissa : mantissa, exponent); +} + +real_t DataBuffer::add_positive_unit_real(real_t p_input, CompressionLevel p_compression_level) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V_MSG(p_input < 0 || p_input > 1, p_input, "Value must be between zero and one."); +#endif + ERR_FAIL_COND_V(is_reading == true, p_input); + + const int bits = get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression_level); + + const double max_value = static_cast(~(UINT64_MAX << bits)); + + const uint64_t compressed_val = compress_unit_float(p_input, max_value); + + make_room_in_bits(bits); + buffer.store_bits(bit_offset, compressed_val, bits); + bit_offset += bits; + +#ifdef DEBUG_ENABLED + // Can't never happen because the buffer size is correctly handled. + CRASH_COND((metadata_size + bit_size) > buffer.size_in_bits() && bit_offset > buffer.size_in_bits()); +#endif + + return decompress_unit_float(compressed_val, max_value); +} + +real_t DataBuffer::read_positive_unit_real(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, 0.0); + + const int bits = get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression_level); + + const double max_value = static_cast(~(UINT64_MAX << bits)); + + const uint64_t compressed_val = buffer.read_bits(bit_offset, bits); + bit_offset += bits; + + return decompress_unit_float(compressed_val, max_value); +} + +real_t DataBuffer::add_unit_real(real_t p_input, CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == true, p_input); + + const real_t added_real = add_positive_unit_real(ABS(p_input), p_compression_level); + + const int bits_for_sign = 1; + const uint32_t is_negative = p_input < 0.0; + make_room_in_bits(bits_for_sign); + buffer.store_bits(bit_offset, is_negative, bits_for_sign); + bit_offset += bits_for_sign; + +#ifdef DEBUG_ENABLED + // Can't never happen because the buffer size is correctly handled. + CRASH_COND((metadata_size + bit_size) > buffer.size_in_bits() && bit_offset > buffer.size_in_bits()); +#endif + + return is_negative ? -added_real : added_real; +} + +real_t DataBuffer::read_unit_real(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, 0.0); + + const real_t value = read_positive_unit_real(p_compression_level); + + const int bits_for_sign = 1; + const bool is_negative = buffer.read_bits(bit_offset, bits_for_sign); + bit_offset += bits_for_sign; + + return is_negative ? -value : value; +} + +Vector2 DataBuffer::add_vector2(Vector2 p_input, CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == true, p_input); + +#ifndef REAL_T_IS_DOUBLE + // Fallback to compression level 1 if real_t is float + if (p_compression_level == DataBuffer::COMPRESSION_LEVEL_0) { + WARN_PRINT_ONCE("Compression level 0 is not supported for Vector2 for a binary compiled with single precision float. Falling back to compression level 1"); + p_compression_level = DataBuffer::COMPRESSION_LEVEL_1; + } +#endif + + Vector2 r; + r[0] = add_real(p_input[0], p_compression_level); + r[1] = add_real(p_input[1], p_compression_level); + return r; +} + +Vector2 DataBuffer::read_vector2(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, Vector2()); + +#ifndef REAL_T_IS_DOUBLE + // Fallback to compression level 1 if real_t is float + if (p_compression_level == DataBuffer::COMPRESSION_LEVEL_0) { + WARN_PRINT_ONCE("Compression level 0 is not supported for Vector2 for a binary compiled with single precision float. Falling back to compression level 1"); + p_compression_level = DataBuffer::COMPRESSION_LEVEL_1; + } +#endif + + Vector2 r; + r[0] = read_real(p_compression_level); + r[1] = read_real(p_compression_level); + return r; +} + +Vector2 DataBuffer::add_normalized_vector2(Vector2 p_input, CompressionLevel p_compression_level) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(p_input.is_normalized() == false, p_input); +#endif + ERR_FAIL_COND_V(is_reading == true, p_input); + + const int bits = get_bit_taken(DATA_TYPE_NORMALIZED_VECTOR2, p_compression_level); + const int bits_for_the_angle = bits - 1; + const int bits_for_zero = 1; + + const double angle = p_input.angle(); + const uint32_t is_not_zero = p_input.length_squared() > CMP_EPSILON; + + const double max_value = static_cast(~(UINT64_MAX << bits_for_the_angle)); + + const uint64_t compressed_angle = compress_unit_float((angle + Math_PI) / Math_TAU, max_value); + + make_room_in_bits(bits); + buffer.store_bits(bit_offset, is_not_zero, bits_for_zero); + buffer.store_bits(bit_offset + 1, compressed_angle, bits_for_the_angle); + bit_offset += bits; + + const real_t decompressed_angle = (decompress_unit_float(compressed_angle, max_value) * Math_TAU) - Math_PI; + const real_t x = Math::cos(decompressed_angle); + const real_t y = Math::sin(decompressed_angle); + +#ifdef DEBUG_ENABLED + // Can't never happen because the buffer size is correctly handled. + CRASH_COND((metadata_size + bit_size) > buffer.size_in_bits() && bit_offset > buffer.size_in_bits()); +#endif + + return Vector2(x, y) * is_not_zero; +} + +Vector2 DataBuffer::read_normalized_vector2(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, Vector2()); + + const int bits = get_bit_taken(DATA_TYPE_NORMALIZED_VECTOR2, p_compression_level); + const int bits_for_the_angle = bits - 1; + const int bits_for_zero = 1; + + const double max_value = static_cast(~(UINT64_MAX << bits_for_the_angle)); + + const real_t is_not_zero = buffer.read_bits(bit_offset, bits_for_zero); + const uint64_t compressed_angle = buffer.read_bits(bit_offset + 1, bits_for_the_angle); + bit_offset += bits; + + const real_t decompressed_angle = (decompress_unit_float(compressed_angle, max_value) * Math_TAU) - Math_PI; + const real_t x = Math::cos(decompressed_angle); + const real_t y = Math::sin(decompressed_angle); + + return Vector2(x, y) * is_not_zero; +} + +Vector3 DataBuffer::add_vector3(Vector3 p_input, CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == true, p_input); + +#ifndef REAL_T_IS_DOUBLE + // Fallback to compression level 1 if real_t is float + if (p_compression_level == DataBuffer::COMPRESSION_LEVEL_0) { + WARN_PRINT_ONCE("Compression level 0 is not supported for Vector3 for a binary compiled with single precision float. Falling back to compression level 1"); + p_compression_level = DataBuffer::COMPRESSION_LEVEL_1; + } +#endif + + Vector3 r; + r[0] = add_real(p_input[0], p_compression_level); + r[1] = add_real(p_input[1], p_compression_level); + r[2] = add_real(p_input[2], p_compression_level); + return r; +} + +Vector3 DataBuffer::read_vector3(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, Vector3()); + +#ifndef REAL_T_IS_DOUBLE + // Fallback to compression level 1 if real_t is float + if (p_compression_level == DataBuffer::COMPRESSION_LEVEL_0) { + WARN_PRINT_ONCE("Compression level 0 is not supported for Vector3 for a binary compiled with single precision float. Falling back to compression level 1"); + p_compression_level = DataBuffer::COMPRESSION_LEVEL_1; + } +#endif + + Vector3 r; + r[0] = read_real(p_compression_level); + r[1] = read_real(p_compression_level); + r[2] = read_real(p_compression_level); + return r; +} + +Vector3 DataBuffer::add_normalized_vector3(Vector3 p_input, CompressionLevel p_compression_level) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(p_input.is_normalized() == false, p_input); +#endif + ERR_FAIL_COND_V(is_reading == true, p_input); + + const real_t x_axis = add_unit_real(p_input.x, p_compression_level); + const real_t y_axis = add_unit_real(p_input.y, p_compression_level); + const real_t z_axis = add_unit_real(p_input.z, p_compression_level); + + return Vector3(x_axis, y_axis, z_axis); +} + +Vector3 DataBuffer::read_normalized_vector3(CompressionLevel p_compression_level) { + ERR_FAIL_COND_V(is_reading == false, Vector3()); + + const real_t x_axis = read_unit_real(p_compression_level); + const real_t y_axis = read_unit_real(p_compression_level); + const real_t z_axis = read_unit_real(p_compression_level); + + return Vector3(x_axis, y_axis, z_axis); +} + +Variant DataBuffer::add_variant(const Variant &p_input) { + // TODO consider to use a method similar to `_encode_and_compress_variant` + // to compress the encoded data a bit. + + // Get the variant size. + int len = 0; + + const Error len_err = encode_variant( + p_input, + nullptr, + len, + false); + + ERR_FAIL_COND_V_MSG( + len_err != OK, + Variant(), + "Was not possible encode the variant."); + + // Variant encoding pads the data to byte, so doesn't make sense write it + // unpadded. + make_room_pad_to_next_byte(); + make_room_in_bits(len * 8); + +#ifdef DEBUG_ENABLED + // This condition is always false thanks to the `make_room_pad_to_next_byte`. + // so it's safe to assume we are starting from the begin of the byte. + CRASH_COND((bit_offset % 8) != 0); +#endif + + const Error write_err = encode_variant( + p_input, + buffer.get_bytes_mut().ptrw() + (bit_offset / 8), + len, + false); + + ERR_FAIL_COND_V_MSG( + write_err != OK, + Variant(), + "Was not possible encode the variant."); + + bit_offset += len * 8; + + return p_input; +} + +Variant DataBuffer::read_variant() { + Variant ret; + + int len = 0; + + // The Variant is always written starting from the beginning of the byte. + const bool success = pad_to_next_byte(); + ERR_FAIL_COND_V_MSG(success == false, Variant(), "Padding failed."); + +#ifdef DEBUG_ENABLED + // This condition is always false thanks to the `pad_to_next_byte`; So is + // safe to assume we are starting from the begin of the byte. + CRASH_COND((bit_offset % 8) != 0); +#endif + + const Error read_err = decode_variant( + ret, + buffer.get_bytes().ptr() + (bit_offset / 8), + buffer.size_in_bytes() - (bit_offset / 8), + &len, + false); + + ERR_FAIL_COND_V_MSG( + read_err != OK, + Variant(), + "Was not possible decode the variant."); + + bit_offset += len * 8; + + return ret; +} + +void DataBuffer::zero() { + buffer.zero(); +} + +void DataBuffer::skip_bool() { + const int bits = get_bool_size(); + skip(bits); +} + +void DataBuffer::skip_int(CompressionLevel p_compression) { + const int bits = get_int_size(p_compression); + skip(bits); +} + +void DataBuffer::skip_real(CompressionLevel p_compression) { + const int bits = get_real_size(p_compression); + skip(bits); +} + +void DataBuffer::skip_unit_real(CompressionLevel p_compression) { + const int bits = get_unit_real_size(p_compression); + skip(bits); +} + +void DataBuffer::skip_vector2(CompressionLevel p_compression) { + const int bits = get_vector2_size(p_compression); + skip(bits); +} + +void DataBuffer::skip_normalized_vector2(CompressionLevel p_compression) { + const int bits = get_normalized_vector2_size(p_compression); + skip(bits); +} + +void DataBuffer::skip_vector3(CompressionLevel p_compression) { + const int bits = get_vector3_size(p_compression); + skip(bits); +} + +void DataBuffer::skip_normalized_vector3(CompressionLevel p_compression) { + const int bits = get_normalized_vector3_size(p_compression); + skip(bits); +} + +int DataBuffer::get_bool_size() const { + return DataBuffer::get_bit_taken(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0); +} + +int DataBuffer::get_int_size(CompressionLevel p_compression) const { + return DataBuffer::get_bit_taken(DATA_TYPE_INT, p_compression); +} + +int DataBuffer::get_real_size(CompressionLevel p_compression) const { + return DataBuffer::get_bit_taken(DATA_TYPE_REAL, p_compression); +} + +int DataBuffer::get_unit_real_size(CompressionLevel p_compression) const { + return DataBuffer::get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression); +} + +int DataBuffer::get_vector2_size(CompressionLevel p_compression) const { + return DataBuffer::get_bit_taken(DATA_TYPE_VECTOR2, p_compression); +} + +int DataBuffer::get_normalized_vector2_size(CompressionLevel p_compression) const { + return DataBuffer::get_bit_taken(DATA_TYPE_NORMALIZED_VECTOR2, p_compression); +} + +int DataBuffer::get_vector3_size(CompressionLevel p_compression) const { + return DataBuffer::get_bit_taken(DATA_TYPE_VECTOR3, p_compression); +} + +int DataBuffer::get_normalized_vector3_size(CompressionLevel p_compression) const { + return DataBuffer::get_bit_taken(DATA_TYPE_NORMALIZED_VECTOR3, p_compression); +} + +int DataBuffer::read_bool_size() { + const int bits = get_bool_size(); + skip(bits); + return bits; +} + +int DataBuffer::read_int_size(CompressionLevel p_compression) { + const int bits = get_int_size(p_compression); + skip(bits); + return bits; +} + +int DataBuffer::read_real_size(CompressionLevel p_compression) { + const int bits = get_real_size(p_compression); + skip(bits); + return bits; +} + +int DataBuffer::read_unit_real_size(CompressionLevel p_compression) { + const int bits = get_unit_real_size(p_compression); + skip(bits); + return bits; +} + +int DataBuffer::read_vector2_size(CompressionLevel p_compression) { + const int bits = get_vector2_size(p_compression); + skip(bits); + return bits; +} + +int DataBuffer::read_normalized_vector2_size(CompressionLevel p_compression) { + const int bits = get_normalized_vector2_size(p_compression); + skip(bits); + return bits; +} + +int DataBuffer::read_vector3_size(CompressionLevel p_compression) { + const int bits = get_vector3_size(p_compression); + skip(bits); + return bits; +} + +int DataBuffer::read_normalized_vector3_size(CompressionLevel p_compression) { + const int bits = get_normalized_vector3_size(p_compression); + skip(bits); + return bits; +} + +int DataBuffer::read_variant_size() { + int len = 0; + + Variant ret; + + // The Variant is always written starting from the beginning of the byte. + const bool success = pad_to_next_byte(); + ERR_FAIL_COND_V_MSG(success == false, Variant(), "Padding failed."); + +#ifdef DEBUG_ENABLED + // This condition is always false thanks to the `pad_to_next_byte`; So is + // safe to assume we are starting from the begin of the byte. + CRASH_COND((bit_offset % 8) != 0); +#endif + + const Error read_err = decode_variant( + ret, + buffer.get_bytes().ptr() + (bit_offset / 8), + buffer.size_in_bytes() - (bit_offset / 8), + &len, + false); + + ERR_FAIL_COND_V_MSG( + read_err != OK, + 0, + "Was not possible to decode the variant, error: " + itos(read_err)); + + bit_offset += len * 8; + + return len * 8; +} + +int DataBuffer::get_bit_taken(DataType p_data_type, CompressionLevel p_compression) { + switch (p_data_type) { + case DATA_TYPE_BOOL: + // No matter what, 1 bit. + return 1; + case DATA_TYPE_INT: { + switch (p_compression) { + case COMPRESSION_LEVEL_0: + return 64; + case COMPRESSION_LEVEL_1: + return 32; + case COMPRESSION_LEVEL_2: + return 16; + case COMPRESSION_LEVEL_3: + return 8; + default: + // Unreachable + CRASH_NOW_MSG("Compression level not supported!"); + } + } break; + case DATA_TYPE_REAL: { + return get_mantissa_bits(p_compression) + + get_exponent_bits(p_compression); + } break; + case DATA_TYPE_POSITIVE_UNIT_REAL: { + switch (p_compression) { + case COMPRESSION_LEVEL_0: + return 10; + case COMPRESSION_LEVEL_1: + return 8; + case COMPRESSION_LEVEL_2: + return 6; + case COMPRESSION_LEVEL_3: + return 4; + default: + // Unreachable + CRASH_NOW_MSG("Compression level not supported!"); + } + } break; + case DATA_TYPE_UNIT_REAL: { + return get_bit_taken(DATA_TYPE_POSITIVE_UNIT_REAL, p_compression) + 1; + } break; + case DATA_TYPE_VECTOR2: { + return get_bit_taken(DATA_TYPE_REAL, p_compression) * 2; + } break; + case DATA_TYPE_NORMALIZED_VECTOR2: { + // +1 bit to know if the vector is 0 or a direction + switch (p_compression) { + case CompressionLevel::COMPRESSION_LEVEL_0: + return 11 + 1; + case CompressionLevel::COMPRESSION_LEVEL_1: + return 10 + 1; + case CompressionLevel::COMPRESSION_LEVEL_2: + return 9 + 1; + case CompressionLevel::COMPRESSION_LEVEL_3: + return 8 + 1; + } + } break; + case DATA_TYPE_VECTOR3: { + return get_bit_taken(DATA_TYPE_REAL, p_compression) * 3; + } break; + case DATA_TYPE_NORMALIZED_VECTOR3: { + switch (p_compression) { + case CompressionLevel::COMPRESSION_LEVEL_0: + return 11 * 3; + case CompressionLevel::COMPRESSION_LEVEL_1: + return 10 * 3; + case CompressionLevel::COMPRESSION_LEVEL_2: + return 8 * 3; + case CompressionLevel::COMPRESSION_LEVEL_3: + return 6 * 3; + } + } break; + case DATA_TYPE_VARIANT: { + ERR_FAIL_V_MSG(0, "The variant size is dynamic and can't be know at compile time."); + } + default: + // Unreachable + CRASH_NOW_MSG("Input type not supported!"); + } + + // Unreachable + CRASH_NOW_MSG("It was not possible to obtain the bit taken by this input data."); + return 0; // Useless, but MS CI is too noisy. +} + +int DataBuffer::get_mantissa_bits(CompressionLevel p_compression) { + // https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats + switch (p_compression) { + case CompressionLevel::COMPRESSION_LEVEL_0: + return 53; // Binary64 format + case CompressionLevel::COMPRESSION_LEVEL_1: + return 24; // Binary32 format + case CompressionLevel::COMPRESSION_LEVEL_2: + return 11; // Binary16 format + case CompressionLevel::COMPRESSION_LEVEL_3: + return 4; // https://en.wikipedia.org/wiki/Minifloat + } + + // Unreachable + CRASH_NOW_MSG("Unknown compression level."); + return 0; // Useless, but MS CI is too noisy. +} + +int DataBuffer::get_exponent_bits(CompressionLevel p_compression) { + // https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats + switch (p_compression) { + case CompressionLevel::COMPRESSION_LEVEL_0: + return 11; // Binary64 format + case CompressionLevel::COMPRESSION_LEVEL_1: + return 8; // Binary32 format + case CompressionLevel::COMPRESSION_LEVEL_2: + return 5; // Binary16 format + case CompressionLevel::COMPRESSION_LEVEL_3: + return 4; // https://en.wikipedia.org/wiki/Minifloat + } + + // Unreachable + CRASH_NOW_MSG("Unknown compression level."); + return 0; // Useless, but MS CI is too noisy. +} + +uint64_t DataBuffer::compress_unit_float(double p_value, double p_scale_factor) { + return Math::round(MIN(p_value * p_scale_factor, p_scale_factor)); +} + +double DataBuffer::decompress_unit_float(uint64_t p_value, double p_scale_factor) { + return static_cast(p_value) / p_scale_factor; +} + +void DataBuffer::make_room_in_bits(int p_dim) { + const int array_min_dim = bit_offset + p_dim; + if (array_min_dim > buffer.size_in_bits()) { + buffer.resize_in_bits(array_min_dim); + } + + if (array_min_dim > metadata_size) { + const int new_bit_size = array_min_dim - metadata_size; + if (new_bit_size > bit_size) { + bit_size = new_bit_size; + } + } +} + +void DataBuffer::make_room_pad_to_next_byte() { + const int bits_to_next_byte = ((bit_offset + 7) & ~7) - bit_offset; + make_room_in_bits(bits_to_next_byte); + bit_offset += bits_to_next_byte; +} + +bool DataBuffer::pad_to_next_byte() { + const int bits_to_next_byte = ((bit_offset + 7) & ~7) - bit_offset; + ERR_FAIL_COND_V_MSG( + bit_offset + bits_to_next_byte > buffer.size_in_bits(), + false, + ""); + bit_offset += bits_to_next_byte; + return true; +} diff --git a/modules/network_synchronizer/data_buffer.h b/modules/network_synchronizer/data_buffer.h new file mode 100644 index 000000000..7a2f54011 --- /dev/null +++ b/modules/network_synchronizer/data_buffer.h @@ -0,0 +1,342 @@ +/*************************************************************************/ +/* data_buffer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "core/class_db.h" + +#include "bit_array.h" + +#ifndef INPUT_BUFFER_H +#define INPUT_BUFFER_H + +class DataBuffer : public Object { + GDCLASS(DataBuffer, Object); + +public: + enum DataType { + DATA_TYPE_BOOL, + DATA_TYPE_INT, + DATA_TYPE_REAL, + DATA_TYPE_POSITIVE_UNIT_REAL, + DATA_TYPE_UNIT_REAL, + DATA_TYPE_VECTOR2, + DATA_TYPE_NORMALIZED_VECTOR2, + DATA_TYPE_VECTOR3, + DATA_TYPE_NORMALIZED_VECTOR3, + // The only dynamic sized value. + DATA_TYPE_VARIANT + }; + + /// Compression level for the stored input data. + /// + /// Depending on the data type and the compression level used the amount of + /// bits used and loss change. + /// + /// + /// ## Bool + /// Always use 1 bit + /// + /// + /// ## Int + /// COMPRESSION_LEVEL_0: 64 bits are used - Stores integers -9223372036854775808 / 9223372036854775807 + /// COMPRESSION_LEVEL_1: 32 bits are used - Stores integers -2147483648 / 2147483647 + /// COMPRESSION_LEVEL_2: 16 bits are used - Stores integers -32768 / 32767 + /// COMPRESSION_LEVEL_3: 8 bits are used - Stores integers -128 / 127 + /// + /// + /// ## Real + /// Precision depends on an integer range + /// COMPRESSION_LEVEL_0: 64 bits are used - Double precision. Up to 16 precision is 0.00000000000000177636 in worst case. Up to 512 precision is 0.00000000000005684342 in worst case. Up to 1024 precision is 0.00000000000011368684 in worst case. + /// COMPRESSION_LEVEL_1: 32 bits are used - Single precision (float). Up to 16 precision is 0.00000095367431640625 in worst case. Up to 512 precision is 0.000030517578125 in worst case. Up to 1024 precision is 0.00006103515625 in worst case. + /// COMPRESSION_LEVEL_2: 16 bits are used - Half precision. Up to 16 precision is 0.0078125 in worst case. Up to 512 precision is 0.25 in worst case. Up to 1024 precision is 0.5. + /// COMPRESSION_LEVEL_3: 8 bits are used - Minifloat: Up to 2 precision is 0.125. Up to 4 precision is 0.25. Up to 8 precision is 0.5. + /// + /// To get the exact precision for the stored number, you need to find the lower power of two relative to the number and divide it by 2^mantissa_bits. + /// To get the mantissa or exponent bits for a specific compression level, you can use the get_mantissa_bits and get_exponent_bits functions. + /// + /// + /// ## Positive unit real + /// COMPRESSION_LEVEL_0: 10 bits are used - Max loss ~0.005% + /// COMPRESSION_LEVEL_1: 8 bits are used - Max loss ~0.020% + /// COMPRESSION_LEVEL_2: 6 bits are used - Max loss ~0.793% + /// COMPRESSION_LEVEL_3: 4 bits are used - Max loss ~3.333% + /// + /// + /// ## Unit real (uses one extra bit for the sign) + /// COMPRESSION_LEVEL_0: 11 bits are used - Max loss ~0.005% + /// COMPRESSION_LEVEL_1: 9 bits are used - Max loss ~0.020% + /// COMPRESSION_LEVEL_2: 7 bits are used - Max loss ~0.793% + /// COMPRESSION_LEVEL_3: 5 bits are used - Max loss ~3.333% + /// + /// + /// ## Vector2 + /// COMPRESSION_LEVEL_0: 2 * 64 bits are used - Double precision (will fallback to level 1 if REAL_T_IS_DOUBLE is not defined) + /// COMPRESSION_LEVEL_1: 2 * 32 bits are used - Single precision + /// COMPRESSION_LEVEL_2: 2 * 16 bits are used - Half precision + /// COMPRESSION_LEVEL_3: 2 * 8 bits are used - Minifloat + /// + /// For floating point precision, check the Real compression section. + /// + /// + /// ## Normalized Vector2 + /// COMPRESSION_LEVEL_0: 12 bits are used - Max loss 0.17° + /// COMPRESSION_LEVEL_1: 11 bits are used - Max loss 0.35° + /// COMPRESSION_LEVEL_2: 10 bits are used - Max loss 0.7° + /// COMPRESSION_LEVEL_3: 9 bits are used - Max loss 1.1° + /// + /// + /// ## Vector3 + /// COMPRESSION_LEVEL_0: 3 * 64 bits are used - Double precision (will fallback to level 1 if REAL_T_IS_DOUBLE is not defined) + /// COMPRESSION_LEVEL_1: 3 * 32 bits are used - Single precision + /// COMPRESSION_LEVEL_2: 3 * 16 bits are used - Half precision + /// COMPRESSION_LEVEL_3: 3 * 8 bits are used - Minifloat + /// + /// For floating point precision, check the Real compression section. + /// + /// + /// ## Normalized Vector3 + /// COMPRESSION_LEVEL_0: 11 * 3 bits are used - Max loss ~0.005% per axis + /// COMPRESSION_LEVEL_1: 9 * 3 bits are used - Max loss ~0.020% per axis + /// COMPRESSION_LEVEL_2: 7 * 3 bits are used - Max loss ~0.793% per axis + /// COMPRESSION_LEVEL_3: 5 * 3 bits are used - Max loss ~3.333% per axis + /// + /// ## Variant + /// It's dynamic sized. It's not possible to compress it. + enum CompressionLevel { + COMPRESSION_LEVEL_0, + COMPRESSION_LEVEL_1, + COMPRESSION_LEVEL_2, + COMPRESSION_LEVEL_3 + }; + +private: + int metadata_size = 0; + int bit_offset = 0; + int bit_size = 0; + bool is_reading = false; + BitArray buffer; + +public: + static void _bind_methods(); + + DataBuffer() = default; + DataBuffer(const DataBuffer &p_other); + DataBuffer(const BitArray &p_buffer); + + const BitArray &get_buffer() const { + return buffer; + } + + BitArray &get_buffer_mut() { + return buffer; + } + + /// Begin write. + void begin_write(int p_metadata_size); + + /// Make sure the buffer takes least space possible. + void dry(); + + /// Seek the offset to a specific bit. Seek to a bit greater than the actual + /// size is not allowed. + void seek(int p_bits); + + /// Set the bit size and the metadata size. + void shrink_to(int p_metadata_bit_size, int p_bit_size); + + /// Returns the metadata size in bits. + int get_metadata_size() const; + /// Returns the buffer size in bits + int size() const; + /// Total size in bits. + int total_size() const; + + /// Returns the bit offset. + int get_bit_offset() const; + + /// Skip n bits. + void skip(int p_bits); + + /// Begin read. + void begin_read(); + + /// Add a boolean to the buffer. + /// Returns the same data. + bool add_bool(bool p_input); + + /// Parse the next data as boolean. + bool read_bool(); + + /// Add the next data as int. + int64_t add_int(int64_t p_input, CompressionLevel p_compression_level); + + /// Parse the next data as int. + int64_t read_int(CompressionLevel p_compression_level); + + /// Add a real into the buffer. Depending on the compression level is possible + /// to store different range level. + /// The fractional part has a precision of ~0.3% + /// + /// Returns the compressed value so both the client and the peers can use + /// the same data. + double add_real(double p_input, CompressionLevel p_compression_level); + + /// Parse the following data as a real. + double read_real(CompressionLevel p_compression_level); + + /// Add a positive unit real into the buffer. + /// + /// **Note:** Not unitary values lead to unexpected behaviour. + /// + /// Returns the compressed value so both the client and the peers can use + /// the same data. + real_t add_positive_unit_real(real_t p_input, CompressionLevel p_compression_level); + + /// Parse the following data as a positive unit real. + real_t read_positive_unit_real(CompressionLevel p_compression_level); + + /// Add a unit real into the buffer. + /// + /// **Note:** Not unitary values lead to unexpected behaviour. + /// + /// Returns the compressed value so both the client and the peers can use + /// the same data. + real_t add_unit_real(real_t p_input, CompressionLevel p_compression_level); + + /// Parse the following data as an unit real. + real_t read_unit_real(CompressionLevel p_compression_level); + + /// Add a vector2 into the buffer. + /// Note: This kind of vector occupies more space than the normalized verison. + /// Consider use a normalized vector to save bandwidth if possible. + /// + /// Returns the decompressed vector so both the client and the peers can use + /// the same data. + Vector2 add_vector2(Vector2 p_input, CompressionLevel p_compression_level); + + /// Parse next data as vector from the input buffer. + Vector2 read_vector2(CompressionLevel p_compression_level); + + /// Add a normalized vector2 into the buffer. + /// Note: The compression algorithm rely on the fact that this is a + /// normalized vector. The behaviour is unexpected for not normalized vectors. + /// + /// Returns the decompressed vector so both the client and the peers can use + /// the same data. + Vector2 add_normalized_vector2(Vector2 p_input, CompressionLevel p_compression_level); + + /// Parse next data as normalized vector from the input buffer. + Vector2 read_normalized_vector2(CompressionLevel p_compression_level); + + /// Add a vector3 into the buffer. + /// Note: This kind of vector occupies more space than the normalized verison. + /// Consider use a normalized vector to save bandwidth if possible. + /// + /// Returns the decompressed vector so both the client and the peers can use + /// the same data. + Vector3 add_vector3(Vector3 p_input, CompressionLevel p_compression_level); + + /// Parse next data as vector3 from the input buffer. + Vector3 read_vector3(CompressionLevel p_compression_level); + + /// Add a normalized vector3 into the buffer. + /// Note: The compression algorithm rely on the fact that this is a + /// normalized vector. The behaviour is unexpected for not normalized vectors. + /// + /// Returns the decompressed vector so both the client and the peers can use + /// the same data. + Vector3 add_normalized_vector3(Vector3 p_input, CompressionLevel p_compression_level); + + /// Parse next data as normalized vector3 from the input buffer. + Vector3 read_normalized_vector3(CompressionLevel p_compression_level); + + /// Add a variant. This is the only supported dynamic sized value. + Variant add_variant(const Variant &p_input); + + /// Parse the next data as Variant and returns it. + Variant read_variant(); + + /// Puts all the bytes to 0. + void zero(); + + /** Skips the amount of bits a type takes. */ + + void skip_bool(); + void skip_int(CompressionLevel p_compression); + void skip_real(CompressionLevel p_compression); + void skip_unit_real(CompressionLevel p_compression); + void skip_vector2(CompressionLevel p_compression); + void skip_normalized_vector2(CompressionLevel p_compression); + void skip_vector3(CompressionLevel p_compression); + void skip_normalized_vector3(CompressionLevel p_compression); + + /** Just returns the size of a specific type. */ + + int get_bool_size() const; + int get_int_size(CompressionLevel p_compression) const; + int get_real_size(CompressionLevel p_compression) const; + int get_unit_real_size(CompressionLevel p_compression) const; + int get_vector2_size(CompressionLevel p_compression) const; + int get_normalized_vector2_size(CompressionLevel p_compression) const; + int get_vector3_size(CompressionLevel p_compression) const; + int get_normalized_vector3_size(CompressionLevel p_compression) const; + + /** Read the size and pass to the next parameter. */ + + int read_bool_size(); + int read_int_size(CompressionLevel p_compression); + int read_real_size(CompressionLevel p_compression); + int read_unit_real_size(CompressionLevel p_compression); + int read_vector2_size(CompressionLevel p_compression); + int read_normalized_vector2_size(CompressionLevel p_compression); + int read_vector3_size(CompressionLevel p_compression); + int read_normalized_vector3_size(CompressionLevel p_compression); + int read_variant_size(); + + static int get_bit_taken(DataType p_data_type, CompressionLevel p_compression); + static int get_mantissa_bits(CompressionLevel p_compression); + static int get_exponent_bits(CompressionLevel p_compression); + +private: + static uint64_t compress_unit_float(double p_value, double p_scale_factor); + static double decompress_unit_float(uint64_t p_value, double p_scale_factor); + + void make_room_in_bits(int p_dim); + void make_room_pad_to_next_byte(); + bool pad_to_next_byte(); +}; + +VARIANT_ENUM_CAST(DataBuffer::DataType) +VARIANT_ENUM_CAST(DataBuffer::CompressionLevel) + +#endif diff --git a/modules/network_synchronizer/interpolator.cpp b/modules/network_synchronizer/interpolator.cpp new file mode 100644 index 000000000..6c8063d20 --- /dev/null +++ b/modules/network_synchronizer/interpolator.cpp @@ -0,0 +1,415 @@ +/*************************************************************************/ +/* interpolator.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +// TODO write unit tests to make sure all cases are covered. + +#include "interpolator.h" + +#include "core/ustring.h" + +void Interpolator::_bind_methods() { + ClassDB::bind_method(D_METHOD("register_variable", "default", "fallback"), &Interpolator::register_variable); + ClassDB::bind_method(D_METHOD("set_variable_default", "var_id", "default"), &Interpolator::set_variable_default); + ClassDB::bind_method(D_METHOD("set_variable_custom_interpolator", "var_id", "object", "function_name"), &Interpolator::set_variable_custom_interpolator); + + ClassDB::bind_method(D_METHOD("epoch_insert", "var_id", "value"), &Interpolator::epoch_insert); + ClassDB::bind_method(D_METHOD("pop_epoch", "epoch"), &Interpolator::pop_epoch); + ClassDB::bind_method(D_METHOD("get_last_pop_epoch"), &Interpolator::get_last_pop_epoch); + + // TODO used to do the tests. + //ClassDB::bind_method(D_METHOD("terminate_init"), &Interpolator::terminate_init); + //ClassDB::bind_method(D_METHOD("begin_write", "epoch"), &Interpolator::begin_write); + //ClassDB::bind_method(D_METHOD("end_write"), &Interpolator::end_write); + + BIND_ENUM_CONSTANT(FALLBACK_INTERPOLATE); + BIND_ENUM_CONSTANT(FALLBACK_DEFAULT); + BIND_ENUM_CONSTANT(FALLBACK_NEW_OR_NEAREST); + BIND_ENUM_CONSTANT(FALLBACK_OLD_OR_NEAREST); +} + +void Interpolator::clear() { + epochs.clear(); + buffer.clear(); + + write_position = UINT32_MAX; +} + +void Interpolator::reset() { + variables.clear(); + epochs.clear(); + buffer.clear(); + + init_phase = true; + write_position = UINT32_MAX; + last_pop_epoch = 0; +} + +int Interpolator::register_variable(const Variant &p_default, Fallback p_fallback) { + ERR_FAIL_COND_V_MSG(init_phase == false, -1, "You cannot add another variable at this point."); + const uint32_t id = variables.size(); + variables.push_back(VariableInfo{ p_default, p_fallback, ObjectID(), StringName() }); + return id; +} + +void Interpolator::set_variable_default(int p_var_id, const Variant &p_default) { + ERR_FAIL_INDEX(p_var_id, int(variables.size())); + ERR_FAIL_COND(variables[p_var_id].default_value.get_type() != p_default.get_type()); + variables[p_var_id].default_value = p_default; +} + +void Interpolator::set_variable_custom_interpolator(int p_var_id, Object *p_object, const StringName &p_function_name) { + ERR_FAIL_COND_MSG(init_phase == false, "You cannot add another variable at this point."); + ERR_FAIL_INDEX_MSG(p_var_id, int(variables.size()), "The variable_id passed is unknown."); + variables[p_var_id].fallback = FALLBACK_CUSTOM_INTERPOLATOR; + variables[p_var_id].custom_interpolator_object = p_object->get_instance_id(); + variables[p_var_id].custom_interpolator_function = p_function_name; +} + +void Interpolator::terminate_init() { + init_phase = false; +} + +uint32_t Interpolator::known_epochs_count() const { + return epochs.size(); +} + +void Interpolator::begin_write(uint32_t p_epoch) { + ERR_FAIL_COND_MSG(write_position != UINT32_MAX, "You can't call this function twice."); + ERR_FAIL_COND_MSG(init_phase, "You cannot write data while the buffer is not fully initialized, call `terminate_init`."); + + // Make room for this epoch. + // Insert the epoch sorted in the buffer. + write_position = UINT32_MAX; + for (uint32_t i = 0; i < epochs.size(); i += 1) { + if (epochs[i] >= p_epoch) { + write_position = i; + break; + } + } + + if (write_position < UINT32_MAX) { + if (epochs[write_position] == p_epoch) { + // This epoch already exists, nothing to do. + return; + } else { + // Make room. + epochs.push_back(UINT32_MAX); + buffer.push_back(Vector()); + // Sort the epochs. + for (int i = epochs.size() - 2; i >= int(write_position); i -= 1) { + epochs[uint32_t(i) + 1] = epochs[uint32_t(i)]; + buffer[uint32_t(i) + 1] = buffer[uint32_t(i)]; + } + // Init the new epoch. + epochs[write_position] = p_epoch; + buffer[write_position].clear(); + buffer[write_position].resize(variables.size()); + } + } else { + // No sort needed. + write_position = epochs.size(); + epochs.push_back(p_epoch); + buffer.push_back(Vector()); + buffer[write_position].resize(variables.size()); + } + + // Set defaults. + Variant *ptr = buffer[write_position].ptrw(); + for (uint32_t i = 0; i < variables.size(); i += 1) { + ptr[i] = variables[i].default_value; + } +} + +void Interpolator::epoch_insert(int p_var_id, const Variant &p_value) { + ERR_FAIL_COND_MSG(write_position == UINT32_MAX, "Please call `begin_write` before."); + ERR_FAIL_INDEX_MSG(p_var_id, int(variables.size()), "The variable_id passed is unknown."); + const uint32_t var_id(p_var_id); + ERR_FAIL_COND_MSG(variables[var_id].default_value.get_type() != p_value.get_type(), "The variable: " + itos(p_var_id) + " expects the variable type: " + Variant::get_type_name(variables[var_id].default_value.get_type()) + ", and not: " + Variant::get_type_name(p_value.get_type())); + buffer[write_position].write[var_id] = p_value; +} + +void Interpolator::end_write() { + ERR_FAIL_COND_MSG(write_position == UINT32_MAX, "You can't call this function before starting the epoch with `begin_write`."); + write_position = UINT32_MAX; +} + +Vector Interpolator::pop_epoch(uint32_t p_epoch, real_t p_fraction) { + ERR_FAIL_COND_V_MSG(init_phase, Vector(), "You can't pop data if the interpolator is not fully initialized."); + ERR_FAIL_COND_V_MSG(write_position != UINT32_MAX, Vector(), "You can't pop data while writing the epoch"); + + double epoch = double(p_epoch) + double(p_fraction); + + // Search the epoch. + uint32_t position = UINT32_MAX; + for (uint32_t i = 0; i < epochs.size(); i += 1) { + if (static_cast(epochs[i]) >= epoch) { + position = i; + break; + } + } + + ObjectID cache_object_id; + Object *cache_object = nullptr; + + Vector data; + if (unlikely(position == UINT32_MAX)) { + data.resize(variables.size()); + Variant *ptr = data.ptrw(); + if (buffer.size() == 0) { + // No data found, set all to default. + for (uint32_t i = 0; i < variables.size(); i += 1) { + ptr[i] = variables[i].default_value; + } + } else { + // No new data. + for (uint32_t i = 0; i < variables.size(); i += 1) { + switch (variables[i].fallback) { + case FALLBACK_DEFAULT: + ptr[i] = variables[i].default_value; + break; + case FALLBACK_INTERPOLATE: // No way to interpolate, so just send the nearest. + case FALLBACK_NEW_OR_NEAREST: // No new data, so send the nearest. + case FALLBACK_OLD_OR_NEAREST: // Just send the oldest, as desired. + ptr[i] = buffer[buffer.size() - 1][i]; + break; + case FALLBACK_CUSTOM_INTERPOLATOR: + ptr[i] = variables[i].default_value; + + if (cache_object_id != variables[i].custom_interpolator_object) { + ERR_CONTINUE_MSG(variables[i].custom_interpolator_object.is_null(), "The variable: " + itos(i) + " has a custom interpolator, but the function is invalid."); + + Object *o = ObjectDB::get_instance(variables[i].custom_interpolator_object); + ERR_CONTINUE_MSG(o == nullptr, "The variable: " + itos(i) + " has a custom interpolator, but the function is invalid."); + + cache_object_id = variables[i].custom_interpolator_object; + cache_object = o; + } + + ptr[i] = cache_object->call( + variables[i].custom_interpolator_function, + epochs[buffer.size() - 1], + buffer[buffer.size() - 1][i], + -1, + variables[i].default_value, + 0.0); + + if (ptr[i].get_type() != variables[i].default_value.get_type()) { + ERR_PRINT("The variable: " + itos(i) + " custom interpolator [" + variables[i].custom_interpolator_function + "], returned a different variant type. Expected: " + Variant::get_type_name(variables[i].default_value.get_type()) + ", returned: " + Variant::get_type_name(ptr[i].get_type())); + ptr[i] = variables[i].default_value; + } + break; + } + } + } + } else if (unlikely(ABS(epochs[position] - epoch) <= CMP_EPSILON)) { + // Precise data. + data = buffer[position]; + } else if (unlikely(position == 0)) { + // No old data. + data.resize(variables.size()); + Variant *ptr = data.ptrw(); + for (uint32_t i = 0; i < variables.size(); i += 1) { + switch (variables[i].fallback) { + case FALLBACK_DEFAULT: + ptr[i] = variables[i].default_value; + break; + case FALLBACK_INTERPOLATE: // No way to interpolate, so just send the nearest. + case FALLBACK_NEW_OR_NEAREST: // Just send the newer data as desired. + case FALLBACK_OLD_OR_NEAREST: // No old data, so send nearest. + ptr[i] = buffer[0][i]; + break; + case FALLBACK_CUSTOM_INTERPOLATOR: + ptr[i] = variables[i].default_value; + if (cache_object_id != variables[i].custom_interpolator_object) { + ERR_CONTINUE_MSG(variables[i].custom_interpolator_object.is_null(), "The variable: " + itos(i) + " has a custom interpolator, but the function is invalid."); + + Object *o = ObjectDB::get_instance(variables[i].custom_interpolator_object); + ERR_CONTINUE_MSG(o == nullptr, "The variable: " + itos(i) + " has a custom interpolator, but the function is invalid."); + + cache_object_id = variables[i].custom_interpolator_object; + cache_object = o; + } + + ptr[i] = cache_object->call( + variables[i].custom_interpolator_function, + -1, + variables[i].default_value, + epochs[0], + buffer[0][i], + 1.0); + + if (ptr[i].get_type() != variables[i].default_value.get_type()) { + ERR_PRINT("The variable: " + itos(i) + " custom interpolator [" + variables[i].custom_interpolator_function + "], returned a different variant type. Expected: " + Variant::get_type_name(variables[i].default_value.get_type()) + ", returned: " + Variant::get_type_name(ptr[i].get_type())); + ptr[i] = variables[i].default_value; + } + break; + } + } + } else { + // Enought data to do anything needed. + data.resize(variables.size()); + Variant *ptr = data.ptrw(); + for (uint32_t i = 0; i < variables.size(); i += 1) { + switch (variables[i].fallback) { + case FALLBACK_DEFAULT: + ptr[i] = variables[i].default_value; + break; + case FALLBACK_INTERPOLATE: { + const real_t delta = (epoch - double(epochs[position - 1])) / double(epochs[position] - epochs[position - 1]); + ptr[i] = interpolate( + buffer[position - 1][i], + buffer[position][i], + delta); + } break; + case FALLBACK_NEW_OR_NEAREST: + ptr[i] = buffer[position][i]; + break; + case FALLBACK_OLD_OR_NEAREST: + ptr[i] = buffer[position - 1][i]; + break; + case FALLBACK_CUSTOM_INTERPOLATOR: { + ptr[i] = variables[i].default_value; + + if (cache_object_id != variables[i].custom_interpolator_object) { + ERR_CONTINUE_MSG(variables[i].custom_interpolator_object.is_null(), "The variable: " + itos(i) + " has a custom interpolator, but the function is invalid."); + + Object *o = ObjectDB::get_instance(variables[i].custom_interpolator_object); + ERR_CONTINUE_MSG(o == nullptr, "The variable: " + itos(i) + " has a custom interpolator, but the function is invalid."); + + cache_object_id = variables[i].custom_interpolator_object; + cache_object = o; + } + + const real_t delta = (epoch - double(epochs[position - 1])) / double(epochs[position] - epochs[position - 1]); + + ptr[i] = cache_object->call( + variables[i].custom_interpolator_function, + epochs[position - 1], + buffer[position - 1][i], + epochs[position], + buffer[position][i], + delta); + + if (ptr[i].get_type() != variables[i].default_value.get_type()) { + ERR_PRINT("The variable: " + itos(i) + " custom interpolator [" + variables[i].custom_interpolator_function + "], returned a different variant type. Expected: " + Variant::get_type_name(variables[i].default_value.get_type()) + ", returned: " + Variant::get_type_name(ptr[i].get_type())); + ptr[i] = variables[i].default_value; + } + } break; + } + } + } + + if (unlikely(position == UINT32_MAX)) { + if (buffer.size() > 1) { + // Remove all the elements but last. This happens when the p_epoch is + // bigger than the one already stored into the queue. + epochs[0] = epochs[buffer.size() - 1]; + buffer[0] = buffer[buffer.size() - 1]; + epochs.resize(1); + buffer.resize(1); + } + } else if (position >= 2) { + // TODO improve this by performing first the shifting then the resizing. + // Remove the old elements, but leave the one used to interpolate. + for (uint32_t i = 0; i < position - 1; i += 1) { + epochs.remove(0); + buffer.remove(0); + } + } + + // TODO this is no more valid since I'm using the fractional part. + last_pop_epoch = MAX(p_epoch, last_pop_epoch); + + return data; +} + +uint32_t Interpolator::get_last_pop_epoch() const { + return last_pop_epoch; +} + +uint32_t Interpolator::get_youngest_epoch() const { + if (epochs.size() <= 0) { + return UINT32_MAX; + } + return epochs[0]; +} + +uint32_t Interpolator::get_oldest_epoch() const { + if (epochs.size() <= 0) { + return UINT32_MAX; + } + return epochs[epochs.size() - 1]; +} + +uint32_t Interpolator::epochs_between_last_time_window() const { + if (epochs.size() <= 1) { + return 0; + } + + return epochs[epochs.size() - 1] - epochs[epochs.size() - 2]; +} + +Variant Interpolator::interpolate(const Variant &p_v1, const Variant &p_v2, real_t p_delta) { + ERR_FAIL_COND_V(p_v1.get_type() != p_v2.get_type(), p_v1); + + switch (p_v1.get_type()) { + case Variant::Type::INT: + return int(Math::round(Math::lerp(p_v1.operator real_t(), p_v2.operator real_t(), p_delta))); + case Variant::Type::FLOAT: + return Math::lerp(p_v1, p_v2, p_delta); + case Variant::Type::VECTOR2: + return p_v1.operator Vector2().lerp(p_v2.operator Vector2(), p_delta); + case Variant::Type::VECTOR2I: + return Vector2i( + int(Math::round(Math::lerp(p_v1.operator Vector2i()[0], p_v2.operator Vector2i()[0], p_delta))), + int(Math::round(Math::lerp(p_v1.operator Vector2i()[1], p_v2.operator Vector2i()[1], p_delta)))); + case Variant::Type::TRANSFORM2D: + return p_v1.operator Transform2D().interpolate_with(p_v2.operator Transform2D(), p_delta); + case Variant::Type::VECTOR3: + return p_v1.operator Vector3().lerp(p_v2.operator Vector3(), p_delta); + case Variant::Type::VECTOR3I: + return Vector3i( + int(Math::round(Math::lerp(p_v1.operator Vector3i()[0], p_v2.operator Vector3i()[0], p_delta))), + int(Math::round(Math::lerp(p_v1.operator Vector3i()[1], p_v2.operator Vector3i()[1], p_delta))), + int(Math::round(Math::lerp(p_v1.operator Vector3i()[2], p_v2.operator Vector3i()[2], p_delta)))); + case Variant::Type::QUATERNION: + return p_v1.operator Quaternion().slerp(p_v2.operator Quaternion(), p_delta); + case Variant::Type::BASIS: + return p_v1.operator Basis().slerp(p_v2.operator Basis(), p_delta); + case Variant::Type::TRANSFORM3D: + return p_v1.operator Transform3D().interpolate_with(p_v2.operator Transform3D(), p_delta); + default: + return p_delta > 0.5 ? p_v2 : p_v1; + } +} diff --git a/modules/network_synchronizer/interpolator.h b/modules/network_synchronizer/interpolator.h new file mode 100644 index 000000000..306543208 --- /dev/null +++ b/modules/network_synchronizer/interpolator.h @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* interpolator.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#ifndef INTERPOLATOR_H +#define INTERPOLATOR_H + +#include "core/class_db.h" +#include "core/vector.h" + +class Interpolator : public Object { + GDCLASS(Interpolator, Object); + +public: + enum Fallback { + FALLBACK_INTERPOLATE, + FALLBACK_DEFAULT, + FALLBACK_OLD_OR_NEAREST, + FALLBACK_NEW_OR_NEAREST, + FALLBACK_CUSTOM_INTERPOLATOR + }; + +private: + struct VariableInfo { + // TODO Do we need a name? + Variant default_value; + Fallback fallback; + ObjectID custom_interpolator_object; + StringName custom_interpolator_function; + }; + + LocalVector variables; + + /// Epoch ids, sorted from youngest to oldest. + LocalVector epochs; + /// Epoch data. + LocalVector> buffer; + + bool init_phase = true; + uint32_t write_position = UINT32_MAX; + uint32_t last_pop_epoch = 0; + + static void _bind_methods(); + +public: + Interpolator() = default; + + void clear(); + void reset(); + + int register_variable(const Variant &p_default, Fallback p_fallback); + void set_variable_default(int p_var_id, const Variant &p_default); + void set_variable_custom_interpolator(int p_var_id, Object *p_object, const StringName &p_function_name); + void terminate_init(); + + /// Returns the epochs stored. + uint32_t known_epochs_count() const; + void begin_write(uint32_t p_epoch); + void epoch_insert(int p_var_id, const Variant &p_value); + void end_write(); + + Vector pop_epoch(uint32_t p_epoch, real_t p_fraction); + uint32_t get_last_pop_epoch() const; // TODO do I need this? Remove if not. + uint32_t get_youngest_epoch() const; + uint32_t get_oldest_epoch() const; + + /// Returns the epochs count between the two last received time window. + uint32_t epochs_between_last_time_window() const; + + static Variant interpolate(const Variant &p_v1, const Variant &p_v2, real_t p_delta); +}; + +VARIANT_ENUM_CAST(Interpolator::Fallback); +#endif diff --git a/modules/network_synchronizer/net_utilities.cpp b/modules/network_synchronizer/net_utilities.cpp new file mode 100644 index 000000000..b2459c69b --- /dev/null +++ b/modules/network_synchronizer/net_utilities.cpp @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* net_utilities.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "net_utilities.h" + +#include "scene/main/node.h" + +bool NetUtility::ChangeListener::operator==(const ChangeListener &p_other) const { + return object_id == p_other.object_id && method == p_other.method; +} + +NetUtility::VarData::VarData(const StringName &p_name) { + var.name = p_name; +} + +NetUtility::VarData::VarData(NetVarId p_id, const StringName &p_name, const Variant &p_val, bool p_skip_rewinding, bool p_enabled) : + id(p_id), + skip_rewinding(p_skip_rewinding), + enabled(p_enabled) { + var.name = p_name; + var.value = p_val.duplicate(true); +} + +bool NetUtility::VarData::operator==(const NetUtility::VarData &p_other) const { + return var.name == p_other.var.name; +} + +bool NetUtility::VarData::operator<(const VarData &p_other) const { + return id < p_other.id; +} + +void NetUtility::NodeData::process(const real_t p_delta) const { + const Variant var_delta = p_delta; + const Variant *fake_array_vars = &var_delta; + + Callable::CallError e; + for (uint32_t i = 0; i < functions.size(); i += 1) { + node->call(functions[i], &fake_array_vars, 1, e); + } +} + +NetUtility::Snapshot::operator String() const { + String s; + s += "Snapshot input ID: " + itos(input_id); + + for (int net_node_id = 0; net_node_id < node_vars.size(); net_node_id += 1) { + s += "\nNode Data: " + itos(net_node_id); + for (int i = 0; i < node_vars[net_node_id].size(); i += 1) { + s += "\n|- Variable: "; + s += node_vars[net_node_id][i].name; + s += " = "; + s += String(node_vars[net_node_id][i].value); + } + } + return s; +} + +bool NetUtility::NodeChangeListener::operator==(const NodeChangeListener &p_other) const { + return node_data == p_other.node_data && var_id == p_other.var_id; +} diff --git a/modules/network_synchronizer/net_utilities.h b/modules/network_synchronizer/net_utilities.h new file mode 100644 index 000000000..e59426e0c --- /dev/null +++ b/modules/network_synchronizer/net_utilities.h @@ -0,0 +1,342 @@ +/*************************************************************************/ +/* net_utilities.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#ifndef NET_UTILITIES_H +#define NET_UTILITIES_H + +#include "core/math/math_funcs.h" +#include "core/templates/local_vector.h" +#include "core/variant/variant.h" + +#ifdef DEBUG_ENABLED +#define NET_DEBUG_PRINT(msg) \ + print_line(String("[Net] ") + msg) +#define NET_DEBUG_WARN(msg) \ + WARN_PRINT(String("[Net] ") + msg) +#define NET_DEBUG_ERR(msg) \ + ERR_PRINT(String("[Net] ") + msg) +#else +#define NET_DEBUG_PRINT(msg) +#define NET_DEBUG_WARN(msg) +#define NET_DEBUG_ERR(msg) +#endif + +typedef uint32_t NetNodeId; +typedef uint32_t NetVarId; + +/// Flags used to control when an event is executed. +enum NetEventFlag { + + // ~~ Flags ~~ // + EMPTY = 0, + + /// Called at the end of the frame, if the value is different. + /// It's also called when a variable is modified by the + /// `apply_scene_changes` function. + CHANGE = 1 << 0, + + /// Called when the variable is modified by the `NetworkSynchronizer` + /// because not in sync with the server. + SYNC_RECOVER = 1 << 1, + + /// Called when the variable is modified by the `NetworkSynchronizer` + /// because it's preparing the node for the rewinding. + SYNC_RESET = 1 << 2, + + /// Called when the variable is modified during the rewinding phase. + SYNC_REWIND = 1 << 3, + + /// Called at the end of the recovering phase, if the value was modified + /// during the rewinding. + END_SYNC = 1 << 4, + + // ~~ Preconfigured ~~ // + + DEFAULT = CHANGE | END_SYNC, + SYNC = SYNC_RECOVER | SYNC_RESET | SYNC_REWIND, + ALWAYS = CHANGE | SYNC_RECOVER | SYNC_RESET | SYNC_REWIND | END_SYNC +}; + +namespace NetUtility { + +template +class StatisticalRingBuffer { + LocalVector data; + uint32_t index = 0; + + T avg_sum = 0; + +public: + StatisticalRingBuffer(uint32_t p_size, T p_default); + void resize(uint32_t p_size, T p_default); + void reset(T p_default); + + void push(T p_value); + + /// Maximum value. + T max() const; + + /// Minumum value. + T min(uint32_t p_consider_last) const; + + /// Median value. + T average() const; + + T get_deviation(T p_mean) const; + +private: + // Used to avoid accumulate precision loss. + void force_recompute_avg_sum(); +}; + +template +StatisticalRingBuffer::StatisticalRingBuffer(uint32_t p_size, T p_default) { + resize(p_size, p_default); +} + +template +void StatisticalRingBuffer::resize(uint32_t p_size, T p_default) { + data.resize(p_size); + + reset(p_default); +} + +template +void StatisticalRingBuffer::reset(T p_default) { + for (uint32_t i = 0; i < data.size(); i += 1) { + data[i] = p_default; + } + + index = 0; + force_recompute_avg_sum(); +} + +template +void StatisticalRingBuffer::push(T p_value) { + avg_sum -= data[index]; + avg_sum += p_value; + data[index] = p_value; + + index = (index + 1) % data.size(); + if (index == 0) { + // Each cycle recompute the sum. + force_recompute_avg_sum(); + } +} + +template +T StatisticalRingBuffer::max() const { + CRASH_COND(data.size() == 0); + + T a = data[0]; + for (uint32_t i = 1; i < data.size(); i += 1) { + a = MAX(a, data[i]); + } + return a; +} + +template +T StatisticalRingBuffer::min(uint32_t p_consider_last) const { + CRASH_COND(data.size() == 0); + p_consider_last = MIN(p_consider_last, data.size()); + + const uint32_t youngest = (index == 0 ? data.size() : index) - 1; + const uint32_t oldest = (index + (data.size() - p_consider_last)) % data.size(); + + T a = data[oldest]; + + uint32_t i = oldest; + do { + i = (i + 1) % data.size(); + a = MIN(a, data[i]); + } while (i != youngest); + + return a; +} + +template +T StatisticalRingBuffer::average() const { + CRASH_COND(data.size() == 0); + +#ifdef DEBUG_ENABLED + T a = data[0]; + for (uint32_t i = 1; i < data.size(); i += 1) { + a += data[i]; + } + a = a / T(data.size()); + T b = avg_sum / T(data.size()); + const T difference = a > b ? a - b : b - a; + ERR_FAIL_COND_V_MSG(difference > (CMP_EPSILON * 4.0), b, "The `avg_sum` accumulated a sensible precision loss: " + rtos(difference)); + return b; +#else + // Divide it by the buffer size is wrong when the buffer is not yet fully + // initialized. However, this is wrong just for the first run. + // I'm leaving it as is because solve it mean do more operations. All this + // just to get the right value for the first few frames. + return avg_sum / T(data.size()); +#endif +} + +template +T StatisticalRingBuffer::get_deviation(T p_mean) const { + if (data.size() <= 0) { + return T(); + } + + double r = 0; + for (uint32_t i = 0; i < data.size(); i += 1) { + r += Math::pow(double(data[i]) - double(p_mean), 2.0); + } + + return Math::sqrt(r / double(data.size())); +} + +template +void StatisticalRingBuffer::force_recompute_avg_sum() { +#ifdef DEBUG_ENABLED + // This class is not supposed to be used with 0 size. + CRASH_COND(data.size() <= 0); +#endif + avg_sum = data[0]; + for (uint32_t i = 1; i < data.size(); i += 1) { + avg_sum += data[i]; + } +} + +/// Specific node listener. Alone this doesn't do much, but allows the +/// `ChangeListener` to know and keep track of the node events. +struct NodeChangeListener { + struct NodeData *node_data = nullptr; + NetVarId var_id = UINT32_MAX; + + bool old_set = false; + Variant old_value; + + bool operator==(const NodeChangeListener &p_other) const; +}; + +/// Change listener that rapresents a pair of Object and Method. +/// This can track the changes of many nodes and variables. It's dispatched +/// if one or more tracked variable change during the tracked phase, specified +/// by the flag. +struct ChangeListener { + // TODO use a callable instead?? + ObjectID object_id = ObjectID(); + StringName method; + uint32_t method_argument_count; + NetEventFlag flag; + + LocalVector watching_vars; + LocalVector old_values; + bool emitted = true; + + bool operator==(const ChangeListener &p_other) const; +}; + +struct Var { + StringName name; + Variant value; +}; + +struct VarData { + NetVarId id = UINT32_MAX; + Var var; + bool skip_rewinding = false; + bool enabled = false; + Vector change_listeners; + + VarData() = default; + VarData(const StringName &p_name); + VarData(NetVarId p_id, const StringName &p_name, const Variant &p_val, bool p_skip_rewinding, bool p_enabled); + + bool operator==(const VarData &p_other) const; + bool operator<(const VarData &p_other) const; +}; + +struct NodeData { + // ID used to reference this Node in the networked calls. + uint32_t id = 0; + ObjectID instance_id = ObjectID(); + NodeData *controlled_by = nullptr; + + /// When `false`, this node is not sync. It's usefult to locally pause sync + /// of specific nodes. + bool sync_enabled = true; + + bool is_controller = false; + LocalVector controlled_nodes; + LocalVector dependency_nodes; + LocalVector dependency_nodes_end; + + /// The sync variables of this node. The order of this vector matters + /// because the index is the `NetVarId`. + LocalVector vars; + LocalVector functions; + + // This is valid to use only inside the process function. + Node *node = nullptr; + + NodeData() = default; + + void process(const real_t p_delta) const; +}; + +struct PeerData { + NetNodeId controller_id = UINT32_MAX; + // For new peers notify the state as soon as possible. + bool force_notify_snapshot = true; + // For new peers a full snapshot is needed. + bool need_full_snapshot = true; + // Used to know if the peer is enabled. + bool enabled = true; +}; + +struct Snapshot { + uint32_t input_id; + /// The Node variables in a particular frame. The order of this vector + /// matters because the index is the `NetNodeId`. + /// The variable array order also matter. + Vector> node_vars; + + operator String() const; +}; + +struct PostponedRecover { + NodeData *node_data = nullptr; + Vector vars; +}; + +} // namespace NetUtility + +#endif diff --git a/modules/network_synchronizer/networked_controller.cpp b/modules/network_synchronizer/networked_controller.cpp new file mode 100644 index 000000000..6c477696c --- /dev/null +++ b/modules/network_synchronizer/networked_controller.cpp @@ -0,0 +1,1571 @@ +/*************************************************************************/ +/* networked_controller.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "networked_controller.h" + +#include "core/config/engine.h" +#include "core/io/marshalls.h" +#include "scene_synchronizer.h" +#include + +#define METADATA_SIZE 1 + +#define MAX_ADDITIONAL_TICK_SPEED 2.0 + +// 2% +#define TICK_SPEED_CHANGE_NOTIF_THRESHOLD 4 + +void NetworkedController::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_player_input_storage_size", "size"), &NetworkedController::set_player_input_storage_size); + ClassDB::bind_method(D_METHOD("get_player_input_storage_size"), &NetworkedController::get_player_input_storage_size); + + ClassDB::bind_method(D_METHOD("set_max_redundant_inputs", "max_redundant_inputs"), &NetworkedController::set_max_redundant_inputs); + ClassDB::bind_method(D_METHOD("get_max_redundant_inputs"), &NetworkedController::get_max_redundant_inputs); + + ClassDB::bind_method(D_METHOD("set_tick_speedup_notification_delay", "tick_speedup_notification_delay"), &NetworkedController::set_tick_speedup_notification_delay); + ClassDB::bind_method(D_METHOD("get_tick_speedup_notification_delay"), &NetworkedController::get_tick_speedup_notification_delay); + + ClassDB::bind_method(D_METHOD("set_network_traced_frames", "size"), &NetworkedController::set_network_traced_frames); + ClassDB::bind_method(D_METHOD("get_network_traced_frames"), &NetworkedController::get_network_traced_frames); + + ClassDB::bind_method(D_METHOD("set_min_frames_delay", "val"), &NetworkedController::set_min_frames_delay); + ClassDB::bind_method(D_METHOD("get_min_frames_delay"), &NetworkedController::get_min_frames_delay); + + ClassDB::bind_method(D_METHOD("set_max_frames_delay", "val"), &NetworkedController::set_max_frames_delay); + ClassDB::bind_method(D_METHOD("get_max_frames_delay"), &NetworkedController::get_max_frames_delay); + + ClassDB::bind_method(D_METHOD("set_net_sensitivity", "val"), &NetworkedController::set_net_sensitivity); + ClassDB::bind_method(D_METHOD("get_net_sensitivity"), &NetworkedController::get_net_sensitivity); + + ClassDB::bind_method(D_METHOD("set_tick_acceleration", "acceleration"), &NetworkedController::set_tick_acceleration); + ClassDB::bind_method(D_METHOD("get_tick_acceleration"), &NetworkedController::get_tick_acceleration); + + ClassDB::bind_method(D_METHOD("set_doll_epoch_collect_rate", "rate"), &NetworkedController::set_doll_epoch_collect_rate); + ClassDB::bind_method(D_METHOD("get_doll_epoch_collect_rate"), &NetworkedController::get_doll_epoch_collect_rate); + + ClassDB::bind_method(D_METHOD("set_doll_epoch_batch_sync_rate", "rate"), &NetworkedController::set_doll_epoch_batch_sync_rate); + ClassDB::bind_method(D_METHOD("get_doll_epoch_batch_sync_rate"), &NetworkedController::get_doll_epoch_batch_sync_rate); + + ClassDB::bind_method(D_METHOD("set_doll_min_frames_delay", "traced"), &NetworkedController::set_doll_min_frames_delay); + ClassDB::bind_method(D_METHOD("get_doll_min_frames_delay"), &NetworkedController::get_doll_min_frames_delay); + + ClassDB::bind_method(D_METHOD("set_doll_max_frames_delay", "sensitivity"), &NetworkedController::set_doll_max_frames_delay); + ClassDB::bind_method(D_METHOD("get_doll_max_frames_delay"), &NetworkedController::get_doll_max_frames_delay); + + ClassDB::bind_method(D_METHOD("set_doll_interpolation_max_speedup", "speedup"), &NetworkedController::set_doll_interpolation_max_speedup); + ClassDB::bind_method(D_METHOD("get_doll_interpolation_max_speedup"), &NetworkedController::get_doll_interpolation_max_speedup); + + ClassDB::bind_method(D_METHOD("set_doll_connection_stats_frame_span", "speedup"), &NetworkedController::set_doll_connection_stats_frame_span); + ClassDB::bind_method(D_METHOD("get_doll_connection_stats_frame_span"), &NetworkedController::get_doll_connection_stats_frame_span); + + ClassDB::bind_method(D_METHOD("set_doll_net_sensitivity", "sensitivity"), &NetworkedController::set_doll_net_sensitivity); + ClassDB::bind_method(D_METHOD("get_doll_net_sensitivity"), &NetworkedController::get_doll_net_sensitivity); + + ClassDB::bind_method(D_METHOD("set_doll_max_delay", "max_delay"), &NetworkedController::set_doll_max_delay); + ClassDB::bind_method(D_METHOD("get_doll_max_delay"), &NetworkedController::get_doll_max_delay); + + ClassDB::bind_method(D_METHOD("get_current_input_id"), &NetworkedController::get_current_input_id); + + ClassDB::bind_method(D_METHOD("player_get_pretended_delta", "iterations_per_seconds"), &NetworkedController::player_get_pretended_delta); + + ClassDB::bind_method(D_METHOD("mark_epoch_as_important"), &NetworkedController::mark_epoch_as_important); + + ClassDB::bind_method(D_METHOD("set_doll_collect_rate_factor", "peer", "factor"), &NetworkedController::set_doll_collect_rate_factor); + + ClassDB::bind_method(D_METHOD("set_doll_peer_active", "peer_id", "active"), &NetworkedController::set_doll_peer_active); + + ClassDB::bind_method(D_METHOD("_rpc_server_send_inputs"), &NetworkedController::_rpc_server_send_inputs); + ClassDB::bind_method(D_METHOD("_rpc_send_tick_additional_speed"), &NetworkedController::_rpc_send_tick_additional_speed); + ClassDB::bind_method(D_METHOD("_rpc_doll_notify_sync_pause"), &NetworkedController::_rpc_doll_notify_sync_pause); + ClassDB::bind_method(D_METHOD("_rpc_doll_send_epoch_batch"), &NetworkedController::_rpc_doll_send_epoch_batch); + + ClassDB::bind_method(D_METHOD("is_server_controller"), &NetworkedController::is_server_controller); + ClassDB::bind_method(D_METHOD("is_player_controller"), &NetworkedController::is_player_controller); + ClassDB::bind_method(D_METHOD("is_doll_controller"), &NetworkedController::is_doll_controller); + ClassDB::bind_method(D_METHOD("is_nonet_controller"), &NetworkedController::is_nonet_controller); + + ClassDB::bind_method(D_METHOD("__on_sync_paused"), &NetworkedController::__on_sync_paused); + + BIND_VMETHOD(MethodInfo("_collect_inputs", PropertyInfo(Variant::FLOAT, "delta"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo("_controller_process", PropertyInfo(Variant::FLOAT, "delta"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo(Variant::BOOL, "_are_inputs_different", PropertyInfo(Variant::OBJECT, "inputs_A", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"), PropertyInfo(Variant::OBJECT, "inputs_B", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo(Variant::INT, "_count_input_size", PropertyInfo(Variant::OBJECT, "inputs", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo("_collect_epoch_data", PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo("_setup_interpolator", PropertyInfo(Variant::OBJECT, "interpolator", PROPERTY_HINT_RESOURCE_TYPE, "Interpolator"))); + BIND_VMETHOD(MethodInfo("_parse_epoch_data", PropertyInfo(Variant::OBJECT, "interpolator", PROPERTY_HINT_RESOURCE_TYPE, "Interpolator"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo("_apply_epoch", PropertyInfo(Variant::FLOAT, "delta"), PropertyInfo(Variant::ARRAY, "interpolated_data"))); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "input_storage_size", PROPERTY_HINT_RANGE, "5,2000,1"), "set_player_input_storage_size", "get_player_input_storage_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redundant_inputs", PROPERTY_HINT_RANGE, "0,1000,1"), "set_max_redundant_inputs", "get_max_redundant_inputs"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tick_speedup_notification_delay", PROPERTY_HINT_RANGE, "0.001,2.0,0.001"), "set_tick_speedup_notification_delay", "get_tick_speedup_notification_delay"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "network_traced_frames", PROPERTY_HINT_RANGE, "1,1000,1"), "set_network_traced_frames", "get_network_traced_frames"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "min_frames_delay", PROPERTY_HINT_RANGE, "0,100,1"), "set_min_frames_delay", "get_min_frames_delay"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_frames_delay", PROPERTY_HINT_RANGE, "0,100,1"), "set_max_frames_delay", "get_max_frames_delay"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "net_sensitivity", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_net_sensitivity", "get_net_sensitivity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tick_acceleration", PROPERTY_HINT_RANGE, "0.1,20.0,0.01"), "set_tick_acceleration", "get_tick_acceleration"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_epoch_collect_rate", PROPERTY_HINT_RANGE, "1,100,1"), "set_doll_epoch_collect_rate", "get_doll_epoch_collect_rate"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "doll_epoch_batch_sync_rate", PROPERTY_HINT_RANGE, "0.01,5.0,0.01"), "set_doll_epoch_batch_sync_rate", "get_doll_epoch_batch_sync_rate"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_min_frames_delay", PROPERTY_HINT_RANGE, "0,10,1"), "set_doll_min_frames_delay", "get_doll_min_frames_delay"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_max_frames_delay", PROPERTY_HINT_RANGE, "0,10,1"), "set_doll_max_frames_delay", "get_doll_max_frames_delay"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "doll_interpolation_max_speedup", PROPERTY_HINT_RANGE, "0.01,5.0,0.01"), "set_doll_interpolation_max_speedup", "get_doll_interpolation_max_speedup"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_connection_stats_frame_span", PROPERTY_HINT_RANGE, "0,1000,1"), "set_doll_connection_stats_frame_span", "get_doll_connection_stats_frame_span"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "doll_net_sensitivity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_doll_net_sensitivity", "get_doll_net_sensitivity"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_max_delay", PROPERTY_HINT_RANGE, "1,1000,1"), "set_doll_max_delay", "get_doll_max_delay"); + + ADD_SIGNAL(MethodInfo("doll_sync_started")); + ADD_SIGNAL(MethodInfo("doll_sync_paused")); +} + +NetworkedController::NetworkedController() { + rpc_config(SNAME("_rpc_server_send_inputs"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); + rpc_config(SNAME("_rpc_send_tick_additional_speed"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); + rpc_config(SNAME("_rpc_doll_notify_sync_pause"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_RELIABLE); + rpc_config(SNAME("_rpc_doll_send_epoch_batch"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); +} + +void NetworkedController::set_player_input_storage_size(int p_size) { + player_input_storage_size = p_size; +} + +int NetworkedController::get_player_input_storage_size() const { + return player_input_storage_size; +} + +void NetworkedController::set_max_redundant_inputs(int p_max) { + max_redundant_inputs = p_max; +} + +int NetworkedController::get_max_redundant_inputs() const { + return max_redundant_inputs; +} + +void NetworkedController::set_tick_speedup_notification_delay(real_t p_delay) { + tick_speedup_notification_delay = p_delay; +} + +real_t NetworkedController::get_tick_speedup_notification_delay() const { + return tick_speedup_notification_delay; +} + +void NetworkedController::set_network_traced_frames(int p_size) { + network_traced_frames = p_size; +} + +int NetworkedController::get_network_traced_frames() const { + return network_traced_frames; +} + +void NetworkedController::set_min_frames_delay(int p_val) { + min_frames_delay = p_val; +} + +int NetworkedController::get_min_frames_delay() const { + return min_frames_delay; +} + +void NetworkedController::set_max_frames_delay(int p_val) { + max_frames_delay = p_val; +} + +int NetworkedController::get_max_frames_delay() const { + return max_frames_delay; +} + +void NetworkedController::set_net_sensitivity(real_t p_val) { + net_sensitivity = p_val; +} + +real_t NetworkedController::get_net_sensitivity() const { + return net_sensitivity; +} + +void NetworkedController::set_tick_acceleration(real_t p_acceleration) { + tick_acceleration = p_acceleration; +} + +real_t NetworkedController::get_tick_acceleration() const { + return tick_acceleration; +} + +void NetworkedController::set_doll_epoch_collect_rate(int p_rate) { + doll_epoch_collect_rate = MAX(p_rate, 1); +} + +int NetworkedController::get_doll_epoch_collect_rate() const { + return doll_epoch_collect_rate; +} + +void NetworkedController::set_doll_epoch_batch_sync_rate(real_t p_rate) { + doll_epoch_batch_sync_rate = MAX(p_rate, 0.001); +} + +real_t NetworkedController::get_doll_epoch_batch_sync_rate() const { + return doll_epoch_batch_sync_rate; +} + +void NetworkedController::set_doll_min_frames_delay(int p_min) { + doll_min_frames_delay = p_min; +} + +int NetworkedController::get_doll_min_frames_delay() const { + return doll_min_frames_delay; +} + +void NetworkedController::set_doll_max_frames_delay(int p_max) { + doll_max_frames_delay = p_max; +} + +int NetworkedController::get_doll_max_frames_delay() const { + return doll_max_frames_delay; +} + +void NetworkedController::set_doll_interpolation_max_speedup(real_t p_speedup) { + doll_interpolation_max_speedup = p_speedup; +} + +real_t NetworkedController::get_doll_interpolation_max_speedup() const { + return doll_interpolation_max_speedup; +} + +void NetworkedController::set_doll_connection_stats_frame_span(int p_span) { + doll_connection_stats_frame_span = p_span; +} + +int NetworkedController::get_doll_connection_stats_frame_span() const { + return doll_connection_stats_frame_span; +} + +void NetworkedController::set_doll_net_sensitivity(real_t p_sensitivity) { + doll_net_sensitivity = p_sensitivity; +} + +real_t NetworkedController::get_doll_net_sensitivity() const { + return doll_net_sensitivity; +} + +void NetworkedController::set_doll_max_delay(uint32_t p_max_delay) { + doll_max_delay = p_max_delay; +} + +uint32_t NetworkedController::get_doll_max_delay() const { + return doll_max_delay; +} + +uint32_t NetworkedController::get_current_input_id() const { + ERR_FAIL_NULL_V(controller, 0); + return controller->get_current_input_id(); +} + +real_t NetworkedController::player_get_pretended_delta(uint32_t p_iterations_per_seconds) const { + ERR_FAIL_COND_V_MSG(is_player_controller() == false, 1.0 / real_t(p_iterations_per_seconds), "This function can be called only on client."); + return get_player_controller()->get_pretended_delta(p_iterations_per_seconds); +} + +void NetworkedController::mark_epoch_as_important() { + ERR_FAIL_COND_MSG(is_server_controller() == false, "This function must be called only within the function `collect_epoch_data`."); + get_server_controller()->is_epoch_important = true; +} + +void NetworkedController::set_doll_collect_rate_factor(int p_peer, real_t p_factor) { + ERR_FAIL_COND_MSG(is_server_controller() == false, "This function can be called only on server."); + ServerController *server_controller = static_cast(controller); + const uint32_t pos = server_controller->find_peer(p_peer); + if (pos == UINT32_MAX) { + // This peers seems disabled, nothing to do here. + return; + } + server_controller->peers[pos].update_rate_factor = CLAMP(p_factor, 0.001, 1.0); +} + +void NetworkedController::set_doll_peer_active(int p_peer_id, bool p_active) { + ERR_FAIL_COND_MSG(is_server_controller() == false, "You can set doll activation only on server"); + ERR_FAIL_COND_MSG(p_peer_id == get_network_master(), "This `peer_id` is equal to the Master `peer_id`, which is not allowed."); + + ServerController *server_controller = static_cast(controller); + const uint32_t pos = server_controller->find_peer(p_peer_id); + if (pos == UINT32_MAX) { + // This peers seems disabled, nothing to do here. + return; + } + if (server_controller->peers[pos].active == p_active) { + // Nothing to do. + return; + } + + server_controller->peers[pos].active = p_active; + server_controller->peers[pos].collect_timer = 0.0; + + if (p_active == false) { + // Notify the doll only for deactivations. The activations are automatically + // handled when the first epoch is received. + rpc_id(p_peer_id, SNAME("_rpc_doll_notify_sync_pause"), server_controller->epoch); + } +} + +void NetworkedController::pause_notify_dolls() { + ERR_FAIL_COND_MSG(is_server_controller() == false, "You can pause the dolls only on server. [BUG]"); + + // Notify the dolls this actor is disabled. + ServerController *server_controller = static_cast(controller); + for (uint32_t i = 0; i < server_controller->peers.size(); i += 1) { + if (server_controller->peers[i].active) { + // Notify this actor is no more active. + rpc_id(server_controller->peers[i].peer, SNAME("_rpc_doll_notify_sync_pause"), server_controller->epoch); + } + } +} + +bool NetworkedController::process_instant(int p_i, real_t p_delta) { + ERR_FAIL_COND_V_MSG(is_player_controller() == false, false, "Can be executed only on player controllers."); + return static_cast(controller)->process_instant(p_i, p_delta); +} + +ServerController *NetworkedController::get_server_controller() { + ERR_FAIL_COND_V_MSG(is_server_controller() == false, nullptr, "This controller is not a server controller."); + return static_cast(controller); +} + +const ServerController *NetworkedController::get_server_controller() const { + ERR_FAIL_COND_V_MSG(is_server_controller() == false, nullptr, "This controller is not a server controller."); + return static_cast(controller); +} + +PlayerController *NetworkedController::get_player_controller() { + ERR_FAIL_COND_V_MSG(is_player_controller() == false, nullptr, "This controller is not a player controller."); + return static_cast(controller); +} + +const PlayerController *NetworkedController::get_player_controller() const { + ERR_FAIL_COND_V_MSG(is_player_controller() == false, nullptr, "This controller is not a player controller."); + return static_cast(controller); +} + +DollController *NetworkedController::get_doll_controller() { + ERR_FAIL_COND_V_MSG(is_doll_controller() == false, nullptr, "This controller is not a doll controller."); + return static_cast(controller); +} + +const DollController *NetworkedController::get_doll_controller() const { + ERR_FAIL_COND_V_MSG(is_doll_controller() == false, nullptr, "This controller is not a doll controller."); + return static_cast(controller); +} + +NoNetController *NetworkedController::get_nonet_controller() { + ERR_FAIL_COND_V_MSG(is_nonet_controller() == false, nullptr, "This controller is not a no net controller."); + return static_cast(controller); +} + +const NoNetController *NetworkedController::get_nonet_controller() const { + ERR_FAIL_COND_V_MSG(is_nonet_controller() == false, nullptr, "This controller is not a no net controller."); + return static_cast(controller); +} + +bool NetworkedController::is_server_controller() const { + return controller_type == CONTROLLER_TYPE_SERVER; +} + +bool NetworkedController::is_player_controller() const { + return controller_type == CONTROLLER_TYPE_PLAYER; +} + +bool NetworkedController::is_doll_controller() const { + return controller_type == CONTROLLER_TYPE_DOLL; +} + +bool NetworkedController::is_nonet_controller() const { + return controller_type == CONTROLLER_TYPE_NONETWORK; +} + +void NetworkedController::set_inputs_buffer(const BitArray &p_new_buffer, uint32_t p_metadata_size_in_bit, uint32_t p_size_in_bit) { + inputs_buffer.get_buffer_mut().get_bytes_mut() = p_new_buffer.get_bytes(); + inputs_buffer.shrink_to(p_metadata_size_in_bit, p_size_in_bit); +} + +void NetworkedController::set_scene_synchronizer(SceneSynchronizer *p_synchronizer) { + if (scene_synchronizer) { + scene_synchronizer->disconnect(SNAME("sync_paused"), Callable(this, SNAME("__on_sync_paused"))); + } + + scene_synchronizer = p_synchronizer; + + if (scene_synchronizer) { + scene_synchronizer->connect(SNAME("sync_paused"), Callable(this, SNAME("__on_sync_paused"))); + } +} + +SceneSynchronizer *NetworkedController::get_scene_synchronizer() const { + return scene_synchronizer; +} + +bool NetworkedController::has_scene_synchronizer() const { + return scene_synchronizer; +} + +void NetworkedController::_rpc_server_send_inputs(const Vector &p_data) { + ERR_FAIL_COND(is_server_controller() == false); + static_cast(controller)->receive_inputs(p_data); +} + +void NetworkedController::_rpc_send_tick_additional_speed(const Vector &p_data) { + ERR_FAIL_COND(is_player_controller() == false); + ERR_FAIL_COND(p_data.size() != 1); + + const uint8_t speed = p_data[0]; + const real_t additional_speed = MAX_ADDITIONAL_TICK_SPEED * (((static_cast(speed) / static_cast(UINT8_MAX)) - 0.5) / 0.5); + + PlayerController *player_controller = static_cast(controller); + player_controller->tick_additional_speed = CLAMP(additional_speed, -MAX_ADDITIONAL_TICK_SPEED, MAX_ADDITIONAL_TICK_SPEED); +} + +void NetworkedController::_rpc_doll_notify_sync_pause(uint32_t p_epoch) { + ERR_FAIL_COND_MSG(is_doll_controller() == false, "Only dolls are supposed to receive this function call"); + + static_cast(controller)->pause(p_epoch); +} + +void NetworkedController::_rpc_doll_send_epoch_batch(const Vector &p_data) { + ERR_FAIL_COND_MSG(is_doll_controller() == false, "Only dolls are supposed to receive this function call."); + ERR_FAIL_COND_MSG(p_data.size() <= 0, "It's not supposed to receive a 0 size data."); + + static_cast(controller)->receive_batch(p_data); +} + +void NetworkedController::player_set_has_new_input(bool p_has) { + has_player_new_input = p_has; +} + +bool NetworkedController::player_has_new_input() const { + return has_player_new_input; +} + +void NetworkedController::__on_sync_paused() { + if (controller_type == CONTROLLER_TYPE_DOLL) { + DollController *doll = static_cast(controller); + doll->pause(doll->current_epoch); + } +} + +void NetworkedController::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + +#ifdef DEBUG_ENABLED + // This can't happen, since only the doll are processed here. + CRASH_COND(is_doll_controller() == false); +#endif + static_cast(controller)->process(get_physics_process_delta_time()); + + } break; +#ifdef DEBUG_ENABLED + case NOTIFICATION_READY: { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + ERR_FAIL_COND_MSG(has_method("_collect_inputs") == false, "In your script you must inherit the virtual method `_collect_inputs` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_controller_process") == false, "In your script you must inherit the virtual method `_controller_process` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_are_inputs_different") == false, "In your script you must inherit the virtual method `_are_inputs_different` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_count_input_size") == false, "In your script you must inherit the virtual method `_count_input_size` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_collect_epoch_data") == false, "In your script you must inherit the virtual method `_collect_epoch_data` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_setup_interpolator") == false, "In your script you must inherit the virtual method `_setup_interpolator` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_parse_epoch_data") == false, "In your script you must inherit the virtual method `_parse_epoch_data` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_apply_epoch") == false, "In your script you must inherit the virtual method `_apply_epoch` to correctly use the `NetworkedController`."); + + } break; +#endif + } +} + +ServerController::ServerController( + NetworkedController *p_node, + int p_traced_frames) : + Controller(p_node), + network_watcher(p_traced_frames, 0) { +} + +void ServerController::process(real_t p_delta) { + if (unlikely(enabled == false)) { + // Disabled by the SceneSynchronizer. + return; + } + + fetch_next_input(); + + if (unlikely(current_input_buffer_id == UINT32_MAX)) { + // Skip this until the first input arrive. + return; + } + + node->get_inputs_buffer_mut().begin_read(); + node->get_inputs_buffer_mut().seek(METADATA_SIZE); + node->call( + SNAME("_controller_process"), + p_delta, + &node->get_inputs_buffer_mut()); + + doll_sync(p_delta); + + if (streaming_paused == false) { + calculates_player_tick_rate(p_delta); + adjust_player_tick_rate(p_delta); + } +} + +bool is_remote_frame_A_older(const FrameSnapshot &p_snap_a, const FrameSnapshot &p_snap_b) { + return p_snap_a.id < p_snap_b.id; +} + +uint32_t ServerController::last_known_input() const { + if (snapshots.size() > 0) { + return snapshots.back().id; + } else { + return UINT32_MAX; + } +} + +uint32_t ServerController::get_current_input_id() const { + return current_input_buffer_id; +} + +void ServerController::set_enabled(bool p_enable) { + if (enabled == p_enable) { + return; + } + enabled = p_enable; + node->pause_notify_dolls(); + + // ~~ On state change, reset everything to avoid accumulate old data. ~~ + + // Client inputs reset. + ghost_input_count = 0; + last_sent_state_input_id = 0; + client_tick_additional_speed = 0.0; + additional_speed_notif_timer = 0.0; + snapshots.clear(); + input_arrival_time = UINT32_MAX; + network_watcher.reset(0.0); + + // Doll reset. + is_epoch_important = false; + batch_sync_timer = 0.0; +} + +void ServerController::clear_peers() { + peers.clear(); +} + +void ServerController::activate_peer(int p_peer) { + // Collects all the dolls. + +#ifdef DEBUG_ENABLED + // Unreachable because this is the server controller. + CRASH_COND(node->get_tree()->get_multiplayer()->is_network_server() == false); +#endif + if (p_peer == node->get_network_master()) { + // This is self, so not a doll. + return; + } + + const uint32_t index = find_peer(p_peer); + if (index == UINT32_MAX) { + peers.push_back(p_peer); + } +} + +void ServerController::deactivate_peer(int p_peer) { + const uint32_t index = find_peer(p_peer); + if (index != UINT32_MAX) { + peers.remove_unordered(index); + } +} + +void ServerController::receive_inputs(const Vector &p_data) { + // The packet is composed as follow: + // |- The following four bytes for the first input ID. + // \- Array of inputs: + // |-- First byte the amount of times this input is duplicated in the packet. + // |-- inputs buffer. + // + // Let's decode it! + + const uint32_t now = OS::get_singleton()->get_ticks_msec(); + // If now is bigger, then the timer has been disabled, so we assume 0. + network_watcher.push(now > input_arrival_time ? now - input_arrival_time : 0); + input_arrival_time = now; + + const int data_len = p_data.size(); + + int ofs = 0; + + ERR_FAIL_COND(data_len < 4); + const uint32_t first_input_id = decode_uint32(p_data.ptr() + ofs); + ofs += 4; + + uint32_t inserted_input_count = 0; + + // Contains the entire packet and in turn it will be seek to specific location + // so I will not need to copy chunk of the packet data. + DataBuffer pir; + pir.begin_read(); + pir.get_buffer_mut().get_bytes_mut() = p_data; + // TODO this is for 3.2 + //pir.get_buffer_mut().resize_in_bytes(data_len); + //memcpy(pir.get_buffer_mut().get_bytes_mut().ptrw(), p_data.ptr(), data_len); + + while (ofs < data_len) { + ERR_FAIL_COND_MSG(ofs + 1 > data_len, "The arrived packet size doesn't meet the expected size."); + // First byte is used for the duplication count. + const uint8_t duplication = p_data[ofs]; + ofs += 1; + + // Validate input + const int input_buffer_offset_bit = ofs * 8; + pir.shrink_to(input_buffer_offset_bit, (data_len - ofs) * 8); + pir.seek(input_buffer_offset_bit); + // Read metadata + const bool has_data = pir.read_bool(); + + const int input_size_in_bits = (has_data ? int(node->call(SNAME("_count_input_size"), &pir)) : 0) + METADATA_SIZE; + // Pad to 8 bits. + const int input_size_padded = + Math::ceil((static_cast(input_size_in_bits)) / 8.0); + ERR_FAIL_COND_MSG(ofs + input_size_padded > data_len, "The arrived packet size doesn't meet the expected size."); + + // The input is valid, populate the buffer. + for (int sub = 0; sub <= duplication; sub += 1) { + const uint32_t input_id = first_input_id + inserted_input_count; + inserted_input_count += 1; + + if (unlikely(current_input_buffer_id != UINT32_MAX && current_input_buffer_id >= input_id)) { + // We already have this input, so we don't need it anymore. + continue; + } + + FrameSnapshot rfs; + rfs.id = input_id; + + const bool found = std::binary_search( + snapshots.begin(), + snapshots.end(), + rfs, + is_remote_frame_A_older); + + if (found == false) { + rfs.buffer_size_bit = input_size_in_bits; + rfs.inputs_buffer.get_bytes_mut().resize(input_size_padded); + memcpy( + rfs.inputs_buffer.get_bytes_mut().ptrw(), + p_data.ptr() + ofs, + input_size_padded); + + snapshots.push_back(rfs); + + // Sort the new inserted snapshot. + std::sort( + snapshots.begin(), + snapshots.end(), + is_remote_frame_A_older); + } + } + + // We can now advance the offset. + ofs += input_size_padded; + } + +#ifdef DEBUG_ENABLED + if (snapshots.empty() == false && current_input_buffer_id != UINT32_MAX) { + // At this point is guaranteed that the current_input_buffer_id is never + // greater than the first item contained by `snapshots`. + CRASH_COND(current_input_buffer_id >= snapshots.front().id); + } +#endif + + ERR_FAIL_COND_MSG(ofs != data_len, "At the end was detected that the arrived packet has an unexpected size."); +} + +int ServerController::get_inputs_count() const { + return snapshots.size(); +} + +bool ServerController::fetch_next_input() { + bool is_new_input = true; + + if (unlikely(current_input_buffer_id == UINT32_MAX)) { + // As initial packet, anything is good. + if (snapshots.empty() == false) { + // First input arrived. + node->set_inputs_buffer(snapshots.front().inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); + current_input_buffer_id = snapshots.front().id; + snapshots.pop_front(); + // Start tracing the packets from this moment on. + network_watcher.reset(0); + input_arrival_time = UINT32_MAX; + } else { + is_new_input = false; + } + } else { + const uint32_t next_input_id = current_input_buffer_id + 1; + + if (unlikely(streaming_paused)) { + // Stream is paused. + if (snapshots.empty() == false && + snapshots.front().id >= next_input_id) { + // A new input is arrived while the streaming is paused. + const bool is_buffer_void = (snapshots.front().buffer_size_bit - METADATA_SIZE) == 0; + streaming_paused = is_buffer_void; + node->set_inputs_buffer(snapshots.front().inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); + current_input_buffer_id = snapshots.front().id; + is_new_input = true; + snapshots.pop_front(); + network_watcher.reset(0); + input_arrival_time = UINT32_MAX; + } else { + // No inputs, or we are not yet arrived to the client input, + // so just pretend the next input is void. + node->set_inputs_buffer(BitArray(METADATA_SIZE), METADATA_SIZE, 0); + is_new_input = false; + } + } else if (unlikely(snapshots.empty() == true)) { + // The input buffer is empty; a packet is missing. + is_new_input = false; + ghost_input_count += 1; + NET_DEBUG_PRINT("Input buffer is void, i'm using the previous one!"); + + } else { + // The input buffer is not empty, search the new input. + if (next_input_id == snapshots.front().id) { + // Wow, the next input is perfect! + node->set_inputs_buffer(snapshots.front().inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); + current_input_buffer_id = snapshots.front().id; + snapshots.pop_front(); + + ghost_input_count = 0; + } else { + // The next packet is not here. This can happen when: + // - The packet is lost or not yet arrived. + // - The client for any reason desync with the server. + // + // In this cases, the server has the hard task to re-sync. + // + // # What it does, then? + // Initially it see that only 1 packet is missing so it just use + // the previous one and increase `ghost_inputs_count` to 1. + // + // The next iteration, if the packet is not yet arrived the + // server trys to take the next packet with the `id` less or + // equal to `next_packet_id + ghost_packet_id`. + // + // As you can see the server doesn't lose immediately the hope + // to find the missing packets, but at the same time deals with + // it so increases its search pool per each iteration. + // + // # Wise input search. + // Let's consider the case when a set of inputs arrive at the + // same time, while the server is struggling for the missing packets. + // + // In the meanwhile that the packets were chilling on the net, + // the server were simulating by guessing on their data; this + // mean that they don't have any longer room to be simulated + // when they arrive, and the right thing would be just forget + // about these. + // + // The thing is that these can still contain meaningful data, so + // instead to jump directly to the newest we restart the inputs + // from the next important packet. + // + // For this reason we keep track the amount of missing packets + // using `ghost_input_count`. + + ghost_input_count += 1; + + const int size = MIN(ghost_input_count, snapshots.size()); + const uint32_t ghost_packet_id = next_input_id + ghost_input_count; + + bool recovered = false; + FrameSnapshot pi; + + DataBuffer pir_A = node->get_inputs_buffer(); + + for (int i = 0; i < size; i += 1) { + if (ghost_packet_id < snapshots.front().id) { + break; + } else { + pi = snapshots.front(); + snapshots.pop_front(); + recovered = true; + + // If this input has some important changes compared to the last + // good input, let's recover to this point otherwise skip it + // until the last one. + // Useful to avoid that the server stay too much behind the + // client. + + DataBuffer pir_B(pi.inputs_buffer); + pir_B.shrink_to(METADATA_SIZE, pi.buffer_size_bit - METADATA_SIZE); + + pir_A.begin_read(); + pir_A.seek(METADATA_SIZE); + pir_B.begin_read(); + pir_B.seek(METADATA_SIZE); + + const bool is_meaningful = node->call(SNAME("_are_inputs_different"), &pir_A, &pir_B); + if (is_meaningful) { + break; + } + } + } + + if (recovered) { + node->set_inputs_buffer(pi.inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); + current_input_buffer_id = pi.id; + ghost_input_count = 0; + NET_DEBUG_PRINT("Packet recovered"); + } else { + is_new_input = false; + NET_DEBUG_PRINT("Packet still missing"); + } + } + } + } + +#ifdef DEBUG_ENABLED + if (snapshots.empty() == false && current_input_buffer_id != UINT32_MAX) { + // At this point is guaranteed that the current_input_buffer_id is never + // greater than the first item contained by `snapshots`. + CRASH_COND(current_input_buffer_id >= snapshots.front().id); + } +#endif + return is_new_input; +} + +void ServerController::notify_send_state() { + last_sent_state_input_id = get_current_input_id(); + // If the notified input is a void buffer, the client is allowed to pause + // the input streaming. So missing packets are just handled as void inputs. + if (node->get_inputs_buffer().size() == 0) { + streaming_paused = true; + } +} + +void ServerController::doll_sync(real_t p_delta) { + // Advance the epoch. + epoch += 1; + batch_sync_timer += p_delta; + const bool send_batch = batch_sync_timer >= node->get_doll_epoch_batch_sync_rate(); + + bool epoch_state_collected = false; + + // Process each peer and send the data if needed. + for (uint32_t i = 0; i < peers.size(); i += 1) { + if (peers[i].active == false) { + // Nothing to do on this peer. + continue; + } + + peers[i].collect_timer += 1; + if ( + is_epoch_important || + peers[i].collect_timer >= peers[i].collect_threshold) { + // Resets the timer. + peers[i].collect_timer -= peers[i].collect_threshold; + // Since is possible to force send the state update, we need to make + // sure the timer doesn't go below 0. + peers[i].collect_timer = MAX(0, peers[i].collect_timer); + + // Prepare the epoch_data cache. + if (epoch_state_collected == false) { + epoch_state_data_cache.begin_write(0); + epoch_state_data_cache.add_int(epoch, DataBuffer::COMPRESSION_LEVEL_1); + node->call(SNAME("_collect_epoch_data"), &epoch_state_data_cache); + epoch_state_data_cache.dry(); + epoch_state_collected = true; + } + + // Store this into epoch batch. + if (unlikely(epoch_state_data_cache.get_buffer().get_bytes().size() > UINT8_MAX)) { + // If the packet is more than 255 it can't be sent. + NET_DEBUG_ERR("The status update is too big, try to staty under 255 bytes per update. This status is dropped."); + } else { + peers[i].batch_size += 1 + epoch_state_data_cache.get_buffer().get_bytes().size(); + peers[i].epoch_batch.push_back(epoch_state_data_cache.get_buffer().get_bytes()); + } + } + + // Send batch data. + if (send_batch) { + const uint8_t next_collect_rate = + MIN(node->get_doll_epoch_collect_rate() / + peers[i].update_rate_factor, + UINT8_MAX); + + // Next rate is + peers[i].collect_threshold = next_collect_rate; + + if (peers[i].epoch_batch.size() > 0) { + // Add space to allocate the next_collect_rate. + peers[i].batch_size += 1; + +#ifdef DEBUG_ENABLED + if (peers[i].batch_size >= 1350) { + NET_DEBUG_WARN("The amount of data collected for this batch is more than 1350 bytes. Please make sure the `doll_sync_timer_rate` is not so big, so to avoid packet fragmentation. Batch size: " + itos(peers[i].batch_size) + " - Epochs into the batch: " + itos(peers[i].epoch_batch.size())); + } +#endif + + // Prepare the batch data. + Vector data; + data.resize(peers[i].batch_size); + uint8_t *data_ptr = data.ptrw(); + uint32_t offset = 0; + data_ptr[offset] = next_collect_rate; + offset += 1; + for (uint32_t x = 0; x < peers[i].epoch_batch.size(); x += 1) { + ERR_CONTINUE_MSG(peers[i].epoch_batch[x].size() > 256, "It's not allowed to send more than 256 bytes per status. This status is dropped."); + data_ptr[offset] = peers[i].epoch_batch[x].size(); + offset += 1; + for (int l = 0; l < peers[i].epoch_batch[x].size(); l += 1) { + data_ptr[offset] = peers[i].epoch_batch[x][l]; + offset += 1; + } + } +#ifdef DEBUG_ENABLED + // This is not supposed to happen because the batch_size is + // correctly computed. + CRASH_COND(offset != peers[i].batch_size); +#endif + peers[i].epoch_batch.clear(); + peers[i].batch_size = 0; + + // Send the data + node->rpc_id( + peers[i].peer, + SNAME("_rpc_doll_send_epoch_batch"), + data); + } + } + } + + if (send_batch) { + batch_sync_timer = 0.0; + } + + is_epoch_important = false; +} + +void ServerController::calculates_player_tick_rate(real_t p_delta) { + const real_t min_frames_delay = node->get_min_frames_delay(); + const real_t max_frames_delay = node->get_max_frames_delay(); + const real_t net_sensitivity = node->get_net_sensitivity(); + + const uint32_t avg_receive_time = network_watcher.average(); + const real_t deviation_receive_time = real_t(network_watcher.get_deviation(avg_receive_time)) / 1000.0; + + // The network quality can be established just by checking the standard + // deviation. Stable connections have standard deviation that tend to 0. + const real_t net_poorness = MIN( + deviation_receive_time / net_sensitivity, + 1.0); + + const int optimal_frame_delay = Math::lerp( + min_frames_delay, + max_frames_delay, + net_poorness); + + int consecutive_inputs = 0; + for (uint32_t i = 0; i < snapshots.size(); i += 1) { + if (snapshots[i].id == (current_input_buffer_id + consecutive_inputs + 1)) { + consecutive_inputs += 1; + } + } + + const real_t distance_to_optimal_count = real_t(optimal_frame_delay - consecutive_inputs); + + const real_t acc = distance_to_optimal_count * node->get_tick_acceleration() * p_delta; + // Used to avoid oscillations. + const real_t damp = -(client_tick_additional_speed * 0.95); + client_tick_additional_speed += acc + damp * ((SGN(acc) * SGN(damp) + 1) / 2.0); + client_tick_additional_speed = CLAMP(client_tick_additional_speed, -MAX_ADDITIONAL_TICK_SPEED, MAX_ADDITIONAL_TICK_SPEED); + +#ifdef DEBUG_ENABLED + const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_server_speedup"); + if (debug) { + print_line("Network poorness: " + rtos(net_poorness) + ", optimal frame delay: " + itos(optimal_frame_delay) + ", deviation: " + rtos(deviation_receive_time) + ", current frame delay: " + itos(consecutive_inputs)); + } +#endif +} + +void ServerController::adjust_player_tick_rate(real_t p_delta) { + additional_speed_notif_timer += p_delta; + if (additional_speed_notif_timer >= node->get_tick_speedup_notification_delay()) { + additional_speed_notif_timer = 0.0; + + const uint8_t new_speed = UINT8_MAX * (((client_tick_additional_speed / MAX_ADDITIONAL_TICK_SPEED) + 1.0) / 2.0); + + Vector packet_data; + packet_data.push_back(new_speed); + + node->rpc_id( + node->get_network_master(), + SNAME("_rpc_send_tick_additional_speed"), + packet_data); + } +} + +uint32_t ServerController::find_peer(int p_peer) const { + for (uint32_t i = 0; i < peers.size(); i += 1) { + if (peers[i].peer == p_peer) { + return i; + } + } + return UINT32_MAX; +} + +PlayerController::PlayerController(NetworkedController *p_node) : + Controller(p_node), + current_input_id(UINT32_MAX), + input_buffers_counter(0), + time_bank(0.0), + tick_additional_speed(0.0) { +} + +void PlayerController::process(real_t p_delta) { + // We need to know if we can accept a new input because in case of bad + // internet connection we can't keep accumulating inputs forever + // otherwise the server will differ too much from the client and we + // introduce virtual lag. + const bool accept_new_inputs = can_accept_new_inputs(); + + if (accept_new_inputs) { + current_input_id = input_buffers_counter; + + node->get_inputs_buffer_mut().begin_write(METADATA_SIZE); + + node->get_inputs_buffer_mut().seek(1); + node->call(SNAME("_collect_inputs"), p_delta, &node->get_inputs_buffer_mut()); + + // Set metadata data. + node->get_inputs_buffer_mut().seek(0); + if (node->get_inputs_buffer().size() > 0) { + node->get_inputs_buffer_mut().add_bool(true); + streaming_paused = false; + } else { + node->get_inputs_buffer_mut().add_bool(false); + } + } else { + NET_DEBUG_WARN("It's not possible to accept new inputs. Is this lagging?"); + } + + node->get_inputs_buffer_mut().dry(); + node->get_inputs_buffer_mut().begin_read(); + node->get_inputs_buffer_mut().seek(METADATA_SIZE); // Skip meta. + + // The physics process is always emitted, because we still need to simulate + // the character motion even if we don't store the player inputs. + node->call(SNAME("_controller_process"), p_delta, &node->get_inputs_buffer()); + + node->player_set_has_new_input(false); + if (accept_new_inputs) { + if (streaming_paused == false) { + input_buffers_counter += 1; + store_input_buffer(current_input_id); + send_frame_input_buffer_to_server(); + node->player_set_has_new_input(true); + } + } +} + +int PlayerController::calculates_sub_ticks(real_t p_delta, real_t p_iteration_per_seconds) { + const real_t pretended_delta = get_pretended_delta(p_iteration_per_seconds); + + time_bank += p_delta; + const int sub_ticks = static_cast(time_bank / pretended_delta); + time_bank -= static_cast(sub_ticks) * pretended_delta; + return sub_ticks; +} + +int PlayerController::notify_input_checked(uint32_t p_input_id) { + if (frames_snapshot.empty() || p_input_id < frames_snapshot.front().id || p_input_id > frames_snapshot.back().id) { + // The received p_input_id is not known, so nothing to do. + NET_DEBUG_ERR("The received snapshot, with input id: " + itos(p_input_id) + " is not known. This is a bug or someone is trying to hack."); + return frames_snapshot.size(); + } + + // Remove inputs prior to the known one. We may still need the known one + // when the stream is paused. + while (frames_snapshot.empty() == false && frames_snapshot.front().id <= p_input_id) { + if (frames_snapshot.front().id == p_input_id) { + streaming_paused = (frames_snapshot.front().buffer_size_bit - METADATA_SIZE) <= 0; + } + frames_snapshot.pop_front(); + } + +#ifdef DEBUG_ENABLED + // Unreachable, because the next input have always the next `p_input_id` or empty. + CRASH_COND(frames_snapshot.empty() == false && (p_input_id + 1) != frames_snapshot.front().id); +#endif + + // Make sure the remaining inputs are 0 sized, if not streaming can't be paused. + if (streaming_paused) { + for (auto it = frames_snapshot.begin(); it != frames_snapshot.end(); it += 1) { + if ((it->buffer_size_bit - METADATA_SIZE) > 0) { + // Streaming can't be paused. + streaming_paused = false; + break; + } + } + } + + return frames_snapshot.size(); +} + +uint32_t PlayerController::last_known_input() const { + return get_stored_input_id(-1); +} + +uint32_t PlayerController::get_stored_input_id(int p_i) const { + if (p_i < 0) { + if (frames_snapshot.empty() == false) { + return frames_snapshot.back().id; + } else { + return UINT32_MAX; + } + } else { + const size_t i = p_i; + if (i < frames_snapshot.size()) { + return frames_snapshot[i].id; + } else { + return UINT32_MAX; + } + } +} + +bool PlayerController::process_instant(int p_i, real_t p_delta) { + const size_t i = p_i; + if (i < frames_snapshot.size()) { + DataBuffer ib(frames_snapshot[i].inputs_buffer); + ib.shrink_to(METADATA_SIZE, frames_snapshot[i].buffer_size_bit - METADATA_SIZE); + ib.begin_read(); + ib.seek(METADATA_SIZE); + node->call(SNAME("_controller_process"), p_delta, &ib); + return (i + 1) < frames_snapshot.size(); + } else { + return false; + } +} + +uint32_t PlayerController::get_current_input_id() const { + return current_input_id; +} + +real_t PlayerController::get_pretended_delta(real_t p_iteration_per_seconds) const { + return 1.0 / (p_iteration_per_seconds + tick_additional_speed); +} + +void PlayerController::store_input_buffer(uint32_t p_id) { + FrameSnapshot inputs; + inputs.id = p_id; + inputs.inputs_buffer = node->get_inputs_buffer().get_buffer(); + inputs.buffer_size_bit = node->get_inputs_buffer().size() + METADATA_SIZE; + inputs.similarity = UINT32_MAX; + frames_snapshot.push_back(inputs); +} + +void PlayerController::send_frame_input_buffer_to_server() { + // The packet is composed as follow: + // - The following four bytes for the first input ID. + // - Array of inputs: + // |-- First byte the amount of times this input is duplicated in the packet. + // |-- Input buffer. + + const size_t inputs_count = MIN(frames_snapshot.size(), static_cast(node->get_max_redundant_inputs() + 1)); + CRASH_COND(inputs_count < 1); // Unreachable + +#define MAKE_ROOM(p_size) \ + if (cached_packet_data.size() < static_cast(ofs + p_size)) \ + cached_packet_data.resize(ofs + p_size); + + int ofs = 0; + + // Let's store the ID of the first snapshot. + MAKE_ROOM(4); + const uint32_t first_input_id = frames_snapshot[frames_snapshot.size() - inputs_count].id; + ofs += encode_uint32(first_input_id, cached_packet_data.ptr() + ofs); + + uint32_t previous_input_id = UINT32_MAX; + uint32_t previous_input_similarity = UINT32_MAX; + int previous_buffer_size = 0; + uint8_t duplication_count = 0; + + DataBuffer pir_A(node->get_inputs_buffer().get_buffer()); + + // Compose the packets + for (size_t i = frames_snapshot.size() - inputs_count; i < frames_snapshot.size(); i += 1) { + bool is_similar = false; + + if (previous_input_id == UINT32_MAX) { + // This happens for the first input of the packet. + // Just write it. + is_similar = false; + } else if (duplication_count == UINT8_MAX) { + // Prevent to overflow the `uint8_t`. + is_similar = false; + } else { + if (frames_snapshot[i].similarity != previous_input_id) { + if (frames_snapshot[i].similarity == UINT32_MAX) { + // This input was never compared, let's do it now. + DataBuffer pir_B(frames_snapshot[i].inputs_buffer); + pir_B.shrink_to(METADATA_SIZE, frames_snapshot[i].buffer_size_bit - METADATA_SIZE); + + pir_A.begin_read(); + pir_A.seek(METADATA_SIZE); + pir_B.begin_read(); + pir_B.seek(METADATA_SIZE); + + const bool are_different = node->call(SNAME("_are_inputs_different"), &pir_A, &pir_B); + is_similar = are_different == false; + + } else if (frames_snapshot[i].similarity == previous_input_similarity) { + // This input is similar to the previous one, the thing is + // that the similarity check was done on an older input. + // Fortunatelly we are able to compare the similarity id + // and detect its similarity correctly. + is_similar = true; + } else { + // This input is simply different from the previous one. + is_similar = false; + } + } else { + // These are the same, let's save some space. + is_similar = true; + } + } + + if (is_similar) { + // This input is similar to the previous one, so just duplicate it. + duplication_count += 1; + // In this way, we don't need to compare these frames again. + frames_snapshot[i].similarity = previous_input_id; + + } else { + // This input is different from the previous one, so let's + // finalize the previous and start another one. + + if (previous_input_id != UINT32_MAX) { + // We can finally finalize the previous input + cached_packet_data[ofs - previous_buffer_size - 1] = duplication_count; + } + + // Resets the duplication count. + duplication_count = 0; + + // Writes the duplication_count for this new input + MAKE_ROOM(1); + cached_packet_data[ofs] = 0; + ofs += 1; + + // Write the inputs + const int buffer_size = frames_snapshot[i].inputs_buffer.get_bytes().size(); + MAKE_ROOM(buffer_size); + memcpy( + cached_packet_data.ptr() + ofs, + frames_snapshot[i].inputs_buffer.get_bytes().ptr(), + buffer_size); + ofs += buffer_size; + + // Let's see if we can duplicate this input. + previous_input_id = frames_snapshot[i].id; + previous_input_similarity = frames_snapshot[i].similarity; + previous_buffer_size = buffer_size; + + pir_A.get_buffer_mut() = frames_snapshot[i].inputs_buffer; + pir_A.shrink_to(METADATA_SIZE, frames_snapshot[i].buffer_size_bit - METADATA_SIZE); + } + } + + // Finalize the last added input_buffer. + cached_packet_data[ofs - previous_buffer_size - 1] = duplication_count; + + // Make the packet data. + Vector packet_data; + packet_data.resize(ofs); + + memcpy( + packet_data.ptrw(), + cached_packet_data.ptr(), + ofs); + + const int server_peer_id = 1; + node->rpc_id( + server_peer_id, + SNAME("_rpc_server_send_inputs"), + packet_data); +} + +bool PlayerController::can_accept_new_inputs() const { + return frames_snapshot.size() < static_cast(node->get_player_input_storage_size()); +} + +DollController::DollController(NetworkedController *p_node) : + Controller(p_node), + network_watcher(node->get_doll_connection_stats_frame_span(), 0) { +} + +void DollController::ready() { + interpolator.reset(); + node->call( + SNAME("_setup_interpolator"), + &interpolator); + interpolator.terminate_init(); +} + +void DollController::process(real_t p_delta) { + const uint32_t frame_epoch = next_epoch(); + + if (unlikely(frame_epoch == UINT32_MAX)) { + // Nothing to do. + return; + } + + const real_t fractional_part = advancing_epoch; + node->call( + SNAME("_apply_epoch"), + p_delta, + interpolator.pop_epoch(frame_epoch, fractional_part)); +} + +uint32_t DollController::get_current_input_id() const { + return current_epoch; +} + +void DollController::receive_batch(const Vector &p_data) { + if (unlikely(node->get_scene_synchronizer()->is_enabled() == false)) { + // The sync is disabled, nothing to do. + return; + } + + // Take the epochs befoe the batch is applied. + const uint32_t youngest_epoch = interpolator.get_youngest_epoch(); + const uint32_t oldest_epoch = interpolator.get_oldest_epoch(); + + int initially_stored_epochs = 0; + if (youngest_epoch != UINT32_MAX && oldest_epoch != UINT32_MAX) { + initially_stored_epochs = oldest_epoch - youngest_epoch; + } + + initially_stored_epochs -= missing_epochs; + missing_epochs = 0; + + uint32_t batch_young_epoch = UINT32_MAX; + + int buffer_start_position = 0; + + const uint8_t next_collect_rate = p_data[buffer_start_position]; + buffer_start_position += 1; + + while (buffer_start_position < p_data.size()) { + const int buffer_size = p_data[buffer_start_position]; + const Vector buffer = p_data.subarray( + buffer_start_position + 1, + buffer_start_position + 1 + buffer_size - 1); + + ERR_FAIL_COND(buffer.size() <= 0); + + const uint32_t epoch = receive_epoch(buffer); + buffer_start_position += 1 + buffer_size; + + batch_young_epoch = MIN(epoch, batch_young_epoch); + } + + // ~~ Establish the interpolation speed ~~ + if (batch_young_epoch == UINT32_MAX) { + // This may just be a late arrived batch, so nothing more to do. + return; + } + + const real_t net_sentitivity = node->get_doll_net_sensitivity(); + + // Establish the connection quality by checking if the batch takes + // always the same time to arrive. + const uint32_t now = OS::get_singleton()->get_ticks_msec(); + // If now is bigger, then the timer has been disabled, so we assume 0. + network_watcher.push(now > batch_receiver_timer ? now - batch_receiver_timer : 0); + batch_receiver_timer = now; + + const uint32_t avg_receive_time = network_watcher.average(); + const real_t deviation_receive_time = real_t(network_watcher.get_deviation(avg_receive_time)) / 1000.0; + + // The network quality can be established just by checking the standard + // deviation. Stable connections have standard deviation that tend to 0. + const real_t net_poorness = MIN( + deviation_receive_time / net_sentitivity, + 1.0); + + const int optimal_frame_delay = Math::lerp( + node->get_doll_min_frames_delay(), + node->get_doll_max_frames_delay(), + net_poorness); + + // TODO cache this? + const double frames_per_batch = node->get_doll_epoch_batch_sync_rate() * real_t(Engine::get_singleton()->get_physics_ticks_per_second()); + const double next_batch_arrives_in = Math::ceil(double(next_collect_rate) / frames_per_batch) * frames_per_batch; + + const real_t doll_interpolation_max_speedup = node->get_doll_interpolation_max_speedup(); + additional_speed = doll_interpolation_max_speedup * (real_t(initially_stored_epochs - optimal_frame_delay) / next_batch_arrives_in); + additional_speed = CLAMP(additional_speed, -doll_interpolation_max_speedup, doll_interpolation_max_speedup); + +#ifdef DEBUG_ENABLED + const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_doll_speedup"); + if (debug) { + print_line("Network poorness: " + rtos(net_poorness) + ", optimal stored epochs: " + rtos(optimal_frame_delay) + ", deviation: " + rtos(deviation_receive_time)); + } +#endif +} + +uint32_t DollController::receive_epoch(const Vector &p_data) { + DataBuffer buffer(p_data); + buffer.begin_read(); + const uint32_t epoch = buffer.read_int(DataBuffer::COMPRESSION_LEVEL_1); + + if (epoch <= paused_epoch) { + // The sync is in pause from this epoch, so just discard this received + // epoch that may just be a late received epoch. + return UINT32_MAX; + } + + interpolator.begin_write(epoch); + node->call(SNAME("_parse_epoch_data"), &interpolator, &buffer); + interpolator.end_write(); + + return epoch; +} + +uint32_t DollController::next_epoch() { + // TODO re-describe. + // This function regulates the epoch ID to process. + // The epoch is not simply increased by one because we need to make sure + // to make the client apply the nearest server state while giving some room + // for the subsequent information to arrive. + + // Step 1, Wait that we have at least two epochs. + if (unlikely(current_epoch == UINT32_MAX)) { + // Interpolator is not yet started. + if (interpolator.known_epochs_count() < 2) { + // Not ready yet. + return UINT32_MAX; + } + +#ifdef DEBUG_ENABLED + // At this point we have 2 epoch, something is always returned at this + // point. + CRASH_COND(interpolator.get_youngest_epoch() == UINT32_MAX); +#endif + + // Start epoch interpolation. + current_epoch = interpolator.get_youngest_epoch(); + node->emit_signal("doll_sync_started"); + } + + // At this point the interpolation is started and the function must + // return the best epoch id which we have to apply the state. + + // Step 2. Make sure we have something to interpolate with. + const uint32_t oldest_epoch = interpolator.get_oldest_epoch(); + if (unlikely(oldest_epoch == UINT32_MAX || oldest_epoch <= current_epoch)) { + missing_epochs += 1; + // Nothing to interpolate with. + return current_epoch; + } + +#ifdef DEBUG_ENABLED + // This can't happen because the current_epoch is advances only if it's + // possible to do so. + CRASH_COND(oldest_epoch < current_epoch); +#endif + + const uint64_t max_delay = node->get_doll_max_delay(); + + if (unlikely((oldest_epoch - current_epoch) > max_delay)) { + // This client seems too much behind at this point. Teleport forward. + const uint32_t youngest_epoch = interpolator.get_youngest_epoch(); + current_epoch = MAX(oldest_epoch - max_delay, youngest_epoch); + } else { + advancing_epoch += 1.0 + additional_speed; + } + + if (advancing_epoch > 0.0) { + // Advance the epoch by the the integral amount. + current_epoch += uint32_t(advancing_epoch); + // Clamp to the oldest epoch. + current_epoch = MIN(current_epoch, oldest_epoch); + + // Keep the floating point part. + advancing_epoch -= uint32_t(advancing_epoch); + } + + return current_epoch; +} + +void DollController::pause(uint32_t p_epoch) { + paused_epoch = p_epoch; + + interpolator.clear(); + additional_speed = 0.0; + current_epoch = UINT32_MAX; + advancing_epoch = 0.0; + missing_epochs = 0; + network_watcher.resize(node->get_doll_connection_stats_frame_span(), 0); + batch_receiver_timer = UINT32_MAX; + + node->emit_signal("doll_sync_paused"); +} + +NoNetController::NoNetController(NetworkedController *p_node) : + Controller(p_node), + frame_id(0) { +} + +void NoNetController::process(real_t p_delta) { + node->get_inputs_buffer_mut().begin_write(0); // No need of meta in this case. + node->call(SNAME("_collect_inputs"), p_delta, &node->get_inputs_buffer_mut()); + node->get_inputs_buffer_mut().dry(); + node->get_inputs_buffer_mut().begin_read(); + node->call(SNAME("_controller_process"), p_delta, &node->get_inputs_buffer_mut()); + frame_id += 1; +} + +uint32_t NoNetController::get_current_input_id() const { + return frame_id; +} diff --git a/modules/network_synchronizer/networked_controller.h b/modules/network_synchronizer/networked_controller.h new file mode 100644 index 000000000..f14bd454c --- /dev/null +++ b/modules/network_synchronizer/networked_controller.h @@ -0,0 +1,526 @@ +/*************************************************************************/ +/* networked_controller.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "scene/main/node.h" + +#include "data_buffer.h" +#include "interpolator.h" +#include "net_utilities.h" +#include + +#ifndef NETWORKED_CONTROLLER_H +#define NETWORKED_CONTROLLER_H + +class SceneSynchronizer; +struct Controller; +struct ServerController; +struct PlayerController; +struct DollController; +struct NoNetController; + +/// The `NetworkedController` is responsible to sync the `Player` inputs between +/// the peers. This allows to control a character, or an object with high precision +/// and replicates that movement on all connected peers. +/// +/// The `NetworkedController` will sync inputs, based on those will perform +/// operations. +/// The result of these operations, are guaranteed to be the same accross the +/// peers, if we stay under the assumption that the initial state is the same. +/// +/// Is possible to use the `SceneSynchronizer` to keep the state in sync with the +/// peers. +/// +// # Implementation details +// +// The `NetworkedController` perform different operations depending where it's +// instantiated. +// The most important part is inside the `PlayerController`, `ServerController`, +// `DollController`, `NoNetController`. +class NetworkedController : public Node { + GDCLASS(NetworkedController, Node); + + friend class SceneSynchronizer; + +public: + enum ControllerType { + CONTROLLER_TYPE_NULL, + CONTROLLER_TYPE_NONETWORK, + CONTROLLER_TYPE_PLAYER, + CONTROLLER_TYPE_SERVER, + CONTROLLER_TYPE_DOLL + }; + +private: + /// The input storage size is used to cap the amount of inputs collected by + /// the `PlayerController`. + /// + /// The server sends a message, to all the connected peers, notifing its + /// status at a fixed interval. + /// The peers, after receiving this update, removes all the old inputs until + /// that moment. + /// + /// `input_storage_size`: is too small, the player may stop collect + /// - Too small value makes the `PlayerController` stop collecting inputs + /// too early, in case of lag. + /// - Too big values may introduce too much latency, because the player keep + /// pushing new inputs without receiving the server snapshot. + /// + /// With 60 iteration per seconds a good value is `180` (60 * 3) so the + /// `PlayerController` can be at max 3 seconds ahead the `ServerController`. + int player_input_storage_size = 180; + + /// Amount of time an inputs is re-sent to each peer. + /// Resenging inputs is necessary because the packets may be lost since as + /// they are sent in an unreliable way. + int max_redundant_inputs = 5; + + /// Time in seconds between each `tick_speedup` that the server sends to the + /// client. + real_t tick_speedup_notification_delay = 0.33; + + /// The connection quality is established by watching the time passed + /// between each input is received. + /// The more this time is the same the more the connection health is good. + /// + /// The `network_traced_frames` defines how many frames have + /// to be used to establish the connection quality. + /// - Big values make the mechanism too slow. + /// - Small values make the mechanism too sensible. + int network_traced_frames = 120; + + /// Sensitivity to network oscillations. The value is in seconds and can be + /// used to establish the connection quality. + /// + /// For each input, the time needed for its arrival is traced; the standard + /// deviation of these is divided by `net_sensitivity`: the result is + /// the connection quality. + /// + /// The more the time needed for each batch to arrive is different the + /// bigger this value is: when this value approaches to + /// `net_sensitivity` (or even surpasses it) the bad the connection is. + /// + /// The result is the `connection_poorness` that goes from 0 to 1 and is + /// used to decide the `optimal_frame_delay` that is interpolated between + /// `min_frames_delay` and `max_frames_delay`. + real_t net_sensitivity = 0.1; + + /// The `ServerController` will try to keep a margin of error, so that + /// network oscillations doesn't leave the `ServerController` without + /// inputs. + /// + /// This margin of error is called `optimal_frame_delay` and it changes + /// depending on the connection health: + /// it can go from `min_frames_delay` to `max_frames_delay`. + int min_frames_delay = 2; + int max_frames_delay = 6; + + /// Rate at which the tick speed changes, so the `optimal_frame_delay` is + /// matched. + real_t tick_acceleration = 2.0; + + /// Collect rate (in frames) used by the server to estabish when to collect + /// the state for a particular peer. + /// It's possible to scale down this rate, for a particular peer, + /// using the function: set_doll_collect_rate_factor(peer, factor); + /// Current default is 10Hz. + /// + /// The collected state is not immediatelly sent to the clients, rather it's + /// delayed so to be sent in batch. The states marked as important are + /// always collected. + int doll_epoch_collect_rate = 1; + + /// The time rate at which a new batch is sent. + real_t doll_epoch_batch_sync_rate = 0.25; + + /// The doll interpolator will try to keep a margin of error, so that network + /// oscillations doesn't make the dolls freeze. + /// + /// This margin of error is called `optimal_frame_delay` and it changes + /// depending on the connection health: + /// it can go from `doll_min_frames_delay` to `doll_max_frames_delay`. + int doll_min_frames_delay = 2; + int doll_max_frames_delay = 5; + + /// Max speedup / slowdown the doll can apply to recover its epoch buffer size. + real_t doll_interpolation_max_speedup = 0.2; + + /// The connection quality is established by watching the time passed + /// between each batch arrival. + /// The more this time is the same the more the connection health is good. + /// + /// The `doll_connection_stats_frame_span` defines how many frames have + /// to be used to establish the connection quality. + /// - Big values make the mechanism too slow. + /// - Small values make the mechanism too sensible. + /// The correct value should be give considering the + /// `doll_epoch_batch_sync_rate`. + int doll_connection_stats_frame_span = 30; + + /// Sensitivity to network oscillations. The value is in seconds and can be + /// used to establish the connection quality. + /// + /// For each batch, the time needed for its arrival is traced; the standard + /// deviation of these is divided by `doll_net_sensitivity`: the result is + /// the connection quality. + /// + /// The more the time needed for each batch to arrive is different the + /// bigger this value is: when this value approaches to + /// `doll_net_sensitivity` (or even surpasses it) the bad the connection is. + /// + /// The result is the `connection_poorness` that goes from 0 to 1 and is + /// used to decide the `optimal_frames_delay` that is interpolated between + /// `doll_min_frames_delay` and `doll_max_frames_delay`. + real_t doll_net_sensitivity = 0.2; + + /// Just after a bad connection phase, may happen that all the sent epochs + /// are received at once. To make sure the actor is always the more uptodate + /// possible, the number of epochs the actor has to fetch is clamped. + /// When `doll_max_delay` is surpassed the doll is teleported forward the + /// timeline so to be the more update possible. + uint64_t doll_max_delay = 60; + + ControllerType controller_type = CONTROLLER_TYPE_NULL; + Controller *controller = nullptr; + DataBuffer inputs_buffer; + + SceneSynchronizer *scene_synchronizer = nullptr; + + bool packet_missing = false; + bool has_player_new_input = false; + +public: + static void _bind_methods(); + +public: + NetworkedController(); + + void set_player_input_storage_size(int p_size); + int get_player_input_storage_size() const; + + void set_max_redundant_inputs(int p_max); + int get_max_redundant_inputs() const; + + void set_tick_speedup_notification_delay(real_t p_delay); + real_t get_tick_speedup_notification_delay() const; + + void set_network_traced_frames(int p_size); + int get_network_traced_frames() const; + + void set_min_frames_delay(int p_val); + int get_min_frames_delay() const; + + void set_max_frames_delay(int p_val); + int get_max_frames_delay() const; + + void set_net_sensitivity(real_t p_val); + real_t get_net_sensitivity() const; + + void set_tick_acceleration(real_t p_acceleration); + real_t get_tick_acceleration() const; + + void set_doll_epoch_collect_rate(int p_rate); + int get_doll_epoch_collect_rate() const; + + void set_doll_epoch_batch_sync_rate(real_t p_rate); + real_t get_doll_epoch_batch_sync_rate() const; + + void set_doll_min_frames_delay(int p_min); + int get_doll_min_frames_delay() const; + + void set_doll_max_frames_delay(int p_max); + int get_doll_max_frames_delay() const; + + void set_doll_interpolation_max_speedup(real_t p_speedup); + real_t get_doll_interpolation_max_speedup() const; + + void set_doll_connection_stats_frame_span(int p_span); + int get_doll_connection_stats_frame_span() const; + + void set_doll_net_sensitivity(real_t p_sensitivity); + real_t get_doll_net_sensitivity() const; + + void set_doll_max_delay(uint32_t p_max_delay); + uint32_t get_doll_max_delay() const; + + uint32_t get_current_input_id() const; + + const DataBuffer &get_inputs_buffer() const { + return inputs_buffer; + } + + DataBuffer &get_inputs_buffer_mut() { + return inputs_buffer; + } + + /// Returns the pretended delta used by the player. + real_t player_get_pretended_delta(uint32_t p_iterations_per_seconds) const; + + void mark_epoch_as_important(); + + void set_doll_collect_rate_factor(int p_peer, real_t p_factor); + void set_doll_peer_active(int p_peer_id, bool p_active); + void pause_notify_dolls(); + + bool process_instant(int p_i, real_t p_delta); + + /// Returns the server controller or nullptr if this is not a server. + ServerController *get_server_controller(); + const ServerController *get_server_controller() const; + /// Returns the player controller or nullptr if this is not a player. + PlayerController *get_player_controller(); + const PlayerController *get_player_controller() const; + /// Returns the doll controller or nullptr if this is not a doll. + DollController *get_doll_controller(); + const DollController *get_doll_controller() const; + /// Returns the no net controller or nullptr if this is not a no net. + NoNetController *get_nonet_controller(); + const NoNetController *get_nonet_controller() const; + + bool is_server_controller() const; + bool is_player_controller() const; + bool is_doll_controller() const; + bool is_nonet_controller() const; + +public: + void set_inputs_buffer(const BitArray &p_new_buffer, uint32_t p_metadata_size_in_bit, uint32_t p_size_in_bit); + + void set_scene_synchronizer(SceneSynchronizer *p_synchronizer); + SceneSynchronizer *get_scene_synchronizer() const; + bool has_scene_synchronizer() const; + + /* On server rpc functions. */ + void _rpc_server_send_inputs(const Vector &p_data); + + /* On client rpc functions. */ + void _rpc_send_tick_additional_speed(const Vector &p_data); + + /* On puppet rpc functions. */ + void _rpc_doll_notify_sync_pause(uint32_t p_epoch); + void _rpc_doll_send_epoch_batch(const Vector &p_data); + + void process(real_t p_delta); + + void player_set_has_new_input(bool p_has); + bool player_has_new_input() const; + + void __on_sync_paused(); + +private: + virtual void _notification(int p_what); +}; + +struct FrameSnapshot { + uint32_t id; + BitArray inputs_buffer; + uint32_t buffer_size_bit; + uint32_t similarity; + + bool operator==(const FrameSnapshot &p_other) const { + return p_other.id == id; + } +}; + +struct Controller { + NetworkedController *node; + + Controller(NetworkedController *p_node) : + node(p_node) {} + + virtual ~Controller() = default; + + virtual void ready() {} + virtual uint32_t get_current_input_id() const = 0; + + virtual void clear_peers() {} + virtual void activate_peer(int p_peer) {} + virtual void deactivate_peer(int p_peer) {} +}; + +struct ServerController : public Controller { + struct Peer { + Peer() = default; + Peer(int p_peer) : + peer(p_peer) {} + + int peer = 0; + bool active = true; + real_t update_rate_factor = 1.0; + int collect_timer = 0; // In frames + int collect_threshold = 0; // In frames + LocalVector> epoch_batch; + uint32_t batch_size = 0; + }; + + uint32_t current_input_buffer_id = UINT32_MAX; + uint32_t ghost_input_count = 0; + uint32_t last_sent_state_input_id = 0; + real_t client_tick_additional_speed = 0.0; + real_t additional_speed_notif_timer = 0.0; + std::deque snapshots; + bool streaming_paused = false; + bool enabled = true; + + uint32_t input_arrival_time = UINT32_MAX; + NetUtility::StatisticalRingBuffer network_watcher; + + /// Used to sync the dolls. + LocalVector peers; + DataBuffer epoch_state_data_cache; + uint32_t epoch = 0; + bool is_epoch_important = false; + real_t batch_sync_timer = 0.0; + + ServerController( + NetworkedController *p_node, + int p_traced_frames); + + void process(real_t p_delta); + uint32_t last_known_input() const; + virtual uint32_t get_current_input_id() const override; + + void set_enabled(bool p_enable); + + virtual void clear_peers() override; + virtual void activate_peer(int p_peer) override; + virtual void deactivate_peer(int p_peer) override; + + void receive_inputs(const Vector &p_data); + int get_inputs_count() const; + + /// Fetch the next inputs, returns true if the input is new. + bool fetch_next_input(); + + void notify_send_state(); + + void doll_sync(real_t p_delta); + + /// This function updates the `tick_additional_speed` so that the `frames_inputs` + /// size is enough to reduce the missing packets to 0. + /// + /// When the internet connection is bad, the packets need more time to arrive. + /// To heal this problem, the server tells the client to speed up a little bit + /// so it send the inputs a bit earlier than the usual. + /// + /// If the `frames_inputs` size is too big the input lag between the client and + /// the server is artificial and no more dependent on the internet. For this + /// reason the server tells the client to slowdown so to keep the `frames_inputs` + /// size moderate to the needs. + void calculates_player_tick_rate(real_t p_delta); + void adjust_player_tick_rate(real_t p_delta); + + uint32_t find_peer(int p_peer) const; +}; + +struct PlayerController : public Controller { + uint32_t current_input_id; + uint32_t input_buffers_counter; + real_t time_bank; + real_t tick_additional_speed; + bool streaming_paused = false; + + std::deque frames_snapshot; + LocalVector cached_packet_data; + + PlayerController(NetworkedController *p_node); + + void process(real_t p_delta); + int calculates_sub_ticks(real_t p_delta, real_t p_iteration_per_seconds); + int notify_input_checked(uint32_t p_input_id); + uint32_t last_known_input() const; + uint32_t get_stored_input_id(int p_i) const; + virtual uint32_t get_current_input_id() const override; + + bool process_instant(int p_i, real_t p_delta); + real_t get_pretended_delta(real_t p_iteration_per_second) const; + + void store_input_buffer(uint32_t p_id); + + /// Sends an unreliable packet to the server, containing a packed array of + /// frame snapshots. + void send_frame_input_buffer_to_server(); + + bool can_accept_new_inputs() const; +}; + +/// The doll controller is kind of special controller, it's using a +/// `ServerController` + `MastertController`. +/// The `DollController` receives inputs from the client as the server does, +/// and fetch them exactly like the server. +/// After the execution of the inputs, the puppet start to act like the player, +/// because it wait the player status from the server to correct its motion. +/// +/// There are some extra features available that allow the doll to stay in sync +/// with the server execution (see `soft_reset_to_server_state`) and the possibility +/// for the server to stop the data streaming. +struct DollController : public Controller { + Interpolator interpolator; + real_t additional_speed = 0.0; + uint32_t current_epoch = UINT32_MAX; + real_t advancing_epoch = 0.0; + uint32_t missing_epochs = 0; + // Any received epoch prior to this one is discarded. + uint32_t paused_epoch = 0; + + // Used to track the time taken for the next batch to arrive. + uint32_t batch_receiver_timer = UINT32_MAX; + /// Used to track how network is performing. + NetUtility::StatisticalRingBuffer network_watcher; + + DollController(NetworkedController *p_node); + + virtual void ready() override; + void process(real_t p_delta); + // TODO consider make this non virtual + virtual uint32_t get_current_input_id() const override; + + void receive_batch(const Vector &p_data); + uint32_t receive_epoch(const Vector &p_data); + + uint32_t next_epoch(); + void pause(uint32_t p_epoch); +}; + +/// This controller is used when the game instance is not a peer of any kind. +/// This controller keeps the workflow as usual so it's possible to use the +/// `NetworkedController` even without network. +struct NoNetController : public Controller { + uint32_t frame_id; + + NoNetController(NetworkedController *p_node); + + void process(real_t p_delta); + virtual uint32_t get_current_input_id() const override; +}; + +#endif diff --git a/modules/network_synchronizer/register_types.cpp b/modules/network_synchronizer/register_types.cpp new file mode 100644 index 000000000..1b591b440 --- /dev/null +++ b/modules/network_synchronizer/register_types.cpp @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "register_types.h" + +#include "data_buffer.h" +#include "interpolator.h" +#include "networked_controller.h" +#include "scene_diff.h" +#include "scene_synchronizer.h" + +void register_network_synchronizer_types() { + GDREGISTER_CLASS(DataBuffer); + GDREGISTER_CLASS(SceneDiff); + GDREGISTER_CLASS(Interpolator); + GDREGISTER_CLASS(NetworkedController); + GDREGISTER_CLASS(SceneSynchronizer); + + GLOBAL_DEF("NetworkSynchronizer/debug_server_speedup", false); + GLOBAL_DEF("NetworkSynchronizer/debug_doll_speedup", false); +} + +void unregister_network_synchronizer_types() { +} diff --git a/modules/network_synchronizer/register_types.h b/modules/network_synchronizer/register_types.h new file mode 100644 index 000000000..5fdca264f --- /dev/null +++ b/modules/network_synchronizer/register_types.h @@ -0,0 +1,36 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +void register_network_synchronizer_types(); +void unregister_network_synchronizer_types(); diff --git a/modules/network_synchronizer/scene_diff.cpp b/modules/network_synchronizer/scene_diff.cpp new file mode 100644 index 000000000..a900215f3 --- /dev/null +++ b/modules/network_synchronizer/scene_diff.cpp @@ -0,0 +1,165 @@ +/*************************************************************************/ +/* scene_diff.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "scene_diff.h" + +#include "scene/main/node.h" +#include "scene_synchronizer.h" + +void SceneDiff::_bind_methods() { +} + +void SceneDiff::start_tracking_scene_changes( + const LocalVector &p_nodes) { + start_tracking_count += 1; + if (start_tracking_count > 1) { + // Nothing to do, the tracking is already started. + return; + } + + tracking.resize(p_nodes.size()); + + for (uint32_t i = 0; i < p_nodes.size(); i += 1) { + if ( + p_nodes[i] == nullptr || + // Check if this is a controller. + p_nodes[i]->is_controller || + p_nodes[i]->controlled_by != nullptr) { + tracking[i].clear(); + continue; + } + +#ifdef DEBUG_ENABLED + // This is never triggered because we always pass the `organized_node_data` + // array. + CRASH_COND(p_nodes[i]->id != i); + // This is never triggered because when the node is invalid the node data + // is destroyed. + CRASH_COND(p_nodes[i]->node == nullptr); +#endif + + tracking[i].resize(p_nodes[i]->vars.size()); + + for (uint32_t v = 0; v < p_nodes[i]->vars.size(); v += 1) { + // Take the current variable value and store it. + if (p_nodes[i]->vars[v].enabled && p_nodes[i]->vars[v].id != UINT32_MAX) { + // Note: Taking the value using `get` so to take the most updated + // value. + tracking[i][v] = p_nodes[i]->node->get(p_nodes[i]->vars[v].var.name).duplicate(true); + } else { + tracking[i][v] = Variant(); + } + } + } +} + +void SceneDiff::stop_tracking_scene_changes(const SceneSynchronizer *p_synchronizer) { + ERR_FAIL_COND_MSG( + start_tracking_count == 0, + "The tracking is not yet started on this SceneDiff, so can't be end."); + + start_tracking_count -= 1; + if (start_tracking_count > 0) { + // Nothing to do, the tracking is still ongoing. + return; + } + + if (p_synchronizer->get_biggest_node_id() == UINT32_MAX) { + // No nodes to track. + tracking.clear(); + return; + } + + if (tracking.size() > (p_synchronizer->get_biggest_node_id() + 1)) { + NET_DEBUG_ERR("[BUG] The tracked nodes are exceeding the sync nodes. Probably the sync is different or it has reset?"); + tracking.clear(); + return; + } + + if (diff.size() < tracking.size()) { + // Make sure the diff has room to store the needed info. + diff.resize(tracking.size()); + } + + for (NetNodeId i = 0; i < tracking.size(); i += 1) { + const NetUtility::NodeData *nd = p_synchronizer->get_node_data(i); + if (nd == nullptr) { + continue; + } + +#ifdef DEBUG_ENABLED + // This is never triggered because we always pass the `organized_node_data` + // array. + CRASH_COND(nd->id != i); + // This is never triggered because when the node is invalid the node data + // is destroyed. + CRASH_COND(nd->node == nullptr); +#endif + + if (nd->vars.size() != tracking[i].size()) { + // These two arrays are different because the node was null + // during the start. So we can assume we are not tracking it. + continue; + } + + if (diff[i].size() < tracking[i].size()) { + // Make sure the diff has room to store the variable info. + diff[i].resize(tracking[i].size()); + } + + for (uint32_t v = 0; v < tracking[i].size(); v += 1) { + if (nd->vars[v].id == UINT32_MAX || nd->vars[v].enabled == false) { + continue; + } + + // Take the current variable value. + const Variant current_value = + nd->node->get(nd->vars[v].var.name); + + // Compare the current value with the one taken during the start. + if (p_synchronizer->compare( + tracking[i][v], + current_value) == false) { + diff[i][v].is_different = true; + diff[i][v].value = current_value; + } + } + } + + tracking.clear(); +} + +bool SceneDiff::is_tracking_in_progress() const { + return start_tracking_count > 0; +} diff --git a/modules/network_synchronizer/scene_diff.h b/modules/network_synchronizer/scene_diff.h new file mode 100644 index 000000000..84814bb88 --- /dev/null +++ b/modules/network_synchronizer/scene_diff.h @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* scene_diff.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#ifndef SCENE_DIFF_H +#define SCENE_DIFF_H + +#include "core/object/class_db.h" +#include "net_utilities.h" + +class SceneSynchronizer; + +struct VarDiff { + bool is_different = false; + Variant value; +}; + +/// This class is used to track the scene changes during a particular period of +/// the frame. You can use it to generate partial FrameSnapshot that contains +/// only portion of a change. +class SceneDiff : public Object { + GDCLASS(SceneDiff, Object); + + friend class SceneSynchronizer; + + static void _bind_methods(); + + uint32_t start_tracking_count = 0; + LocalVector> tracking; + LocalVector> diff; + +public: + SceneDiff() = default; + + void start_tracking_scene_changes(const LocalVector &p_nodes); + void stop_tracking_scene_changes(const SceneSynchronizer *p_synchronizer); + + bool is_tracking_in_progress() const; +}; + +#endif // SCENE_DIFF_H diff --git a/modules/network_synchronizer/scene_synchronizer.cpp b/modules/network_synchronizer/scene_synchronizer.cpp new file mode 100644 index 000000000..6433757b9 --- /dev/null +++ b/modules/network_synchronizer/scene_synchronizer.cpp @@ -0,0 +1,3052 @@ +/*************************************************************************/ +/* scene_synchronizer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "scene_synchronizer.h" + +#include "networked_controller.h" +#include "scene/main/window.h" +#include "scene_diff.h" + +void SceneSynchronizer::_bind_methods() { + BIND_ENUM_CONSTANT(CHANGE) + BIND_ENUM_CONSTANT(SYNC_RECOVER) + BIND_ENUM_CONSTANT(SYNC_RESET) + BIND_ENUM_CONSTANT(SYNC_REWIND) + BIND_ENUM_CONSTANT(END_SYNC) + BIND_ENUM_CONSTANT(DEFAULT) + BIND_ENUM_CONSTANT(SYNC) + BIND_ENUM_CONSTANT(ALWAYS) + + ClassDB::bind_method(D_METHOD("reset_synchronizer_mode"), &SceneSynchronizer::reset_synchronizer_mode); + ClassDB::bind_method(D_METHOD("clear"), &SceneSynchronizer::clear); + + ClassDB::bind_method(D_METHOD("set_server_notify_state_interval", "interval"), &SceneSynchronizer::set_server_notify_state_interval); + ClassDB::bind_method(D_METHOD("get_server_notify_state_interval"), &SceneSynchronizer::get_server_notify_state_interval); + + ClassDB::bind_method(D_METHOD("set_comparison_float_tolerance", "tolerance"), &SceneSynchronizer::set_comparison_float_tolerance); + ClassDB::bind_method(D_METHOD("get_comparison_float_tolerance"), &SceneSynchronizer::get_comparison_float_tolerance); + + ClassDB::bind_method(D_METHOD("register_node", "node"), &SceneSynchronizer::register_node_gdscript); + ClassDB::bind_method(D_METHOD("unregister_node", "node"), &SceneSynchronizer::unregister_node); + ClassDB::bind_method(D_METHOD("get_node_id", "node"), &SceneSynchronizer::get_node_id); + ClassDB::bind_method(D_METHOD("get_node_from_id", "id"), &SceneSynchronizer::get_node_from_id); + + ClassDB::bind_method(D_METHOD("register_variable", "node", "variable", "on_change_notify", "flags"), &SceneSynchronizer::register_variable, DEFVAL(StringName()), DEFVAL(NetEventFlag::DEFAULT)); + ClassDB::bind_method(D_METHOD("unregister_variable", "node", "variable"), &SceneSynchronizer::unregister_variable); + ClassDB::bind_method(D_METHOD("get_variable_id", "node", "variable"), &SceneSynchronizer::get_variable_id); + + ClassDB::bind_method(D_METHOD("start_node_sync", "node"), &SceneSynchronizer::start_node_sync); + ClassDB::bind_method(D_METHOD("stop_node_sync", "node"), &SceneSynchronizer::stop_node_sync); + ClassDB::bind_method(D_METHOD("is_node_sync", "node"), &SceneSynchronizer::is_node_sync); + + ClassDB::bind_method(D_METHOD("set_skip_rewinding", "node", "variable", "skip_rewinding"), &SceneSynchronizer::set_skip_rewinding); + + ClassDB::bind_method(D_METHOD("track_variable_changes", "node", "variable", "object", "method", "flags"), &SceneSynchronizer::track_variable_changes, DEFVAL(NetEventFlag::DEFAULT)); + ClassDB::bind_method(D_METHOD("untrack_variable_changes", "node", "variable", "object", "method"), &SceneSynchronizer::untrack_variable_changes); + + ClassDB::bind_method(D_METHOD("set_node_as_controlled_by", "node", "controller"), &SceneSynchronizer::set_node_as_controlled_by); + + ClassDB::bind_method(D_METHOD("controller_add_dependency", "controller", "node"), &SceneSynchronizer::controller_add_dependency); + ClassDB::bind_method(D_METHOD("controller_remove_dependency", "controller", "node"), &SceneSynchronizer::controller_remove_dependency); + ClassDB::bind_method(D_METHOD("controller_get_dependency_count", "controller"), &SceneSynchronizer::controller_get_dependency_count); + ClassDB::bind_method(D_METHOD("controller_get_dependency", "controller", "index"), &SceneSynchronizer::controller_get_dependency); + + ClassDB::bind_method(D_METHOD("register_process", "node", "function"), &SceneSynchronizer::register_process); + ClassDB::bind_method(D_METHOD("unregister_process", "node", "function"), &SceneSynchronizer::unregister_process); + + ClassDB::bind_method(D_METHOD("start_tracking_scene_changes", "diff_handle"), &SceneSynchronizer::start_tracking_scene_changes); + ClassDB::bind_method(D_METHOD("stop_tracking_scene_changes", "diff_handle"), &SceneSynchronizer::stop_tracking_scene_changes); + ClassDB::bind_method(D_METHOD("pop_scene_changes", "diff_handle"), &SceneSynchronizer::pop_scene_changes); + ClassDB::bind_method(D_METHOD("apply_scene_changes", "sync_data"), &SceneSynchronizer::apply_scene_changes); + + ClassDB::bind_method(D_METHOD("is_recovered"), &SceneSynchronizer::is_recovered); + ClassDB::bind_method(D_METHOD("is_resetted"), &SceneSynchronizer::is_resetted); + ClassDB::bind_method(D_METHOD("is_rewinding"), &SceneSynchronizer::is_rewinding); + ClassDB::bind_method(D_METHOD("is_end_sync"), &SceneSynchronizer::is_end_sync); + + ClassDB::bind_method(D_METHOD("force_state_notify"), &SceneSynchronizer::force_state_notify); + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SceneSynchronizer::set_enabled); + ClassDB::bind_method(D_METHOD("set_peer_networking_enable", "peer", "enabled"), &SceneSynchronizer::set_peer_networking_enable); + ClassDB::bind_method(D_METHOD("get_peer_networking_enable", "peer"), &SceneSynchronizer::is_peer_networking_enable); + + ClassDB::bind_method(D_METHOD("is_server"), &SceneSynchronizer::is_server); + ClassDB::bind_method(D_METHOD("is_client"), &SceneSynchronizer::is_client); + ClassDB::bind_method(D_METHOD("is_networked"), &SceneSynchronizer::is_networked); + + ClassDB::bind_method(D_METHOD("_on_peer_connected"), &SceneSynchronizer::_on_peer_connected); + ClassDB::bind_method(D_METHOD("_on_peer_disconnected"), &SceneSynchronizer::_on_peer_disconnected); + + ClassDB::bind_method(D_METHOD("_on_node_removed"), &SceneSynchronizer::_on_node_removed); + + ClassDB::bind_method(D_METHOD("_rpc_send_state"), &SceneSynchronizer::_rpc_send_state); + ClassDB::bind_method(D_METHOD("_rpc_notify_need_full_snapshot"), &SceneSynchronizer::_rpc_notify_need_full_snapshot); + ClassDB::bind_method(D_METHOD("_rpc_set_network_enabled", "enabled"), &SceneSynchronizer::_rpc_set_network_enabled); + ClassDB::bind_method(D_METHOD("_rpc_notify_peer_status", "enabled"), &SceneSynchronizer::_rpc_notify_peer_status); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "server_notify_state_interval", PROPERTY_HINT_RANGE, "0.001,10.0,0.0001"), "set_server_notify_state_interval", "get_server_notify_state_interval"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "comparison_float_tolerance", PROPERTY_HINT_RANGE, "0.000001,0.01,0.000001"), "set_comparison_float_tolerance", "get_comparison_float_tolerance"); + + ADD_SIGNAL(MethodInfo("sync_started")); + ADD_SIGNAL(MethodInfo("sync_paused")); +} + +void SceneSynchronizer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (Engine::get_singleton()->is_editor_hint()) + return; + + // TODO add a signal that allows to not check this each frame. + if (unlikely(peer_ptr != get_multiplayer()->get_network_peer().ptr())) { + reset_synchronizer_mode(); + } + + const int lowest_priority_number = INT32_MAX; + ERR_FAIL_COND_MSG(get_process_priority() != lowest_priority_number, "The process priority MUST not be changed, it's likely there is a better way of doing what you are trying to do, if you really need it please open an issue."); + + process(); + } break; + case NOTIFICATION_ENTER_TREE: { + if (Engine::get_singleton()->is_editor_hint()) + return; + + clear(); + reset_synchronizer_mode(); + + get_multiplayer()->connect(SNAME("network_peer_connected"), Callable(this, SNAME("_on_peer_connected"))); + get_multiplayer()->connect(SNAME("network_peer_disconnected"), Callable(this, SNAME("_on_peer_disconnected"))); + + get_tree()->connect(SNAME("node_removed"), Callable(this, SNAME("_on_node_removed"))); + + // Make sure to reset all the assigned controllers. + reset_controllers(); + + // Init the peers already connected. + if (get_tree()->get_multiplayer()->get_network_peer().is_valid()) { + const Vector peer_ids = get_tree()->get_multiplayer()->get_network_connected_peers(); + const int *peer_ids_ptr = peer_ids.ptr(); + for (int i = 0; i < peer_ids.size(); i += 1) { + _on_peer_connected(peer_ids_ptr[i]); + } + } + + } break; + case NOTIFICATION_EXIT_TREE: { + if (Engine::get_singleton()->is_editor_hint()) + return; + + clear_peers(); + + get_multiplayer()->disconnect(SNAME("network_peer_connected"), Callable(this, SNAME("_on_peer_connected"))); + get_multiplayer()->disconnect(SNAME("network_peer_disconnected"), Callable(this, SNAME("_on_peer_disconnected"))); + + get_tree()->disconnect(SNAME("node_removed"), Callable(this, SNAME("_on_node_removed"))); + + clear(); + + if (synchronizer) { + memdelete(synchronizer); + synchronizer = nullptr; + synchronizer_type = SYNCHRONIZER_TYPE_NULL; + } + + set_physics_process_internal(false); + + // Make sure to reset all the assigned controllers. + reset_controllers(); + } + } +} + +SceneSynchronizer::SceneSynchronizer() { + rpc_config(SNAME("_rpc_send_state"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_RELIABLE); + rpc_config(SNAME("_rpc_notify_need_full_snapshot"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_RELIABLE); + rpc_config(SNAME("_rpc_set_network_enabled"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_RELIABLE); + rpc_config(SNAME("_rpc_notify_peer_status"), MultiplayerAPI::RPC_MODE_REMOTE, MultiplayerPeer::TRANSFER_MODE_RELIABLE); + + // Avoid too much useless re-allocations. + event_listener.reserve(100); +} + +SceneSynchronizer::~SceneSynchronizer() { + clear(); + if (synchronizer) { + memdelete(synchronizer); + synchronizer = nullptr; + synchronizer_type = SYNCHRONIZER_TYPE_NULL; + } +} + +void SceneSynchronizer::set_server_notify_state_interval(real_t p_interval) { + server_notify_state_interval = p_interval; +} + +real_t SceneSynchronizer::get_server_notify_state_interval() const { + return server_notify_state_interval; +} + +void SceneSynchronizer::set_comparison_float_tolerance(real_t p_tolerance) { + comparison_float_tolerance = p_tolerance; +} + +real_t SceneSynchronizer::get_comparison_float_tolerance() const { + return comparison_float_tolerance; +} + +NetUtility::NodeData *SceneSynchronizer::register_node(Node *p_node) { + ERR_FAIL_COND_V(p_node == nullptr, nullptr); + + NetUtility::NodeData *nd = find_node_data(p_node); + if (unlikely(nd == nullptr)) { + nd = memnew(NetUtility::NodeData); + nd->id = UINT32_MAX; + nd->instance_id = p_node->get_instance_id(); + nd->node = p_node; + + NetworkedController *controller = Object::cast_to(p_node); + if (controller) { + if (unlikely(controller->has_scene_synchronizer())) { + ERR_FAIL_V_MSG(nullptr, "This controller already has a synchronizer. This is a bug!"); + } + + nd->is_controller = true; + controller->set_scene_synchronizer(this); + dirty_peers(); + } + + add_node_data(nd); + + NET_DEBUG_PRINT("New node registered" + (generate_id ? String(" #ID: ") + itos(nd->id) : "") + " : " + p_node->get_path()); + } + + return nd; +} + +uint32_t SceneSynchronizer::register_node_gdscript(Node *p_node) { + NetUtility::NodeData *nd = register_node(p_node); + if (unlikely(nd == nullptr)) { + return UINT32_MAX; + } + return nd->id; +} + +void SceneSynchronizer::unregister_node(Node *p_node) { + ERR_FAIL_COND(p_node == nullptr); + + NetUtility::NodeData *nd = find_node_data(p_node); + if (unlikely(nd == nullptr)) { + // Nothing to do. + return; + } + + drop_node_data(nd); +} + +uint32_t SceneSynchronizer::get_node_id(Node *p_node) { + ERR_FAIL_COND_V(p_node == nullptr, UINT32_MAX); + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND_V_MSG(nd == nullptr, UINT32_MAX, "This node " + p_node->get_path() + " is not yet registered, so there is not an available ID."); + return nd->id; +} + +Node *SceneSynchronizer::get_node_from_id(uint32_t p_id) { + NetUtility::NodeData *nd = get_node_data(p_id); + ERR_FAIL_COND_V_MSG(nd == nullptr, nullptr, "The ID " + itos(p_id) + " is not assigned to any node."); + return nd->node; +} + +void SceneSynchronizer::register_variable(Node *p_node, const StringName &p_variable, const StringName &p_on_change_notify, NetEventFlag p_flags) { + ERR_FAIL_COND(p_node == nullptr); + ERR_FAIL_COND(p_variable == StringName()); + + NetUtility::NodeData *node_data = register_node(p_node); + ERR_FAIL_COND(node_data == nullptr); + + const int index = node_data->vars.find(p_variable); + if (index == -1) { + // The variable is not yet registered. + const Variant old_val = p_node->get(p_variable); + const int var_id = generate_id ? node_data->vars.size() : UINT32_MAX; + node_data->vars.push_back( + NetUtility::VarData( + var_id, + p_variable, + old_val, + false, + true)); + } else { + // Make sure the var is active. + node_data->vars[index].enabled = true; + } + +#ifdef DEBUG_ENABLED + for (uint32_t v = 0; v < node_data->vars.size(); v += 1) { + // This can't happen, because the ID is always consecutive, or UINT32_MAX. + CRASH_COND(node_data->vars[v].id != v && node_data->vars[v].id != UINT32_MAX); + } +#endif + + if (p_on_change_notify != StringName()) { + track_variable_changes(p_node, p_variable, p_node, p_on_change_notify, p_flags); + } + + if (synchronizer) { + synchronizer->on_variable_added(node_data, p_variable); + } +} + +void SceneSynchronizer::unregister_variable(Node *p_node, const StringName &p_variable) { + ERR_FAIL_COND(p_node == nullptr); + ERR_FAIL_COND(p_variable == StringName()); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND(nd == nullptr); + + const int64_t index = nd->vars.find(p_variable); + ERR_FAIL_COND(index == -1); + + const NetVarId var_id = index; + + // Never remove the variable values, because the order of the vars matters. + nd->vars[index].enabled = false; + + for (int i = 0; i < nd->vars[var_id].change_listeners.size(); i += 1) { + const uint32_t event_index = nd->vars[var_id].change_listeners[i]; + // Just erase the tracked variables without removing the listener to + // keep the order. + NetUtility::NodeChangeListener ncl; + ncl.node_data = nd; + ncl.var_id = var_id; + event_listener[event_index].watching_vars.erase(ncl); + } + + nd->vars[index].change_listeners.clear(); +} + +void SceneSynchronizer::start_node_sync(const Node *p_node) { + ERR_FAIL_COND(p_node == nullptr); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND(nd == nullptr); + + nd->sync_enabled = true; +} + +void SceneSynchronizer::stop_node_sync(const Node *p_node) { + ERR_FAIL_COND(p_node == nullptr); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND(nd == nullptr); + + nd->sync_enabled = false; +} + +bool SceneSynchronizer::is_node_sync(const Node *p_node) const { + ERR_FAIL_COND_V(p_node == nullptr, false); + + const NetUtility::NodeData *nd = find_node_data(p_node); + if (nd == nullptr) { + return false; + } + + return nd->sync_enabled; +} + +uint32_t SceneSynchronizer::get_variable_id(Node *p_node, const StringName &p_variable) { + ERR_FAIL_COND_V(p_node == nullptr, UINT32_MAX); + ERR_FAIL_COND_V(p_variable == StringName(), UINT32_MAX); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND_V_MSG(nd == nullptr, UINT32_MAX, "This node " + p_node->get_path() + "is not registered."); + + const int64_t index = nd->vars.find(p_variable); + ERR_FAIL_COND_V_MSG(index == -1, UINT32_MAX, "This variable " + p_node->get_path() + ":" + p_variable + " is not registered."); + + return uint32_t(index); +} + +void SceneSynchronizer::set_skip_rewinding(Node *p_node, const StringName &p_variable, bool p_skip_rewinding) { + ERR_FAIL_COND(p_node == nullptr); + ERR_FAIL_COND(p_variable == StringName()); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND(nd == nullptr); + + const int64_t index = nd->vars.find(p_variable); + ERR_FAIL_COND(index == -1); + + nd->vars[index].skip_rewinding = p_skip_rewinding; +} + +void SceneSynchronizer::track_variable_changes(Node *p_node, const StringName &p_variable, Object *p_object, const StringName &p_method, NetEventFlag p_flags) { + ERR_FAIL_COND(p_node == nullptr); + ERR_FAIL_COND(p_variable == StringName()); + ERR_FAIL_COND(p_method == StringName()); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND_MSG(nd == nullptr, "You need to register the variable to track its changes."); + + const int64_t v = nd->vars.find(p_variable); + ERR_FAIL_COND_MSG(v == -1, "You need to register the variable to track its changes."); + + const NetVarId var_id = v; + + int64_t index; + + { + NetUtility::ChangeListener listener; + listener.object_id = p_object->get_instance_id(); + listener.method = p_method; + + index = event_listener.find(listener); + + if (-1 == index) { + // Add it. + listener.flag = p_flags; + listener.method_argument_count = UINT32_MAX; + + // Search the method and get the argument count. + List methods; + p_object->get_method_list(&methods); + for (List::Element *e = methods.front(); e != nullptr; e = e->next()) { + if (e->get().name != p_method) { + continue; + } + + listener.method_argument_count = e->get().arguments.size(); + + break; + } + ERR_FAIL_COND_MSG(listener.method_argument_count == UINT32_MAX, "The method " + p_method + " doesn't exist in this node: " + p_node->get_path()); + + index = event_listener.size(); + event_listener.push_back(listener); + } else { + ERR_FAIL_COND_MSG(event_listener[index].flag != p_flags, "The event listener is already registered with the flag: " + itos(event_listener[index].flag) + ". You can't specify a different one."); + } + } + + NetUtility::NodeChangeListener ncl; + ncl.node_data = nd; + ncl.var_id = var_id; + + if (event_listener[index].watching_vars.find(ncl) != -1) { + return; + } + + event_listener[index].watching_vars.push_back(ncl); + nd->vars[var_id].change_listeners.push_back(index); +} + +void SceneSynchronizer::untrack_variable_changes(Node *p_node, const StringName &p_variable, Object *p_object, const StringName &p_method) { + ERR_FAIL_COND(p_node == nullptr); + ERR_FAIL_COND(p_variable == StringName()); + ERR_FAIL_COND(p_method == StringName()); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND_MSG(nd == nullptr, "This not is not registered."); + + const int64_t v = nd->vars.find(p_variable); + ERR_FAIL_COND_MSG(v == -1, "This variable is not registered."); + + const NetVarId var_id = v; + + NetUtility::ChangeListener listener; + listener.object_id = p_object->get_instance_id(); + listener.method = p_method; + + const int64_t index = event_listener.find(listener); + + ERR_FAIL_COND_MSG(index == -1, "The variable is not know."); + + NetUtility::NodeChangeListener ncl; + ncl.node_data = nd; + ncl.var_id = var_id; + + event_listener[index].watching_vars.erase(ncl); + nd->vars[var_id].change_listeners.erase(index); + + // Don't remove the listener to preserve the order. +} + +void SceneSynchronizer::set_node_as_controlled_by(Node *p_node, Node *p_controller) { + NetUtility::NodeData *nd = register_node(p_node); + ERR_FAIL_COND(nd == nullptr); + ERR_FAIL_COND_MSG(nd->is_controller, "A controller can't be controlled by another controller."); + + if (nd->controlled_by) { + // Put the node back into global. + nd->controlled_by->controlled_nodes.erase(nd); + nd->controlled_by = nullptr; + } + + if (p_controller) { + NetworkedController *c = Object::cast_to(p_controller); + ERR_FAIL_COND_MSG(c == nullptr, "The controller must be a node of type: NetworkedController."); + + NetUtility::NodeData *controller_node_data = register_node(p_controller); + ERR_FAIL_COND(controller_node_data == nullptr); + ERR_FAIL_COND_MSG(controller_node_data->is_controller == false, "The node can be only controlled by a controller."); + +#ifdef DEBUG_ENABLED + CRASH_COND_MSG(controller_node_data->controlled_nodes.find(nd) != -1, "There is a bug the same node is added twice into the controlled_nodes."); +#endif + controller_node_data->controlled_nodes.push_back(nd); + nd->controlled_by = controller_node_data; + } + +#ifdef DEBUG_ENABLED + // Make sure that all controlled nodes are into the proper controller. + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + for (uint32_t y = 0; y < node_data_controllers[i]->controlled_nodes.size(); y += 1) { + CRASH_COND(node_data_controllers[i]->controlled_nodes[y]->controlled_by != node_data_controllers[i]); + } + } +#endif +} + +void SceneSynchronizer::controller_add_dependency(Node *p_controller, Node *p_node) { + if (is_client() == false) { + // Nothing to do. + return; + } + + NetUtility::NodeData *controller_nd = find_node_data(p_controller); + ERR_FAIL_COND_MSG(controller_nd == nullptr, "The passed controller (" + p_controller->get_path() + ") is not registered."); + ERR_FAIL_COND_MSG(controller_nd->is_controller == false, "The node passed as controller (" + p_controller->get_path() + ") is not a controller."); + + NetUtility::NodeData *node_nd = find_node_data(p_node); + ERR_FAIL_COND_MSG(node_nd == nullptr, "The passed node (" + p_node->get_path() + ") is not registered."); + ERR_FAIL_COND_MSG(node_nd->is_controller, "The node (" + p_node->get_path() + ") set as dependency is supposed to be just a node."); + ERR_FAIL_COND_MSG(node_nd->controlled_by != nullptr, "The node (" + p_node->get_path() + ") set as dependency is supposed to be just a node."); + + const int64_t index = controller_nd->dependency_nodes.find(node_nd); + if (index == -1) { + controller_nd->dependency_nodes.push_back(node_nd); + controller_nd->dependency_nodes_end.push_back(UINT32_MAX); + } else { + // We already have this dependency, just make sure we don't delete it. + controller_nd->dependency_nodes_end[index] = UINT32_MAX; + } +} + +void SceneSynchronizer::controller_remove_dependency(Node *p_controller, Node *p_node) { + if (is_client() == false) { + // Nothing to do. + return; + } + + NetUtility::NodeData *controller_nd = find_node_data(p_controller); + ERR_FAIL_COND_MSG(controller_nd == nullptr, "The passed controller (" + p_controller->get_path() + ") is not registered."); + ERR_FAIL_COND_MSG(controller_nd->is_controller == false, "The node passed as controller (" + p_controller->get_path() + ") is not a controller."); + + NetUtility::NodeData *node_nd = find_node_data(p_node); + ERR_FAIL_COND_MSG(node_nd == nullptr, "The passed node (" + p_node->get_path() + ") is not registered."); + ERR_FAIL_COND_MSG(node_nd->is_controller, "The node (" + p_node->get_path() + ") set as dependency is supposed to be just a node."); + ERR_FAIL_COND_MSG(node_nd->controlled_by != nullptr, "The node (" + p_node->get_path() + ") set as dependency is supposed to be just a node."); + + const int64_t index = controller_nd->dependency_nodes.find(node_nd); + if (index == -1) { + // Nothing to do, this node is not a dependency. + return; + } + + // Instead to remove the dependency immeditaly we have to postpone it till + // the server confirms the valitity via state. + // This operation is required otherwise the dependency is remvoved too early, + // and an eventual rewind may miss it. + // The actual removal is performed at the end of the sync. + controller_nd->dependency_nodes_end[index] = + static_cast(controller_nd->node)->get_current_input_id(); +} + +int SceneSynchronizer::controller_get_dependency_count(Node *p_controller) const { + if (is_client() == false) { + // Nothing to do. + return 0; + } + + const NetUtility::NodeData *controller_nd = find_node_data(p_controller); + ERR_FAIL_COND_V_MSG(controller_nd == nullptr, 0, "The passed controller (" + p_controller->get_path() + ") is not registered."); + ERR_FAIL_COND_V_MSG(controller_nd->is_controller == false, 0, "The node passed as controller (" + p_controller->get_path() + ") is not a controller."); + return controller_nd->dependency_nodes.size(); +} + +Node *SceneSynchronizer::controller_get_dependency(Node *p_controller, int p_index) { + if (is_client() == false) { + // Nothing to do. + return nullptr; + } + + NetUtility::NodeData *controller_nd = find_node_data(p_controller); + ERR_FAIL_COND_V_MSG(controller_nd == nullptr, nullptr, "The passed controller (" + p_controller->get_path() + ") is not registered."); + ERR_FAIL_COND_V_MSG(controller_nd->is_controller == false, nullptr, "The node passed as controller (" + p_controller->get_path() + ") is not a controller."); + ERR_FAIL_INDEX_V(p_index, int(controller_nd->dependency_nodes.size()), nullptr); + + return controller_nd->dependency_nodes[p_index]->node; +} + +void SceneSynchronizer::register_process(Node *p_node, const StringName &p_function) { + ERR_FAIL_COND(p_node == nullptr); + ERR_FAIL_COND(p_function == StringName()); + NetUtility::NodeData *node_data = register_node(p_node); + ERR_FAIL_COND(node_data == nullptr); + + if (node_data->functions.find(p_function) == -1) { + node_data->functions.push_back(p_function); + } +} + +void SceneSynchronizer::unregister_process(Node *p_node, const StringName &p_function) { + ERR_FAIL_COND(p_node == nullptr); + ERR_FAIL_COND(p_function == StringName()); + NetUtility::NodeData *node_data = register_node(p_node); + ERR_FAIL_COND(node_data == nullptr); + node_data->functions.erase(p_function); +} + +void SceneSynchronizer::start_tracking_scene_changes(Object *p_diff_handle) const { + ERR_FAIL_COND_MSG(get_tree()->get_multiplayer()->is_network_server() == false, "This function is supposed to be called only on server."); + SceneDiff *diff = Object::cast_to(p_diff_handle); + ERR_FAIL_COND_MSG(diff == nullptr, "The object is not a SceneDiff class."); + + diff->start_tracking_scene_changes(organized_node_data); +} + +void SceneSynchronizer::stop_tracking_scene_changes(Object *p_diff_handle) const { + ERR_FAIL_COND_MSG(get_tree()->get_multiplayer()->is_network_server() == false, "This function is supposed to be called only on server."); + SceneDiff *diff = Object::cast_to(p_diff_handle); + ERR_FAIL_COND_MSG(diff == nullptr, "The object is not a SceneDiff class."); + + diff->stop_tracking_scene_changes(this); +} + +Variant SceneSynchronizer::pop_scene_changes(Object *p_diff_handle) const { + ERR_FAIL_COND_V_MSG( + synchronizer_type != SYNCHRONIZER_TYPE_SERVER, + Variant(), + "This function is supposed to be called only on server."); + + SceneDiff *diff = Object::cast_to(p_diff_handle); + ERR_FAIL_COND_V_MSG( + diff == nullptr, + Variant(), + "The object is not a SceneDiff class."); + + ERR_FAIL_COND_V_MSG( + diff->is_tracking_in_progress(), + Variant(), + "You can't pop the changes while the tracking is still in progress."); + + // Generates a sync_data and returns it. + Vector ret; + for (NetNodeId node_id = 0; node_id < diff->diff.size(); node_id += 1) { + if (diff->diff[node_id].size() == 0) { + // Nothing to do. + continue; + } + + bool node_id_in_ret = false; + for (NetVarId var_id = 0; var_id < diff->diff[node_id].size(); var_id += 1) { + if (diff->diff[node_id][var_id].is_different == false) { + continue; + } + if (node_id_in_ret == false) { + node_id_in_ret = true; + // Set the node id. + ret.push_back(node_id); + } + ret.push_back(var_id); + ret.push_back(diff->diff[node_id][var_id].value); + } + if (node_id_in_ret) { + // Close the Node data. + ret.push_back(Variant()); + } + } + + // Clear the diff data. + diff->diff.clear(); + + return ret.size() > 0 ? Variant(ret) : Variant(); +} + +void SceneSynchronizer::apply_scene_changes(const Variant &p_sync_data) { + ERR_FAIL_COND_MSG(is_client() == false, "This function is not supposed to be called on server."); + + ClientSynchronizer *client_sync = static_cast(synchronizer); + + change_events_begin(NetEventFlag::CHANGE); + + const bool success = client_sync->parse_sync_data( + p_sync_data, + this, + + // Parse the Node: + [](void *p_user_pointer, NetUtility::NodeData *p_node_data) {}, + + // Parse controller: + [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id) {}, + + // Parse variable: + [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_var_id, const Variant &p_value) { + SceneSynchronizer *scene_sync = static_cast(p_user_pointer); + + const Variant current_val = p_node_data->vars[p_var_id].var.value; + + if (scene_sync->compare(current_val, p_value) == false) { + // There is a difference. + // Set the new value. + p_node_data->vars[p_var_id].var.value = p_value; + p_node_data->node->set( + p_node_data->vars[p_var_id].var.name, + p_value); + + // Add an event. + scene_sync->change_event_add( + p_node_data, + p_var_id, + current_val); + } + }); + + if (success == false) { + NET_DEBUG_ERR("Scene changes:"); + NET_DEBUG_ERR(p_sync_data); + } + + change_events_flush(); +} + +bool SceneSynchronizer::is_recovered() const { + return recover_in_progress; +} + +bool SceneSynchronizer::is_resetted() const { + return reset_in_progress; +} + +bool SceneSynchronizer::is_rewinding() const { + return rewinding_in_progress; +} + +bool SceneSynchronizer::is_end_sync() const { + return end_sync; +} + +void SceneSynchronizer::force_state_notify() { + ERR_FAIL_COND(is_server() == false); + ServerSynchronizer *r = static_cast(synchronizer); + // + 1.0 is just a ridiculous high number to be sure to avoid float + // precision error. + r->state_notifier_timer = get_server_notify_state_interval() + 1.0; +} + +void SceneSynchronizer::dirty_peers() { + peer_dirty = true; +} + +void SceneSynchronizer::set_enabled(bool p_enable) { + ERR_FAIL_COND_MSG(synchronizer_type == SYNCHRONIZER_TYPE_SERVER, "The server is always enabled."); + if (synchronizer_type == SYNCHRONIZER_TYPE_CLIENT) { + rpc_id(1, SNAME("_rpc_set_network_enabled"), p_enable); + if (p_enable == false) { + // If the peer want to disable, we can disable it locally + // immediately. When it wants to enable the networking, the server + // must be notified so it decides when to start the networking + // again. + static_cast(synchronizer)->set_enabled(p_enable); + } + } else if (synchronizer_type == SYNCHRONIZER_TYPE_NONETWORK) { + set_peer_networking_enable(0, p_enable); + } +} + +bool SceneSynchronizer::is_enabled() const { + ERR_FAIL_COND_V_MSG(synchronizer_type == SYNCHRONIZER_TYPE_SERVER, false, "The server is always enabled."); + if (likely(synchronizer_type == SYNCHRONIZER_TYPE_CLIENT)) { + return static_cast(synchronizer)->enabled; + } else if (synchronizer_type == SYNCHRONIZER_TYPE_NONETWORK) { + return static_cast(synchronizer)->enabled; + } else { + return true; + } +} + +void SceneSynchronizer::set_peer_networking_enable(int p_peer, bool p_enable) { + if (synchronizer_type == SYNCHRONIZER_TYPE_SERVER) { + ERR_FAIL_COND_MSG(p_peer == 1, "Disable the server is not possible."); + + NetUtility::PeerData *pd = peer_data.lookup_ptr(p_peer); + ERR_FAIL_COND_MSG(pd == nullptr, "The peer: " + itos(p_peer) + " is not know. [bug]"); + + if (pd->enabled == p_enable) { + // Nothing to do. + return; + } + + pd->enabled = p_enable; + // Set to true, so next time this peer connects a full snapshot is sent. + pd->force_notify_snapshot = true; + pd->need_full_snapshot = true; + + dirty_peers(); + + // Just notify the peer status. + rpc_id(p_peer, SNAME("_rpc_notify_peer_status"), p_enable); + } else { + ERR_FAIL_COND_MSG(synchronizer_type != SYNCHRONIZER_TYPE_NONETWORK, "At this point no network is expected."); + static_cast(synchronizer)->set_enabled(p_enable); + } +} + +bool SceneSynchronizer::is_peer_networking_enable(int p_peer) const { + if (synchronizer_type == SYNCHRONIZER_TYPE_SERVER) { + if (p_peer == 1) { + // Server is always enabled. + return true; + } + + NetUtility::PeerData *pd = peer_data.lookup_ptr(p_peer); + ERR_FAIL_COND_V_MSG(pd == nullptr, false, "The peer: " + itos(p_peer) + " is not know. [bug]"); + return pd->enabled; + } else { + ERR_FAIL_COND_V_MSG(synchronizer_type != SYNCHRONIZER_TYPE_NONETWORK, false, "At this point no network is expected."); + return static_cast(synchronizer)->is_enabled(); + } +} + +void SceneSynchronizer::_on_peer_connected(int p_peer) { + peer_data.insert(p_peer, NetUtility::PeerData()); + dirty_peers(); +} + +void SceneSynchronizer::_on_peer_disconnected(int p_peer) { + peer_data.remove(p_peer); + + // Notify all controllers that this peer is gone. + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + NetworkedController *c = static_cast(node_data_controllers[i]->node); + c->controller->deactivate_peer(p_peer); + } +} + +void SceneSynchronizer::_on_node_removed(Node *p_node) { + unregister_node(p_node); +} + +void SceneSynchronizer::reset_synchronizer_mode() { + set_physics_process_internal(false); + const bool was_generating_ids = generate_id; + generate_id = false; + + if (synchronizer) { + memdelete(synchronizer); + synchronizer = nullptr; + synchronizer_type = SYNCHRONIZER_TYPE_NULL; + } + + peer_ptr = get_multiplayer() == nullptr ? nullptr : get_multiplayer()->get_network_peer().ptr(); + + if (get_tree() == nullptr || get_tree()->get_multiplayer()->get_network_peer().is_null()) { + synchronizer_type = SYNCHRONIZER_TYPE_NONETWORK; + synchronizer = memnew(NoNetSynchronizer(this)); + generate_id = true; + + } else if (get_tree()->get_multiplayer()->is_network_server()) { + synchronizer_type = SYNCHRONIZER_TYPE_SERVER; + synchronizer = memnew(ServerSynchronizer(this)); + generate_id = true; + } else { + synchronizer_type = SYNCHRONIZER_TYPE_CLIENT; + synchronizer = memnew(ClientSynchronizer(this)); + } + + // Always runs the SceneSynchronizer last. + const int lowest_priority_number = INT32_MAX; + set_process_priority(lowest_priority_number); + set_physics_process_internal(true); + + if (was_generating_ids != generate_id) { + organized_node_data.resize(node_data.size()); + for (uint32_t i = 0; i < node_data.size(); i += 1) { + if (node_data[i] == nullptr) { + continue; + } + + // Handle the node ID. + if (generate_id) { + node_data[i]->id = i; + organized_node_data[i] = node_data[i]; + } else { + node_data[i]->id = UINT32_MAX; + organized_node_data[i] = nullptr; + } + + // Handle the variables ID. + for (uint32_t v = 0; v < node_data[i]->vars.size(); v += 1) { + if (generate_id) { + node_data[i]->vars[v].id = v; + } else { + node_data[i]->vars[v].id = UINT32_MAX; + } + } + } + } + + // Notify the presence all available nodes and its variables to the synchronizer. + for (uint32_t i = 0; i < node_data.size(); i += 1) { + synchronizer->on_node_added(node_data[i]); + for (uint32_t y = 0; y < node_data[i]->vars.size(); y += 1) { + synchronizer->on_variable_added(node_data[i], node_data[i]->vars[y].var.name); + } + } + + // Reset the controllers. + reset_controllers(); +} + +void SceneSynchronizer::clear() { + // Drop the node_data. + for (uint32_t i = 0; i < node_data.size(); i += 1) { + if (node_data[i] != nullptr) { + drop_node_data(node_data[i]); + } + } + + node_data.reset(); + organized_node_data.reset(); + node_data_controllers.reset(); + event_listener.reset(); + + // Avoid too much useless re-allocations. + event_listener.reserve(100); + + if (synchronizer) { + synchronizer->clear(); + } +} + +void SceneSynchronizer::_rpc_send_state(const Variant &p_snapshot) { + ERR_FAIL_COND_MSG(is_client() == false, "Only clients are suposed to receive the server snapshot."); + static_cast(synchronizer)->receive_snapshot(p_snapshot); +} + +void SceneSynchronizer::_rpc_notify_need_full_snapshot() { + ERR_FAIL_COND_MSG(is_server() == false, "Only the server can receive the request to send a full snapshot."); + + const int sender_peer = get_tree()->get_multiplayer()->get_rpc_sender_id(); + NetUtility::PeerData *pd = peer_data.lookup_ptr(sender_peer); + ERR_FAIL_COND(pd == nullptr); + pd->need_full_snapshot = true; +} + +void SceneSynchronizer::_rpc_set_network_enabled(bool p_enabled) { + ERR_FAIL_COND_MSG(is_server() == false, "The peer status is supposed to be received by the server."); + set_peer_networking_enable( + get_multiplayer()->get_rpc_sender_id(), + p_enabled); +} + +void SceneSynchronizer::_rpc_notify_peer_status(bool p_enabled) { + ERR_FAIL_COND_MSG(is_client() == false, "The peer status is supposed to be received by the client."); + static_cast(synchronizer)->set_enabled(p_enabled); +} + +void SceneSynchronizer::update_peers() { +#ifdef DEBUG_ENABLED + // This function is only called on server. + CRASH_COND(synchronizer_type != SYNCHRONIZER_TYPE_SERVER); +#endif + + if (likely(peer_dirty == false)) { + return; + } + + peer_dirty = false; + + for (OAHashMap::Iterator it = peer_data.iter(); + it.valid; + it = peer_data.next_iter(it)) { + // Validate the peer. + if (it.value->controller_id != UINT32_MAX) { + NetUtility::NodeData *nd = get_node_data(it.value->controller_id); + if (nd == nullptr || + nd->is_controller == false || + nd->node->get_network_master() != (*it.key)) { + // Invalidate the controller id + it.value->controller_id = UINT32_MAX; + } + } + + if (it.value->controller_id == UINT32_MAX) { + // The controller_id is not assigned, search it. + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + if (node_data_controllers[i]->node->get_network_master() == (*it.key)) { + // Controller found. + it.value->controller_id = node_data_controllers[i]->id; + break; + } + } + } + + // Propagate the peer change to controllers. + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + NetworkedController *c = static_cast(node_data_controllers[i]->node); + + if (it.value->controller_id == node_data_controllers[i]->id) { + // This is the controller owned by this peer. + c->get_server_controller()->set_enabled(it.value->enabled); + } else { + // This is a controller owned by another peer. + if (it.value->enabled) { + c->controller->activate_peer(*it.key); + } else { + c->controller->deactivate_peer(*it.key); + } + } + } + } +} + +void SceneSynchronizer::clear_peers() { + peer_data.clear(); + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + NetworkedController *c = static_cast(node_data_controllers[i]->node); + c->controller->clear_peers(); + } +} + +void SceneSynchronizer::change_events_begin(int p_flag) { +#ifdef DEBUG_ENABLED + // This can't happen because at the end these are reset. + CRASH_COND(recover_in_progress); + CRASH_COND(reset_in_progress); + CRASH_COND(rewinding_in_progress); + CRASH_COND(end_sync); +#endif + event_flag = p_flag; + recover_in_progress = NetEventFlag::SYNC & p_flag; + reset_in_progress = NetEventFlag::SYNC_RESET & p_flag; + rewinding_in_progress = NetEventFlag::SYNC_REWIND & p_flag; + end_sync = NetEventFlag::END_SYNC & p_flag; +} + +void SceneSynchronizer::change_event_add(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old) { + for (int i = 0; i < p_node_data->vars[p_var_id].change_listeners.size(); i += 1) { + const uint32_t listener_index = p_node_data->vars[p_var_id].change_listeners[i]; + NetUtility::ChangeListener &listener = event_listener[listener_index]; + if ((listener.flag & event_flag) == 0) { + // Not listening to this event. + continue; + } + + listener.emitted = false; + + NetUtility::NodeChangeListener ncl; + ncl.node_data = p_node_data; + ncl.var_id = p_var_id; + + const int64_t index = listener.watching_vars.find(ncl); +#ifdef DEBUG_ENABLED + // This can't never happen because the `NodeData::change_listeners` + // tracks the correct listener. + CRASH_COND(index == -1); +#endif + listener.watching_vars[index].old_value = p_old; + listener.watching_vars[index].old_set = true; + } + + // Notify the synchronizer. + if (synchronizer) { + synchronizer->on_variable_changed( + p_node_data, + p_var_id, + p_old, + event_flag); + } +} + +void SceneSynchronizer::change_events_flush() { + LocalVector vars; + LocalVector vars_ptr; + + // TODO this can be optimized by storing the changed listener in a separate + // vector. This change must be inserted into the `change_event_add`. + for (uint32_t listener_i = 0; listener_i < event_listener.size(); listener_i += 1) { + NetUtility::ChangeListener &listener = event_listener[listener_i]; + if (listener.emitted) { + continue; + } + listener.emitted = true; + + Object *obj = ObjectDB::get_instance(listener.object_id); + if (obj == nullptr) { + // Setting the flag to 0 so no events trigger this anymore. + listener.flag = NetEventFlag::EMPTY; + listener.object_id = ObjectID(); + listener.method = StringName(); + + // Make sure this listener is not tracking any variable. + for (uint32_t wv = 0; wv < listener.watching_vars.size(); wv += 1) { + NetUtility::NodeData *nd = listener.watching_vars[wv].node_data; + uint32_t var_id = listener.watching_vars[wv].var_id; + nd->vars[var_id].change_listeners.erase(listener_i); + } + listener.watching_vars.clear(); + continue; + } + + // Initialize the arguments + ERR_CONTINUE_MSG(listener.method_argument_count > listener.watching_vars.size(), "This method " + listener.method + " has more arguments than the watched variables. This listener is broken."); + + vars.resize(MIN(listener.watching_vars.size(), listener.method_argument_count)); + vars_ptr.resize(vars.size()); + for (uint32_t v = 0; v < MIN(listener.watching_vars.size(), listener.method_argument_count); v += 1) { + if (listener.watching_vars[v].old_set) { + vars[v] = listener.watching_vars[v].old_value; + listener.watching_vars[v].old_set = false; + } else { + // This value is not changed, so just retrive the current one. + vars[v] = listener.watching_vars[v].node_data->vars[listener.watching_vars[v].var_id].var.value; + } + vars_ptr[v] = vars.ptr() + v; + } + + Callable::CallError e; + obj->call(listener.method, vars_ptr.ptr(), vars_ptr.size(), e); + } + + recover_in_progress = false; + reset_in_progress = false; + rewinding_in_progress = false; + end_sync = false; +} + +void SceneSynchronizer::add_node_data(NetUtility::NodeData *p_node_data) { + if (generate_id) { +#ifdef DEBUG_ENABLED + // When generate_id is true, the id must always be undefined. + CRASH_COND(p_node_data->id != UINT32_MAX); +#endif + p_node_data->id = organized_node_data.size(); + } + +#ifdef DEBUG_ENABLED + // Make sure the registered nodes have an unique ID. + // Due to an engine bug, it's possible to have two different nodes with the + // exact same path: + // - Create a scene. + // - Add a child with the name `BadChild`. + // - Instance the scene into another scene. + // - Add a child, under the instanced scene, with the name `BadChild`. + // Now you have the scene with two different nodes but same path. + for (uint32_t i = 0; i < node_data.size(); i += 1) { + if (node_data[i]->node->get_path() == p_node_data->node->get_path()) { + NET_DEBUG_ERR("You have two different nodes with the same path: " + p_node_data->node->get_path() + ". This will cause troubles. Fix it."); + break; + } + } +#endif + + node_data.push_back(p_node_data); + + if (generate_id) { + organized_node_data.push_back(p_node_data); + } else { + if (p_node_data->id != UINT32_MAX) { + // This node has an ID, make sure to organize it properly. + + if (organized_node_data.size() <= p_node_data->id) { + expand_organized_node_data_vector((p_node_data->id + 1) - organized_node_data.size()); + } + + organized_node_data[p_node_data->id] = p_node_data; + } + } + + if (p_node_data->is_controller) { + node_data_controllers.push_back(p_node_data); + reset_controller(p_node_data); + } + + if (synchronizer) { + synchronizer->on_node_added(p_node_data); + } +} + +void SceneSynchronizer::drop_node_data(NetUtility::NodeData *p_node_data) { + if (synchronizer) { + synchronizer->on_node_removed(p_node_data); + } + + if (p_node_data->controlled_by) { + // This node is controlled by another one, remove from that node. + p_node_data->controlled_by->controlled_nodes.erase(p_node_data); + p_node_data->controlled_by = nullptr; + } + + if (p_node_data->is_controller) { + // This is a controller, make sure to reset the peers. + static_cast(p_node_data->node)->set_scene_synchronizer(nullptr); + dirty_peers(); + node_data_controllers.erase(p_node_data); + } + + node_data.erase(p_node_data); + + if (p_node_data->id < organized_node_data.size()) { + // Never resize this vector to keep it sort. + organized_node_data[p_node_data->id] = nullptr; + } + + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + const int64_t index = node_data_controllers[i]->dependency_nodes.find(p_node_data); + if (index != -1) { + node_data_controllers[i]->dependency_nodes.remove_unordered(index); + node_data_controllers[i]->dependency_nodes_end.remove_unordered(index); + } + } + + // Remove this `NodeData` from any event listener. + for (uint32_t i = 0; i < event_listener.size(); i += 1) { + while (true) { + uint32_t index_to_remove = UINT32_MAX; + + // Search. + for (uint32_t v = 0; v < event_listener[i].watching_vars.size(); v += 1) { + if (event_listener[i].watching_vars[v].node_data == p_node_data) { + index_to_remove = v; + break; + } + } + + if (index_to_remove == UINT32_MAX) { + // Nothing more to do. + break; + } else { + event_listener[i].watching_vars.remove_unordered(index_to_remove); + } + } + } + + memdelete(p_node_data); +} + +void SceneSynchronizer::set_node_data_id(NetUtility::NodeData *p_node_data, NetNodeId p_id) { +#ifdef DEBUG_ENABLED + CRASH_COND_MSG(generate_id, "This function is not supposed to be called, because this instance is generating the IDs"); +#endif + if (organized_node_data.size() <= p_id) { + expand_organized_node_data_vector((p_id + 1) - organized_node_data.size()); + } + p_node_data->id = p_id; + organized_node_data[p_id] = p_node_data; + NET_DEBUG_PRINT("NetNodeId: " + itos(p_id) + " just assigned to: " + p_node_data->node->get_path()); +} + +bool SceneSynchronizer::compare(const Vector2 &p_first, const Vector2 &p_second) const { + return compare(p_first, p_second, comparison_float_tolerance); +} + +bool SceneSynchronizer::compare(const Vector3 &p_first, const Vector3 &p_second) const { + return compare(p_first, p_second, comparison_float_tolerance); +} + +bool SceneSynchronizer::compare(const Variant &p_first, const Variant &p_second) const { + return compare(p_first, p_second, comparison_float_tolerance); +} + +bool SceneSynchronizer::compare(const Vector2 &p_first, const Vector2 &p_second, real_t p_tolerance) { + return Math::is_equal_approx(p_first.x, p_second.x, p_tolerance) && + Math::is_equal_approx(p_first.y, p_second.y, p_tolerance); +} + +bool SceneSynchronizer::compare(const Vector3 &p_first, const Vector3 &p_second, real_t p_tolerance) { + return Math::is_equal_approx(p_first.x, p_second.x, p_tolerance) && + Math::is_equal_approx(p_first.y, p_second.y, p_tolerance) && + Math::is_equal_approx(p_first.z, p_second.z, p_tolerance); +} + +bool SceneSynchronizer::compare(const Variant &p_first, const Variant &p_second, real_t p_tolerance) { + if (p_first.get_type() != p_second.get_type()) { + return false; + } + + // Custom evaluation methods + switch (p_first.get_type()) { + case Variant::FLOAT: { + return Math::is_equal_approx(p_first, p_second, p_tolerance); + } + case Variant::VECTOR2: { + return compare(Vector2(p_first), Vector2(p_second), p_tolerance); + } + case Variant::RECT2: { + const Rect2 a(p_first); + const Rect2 b(p_second); + if (compare(a.position, b.position, p_tolerance)) { + if (compare(a.size, b.size, p_tolerance)) { + return true; + } + } + return false; + } + case Variant::TRANSFORM2D: { + const Transform2D a(p_first); + const Transform2D b(p_second); + if (compare(a.elements[0], b.elements[0], p_tolerance)) { + if (compare(a.elements[1], b.elements[1], p_tolerance)) { + if (compare(a.elements[2], b.elements[2], p_tolerance)) { + return true; + } + } + } + return false; + } + case Variant::VECTOR3: { + return compare(Vector3(p_first), Vector3(p_second), p_tolerance); + } + case Variant::QUATERNION: { + const Quaternion a = p_first; + const Quaternion b = p_second; + const Quaternion r(a - b); // Element wise subtraction. + return (r.x * r.x + r.y * r.y + r.z * r.z + r.w * r.w) <= (p_tolerance * p_tolerance); + } + case Variant::PLANE: { + const Plane a(p_first); + const Plane b(p_second); + if (Math::is_equal_approx(a.d, b.d, p_tolerance)) { + if (compare(a.normal, b.normal, p_tolerance)) { + return true; + } + } + return false; + } + case Variant::AABB: { + const AABB a(p_first); + const AABB b(p_second); + if (compare(a.position, b.position, p_tolerance)) { + if (compare(a.size, b.size, p_tolerance)) { + return true; + } + } + return false; + } + case Variant::BASIS: { + const Basis a = p_first; + const Basis b = p_second; + if (compare(a.elements[0], b.elements[0], p_tolerance)) { + if (compare(a.elements[1], b.elements[1], p_tolerance)) { + if (compare(a.elements[2], b.elements[2], p_tolerance)) { + return true; + } + } + } + return false; + } + case Variant::TRANSFORM3D: { + const Transform3D a = p_first; + const Transform3D b = p_second; + if (compare(a.origin, b.origin, p_tolerance)) { + if (compare(a.basis.elements[0], b.basis.elements[0], p_tolerance)) { + if (compare(a.basis.elements[1], b.basis.elements[1], p_tolerance)) { + if (compare(a.basis.elements[2], b.basis.elements[2], p_tolerance)) { + return true; + } + } + } + } + return false; + } + case Variant::ARRAY: { + const Array a = p_first; + const Array b = p_second; + if (a.size() != b.size()) { + return false; + } + for (int i = 0; i < a.size(); i += 1) { + if (compare(a[i], b[i], p_tolerance) == false) { + return false; + } + } + return true; + } + case Variant::DICTIONARY: { + const Dictionary a = p_first; + const Dictionary b = p_second; + + if (a.size() != b.size()) { + return false; + } + + List l; + a.get_key_list(&l); + + for (const List::Element *key = l.front(); key; key = key->next()) { + if (b.has(key->get()) == false) { + return false; + } + + if (compare( + a.get(key->get(), Variant()), + b.get(key->get(), Variant()), + p_tolerance) == false) { + return false; + } + } + + return true; + } + default: + return p_first == p_second; + } +} + +bool SceneSynchronizer::is_server() const { + return synchronizer_type == SYNCHRONIZER_TYPE_SERVER; +} + +bool SceneSynchronizer::is_client() const { + return synchronizer_type == SYNCHRONIZER_TYPE_CLIENT; +} + +bool SceneSynchronizer::is_no_network() const { + return synchronizer_type == SYNCHRONIZER_TYPE_NONETWORK; +} + +bool SceneSynchronizer::is_networked() const { + return is_client() || is_server(); +} + +#ifdef DEBUG_ENABLED +void SceneSynchronizer::validate_nodes() { + LocalVector null_objects; + null_objects.reserve(node_data.size()); + + for (uint32_t i = 0; i < node_data.size(); i += 1) { + if (ObjectDB::get_instance(node_data[i]->instance_id) == nullptr) { + // Mark for removal. + null_objects.push_back(node_data[i]); + } + } + + // Removes the invalidated `NodeData`. + if (null_objects.size()) { + NET_DEBUG_ERR("At least one node has been removed from the tree without the SceneSynchronizer noticing. This shouldn't happen."); + for (uint32_t i = 0; i < null_objects.size(); i += 1) { + drop_node_data(null_objects[i]); + } + } +} +#endif + +void SceneSynchronizer::purge_node_dependencies() { + if (is_client() == false) { + return; + } + + // Clear the controller dependencies. + ClientSynchronizer *client_sync = static_cast(synchronizer); + + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + for ( + int d = 0; + d < int(node_data_controllers[i]->dependency_nodes_end.size()); + d += 1) { + if (node_data_controllers[i]->dependency_nodes_end[d] < client_sync->last_checked_input) { + // This controller dependency can be cleared because the server + // snapshot check has + node_data_controllers[i]->dependency_nodes.remove_unordered(d); + node_data_controllers[i]->dependency_nodes_end.remove_unordered(d); + d -= 1; + } + } + } +} + +void SceneSynchronizer::expand_organized_node_data_vector(uint32_t p_size) { + const uint32_t from = organized_node_data.size(); + organized_node_data.resize(from + p_size); + memset(organized_node_data.ptr() + from, 0, sizeof(void *) * p_size); +} + +NetUtility::NodeData *SceneSynchronizer::find_node_data(const Node *p_node) { + for (uint32_t i = 0; i < node_data.size(); i += 1) { + if (node_data[i] == nullptr) { + continue; + } + if (node_data[i]->instance_id == p_node->get_instance_id()) { + return node_data[i]; + } + } + return nullptr; +} + +const NetUtility::NodeData *SceneSynchronizer::find_node_data(const Node *p_node) const { + for (uint32_t i = 0; i < node_data.size(); i += 1) { + if (node_data[i] == nullptr) { + continue; + } + if (node_data[i]->instance_id == p_node->get_instance_id()) { + return node_data[i]; + } + } + return nullptr; +} + +NetUtility::NodeData *SceneSynchronizer::get_node_data(NetNodeId p_id) { + ERR_FAIL_UNSIGNED_INDEX_V(p_id, organized_node_data.size(), nullptr); + return organized_node_data[p_id]; +} + +const NetUtility::NodeData *SceneSynchronizer::get_node_data(NetNodeId p_id) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_id, organized_node_data.size(), nullptr); + return organized_node_data[p_id]; +} + +NetNodeId SceneSynchronizer::get_biggest_node_id() const { + return organized_node_data.size() == 0 ? UINT32_MAX : organized_node_data.size() - 1; +} + +void SceneSynchronizer::reset_controllers() { + for (uint32_t i = 0; i < node_data_controllers.size(); i += 1) { + reset_controller(node_data_controllers[i]); + } +} + +void SceneSynchronizer::reset_controller(NetUtility::NodeData *p_controller_nd) { +#ifdef DEBUG_ENABLED + // This can't happen because the callers make sure the `NodeData` is a + // controller. + CRASH_COND(p_controller_nd->is_controller == false); +#endif + + NetworkedController *controller = static_cast(p_controller_nd->node); + + // Reset the controller type. + if (controller->controller != nullptr) { + memdelete(controller->controller); + controller->controller = nullptr; + controller->controller_type = NetworkedController::CONTROLLER_TYPE_NULL; + controller->set_physics_process_internal(false); + } + + if (get_tree() == nullptr) { + if (synchronizer) { + synchronizer->on_controller_reset(p_controller_nd); + } + + // Nothing to do. + return; + } + + if (get_tree()->get_multiplayer()->get_network_peer().is_null()) { + controller->controller_type = NetworkedController::CONTROLLER_TYPE_NONETWORK; + controller->controller = memnew(NoNetController(controller)); + } else if (get_tree()->get_multiplayer()->is_network_server()) { + controller->controller_type = NetworkedController::CONTROLLER_TYPE_SERVER; + controller->controller = memnew(ServerController(controller, controller->get_network_traced_frames())); + } else if (controller->is_network_master()) { + controller->controller_type = NetworkedController::CONTROLLER_TYPE_PLAYER; + controller->controller = memnew(PlayerController(controller)); + } else { + controller->controller_type = NetworkedController::CONTROLLER_TYPE_DOLL; + controller->controller = memnew(DollController(controller)); + controller->set_physics_process_internal(true); + } + + dirty_peers(); + controller->controller->ready(); + + if (synchronizer) { + synchronizer->on_controller_reset(p_controller_nd); + } +} + +void SceneSynchronizer::process() { +#ifdef DEBUG_ENABLED + validate_nodes(); + // Never triggered because this function is called by `PHYSICS_PROCESS`, + // notification that is emitted only when the node is in the tree. + // When the node is in the tree, there is no way that the `synchronizer` is + // null. + CRASH_COND(synchronizer == nullptr); +#endif + + synchronizer->process(); + purge_node_dependencies(); +} + +void SceneSynchronizer::pull_node_changes(NetUtility::NodeData *p_node_data) { + Node *node = p_node_data->node; + + for (NetVarId var_id = 0; var_id < p_node_data->vars.size(); var_id += 1) { + if (p_node_data->vars[var_id].enabled == false) { + continue; + } + + const Variant old_val = p_node_data->vars[var_id].var.value; + const Variant new_val = node->get(p_node_data->vars[var_id].var.name); + + if (!compare(old_val, new_val)) { + p_node_data->vars[var_id].var.value = new_val.duplicate(true); + change_event_add( + p_node_data, + var_id, + old_val); + } + } +} + +Synchronizer::Synchronizer(SceneSynchronizer *p_node) : + scene_synchronizer(p_node) { +} + +NoNetSynchronizer::NoNetSynchronizer(SceneSynchronizer *p_node) : + Synchronizer(p_node) {} + +void NoNetSynchronizer::clear() { + enabled = true; +} + +void NoNetSynchronizer::process() { + if (unlikely(enabled == false)) { + return; + } + + const real_t delta = scene_synchronizer->get_physics_process_delta_time(); + + // Process the scene + for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; + nd->process(delta); + } + + // Process the controllers_node_data + for (uint32_t i = 0; i < scene_synchronizer->node_data_controllers.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data_controllers[i]; + static_cast(nd->node)->get_nonet_controller()->process(delta); + } + + // Pull the changes. + scene_synchronizer->change_events_begin(NetEventFlag::CHANGE); + for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; + scene_synchronizer->pull_node_changes(nd); + } + scene_synchronizer->change_events_flush(); +} + +void NoNetSynchronizer::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + // Nothing to do. + return; + } + + enabled = p_enabled; + + if (enabled) { + scene_synchronizer->emit_signal("sync_started"); + } else { + scene_synchronizer->emit_signal("sync_paused"); + } +} + +bool NoNetSynchronizer::is_enabled() const { + return enabled; +} + +ServerSynchronizer::ServerSynchronizer(SceneSynchronizer *p_node) : + Synchronizer(p_node) {} + +void ServerSynchronizer::clear() { + state_notifier_timer = 0.0; + // Release the internal memory. + changes.reset(); +} + +void ServerSynchronizer::process() { + scene_synchronizer->update_peers(); + + const real_t delta = scene_synchronizer->get_physics_process_delta_time(); + + // Process the scene + for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; + nd->process(delta); + } + + // Process the controllers_node_data + for (uint32_t i = 0; i < scene_synchronizer->node_data_controllers.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data_controllers[i]; + static_cast(nd->node)->get_server_controller()->process(delta); + } + + // Pull the changes. + scene_synchronizer->change_events_begin(NetEventFlag::CHANGE); + for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; + scene_synchronizer->pull_node_changes(nd); + } + scene_synchronizer->change_events_flush(); + + process_snapshot_notificator(delta); +} + +void ServerSynchronizer::on_node_added(NetUtility::NodeData *p_node_data) { +#ifdef DEBUG_ENABLED + // Can't happen on server + CRASH_COND(scene_synchronizer->is_recovered()); + // On server the ID is always known. + CRASH_COND(p_node_data->id == UINT32_MAX); +#endif + + if (changes.size() <= p_node_data->id) { + changes.resize(p_node_data->id + 1); + } + + changes[p_node_data->id].not_known_before = true; +} + +void ServerSynchronizer::on_variable_added(NetUtility::NodeData *p_node_data, const StringName &p_var_name) { +#ifdef DEBUG_ENABLED + // Can't happen on server + CRASH_COND(scene_synchronizer->is_recovered()); + // On server the ID is always known. + CRASH_COND(p_node_data->id == UINT32_MAX); +#endif + + if (changes.size() <= p_node_data->id) { + changes.resize(p_node_data->id + 1); + } + + changes[p_node_data->id].vars.insert(p_var_name); + changes[p_node_data->id].uknown_vars.insert(p_var_name); +} + +void ServerSynchronizer::on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) { +#ifdef DEBUG_ENABLED + // Can't happen on server + CRASH_COND(scene_synchronizer->is_recovered()); + // On server the ID is always known. + CRASH_COND(p_node_data->id == UINT32_MAX); +#endif + + if (changes.size() <= p_node_data->id) { + changes.resize(p_node_data->id + 1); + } + + changes[p_node_data->id].vars.insert(p_node_data->vars[p_var_id].var.name); +} + +void ServerSynchronizer::process_snapshot_notificator(real_t p_delta) { + if (scene_synchronizer->peer_data.is_empty()) { + // No one is listening. + return; + } + + // Notify the state if needed + state_notifier_timer += p_delta; + const bool notify_state = state_notifier_timer >= scene_synchronizer->get_server_notify_state_interval(); + + if (notify_state) { + state_notifier_timer = 0.0; + } + + Vector full_global_nodes_snapshot; + Vector delta_global_nodes_snapshot; + for ( + OAHashMap::Iterator peer_it = scene_synchronizer->peer_data.iter(); + peer_it.valid; + peer_it = scene_synchronizer->peer_data.next_iter(peer_it)) { + if (unlikely(peer_it.value->controller_id == UINT32_MAX)) { + // This peer still does not have a `NetworkedController`. + continue; + } + if (unlikely(peer_it.value->enabled == false)) { + // This peer is disabled. + continue; + } + if (peer_it.value->force_notify_snapshot == false && notify_state == false) { + // Nothing to do. + continue; + } + + peer_it.value->force_notify_snapshot = false; + + NetUtility::NodeData *nd = scene_synchronizer->get_node_data(peer_it.value->controller_id); + // TODO well that's not really true. I may have peers that doesn't have controllers_node_data in a + // certain moment. Please improve this mechanism trying to just use the + // node->get_network_master() to get the peer. + ERR_CONTINUE_MSG(nd == nullptr, "This should never happen. Likely there is a bug, NedNodeId: " + itos(peer_it.value->controller_id)); + ERR_CONTINUE_MSG(nd->is_controller == false, "[BUG] A controller il expected, The node " + nd->node->get_path() + " is submitted instead."); + + NetworkedController *controller = static_cast(nd->node); + + Vector snap; + if (peer_it.value->need_full_snapshot) { + peer_it.value->need_full_snapshot = false; + if (full_global_nodes_snapshot.size() == 0) { + full_global_nodes_snapshot = global_nodes_generate_snapshot(true); + } + snap = full_global_nodes_snapshot; + controller_generate_snapshot(nd, true, snap); + } else { + if (delta_global_nodes_snapshot.size() == 0) { + delta_global_nodes_snapshot = global_nodes_generate_snapshot(false); + } + snap = delta_global_nodes_snapshot; + controller_generate_snapshot(nd, false, snap); + } + + controller->get_server_controller()->notify_send_state(); + scene_synchronizer->rpc_id(*peer_it.key, SNAME("_rpc_send_state"), snap); + } + + if (notify_state) { + // The state got notified, mark this as checkpoint so the next state + // will contains only the changed things. + changes.clear(); + } +} + +Vector ServerSynchronizer::global_nodes_generate_snapshot(bool p_force_full_snapshot) const { + Vector snapshot_data; + + for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + const NetUtility::NodeData *node_data = scene_synchronizer->node_data[i]; + if (node_data == nullptr) { + continue; + } + if (node_data->is_controller || node_data->controlled_by != nullptr) { + // Skip the controllers. + continue; + } + generate_snapshot_node_data(node_data, p_force_full_snapshot, snapshot_data); + } + + return snapshot_data; +} + +void ServerSynchronizer::controller_generate_snapshot( + const NetUtility::NodeData *p_node_data, + bool p_force_full_snapshot, + Vector &r_snapshot_result) const { + CRASH_COND(p_node_data->is_controller == false); + + generate_snapshot_node_data( + p_node_data, + p_force_full_snapshot, + r_snapshot_result); + + for (uint32_t i = 0; i < p_node_data->controlled_nodes.size(); i += 1) { + generate_snapshot_node_data( + p_node_data->controlled_nodes[i], + p_force_full_snapshot, + r_snapshot_result); + } +} + +void ServerSynchronizer::generate_snapshot_node_data( + const NetUtility::NodeData *p_node_data, + bool p_force_full_snapshot, + Vector &r_snapshot_data) const { + // The packet data is an array that contains the informations to update the + // client snapshot. + // + // It's composed as follows: + // [NODE, VARIABLE, Value, VARIABLE, Value, VARIABLE, value, NIL, + // NODE, INPUT ID, VARIABLE, Value, VARIABLE, Value, NIL, + // NODE, VARIABLE, Value, VARIABLE, Value, NIL] + // + // Each node ends with a NIL, and the NODE and the VARIABLE are special: + // - NODE, can be an array of two variables [Net Node ID, NodePath] or directly + // a Node ID. Obviously the array is sent only the first time. + // - INPUT ID, this is optional and is used only when the node is a controller. + // - VARIABLE, can be an array with the ID and the variable name, or just + // the ID; similarly as is for the NODE the array is send only + // the first time. + + if (p_node_data->node == nullptr || p_node_data->node->is_inside_tree() == false) { + return; + } + + const Change *change = p_node_data->id >= changes.size() ? nullptr : changes.ptr() + p_node_data->id; + + // Insert NODE DATA. + Variant snap_node_data; + if (p_force_full_snapshot || (change != nullptr && change->not_known_before)) { + Vector _snap_node_data; + _snap_node_data.resize(2); + _snap_node_data.write[0] = p_node_data->id; + _snap_node_data.write[1] = p_node_data->node->get_path(); + snap_node_data = _snap_node_data; + } else { + // This node is already known on clients, just set the node ID. + snap_node_data = p_node_data->id; + } + + const bool node_has_changes = p_force_full_snapshot || (change != nullptr && change->vars.is_empty() == false); + + if (p_node_data->is_controller) { + NetworkedController *controller = static_cast(p_node_data->node); + + // TODO make sure to skip un-active controllers_node_data. + // This may no more needed, since the interpolator got integrated and + // the only time the controller is sync is when it's needed. + if (likely(controller->get_current_input_id() != UINT32_MAX)) { + // This is a controller, always sync it. + r_snapshot_data.push_back(snap_node_data); + r_snapshot_data.push_back(controller->get_current_input_id()); + } else { + // The first ID id is not yet arrived, so just skip this node. + return; + } + } else { + if (node_has_changes) { + r_snapshot_data.push_back(snap_node_data); + } else { + // It has no changes, skip this node. + return; + } + } + + if (node_has_changes) { + // Insert the node variables. + for (uint32_t i = 0; i < p_node_data->vars.size(); i += 1) { + const NetUtility::VarData &var = p_node_data->vars[i]; + if (var.enabled == false) { + continue; + } + + if (p_force_full_snapshot == false && change->vars.has(var.var.name) == false) { + // This is a delta snapshot and this variable is the same as + // before. Skip it. + continue; + } + + Variant var_info; + if (p_force_full_snapshot || change->uknown_vars.has(var.var.name)) { + Vector _var_info; + _var_info.resize(2); + _var_info.write[0] = var.id; + _var_info.write[1] = var.var.name; + var_info = _var_info; + } else { + var_info = var.id; + } + + r_snapshot_data.push_back(var_info); + r_snapshot_data.push_back(var.var.value); + } + } + + // Insert NIL. + r_snapshot_data.push_back(Variant()); +} + +ClientSynchronizer::ClientSynchronizer(SceneSynchronizer *p_node) : + Synchronizer(p_node) { + clear(); +} + +void ClientSynchronizer::clear() { + player_controller_node_data = nullptr; + node_paths.clear(); + last_received_snapshot.input_id = UINT32_MAX; + last_received_snapshot.node_vars.clear(); + client_snapshots.clear(); + server_snapshots.clear(); + last_checked_input = 0; + enabled = true; + need_full_snapshot_notified = false; +} + +void ClientSynchronizer::process() { + if (unlikely(player_controller_node_data == nullptr || enabled == false)) { + // No player controller or disabled so nothing to do. + return; + } + + const real_t delta = scene_synchronizer->get_physics_process_delta_time(); + const real_t physics_ticks_per_second = Engine::get_singleton()->get_physics_ticks_per_second(); + +#ifdef DEBUG_ENABLED + if (unlikely(Engine::get_singleton()->get_frames_per_second() < physics_ticks_per_second)) { + WARN_PRINT("Current FPS is " + itos(Engine::get_singleton()->get_frames_per_second()) + ", but the minimum required FPS is " + itos(physics_ticks_per_second) + ", the client is unable to generate enough inputs for the server."); + } +#endif + + NetworkedController *controller = static_cast(player_controller_node_data->node); + PlayerController *player_controller = controller->get_player_controller(); + + // Reset this here, so even when `sub_ticks` is zero (and it's not + // updated due to process is not called), we can still have the corect + // data. + controller->player_set_has_new_input(false); + + // Due to some lag we may want to speed up the input_packet + // generation, for this reason here I'm performing a sub tick. + // + // keep in mind that we are just pretending that the time + // is advancing faster, for this reason we are still using + // `delta` to step the controllers_node_data. + // + // The dolls may want to speed up too, so to consume the inputs faster + // and get back in time with the server. + int sub_ticks = player_controller->calculates_sub_ticks(delta, physics_ticks_per_second); + + while (sub_ticks > 0) { + // Process the scene. + for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; + nd->process(delta); + } + + // Process the player controllers_node_data. + player_controller->process(delta); + + // Pull the changes. + scene_synchronizer->change_events_begin(NetEventFlag::CHANGE); + for (NetNodeId i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; + scene_synchronizer->pull_node_changes(nd); + } + scene_synchronizer->change_events_flush(); + + if (controller->player_has_new_input()) { + store_snapshot(); + } + + sub_ticks -= 1; + } + + process_controllers_recovery(delta); + + // Now trigger the END_SYNC event. + scene_synchronizer->change_events_begin(NetEventFlag::END_SYNC); + for (const Set::Element *e = sync_end_events.front(); + e != nullptr; + e = e->next()) { + // Check if the values between the variables before the sync and the + // current one are different. + if (scene_synchronizer->compare( + e->get().node_data->vars[e->get().var_id].var.value, + e->get().old_value) == false) { + // Are different so we need to emit the `END_SYNC`. + scene_synchronizer->change_event_add( + e->get().node_data, + e->get().var_id, + e->get().old_value); + } + } + sync_end_events.clear(); + + scene_synchronizer->change_events_flush(); +} + +void ClientSynchronizer::receive_snapshot(Variant p_snapshot) { + // The received snapshot is parsed and stored into the `last_received_snapshot` + // that contains always the last received snapshot. + // Later, the snapshot is stored into the server queue. + // In this way, we are free to pop snapshot from the queue without wondering + // about losing the data. Indeed the received snapshot is just and + // incremental update so the last received data is always needed to fully + // reconstruct it. + + // Parse server snapshot. + const bool success = parse_snapshot(p_snapshot); + + if (success == false) { + return; + } + + // Finalize data. + + store_controllers_snapshot( + last_received_snapshot, + server_snapshots); +} + +void ClientSynchronizer::on_node_added(NetUtility::NodeData *p_node_data) { +} + +void ClientSynchronizer::on_node_removed(NetUtility::NodeData *p_node_data) { + if (player_controller_node_data == p_node_data) { + player_controller_node_data = nullptr; + client_snapshots.clear(); + } +} + +void ClientSynchronizer::on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) { + if (p_flag & NetEventFlag::SYNC) { + sync_end_events.insert( + EndSyncEvent{ + p_node_data, + p_var_id, + p_old_value }); + } +} + +void ClientSynchronizer::on_controller_reset(NetUtility::NodeData *p_node_data) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_node_data->is_controller == false); +#endif + + if (player_controller_node_data == p_node_data) { + // Reset the node_data. + player_controller_node_data = nullptr; + client_snapshots.clear(); + } + + if (static_cast(p_node_data->node)->is_player_controller()) { + if (player_controller_node_data != nullptr) { + NET_DEBUG_ERR("Only one player controller is supported, at the moment. Make sure this is the case."); + } else { + // Set this player controller as active. + player_controller_node_data = p_node_data; + client_snapshots.clear(); + } + } +} + +void ClientSynchronizer::store_snapshot() { + NetworkedController *controller = static_cast(player_controller_node_data->node); + +#ifdef DEBUG_ENABLED + if (unlikely(client_snapshots.size() > 0 && controller->get_current_input_id() <= client_snapshots.back().input_id)) { + CRASH_NOW_MSG("[FATAL] During snapshot creation, for controller " + controller->get_path() + ", was found an ID for an older snapshots. New input ID: " + itos(controller->get_current_input_id()) + " Last saved snapshot input ID: " + itos(client_snapshots.back().input_id) + "."); + } +#endif + + client_snapshots.push_back(NetUtility::Snapshot()); + + NetUtility::Snapshot &snap = client_snapshots.back(); + snap.input_id = controller->get_current_input_id(); + + snap.node_vars.resize(scene_synchronizer->organized_node_data.size()); + + // Store the nodes state and skip anything is related to the other + // controllers. + for (uint32_t i = 0; i < scene_synchronizer->organized_node_data.size(); i += 1) { + const NetUtility::NodeData *node_data = scene_synchronizer->organized_node_data[i]; + + if (node_data == nullptr) { + // Nothing to do. + continue; + } + + if ((node_data->is_controller || node_data->controlled_by != nullptr) && + (node_data != player_controller_node_data && node_data->controlled_by != player_controller_node_data)) { + // Ignore this controller. + continue; + } + + if (node_data->id >= uint32_t(snap.node_vars.size())) { + // Make sure this ID is valid. + ERR_FAIL_COND_MSG(node_data->id != UINT32_MAX, "[BUG] It's not expected that the client has a node with the NetNodeId (" + itos(node_data->id) + ") bigger than the registered node count: " + itos(snap.node_vars.size())); + // Skip this node + continue; + } + + Vector *snap_node_vars = snap.node_vars.ptrw() + node_data->id; + snap_node_vars->resize(node_data->vars.size()); + NetUtility::Var *vars = snap_node_vars->ptrw(); + for (uint32_t v = 0; v < node_data->vars.size(); v += 1) { + if (node_data->vars[v].enabled) { + vars[v] = node_data->vars[v].var; + } else { + vars[v].name = StringName(); + } + } + } +} + +void ClientSynchronizer::store_controllers_snapshot( + const NetUtility::Snapshot &p_snapshot, + std::deque &r_snapshot_storage) { + // Put the parsed snapshot into the queue. + + if (p_snapshot.input_id == UINT32_MAX) { + // The snapshot doesn't have any info for this controller; Skip it. + return; + } + + if (r_snapshot_storage.empty() == false) { + // Make sure the snapshots are stored in order. + const uint32_t last_stored_input_id = r_snapshot_storage.back().input_id; + if (p_snapshot.input_id == last_stored_input_id) { + // Update the snapshot. + r_snapshot_storage.back() = p_snapshot; + return; + } else { + ERR_FAIL_COND_MSG(p_snapshot.input_id < last_stored_input_id, "This snapshot (with ID: " + itos(p_snapshot.input_id) + ") is not expected because the last stored id is: " + itos(last_stored_input_id)); + } + } + + r_snapshot_storage.push_back(p_snapshot); +} + +// TODO make this function much simpler. +void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { + // The client is responsible to recover only its local controller, while all + // the other controllers_node_data (dolls) have their state interpolated. There is + // no need to check the correctness of the doll state nor the needs to + // rewind those. + // + // The scene, (global nodes), are always in sync with the reference frame + // of the client. + + NetworkedController *controller = static_cast(player_controller_node_data->node); + PlayerController *player_controller = controller->get_player_controller(); + + // --- Phase one: find the snapshot to check. --- + if (server_snapshots.empty()) { + // No snapshots to recover for this controller. Nothing to do. + return; + } + +#ifdef DEBUG_ENABLED + if (client_snapshots.empty() == false) { + // The SceneSynchronizer and the PlayerController are always in sync. + CRASH_COND_MSG(client_snapshots.back().input_id != player_controller->last_known_input(), "This should not be possible: snapshot input: " + itos(client_snapshots.back().input_id) + " last_know_input: " + itos(player_controller->last_known_input())); + } +#endif + + // Find the best recoverable input_id. + uint32_t checkable_input_id = UINT32_MAX; + // Find the best snapshot to recover from the one already + // processed. + if (client_snapshots.empty() == false) { + for ( + auto s_snap = server_snapshots.rbegin(); + checkable_input_id == UINT32_MAX && s_snap != server_snapshots.rend(); + ++s_snap) { + for (auto c_snap = client_snapshots.begin(); c_snap != client_snapshots.end(); ++c_snap) { + if (c_snap->input_id == s_snap->input_id) { + // Server snapshot also found on client, can be checked. + checkable_input_id = c_snap->input_id; + break; + } + } + } + } else { + // No client input, this happens when the stream is paused. + process_paused_controller_recovery(p_delta); + return; + } + + if (checkable_input_id == UINT32_MAX) { + // No snapshot found, nothing to do. + return; + } + +#ifdef DEBUG_ENABLED + // Unreachable cause the above check + CRASH_COND(server_snapshots.empty()); + CRASH_COND(client_snapshots.empty()); +#endif + + // Drop all the old server snapshots until the one that we need. + while (server_snapshots.front().input_id < checkable_input_id) { + server_snapshots.pop_front(); + } + + // Drop all the old client snapshots until the one that we need. + while (client_snapshots.front().input_id < checkable_input_id) { + client_snapshots.pop_front(); + } + +#ifdef DEBUG_ENABLED + // These are unreachable at this point. + CRASH_COND(server_snapshots.empty()); + CRASH_COND(server_snapshots.front().input_id != checkable_input_id); + + // This is unreachable, because we store all the client shapshots + // each time a new input is processed. Since the `checkable_input_id` + // is taken by reading the processed doll inputs, it's guaranteed + // that here the snapshot exists. + CRASH_COND(client_snapshots.empty()); + CRASH_COND(client_snapshots.front().input_id != checkable_input_id); +#endif + + // --- Phase two: compare the server snapshot with the client snapshot. --- + bool need_recover = false; + bool recover_controller = false; + LocalVector nodes_to_recover; + LocalVector postponed_recover; + + nodes_to_recover.reserve(server_snapshots.front().node_vars.size()); + for (uint32_t net_node_id = 0; net_node_id < uint32_t(server_snapshots.front().node_vars.size()); net_node_id += 1) { + NetUtility::NodeData *rew_node_data = scene_synchronizer->get_node_data(net_node_id); + if (rew_node_data == nullptr || rew_node_data->sync_enabled == false) { + continue; + } + + bool recover_this_node = false; + if (net_node_id >= uint32_t(client_snapshots.front().node_vars.size())) { + NET_DEBUG_PRINT("Rewind is needed because the client snapshot doesn't contain this node: " + rew_node_data->node->get_path()); + recover_this_node = true; + } else { + NetUtility::PostponedRecover rec; + + const bool different = compare_vars( + rew_node_data, + server_snapshots.front().node_vars[net_node_id], + client_snapshots.front().node_vars[net_node_id], + rec.vars); + + if (different) { + NET_DEBUG_PRINT("Rewind is needed because the node on client is different: " + rew_node_data->node->get_path()); + recover_this_node = true; + } else if (rec.vars.size() > 0) { + rec.node_data = rew_node_data; + postponed_recover.push_back(rec); + } + } + + if (recover_this_node) { + need_recover = true; + if (rew_node_data->controlled_by != nullptr || + rew_node_data->is_controller || + player_controller_node_data->dependency_nodes.find(rew_node_data) != -1) { + // Controller node. + recover_controller = true; + } else { + nodes_to_recover.push_back(rew_node_data); + } + } + } + + // Popout the client snapshot. + client_snapshots.pop_front(); + + // --- Phase three: recover and reply. --- + + if (need_recover) { + NET_DEBUG_PRINT("Recover input: " + itos(checkable_input_id) + " - Last input: " + itos(player_controller->get_stored_input_id(-1))); + + if (recover_controller) { + // Put the controlled and the controllers_node_data into the nodes to + // rewind. + // Note, the controller stuffs are added here to ensure that if the + // controller need a recover, all its nodes are added; no matter + // at which point the difference is found. + nodes_to_recover.reserve( + nodes_to_recover.size() + + player_controller_node_data->controlled_nodes.size() + + player_controller_node_data->dependency_nodes.size() + + 1); + + nodes_to_recover.push_back(player_controller_node_data); + + for ( + uint32_t y = 0; + y < player_controller_node_data->controlled_nodes.size(); + y += 1) { + nodes_to_recover.push_back(player_controller_node_data->controlled_nodes[y]); + } + + for ( + uint32_t y = 0; + y < player_controller_node_data->dependency_nodes.size(); + y += 1) { + nodes_to_recover.push_back(player_controller_node_data->dependency_nodes[y]); + } + } + + // Apply the server snapshot so to go back in time till that moment, + // so to be able to correctly reply the movements. + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER | NetEventFlag::SYNC_RESET); + for (uint32_t i = 0; i < nodes_to_recover.size(); i += 1) { + if (nodes_to_recover[i]->id >= uint32_t(server_snapshots.front().node_vars.size())) { + NET_DEBUG_WARN("The node: " + nodes_to_recover[i]->node->get_path() + " was not found on the server snapshot, this is not supposed to happen a lot."); + continue; + } + if (nodes_to_recover[i]->sync_enabled == false) { + // Don't sync this node. + // This check is also here, because the `recover_controller` + // mechanism, may have insert a no sync node. + // The check is here because I feel it more clear, here. + continue; + } + +#ifdef DEBUG_ENABLED + // The parser make sure to properly initialize the snapshot variable + // array size. So the following condition is always `false`. + CRASH_COND(uint32_t(server_snapshots.front().node_vars[nodes_to_recover[i]->id].size()) != nodes_to_recover[i]->vars.size()); +#endif + + Node *node = nodes_to_recover[i]->node; + const Vector s_vars = server_snapshots.front().node_vars[nodes_to_recover[i]->id]; + const NetUtility::Var *s_vars_ptr = s_vars.ptr(); + + NET_DEBUG_PRINT("Full reset node: " + node->get_path()); + + for (int v = 0; v < s_vars.size(); v += 1) { + if (s_vars_ptr[v].name == StringName()) { + // This variable was not set, skip it. + continue; + } + + const Variant current_val = nodes_to_recover[i]->vars[v].var.value; + nodes_to_recover[i]->vars[v].var.value = s_vars_ptr[v].value.duplicate(true); + node->set(s_vars_ptr[v].name, s_vars_ptr[v].value); + + NET_DEBUG_PRINT(" |- Variable: " + s_vars_ptr[v].name + " New value: " + s_vars_ptr[v].value); + scene_synchronizer->change_event_add( + nodes_to_recover[i], + v, + current_val); + } + } + scene_synchronizer->change_events_flush(); + + // Rewind phase. + + const int remaining_inputs = player_controller->notify_input_checked(checkable_input_id); +#ifdef DEBUG_ENABLED + // Unreachable because the SceneSynchronizer and the PlayerController + // have the same stored data at this point. + CRASH_COND(client_snapshots.size() != size_t(remaining_inputs)); +#endif + +#ifdef DEBUG_ENABLED + // Used to double check all the instants have been processed. + bool has_next = false; +#endif + for (int i = 0; i < remaining_inputs; i += 1) { + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER | NetEventFlag::SYNC_REWIND); + + // Step 1 -- Process the scene nodes. + for (uint32_t r = 0; r < nodes_to_recover.size(); r += 1) { + if (nodes_to_recover[r]->sync_enabled == false) { + // This node is not sync. + continue; + } + nodes_to_recover[r]->process(p_delta); +#ifdef DEBUG_ENABLED + if (nodes_to_recover[r]->functions.size()) { + NET_DEBUG_PRINT("Rewind, processed node: " + nodes_to_recover[r]->node->get_path()); + } +#endif + } + + // Step 2 -- Process the controller. + if (recover_controller && player_controller_node_data->sync_enabled) { +#ifdef DEBUG_ENABLED + has_next = +#endif + controller->process_instant(i, p_delta); + NET_DEBUG_PRINT("Rewind, processed controller: " + controller->get_path()); + } + + // Step 3 -- Pull node changes and Update snapshots. + for (uint32_t r = 0; r < nodes_to_recover.size(); r += 1) { + if (nodes_to_recover[r]->sync_enabled == false) { + // This node is not sync. + continue; + } + // Pull changes + scene_synchronizer->pull_node_changes(nodes_to_recover[r]); + + // Update client snapshot. + if (uint32_t(client_snapshots[i].node_vars.size()) <= nodes_to_recover[r]->id) { + client_snapshots[i].node_vars.resize(nodes_to_recover[r]->id + 1); + } + + Vector *snap_node_vars = client_snapshots[i].node_vars.ptrw() + nodes_to_recover[r]->id; + snap_node_vars->resize(nodes_to_recover[r]->vars.size()); + + NetUtility::Var *vars = snap_node_vars->ptrw(); + for (uint32_t v = 0; v < nodes_to_recover[r]->vars.size(); v += 1) { + vars[v] = nodes_to_recover[r]->vars[v].var; + } + } + scene_synchronizer->change_events_flush(); + } + +#ifdef DEBUG_ENABLED + // Unreachable because the above loop consume all instants. + CRASH_COND(has_next); +#endif + + } else { + // Apply found differences without rewind. + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER); + for (uint32_t i = 0; i < postponed_recover.size(); i += 1) { + NetUtility::NodeData *rew_node_data = postponed_recover[i].node_data; + if (rew_node_data->sync_enabled == false) { + // This node sync is disabled. + continue; + } + + Node *node = rew_node_data->node; + const NetUtility::Var *vars_ptr = postponed_recover[i].vars.ptr(); + + NET_DEBUG_PRINT("[Snapshot partial reset] Node: " + node->get_path()); + + // Set the value on the synchronizer too. + for (int v = 0; v < postponed_recover[i].vars.size(); v += 1) { + // We need to search it because the postponed recovered is not + // aligned. + // TODO This array is generated few lines above. + // Can we store the ID too, so to avoid this search???? + const int rew_var_index = rew_node_data->vars.find(vars_ptr[v].name); + // Unreachable, because when the snapshot is received the + // algorithm make sure the `scene_synchronizer` is traking the + // variable. + CRASH_COND(rew_var_index <= -1); + + const Variant old_val = rew_node_data->vars[rew_var_index].var.value; + rew_node_data->vars[rew_var_index].var.value = vars_ptr[v].value.duplicate(true); + node->set(vars_ptr[v].name, vars_ptr[v].value); + + NET_DEBUG_PRINT(" |- Variable: " + vars_ptr[v].name + "; old value: " + old_val + " new value: " + vars_ptr[v].value); + scene_synchronizer->change_event_add( + rew_node_data, + rew_var_index, + old_val); + } + + // Update the last client snapshot. + if (client_snapshots.empty() == false) { + if (uint32_t(client_snapshots.back().node_vars.size()) <= rew_node_data->id) { + client_snapshots.back().node_vars.resize(rew_node_data->id + 1); + } + + Vector *snap_node_vars = client_snapshots.back().node_vars.ptrw() + rew_node_data->id; + snap_node_vars->resize(rew_node_data->vars.size()); + + NetUtility::Var *vars = snap_node_vars->ptrw(); + + for (uint32_t v = 0; v < rew_node_data->vars.size(); v += 1) { + vars[v] = rew_node_data->vars[v].var; + } + } + } + scene_synchronizer->change_events_flush(); + + player_controller->notify_input_checked(checkable_input_id); + } + + // Popout the server snapshot. + server_snapshots.pop_front(); + + last_checked_input = checkable_input_id; +} + +void ClientSynchronizer::process_paused_controller_recovery(real_t p_delta) { +#ifdef DEBUG_ENABLED + CRASH_COND(server_snapshots.empty()); + CRASH_COND(client_snapshots.empty() == false); +#endif + + // Drop the snapshots till the newest. + while (server_snapshots.size() != 1) { + server_snapshots.pop_front(); + } + +#ifdef DEBUG_ENABLED + CRASH_COND(server_snapshots.empty()); +#endif + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER); + for (uint32_t net_node_id = 0; net_node_id < uint32_t(server_snapshots.front().node_vars.size()); net_node_id += 1) { + NetUtility::NodeData *rew_node_data = scene_synchronizer->get_node_data(net_node_id); + if (rew_node_data == nullptr || rew_node_data->sync_enabled == false) { + continue; + } + + Node *node = rew_node_data->node; + + const NetUtility::Var *snap_vars_ptr = server_snapshots.front().node_vars[net_node_id].ptr(); + for (int var_id = 0; var_id < server_snapshots.front().node_vars[net_node_id].size(); var_id += 1) { + // Note: the snapshot variable array is ordered per var_id. + const Variant old_val = rew_node_data->vars[var_id].var.value; + if (!scene_synchronizer->compare( + old_val, + snap_vars_ptr[var_id].value)) { + // Different + rew_node_data->vars[var_id].var.value = snap_vars_ptr[var_id].value; + node->set(snap_vars_ptr[var_id].name, snap_vars_ptr[var_id].value); + NET_DEBUG_PRINT("[Snapshot paused controller] Node: " + node->get_path()); + NET_DEBUG_PRINT(" |- Variable: " + snap_vars_ptr[var_id].name + "; value: " + snap_vars_ptr[var_id].value); + scene_synchronizer->change_event_add( + rew_node_data, + var_id, + old_val); + } + } + } + + server_snapshots.pop_front(); + + scene_synchronizer->change_events_flush(); +} + +bool ClientSynchronizer::parse_sync_data( + Variant p_sync_data, + void *p_user_pointer, + void (*p_node_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data), + void (*p_controller_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id), + void (*p_variable_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_var_id, const Variant &p_value)) { + // The sync data is an array that contains the scene informations. + // It's used for several things, for this reason this function allows to + // customize the parsing. + // + // The data is composed as follows: + // [NODE, VARIABLE, Value, VARIABLE, Value, VARIABLE, value, NIL, + // NODE, INPUT ID, VARIABLE, Value, VARIABLE, Value, NIL, + // NODE, VARIABLE, Value, VARIABLE, Value, NIL] + // + // Each node ends with a NIL, and the NODE and the VARIABLE are special: + // - NODE, can be an array of two variables [Node ID, NodePath] or directly + // a Node ID. Obviously the array is sent only the first time. + // - INPUT ID, this is optional and is used only when the node is a controller. + // - VARIABLE, can be an array with the ID and the variable name, or just + // the ID; similarly as is for the NODE the array is send only + // the first time. + + if (p_sync_data.get_type() == Variant::NIL) { + // Nothing to do. + return true; + } + + ERR_FAIL_COND_V(!p_sync_data.is_array(), false); + + const Vector raw_snapshot = p_sync_data; + const Variant *raw_snapshot_ptr = raw_snapshot.ptr(); + + NetUtility::NodeData *synchronizer_node_data = nullptr; + uint32_t var_id = UINT32_MAX; + + for (int snap_data_index = 0; snap_data_index < raw_snapshot.size(); snap_data_index += 1) { + const Variant v = raw_snapshot_ptr[snap_data_index]; + if (synchronizer_node_data == nullptr) { + // Node is null so we expect `v` has the node info. + + bool skip_this_node = false; + Node *node = nullptr; + uint32_t net_node_id = UINT32_MAX; + NodePath node_path; + + if (v.is_array()) { + // Node info are in verbose form, extract it. + + const Vector node_data = v; + ERR_FAIL_COND_V(node_data.size() != 2, false); + ERR_FAIL_COND_V_MSG(node_data[0].get_type() != Variant::INT, false, "This snapshot is corrupted."); + ERR_FAIL_COND_V_MSG(node_data[1].get_type() != Variant::NODE_PATH, false, "This snapshot is corrupted."); + + net_node_id = node_data[0]; + node_path = node_data[1]; + + // Associate the ID with the path. + node_paths.set(net_node_id, node_path); + + } else if (v.get_type() == Variant::INT) { + // Node info are in short form. + net_node_id = v; + NetUtility::NodeData *nd = scene_synchronizer->get_node_data(net_node_id); + if (nd != nullptr) { + synchronizer_node_data = nd; + goto node_lookup_out; + } + } else { + // The arrived snapshot does't seems to be in the expected form. + ERR_FAIL_V_MSG(false, "This snapshot is corrupted. Now the node is expected, " + String(v) + " was submitted instead."); + } + + if (synchronizer_node_data == nullptr) { + if (node_path.is_empty()) { + const NodePath *node_path_ptr = node_paths.lookup_ptr(net_node_id); + + if (node_path_ptr == nullptr) { + // Was not possible lookup the node_path. + NET_DEBUG_WARN("The node with ID `" + itos(net_node_id) + "` is not know by this peer, this is not supposed to happen."); + notify_server_full_snapshot_is_needed(); + skip_this_node = true; + goto node_lookup_check; + } else { + node_path = *node_path_ptr; + } + } + + node = scene_synchronizer->get_tree()->get_root()->get_node(node_path); + + if (node == nullptr) { + // The node doesn't exists. + NET_DEBUG_ERR("The node " + node_path + " still doesn't exist."); + skip_this_node = true; + goto node_lookup_check; + } + + // Register this node, so to make sure the client is tracking it. + NetUtility::NodeData *nd = scene_synchronizer->register_node(node); + if (nd != nullptr) { + // Set the node ID. + scene_synchronizer->set_node_data_id(nd, net_node_id); + synchronizer_node_data = nd; + } else { + NET_DEBUG_ERR("[BUG] This node " + node->get_path() + " was not know on this client. Though, was not possible to register it."); + skip_this_node = true; + } + } + + node_lookup_check: + if (skip_this_node || synchronizer_node_data == nullptr) { + // This node does't exist; skip it entirely. + for (snap_data_index += 1; snap_data_index < raw_snapshot.size(); snap_data_index += 1) { + if (raw_snapshot_ptr[snap_data_index].get_type() == Variant::NIL) { + break; + } + } + ERR_CONTINUE_MSG(true, "This NetNodeId " + itos(net_node_id) + " doesn't exist on this client."); + } + + node_lookup_out: + +#ifdef DEBUG_ENABLED + // At this point the ID is never UINT32_MAX thanks to the above + // mechanism. + CRASH_COND(synchronizer_node_data->id == UINT32_MAX); +#endif + + p_node_parse(p_user_pointer, synchronizer_node_data); + + if (synchronizer_node_data->is_controller) { + // This is a controller, so the next data is the input ID. + ERR_FAIL_COND_V(snap_data_index + 1 >= raw_snapshot.size(), false); + snap_data_index += 1; + const uint32_t input_id = raw_snapshot_ptr[snap_data_index]; + ERR_FAIL_COND_V_MSG(input_id == UINT32_MAX, false, "The server is always able to send input_id, so this snapshot seems corrupted."); + + p_controller_parse(p_user_pointer, synchronizer_node_data, input_id); + } + + } else if (var_id == UINT32_MAX) { + // When the node is known and the `var_id` not, we expect a + // new variable or the end pf this node data. + + if (v.get_type() == Variant::NIL) { + // NIL found, so this node is done. + synchronizer_node_data = nullptr; + continue; + } + + // This is a new variable, so let's take the variable name. + + if (v.is_array()) { + // The variable info are stored in verbose mode. + + const Vector var_data = v; + ERR_FAIL_COND_V(var_data.size() != 2, false); + ERR_FAIL_COND_V(var_data[0].get_type() != Variant::INT, false); + ERR_FAIL_COND_V(var_data[1].get_type() != Variant::STRING_NAME, false); + + var_id = var_data[0]; + StringName variable_name = var_data[1]; + + { + int64_t index = synchronizer_node_data->vars.find(variable_name); + if (index == -1) { + // The variable is not known locally, so just add it so + // to store the variable ID. + index = synchronizer_node_data->vars.size(); + + const bool skip_rewinding = false; + const bool enabled = false; + synchronizer_node_data->vars + .push_back( + NetUtility::VarData( + var_id, + variable_name, + Variant(), + skip_rewinding, + enabled)); + NET_DEBUG_ERR("The variable " + variable_name + " for the node " + synchronizer_node_data->node->get_path() + " was not known on this client. This should never happen, make sure to register the same nodes on the client and server."); + } + + if (index != var_id) { + if (synchronizer_node_data[var_id].id != UINT32_MAX) { + // It's not expected because if index is different to + // var_id, var_id should have a not yet initialized + // variable. + NET_DEBUG_ERR("This snapshot is corrupted. The var_id, at this point, must have a not yet init variable."); + notify_server_full_snapshot_is_needed(); + return false; + } + + // Make sure the variable is at the right index. + SWAP(synchronizer_node_data->vars[index], synchronizer_node_data->vars[var_id]); + } + } + + // Make sure the ID is properly assigned. + synchronizer_node_data->vars[var_id].id = var_id; + + } else if (v.get_type() == Variant::INT) { + // The variable is stored in the compact form. + + var_id = v; + + if (var_id >= synchronizer_node_data->vars.size() || + synchronizer_node_data->vars[var_id].id == UINT32_MAX) { + NET_DEBUG_PRINT("The var with ID `" + itos(var_id) + "` is not know by this peer, this is not supposed to happen."); + + notify_server_full_snapshot_is_needed(); + + // Skip the next data since it's the value of this variable. + snap_data_index += 1; + var_id = UINT32_MAX; + continue; + } + + } else { + ERR_FAIL_V_MSG(false, "The snapshot received seems corrupted. The variable is expected but " + String(v) + " received instead."); + } + + } else { + // The node is known, also the variable name is known, so the value + // is expected. + + p_variable_parse( + p_user_pointer, + synchronizer_node_data, + var_id, + v); + + // Just reset the variable name so we can continue iterate. + var_id = UINT32_MAX; + } + } + + return true; +} + +void ClientSynchronizer::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + // Nothing to do. + return; + } + + if (p_enabled) { + // Postpone enabling when the next server snapshot is received. + want_to_enable = true; + } else { + // Disabling happens immediately. + enabled = false; + want_to_enable = false; + scene_synchronizer->emit_signal("sync_paused"); + } +} + +bool ClientSynchronizer::parse_snapshot(Variant p_snapshot) { + if (want_to_enable) { + if (enabled) { + NET_DEBUG_ERR("At this point the client is supposed to be disabled. This is a bug that must be solved."); + } + // The netwroking is disabled and we can re-enable it. + enabled = true; + want_to_enable = false; + scene_synchronizer->emit_signal("sync_started"); + } + + need_full_snapshot_notified = false; + last_received_snapshot.input_id = UINT32_MAX; + + ERR_FAIL_COND_V_MSG( + player_controller_node_data == nullptr, + false, + "Is not possible to receive server snapshots if you are not tracking any NetController."); + + struct ParseData { + NetUtility::Snapshot &snapshot; + NetUtility::NodeData *player_controller_node_data; + }; + + ParseData parse_data{ + last_received_snapshot, + player_controller_node_data + }; + + const bool success = parse_sync_data( + p_snapshot, + &parse_data, + + // Parse node: + [](void *p_user_pointer, NetUtility::NodeData *p_node_data) { + ParseData *pd = static_cast(p_user_pointer); + + // Make sure this node is part of the server node too. + if (uint32_t(pd->snapshot.node_vars.size()) <= p_node_data->id) { + pd->snapshot.node_vars.resize(p_node_data->id + 1); + } + + // Make sure this snapshot has all the variables. + pd->snapshot.node_vars.write[p_node_data->id].resize(p_node_data->vars.size()); + }, + + // Parse controller: + [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id) { + ParseData *pd = static_cast(p_user_pointer); + if (p_node_data == pd->player_controller_node_data) { + // This is the main controller, store the input ID. + pd->snapshot.input_id = p_input_id; + } + }, + + // Parse variable: + [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_var_id, const Variant &p_value) { + ParseData *pd = static_cast(p_user_pointer); + +#ifdef DEBUG_ENABLED + // This can't be triggered because th `Parse Node` function + // above make sure to create room for this array. + CRASH_COND(uint32_t(pd->snapshot.node_vars.size()) <= p_node_data->id); +#endif // ~DEBUG_ENABLED + + if (unlikely(p_node_data->vars.size() != uint32_t(pd->snapshot.node_vars[p_node_data->id].size()))) { + // This mean the parser just added a new variable. + // Already notified by the parser. + pd->snapshot.node_vars.write[p_node_data->id].resize(p_node_data->vars.size()); + } + + pd->snapshot.node_vars.write[p_node_data->id].write[p_var_id].name = p_node_data->vars[p_var_id].var.name; + pd->snapshot.node_vars.write[p_node_data->id].write[p_var_id].value = p_value.duplicate(true); + }); + + if (success == false) { + NET_DEBUG_ERR("Snapshot:"); + NET_DEBUG_ERR(p_snapshot); + return false; + } + + // We espect that the player_controller is updated by this new snapshot, + // so make sure it's done so. + if (unlikely(last_received_snapshot.input_id == UINT32_MAX)) { + NET_DEBUG_PRINT("Recovery aborted, the player controller (" + player_controller_node_data->node->get_path() + ") was not part of the received snapshot, probably the server doesn't have important informations for this peer. NetUtility::Snapshot:"); + NET_DEBUG_PRINT(p_snapshot); + return false; + } else { + // Success. + + return true; + } +} + +bool ClientSynchronizer::compare_vars( + const NetUtility::NodeData *p_synchronizer_node_data, + const Vector &p_server_vars, + const Vector &p_client_vars, + Vector &r_postponed_recover) { + const NetUtility::Var *s_vars = p_server_vars.ptr(); + const NetUtility::Var *c_vars = p_client_vars.ptr(); + +#ifdef DEBUG_ENABLED + bool diff = false; +#endif + + if (p_server_vars.size() != p_client_vars.size() || + uint32_t(p_server_vars.size()) > p_synchronizer_node_data->vars.size()) { + NET_DEBUG_PRINT("Difference found: The server has a different variable count (" + itos(p_server_vars.size()) + ") compared to the client (" + itos(p_client_vars.size()) + ")."); + NET_DEBUG_PRINT("Server variables:"); + for (int i = 0; i < p_server_vars.size(); i += 1) { + NET_DEBUG_PRINT(" |- " + p_server_vars[i].name + ": " + p_server_vars[i].value); + } + NET_DEBUG_PRINT(" ------"); + + NET_DEBUG_PRINT("Client variables:"); + for (int i = 0; i < p_client_vars.size(); i += 1) { + NET_DEBUG_PRINT(" |- " + p_client_vars[i].name + ": " + p_client_vars[i].value); + } + NET_DEBUG_PRINT(" ------"); + return true; + } + + for (uint32_t var_index = 0; var_index < uint32_t(p_server_vars.size()); var_index += 1) { + if (s_vars[var_index].name == StringName()) { + // This variable was not set, skip the check. + continue; + } + + // Compare. + const bool different = + // Make sure this variable is set. + c_vars[var_index].name == StringName() || + // Check if the value is different. + !scene_synchronizer->compare( + s_vars[var_index].value, + c_vars[var_index].value); + + if (different) { + if (p_synchronizer_node_data->vars[var_index].skip_rewinding) { + // The vars are different, but this variable don't what to + // trigger a rewind. + r_postponed_recover.push_back(s_vars[var_index]); + } else { + // The vars are different. + NET_DEBUG_PRINT("Difference found on var #" + itos(var_index) + " " + p_synchronizer_node_data->vars[var_index].var.name + " " + + "Server value: `" + s_vars[var_index].value + "` " + + "Client value: `" + c_vars[var_index].value + "`. " + + "[Server name: `" + s_vars[var_index].name + "` " + + "Client name: `" + c_vars[var_index].name + "`]."); +#ifdef DEBUG_ENABLED + diff = true; +#else + return true; +#endif + } + } + } + +#ifdef DEBUG_ENABLED + return diff; +#else + + // The vars are not different. + return false; +#endif +} + +void ClientSynchronizer::notify_server_full_snapshot_is_needed() { + if (need_full_snapshot_notified) { + return; + } + + // Notify the server that a full snapshot is needed. + need_full_snapshot_notified = true; + scene_synchronizer->rpc_id(1, SNAME("_rpc_notify_need_full_snapshot")); +} diff --git a/modules/network_synchronizer/scene_synchronizer.h b/modules/network_synchronizer/scene_synchronizer.h new file mode 100644 index 000000000..cda18e891 --- /dev/null +++ b/modules/network_synchronizer/scene_synchronizer.h @@ -0,0 +1,464 @@ +/*************************************************************************/ +/* scene_synchronizer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/** + @author AndreaCatania +*/ + +#include "scene/main/node.h" + +#include "core/templates/local_vector.h" +#include "core/templates/oa_hash_map.h" +#include "net_utilities.h" +#include + +#ifndef SCENE_SYNCHRONIZER_H +#define SCENE_SYNCHRONIZER_H + +class Synchronizer; +class NetworkedController; + +/// # SceneSynchronizer +/// +/// The `SceneSynchronizer` is responsible to keep the scene of all peers in sync. +/// Usually each peer has it istantiated, and depending if it's istantiated in +/// the server or in the client, it does a different thing. +/// +/// ## The `Player` is playing the game on the server. +/// +/// The server is authoritative and it can't never be wrong. For this reason +/// the `SceneSynchronizer` on the server sends at a fixed interval (defined by +/// `server_notify_state_interval`) a snapshot to all peers. +/// +/// The clients receives the server snapshot, so it compares with the local +/// snapshot and if it's necessary perform the recovery. +/// +/// ## Variable traking +/// +/// The `SceneSynchronizer` is able to track any node variable. It's possible to specify +/// the variables to track using the function `register_variable`. +/// +/// ## NetworkedController +/// The `NetworkedController` is able to aquire the `Player` input and perform +/// operation in sync with other peers. When a discrepancy is found by the +/// `SceneSynchronizer`, it will drive the `NetworkedController` so to recover that +/// missalignment. +/// +/// +/// ## Processing function +/// Some objects, that are not direclty controlled by a `Player`, may need to be +/// in sync between peers; since those are not controlled by a `Player` is +/// not necessary use the `NetworkedController`. +/// +/// It's possible to specify some process functions using `register_process`. +/// The `SceneSynchronizer` will call these functions each frame, in sync with the +/// other peers. +/// +/// As example object we may think about a moving platform, or a bridge that +/// opens and close, or even a simple timer to track the match time. +/// An example implementation would be: +/// ``` +/// var time := 0.0 +/// +/// func _ready(): +/// # Make sure this never go out of sync. +/// SceneSynchronizer.register_variable(self, "time") +/// +/// # Make sure to call this in sync with other peers. +/// SceneSynchronizer.register_process(self, "in_sync_process") +/// +/// func in_sync_process(delta: float): +/// time += delta +/// ``` +/// In the above code the variable `time` will always be in sync. +/// +// +// # Implementation details. +// +// The entry point of the above mechanism is the function `SceneSynchronizer::process()`. +// The server `SceneSynchronizer` code is inside the class `ServerSynchronizer`. +// The client `SceneSynchronizer` code is inside the class `ClientSynchronizer`. +// The no networking `SceneSynchronizer` code is inside the class `NoNetSynchronizer`. +class SceneSynchronizer : public Node { + GDCLASS(SceneSynchronizer, Node); + + friend class Synchronizer; + friend class ServerSynchronizer; + friend class ClientSynchronizer; + friend class NoNetSynchronizer; + friend class SceneDiff; + +public: + enum SynchronizerType { + SYNCHRONIZER_TYPE_NULL, + SYNCHRONIZER_TYPE_NONETWORK, + SYNCHRONIZER_TYPE_CLIENT, + SYNCHRONIZER_TYPE_SERVER + }; + +private: + real_t server_notify_state_interval = 1.0; + real_t comparison_float_tolerance = 0.001; + + SynchronizerType synchronizer_type = SYNCHRONIZER_TYPE_NULL; + Synchronizer *synchronizer = nullptr; + bool recover_in_progress = false; + bool reset_in_progress = false; + bool rewinding_in_progress = false; + bool end_sync = false; + + bool peer_dirty = false; + OAHashMap peer_data; + + bool generate_id = false; + + // All possible registered nodes. + LocalVector node_data; + + // All registered nodes, that have the NetworkNodeId assigned, organized per + // NetworkNodeId. + LocalVector organized_node_data; + + // Controller nodes. + LocalVector node_data_controllers; + + // Just used to detect when the peer change. TODO Remove this and use a singnal instead. + void *peer_ptr = nullptr; + + int event_flag; + LocalVector event_listener; + +public: + static void _bind_methods(); + + virtual void _notification(int p_what); + +public: + SceneSynchronizer(); + ~SceneSynchronizer(); + + void set_doll_desync_tolerance(int p_tolerance); + int get_doll_desync_tolerance() const; + + void set_server_notify_state_interval(real_t p_interval); + real_t get_server_notify_state_interval() const; + + void set_comparison_float_tolerance(real_t p_tolerance); + real_t get_comparison_float_tolerance() const; + + /// Register a new node and returns its `NodeData`. + NetUtility::NodeData *register_node(Node *p_node); + uint32_t register_node_gdscript(Node *p_node); + void unregister_node(Node *p_node); + + /// Returns the node ID. + /// This may return `UINT32_MAX` in various cases: + /// - The node is not registered. + /// - The client doesn't know the ID yet. + uint32_t get_node_id(Node *p_node); + Node *get_node_from_id(uint32_t p_id); + + void register_variable(Node *p_node, const StringName &p_variable, const StringName &p_on_change_notify_to = StringName(), NetEventFlag p_flags = NetEventFlag::DEFAULT); + void unregister_variable(Node *p_node, const StringName &p_variable); + + /// Start local node sync. + void start_node_sync(const Node *p_node); + /// Stop local node sync. + void stop_node_sync(const Node *p_node); + bool is_node_sync(const Node *p_node) const; + + /// Returns the variable ID relative to the `Node`. + /// This may return `UINT32_MAX` in various cases: + /// - The node is not registered. + /// - The variable is not registered. + /// - The client doesn't know the ID yet. + uint32_t get_variable_id(Node *p_node, const StringName &p_variable); + + void set_skip_rewinding(Node *p_node, const StringName &p_variable, bool p_skip_rewinding); + + void track_variable_changes(Node *p_node, const StringName &p_variable, Object *p_object, const StringName &p_method, NetEventFlag p_flags = NetEventFlag::DEFAULT); + void untrack_variable_changes(Node *p_node, const StringName &p_variable, Object *p_object, const StringName &p_method); + + void set_node_as_controlled_by(Node *p_node, Node *p_controller); + + /// Add a dependency to a controller, so that the rewinding mechanism can + /// make sure to rewind that node when the controller is rewinded. + /// You can remove and add dependency at any time. This operation + /// don't need to be perfomed on server. + void controller_add_dependency(Node *p_controller, Node *p_node); + void controller_remove_dependency(Node *p_controller, Node *p_node); + int controller_get_dependency_count(Node *p_controller) const; + Node *controller_get_dependency(Node *p_controller, int p_index); + + void register_process(Node *p_node, const StringName &p_function); + void unregister_process(Node *p_node, const StringName &p_function); + + void start_tracking_scene_changes(Object *p_diff_handle) const; + void stop_tracking_scene_changes(Object *p_diff_handle) const; + Variant pop_scene_changes(Object *p_diff_handle) const; + void apply_scene_changes(const Variant &p_sync_data); + + bool is_recovered() const; + bool is_resetted() const; + bool is_rewinding() const; + bool is_end_sync() const; + + /// This function works only on server. + void force_state_notify(); + /// Make peers as dirty, so they will be reloaded next frame. + void dirty_peers(); + + void set_enabled(bool p_enable); + bool is_enabled() const; + + void set_peer_networking_enable(int p_peer, bool p_enable); + bool is_peer_networking_enable(int p_peer) const; + + void _on_peer_connected(int p_peer); + void _on_peer_disconnected(int p_peer); + + void _on_node_removed(Node *p_node); + + void reset_synchronizer_mode(); + void clear(); + + void _rpc_send_state(const Variant &p_snapshot); + void _rpc_notify_need_full_snapshot(); + void _rpc_set_network_enabled(bool p_enabled); + void _rpc_notify_peer_status(bool p_enabled); + + void update_peers(); + void clear_peers(); + + void change_events_begin(int p_flag); + void change_event_add(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old); + void change_events_flush(); + +private: + void expand_organized_node_data_vector(uint32_t p_size); + + /// This function is slow, but allow to take the node data even if the + /// `NetNodeId` is not yet assigned. + NetUtility::NodeData *find_node_data(const Node *p_node); + const NetUtility::NodeData *find_node_data(const Node *p_node) const; + + /// This function is super fast, but only nodes with a `NetNodeId` assigned + /// can be returned. + NetUtility::NodeData *get_node_data(NetNodeId p_id); + const NetUtility::NodeData *get_node_data(NetNodeId p_id) const; + + /// Returns the latest generated `NetNodeId`. + NetNodeId get_biggest_node_id() const; + + void reset_controllers(); + void reset_controller(NetUtility::NodeData *p_controller); + + void process(); + +#ifdef DEBUG_ENABLED + void validate_nodes(); +#endif + void purge_node_dependencies(); + + real_t get_pretended_delta() const; + + /// Read the node variables and store the value if is different from the + /// previous one and emits a signal. + void pull_node_changes(NetUtility::NodeData *p_node_data); + + /// Add node data and generates the `NetNodeId` if allowed. + void add_node_data(NetUtility::NodeData *p_node_data); + void drop_node_data(NetUtility::NodeData *p_node_data); + + /// Set the node data net id. + void set_node_data_id(NetUtility::NodeData *p_node_data, NetNodeId p_id); + +public: + /// Returns true when the vectors are the same. Uses comparison_float_tolerance member. + bool compare(const Vector2 &p_first, const Vector2 &p_second) const; + /// Returns true when the vectors are the same. Uses comparison_float_tolerance member. + bool compare(const Vector3 &p_first, const Vector3 &p_second) const; + /// Returns true when the variants are the same. Uses comparison_float_tolerance member. + bool compare(const Variant &p_first, const Variant &p_second) const; + + /// Returns true when the vectors are the same. + static bool compare(const Vector2 &p_first, const Vector2 &p_second, real_t p_tolerance); + /// Returns true when the vectors are the same. + static bool compare(const Vector3 &p_first, const Vector3 &p_second, real_t p_tolerance); + /// Returns true when the variants are the same. + static bool compare(const Variant &p_first, const Variant &p_second, real_t p_tolerance); + + /// Returns true if this peer is server. + bool is_server() const; + /// Returns true if this peer is client. + bool is_client() const; + /// Returns true if there is no network. + bool is_no_network() const; + /// Returns true if network is enabled. + bool is_networked() const; +}; + +class Synchronizer { +protected: + SceneSynchronizer *scene_synchronizer; + +public: + Synchronizer(SceneSynchronizer *p_node); + virtual ~Synchronizer() = default; + + virtual void clear() = 0; + + virtual void process() = 0; + virtual void on_node_added(NetUtility::NodeData *p_node_data) {} + virtual void on_node_removed(NetUtility::NodeData *p_node_data) {} + virtual void on_variable_added(NetUtility::NodeData *p_node_data, const StringName &p_var_name) {} + virtual void on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) {} + virtual void on_controller_reset(NetUtility::NodeData *p_node_data) {} +}; + +class NoNetSynchronizer : public Synchronizer { + friend class SceneSynchronizer; + + bool enabled = true; + +public: + NoNetSynchronizer(SceneSynchronizer *p_node); + + virtual void clear() override; + virtual void process() override; + + void set_enabled(bool p_enabled); + bool is_enabled() const; +}; + +class ServerSynchronizer : public Synchronizer { + friend class SceneSynchronizer; + + real_t state_notifier_timer = 0.0; + + struct Change { + bool not_known_before = false; + Set uknown_vars; + Set vars; + }; + + /// The changes; the order matters because the index is the NetNodeId. + LocalVector changes; + +public: + ServerSynchronizer(SceneSynchronizer *p_node); + + virtual void clear() override; + virtual void process() override; + virtual void on_node_added(NetUtility::NodeData *p_node_data) override; + virtual void on_variable_added(NetUtility::NodeData *p_node_data, const StringName &p_var_name) override; + virtual void on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) override; + + void process_snapshot_notificator(real_t p_delta); + Vector global_nodes_generate_snapshot(bool p_force_full_snapshot) const; + void controller_generate_snapshot(const NetUtility::NodeData *p_node_data, bool p_force_full_snapshot, Vector &r_snapshot_result) const; + void generate_snapshot_node_data(const NetUtility::NodeData *p_node_data, bool p_force_full_snapshot, Vector &r_result) const; +}; + +class ClientSynchronizer : public Synchronizer { + friend class SceneSynchronizer; + + NetUtility::NodeData *player_controller_node_data = nullptr; + OAHashMap node_paths; + + NetUtility::Snapshot last_received_snapshot; + std::deque client_snapshots; + std::deque server_snapshots; + uint32_t last_checked_input = 0; + bool enabled = true; + bool want_to_enable = false; + + bool need_full_snapshot_notified = false; + + struct EndSyncEvent { + NetUtility::NodeData *node_data; + NetVarId var_id; + Variant old_value; + + bool operator<(const EndSyncEvent &p_other) const { + if (node_data->id == p_other.node_data->id) { + return var_id < p_other.var_id; + } else { + return node_data->id < p_other.node_data->id; + } + } + }; + + Set sync_end_events; + +public: + ClientSynchronizer(SceneSynchronizer *p_node); + + virtual void clear() override; + + virtual void process() override; + virtual void on_node_added(NetUtility::NodeData *p_node_data) override; + virtual void on_node_removed(NetUtility::NodeData *p_node_data) override; + virtual void on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) override; + virtual void on_controller_reset(NetUtility::NodeData *p_node_data) override; + + void receive_snapshot(Variant p_snapshot); + bool parse_sync_data( + Variant p_snapshot, + void *p_user_pointer, + void (*p_node_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data), + void (*p_controller_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id), + void (*p_variable_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_value)); + + void set_enabled(bool p_enabled); + +private: + /// Store node data organized per controller. + void store_snapshot(); + + void store_controllers_snapshot( + const NetUtility::Snapshot &p_snapshot, + std::deque &r_snapshot_storage); + + void process_controllers_recovery(real_t p_delta); + void process_paused_controller_recovery(real_t p_delta); + bool parse_snapshot(Variant p_snapshot); + bool compare_vars( + const NetUtility::NodeData *p_synchronizer_node_data, + const Vector &p_server_vars, + const Vector &p_client_vars, + Vector &r_postponed_recover); + + void notify_server_full_snapshot_is_needed(); +}; + +VARIANT_ENUM_CAST(NetEventFlag) + +#endif diff --git a/modules/network_synchronizer/tests/test_bit_array.h b/modules/network_synchronizer/tests/test_bit_array.h new file mode 100644 index 000000000..9aa13124e --- /dev/null +++ b/modules/network_synchronizer/tests/test_bit_array.h @@ -0,0 +1,120 @@ +/*************************************************************************/ +/* test_bit_array.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_BIT_ARRAY_H +#define TEST_BIT_ARRAY_H + +#include "modules/network_synchronizer/bit_array.h" + +#include "tests/test_macros.h" + +namespace TestBitArray { + +TEST_CASE("[Modules][BitArray] Read and write") { + BitArray array; + int offset = 0; + int bits = {}; + uint64_t value = {}; + + SUBCASE("[Modules][BitArray] One bit") { + bits = 1; + + SUBCASE("[Modules][BitArray] One") { + value = 0b1; + } + SUBCASE("[Modules][BitArray] Zero") { + value = 0b0; + } + } + SUBCASE("[Modules][BitArray] 16 mixed bits") { + bits = 16; + value = 0b1010101010101010; + } + SUBCASE("[Modules][BitArray] One and 4 zeroes") { + bits = 5; + value = 0b10000; + } + SUBCASE("[Modules][BitArray] 64 bits") { + bits = 64; + + SUBCASE("[Modules][BitArray] One") { + value = UINT64_MAX; + } + SUBCASE("[Modules][BitArray] Zero") { + value = 0; + } + } + SUBCASE("[Modules][BitArray] One bit with offset") { + bits = 1; + offset = 64; + array.resize_in_bits(offset); + + SUBCASE("[Modules][BitArray] One") { + array.store_bits(0, UINT64_MAX, 64); + value = 0b0; + } + SUBCASE("[Modules][BitArray] Zero") { + array.store_bits(0, 0, 64); + value = 0b1; + } + } + + array.resize_in_bits(offset + bits); + array.store_bits(offset, value, bits); + CHECK_MESSAGE(array.read_bits(offset, bits) == value, "Should read the same value"); +} + +TEST_CASE("[Modules][BitArray] Constructing from Vector") { + Vector data; + data.push_back(-1); + data.push_back(0); + data.push_back(1); + + const BitArray array(data); + CHECK_MESSAGE(array.size_in_bits() == data.size() * 8.0, "Number of bits must be equal to size of original data"); + CHECK_MESSAGE(array.size_in_bytes() == data.size(), "Number of bytes must be equal to size of original data"); + for (int i = 0; i < data.size(); ++i) { + CHECK_MESSAGE(array.read_bits(i * 8, 8) == data[i], "Readed bits should be equal to the original"); + } +} + +TEST_CASE("[Modules][BitArray] Pre-allocation and zeroing") { + constexpr uint64_t value = UINT64_MAX; + constexpr int bits = sizeof(value); + + BitArray array(bits); + CHECK_MESSAGE(array.size_in_bits() == bits, "Number of bits must be equal to allocated"); + array.store_bits(0, value, bits); + array.zero(); + CHECK_MESSAGE(array.read_bits(0, bits) == 0, "Should read zero"); +} +} // namespace TestBitArray + +#endif // TEST_BIT_ARRAY_H diff --git a/modules/network_synchronizer/tests/test_data_buffer.h b/modules/network_synchronizer/tests/test_data_buffer.h new file mode 100644 index 000000000..bee7336f7 --- /dev/null +++ b/modules/network_synchronizer/tests/test_data_buffer.h @@ -0,0 +1,550 @@ +/*************************************************************************/ +/* test_data_buffer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_DATA_BUFFER_H +#define TEST_DATA_BUFFER_H + +#include "modules/network_synchronizer/data_buffer.h" +#include "modules/network_synchronizer/scene_synchronizer.h" + +#include "tests/test_macros.h" + +namespace TestDataBuffer { + +inline Vector real_values(DataBuffer::CompressionLevel p_compression_level) { + Vector values; + values.append(Math_PI); + values.append(0.0); + values.append(-3.04); + values.append(3.04); + values.append(0.5); + values.append(-0.5); + values.append(1); + values.append(-1); + values.append(0.9); + values.append(-0.9); + values.append(3.9); + values.append(-3.9); + values.append(8); + + switch (p_compression_level) { + case DataBuffer::COMPRESSION_LEVEL_3: { + values.append(-15'360); + values.append(15'360); + } break; + case DataBuffer::COMPRESSION_LEVEL_2: { + // https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Half_precision_examples + values.append(-65'504); + values.append(65'504); + values.append(Math::pow(2.0, -14) / 1024); + values.append(Math::pow(2.0, -14) * 1023 / 1024); + values.append(Math::pow(2.0, -1) * (1 + 1023.0 / 1024)); + values.append((1 + 1.0 / 1024)); + } break; + case DataBuffer::COMPRESSION_LEVEL_1: { + // https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Single-precision_examples + values.append(FLT_MIN); + values.append(-FLT_MAX); + values.append(FLT_MAX); + values.append(Math::pow(2.0, -149)); + values.append(Math::pow(2.0, -126) * (1 - Math::pow(2.0, -23))); + values.append(1 - Math::pow(2.0, -24)); + values.append(1 + Math::pow(2.0, -23)); + } break; + case DataBuffer::COMPRESSION_LEVEL_0: { + // https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples + values.append(DBL_MIN); + values.append(DBL_MAX); + values.append(-DBL_MAX); + values.append(1.0000000000000002); + values.append(4.9406564584124654 * Math::pow(10.0, -324)); + values.append(2.2250738585072009 * Math::pow(10.0, -308)); + } break; + } + + return values; +} + +TEST_CASE("[Modules][DataBuffer] Bool") { + bool value = {}; + + SUBCASE("[Modules][DataBuffer] false") { + value = false; + } + SUBCASE("[Modules][DataBuffer] true") { + value = true; + } + + DataBuffer buffer; + buffer.begin_write(0); + CHECK_MESSAGE(buffer.add_bool(value) == value, "Should return the same value"); + buffer.begin_read(); + CHECK_MESSAGE(buffer.read_bool() == value, "Should read the same value"); +} + +TEST_CASE("[Modules][DataBuffer] Int") { + DataBuffer::CompressionLevel compression_level = {}; + int64_t value = {}; + + DataBuffer buffer; + SUBCASE("[Modules][DataBuffer] Compression level 3") { + compression_level = DataBuffer::COMPRESSION_LEVEL_3; + + SUBCASE("[Modules][DataBuffer] Positive") { + value = 127; + } + SUBCASE("[Modules][DataBuffer] Zero") { + value = 0; + } + SUBCASE("[Modules][DataBuffer] Negative") { + value = -128; + } + } + + SUBCASE("[Modules][DataBuffer] Compression level 2") { + compression_level = DataBuffer::COMPRESSION_LEVEL_2; + + SUBCASE("[Modules][DataBuffer] Positive") { + value = 32767; + } + SUBCASE("[Modules][DataBuffer] Zero") { + value = 0; + } + SUBCASE("[Modules][DataBuffer] Negative") { + value = -32768; + } + } + + SUBCASE("[Modules][DataBuffer] Compression level 1") { + compression_level = DataBuffer::COMPRESSION_LEVEL_1; + + SUBCASE("[Modules][DataBuffer] Positive") { + value = 2147483647; + } + SUBCASE("[Modules][DataBuffer] Zero") { + value = 0; + } + SUBCASE("[Modules][DataBuffer] Negative") { + value = -2147483648LL; + } + } + + SUBCASE("[Modules][DataBuffer] Compression level 0") { + compression_level = DataBuffer::COMPRESSION_LEVEL_0; + + SUBCASE("[Modules][DataBuffer] Positive") { + value = 2147483647; + } + SUBCASE("[Modules][DataBuffer] Zero") { + value = 0; + } + SUBCASE("[Modules][DataBuffer] Negative") { + value = -9223372036854775807LL; + } + } + + buffer.begin_write(0); + CHECK_MESSAGE(buffer.add_int(value, compression_level) == value, "Should return the same value"); + buffer.begin_read(); + CHECK_MESSAGE(buffer.read_int(compression_level) == value, "Should read the same value"); +} + +TEST_CASE("[Modules][DataBuffer] Real") { + DataBuffer::CompressionLevel compression_level = {}; + + SUBCASE("[Modules][DataBuffer] Compression level 3 (Minifloat)") { + compression_level = DataBuffer::COMPRESSION_LEVEL_3; + } + + SUBCASE("[Modules][DataBuffer] Compression level 2 (Half perception)") { + compression_level = DataBuffer::COMPRESSION_LEVEL_2; + } + + SUBCASE("[Modules][DataBuffer] Compression level 1 (Single perception)") { + compression_level = DataBuffer::COMPRESSION_LEVEL_1; + } + + SUBCASE("[Modules][DataBuffer] Compression level 0 (Double perception)") { + compression_level = DataBuffer::COMPRESSION_LEVEL_0; + } + + DataBuffer buffer; + const Vector values = real_values(compression_level); + const double epsilon = Math::pow(2.0, DataBuffer::get_mantissa_bits(compression_level) - 1); + for (int i = 0; i < values.size(); ++i) { + buffer.begin_write(0); + const double value = values[i]; + CHECK_MESSAGE(buffer.add_real(value, compression_level) == doctest::Approx(value).epsilon(epsilon), "Should return the same value"); + + buffer.begin_read(); + CHECK_MESSAGE(buffer.read_real(compression_level) == doctest::Approx(value).epsilon(epsilon), "Should read the same value"); + } +} + +TEST_CASE("[Modules][DataBuffer] Positive unit real") { + DataBuffer::CompressionLevel compression_level = {}; + double epsilon = {}; + + SUBCASE("[Modules][DataBuffer] Compression level 3") { + compression_level = DataBuffer::COMPRESSION_LEVEL_3; + epsilon = 0.033335; + } + + SUBCASE("[Modules][DataBuffer] Compression level 2") { + compression_level = DataBuffer::COMPRESSION_LEVEL_2; + epsilon = 0.007935; + } + + SUBCASE("[Modules][DataBuffer] Compression level 1") { + compression_level = DataBuffer::COMPRESSION_LEVEL_1; + epsilon = 0.00196; + } + + SUBCASE("[Modules][DataBuffer] Compression level 0") { + compression_level = DataBuffer::COMPRESSION_LEVEL_0; + epsilon = 0.00049; + } + + DataBuffer buffer; + const Vector values = real_values(compression_level); + for (int i = 0; i < values.size(); ++i) { + const double value = values[i]; + if (value < 0) { + // Skip negative values + continue; + } + double value_integral; + const double value_unit = modf(values[i], &value_integral); + buffer.begin_write(0); + CHECK_MESSAGE(buffer.add_positive_unit_real(value_unit, compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should return the same value"); + + buffer.begin_read(); + CHECK_MESSAGE(buffer.read_positive_unit_real(compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should read the same value"); + } +} + +TEST_CASE("[Modules][DataBuffer] Unit real") { + DataBuffer::CompressionLevel compression_level = {}; + double epsilon = {}; + + SUBCASE("[Modules][DataBuffer] Compression level 3") { + compression_level = DataBuffer::COMPRESSION_LEVEL_3; + epsilon = 0.033335; + } + + SUBCASE("[Modules][DataBuffer] Compression level 2") { + compression_level = DataBuffer::COMPRESSION_LEVEL_2; + epsilon = 0.007935; + } + + SUBCASE("[Modules][DataBuffer] Compression level 1") { + compression_level = DataBuffer::COMPRESSION_LEVEL_1; + epsilon = 0.00196; + } + + SUBCASE("[Modules][DataBuffer] Compression level 0") { + compression_level = DataBuffer::COMPRESSION_LEVEL_0; + epsilon = 0.00049; + } + + DataBuffer buffer; + const Vector values = real_values(compression_level); + for (int i = 0; i < values.size(); ++i) { + double value_integral; + const double value_unit = modf(values[i], &value_integral); + buffer.begin_write(0); + CHECK_MESSAGE(buffer.add_unit_real(value_unit, compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should return the same value"); + + buffer.begin_read(); + CHECK_MESSAGE(buffer.read_unit_real(compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should read the same value"); + } +} + +TEST_CASE("[Modules][DataBuffer] Vector2") { + DataBuffer::CompressionLevel compression_level = {}; + + SUBCASE("[Modules][DataBuffer] Compression level 3") { + compression_level = DataBuffer::COMPRESSION_LEVEL_3; + } + + SUBCASE("[Modules][DataBuffer] Compression level 2") { + compression_level = DataBuffer::COMPRESSION_LEVEL_2; + } + + SUBCASE("[Modules][DataBuffer] Compression level 1") { + compression_level = DataBuffer::COMPRESSION_LEVEL_1; + } + + SUBCASE("[Modules][DataBuffer] Compression level 0") { + compression_level = DataBuffer::COMPRESSION_LEVEL_0; + } + + DataBuffer buffer; + const double epsilon = Math::pow(2.0, DataBuffer::get_mantissa_bits(compression_level) - 1); + const Vector values = real_values(compression_level); + for (int i = 0; i < values.size(); ++i) { +#ifdef REAL_T_IS_DOUBLE + const Vector2 value = Vector2(values[i], values[i]); +#else + const real_t clamped_value = CLAMP(values[i], -FLT_MIN, FLT_MAX); + const Vector2 value = Vector2(clamped_value, clamped_value); +#endif + buffer.begin_write(0); + const Vector2 added_value = buffer.add_vector2(value, compression_level); + CHECK_MESSAGE(added_value.x == doctest::Approx(value.x).epsilon(epsilon), "Added Vector2 should have the same x axis"); + CHECK_MESSAGE(added_value.y == doctest::Approx(value.y).epsilon(epsilon), "Added Vector2 should have the same y axis"); + + buffer.begin_read(); + const Vector2 read_value = buffer.read_vector2(compression_level); + CHECK_MESSAGE(read_value.x == doctest::Approx(value.x).epsilon(epsilon), "Read Vector2 should have the same x axis"); + CHECK_MESSAGE(read_value.y == doctest::Approx(value.y).epsilon(epsilon), "Read Vector2 should have the same y axis"); + } +} + +TEST_CASE("[Modules][DataBuffer] Vector3") { + DataBuffer::CompressionLevel compression_level = {}; + + SUBCASE("[Modules][DataBuffer] Compression level 3") { + compression_level = DataBuffer::COMPRESSION_LEVEL_3; + } + + SUBCASE("[Modules][DataBuffer] Compression level 2") { + compression_level = DataBuffer::COMPRESSION_LEVEL_2; + } + + SUBCASE("[Modules][DataBuffer] Compression level 1") { + compression_level = DataBuffer::COMPRESSION_LEVEL_1; + } + + SUBCASE("[Modules][DataBuffer] Compression level 0") { + compression_level = DataBuffer::COMPRESSION_LEVEL_0; + } + + DataBuffer buffer; + const Vector values = real_values(compression_level); + const double epsilon = Math::pow(2.0, DataBuffer::get_mantissa_bits(compression_level) - 1); + for (int i = 0; i < values.size(); ++i) { +#ifdef REAL_T_IS_DOUBLE + const Vector3 value = Vector3(values[i], values[i], values[i]); +#else + const real_t clamped_value = CLAMP(values[i], -FLT_MIN, FLT_MAX); + const Vector3 value = Vector3(clamped_value, clamped_value, clamped_value); +#endif + buffer.begin_write(0); + const Vector3 added_value = buffer.add_vector3(value, compression_level); + CHECK_MESSAGE(added_value.x == doctest::Approx(value.x).epsilon(epsilon), "Added Vector3 should have the same x axis"); + CHECK_MESSAGE(added_value.y == doctest::Approx(value.y).epsilon(epsilon), "Added Vector3 should have the same y axis"); + CHECK_MESSAGE(added_value.z == doctest::Approx(value.z).epsilon(epsilon), "Added Vector3 should have the same z axis"); + + buffer.begin_read(); + const Vector3 read_value = buffer.read_vector3(compression_level); + CHECK_MESSAGE(read_value.x == doctest::Approx(value.x).epsilon(epsilon), "Read Vector3 should have the same x axis"); + CHECK_MESSAGE(read_value.y == doctest::Approx(value.y).epsilon(epsilon), "Read Vector3 should have the same y axis"); + CHECK_MESSAGE(read_value.z == doctest::Approx(value.z).epsilon(epsilon), "Read Vector3 should have the same z axis"); + } +} + +TEST_CASE("[Modules][DataBuffer] Normalized Vector3") { + DataBuffer::CompressionLevel compression_level = {}; + double epsilon = {}; + + SUBCASE("[Modules][DataBuffer] Compression level 3") { + compression_level = DataBuffer::COMPRESSION_LEVEL_3; + epsilon = 0.033335; + } + + SUBCASE("[Modules][DataBuffer] Compression level 2") { + compression_level = DataBuffer::COMPRESSION_LEVEL_2; + epsilon = 0.007935; + } + + SUBCASE("[Modules][DataBuffer] Compression level 1") { + compression_level = DataBuffer::COMPRESSION_LEVEL_1; + epsilon = 0.00196; + } + + SUBCASE("[Modules][DataBuffer] Compression level 0") { + compression_level = DataBuffer::COMPRESSION_LEVEL_0; + epsilon = 0.00049; + } + + DataBuffer buffer; + const Vector values = real_values(compression_level); + for (int i = 0; i < values.size(); ++i) { + Vector3 value = Vector3(values[i], values[i], values[i]).normalized(); + if (!value.is_normalized()) { + // Normalization fails for some numbers, probably a bug! + continue; + } + buffer.begin_write(0); + const Vector3 added_value = buffer.add_normalized_vector3(value, compression_level); + CHECK_MESSAGE(added_value.x == doctest::Approx(value.x).epsilon(epsilon), "Added Vector3 should have the same x axis"); + CHECK_MESSAGE(added_value.y == doctest::Approx(value.y).epsilon(epsilon), "Added Vector3 should have the same y axis"); + CHECK_MESSAGE(added_value.z == doctest::Approx(value.z).epsilon(epsilon), "Added Vector3 should have the same z axis"); + + buffer.begin_read(); + const Vector3 read_value = buffer.read_normalized_vector3(compression_level); + CHECK_MESSAGE(read_value.x == doctest::Approx(value.x).epsilon(epsilon), "Read Vector3 should have the same x axis"); + CHECK_MESSAGE(read_value.y == doctest::Approx(value.y).epsilon(epsilon), "Read Vector3 should have the same y axis"); + CHECK_MESSAGE(read_value.z == doctest::Approx(value.z).epsilon(epsilon), "Read Vector3 should have the same z axis"); + } +} + +TEST_CASE("[Modules][DataBuffer] Variant") { + Variant value = {}; + + SUBCASE("[Modules][DataBuffer] Invalid value") { + value = {}; + } + SUBCASE("[Modules][DataBuffer] String") { + value = "VariantString"; + } + SUBCASE("[Modules][DataBuffer] Vector") { + value = sarray("VariantString1", "VariantString2", "VariantString3"); + } + SUBCASE("[Modules][DataBuffer] Dictionary") { + Dictionary dictionary; + dictionary[1] = "Value"; + dictionary["Key"] = -1; + value = dictionary; + } + SUBCASE("[Modules][DataBuffer] Array") { + Array array; + array.append("VariantString"); + array.append(0); + array.append(-1.2); + value = array; + } + + DataBuffer buffer; + buffer.begin_write(0); + CHECK_MESSAGE(SceneSynchronizer::compare(buffer.add_variant(value), value, DBL_EPSILON), "Should return the same value"); + buffer.begin_read(); + CHECK_MESSAGE(SceneSynchronizer::compare(buffer.read_variant(), value, DBL_EPSILON), "Should read the same value"); +} + +TEST_CASE("[Modules][DataBuffer] Seek") { + DataBuffer buffer; + buffer.begin_write(0); + buffer.add_bool(true); + buffer.add_bool(false); + buffer.begin_read(); + + ERR_PRINT_OFF + buffer.seek(-1); + CHECK_MESSAGE(buffer.get_bit_offset() == 0, "Bit offset should fail for negative values"); + ERR_PRINT_ON + + buffer.seek(1); + CHECK_MESSAGE(buffer.get_bit_offset() == 1, "Bit offset should be 1 after seek to 1"); + CHECK_MESSAGE(buffer.read_bool() == false, "Should read false at position 1"); + + buffer.seek(0); + CHECK_MESSAGE(buffer.get_bit_offset() == 0, "Bit offset should be 0 after seek to 0"); + CHECK_MESSAGE(buffer.read_bool() == true, "Should read true at position 0"); +} + +TEST_CASE("[Modules][DataBuffer] Metadata") { + bool value = {}; + bool metadata = {}; + + SUBCASE("[Modules][DataBuffer] True") { + metadata = true; + value = false; + } + + SUBCASE("[Modules][DataBuffer] False") { + metadata = false; + value = true; + } + + const int metadata_size = DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0); + DataBuffer buffer; + buffer.begin_write(metadata_size); + buffer.add_bool(metadata); + buffer.add_bool(value); + buffer.begin_read(); + CHECK_MESSAGE(buffer.read_bool() == metadata, "Should return correct metadata"); + CHECK_MESSAGE(buffer.read_bool() == value, "Should return correct value after metadata"); + CHECK_MESSAGE(buffer.get_metadata_size() == metadata_size, "Metadata size should be equal to expected"); + CHECK_MESSAGE(buffer.size() == DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0), "Size should be equal to expected"); + CHECK_MESSAGE(buffer.total_size() == DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0) + metadata_size, "Total size should be equal to expected"); +} + +TEST_CASE("[Modules][DataBuffer] Zero") { + constexpr DataBuffer::CompressionLevel compression = DataBuffer::COMPRESSION_LEVEL_0; + DataBuffer buffer; + buffer.begin_write(0); + buffer.add_int(-1, compression); + buffer.zero(); + buffer.begin_read(); + CHECK_MESSAGE(buffer.read_int(compression) == 0, "Should return 0"); +} + +TEST_CASE("[Modules][DataBuffer] Shrinking") { + DataBuffer buffer; + buffer.begin_write(0); + for (int i = 0; i < 2; ++i) { + buffer.add_real(3.14, DataBuffer::COMPRESSION_LEVEL_0); + } + const int original_size = buffer.total_size(); + + ERR_PRINT_OFF; + buffer.shrink_to(0, original_size + 1); + ERR_PRINT_ON; + CHECK_MESSAGE(buffer.total_size() == original_size, "Shrinking to a larger size should fail."); + + ERR_PRINT_OFF; + buffer.shrink_to(0, -1); + ERR_PRINT_ON; + CHECK_MESSAGE(buffer.total_size() == original_size, "Shrinking with a negative bits size should fail."); + + buffer.shrink_to(0, original_size - 8); + CHECK_MESSAGE(buffer.total_size() == original_size - 8, "Shrinking by 1 byte should succeed."); + CHECK_MESSAGE(buffer.get_buffer().size_in_bits() == original_size, "Buffer size after shrinking by 1 byte should be the same."); + + buffer.dry(); + CHECK_MESSAGE(buffer.get_buffer().size_in_bits() == original_size - 8, "Buffer size after dry should changed to the smallest posiible."); +} + +TEST_CASE("[Modules][DataBuffer] Skip") { + const bool value = true; + + DataBuffer buffer; + buffer.add_bool(!value); + buffer.add_bool(value); + + buffer.begin_read(); + buffer.seek(DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0)); + CHECK_MESSAGE(buffer.read_bool() == value, "Should read the same value"); +} +} // namespace TestDataBuffer + +#endif // TEST_DATA_BUFFER_H diff --git a/modules/network_synchronizer/tests/test_interpolator.h b/modules/network_synchronizer/tests/test_interpolator.h new file mode 100644 index 000000000..031bd9765 --- /dev/null +++ b/modules/network_synchronizer/tests/test_interpolator.h @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* test_interpolator.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_INTERPOLATOR_H +#define TEST_INTERPOLATOR_H + +#include "modules/network_synchronizer/interpolator.h" + +#include "tests/test_macros.h" + +namespace TestInterpolator { + +template +T generate_value(real_t value) { + if constexpr (std::is_same_v || std::is_same_v) { + return T(value, value); + } else if constexpr (std::is_same_v || std::is_same_v) { + return T(value, value, value); + } else { + return static_cast(value); + } +} + +// TODO: Add other types +TEST_CASE_TEMPLATE("[Modules][Interpolator] Interpolation", T, int, real_t, Vector2, Vector2i, Vector3) { + LocalVector fractions; + fractions.reserve(7); + fractions.push_back(0.0); + fractions.push_back(1.0); + fractions.push_back(0.5); + fractions.push_back(0.001); + fractions.push_back(0.999); + fractions.push_back(0.25); + fractions.push_back(0.75); + + Map values; + values.insert(0.0, 1.0); + values.insert(-1.0, 1.0); + values.insert(0.0, -1.0); + values.insert(10, 15); + + Interpolator interpolator; + for (const Map::Element *E = values.front(); E; E = E->next()) { + for (uint32_t j = 0; j < fractions.size(); ++j) { + // Skip custom interpolator for now + for (int k = Interpolator::FALLBACK_INTERPOLATE; k < Interpolator::FALLBACK_CUSTOM_INTERPOLATOR; ++k) { + const T first_value = generate_value(E->key()); + const T second_value = generate_value(E->value()); + + interpolator.reset(); + const int variable_id = interpolator.register_variable(T(), static_cast(k)); + interpolator.terminate_init(); + interpolator.begin_write(0); + interpolator.epoch_insert(variable_id, first_value); + interpolator.end_write(); + + interpolator.begin_write(1); + interpolator.epoch_insert(variable_id, second_value); + interpolator.end_write(); + + CAPTURE(k); + CAPTURE(fractions[j]); + CAPTURE(first_value); + CAPTURE(second_value); + const T result = interpolator.pop_epoch(0, fractions[j])[0]; + switch (k) { + case Interpolator::FALLBACK_INTERPOLATE: { + CHECK(result == Interpolator::interpolate(first_value, second_value, fractions[j]).operator T()); + } break; + case Interpolator::FALLBACK_DEFAULT: { + if (fractions[j] == 0.0) { + CHECK(result == first_value); + } else if (fractions[j] == 1.0) { + CHECK(result == second_value); + } else { + CHECK(result == T()); + } + } break; + case Interpolator::FALLBACK_OLD_OR_NEAREST: { + if (fractions[j] == 0.0) { + CHECK(result == first_value); + } else if (fractions[j] == 1.0) { + CHECK(result == second_value); + } else { + CHECK(result == first_value); + } + } break; + case Interpolator::FALLBACK_NEW_OR_NEAREST: { + if (fractions[j] == 0.0) { + CHECK(result == first_value); + } else if (fractions[j] == 1.0) { + CHECK(result == second_value); + } else { + CHECK(result == second_value); + } + } break; + } + } + } + } +} +} // namespace TestInterpolator + +#endif // TEST_INTERPOLATOR_H