[HTML5] Cancel Loader

Hi,

I cannot find a proper solution to cancel openfl.display.Loader which has not yet started sending request (due to queue limited in HTML5HTTPRequest to 4x requests at once or anything) - I tried using method close() but it does not seem to work as I expected (under the hood it seems to run abort() method of HttpRequest). Am I doing something wrong or is there another way to overcome that.

My use case is a long list (~1500x) of elements rendering and lazyloading only visible part + some offsets (only 10x are visible, ~20x are prepared for smooth experience) and those 20x elements have Loaders with URLs attached to them - while scrolling these are loaded and unloaded depending on scroll position and while Iā€™m scrolling through the whole list Loaders (and thus HttpRequest) are stuck in queue and are sent one by one (within 4x-at-once limit) even though some of them are not used anymore and should be canceled.

Any suggestions? :slight_smile:
Thanks!

Iā€™d appreciate your help, if you could?

I think the first issue is that perhaps 4 is too low, and we should pick a higher request limit for the browser. Too many and we run into errors, but there might be a better value in the middle. Happy to consider other approaches to this?

The cancel method here in the backend is called when a Lime HTTPRequest is canceled. It looks like the code here will apply a cancel behavior if the request is active, but does not check if it is waiting in a queue.

Thanks :slight_smile:

Iā€™ve also seen another topic about that limit so I guess Iā€™ll try to propose something either for limiting or for canceling queued requests. Iā€™d love to help a lot more than that but I seem to have no spare time :frowning:

Maybe Iā€™ve looked through URLLoader instead of Loader but for the first one Iā€™ve seen that URLLoaderā€™s close() looks like this (and I guessed similar rules apply to Loader):

public function close ():Void {
		
		if (__httpRequest != null) {
			
			__httpRequest.cancel ();
			
		}
		
	}

__httpRequest is _IHTTPRequest and in __prepareRequest() method it is actually an instance of HTTPRequest which for HTML5 has HTML5HTTPRequest as a backend. Then in HTTPRequest:

public function cancel ():Void {

		#if !doc_gen
		__backend.cancel ();
		#end

	}

and then in Backend:

public function cancel ():Void {

		if (request != null) {

			request.abort ();

		}

	}

So my approach would be to skip aborted requests in processQueue() before asserting type and using appropriate loading method, somewhere here:

private static function processQueue ():Void {

		if (activeRequests < requestLimit && requestQueue.length > 0) {

			activeRequests++;

			var queueItem = requestQueue.pop ();
			queueItem.instance

			switch (queueItem.type) {

				case IMAGE:

					__loadImage (queueItem.uri, queueItem.promise);

				case TEXT:

					queueItem.instance.__loadText (queueItem.uri, queueItem.promise);

				case BINARY:

					queueItem.instance.__loadData (queueItem.uri, queueItem.promise);

				default:

					activeRequests--;

			}

		}

	}

A little bit of feedback on these thoughts would be helpful. Iā€™d love not to start doing something terribly wrong :slight_smile:

Also, maybe you can give me a little bit of insight before I start digging on my own - why does every single request for image is firstly XHR request for URL, then PNG request for base64 encoded data instead of directly requesting for PNG ? It may be a concept I still do not understand about HTML5 :slight_smile:

OK. Found another issue Iā€™ve got before but only with direct URLLoader usage and with Sound files (*.mp3) - whenever URL has attatched parameter to force cache invalidation (for.example/image/foobar.png:1234 -> that ā€˜:1234ā€™ at the end) URLLoader treats it as unrecognized format because the extension pattern in switchā€¦case clause is ā€˜png:1234ā€™ instead of ā€˜pngā€™:

contentLoaderInfo.contentType = switch (extension) {
				
				case "json": "application/json";
				case "swf": "application/x-shockwave-flash";
				case "jpg", "jpeg": "image/jpeg";
				case "png": "image/png";
				case "gif": "image/gif";
				case "js": "application/javascript";
				default: "application/x-www-form-urlencoded"; /*throw "Unrecognized file " + request.url;*/
				
			}

Iā€™ll try to make a fix for that but Iā€™d love to ensure whether such solution was not a part of something else.

EDIT: Ah, I see that you check for ā€˜?ā€™ as query index instead but for library-related things.

So Iā€™m kind of stuck because of this part:

#if (js && html5)
if (contentLoaderInfo.contentType.indexOf ("image/") > -1 && request.method == URLRequestMethod.GET && (request.requestHeaders == null || request.requestHeaders.length == 0) && request.userAgent == null) {

	BitmapData.loadFromFile (request.url).onComplete (BitmapData_onLoad).onError (BitmapData_onError).onProgress (BitmapData_onProgress);
	return;
		
}
#end

This part loads image statically through the whole requestQueue so I cannot access request to mark it as canceled. In the commit message for this part it is said only that it is for permissions fix ā€˜Fix permissions for simple image loads on HTML5ā€™ (7049f279b709cb1f30e23416a91f1b94f6065d04) - could you explain a little bit more what is this shortcut about ? It would help a lot. Iā€™d rather understand things first, then implement something around :slight_smile:

