PartCache, for all your quick part-creation needs

I’ve just finished designing and testing a little module I’m calling “PartCache”.

This module is used to make the illusion of quickly creating and destroying parts for stuff like laser beams and such. This module uses CFrame to its advantage to work, since creating parts with Instance.new() can get very laggy.

Here’s a stress test. I have 64 sentries here each shooting up to 10 bullets per second. It’s completely lagless!

After zeuxcg made a post on a thread talking about how updating properties and creating instances can cause lag especially in regards to render-related things, I decided to make use of what he said – CFrame is the only property you can update every frame without heavy code kicking in. By keeping these cached parts in the workspace and CFraming them to a location really far away when they aren’t in use, I’ve found that I can go so far as to triple the performance of systems like the gif shown above simply by CFraming existing parts rather than creating parts and deleting them after.

While this has a number of other performance implications (e.g. moving parts far away does remove them from their current cluster which means they need to be re-added when being moved back), it’s orders of magnitude better than creating a whole new instance only to delete it right after, so the benefits outweigh the caveats quite extremely.


You can get the module here. PartCache - Open Source - Roblox

Let me know what you think, and please don’t hesitate to post your creations using this module in the replies below!

Click here to see the API / How to use this module.

API

PCache PartCacheModule.new(BasePart template, int precreatedParts = 5)

Creates a new part cache (“PCache”) using the specified template part, creating precreatedParts parts to grab from the cache. This will error if template is nil or precreatedParts < 0.


BasePart PCache:GetPart()

Gets a part from the cache that isn’t currently in use. If there are no more parts in the cache, it will send a warning to the output and add a new part to the cache.


void PCache:ReturnPart(BasePart part)

Returns a part to the cache after you’re done using it. Automatically CFrames it to a location far away. This will throw an error if the part passed in to the function was not part of the cache’s in-use list (either by passing in a part that has already been returned to the cache or a part that was never a member of the cache in the first place).


void PCache:DestroyPart(BasePart part) [Deprecated]

Use PCache::ReturnPart instead.


void PCache:SetCacheParent(Instance parent)

Sets the parent of all cached parts to the specified instance.
Errors if the parent is not an Instance, errors if the parent is nil, errors if the cached parts will not be a descendant of Workspace.


void PCache:Dispose()

Deletes all cached parts regardless of if they are in use or available, then removes the cache object itself. Attempting to call any methods or functions of the cache will throw an error.

277 Likes
:Clone() or :Instance.New()?
Part Pooling - Increase performance with many parts
Is there a way to put an instance into a low-memory state?
Least lag method?
What are you working on currently? (2019)
Extreme amounts of lag when maps (>1000 parts) are parented to workspace
Can someone introduce me to the different ways of moving a projectile?
Client visuals?
Worried about optimization of a game with lots of moving parts
PartCache Rewritten [Store and retrieve parts quickly]
Making a combat game with ranged weapons? FastCast may be the module for you!
Alternative to wait()?
Raycasting in Shadowmap
Client projectiles flying faster on faster pc's
How can I remove a model without causing lag?
Event that fires when rendering finishes
Enabling and disabling a part
Performance considerations, improvements, and optimizations when making a game
Which is better for performance: Debris:AddItem() vs Destroy()
What are you working on currently? (2020)
Best way to do bullet holes? (Placing imagelabels in surfacegui depending on where the bullet hits)
Low performance on high brick count
What would the best way be to move gigantic amounts of bullets?
How would you correctly implement object pooling?
How do I fix this trail
Moving Lots of Projectiles without Lag for my Recreation of Diep.io game
[Closed] Wicked Waves Feedback
Performance Improvements to pathfinding module
Passing on an OOP Object to be used in a different script possible?
Grass System: looking for advice on how to lower script activity
For loop not performing action correctly
Electric bolts/Sparks
Help with Module
How can I reduce lag from this?
Removing models without LAG!
How can i return back in a function?
Laser Beam Reflection (Yes there is raycast reflection stuff on the internet, but those dont help, I need a steady, updating beam.)
Issues with sending table by remote event
Need a guide to make vfx on projectile
Creating Procedural Mountains: A Fractal Noise Tutorial
How do I make my game less laggy?
FPS Game Idea! - Name Needed and Feedback!
How do I delete a bunch of blocks the fastest way possible without any lag?
Optimize game question
The best way to optimize my cloud generation script?
Better way to make Bullets?
Spark particles based on trails cause shadowmap shadow to flicker
Recycler | For your memory recycling needs
Does using Instance.new() lag more than using the :Clone() function?
What's more performant in this situation? Part.Position or Part.CFrame?

This is pretty cool. Would it really be much slower to reparent the object though, instead of moving it far away?

2 Likes

I’ve noticed that reparenting is slower, yes. As for why, I’m not entirely sure, since moving it far away also pulls it from the render queue due to being out of range.

5 Likes

Are you going to combine this with FastCast? Seems very doable but only you can comfirm that it’s a good idea.

