|
…………让GameCache+ReturnBug消失…………
…………让GameCache+ReturnBug消失…………
…………让GameCache+ReturnBug消失…………
首先,把自己的War3升级到1.24b。
这个1.24b补丁在哪里都有,自己随便找。
打上补丁之后,把目录下的因为安了第三方WE而产生的Units和UI这两个文件夹改个名字。这样hashtable的UI就不会被覆盖了。
如果你还想用以前的UI,那么不改也没关系。
因为我们是用J写的。
1.24b目前看来好像只能使用原版WE。
对于想快速将GameCache替换成Hashtable的人来说,以下部分就足够了:
用记事本的替换:
把"gamecache"换成"hashtable"。
把StoredInteger( XXX, XXX, XXX, H2I( XXXX ) )
换成SaveXXXX( XXX, XXX, XXX )
I2X( GetStoredInteger( XXXX, I2S(H2I(XXXX)), XXXX ) )
换成LoadXXXX( XXXX, H2I(XXXX), StringHash( XXXX ) )即可。
[jass]
function H2I takes handle h returns integer
return GetHandleId( h )
endfunction
[/jass]
H2I这样写。
把I2X全删了。
对于新手:
首先,你要把以前所有教程中关于使用Gamecache+ReturnBug的东西全部忘掉。那些东西早就不管用了。
然后,在最开始我们看一下所有关于hashtable的函数:
[jass]
type hashtable extends agent
native InitHashtable takes nothing returns hashtable
native SaveInteger takes hashtable table, integer parentKey, integer childKey, integer value returns nothing
native SaveReal takes hashtable table, integer parentKey, integer childKey, real value returns nothing
native SaveBoolean takes hashtable table, integer parentKey, integer childKey, boolean value returns nothing
native SaveStr takes hashtable table, integer parentKey, integer childKey, string value returns boolean
native SavePlayerHandle takes hashtable table, integer parentKey, integer childKey, player whichPlayer returns boolean
native SaveWidgetHandle takes hashtable table, integer parentKey, integer childKey, widget whichWidget returns boolean
native SaveDestructableHandle takes hashtable table, integer parentKey, integer childKey, destructable whichDestructable returns boolean
native SaveItemHandle takes hashtable table, integer parentKey, integer childKey, item whichItem returns boolean
native SaveUnitHandle takes hashtable table, integer parentKey, integer childKey, unit whichUnit returns boolean
native SaveAbilityHandle takes hashtable table, integer parentKey, integer childKey, ability whichAbility returns boolean
native SaveTimerHandle takes hashtable table, integer parentKey, integer childKey, timer whichTimer returns boolean
native SaveTriggerHandle takes hashtable table, integer parentKey, integer childKey, trigger whichTrigger returns boolean
native SaveTriggerConditionHandle takes hashtable table, integer parentKey, integer childKey, triggercondition whichTriggercondition returns boolean
native SaveTriggerActionHandle takes hashtable table, integer parentKey, integer childKey, triggeraction whichTriggeraction returns boolean
native SaveTriggerEventHandle takes hashtable table, integer parentKey, integer childKey, event whichEvent returns boolean
native SaveForceHandle takes hashtable table, integer parentKey, integer childKey, force whichForce returns boolean
native SaveGroupHandle takes hashtable table, integer parentKey, integer childKey, group whichGroup returns boolean
native SaveLocationHandle takes hashtable table, integer parentKey, integer childKey, location whichLocation returns boolean
native SaveRectHandle takes hashtable table, integer parentKey, integer childKey, rect whichRect returns boolean
native SaveBooleanExprHandle takes hashtable table, integer parentKey, integer childKey, boolexpr whichBoolexpr returns boolean
native SaveSoundHandle takes hashtable table, integer parentKey, integer childKey, sound whichSound returns boolean
native SaveEffectHandle takes hashtable table, integer parentKey, integer childKey, effect whichEffect returns boolean
native SaveUnitPoolHandle takes hashtable table, integer parentKey, integer childKey, unitpool whichUnitpool returns boolean
native SaveItemPoolHandle takes hashtable table, integer parentKey, integer childKey, itempool whichItempool returns boolean
native SaveQuestHandle takes hashtable table, integer parentKey, integer childKey, quest whichQuest returns boolean
native SaveQuestItemHandle takes hashtable table, integer parentKey, integer childKey, questitem whichQuestitem returns boolean
native SaveDefeatConditionHandle takes hashtable table, integer parentKey, integer childKey, defeatcondition whichDefeatcondition returns boolean
native SaveTimerDialogHandle takes hashtable table, integer parentKey, integer childKey, timerdialog whichTimerdialog returns boolean
native SaveLeaderboardHandle takes hashtable table, integer parentKey, integer childKey, leaderboard whichLeaderboard returns boolean
native SaveMultiboardHandle takes hashtable table, integer parentKey, integer childKey, multiboard whichMultiboard returns boolean
native SaveMultiboardItemHandle takes hashtable table, integer parentKey, integer childKey, multiboarditem whichMultiboarditem returns boolean
native SaveTrackableHandle takes hashtable table, integer parentKey, integer childKey, trackable whichTrackable returns boolean
native SaveDialogHandle takes hashtable table, integer parentKey, integer childKey, dialog whichDialog returns boolean
native SaveButtonHandle takes hashtable table, integer parentKey, integer childKey, button whichButton returns boolean
native SaveTextTagHandle takes hashtable table, integer parentKey, integer childKey, texttag whichTexttag returns boolean
native SaveLightningHandle takes hashtable table, integer parentKey, integer childKey, lightning whichLightning returns boolean
native SaveImageHandle takes hashtable table, integer parentKey, integer childKey, image whichImage returns boolean
native SaveUbersplatHandle takes hashtable table, integer parentKey, integer childKey, ubersplat whichUbersplat returns boolean
native SaveRegionHandle takes hashtable table, integer parentKey, integer childKey, region whichRegion returns boolean
native SaveFogStateHandle takes hashtable table, integer parentKey, integer childKey, fogstate whichFogState returns boolean
native SaveFogModifierHandle takes hashtable table, integer parentKey, integer childKey, fogmodifier whichFogModifier returns boolean
native SaveAgentHandle takes hashtable table, integer parentKey, integer childKey, agent whichAgent returns boolean
native SaveHashtableHandle takes hashtable table, integer parentKey, integer childKey, hashtable whichHashtable returns boolean
native LoadInteger takes hashtable table, integer parentKey, integer childKey returns integer
native LoadReal takes hashtable table, integer parentKey, integer childKey returns real
native LoadBoolean takes hashtable table, integer parentKey, integer childKey returns boolean
native LoadStr takes hashtable table, integer parentKey, integer childKey returns string
native LoadPlayerHandle takes hashtable table, integer parentKey, integer childKey returns player
native LoadWidgetHandle takes hashtable table, integer parentKey, integer childKey returns widget
native LoadDestructableHandle takes hashtable table, integer parentKey, integer childKey returns destructable
native LoadItemHandle takes hashtable table, integer parentKey, integer childKey returns item
native LoadUnitHandle takes hashtable table, integer parentKey, integer childKey returns unit
native LoadAbilityHandle takes hashtable table, integer parentKey, integer childKey returns ability
native LoadTimerHandle takes hashtable table, integer parentKey, integer childKey returns timer
native LoadTriggerHandle takes hashtable table, integer parentKey, integer childKey returns trigger
native LoadTriggerConditionHandle takes hashtable table, integer parentKey, integer childKey returns triggercondition
native LoadTriggerActionHandle takes hashtable table, integer parentKey, integer childKey returns triggeraction
native LoadTriggerEventHandle takes hashtable table, integer parentKey, integer childKey returns event
native LoadForceHandle takes hashtable table, integer parentKey, integer childKey returns force
native LoadGroupHandle takes hashtable table, integer parentKey, integer childKey returns group
native LoadLocationHandle takes hashtable table, integer parentKey, integer childKey returns location
native LoadRectHandle takes hashtable table, integer parentKey, integer childKey returns rect
native LoadBooleanExprHandle takes hashtable table, integer parentKey, integer childKey returns boolexpr
native LoadSoundHandle takes hashtable table, integer parentKey, integer childKey returns sound
native LoadEffectHandle takes hashtable table, integer parentKey, integer childKey returns effect
native LoadUnitPoolHandle takes hashtable table, integer parentKey, integer childKey returns unitpool
native LoadItemPoolHandle takes hashtable table, integer parentKey, integer childKey returns itempool
native LoadQuestHandle takes hashtable table, integer parentKey, integer childKey returns quest
native LoadQuestItemHandle takes hashtable table, integer parentKey, integer childKey returns questitem
native LoadDefeatConditionHandle takes hashtable table, integer parentKey, integer childKey returns defeatcondition
native LoadTimerDialogHandle takes hashtable table, integer parentKey, integer childKey returns timerdialog
native LoadLeaderboardHandle takes hashtable table, integer parentKey, integer childKey returns leaderboard
native LoadMultiboardHandle takes hashtable table, integer parentKey, integer childKey returns multiboard
native LoadMultiboardItemHandle takes hashtable table, integer parentKey, integer childKey returns multiboarditem
native LoadTrackableHandle takes hashtable table, integer parentKey, integer childKey returns trackable
native LoadDialogHandle takes hashtable table, integer parentKey, integer childKey returns dialog
native LoadButtonHandle takes hashtable table, integer parentKey, integer childKey returns button
native LoadTextTagHandle takes hashtable table, integer parentKey, integer childKey returns texttag
native LoadLightningHandle takes hashtable table, integer parentKey, integer childKey returns lightning
native LoadImageHandle takes hashtable table, integer parentKey, integer childKey returns image
native LoadUbersplatHandle takes hashtable table, integer parentKey, integer childKey returns ubersplat
native LoadRegionHandle takes hashtable table, integer parentKey, integer childKey returns region
native LoadFogStateHandle takes hashtable table, integer parentKey, integer childKey returns fogstate
native LoadFogModifierHandle takes hashtable table, integer parentKey, integer childKey returns fogmodifier
native LoadHashtableHandle takes hashtable table, integer parentKey, integer childKey returns hashtable
native HaveSavedInteger takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedReal takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedBoolean takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedString takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedHandle takes hashtable table, integer parentKey, integer childKey returns boolean
native RemoveSavedInteger takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedReal takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedBoolean takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedString takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedHandle takes hashtable table, integer parentKey, integer childKey returns nothing
native FlushParentHashtable takes hashtable table returns nothing
native FlushChildHashtable takes hashtable table, integer parentKey returns nothing
[/jass]
hashtable就是一个你用来存储数据的地方,基本上不管是什么都可以存进去。
InitHashtable是创建一个hashtable,可以用来存储数据
Save和Load可以看成同一种类型。
Have是判断。
Remove是清除。
Flush是大范围清除。
那么,你们都看到了,参数上基本都是上前面都是这个样子:
[jass]hashtable table, integer parentKey, integer childKey[/jass]
那么他们的意思是什么呢?
就是在这个table中,parentKey的项目里的childKey的子项目的某些数据。
比如SaveInteger( HT, GetHandleId( t ), Const_ChildKey_Timer_Index, index )
就是在hashtable HT,的timer t的HandleId的项目下,的integer Const_ChildKey_Timer_Index的子项目中存储一个integer index的数据~
举一个形象的例子,把hashtable看成一个非常巨大的图书馆。
图书馆里有很多个横向排列的书架,这些书架都有自己的独一无二的编号,这些编号就是parentKey。
每个书架都有竖向排列的长方体的书柜,这些书柜在每个书架中都有自己独一无二的编号,这些编号就是childKey。
而每个书柜都有5层,每层都有一个格子,上面贴有标签"integer"、"real"、"boolean"、"string"、"handle"。分别表示这个格子存放的书籍中的内容是什么。
InitHashtable就可以理解为在空地上创建一所巨大的图书馆。
Save的话,就相当于,从图书馆大门走进去,首先找到编号为parentKey(图中为1000872)的书架:
然后我们放大这个图像,往上走,找到在这个1000872的书架中的编号为childKey(图中为10)的书柜:
(画工不好,书就不画了)
然后呢,如果是:
[jass]
native SaveInteger takes hashtable table, integer parentKey, integer childKey, integer value returns nothing
native SaveReal takes hashtable table, integer parentKey, integer childKey, real value returns nothing
native SaveBoolean takes hashtable table, integer parentKey, integer childKey, boolean value returns nothing
native SaveStr takes hashtable table, integer parentKey, integer childKey, string value returns boolean
[/jass]
就是直接在这个书柜中的对应格子(integer,real,boolean,string)中的书上写上你要存储的数据。
比如
[jass]
call SaveInteger( HT, 1000872, 10, 4567 )
[/jass]
就是在刚才的书柜中的integer格子中的书上写上4567的意思~
但是大家注意了~因为这些书都薄的惊人,所以为了能写下以后的数据,我们在写新的数据的时候,都会把原来的字擦掉~~
这个就像对一个变量赋值一样。
[jass]
local integer An_Int = 882211
set An_Int = 4567
[/jass]
新的4567会覆盖原先的882211的值。
real、boolean和string都是类似的~
当然啦~计算机可比我们的速度快很多~可以近似认为它有全部图书馆内范围的0CD0耗魔闪烁技能~可以从大门闪烁到对应书架再闪烁到对应的书柜~
所以即使整个图书馆里有无数的书架和书柜~它也可以很快达到目的地~
但是~~
[jass]native SaveXXXXHandle takes hashtable table, integer parentKey, integer childKey, XXXX whichXXXX returns boolean[/jass]
像这样的都注意啦~
这些数据都是存储一个对于whichXXXX的指针。并不是像StoreUnit+RestoreUnit那样可以创造出一个新的单位那样的。
具体待会再说。
这些函数的格子在哪里呢?
注意到了?后缀都是Handle,所以他们所有的数据都是直接存储在handle这种格子里哦~
同类的player或者unit都是像integer那些一样的“后来者居上”的覆盖原则(这个应该可以简单推理得到的吧),但是如果不同类的怎么办?
[jass]
call SavePlayerHandle( HT, 1000872, 10, Player( 0 ) )
call SaveUnitHandle( HT, 1000872, 10, u )
[/jass]
那么那个handle的格子里面的书上(数据)到底写了什么呢?
答案是unit u~
所以不同类别的Handle存储在一起也是后来者居上的原则,u会把之前写的player Player(0)覆盖掉~
非常遗憾他不能存储更加细分的Handle类别~不过这其实也足够了(对我来说一个integer就完全足够了)
既然是图书馆,那么就必然会有人查找资料~~
这时候Load就可以出现啦~
Load是用来查询对应的书架(parentKey)的书柜(childKey)对应的格子的数据的函数~
比如
[jass]
set An_Int = LoadInteger( HT, 1000872, 10 )
[/jass]
这时An_Int其实就是在编号1000872的书架的编号为10的书柜的integer的格子里的书上写的数字啦~
刚刚我们存了一个4567进去,那么现在An_Int就是4567啦~
但是如果格子中的书上什么也没有写怎么办?
那么
[jass]
native LoadInteger takes hashtable table, integer parentKey, integer childKey returns integer
native LoadReal takes hashtable table, integer parentKey, integer childKey returns real
native LoadBoolean takes hashtable table, integer parentKey, integer childKey returns boolean
native LoadStr takes hashtable table, integer parentKey, integer childKey returns string
[/jass]
这四个分别会返回:
[jass]
0
0.0
false
null
[/jass]
其他的后缀为Handle的函数,返回的也都是null
但是如果书上写了integer 0,那么又如何区别写了字的书和没有写字的书呢?
这样就需要用到这些Have判断函数了~
[jass]
native HaveSavedInteger takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedReal takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedBoolean takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedString takes hashtable table, integer parentKey, integer childKey returns boolean
native HaveSavedHandle takes hashtable table, integer parentKey, integer childKey returns boolean
[/jass]
parentKey和childKey我就不多说了~
如果这个对应格子的书上什么也没有写的话,这个函数就会返回false。
但是如果写了字的话,那么就会返回true。
刚才那个情况,一本写了“0”的书,和一本什么也没有写的书。
用LoadInteger,返回的全部是0
用HaveSavedInteger,第一本书返回true,第二本书返回false~
那么~如果我们想把一本书上的内容清空怎么办?
比如我们发现有些书上的内容已经不需要了~
我们想把这些书上的字都擦掉~
可以用
[jass]
native RemoveSavedInteger takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedReal takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedBoolean takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedString takes hashtable table, integer parentKey, integer childKey returns nothing
native RemoveSavedHandle takes hashtable table, integer parentKey, integer childKey returns nothing
[/jass]
这些Remove函数啦~
这种函数不管那本书上存储的什么内容~都会清扫干净哦~
我们为什么要清除这些东西呢?
如果就这么放着不也是不太影响吗?
这可就大错特错了哦~
因为hashtable原理的原因,如果是一本空书的话,就不会再额外占地方(使用内存少)。
写了字的书会额外的占地方(使用内存多),我们不清除数据的话。
每一次使用完毕的数据都会在这个巨大的图书馆中沉淀下来~
虽然说是“巨大”的图书馆,但是如果长年累月这么一直把数据放在里面不整理的话,不仅地方不够大了~而且下一次读取数据的时候,很可能就会读取到以前遗留下来的数据~但是我们认为那应该会读取对应的空值(0,0.0,false,null)~引起的数据混乱可是不堪设想哦~
PS:我在这里小小的解释一下为什么“地方不够大了”的原因:
虽然头目说过貌似10W还是100W的childKey还是parentKey都还是有效的。
第一这一定会使用某些空间来存储数据(比如内存),内存就算是2G,4G……的那也总得有个头吧(虽然依靠Save很难达到这种目标)。
第二,根据头目的描述,我们可以简单判断war3的hashtable是一个开链表HashTable,这种Hashtable理论上childKey和parentKey都可以无限增加(当然,是空间无限的条件下),可悲的是所有的HashTable的效率都会受到这个HashTable里面存储的Key的数量的影响,Key越多,HashTable的效率就会越差。如果不清除Key,那么HashTable的效率会越来越低,真的比GameCache的效率(这玩意效率我记得应该很平稳,但是极端低下)都低了的时候,就变成了一个茶table~
啊啦拉…………一不小心就把后面的Flush函数的东西提出来了呢~
[jass]
native FlushParentHashtable takes hashtable table returns nothing
native FlushChildHashtable takes hashtable table, integer parentKey returns nothing
[/jass]
这两个威力都非常大的函数~
FlushParentHashtable就是直接摧毁(destroy)这个巨大的图书馆的意思啦~
所有的数据都灰飞烟灭了哦~
FlushChildHashtable的威力虽然没有Parent大,但是也算是很强力的喽~
可以直接把一个书架的所有书柜的所有格子里的书的内容都擦掉哦!
也就是说重新初始化(init)一个书架~
里面的数据也都消失了~
这个就是用来清除Key的函数啦~
现在还不知道:
[jass]
call SaveInteger( HT, 1000872, 10, 4567 )
call RemoveSavedInteger( HT, 1000872, 10 )
[/jass]
这样清除一个parentKey书柜里的所有书的内容是不是就真的可以清除掉这个parentKey。
所以保险起见,我们还是直接用FlushChildHashtable来清除整个parentKey(1000872)的数据~
PS:这两个函数的名称就暴露了hashtable其实是嵌套hashtable的事实~
既然我已经让大家熟悉了整个hashtable的所有操作函数。
那么我们现在要开始理解一些有关hashtable的概念了~
1.指针:
这个概念可能理解起来比较困难(对于第一次接触这个概念的人来说的确很难)。
简单来说,就是一个指向目标的数据。但是它又不是目标本身。
类似于购买物品时,悬在英雄头上的小箭头…………
通过这个指针,我们可以访问目标本身。
就拿unit变量来说。
实际上unit(local unit XXX的那个unit)并不是真正的地图中的单位,只不过是从这个unit我们可以访问并且操作真正的单位。
指针就是类似于这样的东西………………
2.hashtable:
先说一下,下文的数组只是表示一种紧密有序排列的一维元素集合,只不过写成数组方便而已。
hashtable是一种根据关键字(Key)计算出存储数据的数组的下标(索引,index)再存储数据的存储结构(table)。
当关键字(Key)的范围远远大于真正Key的数量时,由于不能直接使用Key当数组下标来存储
hashtable会让关键字压缩成一个hashtable预先声明的数组的尺寸允许以内的范围,再找到对应的数组元素(书架或者书柜)进行存储。
比如parentKey是1000872,实际上可能存储在1000872%1000 = 872这个下标的数组元素(书架)上。
至于怎么压缩呢?一般来说都是使用求模(mod)法,把一开始的Key对内部数组的尺寸允许的一个数进行求模,得到的结果就是对应的数组元素了(书架或者书柜)。
这样缩小了需要处理的下标范围。
这样的用来缩小下标范围的函数一律统称为散列函数(Hash)
其实有各种各样的Hash(),但是这里我们只介绍这种通过对Key求模压缩下标的Hash()。
而不幸的是,这样压缩的索引,虽然本身是独一无二的,但是因为对Key进行了压缩,所以:两个关键字(Key)可能会映射到同一个数组元素上去
这种情况我们称之为发生了碰撞,也就是两个关键字冲突了。
幸运的是,我们还有很有效的方法解决这种情况。
由于War3使用的解决方法是链接法(应该是),所以我们也只讨论这种情况,其余的请大家自己阅读《数据结构》。
这种方法让hashtable的数组元素都是一个链表或者为空。
先补充链表的概念:
一个线性有序非紧密排列的节点(node,包括数据域和指针域)的集合。
每个节点至少有一个统一方向的指针指向后一个节点。
这样串联起来的节点,从头到尾称作一个链表。
当一个Key进入hashtable的时候,就在对应的数组元素(链表)里添加一个关键字为Key的节点。
每一次读取和加入Key,都会从这个链表中找到关键字为Key的节点,在单独对这个节点的数据进行操作。
因为Key不相同,所以结点互相就不会冲突,而且还保留了高效的特点,最重要的这样是一个动态的存储结构。没有Key数量的上限。
当然,这样的话,因为Key的数量的增多,每个链表的节点就增多,那必然在链表中搜索(search)指定节点的平均效率就会降低~整个hashtable的平均效率也会下降。所以我们需要对hashtable进行清理工作~
总的来说,这种hashtable的查找效率是极端高效的~
当然和array比起来还是有比较大的差距…………
比Gamecache那种鬼东西好的多。
在hashtable中,除了integer,real,boolean,string是直接把“本体”存储在hashtable中以外,剩下的handle都是只存储一个指向这个handle的指针。
实在理解不能的话……你可以把这个hashtable想成一个二维数组,也就是矩阵…………
parentKey为X坐标,childKey为Y坐标………………
每个格子有五个数据,分别是integer,real,boolean,string,handle…………
实际上,hashtable实际上可能只有100000个横向的书架的位置。
当新存储一个未使用过的书架(parentKey),Hash(Key)得到的索引的书架,其实是在那个地方建造一个新的书架。
如果有前面的旧的书架(即发生碰撞),就直接建造在就书架的上面。书柜(childKey)也是一样。
SaveXXXX的时候,会先检测parentKey对应的书架存不存在,如果不存在则创建新书架,然后检测childKey对应的书柜存不存在,如果不存在就创建新书柜。
再存储数据。
所以对不用的书柜(childKey)/书架(parentKey)进行销毁工作也是十分有必要的。不仅影响hashtable近乎所有函数的效率,而且占空间也是很让人讨厌的一件事情。
概念都理解完了没有~如果没有理解也不着急~我们开始讲它的实际意义~及其应用~
War3的hashtable的实际意义就在于,给指定的integer Key来绑定一些数据。
说得再直白点,就是给handle绑定数据。
比如我想在一个timer身上绑定一个unit source,一个unit target,一个real damage。
在timer到期的时候,令source对target造成damage点伤害~
首先,我们需要对一个timer绑定数据,这可怎么办?
timer可不是integer~
我们没必要把timer转换成integer,其实只需要一个专门对应这个timer的一个独一无二的Id即可~
我们可以通过:
[jass]native GetHandleId takes handle h returns integer[/jass]
来获得一个专门对应handle h的独一无二的值,称作HandleId~
所以我们可以把timer转换成一个Key了~为了清除时的方便,一般来说,HandleId都会作为parentKey。
那另外一个childKey怎么办?
既然我们想存储一个source,一个target,那如果存储在同一个childKey的格子里,就会发生覆盖这样的恶性事件~
所以我们分别存储在childKey分别为1,2,3中。
代码就是下面的:
对了~实际使用hashtable之前,需要把它初始化哦~
[jass]
globals
hashtable HT = null
endglobals
function Init takes nothing returns nothing
call FlushParentHashtable( HT )
set HT = InitHashtable()
endfunction
function TimeDamage_Act takes nothing returns nothing
local integer parentKey = GetHandleId( GetExpiredTimer() )
local unit source = LoadUnitHandle( HT, parentKey, 1 )
local unit target = LoadUnitHandle( HT, parentKey, 2 )
local real damage = LoadReal( HT, parentKey, 3 )
call UnitDamageTargetBJ( source, target, damage, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE )
call FlushChildHashtable( HT, parentKey )
call DestroyTimer( GetExpiredTimer() )
set source = null
set target = null
endfunction
function TimeDamage takes unit source, unit target, real time, real damage returns nothing
local timer t = CreateTimer()
local integer parentKey = GetHandleId( t )
call SaveUnitHandle( HT, parentKey, 1, source )
call SaveUnitHandle( HT, parentKey, 2, target )
call SaveReal( HT, parentKey, 3, damage )
call TimerStart( t, time, false, function TimeDamage_Act )
set t = null
endfunction
[/jass]
看起来1,2,3似乎有些太随便了,而且也不方便我们记忆。
所以我们可以设定常量表明我们的用意:
[jass]
globals
constant integer Hash_Timer_UnitSource_Key = 1
constant integer Hash_Timer_UnitTarget_Key = 2
constant integer Hash_Timer_RealDamage_Key = 3
endglobals
local unit source = LoadUnitHandle( HT, parentKey, Hash_Timer_UnitSource_Key )
local unit target = LoadUnitHandle( HT, parentKey, Hash_Timer_UnitTarget_Key )
local real damage = LoadReal( HT, parentKey, Hash_Timer_RealDamage_Key )
call SaveUnitHandle( HT, parentKey, Hash_Timer_UnitSource_Key, source )
call SaveUnitHandle( HT, parentKey, Hash_Timer_UnitTarget_Key, target )
call SaveReal( HT, parentKey, Hash_Timer_RealDamage_Key, damage )
[/jass]
这样看起来意思简单明了,也知道是这个ChildKey是什么意思。
还有一种比较偷懒的方法,就是利用
[jass]native StringHash takes string s returns integer[/jass]
这个函数。
StringHash和GetHandleId是一样的作用。只不过它是可以把一个字符串转换成一个独一无二的整数Key。
但是和ReturnBug(当然1.24禁止了)的那种转换还不一样。ReturnBug在不同地图中,对于同一字符串转换可能会出现不同结果。
但是StringHash就不一样,在任何地图中都可以转换为同一个Key。
刚才的代码我们可以写成
[jass]
local unit source = LoadUnitHandle( HT, parentKey, StringHash( "source" ) )
local unit target = LoadUnitHandle( HT, parentKey, StringHash( "target" )
local real damage = LoadReal( HT, parentKey, StringHash( "damage" )
call SaveUnitHandle( HT, parentKey, StringHash( "source", source )
call SaveUnitHandle( HT, parentKey, StringHash( "target", target )
call SaveReal( HT, parentKey, StringHash( "damage", damage )
[/jass]
这样可以同样达到不覆盖的效果。
但是相对于StringHash,我更喜欢定义常量的方式。
因为StringHash毕竟是对字符串本身的每个字符进行运算,随着字符串的长度的增长,它的开销应该会增大。这让人比较不爽。
当然,用string意思非常明确,也不需要在地图头文件和触发器里来回换(当然,真要这样的话,直接用空的blizzard.j来声明这些变量其实更好呢~)
新手可以考虑这种东西。
但是因为缺乏统一性,在更换关键字或者更换关键字形式的时候,就比较麻烦。
小药好像在综合区发了一个blizzard.j的东西,里面有专门1.24b的给handle绑定各种数据的函数(Save,Load)。
如果觉得自己写比较麻烦,可以直接用那个(我比较推荐)。
说到应用,就是举实例了~
首先先从比较简单的功能做起吧~
比如我们要做一个持续性的嘲讽技能,我们可以0.1秒对受影响的敌军发布一次smart命令,目标是释放嘲讽技能的单位(这样是不会打断单位的攻击的哦~)
那么我们就要对一个timer绑定一些数据:一个单位组,一个unit target。
timer每0.1秒触发一次,让单位组内的所有单位攻击目标。
[jass]
globals
hashtable HT = null
endglobals
function Init takes nothing returns nothing
call FlushParentHashtable( HT )
set HT = InitHashtable()
endfunction
function TauntUnits_Act takes nothing returns nothing
local integer parentKey = GetHandleId( GetExpiredTimer() )
local unit target = LoadUnitHandle( HT, parentKey, 1 )
local group units = LoadGroupHandle( HT, parentKey, 2 )
local real time = LoadReal( HT, parentKey, 3 )
local real elapsed = LoadReal( HT, parentKey, 4 )
if elapsed < time then
call ForGroup( units, function TauntUnits_Attack )
call SaveReal( HT, parentKey, 4, elapsed + 0.1 )
else
call DestroyGroup( units )
call FlushChildHashtable( HT, parentKey )
call DestroyTimer( GetExpiredTimer() )
endif
set target = null
set units = null
endfunction
function TauntUnits takes unit target, group units, real time returns nothing
local timer t = CreateTimer()
local integer parentKey = GetHandleId( t )
call SaveUnitHandle( HT, parentKey, 1, target )
call SaveGroupHandle( HT, parentKey, 2, units )
call SaveReal( HT, parentKey, 3, time )
call TimerStart( t, 0.1, true, function TauntUnits_Act )
set t = null
endfunction
[/jass]
(为了方便,把麻烦的命令单位组攻击的函数省略不写了)
通过SaveUnitHandle和SaveGroupHandle,我们把unit和group都绑定在了timer上。
SaveReal绑定总时间和已流逝的时间。
在地图初始化的时候call Init(),否则HT为null,无法存储数据!
从中得知:我们只要GetHandleId( h ),把这个Id作为parentKey,那么我们随时随地都可以给handle绑定各种数据。
确定childKey和数据对应的含义,整个hashtable就变成了handle的数据库。
比如我们想给宠物绑定一个“主人”的unit数据:
[jass]call SaveUnitHandle( HT, GetHandleId( pet ), StringHash( "Master" ), master )[/jass]
或者想给单位绑定一个攻击力数据:
[jass]call SaveInteger( HT, GetHandleId( u ), StringHash( "Attack" ), attack )[/jass]
当然,hashtable的功用不仅仅在于只给handle绑定数据
给【物品类型】绑定技能数据:
[jass]call SaveInteger( HT, 'I000', StringHash( "Ability" ), ab )[/jass]
也就是说,不管是什么对象,只要能转换成integer,就可以为其绑定数据。
那么,是第一次小试身手的时候了:
制作一个函数,参数是目标unit target,时间real time。
首先暂时移除target的攻击,在time到期后,返还原来的攻击。
提示:
[jass]
call UnitAddAbility( u, 'Abun' )//移除攻击
call UnitRemoveAbility( u, 'Abun' )//返还攻击
[/jass]
要求:利用hashtable给timer绑定参数。
请大家自己编写代码~~
==========================分割线===========================
==========================分割线===========================
答案:
[jass]
globals
hashtable HT = null
endglobals
function Init takes nothing returns nothing
call FlushParentHashtable( HT )
set HT = InitHashtable()
endfunction
function Attack_Act takes nothing returns nothing
local integer parentKey = GetHandleId( GetExpiredTimer() )
local unit target = LoadUnitHandle( HT, parentKey, 1 )
call UnitRemoveAbility( target, 'Abun' )
call FlushChildHashtable( HT, parentKey )
call DestroyTimer( GetExpiredTimer() )
set target = null
endfunction
function Attack takes unit target, real time returns nothing
local timer t = CreateTimer()
local integer parentKey = GetHandleId( t )
call UnitAddAbility( target, 'Abun' )
call SaveUnitHandle( HT, parentKey, 1, target )
call TimerStart( t, time, false, function Attack_Act )
set t = null
endfunction
[/jass]
是不是很简单呢?
再来一道题:
这道题就比较难了~
要求是:
takes unit source, unit target, real damage
当target受到来自source的伤害(此伤害必须大于20.0)时,source对target额外造成此次伤害40%的伤害
伤害函数:
[jass]call UnitDamageTargetBJ( source, target, damage, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC )[/jass]
此效果从函数被调用开始,target受到5次符合要求的伤害之后,效果消失。
要求:利用hashtable给trigger绑定参数。并且要求对trigger进行排泄。
请大家自己编写代码~~
==========================分割线===========================
==========================分割线===========================
答案:
[jass]
globals
hashtable HT = null
endglobals
function Init takes nothing returns nothing
call FlushParentHashtable( HT )
set HT = InitHashtable()
endfunction
function Damage_Act takes nothing returns boolean
local integer parentKey = GetHandleId( GetTriggeringTrigger() )
local unit source = LoadUnitHandle( HT, parentKey, 1 )
local unit target = LoadUnitHandle( HT, parentKey, 2 )
call FlushChildHashtable( HT, parentKey )
call TriggerRemoveCondition( GetTriggeringTrigger(), LoadTriggerConditionHandle( HT, parentKey, 3 ) )
call DestroyTrigger( GetTriggeringTrigger() )
set source = null
set target = null
return false
endfunction
function Damage takes unit source, unit target returns nothing
local trigger t = CreateTrigger()
local integer parentKey = GetHandleId( t )
local boolexpr cond = Condition( function Damage_Act )
call SaveUnitHandle( HT, parentKey, 1, source )
call SaveUnitHandle( HT, parentKey, 2, target )
call TriggerRegisterUnitEvent( t, target, EVENT_UNIT_DAMAGED )
call SaveTriggerConditionHandle( HT, parentKey, 3, TriggerAddCondition( t, cond ) )
set t = null
set cond = null
endfunction
[/jass]
(不知道为什么我现在很不注意局部变量清空排泄了……貌似是VJstruct全局变量数组使多了的缘故)
不知道大家经过上面的练习,有没有熟练使用hashtable来为一个integer(Key、handle)绑定数据呢?
接下来我要说一些使用hashtable中需要注意的地方:
1.hashtable用全局变量来存储。
2.在地图初始化或者游戏开始0.0秒时(其实是在你使用hashtable之前),必须要InitHashtable()。这可不要忘记了。
3.使用后一定记得FlushChildHashtable,这点影响整个hashtable的效率
4.childKey尽量使用常量或者StringHash,使用1,2,3这样的childKey意义非常不明确,如果忘记了具体含义,想要修改代码的话,就很麻烦了。
5.parentKey尽量使用有代表性或者特殊含义的integer,比如handle的id,而不是1,2,3这样的。
现在就要学习hashtable的高级技巧了~
1.与数组array(vj-struct)混用,大幅提高效率
hashtable的实际存储效率是比不过array的,但是array却不能直接绑定在过大的integer上(数组最大尺寸8192)。
在这里就不累述那些使用array来存储数据的系统了。只是讲一下如何利用hashtable来间接得在array上绑定数据。
我们可以通过hashtable给handle绑定一个integer数据,而这个数据就是这个handle数据索引。
使用这个数据索引就可以在array中找到数据。
但是这个数据索引必须是唯一的,找这样一个数据hashtable就不能帮我们办到了。
我的方法是vj-struct的allocate()和destroy(integer this)。
代码如下:
[jass]
globals
integer Array_Max = 1
integer Array_Top = 0
integer array Array_Next
endglobals
function Array_allocate takes nothing returns integer
local integer this = Array_Top
if this == 0 then
set this = Array_Max
if this >= 8190 then
return 0
endif
set Array_Max = Array_Max + 1
else
set Array_Top = Array_Next[Array_Top]
endif
return this
endfunction
function Array_destroy takes integer this returns nothing
set Array_Next[this] = Array_Top
set Array_Top = this
endfunction
[/jass]
allocate()可以分配给你一个在数组中的独一无二的索引。
destroy(integer this)可以将this索引回收,以便下次利用。
(说下,这两个函数对于每一种要绑定数据integer,比如trigger,effect等,是应该为私有的函数的
也就是说,你给马甲绑定数据,那么所有的这种马甲就应该使用一套allocate()、destroy()以及Top、Max、Next)
利用这两个函数我们就可以通过hashtable作为接口,实际以高效的array为handle绑定数据了。
而且,使用array存储数据,实际上能比hashtable存储更多类型的数据,比如weathereffect,这个hashtable就根本存储不了。
2.尽量不使用StringHash()和整数,利用常量来做childKey,区分数据
大家肯定都觉得StringHash()写起来非常方便,中途也无需声明新的全局变量。但是这并不表示StringHash就一好到底了。
如果你在编写代码时,发现区分两种基础数据的string发生了冲突,那么你为了区分这两种数据不一样但是标识string一样,就需要把所有的两种数据的标识string全部换一边。
这实在是有点太麻烦了。
而如果使用无意义的整数,第一你自己很快就会忘记这些整数存储的数据代表了什么。
第二和StringHash()一样,维护的时候会很麻烦。
所以我建议大家全部使用常量定义childKey。
使用常量,在发生冲突的时候,只需要去globals里改一个数就可以避免冲突。
而且StringHash()需要对string进行运算得出效果,明显不如直接常量传递数据来得快。
效率比用常量低了很多。
3.利用hashtable达到N维数组的效果
hashtable的parentKey和childKey双层integer下标,实际上可以近似看成一个矩阵,也就是2维的数组。
我曾经发过一个帖子,给出了利用运算下标的方式将array(1维数组)转换为N维数组的算法。很麻烦。
但是有了hashtable我们就很好弄了。
注意这个函数:
[jass]
native SaveHashtableHandle takes hashtable table, integer parentKey, integer childKey, hashtable whichHashtable returns boolean
[/jass]
利用这个函数,我们可以嵌套hashtable来达到N维数组的效果。
我这里只举一个3维数组的例子:
[jass]
globals
hashtable HT = null
endglobals
function Init takes nothing returns nothing
call FlushParentHashtable( HT )
set HT = InitHashtable()
endfunction
function CreateArray3 takes integer index, integer size1, integer size2, integer size3 returns nothing
local integer i = 0
local integer j = 0
loop
exitwhen i >= size1
call SaveHashtableHandle( HT, index, i, InitHashtable() )
set i = i + 1
endloop
endfunction
function SaveArray3 takes integer index, integer index1, integer index2, integer index3, integer value returns nothing
local hashtable child = LoadHashtableHandle( HT, index, index1 )
call SaveInteger( child, index2, index3, value )
set child = null
endfunction
function LoadArray3 takes integer index, integer index1, integer index2, integer index3 returns integer
local hashtable child = LoadHashtableHandle( HT, index, index1 )
local integer value = LoadInteger( child, index2, index3 )
set child = null
return value
endfunction
function DestroyArray3 takes integer index, integer size1, integer size2, integer size3 returns nothing
local integer i = 0
local integer j = 0
loop
exitwhen i >= size1
call FlushParentHashtable( LoadHashtableHandle( HT, index, i ) )
set i = i + 1
endloop
call FlushChildHashtable( HT, index )
endfunction
[/jass]
说实话,2维数组用的人就很少了…………
4.利用hashtable来达到I2H的效果
嗯……实际上有了hashtable来存储数据,是应该不需要I2H的函数的。不太推荐。
不过代码如下(相对于ReturnBug和UnionBug,这东西效率简直低下到一定程度):
[jass]
function RegisterUnit takes unit u returns nothing
call SaveUnitHandle( HT, GetHandleId( u ), Const_Unit, u )
endfunction
function I2U takes integer id returns unit
return LoadUnitHandle( HT, id, Const_Unit )
endfunction
[/jass]
使用前,必须用RegisterUnit注册单位,才能I2U,否则得到null。
以下内容为疯人&衰人友情提供~
5.利用hashtable在函数中传递数组
我们可以利用hashtable来在jass中干一件以前很麻烦的事情~
就是在函数中传递数组array~
在parentKey中分配一个独一无二的索引
在特定的childKey中存储这个数组的尺寸size
其他的childKey中存储数据。
传递数组的时候,只要传递独一无二的索引即可。
然后再在对应的parentKey下的childKey里读取数据。
代码如下(childKey=0存储size):
[jass]
globals
hashtable HT = null
constant integer HT_Max_Key = -1
constant integer HT_Top_Key = -2
constant integer HT_Next_Key = -1
constant integer HT_Size_Key = -2
endglobals
function Init takes nothing returns nothing
call FlushParentHashtable( HT )
set HT = InitHashtable()
call SaveInteger( HT, HT_Max_Key, 0, 1 )
endfunction
function CreateArray takes integer size returns integer
local integer this = LoadInteger( HT, HT_Top_Key, 0 )
if this == 0 then
set this = LoadInteger( HT, HT_Max_Key, 0 )
call SaveInteger( HT, HT_Max_Key, 0, this + 1 )
else
call SaveInteger( HT, HT_Top_Key, 0, LoadInteger( HT, this, HT_Next_Key ) )
endif
call SaveInteger( HT, this, HT_Size_Key, size )
return this
endfunction
function GetArraySize takes integer this returns integer
return LoadInteger( HT, this, HT_Size_Key )
endfunction
function DestroyArray takes integer this returns nothing
call FlushChildHashtable( HT, this )
call SaveInteger( HT, this, HT_Next_Key, LoadInteger( HT, HT_Top_Key, 0 ) )
call SaveInteger( HT, HT_Top_Key, 0, this )
endfunction
function SetArrayInt takes integer this, integer index, integer value returns nothing
call SaveInteger( HT, this, index, value )
endfunction
function GetArrayInt takes integer this, integer index returns integer
return LoadInteger( HT, this, index )
endfunction
[/jass]
其实用hashtable达到的数组,不管是数组的个数,还是数组的尺寸理论上都是无限的,所以弄个size反而画蛇添足……
不过考虑到确实很有用,所以加上了。
你可以和数组版的allocate()和destroy(integer this)对比一下~
6.替换hashtable来达到替换所有操作对象的目的
这个…………
这玩意原理很简单,通过更换全局变量hashtable HT的值,来让更改所有函数的操作对象。
虽然有些非常规,但是就某些封装性来说,还是很有效的。
这东西是疯人用来写完全随机地图用的……估计推广很难。
暂时以上内容就是整个hashtable教程的内容了,因为我本身并没有太熟练地使用hashtable,所以仅仅是这样的一个教程~
感谢疯人的大力支持~
相信hashtable可能还有一些要注意的地方或者很棒的用法。我不能在此全部发掘出来~这个只是用来让大家过渡的东西~
应该就是这样。
话说这次我应该写的还可以了吧……疯人哥哥都说还可以了…………
中文字数6600+~ |
|