ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-11-01 17:57:59  阅读:163  来源: 互联网

标签:std 2021SC ReferenceOrValue SizeConverter value Overload OvTools ESizeUnit BYTE


<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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有