How to tracking history for Various World Elements

I am trying to create a way to store dates, but for any type of custom calendar system a user makes. The user could make a plugin for complex Calendars such as the Gregorian or simple calendars where a user can input a map of “Cycles” to Integer representing the count of cycles in the Cycle before it.
Example:
Year → Seconds (Total Seconds for 1 Solar Orbit)
Month → Days in 1 Month
Day → Hours in 1 Day
Hour → Minutes in 1 Hour
Minute → Seconds in 1 Minute

I was thinking I could use a Curve for Months, so that user can define a curve to set number of months and length of each month in 1 Year. I do not know though, yet, how I can create a system so that a user at runtime can create a custom table, and then save that Table to a SaveGame or Text file.

I was thinking I would need to store time as seconds in a Int64, and then I could easily create a historical “log” for any struct by using a map of Int64->Struct.

As an example, the user can create characters, and lets say each character has a character profile struct that contains the various information relating to behavior, mentality, personality etc., but this can change over time as the character develops, and I want to be able to select the time period to load the correct character profile for that character at that time. Simple, I can take the current world date (depending on the calendar the date is in), convert to world time in seconds, then look up that time on the Character Profile Map getting the Int64 and the CharProfile struct for that time (You would find the closest one not exceeding the date), then you could load the Character Profile that would be relevant.

I was also thinking it might be better to only store changes in the Character Prfile Struct, to keep data compressed and not store redundant information. So Then, I would need to get the latest date in the CharProfile, and load each “timestamp” and add the information from the previous changes to get the final result.

I ran into a problem when dividing Int64, as it does not change it to a float (or Double Float?), and to be able to convert from base seconds to a date using a calendar conversion, I would need to know the remainder yes/no? And with Int64 it returns Int64?

I am at a loss trying to figure out how to Go from a date in Seconds to a Calendar Date
If I take X Seconds, and divide by Y I need the total and remainder so I can convert remainder to the next cycle divisor…

there are a few things going on here, that have to do with the C++ back-end the Engine is built on, and maybe a perception issue as well:

in C++ when I perform

int a = 6; int b = 4;
int c = a/b;
cout << c;    // this line just outputs the value held in c to the console
              // value outputted is "1"

the way this works out is that in “most” C++ compilers the math functions called for integer devision will 99% of the time just truncate the value. but if we have one of the values as a float or cast to a float (this is similar to in Blueprints when you CastToPlayerCharacter(OtherActor), but for “integral data types” these conversion are more trusted and less likely to have “side effects” (Floating point approximation not withstanding)

and even if I make c be a float from the beginning

int a = 6; int b = 4;
float c = a/b;   // this becomes internally float(int/int)
cout << c;   // result is still 1

the 2 integers are divided and then the result is converted to a float.

but if I have either of the values stored as a float, or do a conversion/cast to a float during the operation.

int a = 6; float b = 4.0f;
float c = a/b;
cout << c;  // result is 1.5

int d = 6; int e = 4;
float g = a/((float)b);
cout << g;   // result is 1.5

this also has to do with the C++ standards for the compiler define that when performing integral type arithmetic, if the types do not match then the type with the lesser precision shall be converted to the type with the "more precision" this comes out to mean that when one of the types in arithmetic is a float and the others are integers then the values directly interacting with the float will be converted to a float.

// this might not always be intuitive
int a = 20; int b = 4; int c = 6; int d = 7; int e = 10;
float f = 2.0f;
float x = a + f/(b * c -(d*e)); 
// float = int + float/(int * int - (int * int))
// float = int + float/(int * int - int)
// float = int + float/(int - int)
// float = int + float/float
// float = float + float

int64 the 64 is referring to the amount of bits that are used to represent the value (for unsigned int64 [(-2^63)+1, 2^63], while a standard int in Unreal is defined as an int32 (for platform portability, and replication) so ~+/- 5 billion.
the difference between a float and a double: a float is a 32-bit value which you can think of as scientific notation, but held in binary, and as part of the conversion between the 2 system you can get really dumb conversions (resulting in 90 = 89.999999, or 0 = -0 ), a double is just a 64-bit version of a float in “theory” this is more exact, but really the spaces between the values (the scientific notation thing) can be much smaller, leading to more values be represent-able) this is what is meant by “Floating Point Approximation” (and what a clever marketing department back when IBM was making processors called “Floating Point Arithmetic”)


for the problem at hand if you want a “realistic interpretation” where the real world seconds (you can get these a number of ways including stealing and accumulating the value input into Tick(DeltaTime), for consistency purposes storing these as floats is not absolute, but int requires converting at some point.

the reason I say “perception issue” has to do with Multiplication is “faster adding of similar things” (2*4 = 2+2+2+2) and division is “faster subtraction resulting in a multiple” (8/2 = 8-2-2-2-2 → 4), so to get your remainder when using integers to store your values

int a =42; int b = 4;
int c = a/b;    // this will be 10
int r = a-(c*b);   // 42-(10*4) -> 42-40 -> 2

so as long as you have the accumulated value and the conversion factor this should do most of what you need working the integers (either int32 or int64 )

2 Likes

This is such an amazing response! I knew much about 64bit vs 32bit, but you explained alot more in detail than I did know, but not only that, it will be great for someone who dosent who comes across this post.

As I was reading through, it dawned on me that if I subtract the sum from the total it would give me the remaining seconds, and then I read the like your last sentence and bam, I was right!

This helped me a lot in understanding further, thank you! I would much rather understand technicalities than to have someone just a simple answer.

Now that I can do the math, I can just store the conversion results as String with dot separators for each cycle. A User can set a custom calendar by creating custom conversions that is stored in a struct. By using maps, I can map out the conversion since I will know the order of the map, and process it in he same way every time, a user can specify a name for the unit, and an integer value that defines how many are in the previous cycle, with the first unit defined in the map, index 0, always being the smallest unit and will always = the number of seconds in 1 of that unit.

I can then use the string converter to run through the cycles to get seconds, and these seconds could either be related to a reference of a specific second in the world timeline, or based on a specific reference point that could be defined in a blueprint (a Character Profile Struct for example, could have a reference point of the characters birth date, so all changes through time of the characters profile can be tracked in relation to the character itsself, and then the characters birth date would be defined by the calendar used and a specific second on the world timeline.

To store historical Dates, they will all be converted to seconds and stored as Int64 and will be based on a specific reference of time in seconds. If not specifically specified in a script or another variable, when converting, seconds will always be related to the world timeline in seconds. Otherwise, a custom conversion is done to convert the given date to a world timeline moment in seconds.

This way, I can use map variables to track the history of whatever needs to be tracked by using int64->Struct and then have a function to pack and unpack (do date conversions to seconds and vice versa)
For a Characters Profile (Non-numerical data such as mannerisms, behavioral traits, emotional traits, social interaction traits or whatever else in the struct) the CharProfile struct could be broke down into parts that are each a map of time->Struct and custom functions to pack an unpack dates in that character profile struct type

Each version contains changes only, so then each moment of time before the current tme in the character profile would need to be read and appended to the data to get all the data for the current moment
i.e.: n

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.