Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions modules/multiplayer/doc_classes/SceneReplicationConfig.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
Finds the index of the given [param path].
</description>
</method>
<method name="property_get_precision">
<return type="int" enum="SceneReplicationConfig.ReplicationPrecision" />
<param index="0" name="path" type="NodePath" />
<description>
Returns the wire precision used for the property identified by the given [param path]. See [enum ReplicationPrecision].
</description>
</method>
<method name="property_get_replication_mode">
<return type="int" enum="SceneReplicationConfig.ReplicationMode" />
<param index="0" name="path" type="NodePath" />
Expand Down Expand Up @@ -65,6 +72,14 @@
Returns [code]true[/code] if the property identified by the given [param path] is configured to be reliably synchronized when changes are detected on process.
</description>
</method>
<method name="property_set_precision">
<return type="void" />
<param index="0" name="path" type="NodePath" />
<param index="1" name="precision" type="int" enum="SceneReplicationConfig.ReplicationPrecision" />
<description>
Sets the wire precision used for the property identified by the given [param path]. See [enum ReplicationPrecision]. Both peers must use the same configuration.
</description>
</method>
<method name="property_set_replication_mode">
<return type="void" />
<param index="0" name="path" type="NodePath" />
Expand Down Expand Up @@ -115,5 +130,11 @@
<constant name="REPLICATION_MODE_ON_CHANGE" value="2" enum="ReplicationMode">
Replicate the given property on process by sending updates using reliable transfer mode when its value changes.
</constant>
<constant name="PRECISION_FULL" value="0" enum="ReplicationPrecision">
Replicate the given property at full precision using the standard variant encoding. This is the default.
</constant>
<constant name="PRECISION_HALF" value="1" enum="ReplicationPrecision">
Replicate the given property using half-precision (16-bit) floats to save bandwidth. Applies to [float], [Vector2], [Vector3], [Vector4], [Quaternion] and [Color]; other types fall back to full precision. Reduces range and precision, so it is best suited to values that tolerate small errors (positions on modest maps, velocities, rotations, colors). Both peers must use the same configuration.
</constant>
</constants>
</class>
33 changes: 27 additions & 6 deletions modules/multiplayer/editor/replication_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ ReplicationEditor::ReplicationEditor() {

tree = memnew(Tree);
tree->set_hide_root(true);
tree->set_columns(4);
tree->set_columns(5);
tree->set_column_titles_visible(true);
tree->set_column_title(0, TTR("Properties"));
tree->set_column_expand(0, true);
Expand All @@ -275,7 +275,10 @@ ReplicationEditor::ReplicationEditor() {
tree->set_column_title(2, TTR("Replicate"));
tree->set_column_custom_minimum_width(2, 100);
tree->set_column_expand(2, false);
tree->set_column_title(3, TTR("Precision"));
tree->set_column_custom_minimum_width(3, 100);
tree->set_column_expand(3, false);
tree->set_column_expand(4, false);
tree->create_item();
tree->connect("button_clicked", callable_mp(this, &ReplicationEditor::_tree_button_pressed));
tree->connect("item_edited", callable_mp(this, &ReplicationEditor::_tree_item_edited));
Expand Down Expand Up @@ -410,7 +413,7 @@ void ReplicationEditor::_tree_item_edited() {
return;
}
int column = tree->get_edited_column();
ERR_FAIL_COND(column < 1 || column > 2);
ERR_FAIL_COND(column < 1 || column > 3);
const NodePath prop = ti->get_metadata(0);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();

Expand All @@ -437,6 +440,15 @@ void ReplicationEditor::_tree_item_edited() {
undo_redo->add_do_method(this, "_update_value", prop, column, value);
undo_redo->add_undo_method(this, "_update_value", prop, column, old_value);
undo_redo->commit_action();
} else if (column == 3) {
undo_redo->create_action(TTR("Set property precision"));
int value = ti->get_range(column);
int old_value = config->property_get_precision(prop);
undo_redo->add_do_method(config.ptr(), "property_set_precision", prop, value);
undo_redo->add_undo_method(config.ptr(), "property_set_precision", prop, old_value);
undo_redo->add_do_method(this, "_update_value", prop, column, value);
undo_redo->add_undo_method(this, "_update_value", prop, column, old_value);
undo_redo->commit_action();
} else {
ERR_FAIL();
}
Expand Down Expand Up @@ -465,12 +477,14 @@ void ReplicationEditor::_dialog_closed(bool p_confirmed) {
int idx = config->property_get_index(prop);
bool spawn = config->property_get_spawn(prop);
SceneReplicationConfig::ReplicationMode mode = config->property_get_replication_mode(prop);
SceneReplicationConfig::ReplicationPrecision precision = config->property_get_precision(prop);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Remove Property"));
undo_redo->add_do_method(config.ptr(), "remove_property", prop);
undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx);
undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, spawn);
undo_redo->add_undo_method(config.ptr(), "property_set_replication_mode", prop, mode);
undo_redo->add_undo_method(config.ptr(), "property_set_precision", prop, precision);
undo_redo->add_do_method(this, "_update_config");
undo_redo->add_undo_method(this, "_update_config");
undo_redo->commit_action();
Expand All @@ -487,7 +501,7 @@ void ReplicationEditor::_update_value(const NodePath &p_prop, int p_column, int
if (ti->get_metadata(0).operator NodePath() == p_prop) {
if (p_column == 1) {
ti->set_checked(p_column, p_value != 0);
} else if (p_column == 2) {
} else if (p_column == 2 || p_column == 3) {
ti->set_range(p_column, p_value);
}
return;
Expand All @@ -510,7 +524,7 @@ void ReplicationEditor::_update_config() {
}
for (int i = 0; i < props.size(); i++) {
const NodePath path = props[i];
_add_property(path, config->property_get_spawn(path), config->property_get_replication_mode(path));
_add_property(path, config->property_get_spawn(path), config->property_get_replication_mode(path), config->property_get_precision(path));
}
}

Expand Down Expand Up @@ -552,13 +566,14 @@ static bool can_sync(const Variant &p_var) {
}
}

void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, SceneReplicationConfig::ReplicationMode p_mode) {
void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, SceneReplicationConfig::ReplicationMode p_mode, SceneReplicationConfig::ReplicationPrecision p_precision) {
String prop = String(p_property);
TreeItem *item = tree->create_item();
item->set_selectable(0, false);
item->set_selectable(1, false);
item->set_selectable(2, false);
item->set_selectable(3, false);
item->set_selectable(4, false);
item->set_text(0, prop);
item->set_metadata(0, prop);
Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr;
Expand All @@ -583,7 +598,7 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
} else {
item->set_icon(0, icon);
}
item->add_button(3, get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
item->add_button(4, get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
item->set_text_alignment(1, HORIZONTAL_ALIGNMENT_CENTER);
item->set_cell_mode(1, TreeItem::CELL_MODE_CHECK);
item->set_checked(1, p_spawn);
Expand All @@ -594,4 +609,10 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
item->set_text(2, TTR("Never", "Replication Mode") + "," + TTR("Always", "Replication Mode") + "," + TTR("On Change", "Replication Mode"));
item->set_range(2, (int)p_mode);
item->set_editable(2, true);
item->set_text_alignment(3, HORIZONTAL_ALIGNMENT_CENTER);
item->set_cell_mode(3, TreeItem::CELL_MODE_RANGE);
item->set_range_config(3, 0, 1, 1);
item->set_text(3, TTR("Full", "Replication Precision") + "," + TTR("Half", "Replication Precision"));
item->set_range(3, (int)p_precision);
item->set_editable(3, true);
}
2 changes: 1 addition & 1 deletion modules/multiplayer/editor/replication_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ReplicationEditor : public VBoxContainer {
void _update_value(const NodePath &p_prop, int p_column, int p_checked);
void _update_config();
void _dialog_closed(bool p_confirmed);
void _add_property(const NodePath &p_property, bool p_spawn, SceneReplicationConfig::ReplicationMode p_mode);
void _add_property(const NodePath &p_property, bool p_spawn, SceneReplicationConfig::ReplicationMode p_mode, SceneReplicationConfig::ReplicationPrecision p_precision = SceneReplicationConfig::PRECISION_FULL);

void _pick_node_filter_text_changed(const String &p_newtext);
void _pick_node_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates);
Expand Down
143 changes: 141 additions & 2 deletions modules/multiplayer/multiplayer_synchronizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,88 @@
#include "multiplayer_synchronizer.h"

#include "core/config/engine.h"
#include "core/io/marshalls.h"
#include "core/math/math_funcs.h"
#include "scene/main/multiplayer_api.h"

static int _half_float_component_count(Variant::Type p_type) {
switch (p_type) {
case Variant::FLOAT:
return 1;
case Variant::VECTOR2:
return 2;
case Variant::VECTOR3:
return 3;
case Variant::VECTOR4:
case Variant::QUATERNION:
case Variant::COLOR:
return 4;
default:
return 0;
}
}

static void _variant_to_floats(const Variant &p_v, Variant::Type p_type, float *r_components) {
switch (p_type) {
case Variant::FLOAT:
r_components[0] = (float)(double)p_v;
break;
case Variant::VECTOR2: {
Vector2 v = p_v;
r_components[0] = v.x;
r_components[1] = v.y;
} break;
case Variant::VECTOR3: {
Vector3 v = p_v;
r_components[0] = v.x;
r_components[1] = v.y;
r_components[2] = v.z;
} break;
case Variant::VECTOR4: {
Vector4 v = p_v;
r_components[0] = v.x;
r_components[1] = v.y;
r_components[2] = v.z;
r_components[3] = v.w;
} break;
case Variant::QUATERNION: {
Quaternion q = p_v;
r_components[0] = q.x;
r_components[1] = q.y;
r_components[2] = q.z;
r_components[3] = q.w;
} break;
case Variant::COLOR: {
Color c = p_v;
r_components[0] = c.r;
r_components[1] = c.g;
r_components[2] = c.b;
r_components[3] = c.a;
} break;
default:
break;
}
}

static Variant _floats_to_variant(Variant::Type p_type, const float *p_components) {
switch (p_type) {
case Variant::FLOAT:
return (double)p_components[0];
case Variant::VECTOR2:
return Vector2(p_components[0], p_components[1]);
case Variant::VECTOR3:
return Vector3(p_components[0], p_components[1], p_components[2]);
case Variant::VECTOR4:
return Vector4(p_components[0], p_components[1], p_components[2], p_components[3]);
case Variant::QUATERNION:
return Quaternion(p_components[0], p_components[1], p_components[2], p_components[3]);
case Variant::COLOR:
return Color(p_components[0], p_components[1], p_components[2], p_components[3]);
default:
return Variant();
}
}

Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath &p_path) {
if (p_path.get_name_count() == 0) {
return p_obj;
Expand Down Expand Up @@ -190,6 +270,65 @@ Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Obj
return OK;
}

Error MultiplayerSynchronizer::encode_state_quantized(const Variant **p_variants, const int *p_precisions, int p_count, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding) {
r_len = 0;
for (int i = 0; i < p_count; i++) {
const Variant &v = *p_variants[i];
if (p_precisions[i] == SceneReplicationConfig::PRECISION_HALF) {
const Variant::Type type = v.get_type();
const int nc = _half_float_component_count(type);
if (nc > 0) {
if (p_buffer) {
p_buffer[r_len] = (uint8_t)type;
float components[4];
_variant_to_floats(v, type, components);
for (int c = 0; c < nc; c++) {
float f = components[c];
if (Math::is_finite(f)) {
f = CLAMP(f, -65504.0f, 65504.0f);
}
encode_uint16(Math::make_half_float(f), &p_buffer[r_len + 1 + c * 2]);
}
}
r_len += 1 + nc * 2;
continue;
}
}
int size = 0;
Error err = MultiplayerAPI::encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size, p_allow_object_decoding);
ERR_FAIL_COND_V(err != OK, err);
r_len += size;
}
return OK;
}

