Integrations and Deprecations 0.6.x #

This topic includes integrations and deprecations for Pragma Version 0.6.0.

Application Error Reclassification #

We audited each Pragma service to make sure all expected failures are classified as Application Errors and all critical errors are classified as Service Errors. This change requires you to update any handling you have implemented for the reclassified errors. Contact your CSM for a list of all error classification changes.

Unreal:

// Result is a TPragmaResult<>
if (Result.IsFailure())
{
  // Convert failure into a string regardless if it's a service error or application error
  const auto ErrorString = Result.GetErrorAsString();

  if (Result.IsTypedFailure())
  {
    // Add application error handling logic here
    if (Result.GetErrorType() == FPragma_Account_AccountNotVerifiedApplicationError::StaticStruct())
    {
      const auto AppError = Result.ReadError<FPragma_Account_AccountNotVerifiedApplicationError>()
      ...
    }
  }
  else
  {
    // Add service error handling logic here
 
    const auto ServiceError = Result.Error();   
    ...
  }
}

Unity:

// result is a Pragma.Result<TPayloadType>
if (result.IsFailure)
{
  // Convert failure into a string regardless if it's a service error or application error
  var errorString = result.ErrorAsString;

  if (result.IsTypedFailure)
  {
    // Add application error handling logic here
    if (result.ErrorType == typeof(AccountNotVerifiedApplicationError))
    {
      var appError = result.ReadError<AccountNotVerifiedApplicationError>();
      ...
    }
  }
  else
  {
    // Add service error handling logic here
    var serviceError = result.Error;
    ...
  }
}

Kotlin:

// result is a PragmaResult<out TSuccess, out TFailure>
result.onFailure {
  // Convert failure into a string regardless if it's a service error or application error
  val errorString = it.toString()

  if (it.isApplicationError) {
    // Add application error handling logic here
    val appError: Message = it.applicationError!!.parse()
    if (appError is AccountNotVerifiedApplicationError) {
       ...
    }
  }
  else {
    // Add service error handling logic here
    val serviceError: ServiceError = it.serviceError
    ...
  }
}

Behavioral integrations #

This section describes changes in behavior due to new/updated functionality. Extra care should be taken when implementing changes to avoid unexpected behaviors or regressions.

[Multiplayer] Update player event listeners for matchmaking #

We updated the GameInstanceApi.OnLeftMatchmaking SDK event to only fire if a player leaves a party while in matchmaking, or if the party leader leaves matchmaking on behalf of the party. When a match is found, only the GameInstanceApi.OnAddedToGameInstance SDK event fires.

Additionally, the GameInstanceApi.OnFailedToAllocateGameInstance SDK event was removed in favor of the PartyApi.OnMatchmakingFailed SDK event with the ERROR_SENDING_PLAYERS_TO_GAME_INSTANCE reason.

Integration steps:

  • If you were listening to the OnLeftMatchmaking event to detect when a party left matchmaking due to a match being found, you should now listen to the OnAddedToGameInstance event.

  • For remaining OnLeftMatchmaking instances, update the event listeners to expect a player ID and matchmaking left reason.

    Old GameInstanceApi.OnLeftMatchmaking:

    DECLARE_EVENT(UPragmaPartyApi, FLeftMatchmakingEvent);
    FLeftMatchmakingEvent OnLeftMatchmaking;
    
    public event Action OnLeftMatchmaking;
    

    New GameInstanceApi.OnLeftMatchmaking:

    DECLARE_EVENT_TwoParams(UPragmaPartyApi, FLeftMatchmakingEvent,
        const EPragma_Matchmaking_LeftMatchmakingReason& /* LeftReason */,
        const FString& /* PlayerId */);
    FLeftMatchmakingEvent OnLeftMatchmaking;
    
    public event Action<LeftMatchmakingReason, PragmaId> OnLeftMatchmaking;
    
  • If you were listening to the GameInstanceApi.OnFailedToAllocateGameInstance event to determine that players were not successfully matched and added to a game, instead listen for PartyApi.OnMatchmakingFailed with the new ERROR_SENDING_PLAYERS_TO_GAME_INSTANCE failure reason.

