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.
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)
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
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.
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.
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.
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.
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
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?
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.