2011年4月9日
#
介紹:
關于warning C4715:not all control paths return a value (不是所有的控件路徑都返回值).
問題:
一個函數,不是所有路徑都有返回值,如下:
1) 基本數據類型
對于函數的并不是每個分支都有返回值,那么這樣警告會提示不是所有路徑都有返回值。
int test( int value )


{
if ( value > 0 ) return value;
}
調用 int value = test( -1 );
看下test的匯編代碼.
關于8個程序寄存器一般只有esp寄存器作為入棧,出棧,調用和返回指令作為棧指針,其余

比如eax,exc等寄存器都沒有固定的含義和固定值.
看下面test匯編代碼.
int test( int value )


{

; 4個寄存器入棧
; ebp 用于存放函數棧的棧頂指針
; esp 用于存放函數棧的棧底指針

004113A0 push ebp ;將寄存器ebp的內容壓入程序棧
004113A1 mov ebp,esp ;保留esp寄存器
004113A3 sub esp,0C0h ;為該函數留出臨時存儲區
004113A9 push ebx
004113AA push esi
004113AB push edi

; 用0CCCCCCCCh初始化堆棧
004113AC lea edi,[ebp-0C0h] ;lea直接尋址
004113B2 mov ecx,30h ;利用編譯器的offset立即尋址
004113B7 mov eax,0CCCCCCCCh ;eax=0CCCCCCCCh
004113BC rep stos dword ptr es:[edi] ;根據edi的大小來重復指令執行次數

; 如果 cmp為真則把value的值保存到eax寄存器中
; 否則跳轉到地址4113C7h,并沒有對eax做處理

if ( value > 0 ) return value;
004113BE cmp dword ptr [value],0
004113C2 jle test+27h (4113C7h)
004113C4 mov eax,dword ptr [value]
}

;各指針出棧,對應前面3條push

004113C7 pop edi ;彈出edi
004113C8 pop esi ;彈出esi
004113C9 pop ebx ;彈出ebx
004113CA mov esp,ebp ;把esp重新指向ebp(函數棧的棧頂指

針,test函數棧頂)
004113CC pop ebp ;ebp重新指向test調用函數返回地址
004113CD ret
調用匯編代碼
int value = test ( 1 );
004113FE push 1
00411400 call test (4110AFh)
00411405 add esp,4 ;Call test 函數時將壓入棧數據,

由于只有一個參數,所以只有4字節
00411408 mov dword ptr [value],eax
當test 調用小于0時最后value指向的eax是一個0CCCCCCCCh,而對于基本數據類型大多value得到的是0CCCCCCCCh值.
如果我們的test函數:
int test( int value )


{
if ( value > 0 ) return value;
return 0;
}
那么匯編代碼會如下:
if ( value > 0 ) return value;
004113EE cmp dword ptr [value],0
004113F2 jle test+29h (4113F9h)
004113F4 mov eax,dword ptr [value]
004113F7 jmp test+2Bh (4113FBh)
return 0;
004113F9 xor eax,eax ;將eax清零,作為返回值
2 )如果返回的是一個引用對象
obj & test( type value )


{
if( type2 ) return obj;
}

obj & ob = test( type1 );
如果ob是個空引用的話,就出出錯,關于這種出錯是否可以通過什么方式避免呢?
我覺得warning C4715就應該是error C4715.讓開發者從最開始就避免這種錯誤的發生。
2011年3月23日
#
date: 3/23/2011
介紹:
利用Win32 來處理MyGui 3.0.1的中文輸入。
實現:
配置還是參考網上的配置,主要再加中文字體.
如下:
□ 更改配置文件MyGUI3.0\Media\MyGUI_Media下

☆ core_font.xml添加

<Resource type="ResourceTrueTypeFont" name="font_Simhei">
<Property key="Source" value="simhei.ttf"/>
<Property key="Size" value="19"/>
<Property key="Resolution" value="50"/>
<Property key="Antialias" value="false"/>
<Property key="SpaceWidth" value="4"/>
<Property key="TabWidth" value="8"/>
<Property key="CursorWidth" value="2"/>
<Property key="Distance" value="6"/>
<Property key="OffsetHeight" value="0"/>
<Codes>
<Code range="33 126"/>
<Code range="19969 40869"/>
<Code hide="128"/>
<Code hide="1026 1039"/>
<Code hide="1104"/>
</Codes>
</Resource>

