• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] DestructableHider

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
JASS:
library DestructableHider initializer init

    /*
        by Zwiebelchen      v1.3
    
            Destructables create an enormous amount of overhead on warcraft III maps, almost the same as units, especially walkable destructables.
            Thus, a large amount of destructables creates a huge drop of FPS in the game, even on fast machines, due to the poor engine of WC3.
            This effect is fairly noticable at an amount of even less than 1000 destructables, which is reached very fast when using invisible platforms.
            Warcraft III automaticly hides units outside the screen to save performance, however it does not do so for destructables, for unknown reasons.
            
            The purpose of this fully automatic system is to hide all those destructables, that are currently not viewed anyway, to save a lot of processing time.
            To do that, the entire map is splitted into tiles of an editable size and all destructables within those tiles are stored into a table, to allow fast access.
            When a tile is viewed, all destructables on adjacent tiles will also be shown, so that moving the camera doesnt create ugly popup effects when the center of the view is on the edge of a tile.
            
            However, there are some rules you need to consider, in order to make your map work without desyncs in multiplayer:
            - never hide destructables units or players can interact with (attackable, selectable or destructable)
            - hiding destructables that block pathing is safe, as hiding the destructable will not change its pathing
            - hiding destructables that need to be enumed is safe; hidden destructables can be enumerated
            - hiding walkable platforms is also safe, as long as you dont get a location Z for a location placed on that destructable globally; this is not 100% safe anyway, as
              the returned value of GetLocationZ() is dependant on the render state of the destructable
            
            
            API:
                    
                private function filt returns boolean
                    - add custom filter code for the automatic enumeration of destructables on map init inside
                    - if all destructables should be added to the system, let it return true
                    - example:
                        private function filt returns boolean
                            return GetDestructableMaxLife(GetFilterDestructable()) == 1
                        endfunction
                        -> automaticly adds all destructables on the map with a maximum life of 1 on map init to the system
            
            
            Optional:
            
                public function register takes destructable returns nothing
                    - adds a destructable to the system, also hides/shows the destructable depending on the position of the camera
                
                public function unregister takes destructable returns nothing
                    - removes a destructable from the system, also unhides the destructable in case it was hidden
            
            
    */
    
globals
    //==== CONFIGURABLES ====
    private constant real INTERVAL = 0.1 //Update interval in seconds.
                                         //[in multiplayer, the camera positions will only get updated every 0.05-0.1 seconds, so setting it to a lower value than 0.05 makes no sense]
                                         //[update frequency can be much higher in single player mode!]
    private constant integer DRAW_DISTANCE = 512 //the radius around the camera target in which the tiles are considered visible; should be about the same as sight radius (not diameter) of the camera; for 3d cams, use the FarZ value
                                                 //Use multiples of 1024 for maximum efficiency on square division. Recommended value: 5120
    private constant integer TILE_RESOLUTION = 4 //amount of tiles spread over DRAW_DISTANCE
                                        //- higher resolution = more overhead to incrementing loop variables, but less amounts of destructables checked when moving the camera
                                        //- lower resolution = less overhead to incrementing loop variables, but higher amounts of destructables checked when moving the camera
                                        //-> Recommended value: 8-12
    //==== END OF CONFIGURABLES ====
    
    private hashtable hash = InitHashtable()
    private integer columns = 0
    private integer rows = 0
    private integer lastrow = 0
    private integer lastcolumn = 0
    private integer lastid = 0
    private real mapMinX = 0
    private real mapMinY = 0
    private constant integer TILESIZE = DRAW_DISTANCE/TILE_RESOLUTION
endglobals

private function filt takes nothing returns boolean
    //Add code for the enum filter of the automatic registration of destructables on map init
    
    //example:
    //return GetDestructableMaxLife(GetFilterDestructable()) == 1
    //-> automaticly adds all destructables on the map with a maximum life of 1 on map init to the system
    return true
endfunction

public function register takes destructable d returns nothing
    local integer id = R2I((GetDestructableY(d)-mapMinY)/TILESIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILESIZE)
    local integer count = LoadInteger(hash, id, 0)+1
    call SaveInteger(hash, id, 0, count)
    call SaveDestructableHandle(hash, id, count, d)
    call ShowDestructable(d, LoadBoolean(hash, id, -1)) //match visibility state
    call SaveInteger(hash, GetHandleId(d), 0, count) //store the list position for fast lookup
endfunction

