Using the microprofiler, I found 10+ CoreScripts that are always connected to UserInputService
. Most of these connections trace back to closed menus, as well as CoreGui’s that I’ve disabled. Here’s how the CoreScripts compare to my game’s usage while panning the camera and walking:
Every time an input changes, UserInputService.InputChanged fires. This event is extremely busy considering your gamepad/mouse/touch gesture will often update every frame.
This adds up to 0.141 milliseconds on my workstation, but players using a low-end touch device could lose milliseconds of frame time while performing multi-touch gestures. Milliseconds might not seem like much, but the frame needs to take less than 16 milliseconds to achieve a smooth framerate of 60 frames per second. This can also add to already noticeable latency, even if just slightly.
Here’s a rough breakdown of which CoreScripts are the most active.
Topbar: 0.034 + 0.000
StarterScript: 0.020 + 0.007
SettingsHub: 0.005 + 0.007 + 0.003 + 0.003 + 0.002 + 0.002 + 0.004
Help: 0.018
GameSettings: 0.002 + 0.002 + 0.002 + 0.002 + 0.002 + 0.002 + 0.001 + 0.000 + 0.001 + 0.000
PromptCreator: 0.007
Backpack: 0.006
BackpackScript: 0.002 + 0.001
CoreScripts/AvatarContextMenu: 0.003
CoreScripts/MainBotChatScript2: 0.003
The source of this problem is just bad design. Some of the CoreScripts are just old, and most are designed such that they are always fully initialized with a global state. I’ve also mentioned this before:
- The CoreScripts should never collectively have more than a single passive UserInputService.InputChanged connection for use with keybindings.
- Scripts that make a connection to UserInputService that persists while the gui is closed need to be refactored!
- UserInputService connections should have a simple straightforward function body, instead of delving into expensive function calls right away.
- UserInputService connections should make use of simple optimizations, (like localizing
inputObject.UserInputType
andinputObject.Position
instead of accessing it multiple times.)
Here’s an example of what the AvatarContextMenu (from 2018) looks like:
local function functionProcessInput(inputObject, gameProcessedEvent)
trackTouchSwipeInput(inputObject)
if gameProcessedEvent then return end
if inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
inputObject.UserInputType == Enum.UserInputType.Touch then
OnUserInput(Vector2.new(inputObject.Position.X, inputObject.Position.Y), inputObject)
elseif inputObject.UserInputType == Enum.UserInputType.MouseMovement then
OnMouseMoved(Vector2.new(inputObject.Position.X, inputObject.Position.Y))
end
end
UserInputService.InputBegan:Connect(functionProcessInput)
UserInputService.InputChanged:Connect(functionProcessInput)
UserInputService.InputEnded:Connect(functionProcessInput)
UserInputService.TouchSwipe:Connect(function(swipeDir, numOfTouches, gameProcessedEvent)
if not gameProcessedEvent then return end
if not ContextMenuOpen then return end
if not hasTouchSwipeInput then return end
local offset = 0
if swipeDir == Enum.SwipeDirection.Left then
offset = 1
elseif swipeDir == Enum.SwipeDirection.Right then
offset = -1
end
if offset ~= 0 then
ContextMenuGui:OffsetPlayerEntry(offset)
SetSelectedPlayer(ContextMenuGui:GetSelectedPlayer())
end
end)
This gui has an active state even when the menu has never been opened… I work every day to create a huge performant game, and it’s just disappointing to see this in scripts that our games are forced to run
Some further details and feedback
- I didn’t include a profiler dump because it’s a design problem.
- In my experience,
.Visible
is entirely unnecessary, especially for guis that created via scripts (where instances can just be created when needed.) The backpack has 100 instances still sitting around after it’s disabled. - List/grid objects could only create in-frame (and nearly in-frame for console) objects to prevent frame-spiking when opening complex guis.
- CoreScripts should only require/run eachother lazily when they actually need to be run, not on startup.
- IMO, CoreScripts should cut back on string usage. My game almost entirely uses numbers for accessing objects, but it does depend on an elaborate custom compiler for doing inter-module simplification and turning my module tables into arrays.
- CoreScripts would benefit from a compilation process that can fold inter-module constants, and omit unused modules and module keys. This would take engineering resources, but what is learned could be applied to all games, and the maintenance/performance improvements are likely worth it.