From b97c82469a8ea0039234f20444a05da035591725 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Sun, 16 Jul 2017 16:45:49 -1000 Subject: [PATCH] shared_ptr for IWindow; better Cocoa app lifecycle --- include/boo/IApplication.hpp | 2 +- include/boo/IWindow.hpp | 7 ++-- lib/mac/ApplicationCocoa.mm | 69 ++++++++++++++++++++++++++++-------- lib/mac/WindowCocoa.mm | 49 +++++++++++++------------ test/main.cpp | 2 +- 5 files changed, 88 insertions(+), 41 deletions(-) diff --git a/include/boo/IApplication.hpp b/include/boo/IApplication.hpp index d704bf9..55aaca9 100644 --- a/include/boo/IApplication.hpp +++ b/include/boo/IApplication.hpp @@ -51,7 +51,7 @@ public: virtual const std::vector& getArgs() const=0; /* Constructors/initializers for sub-objects */ - virtual IWindow* newWindow(const SystemString& title, uint32_t drawSamples)=0; + virtual std::shared_ptr newWindow(const SystemString& title, uint32_t drawSamples)=0; }; diff --git a/include/boo/IWindow.hpp b/include/boo/IWindow.hpp index 9ac5e8b..8d9e917 100644 --- a/include/boo/IWindow.hpp +++ b/include/boo/IWindow.hpp @@ -254,14 +254,15 @@ enum class EClipboardType PNGImage = 3 }; -class IWindow +class IWindow : public std::enable_shared_from_this { public: - virtual ~IWindow() {} + virtual ~IWindow() = default; virtual void setCallback(IWindowCallback* cb)=0; - + + virtual void closeWindow()=0; virtual void showWindow()=0; virtual void hideWindow()=0; diff --git a/lib/mac/ApplicationCocoa.mm b/lib/mac/ApplicationCocoa.mm index d051f3e..f6ed750 100644 --- a/lib/mac/ApplicationCocoa.mm +++ b/lib/mac/ApplicationCocoa.mm @@ -11,6 +11,12 @@ #error ARC Required #endif +/* If set, application will terminate once client thread reaches end; + * main() will not get back control. Otherwise, main will get back control + * but App will not terminate in the normal Cocoa manner (possibly resulting + * in CoreAnimation warnings). */ +#define COCOA_TERMINATE 1 + namespace boo {class ApplicationCocoa;} @interface AppDelegate : NSObject { @@ -24,8 +30,8 @@ namespace boo { static logvisor::Module Log("boo::ApplicationCocoa"); -IWindow* _WindowCocoaNew(const SystemString& title, NSOpenGLContext* lastGLCtx, - MetalContext* metalCtx, uint32_t sampleCount); +std::shared_ptr _WindowCocoaNew(const SystemString& title, NSOpenGLContext* lastGLCtx, + MetalContext* metalCtx, uint32_t sampleCount); class ApplicationCocoa : public IApplication { @@ -41,7 +47,7 @@ private: NSPanel* aboutPanel; /* All windows */ - std::unordered_map m_windows; + std::unordered_map> m_windows; MetalContext m_metalCtx; @@ -108,6 +114,7 @@ public: std::thread m_clientThread; int m_clientReturn = 0; + bool m_terminateNow = false; int run() { /* Spawn client thread */ @@ -118,11 +125,30 @@ public: /* Run app */ m_clientReturn = m_callback.appMain(this); - /* Cleanup here */ - std::vector> toDelete; - toDelete.reserve(m_windows.size()); - for (auto& window : m_windows) - toDelete.emplace_back(window.second); + /* Cleanup */ + for (auto& w : m_windows) + if (std::shared_ptr window = w.second.lock()) + window->closeWindow(); + +#if COCOA_TERMINATE + /* Continue termination */ + dispatch_sync(dispatch_get_main_queue(), + ^{ + /* Ends modal run loop and continues Cocoa termination */ + [[NSApplication sharedApplication] replyToApplicationShouldTerminate:YES]; + + /* If this is reached, application didn't spawn any windows + * and must be explicitly terminated */ + m_terminateNow = true; + [[NSApplication sharedApplication] terminate:nil]; + }); +#else + /* Return control to main() */ + dispatch_sync(dispatch_get_main_queue(), + ^{ + [[NSApplication sharedApplication] stop:nil]; + }); +#endif }); /* Already in Cocoa's event loop; return now */ @@ -154,9 +180,9 @@ public: return m_args; } - IWindow* newWindow(const std::string& title, uint32_t sampleCount) + std::shared_ptr newWindow(const std::string& title, uint32_t sampleCount) { - IWindow* newWindow = _WindowCocoaNew(title, m_lastGLCtx, &m_metalCtx, sampleCount); + auto newWindow = _WindowCocoaNew(title, m_lastGLCtx, &m_metalCtx, sampleCount); m_windows[newWindow->getPlatformHandle()] = newWindow; return newWindow; } @@ -187,10 +213,14 @@ int ApplicationRun(IApplication::EPlatformType platform, if (platform != IApplication::EPlatformType::Cocoa && platform != IApplication::EPlatformType::Auto) return 1; + /* Never deallocated to ensure window destructors have access */ APP = new ApplicationCocoa(cb, uniqueName, friendlyName, pname, args); } [[NSApplication sharedApplication] run]; - return static_cast(APP)->m_clientReturn; + ApplicationCocoa* appCocoa = static_cast(APP); + if (appCocoa->m_clientThread.joinable()) + appCocoa->m_clientThread.join(); + return appCocoa->m_clientReturn; } } @@ -208,12 +238,23 @@ int ApplicationRun(IApplication::EPlatformType platform, (void)notification; m_app->run(); } -- (void)applicationWillTerminate:(NSNotification*)notification +#if COCOA_TERMINATE +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app { - (void)notification; + (void)app; + if (m_app->m_terminateNow) + return NSTerminateNow; m_app->m_callback.appQuitting(m_app); - m_app->m_clientThread.join(); + return NSTerminateLater; } +#else +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app +{ + (void)app; + m_app->m_callback.appQuitting(m_app); + return NSTerminateCancel; +} +#endif - (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename { std::vector strVec; diff --git a/lib/mac/WindowCocoa.mm b/lib/mac/WindowCocoa.mm index f8fc134..cf27798 100644 --- a/lib/mac/WindowCocoa.mm +++ b/lib/mac/WindowCocoa.mm @@ -17,10 +17,10 @@ namespace boo {class WindowCocoa; class GraphicsContextCocoa;} @interface WindowCocoaInternal : NSWindow { - boo::WindowCocoa* booWindow; + std::shared_ptr booWindow; id touchBarProvider; } -- (id)initWithBooWindow:(boo::WindowCocoa*)bw title:(const std::string&)title; +- (id)initWithBooWindow:(std::shared_ptr&)bw title:(const std::string&)title; - (void)setFrameDefault; - (NSRect)genFrameDefault; - (void)setTouchBarProvider:(id)provider; @@ -218,6 +218,7 @@ public: m_dataFactory->destroyAllData(); delete m_commandQueue; delete m_dataFactory; + printf("CONTEXT DESTROYED\n"); } void _setCallback(IWindowCallback* cb) @@ -1266,19 +1267,19 @@ static NSString* ClipboardTypes[] = class WindowCocoa : public IWindow { - WindowCocoaInternal* m_nsWindow; GraphicsContextCocoa* m_gfxCtx; EMouseCursor m_cursor = EMouseCursor::None; - bool m_closed = false; public: - WindowCocoa(const std::string& title, NSOpenGLContext* lastGLCtx, MetalContext* metalCtx, uint32_t sampleCount) + void setup(const std::string& title, NSOpenGLContext* lastGLCtx, MetalContext* metalCtx, uint32_t sampleCount) { dispatch_sync(dispatch_get_main_queue(), ^{ - m_nsWindow = [[WindowCocoaInternal alloc] initWithBooWindow:this title:title]; + std::shared_ptr windowPtr = + std::static_pointer_cast(shared_from_this()); + m_nsWindow = [[WindowCocoaInternal alloc] initWithBooWindow:windowPtr title:title]; #if BOO_HAS_METAL if (metalCtx->m_dev) m_gfxCtx = static_cast(_GraphicsContextCocoaMetalNew(IGraphicsContext::EGraphicsAPI::Metal, @@ -1293,14 +1294,11 @@ public: void _clearWindow() { - m_closed = true; + m_nsWindow = nullptr; } ~WindowCocoa() { - if (!m_closed) - [m_nsWindow orderOut:nil]; - delete m_gfxCtx; APP->_deletedWindow(this); } @@ -1309,6 +1307,14 @@ public: m_gfxCtx->_setCallback(cb); } + void closeWindow() + { + dispatch_sync(dispatch_get_main_queue(), + ^{ + [m_nsWindow close]; + }); + } + void showWindow() { dispatch_sync(dispatch_get_main_queue(), @@ -1332,13 +1338,10 @@ public: void setTitle(const std::string& title) { - if (!m_closed) - { - dispatch_sync(dispatch_get_main_queue(), - ^{ - [m_nsWindow setTitle:[NSString stringWithUTF8String:title.c_str()]]; - }); - } + dispatch_sync(dispatch_get_main_queue(), + ^{ + [m_nsWindow setTitle:[NSString stringWithUTF8String:title.c_str()]]; + }); } void setCursor(EMouseCursor cursor) @@ -1555,16 +1558,18 @@ public: }; -IWindow* _WindowCocoaNew(const SystemString& title, NSOpenGLContext* lastGLCtx, - MetalContext* metalCtx, uint32_t sampleCount) +std::shared_ptr _WindowCocoaNew(const SystemString& title, NSOpenGLContext* lastGLCtx, + MetalContext* metalCtx, uint32_t sampleCount) { - return new WindowCocoa(title, lastGLCtx, metalCtx, sampleCount); + auto ret = std::make_shared(); + ret->setup(title, lastGLCtx, metalCtx, sampleCount); + return ret; } } @implementation WindowCocoaInternal -- (id)initWithBooWindow:(boo::WindowCocoa *)bw title:(const boo::SystemString&)title +- (id)initWithBooWindow:(std::shared_ptr&)bw title:(const boo::SystemString&)title { self = [self initWithContentRect:[self genFrameDefault] styleMask:NSWindowStyleMaskTitled| @@ -1603,8 +1608,8 @@ IWindow* _WindowCocoaNew(const SystemString& title, NSOpenGLContext* lastGLCtx, } - (void)close { - booWindow->_clearWindow(); [super close]; + booWindow->_clearWindow(); } - (BOOL)acceptsFirstResponder { diff --git a/test/main.cpp b/test/main.cpp index 5362338..fdf74ca 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -202,7 +202,7 @@ struct CTestWindowCallback : IWindowCallback struct TestApplicationCallback : IApplicationCallback { - IWindow* mainWindow; + std::shared_ptr mainWindow; boo::TestDeviceFinder devFinder; CTestWindowCallback windowCallback; bool running = true;