Closed swistakm closed 8 years ago
Thank you for your suggestion!
Let's start with the exception forwarding:
Do you know a case of when forwarding callback exceptions to the main exception context would not be a good idea? Aside of the error callback, all other callbacks are only called from within glfwPollEvents
and glfwWaitEvents
, so if a user wishes to ignore callback exceptions, I think:
try:
glfw.poll_events()
except:
pass
would be the pythonic way to do it in user code, and it would already show the user that ignoring these exceptions might not be a good idea.
Thread-safety is no issue here, as the relevant GLFW functions are not thread-safe anyway.
I'd suggest the following behavior:
If an uncaught exception causes a callback to exit, the exception is re-raised from the current PyGLFW function. The stack trace is not printed.
If the callback was called from inside glfw.poll_events
or glfw.wait_events
, execution of other callbacks is disabled for this call of the function (and of course re-enabled for future calls of the function).
This leads to the typical behavior of executions: prevent code execution and jump to the next except
or quit the program. This would also mean that there can only be one "buffered" exception at a time. I think this would cause the least surprise to a Python developer.
What are your thoughts about this suggested behavior?
For optional features like error checking, I think the approach of using module level constants as done in PyOpenGL is quite good.
import glfw
glfw.ERROR_REPORTING = False
This code would opt-out of the automatic propagation of GLFW errors to Python exceptions. I guess this would be the default error callback and setting the error callback to None
would actually re-install this error-to-exception callback?
Alternatively, an explicit glfw.set_error_callback(None)
could disable the default?
I agree with all suggestions. Especially the fact about threading support in these parts of GLFW is a good point. Unfortunately I won't be able to submit any PR in next two weeks but will do that eventually if no one will take care of that in the meantime. I have already did some tests in one of my projects and it seems like there shouldn't be any problems in implementing this as proposed.
Please take a look at commit 37fbb8c and let me know if you have any suggestions on how to enhace this mechanism. The way I replace the callback function types looks a bit hack-ish, but that way there's less of a chance to forget the callback decorator when adding support for new callbacks.
Everything looks great. Exactly how I imagined it :)
That's good to hear. I merged the branch into master and uploaded the new version to PyPI. Thank you for your help!
Currently there is no way to catch errors from callbacks attached to glfw using
glfw.set_*_callback
.Consider following example:
When executed it gives following output:
This means that exception is raised but the only effect is that traceback is printed on standard output. Program simply continues its flow. This can be problematic in some situations. For instance if we have keyboard handler that fails at some point and it is also responsible for catching input that should close application (
ESC
key) then user will find difficult to exit such program. Unexpected exceptions should be propagated in stack so would terminate application if not handled properly. For this case it is not such big issue becauseglfw.create_window
informs about error with output value but (as stated by GLFW docs) not all functions does that and the only way to reliably handle errors is through error callbacks.Proper propagation of exceptions could be achieved if we provide default error callback that stores latest error state (e.g. in some global/module-level variable) and utilise ctypes'
errcheck
protocol by attaching customerrcheck
function to every function inglfw._glfw
library instance.Here is simple proof of concept of how it could work:
This approach also gives possibility to wrap all the errors with some custom base exception class e.g.
GlfwException
or even differentiate different kind of errors with different error classe by inspecting error codes.Above example is of course not complete because:
thread.get_ident()
as state key)I can happily provide pull request for that change but I would like first to know what do you think about that and how would you prefer to solve some issues:
glfw
API likeenable_exception_propagation()
that will initialize error state structure, set default handler and attacherrcheck
to eachglfw._glfw
(DLL) functionerrcheck
to every library function but internally verify if error propagation was enabled (again new API function).I think also that it could be very useful to expose some more internals of above behaviour so user could easily add their own error callbacks that will still properly propagate exceptions. Maybe some decorator?
As a side note about defining opt-in mechanism: I would not recommend using environment variables for defining expected behaviour because the end-user could accidentally completely corrupt application flow if it would heavily rely on exception handling.
EDIT: I have realised that I had mixed up two different things. One thing is propagating GLFW errors as exceptions and the other thing is propagating exceptions from python input handlers (keyboard, mouse, joystick etc.). Still I think that we can solve two issues using similar approach and propagating GLFW function errors as exceptions is a good start.