Crons/Recurrent/scheduled Jobs

triped

Newbie
Jul 30, 2020
22
25
Crons, as I'm identifying them here, can be many things:
- a status effect applied ever x amount of time
- an event that occurs on a day cycle
- an event that occurs once at a specific time


In building my own system for this, I encountered two issues:
- time skipping
- pausing

While pausing is something games normally account for, time skipping is not. What do I mean by that?

If the MC has a "poisoned" effect and then goes to sleep with that effect, the standard behavior in games is either the sleep clears the status or does nothing to the status.

There is a way to apply status effects in the same way they would be applied if the player were not skipping time (by sleeping). I've built a system to handle this. It is too complex to detail here. But, I'm curious whether such a system already exists somewhere.
 

triped

Newbie
Jul 30, 2020
22
25
What engine?
What I built as a prototype is in coffeescript, which I intend to move over to c# for Unity (it's just faster to prototype in coffeescript). As for my request to find any existing systems - it doesn't matter which engine.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,377
15,290
But, I'm curious whether such a system already exists somewhere.
Yes and no. It's something relatively easy to handle, therefore there isn't effective system for that ; it's by default part of the time mechanism implementation.
If the game rely on realtime-like time advance, then the time mechanism rely on a tick value that is used to represent the ratio between in-game time and real life one. A time skipping isn't a direct increase of the time value, but a lowering of the tick value in order to make the time advance so fast that it will not be perceived by the player ; and obviously, the game continue to act normally, therefore the effects continue to apply, and kick in or out, as naturally as if the tick was still on the "normal ratio" value. It's what AAA games generally use.
And if the game mechanism imply a relative time advance, a time skipping isn't either a direct increase of the time value. This time it's a loop that will simply call the default time advance code until the skipping period is over. Therefore, here again the effects will apply, and kick in or out, totally naturally.
 

Madmanator99

Member
May 1, 2018
225
455
Honestly anne O'nymous is the boss :) So yeah, what she said. No specific system, it's all custom made, but from the same idea/concept that she explained. C# is different from renpy, python, and even java, but the core idea she explained is totaly the same.

Ps: Now for Unity I know people make templates and sell them, if that is what you were asking, then it's the same answer, I don't think there is a template/system ready to use with Unity that does that, but you better check in other forums, and maybe the official Unity forum as well.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,377
15,290
So yeah, what she said.
Sorry to disappoint you, but according to my beard it's "he" ;)


[...] I don't think there is a template/system ready to use with Unity that does that, [...]
I don't think either. It's too embedded in the rest of the code to be easy to make as template.
I don't even think that there's a template to handle time passing. Not only half of the code would be dedicated to the sole integration with the game, but it would also be way slower (relatively speaking) to browse a list of callback to call during each tick, than to have direct calls. And like it's one of the most proceeded task of the whole game, speed really count here.
 

triped

Newbie
Jul 30, 2020
22
25
Yes and no. It's something relatively easy to handle, therefore there isn't effective system for that ; it's by default part of the time mechanism implementation.
If the game rely on realtime-like time advance, then the time mechanism rely on a tick value that is used to represent the ratio between in-game time and real life one. A time skipping isn't a direct increase of the time value, but a lowering of the tick value in order to make the time advance so fast that it will not be perceived by the player ; and obviously, the game continue to act normally, therefore the effects continue to apply, and kick in or out, as naturally as if the tick was still on the "normal ratio" value. It's what AAA games generally use.
And if the game mechanism imply a relative time advance, a time skipping isn't either a direct increase of the time value. This time it's a loop that will simply call the default time advance code until the skipping period is over. Therefore, here again the effects will apply, and kick in or out, totally naturally.
Thank you for the explanation.

That is the way I initially thought I'd implement the time skipping, but there are problems with doing it that way that are particular to RPGs which are not relevant to AAA time slow (long ticks) or time fast forward mechanisms. Say I have an auto recovery that recovers some small amount of HP ever second. If my day cycle in the game were a normal 86400 seconds per day, a player having the MC sleep would require thousands of calls to just the HP recover method, not considering what other crons (status effects, etc) were active.
So, instead, I built something that introduces jumps (skipping 1000 seconds using 10s jumps, for instance), cron compressibility (an HP recover cron is compressible to some extent), cron mandated run times (for crons that must execute at given time).

