Skip to content

Instantly share code, notes, and snippets.

@trevorlinton
Last active February 19, 2020 19:01
Show Gist options
  • Save trevorlinton/5cc934f9264629d4e85c to your computer and use it in GitHub Desktop.
Save trevorlinton/5cc934f9264629d4e85c to your computer and use it in GitHub Desktop.
Integrating node's event loop safely into CFRunLoop OSX (uvcf goes to 100% cpu after App run)
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#include "node.h"
#include "v8_typed_array.h"
#include "CoreFoundation/CoreFoundation.h"
static int init_argc;
static char **init_argv;
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property() bool locked;
@property() bool initialized;
@property() v8::Handle<v8::Object> process_l;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_locked = false;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
node::EmitExit(_process_l);
}
- (void) helperTimer {
if(_locked) return;
_locked = true;
if(!_initialized) {
// Create all the objects, load modules, do everything.
// so your next reading stop should be node::Load()!
node::Load(_process_l);
_initialized = true;
}
uv_run(uv_default_loop(), UV_RUN_NOWAIT);
_locked = false;
}
@end
int main(int argc, char * argv[]) {
NSApplication *app = [NSApplication sharedApplication];
AppDelegate *delegate = [[AppDelegate alloc] init];
init_argc = argc;
init_argv = argv;
init_argv = uv_setup_args(init_argc, init_argv);
// This needs to run *before* V8::Initialize()
node::Init(init_argc, init_argv);
v8::V8::Initialize();
{
v8::Locker locker;
v8::HandleScope handle_scope;
// Create the one and only Context.
v8::Persistent<v8::Context> context = v8::Context::New();
v8::Context::Scope context_scope(context);
// Use original argv, as we're just copying values out of it.
delegate.process_l = node::SetupProcessObject(init_argc, init_argv);
v8_typed_array::AttachBindings(context->Global());
// All our arguments are loaded. We've evaluated all of the scripts. We
// might even have created TCP servers. Now we enter the main eventloop. If
// there are no watchers on the loop (except for the ones that were
// uv_unref'd) then this function exits. As long as there are active
// watchers, it blocks.
delegate.locked = true;
delegate.initialized = false;
[NSTimer scheduledTimerWithTimeInterval:0.05 target:delegate selector:@selector(helperTimer) userInfo:nil repeats:true];
[app setDelegate:delegate];
[app setActivationPolicy:NSApplicationActivationPolicyAccessory];
[app run];
#ifndef NDEBUG
context.Dispose();
#endif
}
#ifndef NDEBUG
// Clean up. Not strictly necessary.
v8::V8::Dispose();
#endif // NDEBUG
}
@trevorlinton
Copy link
Author

This took me a very long time to get right (v8 and obj-c plus UV and node are not the best of bed fellows). This runs without problems, the node context does not exit until the application context exits. Even if there's no callbacks; one can be introduced (such as a native mouse event!) without a lot of hubub.

https://github.com/indutny/node-cf accomplishes this directly in node (merging the CFRunLoop and node), however it uses events in the main loop to trigger node/uv's loop which is a LOT of overhead and causes 100%+ CPU time if (NSApplication run) (or any other event loop) decides to join the party.

This technically can be done with some modifications to uvcf, uvcf uses a source type CFloopref to fire off the events, the simpler model COULD be to use an observer and fire off only a NOWAIT (not a ONCE). In addition i'm not sure why the thread would need to be woken up afterwards.

Note that a NSTimer DOES fire off an event into the run loop queue just like uvcf but its less obtrusive than going through the entire stack to see if there's any other events.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment