Skip to content

Commit 4e6dfc1

Browse files
committed
Fixed apply snapshot issue and processing freeze.
1 parent 2ffc309 commit 4e6dfc1

3 files changed

Lines changed: 224 additions & 67 deletions

File tree

core/peer_networked_controller.cpp

Lines changed: 158 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma optimize("", off) // TODO remove this, which is here just to get good debugging info.
2+
13
#include "peer_networked_controller.h"
24

35
#include "core/config/project_settings.h"
@@ -1372,7 +1374,7 @@ bool DollController::receive_inputs(const Vector<uint8_t> &p_data) {
13721374
}
13731375

13741376
bool is_doll_snap_A_older(const DollController::DollSnapshot &p_snap_a, const DollController::DollSnapshot &p_snap_b) {
1375-
return p_snap_a.data.input_id < p_snap_b.data.input_id;
1377+
return p_snap_a.doll_executed_input < p_snap_b.doll_executed_input;
13761378
}
13771379

13781380
void DollController::on_rewind_frame_begin(FrameIndex p_frame_index, int p_rewinding_index, int p_rewinding_frame_count) {
@@ -1405,6 +1407,11 @@ int DollController::fetch_optimal_queued_inputs() const {
14051407

14061408
bool DollController::fetch_next_input(double p_delta) {
14071409
if (queued_instant_to_process >= 0) {
1410+
if make_unlikely (queued_frame_index_to_process == FrameIndex::NONE) {
1411+
// This happens when the server didn't start to process this doll yet.
1412+
return false;
1413+
}
1414+
14081415
// This offset is defined by the lag compensation algorithm inside the
14091416
// `on_snapshot_applied`, and is used to compensate the lag by
14101417
// getting rid or introduce inputs, during the recdonciliation (rewinding)
@@ -1595,15 +1602,19 @@ void DollController::copy_controlled_objects_snapshot(
15951602

15961603
DollSnapshot *snap;
15971604
{
1598-
snap = find_snapshot_by_snapshot_id(r_snapshots, p_snapshot.input_id);
1599-
if (!snap) {
1605+
auto it = VecFunc::find(r_snapshots, DollSnapshot(doll_executed_input));
1606+
if (it == r_snapshots.end()) {
16001607
r_snapshots.push_back(DollSnapshot(FrameIndex::NONE));
16011608
snap = &r_snapshots.back();
1602-
snap->data.input_id = p_snapshot.input_id;
1609+
snap->doll_executed_input = doll_executed_input;
1610+
} else {
1611+
snap = &*it;
16031612
}
16041613
}
16051614

1606-
snap->doll_executed_input = doll_executed_input;
1615+
ASSERT_COND(snap->doll_executed_input == doll_executed_input);
1616+
snap->data.input_id = p_snapshot.input_id;
1617+
16071618
// Extracts the data from the snapshot.
16081619
MapFunc::assign(snap->data.peers_frames_index, peer_controller->get_authority_peer(), doll_executed_input);
16091620

@@ -1719,56 +1730,169 @@ bool DollController::__pcr__fetch_recovery_info(
17191730
void DollController::on_snapshot_applied(
17201731
const Snapshot &p_global_server_snapshot,
17211732
const int p_frame_count_to_rewind) {
1733+
#ifdef DEBUG_ENABLED
17221734
// The `DollController` is never created on the server, and the below
17231735
// assertion is always satisfied.
17241736
ASSERT_COND(peer_controller->scene_synchronizer->is_client());
1737+
ASSERT_COND(p_frame_count_to_rewind >= 0);
1738+
#endif
1739+
1740+
if make_unlikely (!server_snapshots.empty() && server_snapshots.back().doll_executed_input == FrameIndex::NONE) {
1741+
apply_snapshot_no_input_reconciliation(p_global_server_snapshot);
1742+
}
1743+
1744+
if make_likely (current_input_buffer_id != FrameIndex::NONE) {
1745+
if (p_frame_count_to_rewind == 0) {
1746+
apply_snapshot_instant_input_reconciliation(p_global_server_snapshot, p_frame_count_to_rewind);
1747+
} else {
1748+
apply_snapshot_rewinding_input_reconciliation(p_global_server_snapshot, p_frame_count_to_rewind);
1749+
}
1750+
}
1751+
}
17251752

1726-
queued_frame_index_to_process = FrameIndex{ 0 };
1753+
void DollController::apply_snapshot_no_input_reconciliation(const Snapshot &p_global_server_snapshot) {
1754+
// Apply the latest received server snapshot right away since the doll is not
1755+
// yet still processing on the server.
1756+
1757+
ASSERT_COND(server_snapshots.back().doll_executed_input == FrameIndex::NONE);
1758+
1759+
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(server_snapshots.back().data, 0, 0, nullptr, true, true, true, true, true);
1760+
last_doll_compared_input = FrameIndex::NONE;
1761+
current_input_buffer_id = FrameIndex::NONE;
1762+
queued_frame_index_to_process = FrameIndex::NONE;
1763+
}
1764+
1765+
void DollController::apply_snapshot_instant_input_reconciliation(const Snapshot &p_global_server_snapshot, const int p_frame_count_to_rewind) {
1766+
// This function assume the "frame count to rewind" is always 0.
1767+
ASSERT_COND(p_frame_count_to_rewind == 0);
17271768

17281769
const int input_count = frames_input.size();
1770+
if make_unlikely (input_count == 0) {
1771+
// When there are not inputs to process, it's much better not to apply
1772+
// any snapshot.
1773+
// The reason is that at some point it will receive inputs, and then
1774+
// this algorithm will do much better job applying the snapshot and
1775+
// avoid jittering.
1776+
// NOTE: This logic is extremly important to avoid start discarding
1777+
// the inputs even before processing them, that could happen
1778+
// when the received server snapshot is ahead the received inputs.
1779+
return;
1780+
}
17291781

1730-
// 1. Ensure the input reconciliation algorithm can process, otherwise:
1731-
if make_unlikely (input_count <= 0 || p_frame_count_to_rewind <= 0) {
1732-
// - On the server, the doll was not processed so just apply the server snapshot.
1733-
// - In case of no rewinding, it applies the most up to date server snapshot right away.
1734-
ENSURE_MSG(!server_snapshots.empty(), "This should be impossible. The doll was unable to set the server snapshot since there are no server snapshots.");
1735-
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(server_snapshots.back().data, 0, 0, nullptr, true, true, true, true, true);
1736-
last_doll_compared_input = server_snapshots.back().doll_executed_input;
1737-
// Reset the current_input_buffer_id, to make sure the next Frame processed starts from here.
1782+
// 1. Fetch the optimal queued inputs (how many inputs should be queued based
1783+
// on the current connection).
1784+
const int optimal_queued_inputs = fetch_optimal_queued_inputs();
1785+
1786+
if make_likely (frames_input.back().id.id >= std::uint32_t(optimal_queued_inputs)) {
1787+
last_doll_compared_input = frames_input.back().id - optimal_queued_inputs;
1788+
} else {
1789+
last_doll_compared_input = FrameIndex{ 0 };
1790+
}
1791+
1792+
// Search the snapshot to apply.
1793+
const DollSnapshot *snapshot_to_apply = nullptr;
1794+
for (const DollSnapshot &snapshot : server_snapshots) {
1795+
if (snapshot.doll_executed_input <= last_doll_compared_input) {
1796+
snapshot_to_apply = &snapshot;
1797+
}
1798+
}
1799+
1800+
if (snapshot_to_apply) {
1801+
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(snapshot_to_apply->data, 0, 0, nullptr, true, true, true, true, true);
1802+
// Bring everything back to this point.
1803+
last_doll_compared_input = snapshot_to_apply->doll_executed_input;
17381804
current_input_buffer_id = last_doll_compared_input;
1739-
return;
17401805
}
1806+
}
17411807

1742-
// This function handles the reconciliation mechanism.
1743-
// The reconciliation is implemented in this function because this is the
1744-
// best moment to manipulate the processing (to consume or build the input
1745-
// queue) and avoid to make it noticeable.
1808+
void DollController::apply_snapshot_rewinding_input_reconciliation(const Snapshot &p_global_server_snapshot, const int p_frame_count_to_rewind) {
1809+
// This function apply the snapshot and handles the reconciliation mechanism.
1810+
// over the rewinding.
1811+
// The input reconciliation performed during the rewinding is the best because
1812+
// allows to manipulate the timeline without causing too many rubberbanding.
17461813

1747-
// 2. Fetch the optimal queued inputs (how many inputs should be queued based
1814+
// This function assume the "frame count to rewind" is never 0.
1815+
ASSERT_COND(p_frame_count_to_rewind > 0);
1816+
1817+
// 1. Fetch the optimal queued inputs (how many inputs should be queued based
17481818
// on the current connection).
17491819
const int optimal_queued_inputs = fetch_optimal_queued_inputs();
17501820

1751-
// 3. Fetch the best input to start processing.
1752-
const int optimal_input_count = p_frame_count_to_rewind + optimal_queued_inputs;
1821+
const int input_count = frames_input.size();
1822+
const DollSnapshot *server_snapshot = nullptr;
1823+
FrameIndex new_last_doll_compared_input;
1824+
if make_likely (input_count > 0) {
1825+
// 2. Fetch the best input to start processing.
1826+
const int optimal_input_count = p_frame_count_to_rewind + optimal_queued_inputs;
1827+
1828+
// The lag compensation algorithm offsets the available
1829+
// inputs so that the `input_count` equals to `optimal_queued_inputs`
1830+
// at the end of the reconcilation (rewinding) operation.
1831+
1832+
// 3. Fetch the best FrameInput to reset.
1833+
// Doesn't matter if we have or not the frame input. If not, the doll will
1834+
// just wait idle.
1835+
if make_likely (frames_input.back().id.id >= std::uint32_t(optimal_input_count)) {
1836+
new_last_doll_compared_input = frames_input.back().id - optimal_input_count;
1837+
} else {
1838+
new_last_doll_compared_input = FrameIndex{ 0 };
1839+
}
17531840

1754-
// The lag compensation algorithm offsets the available
1755-
// inputs so that the `input_count` equals to `optimal_queued_inputs`
1756-
// at the end of the reconcilation (rewinding) operation.
1841+
// 4. Ensure there is a server snapshot at some point, in between the new
1842+
// rewinding process queue or return and wait untill there is a
1843+
// server snapshot.
1844+
bool server_snapshot_found = false;
1845+
for (auto it = server_snapshots.rbegin(); it != server_snapshots.rend(); it++) {
1846+
if (it->doll_executed_input < (new_last_doll_compared_input + optimal_input_count)) {
1847+
if make_likely (it->doll_executed_input > new_last_doll_compared_input) {
1848+
// This is the most common case: The server snapshot is in between the rewinding.
1849+
// Nothing to do here.
1850+
} else if (it->doll_executed_input == new_last_doll_compared_input) {
1851+
// In this case the rewinding is still in between the rewinding
1852+
// though as an optimization we just assign the snapshot to apply
1853+
// to avoid searching it.
1854+
server_snapshot = &*it;
1855+
} else {
1856+
// In this case the server snapshot ISN'T part of the rewinding
1857+
// so it brings the rewinding back a bit, to ensure the server
1858+
// snapshot is applied.
1859+
new_last_doll_compared_input = it->doll_executed_input;
1860+
server_snapshot = &*it;
1861+
}
1862+
server_snapshot_found = true;
1863+
break;
1864+
}
1865+
}
1866+
1867+
if (!server_snapshot_found) {
1868+
// Server snapshot not found: Set this to none to signal that this
1869+
// rewind should not be performed.
1870+
new_last_doll_compared_input = FrameIndex::NONE;
1871+
}
1872+
}
17571873

1758-
// 4. Fetch the best FrameInput to reset.
1759-
// Doesn't matter if we have or not the frame input. If not, the doll will
1760-
// just wait idle.
1761-
if make_likely (frames_input.back().id.id >= std::uint32_t(optimal_input_count)) {
1762-
last_doll_compared_input = frames_input.back().id - optimal_input_count;
1874+
if make_unlikely (input_count == 0 || new_last_doll_compared_input == FrameIndex::NONE) {
1875+
// There are no inputs and in this case this function has to avoidhawk:
1876+
// - Advance on the timeline while rewinding (so it needs to set the timeline back).
1877+
// - Try to compensate so to give the inputs enough time to arrive.
1878+
const FrameIndex frames_to_travel = { std::uint32_t(p_frame_count_to_rewind + optimal_queued_inputs) };
1879+
if make_likely (current_input_buffer_id > frames_to_travel) {
1880+
last_doll_compared_input = current_input_buffer_id - frames_to_travel;
1881+
} else {
1882+
last_doll_compared_input = FrameIndex{ 0 };
1883+
}
17631884
} else {
1764-
last_doll_compared_input = FrameIndex{ 0 };
1885+
last_doll_compared_input = new_last_doll_compared_input;
17651886
}
17661887

17671888
// 5. Fetch frame index to start processing.
17681889
queued_frame_index_to_process = last_doll_compared_input + 1;
17691890

1770-
if (!client_snapshots.empty()) {
1771-
// 6. Get the closest available snapshot, and apply it, no need to be
1891+
if make_unlikely (server_snapshot) {
1892+
// 6. Apply the server snapshot.
1893+
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(server_snapshots.back().data, 0, 0, nullptr, true, true, true, true, true);
1894+
} else if make_likely (!client_snapshots.empty()) {
1895+
// 7. Get the closest available snapshot, and apply it, no need to be
17721896
// precise here, since the process will apply the server snapshot
17731897
// when available.
17741898
int distance = std::numeric_limits<int>::max();
@@ -1786,36 +1910,9 @@ void DollController::on_snapshot_applied(
17861910
}
17871911

17881912
if (best) {
1789-
auto best_server_snap_it = NS::VecFunc::find(client_snapshots, best->doll_executed_input);
1790-
if (best_server_snap_it != client_snapshots.end()) {
1791-
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(best_server_snap_it->data, 0, 0, nullptr, true, true, true, true, true);
1792-
} else {
1793-
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(best->data, 0, 0, nullptr, true, true, true, true, true);
1794-
}
1795-
}
1796-
}
1797-
}
1798-
1799-
DollController::DollSnapshot *DollController::find_snapshot_by_snapshot_id(
1800-
std::vector<DollSnapshot> &p_snapshots,
1801-
FrameIndex p_index) const {
1802-
for (DollSnapshot &snap : p_snapshots) {
1803-
if (snap.data.input_id == p_index) {
1804-
return &snap;
1805-
}
1806-
}
1807-
return nullptr;
1808-
}
1809-
1810-
const DollController::DollSnapshot *DollController::find_snapshot_by_snapshot_id(
1811-
const std::vector<DollSnapshot> &p_snapshots,
1812-
FrameIndex p_index) const {
1813-
for (const DollSnapshot &snap : p_snapshots) {
1814-
if (snap.data.input_id == p_index) {
1815-
return &snap;
1913+
static_cast<ClientSynchronizer *>(peer_controller->scene_synchronizer->get_synchronizer_internal())->apply_snapshot(best->data, 0, 0, nullptr, true, true, true, true, true);
18161914
}
18171915
}
1818-
return nullptr;
18191916
}
18201917

18211918
NoNetController::NoNetController(PeerNetworkedController *p_peer_controller) :

core/peer_networked_controller.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,10 @@ struct DollController final : public RemotelyControlledController {
430430
#endif
431431
);
432432

433-
void on_snapshot_applied(const Snapshot &p_snapshot, const int p_frame_count_to_rewind);
434-
435-
DollSnapshot *find_snapshot_by_snapshot_id(std::vector<DollSnapshot> &p_snapshots, FrameIndex p_index) const;
436-
const DollSnapshot *find_snapshot_by_snapshot_id(const std::vector<DollSnapshot> &p_snapshots, FrameIndex p_index) const;
433+
void on_snapshot_applied(const Snapshot &p_global_server_snapshot, const int p_frame_count_to_rewind);
434+
void apply_snapshot_no_input_reconciliation(const Snapshot &p_global_server_snapshot);
435+
void apply_snapshot_instant_input_reconciliation(const Snapshot &p_global_server_snapshot, const int p_frame_count_to_rewind);
436+
void apply_snapshot_rewinding_input_reconciliation(const Snapshot &p_global_server_snapshot, const int p_frame_count_to_rewind);
437437
};
438438

439439
/// This controller is used when the game instance is not a peer of any kind.

tests/test_doll_simulation.cpp

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,8 @@ void test_simulation_with_latency() {
527527
controller_2_last_player_frame_index - latency_factor);
528528
}
529529

530+
// TODO test this again.
531+
// TODO consider to remove this or leave it.
530532
// Partially process.
531533
{
532534
test.network_properties.rtt_seconds = 0.0;
@@ -548,6 +550,9 @@ void test_simulation_with_latency() {
548550
// Ensure the doll keep going forward.
549551
ASSERT_COND(controller_1_doll_frame_index <= controller_1_doll_new_frame_index);
550552
ASSERT_COND(controller_2_doll_frame_index <= controller_2_doll_new_frame_index);
553+
554+
controller_1_doll_frame_index = controller_1_doll_new_frame_index;
555+
controller_2_doll_frame_index = controller_2_doll_new_frame_index;
551556
}
552557
}
553558

@@ -572,6 +577,58 @@ void test_simulation_with_latency() {
572577
int a = 0;
573578
}
574579

580+
void test_simulation_with_hiccups() {
581+
TestDollSimulationStorePositions test;
582+
test.frame_confirmation_timespan = 1.0 / 10.0;
583+
test.init_test();
584+
585+
// Partially process.
586+
test.network_properties.rtt_seconds = 0.0;
587+
588+
{
589+
NS::FrameIndex controller_1_doll_frame_index = test.peer_2_scene.scene_sync->get_controller_for_peer(test.peer_1_scene.get_peer())->get_current_frame_index();
590+
NS::FrameIndex controller_2_doll_frame_index = test.peer_1_scene.scene_sync->get_controller_for_peer(test.peer_2_scene.get_peer())->get_current_frame_index();
591+
592+
for (int i = 0; i < 10; i++) {
593+
if (i % 2 == 0) {
594+
test.do_test(10, false, true, false, true);
595+
} else {
596+
test.do_test(10, false, true, true, false);
597+
}
598+
599+
const NS::FrameIndex controller_1_doll_new_frame_index = test.peer_2_scene.scene_sync->get_controller_for_peer(test.peer_1_scene.get_peer())->get_current_frame_index();
600+
const NS::FrameIndex controller_2_doll_new_frame_index = test.peer_1_scene.scene_sync->get_controller_for_peer(test.peer_2_scene.get_peer())->get_current_frame_index();
601+
602+
// Ensure the doll keep going forward.
603+
ASSERT_COND(controller_1_doll_frame_index == NS::FrameIndex::NONE || controller_1_doll_frame_index <= controller_1_doll_new_frame_index);
604+
ASSERT_COND(controller_2_doll_frame_index == NS::FrameIndex::NONE || controller_2_doll_frame_index <= controller_2_doll_new_frame_index);
605+
606+
controller_1_doll_frame_index = controller_1_doll_new_frame_index;
607+
controller_2_doll_frame_index = controller_2_doll_new_frame_index;
608+
}
609+
}
610+
611+
test.do_test(30);
612+
613+
const NS::FrameIndex controller_1_last_player_frame_index = test.peer_1_scene.scene_sync->get_controller_for_peer(test.peer_1_scene.get_peer())->get_current_frame_index();
614+
const NS::FrameIndex controller_2_last_player_frame_index = test.peer_2_scene.scene_sync->get_controller_for_peer(test.peer_2_scene.get_peer())->get_current_frame_index();
615+
616+
const NS::FrameIndex controller_1_last_doll_frame_index = test.peer_2_scene.scene_sync->get_controller_for_peer(test.peer_1_scene.get_peer())->get_current_frame_index();
617+
const NS::FrameIndex controller_2_last_doll_frame_index = test.peer_1_scene.scene_sync->get_controller_for_peer(test.peer_2_scene.get_peer())->get_current_frame_index();
618+
619+
const int latency_factor = 15;
620+
621+
ASSERT_COND(controller_1_last_player_frame_index - latency_factor <= controller_1_last_doll_frame_index);
622+
ASSERT_COND(controller_2_last_player_frame_index - latency_factor <= controller_2_last_doll_frame_index);
623+
624+
// Make sure the last frames are identical.
625+
test.assert_positions(
626+
test.peer1_desync_detected.back(),
627+
test.peer2_desync_detected.back());
628+
629+
int a = 0;
630+
}
631+
575632
void test_latency() {
576633
TestDollSimulationBase test;
577634
test.init_test();
@@ -618,10 +675,13 @@ void test_doll_simulation() {
618675
//test_simulation_without_reconciliation(0.0);
619676
//test_simulation_without_reconciliation(1. / 30.);
620677
//test_simulation_reconciliation(0.0);
621-
//test_simulation_reconciliation(1.0 / 10.0);
622-
test_simulation_with_latency();
678+
test_simulation_reconciliation(1.0 / 10.0);
679+
//test_simulation_with_latency();
680+
//test_simulation_with_hiccups();
623681
// TODO test with great latency and lag compensation.
624682
//test_latency();
683+
684+
ASSERT_COND(false);
625685
}
626686

627687
}; //namespace NS_Test

0 commit comments

Comments
 (0)