|
http://www.ayssss.cn/ 最近老有人問我是不是出了改dota英雄技能的外掛,什么月騎無限大,劍圣無限斬,巫妖無限彈之類。 我在這里一并回答一下,并且稍微說說原理。因為我對地圖方面其實是一竅不通,如果有說的不對的地方,還請指正。 其實這些現象都是使用了作弊地圖導致的。本來魔獸爭霸是有一個地圖驗證的,如果你跟主機的圖不同,是進不去的(要下載地圖)。但是魔獸對地圖中的war3map.j文件是進行bcc(block check character)校驗的,bcc不同于md5,bcc一般只是用來排錯的,并不是加密算法。所以就有人寫出了這樣的代碼,可以在b文件末尾添加上一些不起作用的字串,來讓b文件的bcc校驗碼等于a文件(具體代碼我就不貼出來了,很容易搜到)。于是呢,我們就可以做到隨意修改地圖中的war3map.j ,然后再處理一下,使之跟原來的war3map.j的bcc校驗碼相同。再把改過并處理后的war3map.j文件替換原來的,這樣做出來的作弊地圖,暴雪的驗證會因為bcc校驗相同,而把它認為和原版圖是相同的。達到的效果就是,別人用正版圖建主機,你可以進入,你用盜版圖建主機,別人用正版圖也可以進入。但是別以為可以為所欲為的修改war3map.j ,雖然你突破了驗證這一關進入了游戲,但是魔獸的聯機機制是沒有辦法突破的。 在這里稍微談一下魔獸的聯機機制,沒興趣的請略過這一段。魔獸聯機時,一直有個同步機制,每個聯機的玩家都會同時計算所有數據,一旦有不一致,就會導致掉線,這也是為什么用金山游俠之類的游戲修改器單機時可以改錢,聯機時一改就掉線。因為你只能修改你自己的機器上的數據,而無法改別人的,單方面修改的結果就是造成你跟其他人不同,你就會掉線。當然,如果所有人同時修改的話,仍然是不會掉線的,所以現在有一些聯機修改器,參加游戲的幾個玩家一起開這個修改器,可以在玩rpg時改錢什么的,我幾個同學就老是用這種修改器來通關一些很難打的rpg圖。順便說一下,這樣玩下來保存的replay是無法正常播放的,因為replay只記錄動作,你使用修改器的改動不會被記錄,播放replay時會因為你并未像你游戲時那樣修改數據,造成replay不合邏輯而出錯。再順便說一下吧,為什么所謂的人品外掛并不能實現。曾有人發帖抱怨,怎么藍胖子次次放招都多重施法,怎么某人每次都暴擊,他們是不是用了人品掛。其實這是不可能的,有人以為魔獸中的隨機數據都是由主機計算的,這樣主機就可以找到辦法來修改隨機數,造成每次都對他有利的結果。但是實際中并非如此,隨機數也是所有人一起計算的,也就是說魔獸里的隨機是個偽隨機。在一局游戲一開始時,主機會發給每個玩家一個隨機數種子(這個種子很有可能就是主機從建立主機到游戲開始所經歷的毫秒數),之后的一整局中,所有的隨機數都根據這個隨機數種子,依照事先定好的算法計算出來,這樣也就保證了所有人計算出同樣的“隨機”結果。另外,這個隨機種子也會記錄進replay,這也從一個側面說明了魔獸里的隨機是偽隨機,如果是真的隨機,replay就無法重現了。說的有點多了,下面回到正題。 因為魔獸聯機機制的存在,你要是隨意改了war3map.j,例如改成給自己增加10000的錢,但是別人是按照的沒有修改的war3map.j,在別人機器中你是沒有那么多錢的。這時你買一個8000的物品,在你自己機器上是可以的,因為你有10000的錢,但是在其他人機器上,你錢卻根本不夠!這樣的不合理動作就會造成你跟其他人斷開連接。 也就是說,你只能修改那些不會造成沖突的地方。例如有些作弊圖可以顯示出地圖全開的效果,因為這些顯示的東西只是在你本地機器上顯示出來的,并不會對其他玩家照成沖突。類似這樣的修改都是可行的,不會掉線。 那么,為什么會出現這種有變態技能效果的dota作弊圖呢?我剛開始也很困惑,這么夸張的改動怎么竟然沒有掉線?我跟朋友要了個作弊圖玩的replay,在我的機器上,用正版dota地圖播放,竟然完全再現了那些變態效果!因為我對地圖方面并不了解,所以開始上網找資料,并通過qq向某些搞地圖的高人請教,又下載了那個變態版dota作弊圖和某平臺私自山寨的所謂“原版”dota圖,提取出來war3map.j來進行對比。經過n久的努力,總算搞明白他是怎么改出來這種效果的了。 原來是因為dota使用到了game cache,而作弊圖是單方面修改了game cache中的數據,然后通過函數同步給了所有的玩家。通俗點說,game cache相當于一個池子,所有玩家共享這塊區域,任意一個玩家都可以修改這個池中的數據,也可以發出通知,讓所有人都來同步這個池子,這樣就變相修改了其他人的數據。舉個例子,例如dota里黑曜石的放逐技能,它可以減少一個人的智力,一分鐘后再歸還給他,dota里關于這個技能的函數,把目標和要歸還的智力值記錄在game cache中,1分鐘之后會再從game cache取出目標和智力值,給目標加上相應的智力值,就完成了歸還這個人的智力的過程。但是在作弊圖中,這里增加了代碼,先進行一個判斷,如果黑曜石是本機玩家,會把game cache中記錄的目標改成本方隨機的一個隊友,然后把game cache中記錄的智力值改為500,然后通知所有玩家同步game cache中的這兩個值,這樣就完成了對所有人game chche中這兩個值的修改。1分鐘一到,dota就會向這個目標“歸還”智力,這樣,本方的一個玩家就憑空增加了500智力。(那個被減少智力的倒霉玩家就無法被歸還了,可憐) 大致的原理就是這樣了,具體細節我就不詳細敘述了。不過dota用到game cache的地方其實并不多,所以能改的地方也就那幾個。這也是為什么作弊圖要專門改這幾個地方,而不是改成例如加錢或者加攻擊力或者直接勝利之類的,不是不想改,而是無法實現。另外,暴雪官方的地圖是不會這樣使用game cache的,所以不用擔心對戰地圖被改(另外對戰圖還有暴雪標志的保護)。其他的rpg地圖,如果本身沒有用到game cache的,也就改不出來什么花樣,最多顯示個全圖之類。 暴雪將會在1.23修補這個地圖驗證漏洞,目前1.23的補丁已經在測試中了,相信升級之后,這種改圖作弊將不復存在。只是不知國內玩家到時是不是還要繼續死守bug頻出的1.20呢? 強行插入廣告一則:浩方平臺會再對地圖進行自己的驗證,md5驗證,作弊圖是無法通過的。 至于做山寨dota圖的某平臺嘛,就我目前來看,它是沒有任何地圖驗證的,唉。 應廣大群眾強烈要求,這里給出山寨版dota 6.57c的作弊圖鏈接地址,請大家自行圍觀(話說我參照這個做出了58b和59c的作弊圖,活活活): http://sc2dota.com/news/310.html
新浪科技訊 北京時間1月10日消息,據國外媒體報道,由于大量用戶爭相排隊下載Windows 7 Beta導致服務器不堪重負,微軟周五下午宣布延遲發布Windows 7 Beta。 微軟CEO史蒂夫·鮑爾默(Steve Ballmer)周三在消費電子展(CES)上發表主題演講時宣布,將于本周五面向公眾發布Windows 7 Beta。但由于排隊下載的用戶過多,導致微軟服務器被擠爆。 微軟在Windows 7官方博客中稱:“由于用戶對Windows 7 Beta熱情較高,導致服務器超負荷運轉。在發布Beta之前,我們將對Microsoft.com網站增加額外的硬件支持。” 微軟還稱:“我們將確保為用戶提供最佳的下載體驗,一旦發布Beta,我們會立即通知用戶。”周五早上,在微軟上傳Beta文件之前,就已經有跡象表明微軟服務器不堪重負。按計劃,微軟只提供250萬份Windows 7 Beta下載。 剛下的,每秒1MB多啊(剛剛鏈接上的速度是10MB多,嚇了我一跳,等穩定下來就1MB多) 補充兩個windows 7地址: Windows 7 Beta 32bit(2.44GB): http://download.microsoft.com/download/6/3/3/633118BD-6C3D-45A4-B985-F0FDFFE1B021/EN/7000.0.081212-1400_client_en-us_Ultimate-GB1CULFRE_EN_DVD.iso Windows 7 Beta 64bit(3.15GB): http://download.microsoft.com/download/6/3/3/633118BD-6C3D-45A4-B985-F0FDFFE1B021/EN/7000.0.081212-1400_client_en-us_Ultimate-GB1CULXFRE_EN_DVD.iso 語言包:http://dl.pconline.com.cn/download/53202-1.html 沒找到官方下載地址,就用這個吧. 還有幾個Server服務器的版本:(cd-key) 標準版/企業版/數據中心版(2850.0MB): http://download.microsoft.com/download/0/E/7/0E76E1CE-BFB2-475B-B14C-3445E6C27B4E/7000.0.081212-1400_server_en-us-GB1SXFRE_EN_DVD.iso Web版(2724.3MB): http://download.microsoft.com/download/0/E/7/0E76E1CE-BFB2-475B-B14C-3445E6C27B4E/7000.0.081212-1400_serverweb_en-us-GB1WXFRE_EN_DVD.iso 安騰版(2512.2MB): http://download.microsoft.com/download/0/E/7/0E76E1CE-BFB2-475B-B14C-3445E6C27B4E/7000.0.081212-1400_serverenterpriseia64_en-us-GB1SIAIFRE_EN_DVD.iso 此次發布的測試版可免費試用30天,輸入以下相應序列號后可以一直使用到今年8月1日。 標準版:2T88R-MBH2C-M7V97-9HVDW-VXTGF 企業版:TFGPQ-J9267-T3R9G-99P7B-HXG47 數據中心版:GQJJW-4RPC9-VGW22-6VTKV-7MCC6 Web版:GT8BY-FRKHB-7PB8W-GQ7YF-3DXJ6 安騰版:CQ936-9K2T8-6GPRX-3JR9T-JF4CJ 如果不能下的話,就是之前有個高手猜測為什么微軟要提前泄露的原因,服務器不能承受全世界的人下載的原因,我剛才截取的圖。  
未獲取函數指針就調用函數(如直接連接mswsock..lib并直接調用AcceptEx)的消耗是很大的,因為AcceptEx 實際上是存在于Winsock2結構體系之外的。每次應用程序常試在服務提供層上(mswsock之上)調用AcceptEx時,都要先通過WSAIoctl獲取該函數指針。如果要避免這個很影響性能的操作,應用程序最好是直接從服務提供層通過WSAIoctl先獲取這些APIs的指針。 奇跡世界 network 類里面就進行指針獲取 void MsWinsockUtil::LoadExtensionFunction( SOCKET ActiveSocket ) { //AcceptEx 竊薦 啊廉坷扁 (dll俊輯..) GUID acceptex_guid = WSAID_ACCEPTEX; LoadExtensionFunction( ActiveSocket, acceptex_guid, (void**) &m_lpfnAccepteEx); //TransmitFile 竊薦 啊廉坷扁 (dll俊輯..) GUID transmitfile_guid = WSAID_TRANSMITFILE; LoadExtensionFunction( ActiveSocket, transmitfile_guid, (void**) &m_lpfnTransmitFile); //GetAcceptExSockaddrs 竊薦 啊廉坷扁 GUID guidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; LoadExtensionFunction( ActiveSocket, guidGetAcceptExSockaddrs, (void**) &m_lpfnGetAcceptExSockAddrs); //DisconnectEx 竊薦 啊廉坷扁 GUID guidDisconnectEx = WSAID_DISCONNECTEX; LoadExtensionFunction( ActiveSocket, guidDisconnectEx, (void**) &m_lpfnDisconnectEx ); } bool MsWinsockUtil::LoadExtensionFunction( SOCKET ActiveSocket, GUID FunctionID, void **ppFunc ) { DWORD dwBytes = 0; if (0 != WSAIoctl( ActiveSocket, SIO_GET_EXTENSION_FUNCTION_POINTER, &FunctionID, sizeof(GUID), ppFunc, sizeof(void *), &dwBytes, 0, 0)) { return false; } return true; } LPFN_ACCEPTEX MsWinsockUtil::m_lpfnAccepteEx = NULL; LPFN_TRANSMITFILE MsWinsockUtil::m_lpfnTransmitFile = NULL; LPFN_GETACCEPTEXSOCKADDRS MsWinsockUtil::m_lpfnGetAcceptExSockAddrs = NULL; LPFN_DISCONNECTEX MsWinsockUtil::m_lpfnDisconnectEx = NULL; 收包和發包循環: 服務器需要進行的連接如下: 1、 與其他服務器連接 2、監聽綁定端口 這個2個內容都封裝進SESSION內里面,通過NETWORKOBJECT對象判斷該進行哪部分的包處理 if( !pIOCPServer->Init( &desc, 1 ) ) 根據參數&desc ,對完成端口進行設置 內容有:創建 io_thread(工作者線程), accept_thread(綁定端口),connect_thread(連接其他服務器), send_thread(收包線程),并根據連接的最大數目分配好session pool。 if( !pIOCPServer->StartListen( CLIENT_IOHANDLER_KEY, "127.0.0.1", 6000 ) ) { printf( "監聽出錯" ); return 0; } pIOCPServer->Connect( CLIENT_IOHANDLER_KEY, pNetObj, "127.0.0.1", 7000 ); 收包: pIOCPServer->Update() ---------》 IOHANDLER_MAP_ITER it->second->Update() ----------》 VOID IoHandler::Update() { ProcessActiveSessionList(); if( !m_pAcceptedSessionList->empty() ) { ProcessAcceptedSessionList(); } if( !m_pConnectSuccessList->empty() ) { ProcessConnectSuccessList(); } if( !m_pConnectFailList->empty() ) { ProcessConnectFailList(); } KickDeadSessions(); } 收包循環 if( !pSession->ProcessRecvdPacket( m_dwMaxPacketSize ) ) { pSession->Remove(); } 發包循環 unsigned __stdcall send_thread( LPVOID param ) { IOCPServer *pIOCPServer = (IOCPServer*)param; IOHANDLER_MAP_ITER it; while( !pIOCPServer->m_bShutdown ) { Sleep( 10 ); for( it = pIOCPServer->m_mapIoHandlers.begin(); it != pIOCPServer->m_mapIoHandlers.end(); ++it ) { it->second->ProcessSend(); } } return 0; }
d、接受SOCKET連接并進行完成端口綁定 VOID IoHandler::ProcessAcceptedSessionList() { SESSION_LIST_ITER it; Session *pSession; // 立加俊 己傍茄 技記甸闌 罐酒敵 烙矯 府膠飄肺 顆辮 m_pAcceptedSessionList->Lock(); m_pTempList->splice( m_pTempList->end(), *m_pAcceptedSessionList );//將m_pAcceptedSessionList 合并到TEMPLIST m_pAcceptedSessionList->Unlock(); // 立加俊 己傍茄 技記俊 措茄 貿府 for( it = m_pTempList->begin(); it != m_pTempList->end(); ++it ) { pSession = *it; // 彌絆悼立薦甫 檬苞竅綽 版快 角菩 if( m_numActiveSessions >= m_dwMaxAcceptSession ) { printf( "connection full! no available accept socket!\n" ); m_pTempList->erase( it-- ); ReuseSession( pSession ); continue; } // IOCP綁定 CreateIoCompletionPort( (HANDLE)pSession->GetSocket(), m_hIOCP, (ULONG_PTR)pSession, 0 ); // Recv俊 角菩竅綽 版快 貿府 if( !pSession->PreRecv() ) { m_pTempList->erase( it-- ); ReuseSession( pSession ); continue; } //-------------------------------- // 己傍利欄肺 立加等 技記 貿府 //-------------------------------- // 匙飄虧 坷宏璃飄 積己 夸沒 NetworkObject *pNetworkObject = m_fnCreateAcceptedObject(); assert( pNetworkObject ); // 匙飄虧 坷宏璃飄 官牢爹 pSession->BindNetworkObject( pNetworkObject ); // 立加矯 檬扁拳 棺 NetworkObject肺 立加 烹瘤 pSession->OnAccept(); // 悼立薦 劉啊 ++m_numActiveSessions; } if( !m_pTempList->empty() ) { // 立加俊 己傍茄 技記甸闌 ActiveSessionList俊 眠啊 m_pActiveSessionList->Lock(); m_pActiveSessionList->splice( m_pActiveSessionList->begin(), *m_pTempList ); m_pActiveSessionList->Unlock(); } } PreRecv() 的動作判斷SOCKET是否繼續有效 BOOL Session::PreRecv() { WSABUF wsabuf; m_pRecvBuffer->GetRecvParam( (BYTE**)&wsabuf.buf, (int&)wsabuf.len ); ZeroMemory( &m_recvIoData, sizeof(OVERLAPPEDEX) ); m_recvIoData.dwOperationType = RECV_POSTED; int ret = WSARecv( GetSocket(), &wsabuf, 1, &m_recvIoData.dwIoSize, &m_recvIoData.dwFlags, &m_recvIoData, NULL ); if( ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING ) { return FALSE; } return TRUE; }
b、代碼實現連接 連接每個服務器都用繼承自ServerSession 的類實現 有如下類 AgentServerSession BattleServerSession FieldServerSession GameDBProxySession GuildServerSession MasterServerSession 基類ServerSession 有 update 實現心跳連接 VOID ServerSession::Update() { if( IsForConnect() ) { // heartbeat 焊郴扁 DWORD dwCurTick = GetTickCount(); if( dwCurTick - m_dwLastHeartbeatTick > 10000 ) { m_dwLastHeartbeatTick = dwCurTick; MSG_HEARTBEAT msg; msg.m_byCategory = 0; msg.m_byProtocol = SERVERCOMMON_HEARTBEAT; Send( (BYTE*)&msg, sizeof(MSG_HEARTBEAT) ); } } } 每個Session要連接服務器的時候 VOID GameDBProxySession::OnConnect( BOOL bSuccess, DWORD dwSessionIndex ) { ServerSession::OnConnect( bSuccess, dwSessionIndex ); if( bSuccess ) { ServerSession::SendServerType(); g_pGameServer->ConnectTo( AGENT_SERVER ); } else { //SUNLOG( eFULL_LOG, "Can't connect to game DB proxy." ); } } VOID GameServer::ConnectTo( eSERVER_TYPE eServerType ) { switch( eServerType ) { case MASTER_SERVER: ConnectToServer( m_pMasterServerSession, (char*)m_pMasterServerSession->GetConnectIP().c_str(), m_pMasterServerSession->GetConnectPort() ); break; case GAME_DBPROXY: ConnectToServer( m_pGameDBProxySession, (char*)m_pGameDBProxySession->GetConnectIP().c_str(), m_pGameDBProxySession->GetConnectPort() ); break; case AGENT_SERVER: ConnectToServer( m_pAgentServerSession, (char*)m_pAgentServerSession->GetConnectIP().c_str(), m_pAgentServerSession->GetConnectPort() ); break; case GUILD_SERVER: ConnectToServer( m_pGuildServerSession, (char*)m_pGuildServerSession->GetConnectIP().c_str(), m_pGuildServerSession->GetConnectPort() ); break; default: ASSERT( !"弊繁 輯滾 鷗澇籃 絕絹夸" ); } } DWORD GameServer::ConnectToServer( NetworkObject * pNetworkObject, char * pszIP, WORD wPort ) { return m_pIOCPServer->Connect( SERVER_IOHANDLER, pNetworkObject, pszIP, wPort ); } DWORD IOCPServer::Connect( DWORD dwIoHandlerKey, NetworkObject *pNetworkObject, char *pszIP, WORD wPort ) { if( pNetworkObject == NULL ) return 0; IOHANDLER_MAP_ITER it = m_mapIoHandlers.find( dwIoHandlerKey ); assert( it != m_mapIoHandlers.end() ); return it->second->Connect( pNetworkObject, pszIP, wPort ); } c、代碼實現監聽 VOID GameServer::StartListen() { SERVER_ENV * pServerEnv = m_pFileParser->GetServerEnv(); if( !m_pIOCPServer->IsListening( SERVER_IOHANDLER ) ) { DISPMSG( "[GameServer::StartListen] Starting listen(%s:%d)...\n", pServerEnv->ServerIoHandler.szIP, pServerEnv->ServerIoHandler.wPort ); if( !m_pIOCPServer->StartListen( SERVER_IOHANDLER, pServerEnv->ServerIoHandler.szIP, pServerEnv->ServerIoHandler.wPort ) ) { DISP_FAIL; return ; } DISP_OK; } } BOOL IOCPServer::StartListen( DWORD dwIoHandlerKey, char *pIP, WORD wPort ) { IOHANDLER_MAP_ITER it = m_mapIoHandlers.find( dwIoHandlerKey ); assert( it != m_mapIoHandlers.end() ); return it->second->StartListen( pIP, wPort ); }
1、服務器內容 a、不同機器上的分為 DBProxy //數據庫 Guild //公會數據 Master //主服務器 Agent //副本服務器 4種服務器,代碼提供了很清晰的每個服務器的HANDLER FUNC TABLE(HASH)。 class PacketHandler : public Singleton<PacketHandler> { typedef VOID (*fnHandler)( CScence * pScence, GamePackHeader * pMsg, WORD wSize ); //typedef VOID (*fnHandler_CG)( Player * pPlayer, GamePackHeader * pMsg, WORD wSize ); public: PacketHandler(); ~PacketHandler(); BOOL RegisterHandler_DG(); //BOOL RegisterHandler_CG(); BOOL RegisterHandler_GM(); BOOL RegisterHandler_AG(); BOOL RegisterHandler_Actor(); VOID ParsePacket_DG( CScence * pScence, GamePackHeader * pMsg, WORD wSize ); //VOID ParsePacket_CG( Player * pPlayer, GamePackHeader * pMsg, WORD wSize ); VOID ParsePacket_GM( CScence * pScence, GamePackHeader * pMsg, WORD wSize ); VOID ParsePacket_AG( CScence * pScence, GamePackHeader * pMsg, WORD wSize ); VOID ParsePacket_Actor( CScence * pScence, GamePackHeader * pMsg, WORD wSize ); private: BOOL AddHandler_DG( BYTE category, BYTE protocol, fnHandler fnHandler ); //BOOL AddHandler_CG( BYTE category, BYTE protocol, fnHandler_CG fnHandler ); BOOL AddHandler_GM( BYTE category, BYTE protocol, fnHandler fnHandler ); BOOL AddHandler_AG( BYTE category, BYTE protocol, fnHandler fnHandler ); BOOL m_FunctionMap_Acotr( BYTE category, BYTE protocol, fnHandler fnHandler ); struct FUNC_DG : public BASE_FUNC { fnHandler m_fnHandler; }; struct FUNC_GM : public BASE_FUNC { fnHandler m_fnHandler; }; struct FUNC_AG : public BASE_FUNC { fnHandler m_fnHandler; }; struct FUNC_ACTOR : public BASE_FUNC { fnHandler m_fnHandler; }; FunctionMap m_FunctionMap_DG; FunctionMap m_FunctionMap_CG; FunctionMap m_FunctionMap_GM; FunctionMap m_FunctionMap_AG; FunctionMap m_FunctionMap_Actor; }; CPP。 #include "PacketHandler.h" PacketHandler::PacketHandler() { } PacketHandler::~PacketHandler() { } BOOL PacketHandler::RegisterHandler_DG() { //#define HANDLER_DG( c, p ) if( !AddHandler_DG( c, p, Handler_DG_CHARINFO::On##p ) ) return FALSE return TRUE; } BOOL PacketHandler::RegisterHandler_Actor() { #define HANDLER_GZ( c, p ) if( !AddHandler_Actor( c, p, Handler_GZ_GUILD::On##p ) ) return FALSE return TRUE; } BOOL PacketHandler::RegisterHandler_GM() { //if( !AddHandler_GM( GM_CONNECTION, GM_CONNECTION_SERVER_INFO_CMD, Handler_GM::OnGM_CONNECTION_SERVER_INFO_CMD ) ) // return FALSE; //if( !AddHandler_GM( GM_OPERATION, GM_RELOAD_DATA_CMD, Handler_GM::OnGM_RELOAD_DATA_CMD ) ) // return FALSE; //if( !AddHandler_GM( SERVERCOMMON, SERVERCOMMON_SERVERSHUTDOWN_REQ, Handler_GM::OnSERVERCOMMON_SERVERSHUTDOWN_REQ ) ) // return FALSE; return TRUE; } BOOL PacketHandler::RegisterHandler_AG() { // CG_CHARINFO //if( !AddHandler_AG( CG_CHARINFO, CG_CHARINFO_SELECT_INFO_SYN, Handler_CG_CHARINFO::OnCG_CHARINFO_SELECT_INFO_SYN)) // return FALSE; return TRUE; } VOID PacketHandler::ParsePacket_Actor( CScence * pScence, GamePackHeader * pMsg, WORD wSize ) { if( 0xff == pMsg->m_byCategory ) { } FUNC_GZ * pFuncInfo = (FUNC_GZ *)m_FunctionMap_GZ.Find( MAKEWORD( pMsg->m_byCategory,pMsg->m_byProtocol ) ); if( NULL == pFuncInfo ) { //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_GZ] PacketType Error GZ!!"); return ; } pFuncInfo->m_fnHandler( pScence, pMsg, wSize ); } VOID PacketHandler::ParsePacket_DG( CScence * pScence, GamePackHeader * pMsg, WORD wSize ) { if( 0xff == pMsg->m_byCategory ) { } FUNC_DG * pFuncInfo = (FUNC_DG *)m_FunctionMap_DG.Find( MAKEWORD( pMsg->wType,pMsg->m_byProtocol ) ); if( NULL == pFuncInfo ) { //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_DG] PacketType Error DG!!"); return ; } pFuncInfo->m_fnHandler( pScence, pMsg, wSize ); } VOID PacketHandler::ParsePacket_GM( CScence * pScence, GamePackHeader * pMsg, WORD wSize ) { if( 0xff == pMsg->m_byCategory ) { } FUNC_GM * pFuncInfo = (FUNC_GM *)m_FunctionMap_GM.Find( MAKEWORD( pMsg->m_byCategory,pMsg->m_byProtocol ) ); if( NULL == pFuncInfo ) { //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_GM] PacketType Error!! GM"); return ; } pFuncInfo->m_fnHandler( pScence, pMsg, wSize ); } VOID PacketHandler::ParsePacket_AG( CScence * pScence, GamePackHeader * pMsg, WORD wSize ) { if( 0xff == pMsg->m_byCategory ) { } FUNC_AG * pFuncInfo = (FUNC_AG *)m_FunctionMap_AG.Find( MAKEWORD( pMsg->m_byCategory,pMsg->m_byProtocol ) ); if( NULL == pFuncInfo ) { //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_AG] PacketType Error!! AG Category[%d] Protocol[%d] ", pMsg->m_byCategory,pMsg->m_byProtocol); return ; } pFuncInfo->m_fnHandler( pScence, pMsg, wSize ); } BOOL PacketHandler::AddHandler_Acotr( BYTE category, BYTE protocol, fnHandler fnHandler) { FUNC_ACTOR * pFuncInfo = new FUNC_ACTOR; pFuncInfo->m_dwFunctionKey = MAKEWORD( category, protocol ); pFuncInfo->m_fnHandler = fnHandler; return m_FunctionMap_Actor.Add( pFuncInfo ); } BOOL PacketHandler::AddHandler_DG( BYTE category, BYTE protocol, fnHandler fnHandler) { FUNC_DG * pFuncInfo = new FUNC_DG; pFuncInfo->m_dwFunctionKey = MAKEWORD( category, protocol ); pFuncInfo->m_fnHandler = fnHandler; return m_FunctionMap_DG.Add( pFuncInfo ); } BOOL PacketHandler::AddHandler_GM( BYTE category, BYTE protocol, fnHandler fnHandler) { FUNC_GM * pFuncInfo = new FUNC_GM; pFuncInfo->m_dwFunctionKey = MAKEWORD( category, protocol ); pFuncInfo->m_fnHandler = fnHandler; return m_FunctionMap_GM.Add( pFuncInfo ); } BOOL PacketHandler::AddHandler_AG( BYTE category, BYTE protocol, fnHandler fnHandler) { FUNC_AG * pFuncInfo = new FUNC_AG; pFuncInfo->m_dwFunctionKey = MAKEWORD( category, protocol ); pFuncInfo->m_fnHandler = fnHandler; return m_FunctionMap_AG.Add( pFuncInfo ); } 值得注意的是此類是singleton,這樣只能實例化一次,帶來的好處就是沒有多個實例造成的代碼泛濫 b、代碼實現
上次已經繪制過基本圖元了, 這次只不過要貼張圖而已..... 本來我想用Graphics的Model渲染流程來做, 不過這一層太高級了, 都是什么場景管理資源映射之類的 做低級的事情, 就要用低級的API嘛 圖形渲染的底層是CoreGraphics, 這個層我不打算再單獨寫(翻譯)一篇了, 因為都是Direct3D概念的一些抽象. 也就是說D3D用熟了基本上一看就明白(用GL的我就不清楚啦, 嘿嘿, N3的作者都放棄用GL去實現@_@). 還記得D3D Tutorial中的Textured例子不? 需要的東西有帶紋理坐標的點, 紋理. N3中也一樣, 不過, 這里沒法用固定管線了. N3的設計的時候就放棄了固定管線(多么明智呀, 別噴我-_-, 我只會shader.......), 所以在這之前我們要先寫一個shader來進行繪制. 因為我們只是進行簡單的演示, 就盡量簡單了, 寫一個2D的紋理繪制, 你可以用來做UI: - //------------------------------------------------------------------------------
- // texture2d.fx
- // texture shader for 2D(UI)
- // (C) xoyojank
- //------------------------------------------------------------------------------
-
- float2 halfWidthHeight : HalfWidthHeight;
- texture diffMap : DiffMap0;
- sampler diffMapSampler = sampler_state
- {
- Texture = <diffMap>;
- AddressU = Clamp;
- AddressV = Clamp;
- MinFilter = Point;
- MagFilter = Point;
- MipFilter = None;
- };
-
- struct VS_INPUT
- {
- float3 pos : POSITION;
- float2 uv : TEXCOORD;
- };
-
- struct VS_OUTPUT
- {
- float4 pos : POSITION;
- float2 uv : TEXCOORD;
- };
-
- //------------------------------------------------------------------------------
- /**
- */
- VS_OUTPUT
- VertexShaderFunc(VS_INPUT input)
- {
- VS_OUTPUT output;
-
- output.pos.xy = float2(input.pos.x - halfWidthHeight.x, halfWidthHeight.y - input.pos.y) / halfWidthHeight;
- output.pos.zw = float2(input.pos.z, 1.0f);
- output.uv = input.uv;
-
- return output;
- }
-
- //------------------------------------------------------------------------------
- /**
- */
- float4
- PixelShaderFunc(float2 uv : TEXCOORD0) : COLOR
- {
- return tex2D(diffMapSampler, uv);
- }
-
- //------------------------------------------------------------------------------
- /**
- */
- technique Default
- {
- pass p0
- {
- ColorWriteEnable = RED|GREEN|BLUE|ALPHA;
- ZEnable = False;
- ZWriteEnable = False;
- StencilEnable = False;
- FogEnable = False;
- AlphaBlendEnable = True;
- SrcBlend = SrcAlpha;
- DestBlend = InvSrcAlpha;
- AlphaTestEnable = False;
- ScissorTestEnable = False;
- CullMode = CW;
- VertexShader = compile vs_3_0 VertexShaderFunc();
- PixelShader = compile ps_3_0 PixelShaderFunc();
- }
- }
值得一提的是CullMode = CW, 為什么? 因為N3用的右手坐標系, 這點又跟D3D不一樣了........為什么呢? 難道寫MAYA跟MAX的插件的時候比較省事? 還是要跟上一次一樣設置頂點格式并載入VertexBuffer: - // vertex
- Array<VertexComponent> vertexComponents;
- vertexComponents.Append(VertexComponent(VertexComponent::Position, 0, VertexComponent::Float3));
- vertexComponents.Append(VertexComponent(VertexComponent::TexCoord, 0, VertexComponent::Float2));
- float vertex[4][5] = {
- {0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
- {0.0f, 256.0f, 0.0f, 0.0f, 1.0f},
- {256.0f,0.0f, 0.0f, 1.0f, 0.0f},
- {256.0f,256.0f, 0.0f, 1.0f, 1.0f}
- };
- vertexBuffer = VertexBuffer::Create();
- Ptr<MemoryVertexBufferLoader> vbLoader = MemoryVertexBufferLoader::Create();
- vbLoader->Setup(vertexComponents, 4, vertex, 4 * 5 * sizeof(float));
- vertexBuffer->SetLoader(vbLoader.upcast<ResourceLoader>());
- vertexBuffer->Load();
- vertexBuffer->SetLoader(NULL);
紋理的創建其實跟頂點差不多, 因為它都是屬于資源的一種, 詳見Nebula3資源子系統 - // texture
- texture = Texture::Create();
- texture->SetResourceId(ResourceId("bin:razor.jpg"));
- texture->SetLoader(StreamTextureLoader::Create());
- texture->Load();
- texture->SetLoader(NULL);
shader的加載跟上一次一樣, 只是參數不同: - // shader
- this->shaderInstance = this->shaderServer->CreateShaderInstance(ResourceId("shd:texture2d"));
- Ptr<ShaderVariable> halfWidthHeight = this->shaderInstance->GetVariableBySemantic(ShaderVariable::Semantic("HalfWidthHeight"));
- float2 halfWH = float2(this->renderDevice->GetDefaultRenderTarget()->GetWidth(), this->renderDevice->GetDefaultRenderTarget()->GetHeight()) * 0.5f;
- halfWidthHeight->SetFloatArray(&halfWH.x(), 2);
- Ptr<ShaderVariable> diffMap = this->shaderInstance->GetVariableBySemantic(ShaderVariable::Semantic("DiffMap0"));
- diffMap->SetTexture(texture);
繪制嘛, 當然改成矩形了, 圖片可貼不到一跟線上: - this->renderDevice->BeginFrame();
- this->renderDevice->BeginPass(this->renderDevice->GetDefaultRenderTarget(), this->shaderInstance);
- PrimitiveGroup primGroup;
- primGroup.SetBaseVertex(0);
- primGroup.SetNumVertices(4);
- primGroup.SetPrimitiveTopology(PrimitiveTopology::TriangleStrip);
-
- this->renderDevice->SetVertexBuffer(this->vertexBuffer);
- this->renderDevice->SetPrimitiveGroup(primGroup);
- this->renderDevice->Draw();
- this->renderDevice->EndPass();
- this->renderDevice->EndFrame();
- this->renderDevice->Present();
上圖: 
圖形子系統是渲染層中圖形相關子系統的最高層. 它基本上是Mangalore圖形子系統的下一個版本, 但是現在整合進了Nebula, 并且與低層的渲染代碼結合得更加緊密. 最基本的思想是實現一個完全自治的圖形”世界”, 它包含模型, 燈光, 還有攝像機實體, 而且只需要與外部世界進行最少的通信. 圖形世界的最主要操作是加入和刪除實體, 還有更新它們的位置. 因為Mangalore的圖形子系統跟Nebula2的完全分界線從Nebula3中移除了, 很多設想都可以用更少的代碼和交互來實現. 圖形子系統也會為了異步渲染而多線程化, 它和所有的底層渲染子系統都會生存在它們自己的fat-thread中. 這本應是Nebula3層次結構中更高級的東西, 但是我選擇了這個位置, 因為這是游戲跟渲染相關通信最少的一部分代碼. 正是因為圖形代碼有了更多的”自治權”, 游戲相關的代碼可以跟圖形以完全不同的幀率來運行, 不過這需要實踐來證明一下. 但是我一定會嘗試, 因為完全沒有必要讓游戲邏輯代碼運行在10幀以上(格斗游戲迷們可能會反對吧). 圖形子系統中最重要的公有類有: - ModelEntity
- CameraEntity
- LightEntity
- Stage
- View
一個ModelEnity表示了一個可見的圖形對象, 它包括位置, 包圍體和內嵌的Model資源. 一個Model資源是一個完全的3D模型, 包括幾何體, 材質, 動畫, 層級變換等…(后面會提到). 一個CameraEntity描述了圖形世界中的一個視景體, 為渲染提供View和Project矩陣. 一個LightEntity描述了一個動態光源. Nebula3的光源屬性還沒有最終確定, 但是我的目標是一個相對靈活地近似(最后一個光源不會超過幾個shader參數). Stage和View是Nebula3圖形子系統新增的內容. 在Mangalore中, 圖形實體是生存在一個單獨的圖形Level類里, 任何時候只能有一個Level和一個攝像機. 這對于只需要渲染一個世界到幀緩存(frame buffer)的情況來說還是不錯的. 但許多游戲程序需要更復雜的渲染, 如在GUI中渲染一個使用單獨燈光的3D對象, 而它又跟其它的圖形世界是隔離的. 還有反射或像監視器之類的東西都需要一個額外的視口, 諸如此類. 在Mangalore中, 這個問題通過OffscreenRenderer類得到解決, 雖說比較容易使用, 但是具有一些使用限制并且需要更多事后的思考. Nebula3提供了一個基于State和View的更加簡潔的解決方案. 一個Stage就是一個圖形實體的容器, 表示一個圖形世界. 同一時間可能存在多個Stage, 但是它們之間是互相隔絕的. 每個實體在一個時刻只連接到了一個Stage(雖說克隆一個已有實體是一件很簡單的事情). 除了簡單地把實體組織到一起外, Stage的主要工作是根據它們之間的關系來加速可見性查詢. 應用程序可以派生Stage的子類來實現完全不同的可見性查詢方案. 一個View對象通過一個CameraEnity渲染stage到一個RenderTarget. 任何stage都可以連接任意數量的View對象. View對象可能會互相依賴(也可能是連接到不同stage的View), 所以更新一個View會首先強制更新另一個View的RenderTarget(這在一個View渲染需要使用另一個View的RenderTarget做為紋理時很方便). View對象完全實現了自己的渲染循環. 應用程序可以在View的子類中方便地實現它自己的渲染策略(如每個light一個pass VS 每個pass多個light, 渲染到cubemap, 等等). 總而言之, 一個Stage完全控制了可見性查詢流程, 而一個View則完全控制了渲染流程. 圖形子系統的一個最主要的工作就是根據可見性查詢的結果來決定哪些實體需要被渲染. 一個可見性查詢在實體間建立了一個雙向的鏈接, 它有兩種形式: 攝像機鏈接和燈光鏈接. 攝像機鏈接把一個攝像機和在它視景體內的模型連接到了一起. 因為鏈接是雙向的, 所以攝像機知道所有的在它視景體范圍內的模型, 而模型也知道所有可以看到它的攝像機. 燈光鏈接在燈光與模型之間建立了相似的關系, 一個燈光具有所有受它影響的模型的鏈接, 一個模型也知道所有影響它的燈光. 加速可見性查詢最重要的類就是Cell類. 一個Cell是一個圖形實體和子Cell的可見性容器, 它必須遵循2條簡單的規則: - 如果一個Cell是完全可見的, 那么它所有的圖形實體和子Cell都必須可見.
- 如果一個Cell是完全不可見的, 那么它所有的圖形實體和子Cell都必須不可見.
Cell是附屬于Stage的, 它們形成了一棵有根Cell的樹形層次結構. 標準的Cell支持簡單的空間劃分方案, 如四叉樹和八叉樹, 但如果像其它的可見性方案, 如portal, 就需要派生Cell的子類來實現了. 子類唯一的功能限制就是上面標出的那兩條規則. 當一個圖形體連接到一個Stage時, 它會被插入”接受” (通常僅僅是容納)它的最低級的Cell中. 當更新圖形實體的變換信息或改變包圍體時, 它會根據需要改變在Cell層次中的位置. Stage居住在StageBuilder類當中, 應用程序應當派生StageBuilder來創建一個Stage的初始狀態(通過加入Cell和實體). Nebula3會提供一些標準的StageBuilder集合, 這應該能夠滿足大多數應用程序的需要了. 這只是圖形子系統的一個粗略的概述. 因為當前只有一個最基本的實現, 很多細節接下來可能會有所更改.
跟N2比起來, N3的資源子系統更加開放, 給予了程序員對資源的創建和管理更多的控制. Nebula3的資源有下面向個屬性: - 包裝了一些其它Nebula子系統需要的數據
- 可以用ResourceId共享
- 可以在任何時候加載(初始化)和卸載
- 可以同步或異步加載
例如典型的圖形資源有網格和紋理, 但資源子系統并不局限于圖形資源. 資源子系統有兩個操作層次( 可能以后會把他們放入兩個不同的命名空間, 現在他們都是在Resources命名空間下 ): 低層提供了真正的資源對象, 處理資源的共享, 加載和(次要的)保存. 低層的資源類有: - ResourceId
- Resource
- ResourceLoader
- ResourceSaver
- SharedResourceServer.
高層資源子系統提供了資源管理, 這意味著根據用戶的反饋動態的加載和卸載資源. 高層資源子系統的類有: - ResourceProxy (又名: ManagedResource)
- ResourceProxyServer (又名: ResourceManager)
- ResourceMapper
下面說明資源子系統的各個類是怎么協同工作的: 一個ResourceId是一個唯一的資源標識符. ResourceId用來共享和定位磁盤上的數據(或者資源保存在哪). ResouceId是一些原子字符串(string atoms). Atom是一個常量字符串的唯一32-bit標識符, 這可以大大加快拷貝和比較, 并且可以減少內存占用, 因為標識符字符串只保存一份. 為了定位磁盤上的數據, ResourceId常常分解成一個合法的URL(例如一個ResourceId “texture:materials/granite.dds”, 會在運行時被分解成”file:///C:/Programme/[AppName]/export/textures/materials/granite.dds”. 一個Resource對象實際上是資源數據的容器. 像紋理和網格這樣特定的資源類型都是Resource類的子類, 并且實現了特定的接口. Resource子類通常都是平臺相關的(如D3D9Texture), 但是通過有條件的類型定義使其變成平臺無關的. 并不像Nebula2那樣, 資源對象并不知道怎樣去組織, 加載或保存自己. 取而代之的是, 一個合適的ResourceLoader或ResourceSaver必須附屬于Resource對象. 因為Nebula程序很少輸出數據, ResourceSaver只 是為了完整性而存在的. 換句話說, ResourceLoader是必須的, 因為他們是啟用Resource對象的唯一途徑. ResourceLoader具有整個資源裝載過程的完全控制. 它們可以是平臺相關的, 而且也許會依賴于相關聯的特定平臺的Resource類. 這使得程序員可以對資源的裝載過程相比Nebula2有更多的控制. 典型的資源加載類有StreadTextureLoader, MemoryVertexBufferLoader和MemoryIndexBufferLoader(從內存中加載頂點緩存和索引緩存). Resource類也提供了一個共同的接口用來同步和異步的資源加載. 同步加載可以這樣做: - res-> SetResourceId("tex:system/white.dds");
- res-> SetLoader(StreamTextureLoader::Create());
- res-> SetAsyncEnabled(false)
- res-> Load()
- if (res-> IsValid()) ... 這時資源加載已經成功了, 否則LoadFailed會返回true.
異步資源加載也很相似: - res->SetResourceId("tex:system/white.dds");
- res->SetLoader(StreamTextureLoader::Create());
- res->SetAsyncEnabled(true);
- res->Load();
- 資源這時進入等待狀態...
- 只要 IsPending() return true, 就要重復地調用Load()... 當然真正的程序會在這時做一些其他的事情
- 接下來的某個調用Load()后時刻, 資源的狀態要么是Valid(資源已經準備好了), Failed(資源加載失敗)或者Cancelled(等待中的資源被取消加載了)
一個應用程序甚至是Nebula3的渲染代碼通常都不需要關心這些, 因為資源管理層會處理他們, 并把異步加載的這些細節隱藏到資源代理后面. SharedResourceServer單件通過ResourceId來共享資源. 通過SharedResourceServer創建資源確保了每個資源只在內存中加載了一份, 而不管客戶端的數目. 如果客戶端的數目降低到了0, 資源會被自動卸載(這并不是合適的資源管理, 而應該是ResourceProxyServer應該關心的). 資源共享完全可以直接通過標準的Nebula3的創建機制來繞過. ResourceProxy(或ManagedResource)是對于實際資源對象的資源管理包裝. 它的思想是包含的資源對象會受資源用途反饋的控制. 例如, 一個紋理代理會在被請求的紋理在后臺加載時提供一個占位紋理, 屏幕上所有使用這個資源的物體都很小的話會被提供一張低分辨率的紋理, 一個X幀沒有被繪制的紋理會被卸載, 等等. ResourceProxyServer(或ResourceManager)單件是資源管理系統的前端. 除了管理附屬于它的ResourceMapper的工作外, 它還是ResourceProxy的工廠, 并且把ResourceMapper跟Resource類型聯系到了一起. ResourceMapper是一個有趣的東西. 一個ResourceMapper跟一種資源類型(如紋理或網格)相關聯, 并被應用程序依附到ResourceProxyServer. 它負責從渲染代碼的使用反饋來加載/卸載資源. ResourceMapper的子類可以實現不同的資源管理策略, 也可以通過派生特定的ResourceMapper和ResourceLoader來創建一個完全定制的平臺和應用相關的資源管理方案. 目標是顯而易見的, Nebula3提供了一些好用的ResourceMapper來加載需要的任何東西. 資源使用反饋是由渲染代碼寫入ResourceProxy對象的, 而且應該包含這個資源的一些信息:是否會在不久后用到, 是否可見, 并估計物體占用的屏幕空間大小. 特定的反饋依賴于ResourceProxy的子類, ResourceProxy中沒有公有的反饋方法. 基于資源的使用反饋, 一個ResourceMapper應該實現下面的一些操作(這取決于具體的mapper): - Load: 根據level-of-detail異步加載資源(如跳過不需要的高分辨率mipmap層次)
- Unload: 完全卸載資源, 釋放珍貴的內存
- Upgrade: 提高已加載資源的level-of-detail(如加載高分辨率的mipmap層次紋理)
- Degrade: 降低已加載資源的level-of-detail(如跟上面相反的情況)
Nebula3的代碼運行在兩種根本不同的方案中. 第一種方案我稱之為”Fat Thread”. 一個Fat Thread在一個線程中運行一個完整的子系統(如渲染, 音頻, AI, 物理, 資源管理), 并且基本上鎖定在一個特定的核心上. 第二種類型的線程我叫它”Job”. 一個job是一些數據和用于處理這些數據的包裝成C++對象的代碼. 工作調度程序掌管了Job對象, 并且把工作分配給低負載的核心來保持它們一直處于忙碌狀態. 顯然, 挑戰就是設計一個經過全面考慮的系統, 以保持所有的核心一直均勻地忙碌著. 這不但意味著連續的活動需要在游戲每幀的空閑時期內輪流交替, 而且要求job對象不得不事先(如每幀前)創建好, 這樣才能在各種Fat Thread空閑時填充當前幀的空白. 這是我希望進行更多試驗和調整的地方. 第二個挑戰就是讓程序員的工作盡量的簡單. 一個游戲應用程序員(邏輯程序員)在任何時候都不應該關心他運行在一個多線程的環境中, 不應該擔心會產生死鎖或改寫了其它線程的數據, 也不應該瞎搞一些臨界區, 事件和信號量. 同樣, 整個引擎的架構也不應該是”脆弱的”. 大部分傳統的多線程代碼在一定程度上都會發生紊亂, 或者忘記了臨界區而打亂數據. 當線程間需要進行數據共享和通信時, 多線程就變得很棘手. 像兩個臨界區這樣的解決方案也會導致脆弱代碼問題. 從大的角度來說, Nebula3通過一個”并行Nebula”的概念解決了這個兩個問題. 其思想就是運行了一個完整子系統的”Fat Thread”都有自己的最小Nebula運行庫, 這個最小運行庫剛好包含了這個子系統需要的部分. 因此, 如果這個運行在它自己線程中的子系統需要進行文件訪問, 它會有一個跟其它Fat Thread完全分離的文件服務器(file server). 這個解決方案的優點是, 大部分Nebula中的代碼都不需要知道它運行在一個多線程的環境中, 因為在fat thread之間沒有數據進行共享. 運行著的每個最小Nebula內核是跟其它Nebula內核完全隔離的. 缺點就是, 重復的數據會浪費一些內存, 但是我們只是占用幾KB, 而不是MB. 這些數據冗余消除了細密的鎖定, 并且解決把程序員從思考每一行代碼的多線程安全性中解放了出來. 當然, 從某種意義上說Fat Thread間的通信是肯定會發生的, 要不然這整個思想就沒有意義了. 方法就是建立一個且只有一個的標準通信系統, 并且保證這個通信系統是可靠而快速的. 這就是消息系統的由來. 要跟一個Fat Thread通信的話只有發送一個消息給它. 消息是一個簡單的C++對象, 它包含了一些帶有get/set方法的數據. 通過這個標準的通信手段, 實際上只有消息子系統才需要是線程安全的(同樣, 訪問跟消息相關的資源時, 如內存緩沖區, 必須受到約束, 因們它們代表了共享數據). (xoyojank: 我說咋那么多Message…) 這樣雖然解決了Fat Thread方案中大多數的多線程問題, 但沒有解決Job對象的任何事情. Nebula3很有可能需要約束一個Job對象能做什么和不能做什么. 最直接的行為就是限制job做內存緩沖區的計算. 那樣的話, job中就不能存在復雜的運行庫(不能文件I/O, 不能訪問渲染等等). 如果這樣還不夠的話, 必須定義一個”job運行時環境”, 就像Fat Thread中的那樣. 因為一個job不會發起它自己的線程, 而且還會被調度到一個已經存在的線程池中. 就這個方面來說, 這不存在什么問題. 到現在為止(xoyojank: 2007/01/21, 最新版本已經實現了多數子系統的多線程化), 只有IO子系統作為概念證明在Fat Thread中得到實現, 并且它運行得很今人滿意. 在做傳統的同步IO工作時, 一個Nebula3程序可以直接調用本地線程的IO子系統. 所以像列出文件夾的內容或刪除一個文件, 只會調用一個簡單的C++方法. 對于異步IO工作, 定義了一些常見的IO操作消息(如ReadStream, WriteStream, CopyFile, DeleteFile, 等等). 進行異步IO只需要幾行代碼: 創建一個消息對象, 填充數據, 并發送這個消息到一個IOInterface單件. 如果必要的話, 這可能會需要等待和輪詢異步操作. 這樣的好處就是, 整個IO子系統沒有一行多線程意義上的代碼, 因為各個在不同的Fat Thread中的IO子系統是完全隔離的(當然, 同步肯定會發生在一些IO操作上, 但那都留給操作系統了).
|