Skip to content

Commit 77aab6b

Browse files
committed
Makes the snapshot parsing on the client more reliable.
1 parent b5d8ebf commit 77aab6b

File tree

4 files changed

+118
-30
lines changed

4 files changed

+118
-30
lines changed

core/snapshot.cpp

+26-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "../scene_synchronizer.h"
44

5+
NS_NAMESPACE_BEGIN
56
NS::Snapshot::operator std::string() const {
67
std::string s;
78
s += "Snapshot input ID: " + input_id;
@@ -167,42 +168,51 @@ bool compare_procedures(
167168
return true;
168169
}
169170

170-
const std::vector<std::optional<NS::VarData>> *NS::Snapshot::get_object_vars(ObjectNetId p_id) const {
171+
const std::vector<std::optional<VarData>> *Snapshot::get_object_vars(ObjectNetId p_id) const {
171172
if (objects.size() > p_id.id) {
172173
return &objects[p_id.id].vars;
173174
}
174175
return nullptr;
175176
}
176177

177-
const std::vector<NS::ScheduledProcedureSnapshot> *NS::Snapshot::get_object_procedures(ObjectNetId p_id) const {
178+
const std::vector<ScheduledProcedureSnapshot> *Snapshot::get_object_procedures(ObjectNetId p_id) const {
178179
if (objects.size() > p_id.id) {
179180
return &objects[p_id.id].procedures;
180181
}
181182
return nullptr;
182183
}
183184

184-
NS::Snapshot NS::Snapshot::make_copy(const Snapshot &p_other) {
185+
Snapshot Snapshot::make_copy(const Snapshot &p_other) {
185186
Snapshot s;
186187
s.copy(p_other);
187188
return s;
188189
}
189190

190-
void NS::Snapshot::copy(const Snapshot &p_other) {
191+
void ObjectDataSnapshot::copy(const ObjectDataSnapshot &p_other) {
192+
vars.resize(p_other.vars.size());
193+
for (std::size_t s = 0; s < p_other.vars.size(); s++) {
194+
if (p_other.vars[s].has_value()) {
195+
vars[s].emplace(VarData::make_copy(p_other.vars[s].value()));
196+
} else {
197+
vars[s].reset();
198+
}
199+
}
200+
procedures = p_other.procedures;
201+
}
202+
203+
void ObjectDataSnapshot::clear() {
204+
vars.clear();
205+
procedures.clear();
206+
}
207+
208+
void Snapshot::copy(const Snapshot &p_other) {
191209
input_id = p_other.input_id;
192210
global_frame_index = p_other.global_frame_index;
193211
simulated_objects = p_other.simulated_objects;
194212
peers_frames_index = p_other.peers_frames_index;
195213
objects.resize(p_other.objects.size());
196214
for (std::size_t i = 0; i < p_other.objects.size(); i++) {
197-
objects[i].vars.resize(p_other.objects[i].vars.size());
198-
for (std::size_t s = 0; s < p_other.objects[i].vars.size(); s++) {
199-
if (p_other.objects[i].vars[s].has_value()) {
200-
objects[i].vars[s].emplace(VarData::make_copy(p_other.objects[i].vars[s].value()));
201-
} else {
202-
objects[i].vars[s].reset();
203-
}
204-
}
205-
objects[i].procedures = p_other.objects[i].procedures;
215+
objects[i].copy(p_other.objects[i]);
206216
}
207217
has_custom_data = p_other.has_custom_data;
208218
custom_data.copy(p_other.custom_data);
@@ -368,4 +378,6 @@ bool NS::Snapshot::compare(
368378
#else
369379
return true;
370380
#endif
371-
}
381+
}
382+
383+
NS_NAMESPACE_END

core/snapshot.h

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ struct ScheduledProcedureSnapshot {
7575
struct ObjectDataSnapshot {
7676
std::vector<std::optional<VarData>> vars;
7777
std::vector<ScheduledProcedureSnapshot> procedures;
78+
79+
void copy(const ObjectDataSnapshot &p_other);
80+
void clear();
7881
};
7982

8083
struct Snapshot {

scene_synchronizer.cpp

+68-16
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ void SceneSynchronizerBase::unregister_variable(ObjectLocalId p_id, const std::s
598598
}
599599

600600
ObjectNetId SceneSynchronizerBase::get_app_object_net_id(ObjectLocalId p_local_id) const {
601-
const ObjectData *nd = objects_data_storage.get_object_data(p_local_id);
601+
const ObjectData *nd = objects_data_storage.get_object_data(p_local_id, false);
602602
if (nd) {
603603
return nd->get_net_id();
604604
} else {
@@ -4249,6 +4249,8 @@ void ClientSynchronizer::process_simulation(float p_delta) {
42494249
bool ClientSynchronizer::parse_sync_data(
42504250
DataBuffer &p_snapshot,
42514251
void *p_user_pointer,
4252+
ClientParsingErrors &r_parsing_errors,
4253+
void (*p_notify_parsing_failed_for_object)(void *p_user_pointer, ObjectData &p_object_data),
42524254
void (*p_notify_update_mode)(void *p_user_pointer, bool p_is_partial_update),
42534255
void (*p_parse_global_frame_index)(void *p_user_pointer, GlobalFrameIndex p_global_frame_index),
42544256
void (*p_custom_data_parse)(void *p_user_pointer, VarData &&p_custom_data),
@@ -4472,13 +4474,14 @@ bool ClientSynchronizer::parse_sync_data(
44724474
// ObjectData not found, fetch it using the object name.
44734475

44744476
if (object_name.empty()) {
4475-
// The object_name was not specified by this snapshot, so fetch it.
4477+
// The object_name was not specified, fetch if using the NetId
44764478
const std::string *object_name_ptr = MapFunc::get_or_null(objects_names, net_id);
44774479

44784480
if (object_name_ptr == nullptr) {
4479-
// The name for this `NetId` doesn't exists yet.
4481+
// The name for this `NetId` doesn't exist, it was never
4482+
// delivered on this client.
44804483
scene_synchronizer->get_debugger().print(WARNING, "The object with ID `" + net_id + "` is not know by this peer yet.");
4481-
notify_server_full_snapshot_is_needed();
4484+
r_parsing_errors.missing_object_names += 1;
44824485
} else {
44834486
object_name = *object_name_ptr;
44844487
}
@@ -4540,7 +4543,6 @@ bool ClientSynchronizer::parse_sync_data(
45404543
#endif
45414544
if (!slicing_success || object_snapshot_buffer.get_bit_offset() != vars_size_in_bits) {
45424545
get_debugger().print(ERROR, "The received snapshot is corrupted because it was impossible to properly slice the Object info using the encoded size `" + std::to_string(vars_size_in_bits) + "`. This should never happen.");
4543-
notify_server_full_snapshot_is_needed();
45444546
return false;
45454547
}
45464548
// Store the extracted object info.
@@ -4554,21 +4556,28 @@ bool ClientSynchronizer::parse_sync_data(
45544556
}
45554557

45564558
// Skip the object data now.
4557-
p_snapshot.seek(p_snapshot.get_bit_offset() + vars_size_in_bits);
4559+
p_snapshot.seek(offset_after_vars_reading);
45584560
} else {
4559-
if (!parse_sync_data_object_info(
4561+
const bool object_data_parsing_state = parse_sync_data_object_info(
45604562
p_snapshot,
45614563
p_user_pointer,
45624564
*synchronizer_object_data,
45634565
p_variable_parse,
4564-
p_scheduled_procedure_parse)) {
4565-
return false;
4566-
}
4566+
p_scheduled_procedure_parse);
4567+
45674568
if make_unlikely(scene_synchronizer->pedantic_checks) {
4569+
NS_ASSERT_COND(object_data_parsing_state);
45684570
NS_ASSERT_COND_MSG(p_snapshot.get_bit_offset() == offset_after_vars_reading, "The snapshot is corrupted because the data_object parsing failed for the object: " + synchronizer_object_data->get_object_name() + " - NetId: " + std::to_string(synchronizer_object_data->get_net_id().id));
45694571
} else {
4570-
notify_server_full_snapshot_is_needed();
4571-
NS_ENSURE_V_MSG(p_snapshot.get_bit_offset() == offset_after_vars_reading, false, "The snapshot is corrupted because the data_object parsing failed for the object: " + synchronizer_object_data->get_object_name() + " - NetId: " + std::to_string(synchronizer_object_data->get_net_id().id));
4572+
if (!object_data_parsing_state || p_snapshot.get_bit_offset() != offset_after_vars_reading) {
4573+
get_debugger().print(ERROR,
4574+
"The snapshot is corrupted because the data_object parsing failed for the object: " + synchronizer_object_data->get_object_name() + " - NetId: " + std::to_string(synchronizer_object_data->get_net_id().id) + " - Size in bits: " + std::to_string(vars_size_in_bits) + " - Expected offset: " + std::to_string(offset_after_vars_reading) + " - Current offset: " + std::to_string(p_snapshot.get_bit_offset()));
4575+
r_parsing_errors.objects += 1;
4576+
p_notify_parsing_failed_for_object(p_user_pointer, *synchronizer_object_data);
4577+
// Set the buffer cursor to the correct offset to keep
4578+
// reading the data for the other objects.
4579+
p_snapshot.seek(offset_after_vars_reading);
4580+
}
45724581
}
45734582
}
45744583
}
@@ -4841,6 +4850,7 @@ bool ClientSynchronizer::parse_snapshot(DataBuffer &p_snapshot, bool p_is_server
48414850

48424851
struct ParseData {
48434852
RollingUpdateSnapshot &snapshot;
4853+
const RollingUpdateSnapshot &last_received_snapshot;
48444854
PeerNetworkedController *player_controller;
48454855
SceneSynchronizerBase *scene_synchronizer;
48464856
ClientSynchronizer *client_synchronizer;
@@ -4849,15 +4859,36 @@ bool ClientSynchronizer::parse_snapshot(DataBuffer &p_snapshot, bool p_is_server
48494859

48504860
ParseData parse_data{
48514861
received_snapshot,
4862+
last_received_snapshot,
48524863
player_controller,
48534864
scene_synchronizer,
48544865
this,
48554866
p_is_server_snapshot
48564867
};
48574868

4869+
ClientParsingErrors parsing_errors;
4870+
48584871
const bool success = parse_sync_data(
48594872
p_snapshot,
48604873
&parse_data,
4874+
parsing_errors,
4875+
4876+
// Failed parsing for object.
4877+
[](void *p_user_pointer, ObjectData &p_object_data) {
4878+
ParseData *pd = static_cast<ParseData *>(p_user_pointer);
4879+
4880+
// Do not mark this object as updated, it has corrupted values.
4881+
VecFunc::remove_unordered(pd->snapshot.just_updated_object_vars, p_object_data.get_net_id());
4882+
4883+
// Resets the objects to the previous snapshots values.
4884+
if (uint32_t(pd->snapshot.objects.size()) > p_object_data.get_net_id().id) {
4885+
if (uint32_t(pd->last_received_snapshot.objects.size()) > p_object_data.get_net_id().id) {
4886+
pd->snapshot.objects[p_object_data.get_net_id().id].copy(pd->last_received_snapshot.objects[p_object_data.get_net_id().id]);
4887+
} else {
4888+
pd->snapshot.objects[p_object_data.get_net_id().id].clear();
4889+
}
4890+
}
4891+
},
48614892

48624893
// Notify update mode:
48634894
[](void *p_user_pointer, bool p_is_partial_update) {
@@ -4891,7 +4922,7 @@ bool ClientSynchronizer::parse_snapshot(DataBuffer &p_snapshot, bool p_is_server
48914922

48924923
pd->snapshot.just_updated_object_vars.push_back(p_object_data->get_net_id());
48934924

4894-
// Make sure this node is part of the server node too.
4925+
// make sure this node is part of the server node too.
48954926
if (uint32_t(pd->snapshot.objects.size()) <= p_object_data->get_net_id().id) {
48964927
pd->snapshot.objects.resize(p_object_data->get_net_id().id + 1);
48974928
}
@@ -4970,8 +5001,27 @@ bool ClientSynchronizer::parse_snapshot(DataBuffer &p_snapshot, bool p_is_server
49705001
pd->snapshot.is_just_updated_simulated_objects = true;
49715002
});
49725003

4973-
if (success == false) {
4974-
scene_synchronizer->get_debugger().print(ERROR, "Snapshot parsing failed.", scene_synchronizer->get_network_interface().get_owner_name());
5004+
if (!success || parsing_errors.objects > 0 || parsing_errors.missing_object_names > 0) {
5005+
snapshot_parsing_failures += 1;
5006+
if (snapshot_parsing_failures > scene_synchronizer->max_snapshot_parsing_failures || parsing_errors.missing_object_names > 0) {
5007+
// Parsing failed for way too many times OR one or more objects
5008+
// names were never delivered to this client.
5009+
// NOTE: It's unlikely that the object name is missing because the
5010+
// SceneSync ensure it never happens, since this happen
5011+
// sporadically or never it's acceptable to request a full
5012+
// snapshot and not just the name: integrating a feature to
5013+
// specifically update the name would be way too much work for
5014+
// no reason.
5015+
snapshot_parsing_failures = 0;
5016+
notify_server_full_snapshot_is_needed();
5017+
scene_synchronizer->get_debugger().print(ERROR, "Snapshot parsing failed way too many times, requesting a full snapshot.", scene_synchronizer->get_network_interface().get_owner_name());
5018+
} else {
5019+
scene_synchronizer->get_debugger().print(WARNING, "Snapshot parsing failed.", scene_synchronizer->get_network_interface().get_owner_name());
5020+
}
5021+
}
5022+
5023+
if (!success) {
5024+
// Can't store this snapshot as the failure was way too deep.
49755025
return false;
49765026
}
49775027

@@ -4983,6 +5033,8 @@ bool ClientSynchronizer::parse_snapshot(DataBuffer &p_snapshot, bool p_is_server
49835033

49845034
last_received_snapshot = std::move(received_snapshot);
49855035

5036+
snapshot_parsing_failures = 0;
5037+
49865038
// Success.
49875039
return true;
49885040
}
@@ -5303,7 +5355,7 @@ void ClientSynchronizer::apply_snapshot(
53035355
// NOTE: Since it's possible to re-register the object changing the variables
53045356
// registered dynamically, the snapshot might contain more variables
53055357
// than the new registered one. The line below address that.
5306-
const VarId::IdType vars_count = std::min(object_data_snapshot.vars.size(), object_data->vars.size());
5358+
const VarId::IdType vars_count = VarId::IdType(std::min(object_data_snapshot.vars.size(), object_data->vars.size()));
53075359
for (VarId v = VarId{ { 0 } }; v < VarId{ { vars_count } }; v += 1) {
53085360
if (!object_data_snapshot.vars[v.id].has_value()) {
53095361
// This variable was not set, skip it.

scene_synchronizer.h

+21
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ class SceneSynchronizerBase {
307307
/// found at the time of the RPC receival.
308308
int store_undelivered_rpcs = true;
309309

310+
/// The number of snapshots failures before the client requests a full
311+
/// snapshot to the server.
312+
int max_snapshot_parsing_failures = 10;
313+
310314
protected: // ----------------------------------------------------- User defined
311315
class NetworkInterface *network_interface = nullptr;
312316
SynchronizerManager *synchronizer_manager = nullptr;
@@ -530,6 +534,14 @@ class SceneSynchronizerBase {
530534
void set_latency_update_rate(float p_rate_seconds);
531535
float get_latency_update_rate() const;
532536

537+
void set_max_snapshot_parsing_failures(int p_max_snapshot_parsing_failures) {
538+
max_snapshot_parsing_failures = p_max_snapshot_parsing_failures;
539+
}
540+
541+
int get_max_snapshot_parsing_failures() const {
542+
return max_snapshot_parsing_failures;
543+
}
544+
533545
bool is_variable_registered(ObjectLocalId p_id, const std::string &p_variable) const;
534546

535547
void set_debug_rewindings_enabled(bool p_enabled);
@@ -1074,6 +1086,13 @@ class ClientSynchronizer final : public Synchronizer {
10741086
float acceleration_fps_timer = 0.0;
10751087
float pretended_delta = 1.0;
10761088

1089+
struct ClientParsingErrors {
1090+
int objects = 0;
1091+
int missing_object_names = 0;
1092+
};
1093+
1094+
int snapshot_parsing_failures = 0;
1095+
10771096
std::vector<SimulatedObjectInfo> simulated_objects;
10781097
std::vector<ObjectData *> active_objects;
10791098
PeerNetworkedController *player_controller = nullptr;
@@ -1213,6 +1232,8 @@ class ClientSynchronizer final : public Synchronizer {
12131232
bool parse_sync_data(
12141233
DataBuffer &p_snapshot,
12151234
void *p_user_pointer,
1235+
ClientParsingErrors &r_parsing_errors,
1236+
void (*p_notify_parsing_failed_for_object)(void *p_user_pointer, ObjectData &p_object_data),
12161237
void (*p_notify_update_mode)(void *p_user_pointer, bool p_is_partial_update),
12171238
void (*p_parse_global_frame_index)(void *p_user_pointer, GlobalFrameIndex p_global_frame_index),
12181239
void (*p_custom_data_parse)(void *p_user_pointer, VarData &&p_custom_data),

0 commit comments

Comments
 (0)