public function unregister takes destructable d returns nothing
    local integer id = R2I((GetDestructableY(d)-mapMinY)/TILESIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILESIZE)
    local integer count = LoadInteger(hash, id, 0)
    local integer a = LoadInteger(hash, GetHandleId(d), 0)
    local destructable temp
    if a < count then //move the last in list up to this slot
        set temp = LoadDestructableHandle(hash, id, count)
        call SaveDestructableHandle(hash, id, a, temp)
        call SaveInteger(hash, GetHandleId(temp), 0, a) //update list position
        set temp = null
    endif
    call RemoveSavedHandle(hash, id, count) //clean up the deserted slot
    call SaveInteger(hash, id, 0, count-1)
    call FlushChildHashtable(hash, GetHandleId(d)) //clean up list position
    call ShowDestructable(d, true) //make sure its shown again in case it was hidden
endfunction

private function autoregister takes nothing returns nothing
    local destructable d = GetEnumDestructable()
    local integer id = R2I((GetDestructableY(d)-mapMinY)/TILESIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILESIZE)
    local integer count = LoadInteger(hash, id, 0)+1
    call SaveInteger(hash, id, 0, count)
    call SaveDestructableHandle(hash, id, count, d)
    call ShowDestructable(d, false) //initially hide everything
    call SaveInteger(hash, GetHandleId(d), 0, count) //store the list position for fast lookup
    set d = null
endfunction

private function EnumGrid takes integer x1, integer x2, integer y1, integer y2, boolean show returns nothing
    local integer a = x1
    local integer b
    local integer j
    local integer id
    local integer count
    loop
        set b = y1
        exitwhen a > x2
        loop
            exitwhen b > y2
            set id = b*columns+a
            call SaveBoolean(hash, id, -1, show)
            set count = LoadInteger(hash, id, 0)
            set j = 0
            loop
                exitwhen j >= count
                set j = j + 1
                call ShowDestructable(LoadDestructableHandle(hash, id, j), show)
            endloop
            set b = b + 1
        endloop
        set a = a + 1
    endloop
endfunction

private function ChangeTiles takes integer r, integer c, integer lr, integer lc returns nothing
    local integer AminX = c-TILE_RESOLUTION
    local integer AmaxX = c+TILE_RESOLUTION
    local integer AminY = r-TILE_RESOLUTION
    local integer AmaxY = r+TILE_RESOLUTION
    local integer BminX = lc-TILE_RESOLUTION
    local integer BmaxX = lc+TILE_RESOLUTION
    local integer BminY = lr-TILE_RESOLUTION
    local integer BmaxY = lr+TILE_RESOLUTION
    //border safety:
    if AminX < 0 then
        set AminX = 0
    endif
    if AminY < 0 then
        set AminY = 0
    endif
    if BminX < 0 then
        set BminX = 0
    endif
    if BminY < 0 then
        set BminY = 0
    endif
    if AmaxX >= columns then
        set AmaxX = columns-1
    endif
    if AmaxY >= rows then
        set AmaxX = rows-1
    endif
    if BmaxX >= columns then
        set BmaxX = columns-1
    endif
    if BmaxY >= rows then
        set BmaxX = rows-1
    endif
    
    if BmaxX < AminX or AmaxX < BminX or BmaxY < AminY or AmaxY < BminY then
        call EnumGrid(AminX, AmaxX, AminY, AmaxY, true)
        call EnumGrid(BminX, BmaxX, BminY, BmaxY, false)
    else
        if c >= lc then
            if c != lc then
                call EnumGrid(BmaxX+1, AmaxX, AminY, AmaxY, true)
                call EnumGrid(BminX, AminX-1, BminY, BmaxY, false)
            endif
            if AminY < BminY then
                call EnumGrid(AminX, BmaxX, AmaxY+1, BmaxY, false)
                call EnumGrid(AminX, BmaxX, AminY, BminY-1, true)
            elseif BminY < AminY then
                call EnumGrid(AminX, BmaxX, BmaxY+1, AmaxY, true)
                call EnumGrid(AminX, BmaxX, BminY, AminY-1, false)
            endif
        else
            call EnumGrid(AminX, BminX-1, AminY, AmaxY, true)
            call EnumGrid(AmaxX+1, BmaxX, BminY, BmaxY, false)
            if AminY < BminY then
                call EnumGrid(BminX, AmaxX, AminY, BminY-1, true)
                call EnumGrid(BminX, AmaxX, AmaxY+1, BmaxY, false)
            elseif BminY < AminY then
                call EnumGrid(BminX, AmaxX, BminY, AminY-1, false)
                call EnumGrid(BminX, AmaxX, BmaxY+1, AmaxY, true)
            endif
        endif
    endif
