|
http://www.ayssss.cn/ 最近老有人問我是不是出了改dota英雄技能的外掛,什么月騎無限大,劍圣無限斬,巫妖無限彈之類。 我在這里一并回答一下,并且稍微說說原理。因?yàn)槲覍?duì)地圖方面其實(shí)是一竅不通,如果有說的不對(duì)的地方,還請(qǐng)指正。 其實(shí)這些現(xiàn)象都是使用了作弊地圖導(dǎo)致的。本來魔獸爭霸是有一個(gè)地圖驗(yàn)證的,如果你跟主機(jī)的圖不同,是進(jìn)不去的(要下載地圖)。但是魔獸對(duì)地圖中的war3map.j文件是進(jìn)行bcc(block check character)校驗(yàn)的,bcc不同于md5,bcc一般只是用來排錯(cuò)的,并不是加密算法。所以就有人寫出了這樣的代碼,可以在b文件末尾添加上一些不起作用的字串,來讓b文件的bcc校驗(yàn)碼等于a文件(具體代碼我就不貼出來了,很容易搜到)。于是呢,我們就可以做到隨意修改地圖中的war3map.j ,然后再處理一下,使之跟原來的war3map.j的bcc校驗(yàn)碼相同。再把改過并處理后的war3map.j文件替換原來的,這樣做出來的作弊地圖,暴雪的驗(yàn)證會(huì)因?yàn)閎cc校驗(yàn)相同,而把它認(rèn)為和原版圖是相同的。達(dá)到的效果就是,別人用正版圖建主機(jī),你可以進(jìn)入,你用盜版圖建主機(jī),別人用正版圖也可以進(jìn)入。但是別以為可以為所欲為的修改war3map.j ,雖然你突破了驗(yàn)證這一關(guān)進(jìn)入了游戲,但是魔獸的聯(lián)機(jī)機(jī)制是沒有辦法突破的。 在這里稍微談一下魔獸的聯(lián)機(jī)機(jī)制,沒興趣的請(qǐng)略過這一段。魔獸聯(lián)機(jī)時(shí),一直有個(gè)同步機(jī)制,每個(gè)聯(lián)機(jī)的玩家都會(huì)同時(shí)計(jì)算所有數(shù)據(jù),一旦有不一致,就會(huì)導(dǎo)致掉線,這也是為什么用金山游俠之類的游戲修改器單機(jī)時(shí)可以改錢,聯(lián)機(jī)時(shí)一改就掉線。因?yàn)槟阒荒苄薷哪阕约旱臋C(jī)器上的數(shù)據(jù),而無法改別人的,單方面修改的結(jié)果就是造成你跟其他人不同,你就會(huì)掉線。當(dāng)然,如果所有人同時(shí)修改的話,仍然是不會(huì)掉線的,所以現(xiàn)在有一些聯(lián)機(jī)修改器,參加游戲的幾個(gè)玩家一起開這個(gè)修改器,可以在玩rpg時(shí)改錢什么的,我?guī)讉€(gè)同學(xué)就老是用這種修改器來通關(guān)一些很難打的rpg圖。順便說一下,這樣玩下來保存的replay是無法正常播放的,因?yàn)閞eplay只記錄動(dòng)作,你使用修改器的改動(dòng)不會(huì)被記錄,播放replay時(shí)會(huì)因?yàn)槟悴⑽聪衲阌螒驎r(shí)那樣修改數(shù)據(jù),造成replay不合邏輯而出錯(cuò)。再順便說一下吧,為什么所謂的人品外掛并不能實(shí)現(xiàn)。曾有人發(fā)帖抱怨,怎么藍(lán)胖子次次放招都多重施法,怎么某人每次都暴擊,他們是不是用了人品掛。其實(shí)這是不可能的,有人以為魔獸中的隨機(jī)數(shù)據(jù)都是由主機(jī)計(jì)算的,這樣主機(jī)就可以找到辦法來修改隨機(jī)數(shù),造成每次都對(duì)他有利的結(jié)果。但是實(shí)際中并非如此,隨機(jī)數(shù)也是所有人一起計(jì)算的,也就是說魔獸里的隨機(jī)是個(gè)偽隨機(jī)。在一局游戲一開始時(shí),主機(jī)會(huì)發(fā)給每個(gè)玩家一個(gè)隨機(jī)數(shù)種子(這個(gè)種子很有可能就是主機(jī)從建立主機(jī)到游戲開始所經(jīng)歷的毫秒數(shù)),之后的一整局中,所有的隨機(jī)數(shù)都根據(jù)這個(gè)隨機(jī)數(shù)種子,依照事先定好的算法計(jì)算出來,這樣也就保證了所有人計(jì)算出同樣的“隨機(jī)”結(jié)果。另外,這個(gè)隨機(jī)種子也會(huì)記錄進(jìn)replay,這也從一個(gè)側(cè)面說明了魔獸里的隨機(jī)是偽隨機(jī),如果是真的隨機(jī),replay就無法重現(xiàn)了。說的有點(diǎn)多了,下面回到正題。 因?yàn)槟ЙF聯(lián)機(jī)機(jī)制的存在,你要是隨意改了war3map.j,例如改成給自己增加10000的錢,但是別人是按照的沒有修改的war3map.j,在別人機(jī)器中你是沒有那么多錢的。這時(shí)你買一個(gè)8000的物品,在你自己機(jī)器上是可以的,因?yàn)槟阌?0000的錢,但是在其他人機(jī)器上,你錢卻根本不夠!這樣的不合理動(dòng)作就會(huì)造成你跟其他人斷開連接。 也就是說,你只能修改那些不會(huì)造成沖突的地方。例如有些作弊圖可以顯示出地圖全開的效果,因?yàn)檫@些顯示的東西只是在你本地機(jī)器上顯示出來的,并不會(huì)對(duì)其他玩家照成沖突。類似這樣的修改都是可行的,不會(huì)掉線。 那么,為什么會(huì)出現(xiàn)這種有變態(tài)技能效果的dota作弊圖呢?我剛開始也很困惑,這么夸張的改動(dòng)怎么竟然沒有掉線?我跟朋友要了個(gè)作弊圖玩的replay,在我的機(jī)器上,用正版dota地圖播放,竟然完全再現(xiàn)了那些變態(tài)效果!因?yàn)槲覍?duì)地圖方面并不了解,所以開始上網(wǎng)找資料,并通過qq向某些搞地圖的高人請(qǐng)教,又下載了那個(gè)變態(tài)版dota作弊圖和某平臺(tái)私自山寨的所謂“原版”dota圖,提取出來war3map.j來進(jìn)行對(duì)比。經(jīng)過n久的努力,總算搞明白他是怎么改出來這種效果的了。 原來是因?yàn)閐ota使用到了game cache,而作弊圖是單方面修改了game cache中的數(shù)據(jù),然后通過函數(shù)同步給了所有的玩家。通俗點(diǎn)說,game cache相當(dāng)于一個(gè)池子,所有玩家共享這塊區(qū)域,任意一個(gè)玩家都可以修改這個(gè)池中的數(shù)據(jù),也可以發(fā)出通知,讓所有人都來同步這個(gè)池子,這樣就變相修改了其他人的數(shù)據(jù)。舉個(gè)例子,例如dota里黑曜石的放逐技能,它可以減少一個(gè)人的智力,一分鐘后再歸還給他,dota里關(guān)于這個(gè)技能的函數(shù),把目標(biāo)和要?dú)w還的智力值記錄在game cache中,1分鐘之后會(huì)再從game cache取出目標(biāo)和智力值,給目標(biāo)加上相應(yīng)的智力值,就完成了歸還這個(gè)人的智力的過程。但是在作弊圖中,這里增加了代碼,先進(jìn)行一個(gè)判斷,如果黑曜石是本機(jī)玩家,會(huì)把game cache中記錄的目標(biāo)改成本方隨機(jī)的一個(gè)隊(duì)友,然后把game cache中記錄的智力值改為500,然后通知所有玩家同步game cache中的這兩個(gè)值,這樣就完成了對(duì)所有人game chche中這兩個(gè)值的修改。1分鐘一到,dota就會(huì)向這個(gè)目標(biāo)“歸還”智力,這樣,本方的一個(gè)玩家就憑空增加了500智力。(那個(gè)被減少智力的倒霉玩家就無法被歸還了,可憐) 大致的原理就是這樣了,具體細(xì)節(jié)我就不詳細(xì)敘述了。不過dota用到game cache的地方其實(shí)并不多,所以能改的地方也就那幾個(gè)。這也是為什么作弊圖要專門改這幾個(gè)地方,而不是改成例如加錢或者加攻擊力或者直接勝利之類的,不是不想改,而是無法實(shí)現(xiàn)。另外,暴雪官方的地圖是不會(huì)這樣使用game cache的,所以不用擔(dān)心對(duì)戰(zhàn)地圖被改(另外對(duì)戰(zhàn)圖還有暴雪標(biāo)志的保護(hù))。其他的rpg地圖,如果本身沒有用到game cache的,也就改不出來什么花樣,最多顯示個(gè)全圖之類。 暴雪將會(huì)在1.23修補(bǔ)這個(gè)地圖驗(yàn)證漏洞,目前1.23的補(bǔ)丁已經(jīng)在測試中了,相信升級(jí)之后,這種改圖作弊將不復(fù)存在。只是不知國內(nèi)玩家到時(shí)是不是還要繼續(xù)死守bug頻出的1.20呢? 強(qiáng)行插入廣告一則:浩方平臺(tái)會(huì)再對(duì)地圖進(jìn)行自己的驗(yàn)證,md5驗(yàn)證,作弊圖是無法通過的。 至于做山寨dota圖的某平臺(tái)嘛,就我目前來看,它是沒有任何地圖驗(yàn)證的,唉。 應(yīng)廣大群眾強(qiáng)烈要求,這里給出山寨版dota 6.57c的作弊圖鏈接地址,請(qǐng)大家自行圍觀(話說我參照這個(gè)做出了58b和59c的作弊圖,活活活): http://sc2dota.com/news/310.html
新浪科技訊 北京時(shí)間1月10日消息,據(jù)國外媒體報(bào)道,由于大量用戶爭相排隊(duì)下載Windows 7 Beta導(dǎo)致服務(wù)器不堪重負(fù),微軟周五下午宣布延遲發(fā)布Windows 7 Beta。 微軟CEO史蒂夫·鮑爾默(Steve Ballmer)周三在消費(fèi)電子展(CES)上發(fā)表主題演講時(shí)宣布,將于本周五面向公眾發(fā)布Windows 7 Beta。但由于排隊(duì)下載的用戶過多,導(dǎo)致微軟服務(wù)器被擠爆。 微軟在Windows 7官方博客中稱:“由于用戶對(duì)Windows 7 Beta熱情較高,導(dǎo)致服務(wù)器超負(fù)荷運(yùn)轉(zhuǎn)。在發(fā)布Beta之前,我們將對(duì)Microsoft.com網(wǎng)站增加額外的硬件支持。” 微軟還稱:“我們將確保為用戶提供最佳的下載體驗(yàn),一旦發(fā)布Beta,我們會(huì)立即通知用戶。”周五早上,在微軟上傳Beta文件之前,就已經(jīng)有跡象表明微軟服務(wù)器不堪重負(fù)。按計(jì)劃,微軟只提供250萬份Windows 7 Beta下載。 剛下的,每秒1MB多啊(剛剛鏈接上的速度是10MB多,嚇了我一跳,等穩(wěn)定下來就1MB多) 補(bǔ)充兩個(gè)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 沒找到官方下載地址,就用這個(gè)吧. 還有幾個(gè)Server服務(wù)器的版本:(cd-key) 標(biāo)準(zhǔn)版/企業(yè)版/數(shù)據(jù)中心版(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 此次發(fā)布的測試版可免費(fèi)試用30天,輸入以下相應(yīng)序列號(hào)后可以一直使用到今年8月1日。 標(biāo)準(zhǔn)版:2T88R-MBH2C-M7V97-9HVDW-VXTGF 企業(yè)版:TFGPQ-J9267-T3R9G-99P7B-HXG47 數(shù)據(jù)中心版:GQJJW-4RPC9-VGW22-6VTKV-7MCC6 Web版:GT8BY-FRKHB-7PB8W-GQ7YF-3DXJ6 安騰版:CQ936-9K2T8-6GPRX-3JR9T-JF4CJ 如果不能下的話,就是之前有個(gè)高手猜測為什么微軟要提前泄露的原因,服務(wù)器不能承受全世界的人下載的原因,我剛才截取的圖。  
未獲取函數(shù)指針就調(diào)用函數(shù)(如直接連接mswsock..lib并直接調(diào)用AcceptEx)的消耗是很大的,因?yàn)锳cceptEx 實(shí)際上是存在于Winsock2結(jié)構(gòu)體系之外的。每次應(yīng)用程序常試在服務(wù)提供層上(mswsock之上)調(diào)用AcceptEx時(shí),都要先通過WSAIoctl獲取該函數(shù)指針。如果要避免這個(gè)很影響性能的操作,應(yīng)用程序最好是直接從服務(wù)提供層通過WSAIoctl先獲取這些APIs的指針。 奇跡世界 network 類里面就進(jìn)行指針獲取 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; 收包和發(fā)包循環(huán): 服務(wù)器需要進(jìn)行的連接如下: 1、 與其他服務(wù)器連接 2、監(jiān)聽綁定端口 這個(gè)2個(gè)內(nèi)容都封裝進(jìn)SESSION內(nèi)里面,通過NETWORKOBJECT對(duì)象判斷該進(jìn)行哪部分的包處理 if( !pIOCPServer->Init( &desc, 1 ) ) 根據(jù)參數(shù)&desc ,對(duì)完成端口進(jìn)行設(shè)置 內(nèi)容有:創(chuàng)建 io_thread(工作者線程), accept_thread(綁定端口),connect_thread(連接其他服務(wù)器), send_thread(收包線程),并根據(jù)連接的最大數(shù)目分配好session pool。 if( !pIOCPServer->StartListen( CLIENT_IOHANDLER_KEY, "127.0.0.1", 6000 ) ) { printf( "監(jiān)聽出錯(cuò)" ); 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(); } 收包循環(huán) if( !pSession->ProcessRecvdPacket( m_dwMaxPacketSize ) ) { pSession->Remove(); } 發(fā)包循環(huán) 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連接并進(jìn)行完成端口綁定 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(); // 立加俊 己傍茄 技記俊 措茄 貿(mào)府 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俊 角菩竅綽 版快 貿(mào)府 if( !pSession->PreRecv() ) { m_pTempList->erase( it-- ); ReuseSession( pSession ); continue; } //-------------------------------- // 己傍利欄肺 立加等 技記 貿(mào)府 //-------------------------------- // 匙飄虧 坷宏璃飄 積己 夸沒 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() 的動(dòng)作判斷SOCKET是否繼續(xù)有效 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、代碼實(shí)現(xiàn)連接 連接每個(gè)服務(wù)器都用繼承自ServerSession 的類實(shí)現(xiàn) 有如下類 AgentServerSession BattleServerSession FieldServerSession GameDBProxySession GuildServerSession MasterServerSession 基類ServerSession 有 update 實(shí)現(xiàn)心跳連接 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) ); } } } 每個(gè)Session要連接服務(wù)器的時(shí)候 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、代碼實(shí)現(xiàn)監(jiān)聽 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、服務(wù)器內(nèi)容 a、不同機(jī)器上的分為 DBProxy //數(shù)據(jù)庫 Guild //公會(huì)數(shù)據(jù) Master //主服務(wù)器 Agent //副本服務(wù)器 4種服務(wù)器,代碼提供了很清晰的每個(gè)服務(wù)器的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,這樣只能實(shí)例化一次,帶來的好處就是沒有多個(gè)實(shí)例造成的代碼泛濫 b、代碼實(shí)現(xiàn)
上次已經(jīng)繪制過基本圖元了, 這次只不過要貼張圖而已..... 本來我想用Graphics的Model渲染流程來做, 不過這一層太高級(jí)了, 都是什么場景管理資源映射之類的 做低級(jí)的事情, 就要用低級(jí)的API嘛 圖形渲染的底層是CoreGraphics, 這個(gè)層我不打算再單獨(dú)寫(翻譯)一篇了, 因?yàn)槎际荄irect3D概念的一些抽象. 也就是說D3D用熟了基本上一看就明白(用GL的我就不清楚啦, 嘿嘿, N3的作者都放棄用GL去實(shí)現(xiàn)@_@). 還記得D3D Tutorial中的Textured例子不? 需要的東西有帶紋理坐標(biāo)的點(diǎn), 紋理. N3中也一樣, 不過, 這里沒法用固定管線了. N3的設(shè)計(jì)的時(shí)候就放棄了固定管線(多么明智呀, 別噴我-_-, 我只會(huì)shader.......), 所以在這之前我們要先寫一個(gè)shader來進(jìn)行繪制. 因?yàn)槲覀冎皇沁M(jìn)行簡單的演示, 就盡量簡單了, 寫一個(gè)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, 為什么? 因?yàn)镹3用的右手坐標(biāo)系, 這點(diǎn)又跟D3D不一樣了........為什么呢? 難道寫MAYA跟MAX的插件的時(shí)候比較省事? 還是要跟上一次一樣設(shè)置頂點(diǎn)格式并載入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);
紋理的創(chuàng)建其實(shí)跟頂點(diǎn)差不多, 因?yàn)樗际菍儆谫Y源的一種, 詳見Nebula3資源子系統(tǒng) - // texture
- texture = Texture::Create();
- texture->SetResourceId(ResourceId("bin:razor.jpg"));
- texture->SetLoader(StreamTextureLoader::Create());
- texture->Load();
- texture->SetLoader(NULL);
shader的加載跟上一次一樣, 只是參數(shù)不同: - // 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);
繪制嘛, 當(dāng)然改成矩形了, 圖片可貼不到一跟線上: - 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();
上圖: 
圖形子系統(tǒng)是渲染層中圖形相關(guān)子系統(tǒng)的最高層. 它基本上是Mangalore圖形子系統(tǒng)的下一個(gè)版本, 但是現(xiàn)在整合進(jìn)了Nebula, 并且與低層的渲染代碼結(jié)合得更加緊密. 最基本的思想是實(shí)現(xiàn)一個(gè)完全自治的圖形”世界”, 它包含模型, 燈光, 還有攝像機(jī)實(shí)體, 而且只需要與外部世界進(jìn)行最少的通信. 圖形世界的最主要操作是加入和刪除實(shí)體, 還有更新它們的位置. 因?yàn)镸angalore的圖形子系統(tǒng)跟Nebula2的完全分界線從Nebula3中移除了, 很多設(shè)想都可以用更少的代碼和交互來實(shí)現(xiàn). 圖形子系統(tǒng)也會(huì)為了異步渲染而多線程化, 它和所有的底層渲染子系統(tǒng)都會(huì)生存在它們自己的fat-thread中. 這本應(yīng)是Nebula3層次結(jié)構(gòu)中更高級(jí)的東西, 但是我選擇了這個(gè)位置, 因?yàn)檫@是游戲跟渲染相關(guān)通信最少的一部分代碼. 正是因?yàn)閳D形代碼有了更多的”自治權(quán)”, 游戲相關(guān)的代碼可以跟圖形以完全不同的幀率來運(yùn)行, 不過這需要實(shí)踐來證明一下. 但是我一定會(huì)嘗試, 因?yàn)橥耆珱]有必要讓游戲邏輯代碼運(yùn)行在10幀以上(格斗游戲迷們可能會(huì)反對(duì)吧). 圖形子系統(tǒng)中最重要的公有類有: - ModelEntity
- CameraEntity
- LightEntity
- Stage
- View
一個(gè)ModelEnity表示了一個(gè)可見的圖形對(duì)象, 它包括位置, 包圍體和內(nèi)嵌的Model資源. 一個(gè)Model資源是一個(gè)完全的3D模型, 包括幾何體, 材質(zhì), 動(dòng)畫, 層級(jí)變換等…(后面會(huì)提到). 一個(gè)CameraEntity描述了圖形世界中的一個(gè)視景體, 為渲染提供View和Project矩陣. 一個(gè)LightEntity描述了一個(gè)動(dòng)態(tài)光源. Nebula3的光源屬性還沒有最終確定, 但是我的目標(biāo)是一個(gè)相對(duì)靈活地近似(最后一個(gè)光源不會(huì)超過幾個(gè)shader參數(shù)). Stage和View是Nebula3圖形子系統(tǒng)新增的內(nèi)容. 在Mangalore中, 圖形實(shí)體是生存在一個(gè)單獨(dú)的圖形Level類里, 任何時(shí)候只能有一個(gè)Level和一個(gè)攝像機(jī). 這對(duì)于只需要渲染一個(gè)世界到幀緩存(frame buffer)的情況來說還是不錯(cuò)的. 但許多游戲程序需要更復(fù)雜的渲染, 如在GUI中渲染一個(gè)使用單獨(dú)燈光的3D對(duì)象, 而它又跟其它的圖形世界是隔離的. 還有反射或像監(jiān)視器之類的東西都需要一個(gè)額外的視口, 諸如此類. 在Mangalore中, 這個(gè)問題通過OffscreenRenderer類得到解決, 雖說比較容易使用, 但是具有一些使用限制并且需要更多事后的思考. Nebula3提供了一個(gè)基于State和View的更加簡潔的解決方案. 一個(gè)Stage就是一個(gè)圖形實(shí)體的容器, 表示一個(gè)圖形世界. 同一時(shí)間可能存在多個(gè)Stage, 但是它們之間是互相隔絕的. 每個(gè)實(shí)體在一個(gè)時(shí)刻只連接到了一個(gè)Stage(雖說克隆一個(gè)已有實(shí)體是一件很簡單的事情). 除了簡單地把實(shí)體組織到一起外, Stage的主要工作是根據(jù)它們之間的關(guān)系來加速可見性查詢. 應(yīng)用程序可以派生Stage的子類來實(shí)現(xiàn)完全不同的可見性查詢方案. 一個(gè)View對(duì)象通過一個(gè)CameraEnity渲染stage到一個(gè)RenderTarget. 任何stage都可以連接任意數(shù)量的View對(duì)象. View對(duì)象可能會(huì)互相依賴(也可能是連接到不同stage的View), 所以更新一個(gè)View會(huì)首先強(qiáng)制更新另一個(gè)View的RenderTarget(這在一個(gè)View渲染需要使用另一個(gè)View的RenderTarget做為紋理時(shí)很方便). View對(duì)象完全實(shí)現(xiàn)了自己的渲染循環(huán). 應(yīng)用程序可以在View的子類中方便地實(shí)現(xiàn)它自己的渲染策略(如每個(gè)light一個(gè)pass VS 每個(gè)pass多個(gè)light, 渲染到cubemap, 等等). 總而言之, 一個(gè)Stage完全控制了可見性查詢流程, 而一個(gè)View則完全控制了渲染流程. 圖形子系統(tǒng)的一個(gè)最主要的工作就是根據(jù)可見性查詢的結(jié)果來決定哪些實(shí)體需要被渲染. 一個(gè)可見性查詢?cè)趯?shí)體間建立了一個(gè)雙向的鏈接, 它有兩種形式: 攝像機(jī)鏈接和燈光鏈接. 攝像機(jī)鏈接把一個(gè)攝像機(jī)和在它視景體內(nèi)的模型連接到了一起. 因?yàn)殒溄邮请p向的, 所以攝像機(jī)知道所有的在它視景體范圍內(nèi)的模型, 而模型也知道所有可以看到它的攝像機(jī). 燈光鏈接在燈光與模型之間建立了相似的關(guān)系, 一個(gè)燈光具有所有受它影響的模型的鏈接, 一個(gè)模型也知道所有影響它的燈光. 加速可見性查詢最重要的類就是Cell類. 一個(gè)Cell是一個(gè)圖形實(shí)體和子Cell的可見性容器, 它必須遵循2條簡單的規(guī)則: - 如果一個(gè)Cell是完全可見的, 那么它所有的圖形實(shí)體和子Cell都必須可見.
- 如果一個(gè)Cell是完全不可見的, 那么它所有的圖形實(shí)體和子Cell都必須不可見.
Cell是附屬于Stage的, 它們形成了一棵有根Cell的樹形層次結(jié)構(gòu). 標(biāo)準(zhǔn)的Cell支持簡單的空間劃分方案, 如四叉樹和八叉樹, 但如果像其它的可見性方案, 如portal, 就需要派生Cell的子類來實(shí)現(xiàn)了. 子類唯一的功能限制就是上面標(biāo)出的那兩條規(guī)則. 當(dāng)一個(gè)圖形體連接到一個(gè)Stage時(shí), 它會(huì)被插入”接受” (通常僅僅是容納)它的最低級(jí)的Cell中. 當(dāng)更新圖形實(shí)體的變換信息或改變包圍體時(shí), 它會(huì)根據(jù)需要改變?cè)贑ell層次中的位置. Stage居住在StageBuilder類當(dāng)中, 應(yīng)用程序應(yīng)當(dāng)派生StageBuilder來創(chuàng)建一個(gè)Stage的初始狀態(tài)(通過加入Cell和實(shí)體). Nebula3會(huì)提供一些標(biāo)準(zhǔn)的StageBuilder集合, 這應(yīng)該能夠滿足大多數(shù)應(yīng)用程序的需要了. 這只是圖形子系統(tǒng)的一個(gè)粗略的概述. 因?yàn)楫?dāng)前只有一個(gè)最基本的實(shí)現(xiàn), 很多細(xì)節(jié)接下來可能會(huì)有所更改.
Nebula3的代碼運(yùn)行在兩種根本不同的方案中. 第一種方案我稱之為”Fat Thread”. 一個(gè)Fat Thread在一個(gè)線程中運(yùn)行一個(gè)完整的子系統(tǒng)(如渲染, 音頻, AI, 物理, 資源管理), 并且基本上鎖定在一個(gè)特定的核心上. 第二種類型的線程我叫它”Job”. 一個(gè)job是一些數(shù)據(jù)和用于處理這些數(shù)據(jù)的包裝成C++對(duì)象的代碼. 工作調(diào)度程序掌管了Job對(duì)象, 并且把工作分配給低負(fù)載的核心來保持它們一直處于忙碌狀態(tài). 顯然, 挑戰(zhàn)就是設(shè)計(jì)一個(gè)經(jīng)過全面考慮的系統(tǒng), 以保持所有的核心一直均勻地忙碌著. 這不但意味著連續(xù)的活動(dòng)需要在游戲每幀的空閑時(shí)期內(nèi)輪流交替, 而且要求job對(duì)象不得不事先(如每幀前)創(chuàng)建好, 這樣才能在各種Fat Thread空閑時(shí)填充當(dāng)前幀的空白. 這是我希望進(jìn)行更多試驗(yàn)和調(diào)整的地方. 第二個(gè)挑戰(zhàn)就是讓程序員的工作盡量的簡單. 一個(gè)游戲應(yīng)用程序員(邏輯程序員)在任何時(shí)候都不應(yīng)該關(guān)心他運(yùn)行在一個(gè)多線程的環(huán)境中, 不應(yīng)該擔(dān)心會(huì)產(chǎn)生死鎖或改寫了其它線程的數(shù)據(jù), 也不應(yīng)該瞎搞一些臨界區(qū), 事件和信號(hào)量. 同樣, 整個(gè)引擎的架構(gòu)也不應(yīng)該是”脆弱的”. 大部分傳統(tǒng)的多線程代碼在一定程度上都會(huì)發(fā)生紊亂, 或者忘記了臨界區(qū)而打亂數(shù)據(jù). 當(dāng)線程間需要進(jìn)行數(shù)據(jù)共享和通信時(shí), 多線程就變得很棘手. 像兩個(gè)臨界區(qū)這樣的解決方案也會(huì)導(dǎo)致脆弱代碼問題. 從大的角度來說, Nebula3通過一個(gè)”并行Nebula”的概念解決了這個(gè)兩個(gè)問題. 其思想就是運(yùn)行了一個(gè)完整子系統(tǒng)的”Fat Thread”都有自己的最小Nebula運(yùn)行庫, 這個(gè)最小運(yùn)行庫剛好包含了這個(gè)子系統(tǒng)需要的部分. 因此, 如果這個(gè)運(yùn)行在它自己線程中的子系統(tǒng)需要進(jìn)行文件訪問, 它會(huì)有一個(gè)跟其它Fat Thread完全分離的文件服務(wù)器(file server). 這個(gè)解決方案的優(yōu)點(diǎn)是, 大部分Nebula中的代碼都不需要知道它運(yùn)行在一個(gè)多線程的環(huán)境中, 因?yàn)樵趂at thread之間沒有數(shù)據(jù)進(jìn)行共享. 運(yùn)行著的每個(gè)最小Nebula內(nèi)核是跟其它Nebula內(nèi)核完全隔離的. 缺點(diǎn)就是, 重復(fù)的數(shù)據(jù)會(huì)浪費(fèi)一些內(nèi)存, 但是我們只是占用幾KB, 而不是MB. 這些數(shù)據(jù)冗余消除了細(xì)密的鎖定, 并且解決把程序員從思考每一行代碼的多線程安全性中解放了出來. 當(dāng)然, 從某種意義上說Fat Thread間的通信是肯定會(huì)發(fā)生的, 要不然這整個(gè)思想就沒有意義了. 方法就是建立一個(gè)且只有一個(gè)的標(biāo)準(zhǔn)通信系統(tǒng), 并且保證這個(gè)通信系統(tǒng)是可靠而快速的. 這就是消息系統(tǒng)的由來. 要跟一個(gè)Fat Thread通信的話只有發(fā)送一個(gè)消息給它. 消息是一個(gè)簡單的C++對(duì)象, 它包含了一些帶有g(shù)et/set方法的數(shù)據(jù). 通過這個(gè)標(biāo)準(zhǔn)的通信手段, 實(shí)際上只有消息子系統(tǒng)才需要是線程安全的(同樣, 訪問跟消息相關(guān)的資源時(shí), 如內(nèi)存緩沖區(qū), 必須受到約束, 因們它們代表了共享數(shù)據(jù)). (xoyojank: 我說咋那么多Message…) 這樣雖然解決了Fat Thread方案中大多數(shù)的多線程問題, 但沒有解決Job對(duì)象的任何事情. Nebula3很有可能需要約束一個(gè)Job對(duì)象能做什么和不能做什么. 最直接的行為就是限制job做內(nèi)存緩沖區(qū)的計(jì)算. 那樣的話, job中就不能存在復(fù)雜的運(yùn)行庫(不能文件I/O, 不能訪問渲染等等). 如果這樣還不夠的話, 必須定義一個(gè)”job運(yùn)行時(shí)環(huán)境”, 就像Fat Thread中的那樣. 因?yàn)橐粋€(gè)job不會(huì)發(fā)起它自己的線程, 而且還會(huì)被調(diào)度到一個(gè)已經(jīng)存在的線程池中. 就這個(gè)方面來說, 這不存在什么問題. 到現(xiàn)在為止(xoyojank: 2007/01/21, 最新版本已經(jīng)實(shí)現(xiàn)了多數(shù)子系統(tǒng)的多線程化), 只有IO子系統(tǒng)作為概念證明在Fat Thread中得到實(shí)現(xiàn), 并且它運(yùn)行得很今人滿意. 在做傳統(tǒng)的同步IO工作時(shí), 一個(gè)Nebula3程序可以直接調(diào)用本地線程的IO子系統(tǒng). 所以像列出文件夾的內(nèi)容或刪除一個(gè)文件, 只會(huì)調(diào)用一個(gè)簡單的C++方法. 對(duì)于異步IO工作, 定義了一些常見的IO操作消息(如ReadStream, WriteStream, CopyFile, DeleteFile, 等等). 進(jìn)行異步IO只需要幾行代碼: 創(chuàng)建一個(gè)消息對(duì)象, 填充數(shù)據(jù), 并發(fā)送這個(gè)消息到一個(gè)IOInterface單件. 如果必要的話, 這可能會(huì)需要等待和輪詢異步操作. 這樣的好處就是, 整個(gè)IO子系統(tǒng)沒有一行多線程意義上的代碼, 因?yàn)楦鱾€(gè)在不同的Fat Thread中的IO子系統(tǒng)是完全隔離的(當(dāng)然, 同步肯定會(huì)發(fā)生在一些IO操作上, 但那都留給操作系統(tǒng)了).
跟N2比起來, N3的資源子系統(tǒng)更加開放, 給予了程序員對(duì)資源的創(chuàng)建和管理更多的控制. Nebula3的資源有下面向個(gè)屬性: - 包裝了一些其它Nebula子系統(tǒng)需要的數(shù)據(jù)
- 可以用ResourceId共享
- 可以在任何時(shí)候加載(初始化)和卸載
- 可以同步或異步加載
例如典型的圖形資源有網(wǎng)格和紋理, 但資源子系統(tǒng)并不局限于圖形資源. 資源子系統(tǒng)有兩個(gè)操作層次( 可能以后會(huì)把他們放入兩個(gè)不同的命名空間, 現(xiàn)在他們都是在Resources命名空間下 ): 低層提供了真正的資源對(duì)象, 處理資源的共享, 加載和(次要的)保存. 低層的資源類有: - ResourceId
- Resource
- ResourceLoader
- ResourceSaver
- SharedResourceServer.
高層資源子系統(tǒng)提供了資源管理, 這意味著根據(jù)用戶的反饋動(dòng)態(tài)的加載和卸載資源. 高層資源子系統(tǒng)的類有: - ResourceProxy (又名: ManagedResource)
- ResourceProxyServer (又名: ResourceManager)
- ResourceMapper
下面說明資源子系統(tǒng)的各個(gè)類是怎么協(xié)同工作的: 一個(gè)ResourceId是一個(gè)唯一的資源標(biāo)識(shí)符. ResourceId用來共享和定位磁盤上的數(shù)據(jù)(或者資源保存在哪). ResouceId是一些原子字符串(string atoms). Atom是一個(gè)常量字符串的唯一32-bit標(biāo)識(shí)符, 這可以大大加快拷貝和比較, 并且可以減少內(nèi)存占用, 因?yàn)闃?biāo)識(shí)符字符串只保存一份. 為了定位磁盤上的數(shù)據(jù), ResourceId常常分解成一個(gè)合法的URL(例如一個(gè)ResourceId “texture:materials/granite.dds”, 會(huì)在運(yùn)行時(shí)被分解成”file:///C:/Programme/[AppName]/export/textures/materials/granite.dds”. 一個(gè)Resource對(duì)象實(shí)際上是資源數(shù)據(jù)的容器. 像紋理和網(wǎng)格這樣特定的資源類型都是Resource類的子類, 并且實(shí)現(xiàn)了特定的接口. Resource子類通常都是平臺(tái)相關(guān)的(如D3D9Texture), 但是通過有條件的類型定義使其變成平臺(tái)無關(guān)的. 并不像Nebula2那樣, 資源對(duì)象并不知道怎樣去組織, 加載或保存自己. 取而代之的是, 一個(gè)合適的ResourceLoader或ResourceSaver必須附屬于Resource對(duì)象. 因?yàn)镹ebula程序很少輸出數(shù)據(jù), ResourceSaver只 是為了完整性而存在的. 換句話說, ResourceLoader是必須的, 因?yàn)樗麄兪菃⒂肦esource對(duì)象的唯一途徑. ResourceLoader具有整個(gè)資源裝載過程的完全控制. 它們可以是平臺(tái)相關(guān)的, 而且也許會(huì)依賴于相關(guān)聯(lián)的特定平臺(tái)的Resource類. 這使得程序員可以對(duì)資源的裝載過程相比Nebula2有更多的控制. 典型的資源加載類有StreadTextureLoader, MemoryVertexBufferLoader和MemoryIndexBufferLoader(從內(nèi)存中加載頂點(diǎn)緩存和索引緩存). Resource類也提供了一個(gè)共同的接口用來同步和異步的資源加載. 同步加載可以這樣做: - res-> SetResourceId("tex:system/white.dds");
- res-> SetLoader(StreamTextureLoader::Create());
- res-> SetAsyncEnabled(false)
- res-> Load()
- if (res-> IsValid()) ... 這時(shí)資源加載已經(jīng)成功了, 否則LoadFailed會(huì)返回true.
異步資源加載也很相似: - res->SetResourceId("tex:system/white.dds");
- res->SetLoader(StreamTextureLoader::Create());
- res->SetAsyncEnabled(true);
- res->Load();
- 資源這時(shí)進(jìn)入等待狀態(tài)...
- 只要 IsPending() return true, 就要重復(fù)地調(diào)用Load()... 當(dāng)然真正的程序會(huì)在這時(shí)做一些其他的事情
- 接下來的某個(gè)調(diào)用Load()后時(shí)刻, 資源的狀態(tài)要么是Valid(資源已經(jīng)準(zhǔn)備好了), Failed(資源加載失敗)或者Cancelled(等待中的資源被取消加載了)
一個(gè)應(yīng)用程序甚至是Nebula3的渲染代碼通常都不需要關(guān)心這些, 因?yàn)橘Y源管理層會(huì)處理他們, 并把異步加載的這些細(xì)節(jié)隱藏到資源代理后面. SharedResourceServer單件通過ResourceId來共享資源. 通過SharedResourceServer創(chuàng)建資源確保了每個(gè)資源只在內(nèi)存中加載了一份, 而不管客戶端的數(shù)目. 如果客戶端的數(shù)目降低到了0, 資源會(huì)被自動(dòng)卸載(這并不是合適的資源管理, 而應(yīng)該是ResourceProxyServer應(yīng)該關(guān)心的). 資源共享完全可以直接通過標(biāo)準(zhǔn)的Nebula3的創(chuàng)建機(jī)制來繞過. ResourceProxy(或ManagedResource)是對(duì)于實(shí)際資源對(duì)象的資源管理包裝. 它的思想是包含的資源對(duì)象會(huì)受資源用途反饋的控制. 例如, 一個(gè)紋理代理會(huì)在被請(qǐng)求的紋理在后臺(tái)加載時(shí)提供一個(gè)占位紋理, 屏幕上所有使用這個(gè)資源的物體都很小的話會(huì)被提供一張低分辨率的紋理, 一個(gè)X幀沒有被繪制的紋理會(huì)被卸載, 等等. ResourceProxyServer(或ResourceManager)單件是資源管理系統(tǒng)的前端. 除了管理附屬于它的ResourceMapper的工作外, 它還是ResourceProxy的工廠, 并且把ResourceMapper跟Resource類型聯(lián)系到了一起. ResourceMapper是一個(gè)有趣的東西. 一個(gè)ResourceMapper跟一種資源類型(如紋理或網(wǎng)格)相關(guān)聯(lián), 并被應(yīng)用程序依附到ResourceProxyServer. 它負(fù)責(zé)從渲染代碼的使用反饋來加載/卸載資源. ResourceMapper的子類可以實(shí)現(xiàn)不同的資源管理策略, 也可以通過派生特定的ResourceMapper和ResourceLoader來創(chuàng)建一個(gè)完全定制的平臺(tái)和應(yīng)用相關(guān)的資源管理方案. 目標(biāo)是顯而易見的, Nebula3提供了一些好用的ResourceMapper來加載需要的任何東西. 資源使用反饋是由渲染代碼寫入ResourceProxy對(duì)象的, 而且應(yīng)該包含這個(gè)資源的一些信息:是否會(huì)在不久后用到, 是否可見, 并估計(jì)物體占用的屏幕空間大小. 特定的反饋依賴于ResourceProxy的子類, ResourceProxy中沒有公有的反饋方法. 基于資源的使用反饋, 一個(gè)ResourceMapper應(yīng)該實(shí)現(xiàn)下面的一些操作(這取決于具體的mapper): - Load: 根據(jù)level-of-detail異步加載資源(如跳過不需要的高分辨率mipmap層次)
- Unload: 完全卸載資源, 釋放珍貴的內(nèi)存
- Upgrade: 提高已加載資源的level-of-detail(如加載高分辨率的mipmap層次紋理)
- Degrade: 降低已加載資源的level-of-detail(如跟上面相反的情況)
|