Skip to content

Commit

Permalink
Apply actions within a threshold of the extrapolation start time. Use…
Browse files Browse the repository at this point in the history
… island timestamp as registry snapshot packet timestamp. Fix issue with unrelated bodies waking up when applying extrapolation result.
  • Loading branch information
xissburg committed Apr 23, 2022
1 parent 4b11818 commit 4ca4f4c
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 28 deletions.
25 changes: 25 additions & 0 deletions include/edyn/networking/networking.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,31 @@ void set_network_client_discontinuity_decay_rate(entt::registry &, scalar);
*/
scalar get_network_client_discontinuity_decay_rate(entt::registry &);

/**
* In case the timestamp of a registry snapshot lies right after the time an
* action happenend, it is possible that the action wasn't still applied in the
* server side at the time the snapshot was generated. Perhaps the action was
* applied at the same time the snapshot was generated and then its effects
* were only visible in the next update, which will cause a glitch on client-side
* extrapolation because the action will not be applied initially and the initial
* state does not include the effects of the action because it wasn't applied in
* the server at the time the snapshot was generated. All actions that happened
* before the snapshot time within this threshold will be applied at the start
* of an extrapolation.
* @param registry Data source.
* @param threshold Actions in the input history which lie before the registry
* snapshot timestamp within this threshold (specified in seconds) will be
* applied before the extrapolation steps start.
*/
void set_network_client_action_time_threshold(entt::registry &, double);

/**
* @brief Get client-side extrapolation action threshold.
* @param registry Data source.
* @return Action threshold in seconds.
*/
double get_network_client_action_time_threshold(entt::registry &);

/**
* @brief Get client packet sink. This sink must be observed and the packets
* that are published into it should be sent over the network immediately.
Expand Down
14 changes: 14 additions & 0 deletions include/edyn/networking/settings/client_network_settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ struct client_network_settings {
// step. That means this value is sensitive to the fixed delta time since
// a lower delta time means higher step rate, thus faster decay.
scalar discontinuity_decay_rate {scalar(0.9)};

/**
* Even though the timestamp of a registry snapshot lies right after the time
* an action happenend, it is possible that the action wasn't still applied
* in the server side at the time the snapshot was generated. Perhaps the
* action was applied at the same time the snapshot was generated and then
* its effects were only visible in the next update, which will cause a glitch
* on client-side extrapolation because the action will not be applied initially
* and the initial state does not include the effects of the action because
* it wasn't applied in the server at the time the snapshot was generated.
* All actions that happened before the snapshot time within this threshold
* will be applied at the start of an extrapolation.
*/
double action_time_threshold {0.06};
};

}
Expand Down
43 changes: 38 additions & 5 deletions include/edyn/networking/util/input_state_history.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ class input_state_history {
}
};

/**
* Even though the timestamp of a registry snapshot lies right after the time
* an action happenend, it is possible that the action wasn't still applied
* in the server side at the time the snapshot was generated. Perhaps the
* action was applied at the same time the snapshot was generated and then
* its effects will only become visible in the next update, which will cause
* a glitch on client-side extrapolation because the action will not be
* applied initially and the initial state does not include the effects of
* the action because it wasn't applied in the server at the time the
* snapshot was generated. All actions that happened before the snapshot
* time within this threshold will be applied at the start of an extrapolation.
*/
double action_time_threshold{0.06};

