Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

About time to provide a consistent asynchronous API for loading stuff in both Java and JavaScript mode #3075

Closed
teo1978 opened this issue Jan 31, 2015 · 9 comments

Comments

@teo1978
Copy link

teo1978 commented Jan 31, 2015

The fact that basic operations loading data such as loadStrings or loadImage (to name just a couple) are synchronous has always been a poor design choice and a crippling limitation in Processing (an asyncronous interface based on callbacks could have been provided since the very beginning), but it's understandable given that Processing was born based on Java and given the work it requires to handle the threading.

But now it is becoming an unforgivable anachronysm.

JavaScript mode has since long become the only way (and a wonderful way) of publishing sketches on the web, and it would be foolish to not consider exporting sketches in JavaScript mode as the primary way, or at least an essential and indispensable way, of sharing Processing sketches.
JavaScript's data loading operations as you well know are asynchronous by default. Processing JS forces them to be synchronous just to ensure compatibility of sketches between JavaScript and Java mode, but having them asynchronous would be trivial in Processing JS if it hadn't had to emulate Java-Processing's behavior.

So, the time has definitely come (about a few years ago) to switch to a seamless, transparent asynchronous callback-based loading mechanism for all the built-in functions and classes that load stuff in Processing, in a way that will be consistent across Java and JavaScript modes.

This should apply to loadStrings(), loadImage(), loadXML() (which by the way has other critical java-vs-javascript incompatibility issues that I've reported long ago and have sadly been ignored, but that's entirely on the ProcessingJS side) and whatever else loads data.

This should have been done already long, long ago, but now it's becoming URGENT as this message shows up in any browser when running a Processing sketch (any that loads data):

Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help http://xhr.spec.whatwg.org/"

That means that soon enough Processing JS will become totally BROKEN; and since exporting Java applets has been dropped long ago, it will become impossible to publish practically any Processing sketch online. That would be awful, if the current situation is not bad enough.

@pvrs12
Copy link
Contributor

pvrs12 commented Jan 31, 2015

I would be willing to attempt to tackle this on the Java side if @benfry thinks that these methods can be added without breaking things. I think processing 3 would be a good time to add these functions. I think that simply naming them something like async_loadStrings makes sense.

For the callback we could either write it to use traditional Java callbacks (small interface implementation classes) or since Processing 3 uses Java8 lambdas could possibly be used.

@pvrs12
Copy link
Contributor

pvrs12 commented Feb 1, 2015

Actually, looking at the docs, the implementation would be essentially the same, passing a Runnable as the callback.

@Pomax
Copy link

Pomax commented Feb 3, 2015

This sound like a change that would be worth committing as a backward-compatibility-breaking API change. Using names like "asyncLoadStrings" will lead to a dense API that will be hard to understand in terms of what one "should" use versus what is available.

Passing a runnable is a nice idea from a java perspective, but not a very nice one from a Processing perspective: it makes things a lot harder for people who come to Processing because it's supposed to be easy, not hard. Especially with an eye towards things like processing.js and processing.py, making sure to bake in as little explicit Java is always worth it, so if I could make a recommendation, I'd use event handling to solve this:

void somethingOrOther() {
  loadImage("something.jpg");
}

void imageLoaded(String filename, PImage img) {
  println(fiename + " finished loading);
  // do something with img
}

This way you keep it simple -and more importantly, Processy- while maintaining the same concepts you're already teaching users. Any load... can be given its own handler in this way, giving you minimal backward compat. breakage: the handling code will mostly needs to be moved into the ...Loaded handlers rather than kept with the load.... call, rather than requiring lots of syntax fluff to set up Runnables.

Another benefit is that you might even be able to keep the functions doing "what they did before" while also supporting the new behaviour, yielding a PImage synchronously as well as calling the imageLoaded function, with a PDE warning that the synchronous pattern is deprecated and removed in a next version of Processing.

@teo1978
Copy link
Author

teo1978 commented Feb 3, 2015

I agree that it must be kept simple, and also take into account that the mechanism needs to be easy to port to ProcessingJS. Async loading in JS is ridiculously easy (it's how JS behaves by default, actually ProcessingJS is crippling itself to keep it synchronous for the sake of compatibility with Processing), so anything too Java-specific and too convoluted would be a great obstacle to port it to ProcessingJS.

I don't know about Lambdas in Java, so I can't argue whether that's a good idea or not.

Care must be taken, however, with "too-simple" solutions. If I understand correctly Pomax's suggestion, it would be something like:

  • I call loadImage()
  • I define a method void imageLoaded() in the sketch
    -> that method will automatically be called when an image is loaded.

That (unless it's just an oversimplified description) is a BAD pattern which, if I'm not mistaken, has been used pretty much in Processing (I don't remember an example right now, but I think I've seen it a few times, and suffered from its limitations).

  1. that does not allow, or makes it very painful, to handle several calls to loadWhatever() and distinguish which one of them is returning when the callback method is called. Note that the name or url of the image is not enough: you may be loading the same url several times.
  2. that pollutes the namespace of the sketch's methods. When we start to have a lot of methods that load things and expect the sketch to have a callback method with a given name, we'll start having callback methods with horrible names to avoid name clashing.

The latter is perhaps acceptable, anyway. But care must be taken to avoid (1). Maybe the solution is just to allow the loadWhatever() method to take a parameter which is a map (of arbitrary key-value pairs), which will be passed to the callback together with the loaded data.

Regarding BC, I agree that it's worth breaking it here if necessary. Processing breaks BC all the time anyway, for things much less worth it. (I rarely upgraded processing without seeing all my old sketches break)

However, perhaps it may even possible to avoid it.
Think about this: (I'm going to oversimplify a bit)
We now have loadStrings(String url);
We don't want to keep loadStrings(String url) and have loadStringsAsync(String url), right?
So what about having loadStrings(String url, Boolean async), or loadStrings(String url, ...) where "..." are whatever parameters are needed for the async version. The second and following parameters will be optionals, and if omitted, the loading will be sync by default (for now). In the future (or even already), the default could be changed to asynchronous, providing a global setting for the sketch to use "synchronous by default mode" for legacy sketches. This is just a starting point of an idea, I'm sure it's not stright-forward to turn this into a decent spec.

@pvrs12
Copy link
Contributor

pvrs12 commented Feb 4, 2015

I also am not a huge fan of the ...Loaded type methods. That implementation (even though it's what is used for things like the mouse) seems clunky. Doing this requires you to break that method into cases if more than one call is used.

I also understand the desire to avoid Java-esque solutions that are more complicated than standard Processing. But I think (haven't done much with it) the standard way of handling asynchronous tasks in JavaScript is also a callback function.

Maybe the correct solution to avoid API bloat is to keep the original name and add an asynchronous method with the same name and added parameters like @teo1978 suggests. This would give a method signature that might look something like this
String[] loadStrings(String url, LoadStringsCallback callback)

Calling the function with an old school interface implementation is a bit syntactically ugly, but using a lambda (which are strange looking at first) makes it a little better

//old version ( <= Java 7)
loadStrings("someString",new Callback() {
                public void done(String[] strings){
                         //do work with strings
                }
});

//new version (>= Java 8)
loadStrings("someString",(String[] strings)->{
               //do work with strings
});

I don't think that the lambda version is difficult to read, but I've programmed in Java for a while so I'm definitely biased. I think that this also is closer to JavaScript's asynchronous method handling which usually looks something like this

loadStrings("someString", function(strings) {
              //do work with strings
}); 

@matteosistisette
Copy link

//new version
loadStrings("someString",(String[] strings)->{
//do work with strings
});

If that's what a lambda is in Java (sorry for the ignorance) that looks to me like the PERFECT solution. Clean, simple and flexible. It's only the syntax (strictly speaking, i.e. the very graphical signs used) that is a bit funny.
It seems to be the exact equivalent of passing a function as argument in JavaScript (which is exactly what you would expect to be able to do in JS, and what you'd want to be able to do to do in Processing JS) only with a kind of bizarre syntax.
Also it should translate in a pretty straight-forward way to JS code when parsing the pde into javascript code in Processing JS.

@matteosistisette
Copy link

Perhaps it is not very "Processy" but that's because the "Processy" way (predetermined callback names for methods defined in the sketch) has never been a good way in the first place (it was only viable as long as sketchs kept small with no ambition to scale). I wish this becomes the new "Processy" way.

@benfry
Copy link
Contributor

benfry commented Feb 4, 2015

@teo1978 It looks like you'd like to file a feature request about adding API for asynchronous IO. If so, please do so. API extensions for asynchronous IO were worked out for 2.x but I didn't have time to finish the implementation and more importantly, test it properly. Since it's not documented publicly, an issue would give us a way to do that.

I'm closing this thread because it's an unnecessarily long-winded rant toward people you haven't met about topics you don't seem to fully understand. It has no place here, because it's not a helpful starting point for a request or a discussion for me (who would be in charge of this implementation) or any of the other volunteers who help make this project happen. The attitude is unnecessary and has no place in the community that we've built around this project.

@benfry benfry closed this as completed Feb 4, 2015
@processing processing locked and limited conversation to collaborators Feb 4, 2015
@benfry
Copy link
Contributor

benfry commented Mar 31, 2015

Moved here: #3165

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants