=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js' --- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js 2015-08-10 20:53:46 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js 2015-08-11 21:58:00 +0000 @@ -309,7 +309,13 @@ case 0x8B: // destroy top-level or child window var id = me.readInt32BinaryMessage(message, 1); - p2j.screen.destroyWindow(id); + var numberImages = me.readInt32BinaryMessage(message, 5); + var images = []; + for ( var i = 0; i < numberImages; i++) + { + images[i] = me.readInt32BinaryMessage(message, 9 + i << 2); + } + p2j.screen.destroyWindow(id, images); break; case 0x8C: // change visibility for top-level or child window === modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java' --- src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java 2015-08-10 20:53:46 +0000 +++ src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java 2015-08-11 21:40:08 +0000 @@ -22,11 +22,11 @@ // TODO: remove this, it is there to allow compilation import java.awt.Font; +import java.util.HashMap; import java.util.HashSet; // don't import the entire logging package as it has naming conflicts with jetty import java.util.logging.Level; import java.util.logging.Logger; - import java.util.*; import org.eclipse.jetty.util.resource.*; @@ -80,8 +80,11 @@ /** Virtual screen for drawing in a memory */ private VirtualScreen virtualScreen; - /** Images loaded by a client. */ - private final HashSet loadedImages; + /** Image usages is a mapping of an image loaded by a client to its owner windows. */ + private final Map > imageUsages; + + /** Owned images is a mapping of a window to its owned images. */ + private final Map > ownedImages; /** A cache of font metrics objects, by their remote font ID. */ private Map fontMetricsCache = new HashMap<>(); @@ -107,7 +110,8 @@ }); this.config = config; - this.loadedImages = new HashSet(); + this.imageUsages = new HashMap>(); + this.ownedImages = new HashMap>(); } /** @@ -196,16 +200,77 @@ } /** + * Returns its virtual screen that represents the physical screen in a memory + * using a sRGB pixel model. * - * @return + * @return The virtual screen to draw images in a memory. */ public VirtualScreen getVirtualScreen() { return virtualScreen; } - public boolean addImage(int imageHash) { - return this.loadedImages.add(imageHash); + /** + * Register a usage of the given image by the given window. + * + * @param imageHash + * The image hash id. + * @param windowId + * The window id. + * + * @return True iff it is the first usage of the given image. + */ + public boolean addImageUsage(int imageHash, int windowId) { + boolean newLoadedImage = false; + + Set usages = imageUsages.get(imageHash); + if (usages == null) + { + usages = new HashSet(); + imageUsages.put(imageHash, usages); + newLoadedImage = true; + } + usages.add(windowId); + + Set images = ownedImages.get(windowId); + if (images == null) + { + images = new HashSet(); + ownedImages.put(windowId, images); + } + images.add(imageHash); + + return newLoadedImage; + } + + /** + * Returns all unused images. Removes the given window from all image usages. + * + * @param windowId + * The window id. + * + * @return The set of all unused images. + */ + public Set removeUnusedImages(int windowId) { + Set imagesToRemove = new HashSet(); + Set images = ownedImages.remove(windowId); + if (images != null && !images.isEmpty()) + { + for (Integer imageHash : images) + { + Set usages = imageUsages.get(imageHash); + if (usages.remove(windowId)) + { + if (usages.isEmpty()) + { + imagesToRemove.add(imageHash); + imageUsages.remove(imageHash); + } + } + } + } + + return imagesToRemove; } /** === modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebEmulatedWindow.java' --- src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebEmulatedWindow.java 2015-08-10 22:08:54 +0000 +++ src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebEmulatedWindow.java 2015-08-11 21:40:08 +0000 @@ -21,6 +21,8 @@ package com.goldencode.p2j.ui.client.gui.driver.web; import java.awt.image.BufferedImage; +import java.util.Set; + import com.goldencode.p2j.ui.client.*; import com.goldencode.p2j.ui.client.gui.driver.*; @@ -87,7 +89,9 @@ @Override public void quit() { - websock.destroyWindow(windowId); + Set imagesToRemove = webdriver.removeUnusedImages(windowId); + + websock.destroyWindow(windowId, imagesToRemove.toArray(new Integer[imagesToRemove.size()])); } /** @@ -397,7 +401,7 @@ int imageHash = wrappedImage.getImage().hashCode(); ImageEncoding encoding; byte[] encodedImage; - if (webdriver.addImage(imageHash)) + if (webdriver.addImageUsage(imageHash, windowId)) { encoding = ImageEncoding.RAW; virtualScreen.drawImage(wrappedImage, ps.x, ps.y); === modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java' --- src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java 2015-08-10 20:53:46 +0000 +++ src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java 2015-08-11 21:51:53 +0000 @@ -132,10 +132,23 @@ * * @param id * The window id to destroy. + * @param cashedImages + * The array of client cached images to remove. */ - public void destroyWindow(int id) + public void destroyWindow(int id, Integer[] cachedImages) { - sendBinaryMessage(MSG_DESTROY_WINDOW, id); + byte[] message = new byte[9 + cachedImages.length << 2]; + + message[0] = MSG_DESTROY_WINDOW; + writeMessageInt32(message, 1, id); + writeMessageInt32(message, 5, cachedImages.length); + int idx = 9; + for(Integer imageHash : cachedImages) + { + writeMessageInt32(message, idx, imageHash); + idx += 4; + } + sendBinaryMessage(message); } /** === modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.screen.js' --- src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.screen.js 2015-08-10 22:08:54 +0000 +++ src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.screen.js 2015-08-11 22:03:51 +0000 @@ -517,10 +517,12 @@ var pixelsInBytes = width * height * 4; var imgData; var imgDataOffset; - if(loadedImages.has(key)) { + if(loadedImages.has(key)) + { imgData = loadedImages.get(key); imgDataOffset = 0; - } else { + } else + { imgData = message; imgDataOffset = idx + 22; loadedImages.set(key, message.subarray(imgDataOffset, imgDataOffset + pixelsInBytes)); @@ -822,7 +824,20 @@ } /** + * It draws an image on the canvas. * + * @param {Number} x + * X coordinate of the top left corner. + * @param {Number} y + * Y coordinate of the top left corner. + * @param {Number} width + * Width of the image in pixels. + * @param {Number} height + * Height of the image in pixels. + * @param {Array} imgData + * The image data array contains the encoded image started from the given offset. + * @param {Number} imgDataOffset + * The offset of the encoded image data. */ function drawImage(x, y, width, height, imgData, imgDataOffset) { @@ -1256,8 +1271,10 @@ * * @param wid * The window id by which this window can be uniquely identified. + * @param images + * The cached images to remove. */ - me.destroyWindow = function(wid) + me.destroyWindow = function(wid, images) { if (!isValidWindowId(wid)) { @@ -1274,7 +1291,10 @@ var win = getWindow(wid); win.cleanup(); - + for (var image in images) + { + loadedImages.delete(image); + } // remove from our window list delete winlist[wid]; }