I wrote this code! From looking at what you have so far, you are definitely on the right track.
There are a few reasons why you are getting worse performance than you expect:
You are testing every single frame to see if it is visible, when you can actually calculate an exact range of frames thare are visible in one step (explained in reply above)
You are looking for an info frame and then destroying / cloning for every frame, when you can instead use a pool and reparent as necessary
You are calling GetChildren every time when you can instead track your main item frames in an array manually (this also helps you be sure about indices & order which is relevant when updating).
Here is an overview of how it works at Bubble Gum Simulator:
Main grid contains a single blank frame for every item in the inventory
Pool of detail / info frames to draw from during an update.
Updating to respond to scrolling / size changes works like this:
Calculate first and last on-screen/visible frame indices using roughly the same techniques helpfully given in the reply above
For each frame in that range, grab a detail / info frame from the pool and reparent it to the frame, also setting any properties in the detail / info frame as necessary
For any remaining detail / info frames in the pool, parent those to nil.
Thank you for replying. So I was reading over your post. And I’m having a little trouble understanding the variables that you have made such as list_width, frame_height, and frame_width. Not sure what the difference is between the list_width and the frame_width is. But it sounds like to me the list_width is going to be how many frames I have in a horizontal row which is 3. Can you explain this a little further or tell me what exactly I am trying to find for those variables?
I’m not sure what frame_array is but I’m gonna assume thats a table containing all the frames with the index equal to which order we put them in the Scrolling Frame. Which I see a flaw with this, how would I update the “frame_array” since my content in my frame will be ordered by a name not by 1 … 2 … 3 … ect… efficiently. Also I have a few questions about the list_canvas_top and list_canvas_bottom. Is that the canvas position from top to bottom or the exact y coordinate on the screen if its the exact y coordinate how would I calculate?
Edit: I just realized I could use table.sort() to sort the table but I’m not sure if it will be efficient enough for this. But could you explain to me list_canvas_top and list_canvas_bottom?
I’ve actually just experimented with this. I created a virtual list of items (managed to get to about 667,000 items before it started glitching) by rendering only what’s visible.
I’ve just made it public if you’d like to have a look at the source code:
You’d have to manually sort your array of items before adding them to the list. What I made was by no means perfect or optimised, or even designed for production. It was more of a personal proof of concept.
These two articles helped me a little when making it:
If you don’t want the lag spike when the ordering changes, keep both sorted lists around and insert/remove individual items as they’re inserted/removed.
It takes 2x as much memory but that’s not a big deal.
Hm. Can you explain to me what exactly you mean keeping both sorted lists? As I recall you are indexing from a huge table of frames which means I would have to sort that “frame_array” that contains over 5000 frames using table.sort(). I want to know how to efficiently do this when clicking a button etc… not when something gets removed or added from the list. And lets say you already have 5,000 frames in this array but want to order them by name or order them by rank. Is there any efficient way to do this because anytime something is ordered differently with the layout I would have to sort it again with table.sort().
And on another note could you explain to me what you mean by list_canvas_top and list_canvas_bottom, as I understand everything else in your code except what these two actually mean.
I am totally lost on what I should be putting for canvas top and bottom.
I actually found out how to sort tables faster by dividing them individually. I just need to understand one more thing about this. Can you explain to me what list_canvas_top and list_canvas_bottom is? Also how I could get this correctly?
Hi Scripting Void I was wondering if you can give the final code for this or something. I am still confused what CanvasTop and Canvas Bottom is. Very sorry.
Alright, here is what I have come up with. I’ll just put this here for learning purposes and for people that are still struggling.
local List = {}
local NewlyVisibleFrames = {}
local PreviouslyVisible = {}
--- ... later in the script...
function update_visible_frames(AmmountInTable)
local CellPaddingY = UIGridLayout.CellPadding.Y.Offset
local TopCanvasPosition = Nest.AbsolutePosition.Y
local TopWindowPosition = ScrollingFrame.AbsolutePosition.Y
local TopOfficialSize = (TopWindowPosition-TopCanvasPosition)
local FrameYTop = math.floor((TopOfficialSize/(UIGridLayout.AbsoluteCellSize.Y+UIGridLayout.CellPadding.Y.Offset))*UIGridLayout.AbsoluteCellCount.X + .5)
local AmmountInWindow = math.floor((ScrollingFrame.AbsoluteSize.Y/(UIGridLayout.AbsoluteCellSize.Y+UIGridLayout.CellPadding.Y.Offset))*UIGridLayout.AbsoluteCellCount.X + .5)
FrameYTop = math.clamp(FrameYTop-UIGridLayout.AbsoluteCellCount.X, 0, 2e9)
local FrameYBottom = (FrameYTop + AmmountInWindow)+(UIGridLayout.AbsoluteCellCount.X*2)
if FrameYBottom > AmmountInTable then
FrameYBottom = AmmountInTable
end
PreviouslyVisible = NewlyVisibleFrames
NewlyVisibleFrames = {}
for i = FrameYTop, FrameYBottom do
if List[i] then
local Frame = List[i]
local find = table.find(PreviouslyVisible, Frame)
if find then
table.remove(PreviouslyVisible, find)
end
if not Frame:FindFirstChildOfClass("Folder") then
ReplicatedStorage.Info:Clone().Parent = Frame
end
table.insert(NewlyVisibleFrames, Frame)
end
end
for i, v in pairs(PreviouslyVisible) do
if v:FindFirstChildOfClass("Folder") then
v.Info:Destroy()
end
end
end
To get this to work properly you will need a Frame that is parented to the scrolling frame which I named Nest and then you put in the UIGridLayout.
You will always need to update the size when the absolute size changes and make sure you always update the canvas size when the scrolling frame canvas size changes with these two examples.
function UpdateNest()
Nest.Size = UDim2.new(Nest.Size.X.Scale, 0, 0, UIGridLayout.AbsoluteContentSize.Y)
end
function UpdateCanvasSize()
ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, UIGridLayout.AbsoluteContentSize.Y)
end
I’ll leave the rest up to you to figure out. If you need any help just ask.
The update_visible_frames() takes the number of items you have in the list. So it would be #list. Also make sure you have created a bunch of blank templates and inserted them into the table.
This is what I mean by creating templates and inserting them inside the Nest. Make sure you also are inserting them into the list table.
local Entrys = 5000
for i = 1, Entrys do
local clone = script.Template:Clone()
clone.Name = HttpService:GenerateGUID(false)
clone.Parent = ScrollingFrame.Nest
table.insert(List, clone)
--if i % (math.ceil(Entrys*1)) == 0 or i == 1 then
--PilesOfLists[#PilesOfLists+1] = {}
--end
--table.insert(PilesOfLists, HttpService:GenerateGUID(false))
end
local Entrys = 5000
for i = 1, Entrys do
local clone = game.ReplicatedStorage.Info:Clone()
clone.Name = HttpService:GenerateGUID(false)
clone.Parent = ScrollingFrame.Nest
table.insert(List, clone)
--if i % (math.ceil(Entrys*1)) == 0 or i == 1 then
--PilesOfLists[#PilesOfLists+1] = {}
--end
--table.insert(PilesOfLists, HttpService:GenerateGUID(false))
end
UIGridLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
UpdateNest()
UpdateCanvasSize()
update_visible_frames(#List)
end)
We are not putting the cloned info into a table at all but instead the templates. We are cloning blank templates into the nest and once the template is shown to the user it grabs the info from ReplicatedStorage and parents it inside the blank template,
This is where your info Cloning should be only and what it does is search through the table and find which blank templates are being currently shown, then it parents the info frame from replicated storage inside it. Once the info is cloned you should add all the events and stuff onto it or whatever you want to be shown.
Also here is an example of how you should be updating them.
function UpdateAll()
--UpdateScrollingFrame()
UpdateNest()
UpdateCanvasSize()
update_visible_frames(#List)
end
UpdateAll()
ScrollingFrame:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
update_visible_frames(#List)
end)
ScrollingFrame:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
UpdateAll()
--UpdateNest()
--UpdateScrollingFrame()
--update_visible_frames(#List)
--UpdateUIGridLayout()
end)
Ok so I wanted to come back to this thing. The adding of info frames and placement of everything works nicely. Problem is that its still lagging. Getting around 50 fps.
local List = {}
local ReplicatedStorage = game.ReplicatedStorage
local UIGridLayout = script.Parent.Nest.UIGridLayout
local HTTPSService = game:GetService("HttpService")
local ScrollingFrame = script.Parent
local Nest = script.Parent.Nest
local NewlyVisibleFrames = {}
local PreviouslyVisible = {}
--- ... later in the script...
function UpdateNest()
Nest.Size = UDim2.new(Nest.Size.X.Scale, 0, 0, UIGridLayout.AbsoluteContentSize.Y)
end
function UpdateCanvasSize()
ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, UIGridLayout.AbsoluteContentSize.Y)
end
function update_visible_frames(AmmountInTable)
local CellPaddingY = UIGridLayout.CellPadding.Y.Offset
local TopCanvasPosition = Nest.AbsolutePosition.Y
local TopWindowPosition = ScrollingFrame.AbsolutePosition.Y
local TopOfficialSize = (TopWindowPosition-TopCanvasPosition)
local FrameYTop = math.floor((TopOfficialSize/(UIGridLayout.AbsoluteCellSize.Y+UIGridLayout.CellPadding.Y.Offset))*UIGridLayout.AbsoluteCellCount.X + .5)
local AmmountInWindow = math.floor((ScrollingFrame.AbsoluteSize.Y/(UIGridLayout.AbsoluteCellSize.Y+UIGridLayout.CellPadding.Y.Offset))*UIGridLayout.AbsoluteCellCount.X + .5)
FrameYTop = math.clamp(FrameYTop-UIGridLayout.AbsoluteCellCount.X, 0, 2e9)
local FrameYBottom = (FrameYTop + AmmountInWindow)+(UIGridLayout.AbsoluteCellCount.X*2)
if FrameYBottom > AmmountInTable then
FrameYBottom = AmmountInTable
end
PreviouslyVisible = NewlyVisibleFrames
NewlyVisibleFrames = {}
for i = FrameYTop, FrameYBottom do
if List[i] then
local Frame = List[i]
local find = table.find(PreviouslyVisible, Frame)
if find then
table.remove(PreviouslyVisible, find)
end
if not Frame:FindFirstChildOfClass("Folder") then
ReplicatedStorage.Info:Clone().Parent = Frame
end
table.insert(NewlyVisibleFrames, Frame)
--print(Frame)
--print(NewlyVisibleFrames)
end
end
for i, v in pairs(PreviouslyVisible) do
if v:FindFirstChildOfClass("Folder") then
v.Info:Destroy()
end
end
end
local Entrys = 10000
for i = 1, Entrys do
local clone = script.Template:Clone()
clone.Name = HTTPSService:GenerateGUID(false)
clone.Parent = ScrollingFrame.Nest
table.insert(List, clone)
--if i % (math.ceil(Entrys*1)) == 0 or i == 1 then
--PilesOfLists[#PilesOfLists+1] = {}
--end
--table.insert(PilesOfLists, HttpService:GenerateGUID(false))
end
function UpdateAll()
--UpdateScrollingFrame()
UpdateNest()
UpdateCanvasSize()
update_visible_frames(#List)
end
UpdateAll()
ScrollingFrame:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
update_visible_frames(#List)
end)
ScrollingFrame:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
UpdateAll()
--UpdateNest()
--UpdateScrollingFrame()
--update_visible_frames(#List)
--UpdateUIGridLayout()
end)
How many entry’s did you add? If you really did add 10,000 then of course you will be getting fps drops. Like the max I would stay with is around 4 - 5k