Appearance
Appearance
This page describes how to use the SDK's server time-skipping feature to test your upcoming game events and other scheduled features. The time skip will make the code act as though the time is in the future. This change applies automatically to most features, and most code will not need to take it explicitly into account in order to be affected by it.
Feature for testing only!
The time skip is a disruptive environment-wide operation and cannot be undone or rewound without resetting the environment's database. Therefore, you should only ever use it in development and testing environments where losing database contents is okay.
You can apply the time skip by navigating to the Settings page on the dashboard and clicking the button on the Time Skip card. The feature is available in local and development environments and is disabled in staging and production for safety.
In the time skip form, select the target date and time in the future you want to skip to. Using the Exact date picker toggle, you can enter it either as an explicit date and time or as an offset to the current moment. When you're done, click Apply Time Skip.
The clock in the dashboard's top-right corner will show the updated time and take on a different appearance to indicate that a time skip is active. When you hover the cursor over it, the tooltip will mention that the time skip is active.
Let's say you have scheduled a few LiveOps events for a specific time in the future, and now you want to try them out in a testing environment.
While you could edit each of your events separately and offset their schedules to happen at the present time, it's less work to just skip the server's time ahead and pretend you're in the future. This is particularly useful if you have a complex LiveOps setup with many events, broadcasts, or other scheduled operations.
In the following image, the time has been skipped to start one of the events. As a reminder, the time indicators become yellow, and their tooltips mention the time skip.
You can now test the events in your game client in this time-skipped testing environment.
The only way to undo the time skip is to reset the database. See Resetting the Database more details.
Warning!
If you reset the database, you will lose all players, events, and other content in the game environment. You'll need to set up the events again if you want to test them again. With some features, like LiveOps Events, you can use the export-import functionality to reduce the manual work of setting up the same events again.
If you're operating the backend infrastructure yourself and are able to use database backups, you can avoid a full database reset. Take a backup before the time skip, and then restore the database from the backup after you're done with the time skip.
If your game is running in a Metaplay-managed cloud environment, there is currently, unfortunately, no way for you to operate database backups. In the future, we're planning to provide tools for you to take and restore backups yourself.
If you're running a local server on your computer rather than in the cloud, the database is stored in the *.db*
files in Backend/Server/bin/
. You can take a backup by simply copying these files to a safe place, and restore from that backup by deleting the files in Backend/Server/bin/
and then copying the backed-up files there.
In C# code, you have three main options for getting the current time:
DateTime.UtcNow
to get the wall clock time without the time skip.MetaTime.Now
to include the time skip. This is based on DateTime.UtcNow
and simply adds the current time skip to it.PlayerModel.CurrentTime
to get the model time. This is the correct practice in PlayerAction
s and ticks. While this is of type MetaTime
, and does include the time skip, it is different from both of the above options as it does not get the wall clock time. Instead, it is coordinated between the server and client to support deterministic execution. This also applies to entity models other than PlayerModel
, such as GuildModel.
In the shared logic, there are no choices to worry about because PlayerModel.CurrentTime
is always the correct option. The rest of this section describes the choice between DateTime.UtcNow
and MetaTime.Now
in non-shared code (server-only or client-only).
For most game code, MetaTime.Now
is the appropriate option. For example, when server-side code checks whether a timed in-game event should be started, it should use MetaTime.Now
to apply the time skip. In contrast, you may want to use DateTime.UtcNow
for things that have no strong relation to the timing of in-game operations, such as for server-internal periodic operations, or timestamps communicated to external services outside the game server (which cannot participate in the time skip).
Note that MetaTime
and DateTime
are different types, but you can convert between them with MetaTime
's methods ToDateTime()
and FromDateTime()
. You may need to use these conversions when dealing with a pre-existing API that accepts one of the types while you readily have the other one. In particular, MetaTime.Now.ToDateTime()
produces a DateTime
that includes the time skip. The reverse is MetaTime.FromDateTime(DateTime.UtcNow)
, which produces a MetaTime
that does not include the time skip.
Similarly, the associated MetaDuration
and TimeSpan
types are interconvertible with MetaDuration
's methods ToTimeSpan()
and FromTimeSpan()
.
Heads up
When using wall clock time, it is almost always recommended to use DateTime.UtcNow
rather than DateTime.Now
unless you specifically want a time in the device's local time zone. Local DateTime
s, as produced by DateTime.Now
, behave unexpectedly in various ways.
Note that Metaplay SDK's serialization does not support DateTime
. As a workaround, you can have the serialized member as DateTimeOffset
or MetaTime
instead, and convert to and from DateTime
.
When porting existing code to account for the time skip, there are a few things to keep in mind:
DateTime.UtcNow
and MetaTime.Now
in a less rigorous manner, because without time skip the choice made little difference. You should review and test the existing usage of these time accessors to make sure the code works correctly with the time skip.PersistStateImpl()
may be assigning DateTime.UtcNow
to the PersistedAt
member. This should be changed to MetaTime.Now.ToDateTime()
because the SDK treats PersistedAt
as being affected by the time skip.DateTimeOffset
and TimeSpan
in addition to MetaTime
and MetaDuration
, these are not drop-in replacements, because their serialization formats are different and incompatible. In existing data members that are persisted or otherwise need to be backward-compatible, it is easiest to avoid changing MetaTime
to/from DateTimeOffset
and MetaDuration
to/from TimeSpan
, and simply convert between the types when accessing the member.MetaDuration
to TimeSpan
: they both have a property named Milliseconds
with type long
, but with different meanings. In MetaDuration
, it is the total milliseconds of the duration. In TimeSpan
, it is only the milliseconds component (always less than 1000). The compiler won't catch this discrepancy for you, because there is no type error. Thus, when changing MetaDuration
s to TimeSpan
s, we recommend using your IDE to find references to the TimeSpan.Milliseconds
property and reviewing those, changing them to use the TotalMilliseconds
property if needed.In the dashboard code, the time offset is applied to luxon, the date-time library used by the LiveOps Dashboard. Using luxon's DateTime.utc()
or its other functions that access the current time will automatically include the time skip. Similarly, the time skip is automatically applied to Metaplay's utilities in the LiveOps Dashboard, such as the meta-time
component.
To access the browser's actual time without the time skips, you can use JavaScript's standard Date.now()
. You can convert it to luxon's DateTime
type with DateTime.fromMillis(Date.now())
.
The time-skipping feature is a development and testing utility and has some rough edges that are good to know.
The skipped time can only be increased; it cannot be undone, decreased back to its old value, or used to go to the past. Once you're done with time skip testing in your development environment, the only way to get back to the current time is to reset the entire database. See Undoing the Time Skip with a Database Reset for more information.
This is an important reason why the time skip should never be done in any environment that deals with important data, particularly production.
Most code is written with the practical assumption that time advances in a normal manner. When time suddenly skips significantly forward, code may behave unexpectedly. While robustly written code should not encounter logically erroneous results due to a time skip, you may see temporary performance degradation or failure to complete time-sensitive operations. These errors are likely to happen during a brief moment when a time skip is applied and should stabilize afterward.
As an example, the server-side code responsible for advancing the guild model timeline (in GuildActorBase
) aims to keep the timeline roughly up to date with MetaTime.Now
. If it detects that a very long time has passed since it last advanced the timeline, it will refuse to advance it in the normal manner. This should not happen during normal operation, but a time skip will easily trigger it and can cause errors to be logged on the server.
As another example, ongoing player sessions typically get forcibly terminated when the time skip happens.
While most of the timestamps that the LiveOps Dashboard deals with are recorded in the game server time and displayed accordingly, some may deal with wall clock time. They may be misleading or incorrect if the Dashboard code has not been written to treat the timestamps consistently as wall clock time.