Only display prediction overlay when triggered by slowness
Here we maintain the predictions always, but we only display the overlay when either a threshold of smoothed round-trip time is met or temporarily after a "glitch."
This commit is contained in:
@@ -29,7 +29,7 @@ using namespace Overlay;
|
||||
bool ConditionalOverlay::start_clock( uint64_t local_frame_acked, uint64_t now, unsigned int send_interval )
|
||||
{
|
||||
if ( (local_frame_acked >= expiration_frame) && (expiration_time == uint64_t(-1)) ) {
|
||||
expiration_time = now + 50 + 125 * send_interval / 100;
|
||||
expiration_time = now + 75 + 125 * send_interval / 100;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -318,9 +318,13 @@ void ConditionalOverlayRow::apply( Framebuffer &fb, uint64_t confirmed_epoch, bo
|
||||
|
||||
void PredictionEngine::apply( Framebuffer &fb ) const
|
||||
{
|
||||
for_each( cursors.begin(), cursors.end(), [&]( const ConditionalCursorMove &x ) { x.apply( fb, confirmed_epoch ); } );
|
||||
bool show = srtt_trigger || glitch_trigger;
|
||||
|
||||
for_each( overlays.begin(), overlays.end(), [&]( const ConditionalOverlayRow &x ){ x.apply( fb, confirmed_epoch, flagging ); } );
|
||||
if ( show ) {
|
||||
for_each( cursors.begin(), cursors.end(), [&]( const ConditionalCursorMove &x ) { x.apply( fb, confirmed_epoch ); } );
|
||||
|
||||
for_each( overlays.begin(), overlays.end(), [&]( const ConditionalOverlayRow &x ){ x.apply( fb, confirmed_epoch, flagging ); } );
|
||||
}
|
||||
}
|
||||
|
||||
void PredictionEngine::kill_epoch( uint64_t epoch, const Framebuffer &fb )
|
||||
@@ -378,10 +382,17 @@ void PredictionEngine::cull( const Framebuffer &fb )
|
||||
{
|
||||
uint64_t now = timestamp();
|
||||
|
||||
/* control srtt_trigger with hysteresis */
|
||||
if ( send_interval > SRTT_TRIGGER_HIGH ) {
|
||||
srtt_trigger = true;
|
||||
} else if ( send_interval <= SRTT_TRIGGER_LOW ) { /* 20 ms is current minimum value */
|
||||
srtt_trigger = false;
|
||||
}
|
||||
|
||||
/* control flagging with hysteresis */
|
||||
if ( send_interval > 80 ) {
|
||||
if ( send_interval > FLAG_TRIGGER_HIGH ) {
|
||||
flagging = true;
|
||||
} else if ( send_interval < 50 ) {
|
||||
} else if ( send_interval <= FLAG_TRIGGER_LOW ) {
|
||||
flagging = false;
|
||||
}
|
||||
|
||||
@@ -457,12 +468,26 @@ void PredictionEngine::cull( const Framebuffer &fb )
|
||||
|
||||
}
|
||||
|
||||
/* When predictions come in quickly, slowly take away the glitch trigger. */
|
||||
if ( (now - j->prediction_time) < GLITCH_THRESHOLD ) {
|
||||
if ( (glitch_trigger > 0) && (now - GLITCH_REPAIR_MININTERVAL >= last_quick_confirmation) ) {
|
||||
glitch_trigger--;
|
||||
last_quick_confirmation = now;
|
||||
}
|
||||
}
|
||||
|
||||
/* no break */
|
||||
case CorrectNoCredit:
|
||||
j->reset();
|
||||
|
||||
break;
|
||||
case Pending:
|
||||
/* When a prediction takes a long time to be confirmed, we
|
||||
activate the predictions even if SRTT is low */
|
||||
if ( (now - j->prediction_time) >= GLITCH_THRESHOLD ) {
|
||||
glitch_trigger = GLITCH_REPAIR_COUNT;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -527,6 +552,8 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
{
|
||||
cull( fb );
|
||||
|
||||
uint64_t now = timestamp();
|
||||
|
||||
/* translate application-mode cursor control function to ANSI cursor control sequence */
|
||||
if ( (last_byte == 0x1b)
|
||||
&& (the_byte == 'O') ) {
|
||||
@@ -560,7 +587,7 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
|
||||
if ( cursor().col > 0 ) {
|
||||
cursor().col--;
|
||||
cursor().expire( local_frame_sent + 1 );
|
||||
cursor().expire( local_frame_sent + 1, now );
|
||||
|
||||
for ( int i = cursor().col; i < fb.ds.get_width(); i++ ) {
|
||||
ConditionalOverlayCell &cell = the_row.overlay_cells[ i ];
|
||||
@@ -568,7 +595,7 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
cell.reset_with_orig();
|
||||
cell.active = true;
|
||||
cell.tentative_until_epoch = prediction_epoch;
|
||||
cell.expire( local_frame_sent + 1 );
|
||||
cell.expire( local_frame_sent + 1, now );
|
||||
cell.original_contents.push_back( *fb.get_cell( cursor().row, i ) );
|
||||
|
||||
if ( i + 2 < fb.ds.get_width() ) {
|
||||
@@ -615,7 +642,7 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
cell.reset_with_orig();
|
||||
cell.active = true;
|
||||
cell.tentative_until_epoch = prediction_epoch;
|
||||
cell.expire( local_frame_sent + 1 );
|
||||
cell.expire( local_frame_sent + 1, now );
|
||||
cell.original_contents.push_back( *fb.get_cell( cursor().row, i ) );
|
||||
|
||||
ConditionalOverlayCell &prev_cell = the_row.overlay_cells[ i - 1 ];
|
||||
@@ -640,7 +667,7 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
cell.reset_with_orig();
|
||||
cell.active = true;
|
||||
cell.tentative_until_epoch = prediction_epoch;
|
||||
cell.expire( local_frame_sent + 1 );
|
||||
cell.expire( local_frame_sent + 1, now );
|
||||
cell.replacement.renditions = fb.ds.get_renditions();
|
||||
cell.replacement.contents.clear();
|
||||
cell.replacement.contents.push_back( ch );
|
||||
@@ -653,7 +680,7 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
cell.tentative_until_epoch );
|
||||
*/
|
||||
|
||||
cursor().expire( local_frame_sent + 1 );
|
||||
cursor().expire( local_frame_sent + 1, now );
|
||||
|
||||
/* do we need to wrap? */
|
||||
if ( cursor().col < fb.ds.get_width() - 1 ) {
|
||||
@@ -679,14 +706,14 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
init_cursor( fb );
|
||||
if ( cursor().col < fb.ds.get_width() - 1 ) {
|
||||
cursor().col++;
|
||||
cursor().expire( local_frame_sent + 1 );
|
||||
cursor().expire( local_frame_sent + 1, now );
|
||||
}
|
||||
} else if ( act->char_present && (act->ch == L'D') ) { /* left arrow */
|
||||
init_cursor( fb );
|
||||
|
||||
if ( cursor().col > 0 ) {
|
||||
cursor().col--;
|
||||
cursor().expire( local_frame_sent + 1 );
|
||||
cursor().expire( local_frame_sent + 1, now );
|
||||
}
|
||||
} else {
|
||||
// fprintf( stderr, "CSI sequence %lc\n", act->ch );
|
||||
@@ -702,6 +729,7 @@ void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||
|
||||
void PredictionEngine::newline_carriage_return( const Framebuffer &fb )
|
||||
{
|
||||
uint64_t now = timestamp();
|
||||
init_cursor( fb );
|
||||
cursor().col = 0;
|
||||
if ( cursor().row == fb.ds.get_height() - 1 ) {
|
||||
@@ -709,7 +737,7 @@ void PredictionEngine::newline_carriage_return( const Framebuffer &fb )
|
||||
i->row_num--;
|
||||
for ( auto j = i->overlay_cells.begin(); j != i->overlay_cells.end(); j++ ) {
|
||||
if ( j->active ) {
|
||||
j->expire( local_frame_sent + 1 );
|
||||
j->expire( local_frame_sent + 1, now );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -719,7 +747,7 @@ void PredictionEngine::newline_carriage_return( const Framebuffer &fb )
|
||||
for ( auto j = the_row.overlay_cells.begin(); j != the_row.overlay_cells.end(); j++ ) {
|
||||
j->active = true;
|
||||
j->tentative_until_epoch = prediction_epoch;
|
||||
j->expire( local_frame_sent + 1 );
|
||||
j->expire( local_frame_sent + 1, now );
|
||||
j->replacement.contents.clear();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -45,11 +45,12 @@ namespace Overlay {
|
||||
int col;
|
||||
bool active; /* represents a prediction at all */
|
||||
uint64_t tentative_until_epoch; /* when to show */
|
||||
uint64_t prediction_time; /* used to find long-pending predictions */
|
||||
|
||||
ConditionalOverlay( uint64_t s_exp, int s_col, uint64_t s_tentative )
|
||||
: expiration_frame( s_exp ), expiration_time( -1 ), col( s_col ),
|
||||
: expiration_frame( s_exp ), expiration_time( uint64_t( -1 ) ), col( s_col ),
|
||||
active( false ),
|
||||
tentative_until_epoch( s_tentative )
|
||||
tentative_until_epoch( s_tentative ), prediction_time( uint64_t( -1 ) )
|
||||
{}
|
||||
|
||||
virtual ~ConditionalOverlay() {}
|
||||
@@ -57,7 +58,10 @@ namespace Overlay {
|
||||
bool tentative( uint64_t confirmed_epoch ) const { return tentative_until_epoch > confirmed_epoch; }
|
||||
void reset( void ) { expiration_frame = expiration_time = tentative_until_epoch = -1; active = false; }
|
||||
bool start_clock( uint64_t local_frame_acked, uint64_t now, unsigned int send_interval );
|
||||
void expire( uint64_t s_exp ) { expiration_frame = s_exp; expiration_time = uint64_t(-1); }
|
||||
void expire( uint64_t s_exp, uint64_t now )
|
||||
{
|
||||
expiration_frame = s_exp; expiration_time = uint64_t(-1); prediction_time = now;
|
||||
}
|
||||
};
|
||||
|
||||
class ConditionalCursorMove : public ConditionalOverlay {
|
||||
@@ -137,6 +141,16 @@ namespace Overlay {
|
||||
|
||||
class PredictionEngine {
|
||||
private:
|
||||
static const uint64_t SRTT_TRIGGER_LOW = 20; /* <= ms cures SRTT trigger to show predictions */
|
||||
static const uint64_t SRTT_TRIGGER_HIGH = 30; /* > ms starts SRTT trigger */
|
||||
|
||||
static const uint64_t FLAG_TRIGGER_LOW = 50; /* <= ms cures flagging */
|
||||
static const uint64_t FLAG_TRIGGER_HIGH = 80; /* > ms starts flagging */
|
||||
|
||||
static const uint64_t GLITCH_THRESHOLD = 250; /* prediction outstanding this long is glitch */
|
||||
static const uint64_t GLITCH_REPAIR_COUNT = 10; /* non-glitches required to cure glitch trigger */
|
||||
static const uint64_t GLITCH_REPAIR_MININTERVAL = 150; /* required time in between non-glitches */
|
||||
|
||||
char last_byte;
|
||||
Parser::UTF8Parser parser;
|
||||
|
||||
@@ -155,7 +169,10 @@ namespace Overlay {
|
||||
|
||||
void newline_carriage_return( const Framebuffer &fb );
|
||||
|
||||
bool flagging;
|
||||
bool flagging; /* whether we are underlining predictions */
|
||||
bool srtt_trigger; /* show predictions because of slow round trip time */
|
||||
int glitch_trigger; /* show predictions temporarily because of long-pending prediction */
|
||||
uint64_t last_quick_confirmation;
|
||||
|
||||
ConditionalCursorMove & cursor( void ) { assert( !cursors.empty() ); return cursors.back(); }
|
||||
|
||||
@@ -186,7 +203,11 @@ namespace Overlay {
|
||||
local_frame_sent( 0 ), local_frame_acked( 0 ),
|
||||
local_frame_late_acked( 0 ),
|
||||
prediction_epoch( 1 ), confirmed_epoch( 0 ),
|
||||
flagging( false ), last_scheduled_timeout( 0 ),
|
||||
flagging( false ),
|
||||
srtt_trigger( false ),
|
||||
glitch_trigger( 0 ),
|
||||
last_quick_confirmation( 0 ),
|
||||
last_scheduled_timeout( 0 ),
|
||||
send_interval( 250 )
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user