It will be some time before I can demonstrate the system in a game since it appears, along with this time system, virtually all the other systems I have envisioned for the game do not exist. It's interesting that not only have turn based games not advanced beyond the mechanics of FF7, they have regressed.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,377
15,290
Say I have an auto recovery that recovers some small amount of HP ever second. If my day cycle in the game were a normal 86400 seconds per day, a player having the MC sleep would require thousands of calls to just the HP recover method, not considering what other crons (status effects, etc) were active.
There's an easy way fix this problem.
You use a semaphore to define the actual MC situation ; he's either "active", "resting" or "sleeping". Then the healing code react according to this semaphore. Default healing value if the MC is "active", higher value if he's "resting", and an even higher value if he's "sleeping".
The most difficult part being to make the difference between "active" and "resting". Unless you create an effective "rest" action, it should switch to "rest" when there's no moving action since 5 seconds, and return to "active" once there's a moving action. Up to you to define what are the "moving actions". It can be effectively moving, but you can also consider that browsing through the inventory, by example, isn't effectively resting since the MC is supposed to move thing in his bag, take some out, put others back, and all ; this while looking at the quest log, still by example, can be effectively a rest since the MC is just reading a book.

Ideally, all your code should rely around the time passing method, and adapt to it, not the opposite. Else you'll struggle with your code, and risk to break it each time you add an action, because it could imply a radical change in the way you handle the time passing.
By using the semaphore approach describe above, your time passing method stay stable whatever you'll add in the future ; those added features will just change their behavior according to the semaphore. Also note that you aren't stuck with just one semaphore.
You can also have a tiredness counter by example. Then the healing feature would weight the healing factor according to the activity semaphore, then weight it a second time according to the tiredness ; unless the MC is sleeping, obviously. The same counter can then be used by the moving method, making the MC walk slower, or why not unable to run, if he's tired.
Then, you can add a stress counter, that will increase when the MC is... in situation of stress, and decrease when he's not ; which imply a stress flag saying if the MC is under pressure or not. And the tiredness will increase more or less fast according to it. And so on.
You've added four different features (healing, tiredness, moving speed, stress) that all deeply rely on the time passing method. Yet you haven't had to change the code for this said method. The time continue to pass exactly in the same way, it's the added features that act differently depending of the effective context.
 
  • Like
Reactions: Cul

triped

Newbie
Jul 30, 2020
22
25
There's an easy way fix this problem.
You use a semaphore to define the actual MC situation ; he's either "active", "resting" or "sleeping". Then the healing code react according to this semaphore. Default healing value if the MC is "active", higher value if he's "resting", and an even higher value if he's "sleeping".
The most difficult part being to make the difference between "active" and "resting". Unless you create an effective "rest" action, it should switch to "rest" when there's no moving action since 5 seconds, and return to "active" once there's a moving action. Up to you to define what are the "moving actions". It can be effectively moving, but you can also consider that browsing through the inventory, by example, isn't effectively resting since the MC is supposed to move thing in his bag, take some out, put others back, and all ; this while looking at the quest log, still by example, can be effectively a rest since the MC is just reading a book.

Ideally, all your code should rely around the time passing method, and adapt to it, not the opposite. Else you'll struggle with your code, and risk to break it each time you add an action, because it could imply a radical change in the way you handle the time passing.
By using the semaphore approach describe above, your time passing method stay stable whatever you'll add in the future ; those added features will just change their behavior according to the semaphore. Also note that you aren't stuck with just one semaphore.
You can also have a tiredness counter by example. Then the healing feature would weight the healing factor according to the activity semaphore, then weight it a second time according to the tiredness ; unless the MC is sleeping, obviously. The same counter can then be used by the moving method, making the MC walk slower, or why not unable to run, if he's tired.
Then, you can add a stress counter, that will increase when the MC is... in situation of stress, and decrease when he's not ; which imply a stress flag saying if the MC is under pressure or not. And the tiredness will increase more or less fast according to it. And so on.
You've added four different features (healing, tiredness, moving speed, stress) that all deeply rely on the time passing method. Yet you haven't had to change the code for this said method. The time continue to pass exactly in the same way, it's the added features that act differently depending of the effective context.


I'm not certain how the semaphore removes the need to call "HP auto recover" every second, thousands of times over a sleep period in order to move the game time (which has a continuous day/night cycle) unless you are saying that during a "sleeping" period the HP recover is called less but with greater effect, or you are saying the tick should decouple from game time, and game time should progress more rapidly during sleep.

Let's look at the poisoned and HP recovery effects.

If a poison status were to end after dealing 110% HP damage over 5m, but the HP recovery could potentially recover 11% HP within 5m, then the poison effect should not kill the MC during a time skip (sleep). However, whether a poison effect should kill the MC during a time skip (sleep) depends on the poison HP damage rate and the HP auto recovery rate. I'm not certain how you envision using semaphores would solve this situation while maintaining the possibility of either outcome.


Ideally, all your code should rely around the time passing method, and adapt to it, not the opposite. Else you'll struggle with your code, and risk to break it each time you add an action, because it could imply a radical change in the way you handle the time passing.

I could be wrong, but I don't see anything breaking by any action I add because any action I add would not change how time works. I think the trouble comes b/c you are applying the normal concept of ticks to my game time concept. In a normal game, it makes no sense to "skip" ticks since everything relies on ticks. I'm not advocating skipping ticks, making the ticks longer or shorter. These crons operate on a separate system based on game time, not ticks. The system I've designed is there to meet the problem that other games do not encounter: how do you compress a days worth, or even a months worth, or ongoing crons into a reasonable UX. And, these crons can be very variable. I estimate, at minimum, there will be over a hundred statuses in my game, and that's just a subsection of the crons.

I should say, I could be wrong about all this. My only significant game development experience was many years back, writing unit behaviors in an RTS.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,377
15,290
I'm not certain how the semaphore removes the need to call "HP auto recover" every second, thousands of times over a sleep period [...]
There's no reason to count the in-game time in seconds unless it's a puzzle game, and also no reason to have a tick time at 1 second ; both are too fast. Ideally it should be something around 1 in-game minute every 10 seconds, therefore 1 in-game hour every 10 minutes of play.
As for the call to the healing routine, since it should start by validating that there's something to do, it's a question of micro seconds. Therefore, even if there's only 1 health point left when the player go to sleep, it would at max take one or two seconds to process the healing when the MC is asleep ; less than 10 if the health is constantly damaged by some poison.


unless you are saying that during a "sleeping" period the HP recover is called less but with greater effect,
With greater effect, obviously, but not less often. This said, I missed your use of a so low time division.


or you are saying the tick should decouple from game time, and game time should progress more rapidly during sleep.
It's what I said in my first comment. During sleep the time past (almost) instantly because of the lower value for the tick.

For a realtime game, the time mechanism should be put in a thread(-like) ; CoffeeScript's intervals are a good enough substitute for threads. And, globally speaking, it should looks like that :
Code:
if ++actualTick != tickTrigger  
   end there
actualTick = 0
time++
// This part have too many possible implementations, it's only one of the many
call of the generic processing routines (the healing process by example)
for each entry in cronTable
   if --entryCounter  == 0  
       call entryCallBack
       clear the entry
With tickTrigger being lower when the MC is sleeping and anytime there's an explicit or implicit time skipping.
The effective value of tickTrigger depend of the frequency at which the time routine is called, as well as the time needed for the basis processing ; CoffeeScript having timer expressed on milliseconds, but being relatively slow you should probably not goes bellow to 100 as value for tickTrigger.
As for cronTable it's a basic structure with a code reference, and counter set at the delay before the said code reference should be proceeded.


I'm not certain how you envision using semaphores would solve this situation while maintaining the possibility of either outcome.
As I said, I missed your low time unit, what probably not helped. But you also clearly missed the point in my initial answer.

Code:
if ++actualTick != tickTrigger
   end there
