前几天策划要求给任务系统加上日常任务的功能,这个需求很简单:象
wow
一样,日常任务在每天某个时候定时刷新。在思考添加这个功能时,很直接想到的方案是服务器取出
3
个时间:任务上次完成时间(
pre
),当前时间(
now
)和当天刷新时间(
refresh
),然后经过一些较复杂的逻辑完成(此处省略
xx
字)。由于该方案需要涉及一些时间操作,而在
lua
和
c++
里面做日期时间的操作又是比较麻烦的一件事。水古给出了一个解法,很好的解决了这个问题:首先用当前时间(比如现在是
18:00
)减去当天刷新时间(比如
wow
的日常每天
15:00
刷新),这样就把时间对齐到前一天的
0
点,然后分别用当前时间和上次完成时间除以一天的秒数(
24*60*60
),就分别得到了当前时间和上次完成时间的天数,如果上次完成时间的天数小于当前时间的天数,则日常任务刷新。
lua
代码大致如下:
local now = os.time()
local pre = task.completed_time
local refresh = (15-8)*(60*60)
--
减
8
是因为
os.time()
得到的是
UTC
时间,而北京时间是
UTC+8
local pre_days = (pre-refresh)/(24*60*60)
local now_days = (now-refresh)/(24*60*60)
if(now_days > pre_days) then
--
刷新任务
end
很简洁,整个算法只需要一个时间函数——
os.time()
。
本以为这个功能就这样完成了,但经过反复测试,悲剧发生了:服务器的时间正确,客户端却有
1
分钟左右的偏差。同样的库,同样的脚本,却得到不同的时间。掘地三尺后发现在客户端中
lua
中的双精度浮点数经过加减运算后变成了单精度浮点数,由于精度问题导致产生一定的时间偏差——和谐的阳光照在杯具上,每一个杯具都笑开颜。
首先想到的还是
lua
库的问题,毕竟是在
lua
脚本里面精度变了,但我直接在
c++
里面写了一个简单的测试,发现精度也改变了。再次和几个朋友讨论这个问题,水古提到:
DX
会改变浮点精度,确实,我记忆中好像在哪看到过,搜索
D3D sdk
,在
CreateDevice
中发现
D3DCREATE
有这么一个选项
D3DCREATE_FPU_PRESERVE |
Set the precision for Direct3D floating-point calculations
|
很明显了,
d3d
在未指定该参数的时候会把
FPU
设置为单精度模式,其原因是因为处理单精度数据比双精度具备更好的性能。由于
D3D
默认修改了
FPU
,影响到了
lua
的精度(
lua
里面
number
只有
double
),造成了一定时间的误差。
至于性能问题,我是这样想的,以单个浮点运算来看,现代
cpu
对
double
和
float
的运算效率非常接近了,更多的效率考虑可能是
cpu
高速缓存的命中和内存带宽的暂用吧,毕竟
double
比
float
多一倍的数据量,而在
3D
环境中浮点运算大量存在。
最后说一下,该问题只是一个中间过程,最后把客户端时间修改成服务器的时间,在客户端就不用很精确的时间控制,所以这里暂时无需修改客户端代码,客户端仍然以默认的
24
位
FPU
精度模式运行,如何兼顾性能和精度,也就没去深究了。