Slide System Concept

emjlr3

Change can be a good thing
Reaction score
396
Something I have been working on a little here and there the past few days.

The main issue I have with the extended knockback system is that you can't have multiple slides on a unit - I alleviate this issue by calculating the vector components of the current slide and the updated slide, which allows me to implement concurrent slides all into one instance.

Also, a few of the things in the previous are a little archaic (ex. no static ifs), that along with its heavy system requirements makes for a less then desirable system.

I chose a unit group enum just to try something different (initially) - after proving to myself that it was tormentingly slow, I switched to an array stack.

Examples of direct struct method use:

JASS:
function Actions takes nothing returns nothing
    local slide d=slide.create(Hero,GetRandomReal(0.,360.),375.,0.)  
    call d.destroy()
endfunction 
function Blurg takes nothing returns boolean
    local slide s
    
    if GetEventPlayerChatString()=="sliding" then
        if slide.isunitsliding(Hero) then
            call BJDebugMsg("yes")
        else
            call BJDebugMsg("no")
        endif
        return false
    elseif GetEventPlayerChatString()=="stop" then
        call slide.unitstopslide(Hero)
        return false
    elseif GetEventPlayerChatString()=="get" then
        set s=slide.unitgetslide(Hero)
        return false
    endif
    
    return true
endfunction

//===========================================================================
function InitTrig_Knock takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerAddAction( t, function Actions )
    call TriggerAddCondition( t, Condition(function Blurg) )
    call TriggerRegisterPlayerChatEvent( t, Player(0), "knock", true )    
    call TriggerRegisterPlayerChatEvent( t, Player(0), "sliding", true )    
    call TriggerRegisterPlayerChatEvent( t, Player(0), "stop", true ) 
    call TriggerRegisterPlayerChatEvent( t, Player(0), "get", true ) 
endfunction


The slide struct should be treated as a unique object, and used as such.

Wrappers for these methods are also included for GUI users.

Critiques and other thoughts welcome.

JASS:
library SlideSystem needs optional GroupUtils, optional TimerUtils, optional DestructableLib, optional IsTerrainWalkable

globals    
//==Configurables==\\
    private constant boolean        ALLOWMOVE   = false // Allow movement while units slide
    private constant boolean        COLLIDE     = true // Unit/Structure collision detection
    private constant boolean        CLIFF       = true // Cliff detection, stops unit movement
    private constant boolean        REHIT       = true // Can collide with the same target more then once during a single slide event
    private constant boolean        SINCELAST   = false // When tracking total slide time and distance, will reset value for each newly created slide
    private constant boolean        TREES       = true // Destroy trees in sliding path
    
    private constant integer        OFFSET      = 0x100000 // Id of the first handle in your map, if unsure don't change
    
    private constant real           BREAK       = 10. // Velocity below which units stop sliding
    private constant real           CLIFFBREAK  = 75. // Break point for a terrian height change to be considered a cliff, if not using IsTerrainWalkable library
    private constant real           DECEL       = .95 // In the abscence of a linear deceleration set point, DECEL/TIMEOUT
    private constant real           FACTORA     = .66 // Percentage of slide velocity transfered to colliding units
    private constant real           FACTORB     = .66 // Percentage of slide velocity retained after a collision
    private constant real           FLYBREAK    = 150. // Units above this fly height will not be struck by collisions or destroy trees
    private constant real           RADIUS      = 180. // Collision detection radius
    private constant real           REHITTIME   = .4 // Elapsed time required before a unit can be collided with again
    private constant real           TIMEOUT     = .05 // Periodic timer interval
    
    private constant string         AIR         = "sfx\\AirSlide.mdx" // Flying units slide effect
    private constant string         ATTACH      = "origin" // Slide effects attachment point
    private constant string         HIT         = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl" // Collision effect
    private constant string         LAND        = "sfx\\LandSlide.mdx" // Land slide effect
    private constant string         SEA         = "sfx\\SeaSlide.mdx" // Water slide effect
endglobals
// Establish custom unit collision conditions
private function CollisionFilter takes unit slider, unit target returns boolean
    return true
endfunction

//==NO TOUCHING PAST THIS POINT==\\
globals
    private          boolexpr       TREEBOOL      
    private          boolexpr       UNITBOOL
    private          group          COLLISION   = CreateGroup()
    private          hashtable      HT          // Only needed if no TimerUtils
    private          integer        COUNT       = 0
    private          slide array    DATA
    private          slide array    DATA2       // Remove O(n) searches
    private          slide          TEMPDATA    
    private          location       LOC         = Location(0.,0.)
    private constant real           FRAMERATE   = 1./TIMEOUT
    private constant real           HALFRADIUS  = RADIUS/2.
    private constant real           REALBREAK   = BREAK/FRAMERATE
    private          rect           RECT    
    private          unit           T
    private          unit           TREECHECK   
    
    // Incase either of these need to be accessed
    public           group          GROUP       = CreateGroup()
    public           timer          TIMER       = CreateTimer()  
