PartCache, for all your quick part-creation needs

local RandomPosModule = require(script.RandomPositionModule)
local PartCacheModule = require(script.Parent.PartCache)

local Droplet = game.ReplicatedStorage.RainDroplet
local ModuleRain = PartCacheModule.new(Droplet, 50)

while wait() do

	for i = 1,(script.Parent.Amount.Value) do

		local DropletClone = PartCacheModule.GetPart(ModuleRain)

		DropletClone.Parent = workspace
		RandomPosModule.RandomPosition(script.Parent,DropletClone)
		
		ModuleRain:ReturnPart(DropletClone)
	end
end

so now theres no errors, but its not spawning. Its just staying un-used

1 Like

You’re instantly returning to the cache afterwards. You’ll probably want something like this.

local RandomPosModule = require(script.RandomPositionModule)
local PartCacheModule = require(script.Parent.PartCache)

local Droplet = game.ReplicatedStorage.RainDroplet
local ModuleRain = PartCacheModule.new(Droplet, 50)

while true do
	local parts = {}
	for i = 1,(script.Parent.Amount.Value) do

		local DropletClone = PartCacheModule.GetPart(ModuleRain)

		DropletClone.Parent = workspace
		RandomPosModule.RandomPosition(script.Parent,DropletClone)
		
		table.insert(parts, DropletClone)
	end

	wait()

	for _, part in ipairs(parts) do
		ModuleRain:ReturnPart(part)
	end
end
5 Likes

It works, thanks! However, I want them to fall when they spawn there. When they touch something they go back to the cache. How can I do this?

1 Like

Sounds like something that would depend on how your code is all setup, but I think use of the Touched event and Anchored property will help you with that.

1 Like

When you put a trail on the part you can see it being sent down and returned. How would you fix this?

1 Like

I had the same issue with the intended use with fastcast. My solution was to disable the trail when the bullets are put away and reenable them when they spawn back in.

Edit: Here is how it goes which uses the fastcasthandler approach. I had to coroutine a yield to guarantee the bullet gets moved into position then the trail activates but turns out it’s not that necessary just there if it happens.

Fastcast fire function which enables the trail if it finds one
		local fastcastHandler = {
			fastcast = castComponent,
			velocity = bulletVelocity or 1000,
			ratePerMinute = ratePerMinuteFire or rpm, --second per bullet
		}
		function fastcastHandler:Fire(origin, direction)
			local activeCast = self.fastcast:Fire(origin, direction, self.velocity, fastCastBehavior)
			local bullet = activeCast.RayInfo.CosmeticBulletObject
			local trail = bullet:FindFirstChildWhichIsA("Trail")
			if trail then
				trail.Enabled = false
				--coroutine.wrap(function()
					--Prevents the trail glitch from occuring
					--RunService.RenderStepped:Wait()
					trail.Enabled = true
				--end)()
			end
Clean up function disabling the trail using cast terminating like a good boy
		local function cleanUpBullet(activeCast)
			local bullet = activeCast.RayInfo.CosmeticBulletObject
			--Debris:AddItem(bullet,1)--normal instance.new clone
			local trail = bullet:FindFirstChildWhichIsA("Trail")
			if trail then
				trail.Enabled = false
			end
			projectileCache:ReturnPart(bullet)
		end

		castComponent.CastTerminating:Connect(cleanUpBullet)

It can be more optimized using bullet.Trail instead of FindFirstChild but I’m lazy :warning: so thats to avoid the fact that not all my bullets have a .Trail so just be aware of that.

10 Likes

Do I need to remove any connections and remove any parented instance in the part before using returnpart() or does the module revert the part automatically?

3 Likes

The module does not dispose of any connections to the part’s events. You will need to track these yourself. Its not possible for the module to do so, unfortunately.

3 Likes

How would I be able to get old part caches made previously in the games runtime (possibly from its parent folder, etc.) I’ve been having difficulties trying to do so with the usage of fast cast with it. To specify, I don’t know if PartCacheModule.new() will always make a new one, and even if it does or doesn’t, if there’s any way to grab it and store it into a variable from a complete seperate script.

1 Like

I really can’t tell if this module is actually increasing FPS.
I ran 3 questionable tests and got mixed results

  1. I fired 1,681 projectiles at once that disappeared in 8 seconds with PartCache and without it. PartCache dropped to ~47 FPS and normally it dropped to ~44 FPS. However, cleaning up the projectiles dropped PartCache to ~57 FPS whereas normally it’s ~59 FPS.

  2. I fired 6,561 projectiles this time and I honestly couldn’t see a difference in FPS when spawning the projectiles in for both methods. However, PartCache had a very noticeable stutter when cleaning up the projectiles.

  3. I fired 121 projectiles every 0.1 seconds. PartCache was at ~59.5 FPS whereas normally it was basically 60 FPS.

I’m not sure if I’m using it wrong or something, but I’m pretty sure that PartCache suffers a lot when cleaning the parts. It does seemingly improve spawn FPS but not enough to entirely rely on it.

Maybe some other people can do proper tests instead of my “look hard at the FPS in view >> summary.”

3 Likes

I feel so lucky to have found this topic. Currently, I am working on infinite two dimensional terrain generation. One optimization that I want to add is part caching. This is something that I planned on making myself. Luckily, I found this system which is exactly what I am looking for.

1 Like

