• 🏆 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!

[vJASS] Timed Struct System

Status
Not open for further replies.

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Yours uses 1 timer per map + 1 trigger + 1 triggercondition per struct which gets added and removed dynamically, that means 1 timer expiration + add condition + evaluation + clear conditions each time your thing expires.

I got rid of the need for the trigger evaluation by using 1 timer per struct (0 triggers / conditions), because the timer callback of each timer will point to the function you want to call, so you kill 2 birds with one stone.

Timer2 has more efficiency and uses the same amount of handles.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Honestly, since you are following in the footsteps of a big Timer-system campaign spearheaded by Nestharus, you would probably end up with the same results we did.

Nestharus had a timer system that was faster than Timer2, however the code length was huge and he was not satisfied with the result. The result was: the system was never published.

Timer2 remains the best one-timer system out there, unfortunately. Getting the logic right to distribute each timer node was an absolute challenge for me, because I had never done anything like that before. Even still, I could slightly optimize it if I figure out how to append nodes with o(1) complexity, without breaking the system.

If you want to have a readable version of Timer2:

JASS:
library T2 initializer OnInit // Timer 2, by Bribe‬
/*
    API
        Module T2
        - static method newTimer takes real timeout returns thistype
          > calls a method from your struct entitled "expire" after "timeout"
            seconds. It repeats this forever until you call "T2_Release" with
            the integer it returned.
        
        function T2_Release takes integer whichTimer returns nothing
        - Releases "whichTimer" when its job is done
        
        function T2_Active takes integer whichTimer returns boolean
        - Was "whichTimer" released?
        
        function T2_Timeout takes integer whichTimer returns real
        - Returns the timeout you specified in "newTimer"
*/

// Timer instance vars
globals
    private boolean array live // If a Timer is running (Live)
    private boolean array merged // Same expire-time as next Timer (Merged)
    private integer array next // Next Timer on queue (Ordinance)
    private real array tout // Original timeout
    private real array ttnx // Time until next Expiration
endglobals
    
// Misc vars
globals
    private timer array queue // 1 timer for each Queue
    private integer array iter // A mini-list for timer expiration (Iteration)
    private boolean array stop // Marks when to exit loops (Stop)
endglobals
    
// Initialize
private function OnInit takes nothing returns nothing
    set stop[0] = true
endfunction
    
//===========================================================================
// Timer operates as a "priority queue", inserting Timers with lower timeouts
// before Timers with higher timeouts to ensure they expire first. It uses a
// search method to scan through each Timer for the right spot, unfortunately
// turning this into an inefficient process in many cases.
//
private function A takes integer head, integer this, code c returns nothing
    local real x = tout[this]   // Stored for efficiency only
    local real y = 0            // The idea is to align "x" and "y"
    local integer i = head      // Volatile Timer (changes frequently)
    loop
        if stop[next[i]] then   // Insert as last Timer
            set ttnx[i] = x - y
            exitwhen true
        endif
        set y = y + ttnx[i]
        if x == y then          // Same expiration-time as next Timer
            set merged[this] = true
            exitwhen true
        elseif x < y then       // Insert "this" between "i" and "next[i]"
            set ttnx[this] = y - x
            set ttnx[i] = ttnx[i] - ttnx[this]
            exitwhen true
        endif
        loop                    // Ignore "merged" Timers
            set i = next[i]
            exitwhen not merged[i]
        endloop
    endloop
    set next[this] = next[i]
    set next[i] = this
    if i == head then
        call TimerStart(queue[head], ttnx[head], false, c)
    endif
endfunction
    
globals // Index generator and recycler
    private integer index = 0   // Index count
    private integer array recy // Recycle list
endglobals
    
// When you call "newTimer", this is what you're really calling
private function I takes integer head, real timeout, code c returns integer
    local integer this = recy[0] // This Timer
    if this == 0 then
        set this = index + 1
        set index = this
    else
        set recy[0] = recy[this]
    endif
    set tout[this] = timeout
    set ttnx[head] = TimerGetRemaining(queue[head])
    call A(head, this, c)
    set live[this] = true
    return this
endfunction
    
// Timer module's init behavior
private function Init takes nothing returns integer
    set index = index + 1
    set next[index] = index
    set queue[index] = CreateTimer()
    set stop[index] = true
    return index
endfunction
    
// Timer expiration behavior
private function E takes integer head, code c returns integer
    local integer i = head // Volatile Timer
    local integer this = 0
    loop
        set i = next[i]
        if live[i] then
            set iter[i] = this
            set this = i
        else
            set recy[i] = recy[0]
            set recy[0] = i
        endif
        exitwhen not merged[i]
        set mergednext[i] = false
    endloop
    if next[i] != head then   // Restart the Timer
        set ttnx[head] = ttnx[i]
        call TimerStart(queue[head], ttnx[head], false, c)
    endif
    set next[head] = next[i]
    return this
