diff --git a/Source/NSXPCConnection+HelperApp.m b/Source/NSXPCConnection+HelperApp.m index 878c777..0f479e2 100644 --- a/Source/NSXPCConnection+HelperApp.m +++ b/Source/NSXPCConnection+HelperApp.m @@ -94,7 +94,7 @@ int xpc_task_key = 0; }]; #ifdef DEBUG_PRINT - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); #else dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC * 2)); if (dispatch_semaphore_wait(sem, waitTime) != 0) diff --git a/Source/OEGameCoreManager.h b/Source/OEGameCoreManager.h index 7dedea7..d1d653f 100644 --- a/Source/OEGameCoreManager.h +++ b/Source/OEGameCoreManager.h @@ -31,6 +31,8 @@ @protocol OEGameCoreOwner; @class OECorePlugin, OEGameCoreController, OESystemPlugin, OEGameStartupInfo; +typedef void(^OEStartupCompletionHandler)(NSError * _Nullable); + NS_ASSUME_NONNULL_BEGIN extern NSString * const OEGameCoreErrorDomain; @@ -60,6 +62,7 @@ typedef NS_ERROR_ENUM(OEGameCoreErrorDomain, OEGameCoreManagerErrorCodes) #pragma mark - Abstract methods, must be overrode in subclasses - (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler; +- (void)loadROMWithCompletionHandler:(OEStartupCompletionHandler)completionHandler; @end diff --git a/Source/OEGameCoreManager.m b/Source/OEGameCoreManager.m index 32987f6..5edf59c 100644 --- a/Source/OEGameCoreManager.m +++ b/Source/OEGameCoreManager.m @@ -60,6 +60,11 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain"; [self doesNotImplementSelector:_cmd]; } +- (void)loadROMWithCompletionHandler:(OEStartupCompletionHandler)completionHandler +{ + [self doesNotImplementSelector:_cmd]; +} + - (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler { [self doesNotImplementSelector:_cmd]; diff --git a/Source/OEThreadGameCoreManager.m b/Source/OEThreadGameCoreManager.m index e800403..6167ac7 100644 --- a/Source/OEThreadGameCoreManager.m +++ b/Source/OEThreadGameCoreManager.m @@ -81,6 +81,20 @@ [_helperThread start]; } +- (void)loadROMWithCompletionHandler:(OEStartupCompletionHandler)completionHandler +{ + _helperThread = [[NSThread alloc] initWithTarget:self selector:@selector(_executionThreadNew:) object:completionHandler]; + _helperThread.name = @"org.openemu.core-manager-thread"; + _helperThread.qualityOfService = NSQualityOfServiceUserInitiated; + + _helper = [[OpenEmuHelperApp alloc] init]; + _helperProxy = [OEThreadProxy threadProxyWithTarget:_helper thread:_helperThread]; + + _gameCoreOwnerProxy = [OEThreadProxy threadProxyWithTarget:[self gameCoreOwner] thread:[NSThread mainThread]]; + + [_helperThread start]; +} + - (void)_executionThread:(OEThreadStartup *)startup { @autoreleasepool @@ -122,6 +136,42 @@ [self _notifyGameCoreDidTerminate]; } +- (void)_executionThreadNew:(OEStartupCompletionHandler)handler +{ + @autoreleasepool + { + [self setGameCoreHelper:(id)_helperProxy]; + [_helper setGameCoreOwner:(id)_gameCoreOwnerProxy]; + + NSError *error; + if(![_helper loadWithStartupInfo:self.startupInfo error:&error]) + { + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ + handler(error); + }); + return; + } + + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ + handler(nil); + }); + + handler = nil; + + _dummyTimer = [NSTimer scheduledTimerWithTimeInterval:1e9 repeats:YES block:^(NSTimer * _Nonnull timer) {}]; + + CFRunLoopRun(); + + if(_stopHandler) + { + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, _stopHandler); + _stopHandler = nil; + } + } + + [self _notifyGameCoreDidTerminate]; +} + - (void)_stopHelperThread:(id)object { [_dummyTimer invalidate]; diff --git a/Source/OEXPCGameCoreManager.m b/Source/OEXPCGameCoreManager.m index d666043..9c59cce 100644 --- a/Source/OEXPCGameCoreManager.m +++ b/Source/OEXPCGameCoreManager.m @@ -63,6 +63,90 @@ return [NSBundle.mainBundle URLForAuxiliaryExecutable:name]; } +- (void)loadROMWithCompletionHandler:(OEStartupCompletionHandler)completionHandler +{ + NSError *error = nil; + _helperConnection = [NSXPCConnection connectionWithServiceName:self.serviceName executableURL:self.executableURL error:&error]; + if(_helperConnection == nil) + { + os_log_error(OE_LOG_HELPER, "No listener endpoint for identifier: %@", self.executableURL); + + if (error == nil) + { + error = [NSError errorWithDomain:OEGameCoreErrorDomain + code:OEGameCoreCouldNotLoadROMError + userInfo:nil]; + } + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ + completionHandler(error); + }); + + // There's no listener endpoint, so don't bother trying to create an NSXPCConnection. + // Returning now since calling initWithListenerEndpoint: and passing it nil results in a memory leak. + // Also, there's no point in trying to get the gameCoreHelper if there's no _helperConnection. + return; + } + + __weak OEXPCGameCoreManager *weakSelf = self; + [_helperConnection setInvalidationHandler:^{ + OEXPCGameCoreManager *strongSelf = weakSelf; + if (strongSelf) + [strongSelf _notifyGameCoreDidTerminate]; + }]; + + _gameCoreOwnerProxy = [OEThreadProxy threadProxyWithTarget:[self gameCoreOwner] thread:[NSThread mainThread]]; + + [_helperConnection setExportedInterface:[NSXPCInterface interfaceWithProtocol:@protocol(OEGameCoreOwner)]]; + [_helperConnection setExportedObject:_gameCoreOwnerProxy]; + + NSXPCInterface *intf = [NSXPCInterface interfaceWithProtocol:@protocol(OEXPCGameCoreHelper)]; + + // startup + [intf setClasses:[NSSet setWithObject:OEGameStartupInfo.class] + forSelector:@selector(loadWithStartupInfo:completionHandler:) + argumentIndex:0 + ofReply:NO]; + + [_helperConnection setRemoteObjectInterface:intf]; + [_helperConnection resume]; + + __block void *gameCoreHelperPointer; + id gameCoreHelper = + [_helperConnection remoteObjectProxyWithErrorHandler: + ^(NSError *error) + { + os_log_error(OE_LOG_HELPER, "Helper Connection (%p) failed with error: %{public}@", + gameCoreHelperPointer, error); + + [self stop]; + }]; + + gameCoreHelperPointer = (__bridge void *)gameCoreHelper; + + if(gameCoreHelper == nil) return; + + [gameCoreHelper loadWithStartupInfo:self.startupInfo completionHandler: + ^(NSError *error) + { + if(error != nil) + { + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ + completionHandler(error); + [self stop]; + }); + + // There's no listener endpoint, so don't bother trying to create an NSXPCConnection. + // Returning now since calling initWithListenerEndpoint: and passing it nil results in a memory leak. + return; + } + + [self setGameCoreHelper:gameCoreHelper]; + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ + completionHandler(nil); + }); + }]; +} + - (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler { NSError *error = nil;