// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #ifndef NL_FILE_MODULE_H #define NL_FILE_MODULE_H #include "nel/misc/factory.h" #include "nel/misc/command.h" #include "nel/misc/string_mapper.h" #include "nel/misc/co_task.h" #include "nel/misc/algo.h" #include "nel/net/message.h" #include "nel/net/unified_network.h" #include "module_common.h" namespace NLNET { class CGatewayRoute; class IModuleInterceptable; class IInterceptorRegistrar { public: virtual ~IInterceptorRegistrar() { } virtual void registerInterceptor(IModuleInterceptable *interceptor) =0; virtual void unregisterInterceptor(IModuleInterceptable *interceptor) =0; }; /** This interface contains some module methods that can be intercepted. * This is used to build responsibility chain for message processing * or to intercept module up/down to automatically track a list of * interesting module proxy. */ class IModuleInterceptable { public: IModuleInterceptable(); virtual ~IModuleInterceptable(); void registerInterceptor(IInterceptorRegistrar *registrar); void interceptorUnregistered(IInterceptorRegistrar *registrar); IInterceptorRegistrar *getRegistrar(); /** Building of the manifest string can involve interceptors. * The system will call this function on each interceptor * and concatenate the returned strings to build the manifest string. * A default implementation is given that return an emty string. */ virtual std::string buildModuleManifest() const { return std::string(); } //@name Callback from module sockets //@{ /** Called by a socket to inform this module that another * module has been created OR has been made accessible (due to * a gateway connection). */ virtual void onModuleUp(IModuleProxy *moduleProxy) = 0; /** Called by a socket to inform this module that another * module has been deleted OR has been no more accessible (due to * some gateway disconnection). */ virtual void onModuleDown(IModuleProxy *moduleProxy) =0; /** Called internally by basic module imp to process the message by * application code. * The system will call each interceptor until one return true. */ virtual bool onProcessModuleMessage(IModuleProxy *senderModuleProxy, const CMessage &message) =0; /** Called by a socket to inform this module that the security * data attached to a proxy have changed. */ virtual void onModuleSecurityChange(IModuleProxy *moduleProxy) =0; private: IInterceptorRegistrar *_Registrar; }; /** This is the interface for the a module. * It describe interaction with the module itself, * with the module manager, and the module socket. */ class IModule : public NLMISC::CRefCount, public IInterceptorRegistrar, public IModuleInterceptable { friend class IModuleFactory; virtual void setFactory(IModuleFactory *factory) =0; public: /// The module is already plugged in the specified pluging class EModuleAlreadyPluggedHere : public NLMISC::Exception { }; /// An operation invocation has failed (mostly because of lost server module) class EInvokeFailed : public NLMISC::Exception { }; /// An operation invocation has failed because of a bad return type from servant class EInvokeBadReturn : public NLMISC::Exception { }; // Module management ===================== virtual ~IModule() {} /** Module initialization. * If the initialization return false, then the module manager deleted * the module immediately. */ virtual bool initModule(const TParsedCommandLine &initInfo) =0; //@name Basic module information //@{ /** Return the module ID. Each module has a local unique ID assigned * by the manager during module creation. * This ID is local because it is only valid inside a given process. * When module are declared in another process, they receive a * local ID that is different than the ID in their host process. */ virtual TModuleId getModuleId() const =0; /** Return the module name. Each module instance must have a unique * name in the host process. * If no mane is given during module creation, the module manager * build a unique name from the module class and a number. */ virtual const std::string &getModuleName() const =0; /// Return the module class. virtual const std::string &getModuleClassName() const =0; /** Return the module fully qualified name. * the MFQN is the identifier that is used across process to identify * each module. * The MDQNis composed from the computer host name, the process ID and * the module name. * Format : :: * This name is guarantied to be unique (at least, if the host name * is unique !) */ virtual const std::string &getModuleFullyQualifiedName() const =0; /** Return the manifest of the module. * The manifest is a simple string of undefined format. * The manifest is used by a module to expose some intention * or affinity (or whatever else you could imagine) of the * module. * The manifest if transmit along with the module proxy, allowing * any module seeing the proxy to read the manifest. * You should not use manifest to put big string because it is * transmit with proxy information. * Likewise, you should never use manifest to transmit critical data * (such as password) because any module can read it. */ virtual std::string getModuleManifest() const =0; /** Tell if the module implementation support immediate dispatching. * Immediate dispatching is when a message is send between * collocated module (i.e module on the same gateway). In that case, * the gateway forward the module immediately to the addressee module. * In some case, this is not the expected behavior because it give * different result depending if the communicating modules are * collocated or not. * It you module didn't support collocation optimisation, you must * override this method and return false. * In this case, message are stored in a queue and dispatching occur * in the next gateway update, just before the network update. */ virtual bool isImmediateDispatchingSupported() const { return true; } //@} //@name Callback from the module manager //@{ /// A Nel layer 5 service has started. virtual void onServiceUp(const std::string &serviceName, NLNET::TServiceId serviceId) =0; /// A Nel layer 5 service has stopped. virtual void onServiceDown(const std::string &serviceName, NLNET::TServiceId serviceId) = 0; /** Regular update from application. * If the application is a service, then it call CModuleManager::updateModules at each * service loop. * If the application is a regular application, then you have to call manually * CModuleManager::updateModules at regular intervals. */ virtual void onModuleUpdate() =0; /** The service main loop is terminating it job', all module while be * disconnected and removed after this callback. */ virtual void onApplicationExit() = 0; //@} // socket management ===================== //@name module sockets operation //@{ /** Plug this module in the specified socket. * Note that a module can be plugged in several socket at the same * time, but not twice in the same socket. */ virtual void plugModule(IModuleSocket *moduleSocket) throw (EModuleAlreadyPluggedHere) =0; /** Unplug this module from the specified socket. * Note that a module can be plugged in several socket at the same * time, but not twice in the same socket. * Throw an exception if the socket is not currently plug into * the specified socket. */ virtual void unplugModule(IModuleSocket *moduleSocket) throw (EModuleNotPluggedHere) =0; /** Fill resultList vector with the list of socket into * witch this module is currently plugged. * This method don't clear the result vector before filling it. */ virtual void getPluggedSocketList(std::vector &resultList) =0; //@} //@name Callback from module sockets //@{ /** Called by a socket to inform this module that another * module has been created OR has been made accessible (due to * a gateway connection). */ // virtual void onModuleUp(IModuleProxy *moduleProxy) = 0; /** Called by a socket to inform this module that another * module has been deleted OR has been no more accessible (due to * some gateway disconnection). */ // virtual void onModuleDown(IModuleProxy *moduleProxy) =0; /** Called by a socket to receive a message in the module context. * Basic implementation either forward directly to onProcessModuleMessage * or queue the message in the coroutine message queue (when a synchronous * messaging coroutine is started) for later dispatching. */ virtual void onReceiveModuleMessage(IModuleProxy *senderModuleProxy, const CMessage &message) =0; /** Called internally by basic module imp to process the message by * application code. */ // virtual void onProcessModuleMessage(IModuleProxy *senderModuleProxy, const CMessage &message) =0; /** Called by a socket to inform this module that the security * data attached to a proxy have changed. */ // virtual void onModuleSecurityChange(IModuleProxy *moduleProxy) =0; /** Do a module operation invocation. * Caller MUST be in a module task to call this method. * The call is blocking until receptions of the operation * result message (or detection of the dest module module is down) */ virtual void invokeModuleOperation(IModuleProxy *destModule, const NLNET::CMessage &opMsg, NLNET::CMessage &resultMsg) throw (EInvokeFailed) =0; //@} //@name Callback from module sockets management //@{ enum TModuleSocketEvent { mse_plugged, mse_before_unplug, mse_unplugged, }; virtual void onModuleSocketEvent(IModuleSocket *moduleSocket, TModuleSocketEvent eventType) =0; /// Called just after a module as been effectively plugged into a socket //@} //@{ //@name internal method, should not be used by client code virtual void _onModuleUp(IModuleProxy *removedProxy) =0; virtual void _onModuleDown(IModuleProxy *removedProxy) =0; //@} }; const TModulePtr NullModule; /** Base class for module identification data * Application writer should derive from this * class to create there own security information. * Security information are bound to proxy data * by a secured gateway. */ struct TSecurityData { // for factory system struct TCtorParam { uint8 DataTag; TCtorParam(uint8 dataTag) : DataTag(dataTag) { } }; /// An application defined identifier uint8 DataTag; /// Pointer to next security data item (or NULL) TSecurityData *NextItem; TSecurityData(const TCtorParam ¶ms) : DataTag(params.DataTag), NextItem(NULL) { } virtual ~TSecurityData() { if (NextItem != NULL) delete NextItem; } virtual void serial(NLMISC::CMemStream &s) =0; }; struct TUnknownSecurityData : public TSecurityData { uint8 RealDataTag; std::vector Data; TUnknownSecurityData(uint8 realDataTag, uint32 size) : TSecurityData(TSecurityData::TCtorParam(0xff)), RealDataTag(realDataTag), Data(size) { } void serial(NLMISC::CMemStream &s) { for (uint i=0; i class TModuleTask : public CModuleTask { public: typedef void (T::*TMethodPtr)(); private: T *_Module; TMethodPtr _TaskMethod; // override CTask::run void run() { initMessageQueue(_Module); try { // run the module task command control to module task method (_Module->*_TaskMethod)(); } catch (const NLMISC::Exception &e) { nlwarning("In module task '%s', exception '%e' thrown", typeid(this).name(), e.what()); } catch (...) { nlwarning("In module task '%s', unknown exception thrown", typeid(this).name()); } // finish the dispatch flushMessageQueue(_Module); } public: TModuleTask(T *module, void (T::*taskMethod)()) : CModuleTask(module), _Module(module), _TaskMethod(taskMethod) { } }; // a special macro for easy module task startup #define NLNET_START_MODULE_TASK(className, methodName) \ { \ NLNET::TModuleTask *task = new NLNET::TModuleTask(this, &className::methodName); \ queueModuleTask(task); \ } \ /** Interface for module factory. * Each module MUST provide a factory and * register an instance of the factory in * the module manager. */ class IModuleFactory : public NLMISC::CRefCount { protected: /// The class name of the factored module std::string _ModuleClassName; /// The list of instantiated modules std::set _ModuleInstances; public: /// Constructor, initialize the module class name IModuleFactory(const std::string &moduleClassName); /** Return the class name of the factored module */ virtual const std::string &getModuleClassName() const; /** Return the initialization string helper */ virtual const std::string &getInitStringHelp() const =0; /** Pretty simple method. Module initialization * is done after construction, so there are * no parameter at construction. */ virtual IModule *createModule() =0; /** The module manager call this to delete a module instance.*/ virtual void deleteModule(IModule *module); /** Virtual destructor. * The destructor while unregister the module factory from the * factory registry and ALL module factored * will also be deleted. */ virtual ~IModuleFactory(); /** Called by concrete factory to initialise the factored object */ void registerModuleInFactory(TModulePtr module); }; // const TModuleFactoryPtr NullModuleFactory; template class CModuleFactory : public IModuleFactory { public: CModuleFactory(const std::string &moduleClassName) : IModuleFactory(moduleClassName) {} virtual IModule *createModule() { IModule *module = new moduleClass; registerModuleInFactory(module); return module; } virtual const std::string &getInitStringHelp() const { return moduleClass::getInitStringHelp(); } }; #define NLNET_REGISTER_MODULE_FACTORY(moduleClassName, registrationName) \ class moduleClassName##Factory : public NLNET::CModuleFactory \ { \ public: \ static const std::string &theModuleClassName() \ { \ static const std::string name(registrationName); \ return name; \ } \ \ moduleClassName##Factory() \ : NLNET::CModuleFactory(theModuleClassName()) \ {} \ };\ NLMISC_REGISTER_OBJECT_INDIRECT(NLNET::IModuleFactory, moduleClassName##Factory, std::string, registrationName) //#define NLNET_MAKE_MODULE_FACTORY_TYPENAME(moduleClassName) moduleClassName##Factory #define NLNET_GET_MODULE_FACTORY(moduleClassName) moduleClassName##Factory class CModuleSocket; /** Basic module implementation. * Module implementor should derive from this class * rather than rebuild a complete module from * scratch (from IModule in fact). * This class provide name and module registration, * message dispatching to message handler, * module socket interaction. */ class CModuleBase : public IModule, public NLMISC::ICommandsHandler { // Module manager is our friend coz it need to feed some field here friend class CModuleManager; friend class CModuleTask; typedef std::set TModuleSockets; /// This is the sockets where the module is plugged in TModuleSockets _ModuleSockets; typedef std::list TInterceptors; /// This is the linked list of interceptor TInterceptors _ModuleInterceptors; //@{ //@name Synchronous messaging typedef std::list > TMessageList; /// dynamically allocated list of synchronous message to process TMessageList _SyncMessages; typedef std::list TModuleTasks; /// list of coroutine to run for synchronous messaging (the first in the list is running) TModuleTasks _ModuleTasks; typedef std::vector TInvokeStack; /// stack of server module with pending invocation response TInvokeStack _InvokeStack; /// The current message to process sender IModuleProxy *_CurrentSender; /// The current message to process const NLNET::CMessage *_CurrentMessage; /// True if the current message processing have generated an exception bool _CurrentMessageFailed; /// task for message dispatching CModuleTask *_MessageDispatchTask; //@} virtual void setFactory(IModuleFactory *factory); virtual IModuleFactory *getFactory(); protected: /// Keep track of the module factory IModuleFactory *_ModuleFactory; /// This is the local unique ID assigned to this module. TModuleId _ModuleId; /// This is the module name. std::string _ModuleName; /// This is the fully qualified module name mutable std::string _FullyQualifedModuleName; CModuleBase(); ~CModuleBase(); virtual void registerInterceptor(IModuleInterceptable *interceptor); virtual void unregisterInterceptor(IModuleInterceptable *interceptor); const std::string &getCommandHandlerName() const; TModuleId getModuleId() const; const std::string &getModuleName() const; const std::string &getModuleClassName() const; const std::string &getModuleFullyQualifiedName() const; std::string getModuleManifest() const; /** Called by a socket to receive a message in the module context. * Basic implementation either forward directly to onProcessModuleMessage * or queue the message in the coroutine message queue (when a synchronous * messaging coroutine is started) for later dispatching. */ virtual void onReceiveModuleMessage(IModuleProxy *senderModuleProxy, const CMessage &message); /// The message dispatching task void _receiveModuleMessageTask(); void queueModuleTask(CModuleTask *task); CModuleTask *getActiveModuleTask(); public: /// return the default init string (empty) static const std::string &getInitStringHelp(); /** Search an interceptor in the interceptor list. * By default, the method begin to search at the first interceptor. * If 'previous' is set to a valid interceptor, then the search * continue after it. * the search is done by attempting a dynamic cast for * each interceptor. * If no interceptor match the required class, then NULL is * returned. */ template T *getInterceptor(T *dummy, IModuleInterceptable *previous = NULL) { TInterceptors::iterator it(_ModuleInterceptors.begin()); if (previous != NULL) { // advance up to next the previous while (it != _ModuleInterceptors.end() && *it != previous) ++it; if (it != _ModuleInterceptors.end()) ++it; } while (it != _ModuleInterceptors.end()) { IModuleInterceptable *mi = *it; T *inter = dynamic_cast(mi); if (inter != NULL) { dummy = inter; return inter; } ++it; } dummy = NULL; return NULL; } protected: // Init base module, init module name bool initModule(const TParsedCommandLine &initInfo); void plugModule(IModuleSocket *moduleSocket) throw (EModuleAlreadyPluggedHere); void unplugModule(IModuleSocket *moduleSocket) throw (EModuleNotPluggedHere); void getPluggedSocketList(std::vector &resultList); void invokeModuleOperation(IModuleProxy *destModule, const NLNET::CMessage &opMsg, NLNET::CMessage &resultMsg) throw (EInvokeFailed); void _onModuleUp(IModuleProxy *removedProxy); void _onModuleDown(IModuleProxy *removedProxy); bool _onProcessModuleMessage(IModuleProxy *senderModuleProxy, const CMessage &message); /// base module command table NLMISC_COMMAND_HANDLER_TABLE_BEGIN(CModuleBase) NLMISC_COMMAND_HANDLER_ADD(CModuleBase, dump, "display information about module instance status", "no args") NLMISC_COMMAND_HANDLER_ADD(CModuleBase, plug, "plug the module in a module socket", "") NLMISC_COMMAND_HANDLER_ADD(CModuleBase, unplug, "unplug the module out of a module socket", "") NLMISC_COMMAND_HANDLER_ADD(CModuleBase, sendPing, "send a ping message to another module using the first available route", "") NLMISC_COMMAND_HANDLER_TABLE_END NLMISC_CLASS_COMMAND_DECL(dump); NLMISC_CLASS_COMMAND_DECL(plug); NLMISC_CLASS_COMMAND_DECL(unplug); NLMISC_CLASS_COMMAND_DECL(sendPing); }; class CGatewayRoute; class CModuleProxy : public IModuleProxy { friend class CModuleManager; friend class CStandardGateway; /// The gateway that received the module information IModuleGateway *_Gateway; /// The route to use for reaching the module CGatewayRoute *_Route; /// The module distance (in term of gateway to cross) uint32 _Distance; /// The module local ID TModuleId _ModuleProxyId; /// The module foreign ID, this is the module ID in case of a local proxy TModuleId _ForeignModuleId; /// the module instance in case of local module, NULL otherwise TModulePtr _LocalModule; /// The module class name; NLMISC::TStringId _ModuleClassName; /// The fully qualified module name. NLMISC::TStringId _FullyQualifiedModuleName; /// The module manifest std::string _Manifest; /// the list of security data TSecurityData *_SecurityData; /// Private constructor, only module manager instantiate module proxies CModuleProxy(TModulePtr localModule, TModuleId localModuleId, const std::string &moduleClassName, const std::string &fullyQualifiedModuleName, const std::string &moduleManifest); public: const CGatewayRoute * getRoute() const { return _Route; } /** Return the module ID. Each module has a local unique ID assigned * by the manager during module creation. * This ID is local because it is only valid inside a given process. * When module are declared in another process, they receive a * local ID that is different than the ID in their host process. */ virtual TModuleId getModuleProxyId() const; virtual TModuleId getForeignModuleId() const; uint32 getModuleDistance() const; IModule *getLocalModule() const; CGatewayRoute *getGatewayRoute() const; /** Return the module name. Each module instance must have a unique * name in the host process. * If no mane is given during module creation, the module manager * build a unique name from the module class and a number. * Distant module name are always the FQMN, ie, it is the same as * getModuleFullyQualifiedName() */ virtual const std::string &getModuleName() const; /// Return the module class. virtual const std::string &getModuleClassName() const; /// return the module manifest virtual const std::string &getModuleManifest() const; /** Return the gateways interface by witch this module is accessible. */ virtual IModuleGateway *getModuleGateway() const; /** Send a message to the module. */ virtual void sendModuleMessage(IModule *senderModule, const NLNET::CMessage &message) throw (EModuleNotReachable); virtual const TSecurityData *getFirstSecurityData() const { return _SecurityData; } virtual const TSecurityData *findSecurityData(uint8 dataTag) const; }; /** Utility class to do broadcast with a container of proxy pointer */ template class TBroadcastModuleMessage { void sendMessage(IModule *sender, const PtrContainer &addresseeProxies, const NLNET::CMessage &message) { typename PtrContainer::iterator first(addresseeProxies.begin()), last(addresseeProxies.end()); for (; first != last; ++first) { IModuleProxy *proxy = static_cast(*first); proxy->sendModuleMessage(sender, message); } } }; } // namespace NLNET #endif // NL_FILE_MODULE_H