endglobals

private struct rehit 
    timer t         = null
    unit target     = null
    unit slider     = null
    group g         = null
    
    method destroy takes nothing returns nothing
        // Clean up
        static if LIBRARY_TimerUtils then
            call ReleaseTimer(this.t)
        else
            call FlushChildHashtable(HT,GetHandleId(this.t))
            call PauseTimer(this.t)
            call DestroyTimer(this.t)
        endif        
        call this.deallocate()
    endmethod
    static method timeout takes nothing returns nothing
        local rehit r=GetTimerData(GetExpiredTimer())
        
        // Unit is still sliding
        if IsUnitInGroup(r.slider,GROUP) then
            call GroupRemoveUnit(r.g,r.target)
        endif
        call r.destroy()
    endmethod
    static method create takes unit target, unit slider, group g returns rehit
        local rehit r=rehit.allocate()
        
        static if LIBRARY_TimerUtils then
            set r.t=NewTimer()
            call SetTimerData(r.t,r)
        else
            set r.t=CreateTimer()
            call SaveInteger(HT,GetHandleId(r.t),0,r)
        endif
        set r.target=target
        set r.slider=slider
        set r.g=g        
        // Remove target from sliders hit group after a duration
        call TimerStart(r.t,REHITTIME,false,function rehit.timeout)
        
        return r
    endmethod
endstruct

private interface face
    method onPeriodic takes nothing returns nothing defaults nothing
    method onEnd takes nothing returns nothing defaults nothing
    method onHit takes nothing returns nothing defaults nothing
