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

Event loop interface #323

Open
DemiMarie opened this issue Mar 29, 2018 · 23 comments
Open

Event loop interface #323

DemiMarie opened this issue Mar 29, 2018 · 23 comments
Labels
custom main loops Tie them together so I don't have to keep looking these up

Comments

@DemiMarie
Copy link

DemiMarie commented Mar 29, 2018

We should provide

  • An interface for integrating external event loops based on epoll/kqueue/IOCP/event ports/etc.
  • An implementation of that interface that uses libuv. This should be available as a separate library, and its availability should be selectable by a configure switch.
  • Wrappers around the libuv filesystem functions that handle Windows pathname oddities, such as making sure that a file named NUL . can be accessed.
@andlabs
Copy link
Owner

andlabs commented Mar 29, 2018

The last bullet point is out of scope for libui.

The first bullet point has been discussed to no end in other issues. I will sum it up: there is no single event handle that polling will handle GUI events on any platform — those details are obscured by each OS, and in many cases the GUI event loop is not that simple to begin with — so I can't provide a way to integrate libui into an existing event loop system. The most I can provide is a platform-specific function to add an event source into libui's event loop, but that's a future possibility.

You will need to explain in more detail specifically what you want out of the second bullet point.

@parro-it
Copy link
Contributor

Libui-node already integrate libuv event loop with GUI ones.
It's not easy stuff, and is not perfect yet, but works.
You can contribute there, or extract the code if you want to use it outside
JS.

@parro-it
Copy link
Contributor

https://github.com/parro-it/libui-node/blob/next-loop/src/EventLoop.cc

@DemiMarie
Copy link
Author

DemiMarie commented Apr 3, 2018

@parro-it The code in libui-node is buggy and inefficient.

The correct way to do this is:

Unix
  • Register the epoll descriptor with GTK
  • Run the libuv event loop when GTK reports that the epoll descriptor is ready.
macOS
  • Create a kqueue that waits on the CFRunLoop Mach port as well as the libuv descriptor
  • Poll the kqueue, with a large enough buffer to support multiple events.
  • As long as the Mach port is generating events, run the CFRunLoop
  • Otherwise, the event must be from libuv; run the libuv loop.
Windows 10

Use MsgWaitForMultipleObjectsEx to wait both on libuv’s I/O completion port and also pump a message loop.

Windows 8.1 and below
  • Create another thread
  • Use GetQueuedCompletionStatus on that thread to get events from libuv.
  • When an event is received, call PostQueuedCompletionStatus, then wake up the main loop and wait on a semaphore or event object.
  • When the main loop runs the callback that was triggered, run libuv’s main loop (once) and then signal the semaphore or event object.

These are the most efficient ways to do it on each platform. In the first three cases, there are other methods that are less efficient.

@parro-it
Copy link
Contributor

parro-it commented Apr 3, 2018

@parro-it The code in libui-node is buggy and inefficient.

Actually, that's the best I was able to do, but feel free to open a PR with your improvements.

Regarding your suggestions, you seems to suppose I have complete control over libuv event loop, which I've not, because node.js control it.