endfunction

private function periodic takes nothing returns nothing
    local integer row = R2I((GetCameraTargetPositionY()-mapMinY)/TILESIZE)
    local integer column = R2I((GetCameraTargetPositionX()-mapMinX)/TILESIZE)
    local integer id = row*columns + column
    if id == lastid then //only check for tiles if the camera has left the last tile
        return
    endif
    call ChangeTiles(row, column, lastrow, lastcolumn)
    set lastrow = row
    set lastcolumn = column
    set lastid = id
endfunction

private function init takes nothing returns nothing
    set mapMinX = GetRectMinX(bj_mapInitialPlayableArea)
    set mapMinY = GetRectMinY(bj_mapInitialPlayableArea)
    set lastrow = R2I((GetCameraTargetPositionY()-mapMinY)/TILESIZE)
    set lastcolumn = R2I((GetCameraTargetPositionX()-mapMinX)/TILESIZE)
    set rows = R2I((GetRectMaxY(bj_mapInitialPlayableArea)-mapMinY)/TILESIZE)+1
    set columns = R2I((GetRectMaxX(bj_mapInitialPlayableArea)-mapMinX)/TILESIZE)+1
    if lastcolumn <= columns/2 then //to make sure the game starts with a full make-visible enum of all destructables on screen
        set lastcolumn = columns-1
    else
        set lastcolumn = 0
    endif
    if lastrow <= rows/2 then
        set lastrow = rows-1
    else
        set lastrow = 0
    endif
    set lastid = lastrow*columns + lastcolumn
    call EnumDestructablesInRect(bj_mapInitialPlayableArea, Filter(function filt), function autoregister)
    call TimerStart(CreateTimer(), INTERVAL, true, function periodic)
    call periodic() //to make sure the destructables on screen after the map loading process finishes are initially shown
endfunction

endlibrary

EDIT:
I forgot to change the documentation inside the demo map. Hiding destructables that block pathing is SAFE! Don't worry about it, the pathing is not affected at all!
 

Attachments

  • DestructableHider.w3x
    31.9 KB · Views: 518
Last edited:
Ah, so that is the method you were talking about. I was considering something similar, using rects. However, I see that you hashed them to avoid enumeration (so you don't need rects), gj creative.

However, for a system like this we'll probably need someone to run some benchmarks with /fps. (of any kind, it doesn't necessarily have to be a stress-test) I tried doing something similar to this on a map with like 5000 invisible destructables and the results were kinda so-so, they were usually within 3-4 fps of not having the system at all (either above or below). Although, your technique most likely has a lot less overhead since it doesn't constantly enumerate, so the results may be different. :)

Good job overall though.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
That was basically what i have in mind.
That's probably enough in the current state but you could even improve by using a MAX number : if the number of not hidden destructables is lower than this constant number, then you don't have to bother to hide destructables.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Updated to v1.1; fixed a small bug that caused the last center tile not to hide correctly.
Also fixed comments (south is north and north is south, due to the tiles starting from the bottom left corner of the map, not the top left corner). Also added a debug mode counter that shows how many destructables got hidden and shown each update.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Just to prove what this thing is capable of:

I implemented it in Gaias Retaliation, edge size 6144, 0.05s ticks.
Almost 900 destructables, 750 of them platforms. 480x256 map size, 50.000 doodads.

The system only hides those destructables, nothing else. This is what happens in the opening scene (you know, when you spawn your level 1 hero at the inn):

Left picture: without the system. Right picture: with the system. Note that those values are pretty stable, with a fluctuation of less than +/- 3 fps on my machine. Also, what this doesn't show: without the system, there are short random freezes due to fps drop spikes. With the system activated, this is totally gone; camera movement is 100% smooth, no fps spikes at all.

attachment.php


For rpgs, this thing is nothing less than a miracle.

That was basically what i have in mind.
That's probably enough in the current state but you could even improve by using a MAX number : if the number of not hidden destructables is lower than this constant number, then you don't have to bother to hide destructables.
I don't think that would actually be neccesary. This thing sucks almost no performance at all, as it only updates if you leave the current tile you are looking at. And everything is simple maths operations. No powers, no squareroots, no handles.
Well I could improve it by splitting the hide and show process over seperate ticks/threads. The amount of processing time would be the same, but it evens out over time better than the current approach. Not that anyone would ever notice a difference...

EDIT:
I also did some further testing. It seems that the pathing of hidden destructables remains intact, so this is safe to use on pathing blockers aswell.
 

