Cleaning and some optimizations
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -17,3 +23,39 @@ jar {
|
||||
'Implementation-Version': archiveVersion
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
groupId = 'haus.nightmare.j3270'
|
||||
artifactId = 'lib3270j'
|
||||
version = project.version
|
||||
|
||||
pom {
|
||||
name = 'lib3270j'
|
||||
description = 'Pure Java IBM 3270 terminal emulation and protocol library with Host On-Demand (HOD) parity'
|
||||
url = 'https://github.com/rudi/j3270'
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Unlicense'
|
||||
url = 'https://unlicense.org/'
|
||||
distribution = 'repo'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'rudi'
|
||||
name = 'Rudi'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection = 'scm:git:git://github.com/rudi/j3270.git'
|
||||
developerConnection = 'scm:git:ssh://github.com/rudi/j3270.git'
|
||||
url = 'https://github.com/rudi/j3270'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ import java.util.Arrays;
|
||||
* Reusable, resizable byte buffer that provides zero-copy access to its internal
|
||||
* array and supports ByteBuffer slicing to minimize heap allocations during
|
||||
* network stream processing.
|
||||
* <p>
|
||||
* <b>Threading Contract:</b> This class is NOT thread-safe and is designed for
|
||||
* high-throughput single-threaded or thread-confined stream accumulation (such as
|
||||
* Telnet network receiver loops matching IBM Host On-Demand {@code sArray} and
|
||||
* {@code sb_buffer}). External synchronization must be used if instances are shared
|
||||
* across threads.
|
||||
*/
|
||||
public class FastByteBuffer extends OutputStream {
|
||||
|
||||
@@ -31,13 +37,13 @@ public class FastByteBuffer extends OutputStream {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int b) {
|
||||
public void write(int b) {
|
||||
ensureCapacity(count + 1);
|
||||
buf[count++] = (byte) b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] b, int off, int len) {
|
||||
public void write(byte[] b, int off, int len) {
|
||||
if (b == null || len <= 0) return;
|
||||
ensureCapacity(count + len);
|
||||
System.arraycopy(b, off, buf, count, len);
|
||||
@@ -48,29 +54,29 @@ public class FastByteBuffer extends OutputStream {
|
||||
* Direct reference to internal buffer array.
|
||||
* Use {@link #size()} to determine active length.
|
||||
*/
|
||||
public synchronized byte[] buffer() {
|
||||
public byte[] buffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
public synchronized int size() {
|
||||
public int size() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
public void reset() {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a read-only ByteBuffer view wrapping active bytes without copying.
|
||||
*/
|
||||
public synchronized ByteBuffer asByteBuffer() {
|
||||
public ByteBuffer asByteBuffer() {
|
||||
return ByteBuffer.wrap(buf, 0, count).asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a read-only ByteBuffer slice for a sub-range without copying.
|
||||
*/
|
||||
public synchronized ByteBuffer slice(int offset, int length) {
|
||||
public ByteBuffer slice(int offset, int length) {
|
||||
if (offset < 0 || length < 0 || offset + length > count) {
|
||||
throw new IndexOutOfBoundsException("offset=" + offset + " length=" + length + " size=" + count);
|
||||
}
|
||||
@@ -80,7 +86,7 @@ public class FastByteBuffer extends OutputStream {
|
||||
/**
|
||||
* Produces a copied byte array if an isolated copy is explicitly required.
|
||||
*/
|
||||
public synchronized byte[] toByteArray() {
|
||||
public byte[] toByteArray() {
|
||||
return Arrays.copyOf(buf, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,14 @@ public class ExtendedAttribute {
|
||||
*/
|
||||
public char ucs4;
|
||||
|
||||
public ExtendedAttribute() {}
|
||||
|
||||
public ExtendedAttribute(ExtendedAttribute other) {
|
||||
if (other != null) {
|
||||
copyFrom(other);
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear all attributes. */
|
||||
public void clear() {
|
||||
ec = 0;
|
||||
|
||||
@@ -197,48 +197,99 @@ public class ScreenBuffer {
|
||||
return buffer[addr];
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable snapshot container for tear-free, lock-free screen display.
|
||||
* Published atomically to UI thread / EDT via volatile reference release.
|
||||
*/
|
||||
public static final class DisplaySnapshot {
|
||||
public final ExtendedAttribute[] cells;
|
||||
public final int rows;
|
||||
public final int cols;
|
||||
public final int cursorAddress;
|
||||
public final long version;
|
||||
|
||||
public DisplaySnapshot(ExtendedAttribute[] cells, int rows, int cols, int cursorAddress) {
|
||||
this(cells, rows, cols, cursorAddress, 0L);
|
||||
}
|
||||
|
||||
public DisplaySnapshot(ExtendedAttribute[] cells, int rows, int cols, int cursorAddress, long version) {
|
||||
this.cells = cells;
|
||||
this.rows = rows;
|
||||
this.cols = cols;
|
||||
this.cursorAddress = cursorAddress;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public ExtendedAttribute getCell(int addr) {
|
||||
if (cells == null || addr < 0 || addr >= cells.length) {
|
||||
return null;
|
||||
}
|
||||
return cells[addr];
|
||||
}
|
||||
|
||||
public int getRows() { return rows; }
|
||||
public int getCols() { return cols; }
|
||||
public int getCursorAddress() { return cursorAddress; }
|
||||
public long getVersion() { return version; }
|
||||
}
|
||||
|
||||
private volatile DisplaySnapshot currentSnapshot;
|
||||
private ExtendedAttribute[] displaySnapshot;
|
||||
private int displayRows;
|
||||
private int displayCols;
|
||||
private int displayCursorAddress;
|
||||
private long snapshotVersion = 0;
|
||||
|
||||
/**
|
||||
* Atomically creates a snapshot of the current presentation buffer for tear-free rendering.
|
||||
* Takes ~2 microseconds and eliminates mutual thread contention with the UI thread.
|
||||
* Atomically creates an immutable snapshot of the current presentation buffer for tear-free rendering.
|
||||
* Published via a volatile release fence, eliminating mutual monitor contention with the UI paint loop.
|
||||
*/
|
||||
public synchronized void updateDisplaySnapshot() {
|
||||
int size = rows * cols;
|
||||
if (displaySnapshot == null || displaySnapshot.length < size) {
|
||||
displaySnapshot = new ExtendedAttribute[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
displaySnapshot[i] = new ExtendedAttribute();
|
||||
}
|
||||
}
|
||||
ExtendedAttribute[] snapshotCells = new ExtendedAttribute[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
displaySnapshot[i].copyFrom(buffer[i]);
|
||||
ExtendedAttribute ea = new ExtendedAttribute();
|
||||
ea.copyFrom(buffer[i]);
|
||||
snapshotCells[i] = ea;
|
||||
}
|
||||
this.displayRows = rows;
|
||||
this.displayCols = cols;
|
||||
this.displayCursorAddress = cursorAddress;
|
||||
this.snapshotVersion++;
|
||||
this.displaySnapshot = snapshotCells;
|
||||
this.currentSnapshot = new DisplaySnapshot(snapshotCells, rows, cols, cursorAddress, snapshotVersion);
|
||||
}
|
||||
|
||||
public synchronized ExtendedAttribute getDisplayCell(int addr) {
|
||||
if (displaySnapshot == null || addr < 0 || addr >= displayRows * displayCols) {
|
||||
public DisplaySnapshot getDisplaySnapshot() {
|
||||
return currentSnapshot;
|
||||
}
|
||||
|
||||
public long getSnapshotVersion() {
|
||||
DisplaySnapshot snap = this.currentSnapshot;
|
||||
return snap != null ? snap.version : snapshotVersion;
|
||||
}
|
||||
|
||||
public ExtendedAttribute getDisplayCell(int addr) {
|
||||
DisplaySnapshot snap = this.currentSnapshot;
|
||||
if (snap == null || addr < 0 || addr >= snap.cells.length) {
|
||||
return getCell(addr);
|
||||
}
|
||||
return displaySnapshot[addr];
|
||||
return snap.cells[addr];
|
||||
}
|
||||
|
||||
public synchronized int getDisplayRows() {
|
||||
return displayRows > 0 ? displayRows : rows;
|
||||
public int getDisplayRows() {
|
||||
DisplaySnapshot snap = this.currentSnapshot;
|
||||
return (snap != null && snap.rows > 0) ? snap.rows : (displayRows > 0 ? displayRows : rows);
|
||||
}
|
||||
|
||||
public synchronized int getDisplayCols() {
|
||||
return displayCols > 0 ? displayCols : cols;
|
||||
public int getDisplayCols() {
|
||||
DisplaySnapshot snap = this.currentSnapshot;
|
||||
return (snap != null && snap.cols > 0) ? snap.cols : (displayCols > 0 ? displayCols : cols);
|
||||
}
|
||||
|
||||
public synchronized int getDisplayCursorAddress() {
|
||||
return displayRows > 0 ? displayCursorAddress : cursorAddress;
|
||||
public int getDisplayCursorAddress() {
|
||||
DisplaySnapshot snap = this.currentSnapshot;
|
||||
return (snap != null && snap.rows > 0) ? snap.cursorAddress : (displayRows > 0 ? displayCursorAddress : cursorAddress);
|
||||
}
|
||||
|
||||
// ========== Dimension accessors ==========
|
||||
@@ -346,6 +397,10 @@ public class ScreenBuffer {
|
||||
oldAddr = this.cursorAddress;
|
||||
this.cursorAddress = addr;
|
||||
this.displayCursorAddress = addr;
|
||||
DisplaySnapshot snap = this.currentSnapshot;
|
||||
if (snap != null) {
|
||||
this.currentSnapshot = new DisplaySnapshot(snap.cells, snap.rows, snap.cols, addr, snap.version);
|
||||
}
|
||||
}
|
||||
if (oldAddr != addr) {
|
||||
notifyCursorMoved(oldAddr, addr);
|
||||
|
||||
@@ -49,6 +49,9 @@ public class TelnetFSM {
|
||||
// 3270 input buffer (accumulated between telnet framing)
|
||||
private final haus.nightmare.lib3270j.datastream.FastByteBuffer ibuf = new haus.nightmare.lib3270j.datastream.FastByteBuffer(32768);
|
||||
|
||||
// Reusable single-byte scratch buffer for plain NVT data dispatch (Phase 27.5 / T1-C)
|
||||
private final byte[] nvtSingleByteScratch = new byte[1];
|
||||
|
||||
// Sub-negotiation buffer
|
||||
private final ByteArrayOutputStream sbbuf = new ByteArrayOutputStream(4096);
|
||||
|
||||
@@ -324,7 +327,8 @@ public class TelnetFSM {
|
||||
boolean isPlainNvt = (connectionState == ConnectionState.CONNECTED_NVT || connectionState == ConnectionState.CONNECTED_NVT_CHAR)
|
||||
&& !(hisOpts[TELOPT_BINARY] && hisOpts[TELOPT_EOR]);
|
||||
if (isPlainNvt) {
|
||||
nvtProcessor.processNVTData(new byte[] { (byte) c }, 0, 1);
|
||||
nvtSingleByteScratch[0] = (byte) c;
|
||||
nvtProcessor.processNVTData(nvtSingleByteScratch, 0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user