Error MultiplayerSynchronizer::decode_state_quantized(Vector<Variant> &r_variants, const int *p_precisions, const uint8_t *p_buffer, int p_len, int &r_len, bool p_allow_object_decoding) {
r_len = 0;
const int count = r_variants.size();
for (int i = 0; i < count; i++) {
ERR_FAIL_COND_V(r_len >= p_len, ERR_INVALID_DATA);
if (p_precisions[i] == SceneReplicationConfig::PRECISION_HALF) {
const Variant::Type type = (Variant::Type)(p_buffer[r_len] & 0x3F);
const int nc = _half_float_component_count(type);
if (nc > 0) {
ERR_FAIL_COND_V(r_len + 1 + nc * 2 > p_len, ERR_INVALID_DATA);
float components[4] = { 0, 0, 0, 0 };
for (int c = 0; c < nc; c++) {
components[c] = Math::half_to_float(decode_uint16(&p_buffer[r_len + 1 + c * 2]));
}
r_variants.write[i] = _floats_to_variant(type, components);
r_len += 1 + nc * 2;
continue;
}
}
int vlen = 0;
Error err = MultiplayerAPI::decode_and_decompress_variant(r_variants.write[i], &p_buffer[r_len], p_len - r_len, &vlen, p_allow_object_decoding);
ERR_FAIL_COND_V(err != OK, err);
r_len += vlen;
}
return OK;
}