endfunction
    
// Returns the timeout you gave the Timer originally
function T2_Timeout takes integer this returns real
    return tout[this]
endfunction

// Returns if the Timer has been destroyed or not
function T2_Active takes integer this returns boolean
    return live[this]
endfunction

// Release the Timer when its job is done (else it repeats)
function T2_Release takes integer this returns nothing
    set live[this] = false
endfunction
    
//===========================================================================
// Running a textmacro (module) is what makes Timer work without triggers. It
// also significantly increases the Add method's performance, allocating one
// timer and one queue per struct rather than merging it all for each struct.
// For what it does, this is a very short module and shouldn't be too painful
// to implement.
// 
module T2
    private static integer head
    private static method zeit takes nothing returns nothing
        local integer i = E(.head, function thistype.zeit)
        loop
            exitwhen stop[i]
            call thistype(i).expire()
            if live[i] then
                call A(.head, i, function thistype.zeit)
            else
                set recy[i] = recy[0]
                set recy[0] = i
            endif
            set i = iter[i]
        endloop
    endmethod
    static method newTimer takes real timeout returns thistype
        return I(.head, timeout, function thistype.zeit)
    endmethod
    private static method onInit takes nothing returns nothing
        set .head = Init()
    endmethod
endmodule
endlibrary
 
Level 7
Joined
Apr 30, 2011
Messages
359
well . . .

what it supports then . . .
1. 1 timer/struct
2. how many types of actions available/struct?

and . . . can you explain the difference between them . . .
sorry if i'm asking too much questions >.<
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Your TimerStack:

JASS:
library TimedFunc requires TimerStack
    private struct TS extends array
        implement TimerStack_Implement
    endstruct
    
    struct TF extends array
        private static integer       c
        private static TS      array i
        private static integer array r
        
        static method start takes real time, code func returns integer
            local integer this = r [0]
            
            if this == 0 then
                set c    = c + 1
                set this = c
            else
                set r [0] = r [this]
            endif
            
            set i [this] = TS.tsCreate(time, func)
            
            return this
        endmethod
        
        method stop takes nothing returns nothing
            call i [this].tsDestroy()
            
            set r [this] = r [0]
            set r [0]    = this
        endmethod
    endstruct
endlibrary

My Timer2:

JASS:
library TimedFunc requires TimerStack
    
    struct TF
        method expire takes nothing returns nothing
            //Use "this" from within this method, instead of passing
            //a code argument to the "start" method - this method will
            //be called when the time expires.
        endmethod
        
        implement T2
        
        static method start takes real time returns integer
            return .newTimer(time)
        endmethod
        
        method stop takes nothing returns nothing
            call T2_Release(this)
        endmethod
    endstruct
endlibrary
 
Level 7
Joined
Apr 30, 2011
Messages
359
alright, i understand . . .
i'll update mine, so users will not need to include indexes . . . .

then, about invisible things . . .
1. speed
2. easy to make lags/not

thanks again for explaining, and sorry again for my new questions >.<
 
Level 7
Joined
Apr 30, 2011
Messages
359
so, too much instances of stacked timers will cause lag . . .

and, the speed difference?
well, if the speed doesn't differ that much, i'll use my TimerStack . . .
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Nestharus will tell you the speed difference is huge as it gets.

I wouldn't accept it unless it offered better utility than the existing stuff, which it doesn't. The fastest way to do a system like this is to use TimerUtils. The best combination of low file size, low RAM and speed would be T2. At least, out of the stuff that's been published.

However, the API provided by T2, while maybe the original format was bad, the system itself is in the right direction. I think I would revise it to something like this:

JASS:
library T2 initializer OnInit /* Timer 2, by Bribe
    
    Timer 2
    
    Timer 2 is a pseudo-timer system optimized for timeouts longer than 0.3
    seconds.
    
    The most common approach to doing what Timer2 does:
    
        local timer t = GetExpiredTimer()
        local integer this = GetTimerData(t)
        call this.deallocate()
        call ReleaseTimer(t)
        set t = null
        ...
        local integer this = thistype.allocate()
        local timer t = NewTimer()
        call SetTimerData(t, this)
        call TimerStart(t, 3.25, false, function Expire)
        set t = null
    
    Timer2's API looks like this:
    
        implement T2Expire
            call this.deallocate()
        implement T2End
        ...
        call this.allocate(3.25)
    
    That's just a quick & dirty show of how to use this resource. However, it
    only uses one timer per "struct/endstruct" instead of one timer per instance.
    A side effect of this is a huge bonus of reducing RAM. Another side effect
    is that the allocate/deallocate are shared for all structs, so you will use
    fewer global variables yet achieve the same end.
    
    API must be implemented into a struct which "extends array" and you must have
    JassHelper 0.A.2.B.
    
        Module T2
        - Declare any local variables here.
        
        - API:
        - static method allocate takes real timeout returns thistype
          > Runs the code between T2Expire and T2Null after "timeout" seconds.
            It repeats this process forever until you call the next method:
        
        - method deallocate takes nothing returns nothing
          > Releases the instance when its job is done.
        
        - method operator allocated takes nothing returns boolean
          > Was "whichTimer" released?
        
        - method operator timeout takes nothing returns real
          > Returns the timeout you specified in "newTimer".
        
        Module T2Expire
        - Place system code here using "this" as a pointer.
        
        Module T2Null
        - Null any local variables here.
        
        Module T2End
        - Implement this to complete the module package process.
*/
globals
    private boolean array live   //If a Timer is running (Live)
    private boolean array merged //Same expire-time as next Timer (Merged)
    private integer array next   //Next Timer on queue (Ordinance)
    private real array tout      //Original timeout
    private real array ttnx      //Time until next Expiration
    private timer array queue    //1 timer for each Queue
    private integer array iter   //A mini-list for timer expiration (Iteration)
    private boolean array stop   //Marks when to exit loops (Stop)
    private integer index = 0    //Index count
    private integer array recy   //Recycle list
endglobals
    
//Initialize
private function OnInit takes nothing returns nothing
    set stop[0] = true
endfunction
    
//===========================================================================
// Timer operates as a "priority queue", inserting Timers with lower timeouts
// before Timers with higher timeouts to ensure they expire first. It uses a
// search method to scan through each Timer for the right spot, unfortunately
// turning this into an inefficient process in many cases.
//
private function A takes integer head, integer this, code c returns nothing
    local real x = tout[this]       //Stored for efficiency
    local real y = 0                //The idea is to align "x" and "y"
    local integer i = head          //Volatile Timer (changes frequently)
    loop
        if stop[next[i]] then       //Insert as last Timer
            set ttnx[i] = x - y
            exitwhen true
        endif
        set y = y + ttnx[i]
        if x == y then              //Same expiration-time as next Timer
            set merged[this] = true
            exitwhen true
        elseif x < y then           //Insert "this" between "i" and "next[i]"
            set ttnx[this] = y - x
            set ttnx[i] = ttnx[i] - ttnx[this]
            exitwhen true
        endif
        loop                        //Ignore "merged" Timers
            set i = next[i]
            exitwhen not merged[i]
        endloop
    endloop
    set next[this] = next[i]
    set next[i] = this
    if i == head then
        call TimerStart(queue[head], ttnx[head], false, c)
    endif
endfunction
    
//Allocates a new timer instance for a node.
private function I takes integer head, real timeout, code c returns integer
    local integer this = recy[0]
    if this == 0 then
        set this = index + 1
        set index = this
    else
        set recy[0] = recy[this]
    endif
    set tout[this] = timeout
    set ttnx[head] = TimerGetRemaining(queue[head])
    call A(head, this, c)
    set live[this] = true
    return this
endfunction
    
//T2 module's onInit behavior
private function Init takes nothing returns integer
    set index = index + 1
    set next[index] = index
    set queue[index] = CreateTimer()
    set stop[index] = true
    return index
endfunction
    
//Timer expiration behavior
private function E takes integer head, code c returns integer
    local integer i = head
    local integer this = 0
    loop
        set i = next[i]
        if live[i] then
            set iter[i] = this
            set this = i
        else
            set recy[i] = recy[0]
            set recy[0] = i
        endif
        exitwhen not merged[i]
        set merged[i] = false
    endloop
    if next[i] != head then
        set ttnx[head] = ttnx[i]
        call TimerStart(queue[head], ttnx[head], false, c)
    endif
    set next[head] = next[i]
    return this
endfunction
    
//===========================================================================
// Running a textmacro (module) is what makes Timer work without triggers. It
// also significantly increases the Add method's performance, allocating one
// timer and one queue per struct rather than merging it all for each struct.
// For what it does, this is a very short module and shouldn't be too painful
// to implement.
//
module T2
    private static integer head
    private static code runCode
    
    //Allocates a new timer instance.
    static method allocate takes real timeout returns thistype
        return I(.head, timeout, .runCode)
    endmethod
    
    //Deallocate the Timer when its job is done (else it repeats forever).
    method deallocate takes nothing returns nothing
        set live[this] = false
    endmethod
    
    //Returns true if the Timer is still scheduled to expire.
    method operator allocated takes nothing returns boolean
        return live[this]
    endmethod
    
    //Returns the timeout you originally passed to the Timer.
    method operator timeout takes nothing returns real
        return tout[this]
    endmethod
    
    private static method expireProxy takes nothing returns nothing
        local integer this = E(.head, .runCode)
