// Ryzom - 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 .
// net
#include
#include
#include
#include
#include
#include
#include "tick_proxy.h"
#include
using namespace std;
using namespace NLMISC;
using namespace NLNET;
// game time
TGameTime CTickProxy::_GameTime = 0;
// game time step
TGameTime CTickProxy::_GameTimeStep = 0;
// game cycle
TGameCycle CTickProxy::_GameCycle = 0;
TAccurateTime CTickProxy::_BeginOfTickTime = 0;
uint CTickProxy::_NbTocked = 0;
std::vector CTickProxy::_Services;
std::vector CTickProxy::_TockedServices;
TServiceId CTickProxy::_MasterTickService(0);
TTickTockState CTickProxy::State = ExpectingMasterTick;
CMirrorGameCycleTimeMeasure CTickProxy::TimeMeasures;
extern NLMISC::CLightMemDisplayer RecentHistory; // get the one in tick_event_handler.obj
extern NLMISC::CLog _QuickLog; // same
string WaitingForServices;
// user callbacks
void (*onTick)() = NULL;
void (*onSync)() = NULL;
extern void cbDoNextTask();
// In the Tick Proxy, set TotalSpeedLoop to the duration of a whole tick cycle on the machine
// (between master send tick and the end of the process after the last local tock received)
// For the Mirror Service, it's between state 1 and state 6 of the automaton (see ms_automaton.cpp).
extern CVariable TotalSpeedLoop;
uint64 getPerfTime()
{
double time = CTime::ticksToSecond( CTime::getPerformanceTime() );
return (uint64)(time * 1000.0);
}
/*
* Receive tick subscription from client service
*/
static void cbRegisterToTickSystem(CMessage& msgin, const std::string &serviceName, TServiceId serviceId)
{
CTickProxy::addService( serviceId );
/*if ( CTickProxy::alreadySyncd() )
CTickProxy::sendSyncToClient( serviceId );*/
cbDoNextTask();
}
//-----------------------------------------------
// cbRegistered
//
//-----------------------------------------------
static void cbSyncFromMaster(CMessage& msgin, const std::string &serviceName, TServiceId serviceId)
{
TGameTime gameTime = 0;
msgin.serial( gameTime );
CTickProxy::setGameTime( gameTime );
TGameTime gameTimeStep = 0;
msgin.serial( gameTimeStep );
CTickProxy::setGameTimeStep( gameTimeStep );
TGameCycle gameCycle = 0;
msgin.serial( gameCycle );
CTickProxy::setGameCycle( gameCycle );
//nldebug( "TCK-%u: Master Sync", gameCycle );
//time_t t; time( &t );
_QuickLog.displayNL( "%"NL_I64"u: TCK-%u: Master Sync", getPerfTime() /*IDisplayer::dateToHumanString( t )*/, gameCycle );
// user callback
onSync();
} // cbRegistered //
//-----------------------------------------------
// cbTick
//
//-----------------------------------------------
static void cbTickFromMaster(CMessage& msgin, const std::string &serviceName, TServiceId serviceId)
{
CTickProxy::masterTickUpdate( serviceId );
} // cbTick //
//-----------------------------------------------
// cbStepAndTick
//
//-----------------------------------------------
static void cbStepAndTick(CMessage& msgin, const std::string &serviceName, TServiceId serviceId)
{
TGameTime gameTimeStep = 0;
msgin.serial( gameTimeStep );
CTickProxy::setGameTimeStep( gameTimeStep );
CTickProxy::masterTickUpdate( serviceId );
} // cbStepAndTick //
// From a client service
void cbTock( CMessage& msgin, const string& serviceName, TServiceId serviceId )
{
nlassert( CTickProxy::State == ExpectingLocalTocks );
CTickProxy::receiveTockFromClient( msgin, serviceId );
}
//-----------------------------------------------
// cbDisplayTime
//
//-----------------------------------------------
static void cbDisplayTime( CMessage& msgin, const string &serviceName, TServiceId serviceId )
{
nlinfo("Time in the service : %f", (double)CTickProxy::getGameTime());
} // cbDisplayTime //
// array of callback items
TUnifiedCallbackItem cbTickProxyArray[] =
{
{ "REGISTER", cbRegisterToTickSystem },
{ "REGISTERED", cbSyncFromMaster },
{ "TICK", cbTickFromMaster },
{ "TOCK", cbTock },
{ "STEP_TICK", cbStepAndTick },
{ "DISPLAY_TIME", cbDisplayTime },
};
void CTickProxy::addService( TServiceId serviceId )
{
bool isFirstService = _Services.empty();
_Services.push_back( serviceId );
// Send sync
if ( CTickProxy::alreadySyncd() )
CTickProxy::sendSyncToClient( serviceId );
if ( State == ExpectingLocalTocks )
{
// Simulate a first tock
CMessage msgin( "", false );
receiveTockFromClient( msgin, serviceId, false );
}
}
/*
* Supports any service id, even one not added before (ignored then)
*/
void CTickProxy::removeService( TServiceId serviceId )
{
vector::iterator it = find( _Services.begin(), _Services.end(), serviceId );
if ( it == _Services.end() )
return; // ignore a service not registered
_Services.erase( it );
// Simulate Tock from leaving service if needed
if ( State == ExpectingLocalTocks )
{
if ( find( _TockedServices.begin(), _TockedServices.end(), serviceId ) != _TockedServices.end() )
{
// The service already tocked and we are still in this state, it means we're waiting for another service to tock
nlassert( _NbTocked != 0 );
--_NbTocked; // decrement because _Service.size() is being decremented
// If other tocks are coming, they will call doNextTask. However, it is possible that no
// other tock is expected, if the tock did not triggered a master tock (changing State).
if ( _NbTocked == _Services.size() )
cbDoNextTask();
else
nldebug( "TCK- Service %hu already tocked, waiting for %u other tocks", serviceId.get(), _Services.size() - _NbTocked );
}
else
{
// The service didn't tock yet, interpret its quitting as a tock
cbDoNextTask();
}
}
}
void CTickProxy::receiveTockFromClient( CMessage& msgin, TServiceId senderId, bool real )
{
// Receive measures of client service
TimeMeasures.ServiceMeasures.push_back( CServiceGameCycleTimeMeasure() );
CServiceGameCycleTimeMeasure& stm = TimeMeasures.ServiceMeasures.back();
stm.ClientServiceId = senderId;
if ( real )
{
stm.ServiceMeasure[TickTockInterval] = accTimeToMs( getAccurateTime() - _BeginOfTickTime );
msgin.serial( stm.ServiceMeasure[TickUpdateDuration] );
msgin.serial( stm.ServiceMeasure[PrevProcessMirrorUpdateDuration] );
msgin.serial( stm.ServiceMeasure[PrevReceiveMsgsViaMirrorDuration] );
msgin.serial( stm.ServiceMeasure[PrevTotalGameCycleDuration] );
}
else
{
for ( uint i=0; i!=NbServiceTimeMeasureTypes; ++i )
stm.ServiceMeasure[i] = 0;
}
//nldebug( "TCK-%u: %hu tocking", getGameCycle(), senderId );
//time_t t; time( &t );
_QuickLog.displayNL( "%"NL_I64"u: TCK-%u: %hu tocking", getPerfTime() /*IDisplayer::dateToHumanString( t )*/, getGameCycle(), senderId.get() );
++_NbTocked;
_TockedServices.push_back( senderId );
cbDoNextTask();
}
void CTickProxy::masterTickUpdate( TServiceId serviceId )
{
H_AUTO(masterTickUpdate);
// increment the time and the number of cycles
_GameTime += (TGameTime)_GameTimeStep;
_GameCycle++;
CTickEventHandler::setGameCycle( _GameCycle ); // set it for shared code
//nldebug( "TCK-%u: Master Tick", getGameCycle() );
//time_t t; time( &t );
_QuickLog.displayNL( "%"NL_I64"u: TCK-%u: Master Tick", getPerfTime() /*IDisplayer::dateToHumanString( t )*/, getGameCycle() );
//nldebug( "--GC-%u-->", _GameCycle );
TimeMeasures.ServiceMeasures.clear();
_BeginOfTickTime = getAccurateTime();
{
H_AUTO(TickUpdate);
onTick();
}
}
void CTickProxy::sendSyncToClient( TServiceId serviceId )
{
CMessage msgout( "REGISTERED" );
msgout.serial( _GameTime );
msgout.serial( _GameTimeStep );
msgout.serial( _GameCycle );
CUnifiedNetwork::getInstance()->send( serviceId, msgout );
//nldebug( "TCK-%u: Sync %hu", getGameCycle(), serviceId );
//time_t t; time( &t );
_QuickLog.displayNL( "%"NL_I64"u: TCK-%u: Sync %hu", getPerfTime() /*IDisplayer::dateToHumanString( t )*/, getGameCycle(), serviceId.get() );
}
void CTickProxy::sendSyncs()
{
vector::const_iterator its;
for ( its=_Services.begin(); its!=_Services.end(); ++its )
{
CMessage msgout( "REGISTERED" );
msgout.serial( _GameTime );
msgout.serial( _GameTimeStep );
msgout.serial( _GameCycle );
CUnifiedNetwork::getInstance()->send( (*its), msgout );
//nldebug( "TCK-%u: Sync %hu", getGameCycle(), *its );
//time_t t; time( &t );
_QuickLog.displayNL( "%"NL_I64"u: TCK-%u: Sync %hu", getPerfTime() /*IDisplayer::dateToHumanString( t )*/, getGameCycle(), its->get());
}
}
void CTickProxy::sendTicks()
{
nlassert( CTickProxy::State == ExpectingMasterTick );
vector::const_iterator its;
for ( its=_Services.begin(); its!=_Services.end(); ++its )
{
CMessage msgout( "TICK" );
CUnifiedNetwork::getInstance()->send( (*its), msgout ); // can produce the warning "Can't find selected connection id 0 to send message to METS because connection is not valid or connected, find a valid connection id", if the service is disconnecting but we aren't aware yet
//nldebug( "TCK-%u: Tick %hu", getGameCycle(), *its );
//time_t t; time( &t );
_QuickLog.displayNL( "%"NL_I64"u: TCK-%u: Tick %hu", getPerfTime() /*IDisplayer::dateToHumanString( t )*/, getGameCycle(), its->get() );
}
// nldebug( "Now expecting local tocks" );
State = ExpectingLocalTocks;
}
void CTickProxy::displayExpectedTocks( /*NLMISC::CDisplayer *log*/ )
{
WaitingForServices.clear();
vector::const_iterator its;
for ( its=_Services.begin(); its!=_Services.end(); ++its )
{
if ( find( _TockedServices.begin(), _TockedServices.end(), (*its) ) == _TockedServices.end() )
{
WaitingForServices += toString( "%hu ", its->get() );
}
}
//log->displayNL( "%s", s.c_str() );
}
void CTickProxy::sendMasterTock()
{
sendTockBack( _MasterTickService );
_NbTocked = 0;
_TockedServices.clear();
State = ExpectingMasterTick;
}
void CTickProxy::setEndOfTick()
{
TotalSpeedLoop = accTimeToMs( getAccurateTime() - _BeginOfTickTime );
}
//-----------------------------------------------
// sendTockBack
//
//-----------------------------------------------
void CTickProxy::sendTockBack( TServiceId serviceId )
{
// send back a tock
CMessage msgout( "TOCK" );
msgout.serial( TimeMeasures );
CUnifiedNetwork::getInstance()->send( serviceId, msgout );
TSockId host;
CCallbackNetBase *cnb;
cnb = CUnifiedNetwork::getInstance()->getNetBase((TServiceId)serviceId, host);
if( cnb )
{
cnb->flush( host );
//nlinfo( "TOCK sent at %.6f", CTime::ticksToSecond( CTime::getPerformanceTime() ) );
}
//nldebug( "TCK-%u: Tocked Master", getGameCycle() );
static uint32 prev = 0;
if ( getGameCycle() == prev )
nlwarning( "Tocked master twice in the same tick!" );
prev = getGameCycle();
//time_t t; time( &t );
_QuickLog.displayNL( "%"NL_I64"u: TCK-%u: Tocked Master", getPerfTime() /*IDisplayer::dateToHumanString( t )*/, getGameCycle() );
}
//-----------------------------------------------
// cbTicksUp
//
//-----------------------------------------------
void cbTPTicksUp(const std::string &serviceName, TServiceId id, void *arg)
{
nlinfo ("TICKS is up, I can start");
// register to tick service
CMessage msgout("REGISTER");
bool tocking = true;
uint16 threshold = 0;
CConfigFile::CVar *cvTocking = IService::getInstance()->ConfigFile.getVarPtr("Tocking");
if(cvTocking)
tocking = (cvTocking->asInt()==0) ? false : true;
if(!tocking)
{
CConfigFile::CVar *cvThreshold = IService::getInstance()->ConfigFile.getVarPtr("Threshold");
if(cvThreshold)
threshold = cvThreshold->asInt();
}
nlinfo("This service %s and has a threshold of %d",(tocking?"tocks":"doesn't tock"), threshold);
msgout.serial( tocking );
msgout.serial( threshold );
CUnifiedNetwork::getInstance()->send( id, msgout );
CTickProxy::setMasterTickService( id );
} // cbTicksUp //
void cbTPTicksDown(const std::string &serviceName, TServiceId id, void *arg)
{
nlinfo ("TICKS is down");
TotalSpeedLoop = -1;
CTickProxy::setMasterTickService( TServiceId(0) );
}
//--------------------------------------------------------------
// init
//
//--------------------------------------------------------------
void CTickProxy::init( void (*updateFunc)(),
void (*syncFunc)() )
{
// set the callbacks
onTick = updateFunc;
onSync = syncFunc;
nlassert( updateFunc );
nlassert( syncFunc );
// Hide tick messages to avoid flooding
DebugLog->addNegativeFilter ("TICK");
DebugLog->addNegativeFilter ("TOCK");
DebugLog->addNegativeFilter ("14+5");
CUnifiedNetwork::getInstance()->addCallbackArray(cbTickProxyArray,sizeof(cbTickProxyArray)/sizeof(cbTickProxyArray[0]));
CUnifiedNetwork::getInstance()->setServiceUpCallback ("TICKS", cbTPTicksUp, NULL);
CUnifiedNetwork::getInstance()->setServiceDownCallback ("TICKS", cbTPTicksDown, NULL);
RecentHistory.setParam( 100 );
_QuickLog.addDisplayer( &RecentHistory, false );
} // init
NLMISC_DYNVARIABLE(string, WaitingForServices, "Services that haven't tocked yet")
{
// we can only read the value
if (get)
{
CTickProxy::displayExpectedTocks();
*pointer = WaitingForServices;
}
}
NLMISC_DYNVARIABLE(NLMISC::TGameCycle, TickGameCycleProxy, "game cycle (in tick)")
{
// we can only read the value
if (get)
*pointer = CTickProxy::getGameCycle ();
}
NLMISC_DYNVARIABLE(NLMISC::TGameTime, TickGameTimeProxy, "game time (in second)")
{
// we can only read the value
if (get)
*pointer = CTickProxy::getGameTime ();
}
NLMISC_COMMAND(displayTickProxyRecentHistory,"Display the history of tick events","")
{
RecentHistory.write( &log );
return true;
}