從書中例子來看,這個餐館例子是第一個真正意義上的仿真例子。因爲前兩個例子的共同點是:所有聯邦都運行同一個程序,僅僅是通過運行設置(比如有的訂閱,有的發佈),才表現爲不同的聯邦。而餐館聯邦直接把聯邦的程序分開,不同的聯邦成員具有不同的程序代碼。
另一個方面,書上對這個例子有較詳細的解釋。這也爲這個例子的分析帶來了很多便利。
書上把production聯邦作爲例子進行了詳細分析,我們這邊也先從production聯邦入手。其實後面可以看到,production,transport,comsumption三個聯邦代碼非常類似,甚至manager和viewer的代碼也類似,只是pruduction聯邦,consumption聯邦使用了NER的時間調度方式,後manager,viewer和transport聯邦使用了TAR的時間調度方式。
總體來看,每個聯邦都有幾個java文件組成,以production聯邦爲例:Production.java,ProductionFrame.java, ProductionInternalError.java, ProductionNames.java,FedAmbImpl.java(production)。其中,frame是GUI類,FedAmbImpl包括所有回調函數。所有對RTI的函數調用,都在Production.java完成,使用_rti變量。
--------------------------------------------------------------------------------------------------------
主函數很簡單
public static void main(String[] args) {
Properties props = parseArgs(args);
loadProperties(props);
Production production = new Production(props);
production.mainThread();
}
主要功能是從命令行或者配置文件中讀取相關參數,產生production對象,運行mainThread線程
---------------------------------------------------------------------------------------------------------
主要的功能都在mainThread函數中
(1)取得相關配置信息,判斷是否有federation execution實例運行,沒有則創建,然後加入join
(2)設置時間受限和時間調節。這裏首先設置了時間受限,同時在設置時間受限和時間調節時,使用了barrier機制來協調各個線程(barrier機制在例子中多次使用,這裏不討論其細節)。
(3)獲取相關對象類,屬性,交互類的handle,存儲下來以備後續使用。--getHandles();
(4)發佈對象類及其相關屬性,包括Serving類及其position屬性和type屬性;Chef對象類及其position,chefStage和servingName
(5)訂閱對象類及其相關屬性,包括Boat對象類及其position,spaceAvailable和cargo屬性,以及SimulationEnds交互類和TransferAccepted交互類。
(6)處理第一個同步點ReadyToPolulate,發出同步點已經achieved請求。使用barrier機制阻塞線程,結束阻塞後輸出已經同步信息。
(7)根據預先給定的chef個數,逐個註冊chef對象實例,並把所註冊的chef實例加入本地表格_chefTable中。同時,把每個chef的信息進行更新_rti.updateAttributeValues(chefHandle, sa, null, sendTime);然後隨機產生一個類型的sushi,並把該sushi加入_internalQueue--_internalQueue.enqueue(new FinishMakingSushiEvent(eventTime, serial, type));(注意這裏,eventTime從0.0開始,每一次增加的時間量是做某種sushi的時間)。 注意,上述所有活動都在循環體內。也就是說,對每個chef,都要做後續的所有工作。同時還要注意,製作不同的sushi的時間不同,但是_internalQueue.enqueue方法將保證製作時間越短的sushi越排在前面。這裏的物理含義是:6個chef一起(從_logicTime=0.0時刻)做sushi,則製作時間越短的sushi越早製作好。
(8)處理第二個同步點ReadyToRun,發出同步點已經achieved請求。使用barrier機制阻塞線程,結束阻塞後輸出已經在該同步點同步的信息。
(9)調用rti函數enableAsynchronousDelivery,目的是instructs the LRC to begin delivering receive-ordered events to the federate even while no time-advancement service is in progress,其實就是production這個時間受限聯邦成員要求在時間批准狀態下也能接收RO事件。
(10)進入時間推進的主循環。循環結束條件是該聯邦收到simulationEnds交互。基本的消息循環流程:production聯邦收到的所有RTI的回調都會進入callbackQueue排隊。然後在消息循環裏面逐一取出消息來處理。
下面是主循環代碼:
timeLoop:
while (!_simulationEndsReceived) {
_userInterface.setTimeStateAdvancing();
LogicalTime timeToMoveTo = _internalQueue.getTimeAtHead();
//_userInterface.post("NER to " + timeToMoveTo);
_rti.nextEventRequest(timeToMoveTo);
boolean wasTimeAdvanceGrant;
do {
Callback callback = _callbackQueue.dequeue();
wasTimeAdvanceGrant = callback.dispatch();
if (_simulationEndsReceived) break timeLoop;
} while (!wasTimeAdvanceGrant);
updateInternalStateAtNewTime();
}
主循環流程解釋如下:
while(沒有收到simulationEnds交互)
{
界面顯示聯邦開始時間推進;
從_internalQueue隊列中取出隊頭事件所包含的時間(對production聯邦而言,開始運行時,internalQueue中存儲的其實就是從0.0時刻開始製作完成的第一個sushi的時間);
請求時間推進NER到上一條語句所取出的時間上,對production聯邦而言,其實就是取出做完一個sushi後的logicTime時間。
do{
從callbackQueue中取出一個事件,返回給callback對象;
調用callback對象的dispatch函數,返回一個布爾量wasTimeAdvanceGrant
判斷是否收到_simulationEnds交互,如果收到,退出循環;
while (!wasTimeAdvanceGrant);
從internalQueue中取出所有小於等於當前時間的事件處理掉,此例中就是取出最短時間做好的sushi,註冊serving對象實例,並把serving對象實例加入_serving結構中;
根據當前_chefTable中所有的chef信息發送updateAttributeValues請求;
根據當前_serving中所有的sushi信息,發送updateAttributeValues請求;
}
(11)處理第三個同步點ReadyToResign,發出同步點已經achieved請求。使用barrier機制阻塞線程,結束阻塞後輸出已經在該同步點同步的信息。然後結束該聯邦的運行。
---------------------------------------------------------------------------------------------------------------------------
下面是運行5個聯邦後,production.java輸出的一些信息以及對這些輸出的解釋:
RTIambassador created
Federation execution restaurant_1 already exists.
Joined as federate 8
Enabling time constraint...
...constraint enabled at time<0.0>
Enabling time regulation...
...regulation enabled at time<0.0>
This point is after subscribe...
Waiting for ReadyToPopulate...
�5.10)startRegistrationForObjectClass:3
�5.10)startRegistrationForObjectClass:6
...federation synchronized.
type0 increase by interval<1.5> //這裏是產生的6個sushi實例,信息包括type和製作時間,其實意思是說6個chef每人做一個
type0 increase by interval<1.5>
type3 increase by interval<4.0>
type1 increase by interval<2.3>
type3 increase by interval<4.0>
type1 increase by interval<2.3>
Waiting for ReadyToRun...
...federation synchronized.
NER to time<1.5> //進入主循環,首先根據第一個sushi製作完成的時間,發出NER to 1.5的時間推進請求
Discovered Boat 115(B_9_0) //此時,外部事件callback隊列中首先出現的時間是tranport.java聯邦註冊的Boat對象,一共8個。這裏callback應該是DiscoverObjectInstanceCallback
Discovered Boat 117(B_9_1)
Discovered Boat 118(B_9_2)
Discovered Boat 119(B_9_3)
Discovered Boat 120(B_9_4)
Discovered Boat 121(B_9_5)
Discovered Boat 122(B_9_6)
Discovered Boat 123(B_9_7)
...granted to time<0.01> //下一個外部事件隊列中出現的是一個time grant
NER to time<1.5> //重新回到主循環的起始,因爲得到的time grant的時間小於第一個sushi製作完成的時間,內部事件隊列中並不刪除第一個sushi製作完成這個事件,重新發出NER to 1.5的時間推進請求
...granted to time<1.01> //又得到一個外部事件隊列事件,是一個time grant
NER to time<1.5> //同理,又發出一個NER to 1.5的時間推進請求
...granted to time<1.5> //這回,終於得到grant了
Dequeued internal event at time<1.5>, chef: 0 //下面就是從_internalQueue中取出兩個1.5時間做好的sushi,註冊相應的對象實例,並從_internalQueue中刪除兩個1.5時間做好的sushi
This is dispatch in FinishMakingSushiEvent
Dequeued internal event at time<1.5>, chef: 1
This is dispatch in FinishMakingSushiEvent
NER to time<2.3> //這回,輪到第三個被做好的sushi了
Chef 1 attempting to load boat B_9_1 //下一個外部事件是reflectiveAttributeValues,是transport聯邦對Boat實例對象屬性的修改更新導致的事件
AODN Serving S_8_1 //再下一個外部事件是AODN callback,即AttributeOwnershipDivestitureNotificationCallback,RTI回調這個服務通知所有權正式轉移
...granted to time<2.01> //下一個外部事件是一個time grant
NER to time<2.3> //沒有得到了2.3的grant,繼續NER to time 2.3
...granted to time<2.3> //終於得到2.3的grant了
Dequeued internal event at time<2.3>, chef: 3 //下面就是從_internalQueue中取出兩個2.3時間做好的sushi,註冊相應的對象實例,並從_internalQueue中刪除兩個2.3時間做好的sushi
This is dispatch in FinishMakingSushiEvent
Dequeued internal event at time<2.3>, chef: 5
This is dispatch in FinishMakingSushiEvent
NER to time<4.0> //重新回到循環的起始,取出下一個做好的sushi的時間是4.0
TransferAccepted: S_8_1 from chef serial 1 time<3.01> //下一個事件是接收到一個交互,
...granted to time<3.01>
NER to time<4.0>
...granted to time<4.0>
Dequeued internal event at time<4.0>, chef: 2
This is dispatch in FinishMakingSushiEvent
Dequeued internal event at time<4.0>, chef: 4
This is dispatch in FinishMakingSushiEvent
---------------------------------------------------------------------------------------------------------------------------
需要注意的是,callback是一個抽象類,dispatch方法也是一個抽象方法。具體從外部事件隊列中取出的事件,調用其dispatch方法時,會根據具體的對象實例所屬的類(比如ExternalEvent ,AODNcallback,DiscoverObjectInstanceCallback,ProvideAttributeValueUpdateCallback,ReceiveInteractionCallback,ReflectAttributeValuesEvent,RemoveObjectInstanceEvent,RAORcallback)調用合適的方法。
----------------------------------------------------------------------------------------------------------------------------
前面例子中,我們初始時(0.0時間起)一共生產了6個sushi,分別由6個chef生產。現在的問題是:從動態運行的輸出來看,當production聯邦的時間已經進展到4.0時,所有6個sushi都已經生產出來。則下一個sushi什麼時候產生呢? 從源代碼查看得知,應該是transport聯邦發出TransferAccepted交互時產生了新的sushi。
在production.java中,類public final class ReceiveInteractionEvent extends ExternalEvent是來處理TransferAccepted交互的。即production收到TransferAccepted交互時,將調用上述類中的dispatch方法,而在dispatch方法中,有如下代碼:
//put event on internal queue
LogicalTime eventTime = new LogicalTimeDouble(0.0);
eventTime.setTo(_logicalTime);
eventTime.increaseBy(_manufactureTimes[type]);
_internalQueue.enqueue(new FinishMakingSushiEvent(eventTime, chefSerial, type));
顯然,上述代碼就是產生新的內部事件,該事件說明生產新的sushi,該sushi的開始生產時間是_logicalTime,結束時間是某種sushi生產的時間+_logicalTime
對評論的回覆:
在下載的book-update-2000-07目錄下,有兩個批處理,一個是rti運行的批處理,一個是對production聯邦運行建立了一個批處理
rti運行:
java -cp bin;lib/prti.jar;lib/jgl3.1.0.jar se.pitch.prti.RTIexec
production聯邦運行:
java -cp src;lib/prti.jar;lib/jgl3.1.0.jar;lib/swingall.jar org.mitre.hla.book.restaurant.production.Production
之前需要設置java系統的運行目錄到path環境變量中,或者jre的bin目錄到path中。