due to investigating that Heisenbug, I understand the storage layout more clearly. It occured to me that there is no reason to copy the terminationHandler (functor) into an instance variable, since it is easily possible to keep all of the invocation and error handling confined within the scope of the run function, i.e. on stack. So the effective memory layout does not change, but the legibility of the code is improved, since we're able to remove the dtor and simplyfy the ctor and avoid most of the member fields.
205 lines
5.9 KiB
C++
205 lines
5.9 KiB
C++
/*
|
|
GuiFacade - access point for communicating with the Lumiera GTK GUI
|
|
|
|
Copyright (C) Lumiera.org
|
|
2008, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
* *****************************************************/
|
|
|
|
|
|
#include "gui/guifacade.hpp"
|
|
#include "include/gui-notification-facade.h"
|
|
#include "lib/sync.hpp"
|
|
#include "lib/error.hpp"
|
|
#include "lib/depend.hpp"
|
|
#include "lib/functor-util.hpp"
|
|
#include "common/instancehandle.hpp"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
|
|
namespace gui {
|
|
|
|
using std::string;
|
|
using std::unique_ptr;
|
|
using lumiera::Subsys;
|
|
using lumiera::InstanceHandle;
|
|
using lib::Sync;
|
|
using lib::RecursiveLock_NoWait;
|
|
|
|
|
|
|
|
/** load and start the GUI as a plugin */
|
|
struct GuiRunner
|
|
: public GuiFacade
|
|
{
|
|
typedef InstanceHandle<LUMIERA_INTERFACE_INAME(lumieraorg_Gui, 1)> GuiHandle;
|
|
|
|
GuiHandle theGUI_;
|
|
|
|
|
|
GuiRunner (Subsys::SigTerm terminationHandle)
|
|
: theGUI_("lumieraorg_Gui", 1, 1, "lumieraorg_GuiStarterPlugin") // load GuiStarterPlugin
|
|
{
|
|
ASSERT (theGUI_);
|
|
bool res = this->launchUI (terminationHandle);
|
|
|
|
if (!res || lumiera_error_peek())
|
|
throw lumiera::error::Fatal("failed to bring up GUI",lumiera_error());
|
|
}
|
|
|
|
|
|
bool launchUI (Subsys::SigTerm& terminationHandle) override
|
|
{
|
|
return theGUI_->launchUI (reinterpret_cast<void*> (&terminationHandle));
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace { // implementation of GUI-"Subsystem" : start GUI through GuiStarterPlugin
|
|
|
|
unique_ptr<GuiRunner> facade;
|
|
|
|
|
|
class GuiSubsysDescriptor
|
|
: public lumiera::Subsys,
|
|
public Sync<RecursiveLock_NoWait>
|
|
{
|
|
operator string () const { return "Lumiera GTK GUI"; }
|
|
|
|
bool
|
|
shouldStart (lumiera::Option& opts) override
|
|
{
|
|
if (opts.isHeadless() or 0 < opts.getPort())
|
|
{
|
|
INFO (guifacade, "*not* starting the GUI...");
|
|
return false;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
start (lumiera::Option&, Subsys::SigTerm termNotification) override
|
|
{
|
|
Lock guard (this);
|
|
if (facade)
|
|
return false; // already started
|
|
|
|
facade.reset ( // trigger loading of the GuiStarterPlugin...
|
|
new GuiRunner (
|
|
[=] (string* problemMessage)
|
|
{ // will be invoked when the UI thread exits
|
|
closeGuiModule();
|
|
termNotification(problemMessage);
|
|
}));
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @warning there is a possible race here,
|
|
* when shutdown is triggered before the GUI was able to open the GuiNotification interface.
|
|
* However, the Lumiera thread handling wrapper/framework ensures that a new thread actually
|
|
* starts to execute (and picks up the arguments), prior to returning from the thread starting
|
|
* function. For this reason, it is rather unlikely this race actually happens in practice,
|
|
* since opening the GuiNotification interface is done early, while starting the UI-Bus.
|
|
* @todo ////////////////////////////////////////////////////////TICKET #1048 : maybe use Thread.sync (barrier) to work around this?
|
|
*/
|
|
void
|
|
triggerShutdown () noexcept override
|
|
{
|
|
try { GuiNotification::facade().triggerGuiShutdown ("Application shutdown"); }
|
|
|
|
ERROR_LOG_AND_IGNORE (guifacade, "trigger shutdown of the GUI");
|
|
}
|
|
|
|
bool
|
|
checkRunningState () noexcept override
|
|
{
|
|
return bool(facade);
|
|
}
|
|
|
|
|
|
void
|
|
closeGuiModule ()
|
|
{
|
|
Lock guard (this);
|
|
if (!facade)
|
|
{
|
|
WARN (guifacade, "Termination signal invoked, but GUI is currently closed. "
|
|
"Probably this is due to some broken startup logic and should be fixed.");
|
|
}
|
|
else
|
|
facade.reset (0);
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
GuiSubsysDescriptor() { }
|
|
|
|
~GuiSubsysDescriptor()
|
|
{
|
|
if (facade)
|
|
{
|
|
WARN (guifacade, "GUI subsystem terminates, but GuiFacade isn't properly closed. "
|
|
"Closing it forcedly; this indicates broken startup logic and should be fixed.");
|
|
try { facade.reset (0); }
|
|
ERROR_LOG_AND_IGNORE (guifacade, "forcibly closing the GUI");
|
|
ENSURE (not lumiera_error_peek());
|
|
}
|
|
}
|
|
};
|
|
|
|
lib::Depend<GuiSubsysDescriptor> theDescriptor;
|
|
|
|
} // (End) impl details
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @internal intended for use by main(). */
|
|
lumiera::Subsys&
|
|
GuiFacade::getDescriptor()
|
|
{
|
|
return theDescriptor();
|
|
}
|
|
|
|
|
|
bool
|
|
GuiFacade::isUp ()
|
|
{
|
|
return bool(facade);
|
|
}
|
|
|
|
|
|
|
|
} // namespace gui
|
|
|
|
/*
|
|
// Local Variables:
|
|
// mode: C++
|
|
// c-file-style: "gnu"
|
|
// indent-tabs-mode: nil
|
|
// End:
|
|
*/
|