Perhaps the Fix permissions for simple image loads on HTML5 has something to do with CORS. If you are correct that the code is falling back to an XHR request rather than a standard img.src = "image.png" load, perhaps thatā€™s whatā€™s going on here ā€“ if we arenā€™t doing something special, we try and load that way (which I think is implemented in BitmapData). I think youā€™re right that canceling that will be more complicated. We can set that aside and focus on the others.

In cancel, I think it should remove the request from the queue, if it is on the queue, or it could occur in the queue processing to detect if it was canceled, but I think removing it from the queue directly may be the better approach.

If png:1234 is really what comes through, though, it definitely would make sense to improve that code for better performance

Let me sum up things and maybe organize them a little bit because Iā€™ve made a little mess in here while I was working on these matters.

  1. Extracting extension from example URL: for.example/image/foobar.png:1234 - Iā€™ve found a solution - itā€™s simple and I could post it tomorrow but Iā€™ve just spoken to our sysadmin and he explained that using :1234 instead of just ?ts=1234 was because some squid proxies (like few years ago) were not caching any URL with ?. So this will be fixed on our side and I see that queryIndex is already implemented within openfl.display.Loader :slight_smile:

  2. After fixing 1) - for now only locally - Iā€™ve achieved a much better performance - no more falling back to XHR on images and it speeded a lot - it is acceptable for now but can be improved more with canceling requests

  3. Iā€™ve spent some time today to check for stuff around HTML5HTTPRequest and found out that Images are requested differently than ex. binaries or strings - Images use HTML5HTTPRequest::loadImage directly and since it is static function it does not have an instance of HTML5HTTPRequest in QueueItem::instance while the other two use URLRequest which creates HTML5HTTPRequest so it is possible to access it from outside to actually cancel the request. Is that really related to CORS ? Both of these approaches use XMLHttpRequest in the end so guess it should be no difference but maybe Iā€™m missing something important here.

  4. About the canceling - I believe that just marking requests as canceled and removing them in HTML5HTTPRequest::processQueue would be better approach. Iā€™d worry for concurrent modifications to occur whenever both processing and canceling access the same List to pop or remove request. I try to propose something tomorrow or maybe over the upcoming weekend :slight_smile:

  5. About requestLimit - Iā€™ve searched through history of the repository as well as searched for any informations on what would be the best number of concurrent requests and Iā€™ve found out that most of the modern browser allow up to 6 concurrent connections to the same host. Iā€™ve tested it briefly today and had no problems with the limit set to 6 (Chrome 69). Also in the history of repository Iā€™ve found that it was previously set to 6 (3a5ab4c472ā€¦ - ā€˜Add queue for HTML5 image loadingā€™) and then set down to 4 (d593685dcbā€¦ - ā€˜Throttle HTML5 image loadingā€™) back in 03.2017.

https://docs.pushtechnology.com/cloud/latest/manual/html/designguide/solution/support/connection_limitations.html

I also wanted to know if I come up with some ideas for fixing different stuff, how do you want me to post it - shall I fork master and pull request or do you want me to create a branch within openfl/lime repositories or maybe I should create an issue so anyone who encounter similar issues will be able to find it - Iā€™m not very experienced with working on github (Iā€™ve forked and pullrequested Spritesheet once but in this case may be different). This will help me later on with other fixes :slight_smile:

1.) Okay! That makes sense

2.) Great :slight_smile:

3.) Iā€™m not sure, I can try and take a look

4.) Iā€™m okay with that approach. I donā€™t think the queue is referenced from multiple threads, though, so Iā€™m not sure if we need to worry too much

5.) Iā€™m happy to try 6 again, I donā€™t remember why it was set lower

The easiest approach is to click the ā€œEditā€ button on any file on the Github site, make your changes online, then save and you can turn that into a pull request. Even if it isnā€™t ā€œperfectā€ I can still merge and make edits.

There are other ways to do it offline, but the online approach is the easiest for smaller changes

Thanks a lot for helping :slight_smile: Iā€™ll update as soon as I have something new to share :slight_smile:

I understand now what have you meant by has something to do with CORS. Now, when I understand what you meant by that I can try refactoring HTML5HTTPRequest keeping it in compliance with that CORS requirement while having canceling possible from where request for an image was called. Iā€™ve tried once and threw everything to trash. I guess it is harder than Iā€™ve thought it may be but thatā€™s OK :slight_smile: Results soon, whenever I have some spare time to work on these things :slight_smile:

if (!__isSameOrigin(uri)) {
    image.crossOrigin = "Anonymous";
}
1 Like

Iā€™ve dumped some more changes to bin but every time I get some new knowledge about how things are working around there. It is harder than I thought it may be but coming soon :slight_smile:

1 Like