mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2025-01-30 12:59:18 +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