bool MultiplayerSynchronizer::is_visibility_public() const {
return peer_visibility.has(0);
}
Expand Down Expand Up @@ -380,7 +519,7 @@ void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_re

Error MultiplayerSynchronizer::_watch_changes(uint64_t p_usec) {
ERR_FAIL_COND_V(replication_config.is_null(), FAILED);
const List<NodePath> props = replication_config->get_watch_properties();
const List<NodePath> &props = replication_config->get_watch_properties();
if (props.size() != watchers.size()) {
watchers.resize(props.size());
}
Expand Down Expand Up @@ -444,7 +583,7 @@ List<Variant> MultiplayerSynchronizer::get_delta_state(uint64_t p_cur_usec, uint
List<NodePath> MultiplayerSynchronizer::get_delta_properties(uint64_t p_indexes) {
List<NodePath> out;
ERR_FAIL_COND_V(replication_config.is_null(), out);
const List<NodePath> watch_props = replication_config->get_watch_properties();
const List<NodePath> &watch_props = replication_config->get_watch_properties();
int idx = 0;
for (const NodePath &prop : watch_props) {
if ((p_indexes & (1ULL << idx++)) == 0) {
Expand Down
3 changes: 3 additions & 0 deletions modules/multiplayer/multiplayer_synchronizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class MultiplayerSynchronizer : public Node {
static Error get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs);
static Error set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state);

static Error encode_state_quantized(const Variant **p_variants, const int *p_precisions, int p_count, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding);
static Error decode_state_quantized(Vector<Variant> &r_variants, const int *p_precisions, const uint8_t *p_buffer, int p_len, int &r_len, bool p_allow_object_decoding);

void reset();
Node *get_root_node();

Expand Down
Loading
Loading