endinterface
struct slide extends face
    private     effect      sfx         = null
    private     integer     marker      = 0 // mark spot in movement loop stack
    private     real        cos         = 0.
    private     real        decel       = 0.
    private     real        sin         = 0.
    
    public      group       g           = null    
    public      real        angle       = 0.    
    public      real        velocity    = 0.
    
    readonly    boolean     customsfx   = false
    readonly    boolean     fly         = false
    readonly    boolean     land        = true
    readonly    boolean     trees       = true
    readonly    real        totaldist   = 0.
    readonly    real        totaltime   = 0.
    readonly    unit        slider      = null
    
    method destroy takes nothing returns nothing
        // Clean up
        if .onEnd.exists then
            call .onEnd.execute()
        endif
        call DestroyEffect(.sfx)
        static if LIBRARY_GroupUtils then
            call ReleaseGroup(.g)
        else
            call DestroyGroup(.g)
        endif
        call GroupRemoveUnit(GROUP,.slider)
        set DATA[.marker]=DATA[COUNT]
        set DATA[.marker].marker=.marker
        set COUNT=COUNT-1
        call .deallocate()
    endmethod
    private static method unitfilter takes nothing returns boolean
        local slide d=TEMPDATA
        local slide dd
        local rehit r
        local real xT
        local real yT
        local real x
        local real y
        local timer t
        set T=GetFilterUnit()
        
        // Make sure target is alive and not invulnerable, is not the slider and has not already been hit, and isn't flying too high, along with
        // conditions from the custom user edited filter
        if CollisionFilter(d.slider,T) and GetUnitFlyHeight(T)<=FLYBREAK and GetUnitAbilityLevel(T,'Avul')==0 and UnitAlive(T) and not IsUnitInGroup(T,d.g) and T!=d.slider then
            if d.onHit.exists then
                call d.onHit.execute()
            endif
            set xT=GetUnitX(T)
            set yT=GetUnitY(T)
            set x=GetUnitX(d.slider)
            set y=GetUnitY(d.slider)
            // Only begin a slide on the target if its not a structure
            if IsUnitType(T,UNIT_TYPE_STRUCTURE)==false then
                // Start collision slide
                set dd=slide.create(T,bj_RADTODEG*Atan2(yT-y,xT-x),(d.velocity*FRAMERATE)*FACTORA,d.decel) 
                call GroupAddUnit(dd.g,d.slider)
                call DestroyEffect(AddSpecialEffectTarget(HIT,T,ATTACH))
                
                // If allowed, start re-hit effects
                static if REHIT then
                    set r=rehit.create(T,d.slider,d.g)                    
                endif
            endif
            
            // Fancy maths for deflection, see my GetDeflectionAngle script
            set d.angle=d.angle+180.+(2.*((bj_RADTODEG*Atan2(yT-y,xT-x))-d.angle))
            set d.cos=Cos(d.angle*bj_DEGTORAD)
            set d.sin=Sin(d.angle*bj_DEGTORAD)
            set d.velocity=d.velocity*FACTORB
            call DestroyEffect(AddSpecialEffectTarget(HIT,d.slider,ATTACH))
            call GroupAddUnit(d.g,T)
        endif
        return false
    endmethod
    private static method killtrees takes nothing returns boolean
        // Make sure the destructable is actually a tree
        static if not LIBRARY_DestructableLib then
            if IssueTargetOrder(TREECHECK,"harvest",GetFilterDestructable()) then
                call KillDestructable(GetFilterDestructable())
            endif
        else
            if IsDestructableTree(GetFilterDestructable()) then
                call KillDestructable(GetFilterDestructable())
            endif
        endif
        return false
    endmethod
    private static method movement takes nothing returns nothing
        local slide d
        local integer i=1
        local real x
        local real xc
        local real y
        local real yc
        local real z
        
        loop
            exitwhen i>COUNT
            set d=DATA<i>
        
            // Slide is over
            if d.velocity&lt;=REALBREAK or not UnitAlive(d.slider) then
                // Clean up
                call d.destroy()    
                set i=i-1
            // Slide is not over
            else
                // Caculate positioning
                set x=GetUnitX(d.slider)+d.velocity*d.cos
                set y=GetUnitY(d.slider)+d.velocity*d.sin
                
                if d.onPeriodic.exists then
                    call d.onPeriodic.execute()
                endif
                
                // Destroy trees
                static if TREES then
                    if d.trees then
                        call MoveRectTo(RECT,x,y)
                        static if not LIBRARY_DestructableLib then
                            call PauseUnit(TREECHECK,false)
                        endif
                        call EnumDestructablesInRect(RECT,TREEBOOL,null)
                        static if not LIBRARY_DestructableLib then
                            call PauseUnit(TREECHECK,true)
                        endif
                    endif
                endif
                
                // Cliff detection
                static if CLIFF then
                    // Check out in front of the slider a bit in the direction it's traveling
                    set xc=x+HALFRADIUS*d.cos
                    set yc=y+HALFRADIUS*d.sin
                    static if LIBRARY_IsTerrainWalkable then
                        // Cliff found
                        if not IsTerrainWalkable(xc,yc) then
                            call d.destroy()
                        endif
                    else
                        call MoveLocation(LOC,x,y)
                        set z=GetLocationZ(LOC)
                        call MoveLocation(LOC,xc,yc)
                        // Cliff found
                        if GetLocationZ(LOC)&gt;z+CLIFFBREAK or GetLocationZ(LOC)&lt;z-CLIFFBREAK then
                            call d.destroy()
                        endif
                    endif
                endif
                
                // Update position and slide speed
                static if ALLOWMOVE then
                    call SetUnitX(d.slider,x)
                    call SetUnitY(d.slider,y)
                else
                    call SetUnitPosition(d.slider,x,y)
                endif
                // Update total time and distance slid
                set d.totaldist=d.totaldist+d.velocity
                set d.totaltime=d.totaltime+TIMEOUT
                // If a deceleration has been set, use it
                if d.decel!=0. then
                    set d.velocity=d.velocity-d.decel
                else
                    set d.velocity=d.velocity*DECEL
                endif
                    
                // Update slide effects    
                if not d.fly or d.customsfx then
                    // Ground            
                    if IsTerrainPathable(x,y,PATHING_TYPE_FLOATABILITY) or not IsTerrainPathable(x,y,PATHING_TYPE_ANY) then
                        if not d.land then
                            set d.land=true
                            call DestroyEffect(d.sfx)
                            set d.sfx=AddSpecialEffectTarget(LAND,d.slider,ATTACH)
                        endif
                    // Water
                    elseif not IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY) then
                        if d.land then
                            set d.land=not d.land
                            call DestroyEffect(d.sfx)
                            set d.sfx=AddSpecialEffectTarget(SEA,d.slider,ATTACH)
                        endif
                    endif
                endif               
                
                // Collision detection
                static if COLLIDE then
                    set TEMPDATA=d
                    call GroupEnumUnitsInRange(COLLISION,x,y,RADIUS,UNITBOOL)
                endif
            endif
            
            set i=i+1
        endloop
        
        if COUNT==0 then
            call PauseTimer(TIMER)
        endif
    endmethod
    
