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

Level 6
Joined
Jan 9, 2019
Messages
102
JASS:
library Timeline requires Alloc
//== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//  _____________
//   #  Timeline
//			v2.0a, by Overfrost
//	----------------------------
//
//	  - keeps track of elapsed game-time, allowing easy retrieval of it
//
//	  - separates the tracked time into 4 variables
//		(hours, minutes, current seconds, total seconds)
//
//	  - can also retrieve the time as a generally-formatted string
//
//	  - has a readonly global instance that tracks the timeline of the game
//
//	  - instances can only track time from the moment of their creation
//		or from each time they are restarted
//
//	  - can return time-difference values by comparing current time
//		and previously marked time
//
//  ______________
//   #  Requires:
//
//    - Alloc (Best)
//          github.com/nestharus/JASS/tree/master/jass/Systems/Alloc/Standard
//
//== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//! novjass

    //
    struct Timeline extends array

        //-------------
        // Game-clock
        //
		readonly static integer game.hours
		readonly static integer game.minutes
		readonly static real game.seconds     // current seconds, 0 to ~59.99
		readonly static real game.elapsed     // total seconds
		//
		static method game.getString takes string separator returns string
		// - returns hours, minutes, and (int)seconds as one formatted string
		// - the format is (H:MM:SS) if (hours > 0) or (M:SS) otherwise, showing 0 minutes
		// - spaces are not included as default separator
		//


		//-------------
		// Instancers
		//
		static method create takes nothing returns thistype
		//
		method lock takes nothing returns nothing
		method unlock takes nothing returns boolean
		// - .unlock replaces destroy, returns true if it destroys
		//

		//-----------------
		// Instance-clock
		//
		readonly integer hours
		readonly integer minutes
		readonly real seconds     // current seconds, 0 to ~59.99
		readonly real elapsed     // total seconds
		//
		method getString takes string separator returns string
		// - all of these work the same way as the ones in game-clock,
		//   but they only count any elapsed time since the instance was created
		//

		//--------------------
		// Additional Fields
		//
		readonly real start
		// - elapsed game-time when this Timeline was created
		//
		real length = 0
		// - affects the return value of .remaining
		//
		readonly real remaining
		// - remaining game-time until this Timeline reaches the end of .length
		// - a negative value means it has passed its end by that many seconds
		// - a Timeline can never stop, so this negative value will continue to drop
		//
		readonly real delta
		// - returns the game-time difference of current elapsed game-time and
		//   current delta-marker, then move the marker to current game-time
		// - on Timeline creation, delta-marker is set to .start
		// - if .delta would return 0, it returns the previous non-0 .delta instead
		//

		//---------------------
		// Additional Methods
		//
		method restart takes nothing returns thistype(this)
		// - sets .start to current elapsed game-time
		// - moves delta-marker to current game-time and sets all previous .delta to 0
		//
		method lengthen takes real seconds returns thistype(this)
		// - increases length by seconds
		//

//! endnovjass
//== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

//
private function pgPad2 takes integer pInt returns string
	if (pInt > 9) then
		return I2S(pInt)
	endif
	return "0" + I2S(pInt)
endfunction
//
private module pm
	private static method onInit takes nothing returns nothing
		call pgInit()
	endmethod
endmodule
private struct ps extends array
	//
	private static timer pgTimer = CreateTimer()

	//--------------------------
	// hours, minutes, seconds
	readonly static integer hours = 0
	readonly static integer minutes = 0
	//
	static method operator seconds takes nothing returns real
		return TimerGetElapsed(pgTimer)
	endmethod

	//----------------
	// total seconds
	private static integer pgSeconds = 0
	//
	static method operator elapsed takes nothing returns real
		return pgSeconds + seconds
	endmethod

	//-------------------
	// formatted string
	static method getString takes string aSep returns string
		if (hours > 0) then
			return I2S(hours) + aSep /*
				*/ + pgPad2(minutes) + aSep /*
				*/ + pgPad2(R2I(seconds))
		endif
		return I2S(minutes) + aSep + pgPad2(R2I(seconds))
	endmethod

	//
	private static method pgOnExpire takes nothing returns nothing
		set minutes = minutes + 1
		set pgSeconds = pgSeconds + 60
		//
		if (minutes == 60) then
			set minutes = 0
			set hours = hours + 1
		endif
	endmethod
	private static method pgInit takes nothing returns nothing
		call TimerStart(pgTimer, 60, true, function thistype.pgOnExpire)
	endmethod
	implement pm
endstruct
struct Timeline extends array
	implement Alloc

	//
	static method operator game takes nothing returns ps
		return 0
	endmethod

	//
	private integer pLock

	//---------
	// fields
	readonly real start
	real length
	//
	private real pMark
	private real pDelta

	//
	static method create takes nothing returns thistype
		local thistype this = allocate()
		set pLock = 0
		//
		set start = game.elapsed
		set length = 0
		//
		set pMark = start
		set pDelta = 0
		//
		return this
	endmethod

	//------------------
	// time difference
	method operator elapsed takes nothing returns real
		return game.elapsed - start
	endmethod
	method operator remaining takes nothing returns real
		return start + length - game.elapsed
	endmethod

	//--------
	// delta
	method operator delta takes nothing returns real
		local real lNow = game.elapsed
		if ((lNow - pMark) > 0) then
			set pDelta = lNow - pMark
			set pMark = lNow
		endif
		//
		return pDelta
	endmethod

	//--------
	// clock
	method operator hours takes nothing returns integer
		return R2I(elapsed*.000277778)  // 2.77e-4 = 1/3600
	endmethod
	method operator minutes takes nothing returns integer
		local integer lTotal = R2I(elapsed*.016666667)  // 1.66e-2 = 1/60
		//
		return lTotal - (lTotal/60)*60
	endmethod
	method operator seconds takes nothing returns real
		local real lTotal = elapsed
		//
		return lTotal - R2I(lTotal*.016666667)*60
	endmethod
	//
	method getString takes string aSep returns string
		local integer lTotal = R2I(elapsed)
		local integer lHours = lTotal/3600
		local integer lMinutes = lTotal/60 - lHours*60
		//
		if (lHours > 0) then
			return I2S(lHours) + aSep /*
				*/ + pgPad2(lMinutes) + aSep /*
				*/ + pgPad2(lTotal - (lTotal/60)*60)
		endif
		return I2S(lMinutes) + aSep + pgPad2(lTotal - (lTotal/60)*60)
	endmethod

	//------------
	// assigners
	method restart takes nothing returns thistype
		set start = game.elapsed
		//
		set pMark = start
		set pDelta = 0
		//
		return this
	endmethod
	//
	method lengthen takes real aLength returns thistype
		set length = length + aLength
		//
		return this
	endmethod

	//
	method lock takes nothing returns nothing
		set pLock = pLock + 1
	endmethod
	method unlock takes nothing returns boolean
		set pLock = pLock - 1
		if (pLock < 0) then
			call deallocate()
			return true
		endif
		return false
	endmethod
endstruct

endlibrary
JASS:
//
function GetTimeline takes real aLength returns Timeline
	return Timeline.create().lengthen(aLength)
    // works because .lengthen returns this
endfunction

//
function GetReformattedTime takes nothing returns string
    if (Timeline.game.hours == 0 and Timeline.game.minutes > 9) then
        return "0" + Timeline.game.getString(":")
    endif
    return Timeline.game.getString(":")
endfunction
// reformat the generic format to (H:MM:SS)/(MM:SS)
// quick reminder, the generic format is (H:MM:SS)/(M:SS), notice the single M

//
function Update takes Timeline aTime, unit aU, real aVel, real aAcc returns nothing
	local real lReal = aVel + aAcc*(aTime.elapsed - aTime.delta)
    // from linear motion equation:  v = v0 + a*t
    // gets the v0 of a motion from t(0) to t(delta)
	set lReal = lReal*aTime.delta + 0.5*aAcc*aTime.delta*aTime.delta
    // gets the displacement of the delta-motion
    //
	call SetUnitX(aU, GetUnitX(aU) + lReal)
	call SetUnitY(aU, GetUnitY(aU) + lReal)
endfunction
// I did test this weird function and it works as intended
Changelog:
  • v2.0a:
    - Renamed the library to Timeline.
    - Moved access of GameTime to Timeline.game.
    - Added hours, minutes, seconds, and getString to regular instances.
  • v1.2a: Added restart.
  • v1.1a: Added getString.
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
What about callback event for when X seconds has passed. Should return the triggering handle so that it can be destroyed.
That's pretty much the definition of a timer mate. Other systems already handle timers in glorious ways, so no to that offer.

What this lib intends to do is to handle time, and not timer. This lib will also alleviate TimerTools' un-preciseness by giving precise time-difference between each TimerTools' time-out.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
  • I don't think you need structs here honestly.
  • Also, instead of having a method operator TimerGetElapsed(pgTiemr) what about simply increasing a counter every second. Right now you'd call TimerGetElapsed() multiple times at same time cycle.

    I think this is better:
JASS:
library GameTime initializer Init 
   
    globals
       
        private constant boolean INCLUDE_REAL_TIMER = true    // decimal accuracy
        private constant boolean INCLUDE_INT_TIMER    = true    // second accuracy
   
        integer GT_elapsed        = 0
        integer GT_seconds         = 0
        integer GT_minutes        = 0
        integer GT_hours         = 0
        private timer t 
    endglobals
   
    function GetRealTime takes nothing returns real 
        return TimerGetElapsed(t)
    endfunction 
   
    // We have periodic redundancy but the benefit is that we don't direct access to globals
    private function OnExpiration takes nothing returns nothing 
        set GT_elapsed = GT_elapsed + 1
        set GT_seconds = ModuloInteger(GT_elapsed, 60)
        if GT_seconds == 0 then 
            set GT_minutes = GT_minutes + 1
            if GT_minutes == 60 then
                set GT_hours = GT_hours + 1
            endif
        endif
    endfunction
   
    private function Init takes nothing returns nothing 
        set t = CreateTimer()
        call TimerStart(t, 1.00, true, function OnExpiration)
    endfunction
    module Timeline 
        real timestamp 
       
        static if INCLUDE_REAL_TIMER then
            method startTimerReal takes nothing returns nothing 
                set timestamp = GetRealTime()
            endmethod
           
            method getRemainingTimeReal takes real cd returns real
                local real remaining = timestamp + cd - GetRealTime()
                if remaining < 0 then 
                    return 0
                endif
                return remaining
            endmethod
        endif
        static if INCLUDE_INT_TIMER then
            method startTimerInt takes nothing returns nothing 
                set timestamp = GT_elapsed
            endmethod
           
            method getRemainingTimeInt takes integer cd returns integer
                local integer remaining = timestamp + cd - GT_elapsed
                if remaining < 0 then 
                    return 0
                endif
                return remaining
            endmethod
        endif
       
    endmodule
   
endlibrary

Now what you should add is retrieving different time formats taking seconds as argument and returning a string.

As for the rest of the thing, im not sure I follow it but I don't think you need to create instances of TimeLine to manage that, it should be done inside your struct. What you could do is a module for it though.
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
I don't think you need structs here honestly.
If you mean GameTime, then WorldBounds doesn't need struct either. If you mean Timeline, then separating them is not a bad idea. I place them in the same lib because Timeline is pretty much GameTime made into instances.

Also, instead of having a method operator TimerGetElapsed(pgTiemr) what about simply increasing a counter every second. Right now you'd call TimerGetElapsed() multiple times at same time cycle.
That's there to efficiently handle milliseconds. Non-milliseconds are actually also stored per 60 seconds as you can see.

Now what you should add is retrieving different time formats.
As in string? What do you mean by different time formats?
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
If you mean GameTime, then WorldBounds doesn't need struct either. If you mean Timeline, then separating them is not a bad idea. I place them in the same lib because Timeline is pretty much GameTime made into instances.

That's there to efficiently handle milliseconds. Non-milliseconds are actually also stored per 60 seconds as you can see.

As in string? What do you mean by different time formats?

To add GetAccurateTime() also added a TimeLine module, you could theoretically add a sturct too implementing the module but not sure how much it would be used.

Yes format as String: hh:mm:ss mm:ss and so on.

One shouldn't be using GetAccurateTime() inside struct onInit so there is no need for module initialization is what I'm trying to say. And if one does, I'm pretty sure it will just return 0 anyway.

=================================================================

JASS:
library GameTime initializer Init 
   
    globals
       
   
        private constant boolean INCLUDE_TIME_EVENT_S    = true
        private constant boolean INCLUDE_TIME_EVENT_M    = true
        private constant boolean INCLUDE_TIME_EVENT_H    = true
       
        integer GT_elapsed        = 0
        integer GT_seconds         = 0
        integer GT_minutes        = 0
        integer GT_hours         = 0
       
        constant integer EVENT_EVERY_SECOND         = 1
        constant integer EVENT_EVERY_MIN            = 2
        constant integer EVENT_EVERY_HOUR            = 3
        real GT_event       
       
        private timer t         = null 
        private real remainingReal
    endglobals
   
    function GetRealTime takes nothing returns real 
//debug    if t == null then                     Not sure if necessary TBH
//debug        call BJDebugMsg("ERROR: You cannot call GetRealTime from initialization")   
//debug        return 0.
//debug    endif
        return GT_elapsed + TimerGetElapsed(t)
    endfunction 
   
    // We have periodic redundancy but the benefit is that we have direct access to updated globals
    private function OnExpiration takes nothing returns nothing 
        set GT_elapsed = GT_elapsed + 1
        set GT_seconds = GT_elapsed - (GT_elapsed/60)*60
        static if INCLUDE_TIME_EVENT_S then
            set GT_event = 0
            set GT_event = EVENT_EVERY_SECOND
        endif   
        if GT_seconds == 0 then 
            set GT_minutes = GT_minutes + 1
            static if INCLUDE_TIME_EVENT_M then
                set GT_event = 0
                set GT_event = EVENT_EVERY_MIN
            endif
            if GT_minutes == 60 then
                set GT_hours = GT_hours + 1
                static if INCLUDE_TIME_EVENT_H then
                    set GT_event = 0
                    set GT_event = EVENT_EVERY_HOUR
                endif
            endif
        endif
    endfunction
    module InstanceTimerReal 
        readonly real duration           
        readonly real timestamp           
        readonly boolean pause       
   
        method startTimer takes real duration returns nothing 
            set .timestamp = GetRealTime()
            set .duration = duration
            set .pause = false
        endmethod
       
        method getRemainingTime takes nothing returns real
            if .pause != 0 then 
                return .duration
            else 
                return.timestamp + .duration - GetRealTime()
            endif
        endmethod
   
        method pauseTime takes nothing returns nothing 
            set .pause = true
            set .duration = .duration - pause + .timestamp
        endmethod 
       
        method resumeTime takes nothing returns nothing
            set .timestamp = GetRealTime()
            set .pause = false
        endmethod
       
        method isRunning takes nothing returns boolean 
            return not .pause
        endmethod 
    endmodule
   
 
    /*
        TEST STUFF
    */
 
 
    struct Timeline extends array
 
        implement InstanceTimerReal
    
    endstruct
 
    private function Resume takes nothing returns nothing
        call BJDebugMsg("RESUMED")
        call Timeline(0).resumeTime()
    endfunction
 
    globals
    private boolean hasPausedOnce = false
    endglobals
    private function Test takes nothing returns nothing
        local timer t2 = GetExpiredTimer()
        local timer t3 = CreateTimer()
        local real remaining = Timeline(0).getRemainingTime()
        call BJDebugMsg(R2S(remaining))
        if remaining <= 5 and Timeline(0).isRunning() and remaining > 0 and not hasPausedOnce then
            call Timeline(0).pauseTime()
            call TimerStart(t3, 3, false, function Resume)
            call BJDebugMsg("PAUSED!")
            set hasPausedOnce = true
        elseif remaining <= 0 then
            call PauseTimer(t2)
        endif
    endfunction
 
    private function Init takes nothing returns nothing
        local timer t2  = CreateTimer()
        set t = CreateTimer()
        call TimerStart(t, 1.0, true, function OnExpiration)
        call Timeline(0).startTimer(10)
        call TimerStart(t2, 0.031, true, function Test)
    endfunction
 
endlibrary

Still don't think you're gaining anything in most cases.
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
Yes format as String: hh:mm:ss mm:ss and so on.
Noted.

so there is no need for module initialization
Perhaps. But it won't hurt anything by using module init.
---

Nope to events because they're what timers do. "Pausing" a timeline seemed to be nice, but after thinking more about it, I'd rather not have it. Will only add getString on next update, and that's it. I'm trying as hard as I can to keep this as simple and accurate as it can be.

EDIT:
Updated to v1.1a. (28/1/2019)
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
Would be nice to be able to get hours, minutes and seconds and a formatted string with TimeLine. At the moment only GameTime has these features and when you create a TimeLine this information gets lost. You could have simple functions to convert seconds (real) into hours, minutes, seconds and a formatted string.
 
Level 6
Joined
Jan 9, 2019
Messages
102
Would be nice to be able to get hours, minutes and seconds and a formatted string with TimeLine. At the moment only GameTime has these features and when you create a TimeLine this information gets lost. You could have simple functions to convert seconds (real) into hours, minutes, seconds and a formatted string.
You enlightened me mate! Now GameTime exists within Timeline instead of oddly standing out alone. I needed some time to write it, that's why this reply is rather late.

Updated to v2.0a. (3/2/2019)
 
Top