• 🏆 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] PseudoTimer

Level 7
Joined
Apr 27, 2011
Messages
272
This thing is actually inspired from the knockback system i submitted last time, because i just found out that i can make a good timer utility based on its coding structure. That's why i decided to break it down into this to make give it more applications. :thumbs_up:

system code:
JASS:
library PseudoTimer // v1.0.0
//===========================================================================
//	PseudoTimer by Alain.Mark
//
// -Info-
//	-This is a great timer system, simply because it allows you to create and manipulate
//	"PseudoTimers", these things aren't handles but they work like timers in a much better
//	fashion. In addition, these "PseudoTimers" have an index of their own that you can use
//	to easily attach values to them. The indexing is very safe and the index retrieval
//	is also very fast. Unlike timers you can actually add multiple callback functions to "PseudoTimers"
//
// -API-
//	-function CreatePseudoTimer takes nothing returns integer
//	-function ReleasePseudoTimer takes integer whichPseudoTimer returns nothing
//    -function StartPseudoTimer takes integer whichPseudoTimer, real duration returns nothing
//	-function OnPseudoTimerCallback takes integer whichPseudoTimer, code whichCode returns nothing
//	-function IsOneSecond takes nothing returns boolean
//	-function ThisPseudoTimer takes nothing returns integer // returns the current pseudo-timer
//    -function GetPseudoTimerId takes integer whichPseudoTimer returns integer
//
//===========================================================================
	globals
		private constant timer Tt = CreateTimer() // this is the only timer that the system will need
		private constant real  BASE_EXPIRATION = 0.031250000 // pseudotimers will run loop at this constant period
	endglobals
	
//===========================================================================
	globals		
	    private integer n=-1
		private integer array O
        private integer e
        private integer thisPseudoTimer
	endglobals
	
//===========================================================================
	globals
		private integer id =-1
		private integer array idQ // pseudo-timer id queue
	endglobals
	
//===========================================================================
	globals
		private integer t =-1
		private integer m = 0 
		private integer array rt // recycled pseudo-timers
        private trigger array cQ // trigger queue
	endglobals
    
//===========================================================================
	globals
		private real array rQ // pseudo-timer duration queue
	endglobals
	
//===========================================================================		
	globals
		private real sec=0.00
	endglobals
	
//===========================================================================
	private function OnLoop takes nothing returns nothing
		set e=n
		set sec=sec+BASE_EXPIRATION
		loop
            set thisPseudoTimer=O[e] // 2-d array acces
			if(rQ[thisPseudoTimer]!=0.00)then
				set rQ[thisPseudoTimer]=rQ[thisPseudoTimer]-BASE_EXPIRATION
                call TriggerEvaluate(cQ[thisPseudoTimer])
			else
				call TriggerClearConditions(cQ[thisPseudoTimer])
				set rQ[thisPseudoTimer]=rQ[O[n]]	// takes the top and places it on a free one, example: [1][2][3] -> [-free-][2][3] -> [3][2]
				set cQ[thisPseudoTimer]=cQ[O[n]]
				set idQ[thisPseudoTimer]=idQ[O[n]]
                set rt[m]=thisPseudoTimer
                set m=m+1
				set n=n-1
				if(n==-1)then
					call PauseTimer(Tt)
				endif
			endif
			set e=e-1
			exitwhen e<0
		endloop
		if(sec==1.00)then
			set sec=0.00
		endif
	endfunction
 
//===========================================================================
	function CreatePseudoTimer takes nothing returns integer
		if(m==0)then
			set t=t+1
            set id=id+1
			set cQ[t]=CreateTrigger() // initialize the pseudo-timer's trigger sheet
			set idQ[t]=id // set the psuedo-timer's index
			return t
		endif
		set m=m-1
		return rt[m] // returns a recycled pseudo-timer
	endfunction
	
//===========================================================================
	function FreezePseudoTimer takes integer i returns nothing
        set rQ[i]=0.00 
    endfunction
    	
//===========================================================================
	function StartPseudoTimer takes integer i, real r returns nothing
        set n=n+1
        set rQ[i]=r
		set O[n]=i 
		if(n==0)then
			call TimerStart(Tt,BASE_EXPIRATION,true,function OnLoop)
		endif
	endfunction
	
//===========================================================================
	function OnPseudoTimerCallback takes integer i, code c returns nothing
        call TriggerAddCondition(cQ[i],Filter(c))
        return
	endfunction
	
//===========================================================================
	function IsOneSecond takes nothing returns boolean
		return sec==1.00
	endfunction
	
//===========================================================================
	function ThisPseudoTimer takes nothing returns integer
        return thisPseudoTimer
	endfunction
    
//===========================================================================
	function GetPseudoTimerId takes integer i returns integer
		return idQ[i] // say goodbye to dangerous offsetting and slow hashtables
	endfunction
    
//===========================================================================
endlibrary

sample usage:
JASS:
scope sample initializer onInit
	globals
		private string array STR
	endglobals
	
	private function A takes nothing returns nothing
		call BJDebugMsg(STR[GetPseudoTimerId(ThisTimer())])
	endfunction
	
	private function B takes nothing returns nothing
		if(IsOneSecond())then
			call BJDebugMsg(STR[GetPseudoTimerId(ThisTimer())])
		endif
	endfunction
	
	private function onInit takes nothing returns nothing
		local integer pseudoTimerA=CreatePseudoTimer() // as you can see a PseudoTimer is actually an integer, but i do not suggest using it as an index for arrays and stuff. (that's the reason why i provided the GetPseudoTimerId  for that purpose)
		local integer pseudoTimerB=CreatePseudoTimer()
		local integer idA=GetPseudoTimerId(A)
		local integer idB=GetPseudoTimerId(B)
		call OnPseudoTimerCallback(pseudoTimerA,function A)
		call OnPseudoTimerCallback(pseudoTimerA,function B)
		set STR[idA]="0.03125"
		set STR[idB]="1.00000"
		call StartPseudoTimer(pseudoTimerA,100.00)
		call StartPseudoTimer(pseudoTimerB,100.00)
	endfunction
	
endscope
 
Last edited:
Moar timer systems? :O
Well, at least it's not obvious to me what timer system this can be replaced with.
It's the second parameter in your StartPseudoTimer function that makes this unique.

I'm still not sure though, for I just woke up.

JASS:
if(n==-1)then
    call TimerStart(PT,0.00,false,null)
endif

->

JASS:
if n==-1 then
    call PauseTimer(PT)
endif

- Instead of storing the max-time, the interval and using another real to store the duration, you should store only the interval and the max-time. Every interval, set max-time = max-time - interval and when max-time is 0, you'd know you're done.

- Your TerminatePseudoTimer function has a useless integer parameter.

- Your OnPseudoTimerCallback function shouldn't clear the conditions of a trigger, you should do that when you destroy a PseudoTimer. With your current algorithm, this will only allow one code per PseudoTimer. If I were to write a system that uses a static PseudoTimer, it would be very bug-prone :/

- Your variable names need to be longer so that we could understand them.
I mean I can understand them totally, but a lot of people won't.

JASS:
o -> current
n -> stack
mQ -> maxQueue
tQ -> intervalQueue
cQ -> triggerQueue
tcQ -> durationQueue
fcQ -> codeRegisteredQueue
idQ -> timerIdQueue

There are chunks of code like this:

JASS:
                    set tQ[o]=tQ[n]
                    set mQ[o]=mQ[n]
                    set cQ[o]=cQ[n]
                    set tcQ[o]=tcQ[n]
                    set fcQ[o]=fcQ[n]
                    set rid[m]=idQ[o]

That you're repeating. You could use textmacros with names like DEALLOCATE and ALLOCATE to make this a lot more readable :)
 
Level 7
Joined
Apr 27, 2011
Messages
272
Code:
updates made:
-changed the API
-added a "CreatePseudoTimer" function
-the "StartPseudoTimer" function now takes a pseudo-timer (which is an integer) and a duration. the interval cannot be set anymore because it now loops at a base interval of "0.031250000" and removed the useless integer argument.
-added a "IsOneSecond" function
-added a "ThisPseudoTimer" function
-most functions are now inline friendly (4)
-added safety and fixed a bug by adding the  "GetPseudoTimerId" function
-the code now runs a little bit faster and a lot more safer
 
