From: Kevin Lamonte Date: Wed, 7 Aug 2019 00:47:36 +0000 (-0500) Subject: #49 reduce use of synchronized X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=ab215e38e98a76ad189ba537e37c420ae515e4c0;p=fanfix-jexer.git #49 reduce use of synchronized --- diff --git a/src/jexer/TTerminalWindow.java b/src/jexer/TTerminalWindow.java index 6d06fc8..5970b6c 100644 --- a/src/jexer/TTerminalWindow.java +++ b/src/jexer/TTerminalWindow.java @@ -123,6 +123,26 @@ public class TTerminalWindow extends TScrollableWindow */ private boolean haveTimer = false; + /** + * The last seen scrollback lines. + */ + private List scrollback; + + /** + * The last seen display lines. + */ + private List display; + + /** + * If true, the display has changed and needs updating. + */ + private volatile boolean dirty = true; + + /** + * Time that the display was last updated. + */ + private long lastUpdateTime = 0; + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -306,102 +326,125 @@ public class TTerminalWindow extends TScrollableWindow */ @Override public void draw() { - // Synchronize against the emulator so we don't stomp on its reader - // thread. - synchronized (emulator) { - - // Update the scroll bars - reflowData(); - // Draw the box using my superclass - super.draw(); - - List scrollback = emulator.getScrollbackBuffer(); - List display = emulator.getDisplayBuffer(); + int width = getDisplayWidth(); + boolean syncEmulator = false; + if ((System.currentTimeMillis() - lastUpdateTime > 125) + && (dirty == true) + ) { + // Too much time has passed, draw it all. + syncEmulator = true; + } else if (emulator.isReading() && (dirty == false)) { + // Wait until the emulator has brought more data in. + syncEmulator = false; + } else if (!emulator.isReading() && (dirty == true)) { + // The emulator won't receive more data, update the display. + syncEmulator = true; + } - // Put together the visible rows - int visibleHeight = getHeight() - 2; - int visibleBottom = scrollback.size() + display.size() - + getVerticalValue(); - assert (visibleBottom >= 0); + if ((syncEmulator == true) + || (scrollback == null) + || (display == null) + ) { + // We want to minimize the amount of time we have the emulator + // locked. Grab a copy of its display. + synchronized (emulator) { + // Update the scroll bars + reflowData(); - List preceedingBlankLines = new ArrayList(); - int visibleTop = visibleBottom - visibleHeight; - if (visibleTop < 0) { - for (int i = visibleTop; i < 0; i++) { - preceedingBlankLines.add(emulator.getBlankDisplayLine()); + if ((scrollback == null) || emulator.isReading()) { + scrollback = copyBuffer(emulator.getScrollbackBuffer()); + display = copyBuffer(emulator.getDisplayBuffer()); } - visibleTop = 0; + width = emulator.getWidth(); } - assert (visibleTop >= 0); + dirty = false; + } - List displayLines = new ArrayList(); - displayLines.addAll(scrollback); - displayLines.addAll(display); + // Draw the box using my superclass + super.draw(); - List visibleLines = new ArrayList(); - visibleLines.addAll(preceedingBlankLines); - visibleLines.addAll(displayLines.subList(visibleTop, - visibleBottom)); + // Put together the visible rows + int visibleHeight = getHeight() - 2; + int visibleBottom = scrollback.size() + display.size() + + getVerticalValue(); + assert (visibleBottom >= 0); - visibleHeight -= visibleLines.size(); - assert (visibleHeight >= 0); + List preceedingBlankLines = new ArrayList(); + int visibleTop = visibleBottom - visibleHeight; + if (visibleTop < 0) { + for (int i = visibleTop; i < 0; i++) { + preceedingBlankLines.add(emulator.getBlankDisplayLine()); + } + visibleTop = 0; + } + assert (visibleTop >= 0); + + List displayLines = new ArrayList(); + displayLines.addAll(scrollback); + displayLines.addAll(display); + + List visibleLines = new ArrayList(); + visibleLines.addAll(preceedingBlankLines); + visibleLines.addAll(displayLines.subList(visibleTop, + visibleBottom)); + + visibleHeight -= visibleLines.size(); + assert (visibleHeight >= 0); + + // Now draw the emulator screen + int row = 1; + for (DisplayLine line: visibleLines) { + int widthMax = width; + if (line.isDoubleWidth()) { + widthMax /= 2; + } + if (widthMax > getWidth() - 2) { + widthMax = getWidth() - 2; + } + for (int i = 0; i < widthMax; i++) { + Cell ch = line.charAt(i); - // Now draw the emulator screen - int row = 1; - for (DisplayLine line: visibleLines) { - int widthMax = emulator.getWidth(); - if (line.isDoubleWidth()) { - widthMax /= 2; - } - if (widthMax > getWidth() - 2) { - widthMax = getWidth() - 2; + if (ch.isImage()) { + putCharXY(i + 1, row, ch); + continue; } - for (int i = 0; i < widthMax; i++) { - Cell ch = line.charAt(i); - if (ch.isImage()) { - putCharXY(i + 1, row, ch); - continue; - } - - Cell newCell = new Cell(); - newCell.setTo(ch); - boolean reverse = line.isReverseColor() ^ ch.isReverse(); - newCell.setReverse(false); - if (reverse) { - if (ch.getForeColorRGB() < 0) { - newCell.setBackColor(ch.getForeColor()); - newCell.setBackColorRGB(-1); - } else { - newCell.setBackColorRGB(ch.getForeColorRGB()); - } - if (ch.getBackColorRGB() < 0) { - newCell.setForeColor(ch.getBackColor()); - newCell.setForeColorRGB(-1); - } else { - newCell.setForeColorRGB(ch.getBackColorRGB()); - } + Cell newCell = new Cell(); + newCell.setTo(ch); + boolean reverse = line.isReverseColor() ^ ch.isReverse(); + newCell.setReverse(false); + if (reverse) { + if (ch.getForeColorRGB() < 0) { + newCell.setBackColor(ch.getForeColor()); + newCell.setBackColorRGB(-1); + } else { + newCell.setBackColorRGB(ch.getForeColorRGB()); } - if (line.isDoubleWidth()) { - putDoubleWidthCharXY(line, (i * 2) + 1, row, newCell); + if (ch.getBackColorRGB() < 0) { + newCell.setForeColor(ch.getBackColor()); + newCell.setForeColorRGB(-1); } else { - putCharXY(i + 1, row, newCell); + newCell.setForeColorRGB(ch.getBackColorRGB()); } } - row++; - if (row == getHeight() - 1) { - // Don't overwrite the box edge - break; + if (line.isDoubleWidth()) { + putDoubleWidthCharXY(line, (i * 2) + 1, row, newCell); + } else { + putCharXY(i + 1, row, newCell); } } - CellAttributes background = new CellAttributes(); - // Fill in the blank lines on bottom - for (int i = 0; i < visibleHeight; i++) { - hLineXY(1, i + row, getWidth() - 2, ' ', background); + row++; + if (row == getHeight() - 1) { + // Don't overwrite the box edge + break; } - - } // synchronized (emulator) + } + CellAttributes background = new CellAttributes(); + // Fill in the blank lines on bottom + for (int i = 0; i < visibleHeight; i++) { + hLineXY(1, i + row, getWidth() - 2, ' ', background); + } } @@ -497,25 +540,21 @@ public class TTerminalWindow extends TScrollableWindow return; } - // Synchronize against the emulator so we don't stomp on its reader - // thread. - synchronized (emulator) { - if (emulator.isReading()) { - // Get out of scrollback - setVerticalValue(0); - emulator.keypress(keypress.getKey()); + if (emulator.isReading()) { + // Get out of scrollback + setVerticalValue(0); + emulator.addUserEvent(keypress); - // UGLY HACK TIME! cmd.exe needs CRLF, not just CR, so if - // this is kBEnter then also send kbCtrlJ. - if (System.getProperty("os.name").startsWith("Windows")) { - if (keypress.equals(kbEnter)) { - emulator.keypress(kbCtrlJ); - } + // UGLY HACK TIME! cmd.exe needs CRLF, not just CR, so if + // this is kBEnter then also send kbCtrlJ. + if (System.getProperty("os.name").startsWith("Windows")) { + if (keypress.equals(kbEnter)) { + emulator.addUserEvent(new TKeypressEvent(kbCtrlJ)); } - - readEmulatorState(); - return; } + + readEmulatorState(); + return; } // Process is closed, honor "normal" TUI keystrokes @@ -548,13 +587,11 @@ public class TTerminalWindow extends TScrollableWindow } } if (mouseOnEmulator(mouse)) { - synchronized (emulator) { - mouse.setX(mouse.getX() - 1); - mouse.setY(mouse.getY() - 1); - emulator.mouse(mouse); - readEmulatorState(); - return; - } + mouse.setX(mouse.getX() - 1); + mouse.setY(mouse.getY() - 1); + emulator.addUserEvent(mouse); + readEmulatorState(); + return; } // Emulator didn't consume it, pass it on @@ -575,13 +612,11 @@ public class TTerminalWindow extends TScrollableWindow } if (mouseOnEmulator(mouse)) { - synchronized (emulator) { - mouse.setX(mouse.getX() - 1); - mouse.setY(mouse.getY() - 1); - emulator.mouse(mouse); - readEmulatorState(); - return; - } + mouse.setX(mouse.getX() - 1); + mouse.setY(mouse.getY() - 1); + emulator.addUserEvent(mouse); + readEmulatorState(); + return; } // Emulator didn't consume it, pass it on @@ -602,13 +637,11 @@ public class TTerminalWindow extends TScrollableWindow } if (mouseOnEmulator(mouse)) { - synchronized (emulator) { - mouse.setX(mouse.getX() - 1); - mouse.setY(mouse.getY() - 1); - emulator.mouse(mouse); - readEmulatorState(); - return; - } + mouse.setX(mouse.getX() - 1); + mouse.setY(mouse.getY() - 1); + emulator.addUserEvent(mouse); + readEmulatorState(); + return; } // Emulator didn't consume it, pass it on @@ -789,37 +822,6 @@ public class TTerminalWindow extends TScrollableWindow } } - /** - * Called by emulator when fresh data has come in. - */ - public void displayChanged() { - getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT)); - } - - /** - * Function to call to obtain the display width. - * - * @return the number of columns in the display - */ - public int getDisplayWidth() { - if (ptypipe) { - return getWidth() - 2; - } - return 80; - } - - /** - * Function to call to obtain the display height. - * - * @return the number of rows in the display - */ - public int getDisplayHeight() { - if (ptypipe) { - return getHeight() - 2; - } - return 24; - } - /** * Hook for subclasses to be notified of the shell termination. */ @@ -906,10 +908,8 @@ public class TTerminalWindow extends TScrollableWindow */ private boolean mouseOnEmulator(final TMouseEvent mouse) { - synchronized (emulator) { - if (!emulator.isReading()) { - return false; - } + if (!emulator.isReading()) { + return false; } if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1) @@ -922,6 +922,20 @@ public class TTerminalWindow extends TScrollableWindow return false; } + /** + * Copy a display buffer. + * + * @param buffer the buffer to copy + * @return a deep copy of the buffer's data + */ + private List copyBuffer(final List buffer) { + ArrayList result = new ArrayList(buffer.size()); + for (DisplayLine line: buffer) { + result.add(new DisplayLine(line)); + } + return result; + } + /** * Draw glyphs for a double-width or double-height VT100 cell to two * screen cells. @@ -1059,4 +1073,40 @@ public class TTerminalWindow extends TScrollableWindow } } + // ------------------------------------------------------------------------ + // DisplayListener -------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Called by emulator when fresh data has come in. + */ + public void displayChanged() { + dirty = true; + getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT)); + } + + /** + * Function to call to obtain the display width. + * + * @return the number of columns in the display + */ + public int getDisplayWidth() { + if (ptypipe) { + return getWidth() - 2; + } + return 80; + } + + /** + * Function to call to obtain the display height. + * + * @return the number of rows in the display + */ + public int getDisplayHeight() { + if (ptypipe) { + return getHeight() - 2; + } + return 24; + } + } diff --git a/src/jexer/bits/Cell.java b/src/jexer/bits/Cell.java index 5db5f43..4be433c 100644 --- a/src/jexer/bits/Cell.java +++ b/src/jexer/bits/Cell.java @@ -134,6 +134,15 @@ public final class Cell extends CellAttributes { this.ch = ch; } + /** + * Public constructor creates a duplicate. + * + * @param cell the instance to copy + */ + public Cell(final Cell cell) { + setTo(cell); + } + // ------------------------------------------------------------------------ // Cell ------------------------------------------------------------------- // ------------------------------------------------------------------------ @@ -260,7 +269,7 @@ public final class Cell extends CellAttributes { /** * Setter for cell width. * - * @param ch new cell width, one of Width.SINGLE, Width.LEFT, or + * @param width new cell width, one of Width.SINGLE, Width.LEFT, or * Width.RIGHT */ public void setWidth(final Width width) { diff --git a/src/jexer/tterminal/DisplayLine.java b/src/jexer/tterminal/DisplayLine.java index 7c76713..74e63a8 100644 --- a/src/jexer/tterminal/DisplayLine.java +++ b/src/jexer/tterminal/DisplayLine.java @@ -80,6 +80,21 @@ public class DisplayLine { // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Public constructor makes a duplicate (deep copy). + * + * @param line the line to duplicate + */ + public DisplayLine(final DisplayLine line) { + chars = new Cell[MAX_LINE_LENGTH]; + for (int i = 0; i < chars.length; i++) { + chars[i] = new Cell(line.chars[i]); + } + doubleWidth = line.doubleWidth; + doubleHeight = line.doubleHeight; + reverseColor = line.reverseColor; + } + /** * Public constructor sets everything to drawing attributes. * diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index f9c9866..80f0ffb 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -52,6 +52,8 @@ import jexer.bits.Color; import jexer.bits.Cell; import jexer.bits.CellAttributes; import jexer.bits.StringUtils; +import jexer.event.TInputEvent; +import jexer.event.TKeypressEvent; import jexer.event.TMouseEvent; import jexer.io.ReadTimeoutException; import jexer.io.TimeoutInputStream; @@ -490,6 +492,12 @@ public class ECMA48 implements Runnable { */ private GlyphMaker glyphMaker = null; + /** + * Input queue for keystrokes and mouse events to send to the remote + * side. + */ + private ArrayList userQueue = new ArrayList(); + /** * DECSC/DECRC save/restore a subset of the total state. This class * encapsulates those specific flags/modes. @@ -688,6 +696,12 @@ public class ECMA48 implements Runnable { } while (!done && !stopReaderThread) { + synchronized (userQueue) { + while (userQueue.size() > 0) { + handleUserEvent(userQueue.remove(0)); + } + } + try { int n = inputStream.available(); @@ -810,6 +824,31 @@ public class ECMA48 implements Runnable { // ECMA48 ----------------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Process keyboard and mouse events from the user. + * + * @param event the input event to consume + */ + private void handleUserEvent(final TInputEvent event) { + if (event instanceof TKeypressEvent) { + keypress(((TKeypressEvent) event).getKey()); + } + if (event instanceof TMouseEvent) { + mouse((TMouseEvent) event); + } + } + + /** + * Add a keyboard and mouse event from the user to the queue. + * + * @param event the input event to consume + */ + public void addUserEvent(final TInputEvent event) { + synchronized (userQueue) { + userQueue.add(event); + } + } + /** * Return the proper primary Device Attributes string. * @@ -1495,7 +1534,7 @@ public class ECMA48 implements Runnable { * * @param mouse mouse event received from the local user */ - public void mouse(final TMouseEvent mouse) { + private void mouse(final TMouseEvent mouse) { /* System.err.printf("mouse(): protocol %s encoding %s mouse %s\n", @@ -1643,7 +1682,7 @@ public class ECMA48 implements Runnable { * * @param keypress keypress received from the local user */ - public void keypress(final TKeypress keypress) { + private void keypress(final TKeypress keypress) { writeRemote(keypressToString(keypress)); } diff --git a/src/jexer/tterminal/Sixel.java b/src/jexer/tterminal/Sixel.java index 63e3c0f..f190877 100644 --- a/src/jexer/tterminal/Sixel.java +++ b/src/jexer/tterminal/Sixel.java @@ -78,9 +78,14 @@ public class Sixel { private ScanState scanState = ScanState.GROUND; /** - * Parameter characters being collected. + * Parameters being collected. */ - private ArrayList colorParams; + private int [] params = new int[5]; + + /** + * Current parameter being collected. + */ + private int paramsI = 0; /** * The sixel palette colors specified. @@ -138,7 +143,6 @@ public class Sixel { */ public Sixel(final String buffer) { this.buffer = buffer; - colorParams = new ArrayList(); palette = new HashMap(); image = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB); for (int i = 0; i < buffer.length(); i++) { @@ -187,32 +191,14 @@ public class Sixel { * Clear the parameters and flags. */ private void toGround() { - colorParams.clear(); + paramsI = 0; + for (int i = 0; i < params.length; i++) { + params[i] = 0; + } scanState = ScanState.GROUND; repeatCount = -1; } - /** - * Save a byte into the color parameters buffer. - * - * @param ch byte to save - */ - private void param(final byte ch) { - if (colorParams.size() == 0) { - colorParams.add(Integer.valueOf(0)); - } - Integer n = colorParams.get(colorParams.size() - 1); - if ((ch >= '0') && (ch <= '9')) { - n *= 10; - n += (ch - '0'); - colorParams.set(colorParams.size() - 1, n); - } - - if ((ch == ';') && (colorParams.size() < 16)) { - colorParams.add(Integer.valueOf(0)); - } - } - /** * Get a color parameter value, with a default. * @@ -221,10 +207,10 @@ public class Sixel { * @return parameter value */ private int getColorParam(final int position, final int defaultValue) { - if (colorParams.size() < position + 1) { + if (position > paramsI) { return defaultValue; } - return colorParams.get(position).intValue(); + return params[position]; } /** @@ -289,36 +275,39 @@ public class Sixel { return; } + int dy = 0; for (int i = 0; i < rep; i++) { if ((n & 0x01) != 0) { - image.setRGB(x, height + 0, rgb); - y = Math.max(y, height); + dy = 0; + image.setRGB(x, height + dy, rgb); } if ((n & 0x02) != 0) { - image.setRGB(x, height + 1, rgb); - y = Math.max(y, height + 1); + dy = 1; + image.setRGB(x, height + dy, rgb); } if ((n & 0x04) != 0) { - image.setRGB(x, height + 2, rgb); - y = Math.max(y, height + 2); + dy = 2; + image.setRGB(x, height + dy, rgb); } if ((n & 0x08) != 0) { - image.setRGB(x, height + 3, rgb); - y = Math.max(y, height + 3); + dy = 3; + image.setRGB(x, height + dy, rgb); } if ((n & 0x10) != 0) { - image.setRGB(x, height + 4, rgb); - y = Math.max(y, height + 4); + dy = 4; + image.setRGB(x, height + dy, rgb); } if ((n & 0x20) != 0) { - image.setRGB(x, height + 5, rgb); - y = Math.max(y, height + 5); + dy = 5; + image.setRGB(x, height + dy, rgb); } - x++; - if (x > width) { - width++; - assert (x == width); + if (height + dy > y) { + y = height + dy; } + x++; + } + if (x > width) { + width = x; } } @@ -328,7 +317,7 @@ public class Sixel { private void setPalette() { int idx = getColorParam(0, 0); - if (colorParams.size() == 1) { + if (paramsI == 0) { Color newColor = palette.get(idx); if (newColor != null) { color = newColor; @@ -378,7 +367,6 @@ public class Sixel { if ((ch >= 63) && (ch < 127)) { if (scanState == ScanState.COLOR) { setPalette(); - toGround(); } addSixel(ch); toGround(); @@ -458,10 +446,13 @@ public class Sixel { case COLOR: // 30-39, 3B --> param if ((ch >= '0') && (ch <= '9')) { - param((byte) ch); + params[paramsI] *= 10; + params[paramsI] += (ch - '0'); } if (ch == ';') { - param((byte) ch); + if (paramsI < params.length - 1) { + paramsI++; + } } return;