What's wrong with my own custom interaction system?

My interaction system uses separate remote functions. For instance, each individual object has its own remote function. In the local handler, it takes 2 or 3 seconds to show the interact bubble after sorting through the other 20 interactive objects. What are your suggestions? thoughts? plans?


My ideas:

  • Have all interactive objects use one remote function to interact from there?

Keynotes:

  • My interaction system has a rate limit to avoid massive use of remote.

A 2 to 3 second lag for 20 objects seems a bit weird. Maybe its just device lag?

I have very low latency (10-12ms). The latency shouldn’t be a problem.

Not enough information to go off of here. Network traffic is an inherent bottleneck here since you require remotes but if something is taking that long then either your server region is bad or you’re doing something wrong and it’s hard to tell if the latter is the case when there’s nothing provided for us to work off of beyond a hypothetical scenario.

Is there anything you can share with us? Code, explorer screenshots, anything. You can’t really be helped otherwise other than being told to… fix your system yourself and use available debugging tools.

Here’s where all the interactions takes place: @colbert2677

	local function checkInteraction()
		lastActive = tick()
		
		local knownfolder = interFolder
		local Core = client.Core
		
		local function check()
			local nowFocusing
			
			if interFolder and Core.RemoteReady then
				for i,v in next, interFolder:GetChildren() do
					if v:IsA"Folder" then
						local objv = v:FindFirstChildOfClass"ObjectValue"
						local rf = v:FindFirstChildOfClass"RemoteFunction"
						local objrets = objv and objv.Value	
						local obj = rf and typeof(objrets) == "Instance" and objrets or nil
						lastActive = tick()
						
						if obj and rf then
							local rets1 = {pcall(rf.InvokeServer, rf, "CanInteract")}
							lastActive = tick()
							
							if rets1[1] and rets1[2] then
								local st1 = tick()
								local rets2 = {pcall(rf.InvokeServer, rf, "GetInfo")}
								warn("Called rets2 for "..(tick()-st1))
								
								if rets2[1] and type(rets2[2]) == "table" then
									local tab = rets2[2]
									local dist = rawget(tab, "Distance")
									local enabled = rawget(tab, "Enabled")
									local interName = rawget(tab, "Name")
									local noInterrupt = rawget(tab, "NoInterruption")
									local ignoreWater = rawget(tab, "IgnoreWater")
									local interType = rawget(tab, "InteractionType")
									local interDisplayType = rawget(tab, "InteractionDisplayType")
									local Hotkey = rawget(tab, "Hotkey")
									if type(Hotkey) ~= "string" then Hotkey = nil end
									if type(interName) ~= "string" then interName = "-Unknown-" end
									
									if enabled then
										local hrp = Functions.GetHRP()
										
										if (hrp and hrp.Parent) and (obj and obj.Parent) then
											local hrp_pos = hrp.Position
											local obj_pos = obj.Position
											
											if curGuiType and curGuiType ~= interDisplayType then
												changeGui(interDisplayType, {})
											end
											
											--warn("Unhiding gui if it isn't visible")
											unHideGui()
											
											if Hotkey then
												if uis.TouchEnabled and not uis.GamepadEnabled then
													changeGui(nil, {Text = "Interact", Text2 = interName, Text3 = "Touch"})
												elseif uis.GamepadEnabled then
													changeGui(nil, {Text = "Interact", Text2 = interName, Text3 = "X"})
												elseif uis.MouseEnabled and uis.KeyboardEnabled then
													changeGui(nil, {Text = "Interact", Text2 = interName, Text3 = Hotkey})
												else
													changeGui(nil, {Text = "Interact", Text2 = interName, Text3 = "Click/Touch"})
												end
												
											end
											
											if not workspace.CurrentCamera then
												--warn("Fixed cam")
												Functions.FixCam()
											end
											
											local cam = workspace.CurrentCamera
											
											local frame = guiFrame
											
											--warn("Adjusted gui's position")
											nowFocusing = obj
											--print("Focused on "..obj:GetFullName())
											
											if curFocusing ~= obj then
												curFocusing = obj
												
												local s,r = Routine(function()
													local mainObj
													local interacting
													local B1 = quadObject
													
													local function interact()
														if not curFocusing or curFocusing ~= obj then return end
														if interacting then return end
														
														lastActive = tick()
														interacting = true
														local rets3 = {pcall(rf.InvokeServer, rf, "Interact")}
														interacting = false
														lastActive = tick()
													end
													
													service.SelfEvent(service.UserInputService.InputBegan, function(self, i, g)
														if not curFocusing or curFocusing ~= obj then self:Disconnect() return end
														
														if i.UserInputType == Enum.UserInputType.Keyboard then
															if not service.UserInputService:GetFocusedTextBox() then				
																if Hotkey and i.KeyCode == Enum.KeyCode[Hotkey] then
																	if not interacting then
																		interact()
																	end
																end
															end
														end
														
														if (i.UserInputType == Enum.UserInputType.Gamepad1) then
															if not service.UserInputService:GetFocusedTextBox() then				
																if Hotkey and i.KeyCode == Enum.KeyCode.ButtonX then
																	if not interacting then
																		interact()
																	end
																end
															end
														end
													end)
													
													service.SelfEvent(B1.MouseButton1Click, function(self)
														if interacting or curFocusing ~= obj then self:Disconnect() return end

														if not interacting then
															interact()
														end
													end)
													
													while curFocusing and (curFocusing == obj and obj.Parent) do
														local wtsp = cam:WorldToScreenPoint(obj.Position)
														if frame.Position ~= UDim2.new(0, wtsp.X, 0, wtsp.Y) then
															tween(frame, {Property = {Position = UDim2.new(0, wtsp.X, 0, wtsp.Y)}, Time = 0.4})
														end
														
														local curhrp = client.Functions.GetSelfHumanoid()
														if not curhrp or (hrp.Position-obj.Position).magnitude > dist then
															if curFocusing == obj then
																curFocusing = nil
																hideGui()
																break
															end
														end
														
														wait(0.1)
													end
												end)
												
												if not s then
													warn("INTERACTION ERROR:", r)
												end
											end
											
											break
										else
											continue
										end
									end
								end
							end
						end
					end
				end
			end
			
			if not nowFocusing then
				hideGui()
				curFocusing = nil
			end
			
			wait(.01)
			check()
		end
		
		check()
		lastActive = tick()
	end

I’m not really sure what to make of this code. There’s a lot of closures and potential vectors for memory leaks (can’t confirm - for example, the SelfEvent thing), so there’s probably a lot of reasons why your interaction system isn’t getting an ideal response time here.

Where do you notice the greatest slowdowns? Are you able to pinpoint exactly where in your code slowdowns start to occur and where the lag isn’t present?

After a day of figuring what the problem was with the interaction, the client-side will now retrieve the interaction distance and check whether the local player’s character’s HumanoidRootPart is close within its range; instead of it retrieving the interaction info and causing overuse of remote functions.