mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2025-04-12 23:02:02 +02:00
Updated NetworkSynchronizer from https://github.com/GameNetworking/network_synchronizer/tree/godot-3.x . f12fee66a7e3eb2013d6c5437837770455105b91 (Merge commit: cc3c7d244adea8b25822f43963618fd2dbf18ec8)
This commit is contained in:
parent
1f291939e9
commit
eb63a4d682
189
modules/network_synchronizer/.clang-format
Normal file
189
modules/network_synchronizer/.clang-format
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# Commented out parameters are those with the same value as base LLVM style.
|
||||||
|
# We can uncomment them if we want to change their value, or enforce the
|
||||||
|
# chosen value in case the base style changes (last sync: Clang 13.0).
|
||||||
|
---
|
||||||
|
### General config, applies to all languages ###
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AlignAfterOpenBracket: DontAlign
|
||||||
|
# AlignArrayOfStructures: None
|
||||||
|
# AlignConsecutiveMacros: None
|
||||||
|
# AlignConsecutiveAssignments: None
|
||||||
|
# AlignConsecutiveBitFields: None
|
||||||
|
# AlignConsecutiveDeclarations: None
|
||||||
|
# AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: DontAlign
|
||||||
|
AlignTrailingComments: false
|
||||||
|
# AllowAllArgumentsOnNextLine: true
|
||||||
|
# AllowAllConstructorInitializersOnNextLine: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
# AllowShortEnumsOnASingleLine: true
|
||||||
|
# AllowShortBlocksOnASingleLine: Never
|
||||||
|
# AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
|
# AllowShortLambdasOnASingleLine: All
|
||||||
|
# AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
# AllowShortLoopsOnASingleLine: false
|
||||||
|
# AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
# AlwaysBreakAfterReturnType: None
|
||||||
|
# AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
# AlwaysBreakTemplateDeclarations: MultiLine
|
||||||
|
# AttributeMacros:
|
||||||
|
# - __capability
|
||||||
|
# BinPackArguments: true
|
||||||
|
# BinPackParameters: true
|
||||||
|
# BraceWrapping:
|
||||||
|
# AfterCaseLabel: false
|
||||||
|
# AfterClass: false
|
||||||
|
# AfterControlStatement: Never
|
||||||
|
# AfterEnum: false
|
||||||
|
# AfterFunction: false
|
||||||
|
# AfterNamespace: false
|
||||||
|
# AfterObjCDeclaration: false
|
||||||
|
# AfterStruct: false
|
||||||
|
# AfterUnion: false
|
||||||
|
# AfterExternBlock: false
|
||||||
|
# BeforeCatch: false
|
||||||
|
# BeforeElse: false
|
||||||
|
# BeforeLambdaBody: false
|
||||||
|
# BeforeWhile: false
|
||||||
|
# IndentBraces: false
|
||||||
|
# SplitEmptyFunction: true
|
||||||
|
# SplitEmptyRecord: true
|
||||||
|
# SplitEmptyNamespace: true
|
||||||
|
# BreakBeforeBinaryOperators: None
|
||||||
|
# BreakBeforeConceptDeclarations: true
|
||||||
|
# BreakBeforeBraces: Attach
|
||||||
|
# BreakBeforeInheritanceComma: false
|
||||||
|
# BreakInheritanceList: BeforeColon
|
||||||
|
# BreakBeforeTernaryOperators: true
|
||||||
|
# BreakConstructorInitializersBeforeComma: false
|
||||||
|
BreakConstructorInitializers: AfterColon
|
||||||
|
# BreakStringLiterals: true
|
||||||
|
ColumnLimit: 0
|
||||||
|
# CommentPragmas: '^ IWYU pragma:'
|
||||||
|
# CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 8
|
||||||
|
ContinuationIndentWidth: 8
|
||||||
|
Cpp11BracedListStyle: false
|
||||||
|
# DeriveLineEnding: true
|
||||||
|
# DerivePointerAlignment: false
|
||||||
|
# DisableFormat: false
|
||||||
|
# EmptyLineAfterAccessModifier: Never
|
||||||
|
# EmptyLineBeforeAccessModifier: LogicalBlock
|
||||||
|
# ExperimentalAutoDetectBinPacking: false
|
||||||
|
# FixNamespaceComments: true
|
||||||
|
# ForEachMacros:
|
||||||
|
# - foreach
|
||||||
|
# - Q_FOREACH
|
||||||
|
# - BOOST_FOREACH
|
||||||
|
# IfMacros:
|
||||||
|
# - KJ_IF_MAYBE
|
||||||
|
# IncludeBlocks: Preserve
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '".*"'
|
||||||
|
Priority: 1
|
||||||
|
- Regex: '^<.*\.h>'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '^<.*'
|
||||||
|
Priority: 3
|
||||||
|
# IncludeIsMainRegex: '(Test)?$'
|
||||||
|
# IncludeIsMainSourceRegex: ''
|
||||||
|
# IndentAccessModifiers: false
|
||||||
|
IndentCaseLabels: true
|
||||||
|
# IndentCaseBlocks: false
|
||||||
|
# IndentGotoLabels: true
|
||||||
|
# IndentPPDirectives: None
|
||||||
|
# IndentExternBlock: AfterExternBlock
|
||||||
|
# IndentRequires: false
|
||||||
|
IndentWidth: 4
|
||||||
|
# IndentWrappedFunctionNames: false
|
||||||
|
# InsertTrailingCommas: None
|
||||||
|
# JavaScriptQuotes: Leave
|
||||||
|
# JavaScriptWrapImports: true
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
# LambdaBodyIndentation: Signature
|
||||||
|
# MacroBlockBegin: ''
|
||||||
|
# MacroBlockEnd: ''
|
||||||
|
# MaxEmptyLinesToKeep: 1
|
||||||
|
# NamespaceIndentation: None
|
||||||
|
# PenaltyBreakAssignment: 2
|
||||||
|
# PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
# PenaltyBreakComment: 300
|
||||||
|
# PenaltyBreakFirstLessLess: 120
|
||||||
|
# PenaltyBreakString: 1000
|
||||||
|
# PenaltyBreakTemplateDeclaration: 10
|
||||||
|
# PenaltyExcessCharacter: 1000000
|
||||||
|
# PenaltyReturnTypeOnItsOwnLine: 60
|
||||||
|
# PenaltyIndentedWhitespace: 0
|
||||||
|
# PointerAlignment: Right
|
||||||
|
# PPIndentWidth: -1
|
||||||
|
# ReferenceAlignment: Pointer
|
||||||
|
# ReflowComments: true
|
||||||
|
# ShortNamespaceLines: 1
|
||||||
|
# SortIncludes: CaseSensitive
|
||||||
|
# SortJavaStaticImport: Before
|
||||||
|
# SortUsingDeclarations: true
|
||||||
|
# SpaceAfterCStyleCast: false
|
||||||
|
# SpaceAfterLogicalNot: false
|
||||||
|
# SpaceAfterTemplateKeyword: true
|
||||||
|
# SpaceBeforeAssignmentOperators: true
|
||||||
|
# SpaceBeforeCaseColon: false
|
||||||
|
# SpaceBeforeCpp11BracedList: false
|
||||||
|
# SpaceBeforeCtorInitializerColon: true
|
||||||
|
# SpaceBeforeInheritanceColon: true
|
||||||
|
# SpaceBeforeParens: ControlStatements
|
||||||
|
# SpaceAroundPointerQualifiers: Default
|
||||||
|
# SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
# SpaceInEmptyParentheses: false
|
||||||
|
# SpacesBeforeTrailingComments: 1
|
||||||
|
# SpaceInEmptyBlock: false
|
||||||
|
# SpaceInEmptyParentheses: false
|
||||||
|
# SpacesBeforeTrailingComments: 1
|
||||||
|
# SpacesInAngles: Never
|
||||||
|
# SpacesInContainerLiterals: true
|
||||||
|
# SpacesInConditionalStatement: false
|
||||||
|
# SpacesInContainerLiterals: true
|
||||||
|
# SpacesInCStyleCastParentheses: false
|
||||||
|
## Godot TODO: We'll want to use a min of 1, but we need to see how to fix
|
||||||
|
## our comment capitalization at the same time.
|
||||||
|
SpacesInLineCommentPrefix:
|
||||||
|
Minimum: 0
|
||||||
|
Maximum: -1
|
||||||
|
# SpacesInParentheses: false
|
||||||
|
# SpacesInSquareBrackets: false
|
||||||
|
# SpaceBeforeSquareBrackets: false
|
||||||
|
# BitFieldColonSpacing: Both
|
||||||
|
# StatementAttributeLikeMacros:
|
||||||
|
# - Q_EMIT
|
||||||
|
# StatementMacros:
|
||||||
|
# - Q_UNUSED
|
||||||
|
# - QT_REQUIRE_VERSION
|
||||||
|
TabWidth: 4
|
||||||
|
# UseCRLF: false
|
||||||
|
UseTab: Always
|
||||||
|
# WhitespaceSensitiveMacros:
|
||||||
|
# - STRINGIZE
|
||||||
|
# - PP_STRINGIZE
|
||||||
|
# - BOOST_PP_STRINGIZE
|
||||||
|
# - NS_SWIFT_NAME
|
||||||
|
# - CF_SWIFT_NAME
|
||||||
|
---
|
||||||
|
### C++ specific config ###
|
||||||
|
Language: Cpp
|
||||||
|
Standard: c++14
|
||||||
|
---
|
||||||
|
### ObjC specific config ###
|
||||||
|
Language: ObjC
|
||||||
|
# ObjCBinPackProtocolList: Auto
|
||||||
|
ObjCBlockIndentWidth: 4
|
||||||
|
# ObjCBreakBeforeNestedBlockParam: true
|
||||||
|
# ObjCSpaceAfterProperty: false
|
||||||
|
# ObjCSpaceBeforeProtocolList: true
|
||||||
|
---
|
||||||
|
### Java specific config ###
|
||||||
|
Language: Java
|
||||||
|
# BreakAfterJavaFieldAnnotations: false
|
||||||
|
JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax']
|
||||||
|
...
|
2
modules/network_synchronizer/.gitattributes
vendored
Normal file
2
modules/network_synchronizer/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Normalize EOL for all files that Git considers text files
|
||||||
|
* text=auto eol=lf
|
45
modules/network_synchronizer/.gitignore
vendored
Normal file
45
modules/network_synchronizer/.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
__generated__debugger_ui.h
|
||||||
|
|
||||||
|
# Godot-specific ignores
|
||||||
|
.import/
|
||||||
|
.godot/
|
||||||
|
export.cfg
|
||||||
|
export_presets.cfg
|
||||||
|
|
||||||
|
# Mono-specific ignores
|
||||||
|
.mono/
|
||||||
|
data_*/
|
||||||
|
|
||||||
|
# General c++ generated files
|
||||||
|
*.lib
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.ox
|
||||||
|
*.a
|
||||||
|
*.ax
|
||||||
|
*.d
|
||||||
|
*.so
|
||||||
|
*.os
|
||||||
|
*.Plo
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# for projects that use SCons for building: http://http://www.scons.org/
|
||||||
|
.sconf_temp
|
||||||
|
.sconsign*.dblite
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
.vscode
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# QtCreator
|
||||||
|
*.cflags
|
||||||
|
*.config
|
||||||
|
*.creator
|
||||||
|
*.creator.user
|
||||||
|
*.cxxflags
|
||||||
|
*.files
|
||||||
|
*.includes
|
||||||
|
|
||||||
|
# CLion
|
||||||
|
.idea/
|
@ -1,5 +1,9 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from debugger_ui import cpplize_debugger
|
||||||
|
|
||||||
|
cpplize_debugger.create_debugger_header()
|
||||||
|
|
||||||
Import("env")
|
Import("env")
|
||||||
Import("env_modules")
|
Import("env_modules")
|
||||||
|
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
/* bit_array.cpp */
|
/* bit_array.cpp */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -36,13 +35,13 @@
|
|||||||
#include "bit_array.h"
|
#include "bit_array.h"
|
||||||
|
|
||||||
#include "core/math/math_funcs.h"
|
#include "core/math/math_funcs.h"
|
||||||
#include "core/string/ustring.h"
|
#include "core/ustring.h"
|
||||||
|
|
||||||
BitArray::BitArray(uint32_t p_initial_size_in_bit) {
|
BitArray::BitArray(uint32_t p_initial_size_in_bit) {
|
||||||
resize_in_bits(p_initial_size_in_bit);
|
resize_in_bits(p_initial_size_in_bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
BitArray::BitArray(const PoolByteArray &p_bytes) :
|
BitArray::BitArray(const Vector<uint8_t> &p_bytes) :
|
||||||
bytes(p_bytes) {
|
bytes(p_bytes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,8 +73,6 @@ void BitArray::store_bits(int p_bit_offset, uint64_t p_value, int p_bits) {
|
|||||||
int bit_offset = p_bit_offset;
|
int bit_offset = p_bit_offset;
|
||||||
uint64_t val = p_value;
|
uint64_t val = p_value;
|
||||||
|
|
||||||
PoolByteArray::Write w = bytes.write();
|
|
||||||
|
|
||||||
while (bits > 0) {
|
while (bits > 0) {
|
||||||
const int bits_to_write = MIN(bits, 8 - bit_offset % 8);
|
const int bits_to_write = MIN(bits, 8 - bit_offset % 8);
|
||||||
const int bits_to_jump = bit_offset % 8;
|
const int bits_to_jump = bit_offset % 8;
|
||||||
@ -87,18 +84,16 @@ void BitArray::store_bits(int p_bit_offset, uint64_t p_value, int p_bits) {
|
|||||||
uint8_t byte_clear = 0xFF >> bits_to_jump;
|
uint8_t byte_clear = 0xFF >> bits_to_jump;
|
||||||
byte_clear = byte_clear << (bits_to_jump + bits_to_skip);
|
byte_clear = byte_clear << (bits_to_jump + bits_to_skip);
|
||||||
byte_clear = ~(byte_clear >> bits_to_skip);
|
byte_clear = ~(byte_clear >> bits_to_skip);
|
||||||
w[byte_offset] &= byte_clear;
|
bytes.write[byte_offset] &= byte_clear;
|
||||||
|
|
||||||
// Now we can continue to write bits
|
// Now we can continue to write bits
|
||||||
w[byte_offset] |= (val & 0xFF) << bits_to_jump;
|
bytes.write[byte_offset] |= (val & 0xFF) << bits_to_jump;
|
||||||
|
|
||||||
bits -= bits_to_write;
|
bits -= bits_to_write;
|
||||||
bit_offset += bits_to_write;
|
bit_offset += bits_to_write;
|
||||||
|
|
||||||
val >>= bits_to_write;
|
val >>= bits_to_write;
|
||||||
}
|
}
|
||||||
|
|
||||||
w.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t BitArray::read_bits(int p_bit_offset, int p_bits) const {
|
uint64_t BitArray::read_bits(int p_bit_offset, int p_bits) const {
|
||||||
@ -109,8 +104,7 @@ uint64_t BitArray::read_bits(int p_bit_offset, int p_bits) const {
|
|||||||
int bit_offset = p_bit_offset;
|
int bit_offset = p_bit_offset;
|
||||||
uint64_t val = 0;
|
uint64_t val = 0;
|
||||||
|
|
||||||
PoolByteArray::Read r = bytes.read();
|
const uint8_t *bytes_ptr = bytes.ptr();
|
||||||
const uint8_t *bytes_ptr = r.ptr();
|
|
||||||
|
|
||||||
int val_bits_to_jump = 0;
|
int val_bits_to_jump = 0;
|
||||||
while (bits > 0) {
|
while (bits > 0) {
|
||||||
@ -130,15 +124,11 @@ uint64_t BitArray::read_bits(int p_bit_offset, int p_bits) const {
|
|||||||
val_bits_to_jump += bits_to_read;
|
val_bits_to_jump += bits_to_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
r.release();
|
|
||||||
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitArray::zero() {
|
void BitArray::zero() {
|
||||||
if (bytes.size() > 0) {
|
if (bytes.size() > 0) {
|
||||||
PoolByteArray::Write w = bytes.write();
|
memset(bytes.ptrw(), 0, sizeof(uint8_t) * bytes.size());
|
||||||
memset(w.ptr(), 0, sizeof(uint8_t) * bytes.size());
|
|
||||||
w.release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
#ifndef BITARRAY_H
|
|
||||||
#define BITARRAY_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* bit_array.h */
|
/* bit_array.h */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -36,21 +32,24 @@
|
|||||||
@author AndreaCatania
|
@author AndreaCatania
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "core/variant/variant.h"
|
#include "core/vector.h"
|
||||||
|
|
||||||
|
#ifndef BITARRAY_H
|
||||||
|
#define BITARRAY_H
|
||||||
|
|
||||||
class BitArray {
|
class BitArray {
|
||||||
PoolByteArray bytes;
|
Vector<uint8_t> bytes;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BitArray() = default;
|
BitArray() = default;
|
||||||
BitArray(uint32_t p_initial_size_in_bit);
|
BitArray(uint32_t p_initial_size_in_bit);
|
||||||
BitArray(const PoolByteArray &p_bytes);
|
BitArray(const Vector<uint8_t> &p_bytes);
|
||||||
|
|
||||||
const PoolByteArray &get_bytes() const {
|
const Vector<uint8_t> &get_bytes() const {
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
PoolByteArray &get_bytes_mut() {
|
Vector<uint8_t> &get_bytes_mut() {
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,19 +6,17 @@ def configure(env):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_doc_classes():
|
|
||||||
return [
|
|
||||||
"DataBuffer",
|
|
||||||
"SceneDiff",
|
|
||||||
"Interpolator",
|
|
||||||
"NetworkedController",
|
|
||||||
"SceneSynchronizer",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_doc_path():
|
def get_doc_path():
|
||||||
return "doc_classes"
|
return "doc_classes"
|
||||||
|
|
||||||
|
|
||||||
|
def get_doc_classes():
|
||||||
|
return [
|
||||||
|
"SceneSynchronizer",
|
||||||
|
"NetworkedController",
|
||||||
|
"DataBuffer",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def is_enabled():
|
def is_enabled():
|
||||||
return True
|
return True
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
/* data_buffer.cpp */
|
/* data_buffer.cpp */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -36,18 +35,48 @@
|
|||||||
#include "data_buffer.h"
|
#include "data_buffer.h"
|
||||||
|
|
||||||
#include "core/io/marshalls.h"
|
#include "core/io/marshalls.h"
|
||||||
|
#include "scene_synchronizer_debugger.h"
|
||||||
|
|
||||||
|
// Beware that this macros was written to make sure nested function call doesn't add debug calls,
|
||||||
|
// making the log unreadable.
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
#define DEB_WRITE(dt, compression, input) \
|
||||||
|
if (debug_enabled) { \
|
||||||
|
SceneSynchronizerDebugger::singleton()->databuffer_write(dt, compression, input); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEB_READ(dt, compression, input) \
|
||||||
|
if (debug_enabled) { \
|
||||||
|
SceneSynchronizerDebugger::singleton()->databuffer_read(dt, compression, input); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEB_DISABLE \
|
||||||
|
const bool was_debug_enabled = debug_enabled; \
|
||||||
|
debug_enabled = false;
|
||||||
|
|
||||||
|
#define DEB_ENABLE debug_enabled = was_debug_enabled;
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define DEB_WRITE(dt, compression, input)
|
||||||
|
#define DEB_READ(dt, compression, input)
|
||||||
|
#define DEB_DISABLE
|
||||||
|
#define DEB_ENABLE
|
||||||
|
#endif
|
||||||
|
|
||||||
// TODO improve the allocation mechanism.
|
// TODO improve the allocation mechanism.
|
||||||
|
|
||||||
void DataBuffer::_bind_methods() {
|
void DataBuffer::_bind_methods() {
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_BOOL);
|
BIND_ENUM_CONSTANT(DATA_TYPE_BOOL);
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_INT);
|
BIND_ENUM_CONSTANT(DATA_TYPE_INT);
|
||||||
|
BIND_ENUM_CONSTANT(DATA_TYPE_UINT);
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_REAL);
|
BIND_ENUM_CONSTANT(DATA_TYPE_REAL);
|
||||||
|
BIND_ENUM_CONSTANT(DATA_TYPE_POSITIVE_UNIT_REAL);
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_UNIT_REAL);
|
BIND_ENUM_CONSTANT(DATA_TYPE_UNIT_REAL);
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_VECTOR2);
|
BIND_ENUM_CONSTANT(DATA_TYPE_VECTOR2);
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_NORMALIZED_VECTOR2);
|
BIND_ENUM_CONSTANT(DATA_TYPE_NORMALIZED_VECTOR2);
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_VECTOR3);
|
BIND_ENUM_CONSTANT(DATA_TYPE_VECTOR3);
|
||||||
BIND_ENUM_CONSTANT(DATA_TYPE_NORMALIZED_VECTOR3);
|
BIND_ENUM_CONSTANT(DATA_TYPE_NORMALIZED_VECTOR3);
|
||||||
|
BIND_ENUM_CONSTANT(DATA_TYPE_VARIANT);
|
||||||
|
|
||||||
BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_0);
|
BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_0);
|
||||||
BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_1);
|
BIND_ENUM_CONSTANT(COMPRESSION_LEVEL_1);
|
||||||
@ -58,6 +87,7 @@ void DataBuffer::_bind_methods() {
|
|||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("add_bool", "value"), &DataBuffer::add_bool);
|
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_int", "value", "compression_level"), &DataBuffer::add_int, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
|
ClassDB::bind_method(D_METHOD("add_uint", "value", "compression_level"), &DataBuffer::add_uint, 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_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_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_unit_real", "value", "compression_level"), &DataBuffer::add_unit_real, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
@ -69,7 +99,9 @@ void DataBuffer::_bind_methods() {
|
|||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("read_bool"), &DataBuffer::read_bool);
|
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_int", "compression_level"), &DataBuffer::read_int, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
|
ClassDB::bind_method(D_METHOD("read_uint", "compression_level"), &DataBuffer::read_uint, 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_real", "compression_level"), &DataBuffer::read_real, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
|
ClassDB::bind_method(D_METHOD("read_positive_unit_real", "compression_level"), &DataBuffer::read_positive_unit_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_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_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_normalized_vector2", "compression_level"), &DataBuffer::read_normalized_vector2, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
@ -79,6 +111,7 @@ void DataBuffer::_bind_methods() {
|
|||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("skip_bool"), &DataBuffer::skip_bool);
|
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_int", "compression_level"), &DataBuffer::skip_int, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
|
ClassDB::bind_method(D_METHOD("skip_uint", "compression_level"), &DataBuffer::skip_uint, 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_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_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_vector2", "compression_level"), &DataBuffer::skip_vector2, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
@ -88,6 +121,7 @@ void DataBuffer::_bind_methods() {
|
|||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_bool_size"), &DataBuffer::get_bool_size);
|
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_int_size", "compression_level"), &DataBuffer::get_int_size, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
|
ClassDB::bind_method(D_METHOD("get_uint_size", "compression_level"), &DataBuffer::get_uint_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_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_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_vector2_size", "compression_level"), &DataBuffer::get_vector2_size, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
@ -97,6 +131,7 @@ void DataBuffer::_bind_methods() {
|
|||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("read_bool_size"), &DataBuffer::read_bool_size);
|
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_int_size", "compression_level"), &DataBuffer::read_int_size, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
|
ClassDB::bind_method(D_METHOD("read_uint_size", "compression_level"), &DataBuffer::read_uint_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_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_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_vector2_size", "compression_level"), &DataBuffer::read_vector2_size, DEFVAL(COMPRESSION_LEVEL_1));
|
||||||
@ -124,6 +159,22 @@ DataBuffer::DataBuffer(const BitArray &p_buffer) :
|
|||||||
is_reading(true),
|
is_reading(true),
|
||||||
buffer(p_buffer) {}
|
buffer(p_buffer) {}
|
||||||
|
|
||||||
|
void DataBuffer::copy(const DataBuffer &p_other) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataBuffer::copy(const BitArray &p_buffer) {
|
||||||
|
metadata_size = 0;
|
||||||
|
bit_offset = 0;
|
||||||
|
bit_size = p_buffer.size_in_bits();
|
||||||
|
is_reading = true;
|
||||||
|
buffer = p_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
void DataBuffer::begin_write(int p_metadata_size) {
|
void DataBuffer::begin_write(int p_metadata_size) {
|
||||||
CRASH_COND_MSG(p_metadata_size < 0, "Metadata size can't be negative");
|
CRASH_COND_MSG(p_metadata_size < 0, "Metadata size can't be negative");
|
||||||
metadata_size = p_metadata_size;
|
metadata_size = p_metadata_size;
|
||||||
@ -178,6 +229,8 @@ void DataBuffer::begin_read() {
|
|||||||
bool DataBuffer::add_bool(bool p_input) {
|
bool DataBuffer::add_bool(bool p_input) {
|
||||||
ERR_FAIL_COND_V(is_reading == true, p_input);
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0, p_input);
|
||||||
|
|
||||||
const int bits = get_bit_taken(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0);
|
const int bits = get_bit_taken(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0);
|
||||||
|
|
||||||
make_room_in_bits(bits);
|
make_room_in_bits(bits);
|
||||||
@ -198,12 +251,17 @@ bool DataBuffer::read_bool() {
|
|||||||
const int bits = get_bit_taken(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0);
|
const int bits = get_bit_taken(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0);
|
||||||
const bool d = buffer.read_bits(bit_offset, bits);
|
const bool d = buffer.read_bits(bit_offset, bits);
|
||||||
bit_offset += bits;
|
bit_offset += bits;
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0, d);
|
||||||
|
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t DataBuffer::add_int(int64_t p_input, CompressionLevel p_compression_level) {
|
int64_t DataBuffer::add_int(int64_t p_input, CompressionLevel p_compression_level) {
|
||||||
ERR_FAIL_COND_V(is_reading == true, p_input);
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_INT, p_compression_level, p_input);
|
||||||
|
|
||||||
const int bits = get_bit_taken(DATA_TYPE_INT, p_compression_level);
|
const int bits = get_bit_taken(DATA_TYPE_INT, p_compression_level);
|
||||||
|
|
||||||
int64_t value = p_input;
|
int64_t value = p_input;
|
||||||
@ -220,6 +278,73 @@ int64_t DataBuffer::add_int(int64_t p_input, CompressionLevel p_compression_leve
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_room_in_bits(bits);
|
make_room_in_bits(bits);
|
||||||
|
|
||||||
|
// Safely convert int to uint.
|
||||||
|
uint64_t uvalue;
|
||||||
|
memcpy(&uvalue, &value, sizeof(uint64_t));
|
||||||
|
|
||||||
|
buffer.store_bits(bit_offset, uvalue, 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 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 uvalue = buffer.read_bits(bit_offset, bits);
|
||||||
|
bit_offset += bits;
|
||||||
|
|
||||||
|
int64_t value;
|
||||||
|
memcpy(&value, &uvalue, sizeof(uint64_t));
|
||||||
|
|
||||||
|
if (bits == 8) {
|
||||||
|
DEB_READ(DATA_TYPE_INT, p_compression_level, static_cast<int8_t>(value));
|
||||||
|
return static_cast<int8_t>(value);
|
||||||
|
|
||||||
|
} else if (bits == 16) {
|
||||||
|
DEB_READ(DATA_TYPE_INT, p_compression_level, static_cast<int16_t>(value));
|
||||||
|
return static_cast<int16_t>(value);
|
||||||
|
|
||||||
|
} else if (bits == 32) {
|
||||||
|
DEB_READ(DATA_TYPE_INT, p_compression_level, static_cast<int32_t>(value));
|
||||||
|
return static_cast<int32_t>(value);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
DEB_READ(DATA_TYPE_INT, p_compression_level, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t DataBuffer::add_uint(uint64_t p_input, CompressionLevel p_compression_level) {
|
||||||
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_UINT, p_compression_level, p_input);
|
||||||
|
|
||||||
|
const int bits = get_bit_taken(DATA_TYPE_UINT, p_compression_level);
|
||||||
|
|
||||||
|
uint64_t value = p_input;
|
||||||
|
|
||||||
|
// Clamp the value to the max that the bit can store.
|
||||||
|
if (bits == 8) {
|
||||||
|
value = MIN(value, UINT8_MAX);
|
||||||
|
} else if (bits == 16) {
|
||||||
|
value = MIN(value, UINT16_MAX);
|
||||||
|
} else if (bits == 32) {
|
||||||
|
value = MIN(value, UINT32_MAX);
|
||||||
|
} else {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
make_room_in_bits(bits);
|
||||||
|
|
||||||
buffer.store_bits(bit_offset, value, bits);
|
buffer.store_bits(bit_offset, value, bits);
|
||||||
bit_offset += bits;
|
bit_offset += bits;
|
||||||
|
|
||||||
@ -228,39 +353,27 @@ int64_t DataBuffer::add_int(int64_t p_input, CompressionLevel p_compression_leve
|
|||||||
CRASH_COND((metadata_size + bit_size) > buffer.size_in_bits() && bit_offset > buffer.size_in_bits());
|
CRASH_COND((metadata_size + bit_size) > buffer.size_in_bits() && bit_offset > buffer.size_in_bits());
|
||||||
#endif
|
#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;
|
return value;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t DataBuffer::read_int(CompressionLevel p_compression_level) {
|
uint64_t DataBuffer::read_uint(CompressionLevel p_compression_level) {
|
||||||
ERR_FAIL_COND_V(is_reading == false, 0);
|
ERR_FAIL_COND_V(is_reading == false, 0);
|
||||||
|
|
||||||
const int bits = get_bit_taken(DATA_TYPE_INT, p_compression_level);
|
const int bits = get_bit_taken(DATA_TYPE_UINT, p_compression_level);
|
||||||
|
|
||||||
const uint64_t value = buffer.read_bits(bit_offset, bits);
|
const uint64_t value = buffer.read_bits(bit_offset, bits);
|
||||||
bit_offset += bits;
|
bit_offset += bits;
|
||||||
|
|
||||||
if (bits == 8) {
|
DEB_READ(DATA_TYPE_UINT, p_compression_level, value);
|
||||||
return static_cast<int8_t>(value);
|
|
||||||
} else if (bits == 16) {
|
return value;
|
||||||
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) {
|
double DataBuffer::add_real(double p_input, CompressionLevel p_compression_level) {
|
||||||
ERR_FAIL_COND_V(is_reading == true, p_input);
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_REAL, p_compression_level, p_input);
|
||||||
|
|
||||||
// Clamp the input value according to the compression level
|
// Clamp the input value according to the compression level
|
||||||
// Minifloat (compression level 0) have a special bias
|
// Minifloat (compression level 0) have a special bias
|
||||||
const int exponent_bits = get_exponent_bits(p_compression_level);
|
const int exponent_bits = get_exponent_bits(p_compression_level);
|
||||||
@ -331,7 +444,11 @@ double DataBuffer::read_real(CompressionLevel p_compression_level) {
|
|||||||
const double mantissa_scale = Math::pow(2.0, exponent <= 0 ? mantissa_bits - 1 : mantissa_bits);
|
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;
|
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);
|
const double value = ldexp(sign ? -mantissa : mantissa, exponent);
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_REAL, p_compression_level, value);
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
real_t DataBuffer::add_positive_unit_real(real_t p_input, CompressionLevel p_compression_level) {
|
real_t DataBuffer::add_positive_unit_real(real_t p_input, CompressionLevel p_compression_level) {
|
||||||
@ -340,7 +457,9 @@ real_t DataBuffer::add_positive_unit_real(real_t p_input, CompressionLevel p_com
|
|||||||
#endif
|
#endif
|
||||||
ERR_FAIL_COND_V(is_reading == true, p_input);
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
const int bits = get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression_level);
|
DEB_WRITE(DATA_TYPE_POSITIVE_UNIT_REAL, p_compression_level, p_input);
|
||||||
|
|
||||||
|
const int bits = get_bit_taken(DATA_TYPE_POSITIVE_UNIT_REAL, p_compression_level);
|
||||||
|
|
||||||
const double max_value = static_cast<double>(~(UINT64_MAX << bits));
|
const double max_value = static_cast<double>(~(UINT64_MAX << bits));
|
||||||
|
|
||||||
@ -361,19 +480,25 @@ real_t DataBuffer::add_positive_unit_real(real_t p_input, CompressionLevel p_com
|
|||||||
real_t DataBuffer::read_positive_unit_real(CompressionLevel p_compression_level) {
|
real_t DataBuffer::read_positive_unit_real(CompressionLevel p_compression_level) {
|
||||||
ERR_FAIL_COND_V(is_reading == false, 0.0);
|
ERR_FAIL_COND_V(is_reading == false, 0.0);
|
||||||
|
|
||||||
const int bits = get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression_level);
|
const int bits = get_bit_taken(DATA_TYPE_POSITIVE_UNIT_REAL, p_compression_level);
|
||||||
|
|
||||||
const double max_value = static_cast<double>(~(UINT64_MAX << bits));
|
const double max_value = static_cast<double>(~(UINT64_MAX << bits));
|
||||||
|
|
||||||
const uint64_t compressed_val = buffer.read_bits(bit_offset, bits);
|
const uint64_t compressed_val = buffer.read_bits(bit_offset, bits);
|
||||||
bit_offset += bits;
|
bit_offset += bits;
|
||||||
|
|
||||||
return decompress_unit_float(compressed_val, max_value);
|
const real_t value = decompress_unit_float(compressed_val, max_value);
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_POSITIVE_UNIT_REAL, p_compression_level, value);
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
real_t DataBuffer::add_unit_real(real_t p_input, CompressionLevel p_compression_level) {
|
real_t DataBuffer::add_unit_real(real_t p_input, CompressionLevel p_compression_level) {
|
||||||
ERR_FAIL_COND_V(is_reading == true, p_input);
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_UNIT_REAL, p_compression_level, p_input);
|
||||||
|
|
||||||
const real_t added_real = add_positive_unit_real(ABS(p_input), p_compression_level);
|
const real_t added_real = add_positive_unit_real(ABS(p_input), p_compression_level);
|
||||||
|
|
||||||
const int bits_for_sign = 1;
|
const int bits_for_sign = 1;
|
||||||
@ -399,7 +524,11 @@ real_t DataBuffer::read_unit_real(CompressionLevel p_compression_level) {
|
|||||||
const bool is_negative = buffer.read_bits(bit_offset, bits_for_sign);
|
const bool is_negative = buffer.read_bits(bit_offset, bits_for_sign);
|
||||||
bit_offset += bits_for_sign;
|
bit_offset += bits_for_sign;
|
||||||
|
|
||||||
return is_negative ? -value : value;
|
const real_t ret = is_negative ? -value : value;
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_UNIT_REAL, p_compression_level, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 DataBuffer::add_vector2(Vector2 p_input, CompressionLevel p_compression_level) {
|
Vector2 DataBuffer::add_vector2(Vector2 p_input, CompressionLevel p_compression_level) {
|
||||||
@ -413,9 +542,16 @@ Vector2 DataBuffer::add_vector2(Vector2 p_input, CompressionLevel p_compression_
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_VECTOR2, p_compression_level, p_input);
|
||||||
|
|
||||||
|
DEB_DISABLE
|
||||||
|
|
||||||
Vector2 r;
|
Vector2 r;
|
||||||
r[0] = add_real(p_input[0], p_compression_level);
|
r[0] = add_real(p_input[0], p_compression_level);
|
||||||
r[1] = add_real(p_input[1], p_compression_level);
|
r[1] = add_real(p_input[1], p_compression_level);
|
||||||
|
|
||||||
|
DEB_ENABLE
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,24 +566,35 @@ Vector2 DataBuffer::read_vector2(CompressionLevel p_compression_level) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
DEB_DISABLE
|
||||||
|
|
||||||
Vector2 r;
|
Vector2 r;
|
||||||
r[0] = read_real(p_compression_level);
|
r[0] = read_real(p_compression_level);
|
||||||
r[1] = read_real(p_compression_level);
|
r[1] = read_real(p_compression_level);
|
||||||
|
|
||||||
|
DEB_ENABLE
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_VECTOR2, p_compression_level, r);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 DataBuffer::add_normalized_vector2(Vector2 p_input, CompressionLevel p_compression_level) {
|
Vector2 DataBuffer::add_normalized_vector2(Vector2 p_input, CompressionLevel p_compression_level) {
|
||||||
|
const uint32_t is_not_zero = p_input.length_squared() > CMP_EPSILON;
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
ERR_FAIL_COND_V(p_input.is_normalized() == false, p_input);
|
ERR_FAIL_COND_V_MSG(p_input.is_normalized() == false && is_not_zero, p_input, "[FATAL] The encoding failed because this function expects a normalized vector.");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ERR_FAIL_COND_V(is_reading == true, p_input);
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_NORMALIZED_VECTOR2, p_compression_level, p_input);
|
||||||
|
|
||||||
const int bits = get_bit_taken(DATA_TYPE_NORMALIZED_VECTOR2, p_compression_level);
|
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_the_angle = bits - 1;
|
||||||
const int bits_for_zero = 1;
|
const int bits_for_zero = 1;
|
||||||
|
|
||||||
const double angle = p_input.angle();
|
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 double max_value = static_cast<double>(~(UINT64_MAX << bits_for_the_angle));
|
||||||
|
|
||||||
@ -487,7 +634,10 @@ Vector2 DataBuffer::read_normalized_vector2(CompressionLevel p_compression_level
|
|||||||
const real_t x = Math::cos(decompressed_angle);
|
const real_t x = Math::cos(decompressed_angle);
|
||||||
const real_t y = Math::sin(decompressed_angle);
|
const real_t y = Math::sin(decompressed_angle);
|
||||||
|
|
||||||
return Vector2(x, y) * is_not_zero;
|
const Vector2 value = Vector2(x, y) * is_not_zero;
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_NORMALIZED_VECTOR2, p_compression_level, value);
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 DataBuffer::add_vector3(Vector3 p_input, CompressionLevel p_compression_level) {
|
Vector3 DataBuffer::add_vector3(Vector3 p_input, CompressionLevel p_compression_level) {
|
||||||
@ -501,10 +651,17 @@ Vector3 DataBuffer::add_vector3(Vector3 p_input, CompressionLevel p_compression_
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_VECTOR3, p_compression_level, p_input);
|
||||||
|
|
||||||
|
DEB_DISABLE
|
||||||
|
|
||||||
Vector3 r;
|
Vector3 r;
|
||||||
r[0] = add_real(p_input[0], p_compression_level);
|
r[0] = add_real(p_input[0], p_compression_level);
|
||||||
r[1] = add_real(p_input[1], p_compression_level);
|
r[1] = add_real(p_input[1], p_compression_level);
|
||||||
r[2] = add_real(p_input[2], p_compression_level);
|
r[2] = add_real(p_input[2], p_compression_level);
|
||||||
|
|
||||||
|
DEB_ENABLE
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,34 +676,56 @@ Vector3 DataBuffer::read_vector3(CompressionLevel p_compression_level) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
DEB_DISABLE
|
||||||
|
|
||||||
Vector3 r;
|
Vector3 r;
|
||||||
r[0] = read_real(p_compression_level);
|
r[0] = read_real(p_compression_level);
|
||||||
r[1] = read_real(p_compression_level);
|
r[1] = read_real(p_compression_level);
|
||||||
r[2] = read_real(p_compression_level);
|
r[2] = read_real(p_compression_level);
|
||||||
|
|
||||||
|
DEB_ENABLE
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_VECTOR3, p_compression_level, r);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 DataBuffer::add_normalized_vector3(Vector3 p_input, CompressionLevel p_compression_level) {
|
Vector3 DataBuffer::add_normalized_vector3(Vector3 p_input, CompressionLevel p_compression_level) {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
ERR_FAIL_COND_V(p_input.is_normalized() == false, p_input);
|
const uint32_t is_not_zero = p_input.length_squared() > CMP_EPSILON;
|
||||||
|
ERR_FAIL_COND_V_MSG(p_input.is_normalized() == false && is_not_zero, p_input, "[FATAL] This function expects a normalized vector.");
|
||||||
#endif
|
#endif
|
||||||
ERR_FAIL_COND_V(is_reading == true, p_input);
|
ERR_FAIL_COND_V(is_reading == true, p_input);
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_NORMALIZED_VECTOR3, p_compression_level, p_input);
|
||||||
|
|
||||||
|
DEB_DISABLE
|
||||||
|
|
||||||
const real_t x_axis = add_unit_real(p_input.x, p_compression_level);
|
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 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);
|
const real_t z_axis = add_unit_real(p_input.z, p_compression_level);
|
||||||
|
|
||||||
|
DEB_ENABLE
|
||||||
|
|
||||||
return Vector3(x_axis, y_axis, z_axis);
|
return Vector3(x_axis, y_axis, z_axis);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 DataBuffer::read_normalized_vector3(CompressionLevel p_compression_level) {
|
Vector3 DataBuffer::read_normalized_vector3(CompressionLevel p_compression_level) {
|
||||||
ERR_FAIL_COND_V(is_reading == false, Vector3());
|
ERR_FAIL_COND_V(is_reading == false, Vector3());
|
||||||
|
|
||||||
|
DEB_DISABLE
|
||||||
|
|
||||||
const real_t x_axis = read_unit_real(p_compression_level);
|
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 y_axis = read_unit_real(p_compression_level);
|
||||||
const real_t z_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);
|
DEB_ENABLE
|
||||||
|
|
||||||
|
const Vector3 value(x_axis, y_axis, z_axis);
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_NORMALIZED_VECTOR3, p_compression_level, value);
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant DataBuffer::add_variant(const Variant &p_input) {
|
Variant DataBuffer::add_variant(const Variant &p_input) {
|
||||||
@ -556,6 +735,8 @@ Variant DataBuffer::add_variant(const Variant &p_input) {
|
|||||||
// Get the variant size.
|
// Get the variant size.
|
||||||
int len = 0;
|
int len = 0;
|
||||||
|
|
||||||
|
DEB_WRITE(DATA_TYPE_VARIANT, COMPRESSION_LEVEL_0, p_input);
|
||||||
|
|
||||||
const Error len_err = encode_variant(
|
const Error len_err = encode_variant(
|
||||||
p_input,
|
p_input,
|
||||||
nullptr,
|
nullptr,
|
||||||
@ -580,7 +761,7 @@ Variant DataBuffer::add_variant(const Variant &p_input) {
|
|||||||
|
|
||||||
const Error write_err = encode_variant(
|
const Error write_err = encode_variant(
|
||||||
p_input,
|
p_input,
|
||||||
buffer.get_bytes_mut().write().ptr() + (bit_offset / 8),
|
buffer.get_bytes_mut().ptrw() + (bit_offset / 8),
|
||||||
len,
|
len,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
@ -611,7 +792,7 @@ Variant DataBuffer::read_variant() {
|
|||||||
|
|
||||||
const Error read_err = decode_variant(
|
const Error read_err = decode_variant(
|
||||||
ret,
|
ret,
|
||||||
buffer.get_bytes_mut().write().ptr() + (bit_offset / 8),
|
buffer.get_bytes().ptr() + (bit_offset / 8),
|
||||||
buffer.size_in_bytes() - (bit_offset / 8),
|
buffer.size_in_bytes() - (bit_offset / 8),
|
||||||
&len,
|
&len,
|
||||||
false);
|
false);
|
||||||
@ -623,6 +804,8 @@ Variant DataBuffer::read_variant() {
|
|||||||
|
|
||||||
bit_offset += len * 8;
|
bit_offset += len * 8;
|
||||||
|
|
||||||
|
DEB_READ(DATA_TYPE_VARIANT, COMPRESSION_LEVEL_0, ret);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -640,11 +823,21 @@ void DataBuffer::skip_int(CompressionLevel p_compression) {
|
|||||||
skip(bits);
|
skip(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DataBuffer::skip_uint(CompressionLevel p_compression) {
|
||||||
|
const int bits = get_uint_size(p_compression);
|
||||||
|
skip(bits);
|
||||||
|
}
|
||||||
|
|
||||||
void DataBuffer::skip_real(CompressionLevel p_compression) {
|
void DataBuffer::skip_real(CompressionLevel p_compression) {
|
||||||
const int bits = get_real_size(p_compression);
|
const int bits = get_real_size(p_compression);
|
||||||
skip(bits);
|
skip(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DataBuffer::skip_positive_unit_real(CompressionLevel p_compression) {
|
||||||
|
const int bits = get_positive_unit_real_size(p_compression);
|
||||||
|
skip(bits);
|
||||||
|
}
|
||||||
|
|
||||||
void DataBuffer::skip_unit_real(CompressionLevel p_compression) {
|
void DataBuffer::skip_unit_real(CompressionLevel p_compression) {
|
||||||
const int bits = get_unit_real_size(p_compression);
|
const int bits = get_unit_real_size(p_compression);
|
||||||
skip(bits);
|
skip(bits);
|
||||||
@ -678,10 +871,18 @@ int DataBuffer::get_int_size(CompressionLevel p_compression) const {
|
|||||||
return DataBuffer::get_bit_taken(DATA_TYPE_INT, p_compression);
|
return DataBuffer::get_bit_taken(DATA_TYPE_INT, p_compression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DataBuffer::get_uint_size(CompressionLevel p_compression) const {
|
||||||
|
return DataBuffer::get_bit_taken(DATA_TYPE_UINT, p_compression);
|
||||||
|
}
|
||||||
|
|
||||||
int DataBuffer::get_real_size(CompressionLevel p_compression) const {
|
int DataBuffer::get_real_size(CompressionLevel p_compression) const {
|
||||||
return DataBuffer::get_bit_taken(DATA_TYPE_REAL, p_compression);
|
return DataBuffer::get_bit_taken(DATA_TYPE_REAL, p_compression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DataBuffer::get_positive_unit_real_size(CompressionLevel p_compression) const {
|
||||||
|
return DataBuffer::get_bit_taken(DATA_TYPE_POSITIVE_UNIT_REAL, p_compression);
|
||||||
|
}
|
||||||
|
|
||||||
int DataBuffer::get_unit_real_size(CompressionLevel p_compression) const {
|
int DataBuffer::get_unit_real_size(CompressionLevel p_compression) const {
|
||||||
return DataBuffer::get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression);
|
return DataBuffer::get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression);
|
||||||
}
|
}
|
||||||
@ -714,12 +915,24 @@ int DataBuffer::read_int_size(CompressionLevel p_compression) {
|
|||||||
return bits;
|
return bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DataBuffer::read_uint_size(CompressionLevel p_compression) {
|
||||||
|
const int bits = get_uint_size(p_compression);
|
||||||
|
skip(bits);
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
int DataBuffer::read_real_size(CompressionLevel p_compression) {
|
int DataBuffer::read_real_size(CompressionLevel p_compression) {
|
||||||
const int bits = get_real_size(p_compression);
|
const int bits = get_real_size(p_compression);
|
||||||
skip(bits);
|
skip(bits);
|
||||||
return bits;
|
return bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DataBuffer::read_positive_unit_real_size(CompressionLevel p_compression) {
|
||||||
|
const int bits = get_positive_unit_real_size(p_compression);
|
||||||
|
skip(bits);
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
int DataBuffer::read_unit_real_size(CompressionLevel p_compression) {
|
int DataBuffer::read_unit_real_size(CompressionLevel p_compression) {
|
||||||
const int bits = get_unit_real_size(p_compression);
|
const int bits = get_unit_real_size(p_compression);
|
||||||
skip(bits);
|
skip(bits);
|
||||||
@ -767,7 +980,7 @@ int DataBuffer::read_variant_size() {
|
|||||||
|
|
||||||
const Error read_err = decode_variant(
|
const Error read_err = decode_variant(
|
||||||
ret,
|
ret,
|
||||||
buffer.get_bytes_mut().write().ptr() + (bit_offset / 8),
|
buffer.get_bytes().ptr() + (bit_offset / 8),
|
||||||
buffer.size_in_bytes() - (bit_offset / 8),
|
buffer.size_in_bytes() - (bit_offset / 8),
|
||||||
&len,
|
&len,
|
||||||
false);
|
false);
|
||||||
@ -802,6 +1015,21 @@ int DataBuffer::get_bit_taken(DataType p_data_type, CompressionLevel p_compressi
|
|||||||
CRASH_NOW_MSG("Compression level not supported!");
|
CRASH_NOW_MSG("Compression level not supported!");
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
case DATA_TYPE_UINT: {
|
||||||
|
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: {
|
case DATA_TYPE_REAL: {
|
||||||
return get_mantissa_bits(p_compression) +
|
return get_mantissa_bits(p_compression) +
|
||||||
get_exponent_bits(p_compression);
|
get_exponent_bits(p_compression);
|
||||||
@ -844,16 +1072,7 @@ int DataBuffer::get_bit_taken(DataType p_data_type, CompressionLevel p_compressi
|
|||||||
return get_bit_taken(DATA_TYPE_REAL, p_compression) * 3;
|
return get_bit_taken(DATA_TYPE_REAL, p_compression) * 3;
|
||||||
} break;
|
} break;
|
||||||
case DATA_TYPE_NORMALIZED_VECTOR3: {
|
case DATA_TYPE_NORMALIZED_VECTOR3: {
|
||||||
switch (p_compression) {
|
return get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression) * 3;
|
||||||
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;
|
} break;
|
||||||
case DATA_TYPE_VARIANT: {
|
case DATA_TYPE_VARIANT: {
|
||||||
ERR_FAIL_V_MSG(0, "The variant size is dynamic and can't be know at compile time.");
|
ERR_FAIL_V_MSG(0, "The variant size is dynamic and can't be know at compile time.");
|
||||||
@ -869,6 +1088,14 @@ int DataBuffer::get_bit_taken(DataType p_data_type, CompressionLevel p_compressi
|
|||||||
}
|
}
|
||||||
|
|
||||||
int DataBuffer::get_mantissa_bits(CompressionLevel p_compression) {
|
int DataBuffer::get_mantissa_bits(CompressionLevel p_compression) {
|
||||||
|
#ifndef REAL_T_IS_DOUBLE
|
||||||
|
// Fallback to compression level 1 if real_t is float
|
||||||
|
if (p_compression == DataBuffer::COMPRESSION_LEVEL_0) {
|
||||||
|
WARN_PRINT_ONCE("Compression level 0 is not supported for a binary compiled with single precision float. Falling back to compression level 1");
|
||||||
|
p_compression = DataBuffer::COMPRESSION_LEVEL_1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
|
// https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
|
||||||
switch (p_compression) {
|
switch (p_compression) {
|
||||||
case CompressionLevel::COMPRESSION_LEVEL_0:
|
case CompressionLevel::COMPRESSION_LEVEL_0:
|
||||||
@ -887,6 +1114,14 @@ int DataBuffer::get_mantissa_bits(CompressionLevel p_compression) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int DataBuffer::get_exponent_bits(CompressionLevel p_compression) {
|
int DataBuffer::get_exponent_bits(CompressionLevel p_compression) {
|
||||||
|
#ifndef REAL_T_IS_DOUBLE
|
||||||
|
// Fallback to compression level 1 if real_t is float
|
||||||
|
if (p_compression == DataBuffer::COMPRESSION_LEVEL_0) {
|
||||||
|
WARN_PRINT_ONCE("Compression level 0 is not supported for a binary compiled with single precision float. Falling back to compression level 1");
|
||||||
|
p_compression = DataBuffer::COMPRESSION_LEVEL_1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
|
// https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
|
||||||
switch (p_compression) {
|
switch (p_compression) {
|
||||||
case CompressionLevel::COMPRESSION_LEVEL_0:
|
case CompressionLevel::COMPRESSION_LEVEL_0:
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
#ifndef INPUT_BUFFER_H
|
|
||||||
#define INPUT_BUFFER_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* data_buffer.h */
|
/* data_buffer.h */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -36,10 +32,13 @@
|
|||||||
@author AndreaCatania
|
@author AndreaCatania
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "core/object/class_db.h"
|
#include "core/class_db.h"
|
||||||
|
|
||||||
#include "bit_array.h"
|
#include "bit_array.h"
|
||||||
|
|
||||||
|
#ifndef INPUT_BUFFER_H
|
||||||
|
#define INPUT_BUFFER_H
|
||||||
|
|
||||||
class DataBuffer : public Object {
|
class DataBuffer : public Object {
|
||||||
GDCLASS(DataBuffer, Object);
|
GDCLASS(DataBuffer, Object);
|
||||||
|
|
||||||
@ -47,6 +46,7 @@ public:
|
|||||||
enum DataType {
|
enum DataType {
|
||||||
DATA_TYPE_BOOL,
|
DATA_TYPE_BOOL,
|
||||||
DATA_TYPE_INT,
|
DATA_TYPE_INT,
|
||||||
|
DATA_TYPE_UINT,
|
||||||
DATA_TYPE_REAL,
|
DATA_TYPE_REAL,
|
||||||
DATA_TYPE_POSITIVE_UNIT_REAL,
|
DATA_TYPE_POSITIVE_UNIT_REAL,
|
||||||
DATA_TYPE_UNIT_REAL,
|
DATA_TYPE_UNIT_REAL,
|
||||||
@ -147,6 +147,10 @@ private:
|
|||||||
bool is_reading = false;
|
bool is_reading = false;
|
||||||
BitArray buffer;
|
BitArray buffer;
|
||||||
|
|
||||||
|
#if DEBUG_ENABLED
|
||||||
|
bool debug_enabled = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
@ -154,6 +158,9 @@ public:
|
|||||||
DataBuffer(const DataBuffer &p_other);
|
DataBuffer(const DataBuffer &p_other);
|
||||||
DataBuffer(const BitArray &p_buffer);
|
DataBuffer(const BitArray &p_buffer);
|
||||||
|
|
||||||
|
void copy(const DataBuffer &p_other);
|
||||||
|
void copy(const BitArray &p_buffer);
|
||||||
|
|
||||||
const BitArray &get_buffer() const {
|
const BitArray &get_buffer() const {
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
@ -204,6 +211,12 @@ public:
|
|||||||
/// Parse the next data as int.
|
/// Parse the next data as int.
|
||||||
int64_t read_int(CompressionLevel p_compression_level);
|
int64_t read_int(CompressionLevel p_compression_level);
|
||||||
|
|
||||||
|
/// Add the next data as uint
|
||||||
|
uint64_t add_uint(uint64_t p_input, CompressionLevel p_compression_level);
|
||||||
|
|
||||||
|
/// Parse the next data as uint.
|
||||||
|
uint64_t read_uint(CompressionLevel p_compression_level);
|
||||||
|
|
||||||
/// Add a real into the buffer. Depending on the compression level is possible
|
/// Add a real into the buffer. Depending on the compression level is possible
|
||||||
/// to store different range level.
|
/// to store different range level.
|
||||||
/// The fractional part has a precision of ~0.3%
|
/// The fractional part has a precision of ~0.3%
|
||||||
@ -238,7 +251,7 @@ public:
|
|||||||
real_t read_unit_real(CompressionLevel p_compression_level);
|
real_t read_unit_real(CompressionLevel p_compression_level);
|
||||||
|
|
||||||
/// Add a vector2 into the buffer.
|
/// Add a vector2 into the buffer.
|
||||||
/// Note: This kind of vector occupies more space than the normalized version.
|
/// Note: This kind of vector occupies more space than the normalized verison.
|
||||||
/// Consider use a normalized vector to save bandwidth if possible.
|
/// Consider use a normalized vector to save bandwidth if possible.
|
||||||
///
|
///
|
||||||
/// Returns the decompressed vector so both the client and the peers can use
|
/// Returns the decompressed vector so both the client and the peers can use
|
||||||
@ -260,7 +273,7 @@ public:
|
|||||||
Vector2 read_normalized_vector2(CompressionLevel p_compression_level);
|
Vector2 read_normalized_vector2(CompressionLevel p_compression_level);
|
||||||
|
|
||||||
/// Add a vector3 into the buffer.
|
/// Add a vector3 into the buffer.
|
||||||
/// Note: This kind of vector occupies more space than the normalized version.
|
/// Note: This kind of vector occupies more space than the normalized verison.
|
||||||
/// Consider use a normalized vector to save bandwidth if possible.
|
/// Consider use a normalized vector to save bandwidth if possible.
|
||||||
///
|
///
|
||||||
/// Returns the decompressed vector so both the client and the peers can use
|
/// Returns the decompressed vector so both the client and the peers can use
|
||||||
@ -294,7 +307,9 @@ public:
|
|||||||
|
|
||||||
void skip_bool();
|
void skip_bool();
|
||||||
void skip_int(CompressionLevel p_compression);
|
void skip_int(CompressionLevel p_compression);
|
||||||
|
void skip_uint(CompressionLevel p_compression);
|
||||||
void skip_real(CompressionLevel p_compression);
|
void skip_real(CompressionLevel p_compression);
|
||||||
|
void skip_positive_unit_real(CompressionLevel p_compression);
|
||||||
void skip_unit_real(CompressionLevel p_compression);
|
void skip_unit_real(CompressionLevel p_compression);
|
||||||
void skip_vector2(CompressionLevel p_compression);
|
void skip_vector2(CompressionLevel p_compression);
|
||||||
void skip_normalized_vector2(CompressionLevel p_compression);
|
void skip_normalized_vector2(CompressionLevel p_compression);
|
||||||
@ -305,7 +320,9 @@ public:
|
|||||||
|
|
||||||
int get_bool_size() const;
|
int get_bool_size() const;
|
||||||
int get_int_size(CompressionLevel p_compression) const;
|
int get_int_size(CompressionLevel p_compression) const;
|
||||||
|
int get_uint_size(CompressionLevel p_compression) const;
|
||||||
int get_real_size(CompressionLevel p_compression) const;
|
int get_real_size(CompressionLevel p_compression) const;
|
||||||
|
int get_positive_unit_real_size(CompressionLevel p_compression) const;
|
||||||
int get_unit_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_vector2_size(CompressionLevel p_compression) const;
|
||||||
int get_normalized_vector2_size(CompressionLevel p_compression) const;
|
int get_normalized_vector2_size(CompressionLevel p_compression) const;
|
||||||
@ -316,7 +333,9 @@ public:
|
|||||||
|
|
||||||
int read_bool_size();
|
int read_bool_size();
|
||||||
int read_int_size(CompressionLevel p_compression);
|
int read_int_size(CompressionLevel p_compression);
|
||||||
|
int read_uint_size(CompressionLevel p_compression);
|
||||||
int read_real_size(CompressionLevel p_compression);
|
int read_real_size(CompressionLevel p_compression);
|
||||||
|
int read_positive_unit_real_size(CompressionLevel p_compression);
|
||||||
int read_unit_real_size(CompressionLevel p_compression);
|
int read_unit_real_size(CompressionLevel p_compression);
|
||||||
int read_vector2_size(CompressionLevel p_compression);
|
int read_vector2_size(CompressionLevel p_compression);
|
||||||
int read_normalized_vector2_size(CompressionLevel p_compression);
|
int read_normalized_vector2_size(CompressionLevel p_compression);
|
||||||
|
30
modules/network_synchronizer/debugger_ui/cpplize_debugger.py
Normal file
30
modules/network_synchronizer/debugger_ui/cpplize_debugger.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from os import listdir
|
||||||
|
from os.path import isfile, join, isdir, exists
|
||||||
|
|
||||||
|
def create_debugger_header():
|
||||||
|
|
||||||
|
f = open("__generated__debugger_ui.h", "w", encoding="utf-8")
|
||||||
|
f.write("#pragma once\n")
|
||||||
|
f.write("\n")
|
||||||
|
f.write("/// This is a generated file by `cpplize_debugger.py`, executed by `SCsub`.\n")
|
||||||
|
f.write("/// \n")
|
||||||
|
f.write("/// DO NOT EDIT this header.\n")
|
||||||
|
f.write("/// If you want to modify this python code, you can simply change `debugger.py`\n")
|
||||||
|
f.write("/// During the next compilation this header will be updated.\n")
|
||||||
|
f.write("/// \n")
|
||||||
|
f.write("/// HOWEVER! The file will not be copied into the `bin` folder unless you remove the\n")
|
||||||
|
f.write("/// existing `bin/debugger.py` first; this algorithm prevents destroying eventual\n")
|
||||||
|
f.write("/// changes made on that file.\n")
|
||||||
|
f.write("\n")
|
||||||
|
f.write("static const char __debugger_ui_code[] = R\"TheCodeRKS(")
|
||||||
|
|
||||||
|
size = 0
|
||||||
|
with open('./debugger_ui/debugger.py', encoding="utf-8") as deb_f:
|
||||||
|
for l in deb_f.readlines():
|
||||||
|
l_utf8 = l.encode('utf-8')
|
||||||
|
size += len(l_utf8)
|
||||||
|
f.write(l);
|
||||||
|
|
||||||
|
f.write(" )TheCodeRKS\";\n")
|
||||||
|
f.write("static unsigned int __debugger_ui_code_size = "+str(size)+";\n")
|
||||||
|
f.close()
|
389
modules/network_synchronizer/debugger_ui/debugger.py
Normal file
389
modules/network_synchronizer/debugger_ui/debugger.py
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# PySimpleGUI call reference manual: https://www.pysimplegui.org/en/latest/call%20reference/
|
||||||
|
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import json
|
||||||
|
from os import listdir
|
||||||
|
from os.path import isfile, join, isdir, exists
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------------------- Config
|
||||||
|
font_size = 9
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------- Utilities
|
||||||
|
def load_json(path):
|
||||||
|
if exists(path):
|
||||||
|
with open(path) as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def compare_arrays(arr_a, arr_b):
|
||||||
|
if len(arr_a) != len(arr_b):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in range(len(arr_a)):
|
||||||
|
if arr_a[i] != arr_b[i]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def repeat_characters(char, n):
|
||||||
|
s = ""
|
||||||
|
for i in range(0, n):
|
||||||
|
s += char
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
# Fetches the directories containing the frame info.
|
||||||
|
directories = [f for f in listdir("./") if (isdir(join("./", f)) and len(listdir(join("./", f))) > 0)]
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------------------------- UI
|
||||||
|
def create_layout():
|
||||||
|
|
||||||
|
# Fetch the frame count from the dirs.
|
||||||
|
frame_count = -1
|
||||||
|
frames_iterations = {}
|
||||||
|
frames_description = {}
|
||||||
|
for dir in directories:
|
||||||
|
dir_path = join("./", dir)
|
||||||
|
file_names = [f for f in listdir(dir_path) if isfile(join(dir_path, f))]
|
||||||
|
for file_name in file_names:
|
||||||
|
file_extension_index = file_name.find("fd-")
|
||||||
|
if file_extension_index == 0:
|
||||||
|
# This file contains information about the frame.
|
||||||
|
# Extract the frame index
|
||||||
|
frame_iteration = file_name.count("@") + 1
|
||||||
|
if frame_iteration > 1:
|
||||||
|
frame_index = int(file_name[3:file_name.index("@")])
|
||||||
|
else:
|
||||||
|
frame_index = int(file_name[3:file_name.index(".json")])
|
||||||
|
|
||||||
|
if frame_index in frames_iterations:
|
||||||
|
frames_iterations[frame_index] = max(frame_iteration, frames_iterations[frame_index])
|
||||||
|
else:
|
||||||
|
frames_iterations[frame_index] = frame_iteration
|
||||||
|
|
||||||
|
frame_count = max(frame_count, frame_index)
|
||||||
|
|
||||||
|
file_path = join(dir_path, file_name)
|
||||||
|
file_json = load_json(file_path)
|
||||||
|
if "frame_summary" in file_json:
|
||||||
|
if frame_index in frames_description:
|
||||||
|
frames_description[frame_index] += " " + file_json["frame_summary"]
|
||||||
|
else:
|
||||||
|
frames_description[frame_index] = file_json["frame_summary"]
|
||||||
|
else:
|
||||||
|
frames_description[frame_index] = ""
|
||||||
|
|
||||||
|
frame_count += 1
|
||||||
|
|
||||||
|
|
||||||
|
# --- UI - Compose timeline ---
|
||||||
|
frame_list_values = []
|
||||||
|
for frame_index in range(frame_count):
|
||||||
|
frame_description = frames_description[frame_index]
|
||||||
|
frame_iterations = frames_iterations[frame_index]
|
||||||
|
for i in range(0, frame_iterations):
|
||||||
|
if i == 0:
|
||||||
|
frame_list_values.append("# " + str(frame_index) + " - " + frame_description)
|
||||||
|
else:
|
||||||
|
frame_list_values.append("# " + str(frame_index) + " @" + str(i + 1) + " - " + frame_description)
|
||||||
|
|
||||||
|
# Release this array, we don't need anylonger.
|
||||||
|
frames_description.clear()
|
||||||
|
|
||||||
|
frames_list = sg.Listbox(frame_list_values, key="FRAMES_LIST", size = [45, 30], enable_events=True, horizontal_scroll=True, select_mode=sg.LISTBOX_SELECT_MODE_BROWSE)
|
||||||
|
frames_list = sg.Frame("Frames", layout=[[frames_list]], vertical_alignment="top")
|
||||||
|
|
||||||
|
# --- UI - Compose frame detail ---
|
||||||
|
# Node list
|
||||||
|
nodes_list_listbox = sg.Listbox([], key="NODE_LIST", size = [45, 0], enable_events=True, horizontal_scroll=True, expand_y=True, expand_x=True, select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE)
|
||||||
|
nodes_list_listbox = sg.Frame("Nodes", layout=[[nodes_list_listbox]], vertical_alignment="top", expand_y=True, expand_x=True);
|
||||||
|
|
||||||
|
# Selected nodes title.
|
||||||
|
node_tile_txt = sg.Text("", key="FRAME_SUMMARY", font="Any, " + str(font_size - 1), justification="left", border_width=1, text_color="dark red")
|
||||||
|
|
||||||
|
# States table
|
||||||
|
table_status_header = ["name"]
|
||||||
|
table_status_widths = [30]
|
||||||
|
for dir in directories:
|
||||||
|
table_status_header.append("Begin ["+dir+"]")
|
||||||
|
table_status_widths.append(30)
|
||||||
|
|
||||||
|
for dir in directories:
|
||||||
|
table_status_header.append("End ("+dir+")")
|
||||||
|
table_status_widths.append(30)
|
||||||
|
|
||||||
|
table_status = sg.Table([], table_status_header, key="TABLE_STATUS", justification='left', auto_size_columns=False, col_widths=table_status_widths, vertical_scroll_only=False, num_rows=38)
|
||||||
|
table_status = sg.Frame("States", layout=[[table_status]], vertical_alignment="top")
|
||||||
|
|
||||||
|
# Messages table
|
||||||
|
tables_logs = []
|
||||||
|
for dir in directories:
|
||||||
|
tables_logs.append(sg.Frame("Log: " + dir + " Iteration: ", key=dir+"_FRAME_TABLE_LOG", layout=[[sg.Table([], [" #", "Log"], key=dir+"_TABLE_LOG", justification='left', auto_size_columns=False, col_widths=[4, 70], vertical_scroll_only=False, num_rows=25)]], vertical_alignment="top"))
|
||||||
|
|
||||||
|
logs = sg.Frame("Messages", layout=[tables_logs], vertical_alignment="top")
|
||||||
|
|
||||||
|
# --- UI - Main Window ---
|
||||||
|
layout = [
|
||||||
|
[
|
||||||
|
sg.Frame("", [[frames_list], [nodes_list_listbox]], vertical_alignment="top", expand_y=True),
|
||||||
|
sg.Frame("Frame detail", [[node_tile_txt], [table_status], [logs]], key="FRAME_FRAME_DETAIL", vertical_alignment="top")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Button("Exit")
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
return layout
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------------ Create window
|
||||||
|
window = sg.Window(title="Network Synchronizer Debugger.", layout=create_layout(), margins=(5, 5), font="Any, " + str(font_size), resizable=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------------- Event handling
|
||||||
|
frame_data = {}
|
||||||
|
nodes_list = []
|
||||||
|
selected_nodes = []
|
||||||
|
used_frame_iteration = {}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, event_values = window.read()
|
||||||
|
|
||||||
|
# EVENT: Close the program.
|
||||||
|
if event == "Exit" or event == sg.WIN_CLOSED:
|
||||||
|
window.close()
|
||||||
|
break
|
||||||
|
|
||||||
|
# EVENT: Show frame
|
||||||
|
if event == "FRAMES_LIST":
|
||||||
|
window["NODE_LIST"].update([])
|
||||||
|
|
||||||
|
if event_values["FRAMES_LIST"] != []:
|
||||||
|
frame_description = event_values["FRAMES_LIST"][0]
|
||||||
|
if "@" in frame_description:
|
||||||
|
frame_iteration = int(frame_description[frame_description.index(" @") + 2:frame_description.index(" - ")])
|
||||||
|
selected_frame_index = int(frame_description[2:frame_description.index(" @")])
|
||||||
|
else:
|
||||||
|
frame_iteration = 1
|
||||||
|
selected_frame_index = int(frame_description[2:frame_description.index(" - ")])
|
||||||
|
|
||||||
|
print("Show frame: ", selected_frame_index, " Iteration: ", frame_iteration)
|
||||||
|
|
||||||
|
frame_data = {}
|
||||||
|
nodes_list = []
|
||||||
|
used_frame_iteration = {}
|
||||||
|
for dir in directories:
|
||||||
|
used_frame_iteration[dir] = frame_iteration
|
||||||
|
frame_file_path = join("./", dir, "fd-" + str(selected_frame_index) + repeat_characters("@", frame_iteration - 1) + ".json")
|
||||||
|
|
||||||
|
if not exists(frame_file_path):
|
||||||
|
print("The path: ", frame_file_path, " was not found. Falling back to no iteration path.")
|
||||||
|
used_frame_iteration[dir] = 1
|
||||||
|
frame_file_path = join("./", dir, "fd-" + str(selected_frame_index) + ".json")
|
||||||
|
print("Path: ", frame_file_path)
|
||||||
|
|
||||||
|
if exists(frame_file_path):
|
||||||
|
frame_data_json = load_json(frame_file_path)
|
||||||
|
frame_data[dir] = frame_data_json
|
||||||
|
|
||||||
|
for node_path in frame_data_json["begin_state"]:
|
||||||
|
if node_path not in nodes_list:
|
||||||
|
# Add this node to the nodelist
|
||||||
|
nodes_list.append(node_path)
|
||||||
|
|
||||||
|
for node_path in frame_data_json["end_state"]:
|
||||||
|
if node_path not in nodes_list:
|
||||||
|
# Add this node to the nodelist
|
||||||
|
nodes_list.append(node_path)
|
||||||
|
|
||||||
|
for node_path in frame_data_json["node_log"]:
|
||||||
|
if node_path not in nodes_list:
|
||||||
|
# Add this node to the nodelist
|
||||||
|
nodes_list.append(node_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Update the node list.
|
||||||
|
window["NODE_LIST"].update(nodes_list)
|
||||||
|
|
||||||
|
if len(selected_nodes) == 0:
|
||||||
|
if len(nodes_list) > 0:
|
||||||
|
selected_nodes = [nodes_list[0]]
|
||||||
|
else:
|
||||||
|
selected_nodes = []
|
||||||
|
|
||||||
|
window["NODE_LIST"].set_value(selected_nodes)
|
||||||
|
event = "NODE_LIST"
|
||||||
|
event_values = {"NODE_LIST": selected_nodes}
|
||||||
|
|
||||||
|
# EVENT: Show node data
|
||||||
|
if event == "NODE_LIST":
|
||||||
|
|
||||||
|
window["FRAME_SUMMARY"].update("")
|
||||||
|
window["TABLE_STATUS"].update([])
|
||||||
|
|
||||||
|
for dir_name in directories:
|
||||||
|
window[dir_name + "_FRAME_TABLE_LOG"].update("Log: " + dir_name + "Frame: " + str(selected_frame_index) + " Iteration: " + str(used_frame_iteration[dir_name]))
|
||||||
|
window[dir_name + "_TABLE_LOG"].update([["", "[Nothing for this node]"]])
|
||||||
|
|
||||||
|
if event_values["NODE_LIST"] != []:
|
||||||
|
instances_count = len(directories)
|
||||||
|
row_size = 1 + (instances_count * 2)
|
||||||
|
|
||||||
|
# Compose the status table
|
||||||
|
states_table_values = []
|
||||||
|
states_row_colors = []
|
||||||
|
table_logs = {}
|
||||||
|
log_row_colors = {}
|
||||||
|
|
||||||
|
selected_nodes = event_values["NODE_LIST"]
|
||||||
|
|
||||||
|
for node_path in selected_nodes:
|
||||||
|
|
||||||
|
# First collects the var names
|
||||||
|
vars_names = ["***"]
|
||||||
|
for dir in directories:
|
||||||
|
if dir in frame_data:
|
||||||
|
if "begin_state" in frame_data[dir]:
|
||||||
|
if node_path in frame_data[dir]["begin_state"]:
|
||||||
|
for var_name in frame_data[dir]["begin_state"][node_path]:
|
||||||
|
if var_name not in vars_names:
|
||||||
|
vars_names.append(var_name)
|
||||||
|
|
||||||
|
if "end_state" in frame_data[dir]:
|
||||||
|
if node_path in frame_data[dir]["end_state"]:
|
||||||
|
for var_name in frame_data[dir]["end_state"][node_path]:
|
||||||
|
if var_name not in vars_names:
|
||||||
|
vars_names.append(var_name)
|
||||||
|
|
||||||
|
vars_names.append("---")
|
||||||
|
|
||||||
|
# Add those to the table.
|
||||||
|
for var_name in vars_names:
|
||||||
|
|
||||||
|
# Initializes the row.
|
||||||
|
row = [""] * row_size
|
||||||
|
row_index = len(states_table_values)
|
||||||
|
|
||||||
|
# Special rows
|
||||||
|
if var_name == "***":
|
||||||
|
# This is a special row to signal the start of a new node data
|
||||||
|
row[0] = node_path
|
||||||
|
states_table_values.append(row)
|
||||||
|
states_row_colors.append((row_index, "black"))
|
||||||
|
continue
|
||||||
|
elif var_name == "---":
|
||||||
|
# This is a special row to signal the end of the node data
|
||||||
|
states_table_values.append(row)
|
||||||
|
continue
|
||||||
|
|
||||||
|
row[0] = var_name.replace("*", "🔄")
|
||||||
|
|
||||||
|
# Set the row data.
|
||||||
|
for dir_i, dir_name in enumerate(directories):
|
||||||
|
if dir_name in frame_data:
|
||||||
|
if "begin_state" in frame_data[dir_name]:
|
||||||
|
if node_path in frame_data[dir_name]["begin_state"]:
|
||||||
|
if var_name in frame_data[dir_name]["begin_state"][node_path]:
|
||||||
|
#print(1, " + (", instances_count, " * 0) + ", dir_i)
|
||||||
|
row[1 + (instances_count * 0) + dir_i] = str(frame_data[dir_name]["begin_state"][node_path][var_name])
|
||||||
|
|
||||||
|
if "end_state" in frame_data[dir_name]:
|
||||||
|
if node_path in frame_data[dir_name]["end_state"]:
|
||||||
|
if var_name in frame_data[dir_name]["end_state"][node_path]:
|
||||||
|
#print(1, " + (", instances_count, " * 1) + ", dir_i)
|
||||||
|
row[1 + (instances_count * 1) + dir_i] = str(frame_data[dir_name]["end_state"][node_path][var_name])
|
||||||
|
|
||||||
|
# Check if different, so mark a worning.
|
||||||
|
for state_index in range(2):
|
||||||
|
for i in range(instances_count - 1):
|
||||||
|
if row[1 + (state_index*instances_count) + i + 0] != row[1 + (state_index*instances_count) + i + 1]:
|
||||||
|
row[1 + (state_index*instances_count) + i + 0] = "⚠️ " + row[1 + (state_index*instances_count) + i + 0]
|
||||||
|
row[1 + (state_index*instances_count) + i + 1] = "⚠️ " + row[1 + (state_index*instances_count) + i + 1]
|
||||||
|
states_row_colors.append((row_index, "dark salmon"))
|
||||||
|
break
|
||||||
|
|
||||||
|
states_table_values.append(row)
|
||||||
|
|
||||||
|
# Compose the log
|
||||||
|
for dir_name in directories:
|
||||||
|
if dir_name in frame_data:
|
||||||
|
if "node_log" in frame_data[dir_name]:
|
||||||
|
if node_path in frame_data[dir_name]["node_log"]:
|
||||||
|
|
||||||
|
table_logs[dir_name] = table_logs.get(dir_name, [])
|
||||||
|
log_row_colors[dir_name] = log_row_colors.get(dir_name, [])
|
||||||
|
|
||||||
|
table_logs[dir_name] += [["", node_path]]
|
||||||
|
log_row_colors[dir_name] += [(len(table_logs[dir_name]) - 1, "black")]
|
||||||
|
|
||||||
|
for val in frame_data[dir_name]["node_log"][node_path]:
|
||||||
|
|
||||||
|
# Append the log
|
||||||
|
table_logs[dir_name] += [["{:4d}".format(val["i"]), val["m"]]]
|
||||||
|
row_index = len(table_logs[dir_name]) - 1
|
||||||
|
|
||||||
|
# Check if this line should be colored
|
||||||
|
if val["m"].find("[WARNING]") == 0:
|
||||||
|
log_row_colors[dir_name] += [(row_index, "dark salmon")]
|
||||||
|
|
||||||
|
elif val["m"].find("[ERROR]") == 0:
|
||||||
|
log_row_colors[dir_name] += [(row_index, "red")]
|
||||||
|
|
||||||
|
elif val["m"].find("[WRITE]") == 0:
|
||||||
|
log_row_colors[dir_name] += [(row_index, "cadet blue")]
|
||||||
|
|
||||||
|
elif val["m"].find("[READ]") == 0:
|
||||||
|
log_row_colors[dir_name] += [(row_index, "medium aquamarine")]
|
||||||
|
|
||||||
|
table_logs[dir_name] += [["", ""]]
|
||||||
|
|
||||||
|
window["FRAME_FRAME_DETAIL"].update("Frame " + str(selected_frame_index) + " details")
|
||||||
|
window["TABLE_STATUS"].update(states_table_values, row_colors=states_row_colors)
|
||||||
|
|
||||||
|
frame_summary = ""
|
||||||
|
for dir_name in directories:
|
||||||
|
if dir_name in frame_data:
|
||||||
|
frame_summary += frame_data[dir_name]["frame_summary"]
|
||||||
|
if dir_name in table_logs:
|
||||||
|
window[dir_name + "_TABLE_LOG"].update(table_logs[dir_name], row_colors=log_row_colors[dir_name]);
|
||||||
|
|
||||||
|
# Check if write and read databuffer is the same.
|
||||||
|
for dir_name in directories:
|
||||||
|
if dir_name in frame_data and (dir_name == "nonet" or dir_name == "client"):
|
||||||
|
are_the_same = compare_arrays(frame_data[dir_name]["data_buffer_writes"], frame_data[dir_name]["data_buffer_reads"])
|
||||||
|
if not are_the_same:
|
||||||
|
if frame_summary != "":
|
||||||
|
frame_summary += "\n"
|
||||||
|
frame_summary += "[BUG] The DataBuffer written by `_collect_inputs` and read by `_controller_process` is different. Both should be exactly the same."
|
||||||
|
|
||||||
|
# Check if the server read correctly the received buffer.
|
||||||
|
if "server" in frame_data and "client" in frame_data:
|
||||||
|
are_the_same = compare_arrays(frame_data["server"]["data_buffer_reads"], frame_data["client"]["data_buffer_reads"])
|
||||||
|
if not are_the_same:
|
||||||
|
if frame_summary != "":
|
||||||
|
frame_summary += "\n"
|
||||||
|
frame_summary += "[BUG] The DataBuffer written by the client is different from the one read on the server."
|
||||||
|
|
||||||
|
# Check if the client sent the correct inputs to the server.
|
||||||
|
if "client" in frame_data:
|
||||||
|
for other_frame_index, is_similar in frame_data["client"]["are_inputs_different_results"].items():
|
||||||
|
other_frame_index = int(other_frame_index)
|
||||||
|
other_file_path = join("./", "client", "fd-" + str(other_frame_index) + ".json")
|
||||||
|
other_frame_json = load_json(other_file_path)
|
||||||
|
if "data_buffer_reads" in other_frame_json:
|
||||||
|
is_really_similar = compare_arrays(other_frame_json["data_buffer_reads"], frame_data["client"]["data_buffer_reads"])
|
||||||
|
if is_really_similar != is_similar:
|
||||||
|
if frame_summary != "":
|
||||||
|
frame_summary += "\n"
|
||||||
|
frame_summary += "[BUG] The function `_are_inputs_different` doesn't seems to work:\n"
|
||||||
|
frame_summary += " As the inputs read on the frame " + str(frame_data["client"]["frame"]) + " and " + str(other_frame_index) + " are " + ("SIMILAR" if is_really_similar else "DIFFERENT") +" but the net sync considered it "+ ("SIMILAR" if is_similar else "DIFFERENT")
|
||||||
|
|
||||||
|
window["FRAME_SUMMARY"].update(frame_summary)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------------------- Exit
|
15
modules/network_synchronizer/debugger_ui/readme.md
Normal file
15
modules/network_synchronizer/debugger_ui/readme.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Network debugger UI
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
- Install python
|
||||||
|
- Install pip
|
||||||
|
- Install PySimpleGUI: `pip install pysymplegui`
|
||||||
|
- Install Tkinter:
|
||||||
|
1. Install Tkinter app:
|
||||||
|
- Fedora: `sudo dnf install python3-tkinter`
|
||||||
|
- Ubuntu: `sudo apt-get install python3-tk`
|
||||||
|
1. Install python library: `pip install tk`
|
||||||
|
|
||||||
|
|
||||||
|
# Missing features
|
||||||
|
1. At the moment it's not supported multiple client debugging.
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<class name="DataBuffer" inherits="Object" version="4.2">
|
<class name="DataBuffer" inherits="Object" version="3.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||||
<brief_description>
|
<brief_description>
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<description>
|
<description>
|
||||||
@ -48,6 +48,13 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="add_uint">
|
||||||
|
<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_unit_real">
|
<method name="add_unit_real">
|
||||||
<return type="float" />
|
<return type="float" />
|
||||||
<argument index="0" name="value" type="float" />
|
<argument index="0" name="value" type="float" />
|
||||||
@ -120,6 +127,12 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="get_uint_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">
|
<method name="get_unit_real_size" qualifiers="const">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
@ -184,6 +197,12 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="read_positive_unit_real">
|
||||||
|
<return type="float" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="read_real">
|
<method name="read_real">
|
||||||
<return type="float" />
|
<return type="float" />
|
||||||
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
@ -196,6 +215,18 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="read_uint">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="read_uint_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">
|
<method name="read_unit_real">
|
||||||
<return type="float" />
|
<return type="float" />
|
||||||
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
@ -276,6 +307,12 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="skip_uint">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="skip_unit_real">
|
<method name="skip_unit_real">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
<argument index="0" name="compression_level" type="int" enum="DataBuffer.CompressionLevel" default="1" />
|
||||||
@ -300,17 +337,23 @@
|
|||||||
</constant>
|
</constant>
|
||||||
<constant name="DATA_TYPE_INT" value="1" enum="DataType">
|
<constant name="DATA_TYPE_INT" value="1" enum="DataType">
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="DATA_TYPE_REAL" value="2" enum="DataType">
|
<constant name="DATA_TYPE_UINT" value="2" enum="DataType">
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="DATA_TYPE_UNIT_REAL" value="4" enum="DataType">
|
<constant name="DATA_TYPE_REAL" value="3" enum="DataType">
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="DATA_TYPE_VECTOR2" value="5" enum="DataType">
|
<constant name="DATA_TYPE_POSITIVE_UNIT_REAL" value="4" enum="DataType">
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="DATA_TYPE_NORMALIZED_VECTOR2" value="6" enum="DataType">
|
<constant name="DATA_TYPE_UNIT_REAL" value="5" enum="DataType">
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="DATA_TYPE_VECTOR3" value="7" enum="DataType">
|
<constant name="DATA_TYPE_VECTOR2" value="6" enum="DataType">
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="DATA_TYPE_NORMALIZED_VECTOR3" value="8" enum="DataType">
|
<constant name="DATA_TYPE_NORMALIZED_VECTOR2" value="7" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_VECTOR3" value="8" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_NORMALIZED_VECTOR3" value="9" enum="DataType">
|
||||||
|
</constant>
|
||||||
|
<constant name="DATA_TYPE_VARIANT" value="10" enum="DataType">
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="COMPRESSION_LEVEL_0" value="0" enum="CompressionLevel">
|
<constant name="COMPRESSION_LEVEL_0" value="0" enum="CompressionLevel">
|
||||||
</constant>
|
</constant>
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<class name="Interpolator" inherits="Object" version="4.2">
|
|
||||||
<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>
|
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<class name="NetworkedController" inherits="Node" version="4.2">
|
<class name="NetworkedController" inherits="Node" version="3.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||||
<brief_description>
|
<brief_description>
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<description>
|
<description>
|
||||||
@ -10,7 +10,9 @@
|
|||||||
<method name="_apply_epoch" qualifiers="virtual">
|
<method name="_apply_epoch" qualifiers="virtual">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="delta" type="float" />
|
<argument index="0" name="delta" type="float" />
|
||||||
<argument index="1" name="interpolated_data" type="Array" />
|
<argument index="1" name="interpolation_alpha" type="float" />
|
||||||
|
<argument index="2" name="past_buffer" type="DataBuffer" />
|
||||||
|
<argument index="3" name="future_buffer" type="DataBuffer" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
@ -47,19 +49,6 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="get_current_input_id" qualifiers="const">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
<description>
|
<description>
|
||||||
@ -92,7 +81,7 @@
|
|||||||
</method>
|
</method>
|
||||||
<method name="player_get_pretended_delta" qualifiers="const">
|
<method name="player_get_pretended_delta" qualifiers="const">
|
||||||
<return type="float" />
|
<return type="float" />
|
||||||
<argument index="0" name="physics_ticks_per_seconds" type="int" />
|
<argument index="0" name="iterations_per_seconds" type="int" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
@ -112,21 +101,17 @@
|
|||||||
</method>
|
</method>
|
||||||
</methods>
|
</methods>
|
||||||
<members>
|
<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 name="doll_connection_stats_frame_span" type="int" setter="set_doll_connection_stats_frame_span" getter="get_doll_connection_stats_frame_span" default="60">
|
||||||
</member>
|
</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 name="doll_interpolation_max_overshot" type="float" setter="set_doll_interpolation_max_overshot" getter="get_doll_interpolation_max_overshot" default="0.2">
|
||||||
</member>
|
</member>
|
||||||
<member name="doll_epoch_collect_rate" type="int" setter="set_doll_epoch_collect_rate" getter="get_doll_epoch_collect_rate" default="1">
|
<member name="doll_max_frames_delay" type="int" setter="set_doll_max_frames_delay" getter="get_doll_max_frames_delay" default="25">
|
||||||
</member>
|
</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 name="doll_min_frames_delay" type="int" setter="set_doll_min_frames_delay" getter="get_doll_min_frames_delay" default="0">
|
||||||
</member>
|
</member>
|
||||||
<member name="doll_max_delay" type="int" setter="set_doll_max_delay" getter="get_doll_max_delay" default="60">
|
<member name="doll_net_sensitivity" type="float" setter="set_doll_net_sensitivity" getter="get_doll_net_sensitivity" default="0.21">
|
||||||
</member>
|
</member>
|
||||||
<member name="doll_max_frames_delay" type="int" setter="set_doll_max_frames_delay" getter="get_doll_max_frames_delay" default="5">
|
<member name="doll_sync_rate" type="int" setter="set_doll_sync_rate" getter="get_doll_sync_rate" default="30">
|
||||||
</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>
|
||||||
<member name="input_storage_size" type="int" setter="set_player_input_storage_size" getter="get_player_input_storage_size" default="180">
|
<member name="input_storage_size" type="int" setter="set_player_input_storage_size" getter="get_player_input_storage_size" default="180">
|
||||||
</member>
|
</member>
|
||||||
@ -134,18 +119,24 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="max_redundant_inputs" type="int" setter="set_max_redundant_inputs" getter="get_max_redundant_inputs" default="5">
|
<member name="max_redundant_inputs" type="int" setter="set_max_redundant_inputs" getter="get_max_redundant_inputs" default="5">
|
||||||
</member>
|
</member>
|
||||||
<member name="min_frames_delay" type="int" setter="set_min_frames_delay" getter="get_min_frames_delay" default="2">
|
<member name="min_frames_delay" type="int" setter="set_min_frames_delay" getter="get_min_frames_delay" default="1">
|
||||||
</member>
|
</member>
|
||||||
<member name="net_sensitivity" type="float" setter="set_net_sensitivity" getter="get_net_sensitivity" default="0.1">
|
<member name="net_sensitivity" type="float" setter="set_net_sensitivity" getter="get_net_sensitivity" default="0.1">
|
||||||
</member>
|
</member>
|
||||||
<member name="network_traced_frames" type="int" setter="set_network_traced_frames" getter="get_network_traced_frames" default="120">
|
<member name="network_traced_frames" type="int" setter="set_network_traced_frames" getter="get_network_traced_frames" default="120">
|
||||||
</member>
|
</member>
|
||||||
|
<member name="server_controlled" type="bool" setter="set_server_controlled" getter="get_server_controlled" default="false">
|
||||||
|
</member>
|
||||||
<member name="tick_acceleration" type="float" setter="set_tick_acceleration" getter="get_tick_acceleration" default="2.0">
|
<member name="tick_acceleration" type="float" setter="set_tick_acceleration" getter="get_tick_acceleration" default="2.0">
|
||||||
</member>
|
</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 name="tick_speedup_notification_delay" type="float" setter="set_tick_speedup_notification_delay" getter="get_tick_speedup_notification_delay" default="0.33">
|
||||||
</member>
|
</member>
|
||||||
</members>
|
</members>
|
||||||
<signals>
|
<signals>
|
||||||
|
<signal name="controller_reset">
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</signal>
|
||||||
<signal name="doll_sync_paused">
|
<signal name="doll_sync_paused">
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<class name="SceneDiff" inherits="Object" version="4.2">
|
|
||||||
<brief_description>
|
|
||||||
</brief_description>
|
|
||||||
<description>
|
|
||||||
</description>
|
|
||||||
<tutorials>
|
|
||||||
</tutorials>
|
|
||||||
<methods>
|
|
||||||
</methods>
|
|
||||||
<constants>
|
|
||||||
</constants>
|
|
||||||
</class>
|
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<class name="SceneSynchronizer" inherits="Node" version="4.2">
|
<class name="SceneSynchronizer" inherits="Node" version="3.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||||
<brief_description>
|
<brief_description>
|
||||||
|
The `SceneSynchronizer` is used to synchronize all the peers using server authoritative networking model.
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
@ -45,6 +46,13 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="find_action_id" qualifiers="const">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="event_name" type="String" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="force_state_notify">
|
<method name="force_state_notify">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
@ -71,7 +79,7 @@
|
|||||||
<method name="get_variable_id">
|
<method name="get_variable_id">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="variable" type="StringName" />
|
<argument index="1" name="variable" type="String" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
@ -122,6 +130,24 @@
|
|||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="register_action">
|
||||||
|
<return type="int" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="action_func" type="String" />
|
||||||
|
<argument index="2" name="action_encoding_func" type="String" />
|
||||||
|
<argument index="3" name="can_client_trigger" type="bool" default="false" />
|
||||||
|
<argument index="4" name="wait_server_validation" type="bool" default="false" />
|
||||||
|
<argument index="5" name="server_action_validation_func" type="String" default="""" />
|
||||||
|
<description>
|
||||||
|
Register an new action.
|
||||||
|
`node` The node that owns the event
|
||||||
|
`action_func` The function that is triggered when the event is executed.
|
||||||
|
`action_encoding_func` The function called to definte the validation encoding.
|
||||||
|
`can_client_trigger` If true this `Action` can be triggered on client.
|
||||||
|
`wait_server_validation` If true the event will be emitted locally only if the server validates it.
|
||||||
|
`server_action_validation_func` The validation function, must return a boolean.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="register_node">
|
<method name="register_node">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
@ -131,15 +157,15 @@
|
|||||||
<method name="register_process">
|
<method name="register_process">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="function" type="StringName" />
|
<argument index="1" name="function" type="String" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="register_variable">
|
<method name="register_variable">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="variable" type="StringName" />
|
<argument index="1" name="variable" type="String" />
|
||||||
<argument index="2" name="on_change_notify" type="StringName" default="@""" />
|
<argument index="2" name="on_change_notify" type="String" default="""" />
|
||||||
<argument index="3" name="flags" type="int" enum="NetEventFlag" default="17" />
|
<argument index="3" name="flags" type="int" enum="NetEventFlag" default="17" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
@ -172,7 +198,7 @@
|
|||||||
<method name="set_skip_rewinding">
|
<method name="set_skip_rewinding">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="variable" type="StringName" />
|
<argument index="1" name="variable" type="String" />
|
||||||
<argument index="2" name="skip_rewinding" type="bool" />
|
<argument index="2" name="skip_rewinding" type="bool" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
@ -204,13 +230,37 @@
|
|||||||
<method name="track_variable_changes">
|
<method name="track_variable_changes">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="variable" type="StringName" />
|
<argument index="1" name="variable" type="String" />
|
||||||
<argument index="2" name="object" type="Object" />
|
<argument index="2" name="object" type="Object" />
|
||||||
<argument index="3" name="method" type="StringName" />
|
<argument index="3" name="method" type="String" />
|
||||||
<argument index="4" name="flags" type="int" enum="NetEventFlag" default="17" />
|
<argument index="4" name="flags" type="int" enum="NetEventFlag" default="17" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="trigger_action">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="action_id" type="int" />
|
||||||
|
<argument index="2" name="arguments" type="Array" default="[ ]" />
|
||||||
|
<argument index="3" name="recipients_peers" type="PoolIntArray" default="PoolIntArray( )" />
|
||||||
|
<description>
|
||||||
|
Trigger an action.
|
||||||
|
This action can be triggered by the client, only if it was registered with `can_client_trigger = true`.
|
||||||
|
Note: If you pass a recipient the action is notified only to that peer; if you leave it unset the action will be propagated to all the peers. Generally you never specify the `recipients`. In any case, only the server can use this feature.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="trigger_action_by_name">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="node" type="Node" />
|
||||||
|
<argument index="1" name="event_name" type="String" />
|
||||||
|
<argument index="2" name="arguments" type="Array" default="[ ]" />
|
||||||
|
<argument index="3" name="recipients_peers" type="PoolIntArray" default="PoolIntArray( )" />
|
||||||
|
<description>
|
||||||
|
Trigger an action.
|
||||||
|
This action can be triggered by the client, only if it was registered with `can_client_trigger = true`.
|
||||||
|
Note: If you pass a recipient the action is notified only to that peer; if you leave it unset the action will be propagated to all the peers. Generally you never specify the `recipients`. In any case, only the server can use this feature.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="unregister_node">
|
<method name="unregister_node">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
@ -220,28 +270,32 @@
|
|||||||
<method name="unregister_process">
|
<method name="unregister_process">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="function" type="StringName" />
|
<argument index="1" name="function" type="String" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="unregister_variable">
|
<method name="unregister_variable">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="variable" type="StringName" />
|
<argument index="1" name="variable" type="String" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="untrack_variable_changes">
|
<method name="untrack_variable_changes">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="variable" type="StringName" />
|
<argument index="1" name="variable" type="String" />
|
||||||
<argument index="2" name="object" type="Object" />
|
<argument index="2" name="object" type="Object" />
|
||||||
<argument index="3" name="method" type="StringName" />
|
<argument index="3" name="method" type="String" />
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
</methods>
|
</methods>
|
||||||
<members>
|
<members>
|
||||||
|
<member name="actions_redundancy" type="int" setter="set_actions_redundancy" getter="get_actions_redundancy" default="3">
|
||||||
|
</member>
|
||||||
|
<member name="actions_resend_time" type="float" setter="set_actions_resend_time" getter="get_actions_resend_time" default="0.0333333">
|
||||||
|
</member>
|
||||||
<member name="comparison_float_tolerance" type="float" setter="set_comparison_float_tolerance" getter="get_comparison_float_tolerance" default="0.001">
|
<member name="comparison_float_tolerance" type="float" setter="set_comparison_float_tolerance" getter="get_comparison_float_tolerance" default="0.001">
|
||||||
</member>
|
</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 name="server_notify_state_interval" type="float" setter="set_server_notify_state_interval" getter="get_server_notify_state_interval" default="1.0">
|
||||||
|
14
modules/network_synchronizer/godot_backward_utility_cpp.h
Normal file
14
modules/network_synchronizer/godot_backward_utility_cpp.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
/// Used to compile against Godot 3.x
|
||||||
|
|
||||||
|
/**
|
||||||
|
@author AndreaCatania
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/engine.h"
|
||||||
|
#include "scene/main/viewport.h"
|
||||||
|
#define FLOAT REAL
|
||||||
|
#define STRING_NAME STRING
|
||||||
|
#define Callable(a, b) a, b
|
||||||
|
#define ObjectID CompatObjectID
|
||||||
|
#define SNAME(a) a
|
23
modules/network_synchronizer/godot_backward_utility_header.h
Normal file
23
modules/network_synchronizer/godot_backward_utility_header.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// Used to compile against Godot 3.x
|
||||||
|
|
||||||
|
/**
|
||||||
|
@author AndreaCatania
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/typedefs.h"
|
||||||
|
|
||||||
|
struct CompatObjectID {
|
||||||
|
uint64_t id;
|
||||||
|
|
||||||
|
CompatObjectID() :
|
||||||
|
id(0){};
|
||||||
|
CompatObjectID(uint64_t p_id) :
|
||||||
|
id(p_id){};
|
||||||
|
|
||||||
|
operator uint64_t() const { return id; }
|
||||||
|
|
||||||
|
bool is_valid() const { return id != 0; }
|
||||||
|
bool is_null() const { return id == 0; }
|
||||||
|
};
|
@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||||
|
sodipodi:docname="icon_networked_controller.svg"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
width="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
height="16"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<metadata
|
||||||
|
id="metadata14">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs12" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
inkscape:current-layer="svg8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:cy="12.711873"
|
||||||
|
inkscape:cx="8.1047594"
|
||||||
|
inkscape:zoom="26.589315"
|
||||||
|
showgrid="false"
|
||||||
|
id="namedview10"
|
||||||
|
inkscape:window-height="1659"
|
||||||
|
inkscape:window-width="3072"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
guidetolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
objecttolerance="10"
|
||||||
|
borderopacity="1"
|
||||||
|
bordercolor="#666666"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1" />
|
||||||
|
<g
|
||||||
|
id="g313"
|
||||||
|
transform="translate(0,-0.47092181)">
|
||||||
|
<circle
|
||||||
|
style="fill:#99ff55;stroke-width:0.916107"
|
||||||
|
id="path711"
|
||||||
|
cx="2.788183"
|
||||||
|
cy="8.4011793"
|
||||||
|
r="2.0042512" />
|
||||||
|
<circle
|
||||||
|
style="fill:#99ff55;stroke-width:0.720163"
|
||||||
|
id="path711-3"
|
||||||
|
cx="7.8340111"
|
||||||
|
cy="4.1984701"
|
||||||
|
r="1.5755664" />
|
||||||
|
<circle
|
||||||
|
style="fill:#99ff55;stroke-width:0.916107"
|
||||||
|
id="path711-6"
|
||||||
|
cx="13.211817"
|
||||||
|
cy="12.314689"
|
||||||
|
r="2.0042512" />
|
||||||
|
<rect
|
||||||
|
style="fill:#99ff55"
|
||||||
|
id="rect267"
|
||||||
|
width="5.6688361"
|
||||||
|
height="2.7708793"
|
||||||
|
x="8.2227888"
|
||||||
|
y="5.7568631"
|
||||||
|
transform="rotate(19.971473)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#99ff55;stroke-width:0.466395"
|
||||||
|
id="rect267-7"
|
||||||
|
width="1.5560057"
|
||||||
|
height="2.1958716"
|
||||||
|
x="-0.56298721"
|
||||||
|
y="7.1034822"
|
||||||
|
transform="rotate(-40.166886)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
400
modules/network_synchronizer/input_network_encoder.cpp
Normal file
400
modules/network_synchronizer/input_network_encoder.cpp
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
#include "input_network_encoder.h"
|
||||||
|
|
||||||
|
#include "scene_synchronizer.h"
|
||||||
|
|
||||||
|
void InputNetworkEncoder::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("register_input", "name", "default_value", "type", "compression_level", "comparison_floating_point_precision"), &InputNetworkEncoder::register_input, DEFVAL(CMP_EPSILON));
|
||||||
|
ClassDB::bind_method(D_METHOD("find_input_id", "name"), &InputNetworkEncoder::find_input_id);
|
||||||
|
ClassDB::bind_method(D_METHOD("encode", "inputs", "buffer"), &InputNetworkEncoder::script_encode);
|
||||||
|
ClassDB::bind_method(D_METHOD("decode", "buffer"), &InputNetworkEncoder::script_decode);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_defaults"), &InputNetworkEncoder::script_get_defaults);
|
||||||
|
ClassDB::bind_method(D_METHOD("are_different", "buffer_a", "buffer_b"), &InputNetworkEncoder::script_are_different);
|
||||||
|
ClassDB::bind_method(D_METHOD("count_size", "buffer"), &InputNetworkEncoder::script_count_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t InputNetworkEncoder::register_input(
|
||||||
|
const StringName &p_name,
|
||||||
|
const Variant &p_default_value,
|
||||||
|
DataBuffer::DataType p_type,
|
||||||
|
DataBuffer::CompressionLevel p_compression_level,
|
||||||
|
real_t p_comparison_floating_point_precision) {
|
||||||
|
switch (p_type) {
|
||||||
|
case DataBuffer::DATA_TYPE_BOOL:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::BOOL, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `BOOL` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_INT:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::INT, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `INT` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UINT:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::INT, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `UINT` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_REAL:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::REAL, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `REAL` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_POSITIVE_UNIT_REAL:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::REAL, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `REAL` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UNIT_REAL:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::REAL, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `REAL` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR2:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::VECTOR2, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `Vector2` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR2:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::VECTOR2, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `Vector2` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR3:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::VECTOR3, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `Vector3` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR3:
|
||||||
|
ERR_FAIL_COND_V_MSG(p_default_value.get_type() != Variant::VECTOR3, UINT32_MAX, "The moveset initialization failed for" + p_name + " the specified data type is `Vector3` but the default parameter is " + itos(p_default_value.get_type()));
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VARIANT:
|
||||||
|
/* No need to check variant, anything is accepted at this point.*/
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint32_t index = input_info.size();
|
||||||
|
|
||||||
|
input_info.resize(input_info.size() + 1);
|
||||||
|
input_info[index].name = p_name;
|
||||||
|
input_info[index].default_value = p_default_value;
|
||||||
|
input_info[index].data_type = p_type;
|
||||||
|
input_info[index].compression_level = p_compression_level;
|
||||||
|
input_info[index].comparison_floating_point_precision = p_comparison_floating_point_precision;
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t InputNetworkEncoder::find_input_id(const StringName &p_name) const {
|
||||||
|
for (uint32_t i = 0; i < input_info.size(); i += 1) {
|
||||||
|
if (input_info[i].name == p_name) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INDEX_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LocalVector<NetworkedInputInfo> &InputNetworkEncoder::get_input_info() const {
|
||||||
|
return input_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputNetworkEncoder::encode(const LocalVector<Variant> &p_input, DataBuffer &r_buffer) const {
|
||||||
|
for (uint32_t i = 0; i < input_info.size(); i += 1) {
|
||||||
|
const NetworkedInputInfo &info = input_info[i];
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (i < p_input.size() && info.default_value.get_type() != p_input[i].get_type() && p_input[i].get_type() != Variant::NIL) {
|
||||||
|
NET_DEBUG_ERR("During the input encoding the passed value `" + p_input[i] + "` has a different type to the expected one. Using the default value `" + info.default_value + "`.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const bool is_default =
|
||||||
|
// If the input exist into the array.
|
||||||
|
i >= p_input.size() ||
|
||||||
|
// Use default if the variable type is different.
|
||||||
|
info.default_value.get_type() != p_input[i].get_type() ||
|
||||||
|
// Use default if the variable value is equal to default.
|
||||||
|
info.default_value == p_input[i];
|
||||||
|
|
||||||
|
if (info.default_value.get_type() != Variant::BOOL) {
|
||||||
|
r_buffer.add_bool(is_default);
|
||||||
|
|
||||||
|
if (!is_default) {
|
||||||
|
const Variant &pending_input = p_input[i];
|
||||||
|
switch (info.data_type) {
|
||||||
|
case DataBuffer::DATA_TYPE_BOOL:
|
||||||
|
CRASH_NOW_MSG("Boolean are handled differently. Thanks to the above IF this condition never occurs.");
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UINT:
|
||||||
|
r_buffer.add_uint(pending_input.operator unsigned int(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_INT:
|
||||||
|
r_buffer.add_int(pending_input.operator int(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_REAL:
|
||||||
|
r_buffer.add_real(pending_input.operator real_t(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_POSITIVE_UNIT_REAL:
|
||||||
|
r_buffer.add_positive_unit_real(pending_input.operator real_t(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UNIT_REAL:
|
||||||
|
r_buffer.add_unit_real(pending_input.operator real_t(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR2:
|
||||||
|
r_buffer.add_vector2(pending_input.operator Vector2(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR2:
|
||||||
|
r_buffer.add_normalized_vector2(pending_input.operator Vector2(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR3:
|
||||||
|
r_buffer.add_vector3(pending_input.operator Vector3(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR3:
|
||||||
|
r_buffer.add_normalized_vector3(pending_input.operator Vector3(), info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VARIANT:
|
||||||
|
r_buffer.add_variant(pending_input);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the data is bool no need to set the default.
|
||||||
|
if (!is_default) {
|
||||||
|
r_buffer.add_bool(p_input[i].operator bool());
|
||||||
|
} else {
|
||||||
|
r_buffer.add_bool(info.default_value.operator bool());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputNetworkEncoder::decode(DataBuffer &p_buffer, LocalVector<Variant> &r_inputs) const {
|
||||||
|
if (r_inputs.size() < input_info.size()) {
|
||||||
|
r_inputs.resize(input_info.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < input_info.size(); i += 1) {
|
||||||
|
const NetworkedInputInfo &info = input_info[i];
|
||||||
|
|
||||||
|
const bool is_bool = info.default_value.get_type() == Variant::BOOL;
|
||||||
|
|
||||||
|
bool is_default = false;
|
||||||
|
if (is_bool == false) {
|
||||||
|
is_default = p_buffer.read_bool();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_default) {
|
||||||
|
r_inputs[i] = info.default_value;
|
||||||
|
} else {
|
||||||
|
switch (info.data_type) {
|
||||||
|
case DataBuffer::DATA_TYPE_BOOL:
|
||||||
|
r_inputs[i] = p_buffer.read_bool();
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UINT:
|
||||||
|
r_inputs[i] = p_buffer.read_uint(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_INT:
|
||||||
|
r_inputs[i] = p_buffer.read_int(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_REAL:
|
||||||
|
r_inputs[i] = p_buffer.read_real(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_POSITIVE_UNIT_REAL:
|
||||||
|
r_inputs[i] = p_buffer.read_positive_unit_real(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UNIT_REAL:
|
||||||
|
r_inputs[i] = p_buffer.read_unit_real(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR2:
|
||||||
|
r_inputs[i] = p_buffer.read_vector2(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR2:
|
||||||
|
r_inputs[i] = p_buffer.read_normalized_vector2(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR3:
|
||||||
|
r_inputs[i] = p_buffer.read_vector3(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR3:
|
||||||
|
r_inputs[i] = p_buffer.read_normalized_vector3(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VARIANT:
|
||||||
|
r_inputs[i] = p_buffer.read_variant();
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputNetworkEncoder::reset_inputs_to_defaults(LocalVector<Variant> &r_input) const {
|
||||||
|
const uint32_t size = r_input.size() < input_info.size() ? r_input.size() : input_info.size();
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < size; i += 1) {
|
||||||
|
r_input[i] = input_info[i].default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputNetworkEncoder::are_different(DataBuffer &p_buffer_A, DataBuffer &p_buffer_B) const {
|
||||||
|
for (uint32_t i = 0; i < input_info.size(); i += 1) {
|
||||||
|
const NetworkedInputInfo &info = input_info[i];
|
||||||
|
|
||||||
|
const bool is_bool = info.default_value.get_type() == Variant::BOOL;
|
||||||
|
|
||||||
|
bool is_default_A = false;
|
||||||
|
bool is_default_B = false;
|
||||||
|
if (is_bool == false) {
|
||||||
|
is_default_A = p_buffer_A.read_bool();
|
||||||
|
is_default_B = p_buffer_B.read_bool();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool are_equals = true;
|
||||||
|
if (is_default_A && is_default_B) {
|
||||||
|
are_equals = true;
|
||||||
|
} else {
|
||||||
|
switch (info.data_type) {
|
||||||
|
case DataBuffer::DATA_TYPE_BOOL:
|
||||||
|
are_equals = p_buffer_A.read_bool() == p_buffer_B.read_bool();
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UINT:
|
||||||
|
are_equals = Math::is_equal_approx(p_buffer_A.read_uint(info.compression_level), p_buffer_B.read_uint(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_INT:
|
||||||
|
are_equals = Math::is_equal_approx(p_buffer_A.read_int(info.compression_level), p_buffer_B.read_int(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_REAL:
|
||||||
|
are_equals = Math::is_equal_approx(static_cast<real_t>(p_buffer_A.read_real(info.compression_level)), static_cast<real_t>(p_buffer_B.read_real(info.compression_level)), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_POSITIVE_UNIT_REAL:
|
||||||
|
are_equals = Math::is_equal_approx(p_buffer_A.read_positive_unit_real(info.compression_level), p_buffer_B.read_positive_unit_real(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UNIT_REAL:
|
||||||
|
are_equals = Math::is_equal_approx(p_buffer_A.read_unit_real(info.compression_level), p_buffer_B.read_unit_real(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR2:
|
||||||
|
are_equals = SceneSynchronizer::compare(p_buffer_A.read_vector2(info.compression_level), p_buffer_B.read_vector2(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR2:
|
||||||
|
are_equals = SceneSynchronizer::compare(p_buffer_A.read_normalized_vector2(info.compression_level), p_buffer_B.read_normalized_vector2(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR3:
|
||||||
|
are_equals = SceneSynchronizer::compare(p_buffer_A.read_vector3(info.compression_level), p_buffer_B.read_vector3(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR3:
|
||||||
|
are_equals = SceneSynchronizer::compare(p_buffer_A.read_normalized_vector3(info.compression_level), p_buffer_B.read_normalized_vector3(info.compression_level), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VARIANT:
|
||||||
|
are_equals = SceneSynchronizer::compare(p_buffer_A.read_variant(), p_buffer_B.read_variant(), info.comparison_floating_point_precision);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!are_equals) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t InputNetworkEncoder::count_size(DataBuffer &p_buffer) const {
|
||||||
|
int size = 0;
|
||||||
|
for (uint32_t i = 0; i < input_info.size(); i += 1) {
|
||||||
|
const NetworkedInputInfo &info = input_info[i];
|
||||||
|
|
||||||
|
const bool is_bool = info.default_value.get_type() == Variant::BOOL;
|
||||||
|
if (is_bool) {
|
||||||
|
// The bool data.
|
||||||
|
size += p_buffer.read_bool_size();
|
||||||
|
} else {
|
||||||
|
// The default marker
|
||||||
|
const bool is_default = p_buffer.read_bool();
|
||||||
|
size += p_buffer.get_bool_size();
|
||||||
|
|
||||||
|
if (is_default == false) {
|
||||||
|
// Non default data set the actual data, so we need to count
|
||||||
|
// the size.
|
||||||
|
switch (info.data_type) {
|
||||||
|
case DataBuffer::DATA_TYPE_BOOL:
|
||||||
|
CRASH_NOW_MSG("This can't ever happen, as the bool is already handled.");
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UINT:
|
||||||
|
size += p_buffer.read_uint_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_INT:
|
||||||
|
size += p_buffer.read_int_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_REAL:
|
||||||
|
size += p_buffer.read_real_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_POSITIVE_UNIT_REAL:
|
||||||
|
size += p_buffer.read_positive_unit_real_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_UNIT_REAL:
|
||||||
|
size += p_buffer.read_unit_real_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR2:
|
||||||
|
size += p_buffer.read_vector2_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR2:
|
||||||
|
size += p_buffer.read_normalized_vector2_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR3:
|
||||||
|
size += p_buffer.read_vector3_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR3:
|
||||||
|
size += p_buffer.read_normalized_vector3_size(info.compression_level);
|
||||||
|
break;
|
||||||
|
case DataBuffer::DATA_TYPE_VARIANT:
|
||||||
|
size += p_buffer.read_variant_size();
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputNetworkEncoder::script_encode(const Array &p_inputs, Object *r_buffer) const {
|
||||||
|
ERR_FAIL_COND(r_buffer == nullptr);
|
||||||
|
DataBuffer *db = Object::cast_to<DataBuffer>(r_buffer);
|
||||||
|
ERR_FAIL_COND(db == nullptr);
|
||||||
|
|
||||||
|
LocalVector<Variant> inputs;
|
||||||
|
inputs.resize(p_inputs.size());
|
||||||
|
for (int i = 0; i < p_inputs.size(); i += 1) {
|
||||||
|
inputs[i] = p_inputs[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(inputs, *db);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array InputNetworkEncoder::script_decode(Object *p_buffer) const {
|
||||||
|
ERR_FAIL_COND_V(p_buffer == nullptr, Array());
|
||||||
|
DataBuffer *db = Object::cast_to<DataBuffer>(p_buffer);
|
||||||
|
ERR_FAIL_COND_V(db == nullptr, Array());
|
||||||
|
|
||||||
|
LocalVector<Variant> inputs;
|
||||||
|
decode(*db, inputs);
|
||||||
|
|
||||||
|
Array out;
|
||||||
|
out.resize(inputs.size());
|
||||||
|
for (uint32_t i = 0; i < inputs.size(); i += 1) {
|
||||||
|
out[i] = inputs[i];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array InputNetworkEncoder::script_get_defaults() const {
|
||||||
|
LocalVector<Variant> inputs;
|
||||||
|
inputs.resize(input_info.size());
|
||||||
|
|
||||||
|
reset_inputs_to_defaults(inputs);
|
||||||
|
|
||||||
|
Array out;
|
||||||
|
out.resize(inputs.size());
|
||||||
|
for (uint32_t i = 0; i < inputs.size(); i += 1) {
|
||||||
|
out[i] = inputs[i];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputNetworkEncoder::script_are_different(Object *p_buffer_A, Object *p_buffer_B) const {
|
||||||
|
ERR_FAIL_COND_V(p_buffer_A == nullptr, true);
|
||||||
|
DataBuffer *db_A = Object::cast_to<DataBuffer>(p_buffer_A);
|
||||||
|
ERR_FAIL_COND_V(db_A == nullptr, false);
|
||||||
|
|
||||||
|
ERR_FAIL_COND_V(p_buffer_B == nullptr, true);
|
||||||
|
DataBuffer *db_B = Object::cast_to<DataBuffer>(p_buffer_B);
|
||||||
|
ERR_FAIL_COND_V(db_B == nullptr, true);
|
||||||
|
|
||||||
|
return are_different(*db_A, *db_B);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t InputNetworkEncoder::script_count_size(Object *p_buffer) const {
|
||||||
|
ERR_FAIL_COND_V(p_buffer == nullptr, 0);
|
||||||
|
DataBuffer *db = Object::cast_to<DataBuffer>(p_buffer);
|
||||||
|
ERR_FAIL_COND_V(db == nullptr, 0);
|
||||||
|
|
||||||
|
return count_size(*db);
|
||||||
|
}
|
51
modules/network_synchronizer/input_network_encoder.h
Normal file
51
modules/network_synchronizer/input_network_encoder.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/local_vector.h"
|
||||||
|
#include "core/resource.h"
|
||||||
|
#include "core/variant.h"
|
||||||
|
#include "data_buffer.h"
|
||||||
|
|
||||||
|
struct NetworkedInputInfo {
|
||||||
|
StringName name;
|
||||||
|
Variant default_value;
|
||||||
|
uint32_t index;
|
||||||
|
DataBuffer::DataType data_type;
|
||||||
|
DataBuffer::CompressionLevel compression_level;
|
||||||
|
real_t comparison_floating_point_precision;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputNetworkEncoder : public Resource {
|
||||||
|
GDCLASS(InputNetworkEncoder, Resource)
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr static uint32_t INDEX_NONE = UINT32_MAX;
|
||||||
|
|
||||||
|
private:
|
||||||
|
LocalVector<NetworkedInputInfo> input_info;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
uint32_t register_input(
|
||||||
|
const StringName &p_name,
|
||||||
|
const Variant &p_default_value,
|
||||||
|
DataBuffer::DataType p_type,
|
||||||
|
DataBuffer::CompressionLevel p_compression_level,
|
||||||
|
real_t p_comparison_floating_point_precision = CMP_EPSILON);
|
||||||
|
|
||||||
|
uint32_t find_input_id(const StringName &p_name) const;
|
||||||
|
const LocalVector<NetworkedInputInfo> &get_input_info() const;
|
||||||
|
|
||||||
|
void encode(const LocalVector<Variant> &p_inputs, DataBuffer &r_buffer) const;
|
||||||
|
void decode(DataBuffer &p_buffer, LocalVector<Variant> &r_inputs) const;
|
||||||
|
void reset_inputs_to_defaults(LocalVector<Variant> &r_inputs) const;
|
||||||
|
bool are_different(DataBuffer &p_buffer_A, DataBuffer &p_buffer_B) const;
|
||||||
|
uint32_t count_size(DataBuffer &p_buffer) const;
|
||||||
|
|
||||||
|
void script_encode(const Array &p_inputs, Object *r_buffer) const;
|
||||||
|
Array script_decode(Object *p_buffer) const;
|
||||||
|
Array script_get_defaults() const;
|
||||||
|
bool script_are_different(Object *p_buffer_A, Object *p_buffer_B) const;
|
||||||
|
uint32_t script_count_size(Object *p_buffer) const;
|
||||||
|
};
|
@ -1,416 +0,0 @@
|
|||||||
/*************************************************************************/
|
|
||||||
/* interpolator.cpp */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* PANDEMONIUM ENGINE */
|
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
|
||||||
/* 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/string/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 = 0;
|
|
||||||
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, "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, "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 {
|
|
||||||
// Enough 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, "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::REAL:
|
|
||||||
return Math::lerp(p_v1, p_v2, p_delta);
|
|
||||||
case Variant::Type::VECTOR2:
|
|
||||||
return p_v1.operator Vector2().linear_interpolate(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().linear_interpolate(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::TRANSFORM:
|
|
||||||
return p_v1.operator Transform().interpolate_with(p_v2.operator Transform(), p_delta);
|
|
||||||
default:
|
|
||||||
return p_delta > 0.5 ? p_v2 : p_v1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
#ifndef INTERPOLATOR_H
|
|
||||||
#define INTERPOLATOR_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
|
||||||
/* interpolator.h */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* PANDEMONIUM ENGINE */
|
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
|
||||||
/* 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/containers/vector.h"
|
|
||||||
#include "core/object/class_db.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
|
|
258
modules/network_synchronizer/net_action.cpp
Normal file
258
modules/network_synchronizer/net_action.cpp
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
#include "net_action.h"
|
||||||
|
|
||||||
|
#include "core/error_macros.h"
|
||||||
|
#include "core/os/os.h"
|
||||||
|
#include "scene/main/node.h"
|
||||||
|
#include "scene_synchronizer.h"
|
||||||
|
#include "scene_synchronizer_debugger.h"
|
||||||
|
|
||||||
|
void SenderNetAction::prepare_processor(
|
||||||
|
NetUtility::NodeData *p_nd,
|
||||||
|
NetActionId p_action_id,
|
||||||
|
const Array &p_vars) {
|
||||||
|
action_processor.action_id = p_action_id;
|
||||||
|
action_processor.nd = p_nd;
|
||||||
|
|
||||||
|
// Compress the vars so that locally we use the compressed version like remotely.
|
||||||
|
const NetActionInfo &info = p_nd->net_actions[p_action_id];
|
||||||
|
|
||||||
|
LocalVector<Variant> raw_vars;
|
||||||
|
raw_vars.resize(p_vars.size());
|
||||||
|
for (int i = 0; i < p_vars.size(); i += 1) {
|
||||||
|
raw_vars[i] = p_vars[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
DataBuffer db;
|
||||||
|
db.begin_write(0);
|
||||||
|
info.network_encoder->encode(raw_vars, db);
|
||||||
|
db.begin_read();
|
||||||
|
info.network_encoder->decode(db, raw_vars);
|
||||||
|
|
||||||
|
action_processor.vars.resize(raw_vars.size());
|
||||||
|
for (int i = 0; i < p_vars.size(); i += 1) {
|
||||||
|
action_processor.vars[i] = raw_vars[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const NetActionInfo &SenderNetAction::get_action_info() const {
|
||||||
|
return action_processor.nd->net_actions[action_processor.action_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SenderNetAction::client_set_executed_input_id(uint32_t p_input_id) {
|
||||||
|
peers_executed_input_id[1] = p_input_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SenderNetAction::client_get_executed_input_id() const {
|
||||||
|
const uint32_t *input_id = peers_executed_input_id.getptr(1);
|
||||||
|
return input_id == nullptr ? UINT32_MAX : *input_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SenderNetAction::peer_get_executed_input_id(int p_peer) const {
|
||||||
|
const uint32_t *input_id = peers_executed_input_id.getptr(p_peer);
|
||||||
|
return input_id == nullptr ? UINT32_MAX : *input_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void net_action::encode_net_action(
|
||||||
|
const LocalVector<SenderNetAction *> &p_actions,
|
||||||
|
int p_peer,
|
||||||
|
DataBuffer &r_data_buffer) {
|
||||||
|
for (uint32_t i = 0; i < p_actions.size(); i++) {
|
||||||
|
// ---------------------------------------------------------- Add a boolean to note a new action
|
||||||
|
const bool has_anotherone = true;
|
||||||
|
r_data_buffer.add_bool(has_anotherone);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------- Add the sender action token
|
||||||
|
r_data_buffer.add_uint(p_actions[i]->action_token, DataBuffer::COMPRESSION_LEVEL_1);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- Add the node id
|
||||||
|
const bool uses_node_id = p_actions[i]->action_processor.nd->id != UINT32_MAX;
|
||||||
|
r_data_buffer.add_bool(uses_node_id);
|
||||||
|
|
||||||
|
if (uses_node_id) {
|
||||||
|
r_data_buffer.add_uint(p_actions[i]->action_processor.nd->id, DataBuffer::COMPRESSION_LEVEL_2);
|
||||||
|
} else {
|
||||||
|
r_data_buffer.add_variant(p_actions[i]->action_processor.nd->node->get_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------- Add the action_id
|
||||||
|
const NetActionId action_id = p_actions[i]->action_processor.action_id;
|
||||||
|
r_data_buffer.add_uint(action_id, DataBuffer::COMPRESSION_LEVEL_2);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------- Add executed frame
|
||||||
|
const uint32_t *executed_frame = p_actions[i]->peers_executed_input_id.getptr(p_peer);
|
||||||
|
const bool has_executed_frame = executed_frame != nullptr;
|
||||||
|
r_data_buffer.add_bool(has_executed_frame);
|
||||||
|
if (has_executed_frame) {
|
||||||
|
r_data_buffer.add_uint(*executed_frame, DataBuffer::COMPRESSION_LEVEL_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------- Add the executed time changed
|
||||||
|
const bool sender_executed_time_changed =
|
||||||
|
p_actions[i]->sender_executed_time_changed &&
|
||||||
|
p_peer == p_actions[i]->sender_peer;
|
||||||
|
|
||||||
|
r_data_buffer.add_bool(sender_executed_time_changed);
|
||||||
|
if (sender_executed_time_changed) {
|
||||||
|
r_data_buffer.add_uint(p_actions[i]->triggerer_action_token, DataBuffer::COMPRESSION_LEVEL_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------- Add the variables
|
||||||
|
LocalVector<Variant> inputs;
|
||||||
|
inputs.resize(p_actions[i]->action_processor.vars.size());
|
||||||
|
for (uint32_t u = 0; u < inputs.size(); u++) {
|
||||||
|
inputs[u] = p_actions[i]->action_processor.vars[u];
|
||||||
|
}
|
||||||
|
p_actions[i]->action_processor.nd->net_actions[action_id].network_encoder->encode(inputs, r_data_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool has_anotherone = false;
|
||||||
|
r_data_buffer.add_bool(has_anotherone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void net_action::decode_net_action(
|
||||||
|
SceneSynchronizer *synchronizer,
|
||||||
|
DataBuffer &p_data_buffer,
|
||||||
|
int p_peer,
|
||||||
|
LocalVector<SenderNetAction> &r_actions) {
|
||||||
|
const int sender_peer = synchronizer->get_tree()->get_multiplayer()->get_rpc_sender_id();
|
||||||
|
|
||||||
|
LocalVector<Variant> variables;
|
||||||
|
|
||||||
|
while (p_data_buffer.get_bit_offset() < p_data_buffer.total_size()) {
|
||||||
|
// ---------------------------------------------------------- Fetch the boolean `has_anotherone`
|
||||||
|
const bool has_anotherone = p_data_buffer.read_bool();
|
||||||
|
if (!has_anotherone) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------- Fetch the sender action token
|
||||||
|
const uint32_t action_token = p_data_buffer.read_uint(DataBuffer::COMPRESSION_LEVEL_1);
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------- Fetch the node id
|
||||||
|
const bool uses_node_id = p_data_buffer.read_bool();
|
||||||
|
|
||||||
|
// Fetch the node_data.
|
||||||
|
NetUtility::NodeData *node_data;
|
||||||
|
if (uses_node_id) {
|
||||||
|
const uint32_t node_data_id = p_data_buffer.read_uint(DataBuffer::COMPRESSION_LEVEL_2);
|
||||||
|
node_data = synchronizer->get_node_data(node_data_id);
|
||||||
|
if (node_data == nullptr) {
|
||||||
|
SceneSynchronizerDebugger::singleton()->debug_error(synchronizer, "The received action data contains a node which is not registered on this peer. NodeDataId: `" + itos(node_data_id) + "`");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Variant node_path = p_data_buffer.read_variant();
|
||||||
|
ERR_FAIL_COND_MSG(node_path.get_type() != Variant::NODE_PATH, "The received acts data is malformed, expected NodePath at this point.");
|
||||||
|
|
||||||
|
Node *node = synchronizer->get_node(node_path);
|
||||||
|
if (node == nullptr) {
|
||||||
|
SceneSynchronizerDebugger::singleton()->debug_error(synchronizer, String("The received action data contains a node path which is unknown: `") + node_path + "`");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
node_data = synchronizer->find_node_data(node);
|
||||||
|
if (node_data == nullptr) {
|
||||||
|
SceneSynchronizerDebugger::singleton()->debug_error(synchronizer, String("The received action data contains a node which is not registered on this peer. NodePath: `") + node_path + "`");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------- Fetch the action_id
|
||||||
|
const NetActionId action_id = p_data_buffer.read_uint(DataBuffer::COMPRESSION_LEVEL_2);
|
||||||
|
if (node_data->net_actions.size() <= action_id) {
|
||||||
|
SceneSynchronizerDebugger::singleton()->debug_error(synchronizer, "The received action data is malformed. This peer doesn't have the action_id (`" + itos(action_id) + "`) for the node `" + node_data->node->get_path() + "`");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------ Fetch executed frame
|
||||||
|
uint32_t executed_frame = UINT32_MAX;
|
||||||
|
const bool has_executed_frame = p_data_buffer.read_bool();
|
||||||
|
if (has_executed_frame) {
|
||||||
|
executed_frame = p_data_buffer.read_uint(DataBuffer::COMPRESSION_LEVEL_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------- Fetch the executed time changed
|
||||||
|
const bool sender_executed_time_changed = p_data_buffer.read_bool();
|
||||||
|
uint32_t triggerer_action_token = action_token;
|
||||||
|
if (sender_executed_time_changed) {
|
||||||
|
triggerer_action_token = p_data_buffer.read_uint(DataBuffer::COMPRESSION_LEVEL_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------- Fetch the variables
|
||||||
|
variables.clear();
|
||||||
|
node_data->net_actions[action_id].network_encoder->decode(p_data_buffer, variables);
|
||||||
|
|
||||||
|
// This should never be triggered because the `has_anotherone` is meant to be false and stop the loop.
|
||||||
|
ERR_FAIL_COND_MSG(p_data_buffer.get_bit_offset() >= p_data_buffer.total_size(), "The received action data is malformed.");
|
||||||
|
|
||||||
|
Array arguments;
|
||||||
|
arguments.resize(variables.size());
|
||||||
|
for (uint32_t i = 0; i < variables.size(); i += 1) {
|
||||||
|
arguments[i] = variables[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t index = r_actions.size();
|
||||||
|
r_actions.resize(index + 1);
|
||||||
|
|
||||||
|
r_actions[index].action_token = action_token;
|
||||||
|
r_actions[index].triggerer_action_token = triggerer_action_token;
|
||||||
|
r_actions[index].sender_executed_time_changed = sender_executed_time_changed;
|
||||||
|
r_actions[index].sender_peer = sender_peer;
|
||||||
|
r_actions[index].peers_executed_input_id[p_peer] = executed_frame;
|
||||||
|
r_actions[index].action_processor.nd = node_data;
|
||||||
|
r_actions[index].action_processor.action_id = action_id;
|
||||||
|
r_actions[index].action_processor.vars = arguments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetActionSenderInfo::process_received_action(uint32_t p_action_index) {
|
||||||
|
const uint64_t now = OS::get_singleton()->get_ticks_msec();
|
||||||
|
|
||||||
|
bool already_received = true;
|
||||||
|
|
||||||
|
if (last_received_action_id != UINT32_MAX) {
|
||||||
|
if (last_received_action_id < p_action_index) {
|
||||||
|
// Add all the in between ids as missing.
|
||||||
|
for (uint32_t missing_action_id = last_received_action_id + 1; missing_action_id < p_action_index; missing_action_id += 1) {
|
||||||
|
missing_actions.push_back({ missing_action_id, now });
|
||||||
|
}
|
||||||
|
|
||||||
|
last_received_action_id = p_action_index;
|
||||||
|
already_received = false;
|
||||||
|
|
||||||
|
} else if (last_received_action_id == p_action_index) {
|
||||||
|
// Already known, drop it.
|
||||||
|
already_received = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Old act, check if it's a missing act.
|
||||||
|
const int64_t index = missing_actions.find({ p_action_index, 0 });
|
||||||
|
const bool known = index == -1;
|
||||||
|
if (known) {
|
||||||
|
already_received = true;
|
||||||
|
} else {
|
||||||
|
already_received = false;
|
||||||
|
missing_actions.remove_unordered(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
last_received_action_id = p_action_index;
|
||||||
|
already_received = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return already_received;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetActionSenderInfo::check_missing_actions_and_clean_up(Node *p_owner) {
|
||||||
|
const uint64_t now = OS::get_singleton()->get_ticks_msec();
|
||||||
|
const uint64_t one_second = 1000;
|
||||||
|
|
||||||
|
for (int64_t i = int64_t(missing_actions.size()) - 1; i >= 0; i -= 1) {
|
||||||
|
if ((missing_actions[i].timestamp + one_second) <= now) {
|
||||||
|
// After more than 1 second the action is still missing.
|
||||||
|
SceneSynchronizerDebugger::singleton()->debug_warning(p_owner, "The action with ID: `" + itos(missing_actions[i].id) + "` was never received.");
|
||||||
|
// Remove it from missing actions, this will:
|
||||||
|
// 1. From now on this action will be discarded if received.
|
||||||
|
// 2. Reduce the `missing_actions` array size.
|
||||||
|
missing_actions.remove_unordered(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
modules/network_synchronizer/net_action.h
Normal file
92
modules/network_synchronizer/net_action.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* net_actions.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 NET_ACTIONS_H
|
||||||
|
#define NET_ACTIONS_H
|
||||||
|
|
||||||
|
#include "core/reference.h"
|
||||||
|
#include "net_action_processor.h"
|
||||||
|
#include "net_utilities.h"
|
||||||
|
|
||||||
|
class SceneSynchronizer;
|
||||||
|
struct NetActionInfo;
|
||||||
|
|
||||||
|
struct SenderNetAction {
|
||||||
|
/// The token used to reference this Action.
|
||||||
|
uint32_t action_token = UINT32_MAX;
|
||||||
|
/// The token generated by the peer that generated the Action: Usually this is the same as `action_token`.
|
||||||
|
uint32_t triggerer_action_token = UINT32_MAX;
|
||||||
|
NetActionProcessor action_processor;
|
||||||
|
Vector<uint8_t> vars_buffer;
|
||||||
|
Vector<int> recipients;
|
||||||
|
int sender_peer = -1;
|
||||||
|
HashMap<int, uint32_t> peers_executed_input_id;
|
||||||
|
bool locally_executed = false;
|
||||||
|
bool sent_by_the_server = false;
|
||||||
|
uint32_t send_count = 0;
|
||||||
|
uint32_t send_timestamp = 0;
|
||||||
|
bool sender_executed_time_changed = false;
|
||||||
|
|
||||||
|
void prepare_processor(NetUtility::NodeData *p_nd, NetActionId p_event_id, const Array &p_vars);
|
||||||
|
const NetActionInfo &get_action_info() const;
|
||||||
|
void client_set_executed_input_id(uint32_t p_input_id);
|
||||||
|
uint32_t client_get_executed_input_id() const;
|
||||||
|
|
||||||
|
uint32_t peer_get_executed_input_id(int p_peer) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace net_action {
|
||||||
|
void encode_net_action(
|
||||||
|
const LocalVector<SenderNetAction *> &p_actions,
|
||||||
|
int p_source_peer,
|
||||||
|
DataBuffer &r_data_buffer);
|
||||||
|
|
||||||
|
void decode_net_action(
|
||||||
|
SceneSynchronizer *synchronizer,
|
||||||
|
DataBuffer &p_data_buffer,
|
||||||
|
int p_source_peer,
|
||||||
|
LocalVector<SenderNetAction> &r_actions);
|
||||||
|
}; //namespace net_action
|
||||||
|
|
||||||
|
struct NetActionSenderInfo {
|
||||||
|
uint32_t last_received_action_id = UINT32_MAX;
|
||||||
|
|
||||||
|
struct ActionAndTimestamp {
|
||||||
|
uint32_t id;
|
||||||
|
uint64_t timestamp;
|
||||||
|
bool operator==(const ActionAndTimestamp &p_other) const { return id == p_other.id; }
|
||||||
|
};
|
||||||
|
LocalVector<ActionAndTimestamp> missing_actions;
|
||||||
|
|
||||||
|
bool process_received_action(uint32_t p_action_id);
|
||||||
|
void check_missing_actions_and_clean_up(Node *p_owner);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
9
modules/network_synchronizer/net_action_info.cpp
Normal file
9
modules/network_synchronizer/net_action_info.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#include "net_action_info.h"
|
||||||
|
|
||||||
|
bool NetActionInfo::operator==(const NetActionInfo &p_other) const {
|
||||||
|
return act_func == p_other.act_func;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetActionInfo::operator<(const NetActionInfo &p_other) const {
|
||||||
|
return id < p_other.id;
|
||||||
|
}
|
24
modules/network_synchronizer/net_action_info.h
Normal file
24
modules/network_synchronizer/net_action_info.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/string_name.h"
|
||||||
|
#include "input_network_encoder.h"
|
||||||
|
#include "net_action_processor.h"
|
||||||
|
|
||||||
|
struct NetActionInfo {
|
||||||
|
NetActionId id = UINT32_MAX;
|
||||||
|
/// The event function
|
||||||
|
StringName act_func;
|
||||||
|
/// The event function encoding
|
||||||
|
StringName act_encoding_func;
|
||||||
|
/// If true the client can trigger this action.
|
||||||
|
bool can_client_trigger;
|
||||||
|
/// If true the client who triggered the event will wait the server validation to execute the event.
|
||||||
|
bool wait_server_validation;
|
||||||
|
/// The function to validate the event: Only executed on the server.
|
||||||
|
StringName server_action_validation_func;
|
||||||
|
/// The network_encoder used to encode decode the environment data.
|
||||||
|
Ref<InputNetworkEncoder> network_encoder;
|
||||||
|
|
||||||
|
bool operator==(const NetActionInfo &p_other) const;
|
||||||
|
bool operator<(const NetActionInfo &p_other) const;
|
||||||
|
};
|
34
modules/network_synchronizer/net_action_processor.cpp
Normal file
34
modules/network_synchronizer/net_action_processor.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "net_action_processor.h"
|
||||||
|
|
||||||
|
#include "input_network_encoder.h"
|
||||||
|
#include "net_action_info.h"
|
||||||
|
#include "net_utilities.h"
|
||||||
|
#include "scene/main/node.h"
|
||||||
|
|
||||||
|
void NetActionProcessor::execute() {
|
||||||
|
const NetActionInfo &info = nd->net_actions[action_id];
|
||||||
|
nd->node->callv(info.act_func, vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetActionProcessor::server_validate() const {
|
||||||
|
const NetActionInfo &info = nd->net_actions[action_id];
|
||||||
|
|
||||||
|
if (info.server_action_validation_func == StringName()) {
|
||||||
|
// Always valid when the func is not set!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Variant is_valid = nd->node->callv(info.server_action_validation_func, vars);
|
||||||
|
|
||||||
|
ERR_FAIL_COND_V_MSG(is_valid.get_type() != Variant::BOOL, false, "[FATAL] The function `" + nd->node->get_path() + "::" + info.server_action_validation_func + "` MUST return a bool.");
|
||||||
|
|
||||||
|
return is_valid.operator bool();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetActionProcessor::operator String() const {
|
||||||
|
const NetActionInfo &info = nd->net_actions[action_id];
|
||||||
|
String v = Variant(vars);
|
||||||
|
// Strip `[]` from the Array string.
|
||||||
|
v = v.substr(1, v.size() - 3);
|
||||||
|
return String(nd->node->get_path()) + "::" + info.act_func + "(" + v + ")";
|
||||||
|
}
|
40
modules/network_synchronizer/net_action_processor.h
Normal file
40
modules/network_synchronizer/net_action_processor.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/array.h"
|
||||||
|
#include "core/ustring.h"
|
||||||
|
|
||||||
|
namespace NetUtility {
|
||||||
|
struct NodeData;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint32_t NetActionId;
|
||||||
|
|
||||||
|
struct NetActionProcessor {
|
||||||
|
NetUtility::NodeData *nd;
|
||||||
|
NetActionId action_id;
|
||||||
|
Array vars;
|
||||||
|
|
||||||
|
NetActionProcessor() = default;
|
||||||
|
NetActionProcessor(
|
||||||
|
NetUtility::NodeData *p_nd,
|
||||||
|
NetActionId p_action_id,
|
||||||
|
const Array &p_vars) :
|
||||||
|
nd(p_nd),
|
||||||
|
action_id(p_action_id),
|
||||||
|
vars(p_vars) {}
|
||||||
|
|
||||||
|
void execute();
|
||||||
|
bool server_validate() const;
|
||||||
|
operator String() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TokenizedNetActionProcessor {
|
||||||
|
uint32_t action_token;
|
||||||
|
NetActionProcessor processor;
|
||||||
|
|
||||||
|
bool operator==(const TokenizedNetActionProcessor &p_other) const { return action_token == p_other.action_token; }
|
||||||
|
|
||||||
|
TokenizedNetActionProcessor() = default;
|
||||||
|
TokenizedNetActionProcessor(uint32_t p_at, NetActionProcessor p_p) :
|
||||||
|
action_token(p_at), processor(p_p) {}
|
||||||
|
};
|
@ -2,12 +2,11 @@
|
|||||||
/* net_utilities.cpp */
|
/* net_utilities.cpp */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -34,9 +33,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "net_utilities.h"
|
#include "net_utilities.h"
|
||||||
|
|
||||||
#include "scene/main/node.h"
|
#include "scene/main/node.h"
|
||||||
|
|
||||||
|
#include "godot_backward_utility_cpp.h"
|
||||||
|
|
||||||
bool NetUtility::ChangeListener::operator==(const ChangeListener &p_other) const {
|
bool NetUtility::ChangeListener::operator==(const ChangeListener &p_other) const {
|
||||||
return object_id == p_other.object_id && method == p_other.method;
|
return object_id == p_other.object_id && method == p_other.method;
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ bool NetUtility::VarData::operator<(const VarData &p_other) const {
|
|||||||
return id < p_other.id;
|
return id < p_other.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetUtility::NodeData::process(const real_t p_delta) const {
|
void NetUtility::NodeData::process(const double p_delta) const {
|
||||||
const Variant var_delta = p_delta;
|
const Variant var_delta = p_delta;
|
||||||
const Variant *fake_array_vars = &var_delta;
|
const Variant *fake_array_vars = &var_delta;
|
||||||
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
#ifndef NET_UTILITIES_H
|
|
||||||
#define NET_UTILITIES_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* net_utilities.h */
|
/* net_utilities.h */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -36,14 +32,25 @@
|
|||||||
@author AndreaCatania
|
@author AndreaCatania
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "core/containers/local_vector.h"
|
#ifndef NET_UTILITIES_H
|
||||||
|
#define NET_UTILITIES_H
|
||||||
|
|
||||||
|
#include "core/local_vector.h"
|
||||||
#include "core/math/math_funcs.h"
|
#include "core/math/math_funcs.h"
|
||||||
#include "core/variant/variant.h"
|
#include "core/project_settings.h"
|
||||||
|
#include "core/variant.h"
|
||||||
|
#include "net_action_info.h"
|
||||||
|
#include "net_action_processor.h"
|
||||||
|
|
||||||
|
#include "godot_backward_utility_header.h"
|
||||||
|
#define ObjectID CompatObjectID
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
#define NET_DEBUG_PRINT(msg) \
|
#define NET_DEBUG_PRINT(msg) \
|
||||||
|
if (ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/log_debug_warnings_and_messages")) \
|
||||||
print_line(String("[Net] ") + msg)
|
print_line(String("[Net] ") + msg)
|
||||||
#define NET_DEBUG_WARN(msg) \
|
#define NET_DEBUG_WARN(msg) \
|
||||||
|
if (ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/log_debug_warnings_and_messages")) \
|
||||||
WARN_PRINT(String("[Net] ") + msg)
|
WARN_PRINT(String("[Net] ") + msg)
|
||||||
#define NET_DEBUG_ERR(msg) \
|
#define NET_DEBUG_ERR(msg) \
|
||||||
ERR_PRINT(String("[Net] ") + msg)
|
ERR_PRINT(String("[Net] ") + msg)
|
||||||
@ -56,6 +63,28 @@
|
|||||||
typedef uint32_t NetNodeId;
|
typedef uint32_t NetNodeId;
|
||||||
typedef uint32_t NetVarId;
|
typedef uint32_t NetVarId;
|
||||||
|
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
|
||||||
|
#include "godot_tracy/profiler.h"
|
||||||
|
|
||||||
|
#define PROFILE \
|
||||||
|
ZoneScoped;
|
||||||
|
|
||||||
|
#define PROFILE_NODE \
|
||||||
|
ZoneScoped; \
|
||||||
|
CharString c = String(get_path()).utf8(); \
|
||||||
|
if (c.size() >= std::numeric_limits<uint16_t>::max()) { \
|
||||||
|
c.resize(std::numeric_limits<uint16_t>::max() - 1); \
|
||||||
|
} \
|
||||||
|
ZoneText(c.ptr(), c.size());
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define PROFILE
|
||||||
|
#define PROFILE_NODE
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/// Flags used to control when an event is executed.
|
/// Flags used to control when an event is executed.
|
||||||
enum NetEventFlag {
|
enum NetEventFlag {
|
||||||
|
|
||||||
@ -108,11 +137,12 @@ public:
|
|||||||
/// Maximum value.
|
/// Maximum value.
|
||||||
T max() const;
|
T max() const;
|
||||||
|
|
||||||
/// Minimum value.
|
/// Minumum value.
|
||||||
T min(uint32_t p_consider_last) const;
|
T min(uint32_t p_consider_last = UINT32_MAX) const;
|
||||||
|
|
||||||
/// Median value.
|
/// Median value.
|
||||||
T average() const;
|
T average() const;
|
||||||
|
T average_rounded() const;
|
||||||
|
|
||||||
T get_deviation(T p_mean) const;
|
T get_deviation(T p_mean) const;
|
||||||
|
|
||||||
@ -209,6 +239,29 @@ T StatisticalRingBuffer<T>::average() const {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T StatisticalRingBuffer<T>::average_rounded() 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 = Math::round(double(a) / double(data.size()));
|
||||||
|
T b = Math::round(double(avg_sum) / double(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 Math::round(double(avg_sum) / double(data.size()));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
T StatisticalRingBuffer<T>::get_deviation(T p_mean) const {
|
T StatisticalRingBuffer<T>::get_deviation(T p_mean) const {
|
||||||
if (data.size() <= 0) {
|
if (data.size() <= 0) {
|
||||||
@ -247,7 +300,7 @@ struct NodeChangeListener {
|
|||||||
bool operator==(const NodeChangeListener &p_other) const;
|
bool operator==(const NodeChangeListener &p_other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Change listener that represents a pair of Object and Method.
|
/// Change listener that rapresents a pair of Object and Method.
|
||||||
/// This can track the changes of many nodes and variables. It's dispatched
|
/// 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
|
/// if one or more tracked variable change during the tracked phase, specified
|
||||||
/// by the flag.
|
/// by the flag.
|
||||||
@ -291,7 +344,7 @@ struct NodeData {
|
|||||||
ObjectID instance_id = ObjectID();
|
ObjectID instance_id = ObjectID();
|
||||||
NodeData *controlled_by = nullptr;
|
NodeData *controlled_by = nullptr;
|
||||||
|
|
||||||
/// When `false`, this node is not sync. It's useful to locally pause sync
|
/// When `false`, this node is not sync. It's usefult to locally pause sync
|
||||||
/// of specific nodes.
|
/// of specific nodes.
|
||||||
bool sync_enabled = true;
|
bool sync_enabled = true;
|
||||||
|
|
||||||
@ -305,12 +358,14 @@ struct NodeData {
|
|||||||
LocalVector<VarData> vars;
|
LocalVector<VarData> vars;
|
||||||
LocalVector<StringName> functions;
|
LocalVector<StringName> functions;
|
||||||
|
|
||||||
|
LocalVector<NetActionInfo> net_actions;
|
||||||
|
|
||||||
// This is valid to use only inside the process function.
|
// This is valid to use only inside the process function.
|
||||||
Node *node = nullptr;
|
Node *node = nullptr;
|
||||||
|
|
||||||
NodeData() = default;
|
NodeData() = default;
|
||||||
|
|
||||||
void process(const real_t p_delta) const;
|
void process(const double p_delta) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PeerData {
|
struct PeerData {
|
||||||
@ -330,6 +385,8 @@ struct Snapshot {
|
|||||||
/// The variable array order also matter.
|
/// The variable array order also matter.
|
||||||
Vector<Vector<Var>> node_vars;
|
Vector<Vector<Var>> node_vars;
|
||||||
|
|
||||||
|
Vector<TokenizedNetActionProcessor> actions;
|
||||||
|
|
||||||
operator String() const;
|
operator String() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -340,4 +397,6 @@ struct PostponedRecover {
|
|||||||
|
|
||||||
} // namespace NetUtility
|
} // namespace NetUtility
|
||||||
|
|
||||||
|
#undef ObjectID
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,12 @@
|
|||||||
#ifndef NETWORKED_CONTROLLER_H
|
|
||||||
#define NETWORKED_CONTROLLER_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* networked_controller.h */
|
/* networked_controller.h */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -39,10 +35,14 @@
|
|||||||
#include "scene/main/node.h"
|
#include "scene/main/node.h"
|
||||||
|
|
||||||
#include "data_buffer.h"
|
#include "data_buffer.h"
|
||||||
#include "interpolator.h"
|
|
||||||
#include "net_utilities.h"
|
#include "net_utilities.h"
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
|
#ifndef NETWORKED_CONTROLLER_H
|
||||||
|
#define NETWORKED_CONTROLLER_H
|
||||||
|
|
||||||
|
#include "godot_backward_utility_header.h"
|
||||||
|
|
||||||
class SceneSynchronizer;
|
class SceneSynchronizer;
|
||||||
struct Controller;
|
struct Controller;
|
||||||
struct ServerController;
|
struct ServerController;
|
||||||
@ -56,7 +56,7 @@ struct NoNetController;
|
|||||||
///
|
///
|
||||||
/// The `NetworkedController` will sync inputs, based on those will perform
|
/// The `NetworkedController` will sync inputs, based on those will perform
|
||||||
/// operations.
|
/// operations.
|
||||||
/// The result of these operations, are guaranteed to be the same across the
|
/// 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.
|
/// 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
|
/// Is possible to use the `SceneSynchronizer` to keep the state in sync with the
|
||||||
@ -78,11 +78,24 @@ public:
|
|||||||
CONTROLLER_TYPE_NULL,
|
CONTROLLER_TYPE_NULL,
|
||||||
CONTROLLER_TYPE_NONETWORK,
|
CONTROLLER_TYPE_NONETWORK,
|
||||||
CONTROLLER_TYPE_PLAYER,
|
CONTROLLER_TYPE_PLAYER,
|
||||||
|
CONTROLLER_TYPE_AUTONOMOUS_SERVER,
|
||||||
CONTROLLER_TYPE_SERVER,
|
CONTROLLER_TYPE_SERVER,
|
||||||
CONTROLLER_TYPE_DOLL
|
CONTROLLER_TYPE_DOLL
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// When `true`, this controller is controlled by the server: All the clients
|
||||||
|
/// see it as a `Doll`.
|
||||||
|
/// This property is really useful to implement bots (Character controlled by
|
||||||
|
/// the AI).
|
||||||
|
///
|
||||||
|
/// NOTICE: Generally you specify this property on the editor, in addition
|
||||||
|
/// it's possible to change this at runtime: this will cause the server to
|
||||||
|
/// notify all the clients; so the switch is not immediate. This feature can be
|
||||||
|
/// used to switch the Character possession between the AI (Server) and
|
||||||
|
/// PlayerController (Client) without the need to re-instantiate the Character.
|
||||||
|
bool server_controlled = false;
|
||||||
|
|
||||||
/// The input storage size is used to cap the amount of inputs collected by
|
/// The input storage size is used to cap the amount of inputs collected by
|
||||||
/// the `PlayerController`.
|
/// the `PlayerController`.
|
||||||
///
|
///
|
||||||
@ -104,11 +117,11 @@ private:
|
|||||||
/// Amount of time an inputs is re-sent to each peer.
|
/// Amount of time an inputs is re-sent to each peer.
|
||||||
/// Resenging inputs is necessary because the packets may be lost since as
|
/// Resenging inputs is necessary because the packets may be lost since as
|
||||||
/// they are sent in an unreliable way.
|
/// they are sent in an unreliable way.
|
||||||
int max_redundant_inputs = 5;
|
int max_redundant_inputs = 6;
|
||||||
|
|
||||||
/// Time in seconds between each `tick_speedup` that the server sends to the
|
/// Time in seconds between each `tick_speedup` that the server sends to the
|
||||||
/// client.
|
/// client. In ms.
|
||||||
real_t tick_speedup_notification_delay = 0.33;
|
int tick_speedup_notification_delay = 600;
|
||||||
|
|
||||||
/// The connection quality is established by watching the time passed
|
/// The connection quality is established by watching the time passed
|
||||||
/// between each input is received.
|
/// between each input is received.
|
||||||
@ -120,22 +133,6 @@ private:
|
|||||||
/// - Small values make the mechanism too sensible.
|
/// - Small values make the mechanism too sensible.
|
||||||
int network_traced_frames = 120;
|
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
|
/// The `ServerController` will try to keep a margin of error, so that
|
||||||
/// network oscillations doesn't leave the `ServerController` without
|
/// network oscillations doesn't leave the `ServerController` without
|
||||||
/// inputs.
|
/// inputs.
|
||||||
@ -143,26 +140,14 @@ private:
|
|||||||
/// This margin of error is called `optimal_frame_delay` and it changes
|
/// This margin of error is called `optimal_frame_delay` and it changes
|
||||||
/// depending on the connection health:
|
/// depending on the connection health:
|
||||||
/// it can go from `min_frames_delay` to `max_frames_delay`.
|
/// it can go from `min_frames_delay` to `max_frames_delay`.
|
||||||
int min_frames_delay = 2;
|
int min_frames_delay = 0;
|
||||||
int max_frames_delay = 6;
|
int max_frames_delay = 7;
|
||||||
|
|
||||||
/// Rate at which the tick speed changes, so the `optimal_frame_delay` is
|
/// Amount of additional frames produced per second.
|
||||||
/// matched.
|
double tick_acceleration = 5.0;
|
||||||
real_t tick_acceleration = 2.0;
|
|
||||||
|
|
||||||
/// Collect rate (in frames) used by the server to establish when to collect
|
/// The doll epoch send rate: in Hz (frames per seconds).
|
||||||
/// the state for a particular peer.
|
uint32_t doll_sync_rate = 30;
|
||||||
/// 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 immediately 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
|
/// The doll interpolator will try to keep a margin of error, so that network
|
||||||
/// oscillations doesn't make the dolls freeze.
|
/// oscillations doesn't make the dolls freeze.
|
||||||
@ -170,11 +155,22 @@ private:
|
|||||||
/// This margin of error is called `optimal_frame_delay` and it changes
|
/// This margin of error is called `optimal_frame_delay` and it changes
|
||||||
/// depending on the connection health:
|
/// depending on the connection health:
|
||||||
/// it can go from `doll_min_frames_delay` to `doll_max_frames_delay`.
|
/// it can go from `doll_min_frames_delay` to `doll_max_frames_delay`.
|
||||||
int doll_min_frames_delay = 2;
|
int doll_min_frames_delay = 0;
|
||||||
int doll_max_frames_delay = 5;
|
int doll_max_frames_delay = 25;
|
||||||
|
|
||||||
/// Max speedup / slowdown the doll can apply to recover its epoch buffer size.
|
/// Sensitivity to network oscillations. The value is in seconds and can be
|
||||||
real_t doll_interpolation_max_speedup = 0.2;
|
/// used to establish the connection quality.
|
||||||
|
///
|
||||||
|
/// The net sync checks the amount of time for each packet to arrive.
|
||||||
|
/// The different it is, the more unreliable the connection is, so virtual latency
|
||||||
|
/// is used to smooth the interpolation.
|
||||||
|
///
|
||||||
|
/// `doll_net_sensitivity` is an amount in seconds used to determine the maximum delta difference
|
||||||
|
/// between the packets.
|
||||||
|
real_t doll_net_sensitivity = 0.21;
|
||||||
|
|
||||||
|
/// Max doll interpolation overshot. Unit: normalized percentage.
|
||||||
|
real_t doll_interpolation_max_overshot = 0.2;
|
||||||
|
|
||||||
/// The connection quality is established by watching the time passed
|
/// The connection quality is established by watching the time passed
|
||||||
/// between each batch arrival.
|
/// between each batch arrival.
|
||||||
@ -185,31 +181,8 @@ private:
|
|||||||
/// - Big values make the mechanism too slow.
|
/// - Big values make the mechanism too slow.
|
||||||
/// - Small values make the mechanism too sensible.
|
/// - Small values make the mechanism too sensible.
|
||||||
/// The correct value should be give considering the
|
/// The correct value should be give considering the
|
||||||
/// `doll_epoch_batch_sync_rate`.
|
/// `doll_sync_rate`.
|
||||||
int doll_connection_stats_frame_span = 30;
|
int doll_connection_stats_frame_span = 60;
|
||||||
|
|
||||||
/// 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 up-to-date
|
|
||||||
/// 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;
|
ControllerType controller_type = CONTROLLER_TYPE_NULL;
|
||||||
Controller *controller = nullptr;
|
Controller *controller = nullptr;
|
||||||
@ -225,6 +198,10 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
NetworkedController();
|
NetworkedController();
|
||||||
|
~NetworkedController();
|
||||||
|
|
||||||
|
void set_server_controlled(bool p_server_controlled);
|
||||||
|
bool get_server_controlled() const;
|
||||||
|
|
||||||
void set_player_input_storage_size(int p_size);
|
void set_player_input_storage_size(int p_size);
|
||||||
int get_player_input_storage_size() const;
|
int get_player_input_storage_size() const;
|
||||||
@ -232,8 +209,8 @@ public:
|
|||||||
void set_max_redundant_inputs(int p_max);
|
void set_max_redundant_inputs(int p_max);
|
||||||
int get_max_redundant_inputs() const;
|
int get_max_redundant_inputs() const;
|
||||||
|
|
||||||
void set_tick_speedup_notification_delay(real_t p_delay);
|
void set_tick_speedup_notification_delay(int p_delay_in_ms);
|
||||||
real_t get_tick_speedup_notification_delay() const;
|
int get_tick_speedup_notification_delay() const;
|
||||||
|
|
||||||
void set_network_traced_frames(int p_size);
|
void set_network_traced_frames(int p_size);
|
||||||
int get_network_traced_frames() const;
|
int get_network_traced_frames() const;
|
||||||
@ -244,17 +221,14 @@ public:
|
|||||||
void set_max_frames_delay(int p_val);
|
void set_max_frames_delay(int p_val);
|
||||||
int get_max_frames_delay() const;
|
int get_max_frames_delay() const;
|
||||||
|
|
||||||
void set_net_sensitivity(real_t p_val);
|
void set_tick_acceleration(double p_acceleration);
|
||||||
real_t get_net_sensitivity() const;
|
double get_tick_acceleration() const;
|
||||||
|
|
||||||
void set_tick_acceleration(real_t p_acceleration);
|
|
||||||
real_t get_tick_acceleration() const;
|
|
||||||
|
|
||||||
void set_doll_epoch_collect_rate(int p_rate);
|
void set_doll_epoch_collect_rate(int p_rate);
|
||||||
int get_doll_epoch_collect_rate() const;
|
int get_doll_epoch_collect_rate() const;
|
||||||
|
|
||||||
void set_doll_epoch_batch_sync_rate(real_t p_rate);
|
void set_doll_sync_rate(uint32_t p_rate);
|
||||||
real_t get_doll_epoch_batch_sync_rate() const;
|
uint32_t get_doll_sync_rate() const;
|
||||||
|
|
||||||
void set_doll_min_frames_delay(int p_min);
|
void set_doll_min_frames_delay(int p_min);
|
||||||
int get_doll_min_frames_delay() const;
|
int get_doll_min_frames_delay() const;
|
||||||
@ -262,17 +236,17 @@ public:
|
|||||||
void set_doll_max_frames_delay(int p_max);
|
void set_doll_max_frames_delay(int p_max);
|
||||||
int get_doll_max_frames_delay() const;
|
int get_doll_max_frames_delay() const;
|
||||||
|
|
||||||
void set_doll_interpolation_max_speedup(real_t p_speedup);
|
void set_doll_net_sensitivity(real_t p_sensitivity);
|
||||||
real_t get_doll_interpolation_max_speedup() const;
|
real_t get_doll_net_sensitivity() const;
|
||||||
|
|
||||||
|
void set_doll_interpolation_max_overshot(real_t p_speedup);
|
||||||
|
real_t get_doll_interpolation_max_overshot() const;
|
||||||
|
|
||||||
void set_doll_connection_stats_frame_span(int p_span);
|
void set_doll_connection_stats_frame_span(int p_span);
|
||||||
int get_doll_connection_stats_frame_span() const;
|
int get_doll_connection_stats_frame_span() const;
|
||||||
|
|
||||||
void set_doll_net_sensitivity(real_t p_sensitivity);
|
void set_doll_virtual_delay_max_bias(uint32_t p_max_delay);
|
||||||
real_t get_doll_net_sensitivity() const;
|
uint32_t get_doll_virtual_delay_max_bias() 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;
|
uint32_t get_current_input_id() const;
|
||||||
|
|
||||||
@ -285,7 +259,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the pretended delta used by the player.
|
/// Returns the pretended delta used by the player.
|
||||||
real_t player_get_pretended_delta(uint32_t p_physics_ticks_per_seconds) const;
|
real_t player_get_pretended_delta() const;
|
||||||
|
|
||||||
void mark_epoch_as_important();
|
void mark_epoch_as_important();
|
||||||
|
|
||||||
@ -293,6 +267,14 @@ public:
|
|||||||
void set_doll_peer_active(int p_peer_id, bool p_active);
|
void set_doll_peer_active(int p_peer_id, bool p_active);
|
||||||
void pause_notify_dolls();
|
void pause_notify_dolls();
|
||||||
|
|
||||||
|
virtual void validate_script_implementation();
|
||||||
|
virtual void native_collect_inputs(double p_delta, DataBuffer &r_buffer);
|
||||||
|
virtual void native_controller_process(double p_delta, DataBuffer &p_buffer);
|
||||||
|
virtual bool native_are_inputs_different(DataBuffer &p_buffer_A, DataBuffer &p_buffer_B);
|
||||||
|
virtual uint32_t native_count_input_size(DataBuffer &p_buffer);
|
||||||
|
virtual void native_collect_epoch_data(DataBuffer &r_buffer);
|
||||||
|
virtual void native_apply_epoch(double p_delta, real_t p_interpolation_alpha, DataBuffer &p_past_buffer, DataBuffer &p_future_buffer);
|
||||||
|
|
||||||
bool process_instant(int p_i, real_t p_delta);
|
bool process_instant(int p_i, real_t p_delta);
|
||||||
|
|
||||||
/// Returns the server controller or nullptr if this is not a server.
|
/// Returns the server controller or nullptr if this is not a server.
|
||||||
@ -308,6 +290,7 @@ public:
|
|||||||
NoNetController *get_nonet_controller();
|
NoNetController *get_nonet_controller();
|
||||||
const NoNetController *get_nonet_controller() const;
|
const NoNetController *get_nonet_controller() const;
|
||||||
|
|
||||||
|
bool is_networking_initialized() const;
|
||||||
bool is_server_controller() const;
|
bool is_server_controller() const;
|
||||||
bool is_player_controller() const;
|
bool is_player_controller() const;
|
||||||
bool is_doll_controller() const;
|
bool is_doll_controller() const;
|
||||||
@ -321,24 +304,26 @@ public:
|
|||||||
bool has_scene_synchronizer() const;
|
bool has_scene_synchronizer() const;
|
||||||
|
|
||||||
/* On server rpc functions. */
|
/* On server rpc functions. */
|
||||||
void _rpc_server_send_inputs(const PoolVector<uint8_t> &p_data);
|
void _rpc_server_send_inputs(const Vector<uint8_t> &p_data);
|
||||||
|
|
||||||
/* On client rpc functions. */
|
/* On client rpc functions. */
|
||||||
void _rpc_send_tick_additional_speed(const PoolVector<uint8_t> &p_data);
|
void _rpc_set_server_controlled(bool p_server_controlled);
|
||||||
|
void _rpc_notify_fps_acceleration(const Vector<uint8_t> &p_data);
|
||||||
|
|
||||||
/* On puppet rpc functions. */
|
/* On puppet rpc functions. */
|
||||||
void _rpc_doll_notify_sync_pause(uint32_t p_epoch);
|
void _rpc_doll_notify_sync_pause(uint32_t p_epoch);
|
||||||
void _rpc_doll_send_epoch_batch(const PoolVector<uint8_t> &p_data);
|
void _rpc_doll_send_epoch_batch(const Vector<uint8_t> &p_data);
|
||||||
|
|
||||||
void process(real_t p_delta);
|
void process(double p_delta);
|
||||||
|
|
||||||
void player_set_has_new_input(bool p_has);
|
void player_set_has_new_input(bool p_has);
|
||||||
bool player_has_new_input() const;
|
bool player_has_new_input() const;
|
||||||
|
|
||||||
void __on_sync_paused();
|
void __on_sync_paused();
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
virtual void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
|
void notify_controller_reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FrameSnapshot {
|
struct FrameSnapshot {
|
||||||
@ -346,6 +331,8 @@ struct FrameSnapshot {
|
|||||||
BitArray inputs_buffer;
|
BitArray inputs_buffer;
|
||||||
uint32_t buffer_size_bit;
|
uint32_t buffer_size_bit;
|
||||||
uint32_t similarity;
|
uint32_t similarity;
|
||||||
|
/// Local timestamp.
|
||||||
|
uint32_t received_timestamp;
|
||||||
|
|
||||||
bool operator==(const FrameSnapshot &p_other) const {
|
bool operator==(const FrameSnapshot &p_other) const {
|
||||||
return p_other.id == id;
|
return p_other.id == id;
|
||||||
@ -376,57 +363,56 @@ struct ServerController : public Controller {
|
|||||||
|
|
||||||
int peer = 0;
|
int peer = 0;
|
||||||
bool active = true;
|
bool active = true;
|
||||||
real_t update_rate_factor = 1.0;
|
real_t doll_sync_rate_factor = 1.0;
|
||||||
int collect_timer = 0; // In frames
|
real_t doll_sync_timer = 0.0;
|
||||||
int collect_threshold = 0; // In frames
|
real_t doll_sync_time_threshold = 0.0;
|
||||||
LocalVector<PoolVector<uint8_t>> epoch_batch;
|
|
||||||
uint32_t batch_size = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t current_input_buffer_id = UINT32_MAX;
|
uint32_t current_input_buffer_id = UINT32_MAX;
|
||||||
uint32_t ghost_input_count = 0;
|
uint32_t ghost_input_count = 0;
|
||||||
uint32_t last_sent_state_input_id = 0;
|
uint32_t last_sent_state_input_id = 0;
|
||||||
real_t client_tick_additional_speed = 0.0;
|
uint32_t additional_fps_notif_timer = 0;
|
||||||
real_t additional_speed_notif_timer = 0.0;
|
|
||||||
std::deque<FrameSnapshot> snapshots;
|
std::deque<FrameSnapshot> snapshots;
|
||||||
bool streaming_paused = false;
|
bool streaming_paused = false;
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
|
|
||||||
uint32_t input_arrival_time = UINT32_MAX;
|
uint32_t previous_frame_received_timestamp = UINT32_MAX;
|
||||||
NetUtility::StatisticalRingBuffer<uint32_t> network_watcher;
|
NetUtility::StatisticalRingBuffer<uint32_t> network_watcher;
|
||||||
|
NetUtility::StatisticalRingBuffer<int> consecutive_input_watcher;
|
||||||
|
|
||||||
/// Used to sync the dolls.
|
/// Used to sync the dolls.
|
||||||
LocalVector<Peer> peers;
|
LocalVector<Peer> peers;
|
||||||
DataBuffer epoch_state_data_cache;
|
DataBuffer epoch_state_data_cache;
|
||||||
uint32_t epoch = 0;
|
uint32_t epoch = 0;
|
||||||
bool is_epoch_important = false;
|
bool is_epoch_important = false;
|
||||||
real_t batch_sync_timer = 0.0;
|
|
||||||
|
|
||||||
ServerController(
|
ServerController(
|
||||||
NetworkedController *p_node,
|
NetworkedController *p_node,
|
||||||
int p_traced_frames);
|
int p_traced_frames);
|
||||||
|
|
||||||
void process(real_t p_delta);
|
void process(double p_delta);
|
||||||
uint32_t last_known_input() const;
|
uint32_t last_known_input() const;
|
||||||
virtual uint32_t get_current_input_id() const;
|
virtual uint32_t get_current_input_id() const override;
|
||||||
|
|
||||||
void set_enabled(bool p_enable);
|
void set_enabled(bool p_enable);
|
||||||
|
|
||||||
virtual void clear_peers();
|
virtual void clear_peers() override;
|
||||||
virtual void activate_peer(int p_peer);
|
virtual void activate_peer(int p_peer) override;
|
||||||
virtual void deactivate_peer(int p_peer);
|
virtual void deactivate_peer(int p_peer) override;
|
||||||
|
|
||||||
void receive_inputs(const PoolVector<uint8_t> &p_data);
|
virtual void receive_inputs(const Vector<uint8_t> &p_data);
|
||||||
int get_inputs_count() const;
|
virtual int get_inputs_count() const;
|
||||||
|
|
||||||
/// Fetch the next inputs, returns true if the input is new.
|
/// Fetch the next inputs, returns true if the input is new.
|
||||||
bool fetch_next_input();
|
virtual bool fetch_next_input(real_t p_delta);
|
||||||
|
|
||||||
|
void set_frame_input(const FrameSnapshot &p_frame_snapshot);
|
||||||
|
|
||||||
void notify_send_state();
|
void notify_send_state();
|
||||||
|
|
||||||
void doll_sync(real_t p_delta);
|
void doll_sync(real_t p_delta);
|
||||||
|
|
||||||
/// This function updates the `tick_additional_speed` so that the `frames_inputs`
|
/// This function updates the `tick_additional_fps` so that the `frames_inputs`
|
||||||
/// size is enough to reduce the missing packets to 0.
|
/// size is enough to reduce the missing packets to 0.
|
||||||
///
|
///
|
||||||
/// When the internet connection is bad, the packets need more time to arrive.
|
/// When the internet connection is bad, the packets need more time to arrive.
|
||||||
@ -437,33 +423,44 @@ struct ServerController : public Controller {
|
|||||||
/// the server is artificial and no more dependent on the internet. For this
|
/// 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`
|
/// reason the server tells the client to slowdown so to keep the `frames_inputs`
|
||||||
/// size moderate to the needs.
|
/// size moderate to the needs.
|
||||||
void calculates_player_tick_rate(real_t p_delta);
|
virtual void adjust_player_tick_rate(double p_delta);
|
||||||
void adjust_player_tick_rate(real_t p_delta);
|
|
||||||
|
|
||||||
uint32_t find_peer(int p_peer) const;
|
uint32_t find_peer(int p_peer) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AutonomousServerController : public ServerController {
|
||||||
|
AutonomousServerController(
|
||||||
|
NetworkedController *p_node);
|
||||||
|
|
||||||
|
virtual void receive_inputs(const Vector<uint8_t> &p_data) override;
|
||||||
|
virtual int get_inputs_count() const override;
|
||||||
|
virtual bool fetch_next_input(real_t p_delta) override;
|
||||||
|
virtual void adjust_player_tick_rate(double p_delta) override;
|
||||||
|
};
|
||||||
|
|
||||||
struct PlayerController : public Controller {
|
struct PlayerController : public Controller {
|
||||||
uint32_t current_input_id;
|
uint32_t current_input_id;
|
||||||
uint32_t input_buffers_counter;
|
uint32_t input_buffers_counter;
|
||||||
real_t time_bank;
|
double time_bank;
|
||||||
real_t tick_additional_speed;
|
double acceleration_fps_speed = 0.0;
|
||||||
|
double acceleration_fps_timer = 1.0;
|
||||||
bool streaming_paused = false;
|
bool streaming_paused = false;
|
||||||
|
double pretended_delta = 1.0;
|
||||||
|
|
||||||
std::deque<FrameSnapshot> frames_snapshot;
|
std::deque<FrameSnapshot> frames_snapshot;
|
||||||
LocalVector<uint8_t> cached_packet_data;
|
LocalVector<uint8_t> cached_packet_data;
|
||||||
|
|
||||||
PlayerController(NetworkedController *p_node);
|
PlayerController(NetworkedController *p_node);
|
||||||
|
|
||||||
void process(real_t p_delta);
|
void process(double p_delta);
|
||||||
int calculates_sub_ticks(real_t p_delta, real_t p_iteration_per_seconds);
|
/// Returns the amount of frames to process for this frame.
|
||||||
|
int calculates_sub_ticks(const double p_delta, const double p_iteration_per_seconds);
|
||||||
int notify_input_checked(uint32_t p_input_id);
|
int notify_input_checked(uint32_t p_input_id);
|
||||||
uint32_t last_known_input() const;
|
uint32_t last_known_input() const;
|
||||||
uint32_t get_stored_input_id(int p_i) const;
|
uint32_t get_stored_input_id(int p_i) const;
|
||||||
virtual uint32_t get_current_input_id() const;
|
virtual uint32_t get_current_input_id() const override;
|
||||||
|
|
||||||
bool process_instant(int p_i, real_t p_delta);
|
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);
|
void store_input_buffer(uint32_t p_id);
|
||||||
|
|
||||||
@ -485,30 +482,35 @@ struct PlayerController : public Controller {
|
|||||||
/// with the server execution (see `soft_reset_to_server_state`) and the possibility
|
/// with the server execution (see `soft_reset_to_server_state`) and the possibility
|
||||||
/// for the server to stop the data streaming.
|
/// for the server to stop the data streaming.
|
||||||
struct DollController : public Controller {
|
struct DollController : public Controller {
|
||||||
Interpolator interpolator;
|
real_t interpolation_alpha = 0.0;
|
||||||
real_t additional_speed = 0.0;
|
real_t interpolation_time_window = 0.0;
|
||||||
uint32_t current_epoch = UINT32_MAX;
|
|
||||||
real_t advancing_epoch = 0.0;
|
uint32_t current_epoch = 0;
|
||||||
uint32_t missing_epochs = 0;
|
|
||||||
|
uint32_t past_epoch = 0;
|
||||||
|
DataBuffer past_epoch_buffer;
|
||||||
|
|
||||||
|
uint32_t future_epoch = 0;
|
||||||
|
DataBuffer future_epoch_buffer;
|
||||||
|
|
||||||
// Any received epoch prior to this one is discarded.
|
// Any received epoch prior to this one is discarded.
|
||||||
uint32_t paused_epoch = 0;
|
uint32_t paused_epoch = 0;
|
||||||
|
|
||||||
// Used to track the time taken for the next batch to arrive.
|
// Used to track the time taken for the next batch to arrive.
|
||||||
uint32_t batch_receiver_timer = UINT32_MAX;
|
uint32_t epoch_received_timestamp = UINT32_MAX;
|
||||||
|
real_t next_epoch_expected_in = 0.0;
|
||||||
/// Used to track how network is performing.
|
/// Used to track how network is performing.
|
||||||
NetUtility::StatisticalRingBuffer<uint32_t> network_watcher;
|
NetUtility::StatisticalRingBuffer<real_t> network_watcher;
|
||||||
|
|
||||||
DollController(NetworkedController *p_node);
|
DollController(NetworkedController *p_node);
|
||||||
|
|
||||||
virtual void ready();
|
virtual void ready() override;
|
||||||
void process(real_t p_delta);
|
void process(double p_delta);
|
||||||
// TODO consider make this non virtual
|
// TODO consider make this non virtual
|
||||||
virtual uint32_t get_current_input_id() const;
|
virtual uint32_t get_current_input_id() const override;
|
||||||
|
|
||||||
void receive_batch(const PoolVector<uint8_t> &p_data);
|
void receive_epoch(const Vector<uint8_t> &p_data);
|
||||||
uint32_t receive_epoch(const PoolVector<uint8_t> &p_data);
|
|
||||||
|
|
||||||
uint32_t next_epoch();
|
|
||||||
void pause(uint32_t p_epoch);
|
void pause(uint32_t p_epoch);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -520,8 +522,8 @@ struct NoNetController : public Controller {
|
|||||||
|
|
||||||
NoNetController(NetworkedController *p_node);
|
NoNetController(NetworkedController *p_node);
|
||||||
|
|
||||||
void process(real_t p_delta);
|
void process(double p_delta);
|
||||||
virtual uint32_t get_current_input_id() const;
|
virtual uint32_t get_current_input_id() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
/* register_types.cpp */
|
/* register_types.cpp */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -35,25 +34,33 @@
|
|||||||
|
|
||||||
#include "register_types.h"
|
#include "register_types.h"
|
||||||
|
|
||||||
#include "core/config/project_settings.h"
|
#include "core/engine.h"
|
||||||
#include "data_buffer.h"
|
#include "data_buffer.h"
|
||||||
#include "interpolator.h"
|
|
||||||
#include "networked_controller.h"
|
#include "networked_controller.h"
|
||||||
#include "scene_diff.h"
|
#include "scene_diff.h"
|
||||||
#include "scene_synchronizer.h"
|
#include "scene_synchronizer.h"
|
||||||
|
#include "scene_synchronizer_debugger.h"
|
||||||
|
#include "input_network_encoder.h"
|
||||||
|
|
||||||
void register_network_synchronizer_types(ModuleRegistrationLevel p_level) {
|
void register_network_synchronizer_types() {
|
||||||
if (p_level == MODULE_REGISTRATION_LEVEL_SCENE) {
|
|
||||||
ClassDB::register_class<DataBuffer>();
|
ClassDB::register_class<DataBuffer>();
|
||||||
ClassDB::register_class<SceneDiff>();
|
ClassDB::register_class<SceneDiff>();
|
||||||
ClassDB::register_class<Interpolator>();
|
|
||||||
ClassDB::register_class<NetworkedController>();
|
ClassDB::register_class<NetworkedController>();
|
||||||
ClassDB::register_class<SceneSynchronizer>();
|
ClassDB::register_class<SceneSynchronizer>();
|
||||||
|
ClassDB::register_class<SceneSynchronizerDebugger>();
|
||||||
|
ClassDB::register_class<InputNetworkEncoder>();
|
||||||
|
|
||||||
|
memnew(SceneSynchronizerDebugger);
|
||||||
|
Engine::get_singleton()->add_singleton(Engine::Singleton("SceneSynchronizerDebugger", SceneSynchronizerDebugger::singleton()));
|
||||||
|
|
||||||
GLOBAL_DEF("NetworkSynchronizer/debug_server_speedup", false);
|
GLOBAL_DEF("NetworkSynchronizer/debug_server_speedup", false);
|
||||||
GLOBAL_DEF("NetworkSynchronizer/debug_doll_speedup", false);
|
GLOBAL_DEF("NetworkSynchronizer/debug_doll_speedup", false);
|
||||||
}
|
GLOBAL_DEF("NetworkSynchronizer/log_debug_warnings_and_messages", true);
|
||||||
|
GLOBAL_DEF("NetworkSynchronizer/debugger/dump_enabled", false);
|
||||||
|
GLOBAL_DEF("NetworkSynchronizer/debugger/dump_classes", Array());
|
||||||
|
GLOBAL_DEF("NetworkSynchronizer/debugger/log_debug_fps_warnings", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unregister_network_synchronizer_types(ModuleRegistrationLevel p_level) {
|
void unregister_network_synchronizer_types() {
|
||||||
|
memdelete(SceneSynchronizerDebugger::singleton());
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
#ifndef NETWORK_SYNCHRONIZER_REGISTER_TYPES_H
|
|
||||||
#define NETWORK_SYNCHRONIZER_REGISTER_TYPES_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* register_types.h */
|
/* register_types.h */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -36,9 +32,5 @@
|
|||||||
@author AndreaCatania
|
@author AndreaCatania
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "modules/register_module_types.h"
|
void register_network_synchronizer_types();
|
||||||
|
void unregister_network_synchronizer_types();
|
||||||
void register_network_synchronizer_types(ModuleRegistrationLevel p_level);
|
|
||||||
void unregister_network_synchronizer_types(ModuleRegistrationLevel p_level);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
/* scene_diff.cpp */
|
/* scene_diff.cpp */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
#ifndef SCENE_DIFF_H
|
|
||||||
#define SCENE_DIFF_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* scene_diff.h */
|
/* scene_diff.h */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -36,8 +32,10 @@
|
|||||||
@author AndreaCatania
|
@author AndreaCatania
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "core/containers/local_vector.h"
|
#ifndef SCENE_DIFF_H
|
||||||
#include "core/object/class_db.h"
|
#define SCENE_DIFF_H
|
||||||
|
|
||||||
|
#include "core/class_db.h"
|
||||||
#include "net_utilities.h"
|
#include "net_utilities.h"
|
||||||
|
|
||||||
class SceneSynchronizer;
|
class SceneSynchronizer;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,12 @@
|
|||||||
#ifndef SCENE_SYNCHRONIZER_H
|
|
||||||
#define SCENE_SYNCHRONIZER_H
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* scene_synchronizer.h */
|
/* scene_synchronizer.h */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* PANDEMONIUM ENGINE */
|
/* GODOT ENGINE */
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
/* https://godotengine.org */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
/* */
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
/* a copy of this software and associated documentation files (the */
|
/* a copy of this software and associated documentation files (the */
|
||||||
@ -38,13 +34,20 @@
|
|||||||
|
|
||||||
#include "scene/main/node.h"
|
#include "scene/main/node.h"
|
||||||
|
|
||||||
#include "core/containers/local_vector.h"
|
#include "core/local_vector.h"
|
||||||
#include "core/containers/oa_hash_map.h"
|
#include "core/oa_hash_map.h"
|
||||||
|
#include "net_action.h"
|
||||||
#include "net_utilities.h"
|
#include "net_utilities.h"
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
|
#ifndef SCENE_SYNCHRONIZER_H
|
||||||
|
#define SCENE_SYNCHRONIZER_H
|
||||||
|
|
||||||
|
#include "godot_backward_utility_header.h"
|
||||||
|
|
||||||
class Synchronizer;
|
class Synchronizer;
|
||||||
class NetworkedController;
|
class NetworkedController;
|
||||||
|
class PlayerController;
|
||||||
|
|
||||||
/// # SceneSynchronizer
|
/// # SceneSynchronizer
|
||||||
///
|
///
|
||||||
@ -61,20 +64,20 @@ class NetworkedController;
|
|||||||
/// The clients receives the server snapshot, so it compares with the local
|
/// The clients receives the server snapshot, so it compares with the local
|
||||||
/// snapshot and if it's necessary perform the recovery.
|
/// snapshot and if it's necessary perform the recovery.
|
||||||
///
|
///
|
||||||
/// ## Variable tracking
|
/// ## Variable traking
|
||||||
///
|
///
|
||||||
/// The `SceneSynchronizer` is able to track any node variable. It's possible to specify
|
/// The `SceneSynchronizer` is able to track any node variable. It's possible to specify
|
||||||
/// the variables to track using the function `register_variable`.
|
/// the variables to track using the function `register_variable`.
|
||||||
///
|
///
|
||||||
/// ## NetworkedController
|
/// ## NetworkedController
|
||||||
/// The `NetworkedController` is able to acquire the `Player` input and perform
|
/// The `NetworkedController` is able to aquire the `Player` input and perform
|
||||||
/// operation in sync with other peers. When a discrepancy is found by the
|
/// operation in sync with other peers. When a discrepancy is found by the
|
||||||
/// `SceneSynchronizer`, it will drive the `NetworkedController` so to recover that
|
/// `SceneSynchronizer`, it will drive the `NetworkedController` so to recover that
|
||||||
/// missalignment.
|
/// missalignment.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// ## Processing function
|
/// ## Processing function
|
||||||
/// Some objects, that are not directly controlled by a `Player`, may need to be
|
/// 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
|
/// in sync between peers; since those are not controlled by a `Player` is
|
||||||
/// not necessary use the `NetworkedController`.
|
/// not necessary use the `NetworkedController`.
|
||||||
///
|
///
|
||||||
@ -127,6 +130,11 @@ public:
|
|||||||
private:
|
private:
|
||||||
real_t server_notify_state_interval = 1.0;
|
real_t server_notify_state_interval = 1.0;
|
||||||
real_t comparison_float_tolerance = 0.001;
|
real_t comparison_float_tolerance = 0.001;
|
||||||
|
/// The amount of time the same act is sent to a client before being considered delivered.
|
||||||
|
/// This is part of the UDP reliability mechanism.
|
||||||
|
int actions_redundancy = 3;
|
||||||
|
/// Send the act again in (seconds):
|
||||||
|
real_t actions_resend_time = 1.0 / 30.0;
|
||||||
|
|
||||||
SynchronizerType synchronizer_type = SYNCHRONIZER_TYPE_NULL;
|
SynchronizerType synchronizer_type = SYNCHRONIZER_TYPE_NULL;
|
||||||
Synchronizer *synchronizer = nullptr;
|
Synchronizer *synchronizer = nullptr;
|
||||||
@ -150,7 +158,7 @@ private:
|
|||||||
// Controller nodes.
|
// Controller nodes.
|
||||||
LocalVector<NetUtility::NodeData *> node_data_controllers;
|
LocalVector<NetUtility::NodeData *> node_data_controllers;
|
||||||
|
|
||||||
// Just used to detect when the peer change. TODO Remove this and use a signal instead.
|
// Just used to detect when the peer change. TODO Remove this and use a singnal instead.
|
||||||
void *peer_ptr = nullptr;
|
void *peer_ptr = nullptr;
|
||||||
|
|
||||||
int event_flag;
|
int event_flag;
|
||||||
@ -159,7 +167,7 @@ private:
|
|||||||
public:
|
public:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
virtual void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SceneSynchronizer();
|
SceneSynchronizer();
|
||||||
@ -174,6 +182,14 @@ public:
|
|||||||
void set_comparison_float_tolerance(real_t p_tolerance);
|
void set_comparison_float_tolerance(real_t p_tolerance);
|
||||||
real_t get_comparison_float_tolerance() const;
|
real_t get_comparison_float_tolerance() const;
|
||||||
|
|
||||||
|
void set_actions_redundancy(int p_redundancy);
|
||||||
|
int get_actions_redundancy() const;
|
||||||
|
|
||||||
|
void set_actions_resend_time(real_t p_time);
|
||||||
|
real_t get_actions_resend_time() const;
|
||||||
|
|
||||||
|
bool is_variable_registered(Node *p_node, const StringName &p_variable) const;
|
||||||
|
|
||||||
/// Register a new node and returns its `NodeData`.
|
/// Register a new node and returns its `NodeData`.
|
||||||
NetUtility::NodeData *register_node(Node *p_node);
|
NetUtility::NodeData *register_node(Node *p_node);
|
||||||
uint32_t register_node_gdscript(Node *p_node);
|
uint32_t register_node_gdscript(Node *p_node);
|
||||||
@ -195,6 +211,36 @@ public:
|
|||||||
void stop_node_sync(const Node *p_node);
|
void stop_node_sync(const Node *p_node);
|
||||||
bool is_node_sync(const Node *p_node) const;
|
bool is_node_sync(const Node *p_node) const;
|
||||||
|
|
||||||
|
/// Register an new action.
|
||||||
|
///
|
||||||
|
/// @param p_node The node that owns the event
|
||||||
|
/// @param p_action_func The function that is triggered when the event is executed.
|
||||||
|
/// @param p_action_encoding_func The function called to definte the validation encoding.
|
||||||
|
/// @param p_can_client_trigger If true this `Action` can be triggered on client.
|
||||||
|
/// @param p_wait_server_validation If true the event will be emitted locally only if the server validates it.
|
||||||
|
/// @param p_server_action_validation_func The validation function, must return a boolean.
|
||||||
|
NetActionId register_action(
|
||||||
|
Node *p_node,
|
||||||
|
const StringName &p_action_func,
|
||||||
|
const StringName &p_action_encoding_func,
|
||||||
|
bool p_can_client_trigger = false,
|
||||||
|
bool p_wait_server_validation = false,
|
||||||
|
const StringName &p_server_action_validation_func = StringName());
|
||||||
|
|
||||||
|
NetActionId find_action_id(Node *p_node, const StringName &p_action_func) const;
|
||||||
|
|
||||||
|
void trigger_action_by_name(
|
||||||
|
Node *p_node,
|
||||||
|
const StringName &p_action_func,
|
||||||
|
const Array &p_arguments = Array(),
|
||||||
|
const Vector<int> &p_recipients = Vector<int>());
|
||||||
|
|
||||||
|
void trigger_action(
|
||||||
|
Node *p_node,
|
||||||
|
NetActionId p_id,
|
||||||
|
const Array &p_arguments = Array(),
|
||||||
|
const Vector<int> &p_recipients = Vector<int>());
|
||||||
|
|
||||||
/// Returns the variable ID relative to the `Node`.
|
/// Returns the variable ID relative to the `Node`.
|
||||||
/// This may return `UINT32_MAX` in various cases:
|
/// This may return `UINT32_MAX` in various cases:
|
||||||
/// - The node is not registered.
|
/// - The node is not registered.
|
||||||
@ -212,7 +258,7 @@ public:
|
|||||||
/// Add a dependency to a controller, so that the rewinding mechanism can
|
/// Add a dependency to a controller, so that the rewinding mechanism can
|
||||||
/// make sure to rewind that node when the controller is rewinded.
|
/// make sure to rewind that node when the controller is rewinded.
|
||||||
/// You can remove and add dependency at any time. This operation
|
/// You can remove and add dependency at any time. This operation
|
||||||
/// don't need to be performed on server.
|
/// don't need to be perfomed on server.
|
||||||
void controller_add_dependency(Node *p_controller, Node *p_node);
|
void controller_add_dependency(Node *p_controller, Node *p_node);
|
||||||
void controller_remove_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;
|
int controller_get_dependency_count(Node *p_controller) const;
|
||||||
@ -250,10 +296,13 @@ public:
|
|||||||
void reset_synchronizer_mode();
|
void reset_synchronizer_mode();
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
void notify_controller_control_mode_changed(NetworkedController *controller);
|
||||||
|
|
||||||
void _rpc_send_state(const Variant &p_snapshot);
|
void _rpc_send_state(const Variant &p_snapshot);
|
||||||
void _rpc_notify_need_full_snapshot();
|
void _rpc_notify_need_full_snapshot();
|
||||||
void _rpc_set_network_enabled(bool p_enabled);
|
void _rpc_set_network_enabled(bool p_enabled);
|
||||||
void _rpc_notify_peer_status(bool p_enabled);
|
void _rpc_notify_peer_status(bool p_enabled);
|
||||||
|
void _rpc_send_actions(const Vector<uint8_t> &p_data);
|
||||||
|
|
||||||
void update_peers();
|
void update_peers();
|
||||||
void clear_peers();
|
void clear_peers();
|
||||||
@ -262,7 +311,7 @@ public:
|
|||||||
void change_event_add(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old);
|
void change_event_add(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old);
|
||||||
void change_events_flush();
|
void change_events_flush();
|
||||||
|
|
||||||
private:
|
public: // -------------------------------------------------------------------------------- INTERNAL
|
||||||
void expand_organized_node_data_vector(uint32_t p_size);
|
void expand_organized_node_data_vector(uint32_t p_size);
|
||||||
|
|
||||||
/// This function is slow, but allow to take the node data even if the
|
/// This function is slow, but allow to take the node data even if the
|
||||||
@ -301,6 +350,8 @@ private:
|
|||||||
/// Set the node data net id.
|
/// Set the node data net id.
|
||||||
void set_node_data_id(NetUtility::NodeData *p_node_data, NetNodeId p_id);
|
void set_node_data_id(NetUtility::NodeData *p_node_data, NetNodeId p_id);
|
||||||
|
|
||||||
|
NetworkedController *fetch_controller_by_peer(int peer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Returns true when the vectors are the same. Uses comparison_float_tolerance member.
|
/// Returns true when the vectors are the same. Uses comparison_float_tolerance member.
|
||||||
bool compare(const Vector2 &p_first, const Vector2 &p_second) const;
|
bool compare(const Vector2 &p_first, const Vector2 &p_second) const;
|
||||||
@ -342,18 +393,33 @@ public:
|
|||||||
virtual void on_variable_added(NetUtility::NodeData *p_node_data, const StringName &p_var_name) {}
|
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_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) {}
|
virtual void on_controller_reset(NetUtility::NodeData *p_node_data) {}
|
||||||
|
virtual void on_action_triggered(
|
||||||
|
NetUtility::NodeData *p_node_data,
|
||||||
|
NetActionId p_id,
|
||||||
|
const Array &p_arguments,
|
||||||
|
const Vector<int> &p_recipients) {}
|
||||||
|
virtual void on_actions_received(
|
||||||
|
int sender_peer,
|
||||||
|
const LocalVector<SenderNetAction> &p_actions) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoNetSynchronizer : public Synchronizer {
|
class NoNetSynchronizer : public Synchronizer {
|
||||||
friend class SceneSynchronizer;
|
friend class SceneSynchronizer;
|
||||||
|
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
|
uint32_t frame_count = 0;
|
||||||
|
LocalVector<NetActionProcessor> pending_actions;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NoNetSynchronizer(SceneSynchronizer *p_node);
|
NoNetSynchronizer(SceneSynchronizer *p_node);
|
||||||
|
|
||||||
virtual void clear();
|
virtual void clear() override;
|
||||||
virtual void process();
|
virtual void process() override;
|
||||||
|
virtual void on_action_triggered(
|
||||||
|
NetUtility::NodeData *p_node_data,
|
||||||
|
NetActionId p_id,
|
||||||
|
const Array &p_arguments,
|
||||||
|
const Vector<int> &p_recipients) override;
|
||||||
|
|
||||||
void set_enabled(bool p_enabled);
|
void set_enabled(bool p_enabled);
|
||||||
bool is_enabled() const;
|
bool is_enabled() const;
|
||||||
@ -366,26 +432,58 @@ class ServerSynchronizer : public Synchronizer {
|
|||||||
|
|
||||||
struct Change {
|
struct Change {
|
||||||
bool not_known_before = false;
|
bool not_known_before = false;
|
||||||
RBSet<StringName> uknown_vars;
|
Set<StringName> uknown_vars;
|
||||||
RBSet<StringName> vars;
|
Set<StringName> vars;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SnapshotGenerationMode {
|
||||||
|
/// The shanpshot will include The NodeId or NodePath and allthe changed variables.
|
||||||
|
SNAPSHOT_GENERATION_MODE_NORMAL,
|
||||||
|
/// The snapshot will include The NodePath only in case it was unknown before.
|
||||||
|
SNAPSHOT_GENERATION_MODE_NODE_PATH_ONLY,
|
||||||
|
/// The snapshot will include The NodePath only.
|
||||||
|
SNAPSHOT_GENERATION_MODE_FORCE_NODE_PATH_ONLY,
|
||||||
|
/// The snapshot will contains everything no matter what.
|
||||||
|
SNAPSHOT_GENERATION_MODE_FORCE_FULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The changes; the order matters because the index is the NetNodeId.
|
/// The changes; the order matters because the index is the NetNodeId.
|
||||||
LocalVector<Change> changes;
|
LocalVector<Change> changes;
|
||||||
|
|
||||||
|
OAHashMap<int, NetActionSenderInfo> senders_info;
|
||||||
|
OAHashMap<int, uint32_t> peers_next_action_trigger_input_id;
|
||||||
|
|
||||||
|
uint32_t server_actions_count = 0;
|
||||||
|
LocalVector<SenderNetAction> server_actions;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServerSynchronizer(SceneSynchronizer *p_node);
|
ServerSynchronizer(SceneSynchronizer *p_node);
|
||||||
|
|
||||||
virtual void clear();
|
virtual void clear() override;
|
||||||
virtual void process();
|
virtual void process() override;
|
||||||
virtual void on_node_added(NetUtility::NodeData *p_node_data);
|
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);
|
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);
|
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;
|
||||||
|
virtual void on_action_triggered(
|
||||||
|
NetUtility::NodeData *p_node_data,
|
||||||
|
NetActionId p_id,
|
||||||
|
const Array &p_arguments,
|
||||||
|
const Vector<int> &p_recipients) override;
|
||||||
|
virtual void on_actions_received(
|
||||||
|
int sender_peer,
|
||||||
|
const LocalVector<SenderNetAction> &p_actions) override;
|
||||||
|
|
||||||
void process_snapshot_notificator(real_t p_delta);
|
void process_snapshot_notificator(real_t p_delta);
|
||||||
Vector<Variant> global_nodes_generate_snapshot(bool p_force_full_snapshot) const;
|
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 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;
|
void generate_snapshot_node_data(const NetUtility::NodeData *p_node_data, SnapshotGenerationMode p_mode, Vector<Variant> &r_result) const;
|
||||||
|
|
||||||
|
void execute_actions();
|
||||||
|
void send_actions_to_clients();
|
||||||
|
|
||||||
|
void clean_pending_actions();
|
||||||
|
void check_missing_actions();
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClientSynchronizer : public Synchronizer {
|
class ClientSynchronizer : public Synchronizer {
|
||||||
@ -417,25 +515,39 @@ class ClientSynchronizer : public Synchronizer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
RBSet<EndSyncEvent> sync_end_events;
|
Set<EndSyncEvent> sync_end_events;
|
||||||
|
|
||||||
|
uint32_t locally_triggered_actions_count = 0;
|
||||||
|
uint32_t actions_input_id = 0;
|
||||||
|
LocalVector<SenderNetAction> pending_actions;
|
||||||
|
NetActionSenderInfo server_sender_info;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ClientSynchronizer(SceneSynchronizer *p_node);
|
ClientSynchronizer(SceneSynchronizer *p_node);
|
||||||
|
|
||||||
virtual void clear();
|
virtual void clear() override;
|
||||||
|
|
||||||
virtual void process();
|
virtual void process() override;
|
||||||
virtual void on_node_added(NetUtility::NodeData *p_node_data);
|
virtual void on_node_added(NetUtility::NodeData *p_node_data) override;
|
||||||
virtual void on_node_removed(NetUtility::NodeData *p_node_data);
|
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);
|
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);
|
virtual void on_controller_reset(NetUtility::NodeData *p_node_data) override;
|
||||||
|
virtual void on_action_triggered(
|
||||||
|
NetUtility::NodeData *p_node_data,
|
||||||
|
NetActionId p_id,
|
||||||
|
const Array &p_arguments,
|
||||||
|
const Vector<int> &p_recipients) override;
|
||||||
|
virtual void on_actions_received(
|
||||||
|
int sender_peer,
|
||||||
|
const LocalVector<SenderNetAction> &p_actions) override;
|
||||||
|
|
||||||
void receive_snapshot(Variant p_snapshot);
|
void receive_snapshot(Variant p_snapshot);
|
||||||
bool parse_sync_data(
|
bool parse_sync_data(
|
||||||
Variant p_snapshot,
|
Variant p_snapshot,
|
||||||
void *p_user_pointer,
|
void *p_user_pointer,
|
||||||
void (*p_node_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data),
|
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_input_id_parse)(void *p_user_pointer, uint32_t p_input_id),
|
||||||
|
void (*p_controller_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data),
|
||||||
void (*p_variable_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_value));
|
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);
|
void set_enabled(bool p_enabled);
|
||||||
@ -449,6 +561,29 @@ private:
|
|||||||
std::deque<NetUtility::Snapshot> &r_snapshot_storage);
|
std::deque<NetUtility::Snapshot> &r_snapshot_storage);
|
||||||
|
|
||||||
void process_controllers_recovery(real_t p_delta);
|
void process_controllers_recovery(real_t p_delta);
|
||||||
|
|
||||||
|
void __pcr__fetch_recovery_info(
|
||||||
|
const uint32_t p_input_id,
|
||||||
|
bool &r_need_recover,
|
||||||
|
bool &r_recover_controller,
|
||||||
|
LocalVector<NetUtility::NodeData *> &r_nodes_to_recover,
|
||||||
|
LocalVector<NetUtility::PostponedRecover> &r_postponed_recover);
|
||||||
|
|
||||||
|
void __pcr__sync_pre_rewind(
|
||||||
|
const LocalVector<NetUtility::NodeData *> &p_nodes_to_recover);
|
||||||
|
|
||||||
|
void __pcr__rewind(
|
||||||
|
real_t p_delta,
|
||||||
|
const uint32_t p_checkable_input_id,
|
||||||
|
NetworkedController *p_controller,
|
||||||
|
PlayerController *p_player_controller,
|
||||||
|
const bool p_recover_controller,
|
||||||
|
const LocalVector<NetUtility::NodeData *> &p_nodes_to_recover);
|
||||||
|
|
||||||
|
void __pcr__sync_no_rewind(
|
||||||
|
const LocalVector<NetUtility::PostponedRecover> &p_postponed_recover);
|
||||||
|
|
||||||
|
void apply_last_received_server_snapshot();
|
||||||
void process_paused_controller_recovery(real_t p_delta);
|
void process_paused_controller_recovery(real_t p_delta);
|
||||||
bool parse_snapshot(Variant p_snapshot);
|
bool parse_snapshot(Variant p_snapshot);
|
||||||
bool compare_vars(
|
bool compare_vars(
|
||||||
@ -458,6 +593,10 @@ private:
|
|||||||
Vector<NetUtility::Var> &r_postponed_recover);
|
Vector<NetUtility::Var> &r_postponed_recover);
|
||||||
|
|
||||||
void notify_server_full_snapshot_is_needed();
|
void notify_server_full_snapshot_is_needed();
|
||||||
|
|
||||||
|
void send_actions_to_server();
|
||||||
|
void clean_pending_actions();
|
||||||
|
void check_missing_actions();
|
||||||
};
|
};
|
||||||
|
|
||||||
VARIANT_ENUM_CAST(NetEventFlag)
|
VARIANT_ENUM_CAST(NetEventFlag)
|
||||||
|
708
modules/network_synchronizer/scene_synchronizer_debugger.cpp
Normal file
708
modules/network_synchronizer/scene_synchronizer_debugger.cpp
Normal file
@ -0,0 +1,708 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* scene_synchronizer_debugger.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. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "scene_synchronizer_debugger.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
|
||||||
|
#include "__generated__debugger_ui.h"
|
||||||
|
#include "core/io/json.h"
|
||||||
|
#include "core/os/dir_access.h"
|
||||||
|
#include "core/os/file_access.h"
|
||||||
|
#include "core/os/os.h"
|
||||||
|
#include "data_buffer.h"
|
||||||
|
#include "net_utilities.h"
|
||||||
|
#include "scene/main/viewport.h"
|
||||||
|
#include "scene_synchronizer.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SceneSynchronizerDebugger *SceneSynchronizerDebugger::the_singleton = nullptr;
|
||||||
|
|
||||||
|
SceneSynchronizerDebugger *SceneSynchronizerDebugger::singleton() {
|
||||||
|
return the_singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("on_node_added"), &SceneSynchronizerDebugger::on_node_added);
|
||||||
|
ClassDB::bind_method(D_METHOD("on_node_removed"), &SceneSynchronizerDebugger::on_node_removed);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("add_node_message", "node", "message"), &SceneSynchronizerDebugger::add_node_message);
|
||||||
|
ClassDB::bind_method(D_METHOD("add_node_message_by_path", "node_path", "message"), &SceneSynchronizerDebugger::add_node_message_by_path);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("debug_print", "node", "message", "silent"), &SceneSynchronizerDebugger::debug_print);
|
||||||
|
ClassDB::bind_method(D_METHOD("debug_warning", "node", "message", "silent"), &SceneSynchronizerDebugger::debug_warning);
|
||||||
|
ClassDB::bind_method(D_METHOD("debug_error", "node", "message", "silent"), &SceneSynchronizerDebugger::debug_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneSynchronizerDebugger::SceneSynchronizerDebugger() :
|
||||||
|
Node() {
|
||||||
|
if (the_singleton == nullptr) {
|
||||||
|
the_singleton = this;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
// Code here
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneSynchronizerDebugger::~SceneSynchronizerDebugger() {
|
||||||
|
if (the_singleton == this) {
|
||||||
|
the_singleton = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
tracked_nodes.reset();
|
||||||
|
classes_property_lists.clear();
|
||||||
|
frame_dump__begin_state.clear();
|
||||||
|
frame_dump__end_state.clear();
|
||||||
|
frame_dump__node_log.clear();
|
||||||
|
frame_dump__data_buffer_writes.clear();
|
||||||
|
frame_dump__data_buffer_reads.clear();
|
||||||
|
frame_dump__are_inputs_different_results.clear();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::set_dump_enabled(bool p_dump_enabled) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
dump_enabled = p_dump_enabled;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneSynchronizerDebugger::get_dump_enabled() const {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
return dump_enabled;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::register_class_for_node_to_dump(Node *p_node) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
register_class_to_dump(p_node->get_class_name());
|
||||||
|
track_node(p_node, false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::register_class_to_dump(const StringName &p_class) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
ERR_FAIL_COND(p_class == StringName());
|
||||||
|
|
||||||
|
if (dump_classes.find(p_class) == -1) {
|
||||||
|
dump_classes.push_back(p_class);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::unregister_class_to_dump(const StringName &p_class) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
const int64_t index = dump_classes.find(p_class);
|
||||||
|
if (index >= 0) {
|
||||||
|
dump_classes.remove_unordered(index);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::setup_debugger(const String &p_dump_name, int p_peer, SceneTree *p_scene_tree) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (setup_done == false) {
|
||||||
|
setup_done = true;
|
||||||
|
|
||||||
|
// Setup `dump_enabled`
|
||||||
|
if (!dump_enabled) {
|
||||||
|
dump_enabled = GLOBAL_GET("NetworkSynchronizer/debugger/dump_enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup `dump_classes`.
|
||||||
|
{
|
||||||
|
Array classes = GLOBAL_GET("NetworkSynchronizer/debugger/dump_classes");
|
||||||
|
for (uint32_t i = 0; i < uint32_t(classes.size()); i += 1) {
|
||||||
|
if (classes[i].get_type() == Variant::STRING) {
|
||||||
|
register_class_to_dump(classes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup directories.
|
||||||
|
main_dump_directory_path = OS::get_singleton()->get_executable_path().get_base_dir() + "/net-sync-debugs/dump";
|
||||||
|
dump_name = p_dump_name;
|
||||||
|
|
||||||
|
prepare_dumping(p_peer, p_scene_tree);
|
||||||
|
setup_debugger_python_ui();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::prepare_dumping(int p_peer, SceneTree *p_scene_tree) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
// Dumping is disabled, nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the dir.
|
||||||
|
{
|
||||||
|
String path = main_dump_directory_path + "/" + dump_name;
|
||||||
|
|
||||||
|
DirAccess *dir = DirAccess::create_for_path(path);
|
||||||
|
|
||||||
|
Error e;
|
||||||
|
e = dir->make_dir_recursive(path);
|
||||||
|
|
||||||
|
if (e != OK) {
|
||||||
|
memdelete(dir);
|
||||||
|
ERR_FAIL_COND(e != OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = dir->change_dir(path);
|
||||||
|
|
||||||
|
if (e != OK) {
|
||||||
|
memdelete(dir);
|
||||||
|
ERR_FAIL_COND(e != OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty the directory making sure we are ready to write.
|
||||||
|
dir->erase_contents_recursive();
|
||||||
|
//if (dir->list_dir_begin() == OK) {
|
||||||
|
// for (String name = dir->get_next(); name != String(); name = dir->get_next()) {
|
||||||
|
// if (name.begins_with("fd-" + itos(p_peer) + "-")) {
|
||||||
|
// dir->remove(name);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// dir->list_dir_end();
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Dispose the pointer.
|
||||||
|
memdelete(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store generic info about this dump.
|
||||||
|
{
|
||||||
|
Error e;
|
||||||
|
FileAccess *file = FileAccess::open(main_dump_directory_path + "/" + "dump-info-" + dump_name + /*"-" + itos(p_peer) +*/ ".json", FileAccess::WRITE, &e);
|
||||||
|
|
||||||
|
ERR_FAIL_COND(e != OK);
|
||||||
|
|
||||||
|
OS::Date date = OS::get_singleton()->get_date();
|
||||||
|
OS::Time time = OS::get_singleton()->get_time();
|
||||||
|
|
||||||
|
Dictionary d;
|
||||||
|
d["dump-name"] = dump_name;
|
||||||
|
d["peer"] = p_peer;
|
||||||
|
d["date"] = itos(date.day) + "/" + itos(date.month) + "/" + itos(date.year);
|
||||||
|
d["time"] = itos(time.hour) + "::" + itos(time.min);
|
||||||
|
|
||||||
|
file->flush();
|
||||||
|
file->store_string(JSON::print(d));
|
||||||
|
file->close();
|
||||||
|
|
||||||
|
memdelete(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scene_tree) {
|
||||||
|
scene_tree->disconnect("node_added", this, "on_node_added");
|
||||||
|
scene_tree->disconnect("node_removed", this, "on_node_removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
tracked_nodes.clear();
|
||||||
|
classes_property_lists.clear();
|
||||||
|
scene_tree = p_scene_tree;
|
||||||
|
|
||||||
|
if (scene_tree) {
|
||||||
|
scene_tree->connect("node_added", this, "on_node_added");
|
||||||
|
scene_tree->connect("node_removed", this, "on_node_removed");
|
||||||
|
|
||||||
|
// Start by tracking the existing node.
|
||||||
|
track_node(scene_tree->get_root(), true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::setup_debugger_python_ui() {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
// Verify if file exists.
|
||||||
|
const String path = main_dump_directory_path + "/debugger.py";
|
||||||
|
|
||||||
|
if (FileAccess::exists(path)) {
|
||||||
|
// Nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the python UI into the directory.
|
||||||
|
FileAccess *f = FileAccess::open(path, FileAccess::WRITE);
|
||||||
|
ERR_FAIL_COND_MSG(!f, "Can't create the `" + path + "` file.");
|
||||||
|
|
||||||
|
f->store_buffer((uint8_t *)__debugger_ui_code, __debugger_ui_code_size);
|
||||||
|
|
||||||
|
f->close();
|
||||||
|
memdelete(f);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::track_node(Node *p_node, bool p_recursive) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (tracked_nodes.find(p_node) == -1) {
|
||||||
|
const bool is_tracked = dump_classes.find(p_node->get_class_name()) != -1;
|
||||||
|
if (is_tracked) {
|
||||||
|
// Verify if the property list already exists.
|
||||||
|
if (!classes_property_lists.has(p_node->get_class_name())) {
|
||||||
|
// Property list not yet cached, fetch it now.
|
||||||
|
classes_property_lists.insert(p_node->get_class_name(), List<PropertyInfo>());
|
||||||
|
List<PropertyInfo> *properties = classes_property_lists.lookup_ptr(p_node->get_class_name());
|
||||||
|
|
||||||
|
p_node->get_property_list(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PropertyInfo> *properties = classes_property_lists.lookup_ptr(p_node->get_class_name());
|
||||||
|
|
||||||
|
// Can't happen, as it was just created.
|
||||||
|
CRASH_COND(properties == nullptr);
|
||||||
|
|
||||||
|
// Assign the property list pointer for fast access.
|
||||||
|
tracked_nodes.push_back(TrackedNode(p_node, properties));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_recursive) {
|
||||||
|
for (int i = 0; i < p_node->get_child_count(); i += 1) {
|
||||||
|
Node *child = p_node->get_child(i);
|
||||||
|
track_node(child, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::on_node_added(Node *p_node) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
track_node(p_node, false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::on_node_removed(Node *p_node) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
const int64_t index = tracked_nodes.find(p_node);
|
||||||
|
if (index != -1) {
|
||||||
|
tracked_nodes.remove_unordered(index);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::write_dump(int p_peer, uint32_t p_frame_index) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_frame_index == UINT32_MAX) {
|
||||||
|
// Nothing to write.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileAccess *file = nullptr;
|
||||||
|
{
|
||||||
|
String file_path = "";
|
||||||
|
|
||||||
|
int iteration = 0;
|
||||||
|
String iteration_mark = "";
|
||||||
|
do {
|
||||||
|
file_path = main_dump_directory_path + "/" + dump_name + "/fd-" /*+ itos(p_peer) + "-"*/ + itos(p_frame_index) + iteration_mark + ".json";
|
||||||
|
iteration_mark += "@";
|
||||||
|
iteration += 1;
|
||||||
|
} while (FileAccess::exists(file_path) && iteration < 100);
|
||||||
|
|
||||||
|
Error e;
|
||||||
|
file = FileAccess::open(file_path, FileAccess::WRITE, &e);
|
||||||
|
ERR_FAIL_COND(e != OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
String frame_summary;
|
||||||
|
|
||||||
|
if (frame_dump__has_warnings) {
|
||||||
|
frame_summary += "* ";
|
||||||
|
} else if (frame_dump__has_errors) {
|
||||||
|
frame_summary += "!️ ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((frame_dump__frame_events & FrameEvent::CLIENT_DESYNC_DETECTED) > 0) {
|
||||||
|
frame_summary += "Client desync; ";
|
||||||
|
|
||||||
|
} else if ((frame_dump__frame_events & FrameEvent::CLIENT_DESYNC_DETECTED_SOFT) > 0) {
|
||||||
|
frame_summary += "Client desync; No controller rewind; ";
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary d;
|
||||||
|
d["frame"] = itos(p_frame_index);
|
||||||
|
d["peer"] = itos(p_peer);
|
||||||
|
d["frame_summary"] = frame_summary;
|
||||||
|
d["begin_state"] = frame_dump__begin_state;
|
||||||
|
d["end_state"] = frame_dump__end_state;
|
||||||
|
d["node_log"] = frame_dump__node_log;
|
||||||
|
d["data_buffer_writes"] = frame_dump__data_buffer_writes;
|
||||||
|
d["data_buffer_reads"] = frame_dump__data_buffer_reads;
|
||||||
|
d["are_inputs_different_results"] = frame_dump__are_inputs_different_results;
|
||||||
|
|
||||||
|
file->store_string(JSON::print(d));
|
||||||
|
file->close();
|
||||||
|
|
||||||
|
memdelete(file);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::start_new_frame() {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
frame_dump__node_log.clear();
|
||||||
|
frame_dump__frame_events = FrameEvent::EMPTY;
|
||||||
|
frame_dump__has_warnings = false;
|
||||||
|
frame_dump__has_errors = false;
|
||||||
|
frame_dump__data_buffer_writes.clear();
|
||||||
|
frame_dump__data_buffer_reads.clear();
|
||||||
|
frame_dump__are_inputs_different_results.clear();
|
||||||
|
log_counter = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
String type_to_string(Variant::Type p_type) {
|
||||||
|
switch (p_type) {
|
||||||
|
case Variant::NIL:
|
||||||
|
return "NIL";
|
||||||
|
case Variant::BOOL:
|
||||||
|
return "BOOL";
|
||||||
|
case Variant::INT:
|
||||||
|
return "INT";
|
||||||
|
case Variant::REAL:
|
||||||
|
return "REAL";
|
||||||
|
case Variant::STRING:
|
||||||
|
return "STRING";
|
||||||
|
case Variant::VECTOR2:
|
||||||
|
return "VECTOR2";
|
||||||
|
case Variant::RECT2:
|
||||||
|
return "RECT2";
|
||||||
|
case Variant::VECTOR3:
|
||||||
|
return "VECTOR3";
|
||||||
|
case Variant::TRANSFORM2D:
|
||||||
|
return "TRANSFORM2D";
|
||||||
|
case Variant::PLANE:
|
||||||
|
return "PLANE";
|
||||||
|
case Variant::QUAT:
|
||||||
|
return "QUAT";
|
||||||
|
case Variant::AABB:
|
||||||
|
return "AABB";
|
||||||
|
case Variant::BASIS:
|
||||||
|
return "BASIS";
|
||||||
|
case Variant::TRANSFORM:
|
||||||
|
return "TRANSFORM";
|
||||||
|
case Variant::COLOR:
|
||||||
|
return "COLOR";
|
||||||
|
case Variant::NODE_PATH:
|
||||||
|
return "NODE_PATH";
|
||||||
|
case Variant::_RID:
|
||||||
|
return "_RID";
|
||||||
|
case Variant::OBJECT:
|
||||||
|
return "OBJECT";
|
||||||
|
case Variant::DICTIONARY:
|
||||||
|
return "DICTIONARY";
|
||||||
|
case Variant::ARRAY:
|
||||||
|
return "ARRAY";
|
||||||
|
case Variant::POOL_BYTE_ARRAY:
|
||||||
|
return "POOL_BYTE_ARRAY";
|
||||||
|
case Variant::POOL_INT_ARRAY:
|
||||||
|
return "POOL_INT_ARRAY";
|
||||||
|
case Variant::POOL_REAL_ARRAY:
|
||||||
|
return "POOL_REAL_ARRAY";
|
||||||
|
case Variant::POOL_STRING_ARRAY:
|
||||||
|
return "POOL_STRING_ARRAY";
|
||||||
|
case Variant::POOL_VECTOR2_ARRAY:
|
||||||
|
return "POOL_VECTOR2_ARRAY";
|
||||||
|
case Variant::POOL_VECTOR3_ARRAY:
|
||||||
|
return "POOL_VECTOR3_ARRAY";
|
||||||
|
case Variant::POOL_COLOR_ARRAY:
|
||||||
|
return "POOL_COLOR_ARRAY";
|
||||||
|
case Variant::VARIANT_MAX:
|
||||||
|
return "VARIANT_MAX";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String data_type_to_string(uint32_t p_type) {
|
||||||
|
switch (p_type) {
|
||||||
|
case DataBuffer::DATA_TYPE_BOOL:
|
||||||
|
return "Bool";
|
||||||
|
case DataBuffer::DATA_TYPE_INT:
|
||||||
|
return "Int";
|
||||||
|
case DataBuffer::DATA_TYPE_UINT:
|
||||||
|
return "Uint";
|
||||||
|
case DataBuffer::DATA_TYPE_REAL:
|
||||||
|
return "Real";
|
||||||
|
case DataBuffer::DATA_TYPE_POSITIVE_UNIT_REAL:
|
||||||
|
return "Positive Unit Real";
|
||||||
|
case DataBuffer::DATA_TYPE_UNIT_REAL:
|
||||||
|
return "Unit Real";
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR2:
|
||||||
|
return "Vector2";
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR2:
|
||||||
|
return "Normalized Vector2";
|
||||||
|
case DataBuffer::DATA_TYPE_VECTOR3:
|
||||||
|
return "Vector3";
|
||||||
|
case DataBuffer::DATA_TYPE_NORMALIZED_VECTOR3:
|
||||||
|
return "Normalized Vector3";
|
||||||
|
case DataBuffer::DATA_TYPE_VARIANT:
|
||||||
|
return "Variant";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "UNDEFINED";
|
||||||
|
}
|
||||||
|
|
||||||
|
String compression_level_to_string(uint32_t p_type) {
|
||||||
|
switch (p_type) {
|
||||||
|
case DataBuffer::COMPRESSION_LEVEL_0:
|
||||||
|
return "Compression Level 0";
|
||||||
|
case DataBuffer::COMPRESSION_LEVEL_1:
|
||||||
|
return "Compression Level 1";
|
||||||
|
case DataBuffer::COMPRESSION_LEVEL_2:
|
||||||
|
return "Compression Level 2";
|
||||||
|
case DataBuffer::COMPRESSION_LEVEL_3:
|
||||||
|
return "Compression Level 3";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Compression Level UNDEFINED";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::scene_sync_process_start(const SceneSynchronizer *p_scene_sync) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dump_tracked_objects(p_scene_sync, frame_dump__begin_state);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::scene_sync_process_end(const SceneSynchronizer *p_scene_sync) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dump_tracked_objects(p_scene_sync, frame_dump__end_state);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::databuffer_operation_begin_record(Node *p_node, DataBufferDumpMode p_mode) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame_dump__data_buffer_name = p_node->get_path();
|
||||||
|
frame_dump_data_buffer_dump_mode = p_mode;
|
||||||
|
|
||||||
|
if (frame_dump_data_buffer_dump_mode == DataBufferDumpMode::WRITE) {
|
||||||
|
add_node_message_by_path(frame_dump__data_buffer_name, "[WRITE] DataBuffer start write");
|
||||||
|
} else {
|
||||||
|
add_node_message_by_path(frame_dump__data_buffer_name, "[READ] DataBuffer start read");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::databuffer_operation_end_record() {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_dump_data_buffer_dump_mode == DataBufferDumpMode::WRITE) {
|
||||||
|
add_node_message_by_path(frame_dump__data_buffer_name, "[WRITE] end");
|
||||||
|
} else {
|
||||||
|
add_node_message_by_path(frame_dump__data_buffer_name, "[READ] end");
|
||||||
|
}
|
||||||
|
|
||||||
|
frame_dump_data_buffer_dump_mode = DataBufferDumpMode::NONE;
|
||||||
|
frame_dump__data_buffer_name = "";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::databuffer_write(uint32_t p_data_type, uint32_t p_compression_level, Variant p_variable) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_dump_data_buffer_dump_mode != DataBufferDumpMode::WRITE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<const void *> stack;
|
||||||
|
String val_string = p_variable.stringify(stack);
|
||||||
|
|
||||||
|
frame_dump__data_buffer_writes.push_back(val_string);
|
||||||
|
|
||||||
|
const String operation = "[WRITE] [" + compression_level_to_string(p_compression_level) + "] [" + data_type_to_string(p_data_type) + "] " + val_string;
|
||||||
|
|
||||||
|
add_node_message_by_path(frame_dump__data_buffer_name, operation);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::databuffer_read(uint32_t p_data_type, uint32_t p_compression_level, Variant p_variable) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_dump_data_buffer_dump_mode != DataBufferDumpMode::READ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<const void *> stack;
|
||||||
|
String val_string = p_variable.stringify(stack);
|
||||||
|
|
||||||
|
frame_dump__data_buffer_reads.push_back(val_string);
|
||||||
|
|
||||||
|
const String operation = "[READ] [" + compression_level_to_string(p_compression_level) + "] [" + data_type_to_string(p_data_type) + "] " + val_string;
|
||||||
|
|
||||||
|
add_node_message_by_path(frame_dump__data_buffer_name, operation);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::notify_input_sent_to_server(Node *p_node, uint32_t p_frame_index, uint32_t p_input_index) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
debug_print(p_node, "The client sent to server the input `" + itos(p_input_index) + "` for frame:`" + itos(p_frame_index) + "`.", true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::notify_are_inputs_different_result(
|
||||||
|
Node *p_node,
|
||||||
|
uint32_t p_other_frame_index,
|
||||||
|
bool p_is_similar) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (p_is_similar) {
|
||||||
|
debug_print(p_node, "This frame input is SIMILAR to `" + itos(p_other_frame_index) + "`", true);
|
||||||
|
} else {
|
||||||
|
debug_print(p_node, "This frame input is DIFFERENT to `" + itos(p_other_frame_index) + "`", true);
|
||||||
|
}
|
||||||
|
frame_dump__are_inputs_different_results[p_other_frame_index] = p_is_similar;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::add_node_message(Node *p_node, const String &p_message) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (p_node) {
|
||||||
|
add_node_message_by_path(p_node->get_path(), p_message);
|
||||||
|
} else {
|
||||||
|
add_node_message_by_path("GLOBAL", p_message);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::add_node_message_by_path(const String &p_node_path, const String &p_message) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame_dump__node_log.has(p_node_path)) {
|
||||||
|
frame_dump__node_log[p_node_path] = Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant *v = frame_dump__node_log.getptr(p_node_path);
|
||||||
|
Array a = *v;
|
||||||
|
|
||||||
|
Dictionary m;
|
||||||
|
m["i"] = log_counter;
|
||||||
|
m["m"] = p_message;
|
||||||
|
a.append(m);
|
||||||
|
log_counter += 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::debug_print(Node *p_node, const String &p_message, bool p_silent) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!p_silent) {
|
||||||
|
NET_DEBUG_PRINT(p_message);
|
||||||
|
}
|
||||||
|
add_node_message(p_node, "[INFO] " + p_message);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::debug_warning(Node *p_node, const String &p_message, bool p_silent) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!p_silent) {
|
||||||
|
NET_DEBUG_WARN(p_message);
|
||||||
|
}
|
||||||
|
add_node_message(p_node, "[WARNING] " + p_message);
|
||||||
|
frame_dump__has_warnings = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::debug_error(Node *p_node, const String &p_message, bool p_silent) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!p_silent) {
|
||||||
|
NET_DEBUG_ERR(p_message);
|
||||||
|
}
|
||||||
|
add_node_message(p_node, "[ERROR] " + p_message);
|
||||||
|
frame_dump__has_errors = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::notify_event(FrameEvent p_event) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
if (!dump_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame_dump__frame_events = FrameEvent(frame_dump__frame_events | p_event);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneSynchronizerDebugger::dump_tracked_objects(const SceneSynchronizer *p_scene_sync, Dictionary &p_dump) {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
p_dump.clear();
|
||||||
|
|
||||||
|
List<const void *> stack;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < tracked_nodes.size(); i += 1) {
|
||||||
|
Dictionary node_dump;
|
||||||
|
|
||||||
|
String node_path = tracked_nodes[i].node->get_path();
|
||||||
|
node_dump["node_path"] = node_path;
|
||||||
|
|
||||||
|
for (List<PropertyInfo>::Element *e = tracked_nodes[i].properties->front(); e; e = e->next()) {
|
||||||
|
String prefix;
|
||||||
|
if (p_scene_sync->is_variable_registered(tracked_nodes[i].node, e->get().name)) {
|
||||||
|
prefix = "* ";
|
||||||
|
}
|
||||||
|
|
||||||
|
node_dump[prefix + e->get().name + "::" + type_to_string(e->get().type)] = tracked_nodes[i].node->get(e->get().name).stringify(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
p_dump[node_path] = node_dump;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
171
modules/network_synchronizer/scene_synchronizer_debugger.h
Normal file
171
modules/network_synchronizer/scene_synchronizer_debugger.h
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* scene_synchronizer_debugger.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. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "scene/main/node.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
|
||||||
|
#include "core/oa_hash_map.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class SceneSynchronizer;
|
||||||
|
class SceneTree;
|
||||||
|
|
||||||
|
class SceneSynchronizerDebugger : public Node {
|
||||||
|
GDCLASS(SceneSynchronizerDebugger, Node);
|
||||||
|
|
||||||
|
static SceneSynchronizerDebugger *the_singleton;
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct TrackedNode {
|
||||||
|
Node *node;
|
||||||
|
List<PropertyInfo> *properties;
|
||||||
|
TrackedNode() = default;
|
||||||
|
TrackedNode(Node *p_node) :
|
||||||
|
node(p_node) {}
|
||||||
|
TrackedNode(Node *p_node, List<PropertyInfo> *p_properties) :
|
||||||
|
node(p_node), properties(p_properties) {}
|
||||||
|
bool operator==(const TrackedNode &p_t) const { return p_t.node == node; }
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DataBufferDumpMode {
|
||||||
|
NONE,
|
||||||
|
WRITE,
|
||||||
|
READ
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FrameEvent : uint32_t {
|
||||||
|
EMPTY = 0,
|
||||||
|
CLIENT_DESYNC_DETECTED = 1 << 0,
|
||||||
|
CLIENT_DESYNC_DETECTED_SOFT = 1 << 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
static SceneSynchronizerDebugger *singleton();
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
bool dump_enabled = false;
|
||||||
|
LocalVector<StringName> dump_classes;
|
||||||
|
bool setup_done = false;
|
||||||
|
|
||||||
|
uint32_t log_counter = 0;
|
||||||
|
SceneTree *scene_tree = nullptr;
|
||||||
|
String main_dump_directory_path;
|
||||||
|
String dump_name;
|
||||||
|
|
||||||
|
LocalVector<TrackedNode> tracked_nodes;
|
||||||
|
// HaskMap between class name and property list: to avoid fetching the property list per object each frame.
|
||||||
|
OAHashMap<StringName, List<PropertyInfo>> classes_property_lists;
|
||||||
|
|
||||||
|
// Dictionary of dictionary containing nodes info.
|
||||||
|
Dictionary frame_dump__begin_state;
|
||||||
|
|
||||||
|
// Dictionary of dictionary containing nodes info.
|
||||||
|
Dictionary frame_dump__end_state;
|
||||||
|
|
||||||
|
// The dictionary containing the data buffer operations performed by the controllers.
|
||||||
|
Dictionary frame_dump__node_log;
|
||||||
|
|
||||||
|
// The controller name for which the data buffer operations is in progress.
|
||||||
|
String frame_dump__data_buffer_name;
|
||||||
|
|
||||||
|
// A really small description about what happens on this frame.
|
||||||
|
FrameEvent frame_dump__frame_events = FrameEvent::EMPTY;
|
||||||
|
|
||||||
|
// This Array contains all the inputs (stringified) written on the `DataBuffer` from the
|
||||||
|
// `_controller_process` function
|
||||||
|
Array frame_dump__data_buffer_writes;
|
||||||
|
|
||||||
|
// This Array contains all the inputs (stringified) read on the `DataBuffer` from the
|
||||||
|
// `_controller_process` function
|
||||||
|
Array frame_dump__data_buffer_reads;
|
||||||
|
|
||||||
|
// This Dictionary contains the comparison (`_are_inputs_different`) fetched by this frame, and
|
||||||
|
// the result.
|
||||||
|
Dictionary frame_dump__are_inputs_different_results;
|
||||||
|
|
||||||
|
DataBufferDumpMode frame_dump_data_buffer_dump_mode = NONE;
|
||||||
|
|
||||||
|
bool frame_dump__has_warnings = false;
|
||||||
|
bool frame_dump__has_errors = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
SceneSynchronizerDebugger();
|
||||||
|
~SceneSynchronizerDebugger();
|
||||||
|
|
||||||
|
void set_dump_enabled(bool p_dump_enabled);
|
||||||
|
bool get_dump_enabled() const;
|
||||||
|
|
||||||
|
void register_class_for_node_to_dump(Node *p_node);
|
||||||
|
void register_class_to_dump(const StringName &p_class);
|
||||||
|
void unregister_class_to_dump(const StringName &p_class);
|
||||||
|
|
||||||
|
void setup_debugger(const String &p_dump_name, int p_peer, SceneTree *p_scene_tree);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void prepare_dumping(int p_peer, SceneTree *p_scene_tree);
|
||||||
|
void setup_debugger_python_ui();
|
||||||
|
void track_node(Node *p_node, bool p_recursive);
|
||||||
|
void on_node_added(Node *p_node);
|
||||||
|
void on_node_removed(Node *p_node);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void write_dump(int p_peer, uint32_t p_frame_index);
|
||||||
|
void start_new_frame();
|
||||||
|
|
||||||
|
void scene_sync_process_start(const SceneSynchronizer *p_scene_sync);
|
||||||
|
void scene_sync_process_end(const SceneSynchronizer *p_scene_sync);
|
||||||
|
|
||||||
|
void databuffer_operation_begin_record(Node *p_node, DataBufferDumpMode p_mode);
|
||||||
|
void databuffer_operation_end_record();
|
||||||
|
void databuffer_write(uint32_t p_data_type, uint32_t p_compression_level, Variant p_variable);
|
||||||
|
void databuffer_read(uint32_t p_data_type, uint32_t p_compression_level, Variant p_variable);
|
||||||
|
|
||||||
|
void notify_input_sent_to_server(Node *p_node, uint32_t p_frame_index, uint32_t p_input_index);
|
||||||
|
void notify_are_inputs_different_result(Node *p_node, uint32_t p_other_frame_index, bool p_is_similar);
|
||||||
|
|
||||||
|
void add_node_message(Node *p_node, const String &p_message);
|
||||||
|
void add_node_message_by_path(const String &p_node_path, const String &p_message);
|
||||||
|
|
||||||
|
void debug_print(Node *p_node, const String &p_message, bool p_silent = false);
|
||||||
|
void debug_warning(Node *p_node, const String &p_message, bool p_silent = false);
|
||||||
|
void debug_error(Node *p_node, const String &p_message, bool p_silent = false);
|
||||||
|
|
||||||
|
void notify_event(FrameEvent p_event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void dump_tracked_objects(const SceneSynchronizer *p_scene_sync, Dictionary &p_dump);
|
||||||
|
};
|
@ -1,121 +0,0 @@
|
|||||||
/*************************************************************************/
|
|
||||||
/* test_bit_array.h */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* PANDEMONIUM ENGINE */
|
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
|
||||||
/* 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
|
|
@ -1,551 +0,0 @@
|
|||||||
/*************************************************************************/
|
|
||||||
/* test_data_buffer.h */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* PANDEMONIUM ENGINE */
|
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
|
||||||
/* 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
|
|
@ -1,132 +0,0 @@
|
|||||||
/*************************************************************************/
|
|
||||||
/* test_interpolator.h */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* PANDEMONIUM ENGINE */
|
|
||||||
/* https://github.com/Relintai/pandemonium_engine */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2022-present Péter Magyar. */
|
|
||||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* */
|
|
||||||
/* 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);
|
|
||||||
|
|
||||||
RBMap<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 RBMap<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