changeset 537:623e409c976a

fix
author riono
date Wed, 05 Jun 2019 17:29:04 +0900
parents 170b1e852b26 (current diff) 17a2d0ea5c03 (diff)
children 6620e04f994c
files
diffstat 2 files changed, 124 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/ZRLEDecoder.java	Wed Jun 05 16:26:28 2019 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/ZRLEDecoder.java	Wed Jun 05 17:29:04 2019 +0900
@@ -31,12 +31,9 @@
 import jp.ac.u_ryukyu.treevnc.CheckDelay;
 import jp.ac.u_ryukyu.treevnc.TreeRFBProto;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
 import java.util.LinkedList;
-import java.util.zip.DataFormatException;
 import java.util.zip.Deflater;
 
 public class ZRLEDecoder extends ZlibDecoder {
@@ -45,6 +42,8 @@
     private int[] palette;
 
     class TileLoop {
+		private final boolean blocking;
+		private final int half;
 		private int deflate_size = 65507;
 		private ByteBuffer c1;
 		private int width; // phase2 length
@@ -55,6 +54,12 @@
 		private int prevoffset;
 		private Deflater deflater;
 		private int rectPos;
+		private int ztileInLine;
+		private int hwidth;
+		private int hc1width;
+		private int hoffset;
+		private int hc1offset;
+		private int discard;
 
 		/**
 		 * Multicast framebufferUpdate to children.
@@ -79,14 +84,25 @@
 
 		public TileLoop(int offset) {
 			prevoffset = prevLineOffset = prevC1Offset = offset;
+			if (offset < deflate_size+spanGap) {
+				// packet size fit in broadcast send it all at once
+				blocking = false;
+			} else
+				blocking = true;
+			discard = 0; half = 0;
 		}
 
-		private void zrleeBlocking(TreeRFBProto rfb, ByteBuffer header, FramebufferUpdateRectangle rect) {
+		private void zrleeBlocking(TreeRFBProto rfb, ByteBuffer header, FramebufferUpdateRectangle rect, byte bytes[]) {
 			// dump32(inputs);
 			deflater = rfb.deflater;
 			newMulticastPacket(rfb, rect);
 			c1.put(header.get(0));
 			c1rect = new FramebufferUpdateRectangle(rect.x, rect.y, 0, 0);
+			if (!blocking) {
+				deflater.setInput(bytes,0,prevoffset);
+				deflater.deflate(c1);
+				flushMuticast(rfb);
+			}
 			return;
 		}
 
@@ -104,19 +120,36 @@
 			c1.putInt(0);     // should be data length
 			width = 0;
 			rectPos = 4;
+			ztileInLine = 0;
 		}
 
 		int spanGap = 128;
 		/**
 		 *
+		 * Series of tiles compose at most three rectangles. SYNC_FLUSH is necessary on
+		 * rectangle boundaries.
+		 *
 		 *                +----+
-		 *                |    |   phase 0
+		 *                |  | |   phase 0
 		 *     +---------------+
-		 *     |               |   phase 1
+		 *     |       |       |   phase 1
 		 *     +----+----------+
-		 *     |    |              phase 2
+		 *     |  | |              phase 2
 		 *     +----+
 		 *
+		 * Broadcast packet have to less than 64kbytes
+		 *     A tile 64x64x3 11288byte, a packet can contain 5 raw tiles, when these are
+		 *     compressed 10 to 100 tiles can be stored. It is impossible to predict the
+		 *     compression rate. To check the compressed capacity, Deflate.needsInputs() can
+		 *     be used. If needsInputs() is false on SYNC_FLUSH, smaller input is necessary.
+		 *
+		 *     We'll try 512 tiles before SYNC_FLUSH in a phase, if it fails try flush former 256 tiles.
+		 *     If it will failed again, flush the previous line and do flush 512 tiles in new Packet.
+		 *     If it failed again try former 256 tiles flushed, if this failes again dicard the former half.
+		 *     The last case cannot happen but former 256 tiles have to be flushed, because the next 256 lines
+		 *     may failed again and restart the from this point.
+		 *     The next packet start with later 256 tiles filled and unflushed.
+		 *
 		 * @param rfb
 		 * @param last
 		 * @param rect
@@ -125,40 +158,34 @@
 		 * @param tileW
 		 * @param tileH
 		 */
+		
+		int MAX_ZTILE = 512;
+
 		public void multicastPut(TreeRFBProto rfb, boolean last, FramebufferUpdateRectangle rect, byte[] bytes, int offset, int tileW, int tileH) {
+			if (!blocking) return;
 			int span = offset - prevoffset;
 			deflater.setInput(bytes,prevoffset,span);
 			prevoffset = offset;
 			c1rect.width  += tileW; width += tileW;
 			if (c1rect.x > rect.x) {  // phase 0
 				if (c1rect.x+c1rect.width < rect.x+rect.width) {
-					if (c1.remaining() > spanGap ) {
-						deflater.deflate(c1, Deflater.NO_FLUSH);
-						if (!deflater.needsInput()) flushRectangle(rect);
-						return;
-					}
+					compressAndCheckFlush(rfb, rect, bytes, offset,false, last);
 				} else {
 					c1rect.width = rect.x+rect.width-c1rect.x ;
 					c1rect.height += tileH;
-					deflater.deflate(c1, Deflater.NO_FLUSH);
-					flushRectangle(rect);
+					compressAndCheckFlush(rfb,rect,bytes,offset,true, last);
 				}
-				return;
-			}
-			if (!last && c1.remaining() > spanGap) { // phase 1
+			} else if (!last && c1.remaining() > spanGap) { // phase 1
 				if (width >= rect.width) {
 					c1rect.width = rect.width;
 					width = 0;
 					c1rect.height += tileH;
 					prevLineOffset = offset;
 					prevC1Offset = c1.position();
-					deflater.deflate(c1, Deflater.SYNC_FLUSH);
-					if (!deflater.needsInput()) flushRectangle(rect);
+					compressAndCheckFlush(rfb,rect,bytes,offset,true, last);
 				} else {
-					deflater.deflate(c1, Deflater.NO_FLUSH);
-					if (!deflater.needsInput()) flushRectangle(rect);
+					compressAndCheckFlush(rfb,rect,bytes,offset,false, last);
 				}
-				return;
 			} else { // phase2
 				//  rewind to the last line finish phase 1
 				int savew = width;
@@ -168,12 +195,73 @@
 					// recompress overrun and flush phase 2
 					c1rect.width = savew;
 					c1rect.height = tileH;
-					deflater.setInput(bytes, prevLineOffset, span);
+					compressAndCheckFlush(rfb,rect,bytes,offset,true, last);
+				}
+			}
+		}
+
+		private void compressAndCheckFlush(TreeRFBProto rfb, FramebufferUpdateRectangle rect, byte[] bytes, int offset, boolean flush, boolean last) {
+			ztileInLine++;
+			if (!flush && ztileInLine < MAX_ZTILE) {
+				if (ztileInLine == MAX_ZTILE/2) {
+					hwidth = width; hc1width = c1rect.width; hoffset = offset; hc1offset = c1.position();
+				}
+				deflater.deflate(c1, Deflater.NO_FLUSH);
+			} else {
+				deflater.deflate(c1, Deflater.SYNC_FLUSH);
+				if (!deflater.needsInput()) {
+					// too large, try half line
+					width = hwidth;
+					c1rect.width = hc1width;
+					c1.position(hc1offset);
+					deflater.setInput(bytes, prevLineOffset, hoffset - prevC1Offset);
 					deflater.deflate(c1, Deflater.SYNC_FLUSH);
-					flushRectangle(rect);
+					int from, len;
+					if (!deflater.needsInput()) {
+						// flush previous line and start new packet
+						c1.position(prevC1Offset);
+						unputrectangle();
+						flushMuticast(rfb);
+						newMulticastPacket(rfb, rect);
+						nextRectangle(rect);
+						// we already reached MIX_ZTILE do half of them, do compress right now
+						from = prevC1Offset;
+						len = offset - prevLineOffset;
+						deflater.setInput(bytes, from, len);
+						deflater.deflate(c1, Deflater.SYNC_FLUSH);
+						if (deflater.needsInput()) {
+							flushRectangle(rect);  // we are the flushed last line
+							return;
+						}
+						// half size should always succeed
+						from = prevC1Offset;
+						len = hoffset - prevLineOffset;
+						deflater.setInput(bytes, from, len);
+						deflater.deflate(c1, Deflater.SYNC_FLUSH);
+						if (!deflater.needsInput()) { /* fatal error discard this line */
+							discard++;
+							if (!last) {
+								newMulticastPacket(rfb, rect);
+								nextRectangle(rect);
+							}
+							return;
+						} else
+							flushRectangle(rect);  // normal case
+						// later half is remain continue
+					} else {
+						flushMuticast(rfb);
+						newMulticastPacket(rfb,rect);
+						nextRectangle(rect);
+					}
+					// do later half in new Packet
+					from = hoffset ; len = offset - hoffset;
+					deflater.setInput(bytes, from, len);
+					deflater.deflate(c1, Deflater.NO_FLUSH);
+					hwidth = width;
+					hoffset = offset;
+					hc1offset = c1.position();
+					ztileInLine = MAX_ZTILE/2;
 				}
-				flushMuticast(rfb);
-				if (!last) newMulticastPacket(rfb,rect);
 			}
 		}
 
@@ -193,6 +281,11 @@
 			rectPos = c1.position();
 			c1.putShort(2,(short)(c1.getShort(2)+1));    // increment rectangle count
 			nextRectangle(rect);
+			ztileInLine = 0;
+		}
+
+		private void unputrectangle() {
+			c1.putShort(2,(short)(c1.getShort(2)-1));    // last rectangle is canceled
 		}
 
 		private void nextRectangle(FramebufferUpdateRectangle rect) {
@@ -208,8 +301,6 @@
 		}
 
 		private void flushMuticast(TreeRFBProto rfb) {
-			deflater.deflate(c1, Deflater.FULL_FLUSH);
-			deflater.finish();
 			c1.flip();
 			//System.out.println("multicastPut: " + c1rect + " length: " + (c1.remaining()-c1headerPos-header.limit()));
 			try {
@@ -266,7 +357,7 @@
         }
 
 		if (rfbProto.multicastBlocking)  {
-			tileloop.zrleeBlocking(rfbProto, header, rect);
+			tileloop.zrleeBlocking(rfbProto, header, rect,bytes);
 		}
 		for (int tileY = rect.y; tileY < maxY; tileY += MAX_TILE_SIZE) {
 			int tileHeight = Math.min(maxY - tileY, MAX_TILE_SIZE);
--- a/src/main/java/jp/ac/u_ryukyu/treevnc/TreeRFBProto.java	Wed Jun 05 16:26:28 2019 +0900
+++ b/src/main/java/jp/ac/u_ryukyu/treevnc/TreeRFBProto.java	Wed Jun 05 17:29:04 2019 +0900
@@ -78,7 +78,9 @@
     private static int uniqueNodeId = 0; // uniquenodeid in all trees (less than MAX_UNIQUE_NODE_ID)
 
     private boolean stopBroadcast;
-    public boolean multicastBlocking = false;
+    // multicastBlocking is an internal test flag
+    //   perform blocking on TCP connection
+    public boolean multicastBlocking = true;
 
     public TreeRFBProto(boolean isTreeManager, ViewerInterface viewer) {
         nets.setMyRfb(this);
@@ -905,7 +907,7 @@
         setProtocolContext(workingProtocol);
         this.connectionPresenter = connectionPresenter;
         connectionPresenter.viewer.setConnectionPresenter(connectionPresenter);
-        if (connectionPresenter.isUseMulticast()) multicastBlocking = true;
+        // if (connectionPresenter.isUseMulticast()) multicastBlocking = true;
         connectionPresenter.addModel("ConnectionParamsModel", connectionPresenter.getConnectionParams());
         if (previousContext != null && isTreeManager() && hasParent()) {
             Reader previousReader = previousContext.getReader();