//==User Methods==\\
    // Create a new instance for a unit, deceleration=0 to use the DECEL global
    static method create takes unit slider, real angle, real velocity, real deceleration returns slide
        local slide this
        local integer id=GetHandleId(slider)-OFFSET
        local real x
        local real y
                
        // Unit wasn't previously sliding
        if not IsUnitInGroup(slider,GROUP) then  
            // Initial slide struct setup
            set this=slide.allocate()
            set .slider=slider
            set .angle=angle
            set .velocity=velocity/FRAMERATE
            set .cos=Cos(.angle*bj_DEGTORAD)
            set .sin=Sin(.angle*bj_DEGTORAD)
            if deceleration!=0 then
                set .decel=deceleration/FRAMERATE
            endif
            static if LIBRARY_GroupUtils then
                set .g=NewGroup()
            else
                set .g=CreateGroup()
            endif
            set x=GetUnitX(.slider)
            set y=GetUnitY(.slider)
        
            call GroupAddUnit(GROUP,slider)
            // Unit is a flyer
            if IsUnitType(slider,UNIT_TYPE_FLYING)==true then
                set .fly=not .fly
                set .sfx=AddSpecialEffectTarget(AIR,.slider,ATTACH)
                // Too high??
                if GetUnitFlyHeight(slider)&gt;FLYBREAK then
                    set .trees=not .trees
                endif
            else
                // Ground                                                  Stairs don't have any pathing...
                if IsTerrainPathable(x,y,PATHING_TYPE_FLOATABILITY) or not IsTerrainPathable(x,y,PATHING_TYPE_ANY) then
                    set .sfx=AddSpecialEffectTarget(LAND,.slider,ATTACH)
                // Water
                elseif not IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY) then
                    set .sfx=AddSpecialEffectTarget(SEA,.slider,ATTACH)
                    set .land=false
                endif
            endif
            
            // Store struct data
            set DATA2[id]=this
            set COUNT=COUNT+1
            set DATA[COUNT]=this    
            set .marker=COUNT
            // First sliding unit
            if COUNT==1 then
                call TimerStart(TIMER,TIMEOUT,true,function slide.movement)
            endif
        // Unit was already sliding
        else            
            // Calculate vector components of slide velocity
            set this=DATA2[id]
            set velocity=velocity/FRAMERATE
            set x=.velocity*.cos+(velocity)*Cos(angle*bj_DEGTORAD)
            set y=.velocity*.sin+(velocity)*Sin(angle*bj_DEGTORAD)
            // Update slide direction and velocity
            set .velocity=SquareRoot(x*x+y*y)
            set .angle=Atan2(y,x)
            set .cos=Cos(.angle)
            set .sin=Sin(.angle)
            set .angle=.angle*bj_RADTODEG
            if deceleration!=0 then
                set .decel=deceleration/FRAMERATE
            endif
            
            // Reset total slide time and distance
            static if SINCELAST then
                set .slidedist=0.
                set .totaltime=0.
            endif
        endif
        
        return this
    endmethod
    // Retrieve the units current slide information
    static method unitgetslide takes unit slider returns slide
        debug if IsUnitInGroup(slider,GROUP) then
            return DATA2[GetHandleId(slider)-OFFSET]
        debug else
            debug call BJDebugMsg(SCOPE_PREFIX+&quot; UnitGetSlide Error: Unit is not sliding.&quot;)
            debug return 0
        debug endif
    endmethod
    // Is the unit currently sliding?
    static method isunitsliding takes unit slider returns boolean
        return IsUnitInGroup(slider,GROUP)
    endmethod
    // Stop the unit from sliding immediately
    static method unitstopslide takes unit slider returns boolean
        if IsUnitInGroup(slider,GROUP) then
            call DATA2[GetHandleId(slider)-OFFSET].destroy()         
            return true
        else
            debug call BJDebugMsg(SCOPE_PREFIX+&quot; UnitStopSlide Error: Unit is not sliding.&quot;)
            return false
        endif
    endmethod
    // Incase you need to set a custom slide effect mid slide
    // Use &quot;&quot; to restore automatic sfx updates
    static method unitsetslideeffect takes unit slider, string sfx returns boolean
        local slide this
    
        if IsUnitInGroup(slider,GROUP) then
            set this=DATA2[GetHandleId(slider)-OFFSET] 
            call DestroyEffect(.sfx)
            if sfx!=&quot;&quot; then
                set .customsfx=true
                set .sfx=AddSpecialEffectTarget(sfx,slider,ATTACH)
            else
                set .customsfx=false
                if .fly then
                    set .sfx=AddSpecialEffectTarget(AIR,slider,ATTACH)
                endif
            endif
            return true
        else
            debug call BJDebugMsg(SCOPE_PREFIX+&quot; UnitSetEffect Error: Unit is not sliding.&quot;)
            return false
        endif
    endmethod
    // Kinematics
    // Slide a unit for a specific duration, given the initial velocity
    static method unitslidetimed takes unit slider, real angle, real velocity, real time returns slide
        // a=Vi-Vf/t
        return slide.create(slider,angle,velocity,(velocity-BREAK)/(time*FRAMERATE))
    endmethod
    // Slide a unit over a specific distance, given the initial velocity
    static method unitslidedistance takes unit slider, real angle, real velocity, real distance returns slide
        // a=(Vf^2-Vi^2)/(2*d)
        return slide.create(slider,angle,velocity,FRAMERATE*(REALBREAK*REALBREAK-velocity*velocity*TIMEOUT)/(2.*distance)))
    endmethod
    
//==Initialize System==\\
    private static method onInit takes nothing returns nothing
        set UNITBOOL=Condition(function slide.unitfilter)
        // Destroy trees in path of sliding units setup
        static if TREES then
            set RECT=Rect(-RADIUS,-RADIUS,RADIUS,RADIUS)
            set TREEBOOL=Condition(function slide.killtrees)
            // Don't have DestructableUtils
            static if not LIBRARY_DestructableLib then                
                set TREECHECK=CreateUnit(Player(15),'hfoo', 0.0, 0.0, 0.0)
                call PauseUnit(TREECHECK,true)
                call ShowUnit(TREECHECK,false)
                call UnitAddAbility(TREECHECK,'Ahrl')
                call UnitAddAbility(TREECHECK,'Aloc')
            endif
        endif
        // If you don't have TimerUtils
        static if not LIBRARY_TimerUtils then
            set HT=InitHashtable()
        endif
    endmethod
endstruct

//==Wrapper Functions==\\
// Create a new instance for a unit, deceleration=0 to use the DECEL global
function UnitStartSlide takes unit slider, real angle, real velocity, real deceleration returns slide
    debug if slider==null or not UnitAlive(slider) or IsUnitType(slider,UNIT_TYPE_DEAD)==true then
        debug call BJDebugMsg(SCOPE_PREFIX+&quot; Create Error: Invalid argument inputs.&quot;)
        debug return 0
    debug endif
    return slide.create(slider,angle,velocity,deceleration)
endfunction
// Retrieve the units current slide information
function UnitGetSlide takes unit slider returns slide
    return slide.unitgetslide(slider)
endfunction
// Is the unit currently sliding?
function IsUnitSliding takes unit slider returns boolean
    return slide.isunitsliding(slider)
endfunction
// Stop the unit from sliding immediately
function UnitStopSlide takes unit slider returns boolean
    return slide.unitstopslide(slider)
endfunction
// Incase you need to set a custom slide effect mid slide
// Use &quot;&quot; to restore automatic sfx updates
function UnitSetSlideEffect takes unit slider, string sfx returns boolean
    return slide.unitsetslideeffect(slider,sfx)
endfunction
// Kinematics
// Slide a unit for a specific duration, given the initial velocity
function UnitSlideTimed takes unit slider, real angle, real velocity, real time returns slide
    return slide.unitslidetimed(slider,angle,velocity,time)
endfunction
// Slide a unit over a specific distance, given the initial velocity
function UnitSlideDistance takes unit slider, real angle, real velocity, real time returns slide
    return slide.unitslidedistance(slider,angle,velocity,time)
endfunction

endlibrary</i>
 

Risen

New Member
Reaction score
4
Looks totally BA. Would you take the time to make some kind of demo map that utilizes everything you added to the system? That would be nice. :)
 

chobibo

Level 1 Crypt Lord
Reaction score
48
Could you add support for third-party attachment systems, I believe some people would like to use a unit indexer for this purpose. I also think that a hash table isn't suitable for this purpose; unit user data or a unit indexing system would be much better for this application.
 

Bribe

vJass errors are legion
Reaction score
67
That, and, with a timeout so low it should just be running on Timer32. This thing must lag like crazy.
 

emjlr3

Change can be a good thing
Reaction score
396
Looks totally BA. Would you take the time to make some kind of demo map that utilizes everything you added to the system? That would be nice.
i will supply a demo map soon - I was hoping for a little more critique before then ;)

That, and, with a timeout so low it should just be running on Timer32. This thing must lag like crazy.
actually i have taken it up to about 15 slides and collisions at a time with no fps drop - have not torture tested it though

you obviously were not around when people were running 10x this using handle vars with no lag, honestly its not all that big a deal - I am for efficiency and all, where practically applicable

running it on timer32 with my group method wouldn't make any sense, it would just add overhead - plus i am trying to make this light and portable - the less requirements the better - really wanted to get rid of grouputils - but it the absence of me adding my own group stack to the system (which would be silly), I don't see how I could

Could you add support for third-party attachment systems, I believe some people would like to use a unit indexer for this purpose. I also think that a hash table isn't suitable for this purpose; unit user data or a unit indexing system would be much better for this application.
thats an idea - but honestly I don't see a hashtable being all that bad - they are reasonably fast, and make it easy for me to identify if a unit is active in this system - flexibility is nice though - hell if I wanted to go that route I could just use my own global array

really this was about trying some new things, and those things seem to work well thus far

and really, the hashtable is only used two times - once during create, and once when the unit is done sliding - nothing is periodic in that sense

you should have seen my first approach - really would have balked at its abuse
 

tooltiperror

Super Moderator
Reaction score
231
That interface is really ... horrible.

In general, any underscores are a bad idea in a public system.
 

emjlr3

Change can be a good thing
Reaction score
396
I am from an ancient sect, with dated ideology

if there is a "better" way of doing things, pray do tell

you want function calls and globals names to be unique to the system, and since the scope prefix for the system is unique itself (must be), it makes sense to use that as an identifier prefix
 

chobibo

Level 1 Crypt Lord
Reaction score
48
and really, the hashtable is only used two times - once during create, and once when the unit is done sliding - nothing is periodic in that sense
Isn't this periodic?
JASS:

    static method movement_child takes nothing returns nothing
        local Data d=LoadInteger(HT,GetHandleId(GetEnumUnit()),0)
        local real x
        local real y
        local real z


Also, I'm not against your use of the hash table, I assumed that this would be a public resource, hence the need for that kind of customization. I'm suggesting you segregate the code into <a> one that holds the data structure in use and <b> one that holds the processing, that way it wouldn't just be easier to update(add-on features), it will be also easier to maintain (debugging).

Use struct static methods, its the same as calling a prefixed function without the prefix.
[ljass]local slider this=this.create(parameters)[/ljass] vs [ljass]local SlideSystem_Data d=SlideSystem_Data.create(parameters)[/ljass]
 

Bribe

vJass errors are legion
Reaction score
67
I wonder what you mean by torture testing? A lot of maps can have hundreds of units at a time, try it with 50, 100, 200... and do this all on a really bad computer with at most 1gb of ram, a horrible graphics card and a less-than 2.0 single core processor, see how lax you get with efficiency then.
 

emjlr3

Change can be a good thing
Reaction score
396
Isn't this periodic?
bollocks, forgot about that ^^

Also, I'm not against your use of the hash table, I assumed that this would be a public resource, hence the need for that kind of customization. I'm suggesting you segregate the code into <a> one that holds the data structure in use and <b> one that holds the processing, that way it wouldn't just be easier to update(add-on features), it will be also easier to maintain (debugging).
not quite sure i understand this

Use struct static methods, its the same as calling a prefixed function without the prefix.
local slider this=this.create(parameters) vs local SlideSystem_Data d=SlideSystem_Data.create(parameters)
or this

I wonder what you mean by torture testing? A lot of maps can have hundreds of units at a time, try it with 50, 100, 200... and do this all on a really bad computer with at most 1gb of ram, a horrible graphics card and a less-than 2.0 single core processor, see how lax you get with efficiency then.
torture testing would be just that - see how far this thing can go - to be honest with you I bet its the gfx that goes before the cpu in this instance - the only thing that would start to get bad is big collision enums with lots of deflection maths - but that is a little harder to artificially create - i think overall the periodic system overhead is menial aside from the aforementioned

any who - am not lax on efficiency - all I am saying is that nit picking in the weeds isn't going to account for much
 

chobibo

Level 1 Crypt Lord
Reaction score
48
What I mean is to allow the user(s) to use a third-party data structure/attachment system.

Secondly, I was referring to the function identifiers with underscores, I think it's best if you make em struct methods instead of a function that has no association with the struct.

JASS:

/* Changed the struct identifier */

struct slide


    /* SYSTEM API */
    
    /* check if the unit is already sliding */
    static method IsUnitSliding takes unit returns boolean
        // procedures here
        return value
    endmethod
    
    /* create a new instance or modify the existing one */
    static method start takes parameters returns thistype
        // procedures here
        return thistype
    endmethod
    
    /* retrieve the attached struct from a unit */
    static method GetData takes unit thisUnit returns thistype
        // procedures here
        return thistype
    endmethod
    
    /* Instance methods */
    
    /* force this instance to stop */
    method stop takes nothing returns nothing
        // procedures here
    endmethod

endstruct

/* Utilizing the given API, we would call the necessary procedures as such: */

    local slide this    = slide.start(parameters)
    local slide other   = slide.GetData(GetEnumUnit())
    
    if slide.IsUnitSliding(unit) or (this==0) then
        // actions
    else
        // actions
    endif
    
/* Currently the system's API is this: */

    local SlideSystem_Data this    = SlideSystem_Data_Create(parameters)
    local SlideSystem_Data other   = SlideSystem_Data_GetUnitData(GetEnumUnit())
    
    if SlideSystem_Data_IsUnitSliding(unit) or (this==0) then
        // actions if true
    else
        // actions if false
    endif


I was looking deeper upon the code and I've noticed some things could be rewritten; declaring system-only variables within the system struct.
 

Bribe

vJass errors are legion
Reaction score
67
Changing from packing it with group enumerations combined with hashtable retrievable versus a simple expire + stack loop is not picking through weeds.

Picking through weeds is like using GetTriggerPlayer() in a spell event instead of GetOwningPlayer(GetTriggerUnit()) (both work in this case, and the difference is hardly felt)
 

emjlr3

Change can be a good thing
Reaction score
396
not bad

but then you have a non-unique struct variable name, which could clash with other public names

I could add methods as suggested - but I still want to retain the ability for GUI users to utilize this system, so they could become wrappers for the struct methods
 

emjlr3

Change can be a good thing
Reaction score
396
Changing from packing it with group enumerations combined with hashtable retrievable versus a simple expire + stack loop is not picking through weeds.

Picking through weeds is like using GetTriggerPlayer() in a spell event instead of GetOwningPlayer(GetTriggerUnit()) (both work in this case, and the difference is hardly felt)
I would like to time the following

JASS:
call ForGroup(group,function Enum)
function Enum
  local data d=LoadInteger(hashtable,GetHandleId(GetEnumUnit()),0)
endfunction

// versus

local integer i=1
local data d

loop
  exitwhen i&gt;count
  set d=DATA[count]

  set i=i+1
endloop

// where count==units in group


there is also less removal overhead (maybe not), but that is in the weeds

how does the stop watch native work?

Please elaborate.
your structs name is slide

any other public variable or function in your map with the name slide will clash

whereas, when using the scope prefix as a variable name prefix, you almost completely ensure a unique variable name

i.e.

JASS:
scope SlideSystem

public struct slide
endstruct

endscope

// to use
local SlideSystem_slide


now the only way there is a name clash is if someone is dumb enough to try and name something with the prefix SlideSystem, which wouldn't really make any sense

maybe others disagree
 

chobibo

Level 1 Crypt Lord
Reaction score
48
Now I see your point! Thanks for explaining :D.

The reason I did not encapsulate it is because I consider this struct as a new data type or a unique object. Now if I want a unit to slide, I use the slide data type, like how I would use the unit data type or the timer data type. If I want to manipulate the slide object then I use it's methods, but for a specific instance I use it's instance methods.

From that definition, I determine that I must not use integer as an identifier. Tis mine opinion alone(lol).

Your point proves valid for people with or without a background in object oriented concept. Still, I prefer the non-encapsulated identifier, I think it fits the OO concept more.
 

Sevion

The DIY Ninja
Reaction score
424
I would prefer not having such a long prefix. Not at all if possible.

Just following some other concepts, I feel the struct name should be capitalized. (Some minor nit picky things)

I feel that naming the struct simply "Slider" is fine.

Most structs are private anyhow. And if they aren't, they most likely are not ever going to be named "Slider".

I feel as if the user interface for this system is bad. Should be reviewed.

As for the ForGroup vs Loop, I would be surprised if ForGroup was faster.

Also that Extended Knockback System should be Graveyarded. It's old, deprecated, and uses PUI.
 

tooltiperror

Super Moderator
Reaction score
231
I am from an ancient sect, with dated ideology

if there is a "better" way of doing things, pray do tell

you want function calls and globals names to be unique to the system, and since the scope prefix for the system is unique itself (must be), it makes sense to use that as an identifier prefix
Look at some examples from TimerUtils.

JASS:

// TimerUtils
function SetTimerData takes timer t, integer value returns nothing
function GetTimerData takes timer t returns integer
function NewTimer takes nothing returns timer


None of them have TU in them, they're just unique enough and not used in any other popular system.

Maybe, take the cool approach, name the library "Slide" and use the struct name "Slide" and .create.
 

emjlr3

Change can be a good thing
Reaction score
396
FYI

ran some poor mans speed tests, stopwatch nativeless using fps as a speed indicator

base fps=~64-65

JASS:
struct Data
    unit u
    method destroy takes nothing returns nothing
        call this.deallocate()
    endmethod
endstruct
globals
    constant integer TORTURE=12
    timer T=CreateTimer()
    group G=CreateGroup()
    hashtable HT=InitHashtable()
    Data array DATA
    Data array DATA2
    boolean ARRAYTEST=false
    boolean GROUPTEST=false
endglobals

function Enum takes nothing returns nothing
    //local Data d=LoadInteger(HT,GetHandleId(GetEnumUnit()),0)
    local Data d=DATA2[GetHandleId(GetEnumUnit())-0x100000]
endfunction
function StartGroup takes nothing returns nothing
    call ForGroup(G,function Enum)
