其他分享
首页 > 其他分享> > <2021SC@SDUSC> 开源游戏引擎 Overload 代码模块分析 之 OvTools(五)—— Utils(中)

<2021SC@SDUSC> 开源游戏引擎 Overload 代码模块分析 之 OvTools(五)—— Utils(中)

作者:互联网

<2021SC@SDUSC>
开源游戏引擎 Overload 代码模块分析 之 OvTools(五)—— Utils(中)

目录

前言

本篇是开源游戏引擎 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 类的函数就没有什么难度了。所以 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 做一个大体总结;太长的话,还是考虑总结单独写一篇吧。

clapping

标签:std,2021SC,ReferenceOrValue,SizeConverter,value,Overload,OvTools,ESizeUnit,BYTE
来源: https://blog.csdn.net/Egovix/article/details/120961284