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. https://www.roblox.com/library/2904807375/PartCache-Open-Source

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.

463 Likes
Part Pooling - Increase performance with many parts
Least lag method?
Is there a way to put an instance into a low-memory state?
What are you working on currently? (2019)
Client visuals?
Extreme amounts of lag when maps (>1000 parts) are parented to workspace
Making a combat game with ranged weapons? FastCast may be the module for you!
Worried about optimization of a game with lots of moving parts
Client projectiles flying faster on faster pc's
Raycasting in Shadowmap
How can I remove a model without causing lag?
:Clone() or :Instance.New()?
Best way to do bullet holes? (Placing imagelabels in surfacegui depending on where the bullet hits)
Performance considerations, improvements, and optimizations when making a game
Recycler | For your memory recycling needs
Need a guide to make vfx on projectile
What would the best way be to move gigantic amounts of bullets?
Alternative to wait()?
How do I fix this trail
What are you working on currently? (2020)
Blood module - item asylum, Combat Warriors styled blood
Enabling and disabling a part
Low performance on high brick count
Event that fires when rendering finishes
How do I delete a bunch of blocks the fastest way possible without any lag?
How would you correctly implement object pooling?
Mochi Skills - One Piece Fruit - Open-Source [UPDATED, 2 SKILLS NOW]
Looking for Tips to Optimize Client-Sided Stress
How much parts is too much? Advice needed for Obby
How to optimize this script, its currently very laggy
How to move lots of projectiles with no lag?
Asking about clone performance
OTS Gun System For SCP Game
What are some algorithms or approaches to work with thousands of parts every frame?
In-Depth Information about Roblox's RemoteEvents, Instance Replication, and Physics Replication (w/ sources!)
Rate my Upgraded Particle System
(topic deleted by author)
How do I make my game less laggy?
Moving meshes into scene laggy
Bullets lagging the game badly
Lag spikes whenever gun is shot
**High network recieved**
Help with raycasting script working weirdly
Electric bolts/Sparks
SimpleCast - An Alternative to FastCast
Which is better for performance: Debris:AddItem() vs Destroy()
How to create an 8-way burst of projectiles?
3D Particles System | WIP
Need help making bullet fly smoother
Can someone introduce me to the different ways of moving a projectile?
How to handle many bullets without lag and problem
Cloning player avatar to a worldmodel on the server every frame
The best way to optimize my cloud generation script?
What's more performant in this situation? Part.Position or Part.CFrame?
Cloning player avatar to a worldmodel on the server every frame
Better way to make Bullets?
Does using Instance.new() lag more than using the :Clone() function?
Question: Visual effects replication
Spark particles based on trails cause shadowmap shadow to flicker
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
How to insert 204.8k parts at once without roblox crashing
For loop not performing action correctly
Help with Module
Trying to smooth out Procedural Generation
Rocks Module - Useful for Anime Games Skill Effects
Removing models without LAG!
FPS Game Idea! - Name Needed and Feedback!
How can i return back in a function?
Random Tower Falling Feedback
Laser Beam Reflection (Yes there is raycast reflection stuff on the internet, but those dont help, I need a steady, updating beam.)
How to set up PartCache without FastCast?
Creating Procedural Mountains: A Fractal Noise Tutorial
3DParticle - finally, mesh particles
[Closed] Wicked Waves Feedback
Issues with sending table by remote event
Optimize game question
How can I reduce lag from this?
Help making a Danmaku/Bullet Hell pattern
How should i replicate bullets?
Pathfinding & Humanoid Grabbing

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

5 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.

14 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.

14 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!

6 Likes

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

4 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.

15 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!

24 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:

10 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.

4 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.

14 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.

3 Likes

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

2 Likes

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.

1 Like

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.

1 Like

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

5 Likes