Giter VIP home page Giter VIP logo

Comments (36)

JeanSebTr avatar JeanSebTr commented on July 28, 2024 3

Maybe this could be useful for someone else using Cocoa:

If you don't want to block libuv's loop with app('run'), you can replace it with:

app('finishLaunching');
var userInfo = $.NSDictionary('dictionaryWithObject', $(1),
                              'forKey', $('NSApplicationLaunchIsDefaultLaunchKey'));
var notifCenter = $.NSNotificationCenter('defaultCenter');
notifCenter('postNotificationName', $.NSApplicationDidFinishLaunchingNotification,
            'object', app, 'userInfo', userInfo);
function tick () {
  var ev;
  while(ev = app('nextEventMatchingMask', 4294967295,
                 //$.NSAnyEventMask is losing precision somewhere
                 'untilDate', null, // don't wait if there is no event
                 'inMode', $.NSDefaultRunLoopMode,
                 'dequeue', 1)) {
    app('sendEvent', ev);
  }
  app('updateWindows');
  if(shouldKeepRunning) {
    process.nextTick(tick);
  }
}
tick();

It's based on Demystifying NSApplication by recreating it
Don't forget the NSApplicationDidFinishLaunchingNotification or your process could segfault while handling events (at least applicationWillTerminate).

Thanks for making NodObjC. It's awesome!

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024 1

For those who come by and see this:

https://gist.github.com/trevorlinton/5cc934f9264629d4e85c

from nodobjc.

cztomsik avatar cztomsik commented on July 28, 2024 1

I know this is old but for anyone else wondering how to integrate node/xxx event loop with cocoa:

  • you need something like setImmediate to "tick" forever (in non-blocking way)
  • you get next timeout/interval (libuv_backend_timeout)
  • you wait for cocoa events with that timeout (block) like this
  • you have a thread which will do kevent(libuv_fd, null, empty_dest_kevent, 1, null)
  • and if it returns anything >0 you know there's a pending nodejs/libuv event (I/O is ready, etc.) so you need to wakeup cocoa (with glfw its glfwPostEmptyEvent and here's how they do it)

It took me a while but it actually works perfectly and it has very low CPU overhead.

