/**
   * Get the GLImage associated with the specified image-id. If the image is cached in memory this
   * call will be very fast, otherwise it will be loaded from disk and saved to cache.
   *
   * @return null if the specified image is not in the current working set
   */
  public GLImage getGLImage(String imageId) {
    synchronized (cache) {
      GLImage img = cache.get(imageId);
      if (img == null && curSet.contains(imageId)) {
        // elevate the urgency of this image
        if (DEBUG) System.out.printf("RamTextureCache: requesting immediate load of %s\n", imageId);
        urgentImageIds.add(imageId);
        loaderPool.makeTopPriority(imageId);

        long startTime = System.currentTimeMillis();

        // wait for image to arrive
        while (curSet.contains(imageId) && !cache.contains(imageId)) {
          try {
            cache.wait(10000);

            img = cache.get(imageId);

            // timed out?
            if (img == null && (System.currentTimeMillis() - startTime) >= 10000) {
              System.err.println("RamTextureCache: timed out waiting for " + imageId);
              break;
            }
          } catch (InterruptedException e) {
            // ignore
          }
        }

        urgentImageIds.remove(imageId);

        // use fallback image
        if (img == null) img = GLImage.createNullImage();
      } else if (DEBUG && img == null)
        System.out.printf("RamTextureCache: %s not in working set\n", imageId);

      return img;
    }
  }