Help me optimize this code

Hello. I need help optimizing this code. I already changed the update method (before it was RunService.Stepped), but it’s still very laggy. Reducing the number of parts and the update time still causes lag, even with smaller intervals.

Context: It moves mesh vertices (generally around 8000 vertices per mesh, which is too many).

Code:

local treesFolder = game:GetService("CollectionService")
local maxDistance = 200
local windDirection = workspace.GlobalWind
local assetservice = game:GetService("AssetService")
local windStrength = 0.01
local leafMovementSpeed = workspace.GlobalWind.Magnitude*4

game.Workspace:GetPropertyChangedSignal("GlobalWind"):Connect(function()
	leafMovementSpeed = workspace.GlobalWind.Magnitude*4
end)

local originalPositions = {}
local vertexCache = {}


local function SimulateLeavesMovement(leavesMesh: EditableMesh)
	if not originalPositions[leavesMesh] then
		originalPositions[leavesMesh] = {}
		vertexCache[leavesMesh] = leavesMesh:GetVertices()
		for _, vertex in pairs(vertexCache[leavesMesh]) do
			local position = leavesMesh:GetPosition(vertex)
			if position then
				originalPositions[leavesMesh][vertex] = position
			end
		end
	end
	for _, vertex in pairs(vertexCache[leavesMesh]) do
		local originalPosition = originalPositions[leavesMesh][vertex]
		if originalPosition then
			local windForce = math.sin(tick() + (originalPosition.Y / 10)) * windStrength
			local displacement = windDirection.Unit * windForce * leafMovementSpeed
			leavesMesh:SetPosition(vertex, originalPosition + displacement)
		end
	end
end

local function CreateOrReuseEditableMesh(leaves: Instance)
	local n = leaves:GetAttribute("Created")
	local editable
	if n == true then
		editable = leaves:FindFirstChild("EditableMesh")
	else
		leaves:SetAttribute("Created", true)
		editable = assetservice:CreateEditableMeshFromPartAsync(leaves)
		editable.Parent = leaves
	end
	return editable
end

local over = OverlapParams.new()
over.RespectCanCollide = false
over.FilterType = Enum.RaycastFilterType.Include
over.FilterDescendantsInstances = treesFolder:GetTagged("Leave")
over.MaxParts = 10

local function UpdateTreesInRegion(player)
	over.FilterDescendantsInstances = treesFolder:GetTagged("Leave")
	local partsInRegion = workspace:GetPartBoundsInRadius(player.PrimaryPart.Position, maxDistance, over)
	for _, part in pairs(partsInRegion) do
		local editable = CreateOrReuseEditableMesh(part)
		if editable then
			SimulateLeavesMovement(editable)	
		end	
	end
end

local simulationActive = false

local function ToggleSimulation()
	simulationActive = not simulationActive
end

local player = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()
local updateInterval = 1/30

game.ReplicatedStorage.ChangeLeave.Event:Connect(function(val)
	simulationActive = val
end)

while true do
	if simulationActive then
		UpdateTreesInRegion(player)
	end
	task.wait(updateInterval)
end

I used ChatGPT to help with the movement of the vertices.

1 Like

Hey sr_animaciones.
You’re showing a 43 ms CPU frametime on there.

I could tell you where you could optimize further, but please keep in mind that your GPU is also getting a 40 ms frametime. You are getting a bottleneck from your GPU.
Optimizing the code might show very little performance gains.

You might want to use less editable-meshes all-together.
or switch to a better gpu :P

OverlapParams & TaggedInstances

Tagged instances get computationally expensive to “Get” when there are a lot of tagged instances. You may get up to 3 milliseconds of stutter on a low-end device, while asking for 8000 instances.

→ Solution: Do GetTagged("Leave") on a previous table out of the simulation cycle.
p.s: You can also probably just use :GetInstanceAddedSignal("Leave") if you are afraid of missing leaves added after the script started running.
Use with caution though, CollectionService tags sometimes cause headaches when over-used.

I also see that you are setting OverlapParams.FilterDescendantsInstances = tbl every simulation cycle. You don’t really need to update it with the same table even when there is a little difference.

→ Solution: Use OverlapParams:AddToFilter() if you see that a new leaf was added.

Computing leaf movement

I’m not sure if you could optimize the leaf movement further.

→ Solution: One thing I can recommend is adding a @native attribute to the function, which will get rid of the overhead when computing a lot of math in Luau scripts.

@native
local function SimulateLeavesMovement(leavesMesh: EditableMesh)


Let me know if you managed to implement these!
2 Likes

With this, with other chatgpt tips, changing the pairs() for a common for loop, I have managed to optimize it. New code:

local treesFolder = game:GetService("CollectionService")
local maxDistance = 200
local windDirection = workspace.GlobalWind
local assetservice = game:GetService("AssetService")
local windStrength = 0.01
local leafMovementSpeed = workspace.GlobalWind.Magnitude * 4
local simulationActive = false
local frameRate = 1/60
game.Workspace:GetPropertyChangedSignal("GlobalWind"):Connect(function()
	leafMovementSpeed = workspace.GlobalWind.Magnitude * 4
end)

local originalPositions = {}
local vertexCache = {}
local coroutines = {}

local function SimulateLeavesMovement(leavesMesh: EditableMesh, distance)

	local interval = math.max(1, math.floor(distance / 30))
	if not originalPositions[leavesMesh] then
		originalPositions[leavesMesh] = {}
		vertexCache[leavesMesh] = leavesMesh:GetVertices()

		for i = 1, #vertexCache[leavesMesh], interval do
			local vertex = vertexCache[leavesMesh][i]
			local position = leavesMesh:GetPosition(vertex)
			if position then
				originalPositions[leavesMesh][vertex] = position
			end
		end
	end
	for i = 1, #vertexCache[leavesMesh], interval do
		local vertex = vertexCache[leavesMesh][i]
		local originalPosition = originalPositions[leavesMesh][vertex]
		if originalPosition then
			local windForce = math.sin(tick() + (originalPosition.Y/10)) * windStrength
			local displacement = windDirection.Unit * windForce * leafMovementSpeed
			leavesMesh:SetPosition(vertex, originalPosition + displacement)
		end
	end
end


local function CreateOrReuseEditableMesh(leaves: Instance)
	local n = leaves:GetAttribute("Created")
	local editable
	if n == true then
		editable = leaves:FindFirstChild("EditableMesh")
	else
		leaves:SetAttribute("Created", true)
		editable = assetservice:CreateEditableMeshFromPartAsync(leaves)
		editable.Parent = leaves
	end
	return editable
end


local function UpdateTreeCoroutine(leavesPart,player:BasePart)
	local editable = CreateOrReuseEditableMesh(leavesPart)
	if not editable then return end
	local function TreeCoroutine()
		while true do
			if simulationActive then
				local distance = (leavesPart.Position - player.Position).Magnitude
				SimulateLeavesMovement(editable, distance)
			end
			task.wait(frameRate)
		end
	end

	if coroutines[leavesPart] then return end

	
	local co = coroutine.create(TreeCoroutine)
	coroutines[leavesPart] = co
	coroutine.resume(co)
end


local function StopTreeCoroutine(leavesPart)
	if coroutines[leavesPart] then
		coroutine.close(coroutines[leavesPart])
		coroutines[leavesPart] = nil
	end
end

local over = OverlapParams.new()
over.RespectCanCollide = false
over.FilterType = Enum.RaycastFilterType.Include
over.MaxParts = 30

local function UpdateTreesInRegion(player)
	
	over.FilterDescendantsInstances = treesFolder:GetTagged("Leave")
	local partsInRegion = workspace:GetPartBoundsInRadius(player.PrimaryPart.Position, maxDistance, over)

	
	for _, part in pairs(partsInRegion) do
		UpdateTreeCoroutine(part,player.PrimaryPart)
	end


	for leavesPart, co in pairs(coroutines) do
		if not table.find(partsInRegion, leavesPart) then
			StopTreeCoroutine(leavesPart)
		end
	end
end


local player = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()


game.ReplicatedStorage.ChangeLeave.Event:Connect(function(val)
	simulationActive = val
end)


local treesToUpdate = {}
while true do
	if simulationActive then
			UpdateTreesInRegion(player)
			for leavesPart, co in pairs(coroutines) do
				if not table.find(treesToUpdate, leavesPart) then
					StopTreeCoroutine(leavesPart)
				end
			end
		end
	task.wait(frameRate)
end

Even so, it lags a lot when I go to a forest.

explain to me what this is: @native attribute to the function

1 Like

What I did is move the number of vertices, with respect to distance, because each mesh had around 8200 vertices, and I plan to move 8200*20 vertices at a time. My first attempt was to move 5 vertices at a time with the for loop, but chatgpt recommended doing it by distance, the farther ones move fewer vertices.

1 Like

Tbh, you shouldn’t really use ChatGPT for help with coding, because then it can lead to further more bugs sometimes and by copying code you don’t really learn anything from it, based on my experience I honestly recommend you to use self experience thats how you gain knowledge on how to code.

2 Likes

No, I designed the code, chatgpt helps me in cases of things I don’t know. Look, it helped me optimize it by more than 100%, using “coroutines”. I gave him the code and he told me the worst optimized points, in this case, it was the “for” loop.

2 Likes

Upon trying to test the code, it keeps erroring: Fetch Asset Error: HTTP 429 (Too Many Requests)
Any fixes?

2 Likes

Well, the Roblox team recently updated the editableMesh and editableImage API, now instances are not used, but objects, and it is impossible to edit meshes or images that are not published by you or your group. In short, this script (so cute it was turning out) is obsolete.

1 Like

The thing is that the mesh is published by me.

1 Like

Well, then wait for me to correct the code and I’ll give it to you.

@native is a tag for functions that eliminate the overhead when doing math operations in luau
8 * 2, 190 / 2, 82 + 929

Also it makes your number based loops faster like:

for i = 1, 200 do

end

But it uses more memory than regular luau. You should only use this attribute when you are sure it benefit your code a lot (do benchmarks).

Though it doesn’t fit for every case, using metatables and getfenv in your code will not benefit from the added speed.

Overall, it is a good function attribute to use when your code is constantly doing a lot of math.

2 Likes

First of all, do it all on client to remove stress from server, idk if it’s possible but you should try BulkMoveTo methood, idk if it works for editable meshes tho, it’s very optimized CFrame change methood that won’t invoke any events

1 Like

Well, the editable meshes only use SetPosition(vertexId, Position), so that is impossible, now, yes, the code is local, because the Editable meshes do not have real-time replication (only local), plus I added the option to activate/deactivate. Anyway, Roblox ruined the API (strangely xd).

Have you tried techniques like only making some of those tree/leaves move when visible to the camera, and made tree’s/leaves that is far away from the player have some low quality movement or a low quality version with less movement?. Also I haven’t used EditableMesh yet but wouldn’t plain animation work better? Or simple bone manipulation?

Can you just decrease how many vertices each leaf has?

You are attempting to render a lot of triangles, see if you can make them less detailed as they are closer or maybe even all of them.

1 Like

Already, the code is very well optimized. The number of vertices to move depends on the distance. Now, the code is useless, because Roblox redesigned the API and it is garbage. So I think that for now, the best thing I can do is… try to learn other methods apart.