I need to implement a custom Cocoa main loop using metal-cpp (the source code is also available here). I use an extended version of metal-cpp, so all called below methods were implemented in my version of metal-cpp.
I've create a custom app delegate:
class AppDelegate: public NS::ApplicationDelegate
{
public:
AppDelegate(const char* app_name);
~AppDelegate();
NS::Menu* createMenuBar();
virtual void applicationWillFinishLaunching(NS::Notification* notification_ptr) override;
virtual void applicationDidFinishLaunching(NS::Notification* notification_ptr) override;
virtual bool applicationShouldTerminateAfterLastWindowClosed(NS::Application* sender_ptr) override;
private:
const char* m_app_name;
};
AppDelegate::AppDelegate(const char* app_name):
m_app_name(app_name)
{
}
AppDelegate::~AppDelegate()
{
}
NS::Menu* AppDelegate::createMenuBar()
{
// Menu creation
}
void AppDelegate::applicationWillFinishLaunching(NS::Notification* notification_ptr)
{
NS::Menu* menu_ptr = createMenuBar();
NS::Application* app_ptr = reinterpret_cast<NS::Application*>(notification_ptr->object());
app_ptr->setMainMenu(menu_ptr);
app_ptr->setActivationPolicy(NS::ActivationPolicy::ActivationPolicyRegular);
}
void AppDelegate::applicationDidFinishLaunching(NS::Notification* notification)
{
NS::Application* application = reinterpret_cast< NS::Application* >( notification->object() );
application->activateIgnoringOtherApps( true );
}
bool AppDelegate::applicationShouldTerminateAfterLastWindowClosed(NS::Application* sender_ptr)
{
return false;
}
And a custom application class:
struct AppConfig {
std::string appName;
};
class App
{
public:
App(AppConfig config);
virtual ~App() {}
virtual Result start() = 0;
virtual Result update() = 0;
virtual bool isFinished() const = 0;
virtual Result stop() = 0;
const AppConfig& configuration() const;
protected:
AppConfig config;
};
App::App(AppConfig config):
config(std::move(config))
{
}
const AppConfig& App::configuration() const {
return config;
}
For macOS the App is implemented by DarwinApp:
class DarwinApp: public App
{
public:
DarwinApp(AppConfig config);
~DarwinApp();
Result start() override;
Result update() override;
bool isFinished() const override;
Result stop() override;
private:
bool m_is_finished;
};
DarwinApp::DarwinApp(AppConfig config):
App(std::move(config)),
m_is_finished(true)
{
}
DarwinApp::~DarwinApp()
{
}
Result DarwinApp::start()
{
auto autorelease_pool = NS::AutoreleasePool::alloc()->init();
AppDelegate app_delegate(config.appName.c_str());
NS::Application* application = NS::Application::sharedApplication();
application->setDelegate(&app_delegate);
application->finishLaunching();
autorelease_pool->release();
m_is_finished = false;
return b3::SUCCESS;
}
Result DarwinApp::update()
{
NS::Application* application = NS::Application::sharedApplication();
NS::String* defaultRunLoopMode = NS::String::string(NS::DefaultRunLoopMode, NS::StringEncoding::ASCIIStringEncoding);
NS::Event* event = application->nextEvent(NS::EventMask::AnyEventMask, nil, defaultRunLoopMode, true);
if (event)
{
application->sendEvent(event);
event->release();
}
usleep(10000);
return SUCCESS;
}
bool DarwinApp::isFinished() const
{
return m_is_finished;
}
Result DarwinApp::stop()
{
m_is_finished = true;
return SUCCESS;
}
This solution is based on that answer.
Finally, I implement an event loop:
int main() {
AppConfig config = {};
config.appName = "Tim Cook, add C++ support!";
Application app(config);
std::cout << "App: initialized" << std::endl;
app.start();
std::cout << "App: started" << std::endl;
do
{
app.update();
} while (!app.isFinished());
return 0;
}
It works, of course, BUT macOS marks such app as Not responding and it doesn't build app menu (if to call a simple NSApplication instance run() the menu shows normally, everything works).
What's a right way to make the app responsible instead of usleep()?
Or is there a some C++ example of a custom main loop?
P.S. I don't remember already this is which iteration of the code refactoring. I've re-tried a lot of approaches, they all lead me to Not responsible or to blocking run() and stop() doesn't help. I know to stop() worked you need to send a custom UI event, unfortunately, in my case it just blocked me on postEvent(). In short, everywhere I meet some problems. This solution at least runs a custom main loop, so I try to fix Not responsible error just.
P.P.S. Please, don't send me to GLFW sources, I am researching it for few days already. As much as I could understand it and implement using metal-cpp led me to infinite postEvent in the app delegate.
main(), we still need to call a NS entrypoint and pass our main loop (run()). I am looking for a way to exclude this wrapper.