protected:
virtual snapshot take_snapshot(const entt::registry &registry,
const entt::sparse_set &entities) const {
Expand Down Expand Up @@ -232,10 +246,13 @@ class input_state_history_impl : public input_state_history {
return snapshot;
}

// Import the last state of all inputs right before the extrapolation start time.
// This ensures correct initial input state before extrapolation begins.
template<typename Input>
struct import_initial_state_single {
static void import(entt::registry &registry, const entity_map &emap,
const std::vector<element> &history, double time) {
const std::vector<element> &history, double time,
double action_threshold) {
// Find history element with the greatest timestamp smaller than `time`
// which contains a pool of the given component type.
for (auto i = history.size(); i > 0; --i) {
Expand All @@ -259,12 +276,28 @@ class input_state_history_impl : public input_state_history {
}
};

// Specialization to prevent action lists from being imported when loading
// initial state, which only applies to regular continuous inputs.
// Apply all actions that happened slightly before the extrapolation start time.
// This prevents glitches due to the effect of actions not yet being present
// in a registry snapshot due to small timing errors.
template<typename Action>
struct import_initial_state_single<action_list<Action>> {
static void import(entt::registry &registry, const entity_map &emap,
const std::vector<element> &history, double time) {}
const std::vector<element> &history, double time,
double action_threshold) {
for (auto &elem : history) {
if (elem.timestamp < time && time - elem.timestamp < action_threshold) {
auto pool_it = std::find_if(
elem.snapshot.pools.begin(), elem.snapshot.pools.end(),
[](auto &&pool) {
return pool->type_id() == entt::type_index<action_list<Action>>::value();
});

if (pool_it != elem.snapshot.pools.end()) {
(*pool_it)->import(registry, emap);
}
}
}
}
};

public:
Expand All @@ -273,7 +306,7 @@ class input_state_history_impl : public input_state_history {

void import_initial_state(entt::registry &registry, const entity_map &emap, double time) override {
std::lock_guard lock(mutex);
(import_initial_state_single<Inputs>::import(registry, emap, history, time), ...);
(import_initial_state_single<Inputs>::import(registry, emap, history, time, action_time_threshold), ...);
}
};

Expand Down
13 changes: 8 additions & 5 deletions include/edyn/util/island_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
namespace edyn {

template<typename It>
entt::sparse_set collect_islands_from_residents(entt::registry &registry, It first_entity, It last_entity) {
entt::sparse_set collect_islands_from_residents(entt::registry &registry, It first_entity, It last_entity,
bool include_multi_resident = true) {
entt::sparse_set island_entities;

for (auto it = first_entity; it != last_entity; ++it) {
Expand All @@ -17,10 +18,12 @@ entt::sparse_set collect_islands_from_residents(entt::registry &registry, It fir
if (resident->island_entity != entt::null && !island_entities.contains(resident->island_entity)) {
island_entities.emplace(resident->island_entity);
}
} else if (auto *resident = registry.try_get<multi_island_resident>(entity)) {
for (auto island_entity : resident->island_entities) {
if (!island_entities.contains(island_entity)) {
island_entities.emplace(island_entity);
} else if (include_multi_resident) {
if (auto *resident = registry.try_get<multi_island_resident>(entity)) {
for (auto island_entity : resident->island_entities) {
if (!island_entities.contains(island_entity)) {
island_entities.emplace(island_entity);
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/edyn/networking/networking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ scalar get_network_client_discontinuity_decay_rate(entt::registry &registry) {
return get_client_settings(registry).discontinuity_decay_rate;
}

void set_network_client_action_time_threshold(entt::registry &registry, double threshold) {
EDYN_ASSERT(!(threshold < 0));
edit_client_settings(registry, [threshold](auto &client_settings) {
client_settings.action_time_threshold = threshold;
});
}

double get_network_client_action_time_threshold(entt::registry &registry) {
return get_client_settings(registry).action_time_threshold;
}

entt::sink<entt::sigh<void(const packet::edyn_packet &)>> network_client_packet_sink(entt::registry &registry) {
auto &ctx = registry.ctx<client_network_context>();
return ctx.packet_sink();
Expand Down
64 changes: 53 additions & 11 deletions src/edyn/networking/sys/client_side.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ static void update_input_history(entt::registry &registry, double timestamp) {
// inputs from that point in time onwards.
auto &settings = registry.ctx<edyn::settings>();
auto &client_settings = std::get<client_network_settings>(settings.network_settings);
const auto client_server_time_difference = ctx.server_playout_delay + client_settings.round_trip_time / 2;
const auto client_server_time_difference =
ctx.server_playout_delay + client_settings.round_trip_time / 2;
ctx.input_history->erase_until(timestamp - (client_server_time_difference * 1.1 + 0.2));
}

Expand Down Expand Up @@ -161,7 +162,9 @@ static void maybe_publish_registry_snapshot(entt::registry &registry, double tim
if (ctx.allow_full_ownership) {
// Include all networked entities in the islands that contain an entity
// owned by this client, excluding entities that are owned by other clients.
auto island_entities = collect_islands_from_residents(registry, ctx.owned_entities.begin(), ctx.owned_entities.end());
auto island_entities = collect_islands_from_residents(registry,
ctx.owned_entities.begin(),
ctx.owned_entities.end());
auto owner_view = registry.view<entity_owner>();
auto island_view = registry.view<island>();

Expand Down Expand Up @@ -209,7 +212,6 @@ static void maybe_publish_registry_snapshot(entt::registry &registry, double tim
}

auto packet = packet::registry_snapshot{};
packet.timestamp = time;
packet.entities.insert(packet.entities.end(), network_dirty_view.begin(), network_dirty_view.end());

auto history_view = registry.view<action_history>();
Expand All @@ -226,6 +228,24 @@ static void maybe_publish_registry_snapshot(entt::registry &registry, double tim
ctx.snapshot_exporter->export_actions(registry, packet);

if (!packet.entities.empty() && !packet.pools.empty()) {
// Assign island timestamp as packet timestamp if available.
// Use current time otherwise.
auto island_entities = collect_islands_from_residents(registry,
packet.entities.begin(),
packet.entities.end());

if (island_entities.empty()) {
packet.timestamp = time;
} else {
auto timestamp_view = registry.view<island_timestamp>();
packet.timestamp = timestamp_view.get<island_timestamp>(*island_entities.begin()).value;

for (auto island_entity : island_entities) {
auto [isle_time] = timestamp_view.get(island_entity);
packet.timestamp = std::min(isle_time.value, packet.timestamp);
}
}

ctx.packet_signal.publish(packet::edyn_packet{std::move(packet)});
}
}
Expand All @@ -237,7 +257,11 @@ static void apply_extrapolation_result(entt::registry &registry, extrapolation_r
[&](auto entity) { return !registry.valid(entity); });
result.entities.erase(invalid_it, result.entities.end());

auto island_entities = collect_islands_from_residents(registry, result.entities.begin(), result.entities.end());
const bool include_multi_resident = false;
auto island_entities = collect_islands_from_residents(registry,
result.entities.begin(),
result.entities.end(),
include_multi_resident);
auto &coordinator = registry.ctx<island_coordinator>();

for (auto island_entity : island_entities) {
Expand Down Expand Up @@ -338,7 +362,8 @@ void create_graph_edge(entt::registry &registry, entt::entity entity) {
}

template<typename... Ts>
void maybe_create_graph_edge(entt::registry &registry, entt::entity entity, [[maybe_unused]] std::tuple<Ts...>) {
void maybe_create_graph_edge(entt::registry &registry, entt::entity entity,
[[maybe_unused]] std::tuple<Ts...>) {
((registry.any_of<Ts>(entity) ? create_graph_edge<Ts>(registry, entity) : void(0)), ...);
}

Expand Down Expand Up @@ -477,7 +502,8 @@ static bool contains_unknown_entities(entt::registry &registry,
return false;
}

static void insert_input_to_state_history(entt::registry &registry, const packet::registry_snapshot &snap, double time) {
static void insert_input_to_state_history(entt::registry &registry,
const packet::registry_snapshot &snap, double time) {
// Insert inputs of entities not owned by this client into the state history.
auto &ctx = registry.ctx<client_network_context>();
entt::sparse_set unwoned_entities;
Expand All @@ -494,9 +520,13 @@ static void insert_input_to_state_history(entt::registry &registry, const packet
}

static void snap_to_registry_snapshot(entt::registry &registry, packet::registry_snapshot &snapshot) {
// Collect all entities present in snapshot and find islands where they
// reside and finally send the snapshot to the island workers.
auto island_entities = collect_islands_from_residents(registry, snapshot.entities.begin(), snapshot.entities.end());
// Collect all procedural entities present in snapshot and find islands
// where they reside and finally send the snapshot to the island workers.
const bool include_multi_resident = false;
auto island_entities = collect_islands_from_residents(registry,
snapshot.entities.begin(),
snapshot.entities.end(),
include_multi_resident);
auto &coordinator = registry.ctx<island_coordinator>();

auto msg = msg::apply_network_pools{std::move(snapshot.entities), std::move(snapshot.pools)};
Expand Down Expand Up @@ -532,7 +562,8 @@ static void process_packet(entt::registry &registry, packet::registry_snapshot &
if (ctx.clock_sync.count > 0) {
snapshot_time = snapshot.timestamp + ctx.clock_sync.time_delta - ctx.server_playout_delay;
} else {
const auto client_server_time_difference = ctx.server_playout_delay + client_settings.round_trip_time / 2;
const auto client_server_time_difference =
ctx.server_playout_delay + client_settings.round_trip_time / 2;
snapshot_time = time - client_server_time_difference;
}

Expand Down Expand Up @@ -577,6 +608,13 @@ static void process_packet(entt::registry &registry, packet::registry_snapshot &
}
}

if (node_indices.empty()) {
// There are no connecting nodes among all entities involved, i.e.
// procedural entities. Then just snap.
snap_to_registry_snapshot(registry, snapshot);
return;
}

// Do not include manifolds as they will not make sense in the server state
// because rigid bodies generally will have quite different transforms
// compared to the client state.
Expand Down Expand Up @@ -628,7 +666,11 @@ static void process_packet(entt::registry &registry, packet::registry_snapshot &

auto &material_table = registry.ctx<material_mix_table>();

auto job = std::make_unique<extrapolation_job>(std::move(input), settings, material_table, ctx.input_history);
// Assign latest value of action threshold before extrapolation.
ctx.input_history->action_time_threshold = client_settings.action_time_threshold;

auto job = std::make_unique<extrapolation_job>(std::move(input), settings,
material_table, ctx.input_history);
job->reschedule();

ctx.extrapolation_jobs.push_back(extrapolation_job_context{std::move(job)});
Expand Down
31 changes: 24 additions & 7 deletions src/edyn/networking/sys/server_side.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ static void process_packet(entt::registry &registry, entt::entity client_entity,
// snapshot to them. They will import the pre-processed state into their
// registries. Later, these components will be updated in the main registry
// via registry operations.
auto island_entities = collect_islands_from_residents(registry, snapshot.entities.begin(), snapshot.entities.end());
bool include_multi_resident = true;
auto island_entities = collect_islands_from_residents(registry,
snapshot.entities.begin(),
snapshot.entities.end(),
include_multi_resident);
auto &coordinator = registry.ctx<island_coordinator>();
auto msg = msg::apply_network_pools{std::move(snapshot.entities), std::move(snapshot.pools)};

Expand Down Expand Up @@ -309,11 +313,7 @@ static void server_process_timed_packets(entt::registry &registry, double time)
}

std::visit([&](auto &&packet) {
using PacketType = std::decay_t<decltype(packet)>;

if constexpr(tuple_has_type<PacketType, packet::timed_packets_tuple_t>::value) {
process_packet(registry, client_entity, packet);
}
process_packet(registry, client_entity, packet);
}, it->packet.var);
}

Expand Down Expand Up @@ -425,7 +425,6 @@ static void maybe_publish_client_registry_snapshot(entt::registry &registry,

auto &ctx = registry.ctx<server_network_context>();
auto packet = packet::registry_snapshot{};
packet.timestamp = time;

// Only include entities which are in islands not fully owned by the client
// since the server allows the client to have full control over entities in
Expand All @@ -446,6 +445,24 @@ static void maybe_publish_client_registry_snapshot(entt::registry &registry,
ctx.snapshot_exporter->export_dirty(registry, packet, client_entity);

if (!packet.entities.empty() && !packet.pools.empty()) {
// Assign island timestamp as packet timestamp if available.
// Use current time otherwise.
auto island_entities = collect_islands_from_residents(registry,
packet.entities.begin(),
packet.entities.end());

if (island_entities.empty()) {
packet.timestamp = time;
} else {
auto timestamp_view = registry.view<island_timestamp>();
packet.timestamp = timestamp_view.get<island_timestamp>(*island_entities.begin()).value;

for (auto island_entity : island_entities) {
auto [isle_time] = timestamp_view.get(island_entity);
packet.timestamp = std::min(isle_time.value, packet.timestamp);
}
}

ctx.packet_signal.publish(client_entity, packet::edyn_packet{packet});
}
}
Expand Down

0 comments on commit 4ca4f4c

Please sign in to comment.