BTW: you need to call event handling from the main thread (that's why you need that setImmediate loop) - even if you've found a way to get events from other thread it could stop working in future and it wouldn't work on IOS anyway.

from nodobjc.

aredridel avatar aredridel commented on July 28, 2024

Doing this right -- having access to a single tick in libuv -- is probably not that hard to add to libuv

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

What about the inverse? Apple doesn't offer a run_once() type of function, but the various games on iOS probably have to integrate with the event loop somehow. Here looks like one example: http://www.71squared.com/2009/04/maingameloop-changes/

I'm currently playing around with some workaround code like:

function tick () {
  while ($.CFRunLoopRunInMode($.kCFRunLoopDefaultMode, 0.02, 1) == $.kCFRunLoopRunHandledSource);
  process.nextTick(tick)
}
// Begin the ghetto event loop
tick();

It actually seems like it's working! I'm going to do some more experimenting. The only downside with a technique like this is that it would require some NodObjC-specific way of starting the event loop, like $.startLoop() or something...

I would also like to experiment with the inverse, like you're talking about @aredridel, but that looks like it will be a little bit tougher, especially if I want to support node <= 0.6.x (node 0.7.x already has uv_run_once()).

from nodobjc.

aredridel avatar aredridel commented on July 28, 2024

That makes sense.

You could special-case that API and do it behind the scenes... Or just document that NodObjC needs a special main loop.

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

@JeanSebTr Wow nice code there! That definitely looks like a nice solution for the time being. Thanks!

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

Doing this should be more "official" using the new uv_backend_fd() API in libuv: https://github.com/joyent/libuv/blob/4ba03ddd569bdd361b1498d5f19ec0075db01500/include/uv.h#L271-L285

Hopefully I'll get a chance to look into that soon!

from nodobjc.

sandeepmistry avatar sandeepmistry commented on July 28, 2024

This might be a good example to look at https://github.com/philips/eventloops

(I also noticed uv_backend_fd is not in node 0.8.x)

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

This might be a good example to look at https://github.com/philips/eventloops

Nice find @sandeepmistry! I'll take a look into that.

(I also noticed uv_backend_fd is not in node 0.8.x)

Yes that's correct, it's a relatively new API. It should be in some of the later v0.9.x releases and newer.

from nodobjc.

dtinth avatar dtinth commented on July 28, 2024

For those who found this issue by Google, here is one solution:

npm install uvcf
var $ = require('NodObjC')
require('uvcf').ref() // ← register libuv's event loop in CF's event loop

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024

I've been working on a project integrating node/osx, i've successfully merged the two loops that pass all of node's unit tests and have the same benchmark performance as node. You can see the code at the link below, perhaps there's a way of porting it into a .mm and including it in nodobjc with node-gyp.

https://github.com/trueinteractions/tint2/blob/master/modules/Runtime/Main_mac.mm

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

Nice Trevor! So attempts to port the code directly to NodObjC JS didn't quite work correctly, IIRC?

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024

@TooTallNate Yeah, there doesn't seem to be a way to instruct uv to run its event loop from JS. uv wants to kick up v8 callbacks, but the v8 isolate is locked every time UV tries because its being called from a javascript stack with an already running isolate.

Unless i'm mistaken that seems to be a core issue that's unavoidable, unless you can figure out a way to create and release a v8::isolate while in javascript. And if you can, i'd be impressed :).

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024

BTW, only the code on lines 50-76 and lines 99 & 100 in https://github.com/trueinteractions/tint2/blob/9558d160e9a18316f76183b0d23afa116c9cb486/modules/Runtime/Main_mac.mm is relevant to the conversation, it could be as easy as doing:

static int embed_closed;
static uv_sem_t embed_sem;
static uv_thread_t embed_thread;

static void uv_event(void *info) {
    int r;
    struct kevent errors[1];

    while (!embed_closed) {
        uv_loop_t* loop = uv_default_loop();

        int timeout = uv_backend_timeout(loop);
        int fd = uv_backend_fd(loop);

        do {
            struct timespec ts;
            ts.tv_sec = timeout / 1000;
            ts.tv_nsec = (timeout % 1000) * 1000000;
            r = kevent(fd, NULL, 0, errors, 1, timeout < 0 ? NULL : &ts);
        } while (r == -1 && errno == EINTR);

        // Do not block, but place a function on the main queue, run the
        // node block then re-post the semaphore to unlock this loop.
        dispatch_async(dispatch_get_main_queue(), ^{
            uv_run(uv_default_loop(), UV_RUN_NOWAIT);
            uv_sem_post(&embed_sem);
        });

        // Wait for the main loop to deal with events.
        uv_sem_wait(&embed_sem);
    }
}

void SomeNodeFunctionThatRunsUV(v8::Arguments ... ) {
    embed_closed = 0;
    uv_sem_init(&embed_sem, 0);
    uv_thread_create(&embed_thread, uv_event, NULL);
}

void SomeNodeFunctionThatStopsUV(v8::Arguments ... ) {
    embed_closed = 1;
    uv_thread_join(&embed_thread);
}

into a node callback function, the only other issue I can see is this would need to be kicked off when NSapp's delegate runs applicationDidFinishLaunching:. I haven't tested it in other contexts.

I'm knee deep in some other things right now, but if I have some spare time i'll through this into a node module and see if I can get it working. Unless, does someone else want to ?

from nodobjc.

mz2 avatar mz2 commented on July 28, 2024

Presumably this would work also in an XPC service's main run loop (if configured with one) or another thread's with its CF run loop set up accordingly?

On 26 Aug 2014, at 19:26, Trevor Linton [email protected] wrote:

BTW, only the code on lines 50-76 and lines 99 & 100 in https://github.com/trueinteractions/tint2/blob/9558d160e9a18316f76183b0d23afa116c9cb486/modules/Runtime/Main_mac.mm is relevant to the conversation, it could be as easy as doing:

static int embed_closed;
static uv_sem_t embed_sem;
static uv_thread_t embed_thread;

static void uv_event(void *info) {
int r;
struct kevent errors[1];

while (!embed_closed) {
    uv_loop_t* loop = uv_default_loop();

    int timeout = uv_backend_timeout(loop);
    int fd = uv_backend_fd(loop);

    do {
        struct timespec ts;
        ts.tv_sec = timeout / 1000;
        ts.tv_nsec = (timeout % 1000) * 1000000;
        r = kevent(fd, NULL, 0, errors, 1, timeout < 0 ? NULL : &ts);
    } while (r == -1 && errno == EINTR);

    // Do not block, but place a function on the main queue, run the
    // node block then re-post the semaphore to unlock this loop.
    dispatch_async(dispatch_get_main_queue(), ^{
        uv_run(uv_default_loop(), UV_RUN_NOWAIT);
        uv_sem_post(&embed_sem);
    });

    // Wait for the main loop to deal with events.
    uv_sem_wait(&embed_sem);
}

}

void SomeNodeFunctionThatRunsUV(v8::Arguments ... ) {
embed_closed = 0;
uv_sem_init(&embed_sem, 0);
uv_thread_create(&embed_thread, uv_event, NULL);
}

void SomeNodeFunctionThatStopsUV(v8::Arguments ... ) {
embed_closed = 1;
uv_thread_join(&embed_thread);
}
into a node callback function, the only other issue I can see is this would need to be kicked off when NSapp's delegate runs applicationDidFinishLaunching:. I haven't tested it in other contexts.

I'm knee deep in some other things right now, but if I have some spare time i'll through this into a node module and see if I can get it working. Unless, does someone else want to ?


Reply to this email directly or view it on GitHub.

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024

@mz2 Hypothetically yes, just as long as the uv_event gets its own thread (otherwise it'll block the current one). I should mention if your CFEventLoop isn't inside the main thread (which i'd be baffled as to why/how) you'll find yourself with a whole other issue.

Curiously, I thought XPC was mainly used as a replacement for IPC and was mostly related to process communications.

from nodobjc.

mz2 avatar mz2 commented on July 28, 2024

XPC is indeed an IPC mechanism, but the reason it exists really is for building "XPC services", which are launchd managed processes created by applications on OSX (and actually on iOS too though the APIs for it are private). XPC services are helpful for a few reasons when architecting software on OSX:

  1. you can separate the privileges of different parts of an application. For instance, suppose I wanted to build some web service client code in Node. That service would not need access to the filesystem.
  2. you can separate the lifetime of different parts of your app, for instance 3rd party code you don't trust for any particular reason (say, due to memory reasons), into a separate child process and have the OS manage the lifetime of the component. For instance if it crashes, it gets restarted, or if its unused it gets killed and only later spun up again when needed.

Node would make a lot of sense on OSX in writing self contained services which a larger app would call onto. An XPC service can be configured either to emply a 'regular' run loop or simply by GCD based threads (default, if I remember correctly).

There's more info on creating XPC services here:
https://developer.apple.com/library/mac/documentation/macosx/conceptual/bpsystemstartup/chapters/CreatingXPCServices.html

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024

mz2, hm i'm not too familiar with XPC to comment if its useful, however if the spun up XPC "service" is intended to run on anything other than the main thread you'd have an issue. I believe UV/node are fine on a different thread they just need to be consistant. You'd have to make sure the uv_run is placed on the correct thread, for the code i submitted it just plops it on the main thread.

from nodobjc.

mz2 avatar mz2 commented on July 28, 2024

It can indeed be configured to have a main thread with a similar run loop to a regular Cocoa app. Sounds promising :)

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024

One last note to leave here, if the uv event loop exits due to uv_stop but the process doesn't the uv_event thread will spin up to 100% cpu (since the backend fd is dead). How this should be dealt with varies based on the use case you have.

My use case required uv to spin endlessly, or never run uv_stop. To deal with this I attached an asynchronous dummy event that will never fire to prevent uv_stop from ever being executed (and simply exit when i want too). There may be other ways of listening for uv_stop to terminate the threads, timeout seems to get set to 0 if the uv loop should be stopped, and -1 is returned/set to timeout if uv is trying to say "i have no idea what the timeout should be, there's still events to be processed, but no events left on the schedule, and we're still waiting but who knows how long".

from nodobjc.

shanewholloway avatar shanewholloway commented on July 28, 2024

Some non-UI NSMetadataSearch example code leveraging the working event loop made possible by pull request 56. The example monkey patches in the absence of the pull request being applied.

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

@shanewholloway Have you figured out a way to make this EventLoop class replace app('run'), i.e. in the NodeCocoaHelloWorld.app example app?

from nodobjc.

shanewholloway avatar shanewholloway commented on July 28, 2024

Sure. Updated pull request #56 with revised NodeCocoaHelloWorld.app example app powered by EventLoop module. Also had forked the cocoa-hellow-world gist. I didn't realize they were so similar.

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

Are you a god?

from nodobjc.

shanewholloway avatar shanewholloway commented on July 28, 2024

Thanks for the high-praise! I've just been here before in a cross-platform C/C++ library with bindings for Node and Python. ;) Most of the work is in learning Node's ffi, ref, and NodObjC specifics. Happy to help out – the knowledge isn't doing anyone good stuck in my head! Can't wait to see what the community does with it.

from nodobjc.

trevorlinton avatar trevorlinton commented on July 28, 2024

@shanewholloway VERY impressive. Your'e the type of guy who if he has a few hours of free time runs a 100 mile marathon, saves a bald eagle... from a shark.

Well done.

+1 for merge.

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

@shanewholloway I'm curious as to why on your gist example, the "applicationDidFinishLaunching" callback never gets invoked. Any thoughts there?

from nodobjc.

shanewholloway avatar shanewholloway commented on July 28, 2024

@trevorlinton @TooTallNate

Apologies for the oversight. Forgot to add the call to app('finishLaunching') toward the end. Revised by iPhone, so gist change not yet verified. See other gist I put together for question in pull request #56 that did work last night.

2015-01-14 21:05 PST: Edit confirmed to be working on my build.

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

Closing. Thanks again @shanewholloway :)

from nodobjc.

bernhard-42 avatar bernhard-42 commented on July 28, 2024

I played with Shane's gist of Jan 14, replaced setInterval by setTimeout to just verify that it works, but not add more load. I observed that helloworld2.js sitting there waiting consumes about 42% of my CPU (Core i7 mac mini) using the EventLoop appraoch.
Moving back to app('run') - obviously the timeout log does not appear any more - the CPU is more or less 0%
Is it expected that the integrated event loop is as "busy"?
The event loop integration works really well, but it seem quite expensive ...

from nodobjc.

shanewholloway avatar shanewholloway commented on July 28, 2024

Yes, a CPU-intensive event loop is the expected behavior of this implementation example. The specific combination I put together for the sample is written to go "flat-out", running the Cocoa event loop non-blocking. This will return control of the process to NodeJS for additional javascript-land processing.

A real GUI app will want to tweak untilDate argument in eventLoopCore to a date a little into the future so the process blocks politely in the Cocoa event loop, providing a responsive GUI. You can try this by setting the value to $.NSDate('distantFuture') – you should see your CPU utilization drop to low single digits. However, doing so will starve out the NodeJS event loop.

This balance is why I put EventLoop implementation forward as an example, but not yet as a package. The code is good enough, hopefully, to be forked and improved upon. But I wasn't able to put enough care and thought into the API to provide control over that balance yet. (That pesky day job…) And there are other concerns as well – for instance, I'd suggest looking into cluster module style forking approach to separate GUI and server processes.

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

Ideally we would be able to have libuv poll the Cocoa event loop fd and be notified by a JavaScript event whenever there's Cocoa events to process. This would give the optimal CPU usage / responsiveness.

@shanewholloway Do you know if there's any possible way to poll the event loop file descriptor, or something equivalent to that?

from nodobjc.

TooTallNate avatar TooTallNate commented on July 28, 2024

Possibly related: http://www.cocoabuilder.com/archive/cocoa/224547-questions-about-nsapplication-run.html#224616

If you can require Leopard, take a look at CFFileDescriptor. Prior to
Leopard, you can use CFSocket with any file descriptor, so long as you don't
use the socket-specific parts of that interface.

I do indeed require Leopard for other reasons (my Cocoa bridge uses
the Objective C 2.0 runtime API) so that's not an issue. This should
be an easy fix, since my non-blocking I/O code has pluggable backends;
I can use the Core Foundation run loop on OS X and select() on other
Unices. Looks I can solve the Exposé problem I mentioned in my
original e-mail, as well as the pesky 1-3% CPU usage when idle, and
get rid of some hackish code, in one fell swoop.

from nodobjc.

shanewholloway avatar shanewholloway commented on July 28, 2024

@TooTallNate I do not know. I would suspect a collection of descriptors.

from nodobjc.

bernhard-42 avatar bernhard-42 commented on July 28, 2024

@shanewholloway I played around with untilDate but did not find a great balance between CPU usage (let's say 1 digit percentage) and GUI responsiveness.
I like your idea about cluster module so I started looking into it. One problem is that server process and GUI process need to communicate. The first idea of using a rpc library like dnode does not work, since GUI process does not react on sockets driven by node eventloop.
Looks like one needs some rpc approach that leverages Cocoa for network connectivity in GUI process and can use some standard node stuff in server process. Need to do some further research ...

from nodobjc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.