PartCache seems to be mainly made for faster creation / destruction of parts (as the part CFrame is the only property that replicates unreliably, which makes it faster), it doesn’t seem to be made to increase the fps when creating / destroying parts.

1 Like

I was just trying to say that “returning” the parts takes up more performance than calling :Destroy() on the BasePart. However, it’s undeniable that caching the part vastly improves performance over calling Instance.new() hence PartCache.

For me, however, I found that CFraming the parts far away actually took more performance than making the object parent nil. Plus, I didn’t like how PartCache only cached BaseParts and wanted more freedom with what :ReturnPart() did, so I made my own InstanceCache.

By the way, a micro-optimization would be using :PivotTo(CFrame) than basePart.CFrame = CFrame now that pivots are here

2 Likes

It might be more performant to disable CanQuery, CanCollide, CanTouch and CastShadow for cached parts. I have not tested this, but maybe this works.

1 Like

is this better than debris service?

1 Like

Short and simple: Yes.
CFrame works best.

1 Like

Sorry for the bump, but oh wow. This module is simple yet soooo effective. It made my part rain script run waaaay much efficiently. I can now have over 300 moving rain parts on screen with maximum performance!

2 Likes

Just found this bug (it will never reach the assertwarn function call if it is == 0):


I’m trying to make it 0 parts big so it will auto fill up as needed. I don’t want to start with a value which seems sort of arbitrary. Is it better to start with a super small value and let it fill up automatically or have some random large number so you never have to worry about auto fill but possibly have excess parts. I would really prefer the former.

2 Likes

I don't know what I'm doing wrong here but, Apparently parenting objects is alot faster than this module.

Here's my code:
local camera = workspace.CurrentCamera
local altitude = 78

local tweenService = game:GetService("TweenService")
local replicatedStorage = game:GetService("ReplicatedStorage")

local partCache = require(replicatedStorage:WaitForChild("Modules"):WaitForChild("PartCache"))

local F_prefabs = replicatedStorage:WaitForChild("Prefabs")
local PF_ambience = F_prefabs:WaitForChild("Ambience")

local P_rainDrop = partCache.new(PF_ambience:WaitForChild("RainDrop"), 1, camera)

local function newDrop(position)
	coroutine.wrap(function()
		wait(math.random())

		local rainDrop = P_rainDrop:GetPart()
		rainDrop.Position = position
		rainDrop.Beam.Enabled = true
		rainDrop.Ripple.WaterRipple0.Size = UDim2.new(0,0,0,0)
		rainDrop.Ripple.WaterRipple1.Size = UDim2.new(0,0,0,0)
		rainDrop.Ripple.WaterRipple1.UIStroke.Thickness = 10
		rainDrop.Ripple.WaterRipple1.UIStroke.Transparency = 0.7
		rainDrop.Ripple.WaterRipple0.Transparency = 0.5
		
		local RCP_physics = RaycastParams.new()
		RCP_physics.FilterDescendantsInstances = {camera}
		local RC_physics = workspace:Raycast(position, Vector3.new(0, -5000, 0), RCP_physics)

		if RC_physics then

			local tweenTime = ((RC_physics.Position - position).Magnitude/270)

			local animation = tweenService:Create(rainDrop, TweenInfo.new(tweenTime, Enum.EasingStyle.Linear), {Position = RC_physics.Position})

			animation:Play()
			animation.Completed:Wait()
			rainDrop.Attachment1.Splash:Emit(50)
			rainDrop.Beam.Enabled = false

			local T_waterRipple0 = tweenService:Create(rainDrop.Ripple.WaterRipple0, TweenInfo.new(0.3, Enum.EasingStyle.Linear), {
				Size = UDim2.fromOffset(150,150);
				Transparency = 1
			})
			local T_waterRipple1 = tweenService:Create(rainDrop.Ripple.WaterRipple1, TweenInfo.new(0.4, Enum.EasingStyle.Linear), {
				Size = UDim2.fromOffset(130,130);
			})
			local T_waterRipple1Stroke = tweenService:Create(rainDrop.Ripple.WaterRipple1.UIStroke, TweenInfo.new(0.4, Enum.EasingStyle.Linear), {
				Transparency = 1;
				Thickness = 0;
			})

			T_waterRipple0:Play()
			T_waterRipple1:Play()
			T_waterRipple1Stroke:Play()
			wait(1)
			P_rainDrop:ReturnPart(rainDrop)
		end
	end)()
end

while wait() do
	
	coroutine.wrap(function()
		local seed = math.random()

		for x = -30, 30, 15 do
			for z = -30, 30, 15 do

				local basePosition = Vector3.new(camera.CFrame.Position.X, altitude, camera.CFrame.Position.Z)

				local x_noise = math.noise(x/15,z/15,seed)
				local z_noise = math.noise(z/15,x/15,seed)

				local position = basePosition + Vector3.new(x, 0, z) + (Vector3.new(x_noise, 0, z_noise) * 16)

				newDrop(position)

			end
		end
	end)()
	
end
2 Likes

Im using CFrames to actually and FINALLY make Occlusion Culling a real thing. Initially i thought reparenting is the best option but i guess im wrong.

Now this is how it works, I’ll be using Camera: WorldToScreenPoint() to check if the player can see the parts/models or not. If they can they will stay at their original position, if not they will be CFramed away.

Thank you @Xan_TheDragon for letting me realize this as a solution.\

The problem is bringing the models/parts back, thought of replacing those with invisible parts.

2 Likes