BigWorld 的逻辑驱动

由于bigworld所提供是一个基础框架,并没有涉及到具体业务,所以其回调逻辑驱动组件只提供了基础的定时器和RPCReply这两个功能。RPCReply功能在前述的RPC章节中已经介绍过了,因此本章节只介绍定时器的代码实现。

计时器队列

bigworld里,定时器的实现是通过lib/cstdmf/time_queue.hpp提供的TimerQueueT模板类来完成的。TimerQueueT内部有一个优先队列timeQueue_,队列中的元素是按照触发时间排序的。队列里每个元素都是一个TimerQueueNodeTimerQueueNode包含了触发时间、重复间隔、处理对象和用户数据等信息,

/**
 * 	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_heapstd::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对外暴露的最重要接口就是addprocess, add负责添加一个计时器,process负责处理所有到期的计时器,这两个接口的实现非常直白,都是调用PriorityQueue提供的的pushpop方法:

/**
 *	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指针,这样就能支持在同一个函数上通过TimerHandlepUser来区分不同的计时器实例:

/**
 *	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_PENDINGSTATE_EXECUTINGSTATE_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_addTimerpy_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的回调到PyTimerhandleTimeout方法中,在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来存储目前还活跃的定时器,MapKey是定时器IDValue是定时器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,同时将IDTimerHandle的映射存储在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还提供了backUpTimersrestoreTimers方法,用于将实体的脚本定时器保存到数据流中,以及从数据流中恢复脚本定时器。这两个方法内部调用了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会一路中转到PriorityQueueadjustBy方法,用于调整所有计时器的时间,这样就可以达到计时器的时间同步:

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类里,会提供startstop方法,用于启动和停止计时器,同时还会提供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在执行的时候,会根据传入的initialOffsetrepeatOffset参数,转换为对应的游戏时间单位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对象注册到所属EntitypControllers_时被自动调用:

/**
 *	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;
}