actualTick = 0
time++
HealMC
PoisonEffect
PoisonEvolution
It will works exactly in the same way whatever the value of tickTrigger ; and therefore whatever if the MC is sleeping or not. Every time unit, the natural healing process will give back some HP to the MC, and right after that, the poison will remove some HP to the MC. In the same time, the poison will continue to evolve every time unit, and kick out once it will reach the condition for that.
The only difference being that the lower is the value of tickTrigger, the faster it will happen. Therefore, during time skipping, 8 hours will be proceeded in few seconds.


I think the trouble comes b/c you are applying the normal concept of ticks to my game time concept. In a normal game, it makes no sense to "skip" ticks since everything relies on ticks.
[...]
The system I've designed is there to meet the problem that other games do not encounter: how do you compress a days worth, or even a months worth, or ongoing crons into a reasonable UX.
Hmm. I wasn't ready to discover that the Fallout and The Elder Scrolls series, to name only the most famous, aren't normal games, and in fact not even games :/
Not only it make sense to skip time in those games, but they also encounter this "problem", including the poison damage during the skipping period. And it happen that they deal with it the way I said.
 

triped

Newbie
Jul 30, 2020
22
25
Hmm. I wasn't ready to discover that the Fallout and The Elder Scrolls series, to name only the most famous, aren't normal games, and in fact not even games :/
Normal games don't skip ticks for time skipping, they change the size of the ticks (`tickTrigger`), as you've already mentioned, so I don't see why you are mentioning fallout or elder scrolls. I suppose this could be a confusion in terminology, But, I'll use your unfriendly sarcasm and your less than perfect english as an indication I should cut to the chase.

Not only it make sense to skip time in those games, but they also encounter this "problem", including the poison damage during the skipping period. And it happen that they deal with it the way I said.even games :/
This tells me that you either do not understand the problem, or you are futilely trying to pigeonhole it.
I've played these games. I hardly recall a case where you can time skip during a status effect. I suppose in Fallout New Vegas you can wait in a radiated zone. But in games like Elder Scrolls poisoning only last a few seconds. In these cases, the situation is a relatively low game time / real time ratio, a relatively low number of ticks necessary to complete a wait period, a wait period that takes up noticeable time, and a very low number of status effects that are active in a wait period.

The problem I've presented is time skipping large periods while having hundreds of crons that lead to potentially tens of thousands of callbacks in a game day period (that matches 86400 seconds), and having the time skip take negligible time (<1s).

Normal games do not have this problem. I tried to simplify the presentation of this problem by presenting just the case of poisoning and HP recovery - for which you gave me the non solution of semaphores. I appreciate your initial response, and your clarification with code of what you meant by lowering the tick value, but again, I already considered doing it that way and determined it was inadequate.

Nice to see this subforum is responsive though.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,377
15,290
But, I'll use your unfriendly sarcasm and your less than perfect english as an indication I should cut to the chase.
Yeah, probably better this way. If, on an international forum, you can't weight what you read, according to the writer's English skills, I doubt that this can go really far.
But like it's a public forum and a question that can interest people, now or latter, I'll still answer.


I've played these games. I hardly recall a case where you can time skip during a status effect.
Despite my, totally assumed, English skills, I know perfectly well that it's not what I wrote.
It's not because they decided to prevent time skipping when some flags are raised, this in order to avoid the, not so fun, surprise for the player to found his own dead body when awaking, that it's not how the time handling code is designed. The said code do not discriminate between positive and negative status, they continue to evolve naturally, even when the time is skipped.
It's, as I said, the devs who decided to add this discrimination, that they put over the possibility to effectively skip time. And they did it for game design reasons.
While knowing perfectly well that I'm not a native English speaker, you decided to extrapolate the possible meaning of what you were reading. Then obviously, it led to a flagrant misunderstanding that, not this surprisingly, told you that your situation is unique, as you already knew.


[...] a wait period that takes up noticeable time, [...]
Once again it's purely for game design reasons.
I don't remember precisely the time skipping tick value in New Vegas, but it was high enough to explicitly express the intent behind it ; a too low value would have damaged the immersion, since the pause wouldn't have been felt.
And obviously, the simplest solution being the best, they achieved that by keeping the tick value high enough, instead of adding a waiting loop that would simulate the time passing.