Counters README
WARNING : I can be wrong, and I encourage you to do your own research before trusting my words at face-value.
Counters is a package for Bevy projects, specifically for scenarios where you need custom types to handle time or generalized counting beyond what you may find in Bevy's Timers or types focused on counting.
If you enjoy negative numbers, an easy way to grab digits, and like to manage the memory of your types then I encourage you to read on and explore the docs.rs page for the package.
Fair warning, the docs.rs page will ultimately contain more information than this README. But that's because it's purposed to help explain things while actively coding with the types in this package. The README is more organized and purposed for summary, all at the cost of less explanation.
cargo add mirth_engine_tickers
.add_plugin(Tickers{}) to your Bevy app.ticker_reflect : Will cause all ticker types to reflect. Will add the bevy_reflect dependency and allow for the Tickers plugin to be usable.ticker_systems : Will turn on all systems for every ticker type. Will add the bevy_time dependency and allow for the Tickers plugin to be usable.ticker_serialize : Will make it so that ticker types can be serialized and deserialized. Will enable ticker_reflect, add the serde dependency, and allow for the Tickers plugin to be usable.
cargo add mirth_engine_tickers --no-default-features --features ticker_reflect, ticker_systems, ticker_serialize
.add_plugin(Tickers{}) to your Bevy app.cargo add mirth_engine_tickers --no-default-features
A struct acting as a Bevy plugin used to enable ticker systems and reflections for ticker types. Use .add_plugin(Tickers{}) inside a Bevy app if you wish to make use of said systems and reflections.
In short, Ticker is a struct used to track the time between events and uses the types TickerValue, TickerPrecision, TickerFloatBridge, and TickerBehavior to support itself.
In long, Ticker is a struct that can be used to create self-contained counters that advance a value (current_value) between two boundaries (start_value and end_value) at a fixed or dynamic rate, driven by calls to the .tick() method. Depending on its behavior, a ticker can loop back to start_value when it reaches a boundary, stop at the boundary it hits, or become locked in place once it reaches end_value.
"Time" here is intentionally generic — .tick() doesn't assume seconds, frames, or any specific unit. It just compares whatever delta you feed .tick() against time_interval and advances current_value accordingly, which is what lets the same ticker logic drive a frame-timed cooldown, a world clock, or anything else that changes over some unit of "time".
A ticker's unit of time will be equal to the unit of the number you pass into its .tick() call(s).
Ticker holds the following fields:
start_valuecurrent_valueend_valuetime_intervalis_pausedis_ticking_upis_handling_time_spikesbehaviorTicker has the following type declarations:
Ticker<i8, f16> : 51+ BitsTicker<i16, f16> : 83+ BitsTicker<i32, f16> : 131+ BitsTicker<i8, f32> : 91+ BitsTicker<i16, f32> : 115+ BitsTicker<i32, f32> : 163+ BitsTicker<i8, f64> : 155+ BitsTicker<i16, f64> : 179+ BitsTicker<i32, f64> : 227+ BitsThe "+" in the bit count is to recognize that the behavior field holds an enum value, and I have no idea how many bits an enum declaration represents.
What Exactly is Mutable in Tickers?
First off, a ticker should always be declared with the mut keyword. We do this since tickers are purposed to tick, and ticking always has the potential to change current_value and stored_time. From there, the actual mutability of a ticker's fields is dependent on its behavior. Here is some info regarding such a thing:
TickerBehavior is Mutable
stored_time can be manipulated directly.stored_time can be changed indirectly through the .hard_reset() method.TickerBehavior is Immutable
TickerBehavior is Mutable or Immutable
stored_time and current_value will be changed indirectly through ticking. How and when these fields change is based on the ticker's boolean fields, what behavior the ticker is set to, and when .tick() gets called.current_value and stored_time's change from .tick() as a factor of mutability. Tickers are purposed to tick, hence the changing of such fields should always be expected unless a ticker is paused.A trait for implementing the V generic to define integer primitives a Tickercan store for its start_value, end_value, and current_value.
V supports i8, i16, and i32.A trait for implementing the P generic to define float types a Ticker can use for its precision in tracking time, P impacts the time_interval and stored_time fields.
P supports f16, f32, and f64.A trait for implementing the F generic which grants the ability for f16, f32, and f64 to be passed in for the float fields of a Ticker constructor, no matter the precision a ticker is set to.
F supports f16, f32, and f64.An enum that establishes the different types of behavior a Ticker can use. Classifications of behavior are as follows:
Looper
current_value hits either start_value or end_value. When a loop triggers, current_value is reset back to start_value.MutLooper
current_value hits either start_value or end_value. When a loop triggers, current_value is reset back to start_value.Oneshot
current_value to a boundary's value if current_value were to hit start_value or end_value; start and end values are the boundaries.current_value hits end_value. This ensures the time state is completely reset once it reaches the end.MutOneshot
current_value to a boundary's value if current_value were to hit start_value or end_value; start and end values are the boundaries.current_value hits end_value. This ensures the time state is completely reset once it reaches the end.Freezing
current_value hits end_value.current_value hits end_value. This ensures the time state is completely reset once it reaches the end.Advances a ticker by taking in a passing of time between 2 events, usually for events that happen both consistently and constantly (such as when frames render); it does have the ability to take in any delta time.
If you're wondering what exactly ticking does, here is the simplified version (the specifics can be found in the doc comments):
stored_time by the value passed to the .tick() call (elapsed_time_between_events).stored_time is greater than or equal to the time_interval, change current_value.stored_time to the result of stored_time %= time_interval. We do this to carry over our remainder to keep the state of time accurate to the events that are being tracked.Creates a MutLooper that has the following properties:
start_value is set to 0.current_value from 0 to the ticker's max integer value (V::MAX).end_value is set to the ticker's max integer value (V::MAX).time_interval is set to 1.0.Used for completely defining a custom ticker.
Creates an unpaused Looper ticker that ticks current_value from the supplied starting value to the passed end value. The ticker is immutable and will loop when current_value hits either boundary, resetting back to the start.
If the initial start and end values are equal, the tick direction defaults to up.
Creates an unpaused Looper ticker with fully custom starting fields. The ticker is immutable and will loop when current_value hits either boundary, resetting back to start_value.
Creates an unpaused MutLooper ticker that ticks current_value from the supplied starting value to the passed end value. The ticker is mutable and will loop when current_value hits either boundary, resetting back to the start.
If the initial start and end values are equal, the tick direction defaults to up.
Creates an unpaused MutLooper ticker with fully custom starting fields. The ticker is mutable and will loop when current_value hits either boundary, resetting back to start_value.
Creates an unpaused Oneshot ticker that ticks current_value from the supplied starting value to the passed end value. The ticker is immutable and will assign current_value to whichever boundary it hits. stored_time resets to 0.0 once current_value reaches end_value.
If the initial start and end values are equal, the tick direction defaults to up.
Creates an unpaused Oneshot ticker with fully custom starting fields. The ticker is immutable and will assign current_value to whichever boundary it hits; stored_time resets to 0.0 once current_value reaches end_value.
Creates an unpaused MutOneshot ticker that ticks current_value from the supplied starting value to the passed end value. The ticker is mutable and will assign current_value to whichever boundary it hits. stored_time resets to 0.0 once current_value reaches end_value.
If the initial start and end values are equal, the tick direction defaults to up.
Creates an unpaused MutOneshot ticker with fully custom starting fields. The ticker is mutable and will assign current_value to whichever boundary it hits; stored_time resets to 0.0 once current_value reaches end_value.
Creates an unpaused Freezing ticker that ticks current_value from the supplied starting value to the passed end value. The ticker begins mutable but becomes immutable once current_value hits end_value; stored_time resets to 0.0 at that point.
If the initial start and end values are equal, the tick direction defaults to up.
Creates an unpaused Freezing ticker with fully custom starting fields. The ticker begins mutable but becomes immutable once current_value hits end_value; stored_time resets to 0.0 at that point.
Creates a copy of a passed ticker.
Creates a copy of a passed ticker with the only field change being the behavior, which is set to the passed TickerBehavior value.
Useful for safely turning an immutable ticker into a mutable one — copy its values into a new mutable instance, then swap the immutable original out for the copy.
Returns the start_value of a ticker.
Returns the current_value of a ticker.
Returns the end_value of a ticker.
Returns the time_interval of a ticker — the amount of time it takes for current_value to increase or decrease by 1.
A ticker has no built-in concept of "seconds" or any other unit; time_interval and stored_time are just two numbers compared inside .tick(), and the unit they represent depends entirely on what's passed into .tick().
Returns the stored_time of a ticker — the leftover remainder from the last .tick() call, not the total elapsed time since creation or reset.
Mainly useful for debugging, logging, or custom structures that need to inspect or manually carry over a ticker's in-progress timing state.
Returns true if the ticker is paused, false otherwise.
Returns true if the ticker is unpaused, false otherwise.
Returns true if a ticker is set to tick its current_value up, false otherwise.
Returns true if a ticker is set to tick its current_value down, false otherwise.
Returns true if the ticker can change current_value by any amount greater than or equal to 0 in a single .tick() call, catching up all built-up time at once.
Returns false if the ticker can only change current_value by 1 or 0 per .tick() call, no matter how much time has built up between calls.
Returns the behavior type of the ticker.
Returns the digit at the given decimal place of current_value, where place is 1-indexed from the right (ones place = 1, tens place = 2, hundreds place = 3, etc.), up to place = 10 (billions place).
Will always return a positive value if the digit exists, otherwise None is returned.
Changes start_value to the passed value.
If the new start_value shifts the valid range such that current_value is left outside the boundaries, current_value is automatically clamped to the nearest valid edge. start_value itself cannot go outside V::MIN to V::MAX and will be clamped down if it does.
Changes current_value to the passed value.
current_value cannot go outside the range that start_value and end_value create and will be clamped down if it does.
Changes end_value to the passed value.
If the new end_value shifts the valid range such that current_value is left outside the boundaries, current_value is automatically clamped to the nearest valid edge. end_value itself cannot go outside V::MIN to V::MAX and will be clamped down if it does.
Prevents .tick() calls on a ticker from doing their job.
Allows .tick() calls on a ticker to do their job.
Causes the ticker's current_value to count up.
Causes the ticker's current_value to count down.
Makes it so that .tick() calls on a ticker add or subtract all built-up integer time since the last .tick() call to current_value (direction depends on is_ticking_up), with any floating remainder carried over into stored_time for the next call.
Makes it so that .tick() calls on a ticker only add or subtract 1 to current_value (direction depends on is_ticking_up), no matter how much time has built up between calls.
Switches the behavior of a ticker to the passed TickerBehavior type.
Does not work for tickers with immutable behavior — use new_copy_with_behavior_change to simulate switching an immutable ticker to mutable behavior, then substitute the copy in for the original.
Returns true if current_value and start_value are equal to one another, false otherwise.
Useful in oneshot tickers that count down to start_value to determine if the oneshot is finished.
Returns true if current_value and end_value are equal to one another, false otherwise.
Useful in oneshot tickers that count up to end_value to determine if the oneshot is finished.
Returns true if start_value and end_value are equal to one another, false otherwise.
This is mainly useful when the bounds of a mutable ticker are slowly tightening and you need to detect when they've finally met.
Returns the difference between current_value and start_value. Will only return positive numbers, including 0.
Returns the difference between current_value and end_value. Will only return positive numbers, including 0.
Returns the difference between start_value and end_value. Will only return positive numbers, including 0.
Adds to the start_value of the ticker by the passed value; negatives subtract. The result will never overflow or wrap, staying within V::MIN to V::MAX (inclusive).
If the new start_value shifts the valid range such that current_value is left outside the boundaries, current_value is automatically clamped to the nearest valid edge.
Adds to the current_value of the ticker by the passed value; negatives subtract. The result will never overflow or wrap, staying within start_value to end_value (inclusive).
Adds to the end_value of the ticker by the passed value; negatives subtract. The result will never overflow or wrap, staying within V::MIN to V::MAX (inclusive).
If the new end_value shifts the valid range such that current_value is left outside the boundaries, current_value is automatically clamped to the nearest valid edge.
Adds to the time_interval of the ticker by the passed value; negatives subtract. time_interval can never be 0, negative, or exceed P::MAX, since that would cause .tick() to produce erratic values.
This cannot be used to flip a ticker's tick direction — use .tick_up() or .tick_down() for that instead.
Returns the exact percentage of completion from start_value to end_value as a float.
A value of 0.0 means current_value is at start_value, and 1.0 means it's at end_value. Returns None if start_value and end_value are equal.
Returns the remaining percentage needed to reach end_value as a float.
A value of 0.0 means current_value is at end_value, and 1.0 means it's at start_value. Returns None if start_value and end_value are equal.
Resets current_value back to start_value.
Resets current_value back to start_value and zeroes out stored_time. Best used when you want to completely wipe whatever has been accumulated, including the timing state.
If you need to carry over the timing remainder from the last .tick(), do not use this.
Returns true if the behavior of a ticker is mutable, false otherwise.
Prints out all the fields and their values of a ticker.