PartCache, for all your quick part-creation needs

I use a similar method in caching particle effects and it works very well. :slight_smile:

For anyone who’s thinking of using PartCache, it’s excellent!

But please, make sure to change the CFrame position it comes with because Roblox has an engine bug that can cause your maps to flicker. This only happened to our team in areas where PartCache was used at a Y axis of >100, why? I have no idea.

Some severe bug within the engine is causing this all and Roblox does not seem to do well with parts that are positioned so far away.

1 Like

does PartCache:Dispose() run if the script that made PartCache.new() is destroyed?

Hello! Does this module work with StreamingEnabled? I’ve been tweaking my game to enable StreamingEnabled for performance purposes but my script using this module doesn’t seem to be working properly, i have it set so 10 bullets should be created on startup, but that doesnt seem to be working, instead bullets are only created when shot out.

local bullet = RP.Misc:WaitForChild("Bullet")

cache = PartCacheModule.new(bullet, 10)
cache:SetCacheParent(workspace:WaitForChild("Bullets"))

I’ve noticed the expansion part of the code works properly when more than 10 bullets are created, so it seems everything else is working properly other than the initial creation of parts

1 Like

Sorry for the double reply but i managed to fix the issue by making a small tweak to the PartCache script, i made it so bullets were created in a closer position to the player’s spawn under the map and then moved them back towards the storage area after a wait()

local function MakeFromTemplate(template: BasePart, currentCacheParent: Instance): BasePart
	local part: BasePart = template:Clone()
	-- ^ Ignore W000 type mismatch between Instance and BasePart. False alert.

	part.CFrame = CFrame.new(0, 0, 0) --modified from part.CFrame = CF_REALLY_FAR_AWAY
	part.Anchored = true
	part.Parent = currentCacheParent
	return part
end

function PartCacheStatic.new(template: BasePart, numPrecreatedParts: number?, currentCacheParent: Instance?): PartCache
...
for _ = 1, newNumPrecreatedParts do
		local part = MakeFromTemplate(template, object.CurrentCacheParent)
		wait()
		part.CFrame = CF_REALLY_FAR_AWAY
		table.insert(object.Open, part)
	end
...