Anyway, I already tried what you suggested for Unix & macOS. The current solution has the advantage that the architecture is similar between architectures (it's doing something very similar to what you suggest for Windows 8.1)

@parro-it
Copy link
Contributor

parro-it commented Apr 3, 2018

When the main loop runs the callback that was triggered, run libuv’s main loop (once) and then signal the semaphore or event object.

Actually, in the background thread I'm sleeping for a fixed amount of 50ms.
Do you know how I can say when libuv tick has been executed, so that I can signal a semaphore as you suggest?

@DemiMarie
Copy link
Author

@parro-it I did not think of that, sorry. I was thinking of the approach that Electron uses, where libuv is embedded into the browser, rather than your situation, where libui is being embedded into Node.

@parro-it Sadly, GUI mainloops really really really do not like being embedded into other event loops. It is impossible on Windows, tricky (at best) on macOS, and requires a custom GTK event dispatcher on Unix. Even then, there is still the problem of GUI starvation, where large amounts of network activity cause the latency for GUI events to soar.

My suggestion is to invert control, and write a custom C++ executable that embeds Node and provides the GUI event loop. You can use a uv_prepare_t to solve the problem. In the callback, run the GUI event loop until it indicates that libuv needs to run. Then return. Since your checking thread already indicated that libuv has events to handle, the event loop will perform an iteration without blocking. libuv will tell you (via uv_backend_timeout) how long it needs to wait, so you do not need to busy-wait.

@parro-it
Copy link
Contributor

parro-it commented Apr 3, 2018

@parro-it I did not think of that, sorry.

Hey, no worries!

My suggestion is to invert control, and write a custom C++ executable that embeds Node and provides the GUI event loop.

Yes, that could simplify things, and it's the way Electron goes. But sadly, I've not the capacity nor the resources to maintain a whole customization of node.js.

You can use a uv_prepare_t to solve the problem.

Oh yes, didn't thought of that. I'll try for that, thank you!

@andlabs
Copy link
Owner

andlabs commented Apr 3, 2018

Run the libuv event loop when GTK reports that the epoll descriptor is ready.

This (and the others like it) seems to imply that libuv runs endlessly when queued; are you sure that's what you mean? Does libuv not use the underlying OS I/O systems to begin with?

Create a kqueue that waits on the CFRunLoop Mach port as well as the libuv descriptor

What are you suggesting here? Somehow access an undocumented implementation detail of CFRunLoop? I have already stated I do not plan on doing this in libui.

Use MsgWaitForMultipleObjectsEx to wait both on libuv’s I/O completion port and also pump a message loop.

Where does it say MsgWaitForMultipleObjectsEx() accepts IOCPs now? MSDN doesn't say this.

@DemiMarie
Copy link
Author

DemiMarie commented Apr 6, 2018

@andlabs

Run the libuv event loop when GTK reports that the epoll descriptor is ready.

This (and the others like it) seems to imply that libuv runs endlessly when queued; are you sure that's what you mean? Does libuv not use the underlying OS I/O systems to begin with?

Libuv does use the underlying OS I/O systems. What I meant is that on Unix, libuv exposes a file descriptor (via uv_backend_fd) that can be polled via poll (which is what GLib uses). You can run the event loop once or tell libuv to run until there are no more events. The first is what libui would use if hosting libui. The second is what Node uses. In the latter case, one can use an uv_prepare_t to run the libui event loop until libuv needs to run again, at which point the binding code will tell libui to run libuv again (by queueing a callback and then returning from the libui event loop).

Create a kqueue that waits on the CFRunLoop Mach port as well as the libuv descriptor

What are you suggesting here? Somehow access an undocumented implementation detail of CFRunLoop? I have already stated I do not plan on doing this in libui.

This is an undocumented performance hack, as you mentioned, and I can very much understand you not wishing to use it. Instead, one can use CFFileDescriptor or use poll with a timeout on a separate thread.

Use MsgWaitForMultipleObjectsEx to wait both on libuv’s I/O completion port and also pump a message loop.

Where does it say MsgWaitForMultipleObjectsEx() accepts IOCPs now? MSDN doesn't say this.

This is another undocumented performance hack, which only works on Windows 10+. Instead, one can use GetQueuedCompletionStatus on a separate thread, again with a timeout.

Electron provides a fantastic example of integrating libuv with a GUI event loop — it is what convinced me that this is even possible.

@andlabs
Copy link
Owner

andlabs commented Apr 6, 2018

I'm sure it is possible — it's precisely the reason why UI event loops allow integrating I/O events to begin with! I guess my first question should really be: what is the libuv main loop? Does libuv need to run something repeatedly before or after polling file descriptors? I know libuv does I/O multiplexing, but I'm not sure if it's just a portability layer on top of OS APIs or something more.

@DemiMarie
Copy link
Author

DemiMarie commented Apr 8, 2018 via email

@parro-it
Copy link
Contributor

parro-it commented Apr 8, 2018

My two cents on this discussion:

An interface for integrating external event loops based on epoll/kqueue/IOCP/event ports/etc.

Actually libui has uiMainStep and uiMainSteps functions that already help to integrate external event loop. What could be added is a function to provide a file descriptor that could be polled to know when there are GUI event pending. Having such a file descriptor allow externals event loop implementors to poll it together with their own file descriptor, achieving great performance.

But, AFAIK, only GTK provide a public API to retrieve such a file descriptor. On macOs, it is possible using _dispatch_get_main_queue_port_4CF, but it's an unsupported Core Foundation function (and @andlabs clearly stated he doesn't want to use unsupported platform features).

