SoUI 0.5版本占坑
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

639 lines
26 KiB

3 years ago
#ifndef YDWEMemoryLeakHelperIncluded
#define YDWEMemoryLeakHelperIncluded
#include "YDWEBase.j"
//library ValueIndexing initializer Init requires YDWEBase
library MemoryLeakHelper initializer Init requires YDWEBase
globals
private constant real HASH_DECAY_TIME = 500.
private constant integer CLEAR_HASH_COUNT = 8190
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
private constant integer MAX_INSTANCES=8100 //400000
// 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 = 10
// 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 = true
// 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
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
private struct Index
static method 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 YDWEFlushStoredIntegerByInteger(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
endmethod
static method GetHash takes integer value returns integer
local integer int = YDWEGetIntegerByInteger(key,value)
if int == 0 then
set int = Index.create()
call YDWESaveIntegerByInteger(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
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call YDWEFlushMissionByInteger(0)
call YDWEFlushMissionByInteger(integer(this))
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
endstruct
private struct HandleTable extends GTable
method operator [] takes handle key returns integer
return YDWEGetIntegerByInteger(integer(this), YDWEH2I(key))
endmethod
method operator []= takes handle key, integer value returns nothing
call YDWESaveIntegerByInteger(integer(this), YDWEH2I(key), value)
endmethod
method flush takes handle key returns nothing
call YDWEFlushStoredIntegerByInteger(integer(this), YDWEH2I(key))
endmethod
method exists takes handle key returns boolean
return YDWEHaveSavedIntegerByInteger(integer(this), YDWEH2I(key))
endmethod
static method flush2D takes string firstkey returns nothing
call HandleTable(- YDWES2I(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns HandleTable
return HandleTable(- YDWES2I(firstkey))
endmethod
endstruct
//===========================================================================
// Trigger: MemoryLeakHelper
//
// //hook GetLocationX CatchLocation
// //hook GetLocationY CatchLocation
// //hook ForGroup FG
// 129k
// 3,02 + 43,32 + 697,86
// 744
//===========================================================================
globals
private HandleTable IndexData
private HandleTable IsSaved
private integer CaughtLocationLeaks = 0
private location array LocationLeakData[MAX_LEAK_INSTANCES]
private integer LocationDestroyCount = 0
private location array LocationDestroyData[MAX_LEAK_INSTANCES]
private integer CaughtEffectLeaks = 0
private effect array EffectLeakData[MAX_LEAK_INSTANCES]
private integer EffectDestroyCount = 0
private effect array EffectDestroyData[MAX_LEAK_INSTANCES]
private integer CaughtGroupLeaks = 0
private group array GroupLeakData[MAX_LEAK_INSTANCES]
private integer GroupDestroyCount = 0
private group array GroupDestroyData[MAX_LEAK_INSTANCES]
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
globals
// 用于判断是否开启内存排泄帮助
private boolean IsOpenMemoryLeakHelper = false
private boolean IsDisplayMemoryLeakHelper = false
endglobals
function YDWEMemoryLeakHelperGetLastCaughtHandle takes nothing returns handle
return LastCaught
endfunction
function YDWEMemoryLeakHelperProtectHandle takes handle h returns nothing
set IsSaved[h] = 1
endfunction
function YDWEMemoryLeakHelperProtectVariable takes handle h returns nothing
set IsSaved[h] = 1
endfunction
private function EnableMMH takes nothing returns nothing
set Disabled = false
endfunction
function YDWEMemoryLeakHelperDelayMMH takes nothing returns nothing
set Disabled = true
call TimerStart(DelayTimer,0.00,false,function EnableMMH)
endfunction
function YDWEMemoryLeakHelperDisplayLeaks takes nothing returns nothing
local location loc=null
if IsDisplayMemoryLeakHelper == false then
call CreateNUnitsAtLoc( 1, 'hfoo', Player(15), GetRectCenter(GetPlayableMapRect()), bj_UNIT_FACING )
call RemoveUnit(bj_lastCreatedUnit)
set loc = GetRectCenter(GetPlayableMapRect())
call CreateNUnitsAtLoc( 1, 'hfoo', Player(15), loc, bj_UNIT_FACING )
call RemoveUnit(bj_lastCreatedUnit)
call RemoveLocation(loc)
set loc=null
set IsDisplayMemoryLeakHelper=true
endif
call ClearTextMessages()
call BJDebugMsg("======= 自动排泄系统 =======")
call BJDebugMsg("排除的泄漏"+I2S(DestroyedLeaks))
call BJDebugMsg("用户手动排除的泄漏"+I2S(DestroyedLeaksUser))
call BJDebugMsg("系统排泄比例: "+R2S(I2R(DestroyedLeaks)/I2R(DestroyedLeaks+DestroyedLeaksUser)*100.)+"%")
call BJDebugMsg("作者排泄比例"+R2S(I2R(DestroyedLeaksUser)/I2R(DestroyedLeaks+DestroyedLeaksUser)*100.)+"%")
call BJDebugMsg("下次排除的泄漏"+I2S(MIN_LEAK_NUMBER-CaughtLeaks))
call BJDebugMsg(" === 待排除的泄漏 === ")
call BJDebugMsg(" 单位组泄漏"+I2S(GroupDestroyCount))
call BJDebugMsg(" 点泄漏"+I2S(LocationDestroyCount))
call BJDebugMsg(" 特效泄漏: "+I2S(EffectDestroyCount))
call BJDebugMsg(" === 还未排除的泄漏=== ")
call BJDebugMsg(" 单位组泄漏"+I2S(CaughtGroupLeaks))
call BJDebugMsg(" 点泄漏"+I2S(CaughtLocationLeaks))
call BJDebugMsg(" 特效泄漏: "+I2S(CaughtEffectLeaks))
call BJDebugMsg("下次排泄时间: "+I2S(R2I(TimerGetRemaining(PassTimer)+0.5))+" seconds.")
call BJDebugMsg("======= 自动排泄系统 =======")
call BJDebugMsg("排除泄漏"+I2S(DestroyedLeaks))
//if DISPLAY_SAVED_MEMORY then
call BJDebugMsg("自动排泄系统释放的总内存为 "+R2S(SavedMemory)+" kb.")
//endif
call BJDebugMsg("================================")
endfunction
private function GroupGetMemoryUsageEnum takes nothing returns nothing
set LastCheckedGroupMemoryUsage = LastCheckedGroupMemoryUsage + GROUP_UNIT_MEMORY_USAGE
endfunction
private function GroupGetMemoryUsage takes group g returns real
set LastCheckedGroupMemoryUsage = 0.
call ForGroup(g,function GroupGetMemoryUsageEnum)
return LastCheckedGroupMemoryUsage + GROUP_MEMORY_USAGE
endfunction
private function CatchLocation takes location l returns nothing
set LastCaught = l
if Disabled then
return
elseif CaughtLocationLeaks == 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 Location")
set CaughtLocationLeaks = CaughtLocationLeaks + 1
set LocationLeakData[CaughtLocationLeaks] = l
set IndexData[l] = CaughtLocationLeaks
endif
endfunction
private function AddToLocationDestroyQueue takes location l returns nothing
set LocationDestroyCount = LocationDestroyCount + 1
set LocationDestroyData[LocationDestroyCount] = l
set IndexData[l] = LocationDestroyCount*-1 // Put his to negative, so we know that this is used in the DestroyQueue now.
endfunction
private function ReleaseLocation takes location 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 LocationLeakData[index] = LocationLeakData[CaughtLocationLeaks]
set CaughtLocationLeaks = CaughtLocationLeaks - 1
else
set index = index * -1
set LocationDestroyData[index] = LocationDestroyData[LocationDestroyCount]
set LocationDestroyCount = LocationDestroyCount - 1
endif
call IndexData.flush(l)
set DestroyedLeaksUser = DestroyedLeaksUser + 1
endif
endfunction
private function CatchGroup takes group l returns nothing
set LastCaught = l
if Disabled then
return
elseif CaughtGroupLeaks == 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 Group")
set CaughtGroupLeaks = CaughtGroupLeaks + 1
set GroupLeakData[CaughtGroupLeaks] = l
set IndexData[l] = CaughtGroupLeaks
endif
endfunction
private function AddToGroupDestroyQueue takes group l returns nothing
set GroupDestroyCount = GroupDestroyCount + 1
set GroupDestroyData[GroupDestroyCount] = l
set IndexData[l] = GroupDestroyCount*-1 // Put his to negative, so we know that this is used in the DestroyQueue now.
endfunction
private function ReleaseGroup takes group 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 GroupLeakData[index] = GroupLeakData[CaughtGroupLeaks]
set CaughtGroupLeaks = CaughtGroupLeaks - 1
else
set index = index * -1
set GroupDestroyData[index] = GroupDestroyData[GroupDestroyCount]
set GroupDestroyCount = GroupDestroyCount - 1
endif
call IndexData.flush(l)
set DestroyedLeaksUser = DestroyedLeaksUser + 1
endif
endfunction
private function CatchEffect takes effect l returns nothing
set LastCaught = l
if Disabled then
return
elseif CaughtEffectLeaks == 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 Effect")
set CaughtEffectLeaks = CaughtEffectLeaks + 1
set EffectLeakData[CaughtEffectLeaks] = l
set IndexData[l] = CaughtEffectLeaks
endif
endfunction
private function AddToEffectDestroyQueue takes effect l returns nothing
set EffectDestroyCount = EffectDestroyCount + 1
set EffectDestroyData[EffectDestroyCount] = l
set IndexData[l] = EffectDestroyCount*-1 // Put his to negative, so we know that this is used in the DestroyQueue now.
endfunction
private function ReleaseEffect takes effect 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 EffectLeakData[index] = EffectLeakData[CaughtEffectLeaks]
set CaughtEffectLeaks = CaughtEffectLeaks - 1
else
set index = index * -1
set EffectDestroyData[index] = EffectDestroyData[EffectDestroyCount]
set EffectDestroyCount = EffectDestroyCount - 1
endif
call IndexData.flush(l)
set DestroyedLeaksUser = DestroyedLeaksUser + 1
endif
endfunction
private function DestroyMemoryLeaks takes nothing returns nothing
set IsDestroying = true
set DestroyedLeaks = DestroyedLeaks + GroupDestroyCount
loop
exitwhen GroupDestroyCount == 0
if DISPLAY_SAVED_MEMORY then
set SavedMemory = SavedMemory + GroupGetMemoryUsage(GroupDestroyData[GroupDestroyCount])
endif
call DestroyGroup(GroupDestroyData[GroupDestroyCount])
call IndexData.flush(GroupDestroyData[GroupDestroyCount])
set GroupDestroyCount = GroupDestroyCount - 1
endloop
set DestroyedLeaks = DestroyedLeaks + LocationDestroyCount
loop
exitwhen LocationDestroyCount == 0
if DISPLAY_SAVED_MEMORY then
set SavedMemory = SavedMemory + LOCATION_MEMORY_USAGE
endif
call RemoveLocation(LocationDestroyData[LocationDestroyCount])
call IndexData.flush(LocationDestroyData[LocationDestroyCount])
set LocationDestroyCount = LocationDestroyCount - 1
endloop
set DestroyedLeaks = DestroyedLeaks + EffectDestroyCount
loop
exitwhen EffectDestroyCount == 0
if DISPLAY_SAVED_MEMORY then
set SavedMemory = SavedMemory + EFFECT_MEMORY_USAGE
endif
call DestroyEffect(EffectDestroyData[EffectDestroyCount])
call IndexData.flush(EffectDestroyData[EffectDestroyCount])
set EffectDestroyCount = EffectDestroyCount - 1
endloop
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("YDWEMemoryLeakStartPassTimer")
endfunction
private 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
set CaughtLeaks = CaughtLeaks + CaughtGroupLeaks
loop
exitwhen CaughtGroupLeaks < 1
if IsSaved.exists(GroupLeakData[CaughtGroupLeaks]) == false and GroupLeakData[CaughtGroupLeaks] != null then
call AddToGroupDestroyQueue(GroupLeakData[CaughtGroupLeaks])
endif
set GroupLeakData[CaughtGroupLeaks] = null
set CaughtGroupLeaks = CaughtGroupLeaks - 1
endloop
set CaughtLeaks = CaughtLeaks + CaughtLocationLeaks
loop
exitwhen CaughtLocationLeaks < 1
if IsSaved.exists(LocationLeakData[CaughtLocationLeaks]) == false and LocationLeakData[CaughtLocationLeaks] != null then
call AddToLocationDestroyQueue(LocationLeakData[CaughtLocationLeaks])
endif
set LocationLeakData[CaughtLocationLeaks] = null
set CaughtLocationLeaks = CaughtLocationLeaks - 1
endloop
set CaughtLeaks = CaughtLeaks + CaughtEffectLeaks
loop
exitwhen CaughtEffectLeaks < 1
if IsSaved.exists(EffectLeakData[CaughtEffectLeaks]) == false and EffectLeakData[CaughtEffectLeaks] != null then
call AddToEffectDestroyQueue(EffectLeakData[CaughtEffectLeaks])
endif
set EffectLeakData[CaughtEffectLeaks] = null
set CaughtEffectLeaks = CaughtEffectLeaks - 1
endloop
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 YDWEMemoryLeakStartPassTimer takes nothing returns nothing
//call BJDebugMsg("Restarting PassTimer")
call TimerStart(PassTimer,PASS_INTERVAL,true,function PassMemoryLeaks)
endfunction
// 开启接口函数
function YDWEMemoryLeakHelperMapIsOpenMemoryLeakHelper takes boolean IsOpenMemoryLeak returns nothing
set IsOpenMemoryLeakHelper = IsOpenMemoryLeak
call PauseTimer(PassTimer)
if ( IsOpenMemoryLeak ) then
call YDWEMemoryLeakStartPassTimer()
endif
endfunction
function YDWEMemoryLeakHelperProtectLastCaughtHandle takes nothing returns nothing
call YDWEMemoryLeakHelperProtectHandle(YDWEMemoryLeakHelperGetLastCaughtHandle())
endfunction
function YDWEMemoryLeakHelperProtectLocation takes location loc returns nothing
call YDWEMemoryLeakHelperProtectHandle(loc)
endfunction
function YDWEMemoryLeakHelperProtectGroup takes group g returns nothing
call YDWEMemoryLeakHelperProtectHandle(g)
endfunction
function YDWEMemoryLeakHelperProtectEffect takes effect e returns nothing
call YDWEMemoryLeakHelperProtectHandle(e)
endfunction
private function Init takes nothing returns nothing
set IndexData = HandleTable.create()
set IsSaved = HandleTable.create()
//call YDWEMemoryLeakStartPassTimer()
call TimerStart(GameTimeTimer,GAMETIME_TIMER_INTERVAL,true,function UpdateGameTime)
endfunction
endlibrary
#endif /// YDWEMemoryLeakHelperIncluded