I was randomly poking around some of the lesser visited areas of the java api in the hope that java.lang.reflect.Proxy could solve a problem of mine (it can’t) when I restumbled upon WeakReference.
Now I’ve been thinking more and more about resource management recently, in particular since most of the time I take the easy option and just load everything at game boot. That works but is sloppy and as I start adding more animation and landscape tiles into RescueSquad it starts getting impractical.
Assuming two levels:
- Stage 1, needs ‘grass.png’ and ‘ice.png’
- Stage 2, needs ‘grass.png’ and ‘lava.png’
Normal flow goes something like:
- Load stage 1
- Play stage 1
- Destroy stage 1
- Load stage 2
- Play stage 2
- Destroy stage 2
Which is nice and easy, but you get something like:
- load(grass.png)
- load(ice.png)
- destroy(grass.png)
- destroy(ice.png)
- load(grass.png)
- load(lava.png)
Obviously this is less than ideal, since we’ve loaded grass.png twice when really we could have just not destroyed it in the first place. Enter weak reference abuse:
class Texture
{
public Texture(String path)
{
// load from disk, create native resource
// ..
}
public void destroy()
{
// destroy native resource
// ..
}
}
class ResourcePool
{
Map<String, ResourceEntry> loadedResources;
ResourceHandle load(String resName)
{
if (loadedResources.contains(resName))
{
ResourceEntry re = loadedResources.get(resName);
return new ResourceHandle(re.texture, re.sentinal);
}
else
{
Texture texture = new Texture(resName);
Object sentinal = new Object();
ResourceHandle h = new ResourceHandle(texture, sentinal );
loadedRes.add( new ResourceEntry(texture, sentinal) );
return h;
}
}
public void tidy()
{
System.gc();
System.gc();
for (Iterator it=loadedResources.iterator(); it.hasNext(); )
{
ResourceEntry e = it.next();
if (!e.isInUse())
e.destroy();
it.remove();
}
}
}
class ResourceHandle
{
private Object sentinal;
private Texture texture;
public ResourceHandle(Texture t, Object s)
{
this.texture = t;
this.sentinal = s;
}
public Texture get() { return texture; } // Game code should only hold on to this temporarily
}
class ResourceEntry
{
Texture texture;
WeakReference sentinal;
public ResourceEntry(Texture t, Object s)
{
this.texture = t;
this.sentinal = new WeakReference(s);
}
public void destroy()
{
texture.destroy();
texture = null;
}
public boolean isInUse()
{
return sentinal.get() != null;
}
}
// Game code:
class Stage
{
ResourceHandle tex1, tex2;
public Stage(ResourcePool pool, String s1, String s2)
{
tex1 = pool.load(s1);
tex2 = pool.load(s2);
}
}
{
ResourcePool pool = new ResourcePool();
Stage currentStage;
// Load stage 1
currentStage = new Stage(pool, "ice.png", "grass.png");
// Do gameplay
// ..
// Stage 1 complete, load next level
currentStage = new Stage(pool, "grass.png", "lava.png");
// Sentinal for 'ice' resource now vanishes, pool tidies and leaves just 'grass' and 'lava' in memory
pool.tidy();
}
(obviously for a proper version ‘Texture’ could become a generic Resource interface for any native resource that needs explicit cleanup).
The ‘trick’ is to dish out handles to our Texture object, which have both an accessable Texture plus a hard reference to a Sentinal object. Internally we keep a hard reference to the same Texture, but a weak reference to the Sentinal. Once a new level has finished loading we scan through and check which sentinals have now vanished (ie. there are no more handles pointing at them). Since we still kept a proper hard ref to the (now unused) Texture object, we can happily destroy it.
This seems like a neat idea to me, since it means Stage/etc. classes don’t have to remember to return/release Textures back to the resource pool, as we let the gc do the heavy lifting for us. Does anyone see any big gaping holes in this trick that I’ve missed?

