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
269 changes: 254 additions & 15 deletions code/network/multi_interpolate.cpp

Large diffs are not rendered by default.

27 changes: 25 additions & 2 deletions code/network/multi_interpolate.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,29 @@

struct physics_info;

constexpr size_t PACKET_INFO_LIMIT = 4; // we should never need more than 4 packets to do interpolation. Overwrite the oldest ones if we do.
// How much packet history to keep per object. This has to span MULTI_INTERP_BUFFER_MS
// worth of updates or the playback clock falls off the back of the history and the object
// drops to dead reckoning -- worst for the *fastest* updating objects, since they cover
// the least wall time per packet. The tightest rate in the Multi_oo_*_update_times tables
// is 20ms (LAN players and targets), and 11 intervals of that is 220ms, comfortably over
// the 150ms buffer.
constexpr size_t PACKET_INFO_LIMIT = 12;

typedef struct packet_info {

int frame; // this allows us to directly compare one packet to another.
int frame; // this allows us to directly compare one packet to another.
int remote_missiontime; // the remote timestamp that matches this packet.
physics_snapshot snapshot; // the received physics info translated into the physics snapshot type for easy interpolation
bool synthetic; // true if replace_packet() built this, in which case `frame` is a made-up number
// and must never be sent back to the server as a rollback reference

packet_info(int frame_in = 0, int time_in = 0, const vec3d* position_in = &vmd_zero_vector, const vec3d* velocity_in = &vmd_zero_vector,
const vec3d* rotational_velocity_in = &vmd_zero_vector, const vec3d* desired_velocity_in = &vmd_zero_vector, const vec3d* desired_rotational_velocity_in = &vmd_zero_vector,
const angles* angles_in = &vmd_zero_angles)
{
frame = frame_in;
remote_missiontime = time_in;
synthetic = false;
snapshot.position = *position_in;
snapshot.velocity = *velocity_in;
snapshot.rotational_velocity = *rotational_velocity_in;
Expand Down Expand Up @@ -59,6 +68,13 @@ class interpolation_manager {
// adds a new packet, whilst also manually sorting the relevant entries
void add_packet(int objnum, int frame, int time_delta, vec3d* position, vec3d* velocity, vec3d* rotational_velocity, vec3d* desired_velocity, vec3d* desired_rotational_velocity, angles* angles, int player_index);
void interpolate_main(vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship);

// Describes the moment this object was last drawn at, as the (server frame, ms after that
// frame) pair that multi_ship_record_find_frame() consumes on the server. Returns false
// if there is no usable history. Fire packets must use this rather than the newest packet
// received: interpolation deliberately draws MULTI_INTERP_BUFFER_MS behind that, and the
// server rewinds every ship to whatever moment the pair resolves to.
bool get_render_reference(int& frame, int& time_after_frame) const;
void reinterpolate_previous(TIMESTAMP stamp, int prev_packet_index, int next_packet_index, vec3d& position, matrix& orientation, vec3d& velocity, vec3d& rotational_velocity);

int get_hull_comparison_frame() const { return _hull_comparison_frame; }
Expand Down Expand Up @@ -166,8 +182,15 @@ class interpolation_manager {

void multi_interpolate_clear_all();

// TEMP INSTRUMENTATION - remove along with the debug block in multi_interpolate.cpp
void multi_interpolate_debug_reset();

void multi_interpolate_clear_helper(int objnum);

// interpolation_manager::get_render_reference for a given object, without exposing Interp_info.
// Returns false if we hold no usable history for it.
bool multi_interpolate_get_render_reference(int objnum, int& frame, int& time_after_frame);

void interpolate_main_helper(int objnum, vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship);

extern SCP_unordered_map<int, interpolation_manager> Interp_info;
145 changes: 108 additions & 37 deletions code/network/multi_obj.cpp

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions code/network/multi_obj.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ vec3d multi_ship_record_lookup_position(object* objp, int frame);
// a quick lookups for orientation
matrix multi_ship_record_lookup_orientation(object* objp, int frame);

// position and orientation interpolated to time_after_frame past the given frame, rather than
// snapped to the frame itself. Rollback should use this: the recorded frames are one server
// frame apart, and snapping to them rewinds every shot up to that much too early.
void multi_ship_record_lookup_interpolated(object* objp, int frame, int time_after_frame, vec3d* pos, matrix* ori);

// figures out how much time has passed bwetween the two frames.
int multi_ship_record_find_time_after_frame(int client_frame, int frame, int time_elapsed);

Expand Down
183 changes: 176 additions & 7 deletions code/network/multi_time_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,29 @@ multiplayer_timing_info Multi_Timing_Info;
// Time Records Manager functions

// make sure that we recalculate the current local time
void multiplayer_timing_info::update_current_time()
void multiplayer_timing_info::update_current_time()
{
// finalize the time skip from last frame.
finalize_skip_time();

// set our _last time.
_last_time = _current_time;

_current_time = timestamp_since(_start_time);
_current_time = timestamp_since(_start_time);

_current_time += _skipped_time;

// now that the local clock has moved, walk each source's playback clock toward its target
update_source_clocks();
}


// checks to see if this is the most recent frame from the source player.
bool multiplayer_timing_info::is_most_recent_frame(int player_index, int frame)
bool multiplayer_timing_info::is_most_recent_frame(int player_index, int frame)
{
// quick sanity check, but nothing to crash over
if (player_index < 0) {
mprintf(("MULTI INTERPOLATION most recent frame got a negative index."));
if (!valid_source(player_index)) {
mprintf(("MULTI INTERPOLATION most recent frame got an out of range index (%d).\n", player_index));
return false;
}

Expand All @@ -40,30 +43,37 @@ bool multiplayer_timing_info::is_most_recent_frame(int player_index, int frame)
return false;
}

multiplayer_timing_info::multiplayer_timing_info()
multiplayer_timing_info::multiplayer_timing_info()
{
constexpr int NO_PACKET_RECEIVED = -1;

_start_time = TIMESTAMP::invalid();
_current_time = 0;
_last_time = 0;
_skipped_time = 0;
_proposed_skip_time = 0;
_in_game_time_set = false;

for (auto& frame : _most_recent_frame) {
frame = NO_PACKET_RECEIVED;
}

reset_source_clocks();
}

// aka reset the class. Needs to be called every time the mission starts.
void multiplayer_timing_info::set_mission_start_time()
void multiplayer_timing_info::set_mission_start_time()
{
_start_time = _timestamp(); // set it to the mission's starting time.

_current_time = 0;
_last_time = 0;
_skipped_time = 0;
_proposed_skip_time = 0;
_in_game_time_set = false;

// the old mission's offsets say nothing about the new one
reset_source_clocks();
}

void multiplayer_timing_info::in_game_set_skip_time(float mission_time)
Expand All @@ -74,5 +84,164 @@ void multiplayer_timing_info::in_game_set_skip_time(float mission_time)
if (!_in_game_time_set){
_skipped_time = static_cast<int>(mission_time * MILLISECONDS_PER_SECOND);
_in_game_time_set = true;

// _current_time is about to move by a large step that has nothing to do with
// elapsed time, so every offset measured against the old one is meaningless.
reset_source_clocks();
}
}

/////////////////////////////////
// Per-source playback clocks

void multiplayer_timing_info::reset_source_clocks()
{
for (auto& sc : _source_clocks) {
sc.acquired = false;
sc.windows_closed = 0;
sc.offset = 0;
sc.target_offset = 0;
sc.window_min_skew = INT_MAX;
sc.window_has_sample = false;
sc.window_end = 0;
}
}

// Called for every position packet that arrives, from add_packet().
void multiplayer_timing_info::note_packet_time(int player_index, int remote_time)
{
if (!valid_source(player_index)) {
return;
}

auto& sc = _source_clocks[player_index];

// How far our clock reads ahead of the timestamp this packet was stamped with.
// This is (clock offset + that packet's network delay), and the delay is always
// positive, so the *minimum* skew over a window is the cleanest estimate of the
// offset alone -- it is the packet that got here fastest.
const int skew = _current_time - remote_time;

if (!sc.acquired) {
// Nothing is drawing off this clock yet, so take the estimate immediately
// instead of slewing in from a meaningless zero. It is only a rough estimate
// though -- one packet, measured while the mission is still starting up -- so
// the short windows that follow keep snapping until it settles.
sc.acquired = true;
sc.windows_closed = 0;
sc.offset = -skew - MULTI_INTERP_BUFFER_MS;
sc.target_offset = sc.offset;
sc.window_min_skew = skew;
sc.window_has_sample = true;
sc.window_end = _current_time + MULTI_CLOCK_ACQUIRE_WINDOW_MS;
return;
}

if (!sc.window_has_sample || (skew < sc.window_min_skew)) {
sc.window_min_skew = skew;
sc.window_has_sample = true;
}
}

// Once per frame, from update_current_time().
void multiplayer_timing_info::update_source_clocks()
{
const int frame_delta = _current_time - _last_time;

for (auto& sc : _source_clocks) {
if (!sc.acquired) {
continue;
}

// window closed, so re-aim at whatever the best packet in it told us
if (_current_time >= sc.window_end) {
if (sc.window_has_sample) {
sc.target_offset = -sc.window_min_skew - MULTI_INTERP_BUFFER_MS;
}

// While still acquiring, take each estimate outright rather than slewing onto
// it. Slewing costs seconds of dead reckoning, and the early samples are poor
// enough that one snap onto the first of them lands well short.
const bool still_acquiring = (sc.windows_closed < MULTI_CLOCK_ACQUIRE_WINDOWS);

if (still_acquiring) {
sc.offset = sc.target_offset;
}

sc.windows_closed++;

sc.window_has_sample = false;
sc.window_min_skew = INT_MAX;
sc.window_end = _current_time + (still_acquiring ? MULTI_CLOCK_ACQUIRE_WINDOW_MS : MULTI_CLOCK_WINDOW_MS);
}

const int diff = sc.target_offset - sc.offset;

if (diff == 0) {
continue;
}

// A correction this large is a real discontinuity in the source's clock, not
// drift. Slewing across it would take many seconds of visibly wrong motion.
if ((diff >= MULTI_CLOCK_SNAP_THRESHOLD_MS) || (diff <= -MULTI_CLOCK_SNAP_THRESHOLD_MS)) {
sc.offset = sc.target_offset;
continue;
}

if (frame_delta <= 0) {
continue;
}

// Cap the correction at a quarter of the frame. Playback time is
// (_current_time + offset), so an offset moving faster than the frame itself
// would run playback backwards and drag every interpolated ship back with it.
// At a quarter, playback always advances, between 0.75x and 1.25x real time.
const int max_step = MAX(1, frame_delta / 4);

if (diff > max_step) {
sc.offset += max_step;
} else if (diff < -max_step) {
sc.offset -= max_step;
} else {
sc.offset = sc.target_offset;
}
}
}

int multiplayer_timing_info::get_playback_time(int player_index) const
{
if (!valid_source(player_index) || !_source_clocks[player_index].acquired) {
return _current_time;
}

return _current_time + _source_clocks[player_index].offset;
}

int multiplayer_timing_info::get_playback_last_time(int player_index) const
{
if (!valid_source(player_index) || !_source_clocks[player_index].acquired) {
return _last_time;
}

return _last_time + _source_clocks[player_index].offset;
}

int multiplayer_timing_info::remote_time_to_local(int player_index, int remote_time) const
{
if (!valid_source(player_index) || !_source_clocks[player_index].acquired) {
return remote_time;
}

// playback == _current_time + offset, so the local time matching remote_time
// is remote_time - offset.
return remote_time - _source_clocks[player_index].offset;
}

int multiplayer_timing_info::local_time_to_remote(int player_index, int local_time) const
{
if (!valid_source(player_index) || !_source_clocks[player_index].acquired) {
return local_time;
}

return local_time + _source_clocks[player_index].offset;
}
Loading
Loading