From b41bad918d1eaedc2140922b5d7aad239c7e2393 Mon Sep 17 00:00:00 2001 From: John Hood Date: Sat, 14 Jun 2014 18:21:38 -0400 Subject: [PATCH] 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 --- configure.ac | 29 +++++++++++++ src/frontend/stmclient.cc | 23 +++++------ src/frontend/stmclient.h | 14 ++----- src/terminal/terminaldisplay.cc | 30 ++++++-------- src/terminal/terminalframebuffer.cc | 63 ++++++++++++++++++----------- src/terminal/terminalframebuffer.h | 55 ++++++++++++++++--------- src/util/Makefile.am | 2 +- src/util/shared.h | 53 ++++++++++++++++++++++++ 8 files changed, 186 insertions(+), 83 deletions(-) create mode 100644 src/util/shared.h diff --git a/configure.ac b/configure.ac index 7390358..a64e818 100644 --- a/configure.ac +++ b/configure.ac @@ -206,6 +206,9 @@ AC_CHECK_HEADERS([endian.h sys/endian.h]) AC_CHECK_HEADERS([utmpx.h]) AC_CHECK_HEADERS([termio.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. AC_HEADER_STDBOOL @@ -371,6 +374,32 @@ public: [AC_MSG_RESULT([no])]) AC_LANG_POP(C++) +AC_MSG_CHECKING([whether std::shared_ptr is available]) +AC_LANG_PUSH(C++) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +class T { +public: + std::shared_ptr Fun( void ) { return std::shared_ptr( 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 +class T { +public: + std::tr1::shared_ptr Fun( void ) { return std::tr1::shared_ptr( 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_DECL([mach_absolute_time], diff --git a/src/frontend/stmclient.cc b/src/frontend/stmclient.cc index 0c9027f..d08124d 100644 --- a/src/frontend/stmclient.cc +++ b/src/frontend/stmclient.cc @@ -237,11 +237,11 @@ void STMClient::main_init( void ) } /* local state */ - local_framebuffer = new Terminal::Framebuffer( window_size.ws_col, window_size.ws_row ); - new_state = new Terminal::Framebuffer( 1, 1 ); + local_framebuffer = Terminal::Framebuffer( window_size.ws_col, window_size.ws_row ); + new_state = Terminal::Framebuffer( 1, 1 ); /* 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() ); /* open network */ @@ -263,26 +263,23 @@ void STMClient::output_new_frame( void ) } /* 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 */ - overlays.apply( *new_state ); + overlays.apply( new_state ); /* apply any mutations */ - display.downgrade( *new_state ); + display.downgrade( new_state ); /* calculate minimal difference from where we are */ const string diff( display.new_frame( !repaint_requested, - *local_framebuffer, - *new_state ) ); + local_framebuffer, + new_state ) ); swrite( STDOUT_FILENO, diff.data(), diff.size() ); repaint_requested = false; - /* switch pointers */ - Terminal::Framebuffer *tmp = new_state; - new_state = local_framebuffer; - local_framebuffer = tmp; + local_framebuffer = new_state; } 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++ ) { 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 ( the_byte == '.' ) { /* Quit sequence is Ctrl-^ . */ diff --git a/src/frontend/stmclient.h b/src/frontend/stmclient.h index 3ee5900..310102b 100644 --- a/src/frontend/stmclient.h +++ b/src/frontend/stmclient.h @@ -58,7 +58,7 @@ private: struct winsize window_size; - Terminal::Framebuffer *local_framebuffer, *new_state; + Terminal::Framebuffer local_framebuffer, new_state; Overlay::OverlayManager overlays; Network::Transport< Network::UserStream, Terminal::Complete > *network; Terminal::Display display; @@ -89,8 +89,8 @@ public: escape_requires_lf( false ), escape_key_help( L"?" ), saved_termios(), raw_termios(), window_size(), - local_framebuffer( NULL ), - new_state( NULL ), + local_framebuffer( 1, 1 ), + new_state( 1, 1 ), overlays(), network( NULL ), display( true ), /* use TERM environment var to initialize display */ @@ -122,14 +122,6 @@ public: ~STMClient() { - if ( local_framebuffer != NULL ) { - delete local_framebuffer; - } - - if ( new_state != NULL ) { - delete new_state; - } - if ( network != NULL ) { delete network; } diff --git a/src/terminal/terminaldisplay.cc b/src/terminal/terminaldisplay.cc index 28710ec..4a271d2 100644 --- a/src/terminal/terminaldisplay.cc +++ b/src/terminal/terminaldisplay.cc @@ -143,25 +143,20 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const } int frame_y = 0; - Row blankrow( 0, 0 ); - Framebuffer::rows_p_type rows(frame.last_frame.get_p_rows()); + Framebuffer::row_pointer blank_row; + Framebuffer::rows_type rows( frame.last_frame.get_rows() ); /* 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() ) { - for ( Framebuffer::rows_p_type::iterator p = rows.begin(); p != rows.end(); p++ ) { - Row *bigger_row = new Row( **p ); - bigger_row->cells.resize( f.ds.get_width(), Cell( f.ds.get_background_rendition() ) ); - *p = bigger_row; + for ( Framebuffer::rows_type::iterator p = rows.begin(); p != rows.end(); p++ ) { + *p = Framebuffer::row_pointer( new Row( **p ) ); + (*p)->cells.resize( f.ds.get_width(), Cell( f.ds.get_background_rendition() ) ); } } /* Add rows if we've gotten a resize and new is taller than old */ if ( static_cast( rows.size() ) < f.ds.get_height() ) { - size_t orig_size = rows.size(); // get a proper blank row - blankrow = Row( f.ds.get_width(), 0 ); - rows.resize( f.ds.get_height() ); - for ( Framebuffer::rows_p_type::iterator p = rows.begin() + orig_size; p != rows.end(); p++ ) { - *p = new Row( blankrow ); - } + blank_row = Framebuffer::row_pointer( new Row( f.ds.get_width(), 0 ) ); + rows.resize( f.ds.get_height(), blank_row ); } /* 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++ ) { 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 row 0, we're looking at ourselves and probably didn't scroll */ if ( row == 0 ) { @@ -201,6 +196,10 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const frame_y = scroll_height; 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 ); 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; } - /* 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 */ for ( int i = top_margin; i <= bottom_margin; i++ ) { if ( i + lines_scrolled <= bottom_margin ) { rows.at( i ) = rows.at( i + lines_scrolled ); } else { - rows.at( i ) = &blankrow; + rows.at( i ) = blank_row; } } } diff --git a/src/terminal/terminalframebuffer.cc b/src/terminal/terminalframebuffer.cc index 88a54bf..7f97cdf 100644 --- a/src/terminal/terminalframebuffer.cc +++ b/src/terminal/terminalframebuffer.cc @@ -84,12 +84,31 @@ DrawState::DrawState( 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_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 ) { if ( N >= 0 ) { @@ -286,14 +305,9 @@ void Framebuffer::insert_line( int before_row, int count ) // delete old rows rows_type::iterator start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count; rows.erase( start, start + count ); - // insert a block of dummy rows + // insert new rows start = rows.begin() + before_row; - rows.insert( start, count, Row( 0, 0 ) ); - // then replace with real new rows - start = rows.begin() + before_row; - for (rows_type::iterator i = start; i < start + count; i++) { - *i = newrow(); - } + rows.insert( start, count, newrow()); } 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 ); // insert a block of dummy rows start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count; - rows.insert( start, count, Row( 0, 0 ) ); - // 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(); - } + rows.insert( start, count, newrow()); } 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 ) { - 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 ) { - 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 ) @@ -388,8 +397,8 @@ void Framebuffer::posterize( void ) for ( rows_type::iterator i = rows.begin(); i != rows.end(); i++ ) { - for ( Row::cells_type::iterator j = i->cells.begin(); - j != i->cells.end(); + for ( Row::cells_type::iterator j = (*i)->cells.begin(); + j != (*i)->cells.end(); j++ ) { j->renditions.posterize(); } @@ -401,15 +410,23 @@ void Framebuffer::resize( int s_width, int s_height ) assert( s_width > 0 ); assert( s_height > 0 ); + int oldheight = ds.get_height(); + int oldwidth = ds.get_width(); 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(); - i != rows.end(); + i != rows.end() && *i != blankrow; i++ ) { - i->set_wrap( false ); - i->cells.resize( s_width, Cell( ds.get_background_rendition() ) ); + *i = Framebuffer::row_pointer( new Row( **i ) ); + (*i)->set_wrap( false ); + (*i)->cells.resize( s_width, Cell( ds.get_background_rendition() ) ); } } diff --git a/src/terminal/terminalframebuffer.h b/src/terminal/terminalframebuffer.h index 15f6033..bbdc0dc 100644 --- a/src/terminal/terminalframebuffer.h +++ b/src/terminal/terminalframebuffer.h @@ -42,9 +42,12 @@ #include #include +#include "shared.h" + /* Terminal framebuffer */ namespace Terminal { + using shared::shared_ptr; typedef uint16_t color_type; class Renditions { @@ -288,41 +291,48 @@ namespace Terminal { }; 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: typedef std::vector title_type; - typedef std::vector rows_p_type; + typedef shared_ptr row_pointer; + typedef std::vector rows_type; /* can be either std::vector or std::deque */ private: - typedef std::vector rows_type; rows_type rows; title_type icon_name; title_type window_title; unsigned int bell_count; 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: Framebuffer( int s_width, int s_height ); + Framebuffer( const Framebuffer &other ); + Framebuffer &operator=( const Framebuffer &other ); DrawState ds; + const rows_type &get_rows() const { return rows; } + void scroll( int N ); void move_rows_autoscroll( int rows ); - rows_p_type get_p_rows() 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 + inline const Row *get_row( int row ) const { 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 @@ -330,17 +340,26 @@ namespace Terminal { if ( row == -1 ) row = ds.get_cursor_row(); 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 ) { - return const_cast(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(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 ); diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 25dc3dd..1eaf684 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -2,4 +2,4 @@ AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(PICKY_CXXFLAGS) $(HARDEN_CFLAGS) $(MISC_CXXF 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 diff --git a/src/util/shared.h b/src/util/shared.h new file mode 100644 index 0000000..9bc87eb --- /dev/null +++ b/src/util/shared.h @@ -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 . + + 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 +#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