Anyways keep up the awesome work, we need more people like you.

9 Likes

I’ve never imaginged the use of caching parts like that. I’ll end up using your module for my gun script.

Thanks for the module!

4 Likes

Wow, that’s pretty cool. I hope I can find a use for it!

2 Likes

It’s not likely that it’ll be default behavior, but it is certainly an option for the user writing the gun script.

Update for people of the future: This was added to FC.

8 Likes

I might do that with my FastCast rewrite.

3 Likes

It would be nice if you could give us some concrete statistics (ie percentages in script performance tab) on how much more efficient this is compared to reparenting / repeated instancing.

1 Like

@zeuxcg is CFrame being the only ‘fast’ updated property still the case?

Or has there been an update in this regard?

I don’t know if you meant to say that to me, but yeah, I’ll check as soon as possible.

Edit: I’m not sure of a good way to actually benchmark it. My benchmark tests seem to indicated the opposite of what zeuxcg said, so I probably implemented it wrong.

1 Like

I hope I don’t get blasted into low Earth orbit for bumping, but your module came in really handy when I was optimizing my arrow tracers. Now instead of rendering everything on the server, I just send some data off to the clients that use this module to render the tracers and I am in love with how it performs!

My own little stress test:

I did notice your API reference doesn’t accurately reflect the module. The latest version of your module creates PartCaches via .new() but your API reference in the OP claims it’s done with :CreateNewCache()

With that aside, you saved me a boatload of work and testing. Thanks for the module!

18 Likes

Thanks for pointing this out. It’s been fixed.

I’m really glad you managed to find such a good use for this, it helps to show how handy this module can get to be! :stuck_out_tongue:

6 Likes

Read the docs – The DestroyPart method returns a part to the cache so that it can be used later. The Destroy method deletes the cache itself and deletes all of the parts that are within it so that it can no longer be used.

If you see it as a good idea, I could rename the method.
Edit: I may do that anyway actually, since I’m sure other users may have stumbled upon the same confusion.
Edit 2: The proper method is now PCache:ReturnPart – DestroyPart will still work for backwards compatibility.

2 Likes

Finally got to testing this module.

PartCache:
	ON_RAY_HIT: 0.007ms
	FIRE: 0.114ms

No PartCache:
	ON_RAY_HIT: 0.014ms 
	FIRE: 0.139ms

I was using this free model along with the microprofiler to test it out. With some optimizations, I got these mildly better results:

Optimized PartCache:
	ON_RAY_HIT: 0.006ms
	FIRE: 0.109ms

So it is faster.

8 Likes

Sorry for the bump, but I have a question as to how I would actually use this module. I’m using a custom implementation of this module for an ambient particle system, but I’m not sure how I would actually return a part to the open list.

100 parts are used every second/few seconds (depends on the algorithm) like this:

local part = Pool:GetUnusedPart()
part.CFrame = CFrame.new(pos.X, pos.Y, pos.Z)
part.ParticleEmitter.Acceleration = Vector3.new(rand:NextInteger(-5, 5), rand:NextInteger(-5, 5), rand:NextInteger(-5, 5))
part.ParticleEmitter:Emit(1)

I assume to put the part back into the open list, I would just use a coroutine:

local part = Pool:GetUnusedPart()
part.CFrame = CFrame.new(pos.X, pos.Y, pos.Z)
part.ParticleEmitter.Acceleration = Vector3.new(rand:NextInteger(-5, 5), rand:NextInteger(-5, 5), rand:NextInteger(-5, 5))
part.ParticleEmitter:Emit(1)
coroutine.wrap(function()
     wait(3)
     Pool:Return(part)
end)()

But I worry that creating a coroutine for every part could cause lag, defeating the entire purpose of such a module. Is this true? Is there some other way that people should add parts back to the unused list? Thanks for the help.

1 Like

I wonder if this module can now be further optimized with the new method :BulkMoveTo()?

Correct me if I’m wrong, but BulkMoveTo doesn’t replicate the movements to clients for optimization?
If so, then it’s not a good solution as the parts will still be visible after being moved to the Cache.

You can specify a CFrame property change (basically replicated to client) I believe but its hard to know since there’s not much API information on it but I think I am correct, as you can specify something like that within a parameter.

It depends on your implementation. In your specific case, I’d advise against using parts if your goal is to have single particles emit at random locations. A better choice might (I say might because I don’t actually know what you’re trying to do) be to parent an Attachment to terrain. CFrame the attachment, emit the particle, delay, repeat. You could have a single attachment + emitter and just randomly emit around your area. That, or better yet, just make a big part and use Roblox’s random placement (but I assume you’re avoiding that for a reason).

To answer your question, I’d advise having it all in one single function, wait included. That, or use spawn instead of coroutine.wrap as spawn ties into Roblox’s scheduler (same system as wait for the most part)

It could be done relatively easily, the issue is that there’s no real purpose for it unless you’re building close to the preset position, which would cause other far more severe problems for your game

3 Likes