Attachments

  • FPSOwnage.jpg
    FPSOwnage.jpg
    114.1 KB · Views: 2,757
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Nice!

Does this work in multiplayer?
It works fine - as long as you exclude targetable destructables.

Also, you should probably give a way to create/remove destructables in game with custom functions (hooks are not enough)
You can always do so by using ordinary natives. No need for custom functions here.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
No, destructables are "picked up" only once, on init.
The destructables which are created later are not considered, plus destructables which are removed are not removed of the lists.
Hmm, correct. Then again, when would you ever need that? I can probably write an unregister function and make the register function public, if you wish.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Don't ask me, i hardly open the editor these times, and since months or even years in fact.

It's just something easy to add and could be useful.

Oh and btw it should be a mistake but the function "register" is public, while it should be private.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
If i would had made a such resource i would use a config function where you manually put each rawcode of destructable.
Now using directly the object editor, i'm just not sure the max life is an appropriate choice, i mean if you open the map (especially if it's since a while you have not opened it) that wouldn't be obvious.
The script way seems more reliable imho, but maybe more annoying, idk.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
If i would had made a such resource i would use a config function where you manually put each rawcode of destructable.
Now using directly the object editor, i'm just not sure the max life is an appropriate choice, i mean if you open the map (especially if it's since a while you have not opened it) that wouldn't be obvious.
The script way seems more reliable imho, but maybe more annoying, idk.
Sure, it might not be a clean method to set up the registry, but it's very elegant and efficient to do so. Well, if I leave it to the player to register/unregister the destructables, there's no need for this anymore anyway.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Sure, it might not be a clean method to set up the registry, but it's very elegant and efficient to do so. Well, if I leave it to the player to register/unregister the destructables, there's no need for this anymore anyway.

What do you mean by "set up the registry" ?

I think there is a need to this automatic behavior, just because i suppose most of these destructables if not all would be "preplaced" most of times.
The reason i suggest a way to handle destructables which are created/removed later is just because you can, not because i think about a useful case.
 
The max life thing is the only thing I hate here :/

Registering types would be much better in my opinion ^_^
Registering specific destructables /could/ be useful.

edit
register shouldn't be a public function, you should have a function called RegisterDestructableType or something that takes a raw code and another function called RegisterDestructable that takes a specific destructable.

Your current register function could be changed to take a destructable so that you can make your enum function call register(GetEnumDestructable()).
You wouldn't notice a speed difference because these are done on map init.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
UPDATE.
Added a register and unregister function.

The max life thing is the only thing I hate here :/

Registering types would be much better in my opinion ^_^
I like it for how it avoids useless registry masses. Simply click the destructable you like in the object editor a change a value. Easy as that. Much better than having annoying long registry lists, if you ask me - especially as you can't change the maxlife of a destructable ingame anyway, so it's kind of a static value that never changes throughout the game and thus relyable.

Also, its way more GUI friendly. And I think that should be a valid point, as the system is full-automatic and because of that interesting for GUIers.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
It's still some kind of "magic" value, i mean the link is not obvious.
Plus, you won't see quickly which ones are considered or not (unless you don't change the max life for other destructables).
But meh i won't argue more about it.

EDIT : Aha the GUI argument, the worst one ever :p
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
It's still some kind of "magic" value, i mean the link is not obvious.
Plus, you won't see quickly which ones are considered or not (unless you don't change the max life for other destructables).
But meh i won't argue more about it.
Well ... if you have a list of cryptic type IDs, you won't know which ids belong to which destructable without checking the object editor either. I think this argument is invalid. ;)
I implemented it that way because, well, I like my systems pragmatic and with as little as possible effort from myself to work.
Assuming you create new destructables in the object editor, it's just fire-and-forget; put in the value and you're done. With a registry, I'd need to Ctrl+D, remember the code, go into trigger editor, search the registry and stuff it in. It's not much but still takes some time and effort. ^^

@Mag: My bad, gonna rename it in case I need to do another update.
 
Or you could CnP this:

