Anyway to Improve Iterator when table grows?

I have an Iterator that loops through Collectionservice and runs a function to it while its being looped

local RunService = game:GetService("RunService")
local Collectionservice = game:GetService("CollectionService")
local GuiCollisionService = require(game:GetService("ReplicatedStorage").GuiCollisionService)
local event


function OnTouch(b)
	local a = b.Parent.Parent.Parent.Parent.DragableFrame
	local touching = GuiCollisionService.isColliding(a,b)
	if touching == true then
		b.UIStroke.Enabled = true	
	else
		b.UIStroke.Enabled = false	
	end
end

RunService.RenderStepped:Connect(function()
	for i, b in pairs(Collectionservice:GetTagged("regions")) do
		if b.Parent.Parent.Parent.Parent.Parent.Parent.Playback.Aplay.paused.Value == false then
			coroutine.wrap(OnTouch)(b)
			
		end
	end
end)

but then its gets ober 500 or more items in the table, its starts to lag.
is there anything i can do to improve performance?

4 Likes

Use event-based programming (“listeners”) instead of polling (“lookups”).

So replace this
RunService.RenderStepped:Connect(function()
	for i, b in pairs(Collectionservice:GetTagged("regions")) do
		if b.Parent.Parent.Parent.Parent.Parent.Parent.Playback.Aplay.paused.Value == false then
			coroutine.wrap(OnTouch)(b)
			
		end
	end
end)

with this:

for _, region in Collectionservice:GetTagged("regions") do
	local paused = not region.Parent.Parent.Parent.Parent.Parent.Parent.Playback.Aplay.paused
	local changedSignal = paused:GetPropertyChangedSignal("Value")

    if not paused.Value then
		coroutine.wrap(OnTouch)(b)
	end
	changedSignal:Connect(function()
		if not paused.Value then
			coroutine.wrap(OnTouch)(b)
		end
	end)
end

That way you’re only running if not paused.Value then coroutine.wrap(OnTouch)(b) end when paused actually changes, instead of checking 100s of things every frame. Oh, and once at the start of the game so things that start off un-paused also call OnTouch.

If you need to support regions getting added or removed during the game, you’ll have to change it to this:

local TagS = game:GetService("CollectionService")

local regionDebris = {}

function clean(obj) --Alternatively, check out the Maid or Trove libraries, they're pretty great and support lots of value types
	if typeof(obj) == "RBXScriptConnection" then
		obj:Disconnect()
	else
		error(`Cannot clean {obj}`)
	end
end

function setupRegion(region)
	local debris = {}
	regionDebris[region] = debris
	
	local paused = not region.Parent.Parent.Parent.Parent.Parent.Parent.Playback.Aplay.paused
	local changedSignal = paused:GetPropertyChangedSignal("Value")
	
	local function update()
		if not paused.Value then
			coroutine.wrap(OnTouch)(region)
		end
	end
	
	--Update immediately
	update()
	
	--Update when paused.Value changes, until region gets cleaned up
	table.insert(debris, changedSignal:Connect(update))
end

function cleanupRegion(region)
	local debris = regionDebris[region]
	assert(debris, `Cannot clean up {region} as it's not currently set up!`)
	regionDebris[region] = nil

	for _, obj in debris do
		clean(obj)
	end
end

for _, region in TagS:GetTagged("regions") do
	setupRegion(region)
end
TagS:GetInstanceAddedSignal("regions"):Connect(setupRegion)
TagS:GetInstanceRemovedSignal("regions"):Connect(cleanupRegion)
3 Likes

its not working, this is whats its supposed to happen

but with the new code, it gives me an error

1 Like

anyone thereeeeeeeeeeeeeeeeeee?

Oh, is that a Sound or something? I thought it was a BoolObject. That changes things a bit, try this version of setupRegion instead:

function setupRegion(region)
	local debris = {}
	regionDebris[region] = debris
	
	local aPlay = not region.Parent.Parent.Parent.Parent.Parent.Parent.Playback.Aplay
	local changedSignal = aPlay:GetPropertyChangedSignal("Paused")
	
	local function update()
		if not aPlay.Paused then
			coroutine.wrap(OnTouch)(region)
		end
	end
	
	--Update immediately
	update()
	
	--Update when paused.Value changes, until region gets cleaned up
	table.insert(debris, changedSignal:Connect(update))
end

it still gives me the same error. and it is a bool object

Use events instead of .RenderStepped to track changes.

Fixed code:

local RunService = game:GetService("RunService")
local Collectionservice = game:GetService("CollectionService")
local GuiCollisionService = require(game:GetService("ReplicatedStorage").GuiCollisionService)

local function OnTouch(b)
	local a = b.Parent.Parent.Parent.Parent.DragableFrame
	local touching = GuiCollisionService.isColliding(a, b)
	b.UIStroke.Enabled = touching
end

local function InitializeRegion(region)
	local boolValue = region.Parent.Parent.Parent.Parent.Parent.Parent.Playback.Aplay.paused
	
	boolValue.Changed:Connect(function(newValue)
		if not newValue then
			OnTouch(region)
		end
	end)
	
	if not boolValue.Value then
		OnTouch(region)
	end
end

Collectionservice:GetInstanceAddedSignal("regions"):Connect(InitializeRegion)

