| 08-05-2008, 11:57 PM | #1 |
A lightweight and leakless damage detection system. Works by swapping over to another trigger, disabling the old one, and then destroying it 600 seconds later. May not be perfectly safe, but commenting out one line can turn it to an efficient and safe (as far as we know) but leaking damage detection system, for rapid debugging or if problems appear. Pros: - Does not leak handle indexes or objects. - Provides a fast base system for anything else to be added on. - Avoids known and identified handle stack corruption issues (read directions below). - Easy to use and very efficient. Cons: - May not be safe. We simply don't know enough. - Only allows conditions (return booleans); faster, no real reason to use actions (could easily be converted). (NB: The only advantage of this would be allowing sleeps, which interfer with other actions, so you'd need to add all actions to their own triggers and execute them.) - The swapping may cause a lag spike. Didn't for me with an empty map with 2000 units, but if the map is already near the limit, it may cause issues. NB: Do not destroy any conditions past to this system. Heck, don't destroy conditions or boolexprs ever, anywhere, since there is no need and it can cause unrelated code to fail. Example Code: JASS:scope Example initializer Init private function Test takes nothing returns boolean call BJDebugMsg("Damage: " + I2S(R2I(GetEventDamage()+0.5))) return false endfunction private function Text takes nothing returns boolean local texttag t = CreateTextTag() call SetTextTagColor(t, 255, 150, 150, 175) call SetTextTagText(t, R2S(GetEventDamage()), 0.025) call SetTextTagPos(t, GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), 0.00) call SetTextTagVelocity(t, 0, 0.03) call SetTextTagVisibility(t, true) call SetTextTagFadepoint(t, 2) call SetTextTagLifespan(t, 3) call SetTextTagPermanent(t, false) return false endfunction private function Init takes nothing returns nothing call AddOnDamageFunc(Condition(function Text)) call AddOnDamageFunc(Condition(function Test)) endfunction endscope System Code: JASS:library LightLeaklessDamageDetect initializer Init // Creating threads off of this that last longer than the timeout below will likely cause issues, like everything blowing up (handle stack corruption) // It seems that threads created by timers, rather than executefunc / .evaluate / .execute are not affected. Any threads created from the timer thread are fine. // This being safe with even the usage laid out above isn't guarenteed. Use at own risk. // If you start getting random bugs, see if commenting out the timer line below (see comments) helps // If it does, report it in the thread for this script at [url]www.wc3campaigns.net[/url] globals private constant real SWAP_TIMEOUT = 600. // keep high; 600 should be about the right balance. endglobals globals private conditionfunc array func private integer funcNext = 0 private trigger current = null private trigger toDestroy = null private group swapGroup private rect mapRect endglobals // One of the only accessible functions. Use it to add a condition. Must return boolean type, and then have return false at the end. // Note that it's technically a condition, so if you put a wait in there, it'll die. But waits are lame anyway. function AddOnDamageFunc takes conditionfunc cf returns nothing call TriggerAddCondition(current, cf) set func[funcNext] = cf set funcNext = funcNext + 1 endfunction // These inline. For avoiding feedback loops. Feel free to make your own wrapper function for damage functions using this. function DisableDamageDetect takes nothing returns nothing call DisableTrigger(current) endfunction function EnableDamageDetect takes nothing returns nothing call EnableTrigger(current) endfunction // no more accessible functions, folks. //! textmacro CGLeaklessDamageDetectAddFilter takes UNIT // add here any conditions to add the unit to the trigger, example below, commented out: // if GetUnitTypeId($UNIT$) != 'h000' then // where 'h000' is a dummy unit call TriggerRegisterUnitEvent(current, $UNIT$, EVENT_UNIT_DAMAGED) // endif //! endtextmacro private function AddEx takes nothing returns boolean //! runtextmacro CGLeaklessDamageDetectAddFilter("GetFilterUnit()") return false endfunction private function Enters takes nothing returns boolean //! runtextmacro CGLeaklessDamageDetectAddFilter("GetTriggerUnit()") return false endfunction private function Swap takes nothing returns nothing local integer i = 0 local boolean b = IsTriggerEnabled(current) call DisableTrigger(current) if toDestroy != null then call DestroyTrigger(toDestroy) endif set toDestroy = current set current = CreateTrigger() if not(b) then call DisableTrigger(current) endif call GroupEnumUnitsInRect(swapGroup, mapRect, Filter(function AddEx)) loop exitwhen i >= funcNext call TriggerAddCondition(current, func[i]) set i = i + 1 endloop endfunction private function Init takes nothing returns nothing local trigger t = CreateTrigger() local region r = CreateRegion() local integer i = 0 set mapRect = GetWorldBounds() call RegionAddRect(r, mapRect) call TriggerRegisterEnterRegion(t, r, null) call TriggerAddCondition(t, Condition(function Enters)) set swapGroup = CreateGroup() set current = CreateTrigger() loop exitwhen i >= funcNext call TriggerAddCondition(current, func[i]) set i = i + 1 endloop call GroupEnumUnitsInRect(swapGroup, GetWorldBounds(), Filter(function AddEx)) // Commenting out the next line will make the system leak indexes and events, but should make it safer. call TimerStart(CreateTimer(), SWAP_TIMEOUT, true, function Swap) endfunction endlibrary |
| 08-06-2008, 01:45 AM | #2 |
Pros: 1) it leaks GetWorldBounds() and there is no cleanup functions. 2) standart trigger condition based system, its is not faster then others. 3 and 4) There is no prove provided Cons: 1) Actually it is safe, since no triggersleep allowed in trigger conditions. 2) There is no code type array allowed, this system cannot be moved to actions. 3) NC |
| 08-06-2008, 01:55 AM | #3 |
Does GetWorldBounds() return a new rect every time? I thought it just returned the same one over and over. If so, you lose a pro Griffen :< |
| 08-06-2008, 02:00 AM | #4 |
GetWorldBounds() == GetWorldBounds() will return FALSE |
| 08-06-2008, 02:00 AM | #5 |
You could use bj_mapInitialPlayableArea, since GetWorldBounds() probably has areas in it that units can't be created in anyways. (Could be wrong, just a suggestion) And is there a way to filter what units get detected? Because it would be kind of stupid to register dummy units and the likes since they appear in the map (thus being registered) and go away pretty quickly, wasting function calls. |
| 08-06-2008, 02:01 AM | #6 | |
Quote:
|
| 08-06-2008, 04:08 AM | #7 |
Lame != Wrong, remember this. |
| 08-06-2008, 09:45 AM | #8 |
// Returns full map bounds, including unplayable borders, in world coordinates native GetWorldBounds takes nothing returns rect kind a wtf?! why everyone is posting his own "solution" for the same problem XXXXXX times? imo such stuff must be posted in trigger/script forums. not as a submission -_- just imagine some user wants to download some dd system from db and he find couple of them and he have no idea what to choose... some are "better" but simple the other one more complicated but offers bit less performance. well MAKE SOME MAPS. =) and the Swap function is so omfg... n/c |
| 08-06-2008, 10:25 AM | #9 | |
Nifty system. Quote:
JASS:call TriggerRegisterUnitEvent(current, GetTriggerUnit(), EVENT_UNIT_DAMAGED) JASS:if GetUnitTypeId(GetTriggerUnit())!='whateverID' then call TriggerRegisterUnitEvent(current, GetTriggerUnit(), EVENT_UNIT_DAMAGED) endif @DioD: This system can be move to triggeraction. Very easily. And by the way are conditions the fastest way i know of. If you can come up with something faster, i suggest you create another Damage Detection system for yourself. |
| 08-06-2008, 10:40 AM | #10 | ||||||||
Quote:
Okay, Blizzard fails... Thanks for the spot, I'll fix it. Quote:
Did I say it was faster? Quote:
3, feel free to go look up what we know, but I'm open about not knowing if it is perfectly safe or no. 4 needs no proof, it's self-evident, so unless you can make it Quote:
Actually tested the identified bug with thread created off it from TriggerExecute/ExecuteFunc? Tests indicate it is. The bigger worry is that we don't have any actual proof that even without waits DestroyTrigger is safe; it makes it safer, but it could just be we haven't found the smoking gun. Quote:
Either I2C, or just running a constant trigger from a changing one. Quote:
I'll just use a private global for it, better safe than sorry. Quote:
Editing Enters and AddEx. I'll document that. Quote:
This system does stuff in a substantially different (probably improved, unless blizzard has some lameness stuck up there somewhere we haven't found yet) way. We have Dusk's Intuitive Damage Detection, designed for working with all triggered spells, and Vex's Attack Detection System (probably outdated?). This is very distinct from both. |
| 08-06-2008, 11:10 AM | #11 | |
Deaod Code:
loop
exitwhen i >= funcNext
call TriggerAddCondition(current, [u]func[i][/u])
set i = i + 1
endloopCannot be done with trigger actions, no code array allowed. Captain Griffen Quote:
I2C Async, crush on natives, cause integer overflow, corrupts arrays. Code:
private function Swap takes nothing returns nothing
local integer i = 0
call DisableTrigger(current)
set toDestroy = current
set current = CreateTrigger()
call GroupEnumUnitsInRect(swapGroup, GetWorldBounds(), Filter(function AddEx))
loop
exitwhen i >= funcNext
call TriggerAddCondition(current, func[i])
set i = i + 1
endloop
call TriggerSleepAction(0.0)
if toDestroy != null then
call DestroyTrigger(toDestroy)
endif
endfunction |
| 08-06-2008, 11:33 AM | #12 | |
Quote:
I2C shouldn't be asyncronous if using a C2I to start with. C2I may or may not crush on natives; setting a native to a code variable crashes the game. As for integer overflow, that might well be possible, but I'm not sure. Test it. |
| 08-06-2008, 01:53 PM | #13 |
Captain Griffen there is also grim's dd system =) // and maybe some others i don't remember. I2C Async - why it's async ? =) wc3 was own virtual machine so it can't depend on some CPU specifics. + every .j file must be the same for all players including functions. (the call might be local but i doubt what it will change the id of code...) we need prove for Async =) the rest is shit and known. (int overflow etc.) |
| 08-06-2008, 04:34 PM | #14 |
Blah, I will never, ever approve anything that claims some logic behind using DestroyTrigger(). If this recycled units of the same unit type rather than destroying triggers (Regardless of the hocus pocus used to ensure it's "safe" to do so), then I might look at it past that. Honestly, though, this is kind of... Misleading. A damage detection system as has become known over the years is something that allows distinguishing of types of damage and gives a semblence of control over the map's damage. This really just provides a way to register the EVENT_UNIT_DAMAGED event without leaking events, but it relies on dynamic triggers to do so. Lame. Also, use bj_mapInitialPlayableArea, it's better. |
| 08-06-2008, 06:58 PM | #15 |
How come dota uses a huge amount of dynamic triggers and releases them by disabling and removing after 60 seconds? Btw each event takes up about 400 bytes. |