JASS:
library DestructableHider initializer init

    /*
        by Zwiebelchen      v1.2
    
            Destructables create an enormous amount of overhead on warcraft III maps, almost the same as units, especially walkable destructables.
            Thus, a large amount of destructables creates a huge drop of FPS in the game, even on fast machines, due to the poor engine of WC3.
            This effect is fairly noticable at an amount of even less than 1000 destructables, which is reached very fast when using invisible platforms.
            Warcraft III automaticly hides units outside the screen to save performance, however it does not do so for destructables, for unknown reasons.
            
            The purpose of this fully automatic system is to hide all those destructables, that are currently not viewed anyway, to save a lot of processing time.
            To do that, the entire map is splitted into tiles of an editable size and all destructables within those tiles are stored into a table, to allow fast access.
            When a tile is viewed, all destructables on adjacent tiles will also be shown, so that moving the camera doesnt create ugly popup effects when the center of the view is on the edge of a tile.
            
            However, there are some rules you need to consider, in order to make your map work without desyncs in multiplayer:
            - never hide destructables units or players can interact with (attackable, selectable or destructable)
            - never hide destructables that block pathing, as hiding a blocking destructable will fuck up the warcraft III pathing
            - hiding destructables that need to be enumed is safe; hidden destructables can be enumerated
            - hiding walkable platforms is also safe, as long as you dont get a location Z for a location placed on that destructable globally; this is not 100% safe anyway
            
            In order for the system to register a destructable to this system, you need to set the maximum life of this destructable to the "checklife" value in the systems globals.
            All destructables with that life number will be hidden/shown by the system. All others will be ignored.
            
            Optional API:
            
                public function register takes destructable returns nothing
                    adds a destructable to the system; the destructable remains visible until viewed once by the player
                
                public function unregister takes destructable returns nothing
                    removes a destructable from the system, also unhides the destructable in case it was hidden
            
            
    */
    
globals
    //==== CONFIGURABLES ====
    private constant real CHECK_LIFE = 37 //all destructables that shall be hidden by the system must have this maximum life value set in the object editor (otherwise will be ignored by the system)
                                         //simply select a number you won't need in your map
    private constant real INTERVAL = 0.5 //the refreshing rate of the system (recommended: 0.05-0.1)
    private constant real TILE_SIZE = 512 //the edge size of the tiles; should be about the same as sight radius (not diameter) of the camera; for 3d cams, use the FarZ value
    //==== END OF CONFIGURABLES ====
    
    private hashtable hash = InitHashtable()
    private integer columns = 0
    private integer rows = 0
    private integer lastrow = 0
    private integer lastcolumn = 0
    private real mapMinX = 0
    private real mapMinY = 0
endglobals

private function filt takes nothing returns boolean
    return GetDestructableMaxLife(GetFilterDestructable()) == CHECK_LIFE
endfunction

public function register takes destructable d returns nothing
    local integer id = R2I((GetDestructableY(d)-mapMinY)/TILE_SIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILE_SIZE)
    local integer count = LoadInteger(hash, id, 0)+1
    call SaveInteger(hash, id, 0, count)
    call SaveDestructableHandle(hash, id, count, d)
    //the destructable remains visible until once viewed by the camera
endfunction

public function unregister takes destructable d returns nothing
    local integer id = R2I((GetDestructableY(d)-mapMinY)/TILE_SIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILE_SIZE)
    local integer count = LoadInteger(hash, id, 0)
    local integer a = 1
    //not very efficient, but it does the job
    loop
        exitwhen a > count
        if LoadDestructableHandle(hash, id, a) == d then
            if a < count then //not the last in list
                call SaveDestructableHandle(hash, id, a, LoadDestructableHandle(hash, id, count)) //move the last one into this slot
            endif
            call RemoveSavedHandle(hash, id, count) //clean the garbage
            call SaveInteger(hash, id, 0, count-1)
            exitwhen true
        endif
        set a = a+1
    endloop
    call ShowDestructable(d, true) //make sure its shown again in case it was hidden
endfunction

private function autoregister takes nothing returns nothing
    local destructable d = GetEnumDestructable()
    local integer id = R2I((GetDestructableY(d)-mapMinY)/TILE_SIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILE_SIZE)
    local integer count = LoadInteger(hash, id, 0)+1
    call SaveInteger(hash, id, 0, count)
    call SaveDestructableHandle(hash, id, count, d)
    call ShowDestructable(d, false) //initially hide everything
    set d = null
endfunction