☆ simhei.ttf要從系統目錄下的Fonts拷貝到當前目錄。

☆ core_settings.xml中將默認字體改成

<MyGUI type="Font">
<Property key="Default" value="font_Simhei"/>
</MyGUI>


運行Demo解決方案: solution_directx。
給BaseManager添加Win32消息響應函數void ProcIO(UINT messgae, WPARAM wParam, LPARAM lParam ).
case WM_CHAR:
case WM_KEYDOWN:
case WM_KEYUP:

{
base::BaseManager *baseManager = (base::BaseManager*)GetWindowLongPtr(hWnd, GWL_USERDATA);

if ( baseManager )
baseManager->ProcIO( uMsg , wParam , lParam );
break;
}
ProcIO主要是對
WM_CHAR 字符響應
WM_KEYDOWN/WM_KEYUP 按鍵響應
在處理字符響應的時候需要區分輸入法狀態和非輸入法狀態的字符響應。
case WM_CHAR:

{
if ( ImmIsIME( GetKeyboardLayout(0) ))
ProcChar( wParam , lParam );
else

{
MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(scan_code), code_point);
};
}
break;
ProcChar函數主要處理中文。因為漢字為8個字節會響應WM_CHAR2次。需要進行組合一次。
WM_KEYDOWN:主要處理一些Widget字符輸入。同時還可以做全局鍵盤信息監控(快捷鍵).
判斷一個Widget是否可以進行字符輸入:
\MyGUIEngine\include\MyGUI_InputManager.cpp
bool InputManager::isKeyInputCapture()

{
if( !mWidgetKeyFocus ) return false;

std::string strName = mWidgetKeyFocus->getTypeName();
if ( strName == "ComboBox" ||
strName == "Edit" ||
strName == "Message" ||
strName == "List")

{
return true;
}
return false;
}
由于采用Win32鍵盤消息,應該屏蔽自帶OIS的。
Input\OIS\InputManager.cpp
void InputManager::captureInput()

{
if (mMouse) mMouse->capture();
//mKeyboard->capture();
}
在Demo中監控按鍵消息( DemoKeeper功能是UIManager )。
void DemoKeeper::injectKeyPress(MyGUI::KeyCode _key, MyGUI::Char _text)

{
if (_key == MyGUI::KeyCode::Grave)

{
mConsole->setVisible(!mConsole->isVisible());
return;
}
else if (_key == MyGUI::KeyCode::F2 )

{
MyGUI::Message::createMessageBox("Message", "Info", "Press F2 ", MyGUI::MessageBoxStyle::Ok | MyGUI::MessageBoxStyle::IconInfo);
return;
}
else if (_key == MyGUI::KeyCode::F3 )

{
MyGUI::Message::createMessageBox("Message", "Info", "Press F3 ", MyGUI::MessageBoxStyle::Ok | MyGUI::MessageBoxStyle::IconInfo);
return;
}

base::BaseManager::injectKeyPress(_key, _text);
}
最后附上源碼解決方案:
/Files/expter/MyGuiDemo.rar
圖片:
2011年2月24日
#
摘要: 描述: 一個常見遇到的解決方案,下面記錄下來。 1個功能模塊,有一個簡單的繼承體系,基類假設為Base. 然后通過一個接口,如何訪問子類的特有操作? /// 外部提供一個下面接口: virutal Base* ...
閱讀全文
2011年1月18日
#
目的:
針對自己的一個內存池如何測試其性能.
介紹:
1.內存池測試用例的選取.
1.單線程的分配和釋放.
2.內存回收.
3.性能關注.
關于內存池的設計和實現網上遍地都是,本文不具體介紹關于內存池的具體實現和方式,主要是介紹設計一個內存池怎樣去測試其性能和安全處理,有一個開源的內存池項目tcmalloc也有介紹很多,但是為了滿足多種需求,代碼過于龐大,最后我用來測試分配性能測試。
1個內存池的測試用例應該包含:
1)該項目內存分配概率隨機性.
2)同時保證釋放的隨機性.
3)可以支持多種分配方式(不同大小,不同對象參數等).
要達到上面要求則可以設計
1.一個數組來設定需要分配的大小。
long arr[ ] = { 16,32,64,128,256,512,1024,2048,5120,5130,7000,6000,10240,15000,20000};
2.根據需求來指定各個大小的分配幾率,這好比有多少概率選中某個數(需特定的分配):
A.針對這個需求可以設定定一個概率數組Odds,數組值arrArr的索引。
B.針對Odds指定數組數據,使其數據分配達到arr需要分配概率。

/**//// 被分配的概率
long Odds[ ] =
{ 0,0,0,1,1,2,2,2,3,3,4,4,4,4,5,5,5,5,6,6,,7,7,7,8,8,8,8,9,9,9,10,10,11,12,12};
C.隨機Odds數組,然后得到其值分配,其值則為Arr的索引。
long Asize= arr[ Odds[ rand()%size ] ] ;
3.釋放保證隨機性。
什么時候釋放,以及分配了做什么用,都是又應用層決定的,所以需要把分配出來的內存通過一個容器來存儲.
由于分配是隨機性,那么釋放的時候也保證了隨機性。
4.支持多種分配方式。
A. 對象分配:
MemFactory Memory;

A* a = Memory.Alloc<A>( );
B* b = Memory.Alloc<B,int>( 2 );

Memory.FreeObj( a );
Memory.FreeObj( b );
B.直接分配
void* p1 = Memory.Alloc( Asize );
5.性能測試
為了測試性能,我選擇了分配1000W次,其中用一個容器保存分配的數據,然后當容器到達100W的時候釋放60W數據(保證數據正在使用,隨機釋放)。
下面的Alloc time 只是統計的Alloc時間累加,Free time只是統計的Free 時間累加,Total time記錄這次測試總共花費時間。
1
測試結果如下:
2
MemPool Alloc time 3242 ms Free time: 2412 ms Total time 22535 ms
3
System Alloc time 33616 ms Free time: 6676 ms Total time 55013 ms
4
TCMalloc Alloc time 3451 ms Free time 1896 ms Toal time 21078 ms
5
6
可以看到TCMalloc的分配和釋放都比較快。。
7
8
其中arr每個分配的大小命中概率。
9
Count[ 1 ] = 2436395
10
Count[ 2 ] = 1281728
11
Count[ 3 ] = 1026009
12
Count[ 4 ] = 769123
13
Count[ 5 ] = 768911
14
Count[ 6 ] = 769335
15
Count[ 7 ] = 640757
16
Count[ 8 ] = 640974
17
Count[ 9 ] = 512378
18
Count[ 10 ] = 384841
19
Count[ 11 ] = 256135
20
Count[ 12 ] = 257367
21
Count[ 13 ] = 256047
PS:
1.內存池的使用:
一般情況下內存池,是整理一整塊內存,然后通過一個list串連起來,然后分配的時候從鏈表中獲取,釋放也是插入到鏈表中。
為了方便多對象的多參數以及無參數的分配,可以一些列宏和模板來實現:
具體的可以參考后面附帶的內存池實現的代碼:
#define DEFINE_CALL_CON( paramcount ) template <class T, DP_STMP_##paramcount( typename, tp ) >\

inline T * Alloc(DP_MTMP_##paramcount( tp, p ) )
{\
unsigned long lSize = sizeof(T);\
void* ptMem = Alloc(lSize);\
if( !ptMem) return NULL; \
T * pt = new(ptMem)T( LP_SNMP_##paramcount( p ) );\
return pt;\
}
A. 對象分配:
MemFactory Memory;

A* a = Memory.Alloc<A>( );
B* b = Memory.Alloc<B,int>( 2 );
C* c = Memory.Alloc<C,int,const char*>(1,"dd");
Memory.FreeObj( a );
Memory.FreeObj( b );
Memory.FreeObj( c );
B.直接分配
void* p1 = Memory.Alloc( Asize );
memset(p1,0,ASize);
2.內存池的代碼:
1) 實現全是利用的freelist,減少內存開銷,分配速度,直接定位。
2) 管理都是通過工廠類來同一的管理。
3) 指定分配策略.
源碼為Vs2008版本...
/Files/expter/Pool.rar
關于實現有疑問和建議,可以提出寶貴的意見。。
2011年1月14日
#
問題描述:
1個容器有大量元素,需要進行erase大部分數據的時候,需要遍歷這些元素,然后釋放item的空間,還要erase刪除其item。
一個庫,為了測試其性能的時候,需要保存所有外部使用者的數據,這里選取了map,vector和list.
為了簡化問題,我寫了下面測試代碼來測試各個操作:
數據節點:
struct node


