Create preloader for images

0 votes
asked Nov 8 by Veleanore (450 points)
Sometimes my game may have some images of really big sizes because I want them to display as a background. The problem is that they may load for too long and sometimes it may be a completely empty passage for pretty long.

I want the preloader to work by itself. What I mean is that to make it work without setting any additional information in every passage to define images I need to preload.

So for that, I want to know how to get images links from the any linked passage with JS(JQ). Then I would be able to load and cache them before the next passage will be shown. As well as I want to find out if that is even possible.

Also, maybe it is possible to transfer an image from one passage to another so that people with disabled cache could also get the preloading feature. But this isn't the main functionality, I can just ask people to enable cache in their browser, it is ok.

Any ideas on how to do this? Maybe other ideas how to achieve the same result?

Thank you for attention.

1 Answer

+1 vote
answered Nov 9 by Chapel (30,050 points)
edited Nov 9 by Chapel

Browsers are pretty smart, and they generally won't cache things that aren't actually getting displayed on screen.  Here's a post on the old forum archive describing how something like this might be done.  I recommend reading the whole thing for a better idea of all your options, as the conversation evolves as it goes.

I wouldn't worry too much about people who have disabled caching--they generally know what they've signed up for. That said, the method described near the end of the conversation creates a box in every passage that holds images: the cache shouldn't be necessary, but will help speed up start-up times on loading up your game after the first time. 

I use a system similar to this in one of my projects, so let me know if you need any help implementing it. 

commented Nov 9 by Veleanore (450 points)
Ok. I finished reading. I know that already. My problem lies in getting images sources from the linked passages to make it simple to use.

So to make it clear. I need a script that finds images from every passage that were linked on this page and preloads them. After clicking the link and moving to the next passage image was already preloaded and the script now preloads new images.

I need this because big images might not be rare in my project and I want them all to simply be loaded by the time passage starts.

I can also use some server technology, but I am not sure if I need any. (maybe I would need to create  some database of images to load them before the passage starts with AJAX or some thing).
commented Nov 9 by Chapel (30,050 points)

So you're looking for a system that looks ahead at upcoming passages and preloads images it finds in those passages? So for example, if there are three links in a given passage, and two of those links lead to passages with images, you'd like those images to get cached after the current passage finishes loading, so those two images are ready to go?

That might ultimately clause greater performance problems than it solves, but it should be possible by adapting the above solutions and having a postdisplay task object do the following: 

  1. Crawl the current DOM for links. 
  2. Look at the passages those links go to and find their images. 
  3. Render those images to a box like in the forum post. 

I can whip you up an example in a bit. 

commented Nov 9 by Veleanore (450 points)
Yes, exactly. I mean how bad can it be? Storing images for few future passages shouldn't be critical. All modern PC's must handle that load. I just want to make so that users experience comfortable gameplay because that's the way to progress.

I don't understand how I can look at the other passages through javascript, that's the problem. If that's not a problem for you I would love to see an example here.

And still, the solution to cache images before the next passage is possible with simple HTML block the same way you showed me before. I will need to manually define all the images to preload, that is a bit more of a work but will get the thing done. If the automatic method won't work I have a backup solution.
commented Nov 9 by Chapel (30,050 points)

I mean how bad can it be? Storing images for few future passages shouldn't be critical. All modern PC's must handle that load.

It's not about the computer.  It's also not about the images.  They'll either be ready or not ready when the user clicks the link and goes to the next passage (I don't think you should actually force them into a 'loading screen' situation) all this code does is give the images a decent head start of however long it takes the user to read the passage.  In reality, it should be long enough for most reasonably-sized images.  If you're loading up 4k bitmaps, I don't know if anything can help you there.

It's about the browser's JavaScript engine. Stacking a large amount of complicated code like this (it uses all your classic performance killers: regex, DOM lookup, etc) onto SugarCube's rendering system will eventually make it seem laggy and unresponsive.  Browsers can only interpret so much code so fast.

I'm not an expert, though; I don't know how big a deal this code will be in the long run.  I just recommend keeping an eye on things.  If your passage transitions start getting wonky, this might be the culprit.  Lots of links and lots of images all being processed, in addition to SugarCube's normal parsing and rendering and State copying, not to mention anything else you have going on in task objects or special passages, it could all start slowing things down.

Anyway, here's the example.  We'll use the same CSS as from the forum post:

