Client loop wont work

-- Place this script in ServerScriptService
local CollectionService = game:GetService("CollectionService")

local parts = {}

-- Bright color palette
local brightColors = {
	Color3.fromRGB(255, 0, 0),      -- Bright Red
	Color3.fromRGB(255, 128, 0),    -- Bright Orange
	Color3.fromRGB(255, 255, 0),    -- Bright Yellow
	Color3.fromRGB(0, 255, 0),      -- Bright Green
	Color3.fromRGB(0, 255, 255),    -- Bright Cyan
	Color3.fromRGB(0, 128, 255),    -- Bright Blue
	Color3.fromRGB(128, 0, 255),    -- Bright Purple
	Color3.fromRGB(255, 0, 255),    -- Bright Magenta
	Color3.fromRGB(255, 192, 203),  -- Bright Pink
	Color3.fromRGB(255, 255, 255)   -- White
}

-- Function to get random bright color
local function getRandomColor()
	return brightColors[math.random(1, #brightColors)]
end

-- Function to setup a model
local function setupModel(model)
	for _, descendant in pairs(model:GetChildren()) do
		if descendant.Name == "Ball" and descendant:IsA("BasePart") then
			table.insert(parts, descendant)
			
			descendant.Massless = true
			descendant.CanCollide = false
			descendant.Anchored = model.Parent.Name == "Lights" 
			
			-- Check if PointLight already exists, if not create one
			local pointLight = descendant:FindFirstChildOfClass("PointLight")
			if not pointLight then
				pointLight = Instance.new("PointLight")
				pointLight.Parent = descendant
				pointLight.Brightness = 1.2
				pointLight.Range = 4
			end

			-- Set initial random color
			local color = getRandomColor()
			descendant.Color = color
			pointLight.Color = color
			
		elseif descendant.Name == "Part" and descendant:IsA("BasePart") then
			model.PrimaryPart = descendant
		end
	end
end

-- Setup all existing tagged models
for _, model in pairs(CollectionService:GetTagged("LightModel")) do
	setupModel(model)
end

-- Setup any models tagged in the future
CollectionService:GetInstanceAddedSignal("LightModel"):Connect(function(model)
	setupModel(model)
end)

-- Change colors every 1 second
task.spawn(function()
	while task.wait(1) do
		print("Changed")
		for _, part in pairs(parts) do
			if part then  -- Check if part still exists
				local color = getRandomColor()
				part.Color = color
				local pointLight = part:FindFirstChildOfClass("PointLight")
				if pointLight then
					pointLight.Color = color
				end
			end
		end
	end
end)

this code changes the color of around 2000 parts. soo doing this on the server makes the recieve die. but when transferring this to a local script in start player. the code just decides to break.

why is this? what am I doing wrong?

I think it might just be with collection service, try just setting up the part without using collection service. If you need to reference new parts just use .DecendentAdded:Connect(function(decendent)

I can’t use collection service in the client? May I say collection service does work in client or does it. But it should atleast. And collection service is upper necessary cat remove it

I tried it and it worked fine, maybe your not tagging the Models Right, I just had this serverscript that tagged it and it worked.

local folder = game.Workspace:WaitForChild("Folder")

for _, part in ipairs(folder:GetChildren()) do
	if part:IsA("Model") then
		game:GetService("CollectionService"):AddTag(part, "LightModel")
	end
end

Yes you can use collection service on the client. I don’t really know what could be going wrong in your script, could you describe what happens? What do you mean by the code deciding to break, does it just do absolutely nothing, or does it throw an error in the output?

A good way to figure out issues like these is to put print()s in different places in the code, to see what happens, or why something doesn’t happen. As a first step, you can put a print inside of the collection service :GetTagged("LightModel") for loop, and the :GetInstanceAddedSignal("LightModel") connected function, check if you get the prints in the output, and then go up from there into the setupModel(model) function, to see where things don’t do what is expected

1 Like

It’s a local script

Well visually wise sometimes the loop runs, But effects a few tagged parts. This outcome doesn’t occur when used by the server. Or it doesn’t rn the loop at all. I have no clue why

I would suggest, as tests, to disable workspace.StreamingEnabled, put some prints in the loop (perhaps printing the parts table, and also, ensuring that you don’t have a server script still running, that updates the properties of those same parts, as that could overwrite what your local script does

1 Like

I have around 2000 of those being updated is sending emote events a good idea for every second?

I would suggests you keep on with the goal of doing it on the client, because the client can perform those actions itself without any real downsides. Things that are for animation purposes can usually all be done from the client

While maybe that amount of network usage might be fine (don’t really know, it is a decent amount of data), I would say it is needless, because it can be done on the client, without much more effort (unless you get a nasty bug lol. Then you play detective)

It works fine with only local scripts, all you need to do is tag correctly. Your script works perfectly fine, just how you are tagging the parts does not

This is just an example local script with your script also as a local script

local folder = game.Workspace:WaitForChild("Folder")

for _, model in ipairs(folder:GetChildren()) do
	if model:IsA("Model") then
		game:GetService("CollectionService"):AddTag(model, "LightModel")
	end
end

Fine ..

All client
-- StarterPlayerScripts (LocalScript)
local CollectionService = game:GetService("CollectionService")
local parts = {}

local brightColors = {
	Color3.fromRGB(255, 0, 0),
	Color3.fromRGB(255, 128, 0),
	Color3.fromRGB(255, 255, 0),
	Color3.fromRGB(0, 255, 0),
	Color3.fromRGB(0, 255, 255),
	Color3.fromRGB(0, 128, 255),
	Color3.fromRGB(128, 0, 255),
	Color3.fromRGB(255, 0, 255),
	Color3.fromRGB(255, 192, 203),
	Color3.fromRGB(255, 255, 255)
}

local function getRandomColor()
	return brightColors[math.random(1, #brightColors)]
end

local function setupModel(model)
	for _, descendant in pairs(model:GetChildren()) do
		if descendant:IsA("BasePart") and descendant.Name == "Ball" then
			table.insert(parts, descendant)
			descendant.Massless = true
			descendant.CanCollide = false
			descendant.Anchored = false
			local pointLight = descendant:FindFirstChildOfClass("PointLight") or Instance.new("PointLight", descendant)
			pointLight.Brightness = 1.2
			pointLight.Range = 4
			local color = getRandomColor()
			descendant.Color = color
			pointLight.Color = color
		elseif descendant:IsA("BasePart") and descendant.Name == "Part" then
			model.PrimaryPart = descendant
		end
	end
end

for _, model in pairs(CollectionService:GetTagged("LightModel")) do
	setupModel(model)
end
CollectionService:GetInstanceAddedSignal("LightModel"):Connect(setupModel)

task.spawn(function()
	local index = 1
	local batchSize = 50
	while true do
		task.wait(0.03)
		for i = 1, batchSize do
			local part = parts[index]
			if part then
				local color = getRandomColor()
				part.Color = color
				local pointLight = part:FindFirstChildOfClass("PointLight")
				if pointLight then
					pointLight.Color = color
				end
			end
			index = index + 1
			if index > #parts then
				index = 1
			end
		end
	end
end)

Ya, I read that.. it’s doing chucks. I’m looking for performance and security..
I know all client is easier, however it’s also totally hackable.

Why do you need another thread? :skull:
Also its a very tight loop, you should optimize it well and no have any checks inside.

Also im pretty sure that the reason is your script simply does not run at all since you parented it inside unreachable area/area where engine does not initiate local scripts.

Parts are not in Workspace. LocalScripts only see replicated parts.

Nope; They are guaranteed to be replicated without streaming enabled if they are a parent of a model that has :WaitForChild() called

Essentially work like Persistent with streaming enabled (could be a model with Persistent if its streaming enabled and behave the same)

Ok sounds like you understand all that.. it’s just the 2,000 parts thing. Are you sure you’re sure…
Pulling 2,000 parts, replication delays, PrimaryPart access, and Light behavior.. are going to hit hard.
This is why I went all commando on it with my first post.

Need your test results not speculation now. You got a few ways to do it.. start testing.. :face_with_crossed_out_eyes:

The code in question doesn’t benefit from being server sided on a security point of view. The script only changes colors and lights, if it isn’t done by the client, it is still being done by the client when the client receives the replication data from the server (or from a remote event).
A hacker also can’t take advantage of visuals to do anything interesting

From optimizing code using the micro profiler, the thread and whatnot doesn’t matter. The bottleneck will very likely be property changes to PointLight.Color. Using the rawRBXmemberSet() function from the post you linked might be a good idea, but I would strongly discourage a beginner look into that

2 Likes

Why do you have to be so good at this stuff? That is why I came back with an all-client version.

1 Like

Excellent — this is one of those Roblox optimization traps that a lot of devs hit the first time they do something large-scale on the client vs server.

Let’s break down why your local version “breaks”, and how to fix it cleanly :backhand_index_pointing_down:


:puzzle_piece: What’s happening

1. On the server

Your script runs fine (because it’s in ServerScriptService), but:

  • The server replicates every .Color and .PointLight.Color change for ~2000 parts to every client.
  • That’s thousands of property updates per second.
  • Roblox network replication chokes → “receive dies”.
    That’s normal.

2. On the client (LocalScript)

When you move it to StarterPlayerScripts, it “breaks” because:

:cross_mark: LocalScripts don’t automatically see server parts

LocalScripts only run on the client and can’t see server-only objects unless they’re replicated to the client (in Workspace, ReplicatedStorage, etc.).
If your “LightModel” objects live in ServerStorage or are spawned by the server, the LocalScript can’t find them.

So your line:

for _, model in pairs(CollectionService:GetTagged("LightModel")) do

returns nothing (empty table), because on the client, those tagged objects don’t exist or aren’t replicated yet.


Keep it client-side (best performance)**

If these lights are visual only and don’t need to be networked:

:light_bulb: Steps

  1. Move all your “LightModel” instances into ReplicatedStorage or Workspace so the client can see them.
  2. Keep your script in StarterPlayerScripts or StarterCharacterScripts.
  3. Make sure the tag LightModel is applied before the player joins, or add a small delay before scanning tags.

Example fix:

task.wait(2) -- give workspace time to load
for _, model in pairs(CollectionService:GetTagged("LightModel")) do
    setupModel(model)
end

Also, ensure the client can see CollectionService tags — they replicate automatically for instances that exist on the client.

:white_check_mark: This will avoid server replication entirely — each player will locally color the lights independently.