Bitmap Loader Cache

I’m trying to create a cache for url requested images. Practically because these images are profile pictures appearing continuously on my game, I don’t want to reload them every time the stage appears again. To achieve this, I made the next class:

class ImageCache {
    private static var imageCache = new Map<String, BitmapData>();

    public static function get(url:String, onCompleteCallback:Dynamic->Void = null):Void {
        if (imageCache.exists(url)) {
            trace(imageCache.get(url));
            if (onCompleteCallback != null) {
                onCompleteCallback(imageCache.get(url));
            }
        } else {
            var loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(event:Event) {
                trace("Loaded " + url);
                imageCache.set(url, cast(event.target.content, Bitmap).bitmapData);
                trace(imageCache.get(url));
                if (onCompleteCallback != null) {
                    onCompleteCallback(imageCache.get(url));
                }
            });
            loader.load(new URLRequest(url));
        }
    }
}

The first time the image is loaded, everything works fine (showing in console { __handle => #abstract, __transparent => true }), but after that, when I call the image again, the image doesn’t load, and the console displays { __handle => null, __transparent => true }.

Do you know any way to prevent this problem, or another way to cache the images so I don’t have to load them again from the url?

Looks like you called dispose() at some point.

https://github.com/openfl/openfl/blob/master/openfl/_legacy/display/BitmapData.hx#L146-L156

Indeed, this was the problem. I was assigning the BitmapData to an object in a library, and this was disposing it when the object was destroyed.

To resolve this problem, I return a clone of the BitmapData instead:

onCompleteCallback(imageCache.get(url).clone());

Thank you for your help.

My “gut feeling” on this matter is probably going to turn out to be accurate:   you have a classic “race condition.”

Your logic, as currently written, assumes that the loader.load() call will actually complete before the next request for a particular image arrives.   But this is not the case.   There are three(!) cases that your logic must consider:

  1. The requested image is present in the cache.
  2. The image is not present, and this is the first(!) request for it.
  3. (Not presently handled)   The page is not yet present, and, although it has already been requested, it has not yet arrived.
Your class will actually need to maintain a list of callbacks that need to be called when the image arrives.

Furthermore(!!), you must carefully consider the potential implications of target-environments that do support “genuine threading.”   You must be certain that access to the “list of callbacks” is, in fact, atomic, and that it is not possible for a callback to become “orphaned” on that list.

I don’t think that counts as a race condition, and I also doubt that daPhyre is planning to use threads. Besides that, you’re right. If ImageCache.get() is called twice in a row on the same url, it’ll load that image twice.

Up until now, I haven’t required to load the same image more than once in my project, yet, I would like to implement your solution in case future cases can benefit with this feature.

As for the callback, I haven’t had any problems with it being non-atomic at some point (or so it seems until now). I have had some conflicts on the element to be assigned to is null by the time the image is returned, but these problems have been solved on the response side.

Thank you for your feedback!