UnrealScript library and basis for all Acedia Framework mods
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

258 lines
7.8 KiB

/**
* Timer class that generates a signal after a set interval, with an option
* to generate recurring signals.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class Timer extends AcediaObject;
/**
* Because the `Timer` depends on the `Tick()` event, it has the same
* resolution as a server's tick rate (frame rate for clients). This means that
* the `OnElapsed()` signal will be emitted at an interval defined by the
* `Tick()`. If the set interval between two signals is less than that, then
* `Timer` might emit several signals at the same point of time.
* Supposing server's tick rate is 30, but `Timer` is set to emit a signal
* 60 time per second, then on average it will emit two signals each tick at
* the same exact time.
*/
// Is timer currently tracking time until the next event?
var private bool isTimerEnabled;
// Should timer automatically reset after the next event to
// also generate recurring signals?
var private bool isTimerAutoReset;
// Currently elapsed time since this timer has started waiting for the
// next event
var private float totalElapsedTime;
// Time interval between timer's start and generating the signal
var private float eventInterval;
// This flag tells `Timer` to stop trying to emit messages that accumulated
// between two `Tick()` updates. Used in case `Timer` was disables or
// stopped during one of them.
var private bool clearEventQueue;
var private Timer_OnElapsed_Signal onElapsedSignal;
protected function Constructor()
{
onElapsedSignal = Timer_OnElapsed_Signal(
_.memory.Allocate(class'Timer_OnElapsed_Signal'));
}
protected function Finalizer()
{
_.memory.Free(onElapsedSignal);
StopMe(); // Disconnects from listening to `_server.unreal.OnTick()`
}
/**
* Signal that will be emitted every time timer's interval is elapsed.
*
* [Signature]
* void <slot>(Timer source)
*
* @param source `Timer` that emitted the signal.
*/
/* SIGNAL */
public final function Timer_OnElapsed_Slot OnElapsed(AcediaObject receiver)
{
return Timer_OnElapsed_Slot(onElapsedSignal.NewSlot(receiver));
}
/**
* This method is called every tick while the caller `Timer` is running and
* can be overloaded to modify how passed time is affected by the
* time dilation.
*
* For example, to make caller `Timer` count real time you need to passed time
* by the `dilationCoefficient`:
* `return timeDelta / dilationCoefficient;`.
*
* @param timeDelta In-game time that has passed since the last
* `Tick()` event. To obtain real time should be divided by
* `dilationCoefficient`.
* @param dilationCoefficient Coefficient of time dilation for the passed
* `Tick()`. Regular speed is `1.0` (corrected from native value `1.1`
* for Unreal Engine 2).
*/
protected function float HandleTimeDilation(
float timeDelta,
float dilationCoefficient)
{
return timeDelta;
}
/**
* Returns current interval between `OnElapsed()` signals for the
* caller `Timer`. In seconds.
*
* @return How many seconds separate two `OnElapsed()` signals
* (or starting a timer and next `OnElapsed()` event).
*/
public final function float GetInterval()
{
return eventInterval;
}
/**
* Sets current interval between `OnElapsed()` signals for the
* caller `Timer`. In seconds.
*
* Setting this value while the caller `Timer` is running resets it (same as
* calling `StopMe().Start()`).
*
* @param newInterval How many seconds should separate two `OnElapsed()`
* signals (or starting a timer and next `OnElapsed()` event)?
* Setting a value `<= 0` disables the timer.
* @return Caller `Timer` to allow for method chaining.
*/
public final function Timer SetInterval(float newInterval)
{
eventInterval = newInterval;
if (eventInterval <= 0)
{
StopMe();
return self;
}
if (isTimerEnabled) {
Start();
}
return self;
}
/**
* Checks whether the timer is currently enabled (emitting signals with
* set interval).
*
* @return `true` if caller `Timer` is enabled and `false` otherwise.
*/
public final function bool IsEnabled()
{
return isTimerEnabled;
}
/**
* Checks whether this `Timer` would automatically reset after the emitted
* `OnElapsed()` signal, allowing for recurring signals.
*
* @return `true` if `Timer` will emit `OnElapse()` signal each time
* the interval elapses and `false` otherwise.
*/
public final function bool IsAutoReset(float newInterval)
{
return isTimerAutoReset;
}
/**
* Sets whether this `Timer` would automatically reset after the emitted
* `OnElapsed()` signal, allowing for recurring signals.
*
* @param doAutoReset `true` if `Timer` will emit `OnElapse()` signal
* each time the interval elapses and `false` otherwise.
* @return Caller `Timer` to allow for method chaining.
*/
public final function Timer SetAutoReset(bool doAutoReset)
{
isTimerAutoReset = doAutoReset;
return self;
}
/**
* Starts emitting `OneElapsed()` signal.
*
* Does nothing if current timer interval (set by `SetInterval()`) is set
* to a value that's `<= 0`.
*
* If caller `Timer` is already running, resets it (same as calling
* `StopMe().Start()`).
*
* @return Caller `Timer` to allow for method chaining.
*/
public final function Timer Start()
{
if (eventInterval <= 0) {
return self;
}
if (!isTimerEnabled) {
_server.unreal.OnTick(self).connect = Tick;
}
isTimerEnabled = true;
totalElapsedTime = 0.0;
return self;
}
/**
* Stops emitting `OneElapsed()` signal.
*
* @return Caller `Timer` to allow for method chaining.
*/
public final function Timer StopMe()
{
_server.unreal.OnTick(self).Disconnect();
isTimerEnabled = false;
clearEventQueue = true;
return self;
}
/**
* Returns currently elapsed time since caller `Timer` has started waiting for
* the next event.
*
* @return Elapsed time since caller `Timer` has started.
*/
public final function float GetElapsedTime()
{
return totalElapsedTime;
}
private final function Tick(float delta, float dilationCoefficient)
{
local int lifeVersion;
if (onElapsedSignal == none || eventInterval <= 0.0)
{
StopMe();
return;
}
totalElapsedTime += HandleTimeDilation(delta, dilationCoefficient);
clearEventQueue = false;
while (totalElapsedTime > eventInterval && !clearEventQueue)
{
// It is important to modify _before_ the signal call in case `Timer`
// is reset there and already has a zeroed `totalElapsedTime`
totalElapsedTime -= eventInterval;
// Stop `Timer` before emitting a signal, to allow user to potentially
// restart it
if (!isTimerAutoReset) {
StopMe();
}
// During signal emission caller `Timer` can get reallocated and
// used to perform a completely different role.
// In such a case we need to bail from this method as soom as
// possible.
lifeVersion = GetLifeVersion();
onElapsedSignal.Emit(self);
if (!isTimerEnabled || lifeVersion != GetLifeVersion()) {
return;
}
}
}
defaultproperties
{
}