Revision: 4687 http://sourceforge.net/p/vexi/code/4687 Author: mkpg2 Date: 2014-04-18 00:35:32 +0000 (Fri, 18 Apr 2014) Log Message: ----------- Fix. vexi.js.loadImage (was not restarting interpreter correctly). Fix. Image Caching. Cache key included fountain hash, and so was reloading url images each time. SoftReferenceCache. Use to store images, will periodically purge keys to avoid unbounded growth when dealing with arbitrary numbers of images (e.g. photo albums).
Modified Paths: -------------- branches/vexi3/org.vexi-core.main/src/main/java/org/vexi/graphics/Picture.java branches/vexi3/org.vexi-core.main/src/main/jpp/org/vexi/core/Vexi.jpp branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Thread.java trunk/org.vexi-library.util/meta/module.xml Added Paths: ----------- trunk/org.vexi-library.util/src/main/java/org/vexi/util/SoftReferenceCache.java Modified: branches/vexi3/org.vexi-core.main/src/main/java/org/vexi/graphics/Picture.java =================================================================== --- branches/vexi3/org.vexi-core.main/src/main/java/org/vexi/graphics/Picture.java 2014-04-17 18:22:57 UTC (rev 4686) +++ branches/vexi3/org.vexi-core.main/src/main/java/org/vexi/graphics/Picture.java 2014-04-18 00:35:32 UTC (rev 4687) @@ -7,17 +7,19 @@ import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; -import java.lang.ref.SoftReference; -import java.util.HashMap; +import org.ibex.js.Fountain; import org.ibex.js.JS; import org.ibex.js.JSExn; import org.ibex.js.JSU; +import org.ibex.js.Scheduler; +import org.ibex.js.Thread; import org.ibex.util.Callable; import org.ibex.util.Vec; import org.vexi.core.Blessing; import org.vexi.core.Main; import org.vexi.plat.Platform; +import org.vexi.util.SoftReferenceCache; /** * The in-memory representation of a PNG or GIF image. It is @@ -30,38 +32,8 @@ */ public class Picture { - public Picture() { this.stream = null; } - public Picture(JS r) { this.stream = r; } - private static HashMap cache = new HashMap(100); - - public JS stream = null; ///< the stream we were loaded from - public int width = -1; ///< the width of the image - public int height = -1; ///< the height of the image - public int[] data = null; ///< argb samples - public boolean isLoaded = false; ///< true iff the image is fully loaded - // FIXME: this is a hack to disguise isLoaded somehow being false when callbacks are run - public JSExn loadFailed = null; ///< not null iff the image loading failed - - private Vec loadedCallbacks; ///< list of callbacks interested in this Picture - - /** invoked when an image is fully loaded; subclasses can use this to initialize platform-specific constructs */ - protected synchronized void loaded() { - if (loadedCallbacks == null) { - return; - } - - synchronized (worker) { - if (loadedPictures != null) { - loadedPictures.addElement(this); - return; - } - - loadedPictures = new Vec(); - loadedPictures.addElement(this); - Main.SCHEDULER.add(worker); - } - } - + static final SoftReferenceCache<String,Picture> cache = new SoftReferenceCache(); + /* * The 'worker' Callable invokes the other Callables; needed * to be done this way because if using a lot of images, there @@ -69,8 +41,8 @@ * individually, and this can lead to 'step by step' building * of the UI - not really ideal. */ - private static Vec loadedPictures; - private static Callable worker = new Callable() { + static private Vec loadedPictures; + static private Callable worker = new Callable() { public Object run(Object o) throws Exception { synchronized (worker) { for (int j=0; loadedPictures.size()>j; j++) { @@ -88,9 +60,16 @@ } } }; + + static String cacheKey(JS stream){ + if(stream instanceof Fountain){ + return ((Fountain)stream).canonical(); + } + return stream.coerceToString(); + } /** turns a stream into a Picture.Source and passes it to the callback */ - public static Picture load(JS stream, Callable callback) { + static public Picture load(JS stream, Callable callback) { // REMARK - as things stand we cannot get the underlying stream object as // due to the way blessings work (they discover their suffix) we can be // opening many inputstreams on the same file, if an image is used in many @@ -99,19 +78,65 @@ // We construct a key here. This should be unique for different streams and blessings. // The construction of the key could be improved upon, if we can make sure // that it is unique/underlying resource then that would be ideal. - String key = stream.coerceToString(); - SoftReference ref = (SoftReference)cache.get(key); - Picture ret = ref==null ? null : (Picture)ref.get(); - // FIXME: is this a small memory leak? JS streams will not be GC'd. + String key = cacheKey(stream); + Picture ret = cache.get(key); if (ret==null) { - cache.put(key, new SoftReference(ret = Platform.createPicture(stream))); + cache.cache(key, ret = Platform.createPicture(stream)); } // can return ret here outside of sync block for caller to assign as callback // is executed in the same interpreter as the caller, synchronously. ret.load(callback); return ret; } + + static public Picture load(JS[] args) throws JSExn { + final Scheduler sched = Scheduler.findCurrent(); + final Thread callback = sched.pauseJSThread(); + Picture p = Picture.load(args[0], new Callable(){ + public Object run(Object o) throws JSExn { + Picture p = (Picture)o; + sched.schedule(callback, p.loadFailed); + return null; + } + }); + if(p.isLoaded){ + sched.schedule(callback, null); + } + return null; + } + + public Picture() { this.stream = null; } + public Picture(JS r) { this.stream = r; } + + public JS stream = null; ///< the stream we were loaded from + public int width = -1; ///< the width of the image + public int height = -1; ///< the height of the image + public int[] data = null; ///< argb samples + public boolean isLoaded = false; ///< true iff the image is fully loaded + // FIXME: this is a hack to disguise isLoaded somehow being false when callbacks are run + public JSExn loadFailed = null; ///< not null iff the image loading failed + + private Vec loadedCallbacks; ///< list of callbacks interested in this Picture + /** invoked when an image is fully loaded; subclasses can use this to initialize platform-specific constructs */ + protected synchronized void loaded() { + if (loadedCallbacks == null) { + return; + } + + synchronized (worker) { + if (loadedPictures != null) { + loadedPictures.addElement(this); + return; + } + + loadedPictures = new Vec(); + loadedPictures.addElement(this); + Main.SCHEDULER.add(worker); + } + } + + /** turns a stream into a Picture.Source and once loaded invokes the * given notification callback (which is added to the Scheduler) * NOTE: the callback is not invoked if picture is already loaded */ Modified: branches/vexi3/org.vexi-core.main/src/main/jpp/org/vexi/core/Vexi.jpp =================================================================== --- branches/vexi3/org.vexi-core.main/src/main/jpp/org/vexi/core/Vexi.jpp 2014-04-17 18:22:57 UTC (rev 4686) +++ branches/vexi3/org.vexi-core.main/src/main/jpp/org/vexi/core/Vexi.jpp 2014-04-18 00:35:32 UTC (rev 4687) @@ -7,6 +7,7 @@ import org.ibex.crypto.MD5; import org.ibex.js.*; +import org.ibex.js.Thread; import org.ibex.js.JSU.*; import org.vexi.js.*; import org.ibex.util.Cache; @@ -787,18 +788,8 @@ ret.put(JSU.S("right"), JSU.N(s.rightInset)); return ret; case "ui.loadImage": - final Scheduler sched = Scheduler.findCurrent(); - final Callable callback = sched.pauseJSThread(); - Picture.load(args[0], new Callable(){ - public Object run(Object o) throws Exception { - Picture p = (Picture)o; - // if loadFailed then interpreter will handle the exception - callback.run(p.loadFailed); - return null; - } - }); - return null; // doesn't matter since we paused - + Picture.load(args); + return null; case "unclone": return args[0].unclone(); //#end break; Modified: branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java 2014-04-17 18:22:57 UTC (rev 4686) +++ branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java 2014-04-18 00:35:32 UTC (rev 4687) @@ -503,7 +503,7 @@ // we do not deal with potential parent interpreters when we reschedule. // A JSExn complaining about the foreground thread will be thrown, but this // will not be accurate. (e.g. load template from a background thread) - public Callable pauseJSThread() throws JSExn { + public Thread pauseJSThread() throws JSExn { jsthread.currentInterpreter.pause(); return jsthread; } Modified: branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Thread.java =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Thread.java 2014-04-17 18:22:57 UTC (rev 4686) +++ branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Thread.java 2014-04-18 00:35:32 UTC (rev 4687) @@ -50,8 +50,9 @@ //Log.info("thread destroyed "+threadCount+" "+id +" "+ this); } - /** Execute JS code in the background. Method executed by scheduler. */ - public Object run(Object o) throws Exception { + /** Execute JS code in the background. Method executed by scheduler. + * @throws JSExn */ + public Object run(Object o) throws JSExn { faction.setJSThread(this); if (currentInterpreter==null) { //First time this thread has been run (i.e. not paused or yielded yet) Modified: trunk/org.vexi-library.util/meta/module.xml =================================================================== --- trunk/org.vexi-library.util/meta/module.xml 2014-04-17 18:22:57 UTC (rev 4686) +++ trunk/org.vexi-library.util/meta/module.xml 2014-04-18 00:35:32 UTC (rev 4687) @@ -3,7 +3,7 @@ <artifact name="java.zip" /> <dependencies> - <system name="java.jre" tag="1.4"/> + <system name="java.jre" tag="1.6"/> <!-- Test dependencies --> <dependency source="local" org="org.vexi" name="library.testing" scope="test"/> <dependency source="ibiblio" org="junit" name="junit" tag="3.8.1" scope="test"/> Added: trunk/org.vexi-library.util/src/main/java/org/vexi/util/SoftReferenceCache.java =================================================================== --- trunk/org.vexi-library.util/src/main/java/org/vexi/util/SoftReferenceCache.java (rev 0) +++ trunk/org.vexi-library.util/src/main/java/org/vexi/util/SoftReferenceCache.java 2014-04-18 00:35:32 UTC (rev 4687) @@ -0,0 +1,34 @@ +package org.vexi.util; + +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +public class SoftReferenceCache<K,V> { + final private Map<K, SoftReference<V>> m = new HashMap<K, SoftReference<V>>(); + int counter = 0; + synchronized public V get(K key){ + SoftReference<V> r = m.get(key); + if(r==null) return null; + return r.get(); + } + + synchronized public void cache(K key, V value){ + counter ++; + if(counter>100){ + counter = 0; + + Iterator<Entry<K, SoftReference<V>>> it = m.entrySet().iterator(); + while (it.hasNext()) { + Entry<K, SoftReference<V>> entry = it.next(); + if(entry.getValue().get()==null){ + it.remove(); + } + } + } + m.put(key, new SoftReference<V>(value)); + } + +} Property changes on: trunk/org.vexi-library.util/src/main/java/org/vexi/util/SoftReferenceCache.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Learn Graph Databases - Download FREE O'Reilly Book "Graph Databases" is the definitive new guide to graph databases and their applications. Written by three acclaimed leaders in the field, this first edition is now available. Download your free book today! http://p.sf.net/sfu/NeoTech _______________________________________________ Vexi-svn mailing list Vexi-svn@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/vexi-svn