583 lines
21 KiB
C++
583 lines
21 KiB
C++
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
#ifndef NL_SERVICE_H
|
|
#define NL_SERVICE_H
|
|
|
|
//
|
|
// Includes
|
|
//
|
|
|
|
#include "nel/misc/types_nl.h"
|
|
|
|
#include "nel/misc/config_file.h"
|
|
#include "nel/misc/entity_id.h"
|
|
#include "nel/misc/variable.h"
|
|
#include "nel/misc/command.h"
|
|
#include "nel/misc/entity_id.h"
|
|
#include "nel/misc/cpu_time_stat.h"
|
|
#include "nel/misc/sstring.h"
|
|
|
|
#include "unified_network.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace NLMISC
|
|
{
|
|
class CWindowDisplayer;
|
|
}
|
|
|
|
#if defined(NL_OS_WINDOWS) && defined(_WINDOWS)
|
|
#ifndef WINAPI
|
|
#define WINAPI __stdcall
|
|
#endif
|
|
#ifndef APIENTRY
|
|
#define APIENTRY WINAPI
|
|
#endif
|
|
|
|
struct HINSTANCE__;
|
|
typedef struct HINSTANCE__ *HINSTANCE;
|
|
|
|
typedef char CHAR;
|
|
typedef CHAR *LPSTR;
|
|
#endif
|
|
|
|
namespace NLNET
|
|
{
|
|
|
|
class CCallbackServer;
|
|
class IServiceUpdatable;
|
|
|
|
|
|
//
|
|
// Macros
|
|
//
|
|
|
|
|
|
/**
|
|
* The goal of this macro is to simplify the service creation, it creates the main body function.
|
|
*
|
|
* If you don't want to give a callback array, just put EmptyCallbackArray in the last argument
|
|
*
|
|
* Example:
|
|
*\code
|
|
// Create the Test Service class
|
|
class CTestService : public IService
|
|
{
|
|
public:
|
|
void init () { nlinfo("init()"); }
|
|
bool update () { nlinfo ("update();"); return true; }
|
|
void release () { nlinfo("release()"); }
|
|
};
|
|
// Create the main() function that create a test service instance and execute it.
|
|
// "TS" is the short service name and "test_service" is the long one.
|
|
// The name of the config file is based on the long name!
|
|
// EmptyCallbackArray means that you don't provide right now the callback
|
|
// the last 2 path are where is the config file is (directory) and where to log info (directory)
|
|
NLNET_SERVICE_MAIN(CTestService, "TS", "test_service", 0, EmptyCallbackArray, "", "");
|
|
*\endcode
|
|
*
|
|
* If you want the port to not be auto-assigned by the naming service, set the port to a number different than 0.
|
|
*
|
|
* Args used by service are always in lower case:
|
|
*
|
|
* -A followed by the path where to execute the service (it uses chdir())
|
|
* -B followed by the IP address where the naming service is
|
|
* -C followed by the path where we can find the config file
|
|
* -D followed by the client listening address of the frontend for the login system (FS only)
|
|
* -I to start the service iconified
|
|
* -L followed by the directory where we have to log
|
|
* -N followed by the alias name (used by the admin system)
|
|
* -P followed by the listen port (ListenAddress)
|
|
* -Q to make the service quit immediately after the first update
|
|
* -S followed by the shard Id (sint32) (WS only)
|
|
* -T followed by the IP address where the login service is (WS only)
|
|
* -W followed by the path where to save all shard data (SaveFilesDirectory)
|
|
* -Z[u] to just init the config file then return (used for test), use Zu to not release the service
|
|
*
|
|
*
|
|
*/
|
|
|
|
|
|
#if defined(NL_OS_WINDOWS) && defined(_WINDOWS)
|
|
#define NLNET_SERVICE_MAIN(__ServiceClassName, __ServiceShortName, __ServiceLongName, __ServicePort, __ServiceCallbackArray, __ConfigDir, __LogDir) \
|
|
\
|
|
int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) \
|
|
{ \
|
|
NLMISC::CApplicationContext serviceContext; \
|
|
__ServiceClassName *scn = new __ServiceClassName; \
|
|
scn->setArgs (lpCmdLine); \
|
|
createDebug(NULL,!scn->haveLongArg("nolog"));\
|
|
scn->setCallbackArray (__ServiceCallbackArray, sizeof(__ServiceCallbackArray)/sizeof(__ServiceCallbackArray[0])); \
|
|
sint retval = scn->main (__ServiceShortName, __ServiceLongName, __ServicePort, __ConfigDir, __LogDir, __DATE__" "__TIME__); \
|
|
delete scn; \
|
|
return retval; \
|
|
}
|
|
|
|
#else
|
|
#define NLNET_SERVICE_MAIN(__ServiceClassName, __ServiceShortName, __ServiceLongName, __ServicePort, __ServiceCallbackArray, __ConfigDir, __LogDir) \
|
|
\
|
|
int main(int argc, const char **argv) \
|
|
{ \
|
|
NLMISC::CApplicationContext serviceContext; \
|
|
__ServiceClassName *scn = new __ServiceClassName; \
|
|
scn->setArgs (argc, argv); \
|
|
createDebug(NULL,!scn->haveLongArg("nolog"));\
|
|
scn->setCallbackArray (__ServiceCallbackArray, sizeof(__ServiceCallbackArray)/sizeof(__ServiceCallbackArray[0])); \
|
|
sint retval = scn->main (__ServiceShortName, __ServiceLongName, __ServicePort, __ConfigDir, __LogDir, __DATE__" "__TIME__); \
|
|
delete scn; \
|
|
return retval; \
|
|
}
|
|
|
|
#endif
|
|
|
|
#define DEFAULT_SHARD_ID 666
|
|
|
|
|
|
//
|
|
// Typedefs
|
|
//
|
|
|
|
//typedef uint16 TServiceId;
|
|
|
|
/// Callback where you can return true for direct clearance, or false for later clearance.
|
|
typedef bool (*TRequestClosureClearanceCallback) ();
|
|
|
|
//
|
|
// Variables provided to application and unused in the NeL library itself.
|
|
//
|
|
|
|
extern TUnifiedCallbackItem EmptyCallbackArray[1];
|
|
|
|
|
|
//
|
|
// Classes
|
|
//
|
|
|
|
/**
|
|
* Base class for all network services.
|
|
* You must inherite from this class to create your own service. You must not
|
|
* create ctor and dtor but implement init() and release() methods.
|
|
* You have to create a global callback array called CallbackArray.
|
|
*
|
|
* \ref service_howto
|
|
*
|
|
* Temporary command line arguments :
|
|
* \li -n<AliasName>
|
|
*
|
|
* \author Vianney Lecroart
|
|
* \author Olivier Cado
|
|
* \author Nevrax France
|
|
* \date 2000
|
|
*/
|
|
class IService
|
|
{
|
|
public:
|
|
|
|
/// \name User overload methods. These methods can be overload by the user do handle init, update and release operation.
|
|
// @{
|
|
|
|
/** Called before the displayer is created, no displayer or network connection are built.
|
|
Use this callback to check some args and perform some command line based stuff */
|
|
virtual void commandStart () {}
|
|
|
|
/// Initializes the service (must be called before the first call to update())
|
|
virtual void init () {}
|
|
|
|
/// This function is called every "frame" (you must call init() before). It returns false if the service is stopped.
|
|
virtual bool update () { return true; }
|
|
|
|
/// Finalization. Release the service. For example, this function frees all allocations made in the init() function.
|
|
virtual void release () {}
|
|
|
|
//@}
|
|
|
|
|
|
/// \name get methods. These methods provide a way to read internal service variables.
|
|
// @{
|
|
|
|
/// Returns the instance of the service to access to methods/variables class
|
|
static IService *getInstance ();
|
|
|
|
/// Returns true if the service singleton has been initialized
|
|
static bool isServiceInitialized() { return _Instance != NULL; }
|
|
|
|
/// Returns the current service short name (ie: TS)
|
|
const std::string &getServiceShortName () const { return _ShortName; }
|
|
|
|
/// Returns the current service long name (ie: test_serivce)
|
|
const std::string &getServiceLongName () const { return _LongName; }
|
|
|
|
/// Returns the current service alias name setted by AES
|
|
const std::string &getServiceAliasName () const { return _AliasName; }
|
|
|
|
/// Returns the current service unified name that is alias/short-id or short-id if alias is empty
|
|
std::string getServiceUnifiedName () const;
|
|
|
|
/// Returns the service identifier
|
|
TServiceId getServiceId () const { return _SId; }
|
|
|
|
/// Return the host name of the host machine
|
|
const std::string &getHostName() const { return _HostName; }
|
|
|
|
/// Returns the status
|
|
sint getExitStatus () const { return _ExitStatus; }
|
|
|
|
/// Returns the date of launch of the service. Unit: see CTime::getSecondsSince1970()
|
|
uint32 getLaunchingDate () const;
|
|
|
|
/// Return true if this service don't use the NS (naming service)
|
|
bool getDontUseNS() const { return _DontUseNS; }
|
|
|
|
/// Return true if this service don't use the AES (admin executor service)
|
|
bool getDontUseAES() const { return _DontUseAES; }
|
|
|
|
/// Returns arguments of the program pass from the user to the program using parameters (ie: "myprog param1 param2")
|
|
const NLMISC::CVectorSString &getArgs () const { return _Args; }
|
|
|
|
/// Returns true if the argument if present in the command line (ie: haveArg('p') will return true if -p is in the command line)
|
|
bool haveArg (char argName) const;
|
|
|
|
/** Returns the parameter linked to an option
|
|
* getArg('p') will return toto if -ptoto is in the command line
|
|
* getArg('p') will return C:\Documents and Settings\toto.tmp if -p"C:\Documents and Settings\toto.tmp" is in the command line
|
|
* It'll thrown an Exception if the argName is not found
|
|
*/
|
|
std::string getArg (char argName) const;
|
|
|
|
/// return true if named long arg is present on the commandline
|
|
/// eg haveLongArg("toto") returns true if "--toto" or "--toto=xxx" can be found on commandline
|
|
bool haveLongArg (const char* argName) const;
|
|
|
|
/// returns the value associated with the given named argument
|
|
/// both "--toto=xxx" and "--toto xxx" are acceptable
|
|
/// quotes round arguments are stripped
|
|
std::string getLongArg (const char* argName) const;
|
|
|
|
/// Returns an uniq id for an entities on this service.
|
|
/*uint64 getEntityId (uint8 type)
|
|
{
|
|
return NLMISC::CEntityId::getNewEntityId( type ).getRawId();
|
|
}*/
|
|
|
|
/// Returns the recording state (don't needed if you use layer5)
|
|
CCallbackNetBase::TRecordingState getRecordingState() const { return _RecordingState; }
|
|
|
|
|
|
//@}
|
|
|
|
|
|
/// \name set methods. These methods provide a way to modify internal service variables.
|
|
// @{
|
|
|
|
/** Sets the status of the service, this status is return to the application. EXIT_SUCCESS is the default status
|
|
* You can set it to EXIT_FAILURE or any value you want. It's useful when you use the service in a script and you
|
|
* want to know the return value of the application to do the appropriate things.
|
|
*/
|
|
void setExitStatus (sint exitStatus) { _ExitStatus = exitStatus; }
|
|
|
|
/** Call this function if you want the service quits next loop. The code will be returned outside of the application.
|
|
* \warning If you set the code to 0, it ll NOT exit the service */
|
|
void exit (sint code = 0x10);
|
|
|
|
/** Selects timeout value in seconds for each update. You are absolutely certain that your update()
|
|
* function will not be called before this amount of time you set.
|
|
* If you set the update timeout value higher than 0, all messages in queues will be process until the time greater than the timeout user update().
|
|
* If you set the update timeout value to 0, all messages in queues will be process one time before calling the user update().
|
|
*
|
|
* The default value is 100 (100ms) or the value found in the config file (UpdateTimeout)
|
|
*/
|
|
void setUpdateTimeout (NLMISC::TTime timeout) { /*if (timeout>1.0) nlerror ("IServer::setUpdateTimeout is now a double in SECOND and not ms");*/ _UpdateTimeout = timeout; }
|
|
|
|
//@}
|
|
|
|
//@{
|
|
//@name Service status management methods
|
|
/// Push a new status on the status stack.
|
|
void setCurrentStatus(const std::string &status);
|
|
/// Remove a status from the status stack. If this status is at top of stack, the next status become the current status
|
|
void clearCurrentStatus(const std::string &status);
|
|
/// Add a tag in the status string
|
|
void addStatusTag(const std::string &statusTag);
|
|
/// Remove a tag from the status string
|
|
void removeStatusTag(const std::string &statusTag);
|
|
/// Get the current status with attached tags
|
|
std::string getFullStatus() const;
|
|
//@}
|
|
|
|
/// \name variables. These variables can be read/modified by the user.
|
|
// @{
|
|
|
|
NLMISC::CConfigFile ConfigFile;
|
|
|
|
// use to display result of command (on file and windows displayer) **without** filter
|
|
NLMISC::CLog CommandLog;
|
|
|
|
//@}
|
|
|
|
|
|
/// \name private methods. These methods are used by internal system but can't be put in private, don't use them.
|
|
// @{
|
|
|
|
/// This main is called by the macro (service5 says if we have to use layer5 or not)
|
|
sint main (const char *serviceShortName, const char *serviceLongName, uint16 servicePort, const char *configDir, const char *logDir, const char *compilationDate);
|
|
|
|
/// Sets the command line and init _Args variable. You must call this before calling main()
|
|
void setArgs (int argc, const char **argv);
|
|
|
|
/// Sets the command line and init _Args variable. You must call this before calling main()
|
|
void setArgs (const char *args);
|
|
|
|
/// Sets the default callback array given from the macro
|
|
void setCallbackArray (TUnifiedCallbackItem *array, uint nbelem) { _CallbackArray = array; _CallbackArraySize = nbelem; }
|
|
|
|
/// Require to reset the hierarchical timer
|
|
void requireResetMeasures();
|
|
|
|
/// Ctor. You must not inherit ctor but overload init() function
|
|
IService ();
|
|
|
|
/// Dtor. You must not inherit dtor but overload release() function
|
|
virtual ~IService ();
|
|
|
|
//@}
|
|
|
|
//@{
|
|
/// \name Updatable are object that require an update at each service loop
|
|
/// Register an updatable interface
|
|
void registerUpdatable(IServiceUpdatable *updatable);
|
|
/// Unregister an updatable interface
|
|
void unregisterUpdatable(IServiceUpdatable *updatable);
|
|
//@}
|
|
|
|
/// The window displayer instance
|
|
NLMISC::CWindowDisplayer *WindowDisplayer;
|
|
|
|
/// Directory where to store files that the services will write but are the same for all shard instance (for example: packet_sheets)
|
|
/// Use .toString() to access to the value
|
|
NLMISC::CVariable<std::string> WriteFilesDirectory;
|
|
|
|
/// Directory where to store files that the services will write during the exploitation of the game (for example: player backup, string cache)
|
|
/// Use .toString() to access to the value
|
|
NLMISC::CVariable<std::string> SaveFilesDirectory;
|
|
|
|
/// If true (default), the provided SaveFilesDirectory will be converted to a full path (ex: "saves" -> "/home/dir/saves")
|
|
NLMISC::CVariable<bool> ConvertSavesFilesDirectoryToFullPath;
|
|
|
|
/** You can provide a callback interface (only one) that will be called if any of the directory variables
|
|
* (WriteFilesDirectory, SaveFilesDirectory, ConfigDirectory, LogDirectory, RunningDirectory) is changed
|
|
* (also called for the first setting read from the .cfg file). Default is NULL.
|
|
*/
|
|
void setDirectoryChangeCallback( NLMISC::IVariableChangedCallback *cbi ) { _DirectoryChangedCBI = cbi; }
|
|
|
|
void setVersion (const std::string &version) { Version = version; }
|
|
|
|
uint16 getPort() { return ListeningPort; }
|
|
|
|
// Warning: can take a moment to be received from the WS. The default value (when not received yet) is DEFAULT_SHARD_ID.
|
|
uint32 getShardId() const { return _ShardId; }
|
|
|
|
const NLMISC::CCPUTimeStat& getCPUUsageStats() const { return _CPUUsageStats; }
|
|
|
|
/// Allow the service to return a status string with important value
|
|
virtual std::string getServiceStatusString() const;
|
|
|
|
/**
|
|
* If your service needs a delay when it is asked to quit, provide a callback here (optional).
|
|
* Then, when the service will be asked to quit, this callback will be called. Then you can
|
|
* either return true to allow immediate closure, or false to delay the closure. The closure
|
|
* will then happen after you call clearForClosure().
|
|
*
|
|
* If you don't provide a callback here, or if you call with NULL, the service will exit
|
|
* immediately when asked to quit.
|
|
*/
|
|
void setClosureClearanceCallback( TRequestClosureClearanceCallback cb ) { _RequestClosureClearanceCallback = cb; }
|
|
|
|
/**
|
|
* If using clearance for closure (see setClosureClearanceCallback()), you can call this method
|
|
* to allow the service to quit. If calling it while your callback has not be called yet, the
|
|
* callback will be bypassed and the service will quit immediately when asked to quit. If calling it
|
|
* after you callback was called (and you returned false), the service will quit shortly.
|
|
*/
|
|
void clearForClosure() { _ClosureClearanceStatus = CCClearedForClosure; }
|
|
|
|
/** Set the shard id (by the user code), when known before IService receives it by the WS).
|
|
* If a non-default value is already set and different than shardId => nlerror.
|
|
* If later IService receives a different value from the WS => nlerror.
|
|
*/
|
|
void anticipateShardId( uint32 shardId );
|
|
|
|
private:
|
|
|
|
/// \name methods. These methods are used by internal system.
|
|
// @{
|
|
|
|
/// Changes the recording state (use if you know what you are doing)
|
|
void setRecordingState( CCallbackNetBase::TRecordingState rec ) { _RecordingState = rec; }
|
|
|
|
/** Set the shard id (received from the WS). See also anticipateShardId().
|
|
*/
|
|
void setShardId( uint32 shardId );
|
|
|
|
//@}
|
|
|
|
/// \name variables. These variables are used by the internal system.
|
|
// @{
|
|
|
|
/// Array of arguments pass from the command line
|
|
NLMISC::CVectorSString _Args;
|
|
|
|
/// Host name of the host machine that run the service
|
|
std::string _HostName;
|
|
/// Listening port of this service
|
|
NLMISC::CVariable<uint16> ListeningPort;
|
|
|
|
/// Recording state
|
|
CCallbackNetBase::TRecordingState _RecordingState;
|
|
|
|
/// Current service name sets by the actual service when declaring NLNET_SERVICE_MAIN
|
|
std::string _ShortName; // ie: "NS"
|
|
std::string _LongName; // ie: "naming_service"
|
|
std::string _AliasName; // this name is initialized by the admin executor service via the args
|
|
|
|
/// Instance of this service (singleton)
|
|
static IService *_Instance;
|
|
|
|
/// Select timeout value in milliseconds between to call of user update()
|
|
NLMISC::TTime _UpdateTimeout;
|
|
|
|
/// the service id of this sevice
|
|
TServiceId _SId;
|
|
|
|
/// the exit status of this service (the status is returned by the service at the release time)
|
|
sint _ExitStatus;
|
|
|
|
/// true if the service initialisation is passed
|
|
bool _Initialized;
|
|
|
|
/// The directory where the configfile is
|
|
NLMISC::CVariable<std::string> ConfigDirectory;
|
|
|
|
/// The directory where the logfiles are
|
|
NLMISC::CVariable<std::string> LogDirectory;
|
|
|
|
/// The directory where the service is running
|
|
NLMISC::CVariable<std::string> RunningDirectory;
|
|
|
|
NLMISC::CVariable<std::string> Version;
|
|
|
|
TUnifiedCallbackItem *_CallbackArray;
|
|
uint _CallbackArraySize;
|
|
|
|
/// true if the service don't use the naming service
|
|
bool _DontUseNS;
|
|
/// true if the service don't use the admin executor service
|
|
bool _DontUseAES;
|
|
|
|
/// Require to reset the hierarchical timer
|
|
bool _ResetMeasures;
|
|
|
|
/// Shard Id
|
|
uint32 _ShardId;
|
|
|
|
/// CPU usage stats
|
|
NLMISC::CCPUTimeStat _CPUUsageStats;
|
|
|
|
/// Registered updatable interface
|
|
std::set<IServiceUpdatable*> _Updatables;
|
|
|
|
//@{
|
|
//@name Service running status management
|
|
/// The status stack is used to display the most recent set status.
|
|
std::vector<std::string> _ServiceStatusStack;
|
|
/// The status tags. All added tags are displayed.
|
|
std::set<std::string> _ServiveStatusTags;
|
|
//@}
|
|
|
|
enum TClosureClearanceStatus { CCMustRequestClearance, CCWaitingForClearance, CCClearedForClosure, CCCallbackThenClose=256 };
|
|
|
|
/// Closure clearance state (either CCMustRequestClearance, CCWaitingForClearance, CCClearedForClosure or CCCallbackThenClose + any other as a backup value)
|
|
uint _ClosureClearanceStatus;
|
|
|
|
/// Closure clearance callback (NULL if no closure clearance required)
|
|
TRequestClosureClearanceCallback _RequestClosureClearanceCallback;
|
|
|
|
/// Directory changed callback
|
|
NLMISC::IVariableChangedCallback* _DirectoryChangedCBI;
|
|
|
|
friend void serviceGetView (uint32 rid, const std::string &rawvarpath, std::vector<std::string> &vara, std::vector<std::string> &vala);
|
|
friend void cbAESConnection (const std::string &serviceName, TServiceId sid, void *arg);
|
|
friend struct nel_serviceInfoClass;
|
|
friend struct nel_getWinDisplayerInfoClass;
|
|
friend void cbDirectoryChanged (NLMISC::IVariable &var);
|
|
friend void cbReceiveShardId (NLNET::CMessage& msgin, const std::string &serviceName, TServiceId serviceId);
|
|
|
|
NLMISC_CATEGORISED_DYNVARIABLE_FRIEND(nel, State);
|
|
};
|
|
|
|
|
|
/** Interface class for object that need an update call during each service loop.
|
|
*/
|
|
class IServiceUpdatable
|
|
{
|
|
public:
|
|
IServiceUpdatable()
|
|
{
|
|
if (IService::isServiceInitialized())
|
|
{
|
|
IService::getInstance()->registerUpdatable(this);
|
|
}
|
|
else
|
|
{
|
|
nlwarning("IServiceUpdatable : IService is not initialized, IUpdatable will not be called");
|
|
}
|
|
}
|
|
virtual ~IServiceUpdatable()
|
|
{
|
|
if (IService::isServiceInitialized())
|
|
{
|
|
IService *service = IService::getInstance();
|
|
|
|
service->unregisterUpdatable(this);
|
|
}
|
|
}
|
|
|
|
/// implemente this virtual in you derived class
|
|
virtual void serviceLoopUpdate() =0;
|
|
};
|
|
|
|
|
|
inline IService *IService::getInstance()
|
|
{
|
|
if (_Instance == NULL)
|
|
{
|
|
/* the nel context MUST be initialised */
|
|
nlassertex(NLMISC::INelContext::isContextInitialised(), ("You are trying to access a safe singleton without having initialized a NeL context. The simplest correction is to add 'NLMISC::CApplicationContext myApplicationContext;' at the very beginning of your application."));
|
|
// try to retrieve the safe singleton pointer
|
|
_Instance = reinterpret_cast<IService*>(NLMISC::INelContext::getInstance().getSingletonPointer("IService"));
|
|
}
|
|
return _Instance;
|
|
}
|
|
|
|
|
|
|
|
}; // NLNET
|
|
|
|
#endif // NL_SERVICE_H
|
|
|
|
/* End of service.h */
|