[Portal] Update Portal imports and configurations. #

We made significant changes to Portal to support new features and bring it in line with common web standards, including making it easier to add your own package dependencies.

Integration steps:

  • Create a packages.json file in [project]/portal. You can now include any dependencies you need in your project in this file. After creating this file you will need to run the portal setup command again: ./pragma portal setup
 {   
    "name": "[project]",
    "type": "module",
    "version": "1.0.0",
    "license": "UNLICENSED",
    "private": true,
    "dependencies": {     
      "pragma-portal": "*"   
    },
    "scripts": {     
      "package": "node ../../web/portal/buildlib/bin/package.js",     
      "start": "node ../../web/portal/buildlib/bin/start.js",     
      "test": "npm run test:unit && npm run test:it && npm run test:rpcs",     
      "test:unit": "node ../../web/portal/buildlib/bin/test.js",     
      "test:rpcs": "node ../../web/portal/buildlib/bin/check-rpcs.js",     
      "test:it": "node ../../web/portal/buildlib/bin/test.js --integration"   
    } 
  }

#

  • Update your Portal configuration file from a commonjs file into an ES6 module. For more information on ES6 modules see Mozilla’s documentation.
    • Before:
const somePackage = require('some-package') 
  module.exports = {    common: { 'assets.js': ['js/global.js'], 'assets.styles': ['css/common.less'] } 
  }
  • After:
import somePackage from 'some-package' 
  export default { common: { 'assets.js': ['js/global.js'], 'assets.styles': ['css/common.less'] } } 
  • Add imports for packages anywhere global variables are used.
    • Before:
const exampleRef = Vue.ref(true)
const router = VueRouter.useRouter()  
const route = VueRouter.useRoute()     
const { validate, validateInfos } = antd.Form.useForm(formState, rulesRef)
  • After:
import * as Vue from 'vue'; 
import * as VueRouter from 'vue-router'; 
import { Form } from 'ant-design-vue'; 
const exampleRef = Vue.ref(true)  
const router = VueRouter.useRouter()  
const route = VueRouter.useRoute()
const { validate, validateInfos } = Form.useForm(formState, rulesRef)

[Portal] Removal of Legacy Inventory Views and Content Tabs #

We removed the legacy Inventory and Content views in the Game Operator Portal.

If you are using Inventory Service, you can reenable these features. You can do this by updating your project’s portal config.

Update portal config at: [project]/portal/src/config/default.js

