Make Terminal::Framebuffer::Rows shared and copy-on-write.

* Support both std:: and std::tr1:: shared_ptr. FreeBSD 10 now uses C++11 by default.
* Remove Framebuffer pointers in STMClient
This commit is contained in:
John Hood
2014-06-14 18:21:38 -04:00
parent c090d257f2
commit b41bad918d
8 changed files with 186 additions and 83 deletions
+29
View File
@@ -206,6 +206,9 @@ AC_CHECK_HEADERS([endian.h sys/endian.h])
AC_CHECK_HEADERS([utmpx.h]) AC_CHECK_HEADERS([utmpx.h])
AC_CHECK_HEADERS([termio.h]) AC_CHECK_HEADERS([termio.h])
AC_CHECK_HEADERS([sys/uio.h]) AC_CHECK_HEADERS([sys/uio.h])
AC_LANG_PUSH(C++)
AC_CHECK_HEADERS([tr1/memory])
AC_LANG_POP(C++)
# Checks for typedefs, structures, and compiler characteristics. # Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL AC_HEADER_STDBOOL
@@ -371,6 +374,32 @@ public:
[AC_MSG_RESULT([no])]) [AC_MSG_RESULT([no])])
AC_LANG_POP(C++) AC_LANG_POP(C++)
AC_MSG_CHECKING([whether std::shared_ptr is available])
AC_LANG_PUSH(C++)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <memory>
class T {
public:
std::shared_ptr<int> Fun( void ) { return std::shared_ptr<int>( new int ( 0 ) ); } };]],
[[T x; return !!x.Fun();]])],
[AC_DEFINE([HAVE_STD_SHARED_PTR], [1],
[Define if std::shared_ptr is available.])
AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])])
AC_LANG_POP(C++)
AC_MSG_CHECKING([whether std::tr1::shared_ptr is available])
AC_LANG_PUSH(C++)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <tr1/memory>
class T {
public:
std::tr1::shared_ptr<int> Fun( void ) { return std::tr1::shared_ptr<int>( new int ( 0 ) ); } };]],
[[T x; return !!x.Fun();]])],
[AC_DEFINE([HAVE_STD_TR1_SHARED_PTR], [1],
[Define if std::tr1::shared_ptr is available.])
AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])])
AC_LANG_POP(C++)
AC_CHECK_DECLS([__builtin_bswap64, __builtin_ctz]) AC_CHECK_DECLS([__builtin_bswap64, __builtin_ctz])
AC_CHECK_DECL([mach_absolute_time], AC_CHECK_DECL([mach_absolute_time],
+10 -13
View File
@@ -237,11 +237,11 @@ void STMClient::main_init( void )
} }
/* local state */ /* local state */
local_framebuffer = new Terminal::Framebuffer( window_size.ws_col, window_size.ws_row ); local_framebuffer = Terminal::Framebuffer( window_size.ws_col, window_size.ws_row );
new_state = new Terminal::Framebuffer( 1, 1 ); new_state = Terminal::Framebuffer( 1, 1 );
/* initialize screen */ /* initialize screen */
string init = display.new_frame( false, *local_framebuffer, *local_framebuffer ); string init = display.new_frame( false, local_framebuffer, local_framebuffer );
swrite( STDOUT_FILENO, init.data(), init.size() ); swrite( STDOUT_FILENO, init.data(), init.size() );
/* open network */ /* open network */
@@ -263,26 +263,23 @@ void STMClient::output_new_frame( void )
} }
/* fetch target state */ /* fetch target state */
*new_state = network->get_latest_remote_state().state.get_fb(); new_state = network->get_latest_remote_state().state.get_fb();
/* apply local overlays */ /* apply local overlays */
overlays.apply( *new_state ); overlays.apply( new_state );
/* apply any mutations */ /* apply any mutations */
display.downgrade( *new_state ); display.downgrade( new_state );
/* calculate minimal difference from where we are */ /* calculate minimal difference from where we are */
const string diff( display.new_frame( !repaint_requested, const string diff( display.new_frame( !repaint_requested,
*local_framebuffer, local_framebuffer,
*new_state ) ); new_state ) );
swrite( STDOUT_FILENO, diff.data(), diff.size() ); swrite( STDOUT_FILENO, diff.data(), diff.size() );
repaint_requested = false; repaint_requested = false;
/* switch pointers */ local_framebuffer = new_state;
Terminal::Framebuffer *tmp = new_state;
new_state = local_framebuffer;
local_framebuffer = tmp;
} }
void STMClient::process_network_input( void ) void STMClient::process_network_input( void )
@@ -318,7 +315,7 @@ bool STMClient::process_user_input( int fd )
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 ];
overlays.get_prediction_engine().new_user_byte( the_byte, *local_framebuffer ); overlays.get_prediction_engine().new_user_byte( the_byte, local_framebuffer );
if ( quit_sequence_started ) { if ( quit_sequence_started ) {
if ( the_byte == '.' ) { /* Quit sequence is Ctrl-^ . */ if ( the_byte == '.' ) { /* Quit sequence is Ctrl-^ . */
+3 -11
View File
@@ -58,7 +58,7 @@ private:
struct winsize window_size; struct winsize window_size;
Terminal::Framebuffer *local_framebuffer, *new_state; Terminal::Framebuffer local_framebuffer, new_state;
Overlay::OverlayManager overlays; Overlay::OverlayManager overlays;
Network::Transport< Network::UserStream, Terminal::Complete > *network; Network::Transport< Network::UserStream, Terminal::Complete > *network;
Terminal::Display display; Terminal::Display display;
@@ -89,8 +89,8 @@ public:
escape_requires_lf( false ), escape_key_help( L"?" ), escape_requires_lf( false ), escape_key_help( L"?" ),
saved_termios(), raw_termios(), saved_termios(), raw_termios(),
window_size(), window_size(),
local_framebuffer( NULL ), local_framebuffer( 1, 1 ),
new_state( NULL ), new_state( 1, 1 ),
overlays(), overlays(),
network( NULL ), network( NULL ),
display( true ), /* use TERM environment var to initialize display */ display( true ), /* use TERM environment var to initialize display */
@@ -122,14 +122,6 @@ public:
~STMClient() ~STMClient()
{ {
if ( local_framebuffer != NULL ) {
delete local_framebuffer;
}
if ( new_state != NULL ) {
delete new_state;
}
if ( network != NULL ) { if ( network != NULL ) {
delete network; delete network;
} }
+13 -17
View File
@@ -143,25 +143,20 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const
} }
int frame_y = 0; int frame_y = 0;
Row blankrow( 0, 0 ); Framebuffer::row_pointer blank_row;
Framebuffer::rows_p_type rows(frame.last_frame.get_p_rows()); Framebuffer::rows_type rows( frame.last_frame.get_rows() );
/* Extend rows if we've gotten a resize and new is wider than old */ /* Extend rows if we've gotten a resize and new is wider than old */
if ( frame.last_frame.ds.get_width() < f.ds.get_width() ) { if ( frame.last_frame.ds.get_width() < f.ds.get_width() ) {
for ( Framebuffer::rows_p_type::iterator p = rows.begin(); p != rows.end(); p++ ) { for ( Framebuffer::rows_type::iterator p = rows.begin(); p != rows.end(); p++ ) {
Row *bigger_row = new Row( **p ); *p = Framebuffer::row_pointer( new Row( **p ) );
bigger_row->cells.resize( f.ds.get_width(), Cell( f.ds.get_background_rendition() ) ); (*p)->cells.resize( f.ds.get_width(), Cell( f.ds.get_background_rendition() ) );
*p = bigger_row;
} }
} }
/* Add rows if we've gotten a resize and new is taller than old */ /* Add rows if we've gotten a resize and new is taller than old */
if ( static_cast<int>( rows.size() ) < f.ds.get_height() ) { if ( static_cast<int>( rows.size() ) < f.ds.get_height() ) {
size_t orig_size = rows.size();
// get a proper blank row // get a proper blank row
blankrow = Row( f.ds.get_width(), 0 ); blank_row = Framebuffer::row_pointer( new Row( f.ds.get_width(), 0 ) );
rows.resize( f.ds.get_height() ); rows.resize( f.ds.get_height(), blank_row );
for ( Framebuffer::rows_p_type::iterator p = rows.begin() + orig_size; p != rows.end(); p++ ) {
*p = new Row( blankrow );
}
} }
/* shortcut -- has display moved up by a certain number of lines? */ /* shortcut -- has display moved up by a certain number of lines? */
@@ -171,7 +166,7 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const
for ( int row = 0; row < f.ds.get_height(); row++ ) { for ( int row = 0; row < f.ds.get_height(); row++ ) {
const Row *new_row = f.get_row( 0 ); const Row *new_row = f.get_row( 0 );
const Row *old_row = rows.at( row ); const Row *old_row = &*rows.at( row );
if ( new_row == old_row || *new_row == *old_row ) { if ( new_row == old_row || *new_row == *old_row ) {
/* if row 0, we're looking at ourselves and probably didn't scroll */ /* if row 0, we're looking at ourselves and probably didn't scroll */
if ( row == 0 ) { if ( row == 0 ) {
@@ -201,6 +196,10 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const
frame_y = scroll_height; frame_y = scroll_height;
if ( lines_scrolled ) { if ( lines_scrolled ) {
/* Now we need a proper blank row. */
if ( blank_row.get() == NULL ) {
blank_row = Framebuffer::row_pointer( new Row( f.ds.get_width(), 0 ) );
}
frame.update_rendition( initial_rendition(), true ); frame.update_rendition( initial_rendition(), true );
int top_margin = 0; int top_margin = 0;
@@ -234,15 +233,12 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const
frame.cursor_x = frame.cursor_y = -1; frame.cursor_x = frame.cursor_y = -1;
} }
/* Create a full-width blank row to represent newly-scrolled area */
blankrow = Row( f.ds.get_width(), 0 );
/* do the move in our local index */ /* do the move in our local index */
for ( int i = top_margin; i <= bottom_margin; i++ ) { for ( int i = top_margin; i <= bottom_margin; i++ ) {
if ( i + lines_scrolled <= bottom_margin ) { if ( i + lines_scrolled <= bottom_margin ) {
rows.at( i ) = rows.at( i + lines_scrolled ); rows.at( i ) = rows.at( i + lines_scrolled );
} else { } else {
rows.at( i ) = &blankrow; rows.at( i ) = blank_row;
} }
} }
} }
+40 -23
View File
@@ -84,12 +84,31 @@ DrawState::DrawState( int s_width, int s_height )
} }
Framebuffer::Framebuffer( int s_width, int s_height ) Framebuffer::Framebuffer( int s_width, int s_height )
: rows( s_height, Row( s_width, 0 ) ), icon_name(), window_title(), bell_count( 0 ), title_initialized( false ), ds( s_width, s_height ) : rows( s_height, row_pointer(new Row( s_width, 0 ))), icon_name(), window_title(), bell_count( 0 ), title_initialized( false ), ds( s_width, s_height )
{ {
assert( s_height > 0 ); assert( s_height > 0 );
assert( s_width > 0 ); assert( s_width > 0 );
} }
Framebuffer::Framebuffer( const Framebuffer &other )
: rows( other.rows ), icon_name( other.icon_name ), window_title( other.window_title ),
bell_count( other.bell_count ), title_initialized( other.title_initialized ), ds( other.ds )
{
}
Framebuffer & Framebuffer::operator=( const Framebuffer &other )
{
if ( this != &other ) {
rows = other.rows;
icon_name = other.icon_name;
window_title = other.window_title;
bell_count = other.bell_count;
title_initialized = other.title_initialized;
ds = other.ds;
}
return *this;
}
void Framebuffer::scroll( int N ) void Framebuffer::scroll( int N )
{ {
if ( N >= 0 ) { if ( N >= 0 ) {
@@ -286,14 +305,9 @@ void Framebuffer::insert_line( int before_row, int count )
// delete old rows // delete old rows
rows_type::iterator start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count; rows_type::iterator start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count;
rows.erase( start, start + count ); rows.erase( start, start + count );
// insert a block of dummy rows // insert new rows
start = rows.begin() + before_row; start = rows.begin() + before_row;
rows.insert( start, count, Row( 0, 0 ) ); rows.insert( start, count, newrow());
// then replace with real new rows
start = rows.begin() + before_row;
for (rows_type::iterator i = start; i < start + count; i++) {
*i = newrow();
}
} }
void Framebuffer::delete_line( int row, int count ) void Framebuffer::delete_line( int row, int count )
@@ -317,12 +331,7 @@ void Framebuffer::delete_line( int row, int count )
rows.erase( start, start + count ); rows.erase( start, start + count );
// insert a block of dummy rows // insert a block of dummy rows
start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count; start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count;
rows.insert( start, count, Row( 0, 0 ) ); rows.insert( start, count, newrow());
// then replace with real new rows
start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count;
for (rows_type::iterator i = start; i < start + count; i++) {
*i = newrow();
}
} }
Row::Row( size_t s_width, color_type background_color ) Row::Row( size_t s_width, color_type background_color )
@@ -355,12 +364,12 @@ void Row::delete_cell( int col, color_type background_color )
void Framebuffer::insert_cell( int row, int col ) void Framebuffer::insert_cell( int row, int col )
{ {
rows.at( row ).insert_cell( col, ds.get_background_rendition() ); get_mutable_row( row )->insert_cell( col, ds.get_background_rendition() );
} }
void Framebuffer::delete_cell( int row, int col ) void Framebuffer::delete_cell( int row, int col )
{ {
rows.at( row ).delete_cell( col, ds.get_background_rendition() ); get_mutable_row( row )->delete_cell( col, ds.get_background_rendition() );
} }
void Framebuffer::reset( void ) void Framebuffer::reset( void )
@@ -388,8 +397,8 @@ void Framebuffer::posterize( void )
for ( rows_type::iterator i = rows.begin(); for ( rows_type::iterator i = rows.begin();
i != rows.end(); i != rows.end();
i++ ) { i++ ) {
for ( Row::cells_type::iterator j = i->cells.begin(); for ( Row::cells_type::iterator j = (*i)->cells.begin();
j != i->cells.end(); j != (*i)->cells.end();
j++ ) { j++ ) {
j->renditions.posterize(); j->renditions.posterize();
} }
@@ -401,15 +410,23 @@ void Framebuffer::resize( int s_width, int s_height )
assert( s_width > 0 ); assert( s_width > 0 );
assert( s_height > 0 ); assert( s_height > 0 );
int oldheight = ds.get_height();
int oldwidth = ds.get_width();
ds.resize( s_width, s_height ); ds.resize( s_width, s_height );
rows.resize( s_height, newrow() ); row_pointer blankrow( newrow());
if ( oldheight != s_height ) {
rows.resize( s_height, blankrow );
}
if (oldwidth == s_width) {
return;
}
for ( rows_type::iterator i = rows.begin(); for ( rows_type::iterator i = rows.begin();
i != rows.end(); i != rows.end() && *i != blankrow;
i++ ) { i++ ) {
i->set_wrap( false ); *i = Framebuffer::row_pointer( new Row( **i ) );
i->cells.resize( s_width, Cell( ds.get_background_rendition() ) ); (*i)->set_wrap( false );
(*i)->cells.resize( s_width, Cell( ds.get_background_rendition() ) );
} }
} }
+37 -18
View File
@@ -42,9 +42,12 @@
#include <string> #include <string>
#include <list> #include <list>
#include "shared.h"
/* Terminal framebuffer */ /* Terminal framebuffer */
namespace Terminal { namespace Terminal {
using shared::shared_ptr;
typedef uint16_t color_type; typedef uint16_t color_type;
class Renditions { class Renditions {
@@ -288,41 +291,48 @@ namespace Terminal {
}; };
class Framebuffer { class Framebuffer {
// To minimize copying of rows and cells, we use shared_ptr to
// share unchanged rows between multiple Framebuffers. If we
// write to a row in a Framebuffer and it is shared with other
// owners, we copy it first. The shared_ptr naturally manages the
// usage of the actual rows themselves.
//
// We gain a couple of free extras by doing this:
//
// * A quick check for equality between rows in different
// Framebuffers is to simply compare the pointer values. If they
// are equal, then the rows are obviously identical.
// * If no row is shared, the frame has not been modified.
public: public:
typedef std::vector<wchar_t> title_type; typedef std::vector<wchar_t> title_type;
typedef std::vector<const Row *> rows_p_type; typedef shared_ptr<Row> row_pointer;
typedef std::vector<row_pointer> rows_type; /* can be either std::vector or std::deque */
private: private:
typedef std::vector<Row> rows_type;
rows_type rows; rows_type rows;
title_type icon_name; title_type icon_name;
title_type window_title; title_type window_title;
unsigned int bell_count; unsigned int bell_count;
bool title_initialized; /* true if the window title has been set via an OSC */ bool title_initialized; /* true if the window title has been set via an OSC */
Row newrow( void ) { return Row( ds.get_width(), ds.get_background_rendition() ); } row_pointer newrow( void ) { return row_pointer( new Row( ds.get_width(), ds.get_background_rendition())); }
public: public:
Framebuffer( int s_width, int s_height ); Framebuffer( int s_width, int s_height );
Framebuffer( const Framebuffer &other );
Framebuffer &operator=( const Framebuffer &other );
DrawState ds; DrawState ds;
const rows_type &get_rows() const { return rows; }
void scroll( int N ); void scroll( int N );
void move_rows_autoscroll( int rows ); void move_rows_autoscroll( int rows );
rows_p_type get_p_rows() const inline const Row *get_row( int row ) const
{
rows_p_type retval;
for ( size_t i = 0; i < rows.size(); i++ ) {
retval.push_back( &rows.at(i) );
}
return retval;
}
const Row *get_row( int row ) const
{ {
if ( row == -1 ) row = ds.get_cursor_row(); if ( row == -1 ) row = ds.get_cursor_row();
return &rows.at( row ); return rows.at( row ).get();
} }
inline const Cell *get_cell( int row = -1, int col = -1 ) const inline const Cell *get_cell( int row = -1, int col = -1 ) const
@@ -330,17 +340,26 @@ namespace Terminal {
if ( row == -1 ) row = ds.get_cursor_row(); if ( row == -1 ) row = ds.get_cursor_row();
if ( col == -1 ) col = ds.get_cursor_col(); if ( col == -1 ) col = ds.get_cursor_col();
return &rows.at( row ).cells.at( col ); return &rows.at( row )->cells.at( col );
} }
Row *get_mutable_row( int row ) Row *get_mutable_row( int row )
{ {
return const_cast<Row *>(get_row( row )); if ( row == -1 ) row = ds.get_cursor_row();
row_pointer &mutable_row = rows.at( row );
// If the row is shared, copy it.
if (!mutable_row.unique()) {
mutable_row = row_pointer( new Row( *mutable_row ));
}
return mutable_row.get();
} }
inline Cell *get_mutable_cell( int row = -1, int col = -1 ) Cell *get_mutable_cell( int row = -1, int col = -1 )
{ {
return const_cast<Cell *>(get_cell( row, col )); if ( row == -1 ) row = ds.get_cursor_row();
if ( col == -1 ) col = ds.get_cursor_col();
return &get_mutable_row( row )->cells.at( col );
} }
Cell *get_combining_cell( void ); Cell *get_combining_cell( void );
+1 -1
View File
@@ -2,4 +2,4 @@ AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(PICKY_CXXFLAGS) $(HARDEN_CFLAGS) $(MISC_CXXF
noinst_LIBRARIES = libmoshutil.a noinst_LIBRARIES = libmoshutil.a
libmoshutil_a_SOURCES = locale_utils.cc locale_utils.h swrite.cc swrite.h dos_assert.h fatal_assert.h select.h select.cc timestamp.h timestamp.cc pty_compat.cc pty_compat.h libmoshutil_a_SOURCES = locale_utils.cc locale_utils.h swrite.cc swrite.h dos_assert.h fatal_assert.h select.h select.cc timestamp.h timestamp.cc pty_compat.cc pty_compat.h shared.h
+53
View File
@@ -0,0 +1,53 @@
/*
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 <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give
permission to link the code of portions of this program with the
OpenSSL library under certain conditions as described in each
individual source file, and distribute linked combinations including
the two.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the
file(s), but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version. If you delete
this exception statement from all source files in the program, then
also delete it here.
*/
#ifndef SHARED_HPP
#define SHARED_HPP
#include "config.h"
#ifdef HAVE_TR1_MEMORY
#include <tr1/memory>
#endif
namespace shared {
#ifdef HAVE_STD_SHARED_PTR
using std::shared_ptr;
#else
#ifdef HAVE_STD_TR1_SHARED_PTR
using std::tr1::shared_ptr;
#else
#error Need a shared_ptr class. Try Boost::TR1.
#endif
#endif
}
#endif