On Windows, that is not possible at all. Well, it seems Windows 10 can now do it, but I never tried yet.

An implementation of that interface that uses libuv. This should be available as a separate library, and its availability should be selectable by a configure switch.

I would really love to have one. I could get rid of my the implementation efforts on libui-node and concentrate on other things.
So, @DemiMarie, why don't you start such a project? You could use (if you want) libui-node code as a starting point. I'll be happy to cooperate with you if you need help.

Wrappers around the libuv filesystem functions that handle Windows pathname oddities, such as making sure that a file named NUL . can be accessed.

I don't understand what you mean here. If you just need to handle pathnames in a platform independent way, it seems to me that integrating libuv just for that is like killing a mosquito with a nuke.

And finally, I suggest to avoid integrating libuv directly within libui. While the Node.js binding would problably benefit from this, I think that it could complicate developing binding for other languages.

@parro-it
Copy link
Contributor

parro-it commented Apr 8, 2018

@DemiMarie, you should read some of the old discussion on this topic:

#147
#146
#95
#125
#117

@andlabs
Copy link
Owner

andlabs commented Apr 8, 2018

I should disambiguate, because I am using undocumented things for minor stuff like font matching: I don't want to use undocumented features for the main loop, lest some important internal code not be run. Some of the Windows stuff I might need to do for dialogs might bring this into a gray area...

But I still want to make all of this possible, because the facilities extend beyond libuv.

@parro-it
Copy link
Contributor

parro-it commented Apr 8, 2018

I don't want to use undocumented features for the main loop

So, this new function would be useful only to integrate an external event loop. It should not be used when relying only on GUI main loop.

On macOS, _dispatch_get_main_queue_port_4CF could be used, and on GTK we have g_main_context_query . But the problem is that there is no equivalent function on Windows anyway.

@DemiMarie
Copy link
Author

DemiMarie commented Apr 9, 2018 via email

@andlabs
Copy link
Owner

andlabs commented Apr 9, 2018

If that's the case, then why is there MsgWaitForMultipleObjects()?

@DemiMarie
Copy link
Author

DemiMarie commented Apr 9, 2018 via email

@andlabs andlabs added this to the Unassigned Backlog milestone Dec 31, 2018
@andlabs
Copy link
Owner

andlabs commented Apr 23, 2019

I asked this elsewhere but I'll ask this here too: is there an example of a macOS native program that needs fine-grained control over the event loop? I'm looking back at -[NSApplication run] and I'm still not entirely sure how I'm going to be able to provide uiMainSteps() like functionality without having to resort to undocumented stuff or guesswork that isn't complete.

@DemiMarie
Copy link
Author

DemiMarie commented Apr 23, 2019 via email

@andlabs
Copy link
Owner

andlabs commented Apr 23, 2019

No, I mean an actual GUI program, such as a game — the kind of things that led people to request a uiMainSteps() in the first place.

@andlabs
Copy link
Owner

andlabs commented Apr 23, 2019

Actually I'm just going to quote the original comment.

Copy-pasting from #21 (comment):

Okay, I need to resurrect this.

It's useful for any apps where you still want control of the main loop - for instance if you have a game or gamedev tool that uses libui, and you need a tight render-loop and other updates alongside your ui. I guess you could delegate the ui handling to a separate thread, but that seems a bit of a convoluted solution (and needless threads usually cause problems).

How do people do this right now on macOS in a macOS-native application? Because there is no real supported way to do this directly in the same way libui does it now, and even if I did just recreate what -[NSApplication run] does internally, that wouldn't be sufficient to replace all of -[NSApplication run].

And for the record, I am specifically referring to GUI apps here, not just programs that roll their own poll/kqueue loop.

@andlabs andlabs added the custom main loops Tie them together so I don't have to keep looking these up label Apr 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
custom main loops Tie them together so I don't have to keep looking these up
Projects
None yet
Development

No branches or pull requests

3 participants