function PartCacheStatic:GetPart(): BasePart
...
if #self.Open == 0 then
		warn("No parts available in the cache! Creating [" .. self.ExpansionSize .. "] new part instance(s) - this amount can be edited by changing the ExpansionSize property of the PartCache instance... (This cache now contains a grand total of " .. tostring(#self.Open + #self.InUse + self.ExpansionSize) .. " parts.)")
		for i = 1, self.ExpansionSize, 1 do
			local part = MakeFromTemplate(self.Template, self.CurrentCacheParent)
			wait()
			part.CFrame = CF_REALLY_FAR_AWAY
			table.insert(self.Open, part)
		end
	end
...

Not sure if this is the cleanest solution but hopefully it helps anyone having a similar issue

Have you tried putting the parts in a model with ModelStreamingMode Persistent?

Can you add in a new function named PCache:GetAvailableParts() or PCache:GetUsingParts(), or even PCache:GetPreCreatedPart()? I am trying to detect when there are no using parts, so that when the ReturnPart() function is called, i can check whether it is the last one to delete the part cache. Basically, i am using FastCast, but i encountered a problem in which the bullets completely disappear. My most recent solution is to create a new partcache with a relatively low precreated part amount, then dispose it once all bullets has been returned.

Putting this here for anyone else who gets this issue:

Sometimes when you return parts to their cache, the shadows glitch and flash off an back on. This is undesirable especially if you’re returning a bunch of parts frequently.

To resolve this just go in the module and change the huge CFrame constant to something smaller. I did 1e7

6 Likes

.CFrame do be taking performances however it is only possible to notify at around thousands of parts, which lets be real will probably not happen in a single frame often, but if you somehow manage to get conditions like this, i’m pretty sure you can use something called workspace:BulkMoveTo(partList, cframeList)

basically it allows you to update more parts cframes in a single frame way faster, here we could kinda get every moved part in the frame, then use bulkmoveto to move all of them at once, which could probably make performances better?

Is this also possible with other instances than only BaseParts like SurfaceGuis for example?

Is there a way to get an already existing partcache?

I found what I think is a bug in PartCache.

If you GetPart(), then set the CFrame and then set Anchored = false the part will fall (as expected).

But if you ReturnPart() then do the same thing again the Part will disappear.

This can be fixed by changing this line in ReturnPart()

part.Anchored = true

Change to:

part.Anchored = true
part.Velocity = Vector3.new(0, 0, 0)
part.RotVelocity = Vector3.new(0, 0, 0)

It seems just setting Anchored = true alone is not enough, otherwise the cached part retains the velocity data and when re-summoned it will be moving at the old velocity as soon as it is unanchored.

Do you know if there a way to get an already existing partcache?

for future people: its too late, i just made my own partcache plugin lol

how would i refill the cache if a cache part gets destroyed?
im using part cache for debris, but if the debris falls into the void and gets destroyed, i dont think the part cache refills itself, as the part isnt being returned. how do i refill the cache?

Thank you for sharing this useful module.

I noticed there is a very minor error in PartCacheStatic.new definition.
You use:

local newNumPrecreatedParts: number = numPrecreatedParts or 5

to deal with the possibility of getting a nil argument for numPrecreatedParts, but then two lines later you

assert(numPrecreatedParts > 0, “PrecreatedParts can not be negative!”)
assertwarn(numPrecreatedParts ~= 0, “PrecreatedParts is 0! This may have adverse effects when initially using the cache.”)

Whilst this still works, it is responsible for some of the type def warnings (using a relational operator against a nil), I changed the function to this:

function PartCacheStatic.new(template: BasePart, _numPrecreatedParts: number?, currentCacheParent: Instance?): PartCache
	local numPrecreatedParts: number = _numPrecreatedParts or 5
	local newCurrentCacheParent: Instance = currentCacheParent or workspace

	assert(numPrecreatedParts > 0, "PrecreatedParts can not be negative!")
	assertwarn(numPrecreatedParts ~= 0, "PrecreatedParts is 0! This may have adverse effects when initially using the cache.")
	assertwarn(template.Archivable, "The template's Archivable property has been set to false, which prevents it from being cloned. It will temporarily be set to true.")
	
	local oldArchivable = template.Archivable
	template.Archivable = true
	local newTemplate: BasePart = template:Clone()
	template.Archivable = oldArchivable
	template = newTemplate
	
	local object: PartCache = {
		Open = {},
		InUse = {},
		CurrentCacheParent = newCurrentCacheParent,
		Template = template,
		ExpansionSize = 10
	}
	setmetatable(object, PartCacheStatic)
	
	for _ = 1, numPrecreatedParts do
		table.insert(object.Open, MakeFromTemplate(template, object.CurrentCacheParent))
	end
	object.Template.Parent = nil
	
	return object
	-- ^ Ignore mismatch here too
end

I think the metatable type warning for the return object is unavoidable.

1 Like

Is PivotTo() faster than directly changing the CFrame? I understand that it doesn’t fire property changed events, but are we sure there isn’t some other overhead?

PivotTo() is basically .CFrame but mostly used for models or objects with a different pivot. PivotTo is just meant to replace SetPrimaryPartCFrame.

I have seen in multiple posts that they are around the same speed as they both update the geometry in the same way kinda (and from an entity systems i have used back then with 2k enemies basically around 14000~16000 parts, pivotto wasn’t any different than .CFrame).

1 thing which is more efficient however is workspace:BulkMoveTo(), which is the best in therm of performance, but you should only use it when you have to move a lot of parts in the same frame (reason why it is called BulkMoveTo), else it kinda becomes useless

i don’t really think it will be of any use for part cache however

yes late answer

So to my understanding, this module premakes parts for you, and alleviates the need to use Instance.new() or destroy for parts. But what if I want multiple hundreds of parts, each with unique properties? It seems like partcache can only contain parts with the same properties within a cache. In my use case, I want to create multiple hundreds of parts, each with different sizes, all at the same time. But from what I can understand from the API, you have to already know the properties and amount of parts you want before you even make them. Which doesn’t seem very intuitive for something that supposedly removes the need to use instance.new(). My understanding is probably entirely wrong, just going off of what I can understand of the API.

edit: Just realized you could probably change the properties of the parts as you grab them from the cache, but would that have any bearing on the performance? And what if there aren’t enough parts in the cache?

I’m dumb, this module is incredible, I just didn’t read the API properly

1 Like

aha
same thing’s happening to me
yesterday i made this post: updateRenderQueue slow - #2 by NGC4637
basically me being very confused on my game’s slowdown

after a bit of scratching my head, i’ve found out the cause, ID_Transparent showing me that it’s computing ~6100 triangles