Get SpatialOS

Sites

Menu

How to upgrade

This page explains how to upgrade a project from SpatialOS 11 to 12.0.0.

Note: This is a guide for upgrading to the new major version, SpatialOS 12.0.0. To upgrade to minor and patch versions, follow the minor and patch version upgrade guide instead.

Background

SpatialOS projects are developed against a specific version of the SpatialOS SDK. The SDK version your project builds against is defined in the spatialos.json file at the root of the project, in the sdk_version field.

You must upgrade through major SDK versions in order. For example, to upgrade from 10.4.2 to 12.0.0, you must upgrade to 11.x.x, then to 12.0.0. To find upgrade guides for older versions, look at the documentation for that version of SpatialOS.

If your project uses the Unreal SDK and is currently on version 11.0 then you must upgrade your project to version 11.1 before upgrading to 12.0. This is due to breaking changes which appeared in version 11.1 of the Unreal SDK.

Upgrading

1. Upgrade your SpatialOS SDK version

Note: It’s very important you start by running spatial clean. Otherwise, intermediate files won’t be cleaned up properly and may cause issues with the new version.

  1. Open a terminal in the root directory of your project.
  2. Run spatial clean.
  3. Open the spatialos.json file at the root of your project.
  4. Replace the sdk_version value and the version value of all dependencies with 12.0.0.
  5. Replace all other instances of the version number in the file.

2. Upgrade worker configurations

  1. In each of your spatialos.<worker-name>.worker.json files replace any occurences of darwin or mac with macos.
  2. If you have any custom build scripts that use MSVC and / or Gradle, update their configurations by referencing macos instead of darwin.

3. (Unity SDK) Upgrade your Unity project

  1. In each of your spatialos.UnityClient.worker.json and spatialos.UnityWorker.worker.json files replace any occurrences of the +ip and +port command-line arguments in your worker’s launch configurations with +receptionistHost and +receptionistPort respectively.
  2. Navigate to the folder Assets/Plugins and delete the following folders: x86, x86_64, core-bundle-x86_64-macos. (These folders will not be removed by running spatial clean.) The content of these folders will be located in Assets/Plugins/CoreSdk in the future.
  3. Authority has now changed from being represented as a boolean to an Enum with values Authoritative, NotAuthoritative and AuthorityLossImminent.

    If HasAuthority was true previously then Authority is either Authority.AuthorityLossImminent or Authority.Authoritative.

    If HasAuthority was false previously then Authority is Authority.NotAuthoritative.

    There are a few cases where you should change this:

    • MyComponent.HasAuthority calls should be changed to MyComponent.Authority.
    • gameObject.HasAuthority(MyComponent.ComponentId) calls should be changed to gameObject.GetAuthority(MyComponent.ComponentId).
    • gameObject.HasAuthority<MyComponent>() calls should be changed to gameObject.GetAuthority<MyComponent>().

    (With the correct equality checks). For example:

    • MyComponent.HasAuthority should be changed to MyComponent.Authority == Authority.Authoritative || MyComponent.Authority == Authority.AuthorityLossImminent.
    • !gameObject.HasAuthority(MyComponent.ComponentId) should be changed to gameObject.GetAuthority(MyComponent.ComponentId) == Authority.NotAuthoritative.

    For more details on this, see Handing over authority between workers.

4. (Unreal SDK) Upgrade your Unreal project

  1. In each of your spatialos.UnrealClient.worker.json and spatialos.UnrealWorker.worker.json files replace any occurrences of the +ip and +port command-line arguments in your worker’s launch configurations with +receptionistHost and +receptionistPort respectively.
  2. Authority has now changed from being represented as a boolean to an Enum with values Authoritative, NotAuthoritative and AuthorityLossImminent.

    For most cases, replace MyComponent.HasAuthority() == true with MyComponent.GetAuthority() == Authority::Authoritative || MyComponent.GetAuthority() == Authority::AuthorityLossImminent and MyComponent.HasAuthority() == false with MyComponent.GetAuthority() == Authority::NotAuthoritative.

    Additionally, the signature of the callbacks for the OnAuthorityChange delegates need to change from void(bool newAuthority) to void(EAuthority newAuthority).

For more details on this, see Handing over authority between workers.

4.1. Change your Unreal code to use automatic replication

In 12.0, component properties are exposed publicly, meaning you don’t need to use Get() or Set() functions to modify them. We’ve also automated component replication so that you don’t have to manually send updates, a move in line with how Unreal Engine 4 replicated properties work.

Note: If you have a very specific use case you can still use manual component updates, but this is inadvisable in the vast majority of cases.

  1. Remove uses of Accessor (getter/setter) functions to access SpatialOS component properties, and instead access the newly exposed public properties.

    For example, FooComponent->GetBar() now becomes FooComponent->Bar.

    Accessor functions have been marked as DEPRECATED and will show a warning:

    warning C4996: 'UPositionComponent::GetCoords': This function is deprecated, access the Coords property directly.
    Please update your code to the new API before upgrading to the next release, otherwise your project
    will no longer compile.
    
  2. In all places you call SendComponentUpdate() to update a component’s properties:

    Previously you had to build and send a component update. Now you can remove it, and instead modify the relevant property and let SpatialOS automatically replicate it. For example, if you were sending an update like this:

    UFooComponentUpdate* update = NewObject<UFooComponentUpdate>();
    update->SetBar(baz);
    fooComponent->SendComponentUpdate(update);
    

    Now you should only need to update the property:

    fooComponent->Bar = baz;
    

    Note: You can modify the MaxUpdatesPerSecond property of the component to 0 to keep sending the updates manually.

    Note: Events still need to be added to an update object and sent manually using SendComponentUpdate().

  3. Create and store an instance of USpatialOSComponentUpdater, eg:

    UFooGameInstance::UFooGameInstance()
    {
        SpatialOSComponentUpdater = CreateDefaultSubobject<USpatialOSComponentUpdater>(TEXT("SpatialOSComponentUpdater"));
    }
    
  4. Invoke USpatialOSComponentUpdater->UpdateComponents() after you call USpatialOS->ProcessOps(), eg:

    void UFooGameInstance::ProcessOps(float DeltaTime)
    {
        if (SpatialOSInstance != nullptr && SpatialOSInstance->GetEntityPipeline() != nullptr)
        {
            SpatialOSInstance->ProcessOps();
            SpatialOSInstance->GetEntityPipeline()->ProcessOps(SpatialOSInstance->GetView(), SpatialOSInstance->GetConnection(), GetWorld());
    
            SpatialOSComponentUpdater->UpdateComponents(EntityRegistry, DeltaTime);
        }
    }
    

4.2. Remove Coordinate conversions

Remove all uses of USpatialOSConversionFunctionLibrary::SpatialOsCoordinatesToUnrealCoordinates and USpatialOSConversionFunctionLibrary::UnrealCoordinatesToSpatialOsCoordinates in both code and Blueprints when reading or writing properties for a SpatialOS Unreal generated types, as this is now done automatically in generated code.

For example this:

void ARpgDemoCharacter::OnPositionComponentReady()
{
	const auto unrealPosition =
	USpatialOSConversionFunctionLibrary::SpatialOsCoordinatesToUnrealCoordinates(PositionComponent->Coords);
	SetActorLocation(unrealPosition);
}

should become:

void ARpgDemoCharacter::OnPositionComponentReady()
{
	SetActorLocation(PositionComponent->Coords);
}

In Blueprints:

Remove conversion

You will still need to convert if you are manually setting SpatialOS Coordinate properties in C++ only classes such as the FEntityBuilder.

4.3. Update your Unreal delegate callbacks

With the new automatic replication system, delegates have been simplified.

Previously, component update callbacks or component property updated callbacks would send the new updated value. This parameter has been removed as the component will already have the updated value and can be read directly from the component.

All component updates use the new FSpatialComponentUpdated delegate. All delegates have been moved out of classes and into the global namespace to both align with Unreal standards and fix potential crashes when using Unreal’s hot reload feature.

Delegates defined in SpatialOSComponent have been renamed. FAuthorityChangeDelegate and FComponentReadyDelegate are now FSpatialAuthorityChangeDelegate and FSpatialComponentReadyDelegate respectively, and declared in the global namespace.