export default {
  common: {
    'app.featureToggles': {
      "LEGACY\_INVENTORY": true
    }
}

You can also enable the views through the Portal’s Development Tools, select the Feature Toggles tab and toggle on LEGACY_INVENTORY.

Syntax integrations #

This section describes changes in naming, file locations, and other syntactical updates.

[Multiplayer] Remove the PartyConfig.repeatInviteDelaySeconds configuration value from your YAML files #

We have discontinued and removed support for the party repeatInviteDelaySeconds configuration, which set the time a player had to wait before sending another party invite to the same player.

Integration steps:

  • If you overrode the repeatInviteDelaySeconds configuration, remove it from your YAML files.
  • If you want to enforce a repeat invite delay value, you can customize the Party Plugin handleSendInviteRequest() and party exts to track when each invite was sent.

[Multiplayer] Update Game Instance Plugin server connection/disconnection and expiration method signatures #

Previously, the Game Instance Plugin onGameServerDisconnected(), onGameServerFailedToConnectInitialPlayers(), and onGameInstanceExpired() methods accepted a read-only copy of the game instance. Now, you can directly modify the game instance.

Integration steps: If you override any of the above Game Instance Plugin methods, make the following changes:

  • In all of the above methods, change the first parameter from readOnlyGameInstance: ReadOnlyGameInstance to gameInstanceSnapshot: GameInstance.GameInstance
  • In onGameServerFailedToConnectInitialPlayers() change the second parameter from initialPlayers: List<ReadOnlyGamePlayer> to initialPlayers: List<GameInstance.GamePlayer>

[Multiplayer] Rename Game Instance Plugin onEndGame() to handleBackendEndRequest() #

To better standardize naming patterns, we renamed GameInstancePlugin.onEndGame() to GameInstancePlugin.handleBackendEndRequest(). Functionality remains the same.

Integration steps: Find all occurrences of GameInstancePlugin.onEndGame() and replace them with GameInstancePlugin.handleBackendEndRequest().

[Multiplayer] Add new ExtGameServerUnlinkRequest proto #

We create a new ExtGameServerUnlinkRequest proto for use in game server unlinking flows. This proto must be defined in your project protos to compile.

Integration steps:

  • Option 1 (preferred): Run the 0-6-0 upgrade script using the Pragma CLI:
    • ./pragma update 0-6-0
  • Option 2: Manually add the proto definition to your PROJECT_PROTOS/src/main/proto/shared/gameInstanceExt.proto file:
    • message ExtGameServerUnlinkRequest {}

We renamed the unlink endpoints to more accurately reflect the actual action of the RPC.

Integration steps:

  • Update any references of the UnlinkIdentityProviderAccount<*>V1 to DeleteIdentityProviderAccount<*>V1 RPC in the platform, Portal, or SDK.
  • Specify the providerAccountId when deleting an identity provider account.

[Accounts] Update custom identity provider plugins #

We added new configuration values to the shared Identity Provider Plugin configuration. These configurations will need to be added to the getPublicInfo method.

Integration steps:

  • Add the following configuration values to your getPublicInfo method in your custom identity provider plugins:
    • accountLinkingCooldownInDays
    • accountLinkingOneAssociationOnly
    • accountUnlinkingEnabled

Before:

override fun getPublicInfo(): Map<String, String> {
  return mapOf(
    "showPortalLoginButton" to config.showPortalLoginButton.toString(),
    "clientId" to config.clientId,
    "playerLoginEnabled" to config.playerLoginEnabled.toString(),
    "operatorLoginEnabled" to config.operatorLoginEnabled.toString(),
    "accountLinkingEnabled" to config.accountLinkingEnabled.toString()
  ) 
} 

After:

override fun getPublicInfo(): Map<String, String> {
  return mapOf(
    "showPortalLoginButton" to config.showPortalLoginButton.toString(),
    "clientId" to config.clientId,
    "playerLoginEnabled" to config.playerLoginEnabled.toString(),
    "operatorLoginEnabled" to config.operatorLoginEnabled.toString(),
    "accountLinkingEnabled" to config.accountLinkingEnabled.toString(),
    "accountUnlinkingEnabled" to config.accountUnlinkingEnabled.toString(),
    "accountLinkingOneAssociationOnly" to config.accountLinkingOneAssociationOnly.toString(),
    "accountLinkingCooldownInDays" to config.accountLinkingCooldownInDays.toString()
  ) 
}

[PlayerData] Update to PlayerDataSnapshot #

The `PlayerDataSnapshot.getUniqueEntity` function no longer throws an exception if the Entity is not found and instead returns null. Update any calls to this function and add in your own handling if the Entity is not found.

beforeafter
fun getUniqueEntity(name: String): Entityfun getUniqueEntity(name: String): Entity?

#

Deprecation removals #

We removed the following previously deprecated items. If you haven’t integrated the changes, follow the instructions in the linked deprecation and/or integration notes.

  • Party Plugin returnFromGameInstance() method. Deprecation note.
  • handleBackendCreateByMatchmaking(), handleBackendAddPlayersByMatchmakingRequest(), and related ext build methods. Deprecation note.
  • NewGameInstance.setExtGamePlayer() method and the ExtGameInstance value in the NewGameInstance constructor. Deprecation note and integration note.
  • ExtAddedToGame payload and the buildExtAddedToGame() method in favor of using the Game Instance data stores. Integration note.

New deprecations #

To match the naming of the new MatchApi.Unlink() method, we added a MatchApi.Link() method to replace MatchApi.RequestStartGame().

Integration steps:

  • Replace instances of MatchApi.RequestStartGame() with MatchApi.Link(). Note that Link() includes a GameStartDataDelegate with game start data.

    Old:

    void RequestStartGame(FString GameInstanceId);
    
    //Unity:
    public void RequestStartGame(PragmaId gameInstanceId)
    

    New:

    void Link(
    const FString& GameInstanceId,
    const FGameStartDataDelegate& OnComplete);
    
    public void Link(
    PragmaId gameInstanceId, 
    GameStartDataDelegate onComplete)
    
  • Instead of listening to the MatchApi.OnGameStart and MatchApi.OnGameStartFailed SDK events, use the Result<GameStart> returned in the GameStartDataDelegate to handle success and failure cases.

Note: The OnGameStart/Failed events will still trigger for the Match API StartReportCapacityPolling() and RequestStartGame() SDK methods.

MatchApi.RequestStartGame() will be removed in version 0.7.0.

[Accounts] Updated Account Plugin signatures #

We updated the onAccountCreated and onAccountLogin methods in the Account Plugin to include the IdProviderAccount object used to login. The old methods have been annotated as deprecated and will be removed in release 0.7.0.

Integration steps: Update any custom Account Plugins to now accept the new idProviderAccount parameter in the onAccountCreated and onAccountLogin methods.

[Engine] Database host credentials deprecated in favor of identifier schemas #

Identifier schemas deduplicate common database config and are being rolled out as the standard.

Integrations steps:
Replace the deprecated service configurations with the new configurations for Pragma-managed or self-manage shards:

Deprecated configuration (unpartitioned):

[Service]DaoConfig:
  databaseConfig:     
    driver: "MYSQLDB"
    username: "superuser"
    password: "[encrypted-password]"
    hostPortSchemas: "database.[shard].[studio].pragmaengine.com:3306/[table schema]"

Deprecated configuration (partitioned):

[Service]DaoConfig:
  databaseConfig:     
    driver: "MYSQLDB"
    username: "superuser"
    password: "[encrypted-password]"
    hostPortSchemas:
      1: "database.[shard].[studio].pragmaengine.com:3306/[table schema]"

For managed shards - Auto-generated identifier schemas: Contact your customer service representative to enable this feature, and then update your database configuration to reference the auto-generated defaultIdentifier field.
For unpartitioned database:

serviceConfigs:
  [Service]DaoConfig:
    databaseConfig:
      identifierSchema:
        identifier: "defaultIdentifier"
        schema: "[table schema]" # ex: for the shard name test: test_social_account

For partitioned database:

serviceConfigs:
  [Service]DaoConfig:
    databaseConfig:
      identifierSchemas:
        1:
          identifier: "defaultIdentifier"
          schema: "[table schema]" # ex: for the shard name test: test_social_account

Self-managed shards / local development: Move the database host credentials to the SharedDatabaseConfigServiceConfig, and then reference the credentials by its identifier field.

serviceConfigs:
  SharedDatabaseConfigServiceConfig:
    databaseConfigsByIdentifier:
      defaultDb:
        username: "superuser"
        password: "password"
        host: "localhost" 

  # do this for all unpartitioned database tables
  [Service]DaoConfig: 
    databaseConfig:
      identifierSchema:
        identifier: "defaultDb"
        schema: "[table schema]"
   
  # do this for all partitioned database tables
  [Service]DaoConfig: 
    databaseConfig:
      identifierSchemas:
        1:
          identifier: "defaultDb"
          schema: "[table schema]"