private function periodic takes nothing returns nothing
    local integer row = R2I((GetCameraTargetPositionY()-mapMinY)/TILE_SIZE)
    local integer column = R2I((GetCameraTargetPositionX()-mapMinX)/TILE_SIZE)
    local integer lastid = lastrow*columns + lastcolumn
    local integer id = row*columns + column
    local integer id2 = 0
    local integer a = 0
    local integer count = LoadInteger(hash, lastid, 0)
    debug local integer hiddenthiscycle = 0
    debug local integer shownthiscycle = 0
    if id != lastid then //only check for tiles if the camera has left the last tile
        loop //hide last tile: center
            exitwhen a >= count
            set a = a + 1
            call ShowDestructable(LoadDestructableHandle(hash, lastid, a), false)
            debug set hiddenthiscycle = hiddenthiscycle + 1
        endloop
        if lastcolumn > 0 then //hide last tile: west
            set a = 0
            set count = LoadInteger(hash, lastid-1, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, lastid-1, a), false)
                debug set hiddenthiscycle = hiddenthiscycle + 1
            endloop
        endif
        if lastcolumn+1 < columns then //hide last tile: east
            set a = 0
            set count = LoadInteger(hash, lastid+1, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, lastid+1, a), false)
                debug set hiddenthiscycle = hiddenthiscycle + 1
            endloop
        endif
        if lastrow > 0 then //hide last tile: south
            set id2 = lastid-columns
            set a = 0
            set count = LoadInteger(hash, id2, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, id2, a), false)
                debug set hiddenthiscycle = hiddenthiscycle + 1
            endloop
            if lastcolumn > 0 then //hide last tile: southwest
                set a = 0
                set count = LoadInteger(hash, id2-1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2-1, a), false)
                    debug set hiddenthiscycle = hiddenthiscycle + 1
                endloop
            endif
            if lastcolumn+1 < columns then //hide last tile: southeast
                set a = 0
                set count = LoadInteger(hash, id2+1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2+1, a), false)
                    debug set hiddenthiscycle = hiddenthiscycle + 1
                endloop
            endif
        endif
        if lastrow+1 < rows then //hide last tile: north
            set id2 = lastid+columns
            set a = 0
            set count = LoadInteger(hash, id2, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, id2, a), false)
                debug set hiddenthiscycle = hiddenthiscycle + 1
            endloop
            if lastcolumn > 0 then //hide last tile: northwest
                set a = 0
                set count = LoadInteger(hash, id2-1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2-1, a), false)
                    debug set hiddenthiscycle = hiddenthiscycle + 1
                endloop
            endif
            if lastcolumn+1 < columns then //hide last tile: northeast
                set a = 0
                set count = LoadInteger(hash, id2+1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2+1, a), false)
                    debug set hiddenthiscycle = hiddenthiscycle + 1
                endloop
            endif
        endif
        debug call BJDebugMsg("hidden this cycle: "+I2S(hiddenthiscycle))
        //all destructables of the old tile and adjacent tiles hidden, now show the new tiles:
        
        set a = 0
        set count = LoadInteger(hash, id, 0)
        loop //show tile: center
            exitwhen a >= count
            set a = a + 1
            call ShowDestructable(LoadDestructableHandle(hash, id, a), true)
            debug set shownthiscycle = shownthiscycle + 1
        endloop
        if column > 0 then //show tile: west
            set a = 0
            set count = LoadInteger(hash, id-1, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, id-1, a), true)
                debug set shownthiscycle = shownthiscycle + 1
            endloop
        endif
        if column+1 < columns then //show tile: east
            set a = 0
            set count = LoadInteger(hash, id+1, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, id+1, a), true)
                debug set shownthiscycle = shownthiscycle + 1
            endloop
        endif
        if row > 0 then //show tile: south
            set id2 = id-columns
            set a = 0
            set count = LoadInteger(hash, id2, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, id2, a), true)
                debug set shownthiscycle = shownthiscycle + 1
            endloop
            if column > 0 then //show tile: southwest
                set a = 0
                set count = LoadInteger(hash, id2-1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2-1, a), true)
                    debug set shownthiscycle = shownthiscycle + 1
                endloop
            endif
            if column+1 < columns then //show tile: southeast
                set a = 0
                set count = LoadInteger(hash, id2+1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2+1, a), true)
                    debug set shownthiscycle = shownthiscycle + 1
                endloop
            endif
        endif
        if row+1 < rows then //show tile: north
            set id2 = id+columns
            set a = 0
            set count = LoadInteger(hash, id2, 0)
            loop
                exitwhen a >= count
                set a = a + 1
                call ShowDestructable(LoadDestructableHandle(hash, id2, a), true)
                debug set shownthiscycle = shownthiscycle + 1
            endloop
            if column > 0 then //show tile: northwest
                set a = 0
                set count = LoadInteger(hash, id2-1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2-1, a), true)
                    debug set shownthiscycle = shownthiscycle + 1
                endloop
            endif
            if column+1 < columns then //show tile: northeast
                set a = 0
                set count = LoadInteger(hash, id2+1, 0)
                loop
                    exitwhen a >= count
                    set a = a + 1
                    call ShowDestructable(LoadDestructableHandle(hash, id2+1, a), true)
                    debug set shownthiscycle = shownthiscycle + 1
                endloop
            endif
        endif
        debug call BJDebugMsg("shown this cycle: "+I2S(shownthiscycle))
    endif
    set lastrow = row
    set lastcolumn = column
