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

request: uiMainStepPoll() #146

Open
andlabs opened this issue Jun 16, 2016 · 24 comments
Open

request: uiMainStepPoll() #146

andlabs opened this issue Jun 16, 2016 · 24 comments
Labels
custom main loops Tie them together so I don't have to keep looking these up enhancement help wanted question
Milestone

Comments

@andlabs
Copy link
Owner

andlabs commented Jun 16, 2016

split from #95
a way to combine uiMainStep() with select() or poll()

on GTK+ and OS X this should be fine

on Windows I don't know one thing:

What would the equivalent of a select() be on Windows? It appears MsgWaitForMultipleObjectsEx() does not accept file or socket handles. Does it have to do with OVERLAPPED? I see GLib accepts file handles on Windows...

@ProPuke
Copy link

ProPuke commented Jul 2, 2016

To clarify - do we just need to be able to wait for a message under windows, with a maximum timeout period specified?

If so the below should do this:

static int waitMessageTimeout(MSG *msg, UINT timeout)
{
    int res;
    UINT_PTR timer = SetTimer(NULL, NULL, timeout, NULL);
    res = GetMessageW(msg, NULL, 0, 0);
    KillTimer(NULL, timer);

    if (res < 0) {
        logLastError(L"error calling GetMessage()");
        return 0;       // bail out on error
    }
    return res != 0;        // returns false on WM_QUIT
}

@andlabs
Copy link
Owner Author

andlabs commented Jul 2, 2016

MsgWaitForMultipleObjectsEx() has a timeout argument; that's probably better to use than SetTimer(). However, waiting some amount of time isn't the point; the point is to wait for some I/O event to complete.

@parro-it
Copy link
Contributor

parro-it commented Jul 5, 2016

I've implemented an event loop based on libuv polling on libui-node.

I second @daurnimator on requesting a public API that expose a file descriptor (unix) or HANDLE (windows) to wait on.

@andlabs you may want to give a look at how I implemented the loop, you could libuv to implement it directly on libui, if you don't mind add another dependency.

libui has a c interface, is supports same platforms as libui and greatly simplify event loop management....

@daurnimator
Copy link

daurnimator commented Jul 5, 2016

@andlabs you may want to give a look at how I implemented the loop, you could libuv to implement it directly on libui, if you don't mind add another dependency.

libuv isn't what you want here. You possibly want something like this: A cross platform way to gather many file handles under a single handle. On windows, such a primitive does not exist and must be synthesised with a thread signalling an event.

The simpler solution (that IMO should be taken) is to just expose all the fds/HANDLEs that libui is interested in, and it's up to the user/host application to provide the main loop. GTK's API can probably be used as the template here.

@andlabs
Copy link
Owner Author

andlabs commented Jul 5, 2016

The problem is there is no file descriptor exposed by any of the widget toolkits for events.

@daurnimator
Copy link

daurnimator commented Jul 5, 2016

The problem is there is no file descriptor exposed by any of the widget toolkits for events.

@andlabs
Copy link
Owner Author

andlabs commented Jul 5, 2016

Is _dispatch_get_main_queue_port_4CF() undocumented? I don't plan on using undocumented functionality in libui core (only in the uiInitOptions for overriding the main thread requirement, and even that's a stretch assuming I can just replace pthread_main_np() and not have to worry about run loops)

@daurnimator
Copy link

daurnimator commented Jul 5, 2016

Is _dispatch_get_main_queue_port_4CF() undocumented?

Yes. though it's not going anywhere; it's part of the libdispatch ABI. (4CF is "for Core Foundation"; it's what CFRunLoop uses under the hood. see https://opensource.apple.com/source/CF/CF-1153.18/CFRunLoop.c)

The closest thing to docs is from dispatch_get_main_queue:

Cocoa applications need not call dispatch_main(). Blocks submitted to
the main queue will be executed as part of the "common modes" of the
application's main NSRunLoop or CFRunLoop. However, blocks submitted to
the main queue in applications using dispatch_main() are not guaranteed
to execute on the main thread.

==> This works due to CFRunLoop's use of _dispatch_get_main_queue_port_4CF (and NSRunLoop's use of CFRunLoop)


I did some work on a demo/example over here: https://gist.github.com/daurnimator/8cc2ef09ad72a5577b66f34957559e47
Though I don't have a mac; so I'm coding half-blind.

@ianclarksmith
Copy link

@daurnimator Just to check that it works on 10.11, I ran clang 4CF.c; ./a.out (while spamming Enter a bit too):

PORT 4CF=3331
PORT SET=3587
GOT EVENT. ident=3587 filter=-8
hello




GOT EVENT. ident=3587 filter=-8
hello from timer
GOT EVENT. ident=3587 filter=-8

@parro-it
Copy link
Contributor

parro-it commented Jul 5, 2016

Great work @daurnimator. I will try to integrate it in libui-node loop using libuv