4.3.1. Update C++ code
  1. Remove previously defined arguments to delegate functions. For example:

    void PlayerController:Possess()
    {
        PositionComponent->OnCoordsUpdate.AddDynamic(this, &PlayerController::OnPositionUpdate);
    }
    
    void PlayerController::OnPositionUpdated(const FVector& NewPosition)
    {
        myActor->SetLocation(NewPosition);
    }
    

    should now be:

    void PlayerController:Possess()
    {
        PositionComponent->OnCoordsUpdate.AddDynamic(this, &PlayerController::OnPositionUpdate);
    }
    
    void PlayerController::OnPositionUpdated()
    {
        MyActor->SetLocation(PositionComponent->Coords);
    }
    
  2. This is uncommon in Unreal code, but if you were using one of these low level SpatialOS functions:

    • Connection::ConnectAsync
    • Locator::ConnectAsync
    • Dispatcher::Dispatcher
    • LoadSnapshot
    • SaveSnapshot

    Now you need to provide an explicit list of components as a parameter. SpatialOSCommon.h provides an autogenerated helper type called improbable::unreal::Components to pass as a template parameter into these functions. For example:

    Connection::ConnectAsync(improbable::unreal::Components{}, /* ... */);
    
4.3.2 Update Blueprints

Every user defined component delegate has been moved to the global namespace, and you need to update them to reflect that. Command delegates are now using their response and request type for their definition.

For example, UTestComponent::Damage, a command delegate which takes a DamageRequest type and responds with DamageResponse, will be defined as FDamageResponseDamageRequestCommand. This change is demonstrated in the screenshots below.

SpatialOS 11:

Command delegate in 11

SpatialOS 12:

Command delegate in 12

Due to the renaming of delegates, you need to re-create any functions referencing them in Blueprints. For example:

component Spawner
{
    id = 21010;

    command SpawnPlayerResponse spawn_player(SpawnPlayerRequest);
}

Would generate FSpawnPlayerCommandRequestDelegate OnSpawnPlayerCommandRequest; in its generated component class in SpatialOS 11.0. In 12.0 this becomes FSpawnPlayerResponseSpawnPlayerRequestCommand OnSpawnPlayerCommandRequest;. Delete the current Blueprint node and re-add it to resolve the missing parameters.

5. Update saving and loading snapshots

SaveSnapshot and LoadSnapshot have been deprecated in favour of SnapshotOutputStream and SnapshotInputStream, so that the entire snapshot does not have to be in memory when loading or saving.

The examples below show how to upgrade both the saving and loading of snapshots in C#; upgrading workers using other SDKs is very similar.

Saving snapshots

Previously, to save a snapshot all of the EntityId, Entity pairs were required to be stored in a single map. A trivial example below populates a map and then saves to a file using the deprecated save function.

Map<EntityId, Entity> entities = new Map<EntityId, Entity>();
for (int i = 0; i < maxEntities; i++)
{
  entities.Add(new EntityId(entityId), CreateEntity());
}

var error = Snapshot.Save(filePath, entities);
if (error.HasValue)
{
  // Log error
}

To upgrade, save each EntityId, Entity pair individually using the SnapshotOutputStream. An equivalent example is given below, using a SnapshotOutputStream.

using (SnapshotOutputStream stream = new SnapshotOutputStream(filePath))
{
  for (int entityId = 0; i < maxEntities; i++)
  {
    var error = stream.WriteEntity(new EntityId(entityId), CreateEntity());
    if (error.HasValue)
    {
      // Log error
      return;
    }
  }
}

Loading snapshots

The load snapshot function took a map and added every EntityId, Entity pair in the snapshot file. Below is an example of loading all entities from a snapshot.

Map<EntityId, Entity> entities;
var error = Snapshot.Load(filepath, entities);
if (error.HasValue)
{
  // Log error
}

Like the SnapshotOutputStream, to upgrade, each entity must be loaded individually. An equivalent example is shown below with the SnapshotInputStream

using (SnapshotInputStream stream = new SnapshotInputStream(filePath))
{
  Map<EntityId, Entity> entities = new Map<EntityId, Entity>();
  while (stream.HasNext())
  {
    EntityId entityId;
    Entity entity;
    var error = stream.ReadEntity(entityId, entity);
    if (error.HasValue)
    {
      // Log error
      return;
    }
    entities.Add(entityId, entity);
  }
}

6. Address API breaking changes

Run spatial worker build. Any errors you see should be caused by a breaking change listed in the release notes.

If you’re using the Unity SDK, make sure to check the changes for the C# SDK, as some of these will affect you too. Similarly, if you’re using the Unreal SDK, make sure to check the changes for the C++ SDK.

  1. For each compilation error, find the relevant breaking change release note, and upgrade to the new API.
  2. Run spatial worker build again.
  3. Repeat steps 1 and 2 until your project compiles!

The end

You’ve now upgraded to SpatialOS 12.0.0. Great! Take a look at the release notes to learn more about what’s new in the latest SpatialOS version.

Let us know how the upgrade went on the SpatialOS forums!

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums