Browser build in any plans?

I will do those things. Might be a while, some family duties this morning. Thanks.

Results so far:

  1. Simplified version, just 2 files, retrieves file no crash. However, it does not call back the calling routine, whether I use direct callback

def httpFetch(url,fname,callBack,listener=fetchListener):
handle = emscripten.async_wget(url, fname, onload=callBack, onerror=onerror, onprogress=onprogress)

or use a signal. Requests works with both designs.

  1. Full version of the code but all signals and other code eliminated after the fetch (fetch is the last command then the program stops): crash just like before.

  2. emscriptenmodule.c with the three patterns commented out (and modified code as in item 2): does not crash, also does not call back the calling routine. But does print downloading… and “loaded”.

It seems to be the callback that kills it. Compare (both with Py_DECREF etc commented out):

  1. Full code, no code execution after emscripten_mget except messenger_send: no crash. Also no delivery of the signal.

  2. Full code, no code execution after emscripten_mget except callBack from the onload routine: crash

First:

>  def httpFetch(url,fname,cbsignal,listener=fetchListener):
> 	callBackSignal = cbsignal
> 		handle = emscripten.async_wget(url, fname, onload=onload, onerror=onerror, onprogress=onprogress)

With:

def onload(handle, file):
    print("loaded")
    messenger.send(callBackSignal,[file])

Second:

> def httpFetch(url,fname,callBack,listener=fetchListener):
	handle = emscripten.async_wget(url, fname, onload=callBack, onerror=onerror, onprogress=onprogress)

This causes the big error dump

I don’t get the crash in my editor.html. (Are you sure you grabbed the newer version of emscriptenmodule.c? The new version should have a line containing Py_XINCREF(callbacks[0]))

When using your code I only get an error flood of NameError due to the reference to text every time “onprogress” is called, but that is expected, since the code is referring to a non-existent object there.

Which browser are you using? I’ve been using Chromium, but a quick test with Firefox seems to work fine as well.

EDIT: It just occurred to me, when you commented out lines containing Py_XDECREF, did you comment out the ones that contain the actual PyObject_Call invocation as well? If so, that would prevent the functions from being called.

On a sidenote, I don’t think the way you are passing callBackSignal will work, since I think onload will just read the value None.

You can use a lambda instead, or use a global dictionary that maps handles to something, but it may be even easier to instead create a class that represents a request so that you can access any data associated with the request (eg. callbacks to call, events to fire, things to pass to that callback) and with how it should be handled under self..

I also just uploaded a new version of emscriptenmodule.c that is, at least in theory, more robust against browsers calling the callbacks in weird ways that might mess up the memory management. Please try the new version:
http://rdb.name/panda3d-webgl/emscriptenmodule.c
Please double-check that you have the latest version, by checking it contains the string __py_onload

I was careful to not comment out Py_XDECREF(PyObject_CallFunction(funcs[0], “IN”, handle, memoryview));

or its variants. Only Py_XDECREF(funcs and Py_DECREF(funcs and the free(arg). I did not comment out Py_DECREF(func) or Py_DECREF(ms)

Module has this twice:

Py_XINCREF(callbacks[0]);
Py_XINCREF(callbacks[1]);
Py_XINCREF(callbacks[2]);

Most of these tests using Chrome, but occasionally I test on Firefox, sometimes Opera and Safari.

So, when I navigate to your museum.html, I get a progress printed in the console, but then I get an error message about “extractData” taking 2 arguments but given 3. The one in your main.py is taking only 1 argument, so I assume it’s not the same as in your museum.html?

I suspect that this may be a coding mix-up that causes a valid exception, but that that exception somehow messes up the interpreter state—fixing the arguments might then fix this issue.

Try also the new emscriptenmodule.c in my previous post.

Oops, you are right. Too much programming in lua (where I put the listener as a function inside the first function so it has the same scope). Thanks.

Sorry, we are crossing versions. I have been changing one thing at a time and throwing them up there to see what happens. I have the latest version in now, but haven’t finished testing.

Not to overload you with stuff, but I wanted to mention that I’ve been improving browsermodule.c in the meantime. It’s now possible to invoke Python callbacks from JavaScript, which greatly expands the scope of the level of interop you can do between Python and JS.

This opens up another interesting avenue: direct access to the browser’s XMLHttpRequest (which emscripten’s wget interface uses under the hood). This is a significantly more powerful interface, if a little less convenient and efficient.

Try this code in my editor.html:

from browser import Reflect, Array, XMLHttpRequest

def onprogress(evt):
    if evt.lengthComputable:
        percentage = evt.loaded / evt.total * 100
        print("Progress:", percentage)

def onload(evt):
    print("Loaded:", this.responseText)

def onerror(evt):
    print("Failed!", dir(evt))

def onabort(evt):
    print("Aborted!", dir(evt))

req = Reflect.construct(XMLHttpRequest, Array())
req.open("GET", "./test.txt")
req.addEventListener("progress", onprogress)
req.addEventListener("load", onload)
req.addEventListener("error", onerror)
req.addEventListener("abort", onabort)
req.send()

Note that because Python has no “new” keyword, it’s necessary to use either Reflect.construct or eval to construct it. The rest works just the same as in JavaScript, so you should be able to consult the XMLHttpRequest reference, or whichever other API you want to use.

All that said, as long as emscripten.async_wget does everything you need, it may still be a better choice.

That is excellent, something I can certainly use in the future. I don’t want to switch yet, until I get this working because I still have to add back in the handling of remote files then figure out why the wall images are not rotated correctly. And then see where I am. But this is a great leap forward, thanks for doing it.

It works now! Thank you for all the work, I know it was a bit painful. I’ll start adding bits back in, and let you know when I get stuck again. (Probably with the images that are not rotated correctly.)

1 Like

All the file downloads work, now have to address the other issues. The images are not rotated correctly, the lighting disappears when I change rooms (I don’t actually move to another place, I just reset the player and re-do the walls), mouse doesn’t hide, and bringing the menu back after the first time doesn’t work. And I am sure there will be lots more. I have work to do.

The one collision and room transition and general moving all seem to work.
Thanks for all your help getting me this far.

I think I have it back down to just the one problem – the images are not rotated correctly, although they have I think the right aspect ratio if they were rotated correctly.

Images are a bit weird, I haven’t figured it out.

In the python app, the images are all rotatee -90 degrees. (Possibly the wall object is? I will check the bam again.) So, I rotate the wall on which the image is textured, with

  	self.walls[wnum].setTexRotate(self.ts[wnum],90)

In the browser, that line has no effect, no matter what angle I use.

I’ll have to figure out another way to rotate the image.

I am rebuilding all the image-walls using the simpler CardMaker. At least all the images show up in the correct proportions and orientations in both application and browser. But now I have to ‘replace’ the images, and destroy/re-create isn’t working, I get a blank. I’ll figure it out. This is at least easier to deal with.

What a weird bug. It just occurred to me: are these jpg images that contain rotation information in the EXIF metadata? We use libjpeg to load rotated images on the desktop, but the lighter stb_image in the browser, so I wonder if the latter perhaps doesn’t understand rotations encoded in the EXIF metadata.

You could solve this by preprocessing the jpeg files to apply the rotation specified in the EXIF metadata, using exiftran -a or by opening and saving the files in an image editor. Or, you could compile libjpeg for emscripten and build Panda against that.

If this turns out to be the problem, I need to look at how we can add support for these EXIF tags to stb_image, or we just need to document this as a limitation and tell people to use libjpeg.

These are jpeg files, yes. I could pre-process 1000 images, but these are supposed to be student-supplied, and I don’t want to make that a required step. I have pre-processed these in bulk with ThumbsUp only to reduce their size. I can do a few with exiftran to see if it makes a difference.

But, as I wrote, using CardMaker solved the problem, they all come out correctly.

Two problems remain: the lighting is different, and replacing the image during game play doesn’t work, that has to be something I am doing differently. But changing rooms does work, even though it runs through the same code. But that is clearly my problem. Replacing images even once in the desktop application doesn’t work at all.

So, after I wrote that, I realised that emscripten has a built-in image loader that uses the browser’s ability to load images. Not only is it more efficient (since it can decode the images in the background), but it also supports more formats, and perhaps it also supports the EXIF tags.

So, I just implemented it, and I recommend using it. You will need to grab the latest source from the webgl-port branch, and the latest emscriptenmodule.c. Note however that I have changed the interface of async_wget()—the previous version still exists but is renamed to async_wget2(). The new async_wget() is able to make use of the preload mechanism, but its interface is a bit more limited (note the lack of onprogress callback, and the lack of handle parameter in the callback):

def onload(file):
    tex = loader.loadTexture(file)

def onerror(file):
    print(file, "failed to load")

emscripten.async_wget(url, "/target_file.jpg", onload, onerror)

I’m curious to see if this fixes the jpg problem without having to change the CardMaker coordinates.

The new module also fixes the exception behaviour. It now just prints a single exception rather than causing an endless stream of exceptions if an error occurred in the callback.

Both the default fixed-function lighting in Panda and the shader generator do not work in WebGL. (The shader generator will be fixed to support it soon, though). In the meantime, if you want lighting, you’ll have to apply your own shader. It has to be a GLSL ES shader, not a regular GLSL shader.