CoreScripts don't disconnect busy UserInputService connections

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 and inputObject.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 :frowning:


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.
19 Likes

Thanks for the report! We’ve filed this internally and we’ll follow up here when we have an update for you.

2 Likes

I am in the process of checking over bug reports and following up on some bugs that haven’t received any activity in a while.
Is this issue still occurring or can you confirm that this bug has been resolved?

1 Like