Why post this here?
In this series of posts, I’m going to be explaining and sharing my process of 1. going through the test process with a practical real-world example, 2. setting up an open-source module, and finally 3. creating and finishing an open-source module.
Abstract goals
- Provide proof that StreamingEnabled isn’t enabled for enormous maps
- Create a framework that allows for huge maps to exist without StreamingEnabled.
- Open-source this framework
The end-test
- Test must not use more than 3 gigabytes of memory
- Must run at 60fps minimum, 144fps optional
- Must not murder visual quality
- Must be at least 20K x 20K studs large
- Must have a fully detailed map- interior and all
Identifying the issues
By stress-testing Roblox’s systems in the first place, we can figure out what areas have the most impact, and alleviate these issues. This test will also help me define what the module needs to do- we do this because we don’t want to be solving a problem that doesn’t exist, as that’s obviously counter-productive.
I grabbed a few free-model buildings off of the toolbox, I optimized said free models and then slapped an interior onto them. My end result was 2 skyscrapers, and 2 homes- each decked with a very basic interior. Made sure that for anything not noticable, CastShadow was off, and I disabled collisions on some parts.
Great! Now we just need to script it. Just a crude, but simple test-script.
Test code
local Home = workspace:WaitForChild("Home")
local HomeLarge = workspace:WaitForChild("HomeLarge")
local Skyscraper = workspace:WaitForChild("Skyscraper")
local BiggerSkyscraper = workspace:WaitForChild("BiggerSkyscraper")
local Path = workspace:WaitForChild("Path")
for x=-20,20 do
for y=-20,20 do
local isLarge = (math.random(1,2) == 2)
local isSkyscraper = (math.random(1,4) == 4 and not isLarge)
local clone
if isLarge then
clone = HomeLarge:Clone()
elseif isSkyscraper then
if math.random(1,2) == 1 then
clone = Skyscraper:Clone()
else
clone = BiggerSkyscraper:Clone()
end
else
clone = Home:Clone()
end
local PathClone = Path:Clone()
PathClone.CFrame = CFrame.new(x*88, 0, y*88)
clone:SetPrimaryPartCFrame(CFrame.new(x*88, math.random(-2,2)*2, y*88))
clone.Parent = workspace
PathClone.Parent = workspace
end
task.wait()
end
Now, what do we want to measure? That’s a critical piece of information- we need hard numbers we can crack down on before continuing. Just “frames per second” won’t cut it, and neither will “memory usage”. We need memory categories, profile labels, milliseconds taken. Something to say “this is why you’re having issues, this is how you solve it, and this is a bundled package which solves it for you”. Luckily, we have the microprofiler:
The microprofiler showed some pretty obvious results. More parts, more materials, more time used up. We can solve this by loading parts in and out- we won’t be settling on implementation details because we’re not that far ahead in development yet. However, yet again, we got some hard targets to decrease. Granted, they might’ve been obvious, but it’s still better practice so we can measure and compare the effectiveness of our module. It’s super easy to imagine a 10% boost as a 30% boost- we want to remain objective.
However there was also the issue of Roblox using almost 3 gigs of RAM too, so we should also investigate memory usage.
Looks like a lot our memory usage is being eaten up by physics collisions. That’s a target. We can cut that down with various optimization methods later down the line- something to say “This module reduces memory usage by over 800 megabytes in this test”.
The other memory category (GraphicsParts) isn’t something our module can cut down upon, unfortunately. I believe that it has to due with actual parts- not something we want to, or can change- and how the user creates them.
As per the final screenshot- I think that can be targeted as well. Optimizing parts by turning off CastShadow based on size, for example. That’s something this hypothetical module can target, and optimize. Again, a hard number that we can optimize.
Conclusion
We’ve defined goals for the module, and we’ve defined an end-test to do later. This can be changed to be more realistic if it proves too difficult or impossible- however that should not be done lightly, as changing your goals to be lesser when they prove difficult to reach is bad.
Asides from that, we’ve successfully ran stress tests, and we have the recorded results from it- something to look back on later. We can reasonably conclude that there is an issue, it is solvable by code we can create, and we know where to hit: Physics collisions and part count.
endnote: why is the roblox corescript freecam taking 2.7ms…