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

2021SC@SDUSC
开源游戏引擎 Overload 代码模块分析 之 OvTools(三)—— Time

前言

本文已是 Overload 引擎相关的第五篇文章了,也是它的 OvTools 功能模块的第三篇探究文章。想大致了解 Overload 可前往这篇文章,想看其他相关文章请前往笔者的 Overload 专栏自主选择。

本篇将探究 OvTools 的第三个小模块 Time 时间操作,让我们马上开始吧!

分析

1、Time 模块概述

义如其名,作为一个时间模块,当然负责处理时间,它包括了下列 Clock(时钟)Date(日期) 两类文件:
Time list1
Time list2
接下来,我们先了解 Clock 类。

2、Clock

2.1 Clock.h

2.1.1 头文件

该文件引入了下列头文件:

#include <string>
#include <chrono>

string 文件不必赘述;chrono 文件是 C++11 的一个头文件,主要依靠 duration(持续时间)、time_point(时间点)等类实现处理时间的功能。该库是 Clock 类功能的一个核心。

值得一提的是,chrono 包含元素的命名空间不是直接在 std:: 下,而是在 std::chrono:: 下,我们也将在 Clock 类的变量定义中看到。

2.1.2 Clock 类

该类负责处理时间计算,.h 文件代码仅包含了各种函数变量的声明定义:

	class Clock
	{
	public:
		/**
		* Update the clock
		*/
		void Update();

		/**
		* Return the actual framerate (Depend on the delta time)
		*/
		float GetFramerate();

		/**
		* Return the actual delta time (Scale applied)
		*/
		float GetDeltaTime();

		/**
		* Return the actual delta time (Scale not applied)
		*/
		float GetDeltaTimeUnscaled();

		/**
		* Return the time elapsed since clock creation
		*/
		float GetTimeSinceStart();

		/**
		* Return the current timescale
		*/
		float GetTimeScale();

		/**
		* Multiply the timescale by the given coefficient
		* @param p_coeff
		*/
		void Scale(float p_coeff);

		/**
		* Define a timescale
		* @param p_timescale
		*/
		void SetTimeScale(float p_timeScale);

	private:
		void Initialize();

		std::chrono::steady_clock::time_point	__START_TIME;
		std::chrono::steady_clock::time_point	__LAST_TIME;
		std::chrono::steady_clock::time_point	__CURRENT_TIME;
		std::chrono::duration<double>			__ELAPSED;

		bool	__INITIALIZED = false;
		float	__TIME_SCALE = 1.0f;
		float	__DELTA_TIME = 0.0f;
		float	__TIME_SINCE_START = 0.0f;
	};

代码的注释已经大致告知了各函数的作用,笔者不多赘述

关于该类定义的变量,代码用到了 std::chrono::steady_clock::time_point :正如上文所述,time_point 是一个类模板,其意义是时间点。该类会设置一个参考的固定时间点,所有使用相同时钟的 time_point 对象都将参考该固定点,对象通过相对该固定时间点的 duration(持续时间)—— time_point 类内含 duration 类的对象 —— 表示一个时间点。

关于 duration,代码中的变量 __ELAPSED 定义也用到了它:std::chrono::duration(持续时间)。其意思也如其名,表示流逝了的时间;显然,该变量也会是其他函数的一个重点操作对象。

其他变量与命名的英文意思一致,也无需赘述。

2.2 Clock.cpp

该文件仅有 Clock.h 头文件,主体代码对上述函数进行了具体定义。其中一部分函数是按要求返回计算后的值,简单易懂,在此直接给出代码:

float OvTools::Time::Clock::GetFramerate()
{
	return 1.0f / (__DELTA_TIME);
}

float OvTools::Time::Clock::GetDeltaTime()
{
	return __DELTA_TIME * __TIME_SCALE;
}

float OvTools::Time::Clock::GetDeltaTimeUnscaled()
{
	return __DELTA_TIME;
}

float OvTools::Time::Clock::GetTimeSinceStart()
{
	return __TIME_SINCE_START;
}

float OvTools::Time::Clock::GetTimeScale()
{
	return __TIME_SCALE;
}

void OvTools::Time::Clock::Scale(float p_coeff)
{
	__TIME_SCALE *= p_coeff;
}

void OvTools::Time::Clock::SetTimeScale(float p_timeScale)
{
	__TIME_SCALE = p_timeScale;
}

除了上列的函数外,仅剩下两个函数,它们会略微复杂一些,我们依次理解:

Initialize() 函数
void OvTools::Time::Clock::Initialize()
{
	__DELTA_TIME = 0.0f;

	__START_TIME = std::chrono::steady_clock::now();
	__CURRENT_TIME = __START_TIME;
	__LAST_TIME = __START_TIME;

	__INITIALIZED = true;
}

该函数是初始化时间差变量 __DELTA_TIME 、开始时间 __START_TIME 、当前时间 __CURRENT_TIME 、最后记录的时间 __LAST_TIME。其中,初始化后三者用到了函数 std::chrono::steady_clock::now(),该函数的作用也很简单,是将当前时间作为值 time_point 返回。

