Core 型別

Godot 的核心由豐富的類別與樣板集組成,而所有東西都是由這些類別與樣板集組成的。

本參照文件會儘量列出這些類別與樣板集,以讓使用者能更加瞭解。

定義

Godot 使用了標準的 C99 資料型別,如 uint8_t, uint32_t, int64_t …等,現代幾乎所有的編譯器都支援。為這些型別重造輪子並不輕鬆,且會讓程式碼更難理解。

通常除非是要處理大型架構或陣列,否則都與特定任務來說,我們不會花心力來挑選最有效率的資料型別使用。除非有必要,否則大多數的程式都使用 int 。會這樣是因為現今大部分的裝置都有至少 32 位元匯流排能使用,而且這些操作都能在單一週期內執行。而且這樣也可以讓程式碼更易讀。

檔案與記憶體的大小則使用 size_t ,這樣可以保證大小為 64 位元。

Unicode 字元則使用 CharType 而不是 wchar_t,因為大多數的架構上 wchar_t 的長度都是 4 位元組,而我們通常需要的是 2 位元組。但是,預設情況下,我們並不強制使用 CharType,而且 CharType 可以直接映射到 wchar_t 上。

參考資料:

記憶體模型

PC 是很出色的架構。大多數電腦通常都有好幾 G 的 RAM、好幾 TB 的儲存空間以及幾 Ghz 的 CPU,當某個應用程式需要更多資源的時候,作業系統便會將目前沒在使用的資源且換過來。其他的架構 (如行動裝置與遊戲主機) 通常來說都有較多的限制。

最常見的記憶體模型為堆積 (Heap)。堆積即應用程式所要求的一塊記憶體區域,而底層的作業系統會嘗試找到符合的區塊並回傳。使用堆積通常效果最佳,且很靈活,但隨著使用時間越長或濫用,則可能會導致記憶體區段問題。

記憶體區段會慢慢地造成許多的洞,這些洞對於大多數的記憶體分配來說都太小了,進而造成浪費。目前已經有許多關於堆積與區段的文獻了,因此這裡不會再詳細討論此一問題。現代作業系統使用了分頁化記憶體,可以協助減緩區段問題,但並無法解決。

但是,在大多數的研究與測試中都指出,當有足夠的記憶體時,若最大分配大小低於最大堆積大小與未使用記憶體大小之比例的某個閥值,則不會有分段的問題,因為不管過多久分段都會維持常數大小。換句話說,只要有 10%~20% 的可用記憶體,就算執行各種小型記憶體分配也不會怎麼樣。

Godot 會確保所有動態分配的物件都儘可能只分配一點點空間 (多數情況下都小於幾 KB)。但如果分配了很大的空間呢 (如圖片或網格幾何以及大型陣列)?這種情況下,Godot 則可選擇使用動態記憶體池。該記憶體必須要進行鎖定才能存取,而當某個分配空間記憶體不足時,該記憶體池會進行重新排列,並依照需求進行壓縮。依據遊戲的需求,程式設計師可以設定動態記憶體池的大小。

記憶體分配

Godot 提供了許多能在遊戲中追蹤記憶體使用的工具,尤其是在除錯期間。也因為這項特性,通常不該使用一般的 C 與 C++ 函式庫,而 Godot 則提供了其他的一些函式庫。

Godot 提供了一些巨集可以處理 C 語言風格的記憶體分配:

memalloc()
memrealloc()
memfree()

這些巨集的效果與標準 C 語言函式庫中的 malloc, realloc, free 相同。

而 C++ 風格的記憶體分配則使用下列特殊的巨集:

memnew( Class / Class(args) )
memdelete( instance )

memnew_arr( Class , amount )
memdelete_arr( pointer to array )

這些巨集就與 new, delete, new[] 與 delete[] 相同。

memnew/memdelete 也使用了一點 C++ 的技術來在物件建立與刪除之後呼叫這些 Object。

動態記憶體的部分,則提供了 PoolVector<> 樣板。PoolVector 是標準向量 (Vector) 類別,與 C++ 標準函式庫中的 Vector 非常相似。要建立 PoolVector 緩衝,請使用:

PoolVector<int> data;

PoolVector 可以通過 [] 運算元來存取,而也有其他一些 Helper 能使用:

PoolVector<int>::Read r = data.read()
int someint = r[4]
PoolVector<int>::Write w = data.write()
w[4] = 22;

這些操作能快速讀寫 PoolVector,並在離開 PoolVector 作用域前都保持 PoolVector 鎖定。但是,PoolVector 應只用於小型且動態記憶體的操作,因為 read() 與 write() 對於大量存取來說速度很慢。

Containers

Godot 提供了一系列常見的容器:

  • 向量 Vector

  • 列表 List

  • 設定

  • 映射 Map

這些容器都設計得保持儘量保持簡潔,因為 C++ 中的樣板通常都會用內嵌的,對於除錯符號與程式碼來說都會讓二進位檔案變大。List, Set 與 Map 可以使用指針來迭代,如下:

for(List<int>::Element *E=somelist.front();E;E=E->next()) {
    print_line(E->get()); // print the element
}

Vector<> 類別也有一些不錯的特性:

  • 寫入時複製,因此只要不修改內容,複製就不需要花費太多資源。

  • 通過在參照計數上使用不可部分完成操作 (Atomic Operation) 來支援多執行緒。

字串

Godot 中也提供了一個字串 String 類別。該類別含有大量的功能、所有函式都支援 Unicode (如大小寫操作)、並支援 UTF-8 解析與截取以及用於轉換與可視化的各種 Helper 函式。

參考資料:

StringName

StringName 與 String 類似,但 StringName 為唯一的。從字串建立 StringName 會產生一個對於相同字串來說都獨立的內部指針。由於比較 StringName 基本上就是比較指針,因此 StringName 適用於將字串作為識別項的情況。

建立 StringName (尤其是新 StringName) 很慢,但進行比較時很快。

參考資料:

數學型別

在 core/math 資料夾中有數種可用的線型數學型別。

參考資料:

NodePath

NodePath 是一個用於保存場景樹中路徑並用來快速參照路徑的一個特殊資料型別。

參考資料:

RID

RID 即為資源 ID (Resource ID)。伺服器會使用 RID 來參照儲存在其內部的資料。RID 是不透明的 (Opaque),這代表 RID 所參照的存取無法被直接存取。同時,RID 也是獨立的,即使是參照資料的不同型別也是獨立的。

參考資料: