feat: General improvements to OpenEmuKit

NOTE:

Replaced `dispatch_async` to `CFRunLoopPerformBlock`, as it was always
dispatching to the main queue. `dispatch_async` is not reentrant,
meaning that a nested call to dispatch and wait would block forever.
`CFRunLoopPerformBlock` allows this behaviour, to enable the synchronous
calls such as `captureOutputImage` and `captureSourceImage`
This commit is contained in:
Stuart Carnie
2021-06-18 09:40:45 +10:00
parent c466923bfa
commit 34934fbf87
12 changed files with 260 additions and 196 deletions
+12 -8
View File
@@ -12,8 +12,6 @@
0518D6E824F32DD10037101D /* NSEvent+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0518D6E724F32DD10037101D /* NSEvent+Combine.swift */; };
0530C3B32512DF680050F90F /* OEScaledGameLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0530C3B22512DF680050F90F /* OEScaledGameLayerView.swift */; };
0547FD9824E7717E005C1FFC /* OEShadersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0547FD9724E7717E005C1FFC /* OEShadersModel.swift */; };
0547FDA424E78EA6005C1FFC /* OEXPCDebugSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 0547FD9F24E78EA6005C1FFC /* OEXPCDebugSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
0547FDA824E78EA6005C1FFC /* OEXPCDebugSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 0547FDA324E78EA6005C1FFC /* OEXPCDebugSupport.m */; };
0547FDCA24E84498005C1FFC /* NSXPCConnection+HelperApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 0547FDC824E84498005C1FFC /* NSXPCConnection+HelperApp.h */; settings = {ATTRIBUTES = (Public, ); }; };
0547FDCB24E84498005C1FFC /* NSXPCConnection+HelperApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 0547FDC924E84498005C1FFC /* NSXPCConnection+HelperApp.m */; };
0547FDCE24E850EB005C1FFC /* NSXPCListener+HelperApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 0547FDCC24E850EB005C1FFC /* NSXPCListener+HelperApp.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -26,6 +24,9 @@
05713219259C33B9001CB13A /* NSFileManager+ExtendedAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 05713217259C33B9001CB13A /* NSFileManager+ExtendedAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; };
0571321A259C33B9001CB13A /* NSFileManager+ExtendedAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 05713218259C33B9001CB13A /* NSFileManager+ExtendedAttributes.m */; };
0578484B25C1392400A842E7 /* ShaderCompilerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0578484A25C1392300A842E7 /* ShaderCompilerOptions.swift */; };
05AAD98726758240004466E3 /* XPCDebugSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05AAD98626758240004466E3 /* XPCDebugSupport.swift */; };
05B48723266309600049A5F6 /* OEGameCoreManager+Synchronous.h in Headers */ = {isa = PBXBuildFile; fileRef = 05B48721266309600049A5F6 /* OEGameCoreManager+Synchronous.h */; settings = {ATTRIBUTES = (Public, ); }; };
05B48724266309600049A5F6 /* OEGameCoreManager+Synchronous.m in Sources */ = {isa = PBXBuildFile; fileRef = 05B48722266309600049A5F6 /* OEGameCoreManager+Synchronous.m */; };
05D828D524E9881E00BB975E /* OEXPCMatchMaking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05D828D424E9881E00BB975E /* OEXPCMatchMaking.swift */; };
05D828DB24E98A8E00BB975E /* OEXPCMatchMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05D828DA24E98A8E00BB975E /* OEXPCMatchMaker.swift */; };
05E6958424CA5D4200ACFB35 /* OpenEmuKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 05E6958224CA5D4200ACFB35 /* OpenEmuKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -100,8 +101,6 @@
0530C3B22512DF680050F90F /* OEScaledGameLayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OEScaledGameLayerView.swift; sourceTree = "<group>"; };
0533EA6624CDFD030095146E /* XADMaster.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XADMaster.framework; path = ../OpenEmu/XADMaster.framework; sourceTree = "<group>"; };
0547FD9724E7717E005C1FFC /* OEShadersModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OEShadersModel.swift; sourceTree = "<group>"; };
0547FD9F24E78EA6005C1FFC /* OEXPCDebugSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OEXPCDebugSupport.h; sourceTree = "<group>"; };
0547FDA324E78EA6005C1FFC /* OEXPCDebugSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OEXPCDebugSupport.m; sourceTree = "<group>"; };
0547FDC824E84498005C1FFC /* NSXPCConnection+HelperApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSXPCConnection+HelperApp.h"; sourceTree = "<group>"; };
0547FDC924E84498005C1FFC /* NSXPCConnection+HelperApp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSXPCConnection+HelperApp.m"; sourceTree = "<group>"; };
0547FDCC24E850EB005C1FFC /* NSXPCListener+HelperApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSXPCListener+HelperApp.h"; sourceTree = "<group>"; };
@@ -114,6 +113,9 @@
05713217259C33B9001CB13A /* NSFileManager+ExtendedAttributes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+ExtendedAttributes.h"; sourceTree = "<group>"; };
05713218259C33B9001CB13A /* NSFileManager+ExtendedAttributes.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+ExtendedAttributes.m"; sourceTree = "<group>"; };
0578484A25C1392300A842E7 /* ShaderCompilerOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShaderCompilerOptions.swift; sourceTree = "<group>"; };
05AAD98626758240004466E3 /* XPCDebugSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCDebugSupport.swift; sourceTree = "<group>"; };
05B48721266309600049A5F6 /* OEGameCoreManager+Synchronous.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OEGameCoreManager+Synchronous.h"; sourceTree = "<group>"; };
05B48722266309600049A5F6 /* OEGameCoreManager+Synchronous.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OEGameCoreManager+Synchronous.m"; sourceTree = "<group>"; };
05D828D424E9881E00BB975E /* OEXPCMatchMaking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OEXPCMatchMaking.swift; sourceTree = "<group>"; };
05D828DA24E98A8E00BB975E /* OEXPCMatchMaker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OEXPCMatchMaker.swift; sourceTree = "<group>"; };
05E6957F24CA5D4200ACFB35 /* OpenEmuKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OpenEmuKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -212,8 +214,7 @@
05ED1CFB259D43E800A2D400 /* LaunchControl.swift */,
05D828D424E9881E00BB975E /* OEXPCMatchMaking.swift */,
05D828DA24E98A8E00BB975E /* OEXPCMatchMaker.swift */,
0547FD9F24E78EA6005C1FFC /* OEXPCDebugSupport.h */,
0547FDA324E78EA6005C1FFC /* OEXPCDebugSupport.m */,
05AAD98626758240004466E3 /* XPCDebugSupport.swift */,
0547FDC824E84498005C1FFC /* NSXPCConnection+HelperApp.h */,
0547FDC924E84498005C1FFC /* NSXPCConnection+HelperApp.m */,
0547FDCC24E850EB005C1FFC /* NSXPCListener+HelperApp.h */,
@@ -342,6 +343,8 @@
05E6BA2C24CCD76700ACFB35 /* Graphics */,
05E6BA3324CCD78300ACFB35 /* Utilities */,
05E6BA4324CCD7D700ACFB35 /* OEGameCoreManager.h */,
05B48721266309600049A5F6 /* OEGameCoreManager+Synchronous.h */,
05B48722266309600049A5F6 /* OEGameCoreManager+Synchronous.m */,
05E6BA4224CCD7D600ACFB35 /* OEGameCoreManager_Internal.h */,
05E6BA4424CCD7D800ACFB35 /* OEGameCoreManager.m */,
0518D6E324F2BA4B0037101D /* OEGameStartupInfo.h */,
@@ -445,10 +448,10 @@
0547FDD224E85783005C1FFC /* OEXPCGameCoreManager.h in Headers */,
05E6BA1F24CCD71D00ACFB35 /* OESystemPlugin.h in Headers */,
05E6BA0724CCD49600ACFB35 /* OEGameHelperMetalLayer.h in Headers */,
0547FDA424E78EA6005C1FFC /* OEXPCDebugSupport.h in Headers */,
0547FDCE24E850EB005C1FFC /* NSXPCListener+HelperApp.h in Headers */,
05E6BA2924CCD75900ACFB35 /* OEAudioUnit.h in Headers */,
05E6BA3C24CCD79600ACFB35 /* OEThreadProxy.h in Headers */,
05B48723266309600049A5F6 /* OEGameCoreManager+Synchronous.h in Headers */,
05E6BA4A24CCD7DB00ACFB35 /* OEGameCoreManager.h in Headers */,
05E6B9FE24CCD46900ACFB35 /* OEMTLGameRenderer.h in Headers */,
05E6BA2024CCD71D00ACFB35 /* OECorePlugin.h in Headers */,
@@ -555,11 +558,13 @@
05E6BA2A24CCD75900ACFB35 /* OEAudioUnit.mm in Sources */,
05E6BA3E24CCD79600ACFB35 /* OEThreadProxy.m in Sources */,
05E6BA3D24CCD79600ACFB35 /* OELogging.m in Sources */,
05AAD98726758240004466E3 /* XPCDebugSupport.swift in Sources */,
0571321A259C33B9001CB13A /* NSFileManager+ExtendedAttributes.m in Sources */,
05ED1CFC259D43E800A2D400 /* LaunchControl.swift in Sources */,
0547FD9824E7717E005C1FFC /* OEShadersModel.swift in Sources */,
05E6BA1D24CCD71D00ACFB35 /* OESystemPlugin.m in Sources */,
0547FDCF24E850EB005C1FFC /* NSXPCListener+HelperApp.m in Sources */,
05B48724266309600049A5F6 /* OEGameCoreManager+Synchronous.m in Sources */,
05EA2F2224FFD16B00569EBA /* OEIntegralWindowResizingDelegate.swift in Sources */,
0578484B25C1392400A842E7 /* ShaderCompilerOptions.swift in Sources */,
05E6B9FA24CCD46900ACFB35 /* OEOpenGL3GameRenderer.m in Sources */,
@@ -571,7 +576,6 @@
05E6BA2224CCD71D00ACFB35 /* OECorePlugin.m in Sources */,
05E6BA0524CCD49600ACFB35 /* OECoreVideoTexture.m in Sources */,
05E6B9FD24CCD46900ACFB35 /* OEOpenGL2GameRenderer.m in Sources */,
0547FDA824E78EA6005C1FFC /* OEXPCDebugSupport.m in Sources */,
05D828DB24E98A8E00BB975E /* OEXPCMatchMaker.swift in Sources */,
05E6BA0424CCD49600ACFB35 /* OEGameHelperMetalLayer.m in Sources */,
0530C3B32512DF680050F90F /* OEScaledGameLayerView.swift in Sources */,
+1
View File
@@ -40,6 +40,7 @@ FOUNDATION_EXPORT const unsigned char OpenEmuKitVersionString[];
#import <OpenEmuKit/OpenEmuHelperApp.h>
#import <OpenEmuKit/OpenEmuXPCHelperApp.h>
#import <OpenEmuKit/OEGameCoreManager.h>
#import <OpenEmuKit/OEGameCoreManager+Synchronous.h>
#import <OpenEmuKit/OEThreadGameCoreManager.h>
#import <OpenEmuKit/OEXPCGameCoreManager.h>
#import <OpenEmuKit/OEGameLayerView.h>
+12 -1
View File
@@ -32,6 +32,11 @@
NS_ASSUME_NONNULL_BEGIN
typedef struct _OEGameCoreHelperSetupResult {
OEIntSize screenSize;
OEIntSize aspectSize;
} OEGameCoreHelperSetupResult;
/*!
* A protocol that defines the behaviour required to control an emulator core.
*
@@ -64,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Emulator control
- (void)setupEmulationWithCompletionHandler:(void(^)(OEIntSize screenSize, OEIntSize aspectSize))handler;
- (void)setupEmulationWithCompletionHandler:(void(^)(OEGameCoreHelperSetupResult result))handler;
- (void)startEmulationWithCompletionHandler:(void(^)(void))handler;
- (void)resetEmulationWithCompletionHandler:(void(^)(void))handler;
- (void)stopEmulationWithCompletionHandler:(void(^)(void))handler;
@@ -101,6 +106,12 @@ NS_ASSUME_NONNULL_BEGIN
@protocol OEGameCoreOwner <NSObject>
#pragma mark - Actions
// These actions are triggered from the game core via the OEGlobalEventsHandler protocol
/*! Notify the host application of the user request to save the current state
*/
- (void)saveState;
- (void)loadState;
- (void)quickSave;
@@ -1,4 +1,4 @@
// Copyright (c) 2019, OpenEmu Team
// Copyright (c) 2021, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
@@ -22,25 +22,24 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@import Foundation;
#import <AppKit/AppKit.h>
#import <OpenEmuKit/OpenEmuKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface OEXPCCDebugSupport: NSObject
/*! Returns a boolean to indicate whether a debugger is currently attached to the process.
/*! Category with APIs that run synchronously on the main thread
*/
@property (class, nonatomic, readonly) BOOL debuggerAttached;
@interface OEGameCoreManager (Synchronous)
/*! Wait up to a specified number of nanoseconds for a debugger to attach.
@returns YES if a debugger attached
/**
* Capture an image of the final core video display buffer, which includes all shader effects.
*/
+ (BOOL)waitForDebuggerUntil:(NSUInteger)nanoseconds;
- (NSBitmapImageRep *)captureOutputImage;
/*! Wait indefinitely until a debugger is attached
/**
* Capture an image of the raw core video display buffer with no effects.
*/
+ (void)waitForDebugger;
- (NSBitmapImageRep *)captureSourceImage;
@end
+82
View File
@@ -0,0 +1,82 @@
// Copyright (c) 2021, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import "OEGameCoreManager+Synchronous.h"
#import "OEGameCoreManager_Internal.h"
#import <stdatomic.h>
@implementation OEGameCoreManager (Synchronous)
#define MAIN_INIT \
__block atomic_bool done = false;
#define MAIN_BEGIN \
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
#define MAIN_END \
atomic_store(&done, true); \
CFRunLoopStop(CFRunLoopGetMain()); \
});
#define MAIN_WAIT \
while (atomic_load(&done) == false) \
{ \
if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10.0, NO) == kCFRunLoopRunFinished) break; \
}
#pragma mark - Synchronous APIs
- (NSBitmapImageRep *)captureOutputImage
{
MAIN_INIT
__block NSBitmapImageRep *res = nil;
[self.gameCoreHelper captureOutputImageWithCompletionHandler:^(NSBitmapImageRep * _Nonnull image) {
MAIN_BEGIN
res = image;
MAIN_END
}];
MAIN_WAIT
return res;
}
- (NSBitmapImageRep *)captureSourceImage
{
MAIN_INIT
__block NSBitmapImageRep *res = nil;
[self.gameCoreHelper captureSourceImageWithCompletionHandler:^(NSBitmapImageRep * _Nonnull image) {
MAIN_BEGIN
res = image;
MAIN_END
}];
MAIN_WAIT
return res;
}
@end
+1 -3
View File
@@ -49,15 +49,13 @@ typedef NS_ERROR_ENUM(OEGameCoreErrorDomain, OEGameCoreManagerErrorCodes)
* @details
* The @c OEGameCoreManager is responsible for brokering communication between the
* host and the game core.
* @param queue The queue to execute completion handlers. If nil, handlers will be executed on the main queue.
*/
- (instancetype)initWithStartupInfo:(OEGameStartupInfo *)startupInfo corePlugin:(OECorePlugin *)plugin systemPlugin:(OESystemPlugin *)systemPlugin gameCoreOwner:(id<OEGameCoreOwner>)gameCoreOwner queue:(dispatch_queue_t _Nullable)queue;
- (instancetype)initWithStartupInfo:(OEGameStartupInfo *)startupInfo corePlugin:(OECorePlugin *)plugin systemPlugin:(OESystemPlugin *)systemPlugin gameCoreOwner:(id<OEGameCoreOwner>)gameCoreOwner;
@property(readonly, copy) OEGameStartupInfo *startupInfo;
@property(readonly, weak) OECorePlugin *plugin;
@property(readonly, weak) OESystemPlugin *systemPlugin;
@property(readonly, weak) id<OEGameCoreOwner> gameCoreOwner;
@property(readonly) dispatch_queue_t queue;
#pragma mark - Abstract methods, must be overrode in subclasses
+36 -39
View File
@@ -39,7 +39,6 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain";
corePlugin:(OECorePlugin *)plugin
systemPlugin:(OESystemPlugin *)systemPlugin
gameCoreOwner:(id<OEGameCoreOwner>)gameCoreOwner
queue:(dispatch_queue_t _Nullable)queue
{
if (self = [super init])
{
@@ -47,7 +46,6 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain";
_plugin = plugin;
_systemPlugin = systemPlugin;
_gameCoreOwner = gameCoreOwner;
_queue = queue ?: dispatch_get_main_queue();
}
return self;
}
@@ -62,22 +60,22 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain";
[self doesNotImplementSelector:_cmd];
}
- (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler;
- (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler
{
[self doesNotImplementSelector:_cmd];
}
- (void)setVolume:(CGFloat)value;
- (void)setVolume:(CGFloat)value
{
[_gameCoreHelper setVolume:value];
}
- (void)setPauseEmulation:(BOOL)pauseEmulation;
- (void)setPauseEmulation:(BOOL)pauseEmulation
{
[_gameCoreHelper setPauseEmulation:pauseEmulation];
}
- (void)setAudioOutputDeviceID:(AudioDeviceID)deviceID;
- (void)setAudioOutputDeviceID:(AudioDeviceID)deviceID
{
[_gameCoreHelper setAudioOutputDeviceID:deviceID];
}
@@ -97,9 +95,9 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain";
[_gameCoreHelper insertFileAtURL:url completionHandler:
^(BOOL success, NSError *error)
{
dispatch_async(self->_queue, ^{
block(success, error);
});
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
block(success, error);
});
}];
}
@@ -120,9 +118,8 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain";
- (void)setShaderURL:(NSURL *)url parameters:(NSDictionary<NSString *, NSNumber *> *)parameters completionHandler:(void (^)(BOOL success, NSError * _Nullable error))block
{
__block __auto_type queue = _queue;
[_gameCoreHelper setShaderURL:url parameters:parameters completionHandler:^(BOOL success, NSError *error) {
dispatch_async(queue, ^{
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
block(success, error);
});
}];
@@ -133,79 +130,79 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain";
[_gameCoreHelper setShaderParameterValue:value forKey:key];
}
- (void)setupEmulationWithCompletionHandler:(void(^)(OEIntSize screenSize, OEIntSize aspectSize))handler;
- (void)setupEmulationWithCompletionHandler:(void(^)(OEGameCoreHelperSetupResult result))handler
{
[_gameCoreHelper setupEmulationWithCompletionHandler:^(OEIntSize screenSize, OEIntSize aspectSize) {
dispatch_async(self->_queue, ^{
handler(screenSize, aspectSize);
[_gameCoreHelper setupEmulationWithCompletionHandler:^(OEGameCoreHelperSetupResult result) {
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
handler(result);
});
}];
}
- (void)startEmulationWithCompletionHandler:(void(^)(void))handler;
- (void)startEmulationWithCompletionHandler:(void(^)(void))handler
{
[_gameCoreHelper startEmulationWithCompletionHandler:
^{
dispatch_async(self->_queue, ^{
handler();
});
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, handler);
}];
}
- (void)resetEmulationWithCompletionHandler:(void(^)(void))handler;
- (void)resetEmulationWithCompletionHandler:(void(^)(void))handler
{
[_gameCoreHelper resetEmulationWithCompletionHandler:
^{
dispatch_async(self->_queue, ^{
handler();
});
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, handler);
}];
}
- (void)stopEmulationWithCompletionHandler:(void(^)(void))handler;
- (void)stopEmulationWithCompletionHandler:(void(^)(void))handler
{
[_gameCoreHelper stopEmulationWithCompletionHandler:
^{
dispatch_async(self->_queue, ^{
handler();
[self stop];
});
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
handler();
[self stop];
});
}];
}
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL success, NSError *error))block;
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL success, NSError *error))block
{
[_gameCoreHelper saveStateToFileAtPath:fileName completionHandler:
^(BOOL success, NSError *error)
{
dispatch_async(self->_queue, ^{
block(success, error);
});
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
block(success, error);
});
}];
}
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL success, NSError *error))block;
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL success, NSError *error))block
{
[_gameCoreHelper loadStateFromFileAtPath:fileName completionHandler:
^(BOOL success, NSError *error)
{
dispatch_async(self->_queue, ^{
block(success, error);
});
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
block(success, error);
});
}];
}
- (void)captureOutputImageWithCompletionHandler:(void (^)(NSBitmapImageRep *image))block
{
[_gameCoreHelper captureOutputImageWithCompletionHandler:^(NSBitmapImageRep *image) {
block(image);
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
block(image);
});
}];
}
- (void)captureSourceImageWithCompletionHandler:(void (^)(NSBitmapImageRep *image))block
{
[_gameCoreHelper captureSourceImageWithCompletionHandler:^(NSBitmapImageRep *image) {
block(image);
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
block(image);
});
}];
}
@@ -237,7 +234,7 @@ NSString * const OEGameCoreErrorDomain = @"OEGameCoreErrorDomain";
- (void)_notifyGameCoreDidTerminate
{
__weak OEGameCoreManager *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
OEGameCoreManager *strongSelf = weakSelf;
if (!strongSelf)
return;
+34 -17
View File
@@ -31,6 +31,27 @@
#import "OESystemPlugin.h"
#import "OpenEmuHelperApp.h"
@interface OEThreadStartup: NSObject
@property (nonatomic, nullable) void(^completionHandler)(void);
@property (nonatomic, nullable) void(^errorHandler)(NSError *error);
- (instancetype)initWithCompletionHandler:(void(^)(void))completion errorHandler:(void(^)(NSError *error))error;
@end
@implementation OEThreadStartup
- (instancetype)initWithCompletionHandler:(void(^)(void))completion errorHandler:(void(^)(NSError *error))error
{
if (self = [super init])
{
_completionHandler = [completion copy];
_errorHandler = [error copy];
}
return self;
}
@end
@implementation OEThreadGameCoreManager
{
NSThread *_helperThread;
@@ -41,17 +62,14 @@
OEThreadProxy *_gameCoreOwnerProxy;
void(^_completionHandler)(void);
void(^_errorHandler)(NSError *error);
void(^_stopHandler)(void);
}
- (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler;
- (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler
{
_completionHandler = [completionHandler copy];
_errorHandler = [errorHandler copy];
OEThreadStartup *startup = [[OEThreadStartup alloc] initWithCompletionHandler:completionHandler errorHandler:errorHandler];
_helperThread = [[NSThread alloc] initWithTarget:self selector:@selector(_executionThread:) object:nil];
_helperThread = [[NSThread alloc] initWithTarget:self selector:@selector(_executionThread:) object:startup];
_helperThread.name = @"org.openemu.core-manager-thread";
_helperThread.qualityOfService = NSQualityOfServiceUserInitiated;
@@ -63,7 +81,7 @@
[_helperThread start];
}
- (void)_executionThread:(id)object
- (void)_executionThread:(OEThreadStartup *)startup
{
@autoreleasepool
{
@@ -73,21 +91,22 @@
NSError *error;
if(![_helper loadWithStartupInfo:self.startupInfo error:&error])
{
if(_errorHandler != nil)
if(startup.errorHandler != nil)
{
__block __auto_type errorHandler = _errorHandler;
_errorHandler = nil;
dispatch_async(self.queue, ^{
__block __auto_type errorHandler = startup.errorHandler;
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
errorHandler(error);
});
}
return;
}
if (_completionHandler) {
dispatch_async(self.queue, _completionHandler);
_completionHandler = nil;
if (startup.completionHandler) {
__block __auto_type completionHandler = startup.completionHandler;
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, completionHandler);
}
startup = nil;
_dummyTimer = [NSTimer scheduledTimerWithTimeInterval:1e9 repeats:YES block:^(NSTimer * _Nonnull timer) {}];
@@ -95,7 +114,7 @@
if(_stopHandler)
{
dispatch_async(self.queue, _stopHandler);
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, _stopHandler);
_stopHandler = nil;
}
}
@@ -116,8 +135,6 @@
_helperProxy = nil;
_helper = nil;
_gameCoreOwnerProxy = nil;
_completionHandler = nil;
_errorHandler = nil;
}
- (void)stop
-102
View File
@@ -1,102 +0,0 @@
// Copyright (c) 2019, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import "OEXPCDebugSupport.h"
#import <AppKit/AppKit.h>
#import <assert.h>
#import <stdbool.h>
#import <sys/types.h>
#import <unistd.h>
#import <sys/sysctl.h>
#import <stdatomic.h>
@implementation OEXPCCDebugSupport
+ (BOOL)debuggerAttached
{
int junk;
int mib[4];
struct kinfo_proc info;
size_t size;
// Initialize the flags so that, if sysctl fails for some bizarre
// reason, we get a predictable result.
info.kp_proc.p_flag = 0;
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
// Call sysctl.
size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
assert(junk == 0);
// We're being debugged if the P_TRACED flag is set.
return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
}
+ (BOOL)OE_waitForDebuggerUntilTime:(dispatch_time_t)t
{
dispatch_queue_global_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
__block dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block atomic_bool done;
atomic_init(&done, false);
dispatch_async(queue, ^{
while (!self.debuggerAttached && atomic_load(&done) == false)
{
usleep(100);
}
dispatch_semaphore_signal(sem);
});
BOOL ok = dispatch_semaphore_wait(sem, t) == 0;
atomic_store(&done, true);
return ok;
}
+ (BOOL)waitForDebuggerUntil:(NSUInteger)nanoseconds
{
return [self OE_waitForDebuggerUntilTime:dispatch_time(DISPATCH_TIME_NOW, nanoseconds)];
}
+ (void)waitForDebugger
{
[self OE_waitForDebuggerUntilTime:DISPATCH_TIME_FOREVER];
}
@end
+6 -12
View File
@@ -64,7 +64,7 @@
return [NSBundle.mainBundle URLForAuxiliaryExecutable:name];
}
- (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler;
- (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler
{
NSError *error = nil;
_helperConnection = [NSXPCConnection connectionWithServiceName:self.serviceName executableURL:self.executableURL error:&error];
@@ -78,8 +78,7 @@
code:OEGameCoreCouldNotLoadROMError
userInfo:nil];
}
dispatch_async(self.queue, ^{
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
errorHandler(error);
});
@@ -120,10 +119,7 @@
os_log_error(OE_LOG_HELPER, "Helper Connection (%p) failed with error: %{public}@",
gameCoreHelperPointer, error);
dispatch_async(self.queue, ^{
errorHandler(error);
[self stop];
});
[self stop];
}];
gameCoreHelperPointer = (__bridge void *)gameCoreHelper;
@@ -135,7 +131,7 @@
{
if(error != nil)
{
dispatch_async(self.queue, ^{
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
errorHandler(error);
[self stop];
});
@@ -146,13 +142,11 @@
}
[self setGameCoreHelper:gameCoreHelper];
dispatch_async(self.queue, ^{
completionHandler();
});
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, completionHandler);
}];
}
- (void)stop{
- (void)stop {
[self setGameCoreHelper:nil];
_gameCoreOwnerProxy = nil;
[_helperConnection invalidate];
+7 -2
View File
@@ -442,13 +442,18 @@ static os_log_t LOG_DISPLAY;
}];
}
- (void)setupEmulationWithCompletionHandler:(void(^)(OEIntSize screenSize, OEIntSize aspectSize))handler
- (void)setupEmulationWithCompletionHandler:(void(^)(OEGameCoreHelperSetupResult result))handler
{
[_gameCore setupEmulationWithCompletionHandler:^{
[self setupGameCoreAudioAndVideo];
if(handler)
handler(self->_previousScreenRect.size, self->_previousAspectSize);
{
handler((OEGameCoreHelperSetupResult){
.screenSize = self->_previousScreenRect.size,
.aspectSize = self->_previousAspectSize
});
}
}];
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2021, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import Foundation
import Darwin
@objc
@objcMembers
public class XPCDebugSupport: NSObject {
/// Returns a value indicating whether a debugger is attached to the current process
public static var isDebuggerAttached: Bool {
let keys = [CTL_KERN, KERN_PROC, KERN_PROC_PID, Int32(getpid())]
return keys.withUnsafeBufferPointer { keysPtr -> Bool in
var info = kinfo_proc()
info.kp_proc.p_flag = 0
var size = MemoryLayout.size(ofValue: info)
let res = Darwin.sysctl(UnsafeMutablePointer<Int32>(mutating: keysPtr.baseAddress), UInt32(keys.count), &info, &size, nil, 0)
// Assume no debugger if sysctl fails
guard res == 0 else { return false }
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
}
/// Wait until a debuger is attached for a specific amount of time.
/// - Parameter time: The time to wait for a debugger to be attached.
/// - Returns: A value indicating whether the debugger is attached.
@discardableResult public static func waitForDebugger(until time: Date = .distantFuture) -> Bool {
while !isDebuggerAttached && Date() < time {
// check every 100ms
Thread.sleep(forTimeInterval: 0.100)
}
return isDebuggerAttached
}
}