Solution for Verse Persistence Issue: unique specifier on classes that lack the allocates effect

After the recent update to version 31.00, many people encountered the following warning:

This warning is mostly found in Persistent functions, as everyone follows the official documentation. However, the official documentation has not been updated and no solution has been provided.

I don’t want my game dropping dead after the next engine update. I found some solutions to avoid this risk:

1. First, we need to remove all the <unique> effect from warning methods

2. Then, We need to add a <transacts> effect to all DebugString methods

Now the stat type class and module will like this:

image

You will then notice a mistake in the Record Stat’s method below:

3. In each if() where an error occurs, add .DebugString() after the Stat class

That is, not directly comparing classes, but using the Debug function in the class for comparison.

The code snippet after repair:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# Represents an abstract stat to update using RecordPlayerStat.
# The class has the unique specifier to make instances of the class comparable.
# The class has the computes specifier to be able to instantiate it at module-scope.
# The class has the abstract specifier so it cannot be instantiated directly, and
# requires subclasses to implement any non-initialized functions, like DebugString().
stat_type := class<computes><abstract>:
    DebugString()<transacts>:string

# we uses instances of stat_type subclasses instead of the enum type
# so we can add more stat_types after the initial published version of the project.    
StatType := module:
    round_stat<public> := class<computes>(stat_type):
        DebugString<override>()<transacts>:string = "Round Count"

    round_win_stat<public> := class<computes>(stat_type):
        DebugString<override>()<transacts>:string = "Round Win Count"
    

    # Instances of each stat_type
    RoundsCount<public>:round_stat = round_stat{}
    RoundWin<public>:round_win_stat = round_win_stat{}

    
# Manages and updates player_stat_tables for each player.
player_stats_manager := class():

    # Return the player_stats_table for the provided Agent.
    GetPlayerStats(Agent:agent)<decides><transacts>:player_stats_table=
        var PlayerStats:player_stats_table = player_stats_table{}
        if:
            Player := player[Agent]
            
            PlayerStatsTable := PlayerStatsMap[Player]
            set PlayerStats = MakePlayerStatsTable(PlayerStatsTable)
        PlayerStats

    # Initialize stats for all current players.
    InitializeAllPlayers(Players:[]player):void =
        for (Player : Players):
            InitializePlayer(Player)

    # Initialize stats for the given player.
    InitializePlayer(Player:player):void=
        if:
            not PlayerStatsMap[Player]
            set PlayerStatsMap[Player] = player_stats_table{}

    # Update the given stat for the given player by creating a new
     # player_stats_table and setting it in the PlayerStatsMap.
    RecordPlayerStat(Agent:agent, Stat:stat_type, ?Score:float = 1200.0, ?Langue: Language = Language.En):void=
        if:
            Player := player[Agent]
            PlayerStatsTable := PlayerStatsMap[Player]

            if(Stat.DebugString() = StatType.RoundsCount.DebugString()):
                RoundStat := PlayerStatsTable.RoundsCount
                set PlayerStatsMap[Player] = player_stats_table:
                        MakePlayerStatsTable<constructor>(PlayerStatsTable)
                        RoundsCount := MakeUpdatedPlayerStat(RoundStat, RoundStat.CurrentValue+1)
            
            else if(Stat.DebugString() = StatType.RoundWin.DebugString()):
                WinRounds := PlayerStatsTable.WinCount
                set PlayerStatsMap[Player] = player_stats_table:
                        MakePlayerStatsTable<constructor>(PlayerStatsTable)
                        WinCount := MakeUpdatedPlayerStat(WinRounds, WinRounds.CurrentValue+1)
6 Likes

This is wonderful, thank you - However, I’m now getting a “no_rollback” error, as seen below. Trying to resolve currently.

[UPDATE]: Whoops. Don’t forget to put <transacts> on the first DebugString in the stat_type declaration.

image

2 Likes

using @CHENSHUO.L solution, here is the corrected commands.verse from the Verse Commander tutorial:

command := class<computes><abstract>:
    DebugString()<transacts>:string

# The Commands module contains definitions of commands that the NPC can perform.
# This example uses instances of command subclasses instead of the enum type
# so you can add more commands after the initial published version of the project.
Commands := module:
    # The following are subclasses of the command class
    # that implement the abstract command class and define what commands are valid.
    # Each has their own implementation of DebugString(), for example,
    # so when you print a command value it has the correct string associated with it.
    # Since the command class is abstract, it means these subclasses are the only valid commands,
    # otherwise there will be a compiler error.
    forward_command<public> := class<computes>(command):
        DebugString<override>()<transacts>:string = "Forward"

    turnright_command<public> := class<computes>(command):
        DebugString<override>()<transacts>:string = "TurnRight"

    turnleft_command<public> := class<computes>(command):
        DebugString<override>()<transacts>:string = "TurnLeft"
        
    # Instances of each command type that can be used in the minigame.
    Forward<public>:forward_command = forward_command{}
    TurnRight<public>:turnright_command = turnright_command{}
    TurnLeft<public>:turnleft_command = turnleft_command{} 

# Convert the command data to a string to be able to print it when debugging issues.
# For example, Print("Player selected {Command} command.")
ToString(Command:command):string=
    Command.DebugString()

Also, in the verse_commander_character.verse file:

            # If the command is forward, create a new navigation target from the target tile.
            if:
                Command.DebugString() = Commands.Forward.DebugString()
            then:
                NavTarget := MakeNavigationTarget(TargetTile.Translation)
                # Navigate the character to the navigation target. The ReachRadius is set to a small float
                # here rather than zero to allow the character a small leniency in how close it needs to get to the 
                # exact position of the target.
                NavResult := Navigatable.NavigateTo(NavTarget, ?ReachRadius := ReachRadius)
            # If the command is right or left, force the character to maintain focus on the taget for
            # a short period of time to turn the character to face that target.
            else if:
                Command.DebugString() = Commands.TurnLeft.DebugString() or Command.DebugString() = Commands.TurnRight.DebugString()

You should then be able to do the same with the ui_manager.verse file!

1 Like

Super awesome, thank you! Strange that they have not yet updated the documentation

1 Like
#change where is 
DebugString<override>():string = "Deaths

#for this
DebugString<override>()<transacts>:string = "Deaths

#add transacts

#in condition change this
if(Stat = StatType.Elo):
#for this
if(Stat.DebugString() = StatType.Elo.DebugString()):

#dont forget this if not instantiate yet

# Instances of each stat_type you can use in your experience
Kills<public>:kill_stat = kill_stat{}
Deaths<public>:death_stat = death_stat{}
Elo<public>:elo_stat = elo_stat{}
Version<public>:version_stat = version_stat{}