{

node(int i)
{data = i;}
int data;
}
1
int _tmain(int argc, _TCHAR* argv[])
2

{
3
typedef std::map<long,node*> Mptable;
4
typedef std::vector<node*> Vec;
5
typedef std::list<node*> List;
6
7
Mptable mapnode;
8
Vec vecnode;
9
List listnode;
10
11
for(int i = 1 ; i <= 100000 ; i++ )
12
{
13
mapnode [ i ] = new node(i);
14
vecnode.push_back( new node(i) );
15
listnode.push_back( new node(i));
16
}
17
18
long time = timeGetTime( );
19
20
for( Mptable::iterator itr = mapnode.begin() ; itr != mapnode.end() ; )
21
{
22
delete itr->second;
23
mapnode.erase( itr++ );
24
}
25
26
std::cout <<"map : spend " << timeGetTime() - time << " msec " << std::endl;
27
28
29
time = timeGetTime( );
30
31
for( Vec::iterator itr = vecnode.begin() ; itr != vecnode.end() ; )
32
{
33
delete *itr;
34
itr = vecnode.erase( itr );
35
}
36
37
std::cout <<"vector : spend " << timeGetTime() - time << " msec " << std::endl;
38
39
40
time = timeGetTime( );
41
42
for( List::iterator itr = listnode.begin() ; itr != listnode.end() ; )
43
{
44
delete *itr;
45
itr = listnode.erase( itr );
46
}
47
48
std::cout <<"list : spend " << timeGetTime() - time << " msec" << std::endl;
49
50
51
return 0;
52
}
Release下運行結果:
map : spend 31 msec
vector : spend 3734 msec
list : spend 35 msec
發現map的速度最快,vector最慢,list相當。
其實vector就是一個Array,只是Array是靜態大小,vector可以擴展,然后查看vector的erase的源碼:
iterator erase(const_iterator _Where)

{ // erase element at where
_Move(_VIPTR(_Where) + 1, this->_Mylast,
_VIPTR(_Where));
_Destroy(this->_Mylast - 1, this->_Mylast);
--this->_Mylast;
return (_Make_iter(_Where));
}
有一個move操作,原來把當前iterator+1的往前移了,這樣的話會遍歷iterator后面所有的元素。
關于map的erase原理可以查看map的實現源碼:
由于map的erase后有一個維護過程,其實map是一個RB-Tree,刪除算法相對比較麻煩,刪除某個item會查找下一個item替換刪除的節點,同時還要考慮紅和黑的節點處理。同時還要保證map的erase后,平衡且有序。
所以map的erase主要做:
1.刪除item.
2.讓樹平衡,且有序。
list其實是一個雙向鏈表:
關于刪除其實是0(1)的操作,我們查看list的erase的操作:
iterator erase(const_iterator _Where)

{ // erase element at _Where
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Where._Getcont() != this || _Where._Ptr == this->_Myhead)
_DEBUG_ERROR("list erase iterator outside range");
_Nodeptr _Pnode = (_Where++)._Mynode();
_Orphan_ptr(*this, _Pnode);

#else /* _ITERATOR_DEBUG_LEVEL == 2 */
_Nodeptr _Pnode = (_Where++)._Mynode();
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */

if (_Pnode != this->_Myhead)

{ // not list head, safe to erase
this->_Nextnode(this->_Prevnode(_Pnode)) =
this->_Nextnode(_Pnode);
this->_Prevnode(this->_Nextnode(_Pnode)) =
this->_Prevnode(_Pnode);

_Dest_val(this->_Alnod, _Pnode);
this->_Alnod.deallocate(_Pnode, 1);

--this->_Mysize;
}
return (_Make_iter(_Where));
}
主要代碼刪除就是下面刪除部分:
對prev和next節點進行處理即可。
關于list的移除竟然比map還要慢.
PS:測試為單線程。
當為100W數據的時候:
map : spend 300 msec
list : spend 385 msec
咋list比map容器還要慢?
還是上面的代碼不能說明問題。
2010年7月31日
#
author:expter
date: 2010/07/31
上次寫的超鏈接是重寫了一個單獨的超鏈接控件,主要實現文本的鼠標事件和文本下劃線,靜態圖像的顯示,主要寫的渲染過程,這次為了解決動態圖片(比如gif有多幀實現的解決方案).
注:主要針對當前CEGUI的最新庫0.7.1。
目的:在游戲聊天框中可能有一個聊天表情,次表情是動態的,需要顯示動態表情圖片,同時可能有超鏈接等功能,我們約定一種解析格式,然后輸入表情代碼或者超鏈接內容即可顯示我們需要的功能。
實現方式:
由于針對聊天窗口,所以這里聊天窗口內容采用CEGUI::ListBox,每條消息內容為ListBoxTextItem,由于ListBox有滾動horzScrollbar條,而一般聊天窗口沒有horzScrollbar此功能,所以假設一段話過長我們需要才分內容,可能一條消息包括多條ListBoxTextItem。每行里面可能有文本,表情,圖片,以及超鏈接等.
由于CEGUi不能直接解析Gif文件,我們需要把gif的每一幀全部到出來,然后實現一個當前表情anim控制類,表情管理變量,文件解析類TalkRenderedStringParser。
Anim主要記錄當前表情應該為那幀的哪張圖片。
表情管理主要記錄當前所有的動態表情,每幀時間到達的時候開始渲染。
TalkRenderedStringParser還是一個文本解析類。
簡單的ChatList的主要文本如下:
[C FFFF0000]StaticTxt test1![\\ 1] [\\ 3]
dasd[\\ 2] [\\ 3] [\\ 4] [\\ 1] [\\ 5]
其實是動態表情,只是切的圖片不能顯示了:
要實現一個與游戲相關的聊天窗口基本功能都具備了,這里包括實現超鏈接,表情圖片,圖片的功能。
然后只需要制定分頻道等功能分別顯示不同的聊天信息。

源碼功能上相對比較簡單,CEGUI庫做了相應的修改。
2010年7月22日
#
基本上實現一個基于靜態文本多任務的過程.
注:主要針對當前CEGUI的最新庫0.7.1。
目的:游戲制作過程中一般打開NPC會彈出一個對話框,一般對話框就是顯示一段話,有圖片,超鏈接,文字,同時文本分別有不同的顏色!
那么只要我們輸入一段文本,對話框的控件解析文本定義好的標簽然后顯示所有文本內容和圖片即可。
實現方式:
1.超鏈接控件既要響應點擊消息,又要有超級鏈接標記的下劃線。實現方式主要參考了Button的Clicked事件,StaticText的render渲染過程,重新寫的一個基于超級鏈接組件。
2.文本解析利用了當前CEGUI的版本的BasicRenderedStringParser類,我們只需要繼承此類,然后設置系統默認的文本解析類為我們當前的類。
3.寫超級鏈接組件的渲染過程主要方便支持CELayoutEditor的可視化編輯。
實現結果:
1.文本顯示顏色。
2.換行操作。
3.支持超級鏈接的顯示,以及事件響應和事件處理,事件響應為CEGUI::HyperText::EventClicked。
4.超鏈接的下劃線繪制。
5.支持圖片顯示和支持圖片事件響應。
假設我們的解析文本如下:
標簽定義如下
[N]則是換行字符
[C]字體顏色
[A]超級鏈接
[M]圖片
相對來說編寫此文本比較簡單.
具體用法
xxx 顯示文本xxx
[C XX] xx表示32位的字體顏色
[A 1: XX] xx 超級鏈接顯示內容。
[M xx] xx表示圖片名字
colorTest:[N]
StaticTxt test1![N]
[C FFFF0000]StaticTxt test2! [N]
[C FFFFFF00]StaticTxt test3! [N] [N] [N]
[C FFFF0000]HyperLink Test: [N]
[A 1:this is the Hyperlink!this is the Hyperlink!][N]
[A 2:this is the second Hyperlink! this is hyperlink!!!][N]
[A 3:this is the third hyperlink!this is hyperlink!this is hyperlink!this is hyperlink!]
[N] [N] [N]
Image Text:[M 381] [M 286] [M 669]
具體過程:

