Name | Type | is_array | initial_value |
AA_A_Group | group | No | |
AA_Cstr_Group | group | No | |
AA_E_HP | real | No | |
AA_HPcount | real | No | |
AA_Loc | location | Yes | |
AA_Percent | real | Yes | |
AA_PreLoop | integervar | No | |
AAngle | real | Yes | |
AbilityID | integer | No | |
AbilityOfTalent | abilcode | Yes | |
ACaster | unit | Yes | |
ACasterPoint | location | Yes | |
ACount | integer | No | |
AD_Attacker | unit | No | |
AD_AttackTriggers | trigger | Yes | |
AD_Defender | unit | No | |
ADamage | real | Yes | |
AddButtonDestructible | destructable | Yes | |
AddFoodFromLevel | integer | No | |
AddSkillDestructable | destructable | Yes | |
ADistance | real | Yes | |
AHas | boolean | Yes | |
AIHero | unitcode | Yes | |
AIndex | integer | No | |
AInteger | integervar | No | |
ALastRecycled | integer | No | |
Already_damaged | group | Yes | |
AM_BAR_COLOUR | string | Yes | |
AM_BAR_COUNT | integer | No | |
AM_BAR_STRING | string | No | |
AM_BARS_COLOURED | integer | Yes | |
AM_CHOSEN_HERO | unit | Yes | |
AM_CREEP_DENIES | integer | Yes | |
AM_CREEP_KILLS | integer | Yes | |
AM_DOMINATION_TEXT | string | No | |
AM_GAME_TIMER | boolean | No | |
AM_HERO_DEATHS | integer | Yes | |
AM_HERO_ICON_PATH | string | Yes | |
AM_HERO_KILLS | integer | Yes | |
AM_HERO_TYPE | unitcode | Yes | |
AM_HERO_TYPE_COUNT | integer | No | |
AM_LB | leaderboard | Yes | |
AM_MB | multiboard | Yes | |
AM_MULTIPLIER | real | Yes | |
AM_NAME | string | No | |
AM_PG | force | Yes | |
AM_PG_ALL | force | No | |
AM_PLAYER_HERO_ICON_PATH | integer | Yes | |
AM_PLAYER_VALUE | integer | Yes | |
AM_SHOW_OPPONENT_VALUE | boolean | No | |
AM_TEAM_ONE_NAME | string | No | |
AM_TEAM_TWO_NAME | string | No | |
AM_TIME_MINUTES | integer | No | |
AM_TIME_SECONDS | integer | No | |
AM_VALUE_ICON | string | No | |
AM_VALUE_NAME | string | No | |
AMax | integer | No | |
AMaxDistance | real | Yes | |
AMove | real | Yes | |
Angle | real | Yes | |
AoELeapers | group | No | |
ArcaneMage | location | No | |
ARecycledList | integer | Yes | |
Arrived | boolean | Yes | |
Arrow | unit | Yes | |
Arrow_damage | real | Yes | |
Arrow_group | group | No | |
Arrow_offset | location | No | |
Arrow_pos | location | No | |
Arrow_value | integer | No | |
ATarget | unit | Yes | |
Attackers | group | No | |
bag_integer | integervar | No | |
bag_items_stored | integer | No | |
bag_not_compatible | itemcode | Yes | |
bag_number_of_not_compatible | integer | No | |
bag_slot | integer | No | |
bag_store_point | location | No | |
bag_table | hashtable | No | |
bag_tempPoint | location | No | |
bf | integer | No | |
BF_DefautInterval | integer | No | |
BF_Hero | unit | Yes | |
BF_Max | integer | No | |
BF_SpecEffect1 | effect | Yes | |
BF_SpecEffect2 | effect | Yes | |
BF_Time | integer | Yes | |
Black_Dragon | unit | No | |
BossNerubian | location | No | |
BossWendigo | location | No | |
CanNOTBeLearnt | hashtable | No | |
Cap | unit | Yes | |
Capped | integer | Yes | |
Caster | unit | Yes | |
Caster_offset | location | No | |
Caster_pos | location | No | |
CDS_Accept | boolean | No | |
CDS_AliiesCanFight | boolean | No | |
CDS_AllyCheck | boolean | No | |
CDS_Dummy | unitcode | No | |
CDS_Index | integer | Yes | |
CDS_Invited | boolean | No | |
CDS_LockPoint | unit | No | |
CDS_On | boolean | No | |
CDS_Player | integer | Yes | |
CDS_Player_Number | integer | No | |
CDS_Point | location | Yes | |
CDS_Point2 | location | Yes | |
CDS_Positions | location | Yes | |
CDS_Region | rect | No | |
CDS_Seconds | real | No | |
CDS_Timer | timer | No | |
CDS_Unit | unit | Yes | |
CDS_Unit_Life | real | Yes | |
CDS_Unit_Mana | real | Yes | |
CDS_UnitGroup | group | Yes | |
ClassTalentIcons | destructablecode | Yes | |
ClassTalents | hashtable | No | |
Code | string | No | |
Coded_1 | string | No | |
Coded_2 | string | No | |
Codeshift | integer | No | |
Colour | string | Yes | |
Count | integer | No | |
CurrentSkills | integer | Yes | |
Damage | real | Yes | |
Damage_group | group | No | |
DamageBlockingAbility | abilcode | No | |
DamageEvent | real | No | |
DamageEventAmount | real | No | |
DamageEventExplodesUnit | boolean | No | |
DamageEventOverride | boolean | No | |
DamageEventPrevAmt | real | No | |
DamageEventSource | unit | No | |
DamageEventsWasted | integer | No | |
DamageEventTarget | unit | No | |
DamageEventTrigger | trigger | No | |
DamageEventType | integer | No | |
DamageModifierEvent | real | No | |
DamageTypeDOT | integer | No | |
DamageTypeRanged | integer | No | |
DamageTypeSpell | integer | No | |
Destroy_Trees | boolean | Yes | |
Display | integer | Yes | |
Distance | real | Yes | |
DmgEvLife | real | No | |
DmgEvN | integer | No | |
DmgEvStack | unit | Yes | |
DmgEvTimer | timer | No | |
DmgTypPrev | integer | No | |
DP_Boolean | boolean | Yes | |
DP_Caster | unit | Yes | |
DP_Damage | real | Yes | |
DP_Dummy | unit | Yes | |
DP_Dummy2 | unit | Yes | |
DP_Duration | real | Yes | |
DP_Expired | real | Yes | |
DP_Group | group | Yes | |
DP_Index1 | integer | No | |
DP_Index2 | integer | No | |
DP_IndexLoop | integervar | No | |
DP_loop | integervar | No | |
DP_Player | player | Yes | |
DP_TargetPoint | location | Yes | |
DP_TempPoint | location | No | |
E0_AoE_Base | real | No | |
E0_AoE_Increment | real | No | |
E0_AttackType | attacktype | No | |
E0_DamageAmount_Base | real | No | |
E0_DamageAmount_Increment | real | No | |
E0_DamageDelay | real | No | |
E0_DamageType | damagetype | No | |
E0_DestroyTree | boolean | No | |
E0_Dummy_Size | real | No | |
E0_Duration_Base | real | No | |
E0_Duration_Increment | real | No | |
E0_LightningType | lightningtype | No | |
E0_MaxHeight | real | No | |
E0_MaxTargets_Base | integer | No | |
E0_MaxTargets_Increment | integer | No | |
E0_MinHeight | real | No | |
E0_S_Chest_Alternative | string | No | |
E0_S_Chest_Enemy | string | No | |
E0_S_Chest_Primary | string | No | |
E0_S_DamageUnit | string | No | |
E0_S_Impact | string | No | |
E0_S_Origin | string | No | |
E0_Speed_Enemy | real | No | |
E0_Travel_Time | real | No | |
E0_TreeChecker | unit | No | |
E1_Boom | boolean | Yes | |
E1_Caster | unit | Yes | |
E1_Current | integer | No | |
E1_Damage | real | Yes | |
E1_Delay | real | Yes | |
E1_Dummy | unit | Yes | |
E1_Duration | real | Yes | |
E1_Index_Itself | integer | Yes | |
E1_Index_Max | integer | No | |
E1_Index_Size | integer | No | |
E1_Level | integer | Yes | |
E1_Loop | integervar | No | |
E1_MaxTarget | integer | Yes | |
E1_SFX_Chest | effect | Yes | |
E1_SFX_Origin | effect | Yes | |
E1_Speed | real | Yes | |
E1_Splash | boolean | Yes | |
E1_ZDuration | real | Yes | |
E1_ZInterval | real | Yes | |
E1_ZMaxHeight | real | Yes | |
E2_Current | integer | No | |
E2_Delay | real | Yes | |
E2_Duration | real | Yes | |
E2_Enemy | unit | Yes | |
E2_Index_Itself | integer | Yes | |
E2_Index_Max | integer | No | |
E2_Index_Size | integer | No | |
E2_Lightning | lightning | Yes | |
E2_Loop | integervar | No | |
E2_SFX_Enemy | effect | Yes | |
E2_Show | boolean | Yes | |
E2_Source | unit | Yes | |
E2_x1 | real | No | |
E2_x2 | real | No | |
E2_y1 | real | No | |
E2_y2 | real | No | |
E3_Angle | real | No | |
EA_Caster | unit | No | |
EA_Caster_Loc | location | No | |
EA_Dummy | unit | No | |
EA_Dummy_Loc | location | No | |
EA_Face_Arrow | real | No | |
EA_Group | group | No | |
EA_Offset | location | No | |
EA_Picked | unit | No | |
EA_Picked_Loc | location | No | |
EA_Point | location | No | |
EA_Timer | real | No | |
Effect | boolean | Yes | |
ElderWendigo | location | No | |
ElderWendigo_Copy | location | No | |
ElderWendigo_Copy_2 | location | No | |
ElderWendigo_Copy_3 | location | No | |
ElderWendigo_Copy_4 | location | No | |
ElderWendigo_Copy_5 | location | No | |
EnergyShield_DamagedAbsorbed | integer | Yes | |
EnergyShield_ExplodePoint | location | No | |
EnergyShield_Hashtable | hashtable | No | |
EnergyShield_UnitGroup | group | No | |
Escape_Artist | unit | No | |
ExtraStreak_Chcker | boolean | Yes | |
ExtraStreak_Kills | integer | Yes | |
ExtraStreak_Sounds | sound | Yes | |
ExtraStreak_Strings | string | Yes | |
ExtraStreak_Timer | integer | Yes | |
ExtraStreakInt | integervar | No | |
FelOrcWarlock | location | No | |
FelOrcWarlock_Guard | location | No | |
FelOrcWarlock_Guard_Copy | location | No | |
FelOrcWarrior | location | No | |
FelOrcWarrior_Guard | location | No | |
FelOrcWarrior_Guard_Copy | location | No | |
fi | integer | No | |
FI_Angle | real | Yes | |
FI_AnimSpeed | real | Yes | |
FI_DefaultHeight | real | No | |
FI_EffectA | string | No | |
FI_EffectB | string | No | |
FI_FallHeight | real | No | |
FI_Height | real | Yes | |
FI_Hero | unit | Yes | |
FI_Max | integer | No | |
FI_Sound | string | No | |
FI_Vp | real | Yes | |
FI_Vz | real | Yes | |
FI_z | real | Yes | |
FireMage | location | No | |
FirstMidHuman | location | No | |
FirstMidOrc | location | No | |
FlagHumanSpawn1 | location | No | |
FlagHumanSpawn2 | location | No | |
FlagOrcSpawn1 | location | No | |
FlagOrcSpawn2 | location | No | |
Float_Text | boolean | Yes | |
FrostMage | location | No | |
FT | texttag | Yes | |
G | integervar | No | |
g | real | No | |
G_Angle | real | Yes | |
G_AoeFinal | real | Yes | |
G_AoeSelect | real | Yes | |
G_Caster | unit | Yes | |
G_Damage | real | Yes | |
G_Distance | real | Yes | |
G_Dummy | unit | Yes | |
G_ExGroup | group | Yes | |
G_Fly | integer | Yes | |
G_Group | group | Yes | |
G_Height | real | Yes | |
G_Level | integer | Yes | |
G_Off | boolean | Yes | |
G_Point | location | Yes | |
G_Skip | integer | No | |
G_Speed | real | Yes | |
G_Times | integer | No | |
GDD__Integers | integer | Yes | |
GDD__LeftMapGroup | group | No | |
GDD__TriggerArray | trigger | Yes | |
GDD__UnitArray | unit | Yes | |
GDD_Damage | real | No | |
GDD_DamagedUnit | unit | No | |
GDD_DamageSource | unit | No | |
GDD_Event | real | No | |
GenericBoolean | boolean | No | |
GenericString | string | No | |
GivenQitem | item | Yes | |
GreenBuffDealer | unit | No | |
GreenBuffGiver | location | No | |
GreenBuffItem | location | No | |
GreenBuffItems | item | No | |
GreenBuffKiller | unit | No | |
group | group | No | |
Grum | unit | No | |
GuAbilityDummy | abilcode | No | |
GuAbilityMain | abilcode | No | |
Hashtable | hashtable | No | |
Hero | unitcode | No | |
Heroes | unit | Yes | |
HPet | location | No | |
Htree1 | location | No | |
Htree2 | location | No | |
Human_Spawn | location | No | |
HumanPlayers | group | No | |
i | integer | No | |
Int | integer | No | |
integer | integer | No | |
intreger | integer | No | |
IsOwningQ | boolean | No | |
item | itemcode | Yes | |
item_value_is | item | Yes | ItemNull |
kb | integer | No | |
KB_Angle | real | Yes | |
KB_Dist | real | Yes | |
KB_Max | integer | No | |
KB_MaxDist | real | Yes | |
KB_Speed | real | Yes | |
KB_Unit | unit | Yes | |
Knockback | real | Yes | |
Landazar | location | No | |
Landazar_Guard | location | No | |
Landazar_Guard_Copy | location | No | |
LastGrumPosition | location | No | |
LastTriggerPlayer | player | No | |
LeaderHydro | location | No | |
LeaderHydro_Guard | location | No | |
LeaderHydro_Guard_Copy | location | No | |
LeavingUnit | unit | No | |
Lightning | lightning | Yes | |
Lightning_On | boolean | Yes | |
Loc1 | location | No | |
Loc2 | location | No | |
Loc3 | location | No | |
localplayer | player | No | |
LS_Caster | unit | Yes | |
LS_DamageHeal | real | Yes | |
LS_Duration | integer | Yes | |
LS_EffectCaster | string | Yes | |
LS_EffectTarget | string | Yes | |
LS_integer | integer | Yes | |
LS_Target | unit | Yes | |
Max_Index | integer | No | |
MaxAbilitiesPerHero | integer | No | |
MoveRegion | location | No | |
MoveRegion2 | location | No | |
MUI_1 | integer | No | |
MUI_2 | integer | Yes | |
MUI_3 | integervar | No | |
MUI_count | integer | No | |
NumberOfCaps | integer | No | |
OPet | location | No | |
Orc_Spawn | location | No | |
OrcPlayers | group | No | |
Otree1 | location | No | |
Otree2 | location | No | |
PH_Cast_Point | location | No | |
PH_Caster | unit | Yes | |
PH_Counter | integer | Yes | |
PH_Damage | integer | No | |
PH_Distance | integer | No | |
PH_Dummy | unit | Yes | |
PH_Dummy_Point | location | Yes | |
PH_Effect | unit | No | |
PH_Hook_Angle | real | Yes | |
PH_Hook_Point | location | Yes | |
PH_Hooked_Is | boolean | Yes | |
PH_Hooked_Unit | unit | Yes | |
PH_Index | integer | Yes | |
PH_Near_Point | location | No | |
PH_Table | hashtable | No | |
PH_Target_Point | location | No | |
Player1 | unit | No | |
Player2 | unit | No | |
Player3 | unit | No | |
Player4 | unit | No | |
Player5 | unit | No | |
Player6 | unit | No | |
PLAYER_Hero | unit | Yes | |
Player_kills | integer | Yes | |
PlayerClass | string | Yes | |
PlayerColors | string | Yes | |
Point | location | No | |
point | location | No | |
Point1 | location | No | |
point1 | location | No | |
point2 | location | No | |
Point2 | location | No | |
Point3 | location | No | |
Points | location | Yes | |
Points_1 | string | No | |
Points_2 | string | No | |
Points_String | string | No | |
Progress | real | Yes | |
PWAngle | real | Yes | |
PWCaster | unit | Yes | |
PWCasterPoint | location | Yes | |
PWCount | integer | No | |
PWDamage | real | Yes | |
PWDistance | real | Yes | |
PWDMGGroup | group | Yes | |
PWHas | boolean | Yes | |
PWIndex | integer | No | |
PWInteger | integervar | No | |
PWLastRecycled | integer | No | |
PWMax | integer | No | |
PWMaxDistance | real | Yes | |
PWPhoenix | unit | Yes | |
PWRecycledList | integer | Yes | |
PWTargetPoint | location | Yes | |
QDB | hashtable | No | |
QuestGenBaseCount | integer | No | |
QuestGenBaseNames | string | Yes | |
QuestGenCompleteLine | string | Yes | |
QuestGenCompleteLineCount | integer | No | |
QuestGenCurrentUnit | unit | No | |
QuestGenGreetingCount | integer | No | |
QuestGenGreetingLines | string | Yes | |
QuestGenHotspotCount | integer | Yes | |
QuestGenHotspots | location | Yes | |
QuestGenLargeHotspots | location | Yes | |
QuestGenMonsterAction | string | Yes | |
QuestGenMonsterActionCount | integer | No | |
QuestGenMonsterBaseBuildingA | unitcode | Yes | |
QuestGenMonsterBaseBuildingB | unitcode | Yes | |
QuestGenMonsterBaseUnits | unitcode | Yes | |
QuestGenMonsterCount | integer | No | |
QuestGenMonsterCrime | string | Yes | |
QuestGenMonsterCrimeCount | integer | No | |
QuestGenMonsterLevels | integer | Yes | |
QuestGenMonsterNames | string | Yes | |
QuestGenMonsters | unitcode | Yes | |
QuestGenPlayerCurrentQuest | string | Yes | |
QuestGenPlayerNumber | integer | No | |
QuestGenQuestCooldown | integer | Yes | |
QuestGenQuestGivers | group | No | |
QuestGenQuestText | string | No | |
QuestGenRetrievalItemCount | integer | No | |
QuestGenRetrievalItems | itemcode | Yes | |
QuestGenRetrievalLineCount | integer | No | |
QuestGenRetrievalLines | string | Yes | |
QuestGenRewardCount | integer | No | |
QuestGenRewardItemPercent | real | Yes | |
QuestGenRewardItems | itemcode | Yes | |
QuestGenTargetDistance | real | No | |
QuestGenXpFactor | real | No | |
Randomizer_1 | integer | No | |
Randomizer_2 | integer | No | |
RandomNameLetter | string | No | |
RandomNameLetterPosition | integer | No | |
Real | real | Yes | |
real | real | No | |
real2 | real | No | |
Rects | rect | Yes | |
RedHero | unit | No | |
RemoveSkillDestructable | destructable | Yes | |
RightSideModifier | boolean | No | |
RItems | location | No | |
RiverItems | itemcode | Yes | |
RiverItemsCount | integer | No | |
RiverItemsRegions | location | Yes | |
RiverItemsRegionsCount | integer | No | |
RiverItemsTaken | boolean | Yes | |
s | string | No | |
Save | integer | Yes | |
SaveCount | integer | No | |
SaveLoad_Abilities | abilcode | Yes | |
SaveLoad_Abilities_LastIndex | integer | No | |
SaveLoad_Alphabet | string | No | |
SaveLoad_CaseSensitive | boolean | No | |
SaveLoad_Compress | integer | Yes | |
SaveLoad_Heroes | unitcode | Yes | |
SaveLoad_Heroes_LastIndex | integer | No | |
SaveLoad_Initialized | boolean | No | |
SaveLoad_Items | itemcode | Yes | |
SaveLoad_Items_LastIndex | integer | No | |
SaveLoad_Uncompress | integer | Yes | |
SaveLoad_UsePlayername | boolean | No | |
SecondMidHuman | location | No | |
SecondMidOrc | location | No | |
Seer | location | No | |
Shadow_power | boolean | No | |
Shadow_power_int | integer | No | |
shadow_power_integer | integer | Yes | |
Shake | real | No | |
Shaman | location | No | |
Shaman_Copy | location | No | |
Shaman_Copy_2 | location | No | |
Shaman_Copy_3 | location | No | |
Skill_BattleFurrySpeed | abilcode | No | |
Skill_BattleFury | abilcode | No | |
Skill_Bonebreaker | abilcode | No | |
Skill_FeralImpact | abilcode | No | |
Skill_Tornado | abilcode | No | |
SkillBonuses | hashtable | No | |
SkillButton | unit | Yes | |
SmokeScreen_Caster | unit | No | |
SmokeScreen_TargetPoint | location | No | |
Speed | real | Yes | |
SpiderLord | location | No | |
SpiderLord_Copy | location | No | |
SpiderLord_Copy_2 | location | No | |
SpiderLord_Copy_3 | location | No | |
SpiderLord_Copy_4 | location | No | |
SpiderLord_Copy_5 | location | No | |
SpiderLord_Copy_6 | location | No | |
SpiderShopGuard | location | No | |
SpiderWarrior | location | No | |
SpiderWarrior_Copy | location | No | |
State | integer | No | |
Streak_Kills | integer | Yes | |
Streak_Sounds | sound | Yes | |
Streak_Strings | string | Yes | |
SubtractButtonDestructible | destructable | Yes | |
TalentAbilitiesClass | hashtable | No | |
TalentAdded | hashtable | No | |
TalentButtonsAdd | unit | Yes | |
TalentButtonsSubtract | unit | Yes | |
TalentClasses | string | Yes | |
TalentClassInt | integer | Yes | |
TalentColumns | integer | No | |
TalentCondition | boolean | No | |
TalentDependanciesBackwards | hashtable | No | |
TalentFloatingTexts | texttag | Yes | |
TalentFontSize | real | No | |
TalentIcons | hashtable | No | |
TalentInfoTexts | texttag | Yes | |
TalentlDependancies | hashtable | No | |
TalentlDescriptions | hashtable | No | |
TalentLevelRequirement | hashtable | No | |
TalentLevels | hashtable | No | |
TalentlExists | hashtable | No | |
TalentLightningEffects | integer | Yes | |
TalentLightningPoints | location | Yes | |
TalentLightnings | hashtable | No | |
TalentlName | hashtable | No | |
TalentMaxClasses | integer | No | |
TalentMaxLevels | hashtable | No | |
TalentPoints | integer | Yes | |
TalentPointsX | location | Yes | |
TalentPosHashtables | hashtable | Yes | |
TalentRows | integer | No | |
TalentSecInt | integer | No | |
TalentTempDestructible | destructable | No | |
TalentTempGroup | force | No | |
TalentTempInt | integer | No | |
TalentTempPlayer | player | No | |
TalentTempPoint | location | No | |
TalentTempReal | real | No | |
TalentTempString | string | No | |
TalentTotalIcons | integer | No | |
TalentTrees | integer | No | |
TalentTreeView | force | No | |
TalentVis | fogmodifier | Yes | |
Target | unit | Yes | |
Target_point | location | Yes | |
TargetRect | rect | No | |
TargetUnit | unit | No | |
Team1 | force | No | Force00 |
Team2 | force | No | Force11 |
Temp | integer | No | |
Temp_Group | group | No | |
Temp_Integer | integer | No | |
Temp_Point | location | No | |
TEMP_point | location | Yes | |
Temp_Point2 | location | No | |
Temp_Real | real | No | |
TempAbility | abilcode | No | |
tempGroup | group | No | |
TempGroup | group | No | |
TempInt | integer | No | |
TempInteger | integer | No | |
TempItem | item | No | |
TempItemType | itemcode | No | |
TempLoc | location | No | |
TempLoc2 | location | No | |
TempPoint | location | No | |
TempSFX | effect | No | |
TempUnit | unit | No | |
TempUnitType | unitcode | No | |
Text_Duration | real | No | |
TextSize_dmg | real | No | |
TextSize_spells | real | No | |
ThirdMidHuman | location | No | |
Time | real | Yes | |
tmpCasterZ | real | No | |
tmpDiffHeight | real | No | |
tmpDistance | real | No | |
tmpHeight | real | No | |
tmpSound | sound | No | |
tmpTargetZ | real | No | |
tmpTime | real | No | |
to | integervar | No | |
TO_Duration | real | Yes | |
TO_Hero | unit | Yes | |
TO_Max | integer | No | |
Total_Damage | real | Yes | |
TrackableTempInt | integer | No | |
TrackTable | hashtable | No | |
UDex | integer | No | |
UDexGen | integer | No | |
UDexNext | integer | Yes | |
UDexPrev | integer | Yes | |
UDexRecycle | integer | No | |
UDexUnits | unit | Yes | |
UDexWasted | integer | No | |
UnitDamageRegistered | boolean | Yes | |
UnitGroup | group | No | |
UnitIndexerEnabled | boolean | No | |
UnitIndexEvent | real | No | |
UnitIndexLock | integer | Yes | |
Validate | boolean | No | |
Waves | timer | No | |
Webspinner | location | No | |
Webspinner_Copy | location | No | |
Webspinner_Copy_2 | location | No | |
Webspinner_Copy_3 | location | No | |
WendigoChild | location | No | |
WendigoChild_Copy | location | No | |
WendigoChild_Copy_2 | location | No | |
WendigoChild_Copy_3 | location | No | |
WendigoChild_Copy_4 | location | No | |
WendigoChild_Copy_5 | location | No | |
WendigoChild_Copy_6 | location | No | |
WendigoShopGuard | location | No | |
WendigoTeenager | location | No | |
WendigoTeenager_Copy | location | No | |
X | integer | No | |
XPos | real | No | |
XX | integervar | No | |
Y | integervar | No | |
YPos | real | No | |
Z | integervar | No | |
Zigg | location | No |
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Memory_leak_destroy_Actions takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_Memory_leak_destroy takes nothing returns nothing
set gg_trg_Memory_leak_destroy = CreateTrigger( )
call TriggerAddAction( gg_trg_Memory_leak_destroy, function Trig_Memory_leak_destroy_Actions )
endfunction
library MemoryLeakHelper initializer Init requires Table
// ==================================
// Give credits to Mr.Malte when used!
//===========================================================================
// Information:
//==============
//
// There are things called 'memory Leaks'. When you create a group or use a location
// without destroying it, you will cause lag that stays in the whole game.
// If you implement this library into your map it will automatically fix a big part
// of those memory leaks, so reduce the lag extremely.
// This should mainfully be used by GUI-users because the system is designed for them.
//
// Of course no system can work totally automatically.
// But there is only one thing you have to do in order to prevent bugs:
// If you make groups or locations that have to be filled for more than CLEAN_UP_INTERVAL seconds
// you have to save them with the code:
//
// call ProtectHandle(XXX)
//
// Where XXX is filled with your variable. Otherwise that variable gets destroyed
// automatically. You can also fill in XXX with
//
// GetLastCaughtHandle()
//
// But if you save the things in a variable, I'd recommend to directly put the
// variable into the brackets. Note: GUI variables have the prefix 'udg_' in JASS.
//
// This gives the 'Do Nothing' function in GUI a sense!
// When you call DoNothing, all data that were caught by this system
// will be destroyed in CLEAN_UP_INTERVAL seconds, ignoring how big
// the number of caught handles is. This will not work, if the system is
// already cleaning up.
//===========================================================================
// Implementation:
//===============
//
// The easiest thing is to directly implement this thing into your map, when you start making
// it, so you don't have to look over your globals and use ProtectHandle on them.
// These are the steps you have to do to clear the memory leaks:
//
// 1. Download a tool called 'JassNewGen', and unpack it somewhere. You need that
// edit to use this tool. The 'JassNewGen' is used very commonly and offers other
// nice features. You can find it at:
// http://www.wc3c.net/showthread.php?t=90999
// 2. Make a new trigger, and convert it to custom text. Insert everything
// the library contains into that trigger.
//
// 3. Download a system called 'Table' from this link:
// http://www.wc3c.net/showthread.php?t=101246
// Do the same installation stuff for 'Table' as for this system.
//
// 4. Save your map and enjoy :-)
//
// Note: Instead of doing 2 and 3 you can also copy and paste the folder 'MemoryLeakHelper'
// from the example map.
//===========================================================================
// How bad are memory leaks?
//==========================
// If you don't remove memory leaks, they suck memory:
//
// Location: 0.361 kb
// Group: 0.62 kb + 0.040 kb for each unit in the group.
// Effect: 11.631 kb
//
// Both, locations and groups are used very frequently. So when you don't fix those memory leaks,
// you will experience lag.
// When you want to see, how useful this is for your map, implement it
// and write 'call DisplayLeaks()' into a custom script that is fired when
// they game ends.
//===========================================================================
// Changelog:
//===========
// v1.00 --- first version
// v1.01 --- able to detect special effects, too now.
// v1.02 --- made the system safer and reduced the number of variables to protect greatly.
// v1.03 --- Gave a sense to 'DoNothing'* GUI function and made the Pass Data part
// more accurate, so the time until data get destroyed are much more explicit
// now.
// v1.04 --- Added the very important constant MAX_LEAK_INSTANCES
//
// *if you don't want it to be hooked, comment line 350.
//===========================================================================
// FAQ:
// ====
// 1. Why don't you hook functions like GetLocationX or the ForGroup without BJ?
//
// Answer: Well, in jass you would never destroy groups, rather have one global group
// and clear/recycle it. But GUI always creates new groups with the functions.
// So actually, jass groups don't have to be destroyed.
// And special effects are mostly instantly destroyed and locations are never used.
// So I don't want to endanger breaking jass systems, I rather make it for GUI, where it is
// really useful and neccessary
//
// 2. Why should I protect my variables instead of killing my leaks?
//
// Answer: In GUI, unitgroup effect and location variables are actually just used
// for destroying stuff. It is rare that you really want to keep the groups.
// So in fact, it is like one-hundred times less frequent that you want to keep
// your data instead of destroying it.
//
// 3. I can't use jass. How can this system be useful for me?
//
// Answer: This system works mainly automatically; You just have to use jass very rarely (and then a simple function).
// The functions you need are:
//
// ProtectVariable(udg_###)
// GetLastCaughtHandle()
//
// where ProtectVariable saves something you want to keep from getting destroyed. Just replace ### with your
// variable name. NOTE: You don't have to protect the variable, when you want to keep the data inside less than
// CLEAN_UP_INTERVAL seconds.
//
// and where GetLastCaughtHandle responses to the handle* that was used lastly.
// That can be for example the point where you just spawned a unit.
//
// * 'handle' means a specialeffect, a location or a unitgroup
//
// 4. If you give functions like 'DelayMMH', why don't you add functions like 'Disable/EnableMMH'?
//
// Answer: Well, I want to protect the user. You do not need Disable/Enable functions.
// To me the danger is too big, that you forget to activate it again or do
// something like that. DelayMMH is totally enough if you don't want this system
// to affect the code that comes next. Also that prevents, that there are
// spells like 'Trojan Horses' that get too much access to this and can
// change it's infrastructure.
//
//===========================================================================
// Functions:
//==========
// ProtectHandle : Saves a handle from getting destroyed
// ProtectVariable : Same.
// DoNothing() : Destroys all data caught by the system right now in X seconds.
// DelayMMD() : Stops the system working until the trigger ends/next wait *
//
// * This is as fast as an automatic memory leak destroyer can get. Why should
// you want to disable the system? Because it offers the possibilty to make things
// more efficient. I don't want to say, this is unefficient, because it is not.
// But this will destroy leaks like 10% slower.
//
//===========================================================================
globals
// The system fires when you do something that creates a leak.
// The data that cause leak are saved in a variable then.
// And every CLEAN_UP_INTERVAL seconds those data are destroyed.
// This shouldn't be too high, or too low.
private constant real CLEAN_UP_INTERVAL = 120.
// If this is set to true, the system will work more slowly (but you wont notice)
// and count, how much memory this system was able to save.
// This value is display by the function DisplayLeaks() then.
// WARNING: This sucks a lot of performance. I would ONLY use it when you want
// to test, if this is useful for your map. Later set it to false.
private constant boolean DISPLAY_SAVED_MEMORY = false
// The Data are only cleaned up, when that many handles were caught
private constant integer MIN_LEAK_NUMBER = 1750
// How often are data passed to the destroyer?
// Leaks stay for a random time between CLEAN_UP_INTERVAL and CLEAN_UP_INTERVAL+PASS_INTERVAL
// in the game
private constant real PASS_INTERVAL = 2.5
// Memory leaks occur pretty frequently. When a leak is caught it is saved in
// an array. But the array can't have more than MAX_LEAK_INSTANCES instances, so
// if more than MAX_LEAK_INSTANCES memory leaks occur during a destroy interval,
// the system fails.
private constant integer MAX_LEAK_INSTANCES = 60000
endglobals
globals
private HandleTable IndexData
private HandleTable IsSaved
//! textmacro MemoryLeakVars takes NAME, TYPE
private integer Caught$NAME$Leaks = 0
private $TYPE$ array $NAME$LeakData[MAX_LEAK_INSTANCES]
private integer $NAME$DestroyCount = 0
private $TYPE$ array $NAME$DestroyData[MAX_LEAK_INSTANCES]
//! endtextmacro
//! runtextmacro MemoryLeakVars("Location","location")
//! runtextmacro MemoryLeakVars("Effect","effect")
//! runtextmacro MemoryLeakVars("Group","group")
private integer DestroyedLeaks = 0
private integer CaughtLeaks = 0
private integer DestroyedLeaksUser = 0
private handle LastCaught
private timer PassTimer = CreateTimer()
private timer CleanTimer = CreateTimer()
private timer DelayTimer = CreateTimer()
private boolean IsDestroying = false
private real SavedMemory = 0.
private real LastCheckedGroupMemoryUsage = 0.
private boolean DestroyThreadRunning = false
private boolean Disabled = false
// These values were found out in a big leak test by gekko.
private constant real LOCATION_MEMORY_USAGE = 0.361
private constant real GROUP_MEMORY_USAGE = 0.62
private constant real GROUP_UNIT_MEMORY_USAGE = 0.040
private constant real EFFECT_MEMORY_USAGE = 11.631
private constant real REMOVED_EFFECT_MEMORY_USAGE = 0.066
endglobals
// ======================================
// ============= Basic Code =============
// ======================================
function GetLastCaughtHandle takes nothing returns handle
return LastCaught
endfunction
function ProtectHandle takes handle h returns nothing
set IsSaved[h] = 1
endfunction
function ProtectVariable takes handle h returns nothing
set IsSaved[h] = 1
endfunction
private function EnableMMH takes nothing returns nothing
set Disabled = false
endfunction
function DelayMMH takes nothing returns nothing
set Disabled = true
call TimerStart(DelayTimer,0.00,false,function EnableMMH)
endfunction
function DisplayLeaks takes nothing returns nothing
call ClearTextMessages()
call BJDebugMsg("======= MemoryLeakHelper =======")
call BJDebugMsg("Destroyed Leaks: "+I2S(DestroyedLeaks))
call BJDebugMsg("Destroyed Leaks by user: "+I2S(DestroyedLeaksUser))
call BJDebugMsg("Percentage System: "+R2S(I2R(DestroyedLeaks)/I2R(DestroyedLeaks+DestroyedLeaksUser)*100.)+"%")
call BJDebugMsg("Percentage User: "+R2S(I2R(DestroyedLeaksUser)/I2R(DestroyedLeaks+DestroyedLeaksUser)*100.)+"%")
call BJDebugMsg("Leaks until next destroy: "+I2S(MIN_LEAK_NUMBER-CaughtLeaks))
call BJDebugMsg(" === In Destroy Queue === ")
call BJDebugMsg(" Group Leaks: "+I2S(GroupDestroyCount))
call BJDebugMsg(" Location Leaks: "+I2S(LocationDestroyCount))
call BJDebugMsg(" Effect Leaks: "+I2S(EffectDestroyCount))
call BJDebugMsg(" === Not in Destroy Queue yet === ")
call BJDebugMsg(" Group Leaks: "+I2S(CaughtGroupLeaks))
call BJDebugMsg(" Location Leaks: "+I2S(CaughtLocationLeaks))
call BJDebugMsg(" Effect Leaks: "+I2S(CaughtEffectLeaks))
call BJDebugMsg("Time until next PassSequence: "+I2S(R2I(TimerGetRemaining(PassTimer)+0.5))+" seconds.")
call BJDebugMsg(" ")
if DISPLAY_SAVED_MEMORY then
call BJDebugMsg("All in all the MemoryLeakHelper could release "+R2S(SavedMemory)+" kb of memory.")
endif
call BJDebugMsg("================================")
endfunction
private function GroupGetMemoryUsageEnum takes nothing returns nothing
set LastCheckedGroupMemoryUsage = LastCheckedGroupMemoryUsage + GROUP_UNIT_MEMORY_USAGE
endfunction
function GroupGetMemoryUsage takes group g returns real
set LastCheckedGroupMemoryUsage = 0.
call ForGroup(g,function GroupGetMemoryUsageEnum)
return LastCheckedGroupMemoryUsage + GROUP_MEMORY_USAGE
endfunction
//! textmacro ResponseOnLeak takes NAME, VALUE
private function Catch$NAME$ takes $VALUE$ l returns nothing
set LastCaught = l
if Disabled then
return
elseif Caught$NAME$Leaks == MAX_LEAK_INSTANCES then
debug call BJDebugMsg("MemoryLeakHelper: Failed to store leak because of size limitations")
return
endif
if IndexData.exists(l) == false then
//call BJDebugMsg("Caught $NAME$")
set Caught$NAME$Leaks = Caught$NAME$Leaks + 1
set $NAME$LeakData[Caught$NAME$Leaks] = l
set IndexData[l] = Caught$NAME$Leaks
endif
endfunction
private function AddTo$NAME$DestroyQueue takes $VALUE$ l returns nothing
set $NAME$DestroyCount = $NAME$DestroyCount + 1
set $NAME$DestroyData[$NAME$DestroyCount] = l
set IndexData[l] = $NAME$DestroyCount*-1 // Put his to negative, so we know that this is used in the DestroyQueue now.
endfunction
private function Release$NAME$ takes $VALUE$ l returns nothing
local integer index
if IsDestroying == false and IndexData.exists(l) then
set index = IndexData[l]
// If this is true, the index wasn't put to a destroy queue yet.
if index > 0 then
set $NAME$LeakData[index] = $NAME$LeakData[Caught$NAME$Leaks]
set Caught$NAME$Leaks = Caught$NAME$Leaks - 1
else
set index = index * -1
set $NAME$DestroyData[index] = $NAME$DestroyData[$NAME$DestroyCount]
set $NAME$DestroyCount = $NAME$DestroyCount - 1
endif
call IndexData.flush(l)
set DestroyedLeaksUser = DestroyedLeaksUser + 1
endif
endfunction
//! endtextmacro
//! runtextmacro ResponseOnLeak("Location","location")
//! runtextmacro ResponseOnLeak("Group","group")
//! runtextmacro ResponseOnLeak("Effect","effect")
private function DestroyMemoryLeaks takes nothing returns nothing
set IsDestroying = true
//call BJDebugMsg("DESTROYING Memory Leaks")
//! textmacro DestroyLeaks takes NAME, DESTROYCALL, MEMORYUSAGE
set DestroyedLeaks = DestroyedLeaks + $NAME$DestroyCount
loop
exitwhen $NAME$DestroyCount == 0
if DISPLAY_SAVED_MEMORY then
set SavedMemory = SavedMemory + $MEMORYUSAGE$
endif
call $DESTROYCALL$($NAME$DestroyData[$NAME$DestroyCount])
call IndexData.flush($NAME$DestroyData[$NAME$DestroyCount])
set $NAME$DestroyCount = $NAME$DestroyCount - 1
endloop
//! endtextmacro
//! runtextmacro DestroyLeaks ("Group","DestroyGroup","GroupGetMemoryUsage(GroupDestroyData[GroupDestroyCount])")
//! runtextmacro DestroyLeaks ("Location","RemoveLocation","LOCATION_MEMORY_USAGE")
//! runtextmacro DestroyLeaks ("Effect","DestroyEffect","EFFECT_MEMORY_USAGE")
set IsDestroying = false
set DestroyThreadRunning = false
//call StartPassTimer.execute() // Strange. This causes bugs sometimes and the function isn't called
// This is slower, but safe.
call ExecuteFunc("StartPassTimer")
endfunction
function StartDestroyThread takes nothing returns nothing
if DestroyThreadRunning == false then
set DestroyThreadRunning = true
call TimerStart(CleanTimer,CLEAN_UP_INTERVAL,false,function DestroyMemoryLeaks)
call PauseTimer(PassTimer)
endif
endfunction
hook DoNothing StartDestroyThread
// We want that the user doesn't have to protect too many variables, but all the variables that are filled longer
// than CLEAN_UP_INTERVAL seconds. But what, when the handle thing is put into the destroy stack and the next destroy is
// in 5 seconds, because the last one was 15 seconds ago? We can simply avoid something like that by using a 2-step-system
// that goes sure, the handle is only destroyed when it passed the CLEAN_UP_INTERVAL twice.
// Having two kinds of variables is simply easier and more efficient than having another variable that refers to
// how many times the handle passed the timer; If it isn't passed/cleared in the Interval then, we can't loop
// that easily through the data and we'd have to fix gaps later; That would suck a lot of performacne.
private function PassMemoryLeaks takes nothing returns nothing
//call BJDebugMsg("PassMemoryLeaks")
//! textmacro PassLeaks takes NAME
set CaughtLeaks = CaughtLeaks + Caught$NAME$Leaks
//call BJDebugMsg("Caught $NAME$s: "+I2S(Caught$NAME$Leaks))
loop
exitwhen Caught$NAME$Leaks < 1
if IsSaved.exists($NAME$LeakData[Caught$NAME$Leaks]) == false and $NAME$LeakData[Caught$NAME$Leaks] != null then
call AddTo$NAME$DestroyQueue($NAME$LeakData[Caught$NAME$Leaks])
endif
set $NAME$LeakData[Caught$NAME$Leaks] = null
set Caught$NAME$Leaks = Caught$NAME$Leaks - 1
endloop
//! endtextmacro
//! runtextmacro PassLeaks ("Group")
//! runtextmacro PassLeaks ("Location")
//! runtextmacro PassLeaks ("Effect")
if CaughtLeaks > MIN_LEAK_NUMBER then
set CaughtLeaks = 0
//call BJDebugMsg("Caught Leaks: "+I2S(MIN_LEAK_NUMBER))
//call BJDebugMsg("Now start Destroy Timer")
set DestroyThreadRunning = true
call TimerStart(CleanTimer,CLEAN_UP_INTERVAL,false,function DestroyMemoryLeaks)
// We have to pause this timer a bit; Otherwise it would break the CLEAN_UP_INTERVAL rule.
call PauseTimer(PassTimer)
endif
endfunction
// =================================
// ============= Usage =============
// =================================
private function PP takes location source, real dist, real angle returns nothing
call CatchLocation(source)
endfunction
private function CU takes integer count, integer unitId, player p, location l, real face returns nothing
call CatchLocation(l)
endfunction
private function IPO takes unit k, string order, location l returns nothing
call CatchLocation(l)
endfunction
private function SUP takes unit who, location l returns nothing
call CatchLocation(l)
endfunction
private function SUF takes unit who, location l, real dur returns nothing
call CatchLocation(l)
endfunction
private function GUR takes real radius, location l, boolexpr filter returns nothing
call CatchLocation(l)
endfunction
private function CUF takes integer count, integer unitId, player whichPlayer, location loc, location lookAt returns nothing
call CatchLocation(loc)
call CatchLocation(lookAt)
endfunction
hook PolarProjectionBJ PP
hook CreateNUnitsAtLoc CU
hook CreateNUnitsAtLocFacingLocBJ CUF
hook IssuePointOrderLocBJ IPO
hook SetUnitPositionLoc SUP
hook SetUnitFacingToFaceLocTimed SUF
hook GetUnitsInRangeOfLocMatching GUR
hook RemoveLocation ReleaseLocation
private function FG takes group g, code callback returns nothing
call CatchGroup(g)
endfunction
hook ForGroupBJ FG // :D This should catch all GUI usages for groups.
hook GroupPickRandomUnit CatchGroup
hook CountUnitsInGroup CatchGroup
hook DestroyGroup ReleaseGroup
private function ASETU takes string bla, widget d, string blu returns nothing
// We can not catch THIS effect, but the effect that was created before.
// So we can destroy all SpecialEffects excpet one.
call CatchEffect(GetLastCreatedEffectBJ())
endfunction
private function ASE takes location where, string modelName returns nothing
call CatchLocation(where)
call CatchEffect(GetLastCreatedEffectBJ())
endfunction
hook AddSpecialEffectLocBJ ASE
hook AddSpecialEffectTargetUnitBJ ASETU
hook DestroyEffect ReleaseEffect
hook DestroyEffectBJ ReleaseEffect
// When I want to make the timer run the PassMemoryLeaks things, I have to use an .execute command which requires an extra func.
function StartPassTimer takes nothing returns nothing
//call BJDebugMsg("Restarting PassTimer")
call TimerStart(PassTimer,PASS_INTERVAL,true,function PassMemoryLeaks)
endfunction
private function Init takes nothing returns nothing
set IndexData = HandleTable.create()
set IsSaved = HandleTable.create()
call StartPassTimer()
endfunction
endlibrary
library ValueIndexing initializer Init
//===========================================================================
// Information:
//==============
// This is a library that allows you to get unique hashvalues for any number you like.
// This also gives the option to be directly added to the struct, so you will have this syntax:
// set Data['some'] = anyValue
//
// But you could also do it with table, right?
// Well that's correct, but in many cases this is faster;
// It is proven that LoadInteger is much faster than SaveInteger.
// When you apply new data to a normal table, SaveInteger is used.
// This system only uses SaveInteger if it hasn't created a hash yet.
//
// But the bad thing is that you should remove the hashvalues after a while, since
// there is a limit of 8191 unique hashnumbers. Also this is not available as a struct,
// just as a module.
//===========================================================================
// FAQ:
// ====
//
// 1. We have a maximum of 8190 possible hashes. And there's no clearHash function.
// Won't this break after a while?
//
// Answer: No, it won't. If the number of hahes reach a critical point, all hashes
// that have been in the game for longer than HASH_DECAY_TIME seconds will
// be removed then.
//
//===========================================================================
globals
private constant real HASH_DECAY_TIME = 500.
private constant integer CLEAR_HASH_COUNT = 8190
endglobals
globals
private hashtable HashTable = InitHashtable()
private integer HashNumber = 0
private integer array HashData
private integer array HashHash
private integer array HashPlace
private real array CreationTime
private integer TempHashNumber = 0
private integer array TempHashHash
private integer array TempHashPlace
private integer array TempHashData
private real array TempCreationTime
private integer LastHashedValue = 0
private integer LastIndex = 0
private real GameTime = 0.
private timer GameTimeTimer = CreateTimer()
private constant real GAMETIME_TIMER_INTERVAL = 30.
private constant integer key = 0
endglobals
private function GetElapsedGameTime takes nothing returns real
return GameTime + TimerGetElapsed(GameTimeTimer)
endfunction
private function UpdateGameTime takes nothing returns nothing
set GameTime = GameTime + GAMETIME_TIMER_INTERVAL
endfunction
// Well, the method to do this that I use in my automatic memory leak destroyer
// is much more efficient, but I think that's not iportant in this library,
// because old Hashes are destroyed very rarely.
private function DestroyHashes takes nothing returns nothing
local real gt = GetElapsedGameTime
local Index ind
// Well, due to the nature of this system, the looking of the code sucks.
loop
exitwhen HashNumber == 0
if gt - CreationTime[HashNumber] > HASH_DECAY_TIME then
set ind = HashHash[HashNumber]
call ind.destroy()
call RemoveSavedInteger(HashTable,key,HashData[HashNumber])
else
set TempHashNumber = TempHashNumber + 1
set TempHashData[TempHashNumber] = HashData[HashNumber]
set TempHashHash[TempHashNumber] = HashHash[HashNumber]
set TempHashPlace[TempHashNumber] = HashPlace[HashNumber]
set TempCreationTime[TempHashNumber] = CreationTime[HashNumber]
endif
set HashData[HashNumber] = 0
set HashHash[HashNumber] = 0
set HashPlace[HashNumber] = 0
set CreationTime[HashNumber] = 0.
set HashNumber = HashNumber - 1
endloop
loop
exitwhen TempHashNumber == 0
set HashNumber = HashNumber + 1
set HashData[HashNumber] = TempHashData[TempHashNumber]
set HashHash[HashNumber] = TempHashHash[TempHashNumber]
set HashPlace[HashNumber] = TempHashPlace[TempHashNumber]
set TempHashData[TempHashNumber] = 0
set TempHashNumber = TempHashNumber - 1
endloop
endfunction
struct Index
static method GetHash takes integer value returns integer
local integer int = LoadInteger(HashTable,key,value)
if int == 0 then
set int = Index.create()
call SaveInteger(HashTable,key,value,int)
set HashNumber = HashNumber + 1
set HashPlace[int] = HashNumber
set HashData[HashNumber] = value
set HashHash[HashNumber] = int
set CreationTime[HashNumber] = GetElapsedGameTime()
if HashNumber >= CLEAR_HASH_COUNT then
call DestroyHashes()
endif
endif
set LastHashedValue = value
set LastIndex = int
return int
endmethod
endstruct
function ProtectHash takes integer value returns nothing
if value == LastHashedValue then
set CreationTime[HashPlace[LastIndex]] = 999999999.
else
set CreationTime[HashPlace[Index.GetHash(value)]] = 999999999.
endif
endfunction
function GetValueIndex takes integer value returns integer
return Index.GetHash(value)
endfunction
private function Init takes nothing returns nothing
call TimerStart(GameTimeTimer,GAMETIME_TIMER_INTERVAL,true,function UpdateGameTime)
endfunction
module ValueIndexing
static integer array Data
static method operator []= takes integer i, integer equals returns nothing
set .Data[Index.GetHash(i)] = equals
endmethod
static method operator [] takes integer i returns thistype
return .Data[Index.GetHash(i)]
endmethod
endmodule
endlibrary
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=93
//TESH.alwaysfold=0
library ValueIndexing initializer Init
//===========================================================================
// Information:
//==============
// This is a library that allows you to get unique hashvalues for any number you like.
// This also gives the option to be directly added to the struct, so you will have this syntax:
// set Data['some'] = anyValue
//
// But you could also do it with table, right?
// Well that's correct, but in many cases this is faster;
// It is proven that LoadInteger is much faster than SaveInteger.
// When you apply new data to a normal table, SaveInteger is used.
// This system only uses SaveInteger if it hasn't created a hash yet.
//
// But the bad thing is that you should remove the hashvalues after a while, since
// there is a limit of 8191 unique hashnumbers. Also this is not available as a struct,
// just as a module.
//===========================================================================
// FAQ:
// ====
//
// 1. We have a maximum of 8190 possible hashes. And there's no clearHash function.
// Won't this break after a while?
//
// Answer: No, it won't. If the number of hahes reach a critical point, all hashes
// that have been in the game for longer than HASH_DECAY_TIME seconds will
// be removed then.
//
//===========================================================================
globals
private constant real HASH_DECAY_TIME = 500.
private constant integer CLEAR_HASH_COUNT = 8190
endglobals
globals
private hashtable HashTable = InitHashtable()
private integer HashNumber = 0
private integer array HashData
private integer array HashHash
private integer array HashPlace
private real array CreationTime
private integer TempHashNumber = 0
private integer array TempHashHash
private integer array TempHashPlace
private integer array TempHashData
private real array TempCreationTime
private integer LastHashedValue = 0
private integer LastIndex = 0
private real GameTime = 0.
private timer GameTimeTimer = CreateTimer()
private constant real GAMETIME_TIMER_INTERVAL = 30.
private constant integer key = 0
endglobals
private function GetElapsedGameTime takes nothing returns real
return GameTime + TimerGetElapsed(GameTimeTimer)
endfunction
private function UpdateGameTime takes nothing returns nothing
set GameTime = GameTime + GAMETIME_TIMER_INTERVAL
endfunction
// Well, the method to do this that I use in my automatic memory leak destroyer
// is much more efficient, but I think that's not iportant in this library,
// because old Hashes are destroyed very rarely.
private function DestroyHashes takes nothing returns nothing
local real gt = GetElapsedGameTime
local Index ind
// Well, due to the nature of this system, the looking of the code sucks.
loop
exitwhen HashNumber == 0
if gt - CreationTime[HashNumber] > HASH_DECAY_TIME then
set ind = HashHash[HashNumber]
call ind.destroy()
call RemoveSavedInteger(HashTable,key,HashData[HashNumber])
else
set TempHashNumber = TempHashNumber + 1
set TempHashData[TempHashNumber] = HashData[HashNumber]
set TempHashHash[TempHashNumber] = HashHash[HashNumber]
set TempHashPlace[TempHashNumber] = HashPlace[HashNumber]
set TempCreationTime[TempHashNumber] = CreationTime[HashNumber]
endif
set HashData[HashNumber] = 0
set HashHash[HashNumber] = 0
set HashPlace[HashNumber] = 0
set CreationTime[HashNumber] = 0.
set HashNumber = HashNumber - 1
endloop
loop
exitwhen TempHashNumber == 0
set HashNumber = HashNumber + 1
set HashData[HashNumber] = TempHashData[TempHashNumber]
set HashHash[HashNumber] = TempHashHash[TempHashNumber]
set HashPlace[HashNumber] = TempHashPlace[TempHashNumber]
set TempHashData[TempHashNumber] = 0
set TempHashNumber = TempHashNumber - 1
endloop
endfunction
struct Index
static method GetHash takes integer value returns integer
local integer int = LoadInteger(HashTable,key,value)
if int == 0 then
set int = Index.create()
call SaveInteger(HashTable,key,value,int)
set HashNumber = HashNumber + 1
set HashPlace[int] = HashNumber
set HashData[HashNumber] = value
set HashHash[HashNumber] = int
set CreationTime[HashNumber] = GetElapsedGameTime()
if HashNumber >= CLEAR_HASH_COUNT then
call DestroyHashes()
endif
endif
set LastHashedValue = value
set LastIndex = int
return int
endmethod
endstruct
function ProtectHash takes integer value returns nothing
if value == LastHashedValue then
set CreationTime[HashPlace[LastIndex]] = 999999999.
else
set CreationTime[HashPlace[Index.GetHash(value)]] = 999999999.
endif
endfunction
function GetValueIndex takes integer value returns integer
return Index.GetHash(value)
endfunction
private function Init takes nothing returns nothing
call TimerStart(GameTimeTimer,GAMETIME_TIMER_INTERVAL,true,function UpdateGameTime)
endfunction
module ValueIndexing
static integer array Data
static method operator []= takes integer i, integer equals returns nothing
set .Data[Index.GetHash(i)] = equals
endmethod
static method operator [] takes integer i returns thistype
return .Data[Index.GetHash(i)]
endmethod
endmodule
endlibrary
//TESH.scrollpos=72
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=24
//TESH.alwaysfold=0
library MemoryLeakHelper initializer Init requires Table
// ==================================
// Give credits to Mr.Malte when used!
//===========================================================================
// Information:
//==============
//
// There are things called 'memory Leaks'. When you create a group or use a location
// without destroying it, you will cause lag that stays in the whole game.
// If you implement this library into your map it will automatically fix a big part
// of those memory leaks, so reduce the lag extremely.
// This should mainfully be used by GUI-users because the system is designed for them.
//
// Of course no system can work totally automatically.
// But there is only one thing you have to do in order to prevent bugs:
// If you make groups or locations that have to be filled for more than CLEAN_UP_INTERVAL seconds
// you have to save them with the code:
//
// call ProtectHandle(XXX)
//
// Where XXX is filled with your variable. Otherwise that variable gets destroyed
// automatically. You can also fill in XXX with
//
// GetLastCaughtHandle()
//
// But if you save the things in a variable, I'd recommend to directly put the
// variable into the brackets. Note: GUI variables have the prefix 'udg_' in JASS.
//
// This gives the 'Do Nothing' function in GUI a sense!
// When you call DoNothing, all data that were caught by this system
// will be destroyed in CLEAN_UP_INTERVAL seconds, ignoring how big
// the number of caught handles is. This will not work, if the system is
// already cleaning up.
//===========================================================================
// Implementation:
//===============
//
// The easiest thing is to directly implement this thing into your map, when you start making
// it, so you don't have to look over your globals and use ProtectHandle on them.
// These are the steps you have to do to clear the memory leaks:
//
// 1. Download a tool called 'JassNewGen', and unpack it somewhere. You need that
// edit to use this tool. The 'JassNewGen' is used very commonly and offers other
// nice features. You can find it at:
// http://www.wc3c.net/showthread.php?t=90999
// 2. Make a new trigger, and convert it to custom text. Insert everything
// the library contains into that trigger.
//
// 3. Download a system called 'Table' from this link:
// http://www.wc3c.net/showthread.php?t=101246
// Do the same installation stuff for 'Table' as for this system.
//
// 4. Save your map and enjoy :-)
//
// Note: Instead of doing 2 and 3 you can also copy and paste the folder 'MemoryLeakHelper'
// from the example map.
//===========================================================================
// How bad are memory leaks?
//==========================
// If you don't remove memory leaks, they suck memory:
//
// Location: 0.361 kb
// Group: 0.62 kb + 0.040 kb for each unit in the group.
// Effect: 11.631 kb
//
// Both, locations and groups are used very frequently. So when you don't fix those memory leaks,
// you will experience lag.
// When you want to see, how useful this is for your map, implement it
// and write 'call DisplayLeaks()' into a custom script that is fired when
// they game ends.
//===========================================================================
// Changelog:
//===========
// v1.00 --- first version
// v1.01 --- able to detect special effects, too now.
// v1.02 --- made the system safer and reduced the number of variables to protect greatly.
// v1.03 --- Gave a sense to 'DoNothing'* GUI function and made the Pass Data part
// more accurate, so the time until data get destroyed are much more explicit
// now.
// v1.04 --- Added the very important constant MAX_LEAK_INSTANCES
//
// *if you don't want it to be hooked, comment line 350.
//===========================================================================
// FAQ:
// ====
// 1. Why don't you hook functions like GetLocationX or the ForGroup without BJ?
//
// Answer: Well, in jass you would never destroy groups, rather have one global group
// and clear/recycle it. But GUI always creates new groups with the functions.
// So actually, jass groups don't have to be destroyed.
// And special effects are mostly instantly destroyed and locations are never used.
// So I don't want to endanger breaking jass systems, I rather make it for GUI, where it is
// really useful and neccessary
//
// 2. Why should I protect my variables instead of killing my leaks?
//
// Answer: In GUI, unitgroup effect and location variables are actually just used
// for destroying stuff. It is rare that you really want to keep the groups.
// So in fact, it is like one-hundred times less frequent that you want to keep
// your data instead of destroying it.
//
// 3. I can't use jass. How can this system be useful for me?
//
// Answer: This system works mainly automatically; You just have to use jass very rarely (and then a simple function).
// The functions you need are:
//
// ProtectVariable(udg_###)
// GetLastCaughtHandle()
//
// where ProtectVariable saves something you want to keep from getting destroyed. Just replace ### with your
// variable name. NOTE: You don't have to protect the variable, when you want to keep the data inside less than
// CLEAN_UP_INTERVAL seconds.
//
// and where GetLastCaughtHandle responses to the handle* that was used lastly.
// That can be for example the point where you just spawned a unit.
//
// * 'handle' means a specialeffect, a location or a unitgroup
//
// 4. If you give functions like 'DelayMMH', why don't you add functions like 'Disable/EnableMMH'?
//
// Answer: Well, I want to protect the user. You do not need Disable/Enable functions.
// To me the danger is too big, that you forget to activate it again or do
// something like that. DelayMMH is totally enough if you don't want this system
// to affect the code that comes next. Also that prevents, that there are
// spells like 'Trojan Horses' that get too much access to this and can
// change it's infrastructure.
//
//===========================================================================
// Functions:
//==========
// ProtectHandle : Saves a handle from getting destroyed
// ProtectVariable : Same.
// DoNothing() : Destroys all data caught by the system right now in X seconds.
// DelayMMD() : Stops the system working until the trigger ends/next wait *
//
// * This is as fast as an automatic memory leak destroyer can get. Why should
// you want to disable the system? Because it offers the possibilty to make things
// more efficient. I don't want to say, this is unefficient, because it is not.
// But this will destroy leaks like 10% slower.
//
//===========================================================================
globals
// The system fires when you do something that creates a leak.
// The data that cause leak are saved in a variable then.
// And every CLEAN_UP_INTERVAL seconds those data are destroyed.
// This shouldn't be too high, or too low.
private constant real CLEAN_UP_INTERVAL = 120.
// If this is set to true, the system will work more slowly (but you wont notice)
// and count, how much memory this system was able to save.
// This value is display by the function DisplayLeaks() then.
// WARNING: This sucks a lot of performance. I would ONLY use it when you want
// to test, if this is useful for your map. Later set it to false.
private constant boolean DISPLAY_SAVED_MEMORY = false
// The Data are only cleaned up, when that many handles were caught
private constant integer MIN_LEAK_NUMBER = 1750
// How often are data passed to the destroyer?
// Leaks stay for a random time between CLEAN_UP_INTERVAL and CLEAN_UP_INTERVAL+PASS_INTERVAL
// in the game
private constant real PASS_INTERVAL = 2.5
// Memory leaks occur pretty frequently. When a leak is caught it is saved in
// an array. But the array can't have more than MAX_LEAK_INSTANCES instances, so
// if more than MAX_LEAK_INSTANCES memory leaks occur during a destroy interval,
// the system fails.
private constant integer MAX_LEAK_INSTANCES = 60000
endglobals
globals
private HandleTable IndexData
private HandleTable IsSaved
//! textmacro MemoryLeakVars takes NAME, TYPE
private integer Caught$NAME$Leaks = 0
private $TYPE$ array $NAME$LeakData[MAX_LEAK_INSTANCES]
private integer $NAME$DestroyCount = 0
private $TYPE$ array $NAME$DestroyData[MAX_LEAK_INSTANCES]
//! endtextmacro
//! runtextmacro MemoryLeakVars("Location","location")
//! runtextmacro MemoryLeakVars("Effect","effect")
//! runtextmacro MemoryLeakVars("Group","group")
private integer DestroyedLeaks = 0
private integer CaughtLeaks = 0
private integer DestroyedLeaksUser = 0
private handle LastCaught
private timer PassTimer = CreateTimer()
private timer CleanTimer = CreateTimer()
private timer DelayTimer = CreateTimer()
private boolean IsDestroying = false
private real SavedMemory = 0.
private real LastCheckedGroupMemoryUsage = 0.
private boolean DestroyThreadRunning = false
private boolean Disabled = false
// These values were found out in a big leak test by gekko.
private constant real LOCATION_MEMORY_USAGE = 0.361
private constant real GROUP_MEMORY_USAGE = 0.62
private constant real GROUP_UNIT_MEMORY_USAGE = 0.040
private constant real EFFECT_MEMORY_USAGE = 11.631
private constant real REMOVED_EFFECT_MEMORY_USAGE = 0.066
endglobals
// ======================================
// ============= Basic Code =============
// ======================================
function GetLastCaughtHandle takes nothing returns handle
return LastCaught
endfunction
function ProtectHandle takes handle h returns nothing
set IsSaved[h] = 1
endfunction
function ProtectVariable takes handle h returns nothing
set IsSaved[h] = 1
endfunction
private function EnableMMH takes nothing returns nothing
set Disabled = false
endfunction
function DelayMMH takes nothing returns nothing
set Disabled = true
call TimerStart(DelayTimer,0.00,false,function EnableMMH)
endfunction
function DisplayLeaks takes nothing returns nothing
call ClearTextMessages()
call BJDebugMsg("======= MemoryLeakHelper =======")
call BJDebugMsg("Destroyed Leaks: "+I2S(DestroyedLeaks))
call BJDebugMsg("Destroyed Leaks by user: "+I2S(DestroyedLeaksUser))
call BJDebugMsg("Percentage System: "+R2S(I2R(DestroyedLeaks)/I2R(DestroyedLeaks+DestroyedLeaksUser)*100.)+"%")
call BJDebugMsg("Percentage User: "+R2S(I2R(DestroyedLeaksUser)/I2R(DestroyedLeaks+DestroyedLeaksUser)*100.)+"%")
call BJDebugMsg("Leaks until next destroy: "+I2S(MIN_LEAK_NUMBER-CaughtLeaks))
call BJDebugMsg(" === In Destroy Queue === ")
call BJDebugMsg(" Group Leaks: "+I2S(GroupDestroyCount))
call BJDebugMsg(" Location Leaks: "+I2S(LocationDestroyCount))
call BJDebugMsg(" Effect Leaks: "+I2S(EffectDestroyCount))
call BJDebugMsg(" === Not in Destroy Queue yet === ")
call BJDebugMsg(" Group Leaks: "+I2S(CaughtGroupLeaks))
call BJDebugMsg(" Location Leaks: "+I2S(CaughtLocationLeaks))
call BJDebugMsg(" Effect Leaks: "+I2S(CaughtEffectLeaks))
call BJDebugMsg("Time until next PassSequence: "+I2S(R2I(TimerGetRemaining(PassTimer)+0.5))+" seconds.")
call BJDebugMsg(" ")
if DISPLAY_SAVED_MEMORY then
call BJDebugMsg("All in all the MemoryLeakHelper could release "+R2S(SavedMemory)+" kb of memory.")
endif
call BJDebugMsg("================================")
endfunction
private function GroupGetMemoryUsageEnum takes nothing returns nothing
set LastCheckedGroupMemoryUsage = LastCheckedGroupMemoryUsage + GROUP_UNIT_MEMORY_USAGE
endfunction
function GroupGetMemoryUsage takes group g returns real
set LastCheckedGroupMemoryUsage = 0.
call ForGroup(g,function GroupGetMemoryUsageEnum)
return LastCheckedGroupMemoryUsage + GROUP_MEMORY_USAGE
endfunction
//! textmacro ResponseOnLeak takes NAME, VALUE
private function Catch$NAME$ takes $VALUE$ l returns nothing
set LastCaught = l
if Disabled then
return
elseif Caught$NAME$Leaks == MAX_LEAK_INSTANCES then
debug call BJDebugMsg("MemoryLeakHelper: Failed to store leak because of size limitations")
return
endif
if IndexData.exists(l) == false then
//call BJDebugMsg("Caught $NAME$")
set Caught$NAME$Leaks = Caught$NAME$Leaks + 1
set $NAME$LeakData[Caught$NAME$Leaks] = l
set IndexData[l] = Caught$NAME$Leaks
endif
endfunction
private function AddTo$NAME$DestroyQueue takes $VALUE$ l returns nothing
set $NAME$DestroyCount = $NAME$DestroyCount + 1
set $NAME$DestroyData[$NAME$DestroyCount] = l
set IndexData[l] = $NAME$DestroyCount*-1 // Put his to negative, so we know that this is used in the DestroyQueue now.
endfunction
private function Release$NAME$ takes $VALUE$ l returns nothing
local integer index
if IsDestroying == false and IndexData.exists(l) then
set index = IndexData[l]
// If this is true, the index wasn't put to a destroy queue yet.
if index > 0 then
set $NAME$LeakData[index] = $NAME$LeakData[Caught$NAME$Leaks]
set Caught$NAME$Leaks = Caught$NAME$Leaks - 1
else
set index = index * -1
set $NAME$DestroyData[index] = $NAME$DestroyData[$NAME$DestroyCount]
set $NAME$DestroyCount = $NAME$DestroyCount - 1
endif
call IndexData.flush(l)
set DestroyedLeaksUser = DestroyedLeaksUser + 1
endif
endfunction
//! endtextmacro
//! runtextmacro ResponseOnLeak("Location","location")
//! runtextmacro ResponseOnLeak("Group","group")
//! runtextmacro ResponseOnLeak("Effect","effect")
private function DestroyMemoryLeaks takes nothing returns nothing
set IsDestroying = true
//call BJDebugMsg("DESTROYING Memory Leaks")
//! textmacro DestroyLeaks takes NAME, DESTROYCALL, MEMORYUSAGE
set DestroyedLeaks = DestroyedLeaks + $NAME$DestroyCount
loop
exitwhen $NAME$DestroyCount == 0
if DISPLAY_SAVED_MEMORY then
set SavedMemory = SavedMemory + $MEMORYUSAGE$
endif
call $DESTROYCALL$($NAME$DestroyData[$NAME$DestroyCount])
call IndexData.flush($NAME$DestroyData[$NAME$DestroyCount])
set $NAME$DestroyCount = $NAME$DestroyCount - 1
endloop
//! endtextmacro
//! runtextmacro DestroyLeaks ("Group","DestroyGroup","GroupGetMemoryUsage(GroupDestroyData[GroupDestroyCount])")
//! runtextmacro DestroyLeaks ("Location","RemoveLocation","LOCATION_MEMORY_USAGE")
//! runtextmacro DestroyLeaks ("Effect","DestroyEffect","EFFECT_MEMORY_USAGE")
set IsDestroying = false
set DestroyThreadRunning = false
//call StartPassTimer.execute() // Strange. This causes bugs sometimes and the function isn't called
// This is slower, but safe.
call ExecuteFunc("StartPassTimer")
endfunction
function StartDestroyThread takes nothing returns nothing
if DestroyThreadRunning == false then
set DestroyThreadRunning = true
call TimerStart(CleanTimer,CLEAN_UP_INTERVAL,false,function DestroyMemoryLeaks)
call PauseTimer(PassTimer)
endif
endfunction
hook DoNothing StartDestroyThread
// We want that the user doesn't have to protect too many variables, but all the variables that are filled longer
// than CLEAN_UP_INTERVAL seconds. But what, when the handle thing is put into the destroy stack and the next destroy is
// in 5 seconds, because the last one was 15 seconds ago? We can simply avoid something like that by using a 2-step-system
// that goes sure, the handle is only destroyed when it passed the CLEAN_UP_INTERVAL twice.
// Having two kinds of variables is simply easier and more efficient than having another variable that refers to
// how many times the handle passed the timer; If it isn't passed/cleared in the Interval then, we can't loop
// that easily through the data and we'd have to fix gaps later; That would suck a lot of performacne.
private function PassMemoryLeaks takes nothing returns nothing
//call BJDebugMsg("PassMemoryLeaks")
//! textmacro PassLeaks takes NAME
set CaughtLeaks = CaughtLeaks + Caught$NAME$Leaks
//call BJDebugMsg("Caught $NAME$s: "+I2S(Caught$NAME$Leaks))
loop
exitwhen Caught$NAME$Leaks < 1
if IsSaved.exists($NAME$LeakData[Caught$NAME$Leaks]) == false and $NAME$LeakData[Caught$NAME$Leaks] != null then
call AddTo$NAME$DestroyQueue($NAME$LeakData[Caught$NAME$Leaks])
endif
set $NAME$LeakData[Caught$NAME$Leaks] = null
set Caught$NAME$Leaks = Caught$NAME$Leaks - 1
endloop
//! endtextmacro
//! runtextmacro PassLeaks ("Group")
//! runtextmacro PassLeaks ("Location")
//! runtextmacro PassLeaks ("Effect")
if CaughtLeaks > MIN_LEAK_NUMBER then
set CaughtLeaks = 0
//call BJDebugMsg("Caught Leaks: "+I2S(MIN_LEAK_NUMBER))
//call BJDebugMsg("Now start Destroy Timer")
set DestroyThreadRunning = true
call TimerStart(CleanTimer,CLEAN_UP_INTERVAL,false,function DestroyMemoryLeaks)
// We have to pause this timer a bit; Otherwise it would break the CLEAN_UP_INTERVAL rule.
call PauseTimer(PassTimer)
endif
endfunction
// =================================
// ============= Usage =============
// =================================
private function PP takes location source, real dist, real angle returns nothing
call CatchLocation(source)
endfunction
private function CU takes integer count, integer unitId, player p, location l, real face returns nothing
call CatchLocation(l)
endfunction
private function IPO takes unit k, string order, location l returns nothing
call CatchLocation(l)
endfunction
private function SUP takes unit who, location l returns nothing
call CatchLocation(l)
endfunction
private function SUF takes unit who, location l, real dur returns nothing
call CatchLocation(l)
endfunction
private function GUR takes real radius, location l, boolexpr filter returns nothing
call CatchLocation(l)
endfunction
private function CUF takes integer count, integer unitId, player whichPlayer, location loc, location lookAt returns nothing
call CatchLocation(loc)
call CatchLocation(lookAt)
endfunction
hook PolarProjectionBJ PP
hook CreateNUnitsAtLoc CU
hook CreateNUnitsAtLocFacingLocBJ CUF
hook IssuePointOrderLocBJ IPO
hook SetUnitPositionLoc SUP
hook SetUnitFacingToFaceLocTimed SUF
hook GetUnitsInRangeOfLocMatching GUR
hook RemoveLocation ReleaseLocation
private function FG takes group g, code callback returns nothing
call CatchGroup(g)
endfunction
hook ForGroupBJ FG // :D This should catch all GUI usages for groups.
hook GroupPickRandomUnit CatchGroup
hook CountUnitsInGroup CatchGroup
hook DestroyGroup ReleaseGroup
private function ASETU takes string bla, widget d, string blu returns nothing
// We can not catch THIS effect, but the effect that was created before.
// So we can destroy all SpecialEffects excpet one.
call CatchEffect(GetLastCreatedEffectBJ())
endfunction
private function ASE takes location where, string modelName returns nothing
call CatchLocation(where)
call CatchEffect(GetLastCreatedEffectBJ())
endfunction
hook AddSpecialEffectLocBJ ASE
hook AddSpecialEffectTargetUnitBJ ASETU
hook DestroyEffect ReleaseEffect
hook DestroyEffectBJ ReleaseEffect
// When I want to make the timer run the PassMemoryLeaks things, I have to use an .execute command which requires an extra func.
function StartPassTimer takes nothing returns nothing
//call BJDebugMsg("Restarting PassTimer")
call TimerStart(PassTimer,PASS_INTERVAL,true,function PassMemoryLeaks)
endfunction
private function Init takes nothing returns nothing
set IndexData = HandleTable.create()
set IsSaved = HandleTable.create()
call StartPassTimer()
endfunction
endlibrary
//TESH.scrollpos=182
//TESH.alwaysfold=0
// GUI-Friendly Damage Detection -- v1.2.1 -- by Weep
// http:// www.thehelper.net/forums/showthread.php?t=137957
//
// Requires: only this trigger and its variables.
//
// -- What? --
// This snippet provides a leak-free, GUI-friendly implementation of an "any unit takes
// damage" event. It requires no JASS knowledge to use.
//
// It uses the Game - Value Of Real Variable event as its method of activating other
// triggers, and passes the event responses through a few globals.
//
// -- Why? --
// The traditional GUI method of setting up a trigger than runs when any unit is damaged
// leaks trigger events. This snippet is easy to implement and removes the need to do
// you own GUI damage detection setup.
//
// -- How To Implement --
// 0. Before you copy triggers that use GDD into a new map, you need to copy over GDD
// with its GDD Variable Creator trigger, or there will be a problem: the variables
// won't be automatically created correctly.
//
// 1. Be sure "Automatically create unknown variables while pasting trigger data" is
// enabled in the World Editor general preferences.
// 2. Copy this trigger category ("GDD") and paste it into your map.
// (Alternately: create the variables listed in the globals block below, create a
// trigger named "GUI Friendly Damage Detection", and paste in this entire text.)
// 3. Create your damage triggers using Game - Value Of Real Variable as the event,
// select GDD_Event as the variable, and leave the rest of the settings to the default
// "becomes Equal to 0.00".
// The event responses are the following variables:
// GDD_Damage is the amount of damage, replacing Event Response - Damage Taken.
// GDD_DamagedUnit is the damaged unit, replacing Event Response - Triggering Unit.
// Triggering Unit can still be used, if you need to use waits.
// Read the -- Notes -- section below for more info.
// GDD_DamageSource is the damaging unit, replacing Event Response - Damage Source.
//
// -- Notes --
// GDD's event response variables are not wait-safe; you can't use them after a wait in
// a trigger. If you need to use waits, Triggering Unit (a.k.a. GetTriggerUnit()) can
// be used in place of GDD_DamageSource. There is no usable wait-safe equivalent to
// Event Damage or Damage Source; you'll need to save the values yourself.
//
// Don't write any values to the variables used as the event responses, or it will mess
// up any other triggers using this snippet for their triggering. Only use their values.
//
// This uses arrays, so can detect damage for a maximum of 8190 units at a time, and
// cleans up data at a rate of 33.33 per second, by default. This should be enough for
// most maps, but if you want to change the rate, change the value returned in the
// GDD_RecycleRate function at the top of the code, below.
//
// By default, GDD will not register units that have Locust at the moment of their
// entering the game, and will not recognize when they take damage (which can only
// happen if the Locust ability is later removed from the unit.) To allow a unit to have
// Locust yet still cause GDD damage events if Locust is removed, you can either design
// the unit to not have Locust by default and add it via triggers after creation, or
// edit the GDD_Filter function at the top of the code, below.
//
// -- Credits --
// Captain Griffin on wc3c.net for the research and concept of GroupRefresh.
//
// Credit in your map not needed, but please include this README.
//
// -- Version History --
// 1.2.1: Minor code cleaning. Added configuration functions. Updated documentation.
// 1.2.0: Made this snippet work properly with recursive damage.
// 1.1.1: Added a check in order to not index units with the Locust ability (dummy units).
// If you wish to check for damage taken by a unit that is unselectable, do not
// give the unit-type Locust in the object editor; instead, add the Locust ability
// 'Aloc' via a trigger after its creation, then remove it.
// 1.1.0: Added a check in case a unit gets moved out of the map and back.
// 1.0.0: First release.
//===================================================================
// Configurables.
function GDD_RecycleRate takes nothing returns real //The rate at which the system checks units to see if they've been removed from the game
return 0.03
endfunction
function GDD_Filter takes unit u returns boolean //The condition a unit has to pass to have it registered for damage detection
return GetUnitAbilityLevel(u, 'Aloc') == 0 //By default, the system ignores Locust units, because they normally can't take damage anyway
endfunction
//===================================================================
// This is just for reference.
// If you use JassHelper, you could uncomment this section instead of creating the variables in the trigger editor.
// globals
// real udg_GDD_Event = 0.
// real udg_GDD_Damage = 0.
// unit udg_GDD_DamagedUnit
// unit udg_GDD_DamageSource
// trigger array udg_GDD__TriggerArray
// integer array udg_GDD__Integers
// unit array udg_GDD__UnitArray
// group udg_GDD__LeftMapGroup = CreateGroup()
// endglobals
//===================================================================
// System code follows. Don't touch!
function GDD_Event takes nothing returns boolean
local unit damagedcache = udg_GDD_DamagedUnit
local unit damagingcache = udg_GDD_DamageSource
local real damagecache = udg_GDD_Damage
set udg_GDD_DamagedUnit = GetTriggerUnit()
set udg_GDD_DamageSource = GetEventDamageSource()
set udg_GDD_Damage = GetEventDamage()
set udg_GDD_Event = 1.
set udg_GDD_Event = 0.
set udg_GDD_DamagedUnit = damagedcache
set udg_GDD_DamageSource = damagingcache
set udg_GDD_Damage = damagecache
set damagedcache = null
set damagingcache = null
return false
endfunction
function GDD_AddDetection takes nothing returns boolean
// if(udg_GDD__Integers[0] > 8190) then
// call BJDebugMsg("GDD: Too many damage events! Decrease number of units present in the map or increase recycle rate.")
// ***Recycle rate is specified in the GDD_RecycleRate function at the top of the code. Smaller is faster.***
// return
// endif
if(IsUnitInGroup(GetFilterUnit(), udg_GDD__LeftMapGroup)) then
call GroupRemoveUnit(udg_GDD__LeftMapGroup, GetFilterUnit())
elseif(GDD_Filter(GetFilterUnit())) then
set udg_GDD__Integers[0] = udg_GDD__Integers[0]+1
set udg_GDD__UnitArray[udg_GDD__Integers[0]] = GetFilterUnit()
set udg_GDD__TriggerArray[udg_GDD__Integers[0]] = CreateTrigger()
call TriggerRegisterUnitEvent(udg_GDD__TriggerArray[udg_GDD__Integers[0]], udg_GDD__UnitArray[udg_GDD__Integers[0]], EVENT_UNIT_DAMAGED)
call TriggerAddCondition(udg_GDD__TriggerArray[udg_GDD__Integers[0]], Condition(function GDD_Event))
endif
return false
endfunction
function GDD_PreplacedDetection takes nothing returns nothing
local group g = CreateGroup()
local integer i = 0
loop
call GroupEnumUnitsOfPlayer(g, Player(i), Condition(function GDD_AddDetection))
set i = i+1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call DestroyGroup(g)
set g = null
endfunction
function GDD_GroupRefresh takes nothing returns nothing
// Based on GroupRefresh by Captain Griffen on wc3c.net
if (bj_slotControlUsed[5063] == true) then
call GroupClear(udg_GDD__LeftMapGroup)
set bj_slotControlUsed[5063] = false
endif
call GroupAddUnit(udg_GDD__LeftMapGroup, GetEnumUnit())
endfunction
function GDD_Recycle takes nothing returns nothing
if(udg_GDD__Integers[0] <= 0) then
return
elseif(udg_GDD__Integers[1] <= 0) then
set udg_GDD__Integers[1] = udg_GDD__Integers[0]
endif
if(GetUnitTypeId(udg_GDD__UnitArray[udg_GDD__Integers[1]]) == 0) then
call DestroyTrigger(udg_GDD__TriggerArray[udg_GDD__Integers[1]])
set udg_GDD__TriggerArray[udg_GDD__Integers[1]] = null
set udg_GDD__TriggerArray[udg_GDD__Integers[1]] = udg_GDD__TriggerArray[udg_GDD__Integers[0]]
set udg_GDD__UnitArray[udg_GDD__Integers[1]] = udg_GDD__UnitArray[udg_GDD__Integers[0]]
set udg_GDD__UnitArray[udg_GDD__Integers[0]] = null
set udg_GDD__Integers[0] = udg_GDD__Integers[0]-1
endif
set udg_GDD__Integers[1] = udg_GDD__Integers[1]-1
endfunction
function GDD_LeaveMap takes nothing returns boolean
local boolean cached = bj_slotControlUsed[5063]
if(udg_GDD__Integers[2] < 64) then
set udg_GDD__Integers[2] = udg_GDD__Integers[2]+1
else
set bj_slotControlUsed[5063] = true
call ForGroup(udg_GDD__LeftMapGroup, function GDD_GroupRefresh)
set udg_GDD__Integers[2] = 0
endif
call GroupAddUnit(udg_GDD__LeftMapGroup, GetFilterUnit())
set bj_slotControlUsed[5063] = cached
return false
endfunction
// ===========================================================================
function InitTrig_GUI_Friendly_Damage_Detection takes nothing returns nothing
local region r = CreateRegion()
call RegionAddRect(r, GetWorldBounds())
call TriggerRegisterEnterRegion(CreateTrigger(), r, Condition(function GDD_AddDetection))
call TriggerRegisterLeaveRegion(CreateTrigger(), r, Condition(function GDD_LeaveMap))
call GDD_PreplacedDetection()
call TimerStart(CreateTimer(), GDD_RecycleRate(), true, function GDD_Recycle)
set r = null
endfunction