» Understanding ElapsedTime

Note: There are reports (as of 1.110) that ElapsedTime is currently not being adjusted based on the simulation speed. This needs a bug fix from Keen.

Relying on time when writing scripts for Space Engineers is oddly difficult.  There’s no in-game clock, and the simulation speed is variable so doesn’t directly correlate to a real life clock.

Screenshot of Space Engineers showing a wall of timer blocks
One or two timer blocks...

The most common solution is to have a timer block with a fixed schedule, and then hard code that value in to your scripts. For example, a timer configured to 5 seconds will run every 5 in-game seconds, and any blocks it triggers would be configured to do 5 seconds worth of work. That’s a bit of a pain if you want to be able to change the frequency, or trigger something at irregular intervals. Imagine a programmable block that you trigger every minute by timer, but which the user can also trigger through a button…

A nicer solution is to use the ElapsedTime field of the programmable block’s IMyGridProgram interface. This gives you the elapsed in-game time since the last time the program was ran. Here’s a simple script that just echoes the elapsed time:

void Main(string argument)
{
    Echo("Time since last run: " + ElapsedTime.TotalSeconds);
}

The first time you run the script, the elapsed time will be 0. Each time you subsequently run the script, it will show the time in in-game seconds since it was last executed. Simple, right?

Pitfalls

OK, it’s not really that simple. There’s a few gotchas you need to look out for.

ElapsedTime assumes a constant sim speed

In order to calculate ElapsedTime, the game stores a (real-world clock) timestamp each time the programmable block is executed. Before it executes the script, it subtracts the last timestamp from the current time, and then multiplies the timespan by the current simulation ratio. If a long time has passed and the simulation speed hasn’t been constant, this may give you weird results. This would be most noticeable if you have extremely long gaps between script invocations; if you’re triggering something every few seconds you shouldn’t be affected much by this.

ElapsedTime is zero on first run, compile and load

As mentioned earlier, the first time you run a script its ElapsedTime is zero. The same is also true when you recompile a script in the programmable block, or when you load a save. In both of these cases the state of the block is reset (any fields in your script will go back to their default values, for example), so it’s like you’re starting from scratch. As long as you make sure your script can cope with zero values (don’t blindly divide by ElapsedTime!), this shouldn’t cause you much of a problem.

ElapsedTime includes time the game was paused

This one makes my head hurt… I’m hoping it’s a bug and will be fixed at some point. When a game is paused — for example on a dedicated server set to pause when no players are there — and then resumed, the ElapsedTime will include all the paused time as well. As I mentioned above the way the time is calculated is by subtracting the last executed time from the current time, so what’s going on kind of makes sense in that regard.  It doesn’t make much sense when you rejoin a dedicated server and your solar panels decide to rotate through a few thousand degrees, though!

There’s not a particularly straight forward work-around for this one. If you know the frequency your script will be called at, you can filter out any values above a hard-coded maximum. Alternatively, if your script is called at a repeatable interval but you don’t want to hard code it, you can store the last ElapsedTime value and ignore a subsequent value if it’s significantly greater:

TimeSpan LastElapsedTime;

void Main(string argument)
{
    if (ElapsedTime.TotalSeconds == 0)
    {
        // First run. The script has just been created/compiled, or the game was loaded.
        SetupThings();
    }
    else if (LastElapsedTime == null || ElapsedTime < LastElapsedTime.Add(TimeSpan.FromSeconds(10)))
    {
        // ElapsedTime looks reasonable: either we have nothing to compare it to, or it's
        // within 10 seconds of the previous value.
        DoThings();
        LastElapsedTime = ElapsedTime;
    }
    else
    {
        // ElapsedTime is weird. Maybe we've unpaused?
        LastElapsedTime = ElapsedTime;
    }
}

With this code, the normal processing will only happen if the elapsed time is at most 10 seconds greater than the previous time the script run. If you decrease the frequency (e.g. change a timer from 10 seconds to 30 seconds), it will skip one run and then continue as normal using the new value as the guide.

comments powered by Disqus