How can I optimize this foliage generation script to take up less script activity?

I have a client-side foliage generator that produces agriculture on a terrain planet where your camera is close to, and is deleted when the camera is no longer.

Here are the results.

It works just as planned, well, sorta. There’s one problem. It takes up a ton of scripting activity. It typically sits at 30-50% activity. I’m obviously doing this wrong, and I need to optimize this entirely.

Here is a pastebin of the script.
(Note that this is a excerpt of an entire, multi-purpose script for the client. This is just the part that’s lagging it.)

Any suggestions on what I should recode, or just redo entirely? Please let me know!

1 Like

Gotta go so I’ll be quick:

I found one issue which might be causing some issues, that is using a while loop.

Instead do something like camera:GetPropertyChangedSignal("CFrame"):Connect(function() and use some sort of debounce-like variable to keep it from running multiple callbacks at once.

Makes it so it doesn’t need to do anything if the camera is still.

1 Like

Your performance hit is most certainly in your planet.getnearbynodes method. I’ll keep thinking, but you need to find a better way to find all the nearby nodes without casting a massive region3 net.

Edit: I may be incorrect about this, but a better method may be to have the server never replicate the nodes to the client, and instead have the server pass a massive table with all the nodes’ CFrames (and all the other foliage data) in it. Maybe you could index said table somehow to make locating nearby node positions less taxing.

That might be it, I’m very concerned about all the looping. I just haven’t found an efficient way to do it that doesn’t involve so much looping. Yeah, I could index the nodes in such a way, like the meridians located on the globe, and somehow call it without using loops. Not sure.

1 Like

If you disable tweening transparency, how much faster does your script become?

Also, judging from these lines in the getnearbynodes() function:

local cfr = cam.CFrame;
local l = cfr.lookVector;
local pos = cfr.p+(v3(1,1,1)*l*50);
local r = r3(pos-v3(400,400,400),pos+v3(400,400,400));

You’re not culling nodes that are behind the camera. Is this intentional?

1 Like

If he culled nodes out of the viewport, performance would be even worse. He’s scanning with a region3 as well, so a cone like search area isn’t possible. (It is but you need many more region3s yada yada)

Maybe since you’re generating planets, making a grid akin to a chess board and pre sorting nodes into them may be a good strategy. Then you’d at least have a smaller sub table to iterate through. (maybe that’s what you meant by meridians but idk my geography lol)

Here’s a top down view of what he’s doing right now.

image

(this isn’t much better when the camera is rotated at an angle)

I’m looking for a good reason why his region checks behind the camera so much.

This probably won’t help because the first check he does (if (prevcfr==nil) or (prevcfr~=cam.CFrame) then --Camera has moved) already checks if the camera has moved, and this isn’t an expensive operation.

Given how often the camera’s CFrame changes when the camera moves, this might even make it worse.

Just tried and it still sticks at around 40% unfortunately.

Yes, I push the midpoint a little bit forward of the camera’s position so most of the agriculture formed is in the camera’s view, while at the same time, leaving some generated behind the viewport in case the player decides to turn around.

Okay, it looks like you’re cloning a tree whenever a node enters the view and destroying it whenever it exits the view.

Have you considered just parenting it to nil instead whenever it exits the view, and reparenting it to workspace whenever it enters the view for the second time?

You might even use some sort of reuse queue in order to save memory. The reuse queue has two operations: enqueue and dequeue. When you dequeue, you either A: Create a new instance of the tree when the queue is empty, or B: Return a previously enqueued instance of the tree. When you enqueue, you insert it to the queue and parent it to nil.

Try pooling. Keep track of all trees ever made in a table.

Next time you need a tree check to see if there’s already a tree that’s out of sight. If so, then CFrame that one to where you need it to be. The trick is to never delete any trees and to absolutely minimize adding new trees unless there’s absolutely no way otherwise.

1 Like

Try this? This implements exactly what I was talking about:

https://pastebin.com/WeJDZ4gn (edited)

1 Like

THe most efficient thing you can do is have a 3D or 2d grid datastructute (2d/3D array) where each index corresponds to an array of nodes(nodes are now just cframes not parts) that belong to that instance and the DS is O(n)XO(n)XO(n) where n is the total number of nodes (might need to do hash table to conserve memory though)

This would get rid of constant looping over nodes to determine whether or not they are in range - keep in mind approximations would work for your scenario too so it doesn’t need to be strict

This would also speed up searching for new nodes bc u have a grid which if the constant factor in O(n) = 1, this is O(# new nodes to add) - this is best u can hope for since ur iterating through all anyways to add the trees - not O(poly lg n + # new nodes)

1 Like