<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>猴子靈藥 [Monkey Potion] &#187; 遊戲程式</title>
	<atom:link href="http://blog.monkeypotion.net/category/gameprog/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.monkeypotion.net</link>
	<description>遊戲開發‧遊戲程式‧遊戲設計</description>
	<lastBuildDate>Wed, 08 Sep 2010 16:14:06 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>實作Lua Closure二合一洗牌發牌機</title>
		<link>http://blog.monkeypotion.net/gameprog/concept/lua-closure-random-shuffle-deck</link>
		<comments>http://blog.monkeypotion.net/gameprog/concept/lua-closure-random-shuffle-deck#comments</comments>
		<pubDate>Fri, 23 Oct 2009 16:30:32 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[觀念技巧]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=1086</guid>
		<description><![CDATA[亂數 (Randomness)，是每一位程式設計者們熟悉又親切的好朋友，無論我們開發製作的是哪一種類型的遊戲，亂數機制總是在遊戲設計程序與撰寫程式碼的過程中，扮演著不可或缺的重要角色。亂數最重要的用途，就是提供遊戲世界中必要的「不確定性」，只要能夠將這份「不確定性」的設計機制運用得宜，就可以為玩家們帶來許多驚喜感與樂趣元素。 然而，有許多時候，亂數的產生並不只是使用 rand() 函式般單純容易而已。在某些遊戲機制裡，無邊無際的亂數數值，並不能滿足程式設計者或企畫設計者所想達到的目標——我們需要的是「亂中有序」——能夠在某個限定範圍之內產生亂數。 何謂「亂中有序」？撲克牌遊戲就是一個最好的實例。撲克牌由 4 種花色與 13 種數字，組合成 52 張「牌組」。而「洗牌」(Shuffle) 動作可以定義為：在一組有限的集合元素內，進行亂數排列的程序。只要將牌組洗完之後，就可以按照牌堆的排列順序，開始一張張地進行「發牌」動作了。 對於程式設計者來說，不論是使用哪一種程式語言，要實作出洗牌與發牌的功能都不是件太困難的事情。假設，我們可以使用 Lua 語言來實作洗牌發牌機，是否能夠創造出什麼樣有趣的變化？ 由於 Lua 是一種無須宣告變數資料型別的程式腳本語言，所以在 Lua 中我們可以非常便利地對任意數量、任意類型的「資料集合」進行洗牌的動作。下列的 Lua 函式 RandShuffle()，接受一個 table 型別的參數做為洗牌用的「牌組」： function RandShuffle(deck) -- 首先確認傳入的參數型態是 table assert(type(deck) == "table"); -- 將 deck 的所有元素複製至暫用的 clone 中 local clone = {}; for k, v in pairs(deck) do clone[k] = v; end -- [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_1541" class="wp-caption alignright" style="width: 338px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/10/dice-triangle.jpg" alt="dice-triangle" title="dice-triangle" width="328" height="328" class="size-full wp-image-1541" /><p class="wp-caption-text">（圖片來源：maniacworld.com）</p></div>
<p><strong>亂數 (Randomness)</strong>，是每一位程式設計者們熟悉又親切的好朋友，無論我們開發製作的是哪一種類型的遊戲，亂數機制總是在遊戲設計程序與撰寫程式碼的過程中，扮演著不可或缺的重要角色。亂數最重要的用途，就是提供遊戲世界中必要的「不確定性」，只要能夠將這份「不確定性」的設計機制運用得宜，就可以為玩家們帶來許多驚喜感與樂趣元素。</p>
<p>然而，有許多時候，亂數的產生並不只是使用 rand() 函式般單純容易而已。在某些遊戲機制裡，無邊無際的亂數數值，並不能滿足程式設計者或企畫設計者所想達到的目標——我們需要的是「亂中有序」——能夠在某個限定範圍之內產生亂數。</p>
<p>何謂「亂中有序」？撲克牌遊戲就是一個最好的實例。撲克牌由 4 種花色與 13 種數字，組合成 52 張「牌組」。而<strong>「洗牌」(Shuffle)</strong> 動作可以定義為：在一組有限的集合元素內，進行亂數排列的程序。只要將牌組洗完之後，就可以按照牌堆的排列順序，開始一張張地進行「發牌」動作了。</p>
<p>對於程式設計者來說，不論是使用哪一種程式語言，要實作出洗牌與發牌的功能都不是件太困難的事情。假設，我們可以使用 Lua 語言來實作洗牌發牌機，是否能夠創造出什麼樣有趣的變化？</p>
<p><span id="more-1086"></span></p>
<p>由於 Lua 是一種無須宣告變數資料型別的程式腳本語言，所以在 Lua 中我們可以非常便利地對任意數量、任意類型的「資料集合」進行洗牌的動作。下列的 Lua 函式 RandShuffle()，接受一個 table 型別的參數做為洗牌用的「牌組」：</p>
<pre name="code" class="lua">
function RandShuffle(deck)
    -- 首先確認傳入的參數型態是 table
	assert(type(deck) == "table");

    -- 將 deck 的所有元素複製至暫用的 clone 中
	local clone = {};
	for k, v in pairs(deck) do
		clone[k] = v;
	end

	-- 取得元素集合的大小
	local range = table.maxn(deck);

	-- 巡訪 deck 結構
	for k, v in pairs(deck) do
    	-- 藉由亂數機制，決定要取出的 clone 索引值
		local index = math.random(1, range);

	    -- 在 clone 中移除該元素，塞回原來的 deck 中
		deck[k] = table.remove(clone, index);

		-- 將亂數取值的範圍減1
		range = range - 1;
	end
end
</pre>
<p>無論 deck 結構中的資料，是數字型別、字串型別或甚至另一個 table 結構，只要經過 RandShuffle() 程序的操作，就可以得到一副經過亂數排列，也就是洗牌後的 deck 牌組：</p>
<pre name="code" class="lua">
-- 數字型別的牌組
NumberDeck = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
-- 洗牌
RandShuffle(NumberDeck);

-- 字串型別的牌組
StringDeck = { "Apple", "Banana", "Cherry", "Lemon", "Orange" };
-- 照樣洗牌
RandShuffle(StringDeck);

-- table結構的牌組
ComplexDeck =
{
	{ 1, Monday, "Apple" },
	{ 2, Tuesday, "Banana" },
	{ 3, Wednesday, "Cherry" },
	{ 4, Thursday, "Lemon" },
	{ 5, Friday, "Orange" },
};
-- 洗！洗！洗！
RandShuffle(ComplexDeck);
</pre>
<p>但是到目前為止，我們只是得到了一副洗過牌的牌組，接下來該如何進行「發牌」動作呢？</p>
<p>站在使用者的立場思考，我們會希望發牌功能可以一次發出一張牌，並且在整副牌組全部使用過後，自動重新進行洗牌。所以，在此需要另一個輔助函式 OutputCard() 來完成發牌程序：</p>
<pre name="code" class="lua">
-- 發牌計數器
g_Counter = 0;

function OutputCard(deck)
    -- 紀錄目前的發牌數
	g_Counter = g_Counter + 1;

    -- 如果發牌數大於總牌數
	if (g_Counter > table.maxn(deck)) then
		-- 重新洗牌
		RandShuffle(deck);
		-- 重設發牌計數器
		g_Counter = 1;
	end

	-- 回傳一張牌
	return deck[g_Counter];
end
</pre>
<p>實作出「洗牌機」RandShuffle() 與「發牌機」OutputCard() 後，就可以很便利地使用：</p>
<pre name="code" class="lua">
-- 牌組
MyDeck = { ... };

-- 先將 g_Deck 洗牌
RandShuffle(MyDeck);

-- 發牌
MyCards = {};
-- 第1張牌
MyCards[1] = OutputCard(MyDeck);
-- 第2張牌
MyCards[2] = OutputCard(MyDeck);
-- 第3張牌
MyCards[3] = OutputCard(MyDeck);
-- 第4張牌
MyCards[4] = OutputCard(MyDeck);
-- 第5張牌
MyCards[5] = OutputCard(MyDeck);
</pre>
<p>但光是這樣還不夠完善。假設我們需要一次操作兩副以上相同的牌組怎麼辦？有沒有其他方法，可以不使用全域變數 g_Counter 做為發牌的計數器？在 Lua 缺少物件導向設計機制特性的情形下，能不能將「洗牌機」與「發牌機」合而為一，讓使用者得以更直覺地操作洗牌與發牌程序？</p>
<p>答案就是 <a href="http://www.lua.org/pil/6.1.html">Lua Closures</a>。利用 Lua 語言提供的 Closure 特性，就可以幫助我們輕巧且優雅地達成目標：</p>
<pre name="code" class="lua">
function RandDeck(t)
	assert(type(t) == "table");

    -- Closure所使用的upvalues
	local pool = t;
	local counter = table.maxn(t);

    -- 回傳一個匿名函式
	return function()
		counter = counter + 1;

		if (counter > table.maxn(pool)) then
			RandShuffle(pool);
			counter = 1;
		end

		return pool[counter];
	end
end
</pre>
<p>上列函式 RandDeck() 的回傳值是一個「匿名函式」，而在這個匿名函式中所做的事情，與先前在 OutputCard() 函式中進行的程序相同。根據 Lua Closures 的功能規範，在 RandDeck() 中宣告為區域變數的 pool 與 counter，被稱為 external local variable 或 upvalue。</p>
<p>藉由 upvalue 的特性，程式設計者得以在函式之間保存變數值，並且讓底下的匿名函式進行讀寫動作。對於全域或 RandDeck() 函式外部的程式碼來說，pool 與 counter 是完全不可見的變數；但是對於底下的匿名函式來說，pool 與 counter 的功用就像是物件導向語言中的「成員變數」(member variables) 一樣。</p>
<p>而 RandDeck() 的使用方式也很簡單明瞭，以 52 張撲克牌為例：</p>
<pre name="code" class="lua">
-- 撲克牌牌組
--   花色：c, d, h, s 分別代表 club, diamond, heart, spade 四種花色
--   數字：1 至 13
Poker =
{
	"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13",
	"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "d12", "d13",
	"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "h9", "h10", "h11", "h12", "h13",
	"s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12", "s13",
};

-- 創造一副牌組
g_Deck = RandDeck(Poker);

MyCard = {};
-- 呼叫 g_Deck() 發第1張牌
MyCard[1] = g_Deck();
-- 呼叫 g_Deck() 發第2張牌
MyCard[2] = g_Deck();
-- 呼叫 g_Deck() 發第3張牌
MyCard[3] = g_Deck();

-- 創造多副不同的牌組
g_Deck1 = RandDeck(Poker);
g_Deck2 = RandDeck(Poker);
g_Deck3 = RandDeck(Poker);

MyCard = {};
-- 使用 g_Deck1() 發第1張牌
MyCard[1] = g_Deck1();
-- 使用 g_Deck2() 發第2張牌
MyCard[2] = g_Deck2();
-- 使用 g_Deck3() 發第3張牌
MyCard[3] = g_Deck3();
</pre>
<p>結合之前的 RandShuffle() 與 RandDeck() 寥寥可數的幾行程式碼，就完成了雙效合一的洗牌發牌機。</p>
<p>最後，可以再進一步修改 RandDeck() 函式，讓它能夠一次發出複數張的牌：</p>
<pre name="code" class="lua">
-- 發牌機加強版
function RandMultiDeck(t)
	assert(type(t) == "table");

	local pool = t;
	local counter = table.maxn(t);

	return function(num)
		num = num or 1;

		if (num == 1) then
			counter = counter + 1;

			if (counter > table.maxn(pool)) then
				RandShuffle(pool);
				counter = 1;
			end

			return pool[counter];
		else
			if (counter + num > table.maxn(pool)) then
				RandShuffle(pool);
				counter = 1;
			end

			local result = {};
			for index = counter, counter + num - 1 do
				table.insert(result, pool[index]);
			end

			counter = counter + num;

			return result;
		end
	end
end

-- 創造一副牌組
g_Deck = RandMultiDeck(Poker);

-- 一次發出5張牌！
MyCard = g_Deck(5);
</pre>
<p>在這篇文章裡，雖然只介紹了非常簡單的洗牌與發牌應用，但應該不難看出 Lua Closures 的威力所在。對於習慣使用 C/C++ 或 Java 語言的程式設計者來說，一開始可能會比較難以理解 Lua Closures 的概念，但只要熟悉了它的運作方式，就可以使用 Lua 做到許多目前 C++ 語言無法達成的事情。</p>
<p>關於洗牌與發牌，你是否有更好更巧妙的作法？關於 Lua Closures，你有更棒更實用的應用嗎？歡迎各位給予指教及討論。</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=1086&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/concept/lua-closure-random-shuffle-deck/feed</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>快快樂樂學遊戲Threading程式設計</title>
		<link>http://blog.monkeypotion.net/gameprog/note/learning-threading-in-game-programming</link>
		<comments>http://blog.monkeypotion.net/gameprog/note/learning-threading-in-game-programming#comments</comments>
		<pubDate>Thu, 26 Mar 2009 16:19:01 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[學習筆記]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=149</guid>
		<description><![CDATA[這是我在近期內對於 Threading 主題的學習整理文。如果你對於多執行緒程式設計沒有半點概念的話，建議可以先從我之前寫的「多核多緒多樂趣」開始閱讀，然後再視個人需求取用以下各項資源。 所謂的「多執行緒」程式設計，或者可簡稱為「多緒」程式設計，在英文中有許多相關的專業技術名詞，例如 Threading、Multithread、Concurrency、Parallel、Multicore 與 Multiprocessor 等等，在搜尋資料時可以嘗試不同的關鍵字，往往可以找到不少意料之外的好東西。而其中最常見的總括性簡稱，應該就是 Threading 了。 基礎定義 既然要學習 Threading 程式設計的知識，首先要瞭解的當然是 Threading 的基礎概念： Thread：看看 Wikipedia 裡的定義，至少把最前頭那段 Thread 的基本定義，以及 Thread 與 Process 的不同之處搞懂。簡單來說，執行緒是用來執行電腦程式的執行環境；而多執行緒，就是能夠使程式系統同時執行多個不同程式碼區段的一種技術。 Thread Safety：雖然使用 Threading 技術能夠提升程式系統的執行效能，但伴隨而來的則是麻煩又難解的 Thread Safety 議題。大致上，我們可以使用 Re-entrancy、Mutual exclusion、Thread-local storage 以及 Atomic operations 這四種方法來達成安全使用多執行緒技術的目標。 教學文章 C++ Multithreading Tutorial @ paulbridger.net：質量非常不錯的基礎教學文章，從 Race Condition、Mutex、Deadlock 到 Condition Variables 等等重要的基本概念，都有清楚易懂的文章教學與程式碼範例。 Multithreading Tutorial @ Homepage [...]]]></description>
			<content:encoded><![CDATA[<p>這是我在近期內對於 Threading 主題的學習整理文。如果你對於多執行緒程式設計沒有半點概念的話，建議可以先從我之前寫的<a href="http://blog.monkeypotion.net/gameprog/note/multicore-multithread-and-multifun"><strong>「多核多緒多樂趣」</strong></a>開始閱讀，然後再視個人需求取用以下各項資源。</p>
<p>所謂的「多執行緒」程式設計，或者可簡稱為「多緒」程式設計，在英文中有許多相關的專業技術名詞，例如 Threading、Multithread、Concurrency、Parallel、Multicore 與 Multiprocessor 等等，在搜尋資料時可以嘗試不同的關鍵字，往往可以找到不少意料之外的好東西。而其中最常見的總括性簡稱，應該就是 Threading 了。</p>
<h4><u><strong>基礎定義</strong></u></h4>
<p>既然要學習 Threading 程式設計的知識，首先要瞭解的當然是 Threading 的基礎概念：</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Thread_(computer_science)">Thread</a>：看看 Wikipedia 裡的定義，至少把最前頭那段 Thread 的基本定義，以及 Thread 與 Process 的不同之處搞懂。簡單來說，執行緒是用來執行電腦程式的執行環境；而多執行緒，就是能夠使程式系統同時執行多個不同程式碼區段的一種技術。</li>
<li><a href="http://en.wikipedia.org/wiki/Thread-safe">Thread Safety</a>：雖然使用 Threading 技術能夠提升程式系統的執行效能，但伴隨而來的則是麻煩又難解的 Thread Safety 議題。大致上，我們可以使用 Re-entrancy、Mutual exclusion、Thread-local storage 以及 Atomic operations 這四種方法來達成安全使用多執行緒技術的目標。</li>
</ul>
<p><span id="more-149"></span></p>
<h4><u><strong>教學文章</strong></u></h4>
<ul>
<li><a href="http://paulbridger.net/multithreading_tutorial">C++ Multithreading Tutorial</a> @ paulbridger.net：質量非常不錯的基礎教學文章，從 Race Condition、Mutex、Deadlock 到 Condition Variables 等等重要的基本概念，都有清楚易懂的文章教學與程式碼範例。</li>
<li><a href="http://www.mario-konrad.ch/index.php?page=30100">Multithreading Tutorial</a> @ Homepage of Mario Konrad：網站裡分成基礎、中間與進階三種等級的教學文章，可以看看 Socket 網路程式與 Producer-Consumer 的程式碼，應該會更加瞭解多執行緒技術的可運用之處。</li>
</ul>
<h4><u><strong>跨平台函式庫</strong></u></h4>
<ul>
<li><a href="http://www.boost.org/doc/libs/1_38_0/doc/html/thread.html">Boost.Thread</a>（連結為 1.38 版）：簡單易用且功能完備的跨平台函式庫。如果沒有使用過 Boost C++ Libraries，可以參照<a href="http://blog.monkeypotion.net/gameprog/note/first-touch-of-boost-cpp-libraries">這篇文章</a>的步驟來建置 Boost.Thread 函式庫。</li>
<li><a href="http://threadpool.sourceforge.net/">threadpool</a>：以 Boost.Thread 為基礎，實做出一個非常好用的 <a href="http://en.wikipedia.org/wiki/Thread_pool_pattern">Thread Pool</a> 設計模式。</li>
</ul>
<h4><u><strong>名家專區</strong></u></h4>
<ul>
<li><a href="http://software.intel.com/en-us/multi-core/">Intel: Parallel Programming and Multi-Core</a>：因為 Intel 的本業是靠 CPU 吃飯的，所以自然相當注重多執行緒程式設計的議題，在這裡可以挖到不少質量兼具的優秀文章。</li>
<li><a href="http://msdn.microsoft.com/zh-tw/concurrency/default(en-us).aspx">MSDN: Parallel Computing</a>：MSDN 中的平行計算處理專區，充滿文章、影片以及部落格等相關資源。</li>
<li><a href="http://www.devx.com/SpecialReports/Door/40893">DevX.com: Move to the Future with Multicore Code</a>：有幾篇不錯的概念性文章，另外還有介紹如何使用 .NET 平台的 Task Parallel Library。</li>
<li><a href="http://herbsutter.wordpress.com/">Sutter&#8217;s Mill</a>：Herb Sutter 大師的個人部落格。目前在 DDJ 雜誌上，有他每個月固定連載的 Effective Concurrency 專欄文章，適合進階高手服用。</li>
</ul>
<h4><u><strong>投影片</strong></u></h4>
<p>微軟於 <a href="http://www.xnagamefest.com/presentations07.htm">2007</a> 年與 <a href="http://www.xnagamefest.com/presentations08.htm">2008</a> 時所舉辦的 Gamefest 開發者會議，其中有許多關於 Threading 的演講主題，內容非常豐富多樣且紮實，而且大部分課程都有投影片與錄音檔可供下載：</p>
<ul>
<li><a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=D6940B55-B805-46B5-B683-B8A2FE9B3D00&#038;displaylang=en">Multi-Threaded Rendering for Games</a></li>
<li><a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=72EFB35C-0C1B-47A2-AAB8-68CA841364F3&#038;displaylang=en">A Detailed Overview of Xbox 360 Direct3D Synchronization and Multithreading</a></li>
<li><a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=E410716F-12BF-4E8F-AC41-97B4440C3B90&#038;displaylang=en">Introduction to the Direct3D 11 Graphics Pipeline</a></li>
<li><a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=EE4C7B70-1606-45E8-8027-50AB9A2F646F&#038;displaylang=en">Practical Parallel Rendering with DirectX 9 and 10, Windows PC Command Buffer Recording</a></li>
<li><a href="http://download.microsoft.com/download/c/f/8/cf8d0552-5e8c-4501-a52e-0986a9295821/Multicore%20Programming%20Two%20Years%20Later.zip">Multicore Programming, Two Years Later</a></li>
<li><a href="http://download.microsoft.com/download/8/0/4/804e5b2b-1308-4584-9644-7ccb9b52bacd/Magic%20and%20Technology%20-%20Migrating%20from%20One%20to%20Many%20Cores.zip">Magic and Technology: Migrating from One to Many Cores in Shadowrun</a></li>
<p>另外這篇是微軟於 GDC 2008 中開設的講座：</p>
<li><a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=A36FE736-5FE7-4E08-84CF-ACCF801538EB&#038;displaylang=en">GDC 2008: Getting More From Multicore</a></li>
</ul>
<h4><u><strong>推薦文章</strong></u></h4>
<ul>
<li><a href="http://software.intel.com/en-us/articles/threading-basics-for-games/">Threading Basics for Games</a>：概略簡介了遊戲所使用的基礎多緒程式架構。</li>
<li><a href="http://www.intel.com/cd/ids/developer/asmo-na/eng/dc/games/331359.htm">Threading the OGRE3D Render System</a>：這篇文章闡述了三種能夠使 OGRE 繪圖引擎充分利用多緒能力的技術，並完整實作出其中一種方法，附源碼可供下載。</li>
<li><a href="http://software.intel.com/en-us/articles/multithreading-the-rendering-pipeline-for-3d-model-animation/">Multithreading the Rendering Pipeline for 3D Model Animation</a>：相對於 GPU 的強大運算能力，這篇文章試圖藉由多核心 CPU 的力量來達成 Skinning Animation 的功能。雖然目前這個作法的實用性不高，但是文章中的設計架構仍然非常值得參考學習，也有完整源碼可下載。</li>
<li><a href="http://www.gamasutra.com/view/feature/3941/sponsored_feature_designing_the_.php">Designing the Framework of a Parallel Game Engine</a>：相當夠份量而且完整的好文章！著重於設計概念與流程架構，沒有提供任何程式碼範例。</li>
</ul>
<h4><u><strong>書籍文章</strong></u></h4>
<p>關於書籍中的文章，我推薦《Game Programming Gems 7》中的「Design and Implementation of a Multi-Platform Threading Engine」與「Multithread Job and Dependency System」這兩篇文章。前者實作出了一個可跨平台的 Threading 引擎架構，後者則著重於更加複雜的任務相依系統上，兩篇文章各有擅場，而且都有完整的程式源碼可供讀者仔細研究！</p>
<p>如果有其他關於遊戲 Threading 程式設計的資源，隨時歡迎各位的回應與補充喔～ ：D</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=149&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/note/learning-threading-in-game-programming/feed</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>MSVC與CRT的恩怨情仇</title>
		<link>http://blog.monkeypotion.net/gameprog/beginner/love-and-hate-between-msvc-and-crt</link>
		<comments>http://blog.monkeypotion.net/gameprog/beginner/love-and-hate-between-msvc-and-crt#comments</comments>
		<pubDate>Sun, 08 Mar 2009 16:26:34 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[入門概念]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=954</guid>
		<description><![CDATA[很久沒有寫程式設計入門知識的相關文章了，這篇文章要來談談程式庫 (Library) 連結，以及關於 MSVC 與 CRT 之間的種種恩怨情仇。 如果你使用的作業系統是 Linux、Mac 或其他非 Windows 平台，你可以忽略這篇文章；如果你使用的作業系統是 Windows 平台，但沒有用 Microsoft Visual Studio C++（以下簡稱為 MSVC）軟體撰寫 C++ 程式的話，這篇文章對你的幫助可能很有限；但如果你的作業系統是 Windows，而且你使用的程式整合開發環境是 MSVC 軟體撰寫 C++ 程式的話，這篇文章應該能夠幫助你釐清一些重要的基礎觀念。 身為程式設計者，在學習程式設計的過程中，你是否曾經遇過某些看起來不知所云的錯誤訊息，卻不知該如何解決？例如當你快快樂樂地寫完程式，並且確認所有的程式碼都能成功通過編譯之後，接著執行「建置方案」(Build Solution) 的步驟，結果卻跑出一堆莫名其妙的錯誤： LIBCMTD.lib(mlock.obj) : error LNK2005: __lock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了 LIBCMTD.lib(mlock.obj) : error LNK2005: __unlock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了 LIBCMTD.lib(crt0.obj) : error LNK2005: _mainCRTStartup 已在 MSVCRTD.lib(crtexe.obj) 中定義過了 ………… LINK [...]]]></description>
			<content:encoded><![CDATA[<p>很久沒有寫程式設計入門知識的相關文章了，這篇文章要來談談程式庫 (Library) 連結，以及關於 MSVC 與 CRT 之間的種種恩怨情仇。</p>
<p>如果你使用的作業系統是 Linux、Mac 或其他非 Windows 平台，你可以忽略這篇文章；如果你使用的作業系統是 Windows 平台，但沒有用 Microsoft Visual Studio C++（以下簡稱為 MSVC）軟體撰寫 C++ 程式的話，這篇文章對你的幫助可能很有限；但如果你的作業系統是 Windows，而且你使用的程式整合開發環境是 MSVC 軟體撰寫 C++ 程式的話，這篇文章應該能夠幫助你釐清一些重要的基礎觀念。</p>
<p>身為程式設計者，在學習程式設計的過程中，你是否曾經遇過某些看起來不知所云的錯誤訊息，卻不知該如何解決？例如當你快快樂樂地寫完程式，並且確認所有的程式碼都能成功通過編譯之後，接著執行「建置方案」(Build Solution) 的步驟，結果卻跑出一堆莫名其妙的錯誤：</p>
<blockquote><p>
LIBCMTD.lib(mlock.obj) : error LNK2005: __lock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了<br />
LIBCMTD.lib(mlock.obj) : error LNK2005: __unlock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了<br />
LIBCMTD.lib(crt0.obj) : error LNK2005: _mainCRTStartup 已在 MSVCRTD.lib(crtexe.obj) 中定義過了</p>
<p>…………</p>
<p>LINK : warning LNK4098: 預設的程式庫 &#8216;MSVCRTD&#8217; 與其他使用的程式庫衝突，請使用 /NODEFAULTLIB:library<br />
LINK : warning LNK4098: 預設的程式庫 &#8216;LIBCMTD&#8217; 與其他使用的程式庫衝突，請使用 /NODEFAULTLIB:library<br />
D:\Workspace\CrtLibTest\Debug\CrtLibTest.exe : fatal error LNK1169: 找到有一或多個已定義的符號
</p></blockquote>
<p>以一般的情況來說，如果在你的程式專案中有使用某些由他人所撰寫的第三方程式庫或是開源專案的程式庫，比較容易會發生上述的錯誤狀況。從上述這些看似離奇而令人摸不著頭緒的錯誤訊息中，我們大概可以猜測問題點應該在於 LIBCMTD.lib 與 MSVCRTD.lib 這兩個程式庫身上。<strong>但到底什麼是 LIBCMTD.lib 和 MSVCRTD.lib？在我們的程式碼中有使用這些程式庫嗎？</strong></p>
<p><span id="more-954"></span></p>
<p>答案是肯定的。</p>
<p>熟悉 C 語言的程式設計者都知道，如果要使用 printf()、scanf() 或者 fopen() 等等 C 語言的基本 I/O 操作函式時，首先必須用 #include 語法將 stdio.h 這個標頭檔納入我們的程式碼中。藉由 stdio.h 中對這些 I/O 操作函式所做出的函式宣告 (function declaration)，編譯器 (Compiler) 才得以確認 printf、scanf 以及 fopen 等等都是合法可用的函式。</p>
<p>而當我們撰寫的程式碼經過編譯器產出 OBJ 形式的檔案之後，需要再經由連結器 (Linker) 的處理程序，將程式碼中全部有使用到的函式定義 (function definition) 連結建置起來，才能夠產生出最後的程式執行檔。問題來了，我們知道 printf、scanf 以及 fopen 的函式宣告存在於 stdio.h 當中，但是這些傢伙的函式定義，也就是真正的實做程式碼，究竟存放在什麼地方呢？</p>
<p><strong>在 C 語言的標準程式庫中。</strong></p>
<p>由 C 語言所制訂的標準程式庫，稱之為<strong>「執行階段程式庫」</strong>，也就是 <strong>C Run-Time Library</strong>，通常可簡稱為 <strong>CRT</strong>。在 C 語言的標準程式庫中，包含了一組常用的基礎函式，例如 I/O 處理與字串操作程序等等，所以只要我們使用 C 語言撰寫程式碼，就一定要將編譯完成後的程式碼 OBJ 檔，連結至 C 語言的執行階段程式庫，才能夠產生出合法的 C 語言程式執行檔。</p>
<p>而 CRT 並非只有單一一種版本存在。事實上，除了可以依「除錯」與「釋出」用途分成兩個版本之外，兩者又可分別衍生分出「靜態連結」與「動態連結」兩種形式：</p>
<blockquote><p>
<strong>靜態連結</strong>：</p>
<ul>
<li>LIBCMTD.lib（除錯版本）</li>
<li>LIBCMT.lib</li>
</ul>
<p><strong>動態連結</strong>：</p>
<ul>
<li>MSVCRTD.lib（除錯版本）</li>
<li>MSVCRT.lib</li>
</ul>
</blockquote>
<p>雖然這四個 CRT 版本的用途與使用方式各不相同，但卻有個共通的特點，就是<strong>它們都是滿足執行緒安全需求，可在多執行緒程式碼中安全使用的程式庫版本</strong>。事實上，在過去 MSVC 6 的版本中，本來還有另外兩個 LIBCD.lib（除錯版本）與 LIBC.lib 程式庫，是專門給單執行緒程式使用的 CRT 版本，但是這兩個選項自 MSVC 2005 開始就從設定選項中被刪除掉了，所以現在大多數程式設計者使用的都是多執行緒的 CRT 版本。</p>
<p>在程式庫連結 (library linking) 的行為中，靜態連結和動態連結的分別，在於使用靜態連結時，會直接將程式庫的函式定義嵌入執行檔之中，而使用動態連結時，程式庫的函式定義則存在於另外的獨立檔案，通常是 DLL 格式的檔案中，然後與程式執行檔一同發佈給使用者。因此在檔案的尺寸上，使用動態連結的執行檔檔案，通常會比使用靜態連結的執行檔檔案來得更小一些。</p>
<p>使用動態連結 CRT 版本的好處，是能夠將經常使用到的標準程式庫們獨立出來，放在 Windows 的系統資料夾中，以減少我們建置出來的執行檔檔案尺寸。但反過來說，<strong>使用動態連結 CRT 版本的缺點也在於這些與執行檔相依為命的 DLL 檔案上</strong>。舉例來說，如果程式以 MSVC 2005 建置出 Debug 組態的執行檔，則此執行檔需要有 msvcr80d.dll 存在才能順利執行；如果是 Release 組態，則相依於 msvcr80.dll。但是如果你把相同的程式碼拿到 MSVC 2008 上建置，產生出來的執行檔則相依於 msvcr90d.dll 與 msvcr90.dll 兩個不同的 DLL 檔案。<strong>不同版本的 MSVC，都會有各自不同的相依 DLL 檔案。</strong></p>
<p>在 MSVC 的程式專案中，如何指定程式碼要使用靜態連結或者動態連結的 CRT 版本？其實很容易，只要在專案屬性的「C/C++」頁面中，選擇「程式碼產生」(Code Generation) 子頁面，其中有個「執行階段程式庫」(Runtime Library) 的項目，也就是專案中用來設定 CRT 連結版本的地方。其中總共有四個選項，正好對應於上述靜態連結與動態連結的四個不同程式庫版本。</p>
<ul>
<li><strong>多執行緒偵錯 (/MTd)</strong>：對應 LIBCMTD.lib</li>
<li><strong>多執行緒 (/MT)</strong>：對應 LIBCMT.lib</li>
<li><strong>多執行緒偵錯 DLL (/MDd)</strong>：對應 MSVCRTD.lib</li>
<li><strong>多執行緒 DLL (/MD)</strong>：對應 MSVCRT.lib</li>
</ul>
<p>如果你沒有做任何設定就開始建置程式的話，MSVC 的預設選項則會使用動態連結的版本。</p>
<div id="attachment_1318" class="wp-caption aligncenter" style="width: 733px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/03/c-runtime-library.jpg" alt="c-runtime-library" title="c-runtime-library" width="723" height="249" class="size-full wp-image-1318" /><p class="wp-caption-text">C Runtime Library</p></div>
<p>請注意，以上只是單純 C 語言的程式庫而沒有包含 C++ 語言在內。如果你的程式系統中，有包含 C++ 語言的程式碼的話，那又是另外一回事了。但是在專案屬性的頁面中，為什麼找不到相關的設定選項呢？<strong>因為 MSVC 悄悄地幫程式設計者代勞處理掉了。</strong>只要在程式碼中使用 #include 語法納入任何一個 C++ 的標頭檔，例如 iostream 或 fstream，MSVC 就會在連結器的運作階段中，自動幫我們連結 C++ 的執行階段程式庫。而 C++ 的執行階段程式庫，同樣可分為四個版本：</p>
<blockquote><p>
<strong>靜態連結</strong>：</p>
<ul>
<li>LIBCPMTD.lib（除錯版本）</li>
<li>LIBCPMT.lib</li>
</ul>
<p><strong>動態連結</strong>：</p>
<ul>
<li>MSVCPRTD.lib（除錯版本）：執行檔相依於 MSVCP90D.dll</li>
<li>MSVCPRT.lib：執行檔相依於 MSVCP90.dll</li>
</ul>
</blockquote>
<p>至於程式執行檔使用的是靜態連結或者動態連結的版本，就仰賴於 C 語言的版本設定選項了。舉個例子來說，如果你撰寫了一個 Debug 組態的 C++ 程式，並且保留專案原先預設的建置選項（動態連結），那麼最終建置出來的程式執行檔將會相依於 MSVCR90D.dll 以及 MSVCP90D.dll 兩個 DLL 檔案。如果將相同的程式以 Release 組態建置完成，則會相依於 MSVCR90.dll 以及 MSVCP90.dll 二者。</p>
<div id="attachment_1317" class="wp-caption aligncenter" style="width: 733px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/03/standard-cpp-library.jpg" alt="standard-cpp-library" title="standard-cpp-library" width="723" height="173" class="size-full wp-image-1317" /><p class="wp-caption-text">Standard C++ Library</p></div>
<p>剛學習程式設計的入門者，經常會在滿心歡喜地完成一件程式作品並且傳給其他人使用時，卻發現不能在別人的電腦上啟動程式，其實就是陷入了使用者電腦缺少 DLL 檔案而無法執行程式的窘境。有三種方法可以解決這個令人困擾的問題：</p>
<ol>
<li>使用者的電腦，必須先安裝「Visual C++ 可轉發套件」（<a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=A5C84275-3B97-4AB7-A40D-3802B2AF5FC2&#038;displaylang=zh-tw">MSVC 2008</a> 或 <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=200B2FD9-AE1A-4A14-984D-389C36F85647&#038;displaylang=zh-tw">MSVC 2005</a> ）。</li>
<li>將所需的 DLL 檔案，例如 MSVCR90D.dll 與 MSVCP90D.dll，直接附在程式的下載包當中。</li>
<li>以靜態連結方式建置程式執行檔。</li>
</ol>
<p>當你無法確定自己的程式或別人的程式，是否相依於某些特定的 DLL 檔案時，有一個非常好用的免費工具程式 <a href="http://www.dependencywalker.com/">Dependency Walker</a>，可以開啟 EXE 格式的執行檔或者 DLL 格式的動態程式庫，然後詳細地條列出它們所相依的 DLL 檔案。</p>
<p>瞭解了幾種不同的 CRT 版本選項之後，回到最前面的錯誤訊息問題，相信各位現在應該能夠很清楚地理解，原來會發生這些奇怪的錯誤狀況，<strong>是因為程式同時連結了 LIBCMTD.lib 與 MSVCRTD.lib 所以造成函式定義版本衝突</strong>。也就是說，程式連結器已經在其中一個 CRT 的版本中找到所需的函式定義，但此時卻又跳出另外一位 CRT，也給了一份相同函式的實作版本，所以連結器無法判斷應該忽略誰並且選擇誰。</p>
<p>而這個狀況的發生原因，就是你的程式與程式所連結的外部程式庫，使用了不同的 CRT 版本之故。例如，當你的程式使用了 Lua，自然必須連結至 Lua 的程式庫 lua5.1.lib，但如果 lua5.1.lib 是以靜態連結版本的 CRT 建置而成，而你的程式卻是以預設選項，動態連結 CRT 來建置程式執行檔的話，如此一來就會產生上述這些錯誤訊息了。至此，問題的答案已昭然若揭，解決方法有二種：<strong>其一是將 Lua 重新以動態連結 CRT 的方式建置出一個新的程式庫，其二則是將自己的程式專案改成以靜態連結 CRT 方式建置。</strong></p>
<p>換個角度想，當你身為一位程式庫的設計開發者，想要將自己寫的東西分享給其他人，但又不想要完全開放自己撰寫的程式源碼時，至少可以同時提供以下四種版本的程式庫，以妥善滿足使用者的各種不同需求：</p>
<ul>
<li>Debug：動態連結除錯版本</li>
<li>Release：動態連結版本</li>
<li>Debug_Static：靜態連結除錯版本</li>
<li>Release_Static：靜態連結版本</li>
</ul>
<p>然而，有時候世界並不會運作得如此理想。在某些特殊的狀況下，當我們使用他人所寫的第三方程式庫時，有時可能只拿得到其中某個特定的版本，例如 Release_Static 版本時，就很有可能會遇到程式庫衝突的錯誤情形。此時就需要視專案的實際需求而定，可以在專案屬性中指定「忽略特定程式庫」(Ignore Specific Library) 這個選項，讓程式碼連結器忽略某些程式庫，以此化解動靜程式庫或新舊程式庫之間的恩怨衝突。</p>
<p><strong>小測驗</strong>：你所撰寫的程式，必須連結某個以靜態多執行緒 (/MT) CRT 建置而成的程式庫。如果你的程式在 Debug 組態下以多執行緒偵錯 (/MTd) 選項建置，是否會產生衝突？如果你的程式在 Release 組態下以多執行緒 (/MT) 選項建置，是否會產生衝突？是的話，應該如何解決？</p>
<p><strong>延伸閱讀：</strong></p>
<ul>
<li><a href="http://msdn.microsoft.com/en-us/library/abx4dbyh.aspx">[MSDN] Visual Studio 2008: C Run-Time Libraries</a></li>
<li><a href="http://kobyk.wordpress.com/2007/07/20/dynamically-linking-with-msvcrtdll-using-visual-c-2005/">Dynamically linking with MSVCRT.DLL using Visual C++ 2005</a></li>
</ul>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=954&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/beginner/love-and-hate-between-msvc-and-crt/feed</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Strategy &amp; State：條件判斷式的消除者</title>
		<link>http://blog.monkeypotion.net/gameprog/pattern/strategy-and-state</link>
		<comments>http://blog.monkeypotion.net/gameprog/pattern/strategy-and-state#comments</comments>
		<pubDate>Sun, 22 Feb 2009 16:48:05 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[設計模式]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=1185</guid>
		<description><![CDATA[身為一位程式設計者，你是否曾面臨條件判斷式繁殖過盛的困擾？經常用折疊不完的 N 層 if-else 結構來考驗自己的腦力？或是只能瞪著超過 500 行的 switch-case 條件判斷式舉手投降？您的困擾我瞭解，請容許我向您引薦本篇文章的雙主打星——Strategy 與 State 範式，讓它們帶領我們一同邁向美妙動人的範式之道吧！ 首先要瞭解的是，為什麼要將 Strategy 與 State 範式放在同一篇文章裡介紹？因為兩者雖然在設計層面的動機與出發點有所差異，但是在實際的應用面中非常地相近。根據《物件導向設計模式》(Design Patterns) 書中的定義，只要將右圖中的 Strategy、ConcreteStrategyA 與 ConcreteStrategyB 角色，更改為 State、ConcreteStateA 與 ConcreteStateB，就會變成 State 範式的結構圖，可以說兩者就像是孿生兄弟般密切相關。 如果真的要區分出 Strategy 與 State 範式之間的差異，可以參考《重構——向範式前進》中論述的內容： State pattern 對「必須在一整族 state classes 的實體之間輕鬆轉換」的 class 有益，而 Strategy pattern 則是有助於讓 class 把演算法執行任務委託給「一整族 Strategy classes 的某一個實體」。 從我理解的角度來解釋，Strategy 範式比較著重於包裝相同派系的演算法，而 State 範式則特別注重在各狀態之間的轉換邏輯。所以只要瞭解 Strategy 或 [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_1268" class="wp-caption alignright" style="width: 466px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/02/strategy-pattern.jpg" alt="strategy-pattern" title="strategy-pattern" width="456" height="231" class="size-full wp-image-1268" /><p class="wp-caption-text">Strategy Pattern</p></div>
<p>身為一位程式設計者，你是否曾面臨條件判斷式繁殖過盛的困擾？經常用折疊不完的 N 層 if-else 結構來考驗自己的腦力？或是只能瞪著超過 500 行的 switch-case 條件判斷式舉手投降？您的困擾我瞭解，請容許我向您引薦本篇文章的雙主打星——Strategy 與 State 範式，讓它們帶領我們一同邁向美妙動人的範式之道吧！</p>
<p>首先要瞭解的是，為什麼要將 Strategy 與 State 範式放在同一篇文章裡介紹？因為兩者雖然在設計層面的動機與出發點有所差異，但是在實際的應用面中非常地相近。根據《物件導向設計模式》(Design Patterns) 書中的定義，只要將右圖中的 Strategy、ConcreteStrategyA 與 ConcreteStrategyB 角色，更改為 State、ConcreteStateA 與 ConcreteStateB，就會變成 State 範式的結構圖，可以說兩者就像是孿生兄弟般密切相關。</p>
<p>如果真的要區分出 Strategy 與 State 範式之間的差異，可以參考《重構——向範式前進》中論述的內容：</p>
<blockquote><p>
State pattern 對「必須在一整族 state classes 的實體之間輕鬆轉換」的 class 有益，而 Strategy pattern 則是有助於讓 class 把演算法執行任務委託給「一整族 Strategy classes 的某一個實體」。
</p></blockquote>
<p>從我理解的角度來解釋，<strong>Strategy 範式比較著重於包裝相同派系的演算法，而 State 範式則特別注重在各狀態之間的轉換邏輯</strong>。所以只要瞭解 Strategy 或 State 範式兩者之一，就等於學會了兩種設計模式，真的是太划算太值得啦！雖然 Strategy 與 State 範式非常單純而且易於理解，但這兩項看似不起眼的小小範式，卻經常能夠在程式系統中發揮很好的應用效果，絕對是程式設計者不可不學的必備基礎知識。本文中將以 Strategy 範式為主，說明兩者在遊戲系統中的相關應用。</p>
<p><span id="more-1185"></span></p>
<p>如上面的 UML 結構圖所示，Strategy 範式由三種角色的交互作用而生：</p>
<blockquote>
<ul>
<li><strong>Strategy</strong>：制訂出一組共通的介面，使 Context 能夠透過此介面呼叫由 ConcreteStrategy 所實做的演算法。</li>
<li><strong>ConcreteStrategy</strong>：根據 Strategy 所制訂的介面，具體實做出合適的演算法。</li>
<li><strong>Context</strong>：Strategy 的操作者，另可定義介面以供 Strategy 存取自己的資料。</li>
</ul>
</blockquote>
<p>讓我們直接來看看，單純的條件判斷式在遊戲系統中產生的問題，以及 Strategy 範式如何解決問題。</p>
<p>在遊戲系統的程式碼中，<strong>特別是與遊戲邏輯相關的部分，經常會面臨需依照各項條件要素，切換不同控制邏輯的情況。</strong>以一般的遊戲主迴圈流程為例，多半由偵測鍵盤滑鼠等輸入裝置、更新遊戲世界，以及繪製遊戲世界三項程序組成：</p>
<pre name="code" class="cpp">
while (m_GameIsRunning) {
	// 處理輸入裝置訊息
	ProcessInput();
	// 更新遊戲邏輯
	Update();
	// 繪製遊戲世界
	Render();
}
</pre>
<p>但是對於一個完整的遊戲來說，光是區分為上述這三項處理程序，無法完整滿足遊戲中的各種設計需求以及狀態轉換。例如，在剛啟動遊戲時、進入遊戲主選單時，以及遊戲運行中時，可能就會需要三種完全不同的偵測、更新與繪製程序，所以在此可以宣告一些列舉值，用來區分各種遊戲狀態，接著再以 if-else 條件判斷句在處理程序中分項處理：</p>
<pre name="code" class="cpp">
enum GameState
{
	GameState_Init,
	GameState_Menu,
	GameState_InGame,
};

void ProcessInput()
{
	if (m_State == GameState_Init) {
		// 處理剛啟動遊戲時的輸入偵測邏輯
	}
	else if (m_State == GameState_Menu) {
		// 處理進入遊戲主選單時的輸入偵測邏輯
	}
	else if (m_State == GameState_InGame) {
		// 處理遊戲運行時的輸入偵測邏輯
	}
}
</pre>
<p>除了 ProcessInput() 函式以外，Update() 與 Render() 函式也以同樣的方法區分成不同的流程控制邏輯。如此一來，只要我們在程式執行的過程中改變 m_State 的數值，當遊戲主迴圈下次進入這三項程序時，自然就會切換至不同的處理流程中了。這樣的程式邏輯看起來完全無害，不是嗎？</p>
<p>但實際上，使用 if-else 敘述句的作法不夠直覺化，有另外一位控制結構的候選者更合適於擔綱這項任務。只要是熟習程式語言基本語法的讀者，應該不難想到可以將上面的 if-else 敘述句，轉換成另外一種作用相同的 switch-case 敘述句：</p>
<pre name="code" class="cpp">
void ProcessInput()
{
	switch (m_State)	{
		case GameState_Init: {
			// 處理剛啟動遊戲時的輸入偵測邏輯
			break;
		}
		case GameState_Menu: {
			// 處理進入遊戲主選單時的輸入偵測邏輯
			break;
		}
		case GameState_InGame: {
			// 處理遊戲運行時的輸入偵測邏輯
			break;
		}
	}
}
</pre>
<p>相較於 if-else 敘述句，使用 switch-case 敘述句的優點，在於每個分支選項都會擁有同等的重要性。所以對於程式設計者來說，只要一見到 switch-case，首先就會聚焦在控制變數 m_State 的身上，並且直覺化地理解其中的各項 case 程序，都是具有同等重要性的程式碼。而也正因為 switch-case 比較符合程式設計者的直覺觀感，所以在遊戲系統的程式碼中，<strong>switch-case 敘述句也是最常被濫用與誤用的條件判斷式</strong>。</p>
<p>當遊戲專案剛啟始，同時整個程式系統仍處於萌芽階段時，使用前述 switch-case 的方法並不會產生任何問題。然而，隨著與日俱增的專案進度與設計需求，程式系統也會無可避免地越來越加錯綜複雜。可能一開始時，程式設計者只是單純想使用一個最簡單明快的方法，以解決切換控制邏輯的需求；然而，隨之而來的程式碼增修幅度，總是遠遠超過原先的預期程度。即使只是偶爾增加個十幾行程式碼，到了專案中後期時，卻有可能使一個小小的 switch-case 敘述句膨脹至 500 行以上的巨型怪物。</p>
<p>為了避免 switch-case 毫無節制地接受程式設計者的餵食，最終成為體態臃腫的大傢伙，我們立即可想到的一項改善方式，就是把原來散落在 case 敘述句中的程式碼，依照功能分門別類，包裝成一個個的中小型函式：</p>
<pre name="code" class="cpp">
void Update()
{
	switch (m_State)
	{
		case GameState_Init:
		{
			UpdateNetwork();
			UpdateGameWorld();
			UpdatePhysicsEngine();
			UpdateAudioEngine();
			UpdateGraphicsEngine();
			break;
		}
		case GameState_Menu:
		{
			break;
		}
		case GameState_InGame:
		{
			break;
		}
	}
}
</pre>
<p>有些奉「效能」兩字為最高指導原則的程式設計者，可能會因為遊戲執行效能上的疑慮，而不願意將原來的程式碼包裝成一個個的小型函式。但事實上，這不僅是沒有必要的顧慮，也是一種「過早進行最佳化」的錯誤觀念。在最低限度的情況下，甚至只需要妥善利用 inline 方式來宣告函式，就能夠大幅降低函式呼叫的成本了。</p>
<p>雖然將 case 敘述句中的程式碼分裝處理是個好的開始，但是仍然沒有解決根本上的條件判斷式過多的問題，而且<strong>使用 switch-case 敘述句，就像是中了流行性感冒病毒一樣，會不斷地在整個程式系統中傳染擴散開來，讓程式設計者越來越難以擺脫</strong>。以上述的幾個分裝函式為例，除了在原有的 Update() 函式中判斷不同的運作條件之外，UpdateNetwork() 是不是同樣也需要進行判斷？還有 UpdateGameWorld()、UpdatePhysicsEngine() 和 UpdateGraphicsEngine() 函式呢？應該也免不了需要切換不同的控制流程吧！</p>
<p>所以，分裝函式最終經常會長出像這樣的程式碼：</p>
<pre name="code" class="cpp">
void UpdateNetwork()
{
	switch (m_State) {
		case GameState_Init: {
			break;
		}
		case GameState_Menu: {
			break;
		}
		case GameState_InGame: {
			break;
		}
	}
}

void UpdateGameWorld()
{
	switch (m_State)	{
		case GameState_Init: {
			break;
		}
		case GameState_Menu: {
			break;
		}
		case GameState_InGame: {
			break;
		}
	}
}
</pre>
<p>還有 UpdatePhysicsEngine()、UpdateGraphicsEngine()、UpdateAudioEngine() 和 UpdateOOXX() 等等，到處都是 switch-case 敘述句，想躲也躲不了！</p>
<p>而除了近乎無性繁殖的 switch-case 程式碼以外，更令人頭痛的是，當程式設計者需要加入一項全新的遊戲狀態，例如 GameState_Pause 來控制遊戲暫停時的邏輯運作時，我們必須得回過頭來，前往每一個與 m_State 變數相關的 switch-case 敘述句中，加上一則新的 case 敘述句使 GameState_Pause 狀態能夠正確運作。或許你會認為：「用 Copy-Paste 的方式很快就可以完成，不是嗎？」<strong>但是人為干預的動作越多，也就代表疏忽犯錯的可能性越高。</strong></p>
<p>清楚瞭解濫用 if-else 與 switch-case 的種種問題之後，就輪到本篇的英雄主角上場，拯救我們程式設計者的世界啦！為了能夠妥善管理各種不同遊戲狀態之間的運作邏輯，首先可以利用純虛擬函式，宣告一個無法直接具現化的抽象類別 GameState，來扮演最前面所提到的 Strategy 角色：</p>
<pre name="code" class="cpp">
class GameState
{
public:
	virtual void Update() = 0;
	virtual void Render() = 0;
	virtual void ProcessInput() = 0;
};
</pre>
<p>將 GameState 宣告為抽象類別，優點在於強制使用者必須自行定義子類別的實做細節，藉此可避免對於 GameState 類別的誤用情形。接著，就可以依照遊戲系統的需求，自 GameState 衍生出一個個獨特的 ConcreteStrategy 角色：</p>
<pre name="code" class="cpp">
class GameInitState : public GameState
{
public:
	virtual void Update() {
		// 處理剛進入遊戲時的更新程序
	}

	virtual void Render() {
		// 處理剛進入遊戲時的繪製程序
	}

	virtual void ProcessInput() {
		// 處理剛進入遊戲時的輸入偵測程序
	}
};

class GameMenuState : public GameState
{
public:
	virtual void Update() {
		// 處理遊戲主選單的更新程序
	}

	virtual void Render() {
		// 處理遊戲主選單的繪製程序
	}

	virtual void ProcessInput() {
		// 處理遊戲主選單的輸入偵測程序
	}
};
</pre>
<p>依此方式製作出 GameInitState、GameMenuState 與 GameInGameState 類別後，就可以快樂地和 switch-case 說再見囉！原來的 ProcessInput()、Update() 與 Render() 三項程序就可以修改為：</p>
<pre name="code" class="cpp">
void ProcessInput()
{
	m_CurrentState->ProcessInput();
}

void Update()
{
	m_CurrentState->Update();
}

void Render()
{
	m_CurrentState->Render();
}
</pre>
<p>是的，你沒看錯！就這麼容易！先前由一個 m_State 變數以及 switch-case 敘述句控制的條件判斷式，轉換成僅需要一個 m_CurrentState 物件進行操控的程式碼邏輯。程式設計者只需要依條件更換 m_CurrentState 所指向的 ConcreteStrategy 物件，就能夠輕鬆地享受後續的免費服務而無須費心。所以只要我們能夠善加利用 C++ 語言的「虛擬」與「多型」特性，就能夠為程式設計者節省下許多非常珍貴的腦容量與腦細胞，使用在其他更有意義的難題上。</p>
<p>而 Strategy 與 State 範式，不僅能夠用在遊戲主迴圈的流程控制程序中，更可以運用在人工智慧等許多不同的系統中。像遊戲系統中常見的角色狀態機制，同樣也可以利用 State 範式的方式來實作。首先定義好基礎的 State 類別之後，就可以進一步衍生出 IdleState、MoveState、CombatState 等等不同用途的狀態類別：</p>
<pre name="code" class="cpp">
class Character;

class State
{
public:
	State(Character* context);
};

class IdleState : public State
{
public:
	IdleState(Character* context);
};

class MoveState : public State
{
public:
	MoveState(Character* context);
};

class CombatState : public State
{
public:
	CombatState(Character* context);
};
</pre>
<p>「萬里長城萬里長～」如同萬里長城般綿延不絕而且橫跨數個頁面的 if-else 或 switch-case 敘述句，絕對是任何一位程式設計者最不願意見到的程式碼結構之一。當你爽快地揮舞著 if-else 與 switch-case 連段 Combo 招式時，請先暫停片刻，冷靜下來想想這樣的做法是否合適？是否有其他方法可以簡化條件判斷式？</p>
<p>另外，如果你習慣於撰寫動輒六、七層以上的條件判斷式深度，不但是在考驗自己的邏輯思考空間，同時也是在殘害程式碼的閱讀者與維護者——而很有可能你自己就是那位可憐的閱讀者與維護者。為了避免寫出深達地底三萬呎的迷宮結構，我們可以多加利用 return、break 敘述句，或者判斷反向的 boolean 值，以減少條件判斷式的巢狀深度。</p>
<p>平時要如何自我檢查是否已經中了條件判斷式的流行性感冒？<strong>通常當你發現自己反覆用著 Copy-Paste 在程式碼檔案之間，處理某些「大部分相同而小部分相異」的程式碼時，很可能就是一種濫用條件判斷式的徵兆。</strong>請記得，早期發現早期治療的效果最好，否則當程式碼日益增長茁壯，重構的痛苦也會越來越厚重。當你發現自己在兩個地方以上寫著相同的條件判斷式時，可能就是就是 Strategy 與 State 範式該出馬的時候了！</p>
<p>體認完 Strategy 與 State 如何拯救世界之後，最後也該瞭解它們可能產生的副作用。若從效能層面考量，比起單純的 switch-case 敘述句，使用 Strategy 或 State 範式需要一次額外的 virtual table 提領步驟，多少會造成某種程度的效能衝擊。然而，就像之前所提到的「不要過早進行最佳化」<strong>，程式設計者應該先以 80/20 法則確認系統的效能瓶頸所在，然後再開始動手改善，才能獲得最佳的優化效益</strong>。另外要注意的是，Strategy 與 State 範式並不是任何狀況都適用的銀子彈，如果用在錯誤的地方，將會產生過多小型的物件類別，反而可能降低類別階層的耦合性與內聚力。</p>
<p>只要善加利用 Strategy 與 State 範式，就可以大幅簡化遊戲系統的運作邏輯，使程式碼更加易於閱讀與維護，同時也會變成一種無須太多註解與文件說明，就能夠在程式設計者間達到良好成效的溝通方式。以後再見到 switch-case 敘述句或是準備下手使用之前，不妨先仔細分析一下，想想是否能夠使用 Strategy 或者 State 範式取而代之吧！這樣可以讓你自己也讓其他人過得更快樂唷～</p>
<p><strong>延伸閱讀</strong>：</p>
<ul>
<li>《重構——改善既有程式的設計》：<br />
〈8.15〉以 State/Strategy 取代型別代碼</li>
<li>《重構——向範式前進》：<br />
〈7.2〉以 Strategy 替代條件邏輯<br />
〈7.4〉以 State 替代「狀態變換」條件句</li>
</ul>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=1185&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/pattern/strategy-and-state/feed</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>遊戲專案的目錄結構規劃</title>
		<link>http://blog.monkeypotion.net/gameprog/concept/directory-structure-planning-for-game-project</link>
		<comments>http://blog.monkeypotion.net/gameprog/concept/directory-structure-planning-for-game-project#comments</comments>
		<pubDate>Sun, 11 Jan 2009 16:12:29 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[觀念技巧]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=922</guid>
		<description><![CDATA[不知道各位如何規劃遊戲專案的目錄結構？各位所使用的是專案管理體系的正規方法，或者只是隨意地將檔案與資料夾散佈在電腦中的各處？在一個遊戲專案的開發過程裡，重要的資產除了程式碼檔案之外，還包括了設計文件、美術素材、音效檔案，以及最終的執行檔等等。該如何妥善管理這些不同類別的項目，以達到良好的成效？在此將以我個人目前使用的方法，提出一套目錄結構的規劃架構，與各位一同進行交流討論。 有些人喜歡將許多各式各樣不同用途的資料夾，直接放置在電腦的「桌面」上，最後經常會使得整個桌布，幾乎被各種不同圖示與資料夾所淹沒。這樣的作法，在一時的使用上或許非常方便，但是如果沒有善加整理歸檔，未來很容易會找不到想尋找的目標文件或檔案。不論是公司的正式專案，或是自己在工作之餘開發的個人專案，都能夠從良好的目錄結構規劃中，得到易於管理與變動的益處。 首先，我習慣在電腦硬碟的作業系統槽（通常是 C 槽）之外，選擇一個空間足夠的磁碟槽，在根目錄下開啟幾個資料夾進行專案的管理： Codespace：專案區，只存放由 CVS 或 SVN 等版本控制系統取出的各項專案。 Workspace：工作區，存放各項測試中的程式、待完成的工作項目，以及各種經常需要查閱使用的文件及資源。 Temp：暫存區，存放各種暫時物件，包括從網路上下載的安裝檔，以及檔案解壓縮後的資料夾等等。 專案區中的資料，應該經常與版本控制系統保持同步化，盡可能減少修改後的版本存在於自己電腦中的時間。即使個人的工作硬碟不幸遭遇毀損的狀況，也能夠由版本控制系統的伺服器中，重新取回整個專案的資料。而工作區，則用來存放各項試驗中的專案；例如在剛開始接觸 Boost.Pool 函式庫時，我就會在工作區裡開設一個測試資料夾命名為 TestBoostPool 進行各項測試，等到成果達到一定程度後，再將專案提交至版本控制系統以及專案區中存放。最後，在暫存區中的資料，則只是便利暫時性的使用，可以隨時將全部的內容清空刪除。 確立了磁碟槽中的工作目錄結構之後，接下來的關鍵要點就是在於遊戲專案的目錄結構規劃。最終規劃完畢的專案目錄結構，一定要達到便利而易用的目標，最好不要產生太深太多層的目錄，但又要能夠維持各個目錄之間的獨立性。一開始，先按照美術設計、企畫設計與程式設計的工作產物，大致上將目錄分為三項： Asset：遊戲素材區，存放美術設計者製作的各種素材，以及音效、音樂、字型等等遊戲資源檔案。 Document：遊戲文件區，存放各項設計文件，包含企畫設計、美術設計以及程式架構的相關文件在內。 Source：遊戲程式碼區，存放遊戲專案的程式碼，其中再依不同子專案，分成不同的子目錄。 然後在最外層的資料夾，應該以遊戲專案的名稱，或者某個獨特的專案代號做為命名。按照上述的架構，所規劃出來的專案目錄結果如下所示： ＜ProjectName＞/ Asset/ Model/ Script/ Shader/ Sound/ Texture/ Document/ Art/ Design/ Programming/ Reference/ Source/ ＜Project#1＞/ ＜Project#2＞/ ＜Project#3＞/ 為了便利於遊戲版本的發佈與包裝作業，最好能夠將完整可執行的遊戲資料，包含遊戲執行檔、函式庫檔與動態連結檔等，放置在一個獨立的資料夾中。所以在＜ProjectName＞目錄下，我們需要再新增一個 Build 目錄。這個目錄中的資料，是由專案執行流程所動態產生出來的結果，其中包含 Debug 與 Release 兩個子目錄分別存放兩種版本的資料。另外，為了預防兩種版本的執行檔被搞混，程式設計者可以在原來的執行檔名稱後，多加一個「_D」做為識別 Debug 版本的方式，例如將 Debug 版與 Release 版的執行檔分別命名為 Game_D.exe 與 Game.exe。 有些人或許會有疑問：「如果執行檔在 [...]]]></description>
			<content:encoded><![CDATA[<p>不知道各位如何規劃遊戲專案的目錄結構？各位所使用的是專案管理體系的正規方法，或者只是隨意地將檔案與資料夾散佈在電腦中的各處？在一個遊戲專案的開發過程裡，重要的資產除了程式碼檔案之外，還包括了設計文件、美術素材、音效檔案，以及最終的執行檔等等。該如何妥善管理這些不同類別的項目，以達到良好的成效？在此將以我個人目前使用的方法，提出一套目錄結構的規劃架構，與各位一同進行交流討論。</p>
<p>有些人喜歡將許多各式各樣不同用途的資料夾，直接放置在電腦的「桌面」上，最後經常會使得整個桌布，幾乎被各種不同圖示與資料夾所淹沒。這樣的作法，在一時的使用上或許非常方便，但是如果沒有善加整理歸檔，未來很容易會找不到想尋找的目標文件或檔案。不論是公司的正式專案，或是自己在工作之餘開發的個人專案，都能夠從良好的目錄結構規劃中，得到易於管理與變動的益處。</p>
<p>首先，我習慣在電腦硬碟的作業系統槽（通常是 C 槽）之外，選擇一個空間足夠的磁碟槽，在根目錄下開啟幾個資料夾進行專案的管理：</p>
<ul>
<li><strong>Codespace</strong>：專案區，只存放由 CVS 或 SVN 等版本控制系統取出的各項專案。</li>
<li><strong>Workspace</strong>：工作區，存放各項測試中的程式、待完成的工作項目，以及各種經常需要查閱使用的文件及資源。</li>
<li><strong>Temp</strong>：暫存區，存放各種暫時物件，包括從網路上下載的安裝檔，以及檔案解壓縮後的資料夾等等。</li>
</ul>
<p>專案區中的資料，應該經常與版本控制系統保持同步化，盡可能減少修改後的版本存在於自己電腦中的時間。即使個人的工作硬碟不幸遭遇毀損的狀況，也能夠由版本控制系統的伺服器中，重新取回整個專案的資料。而工作區，則用來存放各項試驗中的專案；例如在剛開始接觸 Boost.Pool 函式庫時，我就會在工作區裡開設一個測試資料夾命名為 TestBoostPool 進行各項測試，等到成果達到一定程度後，再將專案提交至版本控制系統以及專案區中存放。最後，在暫存區中的資料，則只是便利暫時性的使用，可以隨時將全部的內容清空刪除。</p>
<p><span id="more-922"></span></p>
<p>確立了磁碟槽中的工作目錄結構之後，接下來的關鍵要點就是在於遊戲專案的目錄結構規劃。<strong>最終規劃完畢的專案目錄結構，一定要達到便利而易用的目標，最好不要產生太深太多層的目錄，但又要能夠維持各個目錄之間的獨立性。</strong>一開始，先按照美術設計、企畫設計與程式設計的工作產物，大致上將目錄分為三項：</p>
<ul>
<li><strong>Asset</strong>：遊戲素材區，存放美術設計者製作的各種素材，以及音效、音樂、字型等等遊戲資源檔案。</li>
<li><strong>Document</strong>：遊戲文件區，存放各項設計文件，包含企畫設計、美術設計以及程式架構的相關文件在內。</li>
<li><strong>Source</strong>：遊戲程式碼區，存放遊戲專案的程式碼，其中再依不同子專案，分成不同的子目錄。</li>
</ul>
<p>然後在最外層的資料夾，應該以遊戲專案的名稱，或者某個獨特的專案代號做為命名。按照上述的架構，所規劃出來的專案目錄結果如下所示：</p>
<blockquote>
<pre>
＜ProjectName＞/

    Asset/
        Model/
        Script/
        Shader/
        Sound/
        Texture/

    Document/
        Art/
        Design/
        Programming/
        Reference/

    Source/
        ＜Project#1＞/
        ＜Project#2＞/
        ＜Project#3＞/
</pre>
</blockquote>
<p>為了便利於遊戲版本的發佈與包裝作業，最好能夠將完整可執行的遊戲資料，包含遊戲執行檔、函式庫檔與動態連結檔等，放置在一個獨立的資料夾中。所以在＜ProjectName＞目錄下，我們需要再新增一個 <strong>Build</strong> 目錄。這個目錄中的資料，是由專案執行流程所動態產生出來的結果，其中包含 Debug 與 Release 兩個子目錄分別存放兩種版本的資料。另外，為了預防兩種版本的執行檔被搞混，程式設計者可以在原來的執行檔名稱後，多加一個「_D」做為識別 Debug 版本的方式，例如將 Debug 版與 Release 版的執行檔分別命名為 Game_D.exe 與 Game.exe。</p>
<p>有些人或許會有疑問：「如果執行檔在 Build 目錄下，要怎麼順利存取 Asset 目錄中的遊戲素材？」其實很簡單，以 MSVC 的專案為例，只要在專案屬性的「除錯」頁面中，將「工作目錄」設定為 Build 所在的目錄，接著在程式中設定存取素材的檔案路徑，這樣一來，當程式設計者將專案建置完畢按下 F5 鍵時，就能夠正確無誤地執行程式了。</p>
<p>在開發與除錯階段時，將執行檔以及遊戲素材分開置放在 Build 與 Asset 目錄中，的確可以簡化一些繁瑣的資料搬移步驟，但是對於正式釋出的遊戲版本，程式設計者是否需要重新設定執行檔與素材檔案的相對路徑？並不需要。只要利用以下的方式，就可以一次滿足 Debug 與 Release 兩種建置版本的需求：</p>
<pre name="code" class="cpp">
#ifdef _DEBUG
    // Path settings for debug build
    static const std::string ASSET_PATH = "../../Asset/";
    static const std::string MODEL_PATH = "../../Asset/Model/";
    static const std::string SCRIPT_PATH = "../../Asset/Script/";
#else
    // Path settings for release build
    static const std::string ASSET_PATH = "./Data/";
    static const std::string MODEL_PATH = "./Data/Model/";
    static const std::string SCRIPT_PATH = "./Data/Script/";
#endif
</pre>
<p>到目前為止，看起來一切都還不錯，但如果只是把遊戲專案分成 Asset、Build、Document 與 Source 四個目錄，仍然不足以達到最佳的專案目錄規劃成效。</p>
<p>除了內部自行開發的程式碼以外，很多時候在遊戲專案中，也會使用第三方團體所提供的程式碼，為了不與我們自己所寫的程式碼專案搞混，以利後續進行各種維護或者升級程序，所以應該將這些程式碼或函式庫獨立出來，在＜ProjectName＞目錄下增加一個 <strong>ThirdParty</strong> 子目錄放置這些專案。在個別的專案目錄下，可以再進一步分為 bin、include 與 lib 三個子目錄，分別存放可執行檔、表頭檔以及函式庫檔案。</p>
<p>另外一種處理第三方團體程式碼的方式，是直接安裝在系統槽的目錄，例如 C:\Program Files\ 之中。就我而言，如果像是 Boost C++ Libraries 或者 Lua 這一類比較固定不變，程式設計者也不會自行修改的函式庫，我就會直接安裝在系統槽目錄中；而如果是為了個別專案的特殊需求，有自行做出修改的第三方函式庫，就適合置放在專案資料夾的 ThirdParty 目錄中。</p>
<p>在 Visual Studio 的預設設定中，會將專案的 Project 檔 (*.vcproj) 存放在與原始碼檔案相同的目錄下。然而這樣的做法，對於具有跨平台需求或者更換程式環境需求的專案來說，可能會造成一些額外的困擾。因此我們可以把與程式專案工具相關的 Solution 檔 (*.sln) 與 Project 檔，分離到獨立的目錄中，在＜ProjectName＞目錄下新增 <strong>Project</strong> 子目錄，依不同的程式環境分成不同子目錄，例如 Msvc80、Msvc90、CodeBlocks 與 XCode 等等，就能夠非常便利地增加新的設定了。</p>
<p>有時候，我們會需要在不開啟程式專案工具的情況下，重新建置整個遊戲專案。為了要達到這樣的目標，會需要撰寫一些命令列指令、Shell Script，或是其他專門的建置工具語言。所以同樣可以在＜ProjectName＞目錄下，新增一個 <strong>Script</strong> 目錄，其中再依不同的作業系統平台，例如 Win32、Linux 與 Mac，分成不同的子目錄。</p>
<p>將專案的 Project 檔與建置 Script 抽離了以後，Source 目錄就會變得非常單純，依照不同的子專案分成數個子目錄，其中僅僅存有 *.h、*.cpp 以及 *.inl 檔案而已。另外，還可以將程式專案的「中介檔案目錄」指定到 Build 目錄中，因為建置過程中產生的 *.obj 等相關檔案，不需要保存下來，所以可以置放在動態產生的 Build 目錄中，更進一步保持 Source 與 Project 目錄的簡化與純淨。</p>
<p>最後，在＜ProjectName＞目錄下新增 <strong>Tool</strong> 子目錄，將一些經常會用到的工具程式，例如遊戲編輯器、角色檢視器、資料轉換器等等，置放於其中以利使用。</p>
<p>遊戲專案目錄規劃的最終版本如下：</p>
<blockquote>
<pre>
＜ProjectName＞/

    Asset/
        Model/
        Script/
        Shader/
        Sound/
        Texture/

    Build/
        Debug/
        Release/

    Document/
        Art/
        Design/
        Programming/
        Reference/

    Project/
        Codeblocks/
        Msvc80/
        Msvc90/
        Xcode/

    Script/
        Linux/
        Mac/
        Win32/

    Source/
        ＜Project#1＞/
        ＜Project#2＞/
        ＜Project#3＞/

    ThirdParty/
        ＜Library#1＞/
        ＜Library#2＞/
        ＜Library#3＞/
            bin/
            include/
            lib/
            source/

    Tool/
        Converter/
        Editor/
        Viewer/
</pre>
</blockquote>
<p>只要按照上述的步驟執行，完整的遊戲專案目錄就大功告成囉！一套簡單且易於使用的遊戲專案目錄結構，不只能夠省去許多手動搬移檔案以及刪除檔案的程序，更能協助我們把各種資料妥善分類，將它們分配存放到正確的歸所。<strong>請不要再讓你的文件、檔案、資料夾與專案，走失在電腦裡的某個角落了！</strong></p>
<p>你覺得這樣的規劃如何？合乎你的使用需求以及專案現況嗎？或者還有什麼可以修改之處？歡迎提出來一起討論喔～ ：D</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=922&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/concept/directory-structure-planning-for-game-project/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>記憶體配置：Pooled Allocation技術評比與效能測試</title>
		<link>http://blog.monkeypotion.net/gameprog/advanced/profiling-in-pooled-allocation-techniques</link>
		<comments>http://blog.monkeypotion.net/gameprog/advanced/profiling-in-pooled-allocation-techniques#comments</comments>
		<pubDate>Thu, 04 Dec 2008 16:13:56 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[進階技術]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=842</guid>
		<description><![CDATA[從剛進入遊戲業界開始，我就一直對於「記憶體配置」(Memory Allocation) 的系統架構與相關議題很感興趣。後來隨著閱讀書籍量的增加與工作經驗上的累積，逐漸接觸到各種不同設計架構與實作方法的記憶體管理機制，現在終於能夠將一些初步的心得整理出來了。 在遊戲程式設計的領域中，程式設計者經常需要即時且動態地產生出大量的小型物件，例如怪物、特效、場景物乃至於低階的節點物件等等。如果遊戲程式設計者只是天真爛漫地使用著單純無害的 operator new 以及 operator delete 程序，在經過遊戲執行中不斷反覆地配置與歸還記憶體的行為之後，很快就會使得完整的記憶體區段面臨嚴重的「記憶體破碎」(Memory Fragmentation) 問題。更糟的是，記憶體破碎的問題往往很難被偵測出來，而容易被開發者所忽略。 為了盡可能降低記憶體破碎的狀況，並且得到高效的物件配置程序，除了從高階層面利用資源管理 (Resource Management) 機制減少遊戲物件的生成與毀滅行為之外，同時也需要從比較低階的層面，也就是屬於記憶體配置的功能面向著手改善。在使用者每次進行 operator new 操作向作業系統索取記憶體空間時，C 語言的函式庫除了配置所要求的區塊大小以外，還會另外生成一小塊額外的區塊以簿記相關的資訊。對於經常進行生成與毀滅程序的小型物件來說，這樣的行為模式就顯得十分浪費而不具效率。如果能夠一次性的配置出一大塊記憶體，然後再依使用者的需求傳回部分區塊，可望就能夠改善 operator new 程序所產生的額外負擔。 為了能夠妥善管理一大塊的記憶體空間，程式設計者發展出了「Free List」這個用來處理動態配置記憶體的特殊資料結構。Free List 通常是以鏈結串列 (Linked List) 做為基底結構，將目前可使用以及使用中的記憶體空間紀錄並且連結起來。一般常聽到的「記憶體池」(Memory Pool) 或者「池式配置」(Pooled Allocation)，就是利用 Free List 資料結構實作出來的記憶體管理機制，也是遊戲程式設計者不可不知的記憶體系統管理技巧。而應該如何實作出高效能的 Free List 結構，使創建物件與刪除物件時的負擔減至最小，更是池式配置記憶體機制中最關鍵的要點。 前陣子除了在《Modern C++ Design》與《Game Programming Gems 7》兩本書裡，學習了兩種池式配置的設計實作方法之外，最近正好也剛踏入 Boost C++ Libraries 的國界中，發現其中有個專門做為池式配置功能用途的記憶體管理函式庫 Boost.Pool，不禁使我非常好奇 Boost.Pool 函式庫與其他池式配置技術的效能相比如何，於是決定以下列四個項目做為這次效能試驗的目標： operator new：原始的 C++ [...]]]></description>
			<content:encoded><![CDATA[<p>從剛進入遊戲業界開始，我就一直對於<strong>「記憶體配置」(Memory Allocation)</strong> 的系統架構與相關議題很感興趣。後來隨著閱讀書籍量的增加與工作經驗上的累積，逐漸接觸到各種不同設計架構與實作方法的記憶體管理機制，現在終於能夠將一些初步的心得整理出來了。</p>
<p><strong>在遊戲程式設計的領域中，程式設計者經常需要即時且動態地產生出大量的小型物件</strong>，例如怪物、特效、場景物乃至於低階的節點物件等等。如果遊戲程式設計者只是天真爛漫地使用著單純無害的 operator new 以及 operator delete 程序，在經過遊戲執行中不斷反覆地配置與歸還記憶體的行為之後，很快就會使得完整的記憶體區段面臨嚴重的<a href="http://en.wikipedia.org/wiki/Fragmentation_(computer)"><strong>「記憶體破碎」</strong></a><strong>(Memory Fragmentation)</strong> 問題。更糟的是，記憶體破碎的問題往往很難被偵測出來，而容易被開發者所忽略。</p>
<p>為了盡可能降低記憶體破碎的狀況，並且得到高效的物件配置程序，除了從高階層面利用資源管理 (Resource Management) 機制減少遊戲物件的生成與毀滅行為之外，同時也需要從比較低階的層面，也就是屬於記憶體配置的功能面向著手改善。在使用者每次進行 operator new 操作向作業系統索取記憶體空間時，C 語言的函式庫除了配置所要求的區塊大小以外，還會另外生成一小塊額外的區塊以簿記相關的資訊。對於經常進行生成與毀滅程序的小型物件來說，這樣的行為模式就顯得十分浪費而不具效率。如果能夠一次性的配置出一大塊記憶體，然後再依使用者的需求傳回部分區塊，可望就能夠改善 operator new 程序所產生的額外負擔。</p>
<p>為了能夠妥善管理一大塊的記憶體空間，程式設計者發展出了<a href="http://en.wikipedia.org/wiki/Free_list">「Free List」</a>這個用來處理動態配置記憶體的特殊資料結構。Free List 通常是以鏈結串列 (Linked List) 做為基底結構，將目前可使用以及使用中的記憶體空間紀錄並且連結起來。<strong>一般常聽到的「記憶體池」(Memory Pool) 或者「池式配置」(Pooled Allocation)，就是利用 Free List 資料結構實作出來的記憶體管理機制，也是遊戲程式設計者不可不知的記憶體系統管理技巧。</strong>而應該如何實作出高效能的 Free List 結構，使創建物件與刪除物件時的負擔減至最小，更是池式配置記憶體機制中最關鍵的要點。</p>
<p><span id="more-842"></span></p>
<p>前陣子除了在《Modern C++ Design》與《Game Programming Gems 7》兩本書裡，學習了兩種池式配置的設計實作方法之外，最近正好也剛踏入 Boost C++ Libraries 的國界中，發現其中有個專門做為池式配置功能用途的記憶體管理函式庫 <strong>Boost.Pool</strong>，不禁使我非常好奇 Boost.Pool 函式庫與其他池式配置技術的效能相比如何，於是決定以下列四個項目做為這次效能試驗的目標：</p>
<ul>
<li><strong>operator new</strong>：原始的 C++ 記憶體配置操作。</li>
<li><a href="http://loki-lib.sourceforge.net/"><strong>Loki Library SmallObj</strong></a>：在《Modern C++ Design》書中，作者以 Policy-Based Design 設計思維做為起始點，實作了一個專為小型物件設計的池式配置機制，並且包裝在開源專案 Loki 函式庫中。而在本文的測試專案中，將只使用其中的 SmallObj 類別做為測試用途；在 SmallObj 的類別架構中，完整實作了書中所涵蓋的「小型物件配置器」。另外需注意的是，SmallObj 的預設行為只會處理大小在 256 bytes 以下的物件，若使用者要求創建更大的物件，系統便會直接交由 operator new 配置記憶體。</li>
<li><strong>High Performance Heap Allocator</strong>：在《Game Programming Gems 7》裡，「High Performance Heap Allocator」（簡稱為 HPHA）一文的作者，對 Loki 的池式配置機制提出改善方法，將使用者對於記憶體的要求，分成了兩種不同的運作策略：在 256 bytes 以下的小型物件，使用 Win32 平台的 VirtualAlloc() 函式，配置出自然對齊的記憶體區塊，使物件的歸還程序能夠達到很好的執行效能；而對於超過 256 bytes 的大型物件，則使用「紅黑樹」(Red-Black Tree) 結構實作出特製化的管理機制。</li>
<li><a href="http://www.boost.org/doc/libs/1_37_0/libs/pool/doc/index.html"><strong>Boost.Pool Library</strong></a>：從屬於 Boost C++ Libraries 內的函式庫之一，專門做為記憶體管理與池式配置的功能用途。</li>
</ul>
<p>在使用方法上，Loki、HPHA 與 Boost.Pool 三者各不相同。若使用 Loki SmallObj 的技術，必須先讓使用者的類別繼承自 Loki::SmallObject 類別，而在 Loki::SmallObject 中已經覆寫了類別的 operator new，所以接下來就能夠直接使用 new 與 delete 進行配置與刪除動作；HPHA 則定義了二個全域函式 custom_new< T >() 與 custom_delete() 供使用者配置及刪除物件；最後，在 Boost.Pool 裡需要先建立出管理記憶體用的「物件池」boost::object_pool< T >，然後再以物件池的成員函式 construct() 與 destroy() 創建並且毀滅物件。程式碼如下所示：</p>
<pre name="code" class="cpp">
class TestObject
{
    char data[8];
};

class TestLokiObject : public Loki::SmallObject<>
{
    char data[8];
};

// Loki SmallObj
TestLokiObject* obj = new TestLokiObject;
delete obj;

// HPHA
TestObject* obj = custom_new< TestObject >();
custom_delete(obj);

// Boost Pool
boost::object_pool< TestObject > objPool;
TestObject* obj = objPool.construct();
objPool.destroy(obj);
</pre>
<p>首先，在第一個測試套件中，採用的是「交錯配置」的方式，也就是在創建物件之後，立即將物件刪除：</p>
<pre name="code" class="cpp">
Timer timer;

timer.Start();
for (unsigned int i = 0; i < testNum; i ++)
{
    // 創建物件
	TestObject* obj1 = new TestObject(i);
	// 立即刪除物件
	delete obj1;
	TestObject* obj2 = new TestObject(i);
	delete obj2;
	TestObject* obj3 = new TestObject(i);
	delete obj3;
	TestObject* obj4 = new TestObject(i);
	delete obj4;
	TestObject* obj5 = new TestObject(i);
	delete obj5;
}
timer.Stop();
</pre>
<p>其中的 testNum 變數，為使用者可指定的總測試次數，在此我輸入的測試次數為 500000 次。另外，為了量測物件的大小是否會對各項試驗目標產生影響，在測試專案中會對三個不同大小的物件類別，分別為 8 bytes、64 bytes 以及 280 bytes，進行相同的測試程序。結果如下圖所示：</p>
<div id="attachment_948" class="wp-caption aligncenter" style="width: 650px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2008/12/test-suite-1.jpg" alt="Test Suite 1" title="test-suite-1" class="size-full wp-image-948" /><p class="wp-caption-text">Test Suite 1</p></div>
<p>由測試結果可以清楚地瞭解，<strong>無論物件大小為何，Boost Pool 的表現都獲得了壓倒性的勝利</strong>，特別是當物件大小為 8 bytes 與 64 bytes 時，更是呈現出超過其他試驗目標 10 倍以上的效能！此外，在 HPHA 與 Loki SmallObj 的比較上，在小型的 8 bytes 與 64 bytes 物件上，Loki 的表現仍然勝過 HPHA；而在 280 bytes 的物件上，HPHA 則毫無意外地超越了 Loki 的效能。</p>
<p>接著在第二項測試套件中，則採用「整體配置」的方式，先一次創建出所有的物件後，然後再將這些物件一一刪除：</p>
<pre name="code" class="cpp">
Timer timerNew;
Timer timerDelete;

std::vector< TestObject* > pool;
pool.reserve(testNum);

// 創建所有的物件
timerNew.Start();
for (unsigned int i = 0; i < testNum; i ++)
{
	TestObject* obj = new TestObject(i);
	pool.push_back(obj);
}
timerNew.Stop();

// 一一刪除所有的物件
timerDelete.Start();
for (unsigned int i = 0; i < testNum; i ++)
{
	delete (pool[i]);
}
timerDelete.Stop();
</pre>
<p>測試次數為 50000 次，量測的時間數據則分成「Allocation」與「Deallocation」兩個部分，結果如下圖所示：</p>
<div id="attachment_949" class="wp-caption aligncenter" style="width: 650px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2008/12/test-suite-2.jpg" alt="Test Suite 2" title="test-suite-2" class="size-full wp-image-949" /><p class="wp-caption-text">Test Suite 2</p></div>
<p>喔買尬，<strong>Boost Pool 的表現真是出人意表的慘烈！</strong>對各個物件一一進行刪除程序時，Boost Pool 竟然得出如此令人難以接受的結果，難道之前的「整體配置」測試成果不過是假象而已？為了找出挽救 Boost Pool 的可能性，繼續修改第二項測試，增加一項「Boost Pool Ptr」測試程序，在刪除程序時，不對物件一一進行刪除動作，而是直接刪除掉整個 Boost Pool 的物件池：</p>
<pre name="code" class="cpp">
boost::object_pool< TestObject >* objPoolPtr = new boost::object_pool< TestObject >();

// 創建物件
timerNew.Start();
for (unsigned int i = 0; i < testNum; i ++)
{
	TestObject* obj = objPoolPtr->construct(i);
	pool.push_back(obj);
}
timerNew.Stop();

// 刪除 Boost Pool 物件池
timerDelete.Start();
delete objPoolPtr;
timerDelete.Stop();
</pre>
<p>測試結果如下圖所示：</p>
<div id="attachment_950" class="wp-caption aligncenter" style="width: 650px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2008/12/test-suite-2-mod.jpg" alt="Test Suite 2 Mod" title="test-suite-2-mod" class="size-full wp-image-950" /><p class="wp-caption-text">Test Suite 2 Mod</p></div>
<p>由新增的 Boost Pool Ptr 試驗結果可知，如果直接對 Boost Pool 物件池進行刪除（池中所有物件都會被刪除），將能夠獲得非常出色的效能表現；但是如果對單一物件進行物件毀滅與記憶體歸還動作時，就只能夠得出非常緩慢而糟糕的效能表現。</p>
<p>量測出三者的執行時期效能之後，在其他面向的考量上，Loki Library 與 Boost Pool Library 都擁有能夠跨平台的優勢，而 HPHA 由於使用了 Win32 平台的特殊記憶體配置函式，所以沒辦法直接移植到其他平台上使用。評估跨平台、多執行緒支援、易用性與平均效能表現後，總的來說，我對於這三項池式配置技術的評比如下：</p>
<blockquote>
<ul>
<li>HPHA：★★★</li>
<li>Loki SmallObj：★★★</li>
<li>Boost Pool：★★★★</li>
</ul>
</blockquote>
<p>最後我所得出的結論是，<strong>只要程式設計者能夠確保在遊戲系統中，不會連續性地刪除單一的遊戲物件，就可以放心地使用 Boost Pool 進行記憶體池式配置與管理</strong>，保證能夠得到非常令人滿意的效能表現，遠遠勝過於使用傳統的 operator new 操作，以及 Loki Library 與 HPHA 的池式配置技術。</p>
<p>如果讀者有興趣深入瞭解 Boost.Pool 的設計架構與實作細節，請參考這篇由侯捷先生所寫的<a href="http://www.runpc.com.tw/pdf/160/R160E12.pdf"><strong>「Boost技術與應用系列文章（2）Boost.Pool」</strong></a>（PDF 檔）。我在網路上搜尋 Boost 函式庫的相關資訊時，恰巧挖到了這篇絕佳的文章，才知道侯捷在《Run! PC》雜誌上刊載過許多與 Boost C++ Libraries 主題相關的深入技術文章；可惜的是，目前我只有搜尋到唯一這篇文章可供下載。</p>
<p>對於任何程式系統來說，不論是再精巧神妙的設計或實作方式，都會具有獨特的優勢以及劣勢。有的解決方案執行時期效能很高，代價卻是多出一倍的記憶體使用量；有的解決方案記憶體用量極少，卻會佔去大量的 CPU 資源；有的解決方案兼具速度與空間的優勢，卻無法滿足跨平台的需求。<strong>這是程式系統的設計者與使用者都必然會遭遇到的「取捨」(Tradeoff) 難題，也正是程式設計領域裡最令人目眩神迷的樂趣之一。</strong></p>
<p>執行檔及原始碼下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=19"><strong>PooledAllocation_TestSuite.7z</strong></a> <small>(下載次數： 376 )</small><br />
（附註說明：如果要自行建置測試專案，專案裡已經包含了 HPHA 的原始碼以及編譯完成的 Loki Library 函式庫，另外還需要自行下載 Boost C++ Libraries，並且設定正確的表頭檔路徑才能夠順利建置專案。）</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=842&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/advanced/profiling-in-pooled-allocation-techniques/feed</wfw:commentRss>
		<slash:comments>22</slash:comments>
		</item>
		<item>
		<title>Database Hot Loader 最終曲：Tuning and Measuring Performance</title>
		<link>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-tuning-and-measuring-performance</link>
		<comments>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-tuning-and-measuring-performance#comments</comments>
		<pubDate>Thu, 06 Nov 2008 16:12:38 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[進階技術]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=582</guid>
		<description><![CDATA[歷經了漫長的 DHL 系統一二三部曲後，終於來到了這一系列文章的最終曲。 本篇的重點在於對 DHL 系統進行效能的優化以及量測。首先，對於 LuaDatabaseManager 類別提出幾項效能優化的可能性。以資料庫系統的作用來說，一般使用者會期望在索取資料的程序中，得到最佳化的執行效能，才不會因為對於資料庫的頻繁讀取而拖累了遊戲系統的整體效能表現。在二部曲中曾經提過，為了取得資料庫中的數值，使用者需要利用 LuaDatabaseManager 類別中的 GetData() 函式： void LuaDatabaseManager::GetData(std::string&#038; sheetName, std::string&#038; fieldName, unsigned int id, std::string&#038; value) { int oldTop = lua_gettop(m_DBState); lua_getglobal(m_DBState, sheetName.c_str()); lua_pushstring(m_DBState, fieldName.c_str()); lua_gettable(m_DBState, LUA_GETTABLE_INDEX); lua_pushnumber(m_DBState, id); lua_gettable(m_DBState, LUA_GETTABLE_INDEX); value = luaL_checkstring(m_DBState, lua_gettop(m_DBState)); lua_settop(m_DBState, oldTop); } 在 GetData() 函式中，必須進行三次取出 table 結構的步驟；首先以 lua_getglobal() 函式取得最上層的 table，然後傳入欄位名稱以 lua_gettable() 函式取得第二層 table，最後再傳入資料 [...]]]></description>
			<content:encoded><![CDATA[<p>歷經了漫長的 DHL 系統一二三部曲後，終於來到了這一系列文章的最終曲。</p>
<p>本篇的重點在於對 DHL 系統進行效能的優化以及量測。首先，對於 LuaDatabaseManager 類別提出幾項效能優化的可能性。以資料庫系統的作用來說，一般使用者會期望<strong>在索取資料的程序中，得到最佳化的執行效能</strong>，才不會因為對於資料庫的頻繁讀取而拖累了遊戲系統的整體效能表現。在二部曲中曾經提過，為了取得資料庫中的數值，使用者需要利用 LuaDatabaseManager 類別中的 GetData() 函式：</p>
<pre name="code" class="cpp">
void LuaDatabaseManager::GetData(std::string&#038; sheetName, std::string&#038; fieldName, unsigned int id, std::string&#038; value)
{
	int oldTop = lua_gettop(m_DBState);

	lua_getglobal(m_DBState, sheetName.c_str());
	lua_pushstring(m_DBState, fieldName.c_str());
	lua_gettable(m_DBState, LUA_GETTABLE_INDEX);
	lua_pushnumber(m_DBState, id);
	lua_gettable(m_DBState, LUA_GETTABLE_INDEX);
	value = luaL_checkstring(m_DBState, lua_gettop(m_DBState));

	lua_settop(m_DBState, oldTop);
}
</pre>
<p>在 GetData() 函式中，必須進行三次取出 table 結構的步驟；首先以 lua_getglobal() 函式取得最上層的 table，然後傳入欄位名稱以 lua_gettable() 函式取得第二層 table，最後再傳入資料 ID 以 lua_gettable() 函式以取得使用者要求的資料值。</p>
<p>熟悉 Lua 語言的讀者應該很清楚，除了使用 lua_gettable() 函式取得 table 結構中的數值以外，還有另外一項替代方案：<strong>使用 lua_rawget() 函式。</strong>預設情形下，在 lua_gettable() 函式中，會喚起 table 的 metamethod，而對於將 table 單純當成陣列結構處理的資料表格來說，其實並不需要這層額外的彈性，所以如果使用 lua_rawget() 函式取代原來的 lua_gettable() 函式，理論上應該能夠獲得某種程度的效能提升。</p>
<p><span id="more-582"></span></p>
<p>所以在 LuaDatabaseManager 類別中，以此實作出一個使用 lua_rawget() 版本的 GetDataRawed() 函式：</p>
<pre name="code" class="cpp">
void LuaDatabaseManager::GetDataRawed(std::string&#038; sheetName, std::string&#038; fieldName, unsigned int id, std::string&#038; value)
{
	int oldTop = lua_gettop(m_DBState);

	lua_getglobal(m_DBState, sheetName.c_str());
	lua_pushstring(m_DBState, fieldName.c_str());
	lua_rawget(m_DBState, LUA_GETTABLE_INDEX);
	lua_pushnumber(m_DBState, id);
	lua_rawget(m_DBState, LUA_GETTABLE_INDEX);
	value = luaL_checkstring(m_DBState, lua_gettop(m_DBState));

	lua_settop(m_DBState, oldTop);
}
</pre>
<p>第二種優化的可能性，考量點在於每次向 Lua 端索取資料值時，都必須將表格名稱與欄位名稱傳遞過去，如果能夠省略部分傳入字串的動作，或許有機會增進索取資料的效能。為了達到這個目標，LuaDatabaseManager 類別裡需要新增兩個成員變數，用來記錄前一次使用的表格名稱與欄位名稱；在向 Lua 端取得資料前，先以字串比較的方法檢查名稱是否相同，只要表格名稱或欄位名稱與之前相同，就能夠省略掉傳遞字串值與取得 table 結構的步驟：</p>
<pre name="code" class="cpp">
void LuaDatabaseManager::GetDataCached(std::string&#038; sheetName, std::string&#038; fieldName, unsigned int id, std::string&#038; value)
{
	if (sheetName != m_CurrentSheetName)
	{
		m_CurrentSheetName = sheetName;
		m_CurrentFieldName = fieldName;

		if (lua_gettop(m_DBState) >= 2)
		{
			lua_pop(m_DBState, 2);
		}

		lua_getglobal(m_DBState, sheetName.c_str());
		lua_pushstring(m_DBState, fieldName.c_str());
		lua_gettable(m_DBState, LUA_GETTABLE_INDEX);
	}
	else if (fieldName != m_CurrentFieldName)
	{
		m_CurrentFieldName = fieldName;

		if (lua_gettop(m_DBState) >= 1)
		{
			lua_pop(m_DBState, 1);
		}

		lua_pushstring(m_DBState, fieldName.c_str());
		lua_gettable(m_DBState, LUA_GETTABLE_INDEX);
	}

	lua_pushnumber(m_DBState, id);
	lua_gettable(m_DBState, LUA_GETTABLE_INDEX);
	value = luaL_checkstring(m_DBState, lua_gettop(m_DBState));

	lua_pop(m_DBState, 1);
}
</pre>
<p>如上述程式碼所示，在 GetDataCached() 函式中，<strong>以 std::string 字串比較的代價，換取使用 lua_pushstring() 與 lua_gettable() 函式的成本</strong>。接著再使用前述的第一項優化技巧，以 lua_rawget() 取代 lua_gettable() 函式，製作出另外一個 GetDataRawedCached() 版本的函式，以進一步獲得效能增進的可能性。</p>
<p>以上是對 LuaDatabaseManager 類別取得資料程序進行效能改善的優化程序。而為了與一般傳統做法的資料庫系統進行效能測試評比，在此另外創建了一個 MapDatabaseManager 類別，用來<strong>模擬以 std::map 結構為基礎的 Database 實作方式</strong>。在這個類別中，主要是以資料表格、資料欄位以及資料值三個階層，建立出三層深度的 std::map 結構。其中實作的 GetData() 函式如下所示：</p>
<pre name="code" class="cpp">
void MapDatabaseManager::GetData(std::string&#038; sheetName, std::string&#038; fieldName, DataId id, DataValue&#038; value)
{
	DataSheetListItor dsItor = m_DatabaseList->find(sheetName);
	if (dsItor == m_DatabaseList->end())
	{
		return;
	}

	DataFieldList* dataSheet = dsItor->second;
	DataFieldListItor dfItor = dataSheet->find(fieldName);
	if (dfItor == dataSheet->end())
	{
		return;
	}

	DataIdList* dataId = dfItor->second;
	DataIdListItor idItor = dataId->find(id);
	if (idItor == dataId->end())
	{
		return;
	}

	value = idItor->second;
}
</pre>
<p>總結上述所有的效能優化方法與模擬方法，共計有以下五項測試程序：</p>
<ul>
<li>MapDatabaseManager::GetData()</li>
<li>LuaDatabaseManager::GetData()</li>
<li>LuaDatabaseManager::GetDataRawed()</li>
<li>LuaDatabaseManager::GetDataCached()</li>
<li>LuaDatabaseManager::GetDataRawedCached()</li>
</ul>
<p>在測試程序所使用的資料中，共有 5 張資料表格，每張表格裡有 10 項資料欄位，每項欄位中有 30 筆資料。進行測試時，每一次都會以亂數值決定所取的資料表、資料欄位以及資料 ID，接著再以 Performance Counter 實作的 Timer 類別，計算出每一項測試程序所花費的時間。以 MapDatabaseManager::GetData() 測試程序為例：</p>
<pre name="code" class="cpp">
Timer timer;
timer.Start();

for (unsigned int i = 0; i < testNum; i ++)
{
	GetRandomQuery(sheetName, fieldName, id);
	mapDBMgr.GetData(sheetName, fieldName, id, value);
}

timer.Stop();
</pre>
<p>其中，testNum 變數為使用者可自行輸入的測試次數。以此程序執行後的測試結果如下所示：</p>
<blockquote>
<pre>
<u><strong>測試次數：100</strong></u>

[MapDatabaseManager]
   GetData(): 0.120127 ms

[LuaDatabaseManager]
   GetData(): 0.102527 ms
   GetDataRawed(): 0.0896762 ms
   GetDataCached(): 0.261765 ms
   GetDataRawedCached(): 0.105879 ms
</pre>
</blockquote>
<blockquote>
<pre>
<u><strong>測試次數：5000</strong></u>

[MapDatabaseManager]
   GetData(): 5.40963 ms

[LuaDatabaseManager]
   GetData(): 5.00818 ms
   GetDataRawed(): 4.44218 ms
   GetDataCached(): 5.72419 ms
   GetDataRawedCached(): 5.23363 ms
</pre>
</blockquote>
<blockquote>
<pre>
<u><strong>測試次數：250000</strong></u>

[MapDatabaseManager]
   GetData(): 286 ms

[LuaDatabaseManager]
   GetData(): 251 ms
   GetDataRawed(): 219 ms
   GetDataCached(): 287 ms
   GetDataRawedCached(): 277 ms
</pre>
</blockquote>
<p>由測試的結果可知，LuaDatabaseManager::GetDataRawed() 是無庸置疑的第一名！而使用字串比較的方法，效能並沒有如預期般的效果。更重要的是，不論是 LuaDatabaseManager 的哪一個實作版本，幾乎都比使用三層 std::map 結構的 MapDatabaseManager::GetData() 效能更好更快！</p>
<p>最後，這五項效能測試程序的排名為：</p>
<blockquote><p>
<strong>第一名：LuaDatabaseManager::GetDataRawed()</strong><br />
第二名：LuaDatabaseManager::GetData()<br />
第三名：LuaDatabaseManager::GetDataRawedCached()<br />
第四名：LuaDatabaseManager::GetDataCached()<br />
備取：MapDatabaseManager::GetData()
</p></blockquote>
<p>在我的工作經驗裡，常遇到許多程式設計者習慣以「直覺」、「感覺」，或是個人的喜好來決定效能優化所使用的方法。然而，通常以「我覺得」或者「我不這麼想」這種方式進行優化程序的取捨，往往容易導致偏離甚至是完全相反的優化成果。在程式系統的優化層面上，除了應該奉行<strong>「不要過早進行優化」</strong>這條金科玉律以外，正確的優化技術，<strong>更應當立基於實際的量測程序與實驗數據上，才能夠以客觀的事實選擇出最合適的解決方案。</strong></p>
<p>從 DHL 系統的需求緣由、設計實作、應用實例到效能量測一路走來，可見得 <strong>Database Hot Loader</strong> 系統的確有搞頭，<strong>不僅能夠減輕修改遊戲數據的繁瑣步驟，加速遊戲企畫者的測試流程，更能夠為資料庫系統提供效能提升的附加效益！</strong>在讀完這一系列文章後，你是不是想大聲喊出<a href="http://www.sonance.com.tw/lulala/Obj_Nar.swf?inputStr=Database+Hot+Loader+%8Cn%93%9D%97L%96%E2%91%E8%21%21"><strong>「異議あり！」</strong></a>？歡迎提出你的看法、意見，或是更好的測試程序與效能改善方法吧！</p>
<p>執行檔下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=17"><strong>DatabaseHotLoader_Ep4_Release.zip</strong></a>（需要先行安裝 <a href="http://www.microsoft.com/downloads/details.aspx?displaylang=zh-tw&#038;FamilyID=32bc1bee-a3f9-4c13-9c99-220b62a191ee">vcredist_x86.exe</a>） <small>(下載次數： 230 )</small><br />
原始碼下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=18"><strong>DatabaseHotLoader_Ep4_Source.zip</strong></a> <small>(下載次數： 229 )</small></p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=582&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-tuning-and-measuring-performance/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Database Hot Loader 三部曲：Implementing Observer Entity</title>
		<link>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-implementing-observer-entity</link>
		<comments>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-implementing-observer-entity#comments</comments>
		<pubDate>Sun, 26 Oct 2008 16:13:19 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[進階技術]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=580</guid>
		<description><![CDATA[在前篇文章裡，以 VSTO 與 Lua 實作了 DHL 系統中資料匯出、讀取以及管理的核心功能後，本文將以一個簡單的 3D 程式範例，介紹如何將 DHL 系統與遊戲物件相互結合，以實現真正能夠使用於遊戲專案中的預定目標。 藉由之前建立完成的 LuaDatabaseManager 類別，已經能夠幫助我們動態修改並且載入 Lua 格式的資料庫表格，但是該如何使遊戲中的物件對資料庫的變化產生自動化反應呢？只要利用設計模式中的 Observer 模式，就能夠使我們順利達成目標。 如圖所示，在範例程式中，以 OpenGL 繪製出一個基本的三角形物件以及一個矩形物件，同時在視窗裡不停地旋轉；而視窗程式的建立，則使用 GLUT 函式庫以簡化相關的程序。 首先，在程式碼中創建出 Triangle 與 Quad 類別，分別用來代表畫面中的三角形與矩形物件。而這兩個物件的頂點、顏色、位移、旋轉軸與旋轉速度數值，則由 Excel 工作表中的資料數據進行設定。在範例程式的 Excel 檔案中，共有以下三張工作表： triangle quad speed 在 triangle 表格裡的 vertex1、vertex2、vertex3、color1、color2 與 color3 欄位，分別定義了三角形物件的三個頂點及顏色的資料，而最末的 translate 與 rotate 欄位則用來定義三角形物件的位移量與旋轉軸；quad 表格以此類推。在 speed 表格中，只有 triangle 與 quad 兩個欄位，分別定義了這兩個物件的旋轉速度。 建立了上述遊戲資料與遊戲物件之間的關連性之後，可以很清楚地瞭解，對於 Triangle [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_812" class="wp-caption alignleft" style="width: 366px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2008/10/observer-entity.jpg" alt="Observer Entity" title="observer-entity" width="356" height="374" class="size-full wp-image-812" /><p class="wp-caption-text">Observer Entity</p></div>
<p>在前篇文章裡，以 VSTO 與 Lua 實作了 DHL 系統中資料匯出、讀取以及管理的核心功能後，本文將以一個簡單的 3D 程式範例，介紹如何將 DHL 系統與遊戲物件相互結合，以實現真正能夠使用於遊戲專案中的預定目標。</p>
<p>藉由之前建立完成的 LuaDatabaseManager 類別，已經能夠幫助我們動態修改並且載入 Lua 格式的資料庫表格，但是<strong>該如何使遊戲中的物件對資料庫的變化產生自動化反應呢？</strong>只要利用設計模式中的 Observer 模式，就能夠使我們順利達成目標。</p>
<p>如圖所示，在範例程式中，以 OpenGL 繪製出一個基本的三角形物件以及一個矩形物件，同時在視窗裡不停地旋轉；而視窗程式的建立，則使用 GLUT 函式庫以簡化相關的程序。</p>
<p>首先，在程式碼中創建出 Triangle 與 Quad 類別，分別用來代表畫面中的三角形與矩形物件。而這兩個物件的頂點、顏色、位移、旋轉軸與旋轉速度數值，則由 Excel 工作表中的資料數據進行設定。在範例程式的 Excel 檔案中，共有以下三張工作表：</p>
<ul>
<li>triangle</li>
<li>quad</li>
<li>speed</li>
</ul>
<p>在 triangle 表格裡的 vertex1、vertex2、vertex3、color1、color2 與 color3 欄位，分別定義了三角形物件的三個頂點及顏色的資料，而最末的 translate 與 rotate 欄位則用來定義三角形物件的位移量與旋轉軸；quad 表格以此類推。在 speed 表格中，只有 triangle 與 quad 兩個欄位，分別定義了這兩個物件的旋轉速度。</p>
<p><span id="more-580"></span></p>
<p>建立了上述遊戲資料與遊戲物件之間的關連性之後，可以很清楚地瞭解，對於 Triangle 類別來說，會和 triangle 與 speed 這兩張資料表具有依存關係；而對於 Quad 類別來說，則是和 quad 與 speed 這兩張資料表具有依存關係。</p>
<p><strong>在 Observer 設計模式中，將會以 Triangle 類別與 Quad 類別負責扮演 Observer 的角色，而每張資料表則扮演 Subject 的角色。</strong>為了達成 Observer 設計模式的架構，首先宣告一個 ObserverEntity 純虛擬介面類別：</p>
<pre name="code" class="cpp">
// @file ObserverEntity.h

class ObserverEntity
{
public:
    virtual void Notify(std::string&#038; aspect) = 0;
};
</pre>
<p>接著處理 Subject 所需的訂閱功能，在原先用來處理資料的 LuadDatabaseManager 類別中，加入幾項新的成員函式與成員變數：</p>
<pre name="code" class="cpp">
// @file LuaDatabaseManager.h

class LuaDatabaseManager
{
public:
	void Subscribe(ObserverEntity* observer, std::string&#038; sheetName);

private:
	void NotifySubscribers(std::string&#038; sheetName);

private:
	typedef std::vector< ObserverEntity* > ObserverEntityList;
	typedef std::map< std::string, ObserverEntityList > SubscribersList;

	SubscribersList m_SubscribersList;
};
</pre>
<p>其中，由 Subscribe() 函式提供公用方法給外部的 Observer，使它們能夠自由訂閱自己感興趣的主題，也就是個別的資料表格。而 NotifySubscribers() 函式則是在 Subject 產生變化時，負責通知相對應的 Observer 們：</p>
<pre name="code" class="cpp">
// @file LuaDatabaseManager.cpp

void LuaDatabaseManager::Subscribe(ObserverEntity* observer, std::string&#038; sheetName)
{
	SubscribersListItor itor = m_SubscribersList.find(sheetName);
	if (itor != m_SubscribersList.end())
	{
		itor->second.push_back(observer);
	}
}

void LuaDatabaseManager::NotifySubscribers(std::string&#038; sheetName)
{
	SubscribersListItor itor = m_SubscribersList.find(sheetName);
	if (itor == m_SubscribersList.end())
	{
		return;
	}

	ObserverEntityList list = itor->second;
	for (unsigned int i = 0; i < list.size(); i ++)
	{
		list[i]->Notify(sheetName);
	}
}
</pre>
<p>回過頭來，在 Observer 這一方，需要使 Triangle 與 Quad 類別繼承自 ObserverEntity 介面類別以符合 Observer 的身份，然後再接著覆寫 Notify() 純虛擬函式：</p>
<pre name="code" class="cpp">
// @file Triangle.h

class Triangle : public ObserverEntity
{
public:
	virtual void Notify(std::string&#038; subject);
};
</pre>
<p>為了建立起 Observer 與 Subject 之間的聯繫關係，Triangle 與 Quad 類別需要先在建構式裡，以資料表的名稱向 LuaDatabaseManager 類別訂閱 Subject。當全部或者部分資料表格發生變動時，例如在重新載入 Lua 檔案之後，就能夠藉由呼叫 Notify() 函式的方式通知所有訂閱者，然後<strong>交由各個 Observer 自行操作相關的資料處理程序</strong>。以 Triangle 類別為例：</p>
<pre name="code" class="cpp">
// @file Triangle.cpp

Triangle::Triangle()
{
	g_LuaDataBaseMgr->Subscribe(this, std::string("triangle"));
	g_LuaDataBaseMgr->Subscribe(this, std::string("speed"));
}

void Triangle::Notify(std::string&#038; subject)
{
	if (subject == "triangle")
	{
		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_1, 1, m_Vertex1X);
		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_1, 2, m_Vertex1Y);
		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_1, 3, m_Vertex1Z);

		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_2, 1, m_Vertex2X);
		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_2, 2, m_Vertex2Y);
		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_2, 3, m_Vertex2Z);

		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_3, 1, m_Vertex3X);
		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_3, 2, m_Vertex3Y);
		g_LuaDataBaseMgr->GetData(TRIANGLE, VERTEX_3, 3, m_Vertex3Z);

		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_1, 1, m_Color1R);
		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_1, 2, m_Color1G);
		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_1, 3, m_Color1B);

		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_2, 1, m_Color2R);
		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_2, 2, m_Color2G);
		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_2, 3, m_Color2B);

		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_3, 1, m_Color3R);
		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_3, 2, m_Color3G);
		g_LuaDataBaseMgr->GetData(TRIANGLE, COLOR_3, 3, m_Color3B);

		g_LuaDataBaseMgr->GetData(TRIANGLE, TRANSLATE, 1, m_TranslateX);
		g_LuaDataBaseMgr->GetData(TRIANGLE, TRANSLATE, 2, m_TranslateY);
		g_LuaDataBaseMgr->GetData(TRIANGLE, TRANSLATE, 3, m_TranslateZ);

		g_LuaDataBaseMgr->GetData(TRIANGLE, ROTATE, 1, m_RotateX);
		g_LuaDataBaseMgr->GetData(TRIANGLE, ROTATE, 2, m_RotateY);
		g_LuaDataBaseMgr->GetData(TRIANGLE, ROTATE, 3, m_RotateZ);
	}
	else if (subject == "speed")
	{
		g_LuaDataBaseMgr->GetData(SPEED, TRIANGLE, 1, m_RotateSpeed);
	}
}
</pre>
<p>最後，只需要在 LuaDatabaseManager 類別載入資料庫檔案後，使用 NotifySubscribers() 通知訂閱者即可：</p>
<pre name="code" class="cpp">
// @file LuaDatabaseManager.cpp

bool LuaDatabaseManager::RunScript(std::string&#038; scriptName)
{
	std::string fileName = scriptName + LUA_EXT;
	int result = luaL_dofile(m_DBState, fileName.c_str());

	if (result != LUA_CALL_SUCCESS)
	{
		return false;
	}

	NotifySubscribers(scriptName);

	return true;
}
</pre>
<p>要做到動態熱載入資料的功能，其中很關鍵的一點，就是<strong>遊戲物件的「初始化程序」與「重新載入程序」最好能夠達到完全相同的程度</strong>，否則會使得遊戲程式裡充滿許多重複的程式碼，因而造成不必要的系統複雜度。在 DHL 系統裡，借助了 Observer 設計模式的威力，如前述程式碼所示，不論是物件初始化或者是重新載入遊戲資料，都是使用相同的程序；這樣一來，即使在遊戲資料編輯完成後，也不需要大費周章地將程式碼替換成另外一套資料載入程序。</p>
<p>只要利用以上 Observer 設計模式的概念與技巧，套用到遊戲系統的玩家角色、裝備道具、魔法技能或者場景物件之中，就能夠順利地將遊戲資料與遊戲內容天衣無縫地接合起來，達成原先設計以及實作 DHL 系統的預設目標：<strong>在不需要重新啟動遊戲程式的情況下，讓企畫設計者能夠即時調整遊戲的資料庫數據！</strong></p>
<p>在次篇最終回的 Database Hot Loader 系列裡，將演出感人肺腑的精彩團圓大結局，探索幾項效能優化的可能性，並且實際進行效能的量測，與一般常見的資料庫系統實作方式進行評比。</p>
<p>執行檔下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=15"><strong>DatabaseHotLoader_Ep3_Release.zip</strong></a>（需要先行安裝 <a href="http://www.microsoft.com/downloads/details.aspx?displaylang=zh-tw&#038;FamilyID=32bc1bee-a3f9-4c13-9c99-220b62a191ee">vcredist_x86.exe</a>） <small>(下載次數： 645 )</small><br />
原始碼下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=16"><strong>DatabaseHotLoader_Ep3_Source.zip</strong></a> <small>(下載次數： 662 )</small></p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=580&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-implementing-observer-entity/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Database Hot Loader 二部曲：Using VSTO and Lua</title>
		<link>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-using-vsto-and-lua</link>
		<comments>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-using-vsto-and-lua#comments</comments>
		<pubDate>Tue, 14 Oct 2008 16:22:11 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[進階技術]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=577</guid>
		<description><![CDATA[接續前篇文章的簡短介紹，本文將開始使用 VSTO 與 Lua 語言實作 DHL 系統。首先，定義 DHL 系統的開發環境以及執行環境： Database Hot Loader 系統 程式開發環境：Visual Studio 2005 Team Suite 使用者執行環境：Office 2003 為了使 Office 2003 達成轉換資料格式的任務，必須仰賴 VSTO 所提供的擴充功能。Visual Studio Tools for Office （簡稱為 VSTO）的作用，簡單來說就是讓程式設計者能夠在 Office 應用程式中，使用 .NET 撰寫附加的擴充元件，使一般的文件檔案，也能夠具備客製化的特殊功能。另外，微軟 .NET 平台的優勢，就是能夠使用 .NET 家族中的任何一種語言來撰寫程式，在此我選擇 C# 做為開發 DHL 系統的語言。 只要使用 VSTO，幾乎就能夠達成一切 Winform 可以做到的事情。在活頁簿上放個按鈕元件？沒問題；另外產生一個全新的 Winform？小意思；在 Excel 的主選單上增加自訂項目？一片蛋糕。總是覺得 Office 應用程式某些地方用得不夠順手嗎？有了 VSTO 以後，就可以幫助我們達成自己動手撰寫擴充功能的願望。 [...]]]></description>
			<content:encoded><![CDATA[<p>接續前篇文章的簡短介紹，本文將開始使用 VSTO 與 Lua 語言實作 DHL 系統。首先，定義 DHL 系統的開發環境以及執行環境：</p>
<blockquote><p>
<strong>Database Hot Loader 系統</strong></p>
<ul>
<li>程式開發環境：Visual Studio 2005 Team Suite</li>
<li>使用者執行環境：Office 2003</li>
</ul>
</blockquote>
<p>為了使 Office 2003 達成轉換資料格式的任務，必須仰賴 VSTO 所提供的擴充功能。<a href="http://en.wikipedia.org/wiki/Visual_Studio_Tools_for_Office">Visual Studio Tools for Office</a> （簡稱為 VSTO）的作用，簡單來說就是讓程式設計者能夠在 Office 應用程式中，使用 .NET 撰寫附加的擴充元件，使一般的文件檔案，也能夠具備客製化的特殊功能。另外，微軟 .NET 平台的優勢，就是能夠使用 .NET 家族中的任何一種語言來撰寫程式，在此我選擇 C# 做為開發 DHL 系統的語言。</p>
<p>只要使用 VSTO，幾乎就能夠達成一切 Winform 可以做到的事情。在活頁簿上放個按鈕元件？沒問題；另外產生一個全新的 Winform？小意思；在 Excel 的主選單上增加自訂項目？一片蛋糕。總是覺得 Office 應用程式某些地方用得不夠順手嗎？有了 VSTO 以後，就可以幫助我們達成自己動手撰寫擴充功能的願望。</p>
<p>在前篇文章中，曾特別提到需要使用 Visual Studio 2005 的 <a href="http://www.microsoft.com/taiwan/msdn/vs2005/howtobuy/editions/team/default.mspx">Team Suite 版本</a>，原因在於 DHL 系統必須使用 <strong>VSTO 的「文件層級」(document-level) 擴充程式</strong>，而在 Visual Studio 的眾多版本中，只有 Team Suite 版本能夠開發文件層級的擴充程式，一般程式設計者常用的 Professional 版本則只能夠開發 VSTO 的「應用程式層級」(application-level) 擴充程式。關於文件層級與應用程式層級的介紹，請參考<a href="http://msdn.microsoft.com/zh-tw/library/d2tx7z6d(VS.80).aspx">「Visual Studio Tools for Office 」</a>中文線上文件裡的詳細說明。</p>
<p><span id="more-577"></span></p>
<p>只要安裝了 Team Suite 版本之後，就可以在 Visual Studio 2005 的「新增專案」選項中，「Visual C#」的「Office」頁面裡，看到以下幾種專案範本：</p>
<ul>
<li>Excel 活頁簿</li>
<li>Word 文件</li>
<li>Excel 範本</li>
<li>Word 範本</li>
<li>Outlook 增益集</li>
</ul>
<p>首先，選擇建立<strong>「Excel 活頁簿」</strong>專案類型，Visual Studio 會自動產生出 ThisWorkbook.cs、Sheet1.cs、Sheet2.cs、Sheet3.cs 四個原始碼檔案。然後就可以開始在 ThisWorkbook.cs 裡，撰寫資料格式的轉換程序：</p>
<pre name="code" class="csharp">
// 匯出指定的工作表
public void ExportSheet(Excel.Worksheet ws)
{
    // 以工作表名稱為檔名，創建一個文字格式的檔案
    StreamWriter sw = File.CreateText(String.Format("{0}/{1}{2}", GetCurrentPath(), ws.Name, EXPORT_FILE_EXT));

    sw.WriteLine("{0}{1}", ws.Name, TOKEN_EQUAL);
    sw.WriteLine(TOKEN_LEFT_BRACKET);

    int currentRow = 1;
    int currentColumn = 1;
    string fieldName = GetRangeString(ws, currentRow, currentColumn);
    while (!String.IsNullOrEmpty(fieldName)) {
        // 寫入欄位名稱
        sw.WriteLine("{0}{1}{2}", TOKEN_INDENT, fieldName, TOKEN_EQUAL);
        sw.WriteLine("{0}{1}", TOKEN_INDENT, TOKEN_LEFT_BRACKET);

        ++currentRow;
        string id = GetRangeString(ws, currentRow, 1);
        while (!String.IsNullOrEmpty(id)) {
            string value = GetRangeString(ws, currentRow, currentColumn);

            // 寫入每筆資料
            sw.WriteLine("{0}{1}{2}{3}{4}{5}{6}{7}{8}",
                TOKEN_DOUBLE_INDENT, TOKEN_LEFT_SQ_BRACKET, id, TOKEN_RIGHT_SQ_BRACKET, TOKEN_EQUAL,
                TOKEN_DOUBLE_QUOTE, value, TOKEN_DOUBLE_QUOTE, TOKEN_DOT);

            ++currentRow;

            id = GetRangeString(ws, currentRow, 1);
        }

        // 寫入右括弧與逗號
        sw.WriteLine("{0}{1}{2}", TOKEN_INDENT, TOKEN_RIGHT_BRACKET, TOKEN_DOT);
        sw.WriteLine("");

        currentRow = 1;
        ++currentColumn;
        fieldName = GetRangeString(ws, currentRow, currentColumn);
    }

    // 寫入最終的右括弧
    sw.WriteLine(TOKEN_RIGHT_BRACKET);

    // 關閉檔案
    sw.Flush();
    sw.Close();
}
</pre>
<p>ExportSheet() 函式的作用，就是<strong>抓取 Excel 儲存格中的資料，並且格式化為合法的 Lua 原始碼語法寫入檔案</strong>。利用 .NET 所提供的基底函式庫功能，僅需不到 100 行的程式碼，就能夠相當輕易地寫出轉換程序。接著開啟 Visual Studio 工具箱介面，在專案的 Excel 頁面裡放上一個 Button 元件，然後在 Button 的 Click 事件函式中，把活頁簿裡所有的工作表 (worksheet) 傳入 ExportSheet() 函式中處理。完成之後，Excel 文件的使用者，只需按下文件上的按鈕，就能夠立即將活頁簿中所有的資料輸出為合法的 Lua 檔案。</p>
<p>在轉換資料格式時，會以活頁簿裡各張工作表的名稱，做為檔案名稱個別輸出成 .lua 檔案，同時也做為 Lua 程式碼中最上層的 table 結構。接著把每張工作表中的每個欄位名稱，格式化為第二層的 table 結構，最後再將該欄位每一筆資料輸出成 table 的內容物。輸出完成的 Lua 資料格式非常單純易懂，<strong>第一層是資料表的名稱，第二層是欄位的名稱，最後則將每一筆資料以 id 對應數值的方式，嵌在 table 最內層</strong>。以 triangle 工作表為例：</p>
<pre name="code" class="lua">
triangle =
{
    id =
    {
        [1] = "1",
        [2] = "2",
        [3] = "3",
    },

    vertex1 =
    {
        [1] = "0",
        [2] = "1",
        [3] = "0",
    },

    vertex2 =
    {
        [1] = "-1",
        [2] = "-1",
        [3] = "0",
    },

    vertex3 =
    {
        [1] = "1",
        [2] = "-1",
        [3] = "0",
    },

    color1 =
    {
        [1] = "1",
        [2] = "0",
        [3] = "0",
    },

    color2 =
    {

        [1] = "0",
        [2] = "1",
        [3] = "0",
    },

    color3 =
    {
        [1] = "0",
        [2] = "0",
        [3] = "1",
    },

    translate =
    {
        [1] = "-1.5",
        [2] = "0",
        [3] = "-6",
    },

    rotate =
    {
        [1] = "0",
        [2] = "1",
        [3] = "0",
    },

}
</pre>
<p>專案建置完成後，會產生出一個 Excel 文件檔案以及一個 DLL 檔案。但是如果你馬上把這兩個檔案複製到電腦中的其他目錄下，或者傳給其他人使用，在開啟 Excel 檔案後應該會看到這樣的錯誤訊息：「找不到或無法載入自訂組件」。這是因為 <strong>Office 對於 .NET 擴充元件，有比較嚴格的安全性規範，只要沒有經過安全性設定允許的檔案目錄，就無法順利載入擴充元件的 .dll 檔案</strong>，所以必須再經過一道安全性變更的手續。</p>
<p>舉例來說，如果把專案建置出來的 ExcelToLuaExporter.xls 與 ExcelToLuaExporter.dll 放在 D:\test 資料夾中，就要在程式集選單中，開啟 Microsoft .NET Framework SDK v2.0 的「SDK 命令提示字元」，然後輸入 DLL 檔案所在的完整路徑：</p>
<pre name="code" class="cpp">
caspol.exe -u -ag All_Code -url "D:\test\ExcelToLuaExporter.dll" FullTrust
</pre>
<p>出現確認提示訊息後，按下 y 鍵確認即可：</p>
<blockquote><p>
執行的作業將會變更安全性原則。<br />
您確定要執行這項作業嗎? (yes/no)
</p></blockquote>
<p>為了簡化這道安全性變更的步驟，在文章最後的<strong>執行檔下載</strong>檔案中，我提供了一個 <strong>PermissionConsole.exe</strong> 程式，只要直接點擊執行，然後按下 y 鍵確認，就會執行以上程序而無須手動輸入。</p>
<p>除此之外，要在沒有安裝 Visual Studio Team Suite 的一般使用者電腦上執行 VSTO 程式，必須要先行安裝 VSTO Runtime 與其他相關的項目。VSTO 有許多不同的版本存在，而我使用的是 Visual Studio 2005 能夠運作的 <strong>VSTO 2005 Second Edition</strong> 版本：</p>
<ul>
<li><a href="http://www.microsoft.com/downloads/details.aspx?familyid=0856EACB-4362-4B0D-8EDD-AAB15C5E04F5&#038;displaylang=zh-tw">Microsoft .NET Framework 2.0</a>: dotnetfx.exe (22.4 MB)</li>
<li><a href="http://www.microsoft.com/downloads/details.aspx?familyid=3c9a983a-ac14-4125-8ba0-d36d67e0f4ad&#038;displaylang=en">Office 2003 Update: Redistributable Primary Interop Assemblies</a>: O2003PIA.EXE (4.1 MB)</li>
<li><a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=8315654b-a5ae-4108-b7fc-186402563f2b&#038;DisplayLang=en">Microsoft Visual Studio 2005 Tools for Office Second Edition Runtime</a>: vstor.exe (1.3 MB)</li>
</ul>
<p>只要安裝完上列三個項目後，關於 VSTO 的部分，就大功告成了！</p>
<p>比較熟悉 Excel 操作的讀者可能會想問：「為什麼不直接用 VBA 撰寫資料轉換的程式，而選擇使用比較複雜的 VSTO？」答案很簡單，因為我不會，所以我選擇自己最熟悉的 C# 語言做為開發工具。理論上使用 VBScript 也能撰寫出相同的功能並且得到同樣的結果，只要能夠將 Excel 文件的資料轉換為合法的 Lua 檔案格式即可。</p>
<p>處理完 VSTO 之後，相對來說 Lua 的部分則單純許多。在範例程式中，我創建了一個 <strong>LuaDatabaseManager 類別，用來處理所有相關於 Lua 資料庫的功能</strong>。首先，使用 luaL_dofile() 函式將由 Excel 匯出的檔案載入執行，接著使用 GetData() 函式，傳入資料表名稱、欄位名稱、資料 ID 以及回傳值的參考之後，就可以使用 Lua 的 C API 取得指定 table 中的資料：</p>
<pre name="code" class="cpp">
void LuaDatabaseManager::GetData(std::string&#038; sheetName, std::string&#038; fieldName, unsigned int id, std::string&#038; value)
{
	int oldTop = lua_gettop(m_DBState);

	lua_getglobal(m_DBState, sheetName.c_str());
	lua_pushstring(m_DBState, fieldName.c_str());
	lua_gettable(m_DBState, LUA_GETTABLE_INDEX);
	lua_pushnumber(m_DBState, id);
	lua_gettable(m_DBState, LUA_GETTABLE_INDEX);
	value = luaL_checkstring(m_DBState, lua_gettop(m_DBState));

	lua_settop(m_DBState, oldTop);
}
</pre>
<p>在範例程式中，將 LuaDatabaseManager.exe、ExcelToLuaExporter.xls 與 ExcelToLuaExporter.dll 以及 triangle.lua 這四個檔案放在同目錄下，開啟 ExcelToLuaExporter.xls 並且執行 LuaDatabaseManager.exe。接著任意改變 triangle 工作表中的資料後，按下 Excel 上的「匯出全部資料表」按鈕，再於 LuaDatabaseManager.exe 裡按下 x 鍵即可重新載入資料！</p>
<p>搞定了繁瑣的 VSTO 轉換程序與簡單的 Lua 檔案讀取機制後，下一篇文章將利用 Observer 設計模式，實作 DHL 系統在遊戲程式中的應用範例。</p>
<p>執行檔下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=13"><strong>DatabaseHotLoader_Ep2_Release.zip</strong></a>（需要先行安裝 <a href="http://www.microsoft.com/downloads/details.aspx?displaylang=zh-tw&#038;FamilyID=32bc1bee-a3f9-4c13-9c99-220b62a191ee">vcredist_x86.exe</a>） <small>(下載次數： 663 )</small><br />
原始碼下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=14"><strong>DatabaseHotLoader_Ep2_Source.zip</strong></a> <small>(下載次數： 649 )</small></p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=577&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/advanced/database-hot-loader-using-vsto-and-lua/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>登泰山而小天下：Boost C++ Libraries 初體驗</title>
		<link>http://blog.monkeypotion.net/gameprog/note/first-touch-of-boost-cpp-libraries</link>
		<comments>http://blog.monkeypotion.net/gameprog/note/first-touch-of-boost-cpp-libraries#comments</comments>
		<pubDate>Thu, 09 Oct 2008 16:08:11 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[學習筆記]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=605</guid>
		<description><![CDATA[說來有些慚愧，一直以 C++ 語言做為謀生工具的我，在使用了多年的 C++ 程式語言之後，最近終於與聞名遐邇的開源專案 Boost C++ Libraries（簡稱為 Boost）進行了第一次的親密接觸。在本文裡，將參照 Boost 官網的「Getting Started on Windows」文章，於 Windows 作業系統以及 Visual Studio 2005 程式編譯器的環境下，詳細介紹安裝及建置 Boost 的步驟，並且在最後使用 Boost.Thread 函式庫做為測試範例。 從前就常聽到其他人談論著 Boost 的優點，但是只要知道其中由上千個標頭檔以及數十個函式庫所組成，而且還需要以繁複的步驟手動建立函式庫的 .lib 檔案，很容易就會使初學者望而卻步。對於用慣了程式整合開發環境以及現成函式庫的我來說，Boost 簡直就像是一座美麗動人但又戒備森嚴的堡壘般令人不敢輕易接近。多年以後的現在，總算是鼓起了勇氣重新面對 Boost。花了些時間閱讀文件，然後按照指示一步步完成安裝建置之後，發現原來這些步驟比起從前已經簡化許多，函式庫的使用也不如想像中的困難！ One of the reasons for boost&#8217;s success has been the cross-pollination of ideas between diverse library projects. Boost 是由一群功能獨立的函式庫所組合而成的集合體，其中涵蓋了許多熱門而經常使用的函式庫，以及比較冷門的特殊功能作用函式庫。所有的函式庫都包含在名為 boost 的 namespace 中，不僅能夠使各函式庫維持一致的使用風格，同時也得以避免命名衝突的問題。如官網中的 FAQ [...]]]></description>
			<content:encoded><![CDATA[<p>說來有些慚愧，一直以 C++ 語言做為謀生工具的我，在使用了多年的 C++ 程式語言之後，最近終於與聞名遐邇的開源專案 <a href="http://www.boost.org/"><strong>Boost C++ Libraries</strong></a>（簡稱為 <strong>Boost</strong>）進行了第一次的親密接觸。在本文裡，將參照 Boost 官網的<a href="http://www.boost.org/doc/libs/1_36_0/more/getting_started/windows.html">「Getting Started on Windows」</a>文章，於 Windows 作業系統以及 Visual Studio 2005 程式編譯器的環境下，詳細介紹安裝及建置 Boost 的步驟，並且在最後使用 Boost.Thread 函式庫做為測試範例。</p>
<p>從前就常聽到其他人談論著 Boost 的優點，但是只要知道其中由上千個標頭檔以及數十個函式庫所組成，而且還需要以繁複的步驟手動建立函式庫的 .lib 檔案，很容易就會使初學者望而卻步。對於用慣了程式整合開發環境以及現成函式庫的我來說，Boost 簡直就像是一座美麗動人但又戒備森嚴的堡壘般令人不敢輕易接近。多年以後的現在，總算是鼓起了勇氣重新面對 Boost。花了些時間閱讀文件，然後按照指示一步步完成安裝建置之後，發現原來這些步驟比起從前已經簡化許多，函式庫的使用也不如想像中的困難！</p>
<blockquote><p>
One of the reasons for boost&#8217;s success has been the cross-pollination of ideas between diverse library projects.
</p></blockquote>
<p><strong>Boost 是由一群功能獨立的函式庫所組合而成的集合體</strong>，其中涵蓋了許多熱門而經常使用的函式庫，以及比較冷門的特殊功能作用函式庫。所有的函式庫都包含在名為 boost 的 namespace 中，不僅能夠使各函式庫維持一致的使用風格，同時也得以避免命名衝突的問題。如官網中的 <a href="http://www.boost.org/users/faq.html">FAQ</a> 所述，<strong>Boost 的成功正是由於不同功能函式庫之間的「交叉授粉」(cross-pollination) 作用所致</strong>；雖然其中多數函式庫都能夠分開獨立使用，但正是由於函式庫之間相互支援的功能以及介面，才使得 Boost 的開發者社群與使用者社群能夠蓬勃發展且欣欣向榮。</p>
<p><span id="more-605"></span></p>
<p>而經過千錘百鍊的 Boost，也獲得了 C++ Standards Committee 的認可，其中有部分的函式庫例如 Array、Bind、Tuple 與 Unordered 等等皆已納入 <a href="http://en.wikipedia.org/wiki/Technical_Report_1">C++ Technical Report 1</a>（簡稱為 TR1）的範疇之中，很快地將會在眾所矚目的 C++ 語言新版本中，正式將這些函式庫納入標準。</p>
<p>事不宜遲，讓我們開始進入 Boost 的奇妙領域吧！</p>
<p>首先，於官網下載 Boost C++ Libraries 檔案包，目前最新的版本是於 2008 年 8 月 12 日發佈的 1.36 版。官網提供了多種不同壓縮格式的檔案包，建議下載以 <a href="http://www.7-zip.org/">7-Zip</a> 包裝的 <a href="http://downloads.sourceforge.net/boost/boost_1_36_0.7z?modtime=1218749377&#038;big_mirror=0">boost_1_36_0.7z</a> 版本，檔案大小僅有 ZIP 版本的 50% 而已。下載完成後，直接執行檔案解壓縮，就會生成一個新的目錄 <strong>boost_1_36_0</strong>。</p>
<p>「奇怪，怎麼看來看去都只有 .hpp 的標頭檔？那些 .cpp 跑到哪裡去了？」Boost 與其他開源專案最關鍵的不同之處，就是在於<strong>多數 Boost 中的函式庫，都是以「僅有標頭檔」(header-only) 的形式存在</strong>；也就是說，在使用多數的 Boost 函式庫時，只需要 #include 相對應的 .hpp 檔案，不需要額外連結 .lib 檔案就能夠快快樂樂地撰寫程式。Boost 之所以能夠達到這種令人讚嘆的神妙境界，主要歸功於 <a href="http://en.wikipedia.org/wiki/Template_metaprogramming">Template Metaprogramming</a> 技術的強大威力，因此使 Boost 能夠在編譯時期動態生成所需的程式碼。</p>
<p>雖然多數 Boost 函式庫可以直接使用標頭檔，但是某些 Boost 函式庫例如 FileSystem、IOStream 與 Thread 等等，就必須要建置並且連結 .lib 檔案才能夠使用。而在下載回來的資料包之中，只有包含原始碼檔案，所以我們需要自己進行 .lib 檔案的建置工作。為了建置所需的函式庫，必須使用 bjam 這個工具。再次於官網下載 <a href="http://downloads.sourceforge.net/boost/boost-jam-3.1.16-1-ntx86.zip?modtime=1196632824&#038;big_mirror=0">boost-jam-3.1.16-1-ntx86.zip</a>，解壓縮後將其中的 bjam.exe 移至 boost_1_36_0 目錄下，接著開啟「Visual Studio 2005 命令提示字元」，更換目錄至 boost_1_36_0 後，執行以下指令然後耐心等待（依電腦配備等級不同，約需要數十分鐘的建置時間）：</p>
<pre name="code" class="lua">
bjam --build-dir=".\build" --toolset=msvc stage
</pre>
<p>其中 &#8211;build-dir 參數指定的是建置函式庫的輸出目錄，在此設定為 .\build，就是在 boost_1_36_0 目錄下產生一個新的 build 目錄；而 &#8211;toolset 則是指定用來建置函式庫的工具，設定為 msvc 也就是使用 Microsoft Visual Studio 的編譯器執行建置；最後的 stage 參數則是一個特殊的建置目標，在函式庫建置完畢之後，會將所有的 .lib 與 .dll 檔案複製一份到 boost_1_36_0\stage\lib\ 目錄當中。</p>
<p>在上述的預設指令中，只會建置出 Release 組態的函式庫，如果想要建置出包含 Debug 組態在內的所有函式庫組態，可以在指令中加上 &#8211;build-type=complete 參數；而如果不想花費許多時間建立所有的函式庫，只想要建立其中某個函式庫，就可以使用 &#8211;with 參數後面接上函式庫的名稱。以單獨建置 Boost.Thread 為例：</p>
<pre name="code" class="lua">
bjam --with-thread --build-dir=".\build" --build-type=complete --toolset=msvc stage
</pre>
<p>如果不確定有哪些函式庫可以使用 bjam 建置，可以使用 &#8211;show-libraries 參數列出所有可建置的函式庫：</p>
<pre name="code" class="lua">
bjam --show-libraries
</pre>
<p>接下來，就創建一個專案正式和 Boost 說聲 Hello World 吧！</p>
<p>由於範例中所使用的 Boost.Thread 函式庫，相依於 Boost.DateTime 處理與時間相關的功能，所以首先要按照之前的步驟將 Boost.DateTime 建置起來：</p>
<pre name="code" class="lua">
bjam --with-date_time --build-dir=".\build" --build-type=complete --toolset=msvc stage
</pre>
<p>然後在 Visual Studio 2005 中，創建一個「Win32 主控台應用程式」的空專案，新增一個 .cpp 檔案並且填入以下程式碼：</p>
<pre name="code" class="cpp">
#include < boost/thread/thread.hpp >
#include < iostream >

void helloworld()
{
    std::cout << "Hello World!" << std::endl;
}

void main()
{
    boost::thread thrd(&#038;helloworld);
    thrd.join();
}
</pre>
<p>將專案屬性中的「其他 Include 目錄」設定為 boost_1_36_0 所在的目錄，「其他程式庫目錄」則設定為 boost_1_36_0\stage\lib；在設定時不需要明確指定要連結的 .lib 檔案名稱，Boost 會自動連結 (Automatic linking) 到正確的函式庫版本。以上步驟完成後，按下建置方案然後執行，第一個 Boost.Thread 的 Hello World 程式就誕生囉！</p>
<p>另外，如果將 Boost.Thread 搭配 Boost.Bind 函式庫一起服用，就能夠更便利地傳入 Thread Function 所需的參數值。因為 Boost.Bind 不需連結 .lib 檔案，所以只要 #include 指定 boost/bind.hpp 檔案就可以立即使用：</p>
<pre name="code" class="cpp">
#include < boost/thread/thread.hpp >
#include < boost/bind.hpp >
#include < iostream >

void helloworld(const char* who)
{
    std::cout << who << ": "Hello World!"" << std::endl;
}

void main()
{
    boost::thread thrd(boost::bind(&#038;helloworld, "MonkeyPotion"));
    thrd.join();
}
</pre>
<p>開始使用 Boost，就是這麼輕鬆簡單！</p>
<p>為了取得更多關於 Boost 的深入知識，除了官方文件以外，我也嘗試著在網路上搜尋相關的教學文章與使用心得。結果一如預期，網路上關於 Boost 的中文資訊仍然相當稀少，而其中繁體中文的資訊更是少之又少。<a href="http://www.stlchina.org/twiki/bin/view.pl/Main/BoostChina">「Boost 中文站」</a>是我目前找到內容最為豐富的 Boost 主題網站，其中的<a href="http://www.stlchina.org/twiki/bin/view.pl/Main/BoostThread">「C++ Boost Thread 编程指南」</a>寫得相當出色，是一篇值得仔細閱讀的好文章。</p>
<p>目前為止，我僅僅接觸了這座龐大城堡中的一項 Boost.Thread 函式庫，就已經深深感受到 Boost C++ Libraries 的驚人威力。<strong>登上巨人的肩膀，才會知道世界有多遼闊，而自己又有多渺小。</strong>從現在開始，我會抱持著滿滿的學習熱誠與好奇心，努力探索 Boost 領域的知識，請幫我推薦些實用有趣的 Boost 函式庫吧！</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=605&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/note/first-touch-of-boost-cpp-libraries/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
