Add perf optimizations (#1541)

* pass `uid` to term

* term: keep track of term instance references

* sessions: remvoe `write` object overhead

* hterm: cache measurement of codepoints for single char strings

* sessions: merge less eagerly when we receive a PTY_DATA action

* store: handle the side effect of writing to the terminal from a middleware

* terms: add terms instance cache

* lint
This commit is contained in:
Guillermo Rauch 2017-02-23 03:10:05 -08:00 committed by Matheus Fernandes
parent 1cd2620da0
commit dc3b90028b
9 changed files with 54 additions and 26 deletions

View file

@ -76,7 +76,8 @@ class TermGroup_ extends Component {
onData: this.bind(this.props.onData, null, uid), onData: this.bind(this.props.onData, null, uid),
onURLAbort: this.bind(this.props.onURLAbort, null, uid), onURLAbort: this.bind(this.props.onURLAbort, null, uid),
borderColor: this.props.borderColor, borderColor: this.props.borderColor,
quickEdit: this.props.quickEdit quickEdit: this.props.quickEdit,
uid
}); });
// This will create a new ref_ function for every render, // This will create a new ref_ function for every render,

View file

@ -5,6 +5,7 @@ import uuid from 'uuid';
import hterm from '../hterm'; import hterm from '../hterm';
import Component from '../component'; import Component from '../component';
import getColorList from '../utils/colors'; import getColorList from '../utils/colors';
import terms from '../terms';
import notify from '../utils/notify'; import notify from '../utils/notify';
export default class Term extends Component { export default class Term extends Component {
@ -97,6 +98,7 @@ export default class Term extends Component {
this.getScreenNode().addEventListener('mouseup', this.handleMouseUp); this.getScreenNode().addEventListener('mouseup', this.handleMouseUp);
this.getScreenNode().addEventListener('mousedown', this.handleMouseDown); this.getScreenNode().addEventListener('mousedown', this.handleMouseDown);
terms[this.props.uid] = this;
} }
handleWheel(e) { handleWheel(e) {
@ -364,6 +366,7 @@ export default class Term extends Component {
} }
componentWillUnmount() { componentWillUnmount() {
terms[this.props.uid] = this;
// turn blinking off to prevent leaking a timeout when disposing terminal // turn blinking off to prevent leaking a timeout when disposing terminal
const prefs = this.term.getPrefs(); const prefs = this.term.getPrefs();
prefs.set('cursor-blink', false); prefs.set('cursor-blink', false);

View file

@ -16,13 +16,6 @@ export default class Terms extends Component {
props.ref_(this); props.ref_(this);
} }
componentWillReceiveProps(next) {
const {write} = next;
if (write && this.props.write !== write) {
this.getTermByUid(write.uid).write(write.data);
}
}
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
for (const i in nextProps) { for (const i in nextProps) {
if (i === 'write') { if (i === 'write') {

View file

@ -57,7 +57,12 @@ hterm.TextAttributes.splitWidecharString = function (str) {
}; };
// hterm Unicode patch // hterm Unicode patch
const cache = [];
lib.wc.strWidth = function (str) { lib.wc.strWidth = function (str) {
const shouldCache = str.length === 1;
if (shouldCache && cache[str] !== undefined) {
return cache[str];
}
const chars = runes(str); const chars = runes(str);
let width = 0; let width = 0;
let rv = 0; let rv = 0;
@ -70,6 +75,9 @@ lib.wc.strWidth = function (str) {
} }
rv += width * ((codePoint <= 0xffff) ? 1 : 2); rv += width * ((codePoint <= 0xffff) ? 1 : 2);
} }
if (shouldCache) {
cache[str] = rv;
}
return rv; return rv;
}; };

View file

@ -16,7 +16,6 @@ import {
const initialState = Immutable({ const initialState = Immutable({
sessions: {}, sessions: {},
write: null,
activeUid: null activeUid: null
}); });
@ -26,7 +25,6 @@ function Session(obj) {
title: '', title: '',
cols: null, cols: null,
rows: null, rows: null,
write: null,
url: null, url: null,
cleared: false, cleared: false,
shell: '', shell: '',
@ -34,13 +32,6 @@ function Session(obj) {
}).merge(obj); }).merge(obj);
} }
function Write(obj) {
return Immutable({
uid: '',
data: ''
}).merge(obj);
}
const reducer = (state = initialState, action) => { const reducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case SESSION_ADD: case SESSION_ADD:
@ -73,15 +64,20 @@ const reducer = (state = initialState, action) => {
}, {deep: true}); }, {deep: true});
case SESSION_PTY_DATA: case SESSION_PTY_DATA:
return state // we avoid a direct merge for perf reasons
.set('write', Write(action)) // as this is the most common action
.merge({ if (state.sessions[action.uid] &&
sessions: { state.sessions[action.uid].cleared) {
[action.uid]: { return state
cleared: false .merge({
sessions: {
[action.uid]: {
cleared: false
}
} }
} }, {deep: true});
}, {deep: true}); }
return state;
case SESSION_PTY_EXIT: case SESSION_PTY_EXIT:
if (state.sessions[action.uid]) { if (state.sessions[action.uid]) {

View file

@ -4,6 +4,7 @@ import createLogger from 'redux-logger';
import rootReducer from '../reducers/index'; import rootReducer from '../reducers/index';
import effects from '../utils/effects'; import effects from '../utils/effects';
import * as plugins from '../utils/plugins'; import * as plugins from '../utils/plugins';
import writeMiddleware from './write-middleware';
export default () => { export default () => {
const logger = createLogger({ const logger = createLogger({
@ -17,6 +18,7 @@ export default () => {
plugins.middleware, plugins.middleware,
thunk, thunk,
effects, effects,
writeMiddleware,
logger logger
), ),
window.devToolsExtension() window.devToolsExtension()

View file

@ -3,6 +3,7 @@ import thunk from 'redux-thunk';
import rootReducer from '../reducers/index'; import rootReducer from '../reducers/index';
import effects from '../utils/effects'; import effects from '../utils/effects';
import * as plugins from '../utils/plugins'; import * as plugins from '../utils/plugins';
import writeMiddleware from './write-middleware';
export default () => export default () =>
createStore( createStore(
@ -11,6 +12,7 @@ export default () =>
thunk, thunk,
plugins.middleware, plugins.middleware,
thunk, thunk,
effects effects,
writeMiddleware
) )
); );

View file

@ -0,0 +1,14 @@
import terms from '../terms';
// the only side effect we perform from middleware
// is to write to the react term instance directly
// to avoid a performance hit
export default () => next => action => {
if (action.type === 'SESSION_PTY_DATA') {
const term = terms[action.uid];
if (term) {
term.write(action.data);
}
}
next(action);
};

9
lib/terms.js Normal file
View file

@ -0,0 +1,9 @@
// react Term components add themselves
// to this object upon mounting / unmounting
// this is to allow imperative access to the
// term API, which is a performance
// optimization for the most common action
// within the system
const terms = {};
export default terms;