BigWorld 的逻辑驱动
由于bigworld所提供是一个基础框架,并没有涉及到具体业务,所以其回调逻辑驱动组件只提供了基础的定时器和RPC的Reply这两个功能。RPC的Reply功能在前述的RPC章节中已经介绍过了,因此本章节只介绍定时器的代码实现。
计时器队列
在bigworld里,定时器的实现是通过lib/cstdmf/time_queue.hpp提供的TimerQueueT模板类来完成的。TimerQueueT内部有一个优先队列timeQueue_,队列中的元素是按照触发时间排序的。队列里每个元素都是一个TimerQueueNode,TimerQueueNode包含了触发时间、重复间隔、处理对象和用户数据等信息,
/**
* This class implements a time queue, measured in game ticks. The logic is
* basically stolen from Mercury, but it is intended to be used as a low
* resolution timer. Also, timestamps should be synchronised between servers.
*/
template< class TIME_STAMP >
class TimeQueueT : public TimeQueueBase
{
public:
TimeQueueT();
virtual ~TimeQueueT();
void clear( bool shouldCallOnRelease = true );
/// This is the unit of time used by the time queue
typedef TIME_STAMP TimeStamp;
/// Schedule an event
TimerHandle add( TimeStamp startTime, TimeStamp interval,
TimerHandler* pHandler, void * pUser,
const char * name = "UnnamedTimer" );
/// Process all events older than or equal to now
int process( TimeStamp now );
// 省略很多接口
private:
PriorityQueue timeQueue_;
Node * pProcessingNode_;
TimeStamp lastProcessTime_;
int numCancelled_;
};
这个PriorityQueue的实现非常简单,就是一个基于std::vector的堆实现,堆的每个元素都是一个TimerQueueNode,堆的排序规则是按照TimerQueueNode的触发时间time_来排序的,维持有序的操作就是直接使用std::push_heap和std::pop_heap这两个函数:
/// Comparison object for the priority queue.
class Comparator
{
public:
bool operator()(const Node* a, const Node* b)
{
return a->time() > b->time();
}
};
/**
* This class implements a priority queue. std::priority_queue is not used
* so that access to the underlying container can be gotten.
*/
class PriorityQueue
{
public:
typedef BW::vector< Node * > Container;
typedef typename Container::value_type value_type;
typedef typename Container::size_type size_type;
bool empty() const { return container_.empty(); }
size_type size() const { return container_.size(); }
const value_type & top() const { return container_.front(); }
void push( const value_type & x )
{
container_.push_back( x );
std::push_heap( container_.begin(), container_.end(),
Comparator() );
}
void pop()
{
std::pop_heap( container_.begin(), container_.end(), Comparator() );
container_.pop_back();
}
};
TimeQueueT对外暴露的最重要接口就是add和process, add负责添加一个计时器,process负责处理所有到期的计时器,这两个接口的实现非常直白,都是调用PriorityQueue提供的的push和pop方法:
/**
* This method adds an event to the time queue. If interval is zero,
* the event will happen once and will then be deleted. Otherwise,
* the event will be fired repeatedly.
*
* @param startTime Time of the initial event, in game ticks
* @param interval Number of game ticks between subsequent events
* @param pHandler Object that is to receive the event
* @param pUser User data to be passed with the event.
* @return A handle to the new event.
*/
template <class TIME_STAMP>
TimerHandle TimeQueueT< TIME_STAMP >::add( TimeStamp startTime,
TimeStamp interval, TimerHandler * pHandler, void * pUser,
const char * name )
{
Node * pNode = new Node( *this, startTime, interval, pHandler, pUser, name );
timeQueue_.push( pNode );
pNode->incRef();
return TimerHandle( pNode );
}
返回值是一个TimerHandle对象,这个对象封装了TimeQueueNode对象的指针,外部可以通过这个对象来取消计时器:
/**
* This class is a handle to a timer added to TimeQueue.
*/
class TimerHandle
{
public:
CSTDMF_DLL explicit TimerHandle( TimeQueueNode * pNode = NULL );
CSTDMF_DLL TimerHandle( const TimerHandle & other );
CSTDMF_DLL ~TimerHandle();
CSTDMF_DLL void cancel();
void clearWithoutCancel() { pNode_ = NULL; }
bool isSet() const { return pNode_ != NULL; }
CSTDMF_DLL TimerHandle & operator=( const TimerHandle & other );
friend bool operator==( const TimerHandle & h1, const TimerHandle & h2 );
TimeQueueNode * pNode() const { return pNode_; }
private:
TimeQueueNode * pNode_;
};
这个add接口要求外部提供一个TimerHandler对象和一个pUser指针,这个TimerHandler对象会在计时器到期时被调用,因此TimerHandler必须实现handleTimeout方法。这个方法第一个参数就是add函数返回的TimerHandle对象,第二个参数是用户提供的pUser指针,这样就能支持在同一个函数上通过TimerHandle和pUser来区分不同的计时器实例:
/**
* This is an interface which must be derived from in order to
* receive time queue events.
*/
class TimerHandler
{
public:
TimerHandler() : numTimesRegistered_( 0 ) {}
virtual ~TimerHandler()
{
MF_ASSERT( numTimesRegistered_ == 0 );
};
/**
* This method is called when a timeout expires.
*
* @param handle The handle returned when the event was added.
* @param pUser The user data passed in when the event was added.
*/
virtual void handleTimeout( TimerHandle handle, void * pUser ) = 0;
protected:
virtual void onRelease( TimerHandle /* handle */, void * /* pUser */ ) {}
private:
friend class TimeQueueNode;
void incTimerRegisterCount() { ++numTimesRegistered_; }
void decTimerRegisterCount() { --numTimesRegistered_; }
void release( TimerHandle handle, void * pUser )
{
this->decTimerRegisterCount();
this->onRelease( handle, pUser );
}
int numTimesRegistered_;
};
在执行计时器的添加的时候,会创建一个Node对象,Node对象继承自TimeQueueNode,封装了计时器的触发时间、重复间隔、处理对象和用户数据等信息:
/**
* This class is the base class for the nodes of the time queue.
*/
class TimeQueueNode : public ReferenceCount
{
public:
TimeQueueNode( TimeQueueBase & owner,
TimerHandler * pHandler,
void * pUserData );
void cancel( bool shouldCallOnRelease = true );
void * pUserData() const { return pUserData_; }
bool isCancelled() const { return state_ == STATE_CANCELLED; }
protected:
bool isExecuting() const { return state_ == STATE_EXECUTING; }
/// This enumeration is used to describe the current state of an element on
/// the queue.
enum State
{
STATE_PENDING,
STATE_EXECUTING,
STATE_CANCELLED
};
TimeQueueBase & owner_;
TimerHandler * pHandler_;
void * pUserData_;
State state_;
};
/// This structure represents one event in the time queue.
class Node : public TimeQueueNode
{
public:
Node( TimeQueueBase & owner, TimeStamp startTime, TimeStamp interval,
TimerHandler * pHandler, void * pUser, const char * name );
TIME_STAMP time() const { return time_; }
TIME_STAMP interval() const { return interval_; }
TIME_STAMP & intervalRef() { return interval_; }
TIME_STAMP deliveryTime() const;
void triggerTimer();
void adjustBy( TimeStamp adjustment )
{
time_ += adjustment;
}
const char * name() { return name_; }
private:
TimeStamp time_;
TimeStamp interval_;
const char * name_;
Node( const Node & );
Node & operator=( const Node & );
};
这里的time_字段代表计时器的触发时间,interval_字段代表计时器的重复间隔,如果interval_为0,代表只触发一次。state_字段代表计时器的状态,有三种状态:STATE_PENDING、STATE_EXECUTING和STATE_CANCELLED,分别代表计时器等待触发、正在执行和已取消。新添加的计时器的状态都是STATE_PENDING, 在这个计时器被调度的时候才会标记为STATE_EXECUTING:
/**
* This method triggers the timer assoicated with this node. It also updates
* the state for repeating timers.
*/
template <class TIME_STAMP>
void TimeQueueT< TIME_STAMP >::Node::triggerTimer()
{
if (!this->isCancelled())
{
state_ = STATE_EXECUTING;
#if ENABLE_PROFILER
ScopedProfiler _timerProfiler( name_ );
#endif
pHandler_->handleTimeout( TimerHandle( this ), pUserData_ );
if ((interval_ == 0) && !this->isCancelled())
{
this->cancel();
}
}
// This event could have been cancelled within the callback.
if (!this->isCancelled())
{
time_ += interval_;
state_ = STATE_PENDING;
}
}
在被调度之后,判断这个计时器是否需要重复执行,如果需要,就更新触发时间并调用push重新入队。如果不需要,就通过cancel接口将状态标记为STATE_CANCELLED,等待被清理:
/**
* This method processes the time queue and dispatches events.
* All events with a timestamp earlier than the given one are
* processed.
*
* @param now Process events earlier than or exactly on this.
*
* @return The number of timers that fired.
*/
template <class TIME_STAMP>
int TimeQueueT< TIME_STAMP >::process( TimeStamp now )
{
int numFired = 0;
while ((!timeQueue_.empty()) && (
timeQueue_.top()->time() <= now ||
timeQueue_.top()->isCancelled()))
{
Node * pNode = pProcessingNode_ = timeQueue_.top();
timeQueue_.pop();
if (!pNode->isCancelled())
{
++numFired;
pNode->triggerTimer();
}
if (!pNode->isCancelled())
{
timeQueue_.push( pNode );
}
else
{
pNode->decRef();
MF_ASSERT( numCancelled_ > 0 );
--numCancelled_;
}
}
pProcessingNode_ = NULL;
lastProcessTime_ = now;
return numFired;
}
/**
* This method cancels the timer associated with this node.
*/
inline void TimeQueueNode::cancel( bool shouldCallOnRelease )
{
if (this->isCancelled())
{
return;
}
MF_ASSERT( (state_ == STATE_PENDING) || (state_ == STATE_EXECUTING) );
state_ = STATE_CANCELLED;
if (pHandler_)
{
if (shouldCallOnRelease)
{
pHandler_->release( TimerHandle( this ), pUserData_ );
}
else
{
pHandler_->decTimerRegisterCount();
}
pHandler_ = NULL;
}
owner_.onCancel();
}
由于cancel的时候只是将状态标记为STATE_CANCELLED,并没有立即从队列中移除,为了彻底的释放这个计时器的相关资源,TimeQueueT提供了purgeCancelledNodes来清理所有已取消的计时器节点。这里的清理过程使用了标准库的std::partition算法将已取消的节点与未取消的节点分隔开,然后对于已经被CANCEL的节点执行引用计数的减少操作,这样可以触发这些计时器的析构函数。最后再从数组里删除失效的那些节点,并使用heapify重新构造一下最小堆:
/**
* This method removes all cancelled timers from the priority queue. Generally,
* cancelled timers wait until they have reached the top of the queue before
* being deleted.
*/
template <class TIME_STAMP>
void TimeQueueT< TIME_STAMP >::purgeCancelledNodes()
{
typename PriorityQueue::Container & container = timeQueue_.container();
typename PriorityQueue::Container::iterator newEnd =
std::partition( container.begin(), container.end(),
IsNotCancelled< Node >() );
for (typename PriorityQueue::Container::iterator iter = newEnd;
iter != container.end();
++iter)
{
(*iter)->decRef();
}
size_t numPurgedFull = (container.end() - newEnd);;
MF_ASSERT( numPurgedFull <= INT_MAX );
const int numPurged = static_cast<int>(numPurgedFull);
numCancelled_ -= numPurged;
// numCancelled_ will be 1 when we're in the middle of processing a
// once-off timer.
MF_ASSERT( (numCancelled_ == 0) || (numCancelled_ == 1) );
container.erase( newEnd, container.end() );
timeQueue_.heapify();
}
由于这个函数的复杂度是O(N*Log(N))的,需要避免频繁的执行这个清除操作,因此在TimeQueueT::onCancel方法中只在取消的节点数超过队列一半时才调用purgeCancelledNodes,这个onCancel方法在前面的TimeQueueNode::cancel末尾会被调用到:
/**
* This method is called when a timer has been cancelled.
*/
template <class TIME_STAMP>
void TimeQueueT< TIME_STAMP >::onCancel()
{
++numCancelled_;
// If there are too many cancelled timers in the queue (more than half),
// these are flushed from the queue immediately.
if (numCancelled_ * 2 > int( timeQueue_.size() ))
{
this->purgeCancelledNodes();
}
}
在bigworld里计时器使用的比较广泛,其中最广泛的就是BaseApp里的Base对象和CellApp里的Entity对象。下面我们就来看看Base对象和Entity对象是如何使用计时器的。
Base对象的计时器
每个Base对象都有一个PyTimer对象,用于管理该实体绑定的脚本逻辑定时器,在Base对象上会提供py_addTimer和py_delTimer方法,用于添加和删除脚本逻辑定时器:
class Base: public PyObjectPlus
{
private:
PyTimer pyTimer_;
}
/**
* This method is exposed to scripting. It is used by a base to register a
* timer function to be called with a given period.
*/
PyObject * Base::py_addTimer( PyObject * args )
{
if (isDestroyed_)
{
PyErr_Format( PyExc_TypeError, "Base.addTimer: "
"Entity %d has already been destroyed", id_ );
return NULL;
}
return pyTimer_.addTimer( args );
}
/**
* This method is exposed to scripting. It is used by a base to remove a timer
* from the time queue.
*/
PyObject * Base::py_delTimer( PyObject * args )
{
return pyTimer_.delTimer( args );
}
这个PyTimer对象是一个非常简单的封装,addTimer负责将脚本层的定时器添加到ScriptTimers中,同时delTimer负责从ScriptTimers中删除定时器:
/**
* This method handles timer events associated with the PyTimer.
*/
class PyTimerHandler : public TimerHandler
{
public:
PyTimerHandler( PyTimer & pyTimer ) :
pyTimer_( pyTimer ) {}
private:
// Overrides
virtual void handleTimeout( TimerHandle handle, void * pUser );
virtual void onRelease( TimerHandle handle, void * pUser );
PyTimer & pyTimer_;
};
/**
* This class is used by PyObjects to add, remove and invoke expired
* timers.
*/
class PyTimer
{
public:
PyTimer( PyObject * pyObject, int ownerID );
~PyTimer();
PyObject * addTimer( PyObject * args );
PyObject * delTimer( PyObject * args );
void handleTimeout( TimerHandle handle, void * pUser );
void onTimerReleased( TimerHandle timerHandle );
void backUpTimers( BinaryOStream & stream );
void restoreTimers( BinaryIStream & stream );
void cancelAll();
void ownerID( int ownerID ) { ownerID_ = ownerID; }
private:
friend class PyTimerHandler;
PyTimerHandler timerHandler_;
int ownerID_;
bool isCancelled_;
PyObject * pyObject_;
ScriptTimers * pTimers_;
};
在执行定时器的添加的时候,会调用ScriptTimersUtil::addTimer方法,将定时器添加到ScriptTimers中,同时返回一个整数,代表定时器ID,这个ID会被绑定到脚本层的定时器对象上,用于后续的超时回调和删除操作:
/**
* This method is exposed to scripting. It is used by a PyObject to register a
* timer function to be called with a given period.
*/
PyObject * PyTimer::addTimer( PyObject * args )
{
float initialOffset;
float repeatOffset = 0.f;
int userArg = 0;
if (!PyArg_ParseTuple( args, "f|fi",
&initialOffset, &repeatOffset, &userArg ))
{
return NULL;
}
int id = ScriptTimersUtil::addTimer( &pTimers_, initialOffset, repeatOffset,
userArg, &timerHandler_ );
if (id == 0)
{
PyErr_SetString( PyExc_ValueError, "Unable to add timer" );
return NULL;
}
return PyInt_FromLong( id );
}
回调对象TimerHandler设置为当前PyTimer的成员变量timerHandler_,这个成员变量负责中转ScriptTimers的回调到PyTimer的handleTimeout方法中,在handleTimeout方法中会调用脚本层的onTimer方法:
/**
* This method implements the TimerHandler method. It is called when a timer
* associated with this PyTimer goes off.
*/
void PyTimerHandler::handleTimeout( TimerHandle handle, void * pUser )
{
pyTimer_.handleTimeout( handle, pUser );
}
/**
* This method is called when a timer associated with the PyObject goes off.
*/
void PyTimer::handleTimeout( TimerHandle handle, void * pUser )
{
MF_ASSERT( !isCancelled_ );
int id = ScriptTimersUtil::getIDForHandle( pTimers_, handle );
if (id != 0)
{
// Reference count so that object is not deleted in the middle of
// the call.
Py_INCREF( pyObject_ );
PyObject * pResult = PyObject_CallMethod( pyObject_,
"onTimer", "ik", id, uintptr( pUser ) );
if (pResult == NULL)
{
WARNING_MSG( "PyTimer::handleTimeout(%d): onTimer failed\n",
ownerID_ );
PyErr_Print();
}
else
{
Py_DECREF( pResult );
}
Py_DECREF( pyObject_ );
}
else
{
ERROR_MSG( "PyTimer::handleTimeout: Invalid TimerQueueId\n" );
}
}
执行ScriptTimersUtil::addTimer的时候第一个参数是&pTimers_,在ScriptTimersUtil::addTimer内会判断ppTimers是否为空,如果为空则创建一个ScriptTimers对象,这样做的目的是延迟创建ScriptTimers对象,只有在需要添加定时器时才创建,这样可以节省内存占用:
namespace ScriptTimersUtil
{
/**
* This function is a wrapper to ScriptTimers::addTimer that handles
* ScriptTimers creation.
*/
ScriptTimers::ScriptID addTimer( ScriptTimers ** ppTimers,
float initialOffset, float repeatOffset, int32 userArg,
TimerHandler * pHandler )
{
ScriptTimers *& rpTimers = *ppTimers;
if (rpTimers == NULL)
{
rpTimers = new ScriptTimers;
}
return rpTimers->addTimer( initialOffset, repeatOffset,
userArg, pHandler );
}
}
这个ScriptTimersUtil::addTimer执行完延迟创建的逻辑之后,再调用ScriptTimers::addTimer,这个ScriptTimers才是真正执行定时器管理的地方,内部使用一个Map来存储目前还活跃的定时器,Map的Key是定时器ID,Value是定时器Handle,定时器Handle是一个整数,用于唯一标识一个定时器。
/**
* This class stores a collection of timers that have an associated script id.
*/
class ScriptTimers
{
public:
typedef int32 ScriptID;
static void init( ScriptTimeQueue & timeQueue );
static void fini( ScriptTimeQueue & timeQueue );
ScriptID addTimer( float initialOffset, float repeatOffset, int32 userArg,
TimerHandler * pHandler );
bool delTimer( ScriptID timerID );
void releaseTimer( TimerHandle handle );
void cancelAll();
void writeToStream( BinaryOStream & stream ) const;
void readFromStream( BinaryIStream & stream,
uint32 numTimers, TimerHandler * pHandler );
ScriptID getIDForHandle( TimerHandle handle ) const;
bool isEmpty() const { return map_.empty(); }
private:
typedef BW::map< ScriptID, TimerHandle > Map;
ScriptID getNewID();
Map::const_iterator findTimer( TimerHandle handle ) const;
Map::iterator findTimer( TimerHandle handle );
Map map_;
};
ScriptTimers内并没有提供计时器调度的逻辑,计时器调度的逻辑是在ScriptTimeQueue中实现的,ScriptTimeQueue会根据定时器的过期时间,来调用ScriptTimers中的handleTimeout方法,来处理定时器的过期事件。在进程里会有一个全局静态的ScriptTimeQueue对象g_pTimeQueue,用于管理所有的脚本定时器。因此ScriptTimers::addTimer的逻辑就是往g_pTimeQueue里注册计时器,然后分配一个整数作为计时器的ID,同时将ID与TimerHandle的映射存储在Map里:
/**
* This method adds a timer to the collection. It returns an identifier that
* should be used by ScriptTimers::delTimer.
*/
ScriptTimers::ScriptID ScriptTimers::addTimer( float initialOffset,
float repeatOffset, int32 userArg, TimerHandler * pHandler )
{
if (initialOffset < 0.f)
{
WARNING_MSG( "ScriptTimers::addTimer: Negative timer offset (%f)\n",
initialOffset );
initialOffset = 0.f;
}
MF_ASSERT( g_pTimeQueue );
int hertz = g_pTimeQueue->updateHertz();
int initialTicks =
g_pTimeQueue->time() + uint32( initialOffset * hertz + 0.5f );
int repeatTicks = 0;
if (repeatOffset > 0.f)
{
repeatTicks = uint32( repeatOffset * hertz + 0.5f );
if (repeatTicks < 1)
{
repeatTicks = 1;
}
}
TimerHandle timerHandle = g_pTimeQueue->add(
initialTicks, repeatTicks,
pHandler, reinterpret_cast< void * >( userArg ),
"ScriptTimer" );
if (timerHandle.isSet())
{
int id = this->getNewID();
map_[ id ] = timerHandle;
return id;
}
return 0;
}
注意这里使用调用g_pTimeQueue->add的时候,传入的超时时间与重复间隔都通过updateHertz方法转换为整数,这是因为ScriptTimeQueue内部使用的时间单位是整数,而ScriptTimers内部使用的时间单位是浮点数。这个ScriptTimeQueue继承自TimeQueue,实现非常简单,相当于只增加了updateHertz这个成员变量:
/**
* This TimeQueue subclass encapsulates the queue and time for easy
* ScriptTimers access.
*/
class ScriptTimeQueue : public TimeQueue
{
public:
ScriptTimeQueue( int updateHertz ) :
updateHertz_( updateHertz ) {}
int updateHertz() const { return updateHertz_; }
virtual uint32 time() const = 0;
private:
int updateHertz_;
};
这里的ID生成方式有点特殊,是从1开始递增的,而不是从0开始递增的。这是因为0是一个特殊的ID,表示定时器不存在。同时在递增的时候需要跳过map里已经有的计时器ID,避免活跃的计时器ID被重复使用:
/**
* This method returns an available ScriptID for a new timer.
*/
ScriptTimers::ScriptID ScriptTimers::getNewID()
{
ScriptTimers::ScriptID id = 1;
// Ugly linear search
while (map_.find( id ) != map_.end())
{
++id;
}
return id;
}
/**
* This method cancels the timer with the given id.
*
* @return True if such a timer exists, otherwise false.
*/
bool ScriptTimers::delTimer( ScriptTimers::ScriptID timerID )
{
Map::iterator iter = map_.find( timerID );
if (iter != map_.end())
{
// Take a copy so that the TimerHandle in the map is still set when
// ScriptTimers::releaseTimer is called.
TimerHandle handle = iter->second;
handle.cancel();
// iter->second.cancel();
return true;
}
return false;
}
这里之所以使用整数作为计时器ID是因为cpp与脚本之间只能传递整数,不能传递TimerHandle这个指针。
为了支持迁移的处理,PyTimer还提供了backUpTimers和restoreTimers方法,用于将实体的脚本定时器保存到数据流中,以及从数据流中恢复脚本定时器。这两个方法内部调用了ScriptTimersUtil提供的静态方法来完成实际的读写操作。这样做的好处就是不需要在脚本里处理迁移前后的计时器恢复逻辑:
/**
* This method writes this entity's timers to a backup stream.
*/
void PyTimer::backUpTimers( BinaryOStream & stream )
{
ScriptTimersUtil::writeToStream( pTimers_, stream );
}
/**
* This method restores this entity's timers from a backup stream.
*/
void PyTimer::restoreTimers( BinaryIStream & stream )
{
ScriptTimersUtil::readFromStream( &pTimers_, stream, &timerHandler_ );
}
前面介绍了PyTimer会最终将计时器添加到全局的脚本调度器g_pTimeQueue,它负责管理所有的脚本定时器,其初始化通过静态函数ScriptTimers::init方法来完成:
namespace
{
ScriptTimeQueue * g_pTimeQueue = NULL;
}
// -----------------------------------------------------------------------------
// Section: ScriptTimers
// -----------------------------------------------------------------------------
/**
* This static method sets up the script timers to be able to access the
* ScriptTimeQueue.
*/
void ScriptTimers::init( ScriptTimeQueue & timeQueue )
{
MF_ASSERT( g_pTimeQueue == NULL);
g_pTimeQueue = &timeQueue;
}
这个全局脚本计时器对象的实例会放在EntityApp的成员变量timeQueue_中。这个EntityAppTimeQueue继承自ScriptTimeQueue,实现了time方法,用于返回实体的当前时间:
/**
* This ScriptTimeQueue subclass is specialised for EntityApp's time
*/
class EntityAppTimeQueue : public ScriptTimeQueue
{
public:
EntityAppTimeQueue( int updateHertz, EntityApp & entityApp );
virtual GameTime time() const;
private:
EntityApp & entityApp_;
};
/**
* This class is a common base class for BaseApp and CellApp.
*/
class EntityApp : public ScriptApp
{
public:
EntityApp( Mercury::EventDispatcher & mainDispatcher,
Mercury::NetworkInterface & interface );
virtual ~EntityApp();
virtual bool init( int argc, char * argv[] );
ScriptTimeQueue & timeQueue() { return timeQueue_; }
virtual void onSignalled( int sigNum );
BgTaskManager & bgTaskManager() { return bgTaskManager_; }
protected:
void addWatchers( Watcher & watcher );
void tickStats();
void callTimers();
virtual void onSetStartTime( GameTime oldTime, GameTime newTime );
// Override from ServerApp
virtual void onTickProcessingComplete();
BgTaskManager bgTaskManager_;
private:
EntityAppTimeQueue timeQueue_;
uint32 tickStatsPeriod_;
};
在EntityApp的构造函数中,这个EntityAppTimeQueue实例会被初始化,并通过ScriptTimers::init方法设置到全局指针上:
EntityApp::EntityApp( Mercury::EventDispatcher & mainDispatcher,
Mercury::NetworkInterface & interface ) :
ScriptApp( mainDispatcher, interface ),
timeQueue_( EntityAppConfig::updateHertz(), *this ),
tickStatsPeriod_( 0 ) // Set below
{
ScriptTimers::init( timeQueue_ );
float tickStatsPeriod = BWConfig::get( "tickStatsPeriod", 2.f );
tickStatsPeriod_ =
uint32( tickStatsPeriod * EntityAppConfig::updateHertz() );
}
同时EntityApp还提供了驱动这个全局脚本计时器队列的计时器调度方法callTimers,它会在每个update周期结束时被调用:
/**
* This method calls timers before updatables are run
*/
void EntityApp::onTickProcessingComplete()
{
this->ServerApp::onTickProcessingComplete();
this->callTimers();
}
/**
* This method calls any outstanding timers.
*/
void EntityApp::callTimers()
{
AUTO_SCOPED_PROFILE( "callTimers" );
timeQueue_.process( time_ );
}
在EntityApp上还提供了一个开发期非常有用的方法onSetStartTime,这个方法可以改脚本层的逻辑时间,这样可以非常方便的去测试一些计时器间隔比较大的逻辑,例如每天刷新每周刷新每月刷新相关逻辑:
/**
* This method responds to the game time being adjusted.
*/
void EntityApp::onSetStartTime( GameTime oldTime, GameTime newTime )
{
if (oldTime != newTime)
{
if (!timeQueue_.empty())
{
NOTICE_MSG( "EntityApp::onSetStartTime: Adjusting %d timer%s\n",
timeQueue_.size(), (timeQueue_.size() == 1) ? "" : "s" );
timeQueue_.adjustBy( newTime - oldTime );
}
}
}
这个adjustBy会一路中转到PriorityQueue的adjustBy方法,用于调整所有计时器的时间,这样就可以达到计时器的时间同步:
void PriorityQueue::adjustBy( TimeStamp adjustment )
{
typename Container::iterator iter = container_.begin();
while (iter != container_.end())
{
(*iter)->adjustBy( adjustment );
++iter;
}
}
void Node::adjustBy( TimeStamp adjustment )
{
time_ += adjustment;
}
Entity对象的计时器
Entity对象上的计时器管理方式与前述的Base对象的计时器管理方式非常不一样,并没有在Entity对象上显示的提供计时器相关操作接口,而是通过一个TimerController对象来管理,同时这个TimerController对象会以Controller指针的方式放到Entity对象的pControllers_管理器里:
class Entity : public PyObjectPlus
{
ControllerID addController( ControllerPtr pController, int userArg );
void modController( ControllerPtr pController );
bool delController( ControllerID controllerID,
bool warnOnFailure = true );
bool visitControllers( ControllersVisitor & visitor );
Controllers * pControllers_;
};
class Controllers
{
public:
Controllers();
~Controllers();
void readGhostsFromStream( BinaryIStream & data, Entity * pEntity );
void readRealsFromStream( BinaryIStream & data, Entity * pEntity );
void writeGhostsToStream( BinaryOStream & data );
void writeRealsToStream( BinaryOStream & data );
void createGhost( BinaryIStream & data, Entity * pEntity );
void deleteGhost( BinaryIStream & data, Entity * pEntity );
void updateGhost( BinaryIStream & data );
ControllerID addController( ControllerPtr pController, int userArg,
Entity * pEntity );
bool delController( ControllerID id, Entity * pEntity,
bool warnOnFailure = true );
void modController( ControllerPtr pController, Entity * pEntity );
void startReals();
void stopReals( bool isFinalStop );
PyObject * py_cancel( PyObject * args, Entity * pEntity );
bool visitAll( ControllersVisitor & visitor );
private:
ControllerID nextControllerID();
typedef BW::map< ControllerID, ControllerPtr > Container;
Container container_;
ControllerID lastAllocatedID_;
};
在这个TimerController类里,会提供start和stop方法,用于启动和停止计时器,同时还会提供handleTimeout方法,用于处理计时器超时事件:
class TimerController : public Controller
{
DECLARE_CONTROLLER_TYPE( TimerController )
public:
TimerController( GameTime start = 0, GameTime interval = 0 );
void writeRealToStream( BinaryOStream & stream );
bool readRealFromStream( BinaryIStream & stream );
void handleTimeout();
void onHandlerRelease();
// Controller overrides
virtual void startReal( bool isInitialStart );
virtual void stopReal( bool isFinalStop );
static FactoryFnRet New( float initialOffset, float repeatOffset,
int userArg = 0 );
PY_AUTO_CONTROLLER_FACTORY_DECLARE( TimerController,
ARG( float, OPTARG( float, 0.f, OPTARG( int, 0, END ) ) ) )
private:
/**
* Handler for a timer to go into the global time queue
*/
class Handler : public TimerHandler
{
public:
Handler( TimerController * pController );
void pController( TimerController * pController )
{ pController_ = pController; }
private:
// Overrides from TimerHandler
virtual void handleTimeout( TimerHandle handle, void * pUser );
virtual void onRelease( TimerHandle handle, void * pUser );
TimerController * pController_;
};
Handler * pHandler_;
GameTime start_;
GameTime interval_;
TimerHandle timerHandle_;
};
每个TimerController对象都代表一个脚本层的计时器对象,当脚本层需要创建一个计时器时,都会通过这里暴露的New方法来创建一个TimerController对象,创建的参数就包括初始延迟initialOffset、后续的重复间隔repeatOffset以及用户自定义参数userArg,后面两个参数在脚本层不提供相关值的时候会以默认值来填充:
static FactoryFnRet New( float initialOffset, float repeatOffset,
int userArg = 0 );
PY_AUTO_CONTROLLER_FACTORY_DECLARE( TimerController,
ARG( float, OPTARG( float, 0.f, OPTARG( int, 0, END ) ) ) )
这个TimerController::New在执行的时候,会根据传入的initialOffset和repeatOffset参数,转换为对应的游戏时间单位Ticks,再创建一个对应的TimerController对象,最后返回一个FactoryFnRet对象,包含了创建的TimerController对象指针以及用户自定义参数userArg:
Controller::FactoryFnRet TimerController::New(
float initialOffset, float repeatOffset, int userArg )
{
GameTime repeatTicks = 0;
GameTime initialTicks =
(GameTime)( initialOffset * CellAppConfig::updateHertz() + 0.5f );
if (int(initialTicks) <= 0)
{
// WARNING_MSG( "TimerController::New: "
// "Rounding up initial offset to 1 from %d (initialOffset %f)\n",
// initialTicks, initialOffset );
initialTicks = 1;
}
initialTicks += CellApp::instance().time();
if (repeatOffset > 0.0f)
{
repeatTicks =
(GameTime)(repeatOffset * CellAppConfig::updateHertz() + 0.5f);
if (repeatTicks < 1)
{
WARNING_MSG( "TimerController::New: "
"Rounding up repeatTicks to 1 (repeatOffset %f)\n",
repeatOffset );
repeatTicks = 1;
}
}
return FactoryFnRet(
new TimerController( initialTicks, repeatTicks ), userArg );
}
但是这个TimerController的构造函数里并没有根据传入参数来创建真正的计时器,而是等待外部调用startReal方法来启动计时器,只有在startReal方法被调用的时候,才会往CellApp的全局计时器队列里添加一个计时器,计时器的回调函数就是TimerController::handleTimeout方法:
/**
* Construct the TimerController.
* is zero, there will be only a single callback, and the controller will
* destroy itself automatically after that callback.
*
* @param start Timestamp of the first callback
* @param interval Duration in game ticks between subsequent callbacks
*/
TimerController::TimerController( GameTime start, GameTime interval ) :
pHandler_( NULL ),
start_( start ),
interval_( interval ),
timerHandle_()
{
}
/**
* Start the timer.
*/
void TimerController::startReal( bool isInitialStart )
{
// Make sure it is not already running
MF_ASSERT( !timerHandle_.isSet() );
pHandler_ = new Handler( this );
// if we were offloaded just as our timer was going off - we set the timer
// start time to be now
if (!isInitialStart)
{
if (start_ < CellApp::instance().time())
{
start_ = CellApp::instance().time();
}
}
timerHandle_ = CellApp::instance().timeQueue().add( start_, interval_,
pHandler_, NULL, "TimerController" );
}
这个StartReal会在TimerController对象注册到所属Entity的pControllers_时被自动调用:
/**
* This method adds a controller to the collection.
*/
ControllerID Controllers::addController(
ControllerPtr pController, int userArg, Entity * pEntity )
{
// 省略一些容错代码
ControllerID controllerID = pController->exclusiveID();
if (controllerID != 0)
{
// Deleting exclusive controller. For example, if another movement
// controller already exists, stop it now before adding the new one.
this->delController( controllerID, pEntity, /*warnOnFailure:*/false );
}
else
{
controllerID = this->nextControllerID();
}
pController->init( pEntity, controllerID, userArg );
container_.insert( Container::value_type(
pController->id(), pController ) );
// 省略一些当前无关代码
// Note: This was moved to be after the controller has been streamed on to
// be sent to the ghosts. The reason for this was for the vehicles. In
// startGhost, the entity's local position changes so that the global
// position does not. The ghost entity needs to know that it is on a
// vehicle before it gets this update.
if (pController->domain() & DOMAIN_GHOST)
{
pController->startGhost();
}
if (pController->domain() & DOMAIN_REAL)
{
pController->startReal( true /* Is initial start */ );
}
return pController->id();
}
而这个addController方法则是在ControllerFactoryCaller里在创建完一个Controller对象之后自动调用,这样就完成了计时器对象创建之后自动激活的逻辑:
/**
* This class is the Python object for a controller factory method
*/
class ControllerFactoryCaller : public PyObjectPlus
{
Py_Header( ControllerFactoryCaller, PyObjectPlus )
public:
ControllerFactoryCaller( Entity * pEntity, Controller::FactoryFn factory,
PyTypeObject * pType = &s_type_ ) :
PyObjectPlus( pType ),
pEntity_( pEntity ),
factory_( factory )
{ }
PY_KEYWORD_METHOD_DECLARE( pyCall )
private:
EntityPtr pEntity_;
Controller::FactoryFn factory_;
};
/**
* Call method for a controller factory method
*/
PyObject * ControllerFactoryCaller::pyCall( PyObject * args, PyObject * kwargs )
{
// make sure this entity will accept controllers
if (pEntity_->isDestroyed())
{
PyErr_SetString( PyExc_TypeError,
"Entity for Controller factory no longer exists" );
return NULL;
}
if (!pEntity_->isRealToScript())
{
PyErr_SetString( PyExc_TypeError,
"Entity for Controller factory is not real" );
return NULL;
}
Entity * oldFFnE = Controller::s_factoryFnEntity_;
Controller::s_factoryFnEntity_ = pEntity_.get();
// attempt to create the controller with these arguments then
Controller::FactoryFnRet factoryFnRet = (*factory_)( args, kwargs );
Controller::s_factoryFnEntity_ = oldFFnE;
if (factoryFnRet.pController == NULL) return NULL;
// ok, we have a controller, so let the entity add it,
// and return its controller id to python
return Script::getData( pEntity_->addController(
factoryFnRet.pController, factoryFnRet.userArg ) );
}
PY_TYPEOBJECT_WITH_CALL( ControllerFactoryCaller )
PY_BEGIN_METHODS( ControllerFactoryCaller )
PY_END_METHODS()
PY_BEGIN_ATTRIBUTES( ControllerFactoryCaller )
PY_END_ATTRIBUTES()
至于这个ControllerFactoryCaller类什么时候会被调用,相关的逻辑隐藏的非常的深,具体代码在Entity::pyGetAttribute方法中。如果脚本层访问一个不存在的属性,且这个属性名字的前缀是add,则会自动创建一个ControllerFactoryCaller对象,用于调用Controller工厂函数创建一个Controller对象:
/**
* This method is responsible for getting script attributes associated with
* this object.
*/
ScriptObject Entity::pyGetAttribute( const ScriptString & attrObj )
{
PROFILER_SCOPED( Entity_pyGetAttribute );
const char * attr = attrObj.c_str();
// 省略很多代码
// Try the controller constructors
if (treatAsReal && attr[0] == 'a' && attr[1] == 'd' && attr[2] == 'd')
{
PyObject * ret = Controller::factory( this, attr+3 );
if (ret != NULL)
{
return ScriptObject( ret, ScriptObject::FROM_NEW_REFERENCE );
}
}
// 省略很多代码
}
而这个Controller::factory方法则是用于创建一个Controller对象的工厂函数,它会根据传入的controllerName参数,调用ControllerFactoryCaller来创建对应Controller对象:
/**
* This static method returns a python object representing the factory
* method for the given named controller, if such a controller exists.
*/
PyObject * Controller::factory( Entity * pEntity, const char * name )
{
if (s_pCreators == NULL) return NULL;
uint len = strlen(name);
if (len > 200)
{
return NULL;
}
char fullName[256];
memcpy( fullName, name, len );
memcpy( fullName+len, "Controller", 11 );
// for now do a linear search ... could use a map but I can't be bothered
for (uint i = 0; i < s_pCreators->size(); i++)
{
if ((*s_pCreators)[ i ].factory_ != NULL &&
strcmp( (*s_pCreators)[ i ].typeName_, fullName ) == 0)
{
return new ControllerFactoryCaller(
pEntity, (*s_pCreators)[ i ].factory_ );
}
}
return NULL;
}
所以当脚本层调用Entity.addTimer方法添加一个计时器时,会先获得Timer这个字符串,然后会通过Controller::factory来查找TimerController对应的工厂函数,创建一个TimerController对象。
当这个计时器超时的时候,会调用到脚本Entity对象的onTimer方法,这个方法会在脚本层被定义为一个回调函数,用于处理计时器超时的事件:
/*~ callback Entity.onTimer
* @components{ cell }
* This method is called when a timer associated with this entity is triggered.
* A timer can be added with the Entity.addTimer method.
* @param timerHandle The id of the timer.
* @param userData The user data passed in to Entity.addTimer.
*/
/**
* Handle timer callbacks from TimeQueue and pass them on.
*/
void TimerController::handleTimeout()
{
AUTO_SCOPED_PROFILE( "onTimer" );
// Update our start time, so it is correct if we are streamed
// across the network.
start_ += interval_;
// Keep ourselves alive until we have finished cleaning up,
// with an extra reference count from a smart pointer.
ControllerPtr pController = this;
this->standardCallback( "onTimer" );
}
// -----------------------------------------------------------------------------
// Section: TimerController::Handler
// -----------------------------------------------------------------------------
/**
* Constructor.
*/
TimerController::Handler::Handler( TimerController * pController ) :
pController_( pController )
{
}
/**
* Handle timer callbacks from TimeQueue and pass them on.
*/
void TimerController::Handler::handleTimeout( TimerHandle, void* )
{
if (pController_)
{
pController_->handleTimeout();
}
else
{
WARNING_MSG( "TimerController::Handler::handleTimeout: "
"pController_ is NULL\n" );
}
}
这个OnTimer方法会接收两个参数,第一个参数是计时器的id,第二个参数是用户传入的userData参数,这样就可以区分超时的是哪一个计时器了:
void Controller::standardCallback( const char * methodName )
{
MF_ASSERT( this->isAttached() );
START_PROFILE( SCRIPT_CALL_PROFILE );
EntityID entityID = this->entity().id();
const char* name = this->entity().pType()->name();
ControllerID controllerID = controllerID_;
int userArg = userArg_;
this->entity().callback( methodName,
Py_BuildValue( "(ii)", controllerID, userArg ),
methodName, false );
// note: controller (and even entity) could be have been deleted by here
STOP_PROFILE_WITH_CHECK( SCRIPT_CALL_PROFILE )
{
WARNING_MSG( "Controller::standardCallback: "
"method = %s; type = %s, id = %u; controllerID = %d; "
"userArg = %d\n",
methodName, name, entityID, controllerID, userArg );
}
}
在上述组件的支持下,脚本层可以很方便地使用计时器,例如在Entity脚本中添加一个计时器,当计时器超时的时候,会调用onTimer方法:
# Entity脚本中使用计时器的示例
def __init__(self):
# 5秒后开始,每秒重复执行,用户数据为9
self.timerID = self.addTimer(5, 1, 9)
# 仅执行一次的计时器(10秒后执行)
self.oneTimeTimerID = self.addTimer(10)
def onTimer(self, timerID, userData):
print(f"Timer {timerID} triggered with data {userData}")
# 可以根据timerID和userData执行不同逻辑
if timerID == self.timerID and userData == 9:
# 满足条件时取消计时器
self.cancel(timerID)
这里的cancel方法是一个通用的方法,执行的时候会调用到Entity::py_cancel,这个调用过程比较复杂,首先在entity.hpp的头文件通过PY_METHOD_DECLARE来声明py_cancel方法,然后在entity.cpp的源文件通过PY_METHOD宏来注册这个方法到Entity类的方法列表中:
PY_METHOD_DECLARE( py_cancel )
PY_BEGIN_METHODS( Entity )
/*~ function Entity destroy
* @components{ cell }
* This function destroys entity by destroying its local Entity instance,
* and informing every other application which holds some form of instance of
* it to do the same. It is expected to be called by the entity itself, and
* will throw a TypeError if the entity is a ghost. The callback onDestroy()
* is called if present.
*/
PY_METHOD( destroy )
/*~ function Entity cancel
* @components{ cell }
* The cancel function stops a controller from affecting the Entity. It can
* only be called on a real entity.
* @param controllerID controllerID is an integer which is the index of the
* controller to cancel. Alternatively, a string of an exclusive controller
* category can be passed to cancel the controller of that category. For
* example, only one movement/navigation controller can be active at once.
* This can be cancelled with entity.cancel( "Movement" ).
*/
PY_METHOD( cancel )
这个Entity::py_cancel会调用Controller上的cancel方法,通过传入的第一个参数来寻找对应的Controller。对于TimerController来说,它会根据计时器的id来取消对应的计时器:
/**
* This method is exposed to scripting. It is used by an entity to cancel
* a previously registered controller. The arguments below are passed via
* a Python tuple.
*
* @param args A tuple containing the ID of the previously registered controller.
*
* @return A new reference to PyNone on success, otherwise NULL.
*/
PyObject * Entity::py_cancel( PyObject * args )
{
if (!this->isRealToScript())
{
PyErr_SetString( PyExc_TypeError,
"Entity.cancel() not available on ghost entities" );
return NULL;
}
return pControllers_->py_cancel( args, this );
}
/**
* This method implements the Entity.cancel Python function. On failure, it
* returns NULL and sets the Python exception state.
*/
PyObject * Controllers::py_cancel( PyObject * args, Entity * pEntity )
{
int controllerID;
bool deleteByID = true;
if (PyTuple_Size( args ) != 1)
{
PyErr_Format( PyExc_TypeError,
"cancel takes exactly 1 argument (%" PRIzd " given)",
PyTuple_Size( args ) );
return NULL;
}
ScriptObject arg = ScriptObject( PyTuple_GET_ITEM( args, 0 ),
ScriptObject::FROM_BORROWED_REFERENCE );
if (!arg.convertTo( controllerID, ScriptErrorClear() ))
{
if (PyString_Check( arg.get() ))
{
deleteByID = false;
controllerID =
Controller::getExclusiveID(
PyString_AsString( arg.get() ), /*createIfNecessary:*/ false );
if (controllerID == 0)
{
PyErr_Format( PyExc_TypeError,
"invalid exclusive controller category '%s'",
PyString_AsString( arg.get() ) );
return NULL;
}
}
else
{
PyErr_SetString( PyExc_TypeError,
"argument must be a ControllerID or a string" );
return NULL;
}
}
Container::iterator found = container_.find( controllerID );
if (found != container_.end())
{
ControllerPtr pController = found->second;
pController->cancel();
}
else if (deleteByID)
{
WARNING_MSG( "Entity.cancel(%u): Cancelling an unknown "
"controller ID %d\n",
pEntity->id(), controllerID );
}
Py_RETURN_NONE;
}
这里的TimerController有一个非常值得注意的地方,即这个计时器对象支持迁移,在开始迁移的时候调用writeRealToStream将当前的计时器状态写入到流中,在接收端调用readRealFromStream从流中读取计时器状态并执行恢复:
/**
* Write out our current state.
*/
void TimerController::writeRealToStream( BinaryOStream & stream )
{
this->Controller::writeRealToStream( stream );
stream << start_ << interval_;
}
/**
* Read our state from the stream.
*/
bool TimerController::readRealFromStream( BinaryIStream & stream )
{
this->Controller::readRealFromStream( stream );
stream >> start_ >> interval_;
return true;
}