《计算机仿真中的HLA技术》中给出的餐馆例子,一共包含5个联邦成员:manager,consumption,production,transport 和 viewer。其中,manager这种类型的联邦成员是之前的两个例子中从未出现过的,下面就对manager这个联邦成员的代码和功能进行分析。
在本文中,我们将首先对manager这个联邦成员的动态运行过程进行一下分析,然后再介绍其具体实现中的各个部分的细节。
在餐馆例子中,manager联邦成员的运行中输出的动态信息如下,同时也是它的执行过程:
manager
----------------------------------------------------------------
1.尝试创建一个联邦。
RTIambassador created //对应代码_rti.createFederationExecution(_fedexName, fedURL);
但是名字为restaurant_1的联邦已经存在,所以报异常。
Federation execution restaurant_1 already exists. //对应代码catch (FederationExecutionAlreadyExists e)
2.加入该联邦
Joined as federate 2 //对应代码_rti.joinFederationExecution( )
3.设置时间服务类型为时间受限型和时间调节型(餐馆联邦采用保守的同步机制,每个联邦成员都既是时间受限型,也同时是时间调节型)
Enabling time constraint... //对应代码_rti.enableTimeConstrained()以及rti的回调函数timeConstrainedEnabled()
...constraint enabled at time<0.0>
Enabling time regulation...
...regulation enabled at time<0.0>
4.登记同步点ReadyToPopulate,ReadyToRun和ReadyToResign,这是manager专门负责的工作。
Registering ReadyToPopulate... //对应代码_rti.registerFederationSynchronizationPoint以及rti的回调函数 //synchronizationPointRegistrationSucceeded()
...registration succeeded.
Registering ReadyToRun...
...registration succeeded.
Registering ReadyToResign...
...registration succeeded.
5.等待其它联邦加入,以便开始整个联邦的仿真工作。
Waiting for all federates to join...
This is DiscoverObjectInstanceCallback's dispatch //处理回调函数队列中的事件,主要是更新刚加入的联邦成员的属性值,对应 //代码callback.dispatch(),这里利用了面向对象编程思想中的多态,即一 //种调用,多种处理。
This is DiscoverObjectInstanceCallback's dispatch
This is ReflectAttributeValuesCallback's dispatch
...done.
6.同步点机制,将整个仿真过程分为多个阶段,每当所有的联邦成员到达同一个同步点时,开始进入下一阶段。每个阶段每个联邦成员完成相应的工作,对此在后面会有详细叙述。
Waiting for ReadyToPopulate...
...federation synchronized.
Waiting for ReadyToRun...
...federation synchronized.
7.仿真开始,采用TAR机制推进仿真时间,具体推进过程在后面详述。
Adv int (ms): 1000
wallClockAtWhichToRequestAdvance is1289526424715
_advanceIntervalAsMillis is1000
wallClockWhenGranted is1289526424715
Sleeping for 1000
This is ReflectAttributeValuesCallback's dispatch
This is ReflectAttributeValuesCallback's dispatch
This is GrantEvent's dispatch
...granted to time<1.0>
wallClockAtWhichToRequestAdvance is1289526425715
_advanceIntervalAsMillis is1000
wallClockWhenGranted is1289526426047
Sleeping for 668
This is GrantEvent's dispatch
...granted to time<2.0>
manager这种联邦成员与其他联邦成员不同。consumption,production,transport分别负责模拟仿真餐馆联邦中的消费者、生产者和运输者,而viewer则负责动态显示上述三者的状态,他们都代表着具体的仿真实体。而manager则不代表任何仿真实体,它的存在是为了协调联邦成员之间的活动,方便实现整个联邦的时间管理服务。
首先,在未引入manager之前,由于各个联邦成员加入联邦的先后顺序不同,可能导致某些联邦成员丢失其加入联邦之前的其它联邦的仿真数据值。举例而言,可能会出现这种现象:production发布了serving类,并且登记了1个serving实例,然后接连2次更新了它的某个属性;之后transport才加入联邦,并且订阅了serving类的这个属性,这时transport已经无法获取该属性的最初始的值;而如果transport在production之前加入联邦的话,则可以获取该属性最初始时的值。这样,就会导致联邦每次运行模拟仿真出来的情形不同,导致仿真过程不可重复性,这是我们设计一个仿真系统时所应该避免出现的结果。
其次,各个联邦成员加入联邦的顺序不同,将导致联邦成员间的时间同步出现巨大困难。比如说,时间受限的viewer联邦成员可能在时间调节的consumption联成员设置时间管理类型之前就开始推进自己的仿真时间,导致viewer联邦成员的logicaltime提前于consumption的logicaltime,这样会给时间管理带来巨大的困难。
解决上述问题有很多方法,在餐馆的例子中,采取的是引入两种机制:同步点机制和MOM机制(对象管理模型),而这两种机制具体是由manager这个专门引进的联邦成员负责实现的。
同步点机制,打个比方,就是在一段长长的路上设置若干驿站(也就是HLA中的同步点),各个联邦成员由于硬件运行速度、工作量等方面存在差异,在路上的行进速度会存在差异,有的快,有的慢。我们规定,每当一个联邦成员到达一个驿站(同步点)时,它必须在此等待其它尚未到达此驿站(同步点)的联邦成员,等到所有联邦成员都到达时,再一起向下一个驿站(同步点)进发。manager就是专门负责设置这些驿站(同步点)的联邦成员。在餐馆这个例子中,manager共设置了3个同步点:ReadyToPopulate,ReadyToRun和ReadyToResign。
这是实现同步点机制对应的部分代码:
//register synchronization points
_userInterface.post("Registering " + ManagerNames._readyToPopulate + "...");
barrier = new Barrier(ManagerNames._readyToPopulate);
_fedAmb.setSynchronizationPointRegistrationSucceededBarrier(barrier);
_rti.registerFederationSynchronizationPoint(ManagerNames._readyToPopulate, null);
result = barrier.await();
_userInterface.post("...registration succeeded.");
程序中,设置barrier的目的在于实现多线程,即:当manager调用rti的registerFederationSynchronizationPoint登记完同步点后,程序的执行便转入到rti中;而rti则把该同步点的标识符告诉其它联邦成员,在此期间manager调用barrier.await( )函数,使自己处于空闲状态,直到rti完成同步点的登记工作(也就是告知其它联邦成员该同步点标识符),然后rti回调_fedAmb中的synchronizationPointRegistrationSucceeded函数通知manager同步点登记工作完成,将manager从barrier.await( )函数中“唤醒”。与consumption,production,transport 和 viewer中同步点部分的代码的不同之处在于,manager负责登记同步点,这是它的主要工作之一。同步点集合是所有需要rti通知同步点的联邦成员的集合,在本例中,同步点集合应该默认是将全体联邦成员都放入该集合中,所以在登记同步点的函数中没有传入同步点集合参数。之后的登记其他同步点的代码均与上述代码类似,不再赘述。
MOM机制,主要用途是判断所有的预期联邦成员是否都已加入联邦。MOM包含在FOM中,其设计的目的在于使得rti中能够存储与联邦成员相关的管理信息。为此,餐馆联邦定义了一个federate对象类,,rti负责为每个加入餐馆联邦的联邦成员登记该对象类的一个实例并进行属性更新。这样manager只需要订阅federate对象类,就可以知道哪些联邦成员已经加入,当预期的联邦成员都已经加入时,manager就可以决定进入联邦下一阶段。
这是manager中,MOM机制对应的部分代码:
private void subscribe()
throws RTIexception
{
//subscribe to MOM attributes
_rti.subscribeObjectClassAttributes(_FederateClass, _federateAttributesSet);
//subscribe to ending interaction
_rti.subscribeInteractionClass(_SimulationEndsClass);
}
这里是manager订阅MOM中的联邦成员的属性值和交互,以便于管理和更新manager的GUI上对应的表格的值。
_numberOfFederatesToAwait = Integer.parseInt(
getProperty("Manager.numberOfFederatesToAwait"));
while (_federateTable.getRowCount() < _numberOfFederatesToAwait) {
Callback callback = _callbackQueue.dequeue();
boolean ignore = callback.dispatch();
_federateTable.updateFederates(_rti, _federateAttributesSet);
}
这里是manager判断所有联邦成员是否都已加入联邦的代码。第1句代码用于获取预期的联邦成员数目_numberOfFederatesToAwait,而第2句代码则是等待所有联邦成员加入该联邦,直到已加入联邦成员数目等于预期的联邦成员数,同时实时更新刚加入的联邦成员对应的federateTable的表项的值。
由于manager除了负责登记同步点和动态显示其他联邦成员相关属性值外,并不实际参与仿真,所以它的时间管理机制采用的是TAR机制,即时间驱动的时间管理服务。对应的具体代码如下:
timeLoop:
while (!_simulationEndsReceived) {
//wait till we're not paused
_pausedBarrier.await();
wallClockWhenGranted = System.currentTimeMillis();
long sleepTime = wallClockAtWhichToRequestAdvance + _advanceIntervalAsMillis
- wallClockWhenGranted;
这里为manager设置了一个sleepTime,个人认为其主要原因在于manager采用了regulation&&constrained类型的时间服务,而manager本身除了更新federateTable以外又没有其他的工作,这样如果放任让它自由推进时间,可能会导致它的推进速度偏快,而其他联邦成员,比如说production等可能还没有完成相应的仿真计算工作,就会因为manager发出的TAR要求而导致rit要求production等推进自身的logicalTime。同样,viewer和manager一样,也不做什么工作,但是由于它是constrained类型的时间服务,所以它应该不需要sleepTime,这一点,我们也在其代码中得到了印证。
sleepTime的计算公式的由来:wallClockAtWhichToRequestAdvance + _advanceIntervalAsMillis其实就是下一次发出TAR的墙钟时间,而wallClockWhenGranted则是rti准许TAR时的墙钟时间。也就是说,上述计算公式其实就算的是从rti准许manager推进时间,到其下一次发出推进时间请求的时间间隔,这段时间manager没有任何工作可以做,因此程序中选择让它休眠一段时间。如下图所示:
其中,wallClockWhenGranted可能落于图中的1处或者2处,这取决于从manager发出TAR到manager收到并处理GrantEvent期间所花费的时间是否大于1个_advanceInterval。
如果小于1个_advanceInterval,则wallClockWhenGranted将落在1处时,sleepTime 大于 0,因此会进入如下代码,使得manager会先休眠一段时间。
if (sleepTime > 0) {
try {
Thread.sleep(sleepTime);
}
catch (InterruptedException e) {}
}
如果不小于1个_advanceInterval,则wallClockWhenGranted会落在2处,sleepTime 小于或者等于 0,直接跳过休眠阶段,提出下一个TAR。
//设置目标时间,即下一次前进到的时间。
_targetTime.increaseBy(_advanceInterval);
//将manager设置为时间推进状态。
_userInterface.setTimeStateAdvancing();
//更新wallClockAtWhichToRequestAdvance的值
wallClockAtWhichToRequestAdvance += _advanceIntervalAsMillis;
//发出TAR, 一旦RTI收到调用,会将所有时间戳小于_targetTime的TSO事件和RO事件发送到manager的callback队列中,个人认为在此之前,manager也会将时间戳小于_targetTime+LookAhead时间的TSO事件(如果有的话)发出去。
_rti.timeAdvanceRequest(_targetTime);
boolean wasTimeAdvanceGrant;
//manager开始处理其callback队列中的外部事件,直到manager收到并处理完其RTI针对其TAR的Grant事件,跳出次循环,从时间推进状态进入时间批准状态,从而开始下一轮的“时间批准状态<---->时间推进状态”的循环。
do {
Callback callback = _callbackQueue.dequeue();
//manager处理外部事件。
wasTimeAdvanceGrant = callback.dispatch();
if (_simulationEndsReceived) break timeLoop;
} while (!wasTimeAdvanceGrant);
_federateTable.updateFederates(_rti, _federateAttributesSet);
}