#imgloader-box{position:fixed;left:0;top:0;width:2px;height:2px;overflow:hidden;z-index:0}
#imgloader-box img{position:absolute;left:0;top:0;width:1px;height:1px}

Then, in the JavaScript:

(function () { // set up the element
	var $preloader = $(document.createElement('div'));
	$preloader
		.attr('id', 'imgloader-box')
		.appendTo(document.body);
}());

setup.getMatches = function (string, regex, index) {
  // taken from here: https://stackoverflow.com/a/14210948
  index || (index = 1);
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
};

setup.preload = function (sources) {
	$('#imgloader-box').empty(); // empty the box
	
	if (sources && Array.isArray(sources) && sources.length > 0) {
		sources.forEach(function (src) { // append images
			var $image = $(document.createElement('img'));
			$image
				.attr('src', src)
				.appendTo('#imgloader-box');
		});
	}
};

setup.crawlForLinks = function () { // look for links in current passage
	if (tags().includes('noCrawl')) { // just in case you want to skip some passages
		return;
	}
	
	var $links = $('a').toArray(), linked = [];
	$links.forEach(function (l) {
		if ($(l).attr('data-passage')) {
			linked.push($(l).attr('data-passage'));
		}
	});
	
	return linked;
};

setup.crawlForImages = function (psg) { // look for images in passage source code
	if (!Story.has(psg)) {
		return; // someone made a mistake...
	}
	
	var sources = [];
	psg = Story.get(psg).text;
	
	// find html elements
	var html = /<img\s.*?src=["'](.*?)['"].*?>/gi;
	html = setup.getMatches(psg, html, 1);
	sources = (html.length) ? sources.concat(html) : sources;
	
	// find wiki images
	var wiki = /\[img\[(.*?)[|\]].*?\]/gi;
	wiki = setup.getMatches(psg, wiki, 1);
	sources = (wiki.length) ? sources.concat(wiki) : sources;
	
	return sources;
};

postdisplay['preload-images'] = function (t) { // the postdisplay that brings it all together
	var linked = setup.crawlForLinks(), sources = [];
	if (linked) {
		linked.forEach(function (p) {
			var temp = setup.crawlForImages(p);
			sources = (temp) ? sources.concat(temp) : sources;
		});
	}
	setup.preload(sources);
};

A few caveats:

  • There might be bugs.  I tested it, but not thoroughly.
  • This is a bit messy in a few places, and it doesn't support everything--it won't look at <<goto>>s or <<button>>s, though you could add that functionality pretty easily.
  • I'm not great at regex, so there might be a better way to find the image sources.
  • There is no error handling.
  • Overall, I'd build off of this; I'm not sure you'd want to use it as is.
commented Nov 9 by Veleanore (450 points)
Gosh. Did you write this just for my question or you have already been using something like this? Anyway, this is a very solid piece of work.

I am not using buttons anyway and won't do that in future(most likely) but I will use goto in future. To add it to this script I need to add them like $(goto) or how else? These are macros so I do not know if there is a way to work with them normally, or should I create a new regex rule to get passage name here?

I will test code tomorrow but I went through it and it already looks exactly what I needed. I mean, why not? This code is simple enough, readable to make changes in future and most likely working :D I don't really see downsides for now. But let's wait until I will test it.
commented Nov 9 by Chapel (30,050 points)
edited Nov 10 by Chapel

These are macros so I do not know if there is a way to work with them normally, or should I create a new regex rule to get passage name here?

For <<goto>>s, I would approach that problem from the opposite side and create an invisible link to handle that.  Here's an example.

<<widget 'gotoP'>>\
    @@display:none;<<link 'blah' 'passage'>><</link>>@@\
    <<goto 'passage'>>\
<</widget>>

Usage.

<<gotoP 'passage'>>

For buttons, you'd just add button elements to the $links array in the setup.crawlForLinks(), e.g.:

...
// warning: untested
var $links = $('a').toArray().concat($('button').toArray()), linked = [];
$links.forEach(function (l) {
	if ($(l).attr('data-passage')) {
		linked.push($(l).attr('data-passage'));
	}
});

...

It was something I didn't think of until later and I'd already tested it and everything, so I didn't throw it in. 

Did you write this just for my question or you have already been using something like this?

Like I said, I have a system in my game that was already a modified / updated version of the code from the forum post and that's the core of this.  Though it was a bit more work then I thought it'd be, haha.

...