Verse prints impact performance of published maps

Summary

Prints are used in Verse to debug our code, they are only useful in test sessions where creators can see them.

They are invisible in private/public version but still impact servers performance. This is a huge issue as some of the maps use prints even every server tick or print big blocks of logs on certain actions. So far I haven’t seen anyone being aware of this issue.

Please select what you are reporting on:

Verse

What Type of Bug are you experiencing?

Stability

Steps to Reproduce

Create verse device and place it in a map:

hello_world_device := class(creative_device):
    
    OnBegin<override>()<suspends>:void=
        loop:
            for(X:=0..5000):
                Print("test")
            Sleep(0.0)

Create private or even public version of the map.
Join the map, try to move and see how slow and laggy the game is.

As a comparison, comment out the Print line to see that the loop itself doesn’t impact performance.

Expected Result

No optimization drop in private and public version. Print calls should be completely stripped from final builds.

Observed Result

Lags in private/public versions.

Platform(s)

All

Island Code

7693-8137-7027 (click the button device to see fps drops)

Upload an image

A bit more complex example from the map to make sure empty loop wasnt stripped:


using. /Verse.org/Simulation
using. /Verse.org/Simulation/Tags
using. /Verse.org/Assets
using. /Verse.org/Verse
using. /Verse.org/Random
using. /Verse.org/Colors
using. /Verse.org/Colors/NamedColors
using. /Verse.org/Native
using. /Verse.org/Concurrency
using. /UnrealEngine.com/Temporary
using. /UnrealEngine.com/Temporary/UI
using. /UnrealEngine.com/Temporary/SpatialMath
using. /UnrealEngine.com/Temporary/Diagnostics
using. /UnrealEngine.com/Temporary/Curves
using. /Fortnite.com/UI
using. /Fortnite.com/Devices
using. /Fortnite.com/Devices/CreativeAnimation
using. /Fortnite.com/Devices/CreativeAnimation/InterpolationTypes
using. /Fortnite.com/Vehicles
using. /Fortnite.com/Teams
using. /Fortnite.com/Playspaces
using. /Fortnite.com/Game
using. /Fortnite.com/FortPlayerUtilities
using. /Fortnite.com/Characters
using. /Fortnite.com/AI

hello_world_device := class(creative_device):
    @editable Button:button_device = button_device{}

    OnBegin<override>()<suspends>:void=
        Sleep(0.0)
        loop:
            Ag := Button.InteractedWithEvent.Await()
            spawn. tick_rate_debug{}.InitPlayerAsync(Ag, event(){})
            
            var Sum:int= 0
            loop:
                race:
                    Button.InteractedWithEvent.Await()
                    loop:
                        for(X:=0..5000):
                            Print("test")
                            set Sum += AddInt(1,2)
                        Print("Sum: {Sum}")
                        set Sum = 0
                        Sleep(0.0)
                Button.InteractedWithEvent.Await()
                race:
                    Button.InteractedWithEvent.Await()
                    loop:
                        for(X:=0..5000):
                            set Sum += AddInt(1,2)
                        Print("Sum: {Sum}")
                        set Sum = 0
                        Sleep(0.0)

    AddInt(A:int, B:int):int=
        A + B
    
tick_rate_debug<public> := class():

	TickRateText<localizes><private>(TickRateString : string) : message = {"{TickRateString} TPS"}

	TickRateTextBlock<private> : text_block = text_block:
		DefaultTextColor := NamedColors.White
		DefaultJustification := text_justification.Right
		
	var AverageTickRate : float = 0.0

	GraphMaker<private> : graph_maker = graph_maker {NumBars := 30}
	Canvas<private> : canvas = canvas{}
			
	InitPlayerAsync(FirstPlayer:agent, PlayerRemoved:event())<suspends>:void=


		GraphMaker.Init()

		LabelSlot := canvas_slot:
			Anchors := anchors:
				Minimum := vector2{X := 1.0, Y := 1.0}
				Maximum := vector2{X := 1.0, Y := 1.0}
			Alignment := vector2{X := 1.0, Y := 1.0 }
			Offsets := margin{Left := -195.0, Top := -8.0}
			Widget := TickRateTextBlock

		GraphSlot := canvas_slot:
			Anchors := anchors:
				Minimum := vector2{X := 1.0, Y := 1.0}
				Maximum := vector2{X := 1.0, Y := 1.0}
			Alignment := vector2{X := 1.0, Y := 1.0 }
			Offsets := margin {Left := -10.0, Top := -10.0, Right := 170.0, Bottom := 40.0}
			SizeToContent := false
			Widget := GraphMaker.Canvas

		Canvas.AddWidget(LabelSlot)
		Canvas.AddWidget(GraphSlot)

		if:
			PlayerUI := GetPlayerUI[player[FirstPlayer]]
		then:
			PlayerUI.AddWidget(Canvas)

		var PreviousTime : float = GetSimulationElapsedTime()

		loop:
			Sleep(0.0)
			CurrentTime := GetSimulationElapsedTime()
			DeltaTime := CurrentTime - PreviousTime
			set PreviousTime = CurrentTime

			TickRate := 1.0 / DeltaTime
			#TickRate := GetRandomFloat(0.0,30.0) # use this to test the graph

			set AverageTickRate += (TickRate - AverageTickRate) / 3.0 # smooth it out so the text doesn't flicker distractingly as much
			TickRateTextBlock.SetText(TickRateText(GetRoundedString(AverageTickRate,2)))
			
			BarPercent := TickRate/30.0
			GraphMaker.PushValue(BarPercent)

	GetRoundedString<private>(Input : float, NumDigitsAfterDecimal : int) : string =
		if:
			Multiplier := Pow(10.0, NumDigitsAfterDecimal * 1.0)
			RoundedValue := float[Round[Input*Multiplier]*1.0] / Multiplier
			BeforeDecimal := Floor[RoundedValue]
			AfterDecimal := Abs(Round[(RoundedValue - BeforeDecimal*1.0)*Multiplier])
			var AfterDecimalString : string = ToString(AfterDecimal)

			#pad the number after the decimal with leading zeroes
			for (It := 0..(NumDigitsAfterDecimal-AfterDecimalString.Length-1)):
				set AfterDecimalString = array{'0'} + AfterDecimalString
		then:
			"{BeforeDecimal}.{AfterDecimalString}"
		else:
			ToString(Input)
	

