Better speculative local echo/editing. Also outputs debugging/timing info
This commit is contained in:
@@ -79,6 +79,9 @@ namespace Network {
|
|||||||
void set_verbose( void ) { sender.set_verbose(); verbose = true; }
|
void set_verbose( void ) { sender.set_verbose(); verbose = true; }
|
||||||
|
|
||||||
void set_send_delay( int new_delay ) { sender.set_send_delay( new_delay ); }
|
void set_send_delay( int new_delay ) { sender.set_send_delay( new_delay ); }
|
||||||
|
|
||||||
|
uint64_t get_sent_state_acked( void ) { return sender.get_sent_state_acked(); }
|
||||||
|
uint64_t get_sent_state_last( void ) { return sender.get_sent_state_last(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ std::list<Parser::Action *> Parser::Parser::input( wchar_t ch )
|
|||||||
Parser::UTF8Parser::UTF8Parser()
|
Parser::UTF8Parser::UTF8Parser()
|
||||||
: parser(), buf_len( 0 )
|
: parser(), buf_len( 0 )
|
||||||
{
|
{
|
||||||
if ( strcmp( nl_langinfo( CODESET ), "UTF-8" ) != 0 ) {
|
|
||||||
fprintf( stderr, "stm requires a UTF-8 locale.\n" );
|
|
||||||
throw std::string( "stm requires a UTF-8 locale." );
|
|
||||||
}
|
|
||||||
|
|
||||||
assert( BUF_SIZE >= MB_CUR_MAX );
|
assert( BUF_SIZE >= MB_CUR_MAX );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ int main( int argc, char *argv[] )
|
|||||||
client.main();
|
client.main();
|
||||||
|
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
printf( "\n[stm is exiting.]\n" );
|
printf( "\n[mosh is exiting.]\n" );
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-3
@@ -24,7 +24,7 @@ void STMClient::init( void )
|
|||||||
{
|
{
|
||||||
/* Verify locale calls for UTF-8 */
|
/* Verify locale calls for UTF-8 */
|
||||||
if ( strcmp( nl_langinfo( CODESET ), "UTF-8" ) != 0 ) {
|
if ( strcmp( nl_langinfo( CODESET ), "UTF-8" ) != 0 ) {
|
||||||
fprintf( stderr, "stm requires a UTF-8 locale.\n" );
|
fprintf( stderr, "mosh requires a UTF-8 locale.\n" );
|
||||||
exit( 1 );
|
exit( 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,10 +50,19 @@ void STMClient::init( void )
|
|||||||
|
|
||||||
/* Put terminal in application-cursor-key mode */
|
/* Put terminal in application-cursor-key mode */
|
||||||
swrite( STDOUT_FILENO, Terminal::Emulator::open().c_str() );
|
swrite( STDOUT_FILENO, Terminal::Emulator::open().c_str() );
|
||||||
|
|
||||||
|
/* Add our name to window title */
|
||||||
|
overlays.set_title_prefix( wstring( L"[mosh] " ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void STMClient::shutdown( void )
|
void STMClient::shutdown( void )
|
||||||
{
|
{
|
||||||
|
/* Restore screen state */
|
||||||
|
overlays.get_notification_engine().set_notification_string( wstring( L"" ) );
|
||||||
|
overlays.get_notification_engine().server_heard( timestamp() );
|
||||||
|
overlays.set_title_prefix( wstring( L"" ) );
|
||||||
|
output_new_frame();
|
||||||
|
|
||||||
/* Restore terminal and terminal-driver state */
|
/* Restore terminal and terminal-driver state */
|
||||||
swrite( STDOUT_FILENO, Terminal::Emulator::close().c_str() );
|
swrite( STDOUT_FILENO, Terminal::Emulator::close().c_str() );
|
||||||
|
|
||||||
@@ -126,7 +135,6 @@ void STMClient::output_new_frame( void )
|
|||||||
Terminal::Framebuffer new_state( network->get_latest_remote_state().state.get_fb() );
|
Terminal::Framebuffer new_state( network->get_latest_remote_state().state.get_fb() );
|
||||||
|
|
||||||
/* apply local overlays */
|
/* apply local overlays */
|
||||||
overlays.get_notification_engine().render_notification();
|
|
||||||
overlays.apply( new_state );
|
overlays.apply( new_state );
|
||||||
|
|
||||||
/* calculate minimal difference from where we are */
|
/* calculate minimal difference from where we are */
|
||||||
@@ -143,7 +151,9 @@ bool STMClient::process_network_input( void )
|
|||||||
{
|
{
|
||||||
network->recv();
|
network->recv();
|
||||||
|
|
||||||
overlays.get_notification_engine().server_ping( network->get_latest_remote_state().timestamp );
|
overlays.get_notification_engine().server_heard( network->get_latest_remote_state().timestamp );
|
||||||
|
|
||||||
|
overlays.get_prediction_engine().set_local_frame_acked( network->get_sent_state_acked() );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -163,6 +173,8 @@ bool STMClient::process_user_input( int fd )
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( !network->shutdown_in_progress() ) {
|
if ( !network->shutdown_in_progress() ) {
|
||||||
|
overlays.get_prediction_engine().set_local_frame_sent( network->get_sent_state_last() );
|
||||||
|
|
||||||
for ( int i = 0; i < bytes_read; i++ ) {
|
for ( int i = 0; i < bytes_read; i++ ) {
|
||||||
char the_byte = buf[ i ];
|
char the_byte = buf[ i ];
|
||||||
|
|
||||||
@@ -232,6 +244,9 @@ bool STMClient::process_resize( void )
|
|||||||
i->state.act( &res );
|
i->state.act( &res );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* tell prediction engine */
|
||||||
|
overlays.get_prediction_engine().reset();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -17,8 +17,8 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const
|
|||||||
|| (f.get_window_title() != frame.last_frame.get_window_title()) ) {
|
|| (f.get_window_title() != frame.last_frame.get_window_title()) ) {
|
||||||
/* set window title */
|
/* set window title */
|
||||||
frame.append( "\033]0;" );
|
frame.append( "\033]0;" );
|
||||||
std::vector<wchar_t> window_title = f.get_window_title();
|
const std::deque<wchar_t> &window_title( f.get_window_title() );
|
||||||
for ( std::vector<wchar_t>::iterator i = window_title.begin();
|
for ( auto i = window_title.begin();
|
||||||
i != window_title.end();
|
i != window_title.end();
|
||||||
i++ ) {
|
i++ ) {
|
||||||
snprintf( tmp, 64, "%lc", *i );
|
snprintf( tmp, 64, "%lc", *i );
|
||||||
|
|||||||
@@ -406,3 +406,19 @@ void Row::reset( int background_color )
|
|||||||
i->reset( background_color );
|
i->reset( background_color );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Framebuffer::prefix_window_title( const std::deque<wchar_t> &s )
|
||||||
|
{
|
||||||
|
for ( auto i = s.rbegin(); i != s.rend(); i++ ) {
|
||||||
|
window_title.push_front( *i );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t Cell::debug_contents( void ) const
|
||||||
|
{
|
||||||
|
if ( contents.empty() ) {
|
||||||
|
return '_';
|
||||||
|
} else {
|
||||||
|
return contents.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ namespace Terminal {
|
|||||||
&& (width == x.width)
|
&& (width == x.width)
|
||||||
&& (renditions == x.renditions) );
|
&& (renditions == x.renditions) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wchar_t debug_contents( void ) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Row {
|
class Row {
|
||||||
@@ -179,7 +181,7 @@ namespace Terminal {
|
|||||||
class Framebuffer {
|
class Framebuffer {
|
||||||
private:
|
private:
|
||||||
std::deque<Row> rows;
|
std::deque<Row> rows;
|
||||||
std::vector<wchar_t> window_title;
|
std::deque<wchar_t> window_title;
|
||||||
|
|
||||||
Row newrow( void ) { return Row( ds.get_width(), ds.get_background_rendition() ); }
|
Row newrow( void ) { return Row( ds.get_width(), ds.get_background_rendition() ); }
|
||||||
|
|
||||||
@@ -243,8 +245,10 @@ namespace Terminal {
|
|||||||
void reset( void );
|
void reset( void );
|
||||||
void soft_reset( void );
|
void soft_reset( void );
|
||||||
|
|
||||||
void set_window_title( std::vector<wchar_t> s ) { window_title = s; }
|
void set_window_title( const std::deque<wchar_t> &s ) { window_title = s; }
|
||||||
std::vector<wchar_t> get_window_title( void ) const { return window_title; }
|
const std::deque<wchar_t> & get_window_title( void ) const { return window_title; }
|
||||||
|
|
||||||
|
void prefix_window_title( const std::deque<wchar_t> &s );
|
||||||
|
|
||||||
void resize( int s_width, int s_height );
|
void resize( int s_width, int s_height );
|
||||||
|
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ void Dispatcher::OSC_dispatch( const Parser::OSC_End *act, Framebuffer *fb )
|
|||||||
if ( OSC_string.size() >= 2 ) {
|
if ( OSC_string.size() >= 2 ) {
|
||||||
if ( (OSC_string[ 0 ] == L'0')
|
if ( (OSC_string[ 0 ] == L'0')
|
||||||
&& (OSC_string[ 1 ] == L';') ) {
|
&& (OSC_string[ 1 ] == L';') ) {
|
||||||
std::vector<wchar_t> newtitle = OSC_string;
|
std::deque<wchar_t> newtitle( OSC_string.begin(), OSC_string.end() );
|
||||||
newtitle.erase( newtitle.begin() );
|
newtitle.erase( newtitle.begin() );
|
||||||
newtitle.erase( newtitle.begin() );
|
newtitle.erase( newtitle.begin() );
|
||||||
fb->set_window_title( newtitle );
|
fb->set_window_title( newtitle );
|
||||||
|
|||||||
+400
-308
@@ -8,28 +8,41 @@
|
|||||||
|
|
||||||
using namespace Overlay;
|
using namespace Overlay;
|
||||||
|
|
||||||
Validity OverlayElement::get_validity( const Framebuffer & ) const
|
void ConditionalOverlayCell::apply( Framebuffer &fb, bool show_tentative, int row, bool flag ) const
|
||||||
{
|
{
|
||||||
return (timestamp() < expiration_time) ? Pending : IncorrectOrExpired;
|
if ( (!active)
|
||||||
}
|
|| (row >= fb.ds.get_height())
|
||||||
|
|
||||||
void OverlayCell::apply( Framebuffer &fb ) const
|
|
||||||
{
|
|
||||||
if ( (row >= fb.ds.get_height())
|
|
||||||
|| (col >= fb.ds.get_width()) ) {
|
|| (col >= fb.ds.get_width()) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !(*(fb.get_mutable_cell( row, col )) == replacement) ) {
|
if ( tentative && (!show_tentative) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fprintf( stderr, "APPLYING char %lc to (%d, %d)\n",
|
||||||
|
replacement.debug_contents(), row, col );
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( !(*(fb.get_cell( row, col )) == replacement) ) {
|
||||||
*(fb.get_mutable_cell( row, col )) = replacement;
|
*(fb.get_mutable_cell( row, col )) = replacement;
|
||||||
|
uint64_t now = timestamp();
|
||||||
|
if ( display_time >= now ) {
|
||||||
|
display_time = now;
|
||||||
|
}
|
||||||
if ( flag ) {
|
if ( flag ) {
|
||||||
fb.get_mutable_cell( row, col )->renditions.underlined = true;
|
fb.get_mutable_cell( row, col )->renditions.underlined = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Validity ConditionalOverlayCell::get_validity( const Framebuffer &fb ) const
|
Validity ConditionalOverlayCell::get_validity( const Framebuffer &fb, int row, uint64_t current_frame ) const
|
||||||
{
|
{
|
||||||
|
if ( !active ) {
|
||||||
|
return Inactive;
|
||||||
|
}
|
||||||
|
|
||||||
if ( (row >= fb.ds.get_height())
|
if ( (row >= fb.ds.get_height())
|
||||||
|| (col >= fb.ds.get_width()) ) {
|
|| (col >= fb.ds.get_width()) ) {
|
||||||
return IncorrectOrExpired;
|
return IncorrectOrExpired;
|
||||||
@@ -37,10 +50,16 @@ Validity ConditionalOverlayCell::get_validity( const Framebuffer &fb ) const
|
|||||||
|
|
||||||
const Cell ¤t = *( fb.get_cell( row, col ) );
|
const Cell ¤t = *( fb.get_cell( row, col ) );
|
||||||
|
|
||||||
if ( (timestamp() < expiration_time) && (current == original_contents) ) {
|
/* see if it hasn't been updated yet */
|
||||||
|
if ( (current_frame < expiration_frame) ) {
|
||||||
return Pending;
|
return Pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* special case deletion */
|
||||||
|
if ( current.contents.empty() && (replacement.contents.size() == 1) && (replacement.contents.front() == 0x20) ) {
|
||||||
|
return Correct;
|
||||||
|
}
|
||||||
|
|
||||||
if ( current == replacement ) {
|
if ( current == replacement ) {
|
||||||
return Correct;
|
return Correct;
|
||||||
} else {
|
} else {
|
||||||
@@ -48,224 +67,76 @@ Validity ConditionalOverlayCell::get_validity( const Framebuffer &fb ) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CursorMove::apply( Framebuffer &fb ) const
|
Validity ConditionalCursorMove::get_validity( const Framebuffer &fb, uint64_t current_frame ) const
|
||||||
{
|
{
|
||||||
assert( new_row < fb.ds.get_height() );
|
if ( !active ) {
|
||||||
assert( new_col < fb.ds.get_width() );
|
return Inactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (row >= fb.ds.get_height())
|
||||||
|
|| (col >= fb.ds.get_width()) ) {
|
||||||
|
fprintf( stderr, "Crazy cursor (%d,%d)!\n", row, col );
|
||||||
|
return IncorrectOrExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (fb.ds.get_cursor_col() == col)
|
||||||
|
&& (fb.ds.get_cursor_row() == row) ) {
|
||||||
|
return Correct;
|
||||||
|
} else if ( current_frame < expiration_frame ) {
|
||||||
|
return Pending;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fprintf( stderr, "Bad cursor in %d (i thought %d vs %d).\n", (int)current_frame,
|
||||||
|
col, fb.ds.get_cursor_col() );
|
||||||
|
return IncorrectOrExpired;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConditionalCursorMove::apply( Framebuffer &fb ) const
|
||||||
|
{
|
||||||
|
if ( !active ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int target_row = row;
|
||||||
|
int target_col = col;
|
||||||
|
|
||||||
|
if ( show_frozen_cursor ) {
|
||||||
|
target_row = frozen_row;
|
||||||
|
target_col = frozen_col;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( target_row < fb.ds.get_height() );
|
||||||
|
assert( target_col < fb.ds.get_width() );
|
||||||
assert( !fb.ds.origin_mode );
|
assert( !fb.ds.origin_mode );
|
||||||
|
|
||||||
fb.ds.move_row( new_row, false );
|
fb.ds.move_row( target_row, false );
|
||||||
fb.ds.move_col( new_col, false, false );
|
fb.ds.move_col( target_col, false, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
Validity ConditionalCursorMove::get_validity( const Framebuffer &fb ) const
|
|
||||||
{
|
|
||||||
if ( (new_row >= fb.ds.get_height())
|
|
||||||
|| (new_col >= fb.ds.get_width()) ) {
|
|
||||||
return IncorrectOrExpired;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( timestamp() < expiration_time ) {
|
|
||||||
return Pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( (fb.ds.get_cursor_col() == new_col)
|
|
||||||
&& (fb.ds.get_cursor_row() == new_row) ) {
|
|
||||||
return Correct;
|
|
||||||
} else {
|
|
||||||
return IncorrectOrExpired;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverlayEngine::clear( void )
|
|
||||||
{
|
|
||||||
for_each( elements.begin(), elements.end(), []( OverlayElement *x ){ delete x; } );
|
|
||||||
elements.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
OverlayEngine::~OverlayEngine()
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverlayEngine::apply( Framebuffer &fb ) const
|
|
||||||
{
|
|
||||||
for_each( elements.begin(), elements.end(),
|
|
||||||
[&fb]( OverlayElement *x ) { x->apply( fb ); } );
|
|
||||||
}
|
|
||||||
|
|
||||||
void PredictionEngine::cull( const Framebuffer &fb )
|
|
||||||
{
|
|
||||||
uint64_t now = timestamp();
|
|
||||||
|
|
||||||
auto i = elements.begin();
|
|
||||||
while ( i != elements.end() ) {
|
|
||||||
/* update echo timeout state */
|
|
||||||
if ( (typeid( ConditionalOverlayCell ) == typeid( **i ))
|
|
||||||
&& ((*i)->get_validity( fb ) == Correct) ) {
|
|
||||||
double R = now - (*i)->prediction_time;
|
|
||||||
if ( !RTT_hit ) { /* first measurement */
|
|
||||||
SRTT = R;
|
|
||||||
RTTVAR = R / 2;
|
|
||||||
RTT_hit = true;
|
|
||||||
} else {
|
|
||||||
const double alpha = 1.0 / 32.0;
|
|
||||||
const double beta = 1.0 / 16.0;
|
|
||||||
|
|
||||||
RTTVAR = (1 - beta) * RTTVAR + ( beta * fabs( SRTT - R ) );
|
|
||||||
SRTT = (1 - alpha) * SRTT + ( alpha * R );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eliminate predictions proven correct or incorrect */
|
|
||||||
if ( (*i)->get_validity( fb ) != Pending ) {
|
|
||||||
delete (*i);
|
|
||||||
i = elements.erase( i );
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( SRTT > 150 ) flagging = true; /* start underlining predicted chars */
|
|
||||||
if ( SRTT + 4 * RTTVAR < 100 ) flagging = false; /* use some hysterisis to avoid flapping */
|
|
||||||
}
|
|
||||||
|
|
||||||
OverlayCell::OverlayCell( uint64_t expiration_time, int s_row, int s_col, int background_color )
|
|
||||||
: OverlayElement( expiration_time ), row( s_row ), col( s_col ), replacement( background_color )
|
|
||||||
{}
|
|
||||||
|
|
||||||
CursorMove::CursorMove( uint64_t expiration_time, int s_new_row, int s_new_col )
|
|
||||||
: OverlayElement( expiration_time ), new_row( s_new_row ), new_col( s_new_col )
|
|
||||||
{}
|
|
||||||
|
|
||||||
NotificationEngine::NotificationEngine()
|
NotificationEngine::NotificationEngine()
|
||||||
: needs_render( true ),
|
: last_word_from_server( timestamp() ),
|
||||||
last_word( timestamp() ),
|
|
||||||
last_render( 0 ),
|
|
||||||
message(),
|
message(),
|
||||||
message_expiration( 0 )
|
message_expiration( -1 )
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void NotificationEngine::server_ping( uint64_t s_last_word )
|
|
||||||
{
|
|
||||||
if ( s_last_word - last_word > 4000 ) {
|
|
||||||
needs_render = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
last_word = s_last_word;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NotificationEngine::set_notification_string( const wstring s_message )
|
|
||||||
{
|
|
||||||
message = s_message;
|
|
||||||
message_expiration = timestamp() + 1100;
|
|
||||||
needs_render = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NotificationEngine::render_notification( void )
|
|
||||||
{
|
|
||||||
uint64_t now = timestamp();
|
|
||||||
|
|
||||||
if ( (now - last_render < 250) && (!needs_render) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
needs_render = false;
|
|
||||||
last_render = now;
|
|
||||||
|
|
||||||
clear();
|
|
||||||
|
|
||||||
/* determine string to draw */
|
|
||||||
if ( now >= message_expiration ) {
|
|
||||||
message.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool time_expired = now - last_word > 5000;
|
|
||||||
|
|
||||||
wchar_t tmp[ 128 ];
|
|
||||||
|
|
||||||
if ( message.empty() && (!time_expired) ) {
|
|
||||||
return;
|
|
||||||
} else if ( message.empty() && time_expired ) {
|
|
||||||
swprintf( tmp, 128, L"[stm] Last contact %.0f seconds ago. [To quit: Ctrl-^ .]", (double)(now - last_word) / 1000.0 );
|
|
||||||
} else if ( (!message.empty()) && (!time_expired) ) {
|
|
||||||
swprintf( tmp, 128, L"[stm] %ls [To quit: Ctrl-^ .]", message.c_str() );
|
|
||||||
} else {
|
|
||||||
swprintf( tmp, 128, L"[stm] %ls (%.0f s without contact.) [To quit: Ctrl-^ .]", message.c_str(),
|
|
||||||
(double)(now - last_word) / 1000.0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
wstring string_to_draw( tmp );
|
|
||||||
|
|
||||||
int overlay_col = 0;
|
|
||||||
bool dirty = false;
|
|
||||||
OverlayCell template_cell( now + 1100, 0 /* row */, -1 /* col */, 0 /* background_color */ );
|
|
||||||
|
|
||||||
template_cell.replacement.renditions.bold = true;
|
|
||||||
template_cell.replacement.renditions.foreground_color = 37;
|
|
||||||
template_cell.replacement.renditions.background_color = 44;
|
|
||||||
|
|
||||||
OverlayCell current( template_cell );
|
|
||||||
|
|
||||||
for ( wstring::const_iterator i = string_to_draw.begin(); i != string_to_draw.end(); i++ ) {
|
|
||||||
wchar_t ch = *i;
|
|
||||||
int chwidth = ch == L'\0' ? -1 : wcwidth( ch );
|
|
||||||
|
|
||||||
switch ( chwidth ) {
|
|
||||||
case 1: /* normal character */
|
|
||||||
case 2: /* wide character */
|
|
||||||
/* finish current cell */
|
|
||||||
if ( dirty ) {
|
|
||||||
elements.push_back( new OverlayCell( current ) );
|
|
||||||
dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* initialize new cell */
|
|
||||||
current = template_cell;
|
|
||||||
current.col = overlay_col;
|
|
||||||
current.replacement.contents.push_back( ch );
|
|
||||||
current.replacement.width = chwidth;
|
|
||||||
overlay_col += chwidth;
|
|
||||||
dirty = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0: /* combining character */
|
|
||||||
if ( current.replacement.contents.empty() ) {
|
|
||||||
/* string starts with combining character?? */
|
|
||||||
/* emulate fallback rendering */
|
|
||||||
current = template_cell;
|
|
||||||
current.col = overlay_col;
|
|
||||||
current.replacement.contents.push_back( 0xA0 ); /* no-break space */
|
|
||||||
current.replacement.width = 1;
|
|
||||||
overlay_col++;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
current.replacement.contents.push_back( ch );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case -1:
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert( false );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( dirty ) {
|
|
||||||
elements.push_back( new OverlayCell( current ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NotificationEngine::apply( Framebuffer &fb ) const
|
void NotificationEngine::apply( Framebuffer &fb ) const
|
||||||
{
|
{
|
||||||
if ( elements.empty() ) {
|
uint64_t now = timestamp();
|
||||||
|
|
||||||
|
bool time_expired = need_countup( now );
|
||||||
|
|
||||||
|
if ( message.empty() && !time_expired ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert( fb.ds.get_width() > 0 );
|
assert( fb.ds.get_width() > 0 );
|
||||||
assert( fb.ds.get_height() > 0 );
|
assert( fb.ds.get_height() > 0 );
|
||||||
|
|
||||||
|
/* hide cursor if necessary */
|
||||||
|
if ( fb.ds.get_cursor_row() == 0 ) {
|
||||||
|
fb.ds.cursor_visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
/* draw bar across top of screen */
|
/* draw bar across top of screen */
|
||||||
Cell notification_bar( 0 );
|
Cell notification_bar( 0 );
|
||||||
@@ -277,135 +148,356 @@ void NotificationEngine::apply( Framebuffer &fb ) const
|
|||||||
*(fb.get_mutable_cell( 0, i )) = notification_bar;
|
*(fb.get_mutable_cell( 0, i )) = notification_bar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hide cursor if necessary */
|
/* write message */
|
||||||
if ( fb.ds.get_cursor_row() == 0 ) {
|
wchar_t tmp[ 128 ];
|
||||||
fb.ds.cursor_visible = false;
|
|
||||||
|
if ( message.empty() && (!time_expired) ) {
|
||||||
|
return;
|
||||||
|
} else if ( message.empty() && time_expired ) {
|
||||||
|
swprintf( tmp, 128, L"mosh: Last contact %.0f seconds ago. [To quit: Ctrl-^ .]", (double)(now - last_word_from_server) / 1000.0 );
|
||||||
|
} else if ( (!message.empty()) && (!time_expired) ) {
|
||||||
|
swprintf( tmp, 128, L"mosh: %ls [To quit: Ctrl-^ .]", message.c_str() );
|
||||||
|
} else {
|
||||||
|
swprintf( tmp, 128, L"mosh: %ls (%.0f s without contact.) [To quit: Ctrl-^ .]", message.c_str(),
|
||||||
|
(double)(now - last_word_from_server) / 1000.0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayEngine::apply( fb );
|
wstring string_to_draw( tmp );
|
||||||
|
|
||||||
|
int overlay_col = 0;
|
||||||
|
|
||||||
|
Cell *combining_cell = fb.get_mutable_cell( 0, 0 );
|
||||||
|
|
||||||
|
/* We unfortunately duplicate the terminal's logic for how to render a Unicode sequence into graphemes */
|
||||||
|
for ( wstring::const_iterator i = string_to_draw.begin(); i != string_to_draw.end(); i++ ) {
|
||||||
|
if ( overlay_col >= fb.ds.get_width() ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t ch = *i;
|
||||||
|
int chwidth = ch == L'\0' ? -1 : wcwidth( ch );
|
||||||
|
Cell *this_cell = nullptr;
|
||||||
|
|
||||||
|
switch ( chwidth ) {
|
||||||
|
case 1: /* normal character */
|
||||||
|
case 2: /* wide character */
|
||||||
|
this_cell = fb.get_mutable_cell( 0, overlay_col );
|
||||||
|
fb.reset_cell( this_cell );
|
||||||
|
this_cell->renditions.bold = true;
|
||||||
|
this_cell->renditions.foreground_color = 37;
|
||||||
|
this_cell->renditions.background_color = 44;
|
||||||
|
|
||||||
|
this_cell->contents.push_back( ch );
|
||||||
|
this_cell->width = chwidth;
|
||||||
|
combining_cell = this_cell;
|
||||||
|
|
||||||
|
overlay_col += chwidth;
|
||||||
|
break;
|
||||||
|
case 0: /* combining character */
|
||||||
|
if ( !combining_cell ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( combining_cell->contents.size() == 0 ) {
|
||||||
|
assert( combining_cell->width == 1 );
|
||||||
|
combining_cell->fallback = true;
|
||||||
|
overlay_col++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( combining_cell->contents.size() < 16 ) {
|
||||||
|
combining_cell->contents.push_back( ch );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case -1: /* unprintable character */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationEngine::adjust_message( void )
|
||||||
|
{
|
||||||
|
if ( timestamp() >= message_expiration ) {
|
||||||
|
message.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayManager::apply( Framebuffer &fb )
|
void OverlayManager::apply( Framebuffer &fb )
|
||||||
{
|
{
|
||||||
predictions.calculate_score( fb );
|
|
||||||
|
|
||||||
/* eliminate predictions proven correct or incorrect and update echo timers */
|
|
||||||
predictions.cull( fb );
|
predictions.cull( fb );
|
||||||
|
predictions.apply( fb );
|
||||||
if ( predictions.get_score() > 3 ) {
|
notifications.adjust_message();
|
||||||
predictions.apply( fb );
|
|
||||||
}
|
|
||||||
|
|
||||||
notifications.apply( fb );
|
notifications.apply( fb );
|
||||||
|
title.apply( fb );
|
||||||
}
|
}
|
||||||
|
|
||||||
void PredictionEngine::calculate_score( const Framebuffer &fb )
|
int OverlayManager::wait_time( void )
|
||||||
{
|
{
|
||||||
for ( auto i = begin(); i != end(); i++ ) {
|
uint64_t next_expiry = INT_MAX;
|
||||||
switch( (*i)->get_validity( fb ) ) {
|
|
||||||
case Pending:
|
uint64_t message_delay = notifications.get_message_expiration() - timestamp();
|
||||||
break;
|
|
||||||
case Correct:
|
if ( message_delay < next_expiry ) {
|
||||||
score++;
|
next_expiry = message_delay;
|
||||||
break;
|
}
|
||||||
case IncorrectOrExpired:
|
|
||||||
score = 0;
|
if ( notifications.need_countup( timestamp() ) && ( next_expiry > 1000 ) ) {
|
||||||
clear();
|
next_expiry = 1000;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
return next_expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleEngine::set_prefix( const wstring s )
|
||||||
|
{
|
||||||
|
prefix = deque<wchar_t>( s.begin(), s.end() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConditionalOverlayRow::apply( Framebuffer &fb, bool show_tentative, bool flag ) const
|
||||||
|
{
|
||||||
|
for_each( overlay_cells.begin(), overlay_cells.end(), [&]( const ConditionalOverlayCell &x ) { x.apply( fb, show_tentative, row_num, flag ); } );
|
||||||
|
}
|
||||||
|
|
||||||
|
void PredictionEngine::apply( Framebuffer &fb ) const
|
||||||
|
{
|
||||||
|
if ( (score > 0) || cursor.show_frozen_cursor ) {
|
||||||
|
cursor.apply( fb );
|
||||||
|
}
|
||||||
|
for_each( overlays.begin(), overlays.end(), [&]( const ConditionalOverlayRow &x ){ x.apply( fb, score > 0, flagging ); } );
|
||||||
|
}
|
||||||
|
|
||||||
|
void PredictionEngine::reset( void )
|
||||||
|
{
|
||||||
|
overlays.clear();
|
||||||
|
cursor.reset();
|
||||||
|
become_tentative();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PredictionEngine::cull( const Framebuffer &fb )
|
||||||
|
{
|
||||||
|
if ( score > 0 ) {
|
||||||
|
cursor.thaw();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t now = timestamp();
|
||||||
|
|
||||||
|
/* don't increment score just for correct cursor position */
|
||||||
|
switch ( cursor.get_validity( fb, local_frame_acked) ) {
|
||||||
|
case IncorrectOrExpired:
|
||||||
|
cursor.reset();
|
||||||
|
become_tentative();
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t max_delay = 0;
|
||||||
|
|
||||||
|
auto i = overlays.begin();
|
||||||
|
while ( i != overlays.end() ) {
|
||||||
|
auto inext = i;
|
||||||
|
inext++;
|
||||||
|
if ( (i->row_num < 0) || (i->row_num >= fb.ds.get_height()) ) {
|
||||||
|
overlays.erase( i );
|
||||||
|
i = inext;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for ( auto j = i->overlay_cells.begin(); j != i->overlay_cells.end(); j++ ) {
|
||||||
|
switch ( j->get_validity( fb, i->row_num, local_frame_acked ) ) {
|
||||||
|
case IncorrectOrExpired:
|
||||||
|
if ( j->tentative ) {
|
||||||
|
fprintf( stderr, "Bad tentative prediction in row %d, col %d (thought %lc, was %lc)\n",
|
||||||
|
i->row_num, j->col,
|
||||||
|
j->replacement.debug_contents(), fb.get_cell( i->row_num, j->col )->debug_contents() );
|
||||||
|
j->reset();
|
||||||
|
become_tentative();
|
||||||
|
if ( j->display_time != uint64_t(-1) ) {
|
||||||
|
fprintf( stderr, "TIMING %ld - %ld (TENT)\n", time(NULL), now - j->display_time );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf( stderr, "[%d=>%d] (score=%d) Killing prediction in row %d, col %d (thought %lc, was %lc)\n",
|
||||||
|
(int)local_frame_acked, (int)j->expiration_frame,
|
||||||
|
score,
|
||||||
|
i->row_num, j->col,
|
||||||
|
j->replacement.debug_contents(), fb.get_cell( i->row_num, j->col )->debug_contents() );
|
||||||
|
reset();
|
||||||
|
if ( j->display_time != uint64_t(-1) ) {
|
||||||
|
fprintf( stderr, "TIMING %ld - %ld\n", time(NULL), now - j->display_time );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Correct:
|
||||||
|
if ( j->display_time != uint64_t(-1) ) {
|
||||||
|
fprintf( stderr, "TIMING %ld + %ld\n", now, now - j->display_time );
|
||||||
|
}
|
||||||
|
|
||||||
|
j->reset();
|
||||||
|
if ( j->prediction_time > prediction_checkpoint ) {
|
||||||
|
score++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Pending:
|
||||||
|
max_delay = max( max_delay, now - j->prediction_time );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i = inext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( max_delay > 100 ) {
|
||||||
|
flagging = true;
|
||||||
|
} else if ( max_delay < 50 ) {
|
||||||
|
flagging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionalOverlayRow & PredictionEngine::get_or_make_row( int row_num, int num_cols )
|
||||||
|
{
|
||||||
|
auto it = find_if( overlays.begin(), overlays.end(),
|
||||||
|
[&]( const ConditionalOverlayRow &x ) { return x.row_num == row_num; } );
|
||||||
|
|
||||||
|
if ( it != overlays.end() ) {
|
||||||
|
return *it;
|
||||||
|
} else {
|
||||||
|
/* make row */
|
||||||
|
ConditionalOverlayRow r( row_num );
|
||||||
|
r.overlay_cells.reserve( num_cols );
|
||||||
|
for ( int i = 0; i < num_cols; i++ ) {
|
||||||
|
r.overlay_cells.push_back( ConditionalOverlayCell( i ) );
|
||||||
|
assert( r.overlay_cells[ i ].col == i );
|
||||||
|
}
|
||||||
|
overlays.push_back( r );
|
||||||
|
return overlays.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
|
||||||
{
|
{
|
||||||
|
list<Parser::Action *> actions( parser.input( the_byte ) );
|
||||||
|
|
||||||
uint64_t now = timestamp();
|
uint64_t now = timestamp();
|
||||||
|
|
||||||
if ( elements.empty() ) {
|
for ( auto it = actions.begin(); it != actions.end(); it++ ) {
|
||||||
/* starting from scratch */
|
Parser::Action *act = *it;
|
||||||
|
|
||||||
elements.push_front( new ConditionalCursorMove( now + prediction_len(),
|
if ( typeid( *act ) == typeid( Parser::Print ) ) {
|
||||||
fb.ds.get_cursor_row(),
|
/* make new prediction */
|
||||||
fb.ds.get_cursor_col() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
assert( typeid( ConditionalCursorMove ) == typeid( *elements.front() ) );
|
wchar_t ch = act->ch;
|
||||||
ConditionalCursorMove *ccm = static_cast<ConditionalCursorMove *>( elements.front() );
|
/* XXX handle wide characters */
|
||||||
|
|
||||||
if ( (ccm->new_row >= fb.ds.get_height()) || (ccm->new_col >= fb.ds.get_width()) ) {
|
if ( !cursor.active ) {
|
||||||
return;
|
/* initialize new cursor prediction */
|
||||||
}
|
cursor.row = fb.ds.get_cursor_row();
|
||||||
|
cursor.col = fb.ds.get_cursor_col();
|
||||||
|
cursor.active = true;
|
||||||
|
cursor.prediction_time = now;
|
||||||
|
cursor.expiration_frame = local_frame_sent + 1;
|
||||||
|
}
|
||||||
|
|
||||||
if ( (the_byte >= 0x20) && (the_byte <= 0x7E) && (ccm->new_col < fb.ds.get_width() - 2) ) {
|
if ( ch == 0x7f ) { /* backspace */
|
||||||
/* XXX need to kill existing prediction if present */
|
// fprintf( stderr, "Backspace.\n" );
|
||||||
|
if ( cursor.col > 0 ) {
|
||||||
|
cursor.col--;
|
||||||
|
|
||||||
const Cell *existing_cell = fb.get_cell( ccm->new_row, ccm->new_col );
|
cursor.prediction_time = now;
|
||||||
|
cursor.expiration_frame = local_frame_sent + 1;
|
||||||
|
|
||||||
if ( (existing_cell->contents.size() == 1)
|
ConditionalOverlayCell &cell = get_or_make_row( cursor.row, fb.ds.get_width() ).overlay_cells[ cursor.col ];
|
||||||
&& (existing_cell->contents.front() == the_byte) ) {
|
cell.active = true;
|
||||||
/* do nothing */
|
cell.tentative = (score <= 0);
|
||||||
} else if ( (existing_cell->contents.empty() || ((existing_cell->contents.size() == 1)
|
cell.prediction_time = now;
|
||||||
&& ( (existing_cell->contents.front() == 0x20)
|
cell.expiration_frame = local_frame_sent + 1;
|
||||||
|| (existing_cell->contents.front() == 0xA0) )))
|
cell.replacement.renditions = fb.ds.get_renditions();
|
||||||
&& ( the_byte == 0x20 ) ) {
|
cell.replacement.contents.clear();
|
||||||
/* don't attempt to change existing blank or space cells if user has typed space */
|
cell.replacement.contents.push_back( 0x20 );
|
||||||
} else {
|
cell.display_time = -1;
|
||||||
Renditions current_renditions = fb.ds.get_renditions();
|
}
|
||||||
|
} else if ( (ch < 0x20) || (ch > 0x7E) ) {
|
||||||
|
/* unknown print */
|
||||||
|
become_tentative();
|
||||||
|
} else {
|
||||||
|
/* don't attempt to change existing blank or space cells if user has typed space */
|
||||||
|
const Cell *existing_cell = fb.get_cell( cursor.row, cursor.col );
|
||||||
|
if ( ( existing_cell->contents.empty() || ((existing_cell->contents.size() == 1)
|
||||||
|
&& ( (existing_cell->contents.front() == 0x20)
|
||||||
|
|| (existing_cell->contents.front() == 0xA0) )))
|
||||||
|
&& ( ch == 0x20 ) ) {
|
||||||
|
/* do nothing */
|
||||||
|
} else {
|
||||||
|
assert( cursor.row >= 0 );
|
||||||
|
assert( cursor.col >= 0 );
|
||||||
|
assert( cursor.row < fb.ds.get_height() );
|
||||||
|
assert( cursor.col < fb.ds.get_width() );
|
||||||
|
|
||||||
ConditionalOverlayCell *coc = new ConditionalOverlayCell( now + prediction_len(),
|
if ( cursor.col + 1 >= fb.ds.get_width() ) {
|
||||||
ccm->new_row, ccm->new_col,
|
/* prediction in the last column is tricky */
|
||||||
current_renditions.background_color,
|
/* e.g., emacs will show wrap character, shell will just put the character there */
|
||||||
*existing_cell );
|
become_tentative();
|
||||||
|
cursor.freeze();
|
||||||
|
}
|
||||||
|
|
||||||
coc->replacement = *existing_cell;
|
ConditionalOverlayCell &cell = get_or_make_row( cursor.row, fb.ds.get_width() ).overlay_cells[ cursor.col ];
|
||||||
coc->replacement.renditions = current_renditions;
|
cell.active = true;
|
||||||
coc->replacement.contents.clear();
|
cell.tentative = (score <= 0);
|
||||||
coc->replacement.contents.push_back( the_byte );
|
cell.prediction_time = now;
|
||||||
coc->flag = flagging;
|
cell.expiration_frame = local_frame_sent + 1;
|
||||||
|
cell.replacement.renditions = fb.ds.get_renditions();
|
||||||
|
cell.replacement.contents.clear();
|
||||||
|
cell.replacement.contents.push_back( ch );
|
||||||
|
cell.display_time = -1;
|
||||||
|
|
||||||
elements.push_back( coc );
|
/*
|
||||||
|
fprintf( stderr, "[%d=>%d] Predicting %lc in row %d, col %d\n",
|
||||||
|
(int)local_frame_acked, (int)cell.expiration_frame,
|
||||||
|
ch, cursor.row, cursor.col );
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.prediction_time = now;
|
||||||
|
cursor.expiration_frame = local_frame_sent + 1;
|
||||||
|
cursor.col++;
|
||||||
|
|
||||||
|
/* do we need to wrap? */
|
||||||
|
if ( cursor.col >= fb.ds.get_width() ) {
|
||||||
|
become_tentative();
|
||||||
|
cursor.col--;
|
||||||
|
cursor.freeze();
|
||||||
|
cursor.col = 0;
|
||||||
|
if ( cursor.row == fb.ds.get_height() - 1 ) {
|
||||||
|
for ( auto i = overlays.begin(); i != overlays.end(); i++ ) {
|
||||||
|
i->row_num--;
|
||||||
|
for ( auto j = i->overlay_cells.begin(); j != i->overlay_cells.end(); j++ ) {
|
||||||
|
if ( j->active ) {
|
||||||
|
// j->tentative = (score <= 0);
|
||||||
|
j->prediction_time = now;
|
||||||
|
j->expiration_frame = local_frame_sent + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cursor.row++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ( typeid( *act ) == typeid( Parser::Execute ) ) {
|
||||||
|
become_tentative();
|
||||||
|
cursor.freeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
ccm->new_col++;
|
delete act;
|
||||||
ccm->expiration_time = now + prediction_len();
|
|
||||||
} else {
|
|
||||||
clear();
|
|
||||||
score = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int OverlayManager::wait_time( void )
|
void PredictionEngine::become_tentative( void )
|
||||||
{
|
{
|
||||||
uint64_t now = timestamp();
|
prediction_checkpoint = timestamp();
|
||||||
|
score = 0;
|
||||||
uint64_t next_expiry = uint64_t( -1 );
|
|
||||||
|
|
||||||
for ( auto i = notifications.begin(); i != notifications.end(); i++ ) {
|
|
||||||
if ( (*i)->expiration_time < next_expiry ) {
|
|
||||||
next_expiry = (*i)->expiration_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ( auto i = predictions.begin(); i != predictions.end(); i++ ) {
|
|
||||||
if ( (*i)->expiration_time < next_expiry ) {
|
|
||||||
next_expiry = (*i)->expiration_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret = next_expiry - now;
|
|
||||||
if ( ret < 0 ) {
|
|
||||||
return INT_MAX;
|
|
||||||
} else {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int PredictionEngine::prediction_len( void )
|
|
||||||
{
|
|
||||||
uint64_t RTO = lrint( ceil( 1.5 * SRTT + 8 * RTTVAR ) );
|
|
||||||
if ( RTO < 20 ) {
|
|
||||||
RTO = 20;
|
|
||||||
} else if ( RTO > 4000 ) {
|
|
||||||
RTO = 4000;
|
|
||||||
}
|
|
||||||
return RTO;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-75
@@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
#include "terminalframebuffer.hpp"
|
#include "terminalframebuffer.hpp"
|
||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
|
#include "parser.hpp"
|
||||||
|
|
||||||
#include <list>
|
#include <vector>
|
||||||
|
|
||||||
namespace Overlay {
|
namespace Overlay {
|
||||||
using namespace Terminal;
|
using namespace Terminal;
|
||||||
@@ -14,118 +15,142 @@ namespace Overlay {
|
|||||||
enum Validity {
|
enum Validity {
|
||||||
Pending,
|
Pending,
|
||||||
Correct,
|
Correct,
|
||||||
IncorrectOrExpired
|
IncorrectOrExpired,
|
||||||
|
Inactive
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The individual elements of an overlay -- cursor movements and replaced cells */
|
class ConditionalOverlay {
|
||||||
class OverlayElement {
|
|
||||||
public:
|
public:
|
||||||
uint64_t prediction_time, expiration_time;
|
uint64_t prediction_time, expiration_frame;
|
||||||
bool flag; /* whether to bold for the user */
|
int col;
|
||||||
|
bool active; /* represents a prediction at all */
|
||||||
|
bool tentative; /* whether to hide when score < 0 */
|
||||||
|
|
||||||
virtual void apply( Framebuffer &fb ) const = 0;
|
ConditionalOverlay( uint64_t s_exp, int s_col )
|
||||||
virtual Validity get_validity( const Framebuffer & ) const;
|
: prediction_time( timestamp() ),
|
||||||
|
expiration_frame( s_exp ), col( s_col ),
|
||||||
|
active( false ),
|
||||||
|
tentative( false )
|
||||||
|
{}
|
||||||
|
|
||||||
OverlayElement( uint64_t s_expiration_time ) : prediction_time( timestamp() ),
|
virtual ~ConditionalOverlay() {}
|
||||||
expiration_time( s_expiration_time ),
|
|
||||||
flag( false ) {}
|
|
||||||
virtual ~OverlayElement() {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class OverlayCell : public OverlayElement {
|
class ConditionalCursorMove : public ConditionalOverlay {
|
||||||
|
public:
|
||||||
|
int row;
|
||||||
|
|
||||||
|
int frozen_col, frozen_row; /* after a control character */
|
||||||
|
|
||||||
|
bool show_frozen_cursor;
|
||||||
|
|
||||||
|
void apply( Framebuffer &fb ) const;
|
||||||
|
|
||||||
|
Validity get_validity( const Framebuffer &fb, uint64_t current_frame ) const;
|
||||||
|
|
||||||
|
ConditionalCursorMove( uint64_t s_exp, int s_row, int s_col )
|
||||||
|
: ConditionalOverlay( s_exp, s_col ), row( s_row ), frozen_col( -1 ), frozen_row( -1 ),
|
||||||
|
show_frozen_cursor( false )
|
||||||
|
{}
|
||||||
|
|
||||||
|
void freeze( void ) { if ( show_frozen_cursor ) { return; } frozen_col = col; frozen_row = row; show_frozen_cursor = true; }
|
||||||
|
void thaw( void ) { show_frozen_cursor = false; }
|
||||||
|
void reset( void ) { active = false; thaw(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConditionalOverlayCell : public ConditionalOverlay {
|
||||||
public:
|
public:
|
||||||
int row, col;
|
|
||||||
Cell replacement;
|
Cell replacement;
|
||||||
|
|
||||||
OverlayCell( uint64_t expiration_time, int s_row, int s_col, int background_color );
|
mutable uint64_t display_time;
|
||||||
void apply( Framebuffer &fb ) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ConditionalOverlayCell : public OverlayCell {
|
void apply( Framebuffer &fb, bool show_tentative, int row, bool flag ) const;
|
||||||
public:
|
Validity get_validity( const Framebuffer &fb, int row, uint64_t current_frame ) const;
|
||||||
Cell original_contents;
|
|
||||||
|
|
||||||
Validity get_validity( const Framebuffer &fb ) const;
|
ConditionalOverlayCell( int s_col )
|
||||||
|
: ConditionalOverlay( 0, s_col ),
|
||||||
ConditionalOverlayCell( uint64_t expiration_time, int s_row, int s_col, int background_color,
|
replacement( 0 ),
|
||||||
Cell s_original_contents )
|
display_time( -1 )
|
||||||
: OverlayCell( expiration_time, s_row, s_col, background_color ),
|
|
||||||
original_contents( s_original_contents )
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
void reset( void ) { active = false; display_time = -1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class CursorMove : public OverlayElement {
|
class ConditionalOverlayRow {
|
||||||
public:
|
public:
|
||||||
int new_row, new_col;
|
int row_num;
|
||||||
|
vector<ConditionalOverlayCell> overlay_cells;
|
||||||
|
|
||||||
void apply( Framebuffer &fb ) const;
|
void apply( Framebuffer &fb, bool show_tentative, bool flag ) const;
|
||||||
|
|
||||||
CursorMove( uint64_t expiration_time, int s_new_row, int s_new_col );
|
ConditionalOverlayRow( int s_row_num ) : row_num( s_row_num ), overlay_cells() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConditionalCursorMove : public CursorMove {
|
/* the various overlays */
|
||||||
public:
|
class NotificationEngine {
|
||||||
Validity get_validity( const Framebuffer &fb ) const;
|
|
||||||
|
|
||||||
ConditionalCursorMove( uint64_t expiration_time, int s_new_row, int s_new_col )
|
|
||||||
: CursorMove( expiration_time, s_new_row, s_new_col )
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* the various overlays -- some predictive and some for local notifications */
|
|
||||||
class OverlayEngine {
|
|
||||||
protected:
|
|
||||||
list<OverlayElement *> elements;
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual void apply( Framebuffer &fb ) const;
|
|
||||||
void clear( void );
|
|
||||||
|
|
||||||
typename list<OverlayElement *>::const_iterator begin( void ) { return elements.begin(); }
|
|
||||||
typename list<OverlayElement *>::const_iterator end( void ) { return elements.end(); }
|
|
||||||
|
|
||||||
OverlayEngine() : elements() {}
|
|
||||||
virtual ~OverlayEngine();
|
|
||||||
};
|
|
||||||
|
|
||||||
class NotificationEngine : public OverlayEngine {
|
|
||||||
private:
|
private:
|
||||||
bool needs_render;
|
uint64_t last_word_from_server;
|
||||||
|
|
||||||
uint64_t last_word;
|
|
||||||
uint64_t last_render;
|
|
||||||
|
|
||||||
wstring message;
|
wstring message;
|
||||||
uint64_t message_expiration;
|
uint64_t message_expiration;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
bool need_countup( uint64_t ts ) const { return ts - last_word_from_server > 4500; }
|
||||||
|
void adjust_message( void );
|
||||||
void apply( Framebuffer &fb ) const;
|
void apply( Framebuffer &fb ) const;
|
||||||
void set_notification_string( const wstring s_message );
|
void set_notification_string( const wstring s_message ) { message = s_message; message_expiration = timestamp() + 1000; }
|
||||||
const wstring &get_notification_string( void ) { return message; }
|
const wstring &get_notification_string( void ) const { return message; }
|
||||||
void server_ping( uint64_t s_last_word );
|
void server_heard( uint64_t s_last_word ) { last_word_from_server = s_last_word; }
|
||||||
void render_notification( void );
|
uint64_t get_message_expiration( void ) const { return message_expiration; }
|
||||||
|
|
||||||
NotificationEngine();
|
NotificationEngine();
|
||||||
};
|
};
|
||||||
|
|
||||||
class PredictionEngine : public OverlayEngine {
|
class PredictionEngine {
|
||||||
private:
|
private:
|
||||||
|
Parser::UTF8Parser parser;
|
||||||
|
|
||||||
|
list<ConditionalOverlayRow> overlays;
|
||||||
|
|
||||||
|
ConditionalCursorMove cursor;
|
||||||
|
|
||||||
int score;
|
int score;
|
||||||
|
|
||||||
/* use the TCP timeout algorithm to measure appropriate echo prediction timeout */
|
uint64_t local_frame_sent, local_frame_acked;
|
||||||
bool RTT_hit;
|
|
||||||
double SRTT, RTTVAR;
|
ConditionalOverlayRow & get_or_make_row( int row_num, int num_cols );
|
||||||
|
|
||||||
|
uint64_t prediction_checkpoint;
|
||||||
|
|
||||||
|
void become_tentative( void );
|
||||||
|
|
||||||
bool flagging;
|
bool flagging;
|
||||||
int prediction_len( void );
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void cull( const Framebuffer &fb );
|
void apply( Framebuffer &fb ) const;
|
||||||
void new_user_byte( char the_byte, const Framebuffer &fb );
|
void new_user_byte( char the_byte, const Framebuffer &fb );
|
||||||
void calculate_score( const Framebuffer &fb );
|
void cull( const Framebuffer &fb );
|
||||||
|
|
||||||
PredictionEngine() : score( 0 ), RTT_hit( false ), SRTT( 1000 ), RTTVAR( 500 ), flagging( false ) {}
|
void reset( void );
|
||||||
|
|
||||||
int get_score( void ) { return score; }
|
void set_local_frame_sent( uint64_t x ) { local_frame_sent = x; }
|
||||||
|
void set_local_frame_acked( uint64_t x ) { local_frame_acked = x; }
|
||||||
|
|
||||||
|
PredictionEngine( void ) : parser(), overlays(), cursor( 0, 0, 0 ), score( 0 ),
|
||||||
|
local_frame_sent( 0 ), local_frame_acked( 0 ),
|
||||||
|
prediction_checkpoint( timestamp() ), flagging( false )
|
||||||
|
{
|
||||||
|
become_tentative();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TitleEngine {
|
||||||
|
private:
|
||||||
|
deque<wchar_t> prefix;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void apply( Framebuffer &fb ) const { fb.prefix_window_title( prefix ); }
|
||||||
|
void set_prefix( const wstring s );
|
||||||
|
TitleEngine() : prefix() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* the overlay manager */
|
/* the overlay manager */
|
||||||
@@ -133,6 +158,7 @@ namespace Overlay {
|
|||||||
private:
|
private:
|
||||||
NotificationEngine notifications;
|
NotificationEngine notifications;
|
||||||
PredictionEngine predictions;
|
PredictionEngine predictions;
|
||||||
|
TitleEngine title;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void apply( Framebuffer &fb );
|
void apply( Framebuffer &fb );
|
||||||
@@ -140,7 +166,9 @@ namespace Overlay {
|
|||||||
NotificationEngine & get_notification_engine( void ) { return notifications; }
|
NotificationEngine & get_notification_engine( void ) { return notifications; }
|
||||||
PredictionEngine & get_prediction_engine( void ) { return predictions; }
|
PredictionEngine & get_prediction_engine( void ) { return predictions; }
|
||||||
|
|
||||||
OverlayManager() : notifications(), predictions() {}
|
void set_title_prefix( const wstring s ) { title.set_prefix( s ); }
|
||||||
|
|
||||||
|
OverlayManager() : notifications(), predictions(), title() {}
|
||||||
|
|
||||||
int wait_time( void );
|
int wait_time( void );
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ namespace Network {
|
|||||||
bool get_shutdown_in_progress( void ) { return shutdown_in_progress; }
|
bool get_shutdown_in_progress( void ) { return shutdown_in_progress; }
|
||||||
bool get_shutdown_acknowledged( void ) { return sent_states.front().num == uint64_t(-1); }
|
bool get_shutdown_acknowledged( void ) { return sent_states.front().num == uint64_t(-1); }
|
||||||
bool get_counterparty_shutdown_acknowledged( void ) { return fragmenter.last_ack_sent() == uint64_t(-1); }
|
bool get_counterparty_shutdown_acknowledged( void ) { return fragmenter.last_ack_sent() == uint64_t(-1); }
|
||||||
|
uint64_t get_sent_state_acked( void ) { return sent_states.front().num; }
|
||||||
|
uint64_t get_sent_state_last( void ) { return sent_states.back().num; }
|
||||||
|
|
||||||
bool shutdown_ack_timed_out( void );
|
bool shutdown_ack_timed_out( void );
|
||||||
|
|
||||||
void set_send_delay( int new_delay ) { SEND_MINDELAY = new_delay; }
|
void set_send_delay( int new_delay ) { SEND_MINDELAY = new_delay; }
|
||||||
|
|||||||
Reference in New Issue
Block a user