Skip to content

Commit

Permalink
#5231: Add infrastructure to dispatch messages from (lower level) mod…
Browse files Browse the repository at this point in the history
…ules to any listeners.

First client is the GameManager dispatching the request to open the game setup UI in case it can't find any valid configuration.
  • Loading branch information
codereader committed May 2, 2020
1 parent 3dafa39 commit 536a76d
Show file tree
Hide file tree
Showing 19 changed files with 292 additions and 40 deletions.
95 changes: 95 additions & 0 deletions include/imessagebus.h
@@ -0,0 +1,95 @@
#pragma once

#include <string>
#include <functional>

namespace radiant
{

class IMessage
{
private:
bool _handled;

protected:
IMessage() :
_handled(false)
{}

public:
virtual ~IMessage() {}

// Returns true if this message has been handled
bool isHandled() const
{
return _handled;
}

void setHandled(bool handled)
{
_handled = handled;
}
};

/**
* General-purpose handler used to send and process message to registered listeners.
* Client code can send arbitrary message objects as long as they are deriving from
* IMessage.
* To receive the message, client code needs to add a listener.
*/
class IMessageBus
{
public:
virtual ~IMessageBus() {}

typedef std::function<void(IMessage&)> Listener;

/**
* Register a listener which gets called with the message as argument.
* An ID of the listener will be returned which can be used for unsubscription.
*/
virtual std::size_t addListener(const Listener& listener) = 0;

/**
* Unsubscribe the given listener.
*/
virtual void removeListener(std::size_t listenerId) = 0;

/**
* Send the given message along the wire. Clients need to construct
* the message themselves, and check the possible result.
*/
virtual void sendMessage(IMessage& message) = 0;
};

/**
* Small adaptor to allow for less client setup code
* when only listening for a certain IMessage subtype
*/
template<typename T>
class TypeListener :
private std::function<void(T&)>
{
public:
TypeListener(void (*specialisedFunc)(T&)) :
std::function<void(T&)>(specialisedFunc)
{}

TypeListener(const std::function<void(T&)>& specialisedFunc) :
std::function<void(T&)>(specialisedFunc)
{}

// Fulfills the Listener function signature
void operator()(IMessage& message)
{
// Perform a type cast and dispatch to our base class' call operator
try
{
std::function<void(T&)>::operator()(dynamic_cast<T&>(message));
}
catch (std::bad_cast&)
{}
}
};

}
7 changes: 7 additions & 0 deletions include/iradiant.h
Expand Up @@ -13,6 +13,8 @@ namespace applog { class ILogWriter; }
namespace radiant
{

class IMessageBus; // see imessagebus.h

/**
* Main application host, offering access to the Module Registry
* and the logging infrastructure.
Expand All @@ -39,6 +41,11 @@ class IRadiant :
*/
virtual IModuleRegistry& getModuleRegistry() = 0;

/**
* Get a reference to the central message handler.
*/
virtual IMessageBus& getMessageBus() = 0;

/**
* Loads and initialises all modules, starting up the
* application.
Expand Down
1 change: 1 addition & 0 deletions include/precompiled_interfaces.h
Expand Up @@ -51,6 +51,7 @@
#include "imd5model.h"
#include "imediabrowser.h"
#include "imenu.h"
#include "imessagebus.h"
#include "imodel.h"
#include "imodelcache.h"
#include "imodelsurface.h"
Expand Down
41 changes: 41 additions & 0 deletions libs/GameConfigNeededMessage.h
@@ -0,0 +1,41 @@
#pragma once

#include "igame.h"
#include "imessagebus.h"

namespace game
{

/**
* Message object sent to the MessageBus when the application
* is lacking a game configuration.
*
* Use the setConfig() method to assign a configuration to
* return to the sender.
*
* When this message stays unhandled after it went through the
* MessageBus, the application is unlikely to be able to continue
* and will probably terminate.
*/
class ConfigurationNeeded :
public radiant::IMessage
{
private:
GameConfiguration _config;

public:
ConfigurationNeeded()
{}

const GameConfiguration& getConfig() const
{
return _config;
}

void setConfig(const GameConfiguration& config)
{
_config = config;
}
};

}
10 changes: 8 additions & 2 deletions radiant/Radiant.cpp
Expand Up @@ -9,8 +9,8 @@
#include "log/LogStream.h"
#include "log/LogWriter.h"
#include "log/LogFile.h"
#include "modulesystem/ModuleRegistry.h"
#include "modulesystem/StaticModule.h"
#include "messagebus/MessageBus.h"

#ifndef POSIX
#include "settings/LanguageManager.h"
Expand All @@ -27,7 +27,8 @@ namespace
}

Radiant::Radiant(ApplicationContext& context) :
_context(context)
_context(context),
_messageBus(new MessageBus)
{
// Set the stream references for rMessage(), redirect std::cout, etc.
applog::LogStream::InitialiseStreams(getLogWriter());
Expand Down Expand Up @@ -69,6 +70,11 @@ module::ModuleRegistry& Radiant::getModuleRegistry()
return *_moduleRegistry;
}

radiant::IMessageBus& Radiant::getMessageBus()
{
return *_messageBus;
}

