/*
Mosh: the mobile shell
Copyright 2012 Keith Winstein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef TERMINAL_OVERLAY_HPP
#define TERMINAL_OVERLAY_HPP
#include "terminalframebuffer.h"
#include "network.h"
#include "parser.h"
#include
namespace Overlay {
using namespace Terminal;
using namespace Network;
using std::deque;
using std::list;
using std::vector;
using std::wstring;
enum Validity {
Pending,
Correct,
CorrectNoCredit,
IncorrectOrExpired,
Inactive
};
class ConditionalOverlay {
public:
uint64_t expiration_frame;
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 ), col( s_col ),
active( false ),
tentative_until_epoch( s_tentative ), prediction_time( uint64_t( -1 ) )
{}
virtual ~ConditionalOverlay() {}
bool tentative( uint64_t confirmed_epoch ) const { return tentative_until_epoch > confirmed_epoch; }
void reset( void ) { expiration_frame = tentative_until_epoch = -1; active = false; }
void expire( uint64_t s_exp, uint64_t now )
{
expiration_frame = s_exp; prediction_time = now;
}
};
class ConditionalCursorMove : public ConditionalOverlay {
public:
int row;
void apply( Framebuffer &fb, uint64_t confirmed_epoch ) const;
Validity get_validity( const Framebuffer &fb, uint64_t early_ack, uint64_t late_ack ) const;
ConditionalCursorMove( uint64_t s_exp, int s_row, int s_col, uint64_t s_tentative )
: ConditionalOverlay( s_exp, s_col, s_tentative ), row( s_row )
{}
};
class ConditionalOverlayCell : public ConditionalOverlay {
public:
Cell replacement;
bool unknown;
vector| original_contents; /* we don't give credit for correct predictions
that match the original contents */
void apply( Framebuffer &fb, uint64_t confirmed_epoch, int row, bool flag ) const;
Validity get_validity( const Framebuffer &fb, int row, uint64_t early_ack, uint64_t late_ack ) const;
ConditionalOverlayCell( uint64_t s_exp, int s_col, uint64_t s_tentative )
: ConditionalOverlay( s_exp, s_col, s_tentative ),
replacement( 0 ),
unknown( false ),
original_contents()
{}
void reset( void ) { unknown = false; original_contents.clear(); ConditionalOverlay::reset(); }
void reset_with_orig( void ) {
if ( (!active) || unknown ) {
reset();
return;
}
original_contents.push_back( replacement );
ConditionalOverlay::reset();
}
};
class ConditionalOverlayRow {
public:
int row_num;
typedef vector overlay_cells_t;
overlay_cells_t overlay_cells;
void apply( Framebuffer &fb, uint64_t confirmed_epoch, bool flag ) const;
ConditionalOverlayRow( int s_row_num ) : row_num( s_row_num ), overlay_cells() {}
};
/* the various overlays */
class NotificationEngine {
private:
uint64_t last_word_from_server;
wstring message;
uint64_t message_expiration;
public:
bool need_countup( uint64_t ts ) const { return ts - last_word_from_server > 6500; }
void adjust_message( void );
void apply( Framebuffer &fb ) const;
void set_notification_string( const wstring s_message ) { message = s_message; message_expiration = timestamp() + 1000; }
const wstring &get_notification_string( void ) const { return message; }
void server_heard( uint64_t s_last_word ) { last_word_from_server = s_last_word; }
uint64_t get_message_expiration( void ) const { return message_expiration; }
NotificationEngine();
};
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 */
static const uint64_t GLITCH_FLAG_THRESHOLD = 5000; /* prediction outstanding this long => underline */
char last_byte;
Parser::UTF8Parser parser;
typedef list overlays_t;
overlays_t overlays;
typedef list cursors_t;
cursors_t cursors;
typedef ConditionalOverlayRow::overlay_cells_t overlay_cells_t;
uint64_t local_frame_sent, local_frame_acked, local_frame_late_acked;
ConditionalOverlayRow & get_or_make_row( int row_num, int num_cols );
uint64_t prediction_epoch;
uint64_t confirmed_epoch;
void become_tentative( void );
void newline_carriage_return( const Framebuffer &fb );
bool flagging; /* whether we are underlining predictions */
bool srtt_trigger; /* show predictions because of slow round trip time */
unsigned 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(); }
void kill_epoch( uint64_t epoch, const Framebuffer &fb );
void init_cursor( const Framebuffer &fb );
unsigned int send_interval;
public:
enum DisplayPreference {
Always,
Never,
Adaptive
};
private:
DisplayPreference display_preference;
public:
void set_display_preference( DisplayPreference s_pref ) { display_preference = s_pref; }
void apply( Framebuffer &fb ) const;
void new_user_byte( char the_byte, const Framebuffer &fb );
void cull( const Framebuffer &fb );
void reset( void );
bool active( void ) const;
void set_local_frame_sent( uint64_t x ) { local_frame_sent = x; }
void set_local_frame_acked( uint64_t x ) { local_frame_acked = x; }
void set_local_frame_late_acked( uint64_t x ) { local_frame_late_acked = x; }
void set_send_interval( unsigned int x ) { send_interval = x; }
PredictionEngine( void ) : last_byte( 0 ), parser(), overlays(), cursors(),
local_frame_sent( 0 ), local_frame_acked( 0 ),
local_frame_late_acked( 0 ),
prediction_epoch( 1 ), confirmed_epoch( 0 ),
flagging( false ),
srtt_trigger( false ),
glitch_trigger( 0 ),
last_quick_confirmation( 0 ),
send_interval( 250 ),
display_preference( Adaptive )
{
}
};
class TitleEngine {
private:
deque prefix;
public:
void apply( Framebuffer &fb ) const { fb.prefix_window_title( prefix ); }
void set_prefix( const wstring s );
TitleEngine() : prefix() {}
};
/* the overlay manager */
class OverlayManager {
private:
NotificationEngine notifications;
PredictionEngine predictions;
TitleEngine title;
public:
void apply( Framebuffer &fb );
NotificationEngine & get_notification_engine( void ) { return notifications; }
PredictionEngine & get_prediction_engine( void ) { return predictions; }
void set_title_prefix( const wstring s ) { title.set_prefix( s ); }
OverlayManager() : notifications(), predictions(), title() {}
int wait_time( void );
};
}
#endif
|