for i, region in pairs(Collectionservice:GetTagged("regions")) do
	task.spawn(InitializeRegion, region)
end

(Using a lot of coroutines or task.spawn can cause lag, so if you don’t have to wrap it in a coroutine or task.spawn, just remove it. I’m only keeping it just incase your code needs it.)

but then it only detects if the play button is clicked, after that its stops detecting. the play head doesnt acivate the note anymore until i pause then play it which only playes the ones its currenly touching befor it stops working

Tell me if this works (Lag should be minimal but if it’s still to the level of before, tell me):

local RunService = game:GetService("RunService")
local Collectionservice = game:GetService("CollectionService")
local GuiCollisionService = require(game:GetService("ReplicatedStorage").GuiCollisionService)

local refreshRate = 0.2

local function OnTouch(a, b)
	b.UIStroke.Enabled = GuiCollisionService.isColliding(a, b)
end

local function InitializeRegion(region)
	local boolValue = region.Parent.Parent.Parent.Parent.Parent.Parent.Playback.Aplay.paused
	local a = region.Parent.Parent.Parent.Parent.DragableFrame

	boolValue.Changed:Connect(function(newValue)
		while not boolValue.Value do
			OnTouch(a, region)
			
			task.wait(refreshRate)
		end
	end)

	while not boolValue.Value do
		OnTouch(a, region)

		task.wait(refreshRate)
	end
end

Collectionservice:GetInstanceAddedSignal("regions"):Connect(InitializeRegion)

for i, region in pairs(Collectionservice:GetTagged("regions")) do
	task.spawn(InitializeRegion, region)
end

its about slower than my original one. and sice the refresh rate is greater than renderstepped, it doesnt notes doesnt play on time. i changed to make it less but it made my fps drop from 60(not playing) to 34(when playing) the song runs on fps so i know then its not right when the song’s tempo is fluctuating

Yea, if you do the method that you’ve been sticking with, you will have performance issues.

Here’s what I would recommend.

  1. Add a value that tracks the position of a note based on time or whatever metric you have
  2. Add a value that tracks the position of the playhead based on whatever you used for #1
  3. Check if those two values are the same, if they are, enable the stroke.

The only reason you’re experiencing performance issues is because:

  1. Collision checks are not efficient if used in this way
  2. You’re doing the above every frame

but this needs to be constanly checked in order for it to keep up. and since there so many of them, how would i do it without lag

this is the collision code

function GuiCollisionService.isColliding(guiObject0, Character)		
	if not typeof(guiObject0) == "Instance" or not typeof(Character) == "Instance" then error("argument must be an instance") return end

	local ap1 = guiObject0.AbsolutePosition
	local as1 = guiObject0.AbsoluteSize
	local sum = ap1 + as1

	local ap2 = Character.AbsolutePosition
	local as2 = Character.TextBounds
	local sum2 = ap2 + as2
	
	local corners0 = getCorners(guiObject0)
	local corners1 = getCorners(Character)
	
	local edges = {
		{
			a = corners1.topleft,
			b = corners1.bottomleft
		},
		{
			a = corners1.topleft,
			b = corners1.topright
		},
		{
			a = corners1.bottomleft,
			b = corners1.bottomright
		},
		{
			a = corners1.topright,
			b = corners1.bottomright
		}
	}
	
	local collisions = 0
	
	for _, corner in pairs(corners0) do
		for _, edge in pairs(edges) do			
			if intersects(corner, edge) then
				collisions += 1
			end			
		end
	end
	
	if collisions%2 ~= 0 then
		return true
	end
	
	if (ap1.x < sum2.x and sum.x > ap2.x) and (ap1.y < sum2.y and sum.y > ap2.y) then
		return true
	end
	
	return false
end
1 Like

Value checks are extremely more performant than those collision checks. Also, you don’t have to constantly check. Just check when the value changes and apply the functions needed. It’s a way better solution than doing 50000 collision checks (which even 1 collision check would equate to 30+ value checks) a second.

Why don’t you group all the notes by bar, and then check the bar’s bounds instead of each individual note. And when you’ve iterated over the item, remove it from the list. This would significantly reduce iteration count.

ill try them both. thanks guys

i tried using this rendering script but it lags everytime the scrollingframe moves

local tagservice = game:GetService("CollectionService")
local stop = game.ReplicatedStorage.Remotes.StopSound
local guiInset = game:GetService("GuiService"):GetGuiInset()




local function isInScreen(b)
	local pos = b.AbsolutePosition
	return pos.X  <= b.Parent.Parent.Parent.Parent.Parent.AbsoluteSize.X and pos.X >= 0
end

script.Parent.canvas.Changed:Connect(function(CanvasPosition)
	if CanvasPosition ~= ("CanvasPosition") then return end
	for i, b in pairs(tagservice:GetTagged(script.Parent.Parent.ID.Value)) do
		coroutine.resume(coroutine.create(function()
			if isInScreen(b) then
				tagservice:AddTag(b,"regions")
				b.Visible = true
			else
				if tagservice:HasTag(b,"Selected") then return end
				tagservice:RemoveTag(b,"regions")
				b.Visible = false
			end
		end))

	end
end)

i would love to help but that b.Parent.Parent.Parent.Parent.Playback.Aplay.paused.Value got me acting bonkers

lets just say thats a value in a nested GUI

1 Like