void Radiant::startup()
{
// Register the modules hosted in this binary
Expand Down
5 changes: 5 additions & 0 deletions radiant/Radiant.h
Expand Up @@ -8,6 +8,8 @@ namespace applog { class LogFile; }
namespace radiant
{

class MessageBus;

class Radiant :
public IRadiant
{
Expand All @@ -18,6 +20,8 @@ class Radiant :

std::unique_ptr<module::ModuleRegistry> _moduleRegistry;

std::unique_ptr<MessageBus> _messageBus;

public:
Radiant(ApplicationContext& context);

Expand All @@ -29,6 +33,7 @@ class Radiant :

applog::ILogWriter& getLogWriter() override;
module::ModuleRegistry& getModuleRegistry() override;
radiant::IMessageBus& getMessageBus() override;
void startup() override;

static std::shared_ptr<Radiant>& InstancePtr();
Expand Down
8 changes: 8 additions & 0 deletions radiant/RadiantApp.cpp
Expand Up @@ -6,6 +6,8 @@

#include "log/PIDFile.h"
#include "module/CoreModule.h"
#include "GameConfigNeededMessage.h"
#include "ui/prefdialog/GameSetupDialog.h"
#include "modulesystem/StaticModule.h"

#include <wx/wxprec.h>
Expand Down Expand Up @@ -140,6 +142,12 @@ void RadiantApp::onStartupEvent(wxCommandEvent& ev)
ui::Splash::OnAppStartup();
#endif

// In first-startup scenarios the game configuration is not present
// in which case the GameManager will dispatch a message asking
// for showing a dialog or similar. Connect the listener.
_coreModule->get()->getMessageBus().addListener(
radiant::TypeListener(ui::GameSetupDialog::HandleGameConfigMessage));

// Pick up all the statically defined modules and register them
module::internal::StaticModuleList::RegisterModules();

Expand Down
54 changes: 54 additions & 0 deletions radiant/messagebus/MessageBus.h
@@ -0,0 +1,54 @@
#pragma once

#include <mutex>
#include "imessagebus.h"

namespace radiant
{

class MessageBus :
public IMessageBus
{
private:
std::recursive_mutex _lock;
std::map<std::size_t, Listener> _listeners;

bool _processingMessage;
std::size_t _nextId;

public:
MessageBus() :
_nextId(1)
{}

std::size_t addListener(const Listener & listener) override
{
std::lock_guard<std::recursive_mutex> guard(_lock);

auto id = _nextId++;
_listeners.emplace(id, listener);

return id;
}

void removeListener(std::size_t listenerId) override
{
std::lock_guard<std::recursive_mutex> guard(_lock);

assert(_listeners.find(listenerId) != _listeners.end());

_listeners.erase(listenerId);
}

void sendMessage(IMessage& message) override
{
std::lock_guard<std::recursive_mutex> guard(_lock);

for (auto it = _listeners.begin(); it != _listeners.end(); /* in-loop */)
{
(*it++).second(message);
}
}
};

}
28 changes: 18 additions & 10 deletions radiant/settings/GameManager.cpp
Expand Up @@ -2,10 +2,12 @@

#include "i18n.h"
#include "iregistry.h"
#include "iradiant.h"
#include "imessagebus.h"
#include "icommandsystem.h"
#include "itextstream.h"
#include "ifilesystem.h"
#include "ipreferencesystem.h"
#include "ui/prefdialog/GameSetupDialog.h"

#include "os/file.h"
#include "os/dir.h"
Expand All @@ -21,6 +23,7 @@
#include "wxutil/dialog/MessageBox.h"
#include "modulesystem/StaticModule.h"
#include "modulesystem/ApplicationContextImpl.h"
#include "GameConfigNeededMessage.h"

#include <sigc++/bind.h>

Expand Down Expand Up @@ -135,7 +138,7 @@ IGamePtr Manager::currentGame()
if (_config.gameType.empty())
{
// No game type selected, bail out, the program will crash anyway on module load
wxutil::Messagebox::ShowFatalError(_("GameManager: No game type selected, can't continue."), NULL);
wxutil::Messagebox::ShowFatalError(_("GameManager: No game type selected, can't continue."), nullptr);
}

return _games[_config.gameType];
Expand Down Expand Up @@ -244,8 +247,19 @@ void Manager::applyConfig(const GameConfiguration& config)

void Manager::showGameSetupDialog()
{
// Paths not valid, ask the user to select something
ui::GameSetupDialog::Show(cmd::ArgumentList());
// Paths not valid, dispatch a message
ConfigurationNeeded message;

GlobalRadiantCore().getMessageBus().sendMessage(message);

if (message.isHandled())
{
applyConfig(message.getConfig());
}
else
{
wxutil::Messagebox::ShowFatalError(_("No valid game configuration found, cannot continue."), nullptr);
}
}

void Manager::setMapAndPrefabPaths(const std::string& baseGamePath)
Expand Down Expand Up @@ -428,10 +442,4 @@ void Manager::loadGameFiles(const std::string& appPath)
// The static module definition (self-registers)
module::StaticModule<Manager> gameManagerModule;

Manager& Manager::Instance()
{
return *std::static_pointer_cast<Manager>(
module::GlobalModuleRegistry().getModule(MODULE_GAMEMANAGER));
}

} // namespace game
3 changes: 0 additions & 3 deletions radiant/settings/GameManager.h
Expand Up @@ -77,9 +77,6 @@ class Manager :
// greebo: Stores the given config, initialises VFS and constructs a few secondary paths.
void applyConfig(const GameConfiguration& config) override;

// Module-internal accessor to the GameManager instance
static Manager& Instance();

private:
/**
* greebo: Loads the game type from the saved settings.
Expand Down

0 comments on commit 536a76d

Please sign in to comment.