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.Positioninstead 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,
.Visibleis 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.