Accurate timing in a transaction #16
sprintermax
started this conversation in
Ideas
Replies: 1 comment
-
|
In addition to this, would be better to have the other time utils too, such as I described on this comment on Epic Forums |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Time, Determinism and Precision
On this discussion I will give an overview about one of the things that I think it could be improved in verse: Accuracy and Precision when querying time.
Many times when working on past projects, I reached a point where I needed to measure time - for a specific gameplay mechanic, for debugging, profiling, or working on algorithms.
During these times, the same question keeps coming back to my mind: "Why getting time in verse must be inaccurate?". While I know that there are cases that could benefit from the current behavior, having only that as an option feels limiting and limits flexibility on some usages and ideas.
Bellow, I will talk a little more about it, with some examples and proposing possible solutions for the future.
A small context about the
<reads>and<varies>effectWhen Verse was first released, existed an effect called
<varies>. The meaning of it was: "A function that when called with the same arguments, may produce different output results".Later, this effect was removed. The reason was it being redundant with the
<reads>effect, which means "Can read mutable memory".Since
<reads>implies being able to read values from mutable memory, it means that if the value being read on the memory changes, a function with<reads>would be capable of returning different results even when being called with the same arguments.Example:
Calling
MyFunction()on that example, will print "A is 1, B is 2" and "Different!", because even withGetValue()being called within the same transaction, since it reads the mutable dataMyMemory, the result of it can be different if it is mutated between the two calls.The root of the issue:
Currently, there is only two ways we can get the time in Verse:
As stated on the function description, when calling
GetSecondsSinceEpoch()multiple times during the same transaction it will always returns the same timestamp, regardless of how much time passed between both calls.Besides not being mentioned, the same behavior applies to the
GetSimulationElapsedTime(): When being called multiple times during the same transaction, it also always return the same value.Example:
Running this example, will print "
Same Value!", regardless of the time between Start and End for both functions. It may have passed milisseconds, sometimes even entire seconds, and the time had not advanced at all. The time for these functions is "Frozen" during that same transaction.While I understand the design choice behing this (Keeping transactions deterministic even when they fail and are retried later), there are some aspects of it that I want to discuss:
-
GetSecondsSinceEpoch()with the<reads>effect:As stated before, the
<reads>effects implies that the function can return different values within the same transaction, this was also inherited from the<varies>effect when it still existed. Besides this, the theoretical behavior of it, according to the description, is be deterministic.-
GetSimulationElapsedTime()with the<transacts>effect:This one is a little weird. I don't know why it has the
<transacts>effect (in my mind it could be<reads>like the other function).Besides the *apparently* incorrect effect specifier, the description also lacks information about the deterministic behavior within same transaction: Ideally it should have a comment describing this behavior similar to the
GetSecondsSinceEpoch()function to avoid confusion later.How things can be better?
It is correct that on many cases being deterministic is good, if not the best choice, and having this capability is welcome. The problem is, that it causes confusion and limits flexibility, since we don't have any alternatives for opt-out of that behavior.
There is examples of real scenarios where accurate time is needed not only for technical reasons such as benchmarking, profiling or debugging, but also many algorythms expects accurate time, for sequential data, randomizing and so on.
An example is UUIDv1, where it can take advantage of time between operations (even during same transaction / computation request), to apply ordering to bulk-generating many UUIDs. Many different RNGs also relies on Time to generate pseudo-random numbers based on a Seed. (Both these examples are real cases that I faced when working on projects)
These usages does not need determinism, and having only the "frozen" timing queries limits, sometimes limits usages and implementations more than it should. We should still be able to apply specific programming concepts or ideas that don't care about determinism at all.
To allow more precise timing queries, I have two ideas:
Option A: Change the behavior of the already existing functions
Both functions already have the
<reads>effects, which as mentioned, allows the values to be different even within the same transaction. Due to this, making both these functions returning accurate and precise timings will be a backwards compatible change with no side effects to the signatures."But what if I still want the frozen time behavior?" - If we want to freeze time for the transaction, we can do it by caching the time value the first time queried it, it can be done with a simple local assignment, or a specific time class if we want more granullar control over it.
We can freeze/cache the time if we want - but we can't "unfreeze" it if we don't want that behavior, which is not good.
Option B: Add new non-deterministic functions for getting accurate time
If having any concerns about "the change of precision may break existing projects" (Which I personally find very unlikely), new functions with the non-deterministic behavior can be added to the language, without affecting the old, already existing functions at all:
Example:
With this, we would not need to think about backwards compatibility since the old functions are still existent and untouched - keeping the "frozen time" behavior for who wanting/preferring to use them.
Beta Was this translation helpful? Give feedback.
All reactions