endmodule
    module T2Expire
        implement T2 //To make the implementation of T2 optional.
        loop
            exitwhen stop[this]
    endmodule
    module T2Null
            if live[this] then
                call A(.head, this, .runCode)
            else
                set recy[this] = recy[0]
                set recy[0] = this
            endif
            set this = iter[this]
        endloop
    endmodule
module T2End
        implement T2Null //To make the implementation of T2Null optional.
    endmethod
    private static method onInit takes nothing returns nothing
        set .head = Init()
        set .runCode = function thistype.expireProxy
    endmethod
endmodule
module Timer2 //Simple version for people who like simple.
    implement T2
    implement T2Expire
        call this.expire()
    implement T2Null
    implement T2End
endmodule
endlibrary
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
I wouldn't accept it unless it offered better utility than the existing stuff, which it doesn't.

Wow that's a load of crap Bribe. You accept all of Nestharus' re-writes of the utilities that are already available but suddenly you have standards now.

And Bribe that script is hideous. The documentation is intimidating and it makes your syntax not make any sense. You shouldn't be putting code in between two module implementations.

To be honest this is quite fucked up. You accept Nestharus' Board re-write and then tell this guy that his script doesn't offer any more utility than what is already available. Your T2 is the ugliest script I have seen for WC3 but you expect people to learn your messed up implementation when you won't even take 5 minutes into learning the implementation of someone else' script (Board, for example).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Berb, I was a pretty new moderator when I accepted most of what he did. The thing is I have developed standards over that period of time, and now most of his new stuff doesn't get past "pending".

You are overreacting because you have been pretty passionate proving a point with Nestharus' MultiBoard system, passionate to the point where you though I was in line with his thinking about the documentation of Earth-Fury's "Board" system. I simply stated that the code within the "Board" system is bloated in comparison.

Now, my documentation is utter nonsense, I'll never contest that logic. I have difficulty explaining my programming strategies because programming is basically a language of its own, but one that I can't really translate without further confusing someone. This is probably because I have never had a course in computer science, I have learned almost everything from the Internet.

You can say "Bribe you shouldn't be putting code between two modules", however, it's a hack because JassHelper doesn't inline functions you only call once, so the module inlines it. You can argue the usefulness of this with me, and I'll note it and see if it is compelling enough to change the approach. But it is a useful hack to me.



However, this library the user is developing has a lot of room for improvement. I'll accept a slower system than Timer2, but this also uses more RAM than Timer2. Compared with this particular script, TimerUtils is a far better option because it will use the same amount of handles but will do so with much better speed and modularity.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
It's a great little "hack" but that isn't standard. If you were doing that in your own code that's cool, but you're requiring users to use a hack to get your code to work the way they want. I don't find that to be a good idea.

It's not really a "hack" either, it's obvious what you are doing but that's like using struct in C with function pointers to imitate the API of a class. It can be done, but it's just bad style. It's using the control structure for something it doesn't really infer you use it for. What you should really do is use text-macros, if you're going to be using them like text-macros.

I mean, why do you guys even use vJass if you're not going to use any of its features properly? You don't use structs as they are intended to be used, nobody uses interfaces (great OO concepts here that nobody have ever used), modules are used for text-macros and nothing is modular, everything is built into something else. Then everybody throws around modularity like they know what it means. It's exhausting.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
That's a good point. I could make an extra module which would provide a more familiar interface to the user, like "implement Timer2" which calls a method called "expire", similar to how I did the original version of Timer2.

Would look like this in the T2 library:

JASS:
module Timer2
    implement T2
    implement T2Expire
        call this.expire()
    implement T2Null
    implement T2End
endmodule

That way hackers can get the speed and users can get the simplicity.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
You may want to check if that method exists, or not so you can force them to define it if they implement the module.

Just reading over your code. You declare the allocate method inside the module. Is that really a good idea?

Okay, I see what your doing, but it totally fucks up inheritance.
 
Level 7
Joined
Apr 30, 2011
Messages
359
uhhh . . . .
please don't argue with his script and mine . . . .
just focus on mine please :3
some neat suggestions/improvements is useful . .
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
My suggestion is to use TimerUtils.

Of course no one is going to stop you if you want to use your Timed Struct System within your own map. And you can submit that map to the Maps section and it will probably get approved.

However, for the Spells section, and for the JASS section, this resource should be replaced with TimerUtils or a system that operates the same way as TimerUtils.
 
Status
Not open for further replies.
Top