Update() 函数
void OvTools::Time::Clock::Update()
{
	__LAST_TIME = __CURRENT_TIME;
	__CURRENT_TIME = std::chrono::steady_clock::now();
	__ELAPSED = __CURRENT_TIME - __LAST_TIME;

	if (__INITIALIZED)
	{
		__DELTA_TIME = __ELAPSED.count() > 0.1 ? 0.1f : static_cast<float>(__ELAPSED.count());
		__TIME_SINCE_START += __DELTA_TIME * __TIME_SCALE;
	}
	else
		Initialize();
}

Update() 函数负责实现更新时间点的功能:它先用最后记录的时间与当前的时间(函数 now() )计算出时间差,作为 duration 记录在 __ELAPSED 中;然后判断是否初始化了 __DELTA_TIME 等变量;接着调用 duration 的函数 count() —— 在 chrono 文件中可以看到,该函数负责检索时间间隔内的时钟计时周期数 —— 经过判断后将值 0.1 或用 static_cast 把周期数强制转换为 float 赋值时差变量 __DELTA_TIME,以此控制时差在 0.1 以内;最后,将时差缩放后得到新时间点 __TIME_SINCE_START(相对于 time_point 设置的固定点)。

由此可见,Clock 类虽然有各种各样的计算操作,但是并不复杂,依靠的多是 chrono 库。接下来开始探索 Date 部分:

3、Date

3.1 Date.h

Date.h 的头文件仅包含了 string,不多赘述:

#include <string>
Date 类

文件主体代码是 Date 类,一个日期系统,以字符串格式获取当前日期,其定义仅有两个函数,代码如下:

class Date
	{
	public:
		/**
		* Default constructor
		*/
		Date() = delete;

		/*
		* Return the current date in a string format
		*/
		static std::string GetDateAsString();
	};

函数作用已有注释,不赘述。值得一提的是,代码应用了 C++11 的关键词 delete,表示删除默认构造函数,这可以为简单功能类省下存储空间;相对的,关键词 default 表示默认存在构造函数。

定义很短,所以我们直接看 Date.cpp。

3.2 Date.cpp

头文件包含了 Date.h,与 C++ 标准库的 ctime,不多赘述:

#include <ctime>
#include "OvTools/Time/Date.h"
GetDateAsString() 函数

正如定义,主体代码只有函数 GetDateAsString():

std::string OvTools::Time::Date::GetDateAsString()

首先,让我们看看该函数几个元素的定义与简单处理:

	std::string date;
	const auto now = time(nullptr);
	tm ltm;

	localtime_s(&ltm, &now);

	std::string dateData[6] =
	{
		std::to_string(1900 + ltm.tm_year),
		std::to_string(1 + ltm.tm_mon),
		std::to_string(ltm.tm_mday),
		std::to_string(ltm.tm_hour),
		std::to_string(ltm.tm_min),
		std::to_string(ltm.tm_sec)
	};

1、string 型的日期变量 date,显然将用作返回值,以返回一个字符串形式的日期。

2、const auto 关键词限定的自动判断类型的常量 now:通过 time.h 中的函数 time() 获得系统日期与时间,其中 time() 返回值的类型经过层层解开其重命名,是 __int64。

3、corecrt_wtime.h 中 struct 结构体 tm 的对象 ltm:该结构体包含多个 int 型的时间变量,例如 tm_sec(记录 0~60 秒)、tm_wday(记录一周7天)等等。

代码对其做了简单处理:调用 time.h 的函数 localtime_s()。该函数需要传入 struct tm* _tm 与 const time_t *time 两个参数 —— 显然就是上述的两个元素 —— 并把 time 存储在上述的 tm 结构体中。如果成功,返回值为零;如果失败,返回值将是错误代码(在 Errno.h 中定义,不赘述)。

简单一提,该函数有两个兄弟:localtime() 和 localtime_r()。虽然三兄弟功能是一样的,但是有一定的区别:localtime 是最早定义的,但它因为存在另外两者没有的问题而被淘汰 —— 线程不安全,简单解释为该函数只申请了一个指针,所以有多个线程调用时会互相篡改数据。另外两者区别在于:localtime_s 用于 Windows 系统;localtime_r 用于 Linux 系统。

4、string 型的日期数据数组 dateDate:将上述经过 localtime_s() 初次格式化后的日期通过调用 to_string 转为 string 型存储,依次记录下年、月、日、时、分、秒

了解了定义的元素与部分处理,我们就能快速理解函数的剩余部分 —— 两个 for 循环:

	for (uint8_t i = 1; i < 6; ++i)
		if (dateData[i].size() < 2)
			dateData[i].insert(dateData[i].begin(), '0');

	for (uint8_t i = 0; i < 6; ++i)
	{
		date += dateData[i];
		if (i == 2)
			date += '_';
		else if (i != 5)
			date += '-';
	}

	return date;

第一个循环是依次判断并补充 dateDate 中各变量的位数,可以理解为:当月 / 日 / 时 / 分 / 秒是个位数时,十位数补充一个 “ 0 ” 使其成为 “ 两位数 ”,完成最终的格式化;第二个循环显然是按照要求将 dateDate 中的值依次赋值给字符串 date,使其成为这样的形式:yyyy-mm-dd_hh-mm-ss。最后返回输出 date,完成以字符串格式返回当前日期的功能。

总结

Time 模块的探究就到此结束了,总的来说大多是在调用 C++ 的库来完成一些核心功能,所以内容不多也不复杂。

因此,笔者将很快继续探究下一个小模块 Utils。目测内容有点儿多,可能会分两 P 讲述,一起加油吧!

clapping

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值