后期目標支持動畫的顯示,比如GIF格式圖片.
實現過程相對繁瑣,而且涉及datafiles配置一些處理。
可能真正游戲界面上的實現可能會更豐富,其實也就是增加幾個標簽然后解析即可。
注:上次聽蓋老板說有本書專門介紹足球AI,然后專門去買了,看其介紹他實現上足球仿真AI專門實現比較智能,先學習學習他的在繼續寫我的了。。
2010年6月30日
#
author:expter
date 2010/06/30
介紹: 世界杯現在如此的備受矚目和關注,本文介紹如何實現一個基于足球AI的實現,而作為程序員我們關注的不是目標用其贏得世界杯,而是創造一個把球踢好的智能體,加上最近上班輕松,晚上較閑,加上去年實現的一個AI模型與平時寫的游戲智能算法,想組織起來完成一個足球模擬玩玩。
本文會首先介紹一種基于AI仿真機的實現流程圖,后面我將會用大量的篇幅詳細介紹各個實現細節,與具體足球戰術,此足球AI主要是主動攻擊性AI,所以還需要具體完善加強防御性的AI,所以具體代碼現在將不會現在放出。以后實現完整過后會完整公開,現在主要設計描述如下。
足球的游戲規則不是很復雜,就是2個球隊,然后每個球隊一個守門員與幾名球員,目的就是踢進對方的球門。簡單的足球是沒有傻子的,也就沒有犯規,越位,頭球,點球以及烏龍球。以后可能會增加上面幾種。
一個簡單的游戲的具體環境如下:
1.一個足球場(FootBallPitch)
2.一個足球 FootBall
3.二個球門 Goal
4.二個球隊 FootBallTeam
5.場上12名足球隊員(每隊6名,期中5名為球員2名后衛3名前鋒,還有1名守門員)
6.球員 FootBaller 守門員 GoalKeeper
然后只要理清上面的描述然后把具體的實現封裝到每個類中,就實現了1個簡單的足球仿真模擬,實現上面的功能代碼還是簡單,但是如何組織強大攻擊性強大,防御性強的AI還是挺復雜的。
下面將給出具體UML實現類圖:

由于是基于智能體的足球AI所以還是借鑒了FSM模型,我們可以把每個Player處于不同的狀態進行不同的操作,具體把操作類型和事件處理都放在具體的狀態中。
基于球場上運球的FootBaller有下列狀態ChaseBall 追球狀態,Dribble運球 Gohome 歸位 KickBall 踢球 ReceviveBall傳球
基于守門員GoalKeeper有InterceptBall 攔截 PutBallBackInPlay發球.
這里為了區分隊員是前鋒還是后衛,我們給隊員增加一個行為Behaviors,讓其根據自己的行為做相應的事情.。
其中所有的圖像處理都是用的GDI的繪制,程序采用的Win32編寫方式。
上面的設計基本是現在程序的設計方案和流程圖。
后期完善部分:
1.引入基于事件響應,FootBaller 可以通知同隊FootBaller 的接收響應的消息處理,比如A發現B的位置很好,A可以通知B我要傳球到一個坐標點。
2.加強防御和攻擊AI。
3.完成具體方案后,公布所有的方案設計和具體算法,后期引入腳本機制,通過外部編寫腳本實現不同隊伍AI模擬。
2010年4月14日
#
摘要: 一.XXX 1)概念說明 這里不再具體描述內存池的概念和作用,需要了解請看http://baike.baidu.com/view/2659852.htm?fr=ala0_1_1。 &nbs...
閱讀全文
2010年3月5日
#
摘要: 對于一個游戲而言,定時器是必須的,而它一般作為一個游戲基本公共組件,而定時器在游戲邏輯中運用是非常明顯的(比如吃藥回血,每幾秒回血多少),而對于游戲邏輯而言需要開發一個高效率高精度(毫秒級別)的定時器。
一:分析Ace庫定時器實現方式 1.Ace種定...
閱讀全文