Bläddra i källkod

Merge pull request #104397 from bruvzg/mac_main_loop

[macOS] Replace custom main loop with `[NSApp run]` and `CFRunLoop` observer.
Thaddeus Crews 5 månader sedan
förälder
incheckning
c3ecb72c3a

+ 1 - 0
platform/macos/display_server_macos.h

@@ -441,6 +441,7 @@ public:
 	virtual Key keyboard_get_label_from_physical(Key p_keycode) const override;
 	virtual void show_emoji_and_symbol_picker() const override;
 
+	void _process_events(bool p_pump);
 	virtual void process_events() override;
 	virtual void force_process_and_drop_events() override;
 

+ 21 - 10
platform/macos/display_server_macos.mm

@@ -1901,6 +1901,11 @@ DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode,
 void DisplayServerMacOS::show_window(WindowID p_id) {
 	WindowData &wd = windows[p_id];
 
+	if (p_id == MAIN_WINDOW_ID) {
+		[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+		static_cast<OS_MacOS *>(OS::get_singleton())->activate();
+	}
+
 	popup_open(p_id);
 	if ([wd.window_object isMiniaturized]) {
 		return;
@@ -3204,20 +3209,26 @@ void DisplayServerMacOS::show_emoji_and_symbol_picker() const {
 }
 
 void DisplayServerMacOS::process_events() {
+	_process_events(true);
+}
+
+void DisplayServerMacOS::_process_events(bool p_pump) {
 	ERR_FAIL_COND(!Thread::is_main_thread());
 
-	while (true) {
-		NSEvent *event = [NSApp
-				nextEventMatchingMask:NSEventMaskAny
-							untilDate:[NSDate distantPast]
-							   inMode:NSDefaultRunLoopMode
-							  dequeue:YES];
+	if (p_pump) {
+		while (true) {
+			NSEvent *event = [NSApp
+					nextEventMatchingMask:NSEventMaskAny
+								untilDate:[NSDate distantPast]
+								   inMode:NSDefaultRunLoopMode
+								  dequeue:YES];
 
-		if (event == nil) {
-			break;
-		}
+			if (event == nil) {
+				break;
+			}
 
-		[NSApp sendEvent:event];
+			[NSApp sendEvent:event];
+		}
 	}
 
 	// Process "menu_callback"s.

+ 1 - 1
platform/macos/godot_application_delegate.h

@@ -36,8 +36,8 @@
 #import <Foundation/Foundation.h>
 
 @interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate>
+- (void)activate;
 - (void)forceUnbundledWindowActivationHackStep1;
 - (void)forceUnbundledWindowActivationHackStep2;
 - (void)forceUnbundledWindowActivationHackStep3;
-- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
 @end

+ 47 - 25
platform/macos/godot_application_delegate.mm

@@ -124,7 +124,13 @@
 	}
 }
 
-- (void)applicationDidFinishLaunching:(NSNotification *)notice {
+- (void)applicationDidFinishLaunching:(NSNotification *)notification {
+	static_cast<OS_MacOS *>(OS::get_singleton())->start_main();
+}
+
+- (void)activate {
+	[NSApp activateIgnoringOtherApps:YES];
+
 	NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
 	const char *bundled_id = getenv("__CFBundleIdentifier");
 	NSString *nsbundleid_env = [NSString stringWithUTF8String:(bundled_id != nullptr) ? bundled_id : ""];
@@ -139,11 +145,6 @@
 
 - (id)init {
 	self = [super init];
-
-	NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
-	[aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
-	[aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEOpenDocuments];
-
 	return self;
 }
 
@@ -152,36 +153,45 @@
 	[[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleColorPreferencesChangedNotification" object:nil];
 }
 
-- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
+- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
 	OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
-	if (!event || !os) {
+	if (!os) {
 		return;
 	}
-
 	List<String> args;
-	if (([event eventClass] == kInternetEventClass) && ([event eventID] == kAEGetURL)) {
-		// Opening URL scheme.
-		NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
-		args.push_back(vformat("--uri=\"%s\"", String::utf8([url UTF8String])));
-	}
-
-	if (([event eventClass] == kCoreEventClass) && ([event eventID] == kAEOpenDocuments)) {
-		// Opening file association.
-		NSAppleEventDescriptor *files = [event paramDescriptorForKeyword:keyDirectObject];
-		if (files) {
-			NSInteger count = [files numberOfItems];
-			for (NSInteger i = 1; i <= count; i++) {
-				NSURL *url = [NSURL URLWithString:[[files descriptorAtIndex:i] stringValue]];
-				args.push_back(String::utf8([url.path UTF8String]));
-			}
+	for (NSURL *url in urls) {
+		if ([url isFileURL]) {
+			args.push_back(String::utf8([url.path UTF8String]));
+		} else {
+			args.push_back(vformat("--uri=\"%s\"", String::utf8([url.absoluteString UTF8String])));
 		}
 	}
+	if (!args.is_empty()) {
+		if (os->get_main_loop()) {
+			// Application is already running, open a new instance with the URL/files as command line arguments.
+			os->create_instance(args);
+		} else if (os->get_cmd_argc() == 0) {
+			// Application is just started, add to the list of command line arguments and continue.
+			os->set_cmdline_platform_args(args);
+		}
+	}
+}
 
+- (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames {
+	OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
+	if (!os) {
+		return;
+	}
+	List<String> args;
+	for (NSString *filename in filenames) {
+		NSURL *url = [NSURL URLWithString:filename];
+		args.push_back(String::utf8([url.path UTF8String]));
+	}
 	if (!args.is_empty()) {
 		if (os->get_main_loop()) {
 			// Application is already running, open a new instance with the URL/files as command line arguments.
 			os->create_instance(args);
-		} else {
+		} else if (os->get_cmd_argc() == 0) {
 			// Application is just started, add to the list of command line arguments and continue.
 			os->set_cmdline_platform_args(args);
 		}
@@ -220,11 +230,23 @@
 	}
 }
 
+- (void)applicationWillTerminate:(NSNotification *)notification {
+	OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
+	if (os) {
+		os->cleanup();
+		exit(os->get_exit_code());
+	}
+}
+
 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
 	DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
 	if (ds) {
 		ds->send_window_event(ds->get_window(DisplayServerMacOS::MAIN_WINDOW_ID), DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST);
 	}
+	OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
+	if (!os || os->os_should_terminate()) {
+		return NSTerminateNow;
+	}
 	return NSTerminateCancel;
 }
 

+ 2 - 26
platform/macos/godot_main_macos.mm

@@ -59,36 +59,12 @@ int main(int argc, char **argv) {
 		}
 	}
 
-	OS_MacOS os;
-	Error err;
+	OS_MacOS os(argv[0], argc - first_arg, &argv[first_arg]);
 
 	// We must override main when testing is enabled.
 	TEST_MAIN_OVERRIDE
 
-	@autoreleasepool {
-		err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);
-	}
-
-	if (err != OK) {
-		if (err == ERR_HELP) { // Returned by --help and --version, so success.
-			return EXIT_SUCCESS;
-		}
-		return EXIT_FAILURE;
-	}
-
-	int ret;
-	@autoreleasepool {
-		ret = Main::start();
-	}
-	if (ret == EXIT_SUCCESS) {
-		os.run();
-	} else {
-		os.set_exit_code(EXIT_FAILURE);
-	}
-
-	@autoreleasepool {
-		Main::cleanup();
-	}
+	os.run(); // Note: This function will never return.
 
 	return os.get_exit_code();
 }

+ 17 - 3
platform/macos/os_macos.h

@@ -40,6 +40,13 @@
 #include "servers/audio_server.h"
 
 class OS_MacOS : public OS_Unix {
+	const char *execpath = nullptr;
+	int argc = 0;
+	char **argv = nullptr;
+
+	id delegate = nullptr;
+	bool should_terminate = false;
+
 	JoypadApple *joypad_apple = nullptr;
 
 #ifdef COREAUDIO_ENABLED
@@ -51,7 +58,7 @@ class OS_MacOS : public OS_Unix {
 
 	CrashHandler crash_handler;
 
-	CFRunLoopObserverRef pre_wait_observer;
+	CFRunLoopObserverRef pre_wait_observer = nil;
 
 	MainLoop *main_loop = nullptr;
 
@@ -64,6 +71,8 @@ class OS_MacOS : public OS_Unix {
 	static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
 	static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context);
 
+	void terminate();
+
 protected:
 	virtual void initialize_core() override;
 	virtual void initialize() override;
@@ -131,8 +140,13 @@ public:
 	virtual String get_system_ca_certificates() override;
 	virtual OS::PreferredTextureFormat get_preferred_texture_format() const override;
 
-	void run();
+	void run(); // Runs macOS native event loop.
+	void start_main(); // Initializes and runs Godot main loop.
+	void activate();
+	void cleanup();
+	bool os_should_terminate() const { return should_terminate; }
+	int get_cmd_argc() const { return argc; }
 
-	OS_MacOS();
+	OS_MacOS(const char *p_execpath, int p_argc, char **p_argv);
 	~OS_MacOS();
 };

+ 68 - 51
platform/macos/os_macos.mm

@@ -47,14 +47,20 @@
 #include <sys/sysctl.h>
 
 void OS_MacOS::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) {
-	// Prevent main loop from sleeping and redraw window during modal popup display.
-	// Do not redraw when rendering is done from the separate thread, it will conflict with the OpenGL context updates.
-
-	DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
-	if (get_singleton()->get_main_loop() && ds && !get_singleton()->is_separate_thread_rendering_enabled() && !ds->get_is_resizing()) {
-		Main::force_redraw();
-		if (!Main::is_iterating()) { // Avoid cyclic loop.
-			Main::iteration();
+	OS_MacOS *os = static_cast<OS_MacOS *>(OS::get_singleton());
+
+	@autoreleasepool {
+		@try {
+			if (DisplayServer::get_singleton()) {
+				static_cast<DisplayServerMacOS *>(DisplayServer::get_singleton())->_process_events(false); // Get rid of pending events.
+			}
+			os->joypad_apple->process_joypads();
+
+			if (Main::iteration()) {
+				os->terminate();
+			}
+		} @catch (NSException *exception) {
+			ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
 		}
 	}
 
@@ -823,36 +829,69 @@ OS::PreferredTextureFormat OS_MacOS::get_preferred_texture_format() const {
 }
 
 void OS_MacOS::run() {
-	if (!main_loop) {
-		return;
-	}
+	[NSApp run];
+}
 
+void OS_MacOS::start_main() {
+	Error err;
 	@autoreleasepool {
-		main_loop->initialize();
+		err = Main::setup(execpath, argc, argv);
 	}
 
-	bool quit = false;
-	while (!quit) {
+	if (err == OK) {
+		int ret;
 		@autoreleasepool {
-			@try {
-				if (DisplayServer::get_singleton()) {
-					DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
-				}
-				joypad_apple->process_joypads();
-
-				if (Main::iteration()) {
-					quit = true;
+			ret = Main::start();
+		}
+		if (ret == EXIT_SUCCESS) {
+			if (main_loop) {
+				@autoreleasepool {
+					main_loop->initialize();
 				}
-			} @catch (NSException *exception) {
-				ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
+				pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
+				CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
+				return;
 			}
+		} else {
+			set_exit_code(EXIT_FAILURE);
 		}
+	} else if (err == ERR_HELP) { // Returned by --help and --version, so success.
+		set_exit_code(EXIT_SUCCESS);
+	} else {
+		set_exit_code(EXIT_FAILURE);
+	}
+
+	terminate();
+}
+
+void OS_MacOS::activate() {
+	[delegate activate];
+}
+
+void OS_MacOS::terminate() {
+	if (pre_wait_observer) {
+		CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
+		CFRelease(pre_wait_observer);
+		pre_wait_observer = nil;
 	}
 
-	main_loop->finalize();
+	should_terminate = true;
+	[NSApp terminate:nil];
+}
+
+void OS_MacOS::cleanup() {
+	if (main_loop) {
+		main_loop->finalize();
+		@autoreleasepool {
+			Main::cleanup();
+		}
+	}
 }
 
-OS_MacOS::OS_MacOS() {
+OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) {
+	execpath = p_execpath;
+	argc = p_argc;
+	argv = p_argv;
 	if (is_sandboxed()) {
 		// Load security-scoped bookmarks, request access, remove stale or invalid bookmarks.
 		NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
@@ -886,7 +925,7 @@ OS_MacOS::OS_MacOS() {
 	[GodotApplication sharedApplication];
 
 	// In case we are unbundled, make us a proper UI application.
-	[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+	[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
 
 	// Menu bar setup must go between sharedApplication above and
 	// finishLaunching below, in order to properly emulate the behavior
@@ -894,35 +933,13 @@ OS_MacOS::OS_MacOS() {
 
 	NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""];
 	[NSApp setMainMenu:main_menu];
-	[NSApp finishLaunching];
 
-	id delegate = [[GodotApplicationDelegate alloc] init];
+	delegate = [[GodotApplicationDelegate alloc] init];
 	ERR_FAIL_NULL(delegate);
 	[NSApp setDelegate:delegate];
 	[NSApp registerUserInterfaceItemSearchHandler:delegate];
-
-	pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
-	CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
-
-	// Process application:openFile: event.
-	while (true) {
-		NSEvent *event = [NSApp
-				nextEventMatchingMask:NSEventMaskAny
-							untilDate:[NSDate distantPast]
-							   inMode:NSDefaultRunLoopMode
-							  dequeue:YES];
-
-		if (event == nil) {
-			break;
-		}
-
-		[NSApp sendEvent:event];
-	}
-
-	[NSApp activateIgnoringOtherApps:YES];
 }
 
 OS_MacOS::~OS_MacOS() {
-	CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
-	CFRelease(pre_wait_observer);
+	// NOP
 }