endfunction
function StartArray takes nothing returns nothing
    local integer i=0
    local Data d
    
    loop
        exitwhen i==TORTURE
        set d=DATA<i>
        set i=i+1
    endloop
endfunction
function Trig_Commands_Actions takes nothing returns nothing
    local string s=GetEventPlayerChatString()
    local Data d
    local integer i=0
    
    if s==&quot;start group test&quot; and not ARRAYTEST and not GROUPTEST then
        call PauseTimer(T)
        set GROUPTEST=true
        call DisplayTextToPlayer(GetLocalPlayer(),0.,0.,&quot;Group test started&quot;)
        call TimerStart(T,0.0,true,function StartGroup)        
    elseif s==&quot;start array test&quot; and not GROUPTEST and not ARRAYTEST then
        call PauseTimer(T)
        set ARRAYTEST=true
        call DisplayTextToPlayer(GetLocalPlayer(),0.,0.,&quot;Array test started&quot;)
        call TimerStart(T,0.0,true,function StartArray)        
    elseif s==&quot;stop&quot; and (GROUPTEST or ARRAYTEST) then
        call PauseTimer(T)
        if ARRAYTEST then
            set ARRAYTEST=false
            call DisplayTextToPlayer(GetLocalPlayer(),0.,0.,&quot;Array test stopped&quot;)
        else
            set GROUPTEST=false
            call DisplayTextToPlayer(GetLocalPlayer(),0.,0.,&quot;Group test stopped&quot;)
        endif
    endif
endfunction

//===========================================================================
function InitTrig_Commands takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    local integer i=0
    call TriggerAddAction( t, function Trig_Commands_Actions )
    call TriggerRegisterPlayerChatEvent(t,GetLocalPlayer(),&quot;start group test&quot;,true)
    call TriggerRegisterPlayerChatEvent(t,GetLocalPlayer(),&quot;start array test&quot;,true)
    call TriggerRegisterPlayerChatEvent(t,GetLocalPlayer(),&quot;stop&quot;,true)
    
    loop
        exitwhen i==TORTURE
        set bj_lastCreatedUnit=CreateUnit(GetLocalPlayer(),'hfoo',0.,0.,0.)
        call ShowUnit(bj_lastCreatedUnit,false)
        call GroupAddUnit(G,bj_lastCreatedUnit)
        set DATA<i>=Data.create()
        set DATA2[GetHandleId(bj_lastCreatedUnit)+0x100000]=DATA<i>
        call SaveInteger(HT,GetHandleId(bj_lastCreatedUnit),0,DATA<i>)        
        set i=i+1
    endloop
endfunction</i></i></i></i>


the straight array loop method doesn't even hiccup until TORTURE is set to 60, at which point fps drops to ~40-41.

the straight array loop blows the doors off the forgroup using hashtables, at TORTURE=12, fps~19-20.

removing the hashtable and using an array lookup in the forgroup at the same TORTURE value yields fps~39-40, so basically double that when using hashtables.

removing the GetHandleId call from the ForGroup array method at the same TORTURE value raises fps to ~45, though it fluctuated a bunch.

what does all this mean - well for starters the way this system is now blows something terrible.

hashtable data loading alone is responsible for ~45% of the overhead associated with my method. so in other words, they are slow.

the straight array loop is ~5 times faster than a ForGroup array lookup. whether its the ForGroup or the GetHandleId is irrelevant, as the ForGroup method relies on storage on units to be effective in either case. However, if its any consolation, GetHandleId seems relatively fast, as it only accounted for ~20% of the overhead in the ForGroup array lookup method, so it seems to me the ForGroup is pretty slow too :(.

all in all, looks like I will be switching to a straight array loop
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    Damn here come the bots again - 193 online but they are totally invisible to any stats - bunch of bots!'
  • C cubanismo:
    Re: Taco Tuesday, if there were some way to share edible tacos over the internet, technology would be complete.
    +2
  • The Helper The Helper:
    One can only wish!
  • tom_mai78101 tom_mai78101:
    I'm back from Taiwan
    +1
  • The Helper The Helper:
    I am reorganizing the site I know nobody will notice but I am not done quite yet but the main forums order has been changed and there is a new news category in Other News which is all the remaining headline news stuff not categorized - Headline News is just the stuff that shows on the main page now and the news archive is off the main forums page
  • The Helper The Helper:
    and the real archive lives off of headline news
  • The Helper The Helper:
    Happy Monday!
  • V-SNES V-SNES:
    Happy Saturday!
  • The Helper The Helper:
    Happy Saturday Night!
    +1

    The Helper Discord

    Staff online

    Members online

    Affiliates

    Hive Workshop NUON Dome World Editor Tutorials

    Network Sponsors

    Apex Steel Pipe - Buys and sells Steel Pipe.
    Top