Skip to content

Commit 4aefada

Browse files
committed
Finalized the new lag compensation. It works much better than the previous version.
1 parent 249e157 commit 4aefada

5 files changed

Lines changed: 147 additions & 60 deletions

File tree

core/peer_networked_controller.cpp

Lines changed: 105 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,16 +1005,17 @@ FrameIndex PlayerController::get_stored_frame_index(int p_i) const {
10051005
}
10061006
}
10071007

1008-
void PlayerController::on_rewind_frame_begin(FrameIndex p_frame_index, int p_index, int p_count) {
1008+
void PlayerController::on_rewind_frame_begin(FrameIndex p_frame_index, int p_rewinding_index, int p_rewinding_frame_count) {
1009+
NS_PROFILE
10091010
if (!peer_controller->can_simulate()) {
10101011
return;
10111012
}
10121013

1013-
if (p_index >= 0 && p_index < int(frames_input.size())) {
1014-
queued_instant_to_process = p_index;
1014+
if (p_rewinding_index >= 0 && p_rewinding_index < int(frames_input.size())) {
1015+
queued_instant_to_process = p_rewinding_index;
10151016
#ifdef DEBUG_ENABLED
10161017
// IMPOSSIBLE to trigger - without bugs.
1017-
ASSERT_COND(frames_input[p_index].id == p_frame_index);
1018+
ASSERT_COND(frames_input[p_rewinding_index].id == p_frame_index);
10181019
#endif
10191020
} else {
10201021
queued_instant_to_process = -1;
@@ -1374,7 +1375,9 @@ bool is_doll_snap_A_older(const DollController::DollSnapshot &p_snap_a, const Do
13741375
return p_snap_a.data.input_id < p_snap_b.data.input_id;
13751376
}
13761377

1377-
void DollController::on_rewind_frame_begin(FrameIndex p_frame_index, int p_index, int p_count) {
1378+
void DollController::on_rewind_frame_begin(FrameIndex p_frame_index, int p_rewinding_index, int p_rewinding_frame_count) {
1379+
NS_PROFILE
1380+
13781381
if (!peer_controller->can_simulate()) {
13791382
return;
13801383
}
@@ -1383,16 +1386,9 @@ void DollController::on_rewind_frame_begin(FrameIndex p_frame_index, int p_index
13831386
return;
13841387
}
13851388

1386-
for (size_t i = 0; i < frames_input.size(); ++i) {
1387-
if (frames_input[i].id == p_frame_index) {
1388-
queued_instant_to_process = i;
1389-
return;
1390-
}
1391-
}
1392-
1393-
SceneSynchronizerDebugger::singleton()->print(WARNING, "DollController was uable to find the input: " + std::string(p_frame_index) + " maybe it was never received?", "CONTROLLER-" + std::to_string(peer_controller->authority_peer));
1394-
queued_instant_to_process = frames_input.size();
1395-
return;
1389+
// Just set the rewinding frame count, the fetch_next_input will
1390+
// validate it anyway.
1391+
queued_instant_to_process = p_rewinding_index;
13961392
}
13971393

13981394
int DollController::fetch_optimal_queued_inputs() const {
@@ -1409,12 +1405,19 @@ int DollController::fetch_optimal_queued_inputs() const {
14091405

14101406
bool DollController::fetch_next_input(double p_delta) {
14111407
if (queued_instant_to_process >= 0) {
1412-
if (queued_instant_to_process < int(frames_input.size())) {
1413-
// The SceneSync is rewinding the scene, so let's find the
1414-
set_frame_input(frames_input[queued_instant_to_process], false);
1408+
// This offset is defined by the lag compensation algorithm inside the
1409+
// `on_snapshot_applied`, and is used to compensate the lag by
1410+
// getting rid or introduce inputs, during the recdonciliation (rewinding)
1411+
// phase.
1412+
const int instant_to_process = queued_instant_to_process - queued_instant_offset;
1413+
if (instant_to_process >= 0) {
1414+
ENSURE_V_MSG(instant_to_process < int(frames_input.size()), false, "The doll lag compensation failed. The offsetted instant_to_process is never supposed to overflow the frames_input. Something didn't work inside the `on_snapshot_applied`.");
1415+
set_frame_input(frames_input[instant_to_process], false);
14151416
return true;
1417+
} else {
1418+
// The doll character is compensating for missing inputs, so return false for now.
1419+
return false;
14161420
}
1417-
return false;
14181421
}
14191422

14201423
if make_unlikely (current_input_buffer_id == FrameIndex::NONE) {
@@ -1426,7 +1429,7 @@ bool DollController::fetch_next_input(double p_delta) {
14261429
return false;
14271430
}
14281431

1429-
FrameIndex next_input_id = current_input_buffer_id + 1;
1432+
const FrameIndex next_input_id = current_input_buffer_id + 1;
14301433

14311434
// -------------------------------------------------------- Search the input
14321435
int closest_frame_index = -1;
@@ -1451,9 +1454,18 @@ bool DollController::fetch_next_input(double p_delta) {
14511454
}
14521455
}
14531456

1457+
if (!peer_controller->scene_synchronizer->get_settings().lag_compensation.doll_allow_guess_input_when_missing) {
1458+
// It was not possible to find the input, and the doll is not allowed to guess,
1459+
// so just return false.
1460+
return false;
1461+
}
1462+
14541463
if (closest_frame_index > 0) {
1455-
// It was impossible to find the input, so just pick the closest one.
1456-
set_frame_input(frames_input[closest_frame_index], false);
1464+
// It was impossible to find the input, so just pick the closest one and
1465+
// assume it's the one we are executing.
1466+
FrameInput guessed_fi = frames_input[closest_frame_index];
1467+
guessed_fi.id = next_input_id;
1468+
set_frame_input(guessed_fi, false);
14571469
return true;
14581470
} else {
14591471
// The input is not set and there is no suitable one.
@@ -1462,29 +1474,25 @@ bool DollController::fetch_next_input(double p_delta) {
14621474
}
14631475

14641476
void DollController::process(double p_delta) {
1465-
if (queued_instant_to_process >= 0 && queued_instant_to_process < int(frames_input.size())) {
1466-
// Rewinding in progress.
1467-
// On the doll the rewind's processing takes care to apply the server
1468-
// snapshot if it's found.
1469-
// This operation is done here, because the doll process on a different
1470-
// timeline than the one processed by the client.
1471-
const FrameIndex frame_index = frames_input[queued_instant_to_process].id;
1472-
// 1. Skil the first frame as there is nomthing before it.
1473-
if (frame_index > FrameIndex{ 0 }) {
1474-
// 2. Try fetching the previous server snapshot.
1475-
auto server_snap_it = VecFunc::find(server_snapshots, DollSnapshot(frame_index - 1));
1476-
if (server_snap_it != server_snapshots.end()) {
1477-
// The snapshot was found, so apply it.
1478-
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(server_snap_it->data, 0, 0, nullptr, true, true, true, true, true);
1479-
}
1480-
}
1481-
}
1482-
14831477
notify_frame_checked(peer_controller->scene_synchronizer->client_get_last_checked_frame_index());
14841478

14851479
const bool is_new_input = fetch_next_input(p_delta);
14861480

14871481
if (is_new_input) {
1482+
if (queued_instant_to_process >= 0) {
1483+
// The rewinding is in progress.
1484+
// On the doll the rewind's processing takes care to apply the server
1485+
// snapshot if it's found.
1486+
// This operation is done here, because the doll process on a different
1487+
// timeline than the one processed by the client.
1488+
// 1. Try fetching the previous server snapshot.
1489+
auto server_snap_it = VecFunc::find(server_snapshots, DollSnapshot(current_input_buffer_id - 1));
1490+
if (server_snap_it != server_snapshots.end()) {
1491+
// 2. The snapshot was found, so apply it.
1492+
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(server_snap_it->data, 0, 0, nullptr, true, true, true, true, true);
1493+
}
1494+
}
1495+
14881496
SceneSynchronizerDebugger::singleton()->print(INFO, "Doll process index: " + std::string(current_input_buffer_id), "CONTROLLER-" + std::to_string(peer_controller->authority_peer));
14891497

14901498
peer_controller->get_inputs_buffer_mut().begin_read();
@@ -1698,6 +1706,7 @@ void DollController::on_snapshot_applied(
16981706

16991707
// 2. Get the input count.
17001708
const int input_count = frames_input.size();
1709+
ENSURE_MSG(input_count > 0, "This function is not supposed to work with 0 inputs. Report this bug.")
17011710

17021711
// 3. Fetch the best input to start processing.
17031712
// TODO consider to scale this dynamically to slowly catchup with the server, as too drastic change may result in a less stable simulation.
@@ -1707,24 +1716,27 @@ void DollController::on_snapshot_applied(
17071716
// inputs so that the `input_count` equals to `optimal_queued_inputs`
17081717
// at the end of the reconcilation (rewinding) operation.
17091718

1719+
// 4. Compensate the lag.
17101720
if (input_count < optimal_input_count) {
17111721
// It has less inputs than the optimal input count defined.
1712-
// In this case the offset, mention above, is going to be positive.
1713-
// It will offset the reconciliation, by the delta difference
1714-
// `optimal_input_count - input_count`, with frames where the object are
1715-
// not procesed.
1722+
// In this case the offset, mention above, is going to be positive;
1723+
// making sure the available frames_input are used at the end of the processing
1724+
// so the `optimal_queued_inputs` is left at the endo of the rewinding.
17161725
const int missing_inputs = optimal_input_count - input_count;
1726+
1727+
queued_instant_offset = missing_inputs;
17171728
} else {
17181729
// It has more inputs than the optimal input count defined.
17191730
// In this case the offset is negative, meaning it throws away all the
17201731
// extra inputs to recatch the server.
1721-
const int extra_inputs = input_count - optimal_input_count;
17221732

1723-
current_input_buffer_id += extra_inputs;
1724-
1725-
I need to find a way to define the `current_input_buffer_id` based on the extra inputs and the available frames_input;
1733+
// The following variable is the index pointing to the frame it want to
1734+
// start processing.
1735+
const int index_to_frame_to_keep = input_count - optimal_input_count;
1736+
queued_instant_offset = -index_to_frame_to_keep;
17261737
}
17271738

1739+
// 5. Get the executed frame index on the server side.
17281740
const FrameIndex doll_frame_index =
17291741
MapFunc::at(
17301742
p_global_server_snapshot.peers_frames_index,
@@ -1741,16 +1753,53 @@ void DollController::on_snapshot_applied(
17411753
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(server_snap_it->data, 0, 0, nullptr, true, true, true, true, true);
17421754

17431755
} else {
1744-
// At this point it's necessary to mention that due to the fact the doll is
1745-
// processing on a different timeline, we may not have the server snapshot
1746-
// to reconcile the doll with the server right away.
1747-
// For this reason, this function apply the client snapshot, and leaves to
1748-
// Rewinding's process function the task to apply the server snapshot.
1756+
// At this point it's necessary to mention that the doll is procesing
1757+
// on a different timeline, compared to the one executed on the server;
1758+
// For this reason, this function applies the client snapshot, and leaves to
1759+
// Rewinding's process function the task to apply the server snapshot
1760+
// when it's the time to do it.
1761+
1762+
// 6. Fetch the best FrameInput.
1763+
FrameIndex index;
1764+
if (queued_instant_offset > 0) {
1765+
// Missing inputs, so we need to give room to the rewinding and apply
1766+
// the available frames_input at the end of the rewinding.
1767+
if make_likely (frames_input[0].id.id > std::uint32_t(queued_instant_offset)) {
1768+
// In this case the available ID is big enough and it's enough
1769+
// subtracting the missing amount to find the best index to apply.
1770+
index = index - queued_instant_offset;
1771+
} else {
1772+
// In this case the available ID is not big enough, so just assume
1773+
// we start from 0.
1774+
index = { 0 };
1775+
}
1776+
} else {
1777+
// More inputs than needed.
1778+
index = frames_input[-queued_instant_offset].id;
1779+
}
17491780

1750-
const auto client_snap_it = VecFunc::find(client_snapshots, DollSnapshot(doll_frame_index));
1751-
ENSURE_MSG(client_snap_it != client_snapshots.end(), "The doll was unable to set the snapshot because it was unable to find the client snapshot with ID: " + doll_frame_index);
1781+
if (!client_snapshots.empty()) {
1782+
// 7. Get the closest available snapshot, and apply it, no need to be
1783+
// precise here, since the process will apply the server snapshot
1784+
// when available.
1785+
int distance = std::numeric_limits<int>::max();
1786+
DollSnapshot *best = &client_snapshots.front();
1787+
for (auto &snap : client_snapshots) {
1788+
const int delta = std::abs(std::int64_t(best->doll_executed_input.id) - std::int64_t(snap.doll_executed_input.id));
1789+
if (delta < distance) {
1790+
best = &snap;
1791+
distance = delta;
1792+
} else {
1793+
// Since the snapshots are sorted, it can interrupt the
1794+
// processing right after the distance start increasing.
1795+
break;
1796+
}
1797+
}
17521798

1753-
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(client_snap_it->data, 0, 0, nullptr, true, true, true, true, true);
1799+
if (best) {
1800+
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(best->data, 0, 0, nullptr, true, true, true, true, true);
1801+
}
1802+
}
17541803
}
17551804
}
17561805

core/peer_networked_controller.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ struct PlayerController final : public Controller {
334334
FrameIndex get_stored_frame_index(int p_i) const;
335335
virtual FrameIndex get_current_frame_index() const override;
336336

337-
void on_rewind_frame_begin(FrameIndex p_input_id, int p_index, int p_count);
337+
void on_rewind_frame_begin(FrameIndex p_frame_index, int p_rewinding_index, int p_rewinding_frame_count);
338338
bool has_another_instant_to_process_after(int p_i) const;
339339
virtual void process(double p_delta) override;
340340
void on_state_validated(FrameIndex p_frame_index, bool p_detected_desync);
@@ -385,6 +385,7 @@ struct DollController final : public RemotelyControlledController {
385385

386386
FrameIndex last_checked_input = FrameIndex::NONE;
387387
FrameIndex last_doll_checked_input = FrameIndex::NONE;
388+
int queued_instant_offset = 0;
388389
int queued_instant_to_process = -1;
389390

390391
// Contains the controlled nodes frames snapshot.
@@ -398,7 +399,7 @@ struct DollController final : public RemotelyControlledController {
398399
~DollController();
399400

400401
virtual bool receive_inputs(const Vector<uint8_t> &p_data) override;
401-
void on_rewind_frame_begin(FrameIndex p_input_id, int p_index, int p_count);
402+
void on_rewind_frame_begin(FrameIndex p_frame_index, int p_rewinding_index, int p_rewinding_frame_count);
402403
int fetch_optimal_queued_inputs() const;
403404
virtual bool fetch_next_input(double p_delta) override;
404405
virtual void process(double p_delta) override;

scene_synchronizer.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ void SceneSynchronizerBase::conclude() {
165165
void SceneSynchronizerBase::process(double p_delta) {
166166
NS_PROFILE
167167

168+
if (settings_changed) {
169+
event_settings_changed.broadcast(settings);
170+
settings_changed = false;
171+
}
172+
168173
#ifdef DEBUG_ENABLED
169174
ASSERT_COND_MSG(synchronizer, "Never execute this function unless this synchronizer is ready.");
170175

@@ -307,6 +312,20 @@ bool SceneSynchronizerBase::is_variable_registered(ObjectLocalId p_id, const std
307312
return false;
308313
}
309314

315+
void SceneSynchronizerBase::set_settings(Settings &p_settings) {
316+
settings = p_settings;
317+
settings_changed = true;
318+
}
319+
320+
Settings &SceneSynchronizerBase::get_settings_mutable() {
321+
settings_changed = true;
322+
return settings;
323+
}
324+
325+
const Settings &SceneSynchronizerBase::get_settings() const {
326+
return settings;
327+
}
328+
310329
void SceneSynchronizerBase::register_app_object(ObjectHandle p_app_object_handle, ObjectLocalId *out_id) {
311330
ENSURE(p_app_object_handle != ObjectHandle::NONE);
312331

scene_synchronizer.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ class SynchronizerManager {
4545
virtual bool get_variable(ObjectHandle p_app_object_handle, const char *p_var_name, VarData &p_val) const = 0;
4646
};
4747

48+
struct LagCompensationSettings {
49+
/// If true, the dolls (The objects controlled by other player) will guess
50+
/// the input if the input is missing.
51+
bool doll_allow_guess_input_when_missing = true;
52+
};
53+
54+
struct Settings {
55+
LagCompensationSettings lag_compensation;
56+
};
57+
4858
/// # SceneSynchronizer
4959
///
5060
/// NOTICE: Do not instantiate this class directly, please use `SceneSynchronizer<>` instead.
@@ -191,6 +201,9 @@ class SceneSynchronizerBase {
191201
/// Update the latency each 3 seconds.
192202
float latency_update_rate = 3.0;
193203

204+
Settings settings;
205+
bool settings_changed = true;
206+
194207
SynchronizerType synchronizer_type = SYNCHRONIZER_TYPE_NULL;
195208

196209
class Synchronizer *synchronizer = nullptr;
@@ -217,6 +230,7 @@ class SceneSynchronizerBase {
217230
public: // -------------------------------------------------------------- Events
218231
Processor<> event_sync_started;
219232
Processor<> event_sync_paused;
233+
Processor<const Settings &> event_settings_changed;
220234
Processor<int /*p_peer*/, bool /*p_connected*/, bool /*p_enabled*/> event_peer_status_updated;
221235
Processor<FrameIndex, bool /*p_desync_detected*/> event_state_validated;
222236
Processor<FrameIndex, int /*p_peer*/> event_sent_snapshot;
@@ -228,7 +242,7 @@ class SceneSynchronizerBase {
228242
Processor<const Snapshot & /*p_snapshot*/> event_snapshot_update_finished;
229243
Processor<const Snapshot & /*p_snapshot*/, int /*p_frame_count_to_rewind*/> event_snapshot_applied;
230244
Processor<const Snapshot & /*p_received_snapshot*/> event_received_server_snapshot;
231-
Processor<FrameIndex, int /*p_index*/, int /*p_count*/> event_rewind_frame_begin;
245+
Processor<FrameIndex /*p_frame_index*/, int /*p_rewinding_index*/, int /*p_rewinding_frame_count*/> event_rewind_frame_begin;
232246
Processor<FrameIndex, ObjectHandle /*p_app_object_handle*/, const std::vector<std::string> & /*p_var_names*/, const std::vector<VarData> & /*p_client_values*/, const std::vector<VarData> & /*p_server_values*/> event_desync_detected_with_info;
233247

234248
private:
@@ -338,6 +352,10 @@ class SceneSynchronizerBase {
338352
void rpc_set_server_controlled(int p_peer, bool p_server_controlled);
339353

340354
public: // ---------------------------------------------------------------- APIs
355+
void set_settings(Settings &p_settings);
356+
Settings &get_settings_mutable();
357+
const Settings &get_settings() const;
358+
341359
void register_app_object(ObjectHandle p_app_object_handle, ObjectLocalId *out_id = nullptr);
342360
void unregister_app_object(ObjectLocalId p_id);
343361
void setup_controller(

tests/test_doll_simulation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ void test_doll_simulation() {
356356
test_latency();
357357

358358
// TODO Remove this once the test are all implemented.
359-
ASSERT_COND(false);
359+
//ASSERT_COND(false);
360360
}
361361

362362
}; //namespace NS_Test

0 commit comments

Comments
 (0)