Script timeout on non yielding code/no reason to timeout

I keep getting
Script timeout: exhausted allowed execution time
On the line below

BlockType = Assets.Blocks[BlockData(BlockName).Type] -- PlotService:44

This is in a for loop. I am trying to create a grid 1000x6x1000, so this is running 6000 times on start :sweat_smile: But to me, there should be no reason for timeout? It’s not doing anything that yields

BlockData

local FullBlock = require(script.FullBlock)
local Textures = require(script.Textures)
local Transparent = require(script.Transparent)
local Types = require(script.Types)

return function(blockName)
	return {
		-- Guaranteed
		Type = Types[blockName],
		Transparent = Transparent[blockName],

		-- Variable
		FullBlock = if FullBlock[blockName] then FullBlock[blockName] else nil,
		Textures = if Textures[blockName] then Textures[blockName] else nil, -- BlockData:14
	}
end

Textures

return {
	["Grass_Block"] = {
		Back = { Type = "Decal", Texture = "rbxassetid://7946108713" },
		Bottom = { Type = "Decal", Texture = "rbxassetid://7945343271" },
		Front = { Type = "Decal", Texture = "rbxassetid://7946108713" },
		Left = { Type = "Decal", Texture = "rbxassetid://7946108713" },
		Right = { Type = "Decal", Texture = "rbxassetid://7946108713" },
		Top = { Type = "Decal", Texture = "rbxassetid://7946230915" },
	},
1 Like

I don’t have a solution but isn’t 1000x6x1000 like 6000000?

Sorry yes, it is 6000000, miscalculation :sweat_smile:

That’s probably your problem right there, 6 million calculations, and I doubt you added any sort of yield inside the for loop.

I know this takes more time to execute, but it’s the only way that I (personally) know of. Maybe add a loading screen of some sort, if that’s possible?

Just add a task.wait() at the end of your for loop. Example:

for _, gridPart in ipairs(grid) do -- Whatever your conditions are
    BlockData(gridPart) --  Your code

    task.wait()
end

When I tried that, I ended up waiting 5 minutes and it just kicked me out of studio

You will need a task.wait() when you get each of your 6000000 items The code has to wait sometime, could you do 6,000,000 things without taking a break?? . But, task.wait is around .001 seconds, or more, and if my calculations are correct, it will take you an hour or more to load this. Which is what Fizzy just said. I am afraid that you can’t do this within a reasonable time, unless you just have a large block with this texture you need.

What you COULD do, although I’m not sure what you’re trying to do, is have it pre-made
aka. You’ve made the 1000x6x1000 already, have it saved, and it’s in ReplicatedStorage for quick use.
Possibly won’t help

I am not sure whether allocating a lot of memory in a short timespan would lead to this error, but looks like every iteration you’re invoking a function which returns a newly created table, which I believe could add up to a lot of memory being used up, causing studio to crash. Maybe try returning a table instead of a function returning a table so roblox caches the table and does not allocate a new one.

I tried to calculate the memory usage of one such table that is being returned.
(I believe this returns the memory usage in bytes)

local __data = {
	Type = "Grass_Block", 
	Transparent = false, 
	FullBlock = "some data", 
	Textures = {
		Back = {Type = "...", Texture = "..."}, 
		Bottom = {Type = "...", Texture = "..."}, 
		Front = {Type = "...", Texture = "..."}, 
		Left = {Type = "...", Texture = "..."}, 
		Right = {Type = "...", Texture = "..."}, 
		Top = {Type = "...", Texture = "..."}
	}
} 
print(#game:GetService("HttpService"):JSONEncode(__data)) -- output: 310 bytes

Since one such table takes 310 bytes, 1000x6x1000 should take about 1860000000 bytes or 1.86 gb of memory. I’m guessing the created tables are not garbage collected until the end of the loop, but the program crashes due to accumulation of memory before the loop could end.

Problem is, if I did BlockData as a table, each block has about 12 lines of code. If I had more properties to each BlockData, could eventually get to 20-25 lines, now imagine 1000+ block types, and you’re looking at a script being 20k+ lines long, which is not ideal for management

There are two options for how to break this up, counter-based and timer-based.

Edit: In all of the examples below, I have task.wait(0.1) calls, but you probably just need the minimum-duration task.wait(). I copy-pasted these from plugin code where I needed to yield for longer than default to allow for other things to process. All timings and intervals should be tuned to suit your application.

Counter based. This code yields for 0.1 seconds after every 1000 parts are spawned:

local counter = 0
for i = 1, 6000000 do
	-- Make a part
	counter += 1
	if counter > 1000 then
		task.wait(0.1)
		counter = 0
	end
end

or equivalently:

for i = 1, 6000000 do
	-- Make a part
	if ( i % 1000 ) == 0 then
		task.wait(0.1)
	end
end

Modulo is more expensive than incrementing a counter, but instantiating a Part is so much more expensive than either that this choice makes no real difference.

Timer based. This code yields for 0.1 seconds every 2 seconds:

local interval = 2 -- seconds
local nextBreakTime = tick() + interval
for i = 1, 6000000 do
	-- Make a part
	local now = tick()
	if now > nextBreakTime then
		task.wait(0.1)
		nextBreakTime = now + interval
	end
end

The counter method has less overhead, since it doesn’t call tick() 6000000 times, but it also doesn’t get you a predictable fixed interrupt interval, which can be important sometimes if you need to yield at a nice even framerate in order to keep user interactions from freezing (a common issue when making plugins, not so much for games).

You could split them into individual modules, require them when needed and cache them in a table. What my point was, instead of returning a function which returns a newly created table, you could just return the table which the function is supposed to return directly so it wouldn’t create new copies every time the function is called.