2009年5月18日 星期一

BattleStar Galactica看完了

  image

image

(圖片來自BattleStar官網)

終於,把BattleStar給看完了。

每次看完一季的BattleStar,都會想把EVE Online的帳號再去開起來玩玩,這次也不例外。

雖然說,這最後一季第四季的劇情有點扯(其實從第三季開始就感覺編劇在掰了),中間還碰到一次美國編劇罷工,造成劇情中間有一個段落,不過,還是很好看的啊!

小時候看過所謂的青少年版的BattleStar,那時候最崇拜的角色就是Starbuck,技術最好,最帥的飛行員。現在長大啦,看的又是比較晦暗,成熟版的BattleStar,可是最喜歡的角色還是這裡頭的Starbuck--雖然在這個版本裡被改成了女的。

BattleStar的背景設定是很有趣的,撇開那個被自己製作的機器人追殺不談,就說這個太空艦隊吧。艦隊不光是有軍艦跟戰鬥機而已,還有一大堆平民所搭乘的太空船,提煉燃料的船、運輸囚犯用的船、殖民地流亡政府的殖民地一號、上頭有五星級飯店跟賭場的星雲九號、還有很重要的一艘資源回收垃圾船....

比起其他的太空科幻片,這樣的艦隊設定才是合理又完整。

哪一天我們要設計這類型科幻遊戲的時候,一定要有這樣的設定啊。

話說,第四季劇情走到一半,Starbuck帶隊找到了地球,不過,這個地球已經是一片殘破,只留下核戰之後的廢墟。然後就看美國的編劇們要罷工多久了。

幸好,第四季又拍了後面十集。

整個艦隊放棄了這個核戰廢墟,繼續找另外一個可以居住的星球。經過一場沒成功的軍事政變,死的死,傷的傷,在大結局的時候,找到了一顆有生命可以居住的星球,也就是十五萬年前的我們這顆星球,艾達瑪指揮官就稱它做「地球」。

哈。

所以我們的文明是Starbuck他們帶來的,我們的血統裡頭有一部分是Cylon複製人....

2009年5月6日 星期三

執行緒函式的設計

我在引擎的設計裡,修修改改的弄出了幾種執行緒函式的架構。

一開始是一個thread loop,執行緒函式中有一個無窮迴圈,像是我在Render Thread做的這樣,

DWORD WINAPI CRenderThread::RenderThreadFunc(LPVOID thread_obj)
{
    CRenderThread* thread_ptr=(CRenderThread*)(thread_obj);

    while (!s_bExitThread)
    {
        CRenderCommand* cmd=0;
        thread_ptr->m_CommandQueueLock.Lock();
        if (thread_ptr->m_CommandQueue.size())
        {
            cmd=thread_ptr->m_CommandQueue.front();
            thread_ptr->m_CommandQueue.pop_front();
        }
        thread_ptr->m_CommandQueueLock.Unlock();
        if (cmd)
        {
            cmd->Execute(thread_ptr->m_pRenderThreadState);
            medelete cmd;
        }

        Sleep(0);  // 這個很重要,如果不休息,主緒會忽快忽慢,而且可能兩次執行的速度差異幾十倍
    }
    return 1;
}

迴圈的最後,要讓執行緒睡一下下,因為如果不這麼做的話,在Windows的架構裡,這個執行緒可是會吃掉CPU不肯放出來的,也就會造成主緒所分配到的時間忽多忽少,跑得忽快忽慢。

另一種執行緒函式,是用在背景讀檔的執行緒,每個執行緒只執行一次。

DWORD WINAPI CThreadStream::LoadingThreadFunc(LPVOID thread_obj)
{
    CThreadStream* thread_ptr=(CThreadStream*)(thread_obj); 

    WaitForSingleObject(thread_ptr->m_BeginEvent,INFINITE);  // wait for begin 
    thread_ptr->Load(thread_ptr->m_szRequestFilename,
      thread_ptr->m_szRequestPathID);
    SetEvent(thread_ptr->m_EndEvent);  // loading done
    return 1;
}

執行緒建立之後,並不會馬上進行讀檔,而是會等待一個開始的事件,讀檔完成以後,再設定結束事件,讓主緒可以得知檔案已經讀完。

這個設計,有個問題。也就是每讀取一個檔案,我們就必須建立一個執行緒,讀完了,就刪除執行緒。這在系統資源的使用上,還有執行的效能上,都不是很好的設計方式。

我沒有用Thread Pool來改進,而是做了其他方向的修改。

最先是用了一個無窮迴圈來做。

DWORD WINAPI CThreadStream::LoadingThreadFunc(LPVOID thread_obj)
{
    CThreadStream* thread_ptr=(CThreadStream*)(thread_obj); 
    
    while (!s_bExitThread)
    {
        if (WaitForSingleObject(thread_ptr->m_BeginEvent,0)!=WAIT_OBJECT_0)
        {
            Sleep(0);
            continue;
        }
        thread_ptr->Load(thread_ptr->m_szRequestFilename,
                                      thread_ptr->m_szRequestPathID);

        SetEvent(thread_ptr->m_EndEvent);  // loading done
        ResetEvent(thread_ptr->m_BeginEvent);  // reset begin for next loop
    }
    return 1;
}

這個迴圈每次都檢查開始事件是否被設定,如果沒有,就讓執行緒睡一下下,然後繼續迴圈檢查。用這個方法,我可以讓背景讀檔的執行緒一直活著,有需要的時候,就幫忙讀個檔案。

但是這個執行緒的CPU負擔其實還是很重的,要一直looping檢查。而讀檔的工作,執行的頻率又不是很高。一直looping非常不划算。

所以我將開始事件改成了無限等待,同時加上一個離開執行緒的事件旗標。

DWORD WINAPI CThreadStream::LoadingThreadFunc(LPVOID thread_obj)
{
    CThreadStream* thread_ptr=(CThreadStream*)(thread_obj);

    HANDLE hWaitHandle[2]={ thread_ptr->m_BeginEvent,thread_ptr->m_QuitEvent };
    while (TRUE)
    {
        DWORD signal=WaitForMultipleObjects(2,hWaitHandle,FALSE,INFINITE);  // wait for begin or quit
        if (signal==WAIT_OBJECT_0+1) break;

        thread_ptr->Load(thread_ptr->m_szRequestFilename,
       thread_ptr->m_szRequestPathID);

        SetEvent(thread_ptr->m_EndEvent);  // loading done
        ResetEvent(thread_ptr->m_BeginEvent);  // reset begin for next loop
    }
    return 1;
}

這樣一來,這個執行緒只會在開始事件被設定時才會醒來工作,做完又會繼續等待。

雖然不算是最佳的設計,倒也符合我們現在的需求了。