找回密码
 点一下
查看: 6147|回复: 8

GameCache应用教程

[复制链接]
发表于 2007-4-1 09:01:12 | 显示全部楼层 |阅读模式
Return Bug:

何谓return bug?如以下代码:
[codes=jass]
function h2i takes handle h returns integer
return h
return 0
endfunction

function i2u takes integer i returns unit
return i
return null
endfunction

function i2tm takes integer i returns timer
return i
return null
endfunction[/codes]
以上几个函数都有2个不同类型的return值,本来这种函数应当是被作为错误处理的,但是WE的检错系统只由下往上检查最后一个返回值,导致该类函数并不会被报错
如h2i函数,系统只检查到return 0就通过了,而不会去管上面的return h
那么该bug有什么作用?
学过其它语言的都应该知道,handle变量实际保存的是handle对象的内存地址,而h2i所返回的integer则可以认为是该handle对象对应的内存地址,当然,这不是真正的内存位置,而是War3中所定义的内存位置,故以该方法返回的integer值即可以作为handle对象的唯一标志
如运行以下代码,屏幕上显示的会是2个连续的整数值
call BJDebugMsg(I2S(h2i(CreateTimer()))
call BJDebugMsg(I2S(h2i(CreateTimer()))

而后面的i2u i2tm则是利用该integer值来获取其对应的handle对象
但不要以为使用该bug就可以实现各种对象间的互转,比如单位<=>物品,单位组<=>玩家组,事实上该bug仅适用于handle对象(其实也可以是string、code等非handle对象)与integer之间的转换
但是千万别小瞧这一功能,几乎所有的jass演示都会用到return bug;另外,关于内存泄漏方面的研究,事实上也都是利用return bug来验证的。至于对它的使用,将会在后面再讲到。

GameCache:

GameCache原本只是设计用来作为战役地图转换时的数据传递时用的,但是有了return bug之后,其意义和性质就完全不同了。

以下是所有GameCache相关的函数:
native InitGameCache takes string campaignFile returns gamecache
创建游戏缓存

native SaveGameCache takes gamecache whichCache returns boolean
native ReloadGameCachesFromDisk takes nothing returns boolean
用于战役地图转换时的缓存数据保存

native StoreInteger takes gamecache cache, string missionKey, string key, integer value returns nothing
native StoreReal takes gamecache cache, string missionKey, string key, real value returns nothing
native StoreBoolean takes gamecache cache, string missionKey, string key, boolean value returns nothing
native StoreUnit takes gamecache cache, string missionKey, string key, unit whichUnit returns boolean
native StoreString takes gamecache cache, string missionKey, string key, string value returns boolean

保存缓存数据

native GetStoredInteger takes gamecache cache, string missionKey, string key returns integer
native GetStoredReal takes gamecache cache, string missionKey, string key returns real
native GetStoredBoolean takes gamecache cache, string missionKey, string key returns boolean
native GetStoredString takes gamecache cache, string missionKey, string key returns string
native RestoreUnit takes gamecache cache, string missionKey, string key, player forWhichPlayer, real x, real y, real facing returns unit
读取缓存数据

native HaveStoredInteger takes gamecache cache, string missionKey, string key returns boolean
native HaveStoredReal takes gamecache cache, string missionKey, string key returns boolean
native HaveStoredBoolean takes gamecache cache, string missionKey, string key returns boolean
native HaveStoredUnit takes gamecache cache, string missionKey, string key returns boolean
native HaveStoredString takes gamecache cache, string missionKey, string key returns boolean
检查该缓存项是否已储存该类型数据

native FlushGameCache takes gamecache cache returns nothing
删除并清空整个缓存
native FlushStoredMission takes gamecache cache, string missionKey returns nothing
删除并清空某个缓存分类

native FlushStoredInteger takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredReal takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredBoolean takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredUnit takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredString takes gamecache cache, string missionKey, string key returns nothing

清空某个缓存项所储存的指定数据类型,但并不会删除缓存项

native SyncStoredInteger takes gamecache cache, string missionKey, string key returns nothing
native SyncStoredReal takes gamecache cache, string missionKey, string key returns nothing
native SyncStoredBoolean takes gamecache cache, string missionKey, string key returns nothing
native SyncStoredUnit takes gamecache cache, string missionKey, string key returns nothing
native SyncStoredString takes gamecache cache, string missionKey, string key returns nothing

同步各玩家间的缓存数据

游戏缓存的保存格式为:
缓存名.类别名.缓存项目名:数据类型
native StoreInteger takes gamecache cache, string missionKey, string key, integer value returns nothing
cache.missionKey.key:integer value
与Windows的文件夹系统比较相似,并且同一个缓存项可以保存integer real boolean unit string类型数据各一个
也就是说:
[codes=jass]call StoreInteger(udg_GC,"A","A",123)
call StoreBoolean(udg_GC,"A","A",false) //此时该缓存项所保存的integer值还是123,而boolean值为false,两者互不干涉
call StoreString(udg_GC,"A","A","ABC") //同样,为string类型赋值不会影响以上2个数据
call StoreInteger(udg_GC,"A","A",234) //只有这时,缓存项的integer值才会改变[/codes]


同样,FlushStoredInteger之类的清空缓存项数据的函数也只会清除它对应的数据,而不会删除缓存项,即使你在该缓存项确实只储存了该类数据。
也就是说,只有清空缓存项的函数,而没有单独删除缓存项的函数,如果要删除缓存项则需要删除整个分类。

缓存的使用很简单:
使用Storexxx系列函数存储数据
使用GetStoredxxx系列函数读取数据
使用Flushxxx系列函数删除和清空数据

GameCache+Return Bug的应用:

好吧,先让我们实现一个简单的技能:山丘之王在施放雷霆一击的同时对目标造成每秒10点伤害,持续5秒
当然我们可以简单的用其它持续技能来实现伤害,但我们不希望它和这些技能有冲突,并且,这只是一个简单的举例而已,我们已经将所有非主要因素都最简化了。

范例下载: testMap.w3x (26 KB, 下载次数: 218)

我想很多GUI T用户会这么做:

-单位开始一个技能的效果
-施放技能=雷霆一击
-用全局变量记录单位组, 循环+等待对单位组内所有单位造成伤害


好吧,这确实很简单,但是在技能CD<持续时间,或是多个单位拥有该技能时就会出问题了,因为全局变量是共用的
这时候我们需要把单位组和循环整数换成局部变量,这样就不会再有变量冲突了
但这时我们又发现一个问题,就是但两次技能同时做用于单位身上时,该单位会受到每秒20点伤害,而不是我们所期望的10点,要解决这个问题我们就需要在第2次技能作用于单位时中止前一次技能对它的作用,用Timer计时器能很好的实现这一功能,但是怎么为每个单位绑定一个计时器?显然用全局变量来实现会非常繁琐;而局部变量又不能在各个函数之间传递;这时候我们就需要用到GameCache + Return Bug,如以下代码:
[codes=jass]function Trig_gamecache_TimerFunc takes nothing returns nothing
local integer iu = GetStoredInteger(udg_GC, I2S(h2i(GetExpiredTimer())), "DamagedUnit")
call UnitDamageTarget(i2u(GetStoredInteger(udg_GC, I2S(h2i(GetExpiredTimer())), "Caster")), i2u(iu), 10.0, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_LIGHTNING, WEAPON_TYPE_WHOKNOWS)
if GetUnitAbilityLevel(i2u(iu), 'BHtc') <= 0 then
call FlushStoredMission(udg_GC, I2S(h2i(GetExpiredTimer())))
call FlushStoredInteger(udg_GC, I2S(iu), "ThunderClapTimer")
call DestroyTimer(GetExpiredTimer())
endif
endfunction

function Trig_gamecache_FilterFunc takes nothing returns boolean
if GetUnitAbilityLevel(GetFilterUnit(), 'BHtc') > 0 and not HaveStoredInteger(udg_GC, I2S(h2i(GetFilterUnit())), "ThunderClapTimer") then
set bj_lastStartedTimer = CreateTimer()
call StoreInteger(udg_GC, I2S(h2i(GetFilterUnit())), "ThunderClapTimer", h2i(bj_lastStartedTimer))
call StoreInteger(udg_GC, I2S(h2i(bj_lastStartedTimer)), "DamagedUnit", h2i(GetFilterUnit()))
call StoreInteger(udg_GC, I2S(h2i(bj_lastStartedTimer)), "Caster", h2i(GetTriggerUnit()))
call TimerStart(bj_lastStartedTimer,1.0,true, function Trig_gamecache_TimerFunc)
endif
return false
endfunction

function Trig_gamecache_Conditions takes nothing returns boolean
if GetSpellAbilityId() == 'AHtc' then
call GroupEnumUnitsInRange(udg_TC_Group, GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()),400, Condition(function Trig_gamecache_FilterFunc))
endif
return false
endfunction

function InitTrig_gamecache takes nothing returns nothing
set gg_trg_gamecache = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_gamecache, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerAddCondition(gg_trg_gamecache, Condition(function Trig_gamecache_Conditions))
endfunction[/codes]
call StoreInteger(udg_GC, I2S(h2i(GetFilterUnit())), "ThunderClapTimer", h2i(bj_lastStartedTimer))
call StoreInteger(udg_GC, I2S(h2i(bj_lastStartedTimer)), "DamagedUnit", h2i(GetFilterUnit()))
call StoreInteger(udg_GC, I2S(h2i(bj_lastStartedTimer)), "Caster", h2i(GetTriggerUnit()))
以该形式实现了对单位与计时器之间的互相绑定,使用h2i函数所得到的integer值与单位以及计时器之间是唯一对应的
然后使用GetStoredInteger函数便可以很简便的得到某物件所绑定的数据
该方面的范例很多,几乎所有Jass演示中都有GameCache+Return Bug的应用,可以自己多研究研究。

GameCache的储存格式:
利用GameCache的储存格式大致上有2种:
1.上例所示,以对象的handle值作为类别名,如 call StoreInteger(udg_GC, I2S(h2i(u)), "ThunderClapTimer", h2i(tm)) 就是以单位的handle值作为缓存类别名
2.以对象的handle值+识别名作为类别名,如上面的函数可以这样写:call StoreInteger(udg_GC, "ThunderClap"+I2S(h2i(u)), "Timer", h2i(tm))

那么这2种方法有什么区别呢?
以上我们已经说到,没有单独删除缓存项的函数,如果要删除缓存项则需要删除整个分类。
很显然的使用1方式来储存数据,是不可能去删除分类的,比如你在该技能结束时想要删除单位相关的数据,但是其它技能的数据可能也保存在该类别下,你不能霸道到把其它数据也要一并删除吧?
那么使用2方式就能方便的使用FlushStoredMission函数来删除不需要的缓存项了
2方式的储存格式要比1有条理的多,并且可以随时删除不需要的缓存,曾经笔者也觉得用2方式比1方式要好的多,直到后来发现了另一个问题:

首先让我们来了解一下String对象
War3中为String独立开辟一块内存区,初始分配的string地址为0-246,0=null 1="",2一般为地图名称,之后是各个用到的string。未使用的地址被赋值为"(null)"。
当分配的地址不够用时系统自动再分配256位地址,并全部赋值为"(null)"。
所有触发脚本中出现的字符串都会被记录,并无法删除。
相同的字符串不会被再次记录。
例:
设string内存地址1-25已被使用,那么

[codes=jass]local string s
set s="a" //此时地址26被赋值为"a"
set s="b"+"c" //27="b" 28="c" 29="bc"
set s="bc" //"bc"已经存在,故不会再被记录 [/codes]

我们可以很方便的通过return bug设计i2s和s2i函数来观察其结果,当运行以上代码后
i2s(27)="b"
i2s(200)="(null)"
而如果想要运行 i2s(300) 则游戏会直接弹出,因为该内存地址还未被分配;如果运行以下代码之后就不会发生弹出事件,因为新的内存区域已经被分配,此时i2s(300)="(null)"

[codes=jass]local integer i=0
local string s
loop
set s=I2S(i)
set i=i+1
exitwhen i>250
endloop[/codes]

好吧,讲这么多应该足够了。现在来看看Cache储存方式2的问题吧。问题就在"ThunderClap"+I2S(h2i(u))上面。
I2S(h2i(u))会产生若干个string对象,而"ThunderClap"+又会产生另外的若干个string对象。
并且我们还会有很多诸如"IceBolt"+I2S(h2i(u)) "Blizzard"+I2S(h2i(tm))之类的东西,于是由此而产生的string数量是很大的,这就是方式2的弊端。此时我们就要重新去度量2者的价值了。

方式1:
使用该方式缓存结构无法被删除,但是游戏缓存项所占内存是很小的,100万条Integer类缓存项才占用60多M内存,使用FlushStoredInteger也能大量减少内存占用。并且对于Timer Trigger等一些对象,即使用该方式存储数据,我们也可以用FlushStoredMission来直接删除缓存类,因为这些东西是不会在两个不同的技能或系统中使用的。所以我们也可以将数据更多的绑定在Timer以及Trigger上,而不是Unit上。
而且很重要的一点,缓存项数量的增加,并不会影响到缓存的操作速度,更不会影响到游戏速度。

方式2:
这种存储方式条理比较清晰,并且在清空数据时会比较方便,但是会产生数量很大的string对象,在测试中创建100万个7位数字转换的string需要占用200多M空间,并且很难让人接受的是,当string数量增加后,对string的操作效率会急剧下降,即使只是对string变量的简单赋值而已。不过值得庆幸的是,string是独自使用一块内存区域的,所以string对象的大量存在对string以外的其它操作并不会有影响。

后言:
GameCache可以说是Jass的精华所在,当你学会它时,原来很多比较困惑的问题都会引刃而解。
虽然使用GameCache存储数据非常方便,但是能简单的用变量解决的问题还是应该用变量的,毕竟对变量的操作速度要远大于对GameCache的操作。
发表于 2007-4-2 09:26:23 | 显示全部楼层
看了
回复

使用道具 举报

发表于 2007-4-3 12:16:53 | 显示全部楼层
居然今天才看到嗯嗯...嗯?
回复

使用道具 举报

发表于 2007-4-18 12:34:39 | 显示全部楼层
嗯~有种茅塞顿开的感觉~~呵呵~
回复

使用道具 举报

发表于 2007-4-22 19:35:21 | 显示全部楼层
[s:57]一直有2个疑问,想请教老狼:
1)相同条件下,使用全局变量和GAME CACHE到底哪个好??什么时候用GAME CACHE什么时候用全局,还是抛弃全局(当然都是指以上5种类型,在别的语言中可是尽量抛弃全局而使用指针的)
2)GAME CACHE的排泄.............
回复

使用道具 举报

 楼主| 发表于 2007-4-25 12:30:25 | 显示全部楼层
1 能用变量的,当然是变量好,变量的操作速度是最快的
但是很多时候变量是不能解决问题的,比如很多Jass技能的实现都是必须用到GameCache的

2 GameCache其实没多大的泄漏问题,因为并不会影响游戏速度的,占用内存也是很小一部分
只要注意减少"+"运算所产生的string对象就行
回复

使用道具 举报

发表于 2007-7-19 07:42:35 | 显示全部楼层
555555 在这张帖子耗了几个小时了
回复

使用道具 举报

发表于 2007-7-19 08:52:39 | 显示全部楼层
  [s:130] 好强大....MB一下...
回复

使用道具 举报

发表于 2010-11-19 11:27:27 | 显示全部楼层
楼主大大   

请问  是不是 类似  屠夫的钩子 这样的技能  要做到多人使用  而不用 等待动作  就会用到 缓存  感觉多人使用 比较麻烦  如果不用等待动作
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-5-3 14:13 , Processed in 0.253452 second(s), 21 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表