<2021SC@SDUSC>
開源游戲引擎 Overload 代碼模塊分析 之 OvTools(五)—— Utils(中)
目錄
- 前言
- 分析
- 1、ReferenceOrValue
- 1.1 ReferenceOrValue 類
- 1.1.1 頭檔案
- 1.1.2 主體代碼
- 2、SizeConverter
- 2.1 SizeConverter.h
- 2.1.1 頭檔案
- 2.1.2 主體代碼
- 2.2 SizeConverter.cpp
- Convert() 函式
- ConvertToOptimalUnit() 函式
- UnitToString() 函式
- 總結
前言
本篇是開源游戲引擎 Overload 模塊 OvTools 的 Utils 小模塊的中篇,簡單回顧一下,上篇已經分析了 Utils 六個部分中的 PathParser 與 Random,它們分別是負責路徑處理和亂數生成的類,具體可前往上篇文章查看,本篇咱們就繼續探究接下來兩個部分:ReferenceOrValue 與 SizeConverter,
另外,想先大致了解 Overload 可前往這篇文章,想看其他相關文章請前往筆者的 Overload 專欄自主選擇,
分析
1、ReferenceOrValue
1.1 ReferenceOrValue 類
1.1.1 頭檔案
#include <variant>
該頭檔案來自 C++ 標準庫,實作的功能主要是對變數物件的值的保存和管理,檔案中的核心概念就是 variant 型別,其中文翻譯可以是變體,
具體來說,variant 資料型別是所有沒被顯式宣告(用如 Dim、Private、Public 或 Static等陳述句)為其他型別變數的資料型別,它沒有型別宣告字符,除了定長 String 資料及用戶定義型別外,variant 可以包含任何種類的資料,包括 Empty、Error、Nothing 及 Null 等特殊值,所以,variant 型別的變數所包含的值型別必須是賦予了 variant 模板引數的型別之一,我們把這些模板引數稱為替代項,
此外,檔案還含有多種例如運算子多載等處理變數物件值的函式,可以用 VarType 函式或 TypeName 函式來決定如何處理 variant 中的資料,
1.1.2 主體代碼
主體代碼是一個 ReferenceOrValue 類,其作用是簡化一個變數型別的定義,這樣就不用在意這個變數是參考還是實值等等了,由于該類的操作都比較簡潔,所以沒有另設 cpp 檔案,函式實作代碼及其注釋都已經在 h 檔案,讓我們來直接看看代碼吧:
template <typename T>
class ReferenceOrValue
{
public:
/**
* Construct the ReferenceOrValue instance with a reference
* @param p_reference
*/
ReferenceOrValue(std::reference_wrapper<T> p_reference) : m_data{ &p_reference.get() }
{
}
/**
* Construct the ReferenceOrValue instance with a value
* @param p_value
*/
ReferenceOrValue(T p_value = T()) : m_data{ p_value }
{
}
/**
* Make the ReferenceOrValue a reference
* @param p_reference
*/
void MakeReference(T& p_reference)
{
m_data = &p_reference;
}
/**
* Make the ReferenceOrValue a value
* @param p_value
*/
void MakeValue(T p_value = T())
{
m_data = p_value;
}
/**
* Implicit conversion of a ReferenceOrValue to a T
*/
operator T&()
{
return Get();
}
/**
* Assignment operator thats call the setter of the ReferenceOrValue instance
* @param p_value
*/
ReferenceOrValue<T>& operator=(T p_value)
{
Set(p_value);
return *this;
}
/**
* Returns the value (From reference or directly from the value)
*/
T& Get() const
{
if (auto pval = std::get_if<T>(&m_data))
return *pval;
else
return *std::get<T*>(m_data);
}
/**
* Sets the value (To the reference or directly to the value)
* @param p_value
*/
void Set(T p_value)
{
if (auto pval = std::get_if<T>(&m_data))
* pval = p_value;
else
*std::get<T*>(m_data) = p_value;
}
private:
std::variant<T, T*> m_data;
};
該類宣告的函式中,有呼叫這兩個函式操作:std::get_if 與 std::get,這兩個函式都是來自上面提到的 variant 檔案,兩者都是獲取物件的變體,只是前者多了一個判斷是否存在值,若不存在則回傳 nullptr,該型別將被 if 陳述句判斷為類似 0 的 false 值,
由于 ReferenceOrValue 主要是 variant 的使用與上述的兩個標準庫函式,且大部分函式的實作都很簡單且有注釋,筆者就不多贅述分析了,所以讓我們直接繼續下一個部分吧:
2、SizeConverter
2.1 SizeConverter.h
2.1.1 頭檔案
#include <cstdint>
#include <tuple>
#include <string>
string 檔案不多說;cstdint 檔案包含了 stdint.h 并將關聯名稱添加到 std 命名空間,還能確保使用 std 中的外部鏈接宣告的名稱在 std 命名空間中宣告;tuple 檔案定義了一個模板 tuple,它的實體包括不同型別的物件,
2.1.2 主體代碼
檔案主體代碼包含了一個 SizeConverter 類,該類可以換算位元組單位,代碼如下:
class SizeConverter
{
public:
enum class ESizeUnit
{
BYTE = 0,
KILO_BYTE = 3,
MEGA_BYTE = 6,
GIGA_BYTE = 9,
TERA_BYTE = 12
};
/**
* Disabled constructor
*/
SizeConverter() = delete;
/**
* Converts the given size to the optimal unit to avoid large numbers (Ex: 1000B will returns 1KB)
* @param p_value
* @param p_unit
*/
static std::pair<float, ESizeUnit> ConvertToOptimalUnit(float p_value, ESizeUnit p_unit);
/**
* Converts the given size from one unit to another
* @param p_value
* @param p_from
* @param p_to
*/
static float Convert(float p_value, ESizeUnit p_from, ESizeUnit p_to);
/**
* Converts the given unit to a string
* @param p_unit
*/
static std::string UnitToString(ESizeUnit p_unit);
};
這里有函式的宣告及功能注釋,都是些型別轉換操作,不多贅述,其中注意 public 變數 ESizeUnit 是一個列舉類,包含的是從 B、KB 到 TB 的位元組單位;另外,該類的建構式使用了 delete 默認洗掉,以前的文章也有談及,現在讓我們到 cpp 檔案中看函式們的具體定義:
2.2 SizeConverter.cpp
該檔案除了上方的 h 檔案,沒有其他頭檔案;檔案具體定義了 SizeConverter 類的三個函式,此處筆者按照函式之間的互相呼叫順序依次列出:
Convert() 函式
float OvTools::Utils::SizeConverter::Convert(float p_value, ESizeUnit p_from, ESizeUnit p_to)
{
const float fromValue = powf(1024.0f, static_cast<float>(p_from) / 3.0f);
const float toValue = powf(1024.0f, static_cast<float>(p_to) / 3.0f);
return p_value * (fromValue / toValue);
}
該函式傳入的引數含義分別是要修改的值、原位元組單位、新位元組單位,且兩個單位都是來自類內的列舉類 ESizeUnit;接著函式呼叫 static_cast<> 操作,將兩個單位強制轉換為 float 型,除以 3.0f 求出冪次后,用 corecrt_math.h 的 powf() 冪運算函式計算以 1024.0f 為底數的位元值,這樣就得到了兩個單位之間的進制;最后求比值進行換算,
ConvertToOptimalUnit() 函式
std::pair<float, OvTools::Utils::SizeConverter::ESizeUnit> OvTools::Utils::SizeConverter::ConvertToOptimalUnit(float p_value, ESizeUnit p_unit)
{
if (p_value == 0.0f) return { 0.0f, ESizeUnit::BYTE };
const float bytes = Convert(p_value, p_unit, ESizeUnit::BYTE);
const int digits = static_cast<int>(trunc(log10(bytes)));
const ESizeUnit targetUnit = static_cast<ESizeUnit>(fmin(3.0f * floor(digits / 3.0f), static_cast<float>(ESizeUnit::TERA_BYTE)));
return { Convert(bytes, ESizeUnit::BYTE, targetUnit), targetUnit };
}
該函式能換算原單位為自判定最佳單位,最佳的依據為使除去單位后的數字最小,學習了 Convert() 函式,這個函式的操作也很明朗了,在判斷給出的值是非零后,代碼呼叫 Convert() 先換算為 ESizeUnit 中最小的單位 BYTE,接著呼叫 log10() 求該值的對數,但是顯然,該值不一定就是 10 的整次冪,所以要呼叫 trunc() 取整,并將其用 static_cast 強制轉換為 int 型存在 digits 變數中,
得到了冪數,就可以計算適用的單位了,由于位元單位間的進制是以千即 103 為基礎,digits 需要除以 3.0f 并呼叫 floor() 向下取整,再乘以 3.0f 得到的值才符合 ESizeUnit 中的計量方式;最后呼叫 fmin() 得到該冪次與 ESizeUnit 最大單位 TERA_BYTE 兩者中的較小者,防止溢位,這樣就獲得了最優單位,再呼叫 Convert() 換算就好了,
簡單一提,上述的多個數學運算函式都來自 corecrt_math.h 檔案,該檔案屬于 std 的 math.h,
UnitToString() 函式
std::string OvTools::Utils::SizeConverter::UnitToString(ESizeUnit p_unit)
{
switch (p_unit)
{
case OvTools::Utils::SizeConverter::ESizeUnit::BYTE: return "B";
case OvTools::Utils::SizeConverter::ESizeUnit::KILO_BYTE: return "KB";
case OvTools::Utils::SizeConverter::ESizeUnit::MEGA_BYTE: return "MB";
case OvTools::Utils::SizeConverter::ESizeUnit::GIGA_BYTE: return "GB";
case OvTools::Utils::SizeConverter::ESizeUnit::TERA_BYTE: return "TB";
}
return "?";
}
最后這個函式更加簡單了,用 switch 陳述句判斷給出單位型別,回傳成我們縮寫的單位,例如 MEGA_BYTE 輸出為 MB,
總結
由此可見,ReferenceOrValue 與 SizeConverter 兩部分并不難,都是些標準庫函式呼叫,但是,我們要學習這些方法的實作邏輯,好讓自己的代碼更加簡明,
下一篇,筆者將探究 Utils 的最后兩部分:String 與 SystemCalls,如果篇幅不長,這將不僅是 Utils 的最后一篇,也是 OvTools 模塊分析的最后一篇,筆者會直接在篇尾對 OvTools 做一個大體總結;太長的話,還是考慮總結單獨寫一篇吧,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/345853.html
標籤:其他
