Jump to content
dreadedentity

[CODE SNIPPET] Simple Timer

Recommended Posts

Hello, I'm DreadedEntity pushing out another code snippet for everyone. This time it's a super-simple countdown timer. This script will count down from the amount of seconds you give it, and it will also display it in an optional debug parameter. Actually, all of the parameters are optional. So here's the script:

DE_countdown.sqf:

/*
Usage: [60, "myOutput", true] spawn DE_countDown;
(if using execVM: [60, "myOutput", true] execVM DE_countDown;)

Parameters: 
P1: NUMBER - The number of seconds from which to count down from. (Default: 300, 5 minutes)
P2: STRING - The string name for the global variable which you want to output. (Default: "DE_countdown")
P3: BOOLEAN - Debug. True to display a hint with the current time, false to not. (Default: true)
Returns: Script Handle, allows you to check if the countdown has finished
-Second parameter also allows the content creator to use the timer in any way he/she desires (ie. within a dialog). Format of the output is [hours, minutes, seconds] and can be accessed using:
	_countdown = missionNamespace getVariable "DE_countdown";
*/

DE_timer =
{	
_seconds = [_this, 0, 300, [0]] call BIS_fnc_param;;
_secondsTemp = 0;
_minutes = 0;
_hours = 0;

_globalVar = [_this, 1, "DE_countdown", [""]] call BIS_fnc_param;
_hint = [_this, 2, true, [false]] call BIS_fnc_param;

while {_seconds > 0} do
{	
	_timer = [] spawn	{uiSleep 1;};
	_seconds = _seconds - 1;
	_hours = floor (_seconds / 3600);
	_minutes = floor((_seconds - (_hours * 3600)) / 60);
	_secondsTemp = floor (_seconds - (_hours * 3600) - (_minutes * 60));

	if (_hours < 10) then
	{
		_hours = "0" + (str _hours);
	};
	if (_minutes < 10) then
	{
		_minutes = "0" + (str _minutes);
	};
	if (_secondsTemp < 10) then
	{
		_secondsTemp = "0" + (str _secondsTemp);
	};

	waitUntil {scriptDone _timer};

	missionNamespace setVariable [_globalVar, [_hours, _minutes, _secondsTemp]];

	if (_hint) then
	{
		hint format ["%1:%2:%3", _hours, _minutes, _secondsTemp];
	};
};
};

Enjoy!

  • Like 1

Share this post


Link to post
Share on other sites

can you explain a bit more please?
in my situation i need to start the timer with a trigger, and keep it running until that trigger is true, but if it gets false, it should stop.
thanks

Share this post


Link to post
Share on other sites

Thanks for sharing. One thing to note here is that sleep and uiSleep are not accurate for long durations. I am not sure about the exact reason but I believe it has to do with the way the scheduler works where, if the script with sleep may have to wait for more than the sleep argument declares. The sleep command guarantees that the script will stay "inactive" for at least as much as the argument states. The timing errors of the command accumulate in an unbounded way so the actual timing may drift quite a lot if a long duration is used.

 

Regarding @Alert23's question, I believe that if you change the condition inside the while statement to allow for a stop condition (maybe check a variable/flag that is set outside the loop) and add one instead of subtracting in the second line inside the loop

_seconds = _seconds + 1;

you'll manage to increase the counter instead of decreasing it.

 

Please note that this suggestion is based on mere assumptions and I haven't performed any testing, so treat with caution.

Share this post


Link to post
Share on other sites

Why spawning an uiSleep then waitUntil this scriptdone? As far as you spawn the function, you can use uiSleep inside the while loop.

uiSleep runs even if game is paused, on the contrary of sleep.

 

So, my contrib for timer running both ways (increase/decrease time). added a 4th parameter "_decrease". If set to true (or by default), you have a countdown. If set to false the timer will start from 0 to the value passed in 1st parameter:

 

DE_timer =
{  
  params [["_seconds",300,[0]],["_globalVar","DE_countdown",[""]],["_hint",true,[false]],["_decrease",TRUE,[FALSE]],"_currentCnt"];
 
  private _cnt = [_seconds,0] select _decrease;
  if (!_decrease) then {_seconds = 0};
  while {_cnt != _seconds} do {  
    sleep 1;
    _seconds = _seconds + ([1,-1] select _decrease);
    _currentCnt = [_seconds, "HH:MM:SS"] call BIS_fnc_secondsToString;
    missionNamespace setVariable [_globalVar,_currentCnt splitString ":" apply {parseNumber _x}];
    if (_hint) then {
      hint format ["%1",_currentCnt];
    };
  };
};
 
[60, "myOutput", true,false] spawn DE_timer;

 

Note:

with sleep instead of uiSleep , the timer depends on acceleration time (and pause). With uiSleep, the timer ticks every second, not matter the pause or acceleration time.

 

 

 

 

  • Like 1

Share this post


Link to post
Share on other sites
4 hours ago, ZaellixA said:

One thing to note here is that sleep and uiSleep are not accurate for long durations. I am not sure about the exact reason but I believe it has to do with the way the scheduler works where, if the script with sleep may have to wait for more than the sleep argument declares. The sleep command guarantees that the script will stay "inactive" for at least as much as the argument states.

Yes this is mostly accurate, though it is not a result of long mission time, but rather a side effect. Long missions are likely to accumulate an ever-increasing number of scripts for one reason or another and due to the way the scheduler works this causes the effect above. Specifically, all spawned code is only given a 3ms window to run each frame, any further processing is halted until the next frame. On the next frame, whichever script has not executed in the longest time is resumed, thus the creation of the aforementioned infinite upper bound. See scheduler for (probably) a better explanation

 

2 hours ago, pierremgi said:

Why spawning an uiSleep then waitUntil this scriptdone?

In this case I traded some code size for performance reasons. I wanted there to be as little processing as I could manage, and have it not affect the accuracy of the timer. Unfortunately, I did not fully grasp how the scheduler worked back then so the timer is probably even less accurate than I thought it was due to the behavior described by @ZaellixA above. Notice that the scheduler page did not exist when I made this post. Anyway, here is the reasoning for some of the choices I made:

  • spawn and waituntil - I wanted to make sure that processing time would not affect the accuracy of the timer so eventually I came up with this. Notice that the effect is the calculation and formatting is done before the next cycle, so once the next cycle starts the only processing that needs done is display/saving variable. Otherwise the script would wait for 1 second, then do processing/formatting, then display. It's not much, but it would make the timer permanently inaccurate by a few milliseconds while those calculations are being performed
  • custom formatting - An effort to keep the processing as lightweight as possible. It may seem inefficient at first but it is just 1 comparison then adding 1 character. I would expect this to be many times faster than the BIS function. Essentially I am taking just the functionality that I needed and throwing away everything else, I hoped the result is that less code runs overall and so would be faster
  • Like 1

Share this post


Link to post
Share on other sites

Little improvement for performance, if you don't want to use the old BIS function (the code is twice faster than BI)

 

So, timer / countdown :

(4th parameter "_decrease". set it to true (or by default), for countdown).

DE_MGI_timer =
{
  params [["_seconds",300,[0]],["_globalVar","DE_countdown",[""]],["_hint",true,[false]],["_decrease",TRUE,[FALSE]],"_currentCnt","_rawHH","_hh","_mm","_ss"];

  private _cnt = [_seconds,0] select _decrease;
  private _step = [1,-1] select _decrease;
  if (!_decrease) then {_seconds = 0};
  while {_cnt != _seconds} do {
    sleep 1;
    _seconds = _seconds + _step;
    _rawHH = _seconds/3600;
    _hh = floor _rawHH;
    _mm = floor ((_rawHH - _hh)*60);
    _ss = _seconds mod 60;
    missionNamespace setVariable [_globalVar,[_hh,_mm,_ss]];
    if (_hint) then {
      _currentCnt = ([_hh,_mm,_ss] apply {"0"+ str _x select [[0,1] select (_x >9),2]}) joinString ":";
      hint format ["%1",_currentCnt];
    };
  };
};

Example : [60, "myOutput", true,true] spawn DE_MGI_timer;

 

 

  • Like 2

Share this post


Link to post
Share on other sites
10 hours ago, pierremgi said:

Little improvement for performance, if you don't want to use the old BIS function (the code is twice faster than BI)

 

So, timer / countdown :

(4th parameter "_decrease". set it to true (or by default), for countdown).


DE_MGI_timer =
{
  params [["_seconds",300,[0]],["_globalVar","DE_countdown",[""]],["_hint",true,[false]],["_decrease",TRUE,[FALSE]],"_currentCnt","_rawHH","_hh","_mm","_ss"];

  private _cnt = [_seconds,0] select _decrease;
  private _step = [1,-1] select _decrease;
  if (!_decrease) then {_seconds = 0};
  while {_cnt != _seconds} do {
    sleep 1;
    _seconds = _seconds + _step;
    _rawHH = _seconds/3600;
    _hh = floor _rawHH;
    _mm = floor ((_rawHH - _hh)*60);
    _ss = _seconds mod 60;
    missionNamespace setVariable [_globalVar,[_hh,_mm,_ss]];
    if (_hint) then {
      _currentCnt = ([_hh,_mm,_ss] apply {"0"+ str _x select [[0,1] select (_x >9),2]}) joinString ":";
      hint format ["%1",_currentCnt];
    };
  };
};

Example : [60, "myOutput", true,true] spawn DE_MGI_timer;

 

 

 

If I was using this as a timing clock for a race, how would I stop the timer?

Share this post


Link to post
Share on other sites
9 hours ago, stburr91 said:

 

If I was using this as a timing clock for a race, how would I stop the timer?

It's up to you for a condition added in the while loop.

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×