Appearance
Appearance
Backend/PrebuiltDashboard
that will be used by local runs and docker image builds when present.MetaplayCoreOptions.supportedLogicVersions
to force a synchronized update of your game client and server.pnpm-lock.yaml
file generated by earlier versions of PNPM is deprecated and must be re-generated in your project. npm i -g pnpm@9
.pnpm install
to re-generate the pnpm-lock.yaml
file.INFO
Note: If you have a service contract signed with Metaplay, your SDK integration has already been updated to reflect any backward-incompatible API changes in the Metaplay SDK in the SDK upgrade pull request of your project. Otherwise, please see the migration guide for a full list of changes, along with the HelloWorld and Idler samples for references on how to apply the changes.
We have updated both the infrastructure and game server Helm charts and bumped the minimum requirements. Please consider updating to the latest versions during your next maintenance window.
infra-modules
v0.2.14. metaplay-gameserver
v0.6.1. The minimum required versions are infra-modules
v0.2.9 and metaplay-gameserver
v0.6.0.
MInputDateTime
component is completely re-engineered from scratch for better usability. It now separates the date and time into two separate inputs. The new input only accepts and shows UTC times. MInputDuration
component now shows preview times in UTC instead of local time and has a new, clearer UX for inputting "empty" durations. MetaEventStreamCard
now has a more comprehensive set of utilities for filtering or highlighting events. It also has new props for pre-filtering and highlighting events. This makes it easier to make custom preset projections of and event stream (like player events) while still allowing manual control to search the events further. Frontend/DefaultDashboard
now contains a vanilla LiveOps Dashboard project that we use to both create the pre-built dashboard and as a template to scaffold your own, custom dashboard project.MInputText
now has a @blur:modelValue
event that can be used to trigger input validation.MModal
component offers a simplified and more intuitive API for displaying read-only modals that do not require user interaction.MInputDate
and MInputTime
components for picking dates and times separately.MInputHintMessage
component for consistently styled hint messages in input components./test/generatedUi
. You can now test generated forms and views for different types.?scroll-to-data-testid=ID_HERE
to scroll to a specific element with the given data-testid
attribute. This implementation works with lazy-loaded content.MInputNumber
has a new step
prop to control the increment/decrement value.MInputText
and MInputNumber
now have an optional clear button that can be used to more easily clear the input field. MInputText
has a new showClearButton
prop to show the button, while MInputNumber
uses the already existing allowUndefined
and clearOnZero
to show the button.SubscriptionOptions
type so all useSubscription()
call sites will have typings for the return data. This reduces bugs by making the return data typings easier to maintain./api/userInfo
instead of directly accessing the OAuth2 provider endpoint.MetaButton
is replaced by MButton
, MIconButton
and MTextButton
.MetaClipboardCopy
is replaced by MClipboardCopy
.MetaActionModalButton
is replaced by MActionModalButton
or a combination of either the MButton
, MIconButton
or MTextButton
with an MActionModal
component.MetaCollapse
is replaced by the MCollapse
component.BCollapse
and CollapsePlugin
have been removed in favour of the new MCollapse
component.Frontend/E2ETests
.MCollapse
component to support additional controls for managing the initial state of the collapsible section. Now, you have the option to set the initial state as open or hide the collapsible section entirely.MInputDateTime
component is now a wrapper for the new MInputDate
and MInputTime
components.getIsProductionEnvironment()
, which didn't work as expected, and replaced with getEnvironmentFamily()
instead. This new function allows the code to check if it is deployed into a production environment with getEnvironmentFamily() === EnvironmentFamily.Production
.onlyClose
property in the MActionModal
component has been deprecated in favor of the MModal
component. This was an optional property used to create modals that do not trigger an action when closed.@metaplay/meta-ui
package for type hints and easier future migration.@metaplay/meta-ui
package for type hints and easier future migration.constructExactTimeCompactString()
now say "3mo" instead of "3m" when shortening months to avoid confusion with minutes.MListItem
and MErrorCallout
now have a max-height in their main content slots to prevent accidental large strings from breaking the layout.MetaplaySDK.LocalizationManager.SetCurrentLanguage()
now throws an exception if you try to change to a language that is not defined in the Languages
game config library.noDeselect
prop from MetaInputSelect
as it doesn't make sense to allow deselection in this way.MInputDateTime
.PlayerPurchaseHistoryCard
item collapse in MetaListCard no longer persists when navigating between pages.MButton
component, where the middle click event within the Live-Ops Dashboard didn't trigger the opening of a new window.MModal
and MActionModal
not emitting hide
event when closed by pressing the escape key.MInputNumber
being too aggressive in clamping non-allowed values while typing. Input validation now happens only on focus change instead of on value change.Metaplay/Create Default Game Config Build Window
menu item.None
to disable the checksum verification. This can be used when the overhead of checksum calculations is too expensive and the game logic can be trusted to never cause a divergence between the client and server.--build-arg CHISELED_IMAGE=
argument. We only recommend this to ease transition to the safer and smaller chiseled images.Player
runtime options. See the migration notes for the required actions. Now you can also override these options per player session for advanced debugging.EntityActor.UnsubscribeFromAsync()
now takes optional MetaMessage goodbyeMessage
that is delivered to EntityActor.OnSubscriberUnsubscribedAsync()
.HttpUtil
methods now have an optional argument for authentication and an optional CancellationToken
argument.Dockerfile.server.dockerignore
.MetaValidateInRangeAttribute
now supports unsigned integers and values up to the range of Int64
. Additionally a new MetaValidateInRangeFloatAttribute
supports floats and doubles.DeletionStatus
PlayerModel property.MetaSerializable
object using the constructor now allows the use of optional parameters. This is especially useful when adding new MetaMember
s, allowing you to deserialize and specify default values when deserialize old instances.MetaGameConfigBuildConstructor
attribute. GameConfigKeyValue
s are currently not supported, support coming in a later update.LocalServer
utility for Unity Editor for launching and managing the game server process. See "Menu -> Metaplay -> Local Server".[BigQueryAnalyticsRecursionLimit]
attribute allows recursion of types in BigQuery formatted analytics events.ActiveEnvironmentIdOverride
to MetaplayClientOptions
which is used when calling MetaplayClient.Initialize()
. This allows easily changing the active environment at init-time.IncludeSoldOutOffers
, which you can set to false
to stop sold-out offers from being included in offer groups. By default, this is true
(matching the old behavior), meaning that sold-out offers can get included in offer groups.StartServicesAsync()
(and corresponding StopServicesAsync()
) hook is available in ServerMain
, for initializing custom services before entity clustering.dotnet-counters
, dotnet-dump
, dotnet-gcdump
and dotnet-trace
) are no longer installed by default in the images. This reduces image sizes by 125MB. The tools can be installed with dotnet tool install --global <tool-name>
.AdminApiOptions.JwtConfiguration.BearerTokenSource
. It gets used in case the standard Authorization header is not specified.FileUtil.Write*()
methods explicitly reset file creation time to avoid "file tunneling" of metadata from the overwritten file.WRITE_EXTERNAL_STORAGE
, READ_EXTERNAL_STORAGE
and READ_PHONE_STATE
from the API level 4.MySqlDataSource
object and logging has been enabled to help diagnose database-related issues.Enqueue(Server)Action()
and Execute(Server)Action()
methods cannot be called unsafely from background threads.GameConfigBase.PopulateConfigEntries()
has been fully removed. ModelListener
interfaces now have default empty implementations for the listener methods. Therefore, there's no longer the need to define empty implementations for methods that wouldn't be used otherwise. The affected interfaces are IPlayerModelClientListenerCore
, IPlayerModelServerListenerCore
, IGuildModelClientListenerCore
, IGuildModelServerListenerCore
, IDivisionModelClientListenerCore
, IDivisionModelServerListenerCore
.EntityActor.StartPeriodicTimer()
and EntityActor.StartRandomizedPeriodicTimer()
require handler method to exists and be declared with [CommandHandler]
.EntityActor.StartPeriodicTimer()
and EntityActor.StartRandomizedPeriodicTimer()
takes more time than the timer's interval is (i.e. new work is scheduled faster than executed), new timer invocations triggered before the completion of the handler are dropped and a warning is logged.shard
declaration in helm
values, service
nodeset is now the first nodeset. This makes entities with NodeSetPlacement.All
have EntityId
s with the value of 0 be placed on the service node.MetaMessage
s being routed to a destination node that does not exist or is not available are now logged at Warning
level. If an entity keeps sending these unroutable messages more than once a second on average, the log level for this entity's warnings get temporarily throttled to Verbose
level.EntityShardId
now contains a separate NodeSetIndex
and NodeIndex
.EntityId
s created with ManualShardingStrategy.CreateEntityId()
have changed as the EntityShardId
encoding above has changed. If you store EntityIds generated with ManualShardingStrategy
, they need to be regenerated. Note that this is extremely unlikely as ManualShardingStrategy
cannot be used for Persisted Entities, and storing the EntityIds of Transient Entities is useless. The EntityID with the zero value continues to be valid.EntityActor.GetAssociatedServiceEntityId(EntityKind targetEntityKind)
is now only allowed for targetEntityKind
s that have ServiceShardingStrategy
.Application.Instance.ClusterConfig
is removed. Use RuntimeOptionsRegistry.Instance.GetCurrent<ClusteringOptions>().ClusterConfig
instead.Metaplay.Attributes
containing attributes which are shared between the server and client.GameConfigBuildTemplate
base class no longer requires knowing the build parameters type statically, which allows using context-dependent build parameter types and allows the default implementation to work with user-defined build parameters classes.MetaRef<>
are now resolved in place, this causes the HashCode to change and as such MetaRef<>
should not be used as keys in dictionaries or as elements in hash based collections (e.g. HashSet
or OrderedSet
).playerId
parameter from AuthenticateSocialAuthenticationClaimAsync()
. In general the process of authenticating should now care about the current or claimed playerId, and requiring some playerId in this method has proven to be confusing.DefaultEnvironmentConfigProvider.ClearActiveEnvironmentClientOverride()
has been removed. The DefaultEnvironmentConfigProvider
no longer persists the runtime active environment override to PlayerPrefs
.--Environment:ExitOnLogError
for the BotClient
in integration tests to help catch more errors.Assets/
directory to be in the project root.ConfigArchive
s with entries named in a particular way. This was caused due to the entries being mistakenly sorted in different order on Unity and on the server, causing a sanity check to fail. Now, the server no longer cares which order the archive entries are in.1
can now be used as action codes for ModelAction
s./isReady
endpoint now returns 503 Service Unavailable (instead of 500 Internal Server Error) when the node is not yet ready.MessageDispatcher
handler was a method of a generic class.PlayerActor
crash if enqueued ServerAction
enqueued another ServerAction
via ServerListener
after a game session had ended.Nullable<T>
with a null
value are no longer dropped.string/int/double_value
fields in event_params
RECORD
s. This behavior now matches the documentation.StartApplicationAsync()
is now called only after the cluster has properly initialized, i.e., all entity shards are running.PersistedMultiplayerEntityActorBase.AddEntityAssociation()
in OnClientSessionHandshake()
now adds the association as expected.GameConfigBuildIntegration.GetAvailableBuildSources(string sourcePropertyInBuildParams)
has been removed. It was obsoleted in release 26 by being renamed to GetAvailableGameConfigBuildSources()
.AppTooLongSuspended
timeout which triggers when a frame took too long to execute has been removed, this is instead handled by the server kicking a client when the client has not responded for 60 seconds by default.http://localhost:5550
when running locally.2022.3.28f1
.SharedCode/
folder has been renamed from CloudCore
to SharedCode
to clarify its purpose.INFO
Note: If you have a service contract signed with Metaplay, your SDK integration has already been updated!
Required migration steps for C# code.
Add the MetaplaySDK\Backend\Attributes\Metaplay.Attributes.csproj
project as an existing project to the Metaplay folder in your MyGame-Server
solution file.
MetaplaySDK samples have renamed Backend/CloudCore/CloudCore.csproj
to CloudCore/SharedCode/SharedCode.csproj
to clarify the project mostly contains the Assets/SharedCode
folder. For consistency, it is recommeded that the you update the project name too.
First, you need to update project file and folder name. Then you update the paths Backend Solution file (Note that GUIDs may be different, and the existing GUIDs should not be modified):
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudCore", "CloudCore\CloudCore.csproj", "{D3CE2986-5A54-4300-9AA8-5E0D3434B69F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedCode", "SharedCode\SharedCode.csproj", "{D3CE2986-5A54-4300-9AA8-5E0D3434B69F}"
Finally, you need to change all project references in the Server
, BotClient
and other Backend Projects:
- <ProjectReference Include="..\CloudCore\CloudCore.csproj" />
+ <ProjectReference Include="..\SharedCode\SharedCode.csproj" />
TIP
To find all project references, you can use git grep "CloudCore.csproj"
In order to clarify the purpose of the two different ways to create instances of ConfigArchive
, its public constructors have been replaced with static creation methods. Replace your ConfigArchive
constructor invocations as follows.
Creating a ConfigArchive
from newly-built data:
- new ConfigArchive(creationTime, entries)
+ ConfigArchive.CreateFromBuild(creationTime, entries)
Creating a ConfigArchive
from data that was loaded from a previously-serialized archive:
- new ConfigArchive(contentHash, createdAt, entries)
+ ConfigArchive.CreateLoaded(contentHash, createdAt, entries)
The actual difference between the two methods is that CreateFromBuild
sorts the given entries
by name and computes the contentHash
automatically, whereas CreateLoaded
keeps the entries
in the given order and the contentHash
as given.
DefaultEnvironmentConfigProvider.ClearActiveEnvironmentClientOverride()
has been removed. The runtime active environment override is no longer persisted in PlayerPrefs
and is instead an in-memory override that applies for the current lifecycle of the app. The new, recommended way to use the runtime active environment override is to provide ActiveEnvironmentIdOverride
in MetaplayClientOptions
when calling MetaplayClient.Initialize
.
The member ClientDebugConfig
in class EnvironmentConfig
has been removed and is replaced with ClientLoggingConfig
that only configures the per-environment client logging. If you have custom configuration for the client logging options, please update the environment the logging options in your enviroment configuration.
The configuration of EnablePlayerConsistencyChecks
and PlayerChecksumGranularity
is now done on the server side. If you have custom configuration for these options then migrate them into the appropriate Options.xxx.yaml
files hosted in the server Config
directory. The default options provided by the SDK remain unchanged:
ClientConsistencyChecks
: Enabled for local
and development
environments, disabled otherwise.ChecksumGranularity
: PerOperation
for local
and development
environments, PerActionSingleTickPerFrame
for other environments.For example, to override checksum granularity to PerActionSingleTickPerFrame
also in development environments you would add this to Options.dev.yaml
:
Player:
DebugConfig:
ChecksumGranularity: PerActionSingleTickPerFrame
The signature of the MetaplayClientCreatePlayerContextFunc
and the default implementation DefaultCreatePlayerContext
has been changed. The parameters currentOperation
, enableConsistencyChecks
and checksumGranularity
have been replaced with the single parameter initialState
received from the server. The old parameters are now accessible from initialState
, highlighting that they are now part of server configuration. If you have a custom implementation of IPlayerClientContext
, update your code to read these parameters from the passed in initialState
.
If you create an instance of DefaultPlayerClientContext
in your BotClient.HandleStartSession()
, you'll need to remove the arguments for currentOperation
, enableConsistencyChecks
and checksumGranularity
. For the new initialPlayerState
parameter, pass in success.PlayerState
(where success
is the SessionProtocol.SessionStartSuccess success
to HandleStartSession()
.)
To provide better error handling, PopulateConfigEntries has been removed. If you need to pass custom data to the game config, we recommend doing so by providing an implementation of GameConfigBuildTemplate<TSharedConfig, TServerConfig, TBuildParameters>.GetEntryBuilder(Type configType, string entryName)
. This method does the processing during at build time, reducing the amount of work that needs to be done when the game client imports the game config.
For example, the Idler sample imports a JSON file as part of the build process:
public class IdlerGameConfigBuild : GameConfigBuildTemplate<SharedGameConfig, ServerGameConfig, IdlerGameConfigBuildParameters>
{
protected override ConfigEntryBuilder? GetEntryBuilder(Type configType, string entryName)
{
if (!ShouldBuildEntry(entryName))
return null;
if (configType == typeof(SharedGameConfig))
{
if (entryName == "OpaqueSourceTest")
{
if (!BuildParameters.HasLocalFileSource)
return null;
return CustomEntryBuildSingleSource<byte[]>(
// Fetch data from the OpaqueSourceTest defined in the build parameters
GetGenericFetchFunc("OpaqueSourceTest.json", BuildParameters.OpaqueDataSource),
(builder, data) =>
{
// Deserialize from json to the target type.
JsonSerializer serializer = JsonSerialization.CreateSerializer(
new JsonSerialization.Options(JsonSerialization.Options.DefaultOptions)
{
TraverseGameConfigDataType = typeof(OpaqueSourceTestInfo),
});
List<OpaqueSourceTestInfo> list = JsonSerialization.Deserialize<List<OpaqueSourceTestInfo>>(data, serializer);
// Convert to a type that the SDK can consume, if your data type supports variants, they can also be passed here.
List<VariantConfigItem<OpaqueSourceTestId,OpaqueSourceTestInfo>> items = list.Select(x => new VariantConfigItem<OpaqueSourceTestId, OpaqueSourceTestInfo>(x, null, null, null)).ToList();
// Finally, assign the result to the game config builder
builder.AssignLibraryBuildResult(nameof(SharedGameConfig.OpaqueSourceTest),
items,
null);
});
}
}
// For every other entry, return the default builder.
return base.GetEntryBuilder(configType, entryName);
}
}
Another option is to add binary data to the game config. To do this, add the data as part of the build process:
public class IdlerGameConfigBuild : GameConfigBuildTemplate<SharedGameConfig, ServerGameConfig, IdlerGameConfigBuildParameters>
{
protected override Task Build(IGameConfigBuilder<SharedGameConfig> shared, IGameConfigBuilder<ServerGameConfig> server, IdlerGameConfigBuildParameters buildParams)
{
byte[] bytes = ...;
shared.AddCustomArchiveEntry(ConfigArchiveEntry.FromBlob("CustomEntry", bytes));
return base.Build(shared, server, buildParams);
}
}
This can then be loaded in the GameConfig.PopulateCustomConfigData(GameConfigImportParams importParams)
method as follow:
public class SharedGameConfig : SharedGameConfigBase
{
public override void PopulateCustomConfigData(GameConfigImportParams importParams)
{
ReadOnlyMemory<byte> bytes = importParams.Resources.BaselineArchive.GetEntryBytes("CustomEntry");
// Parse your byte data and assign it to a property in your game config class
}
}
You can instead manually resolve references at runtime. For example, replace your MetaRef
collection with a collection containing StringId
(or the Id of your config items):
- [MetaMember(1)] public OrderedSet<MetaRef<ProducerInfo>> DiscountedProducers { get; private set; }
+ [MetaMember(1)] public OrderedSet<ProducerTypeId> DiscountedProducers { get; private set; }
You can then access the item from the relevant game config library:
foreach (ProducerTypeId id in DiscountedProducers)
ProducerInfo info = GameConfig.Producers[id];
Another option is to use a normal list in the your game config, then construct the hash based collection in the IGameConfigPostLoad.PostLoad()
method:
public class HappyHourInfo : IMetaActivableConfigData<HappyHourId>, IGameConfigPostLoad
{
[MetaMember(1)] private List<MetaRef<ProducerInfo>> discountedProducers { get; set; }
public OrderedSet<MetaRef<ProducerInfo>> DiscountedProducers { get; private set; }
public void PostLoad()
{
DiscountedProducers = new OrderedSet<MetaRef<ProducerInfo>>(discountedProducers);
}
}
Timers started with StartPeriodicTimer
and StartRandomizedPeriodicTimer
require a handler for the scheduled message. The existence of the handler is checked when either method is called.
All handlers using the Akka's Receive<>
methods must be changed to use [CommandHandler]
. Code in the form of:
protected Task Initialize()
{
StartRandomizedPeriodicTimer(TickInterval, Tick.Instance);
}
protected override void RegisterHandlers()
{
ReceiveAsync<Tick>(ReceiveTick);
...
}
async Task ReceiveTick(Tick _)
{
...
}
Should be converted to:
protected Task Initialize()
{
StartRandomizedPeriodicTimer(TickInterval, Tick.Instance);
}
[CommandHandler]
async Task HandleTick(Tick _)
{
...
}
The PlayerActorBase.GetSessionStartAssociatedEntities()
has been removed. Instead the actor keeps track of all entities that have been added with AddEntityAssociation
, and those are sent to new sessions automatically. To port existing code from GetSessionStartAssociatedEntities
:
EntityActor.OnClientSessionHandshakeAsync()
call AddEntityAssociation(.., removeOnSessionEnd: true)
for every associated entity as was called in GetSessionStartAssociatedEntities
.GetSessionStartAssociatedEntities
was added with AddEntityAssociation
, use removeOnSessionEnd: true
to maintain the expected, session lifetime.The PersistedMultiplayerEntityActorBase.GetSessionStartAssociatedEntities()
has been removed. Instead the actor keeps track of all entities that have been added with AddEntityAssociation
, and those are sent to new sessions automatically. To port existing code from GetSessionStartAssociatedEntities
:
AddEntityAssociation
to add an association. This was required previously too.RemoveEntityAssociation
to add an association. This was required previously too.EntityActor.Initialize()
call AddEntityAssociation
for every associated entity as was called in GetSessionStartAssociatedEntities
.Replace void OnSubscriberLost
with Task
OnSubscriberLost
Async
:
- override void OnSubscriberLost(EntitySubscriber subscriber)
+ override Task OnSubscriberLostAsync(EntitySubscriber subscriber)
{
...
+ return Task.CompletedTask;
}
Replace void OnSubscriberTerminated
with Task
OnSubscriberTerminated
Async
:
- override void OnSubscriberTerminated(EntitySubscriber subscriber)
+ override Task OnSubscriberTerminatedAsync(EntitySubscriber subscriber)
{
...
+ return Task.CompletedTask;
}
Replace void OnSubscriberUnsubscribed(EntitySubscriber subscriber)
with Task
OnSubscriberUnsubscribedAsync
Async
(EntitySubscriber subscriber,
MetaMessage goodbyeMessage)
:
- override void OnSubscriberUnsubscribed(EntitySubscriber subscriber)
+ override Task OnSubscriberUnsubscribedAsync(EntitySubscriber subscriber, MetaMessage goodbyeMessage)
{
...
+ return Task.CompletedTask;
}
Rename Task OnSubscriptionLost
to Task OnSubscriptionLost
Async
:
- override Task OnSubscriptionLost(EntitySubscription subscription)
+ override Task OnSubscriptionLostAsync(EntitySubscription subscription)
{
...
}
Rename Task OnSubscriptionKicked
to Task OnSubscriptionKicked
Async
:
- override Task OnSubscriptionKicked(EntitySubscription subscription)
+ override Task OnSubscriptionKickedAsync(EntitySubscription subscription)
{
...
}
The attribute marking an "anonymous" AdminApi HTTP endpoint (i.e., an endpoint that can be accessed as long as the user is authenticated to the dashboard) is changed. If you are using the [AllowAnonymous]
endpoint for any custom AdminApi endpoints, replace it like as follows:
[HttpGet("hello")]
- [AllowAnonymous]
+ [RequirePermission(MetaplayPermissions.Anyone)]
public ActionResult GetHello() { ... }
Required migration steps for the dashboard project.
Ensure that you have Node version 20.12.0 or higher installed. We recommend using the latest LTS version of Node 20 which, at the time of writing, is 20.14.0.
Changes to several configuration files have been made during this release. The MetaplaySDK/Frontend/DefaultDashboard
folder is to be considered the source of truth for these files. You will need to make the following changes to files in your Dashboard
folder, based on the source files in MetaplaySDK/Frontend/DefaultDashboard
:
.eslintrc.cjs
. The file introduces new ESLint configurations.vite.config.ts
. The file introduces new Vite configurations.tsconfig.app.json
. The file contains TypeScript configurations for the browser app.tsconfig.json
. The file introduces new configurations. "../tsconfig.libraries.json"
to point to this folder within your own repository's structure. Typically this will be "../../MetaplaySDK/Frontend/tsconfig.libraries.json"
tsconfig.node.json
. The file introduces new TypeScript configs for the Node tooling.cypress.config.ts
.package.json
has new dependencies and an updated lint
script. You should copy over the source file from DefaultDashboard
and overwrite your existing file. Doing so will overwrite some changes to the file that you will now need to revert. This is best done using a file diff tool: name
property inside the file to that of your project. This is typically of the form <projectName>-dashboard
.You can now remove the following two obsolete files from your Dashboard
's root folder:
meta-components.d.ts
file.cypress.config.js
then you should remove it. Please note the exact filename..eslintrc.js
then you should remove it. Please note the exact filename.Delete all node_modules
folders from your project's repository. npm
can sometimes get confused about stale dependencies when we make big changes to structure or configuration, as we did above. You can remove all modules with the command git clean -fdx ':(glob)**/node_modules/*'
from anywhere inside your repository.
Run command pnpm i
from your Dashboard
directory to freshly re-install all required modules.
Run command pnpm lint
from your Dashboard
directory to get more opinionated TypeScript checks on code quality.
Finally, run pnpm build
to build the Dashboard. Depending on how much customisation you have made to your project, you may now see warnings or errors related to component changes and deprecations inside the Metaplay SDK. The following migration guides will help you to fix these.
To make imports more consistent, the import scope has changed for some components. This will cause build errors of the form: Cannot find module '@metaplay/core/src/components/generatedui/components/MetaGeneratedForm.vue' or its corresponding type declarations.ts-plugin(2307)
You can fix this by changing the import statement to be of the form: @metaplay/core'
i.e. The import now appears at the root of the library and the internal structure of the library is no longer visible to external code. This simplification results in import statements of the form: import { MetaGeneratedForm } from '@metaplay/core'
.
The components that are known to be affected are:
MInputDuration
Component Changes The MInputDuration
component's allowZeroDuration
property has been removed in favor of the new allowEmpty
property. The component's UX has been updated to make this clearer to the user. Additionally, please consider using hintMessage
to provide additional context to the user of what an empty value means in practice. Maybe a feature has been disabled?
To migrate, replace the allowZeroDuration
property with allowEmpty
as follows, and update your surrounding code to handle undefined
outputs from the component:
MInputDuration(
:model-value="duration"
@update:model-value="duration = $event"
- allowZeroDuration
+ allowEmpty
+ :hintMessage="!duration ? 'Feature is disabled' : undefined"
...
)
// Selected duration can now be either Luxon.Duration or undefined
const duration: Ref<Duration | undefined> = ref()
MetaEventStreamCard
Component Changes The MetaEventStreamCard
component has been updated to include new props for pre-filtering and highlighting events. These replace the previous, similar props and overall should make the component easier to use. Please update your callsites to the new props as follows:
meta-event-stream-card(
title="Some events",
:eventStream="eventStream",
- tooltip="Tooltips got removed as a bad UX pattern. Please use a descriptive card title instead."
- searchIsFilter
+ utilitiesMode="filter" <- "highlight" (default, like before) or "filter"
- :initialSearchString="initialSearchString"
- :freezeSearch="freezeSearch"
+ :searchPreHighlight="searchPreHighlight" <- A search string that permanently highlights events, like before (not editable by the user)
- :initialKeywordFilters="initialKeywordFilters"
- :freezeKeywordFilters="freezeKeywordFilters"
+ :keywordPreFilters="keywordPreFilters" <- Array of keyword strings to pre-filter events by, like before (not editable by the user)
- :initialEventTypeFilters="initialEventTypeFilters"
- :freezeEventTypeFilters="freezeEventTypeFilters"
+ :eventTypePreFilters="eventTypePreFilters" <- Array of event type strings to pre-filter events by, like before (not editable by the user)
...
)
doesHavePermission()
Changed The doesHavePermission()
function moved from gameServerApiStore
to usePermissions
. Please update your callsites:
-import { useGameServerApiStore } from '@metaplay/game-server-api'
-const gameServerApiStore = useGameServerAPIStore()
-const hasPermission = gameServerApiStore.doesHavePermission('xxx')
+import { usePermissions } from '@metaplay/meta-ui-next'
+const permissions = usePermissions()
+const hasPermission = permissions.doesHavePermission('xxx')
MetaButton
is replaced by MButton
, MIconButton
and MTextButton
.MetaClipboardCopy
is replaced by MClipboardCopy
.MetaActionModalButton
is replaced by MActionModalButton
or a combination of either the MButton
, MIconButton
or MTextButton
with an MActionModal
component.MetaCollapse
is replaced by the MCollapse
component.For detailed examples of how the new components work, run pnpm storybook
within the MetaplaySDK/NodePackages/MetaUiNext
folder.
The b-collapse
and BCollapsePlugin
have now been removed. To ensure continued functionality, update all game-specific references to use the new MCollapse
component. If you prefer to continue using the b-collapse
component, you can import the library into your game-specific dashboard project and maintain it as a custom dependency.
The MetaInputSelect
component no longer supports the noDeselect
property. This property had no useful effect and is now ignored. You can safely remove the prop from any of your MetaInputSelect
components that use it.
The MActionModal
component no longer supports the onlyClose
property. To achieve the same functionality, replace instances of the MActionModal
with the onlyClose
property to the new MModal
component as follows:
- MActionModal(
+ MModal(
title="modalTitle"
ref="modalRef"
- :action="async () => {}"
- only-close
...
)
The official Vue extension for VS Code has matured to the point that we now highly recommend using it. Take the following steps to ensure that you are using the latest version:
A problem that could arise during the steps mentioned above where VS Code pins the Vue extension to 1.8.27 despite you trying to upgrade to the latest 2.0.xx version.
Released on: July 11th, 2024
Dashboard: The latest version of TypeScript (version "5.5") contains bugs in its handling of generics. We use generics heavily inside our UI components and this latest version causes errors when building the Dashboard. Our project dependencies allowed for the latest version of TypeScript to be installed if you created a new project or updated (with npm update
) an existing one. To fix this we have pinned TypeScript back to version "5.4". We will update to the latest version of TypeScript once these issues have been addressed.
Dashboard: Fixed getEnvironmentFamily
to return the correct value.
Dashboard: Fixed player search to stop it from breaking when trying to display uninitialized players.
Dashboard: Improved formatting of game config validation log messages.
SDK: Fixed login potentially getting stuck a Metaplay Guild transaction is pending.
SDK: Improved caching of the GameConfig List endpoint, causing potential old data to be display.
SDK: Fixed dashboard "game configs updated" and "server restarted" popups not working with newest infra version. The long-poll SSE channel used to communicate the state gets incorrectly buffered in the ingress proxy.
package.json
has new dependencies and an updated lint
script. You should copy over the package.json
from DefaultDashboard
and overwrite your package.json
. Doing so will overwrite some changes to the file that you will now need to revert. This is best done using a file diff tool: name
property inside the file to that of your project. This is typically of the form <projectName>-dashboard
.