endfunction

private function init takes nothing returns nothing
    set mapMinX = GetRectMinX(bj_mapInitialPlayableArea)
    set mapMinY = GetRectMinY(bj_mapInitialPlayableArea)
    set rows = R2I((GetRectMaxY(bj_mapInitialPlayableArea)-mapMinY)/TILE_SIZE)+1
    set columns = R2I((GetRectMaxX(bj_mapInitialPlayableArea)-mapMinX)/TILE_SIZE)+1
    call TimerStart(CreateTimer(), INTERVAL, true, function periodic)
    call EnumDestructablesInRect(bj_mapInitialPlayableArea, Filter(function filt), function autoregister)
endfunction

endlibrary

:p
I kept the version number 1.2 because it wasn't worth taking it to 1.3 all because of some private variable renames =o
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Zwiebelchen said:
Well ... if you have a list of cryptic type IDs, you won't know which ids belong to which destructable without checking the object editor either. I think this argument is invalid. ;)
I implemented it that way because, well, I like my systems pragmatic and with as little as possible effort from myself to work.
Assuming you create new destructables in the object editor, it's just fire-and-forget; put in the value and you're done. With a registry, I'd need to Ctrl+D, remember the code, go into trigger editor, search the registry and stuff it in. It's not much but still takes some time and effort. ^^

Excepted that one other destructable system can't use the max life again, it's quite like the unit user data.
Also what if the max life is used for these destructables as it should be (has a matter), like if you decrease the life of a destructable on some event ?
Plus it you put the config function on the top of your library as it should be, then editing the registry is far easier/faster. You could even create a textmacro or a function to make it easier and less error prone.
Now, i suppose there are not that much destructable systems, so it should be fine if you don't need to use the max life ...
But my point is : doing that you limit the possibilities.

@Mag: My bad, gonna rename it in case I need to do another update.

Since we are on jass conventions, a function is supposed to be spelled LikeThat and not likeThat.
Also personnaly i used the public attribute for functions in the past, but imho it's just better to manually put the prefix yourself.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Excepted that one other destructable system can't use the max life again, it's quite like the unit user data.
Also what if the max life is used for these destructables as it should be (has a matter), like if you decrease the life of a destructable on some event ?
Plus it you put the config function on the top of your library as it should be, then editing the registry is far easier/faster. You could even create a textmacro or a function to make it easier and less error prone.
Now, i suppose there are not that much destructable systems, so it should be fine if you don't need to use the max life ...
But my point is : doing that you limit the possibilities.
I agree. I will write a simple registry once I got the time.
Nice, good job on this system. Could you do this for units and doodads too?
For units, as Troll pointed out, its quite useless and dangerous to do that.

For doodads, first, it's impossible because you cant show/hide doodads with triggers. And second: even if you could, it would make no difference. Doodads create almost no overhead at all - also, all doodads that are not visible won't get rendered anyway, so there's no point in that.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Could you create a multiplayer version so that any destructable viewed by any player is not hidden? I would be VERY VERY grateful :)
(For mineralZ, about 12000 destructables spawn)
What Nightelf125 said.
However, I don't know wether it will work for your map without any desyncs, as you obviously try to have the crystals as interactable objects. But you may always give it a try.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
What Nightelf125 said.
However, I don't know wether it will work for your map without any desyncs, as you obviously try to have the crystals as interactable objects. But you may always give it a try.

If it doesn't change the path/collision in any way it should be safe.

EDIT : Hmm, on a second though, it would be not safe, since an invisible targetable can't be targeted i guess.
Meh, a test is needed ...
 
You need to figure out a way to filter out important destructables that could cause desyncs.

There needs to be a function that the user can use to add certain destructables to a "Black list" of some sort.

Destructables on that "Black list" would be ignored by the system.
It would take nothing more than a hashtable save on insertion and one lookup on iteration.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Black list = any destructable without 37 max life. By default, most things are already blacklisted.

No, we need some overcomplication, we need it to be less GUI friendly.


In a serious note i suppose it would be "useful" when we are talking about the destructables which are created later in game.
But i'm quite surprised that is Mag's suggestion, since it's only to prevent user being dumb, and that's anti-speedfreak.
 
@Purge:

True, but let's say we have this unregistered destructable type, and at one point in the game, some of these destructables are no longer up for interation with the player, so they could be registered to the system now.

It would only give a speed boost if there are a lot of these, but this shouldn't be a problem, because this system as a whole is only for maps that have a lot of doodads :p

And sometimes, there are destructables which you can't interact with at first, so they are registered, but later in the game, some of them become interactable.

The best speed boost is given when we have as many destructables registered as possible, and with this dynamic registry thing, we can achieve that.
 
Level 23
Joined
Jan 1, 2011
Messages
1,504
What Nightelf125 said.
However, I don't know wether it will work for your map without any desyncs, as you obviously try to have the crystals as interactable objects. But you may always give it a try.

No what I mean is, you are hiding destructables locally. I am asking you to convert the system to along the lines of this:
If a destructable is not in view range of ANY player, hide it for all players. Then you could still have targetable destructables without desyncs
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
The best speed boost is given when we have as many destructables registered as possible, and with this dynamic registry thing, we can achieve that.
Well, "register" and "unregister" are there to be used if you need that.

@ maddeem: One could write a system that uses unit position instead of camera position and then hide everything inside the fog of war. In this case that would be possible, although it wouldn't be as effective.
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
Will you release a new Gaia's version with this? Now THAT would be damn helpful (considering the only reason why we didn't play Gaia's on LAN parties is that my friend had really low FPS)

It's clearly useful if you ask me, I think I'll implement this to my map - I don't quite know though how I'm gonna test it as my FPS is constantly 60+, but I think I'll ask my friend to do it for me.
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
Yea, but my map is an AoS, with a relatively low amount of destructables (~5000) - compared to yours it's really small. Though once my friend comes back it'll be remade, but I don't think it'll be that much much bigger (it might get even smaller). Plus, I'm trying to optimize everything for the map, so that nothing useless happens that'd drain the CPU.

And I must admit that it's great that you made and released this - if you update the map this or the next week, we may even play Gaia's on the next LAN party!

The only thing I don't like is that it's based on functions, but oh well, it's just a personal preference to abuse structs whenever it's possible.
 
Level 19
Joined
Oct 12, 2007
Messages
1,821
Imported this system and tested the FPS differences.
Didn't notice a big difference in FPS. Maybe 1-2, but that might be imaginary.
However, at a certain point when I reached a certain area my FPS dropped to 0 (so I had to alt f4).
So I suppose this was when I changed 'tile'.

The destructables I added to this system are: Pathing Blockers, Tree's (untargetable and unkillable) and Walkable stuff like bridges.

Did I do anything wrong?
Here are my configurables:
JASS:
//==== CONFIGURABLES ====
    private constant real CHECK_LIFE = 37 //all destructables that shall be hidden by the system must have this maximum life value set in the object editor (otherwise will be ignored by the system)
                                         //simply select a number you won't need in your map
    private constant real INTERVAL = 0.05 //the refreshing rate of the system (recommended: 0.05-0.1)
    private constant real TILE_SIZE = 6144 //the edge size of the tiles; should be about the same as sight radius (not diameter) of the camera; for 3d cams, use the FarZ value
    //==== END OF CONFIGURABLES ====

(My map is 256x256)
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Didn't notice a big difference in FPS. Maybe 1-2, but that might be imaginary.
Well, the system can not make the fps of your map exceed the fps limit of WC3, which is around 55-60.
If you have a constant 55-60 fps anyway, there's no need to use the system. This is just for those doodad-heavy large-scale rpg maps that reach a significant fps drop at some point of design.

I mean, everything would still function without them because of wc3's pathing map file.
As (destructable) pathing blockers can be removed by trigger/destroyed, I don't think the pathing information of blockers is written into the pathing map file.
 
Level 19
Joined
Oct 12, 2007
Messages
1,821
Well I'm around 16-40 fps, depending on my camera view point.
The system works now! I removed the pathing blockers from it and it didn't cause the huge lag anymore. I think at the moment it gives me like 3-6 FPS which is pretty nice!
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Well I'm around 16-40 fps, depending on my camera view point.
The system works now! I removed the pathing blockers from it and it didn't cause the huge lag anymore. I think at the moment it gives me like 3-6 FPS which is pretty nice!
I don't know what you did that your map sucks so much performance, but I'm glad this little thing could help at least a little.
However, if your map is at around 16-40 fps, I really recommend trying other optimizations aswell, such as replacing destructable pathing blockers by doodad pathing blockers (99% of the time, you don't need destructable pathing blockers, so this is just a waste of performance) and optimizing the map code.
 
Top