@daurnimator
Copy link

Btw, I wrote a blog post with some info: http://daurnimator.com/post/147024385399/using-your-own-main-loop-on-osx

@daurnimator
Copy link

@parro-it how did you go? I see you copied my code into https://github.com/parro-it/libui-node/blob/d734075232cbb5eeb4b2cb90c907b7015332da52/src/arch/darwin/EventLoop_uvpoll.mm#L20 but I'm not sure if you're using it. (and there's still debugging statements in there!)

@parro-it
Copy link
Contributor

parro-it commented Aug 2, 2016

Hi @daurnimator, I'm actually not using it, the one you linked is an old version.
It appear that on macOS libuv cannot poll on a file descriptor obtained as you explained.

So I implemented the event loop using a background thread that repeatedly check, with a blocking call, if there are any event pending.

Whenever there are, it calls a function on main thread to dispatch them.
I use a similar approach both on Linux and on macOS. Unfortunately, I did not find a way in

Windows to check for GUI events pending from a separate thread, it appear it's just impossible. So on Windows I reverted to a less performant timeout check.

I still hope to find a way to improve the Windows part...

@daurnimator
Copy link

It appear that on macOS libuv cannot poll on a file descriptor obtained as you explained.

Howso?
You should be using uv_poll_t with UV_READABLE, see http://docs.libuv.org/en/v1.x/poll.html#c.uv_poll_init

@andlabs
Copy link
Owner Author

andlabs commented Sep 3, 2017

This and the associated 4CF test program are now the second and third (respectively) Google results for _dispatch_get_main_queue_port_4CF(). Good job?

For unrelated reasons I now understand what _dispatch_get_main_queue_port_4CF() does and how it works with regards to Core Foundation (since without it, GCD would treat the main queue as just any other queue and run its objects on worker threads), though I'm not sure how it helps with UI events or with the problem originally asked about (doing I/O as part of the main loop).

Windows to check for GUI events pending from a separate thread, it appear it's just impossible. So on Windows I reverted to a less performant timeout check.

There is; it's called AttachThreadInput() and it's very unsafe. Having a cross-thread parent/child or owner/owned window relation has the same effect. Though a) I don't know if this would cover all possible messages, and b) I still fail to see how this solves the problem of wanting to mix I/O into the main loop.

@daurnimator
Copy link

daurnimator commented Sep 3, 2017

I'm not sure how it helps with UI events or with the problem originally asked about (doing I/O as part of the main loop).

It gives you something to listen on to know when gcd needs to do work. With it you can write your own main loop using your own choice of mach ports/select/poll/kqueue.

@andlabs
Copy link
Owner Author

andlabs commented Sep 3, 2017

Ah, so just doing this wouldn't be enough to handle all of libui for what you want to do then — you'd also need to know about all the other possible event sources. But I suppose you can gleam a hint from the Core Foundation source code and maybe the lesser documented Core Graphics APIs?

@daurnimator
Copy link

need to know about all the other possible event sources.

Other sources such as what?

@andlabs
Copy link
Owner Author

andlabs commented Sep 3, 2017

The window manager and devices. Look at the implementation of CFRunLoopRun(): it uses Mach events.

I should probably make adding I/O to the standard main loop (the opposite desire) a separate possible feature request.

@daurnimator
Copy link

The window manager and devices.

As far as I understand things, all of those use GCD to wait for events.
i.e. if we have a way to wait on GCD (which _dispatch_get_main_queue_port_4CF gives us) then we have a way to wait on everything.

@andlabs
Copy link
Owner Author

andlabs commented Sep 3, 2017

Huh; that sounds like a massive change for 10.6 to do... But sure; if that does work then ok? Easiest way to find out would be to put one of your custom run loops in a real Cocoa app and see what breaks, if anything.

@daurnimator
Copy link

daurnimator commented Sep 3, 2017

Huh; that sounds like a massive change for 10.6 to do

All we need to do is add a function ui_poll_fds that (on OSX) uses _dispatch_get_main_queue_port_4CF and a kqueue.
It doesn't change anything else; and is entirely optional.

@daurnimator
Copy link

daurnimator commented Sep 3, 2017

I now understand what you mean: what if something uses CFRunLoopAddSource to wait for events not using libdispatch?

On OSX USE_DISPATCH_SOURCE_FOR_TIMERS is set which causes CF to use libdispatch for timers. Which leaves us with a single case we need to worry about: when a user adds a "Version 1 source" (i.e. mach port). See https://github.com/opensource-apple/CF/blob/3cc41a76b1491f50813e28a4ec09954ffa359e6f/CFRunLoop.c#L2872

@andlabs andlabs added this to the Post-Alpha 5 milestone Dec 31, 2018
@andlabs
Copy link
Owner Author

andlabs commented Apr 23, 2019

(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 enhancement help wanted question
Projects
None yet
Development

No branches or pull requests

5 participants