From 3458bd202f59de7d45a674d94b1bded55fe2187b Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sat, 17 Apr 2021 17:55:15 +0700 Subject: [PATCH] Add timers and `TimeAPI` --- sources/Global.uc | 2 + sources/Time/Events/Timer_OnElapsed_Signal.uc | 38 +++ sources/Time/Events/Timer_OnElapsed_Slot.uc | 40 +++ sources/Time/RealTimer.uc | 32 +++ sources/Time/TimeAPI.uc | 108 ++++++++ sources/Time/Timer.uc | 248 ++++++++++++++++++ 6 files changed, 468 insertions(+) create mode 100644 sources/Time/Events/Timer_OnElapsed_Signal.uc create mode 100644 sources/Time/Events/Timer_OnElapsed_Slot.uc create mode 100644 sources/Time/RealTimer.uc create mode 100644 sources/Time/TimeAPI.uc create mode 100644 sources/Time/Timer.uc diff --git a/sources/Global.uc b/sources/Global.uc index c7b6767..3f9a88b 100644 --- a/sources/Global.uc +++ b/sources/Global.uc @@ -30,6 +30,7 @@ var public BoxAPI box; var public LoggerAPI logger; var public CollectionsAPI collections; var public UnrealAPI unreal; +var public TimeAPI time; var public AliasesAPI alias; var public TextAPI text; var public MemoryAPI memory; @@ -60,6 +61,7 @@ protected function Initialize() text = TextAPI(memory.Allocate(class'TextAPI')); collections = CollectionsAPI(memory.Allocate(class'CollectionsAPI')); unreal = UnrealAPI(memory.Allocate(class'UnrealAPI')); + time = TimeAPI(memory.Allocate(class'TimeAPI')); logger = LoggerAPI(memory.Allocate(class'LoggerAPI')); alias = AliasesAPI(memory.Allocate(class'AliasesAPI')); console = ConsoleAPI(memory.Allocate(class'ConsoleAPI')); diff --git a/sources/Time/Events/Timer_OnElapsed_Signal.uc b/sources/Time/Events/Timer_OnElapsed_Signal.uc new file mode 100644 index 0000000..ec2cc82 --- /dev/null +++ b/sources/Time/Events/Timer_OnElapsed_Signal.uc @@ -0,0 +1,38 @@ +/** + * Signal class implementation for `Timer`'s `OnElapsed` signal. + * 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 . + */ +class Timer_OnElapsed_Signal extends Signal; + +public final function Emit(Timer source) +{ + local Slot nextSlot; + StartIterating(); + nextSlot = GetNextSlot(); + while (nextSlot != none) + { + Timer_OnElapsed_Slot(nextSlot).connect(source); + nextSlot = GetNextSlot(); + } + CleanEmptySlots(); +} + +defaultproperties +{ + relatedSlotClass = class'Timer_OnElapsed_Slot' +} \ No newline at end of file diff --git a/sources/Time/Events/Timer_OnElapsed_Slot.uc b/sources/Time/Events/Timer_OnElapsed_Slot.uc new file mode 100644 index 0000000..62dca7b --- /dev/null +++ b/sources/Time/Events/Timer_OnElapsed_Slot.uc @@ -0,0 +1,40 @@ +/** + * Slot class implementation for `Timer`'s `OnElapsed` signal. + * 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 . + */ +class Timer_OnElapsed_Slot extends Slot; + +delegate connect(Timer source) +{ + DummyCall(); +} + +protected function Constructor() +{ + connect = none; +} + +protected function Finalizer() +{ + super.Finalizer(); + connect = none; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Time/RealTimer.uc b/sources/Time/RealTimer.uc new file mode 100644 index 0000000..0b05c7a --- /dev/null +++ b/sources/Time/RealTimer.uc @@ -0,0 +1,32 @@ +/** + * Class that acts like `Timer`, except it measures real, instead of in-game, + * time. + * 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 . + */ +class RealTimer extends Timer; + +protected function float HandleTimeDilation( + float timeDelta, + float dilationCoefficient) +{ + return timeDelta / dilationCoefficient; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Time/TimeAPI.uc b/sources/Time/TimeAPI.uc new file mode 100644 index 0000000..9bce64d --- /dev/null +++ b/sources/Time/TimeAPI.uc @@ -0,0 +1,108 @@ +/** + * API that provides time-related methods. + * 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 . + */ +class TimeAPI extends AcediaObject; + +/** + * Creates new `Timer`. Does not start it. + * + * @param interval Returned `Timer` will be configured to emit + * `OnElapsed()` signals every `interval` seconds. + * @param autoReset `true` will configure caller `Timer` to repeatedly emit + * `OnElapsed()` every `interval` seconds, `false` (default value) will + * make returned `Timer` emit that signal only once. + * @return `Timer`, configured to emit `OnElapsed()` every `interval` seconds. + * Not started. Guaranteed to be not `none`. + */ +public final function Timer NewTimer( + optional float interval, + optional bool autoReset) +{ + return Timer(_.memory.Allocate(class'Timer')) + .SetInterval(interval) + .SetAutoReset(autoReset); +} + +/** + * Creates and starts new `Timer`. + * + * @param interval Returned `Timer` will be configured to emit + * `OnElapsed()` signals every `interval` seconds. + * @param autoReset Setting this to `true` will configure caller `Timer` to + * repeatedly emit `OnElapsed()` signal every `interval` seconds, `false` + * (default value) will make returned `Timer` emit that signal only once. + * @return `Timer`, configured to emit `OnElapsed()` every `interval` seconds. + * Guaranteed to be not `none`. + */ +public final function Timer StartTimer(float interval, optional bool autoReset) +{ + return Timer(_.memory.Allocate(class'Timer')) + .SetInterval(interval) + .SetAutoReset(autoReset) + .Start(); +} + +/** + * Creates new `RealTimer`. Does not start it. + * + * @param interval Returned `RealTimer` will be configured to emit + * `OnElapsed()` signals every `interval` seconds. + * @param autoReset `true` will configure caller `RealTimer` to repeatedly + * emit `OnElapsed()` every `interval` seconds, `false` (default value) + * will make returned `RealTimer` emit that signal only once. + * @return `RealTimer`, configured to emit `OnElapsed()` every `interval` + * seconds. Not started. Guaranteed to be not `none`. + */ +public final function RealTimer NewRealTimer( + optional float interval, + optional bool autoReset) +{ + local RealTimer newTimer; + newTimer = RealTimer(_.memory.Allocate(class'RealTimer')); + newTimer.SetInterval(interval).SetAutoReset(autoReset); + return newTimer; +} + +/** + * Creates and starts new `RealTimer`. + * + * @param interval Returned `RealTimer` will be configured to emit + * `OnElapsed()` signals every `interval` seconds. + * @param autoReset Setting this to `true` will configure caller `RealTimer` + * to repeatedly emit `OnElapsed()` signal every `interval` seconds, + * `false` (default value) will make returned `RealTimer` emit that signal + * only once. + * @return `RealTimer`, configured to emit `OnElapsed()` every `interval` + * seconds. Guaranteed to be not `none`. + */ +public final function RealTimer StartRealTimer( + float interval, + optional bool autoReset) +{ + local RealTimer newTimer; + newTimer = RealTimer(_.memory.Allocate(class'RealTimer')); + newTimer.SetInterval(interval) + .SetAutoReset(autoReset) + .Start(); + return newTimer; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Time/Timer.uc b/sources/Time/Timer.uc new file mode 100644 index 0000000..844b3e6 --- /dev/null +++ b/sources/Time/Timer.uc @@ -0,0 +1,248 @@ +/** + * 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 . + */ +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 `_.unreal.OnTick()` +} + +/** + * Signal that will be emitted every time timer's interval is elapsed. + * + * [Signature] + * void (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) { + _.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() +{ + _.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) +{ + 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; + onElapsedSignal.Emit(self); + if (!isTimerAutoReset) + { + StopMe(); + return; + } + } +} + +defaultproperties +{ +} \ No newline at end of file