Last edited:
JASS:
    function OnPseudoTimerCallback takes integer i, code c returns nothing
        call TriggerAddCondition(cQ[i],Filter(c))
        return
    endfunction

Don't you want it to inline? D:

In my opinion, a struct syntax would seem cooler here.
Could you add one while keeping the Jass API? :3

JASS:
struct PseudoTimer extends array
    static method operator current takes nothing returns thistype
    static method create takes nothing returns thistype
    method start takes real r returns nothing
    method stop takes nothing returns nothing
    method enqueueCode takes code c returns nothing
    method enqueueBoolexpr takes boolexpr b returns nothing
    method operator id takes nothing returns integer
endstruct
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
That error is by pJass, PipeDream (I think) had the idea to not let "return nothing" compile at all, if it is passed to a "Filter" or "Condition" function, even though World Editor lets it compile just fine (because it is FINE).

Now, that "return nothing" used to crash Macs, but it doesn't any more. Probably the 1.23 patch fixed it. Most avid Mac users are running Lion anway, and Lion doesn't even RUN WarCraft III. At all.

Now, the "return nothing" is just an annoying thing left over from pJass. And we can't get rid of it without telling everyone to download a new pJass.

Troll-Brain has stated he may make an updated pJass.
 
Level 7
Joined
Apr 27, 2011
Messages
272
Oh? Then I guess it's more logical to me to use something like CTL32 or even Timer32, which only use a trigger evaluation per struct instead of a trigger evaluation per instance, like what you have here.

can you please show me an example, if you don't mind? Im actually planning to add code sharing features but im still working on it, where in two or more instances that has the same callback function will only combine into one instance.

edit: and oh I also realized that this system has a good indexing method.
 

Bribe

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

JASS:
scope sample initializer onInit
    globals
        private string array STR
    endglobals
    
    private function A takes nothing returns nothing
        call BJDebugMsg(STR[GetPseudoTimerId(ThisTimer())])
    endfunction
    
    private function B takes nothing returns nothing
        if(IsOneSecond())then
            call BJDebugMsg(STR[GetPseudoTimerId(ThisTimer())])
        endif
    endfunction
    
    private function onInit takes nothing returns nothing
        local integer pseudoTimerA=CreatePseudoTimer() // as you can see a PseudoTimer is actually an integer, but i do not suggest using it as an index for arrays and stuff. (that's the reason why i provided the GetPseudoTimerId  for that purpose)
        local integer pseudoTimerB=CreatePseudoTimer()
        local integer idA=GetPseudoTimerId(A)
        local integer idB=GetPseudoTimerId(B)
        call OnPseudoTimerCallback(pseudoTimerA,function A)
        call OnPseudoTimerCallback(pseudoTimerA,function B)
        set STR[idA]="0.03125"
        set STR[idB]="1.00000"
        call StartPseudoTimer(pseudoTimerA,100.00)
        call StartPseudoTimer(pseudoTimerB,100.00)
    endfunction
    
endscope

CTL by Nestharus:

JASS:
scope sample initializer onInit
    globals
        private string array STR
        private real array time
    endglobals
    
    private struct A extends array
        implement CTL
        implement CTLExpire
            call BJDebugMsg(STR[this])
            set time[this] = time[this] + 0.031250000
            if time[this] == 100 then
                call this.destroy()
            endif
        implement CTLNull
        implement CTLEnd
    endstruct
    
    private struct B extends array
        implement CTL
        implement CTLExpire
            set time[this] = time[this] + 0.031250000
            if time[this] == time[this] / 1.000000000 * 1.000000000 then
                call BJDebugMsg(STR[this])
                if time[this] == 100.00 then
                    call this.destroy()
                endif
            endif
        implement CTLNull
        implement CTLEnd
    endstruct
    
    private function onInit takes nothing returns nothing
        set STR[A.create()]="0.03125"
        set STR[B.create()]="1.00000"
    endfunction
    
endscope
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
The modules in CTL are actually pretty lightweight, they don't add much code bloat.

Flexibility is gained at a loss of performance, yours is called until a certain cutoff time is met, regardless of if the user wants to do something before that time or not. Which is why using something like TimerUtils makes more sense.

As far as looping through all instances and decreasing their remaining time until it's up, that is not a new concept. GUI users do that all the time. I don't see a need for a seperate resource for something so simple, when you could just code it yourself and not need a rapidly expiring timer + often undesired function evaluations.

I recommend you to recode this to be more like CTL, where you implement a module that also handles the remaining time (like in my example, not having to code it myself could be useful). However, even then I couldn't say this is worth its own submission, because it is only a very small benefit to the user.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You shouldn't be merging timers via putting them on a common timeout and reducing their remaining time until they fire. That is ridiculously slow ;p. If you have a 1 second timeout and a .03125 period, it'll fire 32 times instead of 1 time ;o. Furthermore, if you are doing the loop method, then it'll fire 1 extra time for every timer (2x timers) + extra overhead on reducing times, which is a lot. The method employed by my old 1 timer system on a tree was better, and it was still very, very slow, which is why it was trashed ^)^.


The only good way to do timer merging is to get rid of some of the accuracy and do actual timer merging so that you don't have the extra counter overheads and so that they run exactly when they are supposed to run ^)^.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Alain.Mark, a 1 timer system can never compete with a multi timer system. Don't argue with me, I know what I'm talking about. So long as you submit a 1 timer system, it is guaranteed to be gy'd, no questions asked, no benchmarks done, no testing needed. If it's a 1 timer system, it will be gy'd, end of story.


I've already tested 1 timer systems to their full capabilities and they can not compete with multi timer systems.



And yes, even Bribe's little snippet there is bad. Why? Because it expires multiple times instead of once. If it expires more than once, you have already failed >.>.


Standard timers are incredibly difficult to beat in terms of speed. When I say incredibly difficult, I mean incredibly difficult. You really have to pull out every trick in the book, and even then, they will still beat you in some scenarios. If you look at Timer Tools, it has like 3 or 4 different modules for different situations.



A 1 timer system isn't possible. Even if you use the best algorithms out there, you will still lose to standard timers. So yes, even if you 100% somehow manage to perfect this system and learn more advanced data structures after like 5 months of refinement, this system will still be gy'd. Why? Because it's a 1 timer system and 1 timer systems can't beat multi timer systems ; p.


My suggestion is to just give up with this and move on to something else. Really, not much more can be done with timers. You can take my current stuff and possibly improve on them (I think there were a couple of things that could be improved that I just haven't wanted to do yet) or you can take TimerUtils and improve on it.


Now, I can see if you wanted to do this for fun, like bragging that you made a spiffy 1 timer system, but if that is the case, submit it to Triggers & Scripts to show it off ^)^. A 1 timer system is just never going to win against multiple timers ^)^.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Because as you get more and more timers, the updates of the timer tree become larger and larger -> log(n). Due to this, after a sufficient # of timers are up and running, it will start losing against standard timers. The more timers that are up, the more dramatically it will lose out. This was the case when two perfectly coded heavily optimized 1 timer system experiments were coded by me. Believe me, they blow yours away in terms of performance >.>. They also blew away KT2, and KT2 is a multi timer system, lolz.


When I did experiment #3, which was Timer Tools, I found that Timer Tools performed better than standard timers and the 1 timer systems, even under terrible conditions.


The 1 timer systems were constructed via a min binary heap and timestamps. The timestamps ensured that you only needed to rely on comparisons rather than having to update remaining times. The min binary heap was the lightest structure that could be used for such a system as it was O(1) to run the next expiring timer, but log(n) to perform an update.


A Min Binary Heap is the best possible way to code a single timer system in JASS, and that best possible structure failed against standard timers when enough timers were up and running.


This is the reason why any single timer system, and most timer systems in general, are just plainly gy'd. I'd personally just insta gy any single timer system. Bribe's a pretty nice fellow ;p.
 
Level 7
Joined
Apr 27, 2011
Messages
272
ok. im just curious why is a Binary Heap is better than -> reusing a free slot by filling it with a top instance and decrementing the instances to reduce loops?

[1][2][3] -> [1][free][3] -> [1][3 becomes 2][null] -> [1][2][null]

how many timers do you think it will need before a single timer system is outperformed by a multiple timer system?
 
Last edited:
Top