mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2025-01-30 21:09:19 +01:00
Added the NetworkedController & SceneRewinder module from https://github.com/godotengine/godot/pull/37200. It's disabled for now, it needs to be ported.
This commit is contained in:
parent
5ab825ddf8
commit
6adee8f1b0
8
modules/network_synchronizer/SCsub
Normal file
8
modules/network_synchronizer/SCsub
Normal file
@ -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")
|
134
modules/network_synchronizer/bit_array.cpp
Normal file
134
modules/network_synchronizer/bit_array.cpp
Normal file
@ -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<uint8_t> &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<float>(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<uint64_t>((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());
|
||||||
|
}
|
||||||
|
}
|
69
modules/network_synchronizer/bit_array.h
Normal file
69
modules/network_synchronizer/bit_array.h
Normal file
@ -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<uint8_t> bytes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BitArray() = default;
|
||||||
|
BitArray(uint32_t p_initial_size_in_bit);
|
||||||
|
BitArray(const Vector<uint8_t> &p_bytes);
|
||||||
|
|
||||||
|
const Vector<uint8_t> &get_bytes() const {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<uint8_t> &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
|
324
modules/network_synchronizer/class_docs/DataBuffer.xml
Normal file
324
modules/network_synchronizer/class_docs/DataBuffer.xml
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<class name="DataBuffer" inherits="Object" version="4.0">
|
||||||
|
<brief_description>
|
||||||
|
</brief_description>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<tutorials>
|
||||||
|
</tutorials>
|
||||||
|
<methods>
|
||||||
|
<method name="add_bool">
|
||||||
|
<return type="bool" />
|
||||||
|
<argument index="0" name="value" type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_int">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="value" type="int" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_normalized_vector2">
|
||||||
|
<return type="Vector2" />
|
||||||
|
<argument index="0" name="value" type="Vector2" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_normalized_vector3">
|
||||||
|
<return type="Vector3" />
|
||||||
|
<argument index="0" name="value" type="Vector3" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_positive_unit_real">
|
||||||
|
<return type="float" />
|
||||||
|
<argument index="0" name="value" type="float" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_real">
|
||||||
|
<return type="float" />
|
||||||
|
<argument index="0" name="value" type="float" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_unit_real">
|
||||||
|
<return type="float" />
|
||||||
|
<argument index="0" name="value" type="float" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_variant">
|
||||||
|
<return type="Variant" />
|
||||||
|
<argument index="0" name="value" type="Variant" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_vector2">
|
||||||
|
<return type="Vector2" />
|
||||||
|
<argument index="0" name="value" type="Vector2" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="add_vector3">
|
||||||
|
<return type="Vector3" />
|
||||||
|
<argument index="0" name="value" type="Vector3" />
|
||||||
|
<argument index="1" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="begin_read">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="begin_write">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="meta_size" type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="dry">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_bool_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_int_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_normalized_vector2_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_normalized_vector3_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_real_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_unit_real_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_vector2_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_vector3_size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_bool">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_bool_size">
|
||||||
|
<return type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_int">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_int_size">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_normalized_vector2">
|
||||||
|
<return type="Vector2" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_normalized_vector2_size">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_normalized_vector3">
|
||||||
|
<return type="Vector3" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_normalized_vector3_size">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_real">
|
||||||
|
<return type="float" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_real_size">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_unit_real">
|
||||||
|
<return type="float" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_unit_real_size">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_variant">
|
||||||
|
<return type="Variant" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_variant_size">
|
||||||
|
<return type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_vector2">
|
||||||
|
<return type="Vector2" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_vector2_size">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_vector3">
|
||||||
|
<return type="Vector3" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_vector3_size">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="size" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_bool">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_int">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_normalized_vector2">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_normalized_vector3">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_real">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_unit_real">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_vector2">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="skip_vector3">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
</methods>
|
||||||
|
<constants>
|
||||||
|
<constant name="DATA_TYPE_BOOL" value="0" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_INT" value="1" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_REAL" value="2" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_UNIT_REAL" value="4" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_VECTOR2" value="5" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_NORMALIZED_VECTOR2" value="6" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_VECTOR3" value="7" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_NORMALIZED_VECTOR3" value="8" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="COMPRESSION_LEVEL_0" value="0" enum="CompressionLevel">
|
||||||
|
</constant>
|
||||||
|
<constant name="COMPRESSION_LEVEL_1" value="1" enum="CompressionLevel">
|
||||||
|
</constant>
|
||||||
|
<constant name="COMPRESSION_LEVEL_2" value="2" enum="CompressionLevel">
|
||||||
|
</constant>
|
||||||
|
<constant name="COMPRESSION_LEVEL_3" value="3" enum="CompressionLevel">
|
||||||
|
</constant>
|
||||||
|
</constants>
|
||||||
|
</class>
|
62
modules/network_synchronizer/class_docs/Interpolator.xml
Normal file
62
modules/network_synchronizer/class_docs/Interpolator.xml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<class name="Interpolator" inherits="Object" version="4.0">
|
||||||
|
<brief_description>
|
||||||
|
</brief_description>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<tutorials>
|
||||||
|
</tutorials>
|
||||||
|
<methods>
|
||||||
|
<method name="epoch_insert">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="var_id" type="int" />
|
||||||
|
<argument index="1" name="value" type="Variant" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_last_pop_epoch" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="pop_epoch">
|
||||||
|
<return type="Array" />
|
||||||
|
<argument index="0" name="epoch" type="int" />
|
||||||
|
<argument index="1" name="arg1" type="float" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="register_variable">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="default" type="Variant" />
|
||||||
|
<argument index="1" name="fallback" type="int" enum="Interpolator.Fallback" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_variable_custom_interpolator">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="var_id" type="int" />
|
||||||
|
<argument index="1" name="object" type="Object" />
|
||||||
|
<argument index="2" name="function_name" type="StringName" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_variable_default">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="var_id" type="int" />
|
||||||
|
<argument index="1" name="default" type="Variant" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
</methods>
|
||||||
|
<constants>
|
||||||
|
<constant name="FALLBACK_INTERPOLATE" value="0" enum="Fallback">
|
||||||
|
</constant>
|
||||||
|
<constant name="FALLBACK_DEFAULT" value="1" enum="Fallback">
|
||||||
|
</constant>
|
||||||
|
<constant name="FALLBACK_NEW_OR_NEAREST" value="3" enum="Fallback">
|
||||||
|
</constant>
|
||||||
|
<constant name="FALLBACK_OLD_OR_NEAREST" value="2" enum="Fallback">
|
||||||
|
</constant>
|
||||||
|
</constants>
|
||||||
|
</class>
|
160
modules/network_synchronizer/class_docs/NetworkedController.xml
Normal file
160
modules/network_synchronizer/class_docs/NetworkedController.xml
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<class name="NetworkedController" inherits="Node" version="4.0">
|
||||||
|
<brief_description>
|
||||||
|
</brief_description>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<tutorials>
|
||||||
|
</tutorials>
|
||||||
|
<methods>
|
||||||
|
<method name="_apply_epoch" qualifiers="virtual">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="delta" type="float" />
|
||||||
|
<argument index="1" name="interpolated_data" type="Array" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="_are_inputs_different" qualifiers="virtual">
|
||||||
|
<return type="bool" />
|
||||||
|
<argument index="0" name="inputs_A" type="DataBuffer" />
|
||||||
|
<argument index="1" name="inputs_B" type="DataBuffer" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="_collect_epoch_data" qualifiers="virtual">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="buffer" type="DataBuffer" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="_collect_inputs" qualifiers="virtual">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="delta" type="float" />
|
||||||
|
<argument index="1" name="buffer" type="DataBuffer" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="_controller_process" qualifiers="virtual">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="delta" type="float" />
|
||||||
|
<argument index="1" name="buffer" type="DataBuffer" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="_count_input_size" qualifiers="virtual">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="inputs" type="DataBuffer" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="_parse_epoch_data" qualifiers="virtual">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="interpolator" type="Interpolator" />
|
||||||
|
<argument index="1" name="buffer" type="DataBuffer" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="_setup_interpolator" qualifiers="virtual">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="interpolator" type="Interpolator" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_current_input_id" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_doll_controller" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_nonet_controller" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_player_controller" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_server_controller" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="mark_epoch_as_important">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="player_get_pretended_delta" qualifiers="const">
|
||||||
|
<return type="float" />
|
||||||
|
<argument index="0" name="iterations_per_seconds" type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_doll_collect_rate_factor">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="peer" type="int" />
|
||||||
|
<argument index="1" name="factor" type="float" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_doll_peer_active">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="peer_id" type="int" />
|
||||||
|
<argument index="1" name="active" type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
</methods>
|
||||||
|
<members>
|
||||||
|
<member name="doll_connection_stats_frame_span" type="int" setter="set_doll_connection_stats_frame_span" getter="get_doll_connection_stats_frame_span" default="30">
|
||||||
|
</member>
|
||||||
|
<member name="doll_epoch_batch_sync_rate" type="float" setter="set_doll_epoch_batch_sync_rate" getter="get_doll_epoch_batch_sync_rate" default="0.25">
|
||||||
|
</member>
|
||||||
|
<member name="doll_epoch_collect_rate" type="int" setter="set_doll_epoch_collect_rate" getter="get_doll_epoch_collect_rate" default="1">
|
||||||
|
</member>
|
||||||
|
<member name="doll_interpolation_max_speedup" type="float" setter="set_doll_interpolation_max_speedup" getter="get_doll_interpolation_max_speedup" default="0.2">
|
||||||
|
</member>
|
||||||
|
<member name="doll_max_delay" type="int" setter="set_doll_max_delay" getter="get_doll_max_delay" default="60">
|
||||||
|
</member>
|
||||||
|
<member name="doll_max_frames_delay" type="int" setter="set_doll_max_frames_delay" getter="get_doll_max_frames_delay" default="5">
|
||||||
|
</member>
|
||||||
|
<member name="doll_min_frames_delay" type="int" setter="set_doll_min_frames_delay" getter="get_doll_min_frames_delay" default="2">
|
||||||
|
</member>
|
||||||
|
<member name="doll_net_sensitivity" type="float" setter="set_doll_net_sensitivity" getter="get_doll_net_sensitivity" default="0.2">
|
||||||
|
</member>
|
||||||
|
<member name="input_storage_size" type="int" setter="set_player_input_storage_size" getter="get_player_input_storage_size" default="180">
|
||||||
|
</member>
|
||||||
|
<member name="max_frames_delay" type="int" setter="set_max_frames_delay" getter="get_max_frames_delay" default="6">
|
||||||
|
</member>
|
||||||
|
<member name="max_redundant_inputs" type="int" setter="set_max_redundant_inputs" getter="get_max_redundant_inputs" default="5">
|
||||||
|
</member>
|
||||||
|
<member name="min_frames_delay" type="int" setter="set_min_frames_delay" getter="get_min_frames_delay" default="2">
|
||||||
|
</member>
|
||||||
|
<member name="net_sensitivity" type="float" setter="set_net_sensitivity" getter="get_net_sensitivity" default="0.1">
|
||||||
|
</member>
|
||||||
|
<member name="network_traced_frames" type="int" setter="set_network_traced_frames" getter="get_network_traced_frames" default="120">
|
||||||
|
</member>
|
||||||
|
<member name="tick_acceleration" type="float" setter="set_tick_acceleration" getter="get_tick_acceleration" default="2.0">
|
||||||
|
</member>
|
||||||
|
<member name="tick_speedup_notification_delay" type="float" setter="set_tick_speedup_notification_delay" getter="get_tick_speedup_notification_delay" default="0.33">
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
<signals>
|
||||||
|
<signal name="doll_sync_paused">
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</signal>
|
||||||
|
<signal name="doll_sync_started">
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</signal>
|
||||||
|
</signals>
|
||||||
|
<constants>
|
||||||
|
</constants>
|
||||||
|
</class>
|
13
modules/network_synchronizer/class_docs/SceneDiff.xml
Normal file
13
modules/network_synchronizer/class_docs/SceneDiff.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<class name="SceneDiff" inherits="Object" version="4.0">
|
||||||
|
<brief_description>
|
||||||
|
</brief_description>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<tutorials>
|
||||||
|
</tutorials>
|
||||||
|
<methods>
|
||||||
|
</methods>
|
||||||
|
<constants>
|
||||||
|
</constants>
|
||||||
|
</class>
|
278
modules/network_synchronizer/class_docs/SceneSynchronizer.xml
Normal file
278
modules/network_synchronizer/class_docs/SceneSynchronizer.xml
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<class name="SceneSynchronizer" inherits="Node" version="4.0">
|
||||||
|
<brief_description>
|
||||||
|
</brief_description>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<tutorials>
|
||||||
|
</tutorials>
|
||||||
|
<methods>
|
||||||
|
<method name="apply_scene_changes">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="sync_data" type="Variant" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="clear">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="controller_add_dependency">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="controller" type="Node" />
|
||||||
|
<argument index="1" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="controller_get_dependency">
|
||||||
|
<return type="Node" />
|
||||||
|
<argument index="0" name="controller" type="Node" />
|
||||||
|
<argument index="1" name="index" type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="controller_get_dependency_count" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="controller" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="controller_remove_dependency">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="controller" type="Node" />
|
||||||
|
<argument index="1" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="force_state_notify">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_node_from_id">
|
||||||
|
<return type="Node" />
|
||||||
|
<argument index="0" name="id" type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_node_id">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_peer_networking_enable" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<argument index="0" name="peer" type="int" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="get_variable_id">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="variable" type="StringName" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_client" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_end_sync" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_networked" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_node_sync" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_recovered" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_resetted" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_rewinding" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="is_server" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="pop_scene_changes" qualifiers="const">
|
||||||
|
<return type="Variant" />
|
||||||
|
<argument index="0" name="diff_handle" type="Object" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="register_node">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="register_process">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="function" type="StringName" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="register_variable">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="variable" type="StringName" />
|
||||||
|
<argument index="2" name="on_change_notify" type="StringName" default="&""" />
|
||||||
|
<argument index="3" name="flags" type="int" enum="NetEventFlag" default="17" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="reset_synchronizer_mode">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_enabled">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="enabled" type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_node_as_controlled_by">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="controller" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_peer_networking_enable">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="peer" type="int" />
|
||||||
|
<argument index="1" name="enabled" type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_skip_rewinding">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="variable" type="StringName" />
|
||||||
|
<argument index="2" name="skip_rewinding" type="bool" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="start_node_sync">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="start_tracking_scene_changes" qualifiers="const">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="diff_handle" type="Object" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="stop_node_sync">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="stop_tracking_scene_changes" qualifiers="const">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="diff_handle" type="Object" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="track_variable_changes">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="variable" type="StringName" />
|
||||||
|
<argument index="2" name="object" type="Object" />
|
||||||
|
<argument index="3" name="method" type="StringName" />
|
||||||
|
<argument index="4" name="flags" type="int" enum="NetEventFlag" default="17" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="unregister_node">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="unregister_process">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="function" type="StringName" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="unregister_variable">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="variable" type="StringName" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="untrack_variable_changes">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="variable" type="StringName" />
|
||||||
|
<argument index="2" name="object" type="Object" />
|
||||||
|
<argument index="3" name="method" type="StringName" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
</methods>
|
||||||
|
<members>
|
||||||
|
<member name="comparison_float_tolerance" type="float" setter="set_comparison_float_tolerance" getter="get_comparison_float_tolerance" default="0.001">
|
||||||
|
</member>
|
||||||
|
<member name="server_notify_state_interval" type="float" setter="set_server_notify_state_interval" getter="get_server_notify_state_interval" default="1.0">
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
<signals>
|
||||||
|
<signal name="sync_paused">
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</signal>
|
||||||
|
<signal name="sync_started">
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</signal>
|
||||||
|
</signals>
|
||||||
|
<constants>
|
||||||
|
<constant name="CHANGE" value="1" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
<constant name="SYNC_RECOVER" value="2" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
<constant name="SYNC_RESET" value="4" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
<constant name="SYNC_REWIND" value="8" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
<constant name="END_SYNC" value="16" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
<constant name="DEFAULT" value="17" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
<constant name="SYNC" value="14" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
<constant name="ALWAYS" value="31" enum="NetEventFlag">
|
||||||
|
</constant>
|
||||||
|
</constants>
|
||||||
|
</class>
|
18
modules/network_synchronizer/config.py
Normal file
18
modules/network_synchronizer/config.py
Normal file
@ -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
|
942
modules/network_synchronizer/data_buffer.cpp
Normal file
942
modules/network_synchronizer/data_buffer.cpp
Normal file
@ -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<int8_t>(value);
|
||||||
|
} else if (bits == 16) {
|
||||||
|
return static_cast<int16_t>(value);
|
||||||
|
} else if (bits == 32) {
|
||||||
|
return static_cast<int32_t>(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<int8_t>(value);
|
||||||
|
} else if (bits == 16) {
|
||||||
|
return static_cast<int16_t>(value);
|
||||||
|
} else if (bits == 32) {
|
||||||
|
return static_cast<int32_t>(value);
|
||||||
|
} else {
|
||||||
|
return static_cast<int64_t>(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<int>(buffer.read_bits(bit_offset, exponent_bits)) - static_cast<int>(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<double>(~(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<double>(~(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<double>(~(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<double>(~(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<double>(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;
|
||||||
|
}
|
342
modules/network_synchronizer/data_buffer.h
Normal file
342
modules/network_synchronizer/data_buffer.h
Normal file
@ -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
|
415
modules/network_synchronizer/interpolator.cpp
Normal file
415
modules/network_synchronizer/interpolator.cpp
Normal file
@ -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<Variant>());
|
||||||
|
// 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<Variant>());
|
||||||
|
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<Variant> Interpolator::pop_epoch(uint32_t p_epoch, real_t p_fraction) {
|
||||||
|
ERR_FAIL_COND_V_MSG(init_phase, Vector<Variant>(), "You can't pop data if the interpolator is not fully initialized.");
|
||||||
|
ERR_FAIL_COND_V_MSG(write_position != UINT32_MAX, Vector<Variant>(), "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<double>(epochs[i]) >= epoch) {
|
||||||
|
position = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectID cache_object_id;
|
||||||
|
Object *cache_object = nullptr;
|
||||||
|
|
||||||
|
Vector<Variant> 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;
|
||||||
|
}
|
||||||
|
}
|
104
modules/network_synchronizer/interpolator.h
Normal file
104
modules/network_synchronizer/interpolator.h
Normal file
@ -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<VariableInfo> variables;
|
||||||
|
|
||||||
|
/// Epoch ids, sorted from youngest to oldest.
|
||||||
|
LocalVector<uint32_t> epochs;
|
||||||
|
/// Epoch data.
|
||||||
|
LocalVector<Vector<Variant>> 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<Variant> 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
|
91
modules/network_synchronizer/net_utilities.cpp
Normal file
91
modules/network_synchronizer/net_utilities.cpp
Normal file
@ -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;
|
||||||
|
}
|
342
modules/network_synchronizer/net_utilities.h
Normal file
342
modules/network_synchronizer/net_utilities.h
Normal file
@ -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 T>
|
||||||
|
class StatisticalRingBuffer {
|
||||||
|
LocalVector<T> 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 <class T>
|
||||||
|
StatisticalRingBuffer<T>::StatisticalRingBuffer(uint32_t p_size, T p_default) {
|
||||||
|
resize(p_size, p_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void StatisticalRingBuffer<T>::resize(uint32_t p_size, T p_default) {
|
||||||
|
data.resize(p_size);
|
||||||
|
|
||||||
|
reset(p_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void StatisticalRingBuffer<T>::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 <class T>
|
||||||
|
void StatisticalRingBuffer<T>::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 <class T>
|
||||||
|
T StatisticalRingBuffer<T>::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 <class T>
|
||||||
|
T StatisticalRingBuffer<T>::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 <class T>
|
||||||
|
T StatisticalRingBuffer<T>::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 <class T>
|
||||||
|
T StatisticalRingBuffer<T>::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 <class T>
|
||||||
|
void StatisticalRingBuffer<T>::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<NodeChangeListener> watching_vars;
|
||||||
|
LocalVector<Variant> 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<uint32_t> 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<NodeData *> controlled_nodes;
|
||||||
|
LocalVector<NodeData *> dependency_nodes;
|
||||||
|
LocalVector<uint32_t> dependency_nodes_end;
|
||||||
|
|
||||||
|
/// The sync variables of this node. The order of this vector matters
|
||||||
|
/// because the index is the `NetVarId`.
|
||||||
|
LocalVector<VarData> vars;
|
||||||
|
LocalVector<StringName> 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<Vector<Var>> node_vars;
|
||||||
|
|
||||||
|
operator String() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PostponedRecover {
|
||||||
|
NodeData *node_data = nullptr;
|
||||||
|
Vector<Var> vars;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace NetUtility
|
||||||
|
|
||||||
|
#endif
|
1571
modules/network_synchronizer/networked_controller.cpp
Normal file
1571
modules/network_synchronizer/networked_controller.cpp
Normal file
File diff suppressed because it is too large
Load Diff
526
modules/network_synchronizer/networked_controller.h
Normal file
526
modules/network_synchronizer/networked_controller.h
Normal file
@ -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 <deque>
|
||||||
|
|
||||||
|
#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<uint8_t> &p_data);
|
||||||
|
|
||||||
|
/* On client rpc functions. */
|
||||||
|
void _rpc_send_tick_additional_speed(const Vector<uint8_t> &p_data);
|
||||||
|
|
||||||
|
/* On puppet rpc functions. */
|
||||||
|
void _rpc_doll_notify_sync_pause(uint32_t p_epoch);
|
||||||
|
void _rpc_doll_send_epoch_batch(const Vector<uint8_t> &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<Vector<uint8_t>> 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<FrameSnapshot> snapshots;
|
||||||
|
bool streaming_paused = false;
|
||||||
|
bool enabled = true;
|
||||||
|
|
||||||
|
uint32_t input_arrival_time = UINT32_MAX;
|
||||||
|
NetUtility::StatisticalRingBuffer<uint32_t> network_watcher;
|
||||||
|
|
||||||
|
/// Used to sync the dolls.
|
||||||
|
LocalVector<Peer> 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<uint8_t> &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<FrameSnapshot> frames_snapshot;
|
||||||
|
LocalVector<uint8_t> 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<uint32_t> 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<uint8_t> &p_data);
|
||||||
|
uint32_t receive_epoch(const Vector<uint8_t> &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
|
55
modules/network_synchronizer/register_types.cpp
Normal file
55
modules/network_synchronizer/register_types.cpp
Normal file
@ -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() {
|
||||||
|
}
|
36
modules/network_synchronizer/register_types.h
Normal file
36
modules/network_synchronizer/register_types.h
Normal file
@ -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();
|
165
modules/network_synchronizer/scene_diff.cpp
Normal file
165
modules/network_synchronizer/scene_diff.cpp
Normal file
@ -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<NetUtility::NodeData *> &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;
|
||||||
|
}
|
71
modules/network_synchronizer/scene_diff.h
Normal file
71
modules/network_synchronizer/scene_diff.h
Normal file
@ -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<LocalVector<Variant>> tracking;
|
||||||
|
LocalVector<LocalVector<VarDiff>> diff;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SceneDiff() = default;
|
||||||
|
|
||||||
|
void start_tracking_scene_changes(const LocalVector<NetUtility::NodeData *> &p_nodes);
|
||||||
|
void stop_tracking_scene_changes(const SceneSynchronizer *p_synchronizer);
|
||||||
|
|
||||||
|
bool is_tracking_in_progress() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SCENE_DIFF_H
|
3052
modules/network_synchronizer/scene_synchronizer.cpp
Normal file
3052
modules/network_synchronizer/scene_synchronizer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
464
modules/network_synchronizer/scene_synchronizer.h
Normal file
464
modules/network_synchronizer/scene_synchronizer.h
Normal file
@ -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 <deque>
|
||||||
|
|
||||||
|
#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<int, NetUtility::PeerData> peer_data;
|
||||||
|
|
||||||
|
bool generate_id = false;
|
||||||
|
|
||||||
|
// All possible registered nodes.
|
||||||
|
LocalVector<NetUtility::NodeData *> node_data;
|
||||||
|
|
||||||
|
// All registered nodes, that have the NetworkNodeId assigned, organized per
|
||||||
|
// NetworkNodeId.
|
||||||
|
LocalVector<NetUtility::NodeData *> organized_node_data;
|
||||||
|
|
||||||
|
// Controller nodes.
|
||||||
|
LocalVector<NetUtility::NodeData *> 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<NetUtility::ChangeListener> 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<StringName> uknown_vars;
|
||||||
|
Set<StringName> vars;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The changes; the order matters because the index is the NetNodeId.
|
||||||
|
LocalVector<Change> 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<Variant> 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<Variant> &r_snapshot_result) const;
|
||||||
|
void generate_snapshot_node_data(const NetUtility::NodeData *p_node_data, bool p_force_full_snapshot, Vector<Variant> &r_result) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClientSynchronizer : public Synchronizer {
|
||||||
|
friend class SceneSynchronizer;
|
||||||
|
|
||||||
|
NetUtility::NodeData *player_controller_node_data = nullptr;
|
||||||
|
OAHashMap<NetNodeId, NodePath> node_paths;
|
||||||
|
|
||||||
|
NetUtility::Snapshot last_received_snapshot;
|
||||||
|
std::deque<NetUtility::Snapshot> client_snapshots;
|
||||||
|
std::deque<NetUtility::Snapshot> 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<EndSyncEvent> 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<NetUtility::Snapshot> &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<NetUtility::Var> &p_server_vars,
|
||||||
|
const Vector<NetUtility::Var> &p_client_vars,
|
||||||
|
Vector<NetUtility::Var> &r_postponed_recover);
|
||||||
|
|
||||||
|
void notify_server_full_snapshot_is_needed();
|
||||||
|
};
|
||||||
|
|
||||||
|
VARIANT_ENUM_CAST(NetEventFlag)
|
||||||
|
|
||||||
|
#endif
|
120
modules/network_synchronizer/tests/test_bit_array.h
Normal file
120
modules/network_synchronizer/tests/test_bit_array.h
Normal file
@ -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<uint8_t> 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
|
550
modules/network_synchronizer/tests/test_data_buffer.h
Normal file
550
modules/network_synchronizer/tests/test_data_buffer.h
Normal file
@ -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<double> real_values(DataBuffer::CompressionLevel p_compression_level) {
|
||||||
|
Vector<double> 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<double> 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<double> 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<double> 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<double> 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<double> 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<double> 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
|
131
modules/network_synchronizer/tests/test_interpolator.h
Normal file
131
modules/network_synchronizer/tests/test_interpolator.h
Normal file
@ -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 <class T>
|
||||||
|
T generate_value(real_t value) {
|
||||||
|
if constexpr (std::is_same_v<T, Vector2> || std::is_same_v<T, Vector2i>) {
|
||||||
|
return T(value, value);
|
||||||
|
} else if constexpr (std::is_same_v<T, Vector3> || std::is_same_v<T, Vector3i>) {
|
||||||
|
return T(value, value, value);
|
||||||
|
} else {
|
||||||
|
return static_cast<T>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add other types
|
||||||
|
TEST_CASE_TEMPLATE("[Modules][Interpolator] Interpolation", T, int, real_t, Vector2, Vector2i, Vector3) {
|
||||||
|
LocalVector<real_t> 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<real_t, real_t> 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<real_t, real_t>::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<T>(E->key());
|
||||||
|
const T second_value = generate_value<T>(E->value());
|
||||||
|
|
||||||
|
interpolator.reset();
|
||||||
|
const int variable_id = interpolator.register_variable(T(), static_cast<Interpolator::Fallback>(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
|
Loading…
Reference in New Issue
Block a user