graph_maker := class():

	NumBars : int   
	Canvas : canvas = canvas{}

	var ColorBlocks<private> : []color_block = array{}
	var Values : []float = array{}

	BackgroundSlot : canvas_slot = canvas_slot:
		Widget := color_block:
			DefaultColor := NamedColors.White
			DefaultOpacity := 0.3
		Anchors := anchors:
			Minimum := vector2{X := 0.0, Y := 0.0}
			Maximum := vector2{X := 1.0, Y := 1.0}

	Init() : void =
		Canvas.AddWidget(BackgroundSlot)

		for(B := 0..NumBars-1):
			set Values += array {0.0} #default the bar to all zeroes
			set ColorBlocks += array:
				ColorBlock : color_block = color_block:
					DefaultColor := NamedColors.White
					DefaultOpacity := 0.8

		RedrawAllBars()

	# shift all the values down by one and then replace the last value
	PushValue(Value : float) : void = 
		for:
			B := 0..NumBars-2
			Val := Values[B+1]
			set Values[B] = Val
		do:
			RedrawBar(B)
			
		if(set Values[NumBars-1] = Value) then RedrawBar(NumBars-1) 
		
	
	SetValue(Index : int, Value : float) : void = 
		if (set Values[Index] = Value) then RedrawBar(Index)     
	
	RedrawAllBars() : void = 
		for (B := 0..NumBars-1) do RedrawBar(B)
		
	RedrawBar(Index : int): void = 
		BarWidth := 1.0 / (1.0*NumBars) 
		
		if:
			ColorBlock := ColorBlocks[Index]
			Value := Values[Index]
		then:
			# we do Value*Value for blue so that the transition color tends to be more orange
			ColorBlock.SetColor(color {R := 1.0, G := Lerp(0.0,1.0,Value), B := Lerp(0.0,1.0,Value*Value)})

			# we create a new slot each time because they are immutable and we need to change the anchors
			# and also they are structs so it's a pretty lightweight operation anyway
			NewSlot := canvas_slot: 
				Widget := ColorBlock
				Anchors := anchors:
					Minimum := vector2{X := Index*BarWidth + 0.0, Y := 1.0-Clamp(Value,0.0,1.0)}
					Maximum := vector2{X := Index*BarWidth + BarWidth, Y := 1.0}
				SizeToContent := false

			# we need to add a slot, but being re-added also forces the ColorBlock color to actually update
			# the first time we run this, the widget won't actually be on the canvas, but that's fine, RemoveWidget() can't fail
			Canvas.RemoveWidget(ColorBlock)
			Canvas.AddWidget(NewSlot)

FORT-1093307 has been created and its status is ‘Unconfirmed’. This is now in a queue to be reproduced and confirmed.

Just tested your code in a private version and lagging does indeed happen.

A simple fix that works for me is to use the recommended log.Print() instead of the global Print()

Code example

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

# Create a log channel, this class name will be used when printing 
hello_world_log := class(log_channel){}

hello_world_device := class(creative_device):

    # Create a log instance and set the channel to the one we just created 
    Logger:log = log{Channel := hello_world_log}

    OnBegin<override>()<suspends>:void=
        loop:
            for(X:=0..5000):
                Logger.Print("test", ?Level := log_level.Normal) 
            Sleep(0.0)

Try to update your code with this version and see if this fixes your issue.

Relevant reference page: