-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add a way to remove a single event listener (#20983) #25535
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
base: main
Are you sure you want to change the base?
Conversation
- added emscripten_remove_callback - iterate over all handlers and only remove the one matching the arguments provided - changed all event setters to pass userData and eventTypeId which are required by this API
|
@sbc100 The only failing test is the one about code size, which I can address if you are satisfied with the direction of this PR. In the grand scheme of things it is a pretty low risk PR since the only changes to existing code (17 functions in total) is passing an additional 2 fields in the EventHandler map that are not used prior to this PR. Of course it makes the resulting code bigger. I added a test which does a basic test of the functionality by counting the number of event handlers registered. That being said, during development of such test, I was running it manually in the browser and making sure that the result of removing a handler was actually having an action on the underlying JavaScript event handler being present or not. Let me know your thoughts. Thanks |
sbc100
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The approach seem pretty reasonable.
Do you really need to be able to register and un-register the same function with different user data?
|
@juj what do you think about adding this new API? |
src/lib/libhtml5.js
Outdated
|
|
||
| emscripten_remove_callback__proxy: 'sync', | ||
| emscripten_remove_callback__deps: ['$JSEvents', '$findEventTarget'], | ||
| emscripten_remove_callback: (target, userData, eventTypeId, callback) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, should we key the userData pointer into the removal? I.e. it would be better for this API to be
emscripten_remove_callback: (target, eventTypeId, callback)
The rationale is that the userData pointer is generally just data to the function call, and not identifying. I.e. it is more of the 'value' part in a key->value association.
It might be some function local value in the caller (e.g. a boolean for some behavior), and might result in user having to store the userData field in some global to be able to unregister. So requiring users to know to match the userData value could be a bit tedious.
(If users did specifically want to differentiate unregistrations based on what was seemingly a different userData value, they could do that by separating to a different function with a trampoline)
Also, this function would be good to follow the same _on_thread model, so that it can be called from pthreads symmetric to how registering on pthreads works.
The semantics of function emscripten_remove_callback are to remove all copies of callback registrations of the given function. I wonder if the function naming should somehow reflect that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense to me, but in that case I think it would also be good it if we make it impossible to register the same callback function for the same event with different userData.
emscripten_set_click_callback(window, data1, 0, mycallback); // success
emscripten_set_click_callback(window, data2, 0, mycallback); // error `mycallback` is already registered as a click callback
If we didn't error in the second case, and allowed the same callback to be registred twice, there would be no way to unregister just one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- if we want to error when registering the same callback with a different user data, then it means we need to store the user data (which we don't prior to this changes)
- it also means that code that is currently working could fail after this change
I think what @juj suggested was to indeed un-register all callbacks that were registered with different data but with the same target/event_id/callback combination. That would have eliminated having to store the user data.
It sounds like you don't like this because then "there would be no way to unregister just one of them".
I am definitely not in favor of erroring when registering 2 identical callbacks with different user data, because from a developer point of view, this is absolutely not an error and I can imagine scenarios where that could be useful.
So I guess the solution is to leave the API and changes as implemented in this PR. That way we can un-register a single callback (which IS the point of this PR). So I am personally in favor of this.
I will now work on updating the documentation and fixing the "Code Size" failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am definitely not in favor of erroring when registering 2 identical callbacks with different user data, because from a developer point of view, this is absolutely not an error and I can imagine scenarios where that could be useful.
Can you give an example of when this might be useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This a theoretical example, because I am not implementing it this way at the moment, but imagine for GLFW, you have 2 windows (represented in C by GLFWwindow pointers and canvases in HTML) and you need to set mouse move callbacks to track the mouse movements.
In order to detect moving the mouse outside the canvas while the button is clicked (something my library supports where others don't) you cannot set the callback on each canvas, it has to be set on the window... so the callbacks would be:
emscripten_set_mousemove_callback(WINDOW, glfwWindow1, true, handle_move_callback);
emscripten_set_mousemove_callback(WINDOW, glfwWindow2, true, handle_move_callback);Again I am not doing it this way, but that could be a way to implement it...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. If we do want to officially support that then we probably also want to officially support unregistering them individually (sadly)
Looks good to me. I would consider removing the Apart from that, looks like a great addition. |
|
@juj Thank you for the feedback
Personally I do not have a strong opinion either way. What matters is that if my library (ex: glfw) sets a listener, I am able to remove it without removing other listeners set by the client code on the same target. The eventTypeId/callback combination should be enough to achieve this result (although it might make the code a bit more tedious when the caller wants to include userData as a differentiator). It will also simplify a bit the code and make it smaller since we don't need to store userData anymore. So if @sbc100 is fine with it I will remove it.
At first I tried to implement support for pthread but it doesn't seem to make sense from my understanding of the code. pthread is only used to specify how to invoke the callback once it has been registered. Registering the callback itself (which is done by calling
I suppose we could call it From a schedule point of view, I am traveling so I won't be able to work on it for a couple weeks. I don't think there is urgency anyway ;) |
This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (1) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_hello_dylink_all.json: 819522 => 819904 [+382 bytes / +0.05%] Average change: +0.05% (+0.05% - +0.05%) ```
|
I think I am done on my side: Based on the discussion, I left the code as-is with the
Let me know if there is anything else or if this can be merged as-is. |
sbc100
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given hat we decided to have N different ways to register each callback it seems like we should probably just a N different un-registering functions too.
e.g. emscripten_set_mousedown_callback should have a corresponding emscripten_remove_mousedown_callback or emscripten_unset_mousdown_callback.
Alternatively we are doing to add a generic form such as emscripten_remove_callback the obvious question is why can't we have a generic emscripten_add_callback?
I guess one issue is that each callback has its own signature, which means the generic version requires the use of void* for the callback argument? So one advantage of using N different function is that we get better type safety.
I guess my preference would probably be to stick with current convention and have N different functions?
If we do go with emscripten_remove_callback maybe we can some up with less generic name .. there nothing in the name that suggests html5 or events.. so in isolation I think maybe it could be better named.
My apologies for opening up an log of last minute questions..
|
@sbc100 No worries about the back and forth. I suppose that is the point of a PR. So the "N" you are talking about is 35 in Honestly, I don't think it is very reasonable to add this kind of bloat to Emscripten to add this lacking feature (or fix a bug depending on how you look at it, since after all there is a way to remove callbacks right now, just that removing them, will remove more than you want, see #20983 for details). In addition, I feel like the design was way too granular. I understand the need/want to have a type safe API with the proper function pointers, but there could have been 1 "set_callback" API per type (ex: 1 for all mouse events, instead of 9 like there is...). And yes you could totally have a single generic set_callback api, you would just loose the type safety... For these reasons, I feel like adding an additional 35+ calls just for this is somewhat not the right approach. Having a single generic call to remove any callback is by far the least intrusive. Yes you loose on type safety, but in this instance it is really unnecessary to have it since the function pointer is never used as a type pointer to invoke, but as a comparison argument. I agree with you that taken out of context the name Finally I am wondering if the API should return an error in case the event listener being asked to be removed doesn't exist (right now it doesn't in this instance). That way it could somehow cover cases where the wrong callback/function pointer is being used accidentally (a poor type safety check in some way). In any case, these are my opinion and observations. You have the final call, and I will be fine with any way you want to go. Even if you want to go down the 35+ way, it will be far more work, but I am fine doing it. There is also the other alternative to simply close this PR and forget about it. There is currently no way to remove a specific callback (which is what is being addressed by this PR), but beside me, it doesn't seem that it has bothered anybody else. So I suppose we could just not do it. |
|
Agree that adding 35 new API calls also seems like a bad idea :-/ Maybe we should at at least add a new generic API for this? i.e. if we also add a single API for registering callbacks then we could at least say that the old API is deprecated? On the other hand we do have a general policy of avoiding adding API that do too much multi-plexing since they prevent DCE. Perhaps that is good argument for allowing an asymmetry in this case. |
|
I have no opinion on whether to add a generic API or not but I have no issue implementing it. From your last message it looks like the 35+ option is out of the debate. So what do you want me to do:
Please let me know which option you want me to do. |
Not really no, because it requires changing the code of 17+ functions to pass the information that is currently not stored and needed by But waiting and not having an answer right now until we get it right is fine. I am not blocked. This has been like this for my library since I released it: if a user register/unregister event(s) using any of these functions, it will break the functioning of the library. Also note that adding this function will still not fix the issue that my library can call the new In any case, I am good in waiting. |
| Remove callback function | ||
| ------------------------ | ||
|
|
||
| In order to remove a callback, previously set via a ``emscripten_set_some_callback`` call, there is a dedicated and generic function for this purpose: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe slightly simpler phrasing:
In order to unregister a single event handler callback, call the following function:
I think it would be good to avoid trying to eat too big pie here at once. @ypujante's use case is looking to be able to remove an individual callback, so it would be good to not snowball this into needing to redesign the whole API at once. The name
Thanks for the note. I realize now that there is already a You had a good point at https://github.com/emscripten-core/emscripten/pull/25535/files#r2505004777 about why to keep the So I would recommend:
That would work for me to land. If we wanted to have a modern revision of the HTML5 event handler APIs that would have fewer registration functions, that can be a completely separate story from this one. |
|
Thank you @juj for the review and comments. I am personally in full agreement with the approach. @sbc100 what do you think? The only think I wanted to call out is the fact that this PR does not address the fact that a user can still call (for example) Obviously we could start by adding a deprecation warning when this scenario is detected. I could add this deprecation warning to this PR or open another one once this one is merged. Any preference? |
Fair enough.
Lets consider that (breaking) change separately, as a potentially followup. |
# Conflicts: # test/codesize/test_codesize_hello_dylink_all.json
This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (30) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_cxx_ctors1.json: 149126 => 149173 [+47 bytes / +0.03%] codesize/test_codesize_cxx_ctors2.json: 148530 => 148577 [+47 bytes / +0.03%] codesize/test_codesize_cxx_except.json: 194589 => 194636 [+47 bytes / +0.02%] codesize/test_codesize_cxx_except_wasm.json: 164106 => 164153 [+47 bytes / +0.03%] codesize/test_codesize_cxx_except_wasm_legacy.json: 161764 => 161811 [+47 bytes / +0.03%] codesize/test_codesize_cxx_lto.json: 125452 => 125448 [-4 bytes / -0.00%] codesize/test_codesize_cxx_mangle.json: 258665 => 258718 [+53 bytes / +0.02%] codesize/test_codesize_cxx_noexcept.json: 151543 => 151590 [+47 bytes / +0.03%] codesize/test_codesize_cxx_wasmfs.json: 176720 => 176912 [+192 bytes / +0.11%] codesize/test_codesize_files_wasmfs.json: 55679 => 55818 [+139 bytes / +0.25%] codesize/test_codesize_hello_O0.json: 39336 => 39311 [-25 bytes / -0.06%] codesize/test_codesize_hello_dylink.json: 44313 => 44309 [-4 bytes / -0.01%] codesize/test_codesize_hello_dylink_all.json: 819266 => 819781 [+515 bytes / +0.06%] codesize/test_codesize_mem_O3.json: 9612 => 9610 [-2 bytes / -0.02%] codesize/test_codesize_mem_O3_grow.json: 9898 => 9896 [-2 bytes / -0.02%] codesize/test_codesize_mem_O3_grow_standalone.json: 9651 => 9649 [-2 bytes / -0.02%] codesize/test_codesize_mem_O3_standalone.json: 9509 => 9507 [-2 bytes / -0.02%] codesize/test_codesize_mem_O3_standalone_lib.json: 8804 => 8802 [-2 bytes / -0.02%] codesize/test_codesize_mem_O3_standalone_narg.json: 8837 => 8835 [-2 bytes / -0.02%] codesize/test_codesize_mem_O3_standalone_narg_flto.json: 7649 => 7647 [-2 bytes / -0.03%] codesize/test_codesize_minimal_pthreads.json: 27214 => 27208 [-6 bytes / -0.02%] codesize/test_codesize_minimal_pthreads_memgrowth.json: 27642 => 27636 [-6 bytes / -0.02%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json: 13199 => 13204 [+5 bytes / +0.04%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json: 18543 => 18554 [+11 bytes / +0.06%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm_singlefile.json: 15135 => 15147 [+12 bytes / +0.08%] codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json: 12737 => 12742 [+5 bytes / +0.04%] codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json: 18070 => 18081 [+11 bytes / +0.06%] codesize/test_minimal_runtime_code_size_random_printf_wasm.json: 10982 => 10993 [+11 bytes / +0.10%] codesize/test_minimal_runtime_code_size_random_printf_wasm2js.json: 17225 => 17229 [+4 bytes / +0.02%] codesize/test_unoptimized_code_size.json: 180978 => 180954 [-24 bytes / -0.01%] Average change: +0.02% (-0.06% - +0.25%) ```
# Conflicts: # test/codesize/test_codesize_hello_dylink.json # test/codesize/test_codesize_hello_dylink_all.json
|
@sbc100 I have implemented the changes discussed. I have reverted the code size changes because it was polluting this PR with 30+ file changes and the codesize checks were still failing (a bit of a mystery to me). I can see that the failing codesize checks states that it is a warning that can be ignored, so I have chosen to ignore it... There is only one test failing during package installation as I reported earlier and it is clearly not related to these changes. The relevant test is passing: Let me know if there is anything else you want me to do for this PR |
I know it has been quite a while since I opened #20983 but I finally got the time to work on it. I followed the discussion we had at the time and did the following:
emscripten_remove_callbackuserDataandeventTypeIdwhich are required by this APIThis first pass is to make sure that this would be an ok change to Emscripten and that the changes work (no failures introduced by the changes). I can obviously change whatever you want or drop it entirely if this is not desired. And can work on documentation/examples if that gets approval.
I believe it is a good addition because it allows to remove a previously set listener, which is not possible at the moment, as the APIs provided will remove all of the ones set (see discussion in the #20983 ticket).