From eb63a4d68294030b9a335721e2b8e4390859b85b Mon Sep 17 00:00:00 2001 From: Relintai Date: Mon, 25 Dec 2023 19:27:07 +0100 Subject: [PATCH] Updated NetworkSynchronizer from https://github.com/GameNetworking/network_synchronizer/tree/godot-3.x . f12fee66a7e3eb2013d6c5437837770455105b91 (Merge commit: cc3c7d244adea8b25822f43963618fd2dbf18ec8) --- modules/network_synchronizer/.clang-format | 189 ++ modules/network_synchronizer/.gitattributes | 2 + modules/network_synchronizer/.gitignore | 45 + modules/network_synchronizer/SCsub | 4 + modules/network_synchronizer/bit_array.cpp | 32 +- modules/network_synchronizer/bit_array.h | 27 +- modules/network_synchronizer/config.py | 18 +- modules/network_synchronizer/data_buffer.cpp | 333 ++- modules/network_synchronizer/data_buffer.h | 43 +- .../debugger_ui/cpplize_debugger.py | 30 + .../debugger_ui/debugger.py | 389 ++++ .../debugger_ui/readme.md | 15 + .../doc_classes/DataBuffer.xml | 57 +- .../doc_classes/Interpolator.xml | 62 - .../doc_classes/NetworkedController.xml | 45 +- .../doc_classes/SceneDiff.xml | 13 - .../doc_classes/SceneSynchronizer.xml | 78 +- .../godot_backward_utility_cpp.h | 14 + .../godot_backward_utility_header.h | 23 + .../icons/icon_networked_controller.svg | 94 + .../input_network_encoder.cpp | 400 ++++ .../input_network_encoder.h | 51 + modules/network_synchronizer/interpolator.cpp | 416 ---- modules/network_synchronizer/interpolator.h | 105 - modules/network_synchronizer/net_action.cpp | 258 +++ modules/network_synchronizer/net_action.h | 92 + .../network_synchronizer/net_action_info.cpp | 9 + .../network_synchronizer/net_action_info.h | 24 + .../net_action_processor.cpp | 34 + .../net_action_processor.h | 40 + .../network_synchronizer/net_utilities.cpp | 16 +- modules/network_synchronizer/net_utilities.h | 95 +- .../networked_controller.cpp | 1130 +++++----- .../networked_controller.h | 284 +-- .../network_synchronizer/register_types.cpp | 45 +- modules/network_synchronizer/register_types.h | 22 +- modules/network_synchronizer/scene_diff.cpp | 11 +- modules/network_synchronizer/scene_diff.h | 20 +- .../scene_synchronizer.cpp | 1882 +++++++++++++---- .../network_synchronizer/scene_synchronizer.h | 211 +- .../scene_synchronizer_debugger.cpp | 708 +++++++ .../scene_synchronizer_debugger.h | 171 ++ .../tests/test_bit_array.h | 121 -- .../tests/test_data_buffer.h | 551 ----- .../tests/test_interpolator.h | 132 -- 45 files changed, 5624 insertions(+), 2717 deletions(-) create mode 100644 modules/network_synchronizer/.clang-format create mode 100644 modules/network_synchronizer/.gitattributes create mode 100644 modules/network_synchronizer/.gitignore create mode 100644 modules/network_synchronizer/debugger_ui/cpplize_debugger.py create mode 100644 modules/network_synchronizer/debugger_ui/debugger.py create mode 100644 modules/network_synchronizer/debugger_ui/readme.md delete mode 100644 modules/network_synchronizer/doc_classes/Interpolator.xml delete mode 100644 modules/network_synchronizer/doc_classes/SceneDiff.xml create mode 100644 modules/network_synchronizer/godot_backward_utility_cpp.h create mode 100644 modules/network_synchronizer/godot_backward_utility_header.h create mode 100644 modules/network_synchronizer/icons/icon_networked_controller.svg create mode 100644 modules/network_synchronizer/input_network_encoder.cpp create mode 100644 modules/network_synchronizer/input_network_encoder.h delete mode 100644 modules/network_synchronizer/interpolator.cpp delete mode 100644 modules/network_synchronizer/interpolator.h create mode 100644 modules/network_synchronizer/net_action.cpp create mode 100644 modules/network_synchronizer/net_action.h create mode 100644 modules/network_synchronizer/net_action_info.cpp create mode 100644 modules/network_synchronizer/net_action_info.h create mode 100644 modules/network_synchronizer/net_action_processor.cpp create mode 100644 modules/network_synchronizer/net_action_processor.h create mode 100644 modules/network_synchronizer/scene_synchronizer_debugger.cpp create mode 100644 modules/network_synchronizer/scene_synchronizer_debugger.h delete mode 100644 modules/network_synchronizer/tests/test_bit_array.h delete mode 100644 modules/network_synchronizer/tests/test_data_buffer.h delete mode 100644 modules/network_synchronizer/tests/test_interpolator.h diff --git a/modules/network_synchronizer/.clang-format b/modules/network_synchronizer/.clang-format new file mode 100644 index 000000000..9d436388d --- /dev/null +++ b/modules/network_synchronizer/.clang-format @@ -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'] +... diff --git a/modules/network_synchronizer/.gitattributes b/modules/network_synchronizer/.gitattributes new file mode 100644 index 000000000..d2f18e010 --- /dev/null +++ b/modules/network_synchronizer/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files +* text=auto eol=lf diff --git a/modules/network_synchronizer/.gitignore b/modules/network_synchronizer/.gitignore new file mode 100644 index 000000000..51f5a826d --- /dev/null +++ b/modules/network_synchronizer/.gitignore @@ -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/ diff --git a/modules/network_synchronizer/SCsub b/modules/network_synchronizer/SCsub index 257431408..26efcb931 100644 --- a/modules/network_synchronizer/SCsub +++ b/modules/network_synchronizer/SCsub @@ -1,5 +1,9 @@ #!/usr/bin/env python +from debugger_ui import cpplize_debugger + +cpplize_debugger.create_debugger_header() + Import("env") Import("env_modules") diff --git a/modules/network_synchronizer/bit_array.cpp b/modules/network_synchronizer/bit_array.cpp index c0bcb1e6c..6a39ca42d 100644 --- a/modules/network_synchronizer/bit_array.cpp +++ b/modules/network_synchronizer/bit_array.cpp @@ -1,13 +1,12 @@ /*************************************************************************/ /* bit_array.cpp */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -36,13 +35,13 @@ #include "bit_array.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) { resize_in_bits(p_initial_size_in_bit); } -BitArray::BitArray(const PoolByteArray &p_bytes) : +BitArray::BitArray(const Vector &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; uint64_t val = p_value; - PoolByteArray::Write w = bytes.write(); - while (bits > 0) { const int bits_to_write = MIN(bits, 8 - 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; byte_clear = byte_clear << (bits_to_jump + 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 - w[byte_offset] |= (val & 0xFF) << bits_to_jump; + bytes.write[byte_offset] |= (val & 0xFF) << bits_to_jump; bits -= bits_to_write; bit_offset += bits_to_write; val >>= bits_to_write; } - - w.release(); } 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; uint64_t val = 0; - PoolByteArray::Read r = bytes.read(); - const uint8_t *bytes_ptr = r.ptr(); + const uint8_t *bytes_ptr = bytes.ptr(); int val_bits_to_jump = 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; } - r.release(); - return val; } void BitArray::zero() { if (bytes.size() > 0) { - PoolByteArray::Write w = bytes.write(); - memset(w.ptr(), 0, sizeof(uint8_t) * bytes.size()); - w.release(); + memset(bytes.ptrw(), 0, sizeof(uint8_t) * bytes.size()); } } diff --git a/modules/network_synchronizer/bit_array.h b/modules/network_synchronizer/bit_array.h index 88efa747c..f812936eb 100644 --- a/modules/network_synchronizer/bit_array.h +++ b/modules/network_synchronizer/bit_array.h @@ -1,16 +1,12 @@ -#ifndef BITARRAY_H -#define BITARRAY_H - /*************************************************************************/ /* bit_array.h */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -36,21 +32,24 @@ @author AndreaCatania */ -#include "core/variant/variant.h" +#include "core/vector.h" + +#ifndef BITARRAY_H +#define BITARRAY_H class BitArray { - PoolByteArray bytes; + Vector bytes; public: BitArray() = default; BitArray(uint32_t p_initial_size_in_bit); - BitArray(const PoolByteArray &p_bytes); + BitArray(const Vector &p_bytes); - const PoolByteArray &get_bytes() const { + const Vector &get_bytes() const { return bytes; } - PoolByteArray &get_bytes_mut() { + Vector &get_bytes_mut() { return bytes; } diff --git a/modules/network_synchronizer/config.py b/modules/network_synchronizer/config.py index a7ccfc6bf..9605e76cd 100644 --- a/modules/network_synchronizer/config.py +++ b/modules/network_synchronizer/config.py @@ -6,19 +6,17 @@ def configure(env): pass -def get_doc_classes(): - return [ - "DataBuffer", - "SceneDiff", - "Interpolator", - "NetworkedController", - "SceneSynchronizer", - ] - - def get_doc_path(): return "doc_classes" +def get_doc_classes(): + return [ + "SceneSynchronizer", + "NetworkedController", + "DataBuffer", + ] + + def is_enabled(): return True diff --git a/modules/network_synchronizer/data_buffer.cpp b/modules/network_synchronizer/data_buffer.cpp index e086511b2..3de1ef7b1 100644 --- a/modules/network_synchronizer/data_buffer.cpp +++ b/modules/network_synchronizer/data_buffer.cpp @@ -1,13 +1,12 @@ /*************************************************************************/ /* data_buffer.cpp */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -36,18 +35,48 @@ #include "data_buffer.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. void DataBuffer::_bind_methods() { BIND_ENUM_CONSTANT(DATA_TYPE_BOOL); BIND_ENUM_CONSTANT(DATA_TYPE_INT); + BIND_ENUM_CONSTANT(DATA_TYPE_UINT); 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_VECTOR2); BIND_ENUM_CONSTANT(DATA_TYPE_NORMALIZED_VECTOR2); BIND_ENUM_CONSTANT(DATA_TYPE_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_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_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_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)); @@ -69,7 +99,9 @@ void DataBuffer::_bind_methods() { 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_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_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_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)); @@ -79,6 +111,7 @@ void DataBuffer::_bind_methods() { 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_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_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)); @@ -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_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_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)); @@ -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_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_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)); @@ -124,6 +159,22 @@ DataBuffer::DataBuffer(const BitArray &p_buffer) : is_reading(true), 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) { CRASH_COND_MSG(p_metadata_size < 0, "Metadata size can't be negative"); metadata_size = p_metadata_size; @@ -178,6 +229,8 @@ void DataBuffer::begin_read() { bool DataBuffer::add_bool(bool 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); 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 bool d = buffer.read_bits(bit_offset, bits); bit_offset += bits; + + DEB_READ(DATA_TYPE_BOOL, COMPRESSION_LEVEL_0, d); + return d; } int64_t DataBuffer::add_int(int64_t p_input, CompressionLevel p_compression_level) { 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); 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); + + // 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(value)); + return static_cast(value); + + } else if (bits == 16) { + DEB_READ(DATA_TYPE_INT, p_compression_level, static_cast(value)); + return static_cast(value); + + } else if (bits == 32) { + DEB_READ(DATA_TYPE_INT, p_compression_level, static_cast(value)); + return static_cast(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); 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()); #endif - if (bits == 8) { - return static_cast(value); - } else if (bits == 16) { - return static_cast(value); - } else if (bits == 32) { - return static_cast(value); - } else { - return value; - } + 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); - 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); bit_offset += bits; - if (bits == 8) { - return static_cast(value); - } else if (bits == 16) { - return static_cast(value); - } else if (bits == 32) { - return static_cast(value); - } else { - return static_cast(value); - } + DEB_READ(DATA_TYPE_UINT, p_compression_level, value); + + return value; } double DataBuffer::add_real(double p_input, CompressionLevel p_compression_level) { 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 // Minifloat (compression level 0) have a special bias 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 = 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) { @@ -340,7 +457,9 @@ real_t DataBuffer::add_positive_unit_real(real_t p_input, CompressionLevel p_com #endif 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(~(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) { 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(~(UINT64_MAX << bits)); const uint64_t compressed_val = buffer.read_bits(bit_offset, bits); bit_offset += bits; - return decompress_unit_float(compressed_val, max_value); + 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) { 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 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); 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) { @@ -413,9 +542,16 @@ Vector2 DataBuffer::add_vector2(Vector2 p_input, CompressionLevel p_compression_ } #endif + DEB_WRITE(DATA_TYPE_VECTOR2, p_compression_level, p_input); + + DEB_DISABLE + Vector2 r; r[0] = add_real(p_input[0], p_compression_level); r[1] = add_real(p_input[1], p_compression_level); + + DEB_ENABLE + return r; } @@ -430,24 +566,35 @@ Vector2 DataBuffer::read_vector2(CompressionLevel p_compression_level) { } #endif + DEB_DISABLE + Vector2 r; r[0] = 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; } 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 - 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 + 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_for_the_angle = bits - 1; const int bits_for_zero = 1; const double angle = p_input.angle(); - const uint32_t is_not_zero = p_input.length_squared() > CMP_EPSILON; const double max_value = static_cast(~(UINT64_MAX << bits_for_the_angle)); @@ -487,7 +634,10 @@ Vector2 DataBuffer::read_normalized_vector2(CompressionLevel p_compression_level const real_t x = Math::cos(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) { @@ -501,10 +651,17 @@ Vector3 DataBuffer::add_vector3(Vector3 p_input, CompressionLevel p_compression_ } #endif + DEB_WRITE(DATA_TYPE_VECTOR3, p_compression_level, p_input); + + DEB_DISABLE + Vector3 r; r[0] = add_real(p_input[0], p_compression_level); r[1] = add_real(p_input[1], p_compression_level); r[2] = add_real(p_input[2], p_compression_level); + + DEB_ENABLE + return r; } @@ -519,34 +676,56 @@ Vector3 DataBuffer::read_vector3(CompressionLevel p_compression_level) { } #endif + DEB_DISABLE + Vector3 r; r[0] = read_real(p_compression_level); r[1] = 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; } Vector3 DataBuffer::add_normalized_vector3(Vector3 p_input, CompressionLevel p_compression_level) { #ifdef DEBUG_ENABLED - ERR_FAIL_COND_V(p_input.is_normalized() == false, p_input); + 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 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 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); + DEB_ENABLE + return Vector3(x_axis, y_axis, z_axis); } Vector3 DataBuffer::read_normalized_vector3(CompressionLevel p_compression_level) { ERR_FAIL_COND_V(is_reading == false, Vector3()); + DEB_DISABLE + const real_t x_axis = read_unit_real(p_compression_level); const real_t y_axis = read_unit_real(p_compression_level); const real_t z_axis = read_unit_real(p_compression_level); - return Vector3(x_axis, y_axis, z_axis); + 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) { @@ -556,6 +735,8 @@ Variant DataBuffer::add_variant(const Variant &p_input) { // Get the variant size. int len = 0; + DEB_WRITE(DATA_TYPE_VARIANT, COMPRESSION_LEVEL_0, p_input); + const Error len_err = encode_variant( p_input, nullptr, @@ -580,7 +761,7 @@ Variant DataBuffer::add_variant(const Variant &p_input) { const Error write_err = encode_variant( p_input, - buffer.get_bytes_mut().write().ptr() + (bit_offset / 8), + buffer.get_bytes_mut().ptrw() + (bit_offset / 8), len, false); @@ -611,7 +792,7 @@ Variant DataBuffer::read_variant() { const Error read_err = decode_variant( ret, - buffer.get_bytes_mut().write().ptr() + (bit_offset / 8), + buffer.get_bytes().ptr() + (bit_offset / 8), buffer.size_in_bytes() - (bit_offset / 8), &len, false); @@ -623,6 +804,8 @@ Variant DataBuffer::read_variant() { bit_offset += len * 8; + DEB_READ(DATA_TYPE_VARIANT, COMPRESSION_LEVEL_0, ret); + return ret; } @@ -640,11 +823,21 @@ void DataBuffer::skip_int(CompressionLevel p_compression) { 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) { const int bits = get_real_size(p_compression); 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) { const int bits = get_unit_real_size(p_compression); 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); } +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 { 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 { 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; } +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) { const int bits = get_real_size(p_compression); skip(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) { const int bits = get_unit_real_size(p_compression); skip(bits); @@ -767,7 +980,7 @@ int DataBuffer::read_variant_size() { const Error read_err = decode_variant( ret, - buffer.get_bytes_mut().write().ptr() + (bit_offset / 8), + buffer.get_bytes().ptr() + (bit_offset / 8), buffer.size_in_bytes() - (bit_offset / 8), &len, false); @@ -802,6 +1015,21 @@ int DataBuffer::get_bit_taken(DataType p_data_type, CompressionLevel p_compressi CRASH_NOW_MSG("Compression level not supported!"); } } 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: { return get_mantissa_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; } break; case DATA_TYPE_NORMALIZED_VECTOR3: { - switch (p_compression) { - case CompressionLevel::COMPRESSION_LEVEL_0: - return 11 * 3; - case CompressionLevel::COMPRESSION_LEVEL_1: - return 10 * 3; - case CompressionLevel::COMPRESSION_LEVEL_2: - return 8 * 3; - case CompressionLevel::COMPRESSION_LEVEL_3: - return 6 * 3; - } + return get_bit_taken(DATA_TYPE_UNIT_REAL, p_compression) * 3; } break; case DATA_TYPE_VARIANT: { 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) { +#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 switch (p_compression) { 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) { +#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 switch (p_compression) { case CompressionLevel::COMPRESSION_LEVEL_0: diff --git a/modules/network_synchronizer/data_buffer.h b/modules/network_synchronizer/data_buffer.h index d4794e3ab..8683489ca 100644 --- a/modules/network_synchronizer/data_buffer.h +++ b/modules/network_synchronizer/data_buffer.h @@ -1,16 +1,12 @@ -#ifndef INPUT_BUFFER_H -#define INPUT_BUFFER_H - /*************************************************************************/ /* data_buffer.h */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -36,10 +32,13 @@ @author AndreaCatania */ -#include "core/object/class_db.h" +#include "core/class_db.h" #include "bit_array.h" +#ifndef INPUT_BUFFER_H +#define INPUT_BUFFER_H + class DataBuffer : public Object { GDCLASS(DataBuffer, Object); @@ -47,6 +46,7 @@ public: enum DataType { DATA_TYPE_BOOL, DATA_TYPE_INT, + DATA_TYPE_UINT, DATA_TYPE_REAL, DATA_TYPE_POSITIVE_UNIT_REAL, DATA_TYPE_UNIT_REAL, @@ -147,6 +147,10 @@ private: bool is_reading = false; BitArray buffer; +#if DEBUG_ENABLED + bool debug_enabled = true; +#endif + public: static void _bind_methods(); @@ -154,6 +158,9 @@ public: DataBuffer(const DataBuffer &p_other); DataBuffer(const BitArray &p_buffer); + void copy(const DataBuffer &p_other); + void copy(const BitArray &p_buffer); + const BitArray &get_buffer() const { return buffer; } @@ -204,6 +211,12 @@ public: /// Parse the next data as int. 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 /// to store different range level. /// The fractional part has a precision of ~0.3% @@ -238,7 +251,7 @@ public: real_t read_unit_real(CompressionLevel p_compression_level); /// Add a vector2 into the buffer. - /// Note: This kind of vector occupies more space than the normalized version. + /// Note: This kind of vector occupies more space than the normalized verison. /// Consider use a normalized vector to save bandwidth if possible. /// /// Returns the decompressed vector so both the client and the peers can use @@ -260,7 +273,7 @@ public: Vector2 read_normalized_vector2(CompressionLevel p_compression_level); /// Add a vector3 into the buffer. - /// Note: This kind of vector occupies more space than the normalized version. + /// Note: This kind of vector occupies more space than the normalized verison. /// Consider use a normalized vector to save bandwidth if possible. /// /// Returns the decompressed vector so both the client and the peers can use @@ -294,7 +307,9 @@ public: void skip_bool(); void skip_int(CompressionLevel p_compression); + void skip_uint(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_vector2(CompressionLevel p_compression); void skip_normalized_vector2(CompressionLevel p_compression); @@ -305,7 +320,9 @@ public: int get_bool_size() 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_positive_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_normalized_vector2_size(CompressionLevel p_compression) const; @@ -316,7 +333,9 @@ public: int read_bool_size(); int read_int_size(CompressionLevel p_compression); + int read_uint_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_vector2_size(CompressionLevel p_compression); int read_normalized_vector2_size(CompressionLevel p_compression); diff --git a/modules/network_synchronizer/debugger_ui/cpplize_debugger.py b/modules/network_synchronizer/debugger_ui/cpplize_debugger.py new file mode 100644 index 000000000..99439b6ea --- /dev/null +++ b/modules/network_synchronizer/debugger_ui/cpplize_debugger.py @@ -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() diff --git a/modules/network_synchronizer/debugger_ui/debugger.py b/modules/network_synchronizer/debugger_ui/debugger.py new file mode 100644 index 000000000..713830639 --- /dev/null +++ b/modules/network_synchronizer/debugger_ui/debugger.py @@ -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 diff --git a/modules/network_synchronizer/debugger_ui/readme.md b/modules/network_synchronizer/debugger_ui/readme.md new file mode 100644 index 000000000..203c05beb --- /dev/null +++ b/modules/network_synchronizer/debugger_ui/readme.md @@ -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. diff --git a/modules/network_synchronizer/doc_classes/DataBuffer.xml b/modules/network_synchronizer/doc_classes/DataBuffer.xml index 3f8e45e8d..ba8ac4c53 100644 --- a/modules/network_synchronizer/doc_classes/DataBuffer.xml +++ b/modules/network_synchronizer/doc_classes/DataBuffer.xml @@ -1,5 +1,5 @@ - + @@ -48,6 +48,13 @@ + + + + + + + @@ -120,6 +127,12 @@ + + + + + + @@ -184,6 +197,12 @@ + + + + + + @@ -196,6 +215,18 @@ + + + + + + + + + + + + @@ -276,6 +307,12 @@ + + + + + + @@ -300,17 +337,23 @@ - + - + - + - + - + - + + + + + + + diff --git a/modules/network_synchronizer/doc_classes/Interpolator.xml b/modules/network_synchronizer/doc_classes/Interpolator.xml deleted file mode 100644 index b95511224..000000000 --- a/modules/network_synchronizer/doc_classes/Interpolator.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/modules/network_synchronizer/doc_classes/NetworkedController.xml b/modules/network_synchronizer/doc_classes/NetworkedController.xml index 3cc9e17b8..5e5b27693 100644 --- a/modules/network_synchronizer/doc_classes/NetworkedController.xml +++ b/modules/network_synchronizer/doc_classes/NetworkedController.xml @@ -1,5 +1,5 @@ - + @@ -10,7 +10,9 @@ - + + + @@ -47,19 +49,6 @@ - - - - - - - - - - - - - @@ -92,7 +81,7 @@ - + @@ -112,21 +101,17 @@ - + - + - + - + - + - - - - - + @@ -134,18 +119,24 @@ - + + + + + + + diff --git a/modules/network_synchronizer/doc_classes/SceneDiff.xml b/modules/network_synchronizer/doc_classes/SceneDiff.xml deleted file mode 100644 index 4cac80dbd..000000000 --- a/modules/network_synchronizer/doc_classes/SceneDiff.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/modules/network_synchronizer/doc_classes/SceneSynchronizer.xml b/modules/network_synchronizer/doc_classes/SceneSynchronizer.xml index 9e3bb6b7e..8d9d68446 100644 --- a/modules/network_synchronizer/doc_classes/SceneSynchronizer.xml +++ b/modules/network_synchronizer/doc_classes/SceneSynchronizer.xml @@ -1,6 +1,7 @@ - + + The `SceneSynchronizer` is used to synchronize all the peers using server authoritative networking model. @@ -45,6 +46,13 @@ + + + + + + + @@ -71,7 +79,7 @@ - + @@ -122,6 +130,24 @@ + + + + + + + + + + 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. + + @@ -131,15 +157,15 @@ - + - - + + @@ -172,7 +198,7 @@ - + @@ -204,13 +230,37 @@ - + - + + + + + + + + + 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. + + + + + + + + + + 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. + + @@ -220,28 +270,32 @@ - + - + - + - + + + + + diff --git a/modules/network_synchronizer/godot_backward_utility_cpp.h b/modules/network_synchronizer/godot_backward_utility_cpp.h new file mode 100644 index 000000000..6835c4ae8 --- /dev/null +++ b/modules/network_synchronizer/godot_backward_utility_cpp.h @@ -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 \ No newline at end of file diff --git a/modules/network_synchronizer/godot_backward_utility_header.h b/modules/network_synchronizer/godot_backward_utility_header.h new file mode 100644 index 000000000..6e45ff0b3 --- /dev/null +++ b/modules/network_synchronizer/godot_backward_utility_header.h @@ -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; } +}; \ No newline at end of file diff --git a/modules/network_synchronizer/icons/icon_networked_controller.svg b/modules/network_synchronizer/icons/icon_networked_controller.svg new file mode 100644 index 000000000..f49d8479e --- /dev/null +++ b/modules/network_synchronizer/icons/icon_networked_controller.svg @@ -0,0 +1,94 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/modules/network_synchronizer/input_network_encoder.cpp b/modules/network_synchronizer/input_network_encoder.cpp new file mode 100644 index 000000000..d170b946d --- /dev/null +++ b/modules/network_synchronizer/input_network_encoder.cpp @@ -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 &InputNetworkEncoder::get_input_info() const { + return input_info; +} + +void InputNetworkEncoder::encode(const LocalVector &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 &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 &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(p_buffer_A.read_real(info.compression_level)), static_cast(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(r_buffer); + ERR_FAIL_COND(db == nullptr); + + LocalVector 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(p_buffer); + ERR_FAIL_COND_V(db == nullptr, Array()); + + LocalVector 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 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(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(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(p_buffer); + ERR_FAIL_COND_V(db == nullptr, 0); + + return count_size(*db); +} diff --git a/modules/network_synchronizer/input_network_encoder.h b/modules/network_synchronizer/input_network_encoder.h new file mode 100644 index 000000000..d0bbe9e56 --- /dev/null +++ b/modules/network_synchronizer/input_network_encoder.h @@ -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 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 &get_input_info() const; + + void encode(const LocalVector &p_inputs, DataBuffer &r_buffer) const; + void decode(DataBuffer &p_buffer, LocalVector &r_inputs) const; + void reset_inputs_to_defaults(LocalVector &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; +}; diff --git a/modules/network_synchronizer/interpolator.cpp b/modules/network_synchronizer/interpolator.cpp deleted file mode 100644 index 0b8a86779..000000000 --- a/modules/network_synchronizer/interpolator.cpp +++ /dev/null @@ -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()); - // Sort the epochs. - for (int i = epochs.size() - 2; i >= int(write_position); i -= 1) { - epochs[uint32_t(i) + 1] = epochs[uint32_t(i)]; - buffer[uint32_t(i) + 1] = buffer[uint32_t(i)]; - } - // Init the new epoch. - epochs[write_position] = p_epoch; - buffer[write_position].clear(); - buffer[write_position].resize(variables.size()); - } - } else { - // No sort needed. - write_position = epochs.size(); - epochs.push_back(p_epoch); - buffer.push_back(Vector()); - buffer[write_position].resize(variables.size()); - } - - // Set defaults. - Variant *ptr = buffer[write_position].ptrw(); - for (uint32_t i = 0; i < variables.size(); i += 1) { - ptr[i] = variables[i].default_value; - } -} - -void Interpolator::epoch_insert(int p_var_id, const Variant &p_value) { - ERR_FAIL_COND_MSG(write_position == UINT32_MAX, "Please call `begin_write` before."); - ERR_FAIL_INDEX_MSG(p_var_id, int(variables.size()), "The variable_id passed is unknown."); - const uint32_t var_id(p_var_id); - ERR_FAIL_COND_MSG(variables[var_id].default_value.get_type() != p_value.get_type(), "The variable: " + itos(p_var_id) + " expects the variable type: " + Variant::get_type_name(variables[var_id].default_value.get_type()) + ", and not: " + Variant::get_type_name(p_value.get_type())); - buffer[write_position].write[var_id] = p_value; -} - -void Interpolator::end_write() { - ERR_FAIL_COND_MSG(write_position == UINT32_MAX, "You can't call this function before starting the epoch with `begin_write`."); - write_position = UINT32_MAX; -} - -Vector Interpolator::pop_epoch(uint32_t p_epoch, real_t p_fraction) { - ERR_FAIL_COND_V_MSG(init_phase, Vector(), "You can't pop data if the interpolator is not fully initialized."); - ERR_FAIL_COND_V_MSG(write_position != UINT32_MAX, Vector(), "You can't pop data while writing the epoch"); - - double epoch = double(p_epoch) + double(p_fraction); - - // Search the epoch. - uint32_t position = UINT32_MAX; - for (uint32_t i = 0; i < epochs.size(); i += 1) { - if (static_cast(epochs[i]) >= epoch) { - position = i; - break; - } - } - - ObjectID cache_object_id = 0; - Object *cache_object = nullptr; - - Vector data; - if (unlikely(position == UINT32_MAX)) { - data.resize(variables.size()); - Variant *ptr = data.ptrw(); - if (buffer.size() == 0) { - // No data found, set all to default. - for (uint32_t i = 0; i < variables.size(); i += 1) { - ptr[i] = variables[i].default_value; - } - } else { - // No new data. - for (uint32_t i = 0; i < variables.size(); i += 1) { - switch (variables[i].fallback) { - case FALLBACK_DEFAULT: - ptr[i] = variables[i].default_value; - break; - case FALLBACK_INTERPOLATE: // No way to interpolate, so just send the nearest. - case FALLBACK_NEW_OR_NEAREST: // No new data, so send the nearest. - case FALLBACK_OLD_OR_NEAREST: // Just send the oldest, as desired. - ptr[i] = buffer[buffer.size() - 1][i]; - break; - case FALLBACK_CUSTOM_INTERPOLATOR: - ptr[i] = variables[i].default_value; - - if (cache_object_id != variables[i].custom_interpolator_object) { - ERR_CONTINUE_MSG(!variables[i].custom_interpolator_object, "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; - } -} diff --git a/modules/network_synchronizer/interpolator.h b/modules/network_synchronizer/interpolator.h deleted file mode 100644 index 4ad32c4ee..000000000 --- a/modules/network_synchronizer/interpolator.h +++ /dev/null @@ -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 variables; - - /// Epoch ids, sorted from youngest to oldest. - LocalVector epochs; - /// Epoch data. - LocalVector> buffer; - - bool init_phase = true; - uint32_t write_position = UINT32_MAX; - uint32_t last_pop_epoch = 0; - - static void _bind_methods(); - -public: - Interpolator() = default; - - void clear(); - void reset(); - - int register_variable(const Variant &p_default, Fallback p_fallback); - void set_variable_default(int p_var_id, const Variant &p_default); - void set_variable_custom_interpolator(int p_var_id, Object *p_object, const StringName &p_function_name); - void terminate_init(); - - /// Returns the epochs stored. - uint32_t known_epochs_count() const; - void begin_write(uint32_t p_epoch); - void epoch_insert(int p_var_id, const Variant &p_value); - void end_write(); - - Vector pop_epoch(uint32_t p_epoch, real_t p_fraction); - uint32_t get_last_pop_epoch() const; // TODO do I need this? Remove if not. - uint32_t get_youngest_epoch() const; - uint32_t get_oldest_epoch() const; - - /// Returns the epochs count between the two last received time window. - uint32_t epochs_between_last_time_window() const; - - static Variant interpolate(const Variant &p_v1, const Variant &p_v2, real_t p_delta); -}; - -VARIANT_ENUM_CAST(Interpolator::Fallback); -#endif diff --git a/modules/network_synchronizer/net_action.cpp b/modules/network_synchronizer/net_action.cpp new file mode 100644 index 000000000..af0fe0df4 --- /dev/null +++ b/modules/network_synchronizer/net_action.cpp @@ -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 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 &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 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 &r_actions) { + const int sender_peer = synchronizer->get_tree()->get_multiplayer()->get_rpc_sender_id(); + + LocalVector 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); + } + } +} diff --git a/modules/network_synchronizer/net_action.h b/modules/network_synchronizer/net_action.h new file mode 100644 index 000000000..5c4b84ab8 --- /dev/null +++ b/modules/network_synchronizer/net_action.h @@ -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 vars_buffer; + Vector recipients; + int sender_peer = -1; + HashMap 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 &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 &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 missing_actions; + + bool process_received_action(uint32_t p_action_id); + void check_missing_actions_and_clean_up(Node *p_owner); +}; + +#endif diff --git a/modules/network_synchronizer/net_action_info.cpp b/modules/network_synchronizer/net_action_info.cpp new file mode 100644 index 000000000..29c2672c1 --- /dev/null +++ b/modules/network_synchronizer/net_action_info.cpp @@ -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; +} \ No newline at end of file diff --git a/modules/network_synchronizer/net_action_info.h b/modules/network_synchronizer/net_action_info.h new file mode 100644 index 000000000..1043b3d76 --- /dev/null +++ b/modules/network_synchronizer/net_action_info.h @@ -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 network_encoder; + + bool operator==(const NetActionInfo &p_other) const; + bool operator<(const NetActionInfo &p_other) const; +}; \ No newline at end of file diff --git a/modules/network_synchronizer/net_action_processor.cpp b/modules/network_synchronizer/net_action_processor.cpp new file mode 100644 index 000000000..dd627e9a1 --- /dev/null +++ b/modules/network_synchronizer/net_action_processor.cpp @@ -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 + ")"; +} \ No newline at end of file diff --git a/modules/network_synchronizer/net_action_processor.h b/modules/network_synchronizer/net_action_processor.h new file mode 100644 index 000000000..d54d9acdb --- /dev/null +++ b/modules/network_synchronizer/net_action_processor.h @@ -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) {} +}; diff --git a/modules/network_synchronizer/net_utilities.cpp b/modules/network_synchronizer/net_utilities.cpp index e91ceb88e..5ed33d960 100644 --- a/modules/network_synchronizer/net_utilities.cpp +++ b/modules/network_synchronizer/net_utilities.cpp @@ -1,13 +1,12 @@ /*************************************************************************/ /* net_utilities.cpp */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -34,9 +33,10 @@ */ #include "net_utilities.h" - #include "scene/main/node.h" +#include "godot_backward_utility_cpp.h" + bool NetUtility::ChangeListener::operator==(const ChangeListener &p_other) const { 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; } -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 *fake_array_vars = &var_delta; diff --git a/modules/network_synchronizer/net_utilities.h b/modules/network_synchronizer/net_utilities.h index 70b9eebf5..a9ee44d42 100644 --- a/modules/network_synchronizer/net_utilities.h +++ b/modules/network_synchronizer/net_utilities.h @@ -1,16 +1,12 @@ -#ifndef NET_UTILITIES_H -#define NET_UTILITIES_H - /*************************************************************************/ /* net_utilities.h */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -36,14 +32,25 @@ @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/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 -#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) -#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) #define NET_DEBUG_ERR(msg) \ ERR_PRINT(String("[Net] ") + msg) @@ -56,6 +63,28 @@ typedef uint32_t NetNodeId; 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::max()) { \ + c.resize(std::numeric_limits::max() - 1); \ + } \ + ZoneText(c.ptr(), c.size()); + +#else + +#define PROFILE +#define PROFILE_NODE + +#endif + /// Flags used to control when an event is executed. enum NetEventFlag { @@ -108,11 +137,12 @@ public: /// Maximum value. T max() const; - /// Minimum value. - T min(uint32_t p_consider_last) const; + /// Minumum value. + T min(uint32_t p_consider_last = UINT32_MAX) const; /// Median value. T average() const; + T average_rounded() const; T get_deviation(T p_mean) const; @@ -209,6 +239,29 @@ T StatisticalRingBuffer::average() const { #endif } +template +T StatisticalRingBuffer::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 T StatisticalRingBuffer::get_deviation(T p_mean) const { if (data.size() <= 0) { @@ -247,7 +300,7 @@ struct NodeChangeListener { 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 /// if one or more tracked variable change during the tracked phase, specified /// by the flag. @@ -291,7 +344,7 @@ struct NodeData { ObjectID instance_id = ObjectID(); 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. bool sync_enabled = true; @@ -305,12 +358,14 @@ struct NodeData { LocalVector vars; LocalVector functions; + LocalVector net_actions; + // This is valid to use only inside the process function. Node *node = nullptr; NodeData() = default; - void process(const real_t p_delta) const; + void process(const double p_delta) const; }; struct PeerData { @@ -330,6 +385,8 @@ struct Snapshot { /// The variable array order also matter. Vector> node_vars; + Vector actions; + operator String() const; }; @@ -340,4 +397,6 @@ struct PostponedRecover { } // namespace NetUtility +#undef ObjectID + #endif diff --git a/modules/network_synchronizer/networked_controller.cpp b/modules/network_synchronizer/networked_controller.cpp index 8567f8305..c85e1be6c 100644 --- a/modules/network_synchronizer/networked_controller.cpp +++ b/modules/network_synchronizer/networked_controller.cpp @@ -1,13 +1,12 @@ /*************************************************************************/ /* networked_controller.cpp */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -35,28 +34,29 @@ #include "networked_controller.h" -#include "core/config/engine.h" -#include "core/config/project_settings.h" +#include "core/engine.h" #include "core/io/marshalls.h" #include "core/os/os.h" #include "scene_synchronizer.h" +#include "scene_synchronizer_debugger.h" #include +#include "godot_backward_utility_cpp.h" + #define METADATA_SIZE 1 - -#define MAX_ADDITIONAL_TICK_SPEED 2.0 - -// 2% -#define TICK_SPEED_CHANGE_NOTIF_THRESHOLD 4 +#define DOLL_EPOCH_METADATA_SIZE (DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_REAL, DataBuffer::COMPRESSION_LEVEL_1) + DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_INT, DataBuffer::COMPRESSION_LEVEL_1)) void NetworkedController::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_server_controlled", "server_controlled"), &NetworkedController::set_server_controlled); + ClassDB::bind_method(D_METHOD("get_server_controlled"), &NetworkedController::get_server_controlled); + ClassDB::bind_method(D_METHOD("set_player_input_storage_size", "size"), &NetworkedController::set_player_input_storage_size); ClassDB::bind_method(D_METHOD("get_player_input_storage_size"), &NetworkedController::get_player_input_storage_size); ClassDB::bind_method(D_METHOD("set_max_redundant_inputs", "max_redundant_inputs"), &NetworkedController::set_max_redundant_inputs); ClassDB::bind_method(D_METHOD("get_max_redundant_inputs"), &NetworkedController::get_max_redundant_inputs); - ClassDB::bind_method(D_METHOD("set_tick_speedup_notification_delay", "tick_speedup_notification_delay"), &NetworkedController::set_tick_speedup_notification_delay); + ClassDB::bind_method(D_METHOD("set_tick_speedup_notification_delay", "delay_in_ms"), &NetworkedController::set_tick_speedup_notification_delay); ClassDB::bind_method(D_METHOD("get_tick_speedup_notification_delay"), &NetworkedController::get_tick_speedup_notification_delay); ClassDB::bind_method(D_METHOD("set_network_traced_frames", "size"), &NetworkedController::set_network_traced_frames); @@ -68,39 +68,30 @@ void NetworkedController::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_frames_delay", "val"), &NetworkedController::set_max_frames_delay); ClassDB::bind_method(D_METHOD("get_max_frames_delay"), &NetworkedController::get_max_frames_delay); - ClassDB::bind_method(D_METHOD("set_net_sensitivity", "val"), &NetworkedController::set_net_sensitivity); - ClassDB::bind_method(D_METHOD("get_net_sensitivity"), &NetworkedController::get_net_sensitivity); - ClassDB::bind_method(D_METHOD("set_tick_acceleration", "acceleration"), &NetworkedController::set_tick_acceleration); ClassDB::bind_method(D_METHOD("get_tick_acceleration"), &NetworkedController::get_tick_acceleration); - ClassDB::bind_method(D_METHOD("set_doll_epoch_collect_rate", "rate"), &NetworkedController::set_doll_epoch_collect_rate); - ClassDB::bind_method(D_METHOD("get_doll_epoch_collect_rate"), &NetworkedController::get_doll_epoch_collect_rate); + ClassDB::bind_method(D_METHOD("set_doll_sync_rate", "rate"), &NetworkedController::set_doll_sync_rate); + ClassDB::bind_method(D_METHOD("get_doll_sync_rate"), &NetworkedController::get_doll_sync_rate); - ClassDB::bind_method(D_METHOD("set_doll_epoch_batch_sync_rate", "rate"), &NetworkedController::set_doll_epoch_batch_sync_rate); - ClassDB::bind_method(D_METHOD("get_doll_epoch_batch_sync_rate"), &NetworkedController::get_doll_epoch_batch_sync_rate); - - ClassDB::bind_method(D_METHOD("set_doll_min_frames_delay", "traced"), &NetworkedController::set_doll_min_frames_delay); + ClassDB::bind_method(D_METHOD("set_doll_min_frames_delay", "delay"), &NetworkedController::set_doll_min_frames_delay); ClassDB::bind_method(D_METHOD("get_doll_min_frames_delay"), &NetworkedController::get_doll_min_frames_delay); - ClassDB::bind_method(D_METHOD("set_doll_max_frames_delay", "sensitivity"), &NetworkedController::set_doll_max_frames_delay); + ClassDB::bind_method(D_METHOD("set_doll_max_frames_delay", "delay"), &NetworkedController::set_doll_max_frames_delay); ClassDB::bind_method(D_METHOD("get_doll_max_frames_delay"), &NetworkedController::get_doll_max_frames_delay); - ClassDB::bind_method(D_METHOD("set_doll_interpolation_max_speedup", "speedup"), &NetworkedController::set_doll_interpolation_max_speedup); - ClassDB::bind_method(D_METHOD("get_doll_interpolation_max_speedup"), &NetworkedController::get_doll_interpolation_max_speedup); - - ClassDB::bind_method(D_METHOD("set_doll_connection_stats_frame_span", "speedup"), &NetworkedController::set_doll_connection_stats_frame_span); - ClassDB::bind_method(D_METHOD("get_doll_connection_stats_frame_span"), &NetworkedController::get_doll_connection_stats_frame_span); - ClassDB::bind_method(D_METHOD("set_doll_net_sensitivity", "sensitivity"), &NetworkedController::set_doll_net_sensitivity); ClassDB::bind_method(D_METHOD("get_doll_net_sensitivity"), &NetworkedController::get_doll_net_sensitivity); - ClassDB::bind_method(D_METHOD("set_doll_max_delay", "max_delay"), &NetworkedController::set_doll_max_delay); - ClassDB::bind_method(D_METHOD("get_doll_max_delay"), &NetworkedController::get_doll_max_delay); + ClassDB::bind_method(D_METHOD("set_doll_interpolation_max_overshot", "speedup"), &NetworkedController::set_doll_interpolation_max_overshot); + ClassDB::bind_method(D_METHOD("get_doll_interpolation_max_overshot"), &NetworkedController::get_doll_interpolation_max_overshot); + + ClassDB::bind_method(D_METHOD("set_doll_connection_stats_frame_span", "speedup"), &NetworkedController::set_doll_connection_stats_frame_span); + ClassDB::bind_method(D_METHOD("get_doll_connection_stats_frame_span"), &NetworkedController::get_doll_connection_stats_frame_span); ClassDB::bind_method(D_METHOD("get_current_input_id"), &NetworkedController::get_current_input_id); - ClassDB::bind_method(D_METHOD("player_get_pretended_delta", "physics_ticks_per_seconds"), &NetworkedController::player_get_pretended_delta); + ClassDB::bind_method(D_METHOD("player_get_pretended_delta"), &NetworkedController::player_get_pretended_delta); ClassDB::bind_method(D_METHOD("mark_epoch_as_important"), &NetworkedController::mark_epoch_as_important); @@ -109,7 +100,8 @@ void NetworkedController::_bind_methods() { ClassDB::bind_method(D_METHOD("set_doll_peer_active", "peer_id", "active"), &NetworkedController::set_doll_peer_active); ClassDB::bind_method(D_METHOD("_rpc_server_send_inputs"), &NetworkedController::_rpc_server_send_inputs); - ClassDB::bind_method(D_METHOD("_rpc_send_tick_additional_speed"), &NetworkedController::_rpc_send_tick_additional_speed); + ClassDB::bind_method(D_METHOD("_rpc_set_server_controlled"), &NetworkedController::_rpc_set_server_controlled); + ClassDB::bind_method(D_METHOD("_rpc_notify_fps_acceleration"), &NetworkedController::_rpc_notify_fps_acceleration); ClassDB::bind_method(D_METHOD("_rpc_doll_notify_sync_pause"), &NetworkedController::_rpc_doll_notify_sync_pause); ClassDB::bind_method(D_METHOD("_rpc_doll_send_epoch_batch"), &NetworkedController::_rpc_doll_send_epoch_batch); @@ -120,41 +112,121 @@ void NetworkedController::_bind_methods() { ClassDB::bind_method(D_METHOD("__on_sync_paused"), &NetworkedController::__on_sync_paused); - BIND_VMETHOD(MethodInfo("_collect_inputs", PropertyInfo(Variant::REAL, "delta"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); - BIND_VMETHOD(MethodInfo("_controller_process", PropertyInfo(Variant::REAL, "delta"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo("_collect_inputs", PropertyInfo(Variant::FLOAT, "delta"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + BIND_VMETHOD(MethodInfo("_controller_process", PropertyInfo(Variant::FLOAT, "delta"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); BIND_VMETHOD(MethodInfo(Variant::BOOL, "_are_inputs_different", PropertyInfo(Variant::OBJECT, "inputs_A", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"), PropertyInfo(Variant::OBJECT, "inputs_B", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); BIND_VMETHOD(MethodInfo(Variant::INT, "_count_input_size", PropertyInfo(Variant::OBJECT, "inputs", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); BIND_VMETHOD(MethodInfo("_collect_epoch_data", PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); - BIND_VMETHOD(MethodInfo("_setup_interpolator", PropertyInfo(Variant::OBJECT, "interpolator", PROPERTY_HINT_RESOURCE_TYPE, "Interpolator"))); - BIND_VMETHOD(MethodInfo("_parse_epoch_data", PropertyInfo(Variant::OBJECT, "interpolator", PROPERTY_HINT_RESOURCE_TYPE, "Interpolator"), PropertyInfo(Variant::OBJECT, "buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); - BIND_VMETHOD(MethodInfo("_apply_epoch", PropertyInfo(Variant::REAL, "delta"), PropertyInfo(Variant::ARRAY, "interpolated_data"))); + BIND_VMETHOD(MethodInfo("_apply_epoch", PropertyInfo(Variant::FLOAT, "delta"), PropertyInfo(Variant::FLOAT, "interpolation_alpha"), PropertyInfo(Variant::OBJECT, "past_buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"), PropertyInfo(Variant::OBJECT, "future_buffer", PROPERTY_HINT_RESOURCE_TYPE, "DataBuffer"))); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_controlled"), "set_server_controlled", "get_server_controlled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "input_storage_size", PROPERTY_HINT_RANGE, "5,2000,1"), "set_player_input_storage_size", "get_player_input_storage_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redundant_inputs", PROPERTY_HINT_RANGE, "0,1000,1"), "set_max_redundant_inputs", "get_max_redundant_inputs"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "tick_speedup_notification_delay", PROPERTY_HINT_RANGE, "0.001,2.0,0.001"), "set_tick_speedup_notification_delay", "get_tick_speedup_notification_delay"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tick_speedup_notification_delay", PROPERTY_HINT_RANGE, "0,5000,1"), "set_tick_speedup_notification_delay", "get_tick_speedup_notification_delay"); ADD_PROPERTY(PropertyInfo(Variant::INT, "network_traced_frames", PROPERTY_HINT_RANGE, "1,1000,1"), "set_network_traced_frames", "get_network_traced_frames"); ADD_PROPERTY(PropertyInfo(Variant::INT, "min_frames_delay", PROPERTY_HINT_RANGE, "0,100,1"), "set_min_frames_delay", "get_min_frames_delay"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_frames_delay", PROPERTY_HINT_RANGE, "0,100,1"), "set_max_frames_delay", "get_max_frames_delay"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "net_sensitivity", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_net_sensitivity", "get_net_sensitivity"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "tick_acceleration", PROPERTY_HINT_RANGE, "0.1,20.0,0.01"), "set_tick_acceleration", "get_tick_acceleration"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_epoch_collect_rate", PROPERTY_HINT_RANGE, "1,100,1"), "set_doll_epoch_collect_rate", "get_doll_epoch_collect_rate"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "doll_epoch_batch_sync_rate", PROPERTY_HINT_RANGE, "0.01,5.0,0.01"), "set_doll_epoch_batch_sync_rate", "get_doll_epoch_batch_sync_rate"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_min_frames_delay", PROPERTY_HINT_RANGE, "0,10,1"), "set_doll_min_frames_delay", "get_doll_min_frames_delay"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_max_frames_delay", PROPERTY_HINT_RANGE, "0,10,1"), "set_doll_max_frames_delay", "get_doll_max_frames_delay"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "doll_interpolation_max_speedup", PROPERTY_HINT_RANGE, "0.01,5.0,0.01"), "set_doll_interpolation_max_speedup", "get_doll_interpolation_max_speedup"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_connection_stats_frame_span", PROPERTY_HINT_RANGE, "0,1000,1"), "set_doll_connection_stats_frame_span", "get_doll_connection_stats_frame_span"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "doll_net_sensitivity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_doll_net_sensitivity", "get_doll_net_sensitivity"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_max_delay", PROPERTY_HINT_RANGE, "1,1000,1"), "set_doll_max_delay", "get_doll_max_delay"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tick_acceleration", PROPERTY_HINT_RANGE, "0.1,20.0,0.01"), "set_tick_acceleration", "get_tick_acceleration"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_sync_rate", PROPERTY_HINT_RANGE, "1,240,1"), "set_doll_sync_rate", "get_doll_sync_rate"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_min_frames_delay", PROPERTY_HINT_RANGE, "0,240,1"), "set_doll_min_frames_delay", "get_doll_min_frames_delay"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_max_frames_delay", PROPERTY_HINT_RANGE, "0,240,1"), "set_doll_max_frames_delay", "get_doll_max_frames_delay"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "doll_net_sensitivity", PROPERTY_HINT_RANGE, "0,1.0,0.00001"), "set_doll_net_sensitivity", "get_doll_net_sensitivity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "doll_interpolation_max_overshot", PROPERTY_HINT_RANGE, "0.01,5.0,0.01"), "set_doll_interpolation_max_overshot", "get_doll_interpolation_max_overshot"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "doll_connection_stats_frame_span", PROPERTY_HINT_RANGE, "1,1000,1"), "set_doll_connection_stats_frame_span", "get_doll_connection_stats_frame_span"); ADD_SIGNAL(MethodInfo("doll_sync_started")); ADD_SIGNAL(MethodInfo("doll_sync_paused")); + ADD_SIGNAL(MethodInfo("controller_reset")); + ADD_SIGNAL(MethodInfo("input_missed", PropertyInfo(Variant::INT, "missing_input_id"))); + ADD_SIGNAL(MethodInfo("client_speedup_adjusted", PropertyInfo(Variant::INT, "input_worst_receival_time_ms"), PropertyInfo(Variant::INT, "optimal_frame_delay"), PropertyInfo(Variant::INT, "current_frame_delay"), PropertyInfo(Variant::INT, "distance_to_optimal"))); } NetworkedController::NetworkedController() { - rpc_config("_rpc_server_send_inputs", MultiplayerAPI::RPC_MODE_REMOTE); - rpc_config("_rpc_send_tick_additional_speed", MultiplayerAPI::RPC_MODE_REMOTE); - rpc_config("_rpc_doll_notify_sync_pause", MultiplayerAPI::RPC_MODE_REMOTE); - rpc_config("_rpc_doll_send_epoch_batch", MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_server_send_inputs"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_set_server_controlled"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_notify_fps_acceleration"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_doll_notify_sync_pause"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_doll_send_epoch_batch"), MultiplayerAPI::RPC_MODE_REMOTE); +} + +NetworkedController::~NetworkedController() { + if (controller != nullptr) { + memdelete(controller); + controller = nullptr; + controller_type = CONTROLLER_TYPE_NULL; + } +} + +void NetworkedController::set_server_controlled(bool p_server_controlled) { + if (server_controlled == p_server_controlled) { + // It's the same, nothing to do. + return; + } + + if (is_networking_initialized()) { + if (is_server_controller()) { + // This is the server, let's start the procedure to switch controll mode. + +#ifdef DEBUG_ENABLED + CRASH_COND_MSG(scene_synchronizer == nullptr, "When the `NetworkedController` is a server, the `scene_synchronizer` is always set."); +#endif + + // First update the variable. + server_controlled = p_server_controlled; + + // Notify the `SceneSynchronizer` about it. + scene_synchronizer->notify_controller_control_mode_changed(this); + + // Tell the client to do the switch too. + if (get_network_master() != 1) { + rpc_id( + get_network_master(), + SNAME("_rpc_set_server_controlled"), + server_controlled); + } else { + SceneSynchronizerDebugger::singleton()->debug_warning(this, "The node is owned by the server, there is no client that can control it; please assign the proper authority."); + } + + } else if (is_player_controller() || is_doll_controller()) { + SceneSynchronizerDebugger::singleton()->debug_warning(this, "You should never call the function `set_server_controlled` on the client, this has an effect only if called on the server."); + + } else if (is_nonet_controller()) { + // There is no networking, the same instance is both the client and the + // server already, nothing to do. + server_controlled = p_server_controlled; + + } else { +#ifdef DEBUG_ENABLED + CRASH_NOW_MSG("Unreachable, all the cases are handled."); +#endif + } + } else { + // This called during initialization or on the editor, nothing special just + // set it. + server_controlled = p_server_controlled; + } + +#ifdef DEBUG_ENABLED + if (has_method("_collect_inputs") == false && server_controlled == false) { + WARN_PRINT("In your script you must inherit the virtual method `_collect_inputs` to correctly use the `NetworkedController`."); + } + + if (has_method("_controller_process") == false && server_controlled == false) { + WARN_PRINT("In your script you must inherit the virtual method `_controller_process` to correctly use the `NetworkedController`."); + } + + if (has_method("_are_inputs_different") == false && server_controlled == false) { + WARN_PRINT("In your script you must inherit the virtual method `_are_inputs_different` to correctly use the `NetworkedController`."); + } + + if (has_method("_count_input_size") == false && server_controlled == false) { + WARN_PRINT("In your script you must inherit the virtual method `_count_input_size` to correctly use the `NetworkedController`."); + } +#endif +} + +bool NetworkedController::get_server_controlled() const { + return server_controlled; } void NetworkedController::set_player_input_storage_size(int p_size) { @@ -173,11 +245,11 @@ int NetworkedController::get_max_redundant_inputs() const { return max_redundant_inputs; } -void NetworkedController::set_tick_speedup_notification_delay(real_t p_delay) { +void NetworkedController::set_tick_speedup_notification_delay(int p_delay) { tick_speedup_notification_delay = p_delay; } -real_t NetworkedController::get_tick_speedup_notification_delay() const { +int NetworkedController::get_tick_speedup_notification_delay() const { return tick_speedup_notification_delay; } @@ -205,36 +277,20 @@ int NetworkedController::get_max_frames_delay() const { return max_frames_delay; } -void NetworkedController::set_net_sensitivity(real_t p_val) { - net_sensitivity = p_val; -} - -real_t NetworkedController::get_net_sensitivity() const { - return net_sensitivity; -} - -void NetworkedController::set_tick_acceleration(real_t p_acceleration) { +void NetworkedController::set_tick_acceleration(double p_acceleration) { tick_acceleration = p_acceleration; } -real_t NetworkedController::get_tick_acceleration() const { +double NetworkedController::get_tick_acceleration() const { return tick_acceleration; } -void NetworkedController::set_doll_epoch_collect_rate(int p_rate) { - doll_epoch_collect_rate = MAX(p_rate, 1); +void NetworkedController::set_doll_sync_rate(uint32_t p_rate) { + doll_sync_rate = p_rate; } -int NetworkedController::get_doll_epoch_collect_rate() const { - return doll_epoch_collect_rate; -} - -void NetworkedController::set_doll_epoch_batch_sync_rate(real_t p_rate) { - doll_epoch_batch_sync_rate = MAX(p_rate, 0.001); -} - -real_t NetworkedController::get_doll_epoch_batch_sync_rate() const { - return doll_epoch_batch_sync_rate; +uint32_t NetworkedController::get_doll_sync_rate() const { + return doll_sync_rate; } void NetworkedController::set_doll_min_frames_delay(int p_min) { @@ -253,22 +309,6 @@ int NetworkedController::get_doll_max_frames_delay() const { return doll_max_frames_delay; } -void NetworkedController::set_doll_interpolation_max_speedup(real_t p_speedup) { - doll_interpolation_max_speedup = p_speedup; -} - -real_t NetworkedController::get_doll_interpolation_max_speedup() const { - return doll_interpolation_max_speedup; -} - -void NetworkedController::set_doll_connection_stats_frame_span(int p_span) { - doll_connection_stats_frame_span = p_span; -} - -int NetworkedController::get_doll_connection_stats_frame_span() const { - return doll_connection_stats_frame_span; -} - void NetworkedController::set_doll_net_sensitivity(real_t p_sensitivity) { doll_net_sensitivity = p_sensitivity; } @@ -277,12 +317,20 @@ real_t NetworkedController::get_doll_net_sensitivity() const { return doll_net_sensitivity; } -void NetworkedController::set_doll_max_delay(uint32_t p_max_delay) { - doll_max_delay = p_max_delay; +void NetworkedController::set_doll_interpolation_max_overshot(real_t p_speedup) { + doll_interpolation_max_overshot = p_speedup; } -uint32_t NetworkedController::get_doll_max_delay() const { - return doll_max_delay; +real_t NetworkedController::get_doll_interpolation_max_overshot() const { + return doll_interpolation_max_overshot; +} + +void NetworkedController::set_doll_connection_stats_frame_span(int p_span) { + doll_connection_stats_frame_span = MAX(1, p_span); +} + +int NetworkedController::get_doll_connection_stats_frame_span() const { + return doll_connection_stats_frame_span; } uint32_t NetworkedController::get_current_input_id() const { @@ -290,9 +338,9 @@ uint32_t NetworkedController::get_current_input_id() const { return controller->get_current_input_id(); } -real_t NetworkedController::player_get_pretended_delta(uint32_t p_physics_ticks_per_seconds) const { - ERR_FAIL_COND_V_MSG(is_player_controller() == false, 1.0 / real_t(p_physics_ticks_per_seconds), "This function can be called only on client."); - return get_player_controller()->get_pretended_delta(p_physics_ticks_per_seconds); +real_t NetworkedController::player_get_pretended_delta() const { + ERR_FAIL_COND_V_MSG(is_player_controller() == false, 1.0, "This function can be called only on client."); + return get_player_controller()->pretended_delta; } void NetworkedController::mark_epoch_as_important() { @@ -308,7 +356,7 @@ void NetworkedController::set_doll_collect_rate_factor(int p_peer, real_t p_fact // This peers seems disabled, nothing to do here. return; } - server_controller->peers[pos].update_rate_factor = CLAMP(p_factor, 0.001, 1.0); + server_controller->peers[pos].doll_sync_rate_factor = CLAMP(p_factor, 0.001, 1.0); } void NetworkedController::set_doll_peer_active(int p_peer_id, bool p_active) { @@ -327,12 +375,15 @@ void NetworkedController::set_doll_peer_active(int p_peer_id, bool p_active) { } server_controller->peers[pos].active = p_active; - server_controller->peers[pos].collect_timer = 0.0; + // Reset the sync timer. + server_controller->peers[pos].doll_sync_timer = 0.0; + // Set to 0 so we sync immediately + server_controller->peers[pos].doll_sync_time_threshold = 0.0; if (p_active == false) { // Notify the doll only for deactivations. The activations are automatically // handled when the first epoch is received. - rpc_id(p_peer_id, "_rpc_doll_notify_sync_pause", server_controller->epoch); + rpc_id(p_peer_id, SNAME("_rpc_doll_notify_sync_pause"), server_controller->epoch); } } @@ -344,11 +395,67 @@ void NetworkedController::pause_notify_dolls() { for (uint32_t i = 0; i < server_controller->peers.size(); i += 1) { if (server_controller->peers[i].active) { // Notify this actor is no more active. - rpc_id(server_controller->peers[i].peer, "_rpc_doll_notify_sync_pause", server_controller->epoch); + rpc_id(server_controller->peers[i].peer, SNAME("_rpc_doll_notify_sync_pause"), server_controller->epoch); } } } +void NetworkedController::validate_script_implementation() { + ERR_FAIL_COND_MSG(has_method("_collect_inputs") == false && server_controlled == false, "In your script you must inherit the virtual method `_collect_inputs` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_controller_process") == false && server_controlled == false, "In your script you must inherit the virtual method `_controller_process` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_are_inputs_different") == false && server_controlled == false, "In your script you must inherit the virtual method `_are_inputs_different` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_count_input_size") == false && server_controlled == false, "In your script you must inherit the virtual method `_count_input_size` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_collect_epoch_data") == false, "In your script you must inherit the virtual method `_collect_epoch_data` to correctly use the `NetworkedController`."); + ERR_FAIL_COND_MSG(has_method("_apply_epoch") == false, "In your script you must inherit the virtual method `_apply_epoch` to correctly use the `NetworkedController`."); +} + +void NetworkedController::native_collect_inputs(double p_delta, DataBuffer &r_buffer) { + PROFILE_NODE + + call( + SNAME("_collect_inputs"), + p_delta, + &r_buffer); +} + +void NetworkedController::native_controller_process(double p_delta, DataBuffer &p_buffer) { + PROFILE_NODE + + call( + SNAME("_controller_process"), + p_delta, + &p_buffer); +} + +bool NetworkedController::native_are_inputs_different(DataBuffer &p_buffer_A, DataBuffer &p_buffer_B) { + PROFILE_NODE + + return call(SNAME("_are_inputs_different"), &p_buffer_A, &p_buffer_B); +} + +uint32_t NetworkedController::native_count_input_size(DataBuffer &p_buffer) { + PROFILE_NODE + + return call(SNAME("_count_input_size"), &p_buffer); +} + +void NetworkedController::native_collect_epoch_data(DataBuffer &r_buffer) { + PROFILE_NODE + + call(SNAME("_collect_epoch_data"), &r_buffer); +} + +void NetworkedController::native_apply_epoch(double p_delta, real_t p_interpolation_alpha, DataBuffer &p_past_buffer, DataBuffer &p_future_buffer) { + PROFILE_NODE + + call( + SNAME("_apply_epoch"), + p_delta, + p_interpolation_alpha, + &p_past_buffer, + &p_future_buffer); +} + bool NetworkedController::process_instant(int p_i, real_t p_delta) { ERR_FAIL_COND_V_MSG(is_player_controller() == false, false, "Can be executed only on player controllers."); return static_cast(controller)->process_instant(p_i, p_delta); @@ -394,8 +501,12 @@ const NoNetController *NetworkedController::get_nonet_controller() const { return static_cast(controller); } +bool NetworkedController::is_networking_initialized() const { + return controller_type != CONTROLLER_TYPE_NULL; +} + bool NetworkedController::is_server_controller() const { - return controller_type == CONTROLLER_TYPE_SERVER; + return controller_type == CONTROLLER_TYPE_SERVER || controller_type == CONTROLLER_TYPE_AUTONOMOUS_SERVER; } bool NetworkedController::is_player_controller() const { @@ -417,13 +528,13 @@ void NetworkedController::set_inputs_buffer(const BitArray &p_new_buffer, uint32 void NetworkedController::set_scene_synchronizer(SceneSynchronizer *p_synchronizer) { if (scene_synchronizer) { - scene_synchronizer->disconnect("sync_paused", this, "__on_sync_paused"); + scene_synchronizer->disconnect(SNAME("sync_paused"), Callable(this, SNAME("__on_sync_paused"))); } scene_synchronizer = p_synchronizer; if (scene_synchronizer) { - scene_synchronizer->connect("sync_paused", this, "__on_sync_paused"); + scene_synchronizer->connect(SNAME("sync_paused"), Callable(this, SNAME("__on_sync_paused"))); } } @@ -435,20 +546,53 @@ bool NetworkedController::has_scene_synchronizer() const { return scene_synchronizer; } -void NetworkedController::_rpc_server_send_inputs(const PoolVector &p_data) { +void NetworkedController::_rpc_server_send_inputs(const Vector &p_data) { ERR_FAIL_COND(is_server_controller() == false); static_cast(controller)->receive_inputs(p_data); } -void NetworkedController::_rpc_send_tick_additional_speed(const PoolVector &p_data) { +void NetworkedController::_rpc_set_server_controlled(bool p_server_controlled) { + ERR_FAIL_COND_MSG(is_player_controller() == false, "This function is supposed to be called on the server."); + server_controlled = p_server_controlled; + + ERR_FAIL_COND_MSG(scene_synchronizer == nullptr, "The server controller is supposed to be set on the client at this point."); + scene_synchronizer->notify_controller_control_mode_changed(this); +} + +void NetworkedController::_rpc_notify_fps_acceleration(const Vector &p_data) { ERR_FAIL_COND(is_player_controller() == false); ERR_FAIL_COND(p_data.size() != 1); - const uint8_t speed = p_data[0]; - const real_t additional_speed = MAX_ADDITIONAL_TICK_SPEED * (((static_cast(speed) / static_cast(UINT8_MAX)) - 0.5) / 0.5); + int8_t additional_frames_to_produce; + memcpy( + &additional_frames_to_produce, + &p_data[0], + sizeof(int8_t)); PlayerController *player_controller = static_cast(controller); - player_controller->tick_additional_speed = CLAMP(additional_speed, -MAX_ADDITIONAL_TICK_SPEED, MAX_ADDITIONAL_TICK_SPEED); + + // Slowdown the acceleration when near the target. + player_controller->acceleration_fps_speed = CLAMP(double(additional_frames_to_produce) / get_tick_acceleration(), -1.0, 1.0) * get_tick_acceleration(); + const double acceleration_fps_speed_ABS = ABS(player_controller->acceleration_fps_speed); + + if (acceleration_fps_speed_ABS >= CMP_EPSILON2) { + const double acceleration_time = double(ABS(additional_frames_to_produce)) / acceleration_fps_speed_ABS; + player_controller->acceleration_fps_timer = acceleration_time; + } else { + player_controller->acceleration_fps_timer = 0.0; + } + +#ifdef DEBUG_ENABLED + const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_server_speedup"); + if (debug) { + print_line( + String() + + "Client received speedup." + + " Frames to produce: `" + itos(additional_frames_to_produce) + "`" + + " Acceleration fps: `" + rtos(player_controller->acceleration_fps_speed) + "`" + + " Acceleration time: `" + rtos(player_controller->acceleration_fps_timer) + "`"); + } +#endif } void NetworkedController::_rpc_doll_notify_sync_pause(uint32_t p_epoch) { @@ -457,11 +601,11 @@ void NetworkedController::_rpc_doll_notify_sync_pause(uint32_t p_epoch) { static_cast(controller)->pause(p_epoch); } -void NetworkedController::_rpc_doll_send_epoch_batch(const PoolVector &p_data) { +void NetworkedController::_rpc_doll_send_epoch_batch(const Vector &p_data) { ERR_FAIL_COND_MSG(is_doll_controller() == false, "Only dolls are supposed to receive this function call."); ERR_FAIL_COND_MSG(p_data.size() <= 0, "It's not supposed to receive a 0 size data."); - static_cast(controller)->receive_batch(p_data); + static_cast(controller)->receive_epoch(p_data); } void NetworkedController::player_set_has_new_input(bool p_has) { @@ -490,7 +634,9 @@ void NetworkedController::_notification(int p_what) { // This can't happen, since only the doll are processed here. CRASH_COND(is_doll_controller() == false); #endif - static_cast(controller)->process(get_physics_process_delta_time()); + const double physics_ticks_per_second = Engine::get_singleton()->get_iterations_per_second(); + const double delta = 1.0 / physics_ticks_per_second; + static_cast(controller)->process(delta); } break; #ifdef DEBUG_ENABLED @@ -499,51 +645,58 @@ void NetworkedController::_notification(int p_what) { return; } - ERR_FAIL_COND_MSG(has_method("_collect_inputs") == false, "In your script you must inherit the virtual method `_collect_inputs` to correctly use the `NetworkedController`."); - ERR_FAIL_COND_MSG(has_method("_controller_process") == false, "In your script you must inherit the virtual method `_controller_process` to correctly use the `NetworkedController`."); - ERR_FAIL_COND_MSG(has_method("_are_inputs_different") == false, "In your script you must inherit the virtual method `_are_inputs_different` to correctly use the `NetworkedController`."); - ERR_FAIL_COND_MSG(has_method("_count_input_size") == false, "In your script you must inherit the virtual method `_count_input_size` to correctly use the `NetworkedController`."); - ERR_FAIL_COND_MSG(has_method("_collect_epoch_data") == false, "In your script you must inherit the virtual method `_collect_epoch_data` to correctly use the `NetworkedController`."); - ERR_FAIL_COND_MSG(has_method("_setup_interpolator") == false, "In your script you must inherit the virtual method `_setup_interpolator` to correctly use the `NetworkedController`."); - ERR_FAIL_COND_MSG(has_method("_parse_epoch_data") == false, "In your script you must inherit the virtual method `_parse_epoch_data` to correctly use the `NetworkedController`."); - ERR_FAIL_COND_MSG(has_method("_apply_epoch") == false, "In your script you must inherit the virtual method `_apply_epoch` to correctly use the `NetworkedController`."); + validate_script_implementation(); } break; #endif } } +void NetworkedController::notify_controller_reset() { + emit_signal("controller_reset"); +} + ServerController::ServerController( NetworkedController *p_node, int p_traced_frames) : Controller(p_node), - network_watcher(p_traced_frames, 0) { + network_watcher(p_traced_frames, 0), + consecutive_input_watcher(p_traced_frames, 0) { } -void ServerController::process(real_t p_delta) { +void ServerController::process(double p_delta) { if (unlikely(enabled == false)) { // Disabled by the SceneSynchronizer. return; } - fetch_next_input(); + const bool is_new_input = fetch_next_input(p_delta); if (unlikely(current_input_buffer_id == UINT32_MAX)) { // Skip this until the first input arrive. + SceneSynchronizerDebugger::singleton()->debug_print(node, "Server skips this frame as the current_input_buffer_id == UINT32_MAX", true); return; } + +#ifdef DEBUG_ENABLED + if (!is_new_input) { + node->emit_signal("input_missed", current_input_buffer_id + 1); + } +#endif + + SceneSynchronizerDebugger::singleton()->debug_print(node, "Server process index: " + itos(current_input_buffer_id), true); node->get_inputs_buffer_mut().begin_read(); node->get_inputs_buffer_mut().seek(METADATA_SIZE); - node->call( - "_controller_process", + SceneSynchronizerDebugger::singleton()->databuffer_operation_begin_record(node, SceneSynchronizerDebugger::READ); + node->native_controller_process( p_delta, - &node->get_inputs_buffer_mut()); + node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->databuffer_operation_end_record(); doll_sync(p_delta); if (streaming_paused == false) { - calculates_player_tick_rate(p_delta); adjust_player_tick_rate(p_delta); } } @@ -576,15 +729,14 @@ void ServerController::set_enabled(bool p_enable) { // Client inputs reset. ghost_input_count = 0; last_sent_state_input_id = 0; - client_tick_additional_speed = 0.0; - additional_speed_notif_timer = 0.0; + additional_fps_notif_timer = 0.0; snapshots.clear(); - input_arrival_time = UINT32_MAX; + previous_frame_received_timestamp = UINT32_MAX; network_watcher.reset(0.0); + consecutive_input_watcher.reset(0.0); // Doll reset. is_epoch_important = false; - batch_sync_timer = 0.0; } void ServerController::clear_peers() { @@ -616,7 +768,7 @@ void ServerController::deactivate_peer(int p_peer) { } } -void ServerController::receive_inputs(const PoolVector &p_data) { +void ServerController::receive_inputs(const Vector &p_data) { // The packet is composed as follow: // |- The following four bytes for the first input ID. // \- Array of inputs: @@ -626,16 +778,13 @@ void ServerController::receive_inputs(const PoolVector &p_data) { // Let's decode it! const uint32_t now = OS::get_singleton()->get_ticks_msec(); - // If now is bigger, then the timer has been disabled, so we assume 0. - network_watcher.push(now > input_arrival_time ? now - input_arrival_time : 0); - input_arrival_time = now; const int data_len = p_data.size(); int ofs = 0; ERR_FAIL_COND(data_len < 4); - const uint32_t first_input_id = decode_uint32(p_data.read().ptr() + ofs); + const uint32_t first_input_id = decode_uint32(p_data.ptr() + ofs); ofs += 4; uint32_t inserted_input_count = 0; @@ -662,7 +811,7 @@ void ServerController::receive_inputs(const PoolVector &p_data) { // Read metadata const bool has_data = pir.read_bool(); - const int input_size_in_bits = (has_data ? int(node->call("_count_input_size", &pir)) : 0) + METADATA_SIZE; + const int input_size_in_bits = (has_data ? int(node->native_count_input_size(pir)) : 0) + METADATA_SIZE; // Pad to 8 bits. const int input_size_padded = Math::ceil((static_cast(input_size_in_bits)) / 8.0); @@ -690,9 +839,10 @@ void ServerController::receive_inputs(const PoolVector &p_data) { if (found == false) { rfs.buffer_size_bit = input_size_in_bits; rfs.inputs_buffer.get_bytes_mut().resize(input_size_padded); + rfs.received_timestamp = now; memcpy( - rfs.inputs_buffer.get_bytes_mut().write().ptr(), - p_data.read().ptr() + ofs, + rfs.inputs_buffer.get_bytes_mut().ptrw(), + p_data.ptr() + ofs, input_size_padded); snapshots.push_back(rfs); @@ -724,38 +874,42 @@ int ServerController::get_inputs_count() const { return snapshots.size(); } -bool ServerController::fetch_next_input() { +bool ServerController::fetch_next_input(real_t p_delta) { bool is_new_input = true; if (unlikely(current_input_buffer_id == UINT32_MAX)) { // As initial packet, anything is good. if (snapshots.empty() == false) { // First input arrived. - node->set_inputs_buffer(snapshots.front().inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); - current_input_buffer_id = snapshots.front().id; + set_frame_input(snapshots.front()); snapshots.pop_front(); // Start tracing the packets from this moment on. network_watcher.reset(0); - input_arrival_time = UINT32_MAX; + consecutive_input_watcher.reset(0.0); + previous_frame_received_timestamp = UINT32_MAX; + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] Input `" + itos(current_input_buffer_id) + "` selected as first input.", true); } else { is_new_input = false; + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] Still no inputs.", true); } } else { const uint32_t next_input_id = current_input_buffer_id + 1; + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The server is looking for: " + itos(next_input_id), true); if (unlikely(streaming_paused)) { + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The streaming is paused.", true); // Stream is paused. if (snapshots.empty() == false && snapshots.front().id >= next_input_id) { // A new input is arrived while the streaming is paused. const bool is_buffer_void = (snapshots.front().buffer_size_bit - METADATA_SIZE) == 0; streaming_paused = is_buffer_void; - node->set_inputs_buffer(snapshots.front().inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); - current_input_buffer_id = snapshots.front().id; + set_frame_input(snapshots.front()); is_new_input = true; snapshots.pop_front(); network_watcher.reset(0); - input_arrival_time = UINT32_MAX; + consecutive_input_watcher.reset(0.0); + previous_frame_received_timestamp = UINT32_MAX; } else { // No inputs, or we are not yet arrived to the client input, // so just pretend the next input is void. @@ -764,16 +918,20 @@ bool ServerController::fetch_next_input() { } } else if (unlikely(snapshots.empty() == true)) { // The input buffer is empty; a packet is missing. + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] Missing input: " + itos(next_input_id) + " Input buffer is void, i'm using the previous one!"); + is_new_input = false; ghost_input_count += 1; - NET_DEBUG_PRINT("Input buffer is void, i'm using the previous one!"); } else { + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The input buffer is not empty, so looking for the next input. Hopefully `" + itos(next_input_id) + "`", true); + // The input buffer is not empty, search the new input. if (next_input_id == snapshots.front().id) { + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The input `" + itos(next_input_id) + "` was found.", true); + // Wow, the next input is perfect! - node->set_inputs_buffer(snapshots.front().inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); - current_input_buffer_id = snapshots.front().id; + set_frame_input(snapshots.front()); snapshots.pop_front(); ghost_input_count = 0; @@ -789,7 +947,7 @@ bool ServerController::fetch_next_input() { // the previous one and increase `ghost_inputs_count` to 1. // // The next iteration, if the packet is not yet arrived the - // server tries to take the next packet with the `id` less or + // server trys to take the next packet with the `id` less or // equal to `next_packet_id + ghost_packet_id`. // // As you can see the server doesn't lose immediately the hope @@ -813,7 +971,8 @@ bool ServerController::fetch_next_input() { // For this reason we keep track the amount of missing packets // using `ghost_input_count`. - ghost_input_count += 1; + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The input `" + itos(next_input_id) + "` was NOT found. Recovering process started.", true); + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] ghost_input_count: `" + itos(ghost_input_count) + "`", true); const int size = MIN(ghost_input_count, snapshots.size()); const uint32_t ghost_packet_id = next_input_id + ghost_input_count; @@ -824,9 +983,15 @@ bool ServerController::fetch_next_input() { DataBuffer pir_A = node->get_inputs_buffer(); for (int i = 0; i < size; i += 1) { + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] checking if `" + itos(snapshots.front().id) + "` can be used to recover `" + itos(next_input_id) + "`.", true); + if (ghost_packet_id < snapshots.front().id) { + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The input `" + itos(snapshots.front().id) + "` can't be used as the ghost_packet_id (`" + itos(ghost_packet_id) + "`) is more than the input.", true); break; } else { + const uint32_t input_id = snapshots.front().id; + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The input `" + itos(input_id) + "` is eligible as next frame.", true); + pi = snapshots.front(); snapshots.pop_front(); recovered = true; @@ -845,21 +1010,22 @@ bool ServerController::fetch_next_input() { pir_B.begin_read(); pir_B.seek(METADATA_SIZE); - const bool is_meaningful = node->call("_are_inputs_different", &pir_A, &pir_B); + const bool is_meaningful = node->native_are_inputs_different(pir_A, pir_B); if (is_meaningful) { + SceneSynchronizerDebugger::singleton()->debug_print(node, "[ServerController::fetch_next_input] The input `" + itos(input_id) + "` is different from the one executed so far, so better to execute it.", true); break; } } } if (recovered) { - node->set_inputs_buffer(pi.inputs_buffer, METADATA_SIZE, snapshots.front().buffer_size_bit - METADATA_SIZE); - current_input_buffer_id = pi.id; + set_frame_input(pi); ghost_input_count = 0; - NET_DEBUG_PRINT("Packet recovered"); + SceneSynchronizerDebugger::singleton()->debug_print(node, "Packet recovered. The new InputID is: `" + itos(current_input_buffer_id) + "`"); } else { + ghost_input_count += 1; is_new_input = false; - NET_DEBUG_PRINT("Packet still missing"); + SceneSynchronizerDebugger::singleton()->debug_print(node, "Packet still missing, the server is still using the old input."); } } } @@ -875,6 +1041,26 @@ bool ServerController::fetch_next_input() { return is_new_input; } +void ServerController::set_frame_input(const FrameSnapshot &p_frame_snapshot) { + // If `previous_frame_received_timestamp` is bigger, the controller was disabled, so nothing to do. + if (previous_frame_received_timestamp < p_frame_snapshot.received_timestamp) { + const double physics_ticks_per_second = Engine::get_singleton()->get_iterations_per_second(); + const uint32_t frame_delta_ms = (1.0 / physics_ticks_per_second) * 1000.0; + + const uint32_t receival_time = p_frame_snapshot.received_timestamp - previous_frame_received_timestamp; + const uint32_t network_time = receival_time > frame_delta_ms ? receival_time - frame_delta_ms : 0; + + network_watcher.push(network_time); + } + + node->set_inputs_buffer( + p_frame_snapshot.inputs_buffer, + METADATA_SIZE, + p_frame_snapshot.buffer_size_bit - METADATA_SIZE); + current_input_buffer_id = p_frame_snapshot.id; + previous_frame_received_timestamp = p_frame_snapshot.received_timestamp; +} + void ServerController::notify_send_state() { last_sent_state_input_id = get_current_input_id(); // If the notified input is a void buffer, the client is allowed to pause @@ -887,8 +1073,7 @@ void ServerController::notify_send_state() { void ServerController::doll_sync(real_t p_delta) { // Advance the epoch. epoch += 1; - batch_sync_timer += p_delta; - const bool send_batch = batch_sync_timer >= node->get_doll_epoch_batch_sync_rate(); + const real_t sync_rate_time = 1.0 / static_cast(node->get_doll_sync_rate()); bool epoch_state_collected = false; @@ -899,150 +1084,112 @@ void ServerController::doll_sync(real_t p_delta) { continue; } - peers[i].collect_timer += 1; - if ( - is_epoch_important || - peers[i].collect_timer >= peers[i].collect_threshold) { - // Resets the timer. - peers[i].collect_timer -= peers[i].collect_threshold; - // Since is possible to force send the state update, we need to make - // sure the timer doesn't go below 0. - peers[i].collect_timer = MAX(0, peers[i].collect_timer); + peers[i].doll_sync_timer += p_delta; - // Prepare the epoch_data cache. - if (epoch_state_collected == false) { - epoch_state_data_cache.begin_write(0); - epoch_state_data_cache.add_int(epoch, DataBuffer::COMPRESSION_LEVEL_1); - node->call("_collect_epoch_data", &epoch_state_data_cache); - epoch_state_data_cache.dry(); - epoch_state_collected = true; - } - - // Store this into epoch batch. - if (unlikely(epoch_state_data_cache.get_buffer().get_bytes().size() > UINT8_MAX)) { - // If the packet is more than 255 it can't be sent. - NET_DEBUG_ERR("The status update is too big, try to staty under 255 bytes per update. This status is dropped."); - } else { - peers[i].batch_size += 1 + epoch_state_data_cache.get_buffer().get_bytes().size(); - peers[i].epoch_batch.push_back(epoch_state_data_cache.get_buffer().get_bytes()); - } + if (is_epoch_important == false && peers[i].doll_sync_timer < peers[i].doll_sync_time_threshold) { + // Not time to sync. + continue; } + peers[i].doll_sync_timer = 0.0; + peers[i].doll_sync_time_threshold = sync_rate_time * peers[i].doll_sync_rate_factor; - // Send batch data. - if (send_batch) { - const uint8_t next_collect_rate = - MIN(node->get_doll_epoch_collect_rate() / - peers[i].update_rate_factor, - UINT8_MAX); - - // Next rate is - peers[i].collect_threshold = next_collect_rate; - - if (peers[i].epoch_batch.size() > 0) { - // Add space to allocate the next_collect_rate. - peers[i].batch_size += 1; + // Prepare the epoch_data cache. + if (epoch_state_collected == false) { + epoch_state_data_cache.begin_write(DOLL_EPOCH_METADATA_SIZE); + epoch_state_data_cache.add_real(0.0, DataBuffer::COMPRESSION_LEVEL_1); // Sync time + epoch_state_data_cache.add_int(epoch, DataBuffer::COMPRESSION_LEVEL_1); #ifdef DEBUG_ENABLED - if (peers[i].batch_size >= 1350) { - NET_DEBUG_WARN("The amount of data collected for this batch is more than 1350 bytes. Please make sure the `doll_sync_timer_rate` is not so big, so to avoid packet fragmentation. Batch size: " + itos(peers[i].batch_size) + " - Epochs into the batch: " + itos(peers[i].epoch_batch.size())); - } + // This can't happen because the metadata size is correct. + CRASH_COND(epoch_state_data_cache.get_bit_offset() != DOLL_EPOCH_METADATA_SIZE); #endif - // Prepare the batch data. - Vector data; - data.resize(peers[i].batch_size); - uint8_t *data_ptr = data.ptrw(); - uint32_t offset = 0; - data_ptr[offset] = next_collect_rate; - offset += 1; - for (uint32_t x = 0; x < peers[i].epoch_batch.size(); x += 1) { - ERR_CONTINUE_MSG(peers[i].epoch_batch[x].size() > 256, "It's not allowed to send more than 256 bytes per status. This status is dropped."); - data_ptr[offset] = peers[i].epoch_batch[x].size(); - offset += 1; - for (int l = 0; l < peers[i].epoch_batch[x].size(); l += 1) { - data_ptr[offset] = peers[i].epoch_batch[x][l]; - offset += 1; - } - } -#ifdef DEBUG_ENABLED - // This is not supposed to happen because the batch_size is - // correctly computed. - CRASH_COND(offset != peers[i].batch_size); -#endif - peers[i].epoch_batch.clear(); - peers[i].batch_size = 0; - - // Send the data - node->rpc_id( - peers[i].peer, - "_rpc_doll_send_epoch_batch", - data); - } + node->native_collect_epoch_data(epoch_state_data_cache); + epoch_state_data_cache.dry(); + epoch_state_collected = true; } - } - if (send_batch) { - batch_sync_timer = 0.0; + epoch_state_data_cache.seek(0); + epoch_state_data_cache.add_real(peers[i].doll_sync_time_threshold, DataBuffer::COMPRESSION_LEVEL_1); + + // Send the data + node->rpc_unreliable_id( + peers[i].peer, + SNAME("_rpc_doll_send_epoch_batch"), + epoch_state_data_cache.get_buffer().get_bytes()); } is_epoch_important = false; } -void ServerController::calculates_player_tick_rate(real_t p_delta) { - const real_t min_frames_delay = node->get_min_frames_delay(); - const real_t max_frames_delay = node->get_max_frames_delay(); - const real_t net_sensitivity = node->get_net_sensitivity(); - - const uint32_t avg_receive_time = network_watcher.average(); - const real_t deviation_receive_time = real_t(network_watcher.get_deviation(avg_receive_time)) / 1000.0; - - // The network quality can be established just by checking the standard - // deviation. Stable connections have standard deviation that tend to 0. - const real_t net_poorness = MIN( - deviation_receive_time / net_sensitivity, - 1.0); - - const int optimal_frame_delay = Math::lerp( - min_frames_delay, - max_frames_delay, - net_poorness); - - int consecutive_inputs = 0; - for (uint32_t i = 0; i < snapshots.size(); i += 1) { - if (snapshots[i].id == (current_input_buffer_id + consecutive_inputs + 1)) { - consecutive_inputs += 1; - } - } - - const real_t distance_to_optimal_count = real_t(optimal_frame_delay - consecutive_inputs); - - const real_t acc = distance_to_optimal_count * node->get_tick_acceleration() * p_delta; - // Used to avoid oscillations. - const real_t damp = -(client_tick_additional_speed * 0.95); - client_tick_additional_speed += acc + damp * ((SGN(acc) * SGN(damp) + 1) / 2.0); - client_tick_additional_speed = CLAMP(client_tick_additional_speed, -MAX_ADDITIONAL_TICK_SPEED, MAX_ADDITIONAL_TICK_SPEED); - -#ifdef DEBUG_ENABLED - const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_server_speedup"); - if (debug) { - print_line("Network poorness: " + rtos(net_poorness) + ", optimal frame delay: " + itos(optimal_frame_delay) + ", deviation: " + rtos(deviation_receive_time) + ", current frame delay: " + itos(consecutive_inputs)); - } -#endif +int ceil_with_tolerance(double p_value, double p_tolerance) { + return Math::ceil(p_value - p_tolerance); } -void ServerController::adjust_player_tick_rate(real_t p_delta) { - additional_speed_notif_timer += p_delta; - if (additional_speed_notif_timer >= node->get_tick_speedup_notification_delay()) { - additional_speed_notif_timer = 0.0; +void ServerController::adjust_player_tick_rate(double p_delta) { + // Update the consecutive inputs. + { + int consecutive_inputs = 0; + for (uint32_t i = 0; i < snapshots.size(); i += 1) { + if (snapshots[i].id == (current_input_buffer_id + consecutive_inputs + 1)) { + consecutive_inputs += 1; + } + } + consecutive_input_watcher.push(consecutive_inputs); + } - const uint8_t new_speed = UINT8_MAX * (((client_tick_additional_speed / MAX_ADDITIONAL_TICK_SPEED) + 1.0) / 2.0); + const uint32_t now = OS::get_singleton()->get_ticks_msec(); + + if ((additional_fps_notif_timer + node->get_tick_speedup_notification_delay()) < now) { + // Time to tell the client a new speedup. + + additional_fps_notif_timer = now; + + const real_t min_frames_delay = node->get_min_frames_delay(); + const real_t max_frames_delay = node->get_max_frames_delay(); + + // `worst_receival_time` is in ms and indicates the maximum time passed to receive a consecutive + // input in the last `network_traced_frames` frames. + const uint32_t worst_receival_time_ms = network_watcher.max(); + + const double worst_receival_time = double(worst_receival_time_ms) / 1000.0; + + const int optimal_frame_delay_unclamped = ceil_with_tolerance( + worst_receival_time / p_delta, + p_delta * 0.05); // Tolerance of 5% of frame time. + + const int optimal_frame_delay = CLAMP(optimal_frame_delay_unclamped, min_frames_delay, max_frames_delay); + + const int consecutive_inputs = consecutive_input_watcher.average_rounded(); + + const int8_t distance_to_optimal = CLAMP(optimal_frame_delay - consecutive_inputs, INT8_MIN, INT8_MAX); + + uint8_t compressed_distance; + memcpy( + &compressed_distance, + &distance_to_optimal, + sizeof(uint8_t)); + +#ifdef DEBUG_ENABLED + const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_server_speedup"); + const int current_frame_delay = consecutive_inputs; + if (debug) { + print_line( + "Worst receival time (ms): `" + itos(worst_receival_time_ms) + + "` Optimal frame delay: `" + itos(optimal_frame_delay) + + "` Current frame delay: `" + itos(current_frame_delay) + + "` Distance to optimal: `" + itos(distance_to_optimal) + + "`"); + } + node->emit_signal("client_speedup_adjusted", worst_receival_time_ms, optimal_frame_delay, current_frame_delay, distance_to_optimal); +#endif Vector packet_data; - packet_data.push_back(new_speed); + packet_data.push_back(compressed_distance); - node->rpc_id( + node->rpc_unreliable_id( node->get_network_master(), - "_rpc_send_tick_additional_speed", + SNAME("_rpc_notify_fps_acceleration"), packet_data); } } @@ -1056,15 +1203,55 @@ uint32_t ServerController::find_peer(int p_peer) const { return UINT32_MAX; } +AutonomousServerController::AutonomousServerController( + NetworkedController *p_node) : + ServerController(p_node, 1) { +} + +void AutonomousServerController::receive_inputs(const Vector &p_data) { + SceneSynchronizerDebugger::singleton()->debug_warning(node, "`receive_input` called on the `AutonomousServerController` - If this is called just after `set_server_controlled(true)` is called, you can ignore this warning, as the client is not aware about the switch for a really small window after this function call."); +} + +int AutonomousServerController::get_inputs_count() const { + // No input collected by this class. + return 0; +} + +bool AutonomousServerController::fetch_next_input(real_t p_delta) { + SceneSynchronizerDebugger::singleton()->debug_print(node, "Autonomous server fetch input.", true); + + node->get_inputs_buffer_mut().begin_write(METADATA_SIZE); + node->get_inputs_buffer_mut().seek(METADATA_SIZE); + SceneSynchronizerDebugger::singleton()->databuffer_operation_begin_record(node, SceneSynchronizerDebugger::WRITE); + node->native_collect_inputs(p_delta, node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->databuffer_operation_end_record(); + node->get_inputs_buffer_mut().dry(); + + if (unlikely(current_input_buffer_id == UINT32_MAX)) { + // This is the first input. + current_input_buffer_id = 0; + } else { + // Just advance from now on. + current_input_buffer_id += 1; + } + + // The input is always new. + return true; +} + +void AutonomousServerController::adjust_player_tick_rate(double p_delta) { + // Nothing to do, since the inputs are being collected on the server already. +} + PlayerController::PlayerController(NetworkedController *p_node) : Controller(p_node), current_input_id(UINT32_MAX), input_buffers_counter(0), time_bank(0.0), - tick_additional_speed(0.0) { + acceleration_fps_timer(0.0) { } -void PlayerController::process(real_t p_delta) { +void PlayerController::process(double p_delta) { // We need to know if we can accept a new input because in case of bad // internet connection we can't keep accumulating inputs forever // otherwise the server will differ too much from the client and we @@ -1074,10 +1261,15 @@ void PlayerController::process(real_t p_delta) { if (accept_new_inputs) { current_input_id = input_buffers_counter; + SceneSynchronizerDebugger::singleton()->debug_print(node, "Player process index: " + itos(current_input_id), true); + node->get_inputs_buffer_mut().begin_write(METADATA_SIZE); - node->get_inputs_buffer_mut().seek(1); - node->call("_collect_inputs", p_delta, &node->get_inputs_buffer_mut()); + node->get_inputs_buffer_mut().seek(METADATA_SIZE); + + SceneSynchronizerDebugger::singleton()->databuffer_operation_begin_record(node, SceneSynchronizerDebugger::WRITE); + node->native_collect_inputs(p_delta, node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->databuffer_operation_end_record(); // Set metadata data. node->get_inputs_buffer_mut().seek(0); @@ -1088,16 +1280,18 @@ void PlayerController::process(real_t p_delta) { node->get_inputs_buffer_mut().add_bool(false); } } else { - NET_DEBUG_WARN("It's not possible to accept new inputs. Is this lagging?"); + SceneSynchronizerDebugger::singleton()->debug_warning(node, "It's not possible to accept new inputs. Is this lagging?"); } node->get_inputs_buffer_mut().dry(); node->get_inputs_buffer_mut().begin_read(); node->get_inputs_buffer_mut().seek(METADATA_SIZE); // Skip meta. + SceneSynchronizerDebugger::singleton()->databuffer_operation_begin_record(node, SceneSynchronizerDebugger::READ); // The physics process is always emitted, because we still need to simulate // the character motion even if we don't store the player inputs. - node->call("_controller_process", p_delta, &node->get_inputs_buffer()); + node->native_controller_process(p_delta, node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->databuffer_operation_end_record(); node->player_set_has_new_input(false); if (accept_new_inputs) { @@ -1110,19 +1304,40 @@ void PlayerController::process(real_t p_delta) { } } -int PlayerController::calculates_sub_ticks(real_t p_delta, real_t p_iteration_per_seconds) { - const real_t pretended_delta = get_pretended_delta(p_iteration_per_seconds); +int PlayerController::calculates_sub_ticks(const double p_delta, const double p_iteration_per_seconds) { + // Extract the frame acceleration: + // 1. convert the Accelerated Tick Hz to second. + const double fully_accelerated_delta = 1.0 / (p_iteration_per_seconds + acceleration_fps_speed); + + // 2. Subtract the `accelerated delta - delta` to obtain the acceleration magnitude. + const double acceleration_delta = ABS(fully_accelerated_delta - p_delta); + + // 3. Avoids overshots by taking the smallest value between `acceleration_delta` and the `remaining timer`. + const double frame_acceleration_delta = MIN(acceleration_delta, acceleration_fps_timer); + + // Updates the timer by removing the extra accelration. + acceleration_fps_timer = MAX(acceleration_fps_timer - frame_acceleration_delta, 0.0); + + // Calculates the pretended delta. + pretended_delta = p_delta + (frame_acceleration_delta * SGN(acceleration_fps_speed)); + + // Add the current delta to the bank + time_bank += pretended_delta; + + const int sub_ticks = int(time_bank / p_delta); + + time_bank -= static_cast(sub_ticks) * p_delta; + if (unlikely(time_bank < 0.0)) { + time_bank = 0.0; + } - time_bank += p_delta; - const int sub_ticks = static_cast(time_bank / pretended_delta); - time_bank -= static_cast(sub_ticks) * pretended_delta; return sub_ticks; } int PlayerController::notify_input_checked(uint32_t p_input_id) { if (frames_snapshot.empty() || p_input_id < frames_snapshot.front().id || p_input_id > frames_snapshot.back().id) { // The received p_input_id is not known, so nothing to do. - NET_DEBUG_ERR("The received snapshot, with input id: " + itos(p_input_id) + " is not known. This is a bug or someone is trying to hack."); + SceneSynchronizerDebugger::singleton()->debug_error(node, "The received snapshot, with input id: " + itos(p_input_id) + " is not known. This is a bug or someone is trying to hack."); return frames_snapshot.size(); } @@ -1182,7 +1397,7 @@ bool PlayerController::process_instant(int p_i, real_t p_delta) { ib.shrink_to(METADATA_SIZE, frames_snapshot[i].buffer_size_bit - METADATA_SIZE); ib.begin_read(); ib.seek(METADATA_SIZE); - node->call("_controller_process", p_delta, &ib); + node->native_controller_process(p_delta, ib); return (i + 1) < frames_snapshot.size(); } else { return false; @@ -1193,16 +1408,13 @@ uint32_t PlayerController::get_current_input_id() const { return current_input_id; } -real_t PlayerController::get_pretended_delta(real_t p_iteration_per_seconds) const { - return 1.0 / (p_iteration_per_seconds + tick_additional_speed); -} - void PlayerController::store_input_buffer(uint32_t p_id) { FrameSnapshot inputs; inputs.id = p_id; inputs.inputs_buffer = node->get_inputs_buffer().get_buffer(); inputs.buffer_size_bit = node->get_inputs_buffer().size() + METADATA_SIZE; inputs.similarity = UINT32_MAX; + inputs.received_timestamp = UINT32_MAX; frames_snapshot.push_back(inputs); } @@ -1257,13 +1469,13 @@ void PlayerController::send_frame_input_buffer_to_server() { pir_B.begin_read(); pir_B.seek(METADATA_SIZE); - const bool are_different = node->call("_are_inputs_different", &pir_A, &pir_B); + const bool are_different = node->native_are_inputs_different(pir_A, pir_B); is_similar = are_different == false; } else if (frames_snapshot[i].similarity == previous_input_similarity) { // This input is similar to the previous one, the thing is // that the similarity check was done on an older input. - // Fortunately we are able to compare the similarity id + // Fortunatelly we are able to compare the similarity id // and detect its similarity correctly. is_similar = true; } else { @@ -1276,16 +1488,26 @@ void PlayerController::send_frame_input_buffer_to_server() { } } + if (current_input_id == previous_input_id) { + SceneSynchronizerDebugger::singleton()->notify_are_inputs_different_result(node, frames_snapshot[i].id, is_similar); + } else if (current_input_id == frames_snapshot[i].id) { + SceneSynchronizerDebugger::singleton()->notify_are_inputs_different_result(node, previous_input_id, is_similar); + } + if (is_similar) { // This input is similar to the previous one, so just duplicate it. duplication_count += 1; // In this way, we don't need to compare these frames again. frames_snapshot[i].similarity = previous_input_id; + SceneSynchronizerDebugger::singleton()->notify_input_sent_to_server(node, frames_snapshot[i].id, previous_input_id); + } else { // This input is different from the previous one, so let's // finalize the previous and start another one. + SceneSynchronizerDebugger::singleton()->notify_input_sent_to_server(node, frames_snapshot[i].id, frames_snapshot[i].id); + if (previous_input_id != UINT32_MAX) { // We can finally finalize the previous input cached_packet_data[ofs - previous_buffer_size - 1] = duplication_count; @@ -1304,7 +1526,7 @@ void PlayerController::send_frame_input_buffer_to_server() { MAKE_ROOM(buffer_size); memcpy( cached_packet_data.ptr() + ofs, - frames_snapshot[i].inputs_buffer.get_bytes().read().ptr(), + frames_snapshot[i].inputs_buffer.get_bytes().ptr(), buffer_size); ofs += buffer_size; @@ -1331,9 +1553,9 @@ void PlayerController::send_frame_input_buffer_to_server() { ofs); const int server_peer_id = 1; - node->rpc_id( + node->rpc_unreliable_id( server_peer_id, - "_rpc_server_send_inputs", + SNAME("_rpc_server_send_inputs"), packet_data); } @@ -1346,211 +1568,152 @@ DollController::DollController(NetworkedController *p_node) : network_watcher(node->get_doll_connection_stats_frame_span(), 0) { } -void DollController::ready() { - interpolator.reset(); - node->call( - "_setup_interpolator", - &interpolator); - interpolator.terminate_init(); -} +void DollController::ready() {} -void DollController::process(real_t p_delta) { - const uint32_t frame_epoch = next_epoch(); - - if (unlikely(frame_epoch == UINT32_MAX)) { - // Nothing to do. +void DollController::process(double p_delta) { + if (future_epoch_buffer.size() <= DOLL_EPOCH_METADATA_SIZE) { + // The interpolation epoch is not yet received, nothing to interpolate. return; } - const real_t fractional_part = advancing_epoch; - node->call( - "_apply_epoch", + if (interpolation_time_window <= CMP_EPSILON) { + interpolation_alpha = 1.0; + } else { + interpolation_alpha += p_delta / interpolation_time_window; + // Constraint the overshot. + interpolation_alpha = MIN(interpolation_alpha, 1.0 + node->get_doll_interpolation_max_overshot()); + } + + // Allow extrapolation, in case the other para didn't arrive yet. + current_epoch = Math::round(Math::lerp(real_t(past_epoch), real_t(future_epoch), interpolation_alpha)); + + past_epoch_buffer.begin_read(); + future_epoch_buffer.begin_read(); + // Skip metadata. + future_epoch_buffer.seek(DOLL_EPOCH_METADATA_SIZE); + + node->native_apply_epoch( p_delta, - interpolator.pop_epoch(frame_epoch, fractional_part)); + interpolation_alpha, + past_epoch_buffer, + future_epoch_buffer); } uint32_t DollController::get_current_input_id() const { return current_epoch; } -void DollController::receive_batch(const PoolVector &p_data) { +void DollController::receive_epoch(const Vector &p_data) { if (unlikely(node->get_scene_synchronizer()->is_enabled() == false)) { // The sync is disabled, nothing to do. return; } - // Take the epochs befoe the batch is applied. - const uint32_t youngest_epoch = interpolator.get_youngest_epoch(); - const uint32_t oldest_epoch = interpolator.get_oldest_epoch(); - - int initially_stored_epochs = 0; - if (youngest_epoch != UINT32_MAX && oldest_epoch != UINT32_MAX) { - initially_stored_epochs = oldest_epoch - youngest_epoch; - } - - initially_stored_epochs -= missing_epochs; - missing_epochs = 0; - - uint32_t batch_young_epoch = UINT32_MAX; - - int buffer_start_position = 0; - - const uint8_t next_collect_rate = p_data[buffer_start_position]; - buffer_start_position += 1; - - while (buffer_start_position < p_data.size()) { - const int buffer_size = p_data[buffer_start_position]; - const PoolVector buffer = p_data.subarray( - buffer_start_position + 1, - buffer_start_position + 1 + buffer_size - 1); - - ERR_FAIL_COND(buffer.size() <= 0); - - const uint32_t epoch = receive_epoch(buffer); - buffer_start_position += 1 + buffer_size; - - batch_young_epoch = MIN(epoch, batch_young_epoch); - } - - // ~~ Establish the interpolation speed ~~ - if (batch_young_epoch == UINT32_MAX) { - // This may just be a late arrived batch, so nothing more to do. - return; - } - - const real_t net_sentitivity = node->get_doll_net_sensitivity(); - - // Establish the connection quality by checking if the batch takes - // always the same time to arrive. - const uint32_t now = OS::get_singleton()->get_ticks_msec(); - // If now is bigger, then the timer has been disabled, so we assume 0. - network_watcher.push(now > batch_receiver_timer ? now - batch_receiver_timer : 0); - batch_receiver_timer = now; - - const uint32_t avg_receive_time = network_watcher.average(); - const real_t deviation_receive_time = real_t(network_watcher.get_deviation(avg_receive_time)) / 1000.0; - - // The network quality can be established just by checking the standard - // deviation. Stable connections have standard deviation that tend to 0. - const real_t net_poorness = MIN( - deviation_receive_time / net_sentitivity, - 1.0); - - const int optimal_frame_delay = Math::lerp( - node->get_doll_min_frames_delay(), - node->get_doll_max_frames_delay(), - net_poorness); - - // TODO cache this? - const double frames_per_batch = node->get_doll_epoch_batch_sync_rate() * real_t(Engine::get_singleton()->get_physics_ticks_per_second()); - const double next_batch_arrives_in = Math::ceil(double(next_collect_rate) / frames_per_batch) * frames_per_batch; - - const real_t doll_interpolation_max_speedup = node->get_doll_interpolation_max_speedup(); - additional_speed = doll_interpolation_max_speedup * (real_t(initially_stored_epochs - optimal_frame_delay) / next_batch_arrives_in); - additional_speed = CLAMP(additional_speed, -doll_interpolation_max_speedup, doll_interpolation_max_speedup); - -#ifdef DEBUG_ENABLED - const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_doll_speedup"); - if (debug) { - print_line("Network poorness: " + rtos(net_poorness) + ", optimal stored epochs: " + rtos(optimal_frame_delay) + ", deviation: " + rtos(deviation_receive_time)); - } -#endif -} - -uint32_t DollController::receive_epoch(const PoolVector &p_data) { - DataBuffer buffer(p_data); - buffer.begin_read(); - const uint32_t epoch = buffer.read_int(DataBuffer::COMPRESSION_LEVEL_1); + future_epoch_buffer.copy(p_data); + future_epoch_buffer.begin_read(); + // Read from METADATA: + const real_t next_sync_time = future_epoch_buffer.read_real(DataBuffer::COMPRESSION_LEVEL_1); + const uint32_t epoch = future_epoch_buffer.read_int(DataBuffer::COMPRESSION_LEVEL_1); if (epoch <= paused_epoch) { // The sync is in pause from this epoch, so just discard this received // epoch that may just be a late received epoch. - return UINT32_MAX; + return; } - interpolator.begin_write(epoch); - node->call("_parse_epoch_data", &interpolator, &buffer); - interpolator.end_write(); + if (epoch <= future_epoch) { + // This epoch is old, it arrived too late; nothing to do. + return; + } - return epoch; -} + const int64_t current_virtual_delay = int64_t(future_epoch) - int64_t(current_epoch); -uint32_t DollController::next_epoch() { - // TODO re-describe. - // This function regulates the epoch ID to process. - // The epoch is not simply increased by one because we need to make sure - // to make the client apply the nearest server state while giving some room - // for the subsequent information to arrive. + if (current_epoch > future_epoch) { + // Make sure we set back the current epoch in case of overshot. + // The overshot is wanted to correctly calculate `current_virtual_delay`, but at this point + // the overshot need to be normalized. + current_epoch = future_epoch; + } + past_epoch = current_epoch; + future_epoch = epoch; - // Step 1, Wait that we have at least two epochs. - if (unlikely(current_epoch == UINT32_MAX)) { - // Interpolator is not yet started. - if (interpolator.known_epochs_count() < 2) { - // Not ready yet. - return UINT32_MAX; - } + // Make sure to store the current state, so we can use it to interpolate. + // This is necessary so we allow a bit of overshot. + past_epoch_buffer.begin_write(0); + node->native_collect_epoch_data(past_epoch_buffer); + + // ~~ Establish the interpolation speed ~~ + + // Establish the connection quality by checking if the batch takes + // always the same time to arrive. + const uint32_t now = OS::get_singleton()->get_ticks_msec(); + + // If now is bigger, then the timer has been disabled, so we assume 0. + + real_t packet_arrived_in = 0.0; + if (now > epoch_received_timestamp) { + packet_arrived_in = static_cast(now - epoch_received_timestamp) / 1000.0; + const real_t delta_difference = packet_arrived_in - next_epoch_expected_in; + network_watcher.push(ABS(delta_difference)); + } + epoch_received_timestamp = now; + next_epoch_expected_in = next_sync_time; + + const real_t avg_arrival_delta_time = network_watcher.max(); + const real_t deviation_arrival_delta_time = network_watcher.get_deviation(avg_arrival_delta_time); + + // The network poorness is computed by looking at all the delta differences between the expected + // arrival time and the actual arrival time: the bigger this difference is the more oscillating + // the connection is so a virtual delay is needed to make sure the character doesn't bounces. + const real_t net_poorness = MIN(1.0, (avg_arrival_delta_time + deviation_arrival_delta_time) / node->get_doll_net_sensitivity()); + + const int64_t target_virtual_delay = Math::lerp( + node->get_doll_min_frames_delay(), + node->get_doll_max_frames_delay(), + net_poorness); + + const real_t epochs_span = target_virtual_delay - current_virtual_delay; + const real_t frame_time = 1.0 / real_t(Engine::get_singleton()->get_iterations_per_second()); + interpolation_time_window = + next_sync_time + + (current_virtual_delay * frame_time) + + (epochs_span * frame_time); + + interpolation_alpha = 0.0; #ifdef DEBUG_ENABLED - // At this point we have 2 epoch, something is always returned at this - // point. - CRASH_COND(interpolator.get_youngest_epoch() == UINT32_MAX); + const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_doll_speedup"); + if (debug) { + String msg = "~~~~~~~~~~~~~~~\n"; + msg += "Epoch #" + itos(epoch) + " "; + msg += "Epoch arrived in: `" + rtos(packet_arrived_in) + "` \n"; + msg += "\n"; + msg += "Avg arrival time difference: `" + rtos(avg_arrival_delta_time) + "` \n"; + msg += "Deviation arrival difference: `" + rtos(deviation_arrival_delta_time) + "` \n"; + msg += "Arrival difference: `" + rtos(avg_arrival_delta_time + deviation_arrival_delta_time) + "` \n"; + msg += "Sensitivity: `" + rtos(node->get_doll_net_sensitivity()) + "` \n"; + msg += "Network poorness: `" + rtos(net_poorness) + "` \n"; + msg += "\n"; + msg += "Next sync time`" + rtos(next_sync_time) + "` \n"; + msg += "Interpolation time window no speedup `" + rtos(next_sync_time + (current_virtual_delay * frame_time)) + "` \n"; + msg += "Interpolation time window `" + rtos(interpolation_time_window) + "` \n"; + msg += "Epochs span `" + rtos(epochs_span) + "` \n"; + msg += "Current virtual delay `" + itos(current_virtual_delay) + "` \n"; + msg += "Target virtual delay `" + itos(target_virtual_delay) + "` \n"; + msg += "Current epoch `" + itos(current_epoch) + "` \n"; + msg += "Past epoch `" + itos(past_epoch) + "` \n"; + msg += "Future epoch `" + itos(future_epoch) + "` \n"; + msg += "~~~~~~~~~~~~~~~\n"; + print_line(msg); + } #endif - - // Start epoch interpolation. - current_epoch = interpolator.get_youngest_epoch(); - node->emit_signal("doll_sync_started"); - } - - // At this point the interpolation is started and the function must - // return the best epoch id which we have to apply the state. - - // Step 2. Make sure we have something to interpolate with. - const uint32_t oldest_epoch = interpolator.get_oldest_epoch(); - if (unlikely(oldest_epoch == UINT32_MAX || oldest_epoch <= current_epoch)) { - missing_epochs += 1; - // Nothing to interpolate with. - return current_epoch; - } - -#ifdef DEBUG_ENABLED - // This can't happen because the current_epoch is advances only if it's - // possible to do so. - CRASH_COND(oldest_epoch < current_epoch); -#endif - - const uint64_t max_delay = node->get_doll_max_delay(); - - if (unlikely((oldest_epoch - current_epoch) > max_delay)) { - // This client seems too much behind at this point. Teleport forward. - const uint32_t youngest_epoch = interpolator.get_youngest_epoch(); - current_epoch = MAX(oldest_epoch - max_delay, youngest_epoch); - } else { - advancing_epoch += 1.0 + additional_speed; - } - - if (advancing_epoch > 0.0) { - // Advance the epoch by the the integral amount. - current_epoch += uint32_t(advancing_epoch); - // Clamp to the oldest epoch. - current_epoch = MIN(current_epoch, oldest_epoch); - - // Keep the floating point part. - advancing_epoch -= uint32_t(advancing_epoch); - } - - return current_epoch; } void DollController::pause(uint32_t p_epoch) { paused_epoch = p_epoch; - interpolator.clear(); - additional_speed = 0.0; - current_epoch = UINT32_MAX; - advancing_epoch = 0.0; - missing_epochs = 0; network_watcher.resize(node->get_doll_connection_stats_frame_span(), 0); - batch_receiver_timer = UINT32_MAX; + epoch_received_timestamp = UINT32_MAX; node->emit_signal("doll_sync_paused"); } @@ -1560,12 +1723,17 @@ NoNetController::NoNetController(NetworkedController *p_node) : frame_id(0) { } -void NoNetController::process(real_t p_delta) { +void NoNetController::process(double p_delta) { node->get_inputs_buffer_mut().begin_write(0); // No need of meta in this case. - node->call("_collect_inputs", p_delta, &node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->debug_print(node, "Nonet process index: " + itos(frame_id), true); + SceneSynchronizerDebugger::singleton()->databuffer_operation_begin_record(node, SceneSynchronizerDebugger::WRITE); + node->native_collect_inputs(p_delta, node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->databuffer_operation_end_record(); node->get_inputs_buffer_mut().dry(); node->get_inputs_buffer_mut().begin_read(); - node->call("_controller_process", p_delta, &node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->databuffer_operation_begin_record(node, SceneSynchronizerDebugger::READ); + node->native_controller_process(p_delta, node->get_inputs_buffer_mut()); + SceneSynchronizerDebugger::singleton()->databuffer_operation_end_record(); frame_id += 1; } diff --git a/modules/network_synchronizer/networked_controller.h b/modules/network_synchronizer/networked_controller.h index 731b0bd08..f9ff66e75 100644 --- a/modules/network_synchronizer/networked_controller.h +++ b/modules/network_synchronizer/networked_controller.h @@ -1,16 +1,12 @@ -#ifndef NETWORKED_CONTROLLER_H -#define NETWORKED_CONTROLLER_H - /*************************************************************************/ /* networked_controller.h */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -39,10 +35,14 @@ #include "scene/main/node.h" #include "data_buffer.h" -#include "interpolator.h" #include "net_utilities.h" #include +#ifndef NETWORKED_CONTROLLER_H +#define NETWORKED_CONTROLLER_H + +#include "godot_backward_utility_header.h" + class SceneSynchronizer; struct Controller; struct ServerController; @@ -56,7 +56,7 @@ struct NoNetController; /// /// The `NetworkedController` will sync inputs, based on those will perform /// 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. /// /// Is possible to use the `SceneSynchronizer` to keep the state in sync with the @@ -78,11 +78,24 @@ public: CONTROLLER_TYPE_NULL, CONTROLLER_TYPE_NONETWORK, CONTROLLER_TYPE_PLAYER, + CONTROLLER_TYPE_AUTONOMOUS_SERVER, CONTROLLER_TYPE_SERVER, CONTROLLER_TYPE_DOLL }; 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 `PlayerController`. /// @@ -104,11 +117,11 @@ private: /// Amount of time an inputs is re-sent to each peer. /// Resenging inputs is necessary because the packets may be lost since as /// they are sent in an unreliable way. - int max_redundant_inputs = 5; + int max_redundant_inputs = 6; /// Time in seconds between each `tick_speedup` that the server sends to the - /// client. - real_t tick_speedup_notification_delay = 0.33; + /// client. In ms. + int tick_speedup_notification_delay = 600; /// The connection quality is established by watching the time passed /// between each input is received. @@ -120,22 +133,6 @@ private: /// - Small values make the mechanism too sensible. int network_traced_frames = 120; - /// Sensitivity to network oscillations. The value is in seconds and can be - /// used to establish the connection quality. - /// - /// For each input, the time needed for its arrival is traced; the standard - /// deviation of these is divided by `net_sensitivity`: the result is - /// the connection quality. - /// - /// The more the time needed for each batch to arrive is different the - /// bigger this value is: when this value approaches to - /// `net_sensitivity` (or even surpasses it) the bad the connection is. - /// - /// The result is the `connection_poorness` that goes from 0 to 1 and is - /// used to decide the `optimal_frame_delay` that is interpolated between - /// `min_frames_delay` and `max_frames_delay`. - real_t net_sensitivity = 0.1; - /// The `ServerController` will try to keep a margin of error, so that /// network oscillations doesn't leave the `ServerController` without /// inputs. @@ -143,26 +140,14 @@ private: /// This margin of error is called `optimal_frame_delay` and it changes /// depending on the connection health: /// it can go from `min_frames_delay` to `max_frames_delay`. - int min_frames_delay = 2; - int max_frames_delay = 6; + int min_frames_delay = 0; + int max_frames_delay = 7; - /// Rate at which the tick speed changes, so the `optimal_frame_delay` is - /// matched. - real_t tick_acceleration = 2.0; + /// Amount of additional frames produced per second. + double tick_acceleration = 5.0; - /// Collect rate (in frames) used by the server to establish when to collect - /// the state for a particular peer. - /// It's possible to scale down this rate, for a particular peer, - /// using the function: set_doll_collect_rate_factor(peer, factor); - /// Current default is 10Hz. - /// - /// The collected state is not 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 epoch send rate: in Hz (frames per seconds). + uint32_t doll_sync_rate = 30; /// The doll interpolator will try to keep a margin of error, so that network /// oscillations doesn't make the dolls freeze. @@ -170,11 +155,22 @@ private: /// This margin of error is called `optimal_frame_delay` and it changes /// depending on the connection health: /// it can go from `doll_min_frames_delay` to `doll_max_frames_delay`. - int doll_min_frames_delay = 2; - int doll_max_frames_delay = 5; + int doll_min_frames_delay = 0; + int doll_max_frames_delay = 25; - /// Max speedup / slowdown the doll can apply to recover its epoch buffer size. - real_t doll_interpolation_max_speedup = 0.2; + /// Sensitivity to network oscillations. The value is in seconds and can be + /// 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 /// between each batch arrival. @@ -185,31 +181,8 @@ private: /// - Big values make the mechanism too slow. /// - Small values make the mechanism too sensible. /// The correct value should be give considering the - /// `doll_epoch_batch_sync_rate`. - int doll_connection_stats_frame_span = 30; - - /// Sensitivity to network oscillations. The value is in seconds and can be - /// used to establish the connection quality. - /// - /// For each batch, the time needed for its arrival is traced; the standard - /// deviation of these is divided by `doll_net_sensitivity`: the result is - /// the connection quality. - /// - /// The more the time needed for each batch to arrive is different the - /// bigger this value is: when this value approaches to - /// `doll_net_sensitivity` (or even surpasses it) the bad the connection is. - /// - /// The result is the `connection_poorness` that goes from 0 to 1 and is - /// used to decide the `optimal_frames_delay` that is interpolated between - /// `doll_min_frames_delay` and `doll_max_frames_delay`. - real_t doll_net_sensitivity = 0.2; - - /// Just after a bad connection phase, may happen that all the sent epochs - /// are received at once. To make sure the actor is always the more 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; + /// `doll_sync_rate`. + int doll_connection_stats_frame_span = 60; ControllerType controller_type = CONTROLLER_TYPE_NULL; Controller *controller = nullptr; @@ -225,6 +198,10 @@ public: public: NetworkedController(); + ~NetworkedController(); + + void set_server_controlled(bool p_server_controlled); + bool get_server_controlled() const; void set_player_input_storage_size(int p_size); int get_player_input_storage_size() const; @@ -232,8 +209,8 @@ public: void set_max_redundant_inputs(int p_max); int get_max_redundant_inputs() const; - void set_tick_speedup_notification_delay(real_t p_delay); - real_t get_tick_speedup_notification_delay() const; + void set_tick_speedup_notification_delay(int p_delay_in_ms); + int get_tick_speedup_notification_delay() const; void set_network_traced_frames(int p_size); int get_network_traced_frames() const; @@ -244,17 +221,14 @@ public: void set_max_frames_delay(int p_val); int get_max_frames_delay() const; - void set_net_sensitivity(real_t p_val); - real_t get_net_sensitivity() const; - - void set_tick_acceleration(real_t p_acceleration); - real_t get_tick_acceleration() const; + void set_tick_acceleration(double p_acceleration); + double get_tick_acceleration() const; void set_doll_epoch_collect_rate(int p_rate); int get_doll_epoch_collect_rate() const; - void set_doll_epoch_batch_sync_rate(real_t p_rate); - real_t get_doll_epoch_batch_sync_rate() const; + void set_doll_sync_rate(uint32_t p_rate); + uint32_t get_doll_sync_rate() const; void set_doll_min_frames_delay(int p_min); int get_doll_min_frames_delay() const; @@ -262,17 +236,17 @@ public: void set_doll_max_frames_delay(int p_max); int get_doll_max_frames_delay() const; - void set_doll_interpolation_max_speedup(real_t p_speedup); - real_t get_doll_interpolation_max_speedup() const; + void set_doll_net_sensitivity(real_t p_sensitivity); + 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); int get_doll_connection_stats_frame_span() const; - void set_doll_net_sensitivity(real_t p_sensitivity); - real_t get_doll_net_sensitivity() const; - - void set_doll_max_delay(uint32_t p_max_delay); - uint32_t get_doll_max_delay() const; + void set_doll_virtual_delay_max_bias(uint32_t p_max_delay); + uint32_t get_doll_virtual_delay_max_bias() const; uint32_t get_current_input_id() const; @@ -285,7 +259,7 @@ public: } /// 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(); @@ -293,6 +267,14 @@ public: void set_doll_peer_active(int p_peer_id, bool p_active); 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); /// Returns the server controller or nullptr if this is not a server. @@ -308,6 +290,7 @@ public: NoNetController *get_nonet_controller(); const NoNetController *get_nonet_controller() const; + bool is_networking_initialized() const; bool is_server_controller() const; bool is_player_controller() const; bool is_doll_controller() const; @@ -321,24 +304,26 @@ public: bool has_scene_synchronizer() const; /* On server rpc functions. */ - void _rpc_server_send_inputs(const PoolVector &p_data); + void _rpc_server_send_inputs(const Vector &p_data); /* On client rpc functions. */ - void _rpc_send_tick_additional_speed(const PoolVector &p_data); + void _rpc_set_server_controlled(bool p_server_controlled); + void _rpc_notify_fps_acceleration(const Vector &p_data); /* On puppet rpc functions. */ void _rpc_doll_notify_sync_pause(uint32_t p_epoch); - void _rpc_doll_send_epoch_batch(const PoolVector &p_data); + void _rpc_doll_send_epoch_batch(const Vector &p_data); - void process(real_t p_delta); + void process(double p_delta); void player_set_has_new_input(bool p_has); bool player_has_new_input() const; void __on_sync_paused(); -private: - virtual void _notification(int p_what); +protected: + void _notification(int p_what); + void notify_controller_reset(); }; struct FrameSnapshot { @@ -346,6 +331,8 @@ struct FrameSnapshot { BitArray inputs_buffer; uint32_t buffer_size_bit; uint32_t similarity; + /// Local timestamp. + uint32_t received_timestamp; bool operator==(const FrameSnapshot &p_other) const { return p_other.id == id; @@ -376,57 +363,56 @@ struct ServerController : public Controller { int peer = 0; bool active = true; - real_t update_rate_factor = 1.0; - int collect_timer = 0; // In frames - int collect_threshold = 0; // In frames - LocalVector> epoch_batch; - uint32_t batch_size = 0; + real_t doll_sync_rate_factor = 1.0; + real_t doll_sync_timer = 0.0; + real_t doll_sync_time_threshold = 0.0; }; uint32_t current_input_buffer_id = UINT32_MAX; uint32_t ghost_input_count = 0; uint32_t last_sent_state_input_id = 0; - real_t client_tick_additional_speed = 0.0; - real_t additional_speed_notif_timer = 0.0; + uint32_t additional_fps_notif_timer = 0; std::deque snapshots; bool streaming_paused = false; bool enabled = true; - uint32_t input_arrival_time = UINT32_MAX; + uint32_t previous_frame_received_timestamp = UINT32_MAX; NetUtility::StatisticalRingBuffer network_watcher; + NetUtility::StatisticalRingBuffer consecutive_input_watcher; /// Used to sync the dolls. LocalVector peers; DataBuffer epoch_state_data_cache; uint32_t epoch = 0; bool is_epoch_important = false; - real_t batch_sync_timer = 0.0; ServerController( NetworkedController *p_node, int p_traced_frames); - void process(real_t p_delta); + void process(double p_delta); 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); - virtual void clear_peers(); - virtual void activate_peer(int p_peer); - virtual void deactivate_peer(int p_peer); + virtual void clear_peers() override; + virtual void activate_peer(int p_peer) override; + virtual void deactivate_peer(int p_peer) override; - void receive_inputs(const PoolVector &p_data); - int get_inputs_count() const; + virtual void receive_inputs(const Vector &p_data); + virtual int get_inputs_count() const; /// 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 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. /// /// 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 /// reason the server tells the client to slowdown so to keep the `frames_inputs` /// size moderate to the needs. - void calculates_player_tick_rate(real_t p_delta); - void adjust_player_tick_rate(real_t p_delta); + virtual void adjust_player_tick_rate(double p_delta); uint32_t find_peer(int p_peer) const; }; +struct AutonomousServerController : public ServerController { + AutonomousServerController( + NetworkedController *p_node); + + virtual void receive_inputs(const Vector &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 { uint32_t current_input_id; uint32_t input_buffers_counter; - real_t time_bank; - real_t tick_additional_speed; + double time_bank; + double acceleration_fps_speed = 0.0; + double acceleration_fps_timer = 1.0; bool streaming_paused = false; + double pretended_delta = 1.0; std::deque frames_snapshot; LocalVector cached_packet_data; PlayerController(NetworkedController *p_node); - void process(real_t p_delta); - int calculates_sub_ticks(real_t p_delta, real_t p_iteration_per_seconds); + void process(double p_delta); + /// 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); uint32_t last_known_input() 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); - real_t get_pretended_delta(real_t p_iteration_per_second) const; 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 /// for the server to stop the data streaming. struct DollController : public Controller { - Interpolator interpolator; - real_t additional_speed = 0.0; - uint32_t current_epoch = UINT32_MAX; - real_t advancing_epoch = 0.0; - uint32_t missing_epochs = 0; + real_t interpolation_alpha = 0.0; + real_t interpolation_time_window = 0.0; + + uint32_t current_epoch = 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. uint32_t paused_epoch = 0; // 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. - NetUtility::StatisticalRingBuffer network_watcher; + NetUtility::StatisticalRingBuffer network_watcher; DollController(NetworkedController *p_node); - virtual void ready(); - void process(real_t p_delta); + virtual void ready() override; + void process(double p_delta); // 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 &p_data); - uint32_t receive_epoch(const PoolVector &p_data); + void receive_epoch(const Vector &p_data); - uint32_t next_epoch(); void pause(uint32_t p_epoch); }; @@ -520,8 +522,8 @@ struct NoNetController : public Controller { NoNetController(NetworkedController *p_node); - void process(real_t p_delta); - virtual uint32_t get_current_input_id() const; + void process(double p_delta); + virtual uint32_t get_current_input_id() const override; }; #endif diff --git a/modules/network_synchronizer/register_types.cpp b/modules/network_synchronizer/register_types.cpp index 2c397dea9..9273b9bd2 100644 --- a/modules/network_synchronizer/register_types.cpp +++ b/modules/network_synchronizer/register_types.cpp @@ -1,13 +1,12 @@ /*************************************************************************/ /* register_types.cpp */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -35,25 +34,33 @@ #include "register_types.h" -#include "core/config/project_settings.h" +#include "core/engine.h" #include "data_buffer.h" -#include "interpolator.h" #include "networked_controller.h" #include "scene_diff.h" #include "scene_synchronizer.h" +#include "scene_synchronizer_debugger.h" +#include "input_network_encoder.h" -void register_network_synchronizer_types(ModuleRegistrationLevel p_level) { - if (p_level == MODULE_REGISTRATION_LEVEL_SCENE) { - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); +void register_network_synchronizer_types() { + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); - GLOBAL_DEF("NetworkSynchronizer/debug_server_speedup", false); - GLOBAL_DEF("NetworkSynchronizer/debug_doll_speedup", false); - } + memnew(SceneSynchronizerDebugger); + Engine::get_singleton()->add_singleton(Engine::Singleton("SceneSynchronizerDebugger", SceneSynchronizerDebugger::singleton())); + + GLOBAL_DEF("NetworkSynchronizer/debug_server_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()); } diff --git a/modules/network_synchronizer/register_types.h b/modules/network_synchronizer/register_types.h index ec72dcc33..5fdca264f 100644 --- a/modules/network_synchronizer/register_types.h +++ b/modules/network_synchronizer/register_types.h @@ -1,16 +1,12 @@ -#ifndef NETWORK_SYNCHRONIZER_REGISTER_TYPES_H -#define NETWORK_SYNCHRONIZER_REGISTER_TYPES_H - /*************************************************************************/ /* register_types.h */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -36,9 +32,5 @@ @author AndreaCatania */ -#include "modules/register_module_types.h" - -void register_network_synchronizer_types(ModuleRegistrationLevel p_level); -void unregister_network_synchronizer_types(ModuleRegistrationLevel p_level); - -#endif \ No newline at end of file +void register_network_synchronizer_types(); +void unregister_network_synchronizer_types(); diff --git a/modules/network_synchronizer/scene_diff.cpp b/modules/network_synchronizer/scene_diff.cpp index 9fe8754b4..a900215f3 100644 --- a/modules/network_synchronizer/scene_diff.cpp +++ b/modules/network_synchronizer/scene_diff.cpp @@ -1,13 +1,12 @@ /*************************************************************************/ /* scene_diff.cpp */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ diff --git a/modules/network_synchronizer/scene_diff.h b/modules/network_synchronizer/scene_diff.h index 39152adc2..087cd58f2 100644 --- a/modules/network_synchronizer/scene_diff.h +++ b/modules/network_synchronizer/scene_diff.h @@ -1,16 +1,12 @@ -#ifndef SCENE_DIFF_H -#define SCENE_DIFF_H - /*************************************************************************/ /* scene_diff.h */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -36,8 +32,10 @@ @author AndreaCatania */ -#include "core/containers/local_vector.h" -#include "core/object/class_db.h" +#ifndef SCENE_DIFF_H +#define SCENE_DIFF_H + +#include "core/class_db.h" #include "net_utilities.h" class SceneSynchronizer; diff --git a/modules/network_synchronizer/scene_synchronizer.cpp b/modules/network_synchronizer/scene_synchronizer.cpp index 64c23d32b..924078ef4 100644 --- a/modules/network_synchronizer/scene_synchronizer.cpp +++ b/modules/network_synchronizer/scene_synchronizer.cpp @@ -1,13 +1,12 @@ /*************************************************************************/ /* scene_synchronizer.cpp */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -35,11 +34,14 @@ #include "scene_synchronizer.h" -#include "core/config/engine.h" -#include "core/object/class_db.h" +#include "core/method_bind_ext.gen.inc" +#include "core/os/os.h" +#include "input_network_encoder.h" #include "networked_controller.h" -#include "scene/main/viewport.h" #include "scene_diff.h" +#include "scene_synchronizer_debugger.h" + +#include "godot_backward_utility_cpp.h" void SceneSynchronizer::_bind_methods() { BIND_ENUM_CONSTANT(CHANGE) @@ -60,6 +62,12 @@ void SceneSynchronizer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_comparison_float_tolerance", "tolerance"), &SceneSynchronizer::set_comparison_float_tolerance); ClassDB::bind_method(D_METHOD("get_comparison_float_tolerance"), &SceneSynchronizer::get_comparison_float_tolerance); + ClassDB::bind_method(D_METHOD("set_actions_redundancy", "redundancy"), &SceneSynchronizer::set_actions_redundancy); + ClassDB::bind_method(D_METHOD("get_actions_redundancy"), &SceneSynchronizer::get_actions_redundancy); + + ClassDB::bind_method(D_METHOD("set_actions_resend_time", "time"), &SceneSynchronizer::set_actions_resend_time); + ClassDB::bind_method(D_METHOD("get_actions_resend_time"), &SceneSynchronizer::get_actions_resend_time); + ClassDB::bind_method(D_METHOD("register_node", "node"), &SceneSynchronizer::register_node_gdscript); ClassDB::bind_method(D_METHOD("unregister_node", "node"), &SceneSynchronizer::unregister_node); ClassDB::bind_method(D_METHOD("get_node_id", "node"), &SceneSynchronizer::get_node_id); @@ -73,6 +81,11 @@ void SceneSynchronizer::_bind_methods() { ClassDB::bind_method(D_METHOD("stop_node_sync", "node"), &SceneSynchronizer::stop_node_sync); ClassDB::bind_method(D_METHOD("is_node_sync", "node"), &SceneSynchronizer::is_node_sync); + ClassDB::bind_method(D_METHOD("register_action", "node", "action_func", "action_encoding_func", "can_client_trigger", "wait_server_validation", "server_action_validation_func"), &SceneSynchronizer::register_action, DEFVAL(false), DEFVAL(false), DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("find_action_id", "node", "event_name"), &SceneSynchronizer::find_action_id); + ClassDB::bind_method(D_METHOD("trigger_action_by_name", "node", "event_name", "arguments", "recipients_peers"), &SceneSynchronizer::trigger_action_by_name, DEFVAL(Array()), DEFVAL(Vector())); + ClassDB::bind_method(D_METHOD("trigger_action", "node", "action_id", "arguments", "recipients_peers"), &SceneSynchronizer::trigger_action, DEFVAL(Array()), DEFVAL(Vector())); + ClassDB::bind_method(D_METHOD("set_skip_rewinding", "node", "variable", "skip_rewinding"), &SceneSynchronizer::set_skip_rewinding); ClassDB::bind_method(D_METHOD("track_variable_changes", "node", "variable", "object", "method", "flags"), &SceneSynchronizer::track_variable_changes, DEFVAL(NetEventFlag::DEFAULT)); @@ -117,12 +130,17 @@ void SceneSynchronizer::_bind_methods() { ClassDB::bind_method(D_METHOD("_rpc_notify_need_full_snapshot"), &SceneSynchronizer::_rpc_notify_need_full_snapshot); ClassDB::bind_method(D_METHOD("_rpc_set_network_enabled", "enabled"), &SceneSynchronizer::_rpc_set_network_enabled); ClassDB::bind_method(D_METHOD("_rpc_notify_peer_status", "enabled"), &SceneSynchronizer::_rpc_notify_peer_status); + ClassDB::bind_method(D_METHOD("_rpc_send_actions", "enabled"), &SceneSynchronizer::_rpc_send_actions); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "server_notify_state_interval", PROPERTY_HINT_RANGE, "0.001,10.0,0.0001"), "set_server_notify_state_interval", "get_server_notify_state_interval"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "comparison_float_tolerance", PROPERTY_HINT_RANGE, "0.000001,0.01,0.000001"), "set_comparison_float_tolerance", "get_comparison_float_tolerance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "server_notify_state_interval", PROPERTY_HINT_RANGE, "0.001,10.0,0.0001"), "set_server_notify_state_interval", "get_server_notify_state_interval"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "comparison_float_tolerance", PROPERTY_HINT_RANGE, "0.000001,0.01,0.000001"), "set_comparison_float_tolerance", "get_comparison_float_tolerance"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "actions_redundancy", PROPERTY_HINT_RANGE, "1,10,1"), "set_actions_redundancy", "get_actions_redundancy"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "actions_resend_time", PROPERTY_HINT_RANGE, "0.000001,0.5,0.000001"), "set_actions_resend_time", "get_actions_resend_time"); ADD_SIGNAL(MethodInfo("sync_started")); ADD_SIGNAL(MethodInfo("sync_paused")); + + ADD_SIGNAL(MethodInfo("desync_detected", PropertyInfo(Variant::INT, "input_id"), PropertyInfo(Variant::OBJECT, "node"), PropertyInfo(Variant::ARRAY, "var_names"), PropertyInfo(Variant::ARRAY, "client_values"), PropertyInfo(Variant::ARRAY, "server_values"))); } void SceneSynchronizer::_notification(int p_what) { @@ -148,10 +166,10 @@ void SceneSynchronizer::_notification(int p_what) { clear(); reset_synchronizer_mode(); - get_multiplayer()->connect("network_peer_connected", this, "_on_peer_connected"); - get_multiplayer()->connect("network_peer_disconnected", this, "_on_peer_disconnected"); + get_multiplayer()->connect(SNAME("network_peer_connected"), Callable(this, SNAME("_on_peer_connected"))); + get_multiplayer()->connect(SNAME("network_peer_disconnected"), Callable(this, SNAME("_on_peer_disconnected"))); - get_tree()->connect("node_removed", this, "_on_node_removed"); + get_tree()->connect(SNAME("node_removed"), Callable(this, SNAME("_on_node_removed"))); // Make sure to reset all the assigned controllers. reset_controllers(); @@ -172,10 +190,10 @@ void SceneSynchronizer::_notification(int p_what) { clear_peers(); - get_multiplayer()->disconnect("network_peer_connected", this, "_on_peer_connected"); - get_multiplayer()->disconnect("network_peer_disconnected", this, "_on_peer_disconnected"); + get_multiplayer()->disconnect(SNAME("network_peer_connected"), Callable(this, SNAME("_on_peer_connected"))); + get_multiplayer()->disconnect(SNAME("network_peer_disconnected"), Callable(this, SNAME("_on_peer_disconnected"))); - get_tree()->disconnect("node_removed", this, "_on_node_removed"); + get_tree()->disconnect(SNAME("node_removed"), Callable(this, SNAME("_on_node_removed"))); clear(); @@ -194,12 +212,13 @@ void SceneSynchronizer::_notification(int p_what) { } SceneSynchronizer::SceneSynchronizer() { - rpc_config("_rpc_send_state", MultiplayerAPI::RPC_MODE_REMOTE); - rpc_config("_rpc_notify_need_full_snapshot", MultiplayerAPI::RPC_MODE_REMOTE); - rpc_config("_rpc_set_network_enabled", MultiplayerAPI::RPC_MODE_REMOTE); - rpc_config("_rpc_notify_peer_status", MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_send_state"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_notify_need_full_snapshot"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_set_network_enabled"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_notify_peer_status"), MultiplayerAPI::RPC_MODE_REMOTE); + rpc_config(SNAME("_rpc_send_actions"), MultiplayerAPI::RPC_MODE_REMOTE); - // Avoid too much useless re-allocations. + // Avoid too much useless re-allocations event_listener.reserve(100); } @@ -228,6 +247,30 @@ real_t SceneSynchronizer::get_comparison_float_tolerance() const { return comparison_float_tolerance; } +void SceneSynchronizer::set_actions_redundancy(int p_redundancy) { + actions_redundancy = p_redundancy; +} + +int SceneSynchronizer::get_actions_redundancy() const { + return actions_redundancy; +} + +void SceneSynchronizer::set_actions_resend_time(real_t p_time) { + actions_resend_time = p_time; +} + +real_t SceneSynchronizer::get_actions_resend_time() const { + return actions_resend_time; +} + +bool SceneSynchronizer::is_variable_registered(Node *p_node, const StringName &p_variable) const { + const NetUtility::NodeData *nd = find_node_data(p_node); + if (nd != nullptr) { + return nd->vars.find(p_variable) >= 0; + } + return false; +} + NetUtility::NodeData *SceneSynchronizer::register_node(Node *p_node) { ERR_FAIL_COND_V(p_node == nullptr, nullptr); @@ -251,9 +294,11 @@ NetUtility::NodeData *SceneSynchronizer::register_node(Node *p_node) { add_node_data(nd); - NET_DEBUG_PRINT("New node registered" + (generate_id ? String(" #ID: ") + itos(nd->id) : "") + " : " + p_node->get_path()); + SceneSynchronizerDebugger::singleton()->debug_print(this, "New node registered" + (generate_id ? String(" #ID: ") + itos(nd->id) : "") + " : " + p_node->get_path()); } + SceneSynchronizerDebugger::singleton()->register_class_for_node_to_dump(p_node); + return nd; } @@ -300,7 +345,11 @@ void SceneSynchronizer::register_variable(Node *p_node, const StringName &p_vari const int index = node_data->vars.find(p_variable); if (index == -1) { // The variable is not yet registered. - const Variant old_val = p_node->get(p_variable); + bool valid = false; + const Variant old_val = p_node->get(p_variable, &valid); + if (valid == false) { + SceneSynchronizerDebugger::singleton()->debug_error(this, "The variable `" + p_variable + "` on the node `" + p_node->get_path() + "` was not found, make sure the variable exist."); + } const int var_id = generate_id ? node_data->vars.size() : UINT32_MAX; node_data->vars.push_back( NetUtility::VarData( @@ -387,6 +436,132 @@ bool SceneSynchronizer::is_node_sync(const Node *p_node) const { return nd->sync_enabled; } +NetActionId SceneSynchronizer::register_action( + Node *p_node, + const StringName &p_action_func, + const StringName &p_action_encoding_func, + bool p_can_client_trigger, + bool p_wait_server_validation, + const StringName &p_server_action_validation_func) { + ERR_FAIL_COND_V(p_node == nullptr, UINT32_MAX); + + // Validate the functions. + List methods; + p_node->get_method_list(&methods); + + MethodInfo *act_func_info = nullptr; + MethodInfo *act_encoding_func_info = nullptr; + MethodInfo *server_event_validation_info = nullptr; + + for (List::Element *e = methods.front(); e; e = e->next()) { + if (e->get().name == p_action_func) { + act_func_info = &e->get(); + } else if (e->get().name == p_action_encoding_func) { + act_encoding_func_info = &e->get(); + } else if (p_server_action_validation_func != StringName() && e->get().name == p_server_action_validation_func) { + server_event_validation_info = &e->get(); + } + } + + ERR_FAIL_COND_V_MSG(act_func_info == nullptr, UINT32_MAX, "The passed `" + p_node->get_path() + "` doesn't have the event function `" + p_action_func + "`"); + ERR_FAIL_COND_V_MSG(act_encoding_func_info == nullptr, UINT32_MAX, "The passed `" + p_node->get_path() + "` doesn't have the event_encoding function `" + p_action_encoding_func + "`"); + + ERR_FAIL_COND_V_MSG(act_encoding_func_info->arguments.size() != 1, UINT32_MAX, "`" + p_node->get_path() + "` - The passed event_encoding function `" + p_action_encoding_func + "` should have 1 argument with type `InputNetworkEncoder`."); + if (act_encoding_func_info->arguments[0].type != Variant::NIL) { + // If the paramter is typed, make sure it's the correct type. + ERR_FAIL_COND_V_MSG(act_encoding_func_info->arguments[0].type != Variant::OBJECT, UINT32_MAX, "`" + p_node->get_path() + "` - The passed event_encoding function `" + p_action_encoding_func + "` should have 1 argument with type `InputNetworkEncoder`."); + ERR_FAIL_COND_V_MSG(act_encoding_func_info->arguments[0].hint != PropertyHint::PROPERTY_HINT_RESOURCE_TYPE, UINT32_MAX, "`" + p_node->get_path() + "` - The passed event_encoding function `" + p_action_encoding_func + "` should have 1 argument with type `InputNetworkEncoder`."); + ERR_FAIL_COND_V_MSG(act_encoding_func_info->arguments[0].hint_string != "InputNetworkEncoder", UINT32_MAX, "`" + p_node->get_path() + "` - The passed event_encoding function `" + p_action_encoding_func + "` should have 1 argument with type `InputNetworkEncoder`."); + } + + if (server_event_validation_info) { + if (server_event_validation_info->return_val.type != Variant::NIL) { + ERR_FAIL_COND_V_MSG(server_event_validation_info->return_val.type != Variant::BOOL, UINT32_MAX, "`" + p_node->get_path() + "` - The passed server_action_validation_func `" + p_server_action_validation_func + "` should return a boolean."); + } + + // Validate the arguments count. + ERR_FAIL_COND_V_MSG(server_event_validation_info->arguments.size() != act_func_info->arguments.size(), UINT32_MAX, "`" + p_node->get_path() + "` - The function `" + p_server_action_validation_func + "` and `" + p_action_func + "` should have the same arguments."); + + // Validate the argument types. + List::Element *e_e = act_func_info->arguments.front(); + List::Element *sevi_e = server_event_validation_info->arguments.front(); + for (; e_e; e_e = e_e->next(), sevi_e = sevi_e->next()) { + ERR_FAIL_COND_V_MSG(sevi_e->get().type != e_e->get().type, UINT32_MAX, "`" + p_node->get_path() + "` - The function `" + p_server_action_validation_func + "` and `" + p_action_func + "` should have the same arguments."); + } + } + + // Fetch the function encoder and verify it can property encode the act_func arguments. + Ref network_encoder; + network_encoder.instance(); + p_node->call(p_action_encoding_func, network_encoder); + const LocalVector &encoding_info = network_encoder->get_input_info(); + + ERR_FAIL_COND_V_MSG(encoding_info.size() != (uint32_t)act_func_info->arguments.size(), UINT32_MAX, "`" + p_node->get_path() + "` - The encoding function should provide an encoding for each argument of `" + p_action_func + "` function (Note the order matters)."); + int i = 0; + for (List::Element *e = act_func_info->arguments.front(); e; e = e->next()) { + if (e->get().type != Variant::NIL) { + ERR_FAIL_COND_V_MSG(encoding_info[i].default_value.get_type() != e->get().type, UINT32_MAX, "`" + p_node->get_path() + "` - The encoding function " + itos(i) + " parameter is providing a wrong encoding for `" + e->get().name + "`."); + } + i++; + } + + // At this point the validation passed. Just register the event. + NetUtility::NodeData *node_data = register_node(p_node); + ERR_FAIL_COND_V(node_data == nullptr, UINT32_MAX); + + NetActionId action_id = find_action_id(p_node, p_action_func); + ERR_FAIL_COND_V_MSG(action_id != UINT32_MAX, UINT32_MAX, "`" + p_node->get_path() + "` The event `" + p_action_func + "` is already registered, this should never happen."); + + action_id = node_data->net_actions.size(); + node_data->net_actions.resize(action_id + 1); + node_data->net_actions[action_id].id = action_id; + node_data->net_actions[action_id].act_func = p_action_func; + node_data->net_actions[action_id].act_encoding_func = p_action_encoding_func; + node_data->net_actions[action_id].can_client_trigger = p_can_client_trigger; + node_data->net_actions[action_id].wait_server_validation = p_wait_server_validation; + node_data->net_actions[action_id].server_action_validation_func = p_server_action_validation_func; + node_data->net_actions[action_id].network_encoder = network_encoder; + + SceneSynchronizerDebugger::singleton()->debug_print(this, "The event `" + p_action_func + "` on the node `" + p_node->get_path() + "` registered (act_encoding_func: `" + p_action_encoding_func + "`, wait_server_validation: `" + (p_server_action_validation_func ? "true" : "false") + "`, server_action_validation_func: `" + p_server_action_validation_func + "`)."); + return action_id; +} + +NetActionId SceneSynchronizer::find_action_id(Node *p_node, const StringName &p_action_func) const { + const NetUtility::NodeData *nd = find_node_data(p_node); + if (nd) { + NetActionInfo e; + e.act_func = p_action_func; + const int64_t i = nd->net_actions.find(e); + return i == -1 ? UINT32_MAX : NetActionId(i); + } + return UINT32_MAX; +} + +void SceneSynchronizer::trigger_action_by_name( + Node *p_node, + const StringName &p_action_func, + const Array &p_arguments, + const Vector &p_recipients) { + const NetActionId id = find_action_id(p_node, p_action_func); + trigger_action(p_node, id, p_arguments, p_recipients); +} + +void SceneSynchronizer::trigger_action( + Node *p_node, + NetActionId p_action_id, + const Array &p_arguments, + const Vector &p_recipients) { + ERR_FAIL_COND(p_node == nullptr); + + NetUtility::NodeData *nd = find_node_data(p_node); + ERR_FAIL_COND_MSG(nd == nullptr, "The event was not found."); + ERR_FAIL_COND_MSG(p_action_id >= nd->net_actions.size(), "The event was not found."); + ERR_FAIL_COND_MSG(nd->net_actions[p_action_id].network_encoder->get_input_info().size() != uint32_t(p_arguments.size()), "The event `" + p_node->get_path() + "::" + nd->net_actions[p_action_id].act_func + "` was called with the wrong amount of arguments."); + ERR_FAIL_COND_MSG(nd->net_actions[p_action_id].can_client_trigger == false && is_client(), "The client is not allowed to trigger this action `" + nd->net_actions[p_action_id].act_func + "`."); + + synchronizer->on_action_triggered(nd, p_action_id, p_arguments, p_recipients); +} + uint32_t SceneSynchronizer::get_variable_id(Node *p_node, const StringName &p_variable) { ERR_FAIL_COND_V(p_node == nullptr, UINT32_MAX); ERR_FAIL_COND_V(p_variable == StringName(), UINT32_MAX); @@ -586,9 +761,9 @@ void SceneSynchronizer::controller_remove_dependency(Node *p_controller, Node *p return; } - // Instead to remove the dependency immediately we have to postpone it till + // Instead to remove the dependency immeditaly we have to postpone it till // the server confirms the valitity via state. - // This operation is required otherwise the dependency is removed too early, + // This operation is required otherwise the dependency is remvoved too early, // and an eventual rewind may miss it. // The actual removal is performed at the end of the sync. controller_nd->dependency_nodes_end[index] = @@ -720,8 +895,11 @@ void SceneSynchronizer::apply_scene_changes(const Variant &p_sync_data) { // Parse the Node: [](void *p_user_pointer, NetUtility::NodeData *p_node_data) {}, + // Parse InputID: + [](void *p_user_pointer, uint32_t p_input_id) {}, + // Parse controller: - [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id) {}, + [](void *p_user_pointer, NetUtility::NodeData *p_node_data) {}, // Parse variable: [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_var_id, const Variant &p_value) { @@ -746,8 +924,8 @@ void SceneSynchronizer::apply_scene_changes(const Variant &p_sync_data) { }); if (success == false) { - NET_DEBUG_ERR("Scene changes:"); - NET_DEBUG_ERR(p_sync_data); + SceneSynchronizerDebugger::singleton()->debug_error(this, "Scene changes:"); + SceneSynchronizerDebugger::singleton()->debug_error(this, p_sync_data); } change_events_flush(); @@ -784,7 +962,7 @@ void SceneSynchronizer::dirty_peers() { void SceneSynchronizer::set_enabled(bool p_enable) { ERR_FAIL_COND_MSG(synchronizer_type == SYNCHRONIZER_TYPE_SERVER, "The server is always enabled."); if (synchronizer_type == SYNCHRONIZER_TYPE_CLIENT) { - rpc_id(1, "_rpc_set_network_enabled", p_enable); + rpc_id(1, SNAME("_rpc_set_network_enabled"), p_enable); if (p_enable == false) { // If the peer want to disable, we can disable it locally // immediately. When it wants to enable the networking, the server @@ -828,7 +1006,7 @@ void SceneSynchronizer::set_peer_networking_enable(int p_peer, bool p_enable) { dirty_peers(); // Just notify the peer status. - rpc_id(p_peer, "_rpc_notify_peer_status", p_enable); + rpc_id(p_peer, SNAME("_rpc_notify_peer_status"), p_enable); } else { ERR_FAIL_COND_MSG(synchronizer_type != SYNCHRONIZER_TYPE_NONETWORK, "At this point no network is expected."); static_cast(synchronizer)->set_enabled(p_enable); @@ -842,7 +1020,7 @@ bool SceneSynchronizer::is_peer_networking_enable(int p_peer) const { return true; } - NetUtility::PeerData *pd = peer_data.lookup_ptr(p_peer); + const NetUtility::PeerData *pd = peer_data.lookup_ptr(p_peer); ERR_FAIL_COND_V_MSG(pd == nullptr, false, "The peer: " + itos(p_peer) + " is not know. [bug]"); return pd->enabled; } else { @@ -949,10 +1127,10 @@ void SceneSynchronizer::clear() { } } - node_data.clear(); - organized_node_data.clear(); - node_data_controllers.clear(); - event_listener.clear(); + node_data.reset(); + organized_node_data.reset(); + node_data_controllers.reset(); + event_listener.reset(); // Avoid too much useless re-allocations. event_listener.reserve(100); @@ -962,8 +1140,12 @@ void SceneSynchronizer::clear() { } } +void SceneSynchronizer::notify_controller_control_mode_changed(NetworkedController *controller) { + reset_controller(find_node_data(controller)); +} + void SceneSynchronizer::_rpc_send_state(const Variant &p_snapshot) { - ERR_FAIL_COND_MSG(is_client() == false, "Only clients are supposed to receive the server snapshot."); + ERR_FAIL_COND_MSG(is_client() == false, "Only clients are suposed to receive the server snapshot."); static_cast(synchronizer)->receive_snapshot(p_snapshot); } @@ -988,6 +1170,24 @@ void SceneSynchronizer::_rpc_notify_peer_status(bool p_enabled) { static_cast(synchronizer)->set_enabled(p_enabled); } +void SceneSynchronizer::_rpc_send_actions(const Vector &p_data) { + // Anyone can receive acts. + DataBuffer db(p_data); + db.begin_read(); + + LocalVector received_actions; + + const int sender_peer = get_tree()->get_multiplayer()->get_rpc_sender_id(); + + net_action::decode_net_action( + this, + db, + sender_peer, + received_actions); + + synchronizer->on_actions_received(sender_peer, received_actions); +} + void SceneSynchronizer::update_peers() { #ifdef DEBUG_ENABLED // This function is only called on server. @@ -1142,7 +1342,7 @@ void SceneSynchronizer::change_events_flush() { vars[v] = listener.watching_vars[v].old_value; listener.watching_vars[v].old_set = false; } else { - // This value is not changed, so just retrieve the current one. + // This value is not changed, so just retrive the current one. vars[v] = listener.watching_vars[v].node_data->vars[listener.watching_vars[v].var_id].var.value; } vars_ptr[v] = vars.ptr() + v; @@ -1178,7 +1378,7 @@ void SceneSynchronizer::add_node_data(NetUtility::NodeData *p_node_data) { // Now you have the scene with two different nodes but same path. for (uint32_t i = 0; i < node_data.size(); i += 1) { if (node_data[i]->node->get_path() == p_node_data->node->get_path()) { - NET_DEBUG_ERR("You have two different nodes with the same path: " + p_node_data->node->get_path() + ". This will cause troubles. Fix it."); + SceneSynchronizerDebugger::singleton()->debug_error(this, "You have two different nodes with the same path: " + p_node_data->node->get_path() + ". This will cause troubles. Fix it."); break; } } @@ -1221,6 +1421,12 @@ void SceneSynchronizer::drop_node_data(NetUtility::NodeData *p_node_data) { p_node_data->controlled_by = nullptr; } + // Set all controlled nodes as not controlled by this. + for (uint32_t i = 0; i < p_node_data->controlled_nodes.size(); i += 1) { + p_node_data->controlled_nodes[i]->controlled_by = nullptr; + } + p_node_data->controlled_nodes.clear(); + if (p_node_data->is_controller) { // This is a controller, make sure to reset the peers. static_cast(p_node_data->node)->set_scene_synchronizer(nullptr); @@ -1277,7 +1483,20 @@ void SceneSynchronizer::set_node_data_id(NetUtility::NodeData *p_node_data, NetN } p_node_data->id = p_id; organized_node_data[p_id] = p_node_data; - NET_DEBUG_PRINT("NetNodeId: " + itos(p_id) + " just assigned to: " + p_node_data->node->get_path()); + SceneSynchronizerDebugger::singleton()->debug_print(this, "NetNodeId: " + itos(p_id) + " just assigned to: " + p_node_data->node->get_path()); +} + +NetworkedController *SceneSynchronizer::fetch_controller_by_peer(int peer) { + NetUtility::PeerData *data = peer_data.lookup_ptr(peer); + if (data && data->controller_id != UINT32_MAX) { + NetUtility::NodeData *nd = get_node_data(data->controller_id); + if (nd) { + if (nd->is_controller) { + return static_cast(nd->node); + } + } + } + return nullptr; } bool SceneSynchronizer::compare(const Vector2 &p_first, const Vector2 &p_second) const { @@ -1310,7 +1529,7 @@ bool SceneSynchronizer::compare(const Variant &p_first, const Variant &p_second, // Custom evaluation methods switch (p_first.get_type()) { - case Variant::REAL: { + case Variant::FLOAT: { return Math::is_equal_approx(p_first, p_second, p_tolerance); } case Variant::VECTOR2: { @@ -1329,22 +1548,22 @@ bool SceneSynchronizer::compare(const Variant &p_first, const Variant &p_second, case Variant::TRANSFORM2D: { const Transform2D a(p_first); const Transform2D b(p_second); - if (compare(a.columns[0], b.columns[0], p_tolerance)) { - if (compare(a.columns[1], b.columns[1], p_tolerance)) { - if (compare(a.columns[2], b.columns[2], p_tolerance)) { + if (compare(a.elements[0], b.elements[0], p_tolerance)) { + if (compare(a.elements[1], b.elements[1], p_tolerance)) { + if (compare(a.elements[2], b.elements[2], p_tolerance)) { return true; } } } return false; } - case Variant::VECTOR3: { + case Variant::VECTOR3: return compare(Vector3(p_first), Vector3(p_second), p_tolerance); - } - case Variant::QUATERNION: { - const Quaternion a = p_first; - const Quaternion b = p_second; - const Quaternion r(a - b); // Element wise subtraction. + + case Variant::QUAT: { + const Quat a = p_first; + const Quat b = p_second; + const Quat r(a - b); // Element wise subtraction. return (r.x * r.x + r.y * r.y + r.z * r.z + r.w * r.w) <= (p_tolerance * p_tolerance); } case Variant::PLANE: { @@ -1370,9 +1589,9 @@ bool SceneSynchronizer::compare(const Variant &p_first, const Variant &p_second, case Variant::BASIS: { const Basis a = p_first; const Basis b = p_second; - if (compare(a.rows[0], b.rows[0], p_tolerance)) { - if (compare(a.rows[1], b.rows[1], p_tolerance)) { - if (compare(a.rows[2], b.rows[2], p_tolerance)) { + if (compare(a.elements[0], b.elements[0], p_tolerance)) { + if (compare(a.elements[1], b.elements[1], p_tolerance)) { + if (compare(a.elements[2], b.elements[2], p_tolerance)) { return true; } } @@ -1383,9 +1602,9 @@ bool SceneSynchronizer::compare(const Variant &p_first, const Variant &p_second, const Transform a = p_first; const Transform b = p_second; if (compare(a.origin, b.origin, p_tolerance)) { - if (compare(a.basis.rows[0], b.basis.rows[0], p_tolerance)) { - if (compare(a.basis.rows[1], b.basis.rows[1], p_tolerance)) { - if (compare(a.basis.rows[2], b.basis.rows[2], p_tolerance)) { + if (compare(a.basis.elements[0], b.basis.elements[0], p_tolerance)) { + if (compare(a.basis.elements[1], b.basis.elements[1], p_tolerance)) { + if (compare(a.basis.elements[2], b.basis.elements[2], p_tolerance)) { return true; } } @@ -1467,7 +1686,7 @@ void SceneSynchronizer::validate_nodes() { // Removes the invalidated `NodeData`. if (null_objects.size()) { - NET_DEBUG_ERR("At least one node has been removed from the tree without the SceneSynchronizer noticing. This shouldn't happen."); + SceneSynchronizerDebugger::singleton()->debug_error(this, "At least one node has been removed from the tree without the SceneSynchronizer noticing. This shouldn't happen."); for (uint32_t i = 0; i < null_objects.size(); i += 1) { drop_node_data(null_objects[i]); } @@ -1530,12 +1749,12 @@ const NetUtility::NodeData *SceneSynchronizer::find_node_data(const Node *p_node } NetUtility::NodeData *SceneSynchronizer::get_node_data(NetNodeId p_id) { - ERR_FAIL_INDEX_V(p_id, organized_node_data.size(), nullptr); + ERR_FAIL_UNSIGNED_INDEX_V(p_id, organized_node_data.size(), nullptr); return organized_node_data[p_id]; } const NetUtility::NodeData *SceneSynchronizer::get_node_data(NetNodeId p_id) const { - ERR_FAIL_INDEX_V(p_id, organized_node_data.size(), nullptr); + ERR_FAIL_UNSIGNED_INDEX_V(p_id, organized_node_data.size(), nullptr); return organized_node_data[p_id]; } @@ -1579,9 +1798,14 @@ void SceneSynchronizer::reset_controller(NetUtility::NodeData *p_controller_nd) controller->controller_type = NetworkedController::CONTROLLER_TYPE_NONETWORK; controller->controller = memnew(NoNetController(controller)); } else if (get_tree()->get_multiplayer()->is_network_server()) { - controller->controller_type = NetworkedController::CONTROLLER_TYPE_SERVER; - controller->controller = memnew(ServerController(controller, controller->get_network_traced_frames())); - } else if (controller->is_network_master()) { + if (controller->get_server_controlled()) { + controller->controller_type = NetworkedController::CONTROLLER_TYPE_AUTONOMOUS_SERVER; + controller->controller = memnew(AutonomousServerController(controller)); + } else { + controller->controller_type = NetworkedController::CONTROLLER_TYPE_SERVER; + controller->controller = memnew(ServerController(controller, controller->get_network_traced_frames())); + } + } else if (controller->is_network_master() && controller->get_server_controlled() == false) { controller->controller_type = NetworkedController::CONTROLLER_TYPE_PLAYER; controller->controller = memnew(PlayerController(controller)); } else { @@ -1592,6 +1816,7 @@ void SceneSynchronizer::reset_controller(NetUtility::NodeData *p_controller_nd) dirty_peers(); controller->controller->ready(); + controller->notify_controller_reset(); if (synchronizer) { synchronizer->on_controller_reset(p_controller_nd); @@ -1599,6 +1824,8 @@ void SceneSynchronizer::reset_controller(NetUtility::NodeData *p_controller_nd) } void SceneSynchronizer::process() { + PROFILE_NODE + #ifdef DEBUG_ENABLED validate_nodes(); // Never triggered because this function is called by `PHYSICS_PROCESS`, @@ -1638,10 +1865,13 @@ Synchronizer::Synchronizer(SceneSynchronizer *p_node) : } NoNetSynchronizer::NoNetSynchronizer(SceneSynchronizer *p_node) : - Synchronizer(p_node) {} + Synchronizer(p_node) { + SceneSynchronizerDebugger::singleton()->setup_debugger("nonet", 0, scene_synchronizer->get_tree()); +} void NoNetSynchronizer::clear() { enabled = true; + frame_count = 0; } void NoNetSynchronizer::process() { @@ -1649,7 +1879,15 @@ void NoNetSynchronizer::process() { return; } - const real_t delta = scene_synchronizer->get_physics_process_delta_time(); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "NoNetSynchronizer::process", true); + + const uint32_t frame_index = frame_count; + frame_count += 1; + + SceneSynchronizerDebugger::singleton()->scene_sync_process_start(scene_synchronizer); + + const double physics_ticks_per_second = Engine::get_singleton()->get_iterations_per_second(); + const double delta = 1.0 / physics_ticks_per_second; // Process the scene for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { @@ -1663,6 +1901,13 @@ void NoNetSynchronizer::process() { static_cast(nd->node)->get_nonet_controller()->process(delta); } + // Execute the actions. + for (uint32_t i = 0; i < pending_actions.size(); i += 1) { + pending_actions[i].execute(); + } + // No need to do anything else, just claen the acts. + pending_actions.clear(); + // Pull the changes. scene_synchronizer->change_events_begin(NetEventFlag::CHANGE); for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { @@ -1670,6 +1915,23 @@ void NoNetSynchronizer::process() { scene_synchronizer->pull_node_changes(nd); } scene_synchronizer->change_events_flush(); + + SceneSynchronizerDebugger::singleton()->scene_sync_process_end(scene_synchronizer); + SceneSynchronizerDebugger::singleton()->write_dump(0, frame_index); + SceneSynchronizerDebugger::singleton()->start_new_frame(); +} + +void NoNetSynchronizer::on_action_triggered( + NetUtility::NodeData *p_node_data, + NetActionId p_id, + const Array &p_arguments, + const Vector &p_recipients) { + NetActionProcessor action_processor = NetActionProcessor(p_node_data, p_id, p_arguments); + if (action_processor.server_validate()) { + pending_actions.push_back(action_processor); + } else { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "The `" + action_processor + "` action validation returned `false`. The action is discarded."); + } } void NoNetSynchronizer::set_enabled(bool p_enabled) { @@ -1692,7 +1954,9 @@ bool NoNetSynchronizer::is_enabled() const { } ServerSynchronizer::ServerSynchronizer(SceneSynchronizer *p_node) : - Synchronizer(p_node) {} + Synchronizer(p_node) { + SceneSynchronizerDebugger::singleton()->setup_debugger("server", 0, scene_synchronizer->get_tree()); +} void ServerSynchronizer::clear() { state_notifier_timer = 0.0; @@ -1701,9 +1965,14 @@ void ServerSynchronizer::clear() { } void ServerSynchronizer::process() { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "ServerSynchronizer::process", true); + scene_synchronizer->update_peers(); - const real_t delta = scene_synchronizer->get_physics_process_delta_time(); + const double physics_ticks_per_second = Engine::get_singleton()->get_iterations_per_second(); + const double delta = 1.0 / physics_ticks_per_second; + + SceneSynchronizerDebugger::singleton()->scene_sync_process_start(scene_synchronizer); // Process the scene for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { @@ -1717,6 +1986,9 @@ void ServerSynchronizer::process() { static_cast(nd->node)->get_server_controller()->process(delta); } + // Process the actions + execute_actions(); + // Pull the changes. scene_synchronizer->change_events_begin(NetEventFlag::CHANGE); for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { @@ -1726,6 +1998,28 @@ void ServerSynchronizer::process() { scene_synchronizer->change_events_flush(); process_snapshot_notificator(delta); + + SceneSynchronizerDebugger::singleton()->scene_sync_process_end(scene_synchronizer); + + clean_pending_actions(); + check_missing_actions(); + +#if DEBUG_ENABLED + // Write the debug dump for each peer. + for ( + OAHashMap::Iterator peer_it = scene_synchronizer->peer_data.iter(); + peer_it.valid; + peer_it = scene_synchronizer->peer_data.next_iter(peer_it)) { + if (unlikely(peer_it.value->controller_id == UINT32_MAX)) { + continue; + } + + const NetUtility::NodeData *nd = scene_synchronizer->get_node_data(peer_it.value->controller_id); + const uint32_t current_input_id = static_cast(nd->node)->get_server_controller()->get_current_input_id(); + SceneSynchronizerDebugger::singleton()->write_dump(*(peer_it.key), current_input_id); + } + SceneSynchronizerDebugger::singleton()->start_new_frame(); +#endif } void ServerSynchronizer::on_node_added(NetUtility::NodeData *p_node_data) { @@ -1743,6 +2037,15 @@ void ServerSynchronizer::on_node_added(NetUtility::NodeData *p_node_data) { changes[p_node_data->id].not_known_before = true; } +void ServerSynchronizer::on_node_removed(NetUtility::NodeData *p_node_data) { + // Remove the actions as the `NodeData` is gone. + for (int64_t i = int64_t(server_actions.size()) - 1; i >= 0; i -= 1) { + if (server_actions[i].action_processor.nd == p_node_data) { + server_actions.remove_unordered(i); + } + } +} + void ServerSynchronizer::on_variable_added(NetUtility::NodeData *p_node_data, const StringName &p_var_name) { #ifdef DEBUG_ENABLED // Can't happen on server @@ -1774,6 +2077,128 @@ void ServerSynchronizer::on_variable_changed(NetUtility::NodeData *p_node_data, changes[p_node_data->id].vars.insert(p_node_data->vars[p_var_id].var.name); } +void ServerSynchronizer::on_action_triggered( + NetUtility::NodeData *p_node_data, + NetActionId p_id, + const Array &p_arguments, + const Vector &p_recipients) { + // The server can just trigger the action. + + // Definte the action index. + const uint32_t server_action_token = server_actions_count; + server_actions_count += 1; + + const uint32_t index = server_actions.size(); + server_actions.resize(index + 1); + + server_actions[index].prepare_processor(p_node_data, p_id, p_arguments); + server_actions[index].sender_peer = 1; + server_actions[index].triggerer_action_token = server_action_token; + server_actions[index].action_token = server_action_token; + + // Trigger this action on the next tick. + for ( + OAHashMap::Iterator it = peers_next_action_trigger_input_id.iter(); + it.valid; + it = peers_next_action_trigger_input_id.next_iter(it)) { + server_actions[index].peers_executed_input_id[*it.key] = *it.value; + } + server_actions[index].recipients = p_recipients; + + // Now the server can propagate the actions to the clients. + send_actions_to_clients(); +} + +void ServerSynchronizer::on_actions_received( + int p_sender_peer, + const LocalVector &p_actions) { + NetActionSenderInfo *sender = senders_info.lookup_ptr(p_sender_peer); + if (sender == nullptr) { + senders_info.set(p_sender_peer, NetActionSenderInfo()); + sender = senders_info.lookup_ptr(p_sender_peer); + } + + NetworkedController *controller = scene_synchronizer->fetch_controller_by_peer(p_sender_peer); + ERR_FAIL_COND_MSG(controller == nullptr, "[FATAL] The peer `" + itos(p_sender_peer) + "` is not associated to any controller, though an Action was generated by the client."); + + for (uint32_t g = 0; g < p_actions.size(); g += 1) { + const SenderNetAction &action = p_actions[g]; + + ERR_CONTINUE_MSG(action.get_action_info().can_client_trigger, "[CHEATER WARNING] This action `" + action.get_action_info().act_func + "` is not supposed to be triggered by the client. Is the client cheating? (Normally the NetSync aborts this kind of request on client side)."); + + const bool already_received = sender->process_received_action(action.action_token); + if (already_received) { + // Already received: nothing to do. + continue; + } + + // Validate the action. + if (!action.action_processor.server_validate()) { + // The action is not valid just discard it. + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "The `" + action.action_processor + "` action validation returned `false`. The action is discarded. SenderPeer: `" + itos(p_sender_peer) + "`."); + continue; + } + + // The server assigns a new action index, so we can globally reference the actions using a + // single number. + const uint32_t server_action_token = server_actions_count; + server_actions_count += 1; + + const uint32_t index = server_actions.size(); + server_actions.push_back(action); + server_actions[index].triggerer_action_token = server_actions[index].action_token; + server_actions[index].action_token = server_action_token; + + // Set the action execution so for all peer based on when it was executed on the client. + const uint32_t action_executed_input_id = action.peer_get_executed_input_id(p_sender_peer); + if (action.get_action_info().wait_server_validation || + controller->get_current_input_id() >= action_executed_input_id) { + // The action will be executed by the sxerver ASAP: + // - This can happen if `wait_server_validation` is used + // - The action was received too late. It's necessary to re-schedule the action and notify the + // client. + + for ( + OAHashMap::Iterator it = peers_next_action_trigger_input_id.iter(); + it.valid; + it = peers_next_action_trigger_input_id.next_iter(it)) { + // This code set the `executed_input_id` to the next frame: similarly as done when the + // `Action is triggered on the server (check `ServerSynchronizer::on_action_triggered`). + server_actions[index].peers_executed_input_id[*it.key] = *it.value; + } + + // Notify the sender client to adjust its snapshots. + server_actions[index].sender_executed_time_changed = true; + + } else { + ERR_CONTINUE_MSG(action_executed_input_id == UINT32_MAX, "[FATAL] The `executed_input_id` is missing on the received action: The action is not `wait_server_validation` so the `input_id` should be available."); + + // All is looking good. Set the action input_id to other peers relative to the sender_peer: + // so to execute the Action in Sync. + const uint32_t delta_actions = action_executed_input_id - controller->get_current_input_id(); + + for ( + OAHashMap::Iterator it = peers_next_action_trigger_input_id.iter(); + it.valid; + it = peers_next_action_trigger_input_id.next_iter(it)) { + if ((*it.key) == p_sender_peer) { + // Already set. + continue; + } else { + // Each controller has its own `input_id`: This code calculates and set the `input_id` + // relative to the specific peer, so that all will execute the action at the right time. + NetworkedController *peer_controller = scene_synchronizer->fetch_controller_by_peer(*it.key); + ERR_CONTINUE(peer_controller == nullptr); + server_actions[index].peers_executed_input_id[*it.key] = peer_controller->get_current_input_id() + delta_actions; + } + } + } + } + + // Now the server can propagate the actions to the clients. + send_actions_to_clients(); +} + void ServerSynchronizer::process_snapshot_notificator(real_t p_delta) { if (scene_synchronizer->peer_data.empty()) { // No one is listening. @@ -1794,10 +2219,6 @@ void ServerSynchronizer::process_snapshot_notificator(real_t p_delta) { OAHashMap::Iterator peer_it = scene_synchronizer->peer_data.iter(); peer_it.valid; peer_it = scene_synchronizer->peer_data.next_iter(peer_it)) { - if (unlikely(peer_it.value->controller_id == UINT32_MAX)) { - // This peer still does not have a `NetworkedController`. - continue; - } if (unlikely(peer_it.value->enabled == false)) { // This peer is disabled. continue; @@ -1809,33 +2230,41 @@ void ServerSynchronizer::process_snapshot_notificator(real_t p_delta) { peer_it.value->force_notify_snapshot = false; - NetUtility::NodeData *nd = scene_synchronizer->get_node_data(peer_it.value->controller_id); - // TODO well that's not really true. I may have peers that doesn't have controllers_node_data in a - // certain moment. Please improve this mechanism trying to just use the - // node->get_network_master() to get the peer. - ERR_CONTINUE_MSG(nd == nullptr, "This should never happen. Likely there is a bug, NedNodeId: " + itos(peer_it.value->controller_id)); - ERR_CONTINUE_MSG(nd->is_controller == false, "[BUG] A controller il expected, The node " + nd->node->get_path() + " is submitted instead."); - - NetworkedController *controller = static_cast(nd->node); - Vector snap; + + NetUtility::NodeData *nd = peer_it.value->controller_id == UINT32_MAX ? nullptr : scene_synchronizer->get_node_data(peer_it.value->controller_id); + if (nd) { + // Add the controller input id at the beginning of the frame. + snap.push_back(true); + NetworkedController *controller = static_cast(nd->node); + snap.push_back(controller->get_current_input_id()); + + ERR_CONTINUE_MSG(nd->is_controller == false, "[BUG] The NodeData fetched is not a controller: `" + nd->node->get_path() + "`."); + controller_generate_snapshot(nd, peer_it.value->need_full_snapshot, snap); + } else { + snap.push_back(false); + } + if (peer_it.value->need_full_snapshot) { peer_it.value->need_full_snapshot = false; if (full_global_nodes_snapshot.size() == 0) { full_global_nodes_snapshot = global_nodes_generate_snapshot(true); } - snap = full_global_nodes_snapshot; - controller_generate_snapshot(nd, true, snap); + snap.append_array(full_global_nodes_snapshot); + } else { if (delta_global_nodes_snapshot.size() == 0) { delta_global_nodes_snapshot = global_nodes_generate_snapshot(false); } - snap = delta_global_nodes_snapshot; - controller_generate_snapshot(nd, false, snap); + snap.append_array(delta_global_nodes_snapshot); } - controller->get_server_controller()->notify_send_state(); - scene_synchronizer->rpc_id(*peer_it.key, "_rpc_send_state", snap); + scene_synchronizer->rpc_id(*peer_it.key, SNAME("_rpc_send_state"), snap); + + if (nd) { + NetworkedController *controller = static_cast(nd->node); + controller->get_server_controller()->notify_send_state(); + } } if (notify_state) { @@ -1850,14 +2279,20 @@ Vector ServerSynchronizer::global_nodes_generate_snapshot(bool p_force_ for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { const NetUtility::NodeData *node_data = scene_synchronizer->node_data[i]; + if (node_data == nullptr) { continue; - } - if (node_data->is_controller || node_data->controlled_by != nullptr) { - // Skip the controllers. + + } else if (node_data->is_controller || node_data->controlled_by != nullptr) { + // Stkip any controller. continue; + + } else { + generate_snapshot_node_data( + node_data, + p_force_full_snapshot ? SNAPSHOT_GENERATION_MODE_FORCE_FULL : SNAPSHOT_GENERATION_MODE_NORMAL, + snapshot_data); } - generate_snapshot_node_data(node_data, p_force_full_snapshot, snapshot_data); } return snapshot_data; @@ -1869,24 +2304,48 @@ void ServerSynchronizer::controller_generate_snapshot( Vector &r_snapshot_result) const { CRASH_COND(p_node_data->is_controller == false); + // Add the Controller and Controlled node `NodePath`: if unknown. + for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + const NetUtility::NodeData *node_data = scene_synchronizer->node_data[i]; + + if (node_data == nullptr) { + continue; + + } else if (node_data->is_controller == false && node_data->controlled_by == nullptr) { + // This is not a controller, skip. + continue; + + } else if (node_data == p_node_data || node_data->controlled_by == p_node_data) { + // Skip this node because we will collect those info just after this loop. + // Here we want to collect only the other controllers. + continue; + } + + // This is a controller, network only the `NodePath` if it`s unkwnown. + generate_snapshot_node_data( + node_data, + p_force_full_snapshot ? SNAPSHOT_GENERATION_MODE_FORCE_NODE_PATH_ONLY : SNAPSHOT_GENERATION_MODE_NODE_PATH_ONLY, + r_snapshot_result); + } + generate_snapshot_node_data( p_node_data, - p_force_full_snapshot, + p_force_full_snapshot ? SNAPSHOT_GENERATION_MODE_FORCE_FULL : SNAPSHOT_GENERATION_MODE_NORMAL, r_snapshot_result); for (uint32_t i = 0; i < p_node_data->controlled_nodes.size(); i += 1) { generate_snapshot_node_data( p_node_data->controlled_nodes[i], - p_force_full_snapshot, + p_force_full_snapshot ? SNAPSHOT_GENERATION_MODE_FORCE_FULL : SNAPSHOT_GENERATION_MODE_NORMAL, r_snapshot_result); } } void ServerSynchronizer::generate_snapshot_node_data( const NetUtility::NodeData *p_node_data, - bool p_force_full_snapshot, + SnapshotGenerationMode p_mode, Vector &r_snapshot_data) const { - // The packet data is an array that contains the information to update the + // The packet data is an array that contains the informations to update the // client snapshot. // // It's composed as follows: @@ -1906,11 +2365,20 @@ void ServerSynchronizer::generate_snapshot_node_data( return; } + const bool force_using_node_path = p_mode == SNAPSHOT_GENERATION_MODE_FORCE_FULL || p_mode == SNAPSHOT_GENERATION_MODE_FORCE_NODE_PATH_ONLY || p_mode == SNAPSHOT_GENERATION_MODE_NODE_PATH_ONLY; + const bool force_snapshot_node_path = p_mode == SNAPSHOT_GENERATION_MODE_FORCE_FULL || p_mode == SNAPSHOT_GENERATION_MODE_FORCE_NODE_PATH_ONLY; + const bool force_snapshot_variables = p_mode == SNAPSHOT_GENERATION_MODE_FORCE_FULL; + const bool skip_snapshot_variables = p_mode == SNAPSHOT_GENERATION_MODE_FORCE_NODE_PATH_ONLY || p_mode == SNAPSHOT_GENERATION_MODE_NODE_PATH_ONLY; + const bool force_using_variable_name = p_mode == SNAPSHOT_GENERATION_MODE_FORCE_FULL; + const Change *change = p_node_data->id >= changes.size() ? nullptr : changes.ptr() + p_node_data->id; + const bool unknown = change != nullptr && change->not_known_before; + const bool node_has_changes = change != nullptr && change->vars.empty() == false; + // Insert NODE DATA. Variant snap_node_data; - if (p_force_full_snapshot || (change != nullptr && change->not_known_before)) { + if (force_using_node_path || unknown) { Vector _snap_node_data; _snap_node_data.resize(2); _snap_node_data.write[0] = p_node_data->id; @@ -1921,32 +2389,14 @@ void ServerSynchronizer::generate_snapshot_node_data( snap_node_data = p_node_data->id; } - const bool node_has_changes = p_force_full_snapshot || (change != nullptr && change->vars.empty() == false); - - if (p_node_data->is_controller) { - NetworkedController *controller = static_cast(p_node_data->node); - - // TODO make sure to skip un-active controllers_node_data. - // This may no more needed, since the interpolator got integrated and - // the only time the controller is sync is when it's needed. - if (likely(controller->get_current_input_id() != UINT32_MAX)) { - // This is a controller, always sync it. - r_snapshot_data.push_back(snap_node_data); - r_snapshot_data.push_back(controller->get_current_input_id()); - } else { - // The first ID id is not yet arrived, so just skip this node. - return; - } + if ((node_has_changes && skip_snapshot_variables == false) || force_snapshot_node_path || unknown) { + r_snapshot_data.push_back(snap_node_data); } else { - if (node_has_changes) { - r_snapshot_data.push_back(snap_node_data); - } else { - // It has no changes, skip this node. - return; - } + // It has no changes, skip this node. + return; } - if (node_has_changes) { + if (force_snapshot_variables || (node_has_changes && skip_snapshot_variables == false)) { // Insert the node variables. for (uint32_t i = 0; i < p_node_data->vars.size(); i += 1) { const NetUtility::VarData &var = p_node_data->vars[i]; @@ -1954,14 +2404,14 @@ void ServerSynchronizer::generate_snapshot_node_data( continue; } - if (p_force_full_snapshot == false && change->vars.has(var.var.name) == false) { + if (force_snapshot_variables == false && change->vars.has(var.var.name) == false) { // This is a delta snapshot and this variable is the same as // before. Skip it. continue; } Variant var_info; - if (p_force_full_snapshot || change->uknown_vars.has(var.var.name)) { + if (force_using_variable_name || change->uknown_vars.has(var.var.name)) { Vector _var_info; _var_info.resize(2); _var_info.write[0] = var.id; @@ -1980,9 +2430,174 @@ void ServerSynchronizer::generate_snapshot_node_data( r_snapshot_data.push_back(Variant()); } +void ServerSynchronizer::execute_actions() { + for (uint32_t i = 0; i < server_actions.size(); i += 1) { + if (server_actions[i].locally_executed) { + // Already executed. + continue; + } + + // Take the controller associated to the sender_peer, to extract the current `input_id`. + int sender_peer = server_actions[i].sender_peer; + uint32_t executed_input_id = UINT32_MAX; + if (sender_peer == 1) { + if (unlikely(scene_synchronizer->peer_data.iter().valid == false)) { + // No peers to take as reference to execute this Action, so just execute it right away. + server_actions[i].locally_executed = true; + server_actions[i].action_processor.execute(); + continue; + } + + // Since this action was triggered by the server, and the server specify as + // execution_input_id the same delta for all the peers: in order to execute this action + // on the server we can just use any peer as reference to know when it's the right time + // to execute the Action. + // So it uses the first available peer. + sender_peer = *scene_synchronizer->peer_data.iter().key; + } + + NetworkedController *controller = scene_synchronizer->fetch_controller_by_peer(sender_peer); + + if (unlikely(controller == nullptr)) { + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "ServerSnchronizer::execute_actions. The peer `" + itos(sender_peer) + "` doesn't have any controller associated, but the Action (`" + server_actions[i].action_processor + "`) was generated. Maybe the character disconnected?"); + server_actions[i].locally_executed = true; + server_actions[i].action_processor.execute(); + continue; + } + + executed_input_id = server_actions[i].peer_get_executed_input_id(sender_peer); + if (unlikely(executed_input_id == UINT32_MAX)) { + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "[FATAL] The `executed_input_id` is `UINT32_MAX` which means it was unable to fetch the `controller_input_id` from the peer `" + itos(executed_input_id) + "`. Action: `" + server_actions[i].action_processor + "`"); + // This is likely a bug, so do not even bother executing it. + // Marking as executed so this action is dropped. + server_actions[i].locally_executed = true; + continue; + } + + if (controller->get_current_input_id() >= executed_input_id) { + if (unlikely(controller->get_current_input_id() > executed_input_id)) { + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "ServerSnchronizer::execute_actions. The action `" + server_actions[i].action_processor + "` was planned to be executed on the frame `" + itos(executed_input_id) + "` while the current controller (`" + controller->get_path() + "`) frame is `" + itos(controller->get_current_input_id()) + "`. Since the execution_frame is adjusted when the action is received on the server, this case is triggered when the client stop communicating for some time and some inputs are skipped."); + } + + // It's time to execute the Action, Yey! + server_actions[i].locally_executed = true; + server_actions[i].action_processor.execute(); + } + } + + // Advance the action `input_id` for each peer, so we know when the next action will be triggered. + for (OAHashMap::Iterator it = scene_synchronizer->peer_data.iter(); + it.valid; + it = scene_synchronizer->peer_data.next_iter(it)) { + // The peer + const int peer_id = *it.key; + + NetworkedController *controller = scene_synchronizer->fetch_controller_by_peer(peer_id); + if (controller && controller->get_current_input_id() != UINT32_MAX) { + peers_next_action_trigger_input_id.set(peer_id, controller->get_current_input_id() + 1); + } + } +} + +void ServerSynchronizer::send_actions_to_clients() { + const uint64_t now = OS::get_singleton()->get_ticks_msec(); + + LocalVector packet_actions; + + // First take the significant actions to network. + for (uint32_t i = 0; i < server_actions.size(); i += 1) { + if (int(server_actions[i].send_count) >= scene_synchronizer->get_actions_redundancy()) { + // Nothing to do. + continue; + } + + if ((server_actions[i].send_timestamp + 2 /*ms - give some room*/) > now) { + // Nothing to do. + continue; + } + + server_actions[i].send_timestamp = now; + server_actions[i].send_count += 1; + packet_actions.push_back(&server_actions[i]); + } + + if (packet_actions.size() == 0) { + // Nothing to send. + return; + } + + // For each peer + for (OAHashMap::Iterator it = scene_synchronizer->peer_data.iter(); + it.valid; + it = scene_synchronizer->peer_data.next_iter(it)) { + // Send to peers. + const int peer_id = *it.key; + + // Collects the actions importants for this peer. + LocalVector peer_packet_actions; + for (uint32_t i = 0; i < packet_actions.size(); i += 1) { + if ( + ( + packet_actions[i]->sender_peer == peer_id && + packet_actions[i]->get_action_info().wait_server_validation == false && + packet_actions[i]->sender_executed_time_changed == false) || + (packet_actions[i]->recipients.size() > 0 && + packet_actions[i]->recipients.find(peer_id) == -1)) { + // This peer must not receive the action. + continue; + } else { + // This peer has to receive and execute this action. + peer_packet_actions.push_back(packet_actions[i]); + } + } + + if (peer_packet_actions.size() == 0) { + // Nothing to network for this peer. + continue; + } + + // Encode the actions. + DataBuffer db; + db.begin_write(0); + net_action::encode_net_action(packet_actions, peer_id, db); + db.dry(); + + // Send the action to the peer. + scene_synchronizer->rpc_unreliable_id( + peer_id, + "_rpc_send_actions", + db.get_buffer().get_bytes()); + } +} + +void ServerSynchronizer::clean_pending_actions() { + // The packet will contains the most recent actions. + for (int64_t i = int64_t(server_actions.size()) - 1; i >= 0; i -= 1) { + if ( + server_actions[i].locally_executed == false || + int(server_actions[i].send_count) < scene_synchronizer->get_actions_redundancy()) { + // Still somethin to do. + continue; + } + + server_actions.remove_unordered(i); + } +} + +void ServerSynchronizer::check_missing_actions() { + for ( + OAHashMap::Iterator it = senders_info.iter(); + it.valid; + it = senders_info.next_iter(it)) { + it.value->check_missing_actions_and_clean_up(scene_synchronizer); + } +} + ClientSynchronizer::ClientSynchronizer(SceneSynchronizer *p_node) : Synchronizer(p_node) { clear(); + + SceneSynchronizerDebugger::singleton()->setup_debugger("client", 0, scene_synchronizer->get_tree()); } void ClientSynchronizer::clear() { @@ -1998,17 +2613,20 @@ void ClientSynchronizer::clear() { } void ClientSynchronizer::process() { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "ClientSynchronizer::process", true); + if (unlikely(player_controller_node_data == nullptr || enabled == false)) { // No player controller or disabled so nothing to do. return; } - const real_t delta = scene_synchronizer->get_physics_process_delta_time(); - const real_t physics_ticks_per_second = Engine::get_singleton()->get_physics_ticks_per_second(); + const double physics_ticks_per_second = Engine::get_singleton()->get_iterations_per_second(); + const double delta = 1.0 / physics_ticks_per_second; #ifdef DEBUG_ENABLED if (unlikely(Engine::get_singleton()->get_frames_per_second() < physics_ticks_per_second)) { - WARN_PRINT("Current FPS is " + itos(Engine::get_singleton()->get_frames_per_second()) + ", but the minimum required FPS is " + itos(physics_ticks_per_second) + ", the client is unable to generate enough inputs for the server."); + const bool silent = !ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debugger/log_debug_fps_warnings"); + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "Current FPS is " + itos(Engine::get_singleton()->get_frames_per_second()) + ", but the minimum required FPS is " + itos(physics_ticks_per_second) + ", the client is unable to generate enough inputs for the server.", silent); } #endif @@ -2016,7 +2634,7 @@ void ClientSynchronizer::process() { PlayerController *player_controller = controller->get_player_controller(); // Reset this here, so even when `sub_ticks` is zero (and it's not - // updated due to process is not called), we can still have the correct + // updated due to process is not called), we can still have the corect // data. controller->player_set_has_new_input(false); @@ -2031,7 +2649,14 @@ void ClientSynchronizer::process() { // and get back in time with the server. int sub_ticks = player_controller->calculates_sub_ticks(delta, physics_ticks_per_second); + if (sub_ticks == 0) { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "No sub ticks: this is not bu a bug; it's the lag compensation algorithm.", true); + } + while (sub_ticks > 0) { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "ClientSynchronizer::process::sub_process " + itos(sub_ticks), true); + SceneSynchronizerDebugger::singleton()->scene_sync_process_start(scene_synchronizer); + // Process the scene. for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; @@ -2041,9 +2666,35 @@ void ClientSynchronizer::process() { // Process the player controllers_node_data. player_controller->process(delta); + // Process the actions + for (uint32_t i = 0; i < pending_actions.size(); i += 1) { + if (pending_actions[i].locally_executed) { + // Already executed. + continue; + } + + if (pending_actions[i].client_get_executed_input_id() > player_controller->get_current_input_id()) { + // Not time yet. + continue; + } + +#ifdef DEBUG_ENABLED + if (pending_actions[i].sent_by_the_server == false) { + // The executed_frame is set using `actions_input_id` which is correctly advanced: so itsn't + // expected that this is different. Please make sure this never happens. + CRASH_COND_MSG(pending_actions[i].client_get_executed_input_id() != player_controller->get_current_input_id(), "Action executed_input_id: `" + itos(pending_actions[i].client_get_executed_input_id()) + "` is different from current action `" + itos(player_controller->get_current_input_id()) + "`"); + } +#endif + + pending_actions[i].locally_executed = true; + pending_actions[i].action_processor.execute(); + } + + actions_input_id = player_controller->get_current_input_id() + 1; + // Pull the changes. scene_synchronizer->change_events_begin(NetEventFlag::CHANGE); - for (uint32_t i = 0; i < scene_synchronizer->node_data.size(); i += 1) { + for (NetNodeId i = 0; i < scene_synchronizer->node_data.size(); i += 1) { NetUtility::NodeData *nd = scene_synchronizer->node_data[i]; scene_synchronizer->pull_node_changes(nd); } @@ -2054,13 +2705,24 @@ void ClientSynchronizer::process() { } sub_ticks -= 1; + SceneSynchronizerDebugger::singleton()->scene_sync_process_end(scene_synchronizer); + +#if DEBUG_ENABLED + if (sub_ticks > 0) { + // This is an intermediate sub tick, so store the dumping. + // The last sub frame is not dumped, untile the end of the frame, so we can capture any subsequent message. + const int client_peer = scene_synchronizer->get_multiplayer()->get_network_unique_id(); + SceneSynchronizerDebugger::singleton()->write_dump(client_peer, player_controller->get_current_input_id()); + SceneSynchronizerDebugger::singleton()->start_new_frame(); + } +#endif } process_controllers_recovery(delta); // Now trigger the END_SYNC event. scene_synchronizer->change_events_begin(NetEventFlag::END_SYNC); - for (const RBSet::Element *e = sync_end_events.front(); + for (const Set::Element *e = sync_end_events.front(); e != nullptr; e = e->next()) { // Check if the values between the variables before the sync and the @@ -2078,6 +2740,16 @@ void ClientSynchronizer::process() { sync_end_events.clear(); scene_synchronizer->change_events_flush(); + + send_actions_to_server(); + clean_pending_actions(); + check_missing_actions(); + +#if DEBUG_ENABLED + const int client_peer = scene_synchronizer->get_multiplayer()->get_network_unique_id(); + SceneSynchronizerDebugger::singleton()->write_dump(client_peer, player_controller->get_current_input_id()); + SceneSynchronizerDebugger::singleton()->start_new_frame(); +#endif } void ClientSynchronizer::receive_snapshot(Variant p_snapshot) { @@ -2089,6 +2761,8 @@ void ClientSynchronizer::receive_snapshot(Variant p_snapshot) { // incremental update so the last received data is always needed to fully // reconstruct it. + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "The Client received the server snapshot.", true); + // Parse server snapshot. const bool success = parse_snapshot(p_snapshot); @@ -2109,8 +2783,20 @@ void ClientSynchronizer::on_node_added(NetUtility::NodeData *p_node_data) { void ClientSynchronizer::on_node_removed(NetUtility::NodeData *p_node_data) { if (player_controller_node_data == p_node_data) { player_controller_node_data = nullptr; + server_snapshots.clear(); client_snapshots.clear(); } + + // Remove the actions as the `NodeData` is gone. + for (int64_t i = int64_t(pending_actions.size()) - 1; i >= 0; i -= 1) { + if (pending_actions[i].action_processor.nd == p_node_data) { + pending_actions.remove_unordered(i); + } + } + + if (p_node_data->id < uint32_t(last_received_snapshot.node_vars.size())) { + last_received_snapshot.node_vars.ptrw()[p_node_data->id].clear(); + } } void ClientSynchronizer::on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) { @@ -2131,20 +2817,128 @@ void ClientSynchronizer::on_controller_reset(NetUtility::NodeData *p_node_data) if (player_controller_node_data == p_node_data) { // Reset the node_data. player_controller_node_data = nullptr; + server_snapshots.clear(); client_snapshots.clear(); } if (static_cast(p_node_data->node)->is_player_controller()) { if (player_controller_node_data != nullptr) { - NET_DEBUG_ERR("Only one player controller is supported, at the moment. Make sure this is the case."); + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "Only one player controller is supported, at the moment. Make sure this is the case."); } else { // Set this player controller as active. player_controller_node_data = p_node_data; + server_snapshots.clear(); client_snapshots.clear(); } } } +void ClientSynchronizer::on_action_triggered( + NetUtility::NodeData *p_node_data, + NetActionId p_id, + const Array &p_arguments, + const Vector &p_recipients) { + ERR_FAIL_COND_MSG(p_recipients.size() > 0, "The client can't specify any peer, this feature is restricted to the server."); + + const uint32_t index = pending_actions.size(); + pending_actions.resize(index + 1); + + pending_actions[index].action_token = locally_triggered_actions_count; + pending_actions[index].triggerer_action_token = locally_triggered_actions_count; + locally_triggered_actions_count++; + + pending_actions[index].prepare_processor(p_node_data, p_id, p_arguments); + + if (!pending_actions[index].get_action_info().wait_server_validation) { + // Will be immeditaly executed locally, set the execution frame now so we can network the + // action right away before it's even executed locally: It's necessary to make this fast! + pending_actions[index].client_set_executed_input_id(actions_input_id); + pending_actions[index].locally_executed = false; + } else { + // Do not execute locally, the server will send it back once it's time. + pending_actions[index].locally_executed = true; + } + + pending_actions[index].sent_by_the_server = false; + + // Network the action. + send_actions_to_server(); +} + +void ClientSynchronizer::on_actions_received( + int sender_peer, + const LocalVector &p_actions) { + ERR_FAIL_COND_MSG(sender_peer != 1, "[FATAL] Actions dropped becouse was not sent by the server!"); + + for (uint32_t g = 0; g < p_actions.size(); g += 1) { + const SenderNetAction &action = p_actions[g]; + + const bool already_received = server_sender_info.process_received_action(action.action_token); + if (already_received) { + // Already known nothing to do. + continue; + } + + // Search the snapshot and add the Action to it. + // NOTE: This is needed in case of rewind to take the action into account. + NetworkedController *controller = static_cast(player_controller_node_data->node); + const uint32_t current_input_id = controller->get_current_input_id(); + + if (action.client_get_executed_input_id() <= current_input_id) { + // On the server this action was executed on an already passed frame, insert it inside the snapshot. + // First search the snapshot: + bool add_to_snapshots = false; + for (uint32_t x = 0; x < client_snapshots.size(); x += 1) { + if (client_snapshots[x].input_id == action.client_get_executed_input_id()) { + // Insert the action inside the snapshot, so we can execute to reconcile the client and + // the server: I'm using `UINT32_MAX` because we track only locally executed actions. + client_snapshots[x].actions.push_back(TokenizedNetActionProcessor(UINT32_MAX, action.action_processor)); + add_to_snapshots = true; + break; + } + } + + if (!add_to_snapshots) { + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "The Action `" + action.get_action_info().act_func + "` was not add to any snapshot as the snapshot was not found. The executed_input_id sent by the server is `" + itos(action.client_get_executed_input_id()) + "`. The actions is dropped."); + continue; + } + } + + // Add the action to the pending actions so it's executed ASAP. + const uint32_t index = pending_actions.size(); + pending_actions.push_back(action); + // Never networkd this back to server: afterall the server just sent it! + pending_actions[index].sent_by_the_server = true; + + if (action.sender_executed_time_changed) { + // This action was generated by this peer, but it arrived too late to the server that + // rescheduled it: + // Remove the original Action from any stored snapshot to avoid executing it at the wrong time. + ERR_CONTINUE_MSG(action.triggerer_action_token == UINT32_MAX, "[FATAL] The server sent an action marked with `sender_executed_time_changed` but the `truggerer_action_token` is not set, this is a bug. Report that."); + + for (uint32_t x = 0; x < client_snapshots.size(); x += 1) { + const int64_t action_i = client_snapshots[x].actions.find(TokenizedNetActionProcessor(action.triggerer_action_token, NetActionProcessor())); + if (action_i >= 0) { + client_snapshots[x].actions.remove(action_i); + break; + } + } + + if (pending_actions[index].get_action_info().wait_server_validation == false) { + // Since it was already executed, no need to execute again, it's enough just put the Action + // to the proper snapshot. + pending_actions[index].locally_executed = true; + } else { + // Execute this locally. + pending_actions[index].locally_executed = false; + } + } else { + // Execute this locally. + pending_actions[index].locally_executed = false; + } + } +} + void ClientSynchronizer::store_snapshot() { NetworkedController *controller = static_cast(player_controller_node_data->node); @@ -2195,6 +2989,19 @@ void ClientSynchronizer::store_snapshot() { } } } + + // Store the actions + for (uint32_t i = 0; i < pending_actions.size(); i += 1) { + if (pending_actions[i].client_get_executed_input_id() != snap.input_id) { + continue; + } + + if (pending_actions[i].sent_by_the_server) { + // Nothing to do because it was add on arrival into the correct snapshot. + } else { + snap.actions.push_back(TokenizedNetActionProcessor(pending_actions[i].action_token, pending_actions[i].action_processor)); + } + } } void ClientSynchronizer::store_controllers_snapshot( @@ -2202,27 +3009,58 @@ void ClientSynchronizer::store_controllers_snapshot( std::deque &r_snapshot_storage) { // Put the parsed snapshot into the queue. - if (p_snapshot.input_id == UINT32_MAX) { + if (p_snapshot.input_id == UINT32_MAX && player_controller_node_data != nullptr) { // The snapshot doesn't have any info for this controller; Skip it. return; } - if (r_snapshot_storage.empty() == false) { - // Make sure the snapshots are stored in order. - const uint32_t last_stored_input_id = r_snapshot_storage.back().input_id; - if (p_snapshot.input_id == last_stored_input_id) { - // Update the snapshot. - r_snapshot_storage.back() = p_snapshot; - return; - } else { - ERR_FAIL_COND_MSG(p_snapshot.input_id < last_stored_input_id, "This snapshot (with ID: " + itos(p_snapshot.input_id) + ") is not expected because the last stored id is: " + itos(last_stored_input_id)); - } - } + if (p_snapshot.input_id == UINT32_MAX) { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "The Client received the server snapshot WITHOUT `input_id`.", true); + // The controller node is not registered so just assume this snapshot is the most up-to-date. + r_snapshot_storage.clear(); + r_snapshot_storage.push_back(p_snapshot); - r_snapshot_storage.push_back(p_snapshot); + } else { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "The Client received the server snapshot: " + itos(p_snapshot.input_id), true); + + // Store the snapshot sorted by controller input ID. + if (r_snapshot_storage.empty() == false) { + // Make sure the snapshots are stored in order. + const uint32_t last_stored_input_id = r_snapshot_storage.back().input_id; + if (p_snapshot.input_id == last_stored_input_id) { + // Update the snapshot. + r_snapshot_storage.back() = p_snapshot; + return; + } else { + ERR_FAIL_COND_MSG(p_snapshot.input_id < last_stored_input_id, "This snapshot (with ID: " + itos(p_snapshot.input_id) + ") is not expected because the last stored id is: " + itos(last_stored_input_id)); + } + } + + r_snapshot_storage.push_back(p_snapshot); + } +} + +void ClientSynchronizer::apply_last_received_server_snapshot() { + const Vector *vars = server_snapshots.back().node_vars.ptr(); + + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER); + for (int i = 0; i < server_snapshots.back().node_vars.size(); i += 1) { + NetNodeId id = i; + NetUtility::NodeData *nd = scene_synchronizer->get_node_data(id); + for (int v = 0; v < vars[i].size(); v += 1) { + const Variant current_val = nd->node->get(vars[i][v].name); + if (scene_synchronizer->compare(current_val, vars[i][v].value)) { + nd->node->set(vars[i][v].name, vars[i][v].value); + scene_synchronizer->change_event_add( + nd, + v, + current_val); + } + } + } + scene_synchronizer->change_events_flush(); } -// TODO make this function much simpler. void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { // The client is responsible to recover only its local controller, while all // the other controllers_node_data (dolls) have their state interpolated. There is @@ -2241,6 +3079,17 @@ void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { return; } + if (server_snapshots.back().input_id == UINT32_MAX) { + // The server last received snapshot is a no input snapshot. Just assume it's the most up-to-date. + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "The client received a \"no input\" snapshot, so the client is setting it right away assuming is the most updated one.", true); + + apply_last_received_server_snapshot(); + + server_snapshots.clear(); + client_snapshots.clear(); + return; + } + #ifdef DEBUG_ENABLED if (client_snapshots.empty() == false) { // The SceneSynchronizer and the PlayerController are always in sync. @@ -2297,7 +3146,7 @@ void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { CRASH_COND(server_snapshots.empty()); CRASH_COND(server_snapshots.front().input_id != checkable_input_id); - // This is unreachable, because we store all the client snapshots + // This is unreachable, because we store all the client shapshots // each time a new input is processed. Since the `checkable_input_id` // is taken by reading the processed doll inputs, it's guaranteed // that here the snapshot exists. @@ -2311,47 +3160,12 @@ void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { LocalVector nodes_to_recover; LocalVector postponed_recover; - nodes_to_recover.reserve(server_snapshots.front().node_vars.size()); - for (uint32_t net_node_id = 0; net_node_id < uint32_t(server_snapshots.front().node_vars.size()); net_node_id += 1) { - NetUtility::NodeData *rew_node_data = scene_synchronizer->get_node_data(net_node_id); - if (rew_node_data == nullptr || rew_node_data->sync_enabled == false) { - continue; - } - - bool recover_this_node = false; - if (net_node_id >= uint32_t(client_snapshots.front().node_vars.size())) { - NET_DEBUG_PRINT("Rewind is needed because the client snapshot doesn't contain this node: " + rew_node_data->node->get_path()); - recover_this_node = true; - } else { - NetUtility::PostponedRecover rec; - - const bool different = compare_vars( - rew_node_data, - server_snapshots.front().node_vars[net_node_id], - client_snapshots.front().node_vars[net_node_id], - rec.vars); - - if (different) { - NET_DEBUG_PRINT("Rewind is needed because the node on client is different: " + rew_node_data->node->get_path()); - recover_this_node = true; - } else if (rec.vars.size() > 0) { - rec.node_data = rew_node_data; - postponed_recover.push_back(rec); - } - } - - if (recover_this_node) { - need_recover = true; - if (rew_node_data->controlled_by != nullptr || - rew_node_data->is_controller || - player_controller_node_data->dependency_nodes.find(rew_node_data) != -1) { - // Controller node. - recover_controller = true; - } else { - nodes_to_recover.push_back(rew_node_data); - } - } - } + __pcr__fetch_recovery_info( + checkable_input_id, + need_recover, + recover_controller, + nodes_to_recover, + postponed_recover); // Popout the client snapshot. client_snapshots.pop_front(); @@ -2359,7 +3173,13 @@ void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { // --- Phase three: recover and reply. --- if (need_recover) { - NET_DEBUG_PRINT("Recover input: " + itos(checkable_input_id) + " - Last input: " + itos(player_controller->get_stored_input_id(-1))); + SceneSynchronizerDebugger::singleton()->notify_event(recover_controller ? SceneSynchronizerDebugger::FrameEvent::CLIENT_DESYNC_DETECTED : SceneSynchronizerDebugger::FrameEvent::CLIENT_DESYNC_DETECTED_SOFT); + SceneSynchronizerDebugger::singleton()->add_node_message(scene_synchronizer, "Recover input: " + itos(checkable_input_id) + " - Last input: " + itos(player_controller->get_stored_input_id(-1))); + + // Add the postponed recover. + for (uint32_t y = 0; y < postponed_recover.size(); ++y) { + nodes_to_recover.push_back(postponed_recover[y].node_data); + } if (recover_controller) { // Put the controlled and the controllers_node_data into the nodes to @@ -2390,178 +3210,19 @@ void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { } } - // Apply the server snapshot so to go back in time till that moment, - // so to be able to correctly reply the movements. - scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER | NetEventFlag::SYNC_RESET); - for (uint32_t i = 0; i < nodes_to_recover.size(); i += 1) { - if (nodes_to_recover[i]->id >= uint32_t(server_snapshots.front().node_vars.size())) { - NET_DEBUG_WARN("The node: " + nodes_to_recover[i]->node->get_path() + " was not found on the server snapshot, this is not supposed to happen a lot."); - continue; - } - if (nodes_to_recover[i]->sync_enabled == false) { - // Don't sync this node. - // This check is also here, because the `recover_controller` - // mechanism, may have insert a no sync node. - // The check is here because I feel it more clear, here. - continue; - } - -#ifdef DEBUG_ENABLED - // The parser make sure to properly initialize the snapshot variable - // array size. So the following condition is always `false`. - CRASH_COND(uint32_t(server_snapshots.front().node_vars[nodes_to_recover[i]->id].size()) != nodes_to_recover[i]->vars.size()); -#endif - - Node *node = nodes_to_recover[i]->node; - const Vector s_vars = server_snapshots.front().node_vars[nodes_to_recover[i]->id]; - const NetUtility::Var *s_vars_ptr = s_vars.ptr(); - - NET_DEBUG_PRINT("Full reset node: " + node->get_path()); - - for (int v = 0; v < s_vars.size(); v += 1) { - if (s_vars_ptr[v].name == StringName()) { - // This variable was not set, skip it. - continue; - } - - const Variant current_val = nodes_to_recover[i]->vars[v].var.value; - nodes_to_recover[i]->vars[v].var.value = s_vars_ptr[v].value.duplicate(true); - node->set(s_vars_ptr[v].name, s_vars_ptr[v].value); - - NET_DEBUG_PRINT(" |- Variable: " + s_vars_ptr[v].name + " New value: " + s_vars_ptr[v].value); - scene_synchronizer->change_event_add( - nodes_to_recover[i], - v, - current_val); - } - } - scene_synchronizer->change_events_flush(); + __pcr__sync_pre_rewind( + nodes_to_recover); // Rewind phase. - - const int remaining_inputs = player_controller->notify_input_checked(checkable_input_id); -#ifdef DEBUG_ENABLED - // Unreachable because the SceneSynchronizer and the PlayerController - // have the same stored data at this point. - CRASH_COND(client_snapshots.size() != size_t(remaining_inputs)); -#endif - -#ifdef DEBUG_ENABLED - // Used to double check all the instants have been processed. - bool has_next = false; -#endif - for (int i = 0; i < remaining_inputs; i += 1) { - scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER | NetEventFlag::SYNC_REWIND); - - // Step 1 -- Process the scene nodes. - for (uint32_t r = 0; r < nodes_to_recover.size(); r += 1) { - if (nodes_to_recover[r]->sync_enabled == false) { - // This node is not sync. - continue; - } - nodes_to_recover[r]->process(p_delta); -#ifdef DEBUG_ENABLED - if (nodes_to_recover[r]->functions.size()) { - NET_DEBUG_PRINT("Rewind, processed node: " + nodes_to_recover[r]->node->get_path()); - } -#endif - } - - // Step 2 -- Process the controller. - if (recover_controller && player_controller_node_data->sync_enabled) { -#ifdef DEBUG_ENABLED - has_next = -#endif - controller->process_instant(i, p_delta); - NET_DEBUG_PRINT("Rewind, processed controller: " + controller->get_path()); - } - - // Step 3 -- Pull node changes and Update snapshots. - for (uint32_t r = 0; r < nodes_to_recover.size(); r += 1) { - if (nodes_to_recover[r]->sync_enabled == false) { - // This node is not sync. - continue; - } - // Pull changes - scene_synchronizer->pull_node_changes(nodes_to_recover[r]); - - // Update client snapshot. - if (uint32_t(client_snapshots[i].node_vars.size()) <= nodes_to_recover[r]->id) { - client_snapshots[i].node_vars.resize(nodes_to_recover[r]->id + 1); - } - - Vector *snap_node_vars = client_snapshots[i].node_vars.ptrw() + nodes_to_recover[r]->id; - snap_node_vars->resize(nodes_to_recover[r]->vars.size()); - - NetUtility::Var *vars = snap_node_vars->ptrw(); - for (uint32_t v = 0; v < nodes_to_recover[r]->vars.size(); v += 1) { - vars[v] = nodes_to_recover[r]->vars[v].var; - } - } - scene_synchronizer->change_events_flush(); - } - -#ifdef DEBUG_ENABLED - // Unreachable because the above loop consume all instants. - CRASH_COND(has_next); -#endif - + __pcr__rewind( + p_delta, + checkable_input_id, + controller, + player_controller, + recover_controller, + nodes_to_recover); } else { - // Apply found differences without rewind. - scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER); - for (uint32_t i = 0; i < postponed_recover.size(); i += 1) { - NetUtility::NodeData *rew_node_data = postponed_recover[i].node_data; - if (rew_node_data->sync_enabled == false) { - // This node sync is disabled. - continue; - } - - Node *node = rew_node_data->node; - const NetUtility::Var *vars_ptr = postponed_recover[i].vars.ptr(); - - NET_DEBUG_PRINT("[Snapshot partial reset] Node: " + node->get_path()); - - // Set the value on the synchronizer too. - for (int v = 0; v < postponed_recover[i].vars.size(); v += 1) { - // We need to search it because the postponed recovered is not - // aligned. - // TODO This array is generated few lines above. - // Can we store the ID too, so to avoid this search???? - const int rew_var_index = rew_node_data->vars.find(vars_ptr[v].name); - // Unreachable, because when the snapshot is received the - // algorithm make sure the `scene_synchronizer` is tracking the - // variable. - CRASH_COND(rew_var_index <= -1); - - const Variant old_val = rew_node_data->vars[rew_var_index].var.value; - rew_node_data->vars[rew_var_index].var.value = vars_ptr[v].value.duplicate(true); - node->set(vars_ptr[v].name, vars_ptr[v].value); - - NET_DEBUG_PRINT(" |- Variable: " + vars_ptr[v].name + "; old value: " + old_val + " new value: " + vars_ptr[v].value); - scene_synchronizer->change_event_add( - rew_node_data, - rew_var_index, - old_val); - } - - // Update the last client snapshot. - if (client_snapshots.empty() == false) { - if (uint32_t(client_snapshots.back().node_vars.size()) <= rew_node_data->id) { - client_snapshots.back().node_vars.resize(rew_node_data->id + 1); - } - - Vector *snap_node_vars = client_snapshots.back().node_vars.ptrw() + rew_node_data->id; - snap_node_vars->resize(rew_node_data->vars.size()); - - NetUtility::Var *vars = snap_node_vars->ptrw(); - - for (uint32_t v = 0; v < rew_node_data->vars.size(); v += 1) { - vars[v] = rew_node_data->vars[v].var; - } - } - } - scene_synchronizer->change_events_flush(); - + __pcr__sync_no_rewind(postponed_recover); player_controller->notify_input_checked(checkable_input_id); } @@ -2571,6 +3232,296 @@ void ClientSynchronizer::process_controllers_recovery(real_t p_delta) { last_checked_input = checkable_input_id; } +void ClientSynchronizer::__pcr__fetch_recovery_info( + const uint32_t p_input_id, + bool &r_need_recover, + bool &r_recover_controller, + LocalVector &r_nodes_to_recover, + LocalVector &r_postponed_recover) { + r_need_recover = false; + r_recover_controller = false; + + Vector variable_names; + Vector server_values; + Vector client_values; + + r_nodes_to_recover.reserve(server_snapshots.front().node_vars.size()); + for (uint32_t net_node_id = 0; net_node_id < uint32_t(server_snapshots.front().node_vars.size()); net_node_id += 1) { + NetUtility::NodeData *rew_node_data = scene_synchronizer->get_node_data(net_node_id); + if (rew_node_data == nullptr || rew_node_data->sync_enabled == false) { + continue; + } + + bool recover_this_node = false; + bool different = false; + if (net_node_id >= uint32_t(client_snapshots.front().node_vars.size())) { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "Rewind is needed because the client snapshot doesn't contain this node: " + rew_node_data->node->get_path()); + recover_this_node = true; + different = true; + } else { + NetUtility::PostponedRecover rec; + + different = compare_vars( + rew_node_data, + server_snapshots.front().node_vars[net_node_id], + client_snapshots.front().node_vars[net_node_id], + rec.vars); + + if (different) { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "Rewind on frame " + itos(p_input_id) + " is needed because the node on client is different: " + rew_node_data->node->get_path()); + recover_this_node = true; + } else if (rec.vars.size() > 0) { + rec.node_data = rew_node_data; + r_postponed_recover.push_back(rec); + } + } + + if (recover_this_node) { + r_need_recover = true; + if (rew_node_data->controlled_by != nullptr || + rew_node_data->is_controller || + player_controller_node_data->dependency_nodes.find(rew_node_data) != -1) { + // Controller node. + r_recover_controller = true; + } else { + r_nodes_to_recover.push_back(rew_node_data); + } + } + +#ifdef DEBUG_ENABLED + if (different) { + // Emit the de-sync detected signal. + + static const Vector const_empty_vector; + const Vector &server_node_vars = uint32_t(server_snapshots.front().node_vars.size()) <= net_node_id ? const_empty_vector : server_snapshots.front().node_vars[net_node_id]; + const Vector &client_node_vars = uint32_t(client_snapshots.front().node_vars.size()) <= net_node_id ? const_empty_vector : client_snapshots.front().node_vars[net_node_id]; + + const int count = MAX(server_node_vars.size(), client_node_vars.size()); + + variable_names.resize(count); + server_values.resize(count); + client_values.resize(count); + + for (int g = 0; g < count; ++g) { + if (g < server_node_vars.size()) { + variable_names.ptrw()[g] = server_node_vars[g].name; + server_values.ptrw()[g] = server_node_vars[g].value; + } else { + server_values.ptrw()[g] = Variant(); + } + + if (g < client_node_vars.size()) { + variable_names.ptrw()[g] = client_node_vars[g].name; + client_values.ptrw()[g] = client_node_vars[g].value; + } else { + client_values.ptrw()[g] = Variant(); + } + } + + if (rew_node_data->node) { + scene_synchronizer->emit_signal("desync_detected", p_input_id, rew_node_data->node, variable_names, client_values, server_values); + } else { + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "No node associated to `" + itos(net_node_id) + "`, was not possible to generate the event `desync_detected`."); + } + } +#endif + } +} + +void ClientSynchronizer::__pcr__sync_pre_rewind( + const LocalVector &p_nodes_to_recover) { + // Apply the server snapshot so to go back in time till that moment, + // so to be able to correctly reply the movements. + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER | NetEventFlag::SYNC_RESET); + for (uint32_t i = 0; i < p_nodes_to_recover.size(); i += 1) { + if (p_nodes_to_recover[i]->id >= uint32_t(server_snapshots.front().node_vars.size())) { + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "The node: " + p_nodes_to_recover[i]->node->get_path() + " was not found on the server snapshot, this is not supposed to happen a lot."); + continue; + } + if (p_nodes_to_recover[i]->sync_enabled == false) { + // Don't sync this node. + // This check is also here, because the `recover_controller` + // mechanism, may have insert a no sync node. + // The check is here because I feel it more clear, here. + continue; + } + +#ifdef DEBUG_ENABLED + // The parser make sure to properly initialize the snapshot variable + // array size. So the following condition is always `false`. + CRASH_COND(uint32_t(server_snapshots.front().node_vars[p_nodes_to_recover[i]->id].size()) != p_nodes_to_recover[i]->vars.size()); +#endif + + Node *node = p_nodes_to_recover[i]->node; + const Vector s_vars = server_snapshots.front().node_vars[p_nodes_to_recover[i]->id]; + const NetUtility::Var *s_vars_ptr = s_vars.ptr(); + + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "Full reset node: " + node->get_path()); + + for (int v = 0; v < s_vars.size(); v += 1) { + if (s_vars_ptr[v].name == StringName()) { + // This variable was not set, skip it. + continue; + } + + const Variant current_val = p_nodes_to_recover[i]->vars[v].var.value; + p_nodes_to_recover[i]->vars[v].var.value = s_vars_ptr[v].value.duplicate(true); + node->set(s_vars_ptr[v].name, s_vars_ptr[v].value); + + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, " |- Variable: " + s_vars_ptr[v].name + " New value: " + s_vars_ptr[v].value); + scene_synchronizer->change_event_add( + p_nodes_to_recover[i], + v, + current_val); + } + } + scene_synchronizer->change_events_flush(); +} + +void ClientSynchronizer::__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 &p_nodes_to_recover) { + const int remaining_inputs = p_player_controller->notify_input_checked(p_checkable_input_id); + +#ifdef DEBUG_ENABLED + // Unreachable because the SceneSynchronizer and the PlayerController + // have the same stored data at this point. + CRASH_COND(client_snapshots.size() != size_t(remaining_inputs)); +#endif + +#ifdef DEBUG_ENABLED + // Used to double check all the instants have been processed. + bool has_next = false; +#endif + for (int i = 0; i < remaining_inputs; i += 1) { + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER | NetEventFlag::SYNC_REWIND); + + // Step 1 -- Process the scene nodes. + for (uint32_t r = 0; r < p_nodes_to_recover.size(); r += 1) { + if (p_nodes_to_recover[r]->sync_enabled == false) { + // This node is not sync. + continue; + } + p_nodes_to_recover[r]->process(p_delta); +#ifdef DEBUG_ENABLED + if (p_nodes_to_recover[r]->functions.size()) { + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "Rewind, processed node: " + p_nodes_to_recover[r]->node->get_path()); + } +#endif + } + + // Step 2 -- Process the controller. + if (p_recover_controller && player_controller_node_data->sync_enabled) { +#ifdef DEBUG_ENABLED + has_next = +#endif + p_controller->process_instant(i, p_delta); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "Rewind, processed controller: " + p_controller->get_path()); + } + + // Step 3 -- Process the Actions. +#ifdef DEBUG_ENABLED + // This can't happen because the client stores a snapshot for each frame. + CRASH_COND(client_snapshots[i].input_id == p_checkable_input_id + i); +#endif + for (int a_i = 0; a_i < client_snapshots[i].actions.size(); a_i += 1) { + // Leave me alone, I don't want to make `execute()` const. 😫 + NetActionProcessor(client_snapshots[i].actions[a_i].processor).execute(); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "Rewind, processed Action: " + String(client_snapshots[i].actions[a_i].processor)); + } + + // Step 4 -- Pull node changes and Update snapshots. + for (uint32_t r = 0; r < p_nodes_to_recover.size(); r += 1) { + if (p_nodes_to_recover[r]->sync_enabled == false) { + // This node is not sync. + continue; + } + // Pull changes + scene_synchronizer->pull_node_changes(p_nodes_to_recover[r]); + + // Update client snapshot. + if (uint32_t(client_snapshots[i].node_vars.size()) <= p_nodes_to_recover[r]->id) { + client_snapshots[i].node_vars.resize(p_nodes_to_recover[r]->id + 1); + } + + Vector *snap_node_vars = client_snapshots[i].node_vars.ptrw() + p_nodes_to_recover[r]->id; + snap_node_vars->resize(p_nodes_to_recover[r]->vars.size()); + + NetUtility::Var *vars = snap_node_vars->ptrw(); + for (uint32_t v = 0; v < p_nodes_to_recover[r]->vars.size(); v += 1) { + vars[v] = p_nodes_to_recover[r]->vars[v].var; + } + } + scene_synchronizer->change_events_flush(); + } + +#ifdef DEBUG_ENABLED + // Unreachable because the above loop consume all instants. + CRASH_COND(has_next); +#endif +} + +void ClientSynchronizer::__pcr__sync_no_rewind(const LocalVector &p_postponed_recover) { + // Apply found differences without rewind. + scene_synchronizer->change_events_begin(NetEventFlag::SYNC_RECOVER); + for (uint32_t i = 0; i < p_postponed_recover.size(); i += 1) { + NetUtility::NodeData *rew_node_data = p_postponed_recover[i].node_data; + if (rew_node_data->sync_enabled == false) { + // This node sync is disabled. + continue; + } + + Node *node = rew_node_data->node; + const NetUtility::Var *vars_ptr = p_postponed_recover[i].vars.ptr(); + + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "[Snapshot partial reset] Node: " + node->get_path()); + + // Set the value on the synchronizer too. + for (int v = 0; v < p_postponed_recover[i].vars.size(); v += 1) { + // We need to search it because the postponed recovered is not + // aligned. + // TODO This array is generated few lines above. + // Can we store the ID too, so to avoid this search???? + const int rew_var_index = rew_node_data->vars.find(vars_ptr[v].name); + // Unreachable, because when the snapshot is received the + // algorithm make sure the `scene_synchronizer` is traking the + // variable. + CRASH_COND(rew_var_index <= -1); + + const Variant old_val = rew_node_data->vars[rew_var_index].var.value; + rew_node_data->vars[rew_var_index].var.value = vars_ptr[v].value.duplicate(true); + node->set(vars_ptr[v].name, vars_ptr[v].value); + + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, " |- Variable: " + vars_ptr[v].name + "; old value: " + old_val + " new value: " + vars_ptr[v].value); + scene_synchronizer->change_event_add( + rew_node_data, + rew_var_index, + old_val); + } + + // Update the last client snapshot. + if (client_snapshots.empty() == false) { + if (uint32_t(client_snapshots.back().node_vars.size()) <= rew_node_data->id) { + client_snapshots.back().node_vars.resize(rew_node_data->id + 1); + } + + Vector *snap_node_vars = client_snapshots.back().node_vars.ptrw() + rew_node_data->id; + snap_node_vars->resize(rew_node_data->vars.size()); + + NetUtility::Var *vars = snap_node_vars->ptrw(); + + for (uint32_t v = 0; v < rew_node_data->vars.size(); v += 1) { + vars[v] = rew_node_data->vars[v].var; + } + } + } + scene_synchronizer->change_events_flush(); +} + void ClientSynchronizer::process_paused_controller_recovery(real_t p_delta) { #ifdef DEBUG_ENABLED CRASH_COND(server_snapshots.empty()); @@ -2604,8 +3555,8 @@ void ClientSynchronizer::process_paused_controller_recovery(real_t p_delta) { // Different rew_node_data->vars[var_id].var.value = snap_vars_ptr[var_id].value; node->set(snap_vars_ptr[var_id].name, snap_vars_ptr[var_id].value); - NET_DEBUG_PRINT("[Snapshot paused controller] Node: " + node->get_path()); - NET_DEBUG_PRINT(" |- Variable: " + snap_vars_ptr[var_id].name + "; value: " + snap_vars_ptr[var_id].value); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "[Snapshot paused controller] Node: " + node->get_path()); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, " |- Variable: " + snap_vars_ptr[var_id].name + "; value: " + snap_vars_ptr[var_id].value); scene_synchronizer->change_event_add( rew_node_data, var_id, @@ -2623,18 +3574,21 @@ bool ClientSynchronizer::parse_sync_data( Variant p_sync_data, void *p_user_pointer, void (*p_node_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data), - void (*p_controller_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id), + void (*p_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, uint32_t p_var_id, const Variant &p_value)) { - // The sync data is an array that contains the scene information. + // The sync data is an array that contains the scene informations. // It's used for several things, for this reason this function allows to // customize the parsing. // // The data is composed as follows: - // [NODE, VARIABLE, Value, VARIABLE, Value, VARIABLE, value, NIL, + // [TRUE/FALSE, InputID, + // NODE, VARIABLE, Value, VARIABLE, Value, VARIABLE, value, NIL, // NODE, INPUT ID, VARIABLE, Value, VARIABLE, Value, NIL, // NODE, VARIABLE, Value, VARIABLE, Value, NIL] // // Each node ends with a NIL, and the NODE and the VARIABLE are special: + // - InputID: The first parameter is a boolean; when is true the following input is the `InputID`. // - NODE, can be an array of two variables [Node ID, NodePath] or directly // a Node ID. Obviously the array is sent only the first time. // - INPUT ID, this is optional and is used only when the node is a controller. @@ -2652,10 +3606,27 @@ bool ClientSynchronizer::parse_sync_data( const Vector raw_snapshot = p_sync_data; const Variant *raw_snapshot_ptr = raw_snapshot.ptr(); + int snap_data_index = 0; + + // Fetch the `InputID`. + ERR_FAIL_COND_V_MSG(raw_snapshot.size() < 1, false, "This snapshot is corrupted as it doesn't even contains the first parameter used to specify the `InputID`."); + ERR_FAIL_COND_V_MSG(raw_snapshot[0].get_type() != Variant::BOOL, false, "This snapshot is corrupted as the first parameter is not a boolean."); + snap_data_index += 1; + if (raw_snapshot[0].operator bool()) { + // The InputId is set. + ERR_FAIL_COND_V_MSG(raw_snapshot.size() < 2, false, "This snapshot is corrupted as the second parameter containing the `InputID` is not set."); + ERR_FAIL_COND_V_MSG(raw_snapshot[1].get_type() != Variant::INT, false, "This snapshot is corrupted as the second parameter containing the `InputID` is not an INTEGER."); + const uint32_t input_id = raw_snapshot[1]; + p_input_id_parse(p_user_pointer, input_id); + snap_data_index += 1; + } else { + p_input_id_parse(p_user_pointer, UINT32_MAX); + } + NetUtility::NodeData *synchronizer_node_data = nullptr; uint32_t var_id = UINT32_MAX; - for (int snap_data_index = 0; snap_data_index < raw_snapshot.size(); snap_data_index += 1) { + for (; snap_data_index < raw_snapshot.size(); snap_data_index += 1) { const Variant v = raw_snapshot_ptr[snap_data_index]; if (synchronizer_node_data == nullptr) { // Node is null so we expect `v` has the node info. @@ -2688,7 +3659,7 @@ bool ClientSynchronizer::parse_sync_data( goto node_lookup_out; } } else { - // The arrived snapshot doesn't seems to be in the expected form. + // The arrived snapshot does't seems to be in the expected form. ERR_FAIL_V_MSG(false, "This snapshot is corrupted. Now the node is expected, " + String(v) + " was submitted instead."); } @@ -2698,7 +3669,7 @@ bool ClientSynchronizer::parse_sync_data( if (node_path_ptr == nullptr) { // Was not possible lookup the node_path. - NET_DEBUG_WARN("The node with ID `" + itos(net_node_id) + "` is not know by this peer, this is not supposed to happen."); + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "The node with ID `" + itos(net_node_id) + "` is not know by this peer, this is not supposed to happen."); notify_server_full_snapshot_is_needed(); skip_this_node = true; goto node_lookup_check; @@ -2707,11 +3678,10 @@ bool ClientSynchronizer::parse_sync_data( } } - node = scene_synchronizer->get_tree()->get_root()->get_node(node_path); - + node = scene_synchronizer->get_tree()->get_root()->get_node_or_null(node_path); if (node == nullptr) { // The node doesn't exists. - NET_DEBUG_ERR("The node " + node_path + " still doesn't exist."); + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "The node " + node_path + " still doesn't exist."); skip_this_node = true; goto node_lookup_check; } @@ -2723,20 +3693,26 @@ bool ClientSynchronizer::parse_sync_data( scene_synchronizer->set_node_data_id(nd, net_node_id); synchronizer_node_data = nd; } else { - NET_DEBUG_ERR("[BUG] This node " + node->get_path() + " was not know on this client. Though, was not possible to register it."); + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "[BUG] This node " + node->get_path() + " was not know on this client. Though, was not possible to register it."); skip_this_node = true; } } node_lookup_check: if (skip_this_node || synchronizer_node_data == nullptr) { - // This node doesn't exist; skip it entirely. + synchronizer_node_data = nullptr; + + // This node does't exist; skip it entirely. for (snap_data_index += 1; snap_data_index < raw_snapshot.size(); snap_data_index += 1) { if (raw_snapshot_ptr[snap_data_index].get_type() == Variant::NIL) { break; } } - ERR_CONTINUE_MSG(true, "This NetNodeId " + itos(net_node_id) + " doesn't exist on this client."); + + if (!skip_this_node) { + SceneSynchronizerDebugger::singleton()->debug_warning(scene_synchronizer, "This NetNodeId " + itos(net_node_id) + " doesn't exist on this client."); + } + continue; } node_lookup_out: @@ -2750,13 +3726,13 @@ bool ClientSynchronizer::parse_sync_data( p_node_parse(p_user_pointer, synchronizer_node_data); if (synchronizer_node_data->is_controller) { - // This is a controller, so the next data is the input ID. - ERR_FAIL_COND_V(snap_data_index + 1 >= raw_snapshot.size(), false); - snap_data_index += 1; - const uint32_t input_id = raw_snapshot_ptr[snap_data_index]; - ERR_FAIL_COND_V_MSG(input_id == UINT32_MAX, false, "The server is always able to send input_id, so this snapshot seems corrupted."); - - p_controller_parse(p_user_pointer, synchronizer_node_data, input_id); + if (synchronizer_node_data == player_controller_node_data) { + // The current controller. + p_controller_parse(p_user_pointer, synchronizer_node_data); + } else { + // This is just a remoote controller + p_controller_parse(p_user_pointer, synchronizer_node_data); + } } } else if (var_id == UINT32_MAX) { @@ -2799,7 +3775,7 @@ bool ClientSynchronizer::parse_sync_data( Variant(), skip_rewinding, enabled)); - NET_DEBUG_ERR("The variable " + variable_name + " for the node " + synchronizer_node_data->node->get_path() + " was not known on this client. This should never happen, make sure to register the same nodes on the client and server."); + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "The variable " + variable_name + " for the node " + synchronizer_node_data->node->get_path() + " was not known on this client. This should never happen, make sure to register the same nodes on the client and server."); } if (index != var_id) { @@ -2807,7 +3783,7 @@ bool ClientSynchronizer::parse_sync_data( // It's not expected because if index is different to // var_id, var_id should have a not yet initialized // variable. - NET_DEBUG_ERR("This snapshot is corrupted. The var_id, at this point, must have a not yet init variable."); + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "This snapshot is corrupted. The var_id, at this point, must have a not yet init variable."); notify_server_full_snapshot_is_needed(); return false; } @@ -2827,7 +3803,7 @@ bool ClientSynchronizer::parse_sync_data( if (var_id >= synchronizer_node_data->vars.size() || synchronizer_node_data->vars[var_id].id == UINT32_MAX) { - NET_DEBUG_PRINT("The var with ID `" + itos(var_id) + "` is not know by this peer, this is not supposed to happen."); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "The var with ID `" + itos(var_id) + "` is not know by this peer, this is not supposed to happen."); notify_server_full_snapshot_is_needed(); @@ -2879,7 +3855,7 @@ void ClientSynchronizer::set_enabled(bool p_enabled) { bool ClientSynchronizer::parse_snapshot(Variant p_snapshot) { if (want_to_enable) { if (enabled) { - NET_DEBUG_ERR("At this point the client is supposed to be disabled. This is a bug that must be solved."); + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "At this point the client is supposed to be disabled. This is a bug that must be solved."); } // The netwroking is disabled and we can re-enable it. enabled = true; @@ -2888,12 +3864,9 @@ bool ClientSynchronizer::parse_snapshot(Variant p_snapshot) { } need_full_snapshot_notified = false; - last_received_snapshot.input_id = UINT32_MAX; - ERR_FAIL_COND_V_MSG( - player_controller_node_data == nullptr, - false, - "Is not possible to receive server snapshots if you are not tracking any NetController."); + NetUtility::Snapshot received_snapshot = last_received_snapshot; + received_snapshot.input_id = UINT32_MAX; struct ParseData { NetUtility::Snapshot &snapshot; @@ -2901,7 +3874,7 @@ bool ClientSynchronizer::parse_snapshot(Variant p_snapshot) { }; ParseData parse_data{ - last_received_snapshot, + received_snapshot, player_controller_node_data }; @@ -2918,56 +3891,57 @@ bool ClientSynchronizer::parse_snapshot(Variant p_snapshot) { pd->snapshot.node_vars.resize(p_node_data->id + 1); } - // Make sure this snapshot has all the variables. - pd->snapshot.node_vars.write[p_node_data->id].resize(p_node_data->vars.size()); + if (p_node_data->vars.size() != uint32_t(pd->snapshot.node_vars[p_node_data->id].size())) { + // This mean the parser just added a new variable. + // Already notified by the parser. + pd->snapshot.node_vars.write[p_node_data->id].resize(p_node_data->vars.size()); + } }, - // Parse controller: - [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id) { + // Parse InputID: + [](void *p_user_pointer, uint32_t p_input_id) { ParseData *pd = static_cast(p_user_pointer); - if (p_node_data == pd->player_controller_node_data) { - // This is the main controller, store the input ID. + if (pd->player_controller_node_data != nullptr) { + // This is the main controller, store the `InputID`. pd->snapshot.input_id = p_input_id; } }, + // Parse controller: + [](void *p_user_pointer, NetUtility::NodeData *p_node_data) {}, + // Parse variable: [](void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_var_id, const Variant &p_value) { ParseData *pd = static_cast(p_user_pointer); #ifdef DEBUG_ENABLED - // This can't be triggered because th `Parse Node` function + // This can't be triggered because the `Parse Node` function // above make sure to create room for this array. CRASH_COND(uint32_t(pd->snapshot.node_vars.size()) <= p_node_data->id); + CRASH_COND(uint32_t(pd->snapshot.node_vars[p_node_data->id].size()) != p_node_data->vars.size()); #endif // ~DEBUG_ENABLED - if (unlikely(p_node_data->vars.size() != uint32_t(pd->snapshot.node_vars[p_node_data->id].size()))) { - // This mean the parser just added a new variable. - // Already notified by the parser. - pd->snapshot.node_vars.write[p_node_data->id].resize(p_node_data->vars.size()); - } - pd->snapshot.node_vars.write[p_node_data->id].write[p_var_id].name = p_node_data->vars[p_var_id].var.name; pd->snapshot.node_vars.write[p_node_data->id].write[p_var_id].value = p_value.duplicate(true); }); if (success == false) { - NET_DEBUG_ERR("Snapshot:"); - NET_DEBUG_ERR(p_snapshot); + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, "Snapshot:"); + SceneSynchronizerDebugger::singleton()->debug_error(scene_synchronizer, p_snapshot); return false; } - // We expect that the player_controller is updated by this new snapshot, - // so make sure it's done so. - if (unlikely(last_received_snapshot.input_id == UINT32_MAX)) { - NET_DEBUG_PRINT("Recovery aborted, the player controller (" + player_controller_node_data->node->get_path() + ") was not part of the received snapshot, probably the server doesn't have important information for this peer. NetUtility::Snapshot:"); - NET_DEBUG_PRINT(p_snapshot); - return false; - } else { - // Success. - - return true; + if (unlikely(received_snapshot.input_id == UINT32_MAX && player_controller_node_data != nullptr)) { + // We espect that the player_controller is updated by this new snapshot, + // so make sure it's done so. + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "[INFO] the player controller (" + player_controller_node_data->node->get_path() + ") was not part of the received snapshot, this happens when the server destroys the peer controller. NetUtility::Snapshot:"); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, p_snapshot); } + + last_received_snapshot = received_snapshot; + + // Success. + return true; } bool ClientSynchronizer::compare_vars( @@ -2982,24 +3956,12 @@ bool ClientSynchronizer::compare_vars( bool diff = false; #endif - if (p_server_vars.size() != p_client_vars.size() || - uint32_t(p_server_vars.size()) > p_synchronizer_node_data->vars.size()) { - NET_DEBUG_PRINT("Difference found: The server has a different variable count (" + itos(p_server_vars.size()) + ") compared to the client (" + itos(p_client_vars.size()) + ")."); - NET_DEBUG_PRINT("Server variables:"); - for (int i = 0; i < p_server_vars.size(); i += 1) { - NET_DEBUG_PRINT(" |- " + p_server_vars[i].name + ": " + p_server_vars[i].value); + for (uint32_t var_index = 0; var_index < uint32_t(p_client_vars.size()); var_index += 1) { + if (uint32_t(p_server_vars.size()) <= var_index) { + // This variable isn't defined into the server snapshot, so assuming it's correct. + continue; } - NET_DEBUG_PRINT(" ------"); - NET_DEBUG_PRINT("Client variables:"); - for (int i = 0; i < p_client_vars.size(); i += 1) { - NET_DEBUG_PRINT(" |- " + p_client_vars[i].name + ": " + p_client_vars[i].value); - } - NET_DEBUG_PRINT(" ------"); - return true; - } - - for (uint32_t var_index = 0; var_index < uint32_t(p_server_vars.size()); var_index += 1) { if (s_vars[var_index].name == StringName()) { // This variable was not set, skip the check. continue; @@ -3021,11 +3983,7 @@ bool ClientSynchronizer::compare_vars( r_postponed_recover.push_back(s_vars[var_index]); } else { // The vars are different. - NET_DEBUG_PRINT("Difference found on var #" + itos(var_index) + " " + p_synchronizer_node_data->vars[var_index].var.name + " " + - "Server value: `" + s_vars[var_index].value + "` " + - "Client value: `" + c_vars[var_index].value + "`. " + - "[Server name: `" + s_vars[var_index].name + "` " + - "Client name: `" + c_vars[var_index].name + "`]."); + SceneSynchronizerDebugger::singleton()->debug_print(scene_synchronizer, "Difference found on var #" + itos(var_index) + " " + p_synchronizer_node_data->vars[var_index].var.name + " " + "Server value: `" + s_vars[var_index].value + "` " + "Client value: `" + c_vars[var_index].value + "`. " + "[Server name: `" + s_vars[var_index].name + "` " + "Client name: `" + c_vars[var_index].name + "`]."); #ifdef DEBUG_ENABLED diff = true; #else @@ -3051,5 +4009,69 @@ void ClientSynchronizer::notify_server_full_snapshot_is_needed() { // Notify the server that a full snapshot is needed. need_full_snapshot_notified = true; - scene_synchronizer->rpc_id(1, "_rpc_notify_need_full_snapshot"); + scene_synchronizer->rpc_id(1, SNAME("_rpc_notify_need_full_snapshot")); +} + +void ClientSynchronizer::send_actions_to_server() { + const uint64_t now = OS::get_singleton()->get_ticks_msec(); + + LocalVector packet_actions; + + // The packet will contains the most recent actions. + for (uint32_t i = 0; i < pending_actions.size(); i += 1) { + if (pending_actions[i].sent_by_the_server) { + // This is a remote Action. Do not network it. + continue; + } + + if (int(pending_actions[i].send_count) >= scene_synchronizer->get_actions_redundancy()) { + // Nothing to do. + continue; + } + + if ((pending_actions[i].send_timestamp + 2 /*ms - give some room*/) > now) { + // Nothing to do. + continue; + } + + pending_actions[i].send_timestamp = now; + pending_actions[i].send_count += 1; + packet_actions.push_back(&pending_actions[i]); + } + + if (packet_actions.size() <= 0) { + // Nothing to send. + return; + } + + // Encode the actions. + DataBuffer db; + db.begin_write(0); + net_action::encode_net_action(packet_actions, 1, db); + db.dry(); + + // Send to the server. + const int server_peer_id = 1; + scene_synchronizer->rpc_unreliable_id( + server_peer_id, + "_rpc_send_actions", + db.get_buffer().get_bytes()); +} + +void ClientSynchronizer::clean_pending_actions() { + // The packet will contains the most recent actions. + for (int64_t i = int64_t(pending_actions.size()) - 1; i >= 0; i -= 1) { + if ( + pending_actions[i].locally_executed == false || + (pending_actions[i].sent_by_the_server == false && int(pending_actions[i].send_count) < scene_synchronizer->get_actions_redundancy())) { + // Still somethin to do. + continue; + } + + pending_actions.remove_unordered(i); + } +} + +void ClientSynchronizer::check_missing_actions() { + server_sender_info.check_missing_actions_and_clean_up(scene_synchronizer); } diff --git a/modules/network_synchronizer/scene_synchronizer.h b/modules/network_synchronizer/scene_synchronizer.h index 59da71084..f949523c1 100644 --- a/modules/network_synchronizer/scene_synchronizer.h +++ b/modules/network_synchronizer/scene_synchronizer.h @@ -1,16 +1,12 @@ -#ifndef SCENE_SYNCHRONIZER_H -#define SCENE_SYNCHRONIZER_H - /*************************************************************************/ /* scene_synchronizer.h */ /*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://github.com/Relintai/pandemonium_engine */ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ /*************************************************************************/ -/* 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. */ +/* 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 */ @@ -38,13 +34,20 @@ #include "scene/main/node.h" -#include "core/containers/local_vector.h" -#include "core/containers/oa_hash_map.h" +#include "core/local_vector.h" +#include "core/oa_hash_map.h" +#include "net_action.h" #include "net_utilities.h" #include +#ifndef SCENE_SYNCHRONIZER_H +#define SCENE_SYNCHRONIZER_H + +#include "godot_backward_utility_header.h" + class Synchronizer; class NetworkedController; +class PlayerController; /// # SceneSynchronizer /// @@ -61,20 +64,20 @@ class NetworkedController; /// The clients receives the server snapshot, so it compares with the local /// 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 variables to track using the function `register_variable`. /// /// ## 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 /// `SceneSynchronizer`, it will drive the `NetworkedController` so to recover that /// missalignment. /// /// /// ## 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 /// not necessary use the `NetworkedController`. /// @@ -127,6 +130,11 @@ public: private: real_t server_notify_state_interval = 1.0; 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; Synchronizer *synchronizer = nullptr; @@ -150,7 +158,7 @@ private: // Controller nodes. LocalVector 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; int event_flag; @@ -159,7 +167,7 @@ private: public: static void _bind_methods(); - virtual void _notification(int p_what); + void _notification(int p_what); public: SceneSynchronizer(); @@ -174,6 +182,14 @@ public: void set_comparison_float_tolerance(real_t p_tolerance); 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`. NetUtility::NodeData *register_node(Node *p_node); uint32_t register_node_gdscript(Node *p_node); @@ -195,6 +211,36 @@ public: void stop_node_sync(const Node *p_node); 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 &p_recipients = Vector()); + + void trigger_action( + Node *p_node, + NetActionId p_id, + const Array &p_arguments = Array(), + const Vector &p_recipients = Vector()); + /// Returns the variable ID relative to the `Node`. /// This may return `UINT32_MAX` in various cases: /// - The node is not registered. @@ -212,7 +258,7 @@ public: /// Add a dependency to a controller, so that the rewinding mechanism can /// make sure to rewind that node when the controller is rewinded. /// You can remove and add dependency at any time. This operation - /// don't need to be performed on server. + /// don't need to be perfomed on server. void controller_add_dependency(Node *p_controller, Node *p_node); void controller_remove_dependency(Node *p_controller, Node *p_node); int controller_get_dependency_count(Node *p_controller) const; @@ -250,10 +296,13 @@ public: void reset_synchronizer_mode(); void clear(); + void notify_controller_control_mode_changed(NetworkedController *controller); + void _rpc_send_state(const Variant &p_snapshot); void _rpc_notify_need_full_snapshot(); void _rpc_set_network_enabled(bool p_enabled); void _rpc_notify_peer_status(bool p_enabled); + void _rpc_send_actions(const Vector &p_data); void update_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_events_flush(); -private: +public: // -------------------------------------------------------------------------------- INTERNAL void expand_organized_node_data_vector(uint32_t p_size); /// 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. void set_node_data_id(NetUtility::NodeData *p_node_data, NetNodeId p_id); + NetworkedController *fetch_controller_by_peer(int peer); + public: /// Returns true when the vectors are the same. Uses comparison_float_tolerance member. bool compare(const Vector2 &p_first, const Vector2 &p_second) const; @@ -342,18 +393,33 @@ public: virtual void on_variable_added(NetUtility::NodeData *p_node_data, const StringName &p_var_name) {} virtual void on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) {} virtual void on_controller_reset(NetUtility::NodeData *p_node_data) {} + virtual void on_action_triggered( + NetUtility::NodeData *p_node_data, + NetActionId p_id, + const Array &p_arguments, + const Vector &p_recipients) {} + virtual void on_actions_received( + int sender_peer, + const LocalVector &p_actions) {} }; class NoNetSynchronizer : public Synchronizer { friend class SceneSynchronizer; bool enabled = true; + uint32_t frame_count = 0; + LocalVector pending_actions; public: NoNetSynchronizer(SceneSynchronizer *p_node); - virtual void clear(); - virtual void process(); + virtual void clear() override; + virtual void process() override; + virtual void on_action_triggered( + NetUtility::NodeData *p_node_data, + NetActionId p_id, + const Array &p_arguments, + const Vector &p_recipients) override; void set_enabled(bool p_enabled); bool is_enabled() const; @@ -366,26 +432,58 @@ class ServerSynchronizer : public Synchronizer { struct Change { bool not_known_before = false; - RBSet uknown_vars; - RBSet vars; + Set uknown_vars; + Set 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. LocalVector changes; + OAHashMap senders_info; + OAHashMap peers_next_action_trigger_input_id; + + uint32_t server_actions_count = 0; + LocalVector server_actions; + public: ServerSynchronizer(SceneSynchronizer *p_node); - virtual void clear(); - virtual void process(); - virtual void on_node_added(NetUtility::NodeData *p_node_data); - virtual void on_variable_added(NetUtility::NodeData *p_node_data, const StringName &p_var_name); - virtual void on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag); + virtual void clear() override; + virtual void process() override; + virtual void on_node_added(NetUtility::NodeData *p_node_data) override; + virtual void on_node_removed(NetUtility::NodeData *p_node_data) override; + virtual void on_variable_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 &p_recipients) override; + virtual void on_actions_received( + int sender_peer, + const LocalVector &p_actions) override; void process_snapshot_notificator(real_t p_delta); Vector global_nodes_generate_snapshot(bool p_force_full_snapshot) const; void controller_generate_snapshot(const NetUtility::NodeData *p_node_data, bool p_force_full_snapshot, Vector &r_snapshot_result) const; - void generate_snapshot_node_data(const NetUtility::NodeData *p_node_data, bool p_force_full_snapshot, Vector &r_result) const; + void generate_snapshot_node_data(const NetUtility::NodeData *p_node_data, SnapshotGenerationMode p_mode, Vector &r_result) const; + + void execute_actions(); + void send_actions_to_clients(); + + void clean_pending_actions(); + void check_missing_actions(); }; class ClientSynchronizer : public Synchronizer { @@ -417,25 +515,39 @@ class ClientSynchronizer : public Synchronizer { } }; - RBSet sync_end_events; + Set sync_end_events; + + uint32_t locally_triggered_actions_count = 0; + uint32_t actions_input_id = 0; + LocalVector pending_actions; + NetActionSenderInfo server_sender_info; public: ClientSynchronizer(SceneSynchronizer *p_node); - virtual void clear(); + virtual void clear() override; - virtual void process(); - virtual void on_node_added(NetUtility::NodeData *p_node_data); - virtual void on_node_removed(NetUtility::NodeData *p_node_data); - 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 process() override; + virtual void on_node_added(NetUtility::NodeData *p_node_data) override; + virtual void on_node_removed(NetUtility::NodeData *p_node_data) override; + virtual void on_variable_changed(NetUtility::NodeData *p_node_data, NetVarId p_var_id, const Variant &p_old_value, int p_flag) override; + virtual void on_controller_reset(NetUtility::NodeData *p_node_data) override; + virtual void on_action_triggered( + NetUtility::NodeData *p_node_data, + NetActionId p_id, + const Array &p_arguments, + const Vector &p_recipients) override; + virtual void on_actions_received( + int sender_peer, + const LocalVector &p_actions) override; void receive_snapshot(Variant p_snapshot); bool parse_sync_data( Variant p_snapshot, void *p_user_pointer, void (*p_node_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data), - void (*p_controller_parse)(void *p_user_pointer, NetUtility::NodeData *p_node_data, uint32_t p_input_id), + void (*p_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 set_enabled(bool p_enabled); @@ -449,6 +561,29 @@ private: std::deque &r_snapshot_storage); 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 &r_nodes_to_recover, + LocalVector &r_postponed_recover); + + void __pcr__sync_pre_rewind( + const LocalVector &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 &p_nodes_to_recover); + + void __pcr__sync_no_rewind( + const LocalVector &p_postponed_recover); + + void apply_last_received_server_snapshot(); void process_paused_controller_recovery(real_t p_delta); bool parse_snapshot(Variant p_snapshot); bool compare_vars( @@ -458,6 +593,10 @@ private: Vector &r_postponed_recover); void notify_server_full_snapshot_is_needed(); + + void send_actions_to_server(); + void clean_pending_actions(); + void check_missing_actions(); }; VARIANT_ENUM_CAST(NetEventFlag) diff --git a/modules/network_synchronizer/scene_synchronizer_debugger.cpp b/modules/network_synchronizer/scene_synchronizer_debugger.cpp new file mode 100644 index 000000000..538b80f71 --- /dev/null +++ b/modules/network_synchronizer/scene_synchronizer_debugger.cpp @@ -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()); + List *properties = classes_property_lists.lookup_ptr(p_node->get_class_name()); + + p_node->get_property_list(properties); + } + + List *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 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 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 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::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 +} diff --git a/modules/network_synchronizer/scene_synchronizer_debugger.h b/modules/network_synchronizer/scene_synchronizer_debugger.h new file mode 100644 index 000000000..2434d497a --- /dev/null +++ b/modules/network_synchronizer/scene_synchronizer_debugger.h @@ -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 *properties; + TrackedNode() = default; + TrackedNode(Node *p_node) : + node(p_node) {} + TrackedNode(Node *p_node, List *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 dump_classes; + bool setup_done = false; + + uint32_t log_counter = 0; + SceneTree *scene_tree = nullptr; + String main_dump_directory_path; + String dump_name; + + LocalVector tracked_nodes; + // HaskMap between class name and property list: to avoid fetching the property list per object each frame. + OAHashMap> 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); +}; diff --git a/modules/network_synchronizer/tests/test_bit_array.h b/modules/network_synchronizer/tests/test_bit_array.h deleted file mode 100644 index 8a1513231..000000000 --- a/modules/network_synchronizer/tests/test_bit_array.h +++ /dev/null @@ -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 data; - data.push_back(-1); - data.push_back(0); - data.push_back(1); - - const BitArray array(data); - CHECK_MESSAGE(array.size_in_bits() == data.size() * 8.0, "Number of bits must be equal to size of original data"); - CHECK_MESSAGE(array.size_in_bytes() == data.size(), "Number of bytes must be equal to size of original data"); - for (int i = 0; i < data.size(); ++i) { - CHECK_MESSAGE(array.read_bits(i * 8, 8) == data[i], "Readed bits should be equal to the original"); - } -} - -TEST_CASE("[Modules][BitArray] Pre-allocation and zeroing") { - constexpr uint64_t value = UINT64_MAX; - constexpr int bits = sizeof(value); - - BitArray array(bits); - CHECK_MESSAGE(array.size_in_bits() == bits, "Number of bits must be equal to allocated"); - array.store_bits(0, value, bits); - array.zero(); - CHECK_MESSAGE(array.read_bits(0, bits) == 0, "Should read zero"); -} -} // namespace TestBitArray - -#endif // TEST_BIT_ARRAY_H diff --git a/modules/network_synchronizer/tests/test_data_buffer.h b/modules/network_synchronizer/tests/test_data_buffer.h deleted file mode 100644 index cb822e98e..000000000 --- a/modules/network_synchronizer/tests/test_data_buffer.h +++ /dev/null @@ -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 real_values(DataBuffer::CompressionLevel p_compression_level) { - Vector values; - values.append(Math_PI); - values.append(0.0); - values.append(-3.04); - values.append(3.04); - values.append(0.5); - values.append(-0.5); - values.append(1); - values.append(-1); - values.append(0.9); - values.append(-0.9); - values.append(3.9); - values.append(-3.9); - values.append(8); - - switch (p_compression_level) { - case DataBuffer::COMPRESSION_LEVEL_3: { - values.append(-15'360); - values.append(15'360); - } break; - case DataBuffer::COMPRESSION_LEVEL_2: { - // https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Half_precision_examples - values.append(-65'504); - values.append(65'504); - values.append(Math::pow(2.0, -14) / 1024); - values.append(Math::pow(2.0, -14) * 1023 / 1024); - values.append(Math::pow(2.0, -1) * (1 + 1023.0 / 1024)); - values.append((1 + 1.0 / 1024)); - } break; - case DataBuffer::COMPRESSION_LEVEL_1: { - // https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Single-precision_examples - values.append(FLT_MIN); - values.append(-FLT_MAX); - values.append(FLT_MAX); - values.append(Math::pow(2.0, -149)); - values.append(Math::pow(2.0, -126) * (1 - Math::pow(2.0, -23))); - values.append(1 - Math::pow(2.0, -24)); - values.append(1 + Math::pow(2.0, -23)); - } break; - case DataBuffer::COMPRESSION_LEVEL_0: { - // https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples - values.append(DBL_MIN); - values.append(DBL_MAX); - values.append(-DBL_MAX); - values.append(1.0000000000000002); - values.append(4.9406564584124654 * Math::pow(10.0, -324)); - values.append(2.2250738585072009 * Math::pow(10.0, -308)); - } break; - } - - return values; -} - -TEST_CASE("[Modules][DataBuffer] Bool") { - bool value = {}; - - SUBCASE("[Modules][DataBuffer] false") { - value = false; - } - SUBCASE("[Modules][DataBuffer] true") { - value = true; - } - - DataBuffer buffer; - buffer.begin_write(0); - CHECK_MESSAGE(buffer.add_bool(value) == value, "Should return the same value"); - buffer.begin_read(); - CHECK_MESSAGE(buffer.read_bool() == value, "Should read the same value"); -} - -TEST_CASE("[Modules][DataBuffer] Int") { - DataBuffer::CompressionLevel compression_level = {}; - int64_t value = {}; - - DataBuffer buffer; - SUBCASE("[Modules][DataBuffer] Compression level 3") { - compression_level = DataBuffer::COMPRESSION_LEVEL_3; - - SUBCASE("[Modules][DataBuffer] Positive") { - value = 127; - } - SUBCASE("[Modules][DataBuffer] Zero") { - value = 0; - } - SUBCASE("[Modules][DataBuffer] Negative") { - value = -128; - } - } - - SUBCASE("[Modules][DataBuffer] Compression level 2") { - compression_level = DataBuffer::COMPRESSION_LEVEL_2; - - SUBCASE("[Modules][DataBuffer] Positive") { - value = 32767; - } - SUBCASE("[Modules][DataBuffer] Zero") { - value = 0; - } - SUBCASE("[Modules][DataBuffer] Negative") { - value = -32768; - } - } - - SUBCASE("[Modules][DataBuffer] Compression level 1") { - compression_level = DataBuffer::COMPRESSION_LEVEL_1; - - SUBCASE("[Modules][DataBuffer] Positive") { - value = 2147483647; - } - SUBCASE("[Modules][DataBuffer] Zero") { - value = 0; - } - SUBCASE("[Modules][DataBuffer] Negative") { - value = -2147483648LL; - } - } - - SUBCASE("[Modules][DataBuffer] Compression level 0") { - compression_level = DataBuffer::COMPRESSION_LEVEL_0; - - SUBCASE("[Modules][DataBuffer] Positive") { - value = 2147483647; - } - SUBCASE("[Modules][DataBuffer] Zero") { - value = 0; - } - SUBCASE("[Modules][DataBuffer] Negative") { - value = -9223372036854775807LL; - } - } - - buffer.begin_write(0); - CHECK_MESSAGE(buffer.add_int(value, compression_level) == value, "Should return the same value"); - buffer.begin_read(); - CHECK_MESSAGE(buffer.read_int(compression_level) == value, "Should read the same value"); -} - -TEST_CASE("[Modules][DataBuffer] Real") { - DataBuffer::CompressionLevel compression_level = {}; - - SUBCASE("[Modules][DataBuffer] Compression level 3 (Minifloat)") { - compression_level = DataBuffer::COMPRESSION_LEVEL_3; - } - - SUBCASE("[Modules][DataBuffer] Compression level 2 (Half perception)") { - compression_level = DataBuffer::COMPRESSION_LEVEL_2; - } - - SUBCASE("[Modules][DataBuffer] Compression level 1 (Single perception)") { - compression_level = DataBuffer::COMPRESSION_LEVEL_1; - } - - SUBCASE("[Modules][DataBuffer] Compression level 0 (Double perception)") { - compression_level = DataBuffer::COMPRESSION_LEVEL_0; - } - - DataBuffer buffer; - const Vector values = real_values(compression_level); - const double epsilon = Math::pow(2.0, DataBuffer::get_mantissa_bits(compression_level) - 1); - for (int i = 0; i < values.size(); ++i) { - buffer.begin_write(0); - const double value = values[i]; - CHECK_MESSAGE(buffer.add_real(value, compression_level) == doctest::Approx(value).epsilon(epsilon), "Should return the same value"); - - buffer.begin_read(); - CHECK_MESSAGE(buffer.read_real(compression_level) == doctest::Approx(value).epsilon(epsilon), "Should read the same value"); - } -} - -TEST_CASE("[Modules][DataBuffer] Positive unit real") { - DataBuffer::CompressionLevel compression_level = {}; - double epsilon = {}; - - SUBCASE("[Modules][DataBuffer] Compression level 3") { - compression_level = DataBuffer::COMPRESSION_LEVEL_3; - epsilon = 0.033335; - } - - SUBCASE("[Modules][DataBuffer] Compression level 2") { - compression_level = DataBuffer::COMPRESSION_LEVEL_2; - epsilon = 0.007935; - } - - SUBCASE("[Modules][DataBuffer] Compression level 1") { - compression_level = DataBuffer::COMPRESSION_LEVEL_1; - epsilon = 0.00196; - } - - SUBCASE("[Modules][DataBuffer] Compression level 0") { - compression_level = DataBuffer::COMPRESSION_LEVEL_0; - epsilon = 0.00049; - } - - DataBuffer buffer; - const Vector values = real_values(compression_level); - for (int i = 0; i < values.size(); ++i) { - const double value = values[i]; - if (value < 0) { - // Skip negative values - continue; - } - double value_integral; - const double value_unit = modf(values[i], &value_integral); - buffer.begin_write(0); - CHECK_MESSAGE(buffer.add_positive_unit_real(value_unit, compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should return the same value"); - - buffer.begin_read(); - CHECK_MESSAGE(buffer.read_positive_unit_real(compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should read the same value"); - } -} - -TEST_CASE("[Modules][DataBuffer] Unit real") { - DataBuffer::CompressionLevel compression_level = {}; - double epsilon = {}; - - SUBCASE("[Modules][DataBuffer] Compression level 3") { - compression_level = DataBuffer::COMPRESSION_LEVEL_3; - epsilon = 0.033335; - } - - SUBCASE("[Modules][DataBuffer] Compression level 2") { - compression_level = DataBuffer::COMPRESSION_LEVEL_2; - epsilon = 0.007935; - } - - SUBCASE("[Modules][DataBuffer] Compression level 1") { - compression_level = DataBuffer::COMPRESSION_LEVEL_1; - epsilon = 0.00196; - } - - SUBCASE("[Modules][DataBuffer] Compression level 0") { - compression_level = DataBuffer::COMPRESSION_LEVEL_0; - epsilon = 0.00049; - } - - DataBuffer buffer; - const Vector values = real_values(compression_level); - for (int i = 0; i < values.size(); ++i) { - double value_integral; - const double value_unit = modf(values[i], &value_integral); - buffer.begin_write(0); - CHECK_MESSAGE(buffer.add_unit_real(value_unit, compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should return the same value"); - - buffer.begin_read(); - CHECK_MESSAGE(buffer.read_unit_real(compression_level) == doctest::Approx(value_unit).epsilon(epsilon), "Should read the same value"); - } -} - -TEST_CASE("[Modules][DataBuffer] Vector2") { - DataBuffer::CompressionLevel compression_level = {}; - - SUBCASE("[Modules][DataBuffer] Compression level 3") { - compression_level = DataBuffer::COMPRESSION_LEVEL_3; - } - - SUBCASE("[Modules][DataBuffer] Compression level 2") { - compression_level = DataBuffer::COMPRESSION_LEVEL_2; - } - - SUBCASE("[Modules][DataBuffer] Compression level 1") { - compression_level = DataBuffer::COMPRESSION_LEVEL_1; - } - - SUBCASE("[Modules][DataBuffer] Compression level 0") { - compression_level = DataBuffer::COMPRESSION_LEVEL_0; - } - - DataBuffer buffer; - const double epsilon = Math::pow(2.0, DataBuffer::get_mantissa_bits(compression_level) - 1); - const Vector values = real_values(compression_level); - for (int i = 0; i < values.size(); ++i) { -#ifdef REAL_T_IS_DOUBLE - const Vector2 value = Vector2(values[i], values[i]); -#else - const real_t clamped_value = CLAMP(values[i], -FLT_MIN, FLT_MAX); - const Vector2 value = Vector2(clamped_value, clamped_value); -#endif - buffer.begin_write(0); - const Vector2 added_value = buffer.add_vector2(value, compression_level); - CHECK_MESSAGE(added_value.x == doctest::Approx(value.x).epsilon(epsilon), "Added Vector2 should have the same x axis"); - CHECK_MESSAGE(added_value.y == doctest::Approx(value.y).epsilon(epsilon), "Added Vector2 should have the same y axis"); - - buffer.begin_read(); - const Vector2 read_value = buffer.read_vector2(compression_level); - CHECK_MESSAGE(read_value.x == doctest::Approx(value.x).epsilon(epsilon), "Read Vector2 should have the same x axis"); - CHECK_MESSAGE(read_value.y == doctest::Approx(value.y).epsilon(epsilon), "Read Vector2 should have the same y axis"); - } -} - -TEST_CASE("[Modules][DataBuffer] Vector3") { - DataBuffer::CompressionLevel compression_level = {}; - - SUBCASE("[Modules][DataBuffer] Compression level 3") { - compression_level = DataBuffer::COMPRESSION_LEVEL_3; - } - - SUBCASE("[Modules][DataBuffer] Compression level 2") { - compression_level = DataBuffer::COMPRESSION_LEVEL_2; - } - - SUBCASE("[Modules][DataBuffer] Compression level 1") { - compression_level = DataBuffer::COMPRESSION_LEVEL_1; - } - - SUBCASE("[Modules][DataBuffer] Compression level 0") { - compression_level = DataBuffer::COMPRESSION_LEVEL_0; - } - - DataBuffer buffer; - const Vector values = real_values(compression_level); - const double epsilon = Math::pow(2.0, DataBuffer::get_mantissa_bits(compression_level) - 1); - for (int i = 0; i < values.size(); ++i) { -#ifdef REAL_T_IS_DOUBLE - const Vector3 value = Vector3(values[i], values[i], values[i]); -#else - const real_t clamped_value = CLAMP(values[i], -FLT_MIN, FLT_MAX); - const Vector3 value = Vector3(clamped_value, clamped_value, clamped_value); -#endif - buffer.begin_write(0); - const Vector3 added_value = buffer.add_vector3(value, compression_level); - CHECK_MESSAGE(added_value.x == doctest::Approx(value.x).epsilon(epsilon), "Added Vector3 should have the same x axis"); - CHECK_MESSAGE(added_value.y == doctest::Approx(value.y).epsilon(epsilon), "Added Vector3 should have the same y axis"); - CHECK_MESSAGE(added_value.z == doctest::Approx(value.z).epsilon(epsilon), "Added Vector3 should have the same z axis"); - - buffer.begin_read(); - const Vector3 read_value = buffer.read_vector3(compression_level); - CHECK_MESSAGE(read_value.x == doctest::Approx(value.x).epsilon(epsilon), "Read Vector3 should have the same x axis"); - CHECK_MESSAGE(read_value.y == doctest::Approx(value.y).epsilon(epsilon), "Read Vector3 should have the same y axis"); - CHECK_MESSAGE(read_value.z == doctest::Approx(value.z).epsilon(epsilon), "Read Vector3 should have the same z axis"); - } -} - -TEST_CASE("[Modules][DataBuffer] Normalized Vector3") { - DataBuffer::CompressionLevel compression_level = {}; - double epsilon = {}; - - SUBCASE("[Modules][DataBuffer] Compression level 3") { - compression_level = DataBuffer::COMPRESSION_LEVEL_3; - epsilon = 0.033335; - } - - SUBCASE("[Modules][DataBuffer] Compression level 2") { - compression_level = DataBuffer::COMPRESSION_LEVEL_2; - epsilon = 0.007935; - } - - SUBCASE("[Modules][DataBuffer] Compression level 1") { - compression_level = DataBuffer::COMPRESSION_LEVEL_1; - epsilon = 0.00196; - } - - SUBCASE("[Modules][DataBuffer] Compression level 0") { - compression_level = DataBuffer::COMPRESSION_LEVEL_0; - epsilon = 0.00049; - } - - DataBuffer buffer; - const Vector values = real_values(compression_level); - for (int i = 0; i < values.size(); ++i) { - Vector3 value = Vector3(values[i], values[i], values[i]).normalized(); - if (!value.is_normalized()) { - // Normalization fails for some numbers, probably a bug! - continue; - } - buffer.begin_write(0); - const Vector3 added_value = buffer.add_normalized_vector3(value, compression_level); - CHECK_MESSAGE(added_value.x == doctest::Approx(value.x).epsilon(epsilon), "Added Vector3 should have the same x axis"); - CHECK_MESSAGE(added_value.y == doctest::Approx(value.y).epsilon(epsilon), "Added Vector3 should have the same y axis"); - CHECK_MESSAGE(added_value.z == doctest::Approx(value.z).epsilon(epsilon), "Added Vector3 should have the same z axis"); - - buffer.begin_read(); - const Vector3 read_value = buffer.read_normalized_vector3(compression_level); - CHECK_MESSAGE(read_value.x == doctest::Approx(value.x).epsilon(epsilon), "Read Vector3 should have the same x axis"); - CHECK_MESSAGE(read_value.y == doctest::Approx(value.y).epsilon(epsilon), "Read Vector3 should have the same y axis"); - CHECK_MESSAGE(read_value.z == doctest::Approx(value.z).epsilon(epsilon), "Read Vector3 should have the same z axis"); - } -} - -TEST_CASE("[Modules][DataBuffer] Variant") { - Variant value = {}; - - SUBCASE("[Modules][DataBuffer] Invalid value") { - value = {}; - } - SUBCASE("[Modules][DataBuffer] String") { - value = "VariantString"; - } - SUBCASE("[Modules][DataBuffer] Vector") { - value = sarray("VariantString1", "VariantString2", "VariantString3"); - } - SUBCASE("[Modules][DataBuffer] Dictionary") { - Dictionary dictionary; - dictionary[1] = "Value"; - dictionary["Key"] = -1; - value = dictionary; - } - SUBCASE("[Modules][DataBuffer] Array") { - Array array; - array.append("VariantString"); - array.append(0); - array.append(-1.2); - value = array; - } - - DataBuffer buffer; - buffer.begin_write(0); - CHECK_MESSAGE(SceneSynchronizer::compare(buffer.add_variant(value), value, DBL_EPSILON), "Should return the same value"); - buffer.begin_read(); - CHECK_MESSAGE(SceneSynchronizer::compare(buffer.read_variant(), value, DBL_EPSILON), "Should read the same value"); -} - -TEST_CASE("[Modules][DataBuffer] Seek") { - DataBuffer buffer; - buffer.begin_write(0); - buffer.add_bool(true); - buffer.add_bool(false); - buffer.begin_read(); - - ERR_PRINT_OFF - buffer.seek(-1); - CHECK_MESSAGE(buffer.get_bit_offset() == 0, "Bit offset should fail for negative values"); - ERR_PRINT_ON - - buffer.seek(1); - CHECK_MESSAGE(buffer.get_bit_offset() == 1, "Bit offset should be 1 after seek to 1"); - CHECK_MESSAGE(buffer.read_bool() == false, "Should read false at position 1"); - - buffer.seek(0); - CHECK_MESSAGE(buffer.get_bit_offset() == 0, "Bit offset should be 0 after seek to 0"); - CHECK_MESSAGE(buffer.read_bool() == true, "Should read true at position 0"); -} - -TEST_CASE("[Modules][DataBuffer] Metadata") { - bool value = {}; - bool metadata = {}; - - SUBCASE("[Modules][DataBuffer] True") { - metadata = true; - value = false; - } - - SUBCASE("[Modules][DataBuffer] False") { - metadata = false; - value = true; - } - - const int metadata_size = DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0); - DataBuffer buffer; - buffer.begin_write(metadata_size); - buffer.add_bool(metadata); - buffer.add_bool(value); - buffer.begin_read(); - CHECK_MESSAGE(buffer.read_bool() == metadata, "Should return correct metadata"); - CHECK_MESSAGE(buffer.read_bool() == value, "Should return correct value after metadata"); - CHECK_MESSAGE(buffer.get_metadata_size() == metadata_size, "Metadata size should be equal to expected"); - CHECK_MESSAGE(buffer.size() == DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0), "Size should be equal to expected"); - CHECK_MESSAGE(buffer.total_size() == DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0) + metadata_size, "Total size should be equal to expected"); -} - -TEST_CASE("[Modules][DataBuffer] Zero") { - constexpr DataBuffer::CompressionLevel compression = DataBuffer::COMPRESSION_LEVEL_0; - DataBuffer buffer; - buffer.begin_write(0); - buffer.add_int(-1, compression); - buffer.zero(); - buffer.begin_read(); - CHECK_MESSAGE(buffer.read_int(compression) == 0, "Should return 0"); -} - -TEST_CASE("[Modules][DataBuffer] Shrinking") { - DataBuffer buffer; - buffer.begin_write(0); - for (int i = 0; i < 2; ++i) { - buffer.add_real(3.14, DataBuffer::COMPRESSION_LEVEL_0); - } - const int original_size = buffer.total_size(); - - ERR_PRINT_OFF; - buffer.shrink_to(0, original_size + 1); - ERR_PRINT_ON; - CHECK_MESSAGE(buffer.total_size() == original_size, "Shrinking to a larger size should fail."); - - ERR_PRINT_OFF; - buffer.shrink_to(0, -1); - ERR_PRINT_ON; - CHECK_MESSAGE(buffer.total_size() == original_size, "Shrinking with a negative bits size should fail."); - - buffer.shrink_to(0, original_size - 8); - CHECK_MESSAGE(buffer.total_size() == original_size - 8, "Shrinking by 1 byte should succeed."); - CHECK_MESSAGE(buffer.get_buffer().size_in_bits() == original_size, "Buffer size after shrinking by 1 byte should be the same."); - - buffer.dry(); - CHECK_MESSAGE(buffer.get_buffer().size_in_bits() == original_size - 8, "Buffer size after dry should changed to the smallest posiible."); -} - -TEST_CASE("[Modules][DataBuffer] Skip") { - const bool value = true; - - DataBuffer buffer; - buffer.add_bool(!value); - buffer.add_bool(value); - - buffer.begin_read(); - buffer.seek(DataBuffer::get_bit_taken(DataBuffer::DATA_TYPE_BOOL, DataBuffer::COMPRESSION_LEVEL_0)); - CHECK_MESSAGE(buffer.read_bool() == value, "Should read the same value"); -} -} // namespace TestDataBuffer - -#endif // TEST_DATA_BUFFER_H diff --git a/modules/network_synchronizer/tests/test_interpolator.h b/modules/network_synchronizer/tests/test_interpolator.h deleted file mode 100644 index afea8af0f..000000000 --- a/modules/network_synchronizer/tests/test_interpolator.h +++ /dev/null @@ -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 -T generate_value(real_t value) { - if constexpr (std::is_same_v || std::is_same_v) { - return T(value, value); - } else if constexpr (std::is_same_v || std::is_same_v) { - return T(value, value, value); - } else { - return static_cast(value); - } -} - -// TODO: Add other types -TEST_CASE_TEMPLATE("[Modules][Interpolator] Interpolation", T, int, real_t, Vector2, Vector2i, Vector3) { - LocalVector fractions; - fractions.reserve(7); - fractions.push_back(0.0); - fractions.push_back(1.0); - fractions.push_back(0.5); - fractions.push_back(0.001); - fractions.push_back(0.999); - fractions.push_back(0.25); - fractions.push_back(0.75); - - RBMap 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::Element *E = values.front(); E; E = E->next()) { - for (uint32_t j = 0; j < fractions.size(); ++j) { - // Skip custom interpolator for now - for (int k = Interpolator::FALLBACK_INTERPOLATE; k < Interpolator::FALLBACK_CUSTOM_INTERPOLATOR; ++k) { - const T first_value = generate_value(E->key()); - const T second_value = generate_value(E->value()); - - interpolator.reset(); - const int variable_id = interpolator.register_variable(T(), static_cast(k)); - interpolator.terminate_init(); - interpolator.begin_write(0); - interpolator.epoch_insert(variable_id, first_value); - interpolator.end_write(); - - interpolator.begin_write(1); - interpolator.epoch_insert(variable_id, second_value); - interpolator.end_write(); - - CAPTURE(k); - CAPTURE(fractions[j]); - CAPTURE(first_value); - CAPTURE(second_value); - const T result = interpolator.pop_epoch(0, fractions[j])[0]; - switch (k) { - case Interpolator::FALLBACK_INTERPOLATE: { - CHECK(result == Interpolator::interpolate(first_value, second_value, fractions[j]).operator T()); - } break; - case Interpolator::FALLBACK_DEFAULT: { - if (fractions[j] == 0.0) { - CHECK(result == first_value); - } else if (fractions[j] == 1.0) { - CHECK(result == second_value); - } else { - CHECK(result == T()); - } - } break; - case Interpolator::FALLBACK_OLD_OR_NEAREST: { - if (fractions[j] == 0.0) { - CHECK(result == first_value); - } else if (fractions[j] == 1.0) { - CHECK(result == second_value); - } else { - CHECK(result == first_value); - } - } break; - case Interpolator::FALLBACK_NEW_OR_NEAREST: { - if (fractions[j] == 0.0) { - CHECK(result == first_value); - } else if (fractions[j] == 1.0) { - CHECK(result == second_value); - } else { - CHECK(result == second_value); - } - } break; - } - } - } - } -} -} // namespace TestInterpolator - -#endif // TEST_INTERPOLATOR_H