Suspicions for a memory leak in a Ship script

What do you want to achieve? Keep it simple and clear!
So basically while testing the game in both Studio and Roblox Player, I noticed that while driving a ship the memory jumps gradually. And so I suspect that there might be a memory leak inside of the script which I can’t seem to find and fix. I would be grateful if anybody could find where the leak might be coming from and offer a solution or alternative method of the script.

math.randomseed(tick())

local Players = game:GetService('Players')
local StarterPack = game:GetService('StarterPack')
local StarterGui = game:GetService('StarterGui')
local Lighting = game:GetService('Lighting')
local Debris = game:GetService('Debris')
local Teams = game:GetService('Teams')
local BadgeService = game:GetService('BadgeService')
local InsertService = game:GetService('InsertService')
local Terrain = workspace.Terrain
local VerifyArg

local function IsA(value, data_type)
	data_type = VerifyArg(data_type, 'string', "data_type")
	if type(value) == data_type then return true end -- Lua types
	if pcall(function() assert(game.IsA(value, data_type)) end) then return true end -- Instance types
	if pcall(function() assert(Enum[data_type]) end) then -- Enum types
		for _, enum in next, Enum[data_type]:GetEnumItems() do
			if value == enum then
				return true
			end
		end
	elseif pcall(Enum.Material.GetEnumItems, value) then
		for _, enum in next, value:GetEnumItems() do
			if value == enum then
				return true
			end
		end
	end
	if data_type == 'Color3' and pcall(function() Instance.new('Color3Value').Value = value end) then return true -- Color3
	elseif data_type == 'BrickColor' and pcall(function() Instance.new('BrickColorValue').Value = value end) then return true -- BrickColor
	elseif data_type == 'Vector2' and pcall(function() return Vector2.new() + value end) then return true -- Vector2
	elseif data_type == 'Vector3' and pcall(function() Instance.new('Vector3Value').Value = value end) then return true -- Vector3
	elseif (data_type == 'CFrame' or data_type == 'CoordinateFrame') and pcall(function() Instance.new('CFrameValue').Value = value end) then return true -- CFrame
	elseif data_type == 'UDim' and pcall(function() return UDim.new() + value end) then return true -- UDim
	elseif data_type == 'UDim2' and pcall(function() Instance.new('Frame').Position = value end) then return true -- UDim2
	elseif data_type == 'Ray' and pcall(function() Ray.new(Vector3.new(), Vector3.new()).Distance(value, Vector3.new()) end) then return true -- Ray
	elseif data_type == 'Axes' and pcall(function() Instance.new('ArcHandles').Axes = value end) then return true -- Axes
	elseif data_type == 'Faces' and pcall(function() Instance.new('Handles').Faces = value end) then return true -- Faces
	elseif data_type == 'Enum' and pcall(Enum.Material.GetEnumItems, value) then return true -- Enum
	elseif data_type == 'RBXScriptSignal' then
		local _, connection = pcall(function() return game.AllowedGearTypeChanged.connect(value) end)
		if _ and connection then
			connection:disconnect()
			return true
		end
	end
	return false
end

local function CppIsA(value, data_type)
	data_type = VerifyArg(data_type, 'string', "data_type")
	if data_type == 'int' then
		if pcall(function() Instance.new('IntValue').Value = value end) then
			return true
		end
	elseif data_type == 'double' then
		if pcall(function() Instance.new('NumberValue').Value = value end) then
			return true
		end
	elseif data_type == 'bool' then
		if pcall(function() Instance.new('BoolValue').Value = value end) then
			return true
		end
	elseif data_type == 'string' then
		if pcall(function() Instance.new('StringValue').Value = value end) then
			return true
		end
	elseif data_type == 'float' then
		if pcall(function() Instance.new('ClickDetector').MaxActivationDistance = value end) then
			return true
		end
	end
	return false
end

local function GetType(value)
	if IsA(value, 'Instance') then return value.ClassName
	elseif IsA(value, 'Enum') then return 'Enum'
	elseif IsA(value, 'Color3') then return 'Color3'
	elseif IsA(value, 'BrickColor') then return 'BrickColor'
	elseif IsA(value, 'Vector2') then return 'Vector2'
	elseif IsA(value, 'Vector3') then return 'Vector3'
	elseif IsA(value, 'CFrame') then return 'CFrame'
	elseif IsA(value, 'UDim') then return 'UDim'
	elseif IsA(value, 'UDim2') then return 'UDim2'
	elseif IsA(value, 'Ray') then return 'Ray'
	elseif IsA(value, 'Axes') then return 'Axes'
	elseif IsA(value, 'Faces') then return 'Faces'
	elseif IsA(value, 'RBXScriptSignal') then return 'RBXScriptSignal'
	else return type(value)
	end
end

function VerifyArg(value, data_type, arg_name, optional)
	if type(data_type) ~= 'string' then error("bad 'data_type' argument (string expected, got " .. GetType(data_type) .. ")", 2) end
	if type(arg_name) ~= 'string' then error("bad 'arg_name' argument (string expected, got " .. GetType(arg_name) .. ")") end

	if optional and value == nil then
		return value
	elseif type(value) == data_type then
		return value
	elseif IsA(value, data_type) or CppIsA(value, data_type) then
		return value
	elseif data_type == 'number' and tonumber(value) then
		return tonumber(value)
	elseif data_type == 'string' and type(value) == 'number' then
		return tostring(value)
	else
		error("bad '" .. arg_name .. "'" .. (optional and " optional" or "") .. " argument (" .. data_type .. " expected, got " .. GetType(value) .. ")", 3)
	end
end

local function Modify(instance, t)
	instance = VerifyArg(instance, 'Instance', "instance")
	t = VerifyArg(t, 'table', "t")
	for key, value in next, t do
		if type(key) == 'number' then
			value.Parent = instance 
		else
			instance[key] = value
		end
	end
	return instance
end

local function WaitForChild(Parent, Name)
	Parent = VerifyArg(Parent, 'Instance', "Parent")
	Name = VerifyArg(Name, 'string', "Name")
	local Item = Parent:FindFirstChild(Name)
	if not Item then
		repeat wait(0) print("Waiting for "..Name) Item = Parent:FindFirstChild(Name) until Item
	end
	return Item;
end

local function CallOnChildren(instance, func)
	instance = VerifyArg(instance, 'Instance', "instance")
	func = VerifyArg(func, 'function', "func")
	func(instance)
	for _, child in next, instance:GetChildren() do
		CallOnChildren(child, func)
	end
end

local function GetNearestParent(instance, class_name)
	instance = VerifyArg(instance, 'Instance', "instance")
	class_name = VerifyArg(class_name, 'string', "class_name")
	local ancestor = instance
	repeat
		ancestor = ancestor.Parent
		if ancestor == nil then
			return nil
		end
	until ancestor:IsA(class_name)
	return ancestor
end

local function GetCharacter(descendant)
	descendant = VerifyArg(descendant, 'Instance', "descendant")
	local character = descendant
	repeat
		if character.Parent then
			character = character.Parent
		else
			return nil
		end
	until Players:GetPlayerFromCharacter(character)
	return character, Players:GetPlayerFromCharacter(character)
end


-- Variables --

local ParentNamesNoDetect = {Water = true; Bodykit = true; Projectiles = true; Water = true; ["!Water"] = true; Wake = true;}
local ChildrenParentNoDetect = {"Humanoid"}
local NamesNoDetect = {Wake = true;}
local WaterLevel = 27
local ShipGui = WaitForChild(script, "ShipGui")

local function RoundNumber(Number, Divider)
	Divider = Divider or 1
	return (math.floor((Number/Divider)+0.5)*Divider)
end

local function ClampNumber(Number, High, Low)
	if Number > High then 
		return High;
	elseif Number < Low then
		return Low;
	end
	return Number;
end

local function MPStoKPS(MPS) -- Meters per a econd to Kilometers per a second
	return MPS * 3.6
end


local function SetupBoat(Boat, VehicleSeat, Configuration)
	--local Boat = script.Parent
	--local VehicleSeat = WaitForChild(Boat, "VehicleSeat")
	--local Base = WaitForChild(Boat, "Base")
	local EngineSound         = WaitForChild(VehicleSeat, "EngineSound")
	local BodyGyro            = WaitForChild(VehicleSeat, "BodyGyro")
	local BodyVelocity        = WaitForChild(VehicleSeat, "BodyVelocity")
	local BodyPosition        = WaitForChild(VehicleSeat, "BodyPosition")
	local BodyAngularVelocity = WaitForChild(VehicleSeat, "BodyAngularVelocity")
	local Wake                = Modify(Instance.new("Model", Boat), {Name="Wake"})
	local CurrentHeight       = VehicleSeat.Position.Y

	local CannonReloadTime    = 7
	local CannonVelocity      = 300   
	local CannonLifeTime      = 60    
	local maxfiretime         = 1     
	local lestfiretime        = 0.1   
	local multiplier          = 100   

	local function GetCharacter(descendant)
		local character = descendant
		repeat
			if character.Parent then
				character = character.Parent
			else
				return nil
			end
		until Players:GetPlayerFromCharacter(character)
		return character, Players:GetPlayerFromCharacter(character)
	end


	local function SetCannonRow(TextButton, RowTag, Ship)
		local debounce = false
		local FireableRows = {}
		local Smoke 			= Instance.new("Smoke")
		Smoke.Name 				= "CannonSmoke"
		Smoke.Color 			= Color3.new(1,1,1)
		Smoke.Size 				= 3
		Smoke.Enabled 			= true
		Smoke.RiseVelocity 	= 6
		Smoke.Opacity 			= .25

		local Ball 				= Instance.new("Part")
		Ball.Shape 				= "Ball"
		Ball.Anchored 			= false
		Ball.CanCollide 		= false
		Ball.Locked 			= true
		Ball.FormFactor 		= "Custom"
		Ball.TopSurface 		= "Smooth"
		Ball.BottomSurface 	= "Smooth"
		Ball.BrickColor 		= BrickColor.new("Dark stone grey")
		Ball.Material 			= "CorrodedMetal"
		Ball.Size 				= Vector3.new(1.5, 1.5, 1.5)
		Ball.Name 				= "CannonBall"

		local function FireOffRow(row)
			if row:isA("Model") then 
				for a, b in pairs(row:getChildren()) do
					if b:isA("Model") and b.Name == "Cannon" and b:findFirstChild("Fire") and b:findFirstChild("SmokeHold") then
						delay(math.random((lestfiretime*multiplier),(maxfiretime*multiplier))/multiplier, function()	
							local Smoke2 		= Smoke:clone()
							Smoke2.Parent 		= b.SmokeHold
							Debris:AddItem(Smoke2, SmokeExistanceTime)
							local Ball2 		= Ball:clone()
							Ball2.Parent 		= workspace
							Debris:AddItem(Ball2, BallLifeTime)
							Ball2.CFrame 		= b.Fire.CFrame * CFrame.new(0, 0,4)
							Ball2.Velocity 		= b.Fire.CFrame.lookVector*(CannonVelocity*-1)

							Ball2.Touched:connect(function(p)
								if Ball2 and p and p.Parent then
									local Character, Player = GetCharacter(p)

									if Character and Character:FindFirstChild("Humanoid") and Character.Humanoid:IsA("Humanoid") then
										Character.Humanoid.Health = Character.Humanoid.Health - 100;
									end

									local Explosion 				= Instance.new("Explosion",workspace)
									Explosion.Name 				= "CannonExplosion"
									Explosion.BlastRadius		= 7
									Explosion.Position			= Ball2.Position

									Ball2:Destroy()
								end
							end)
							print("Cannon fired!")
						end)
					end
				end
			end
		end

		for a, b in pairs(Ship:GetChildren()) do
			if b:IsA("Model") and b:FindFirstChild("Is_A_Cannon") and b:FindFirstChild("Is_A_Cannon").Value == RowTag then
				table.insert(FireableRows, b)
			end
		end

		TextButton.MouseButton1Down:connect(function()
			if not debounce then
				debounce = true
				TextButton.Selected = true
				TextButton.TextColor3 = Color3.new(1, 0, 0)
				for a, b in pairs(FireableRows) do
					FireOffRow(b)
				end
				local WaitTime = CannonReloadTime / 0.05
				for i=0, 1, (1/WaitTime) do
					wait(0.05)
					TextButton.TextColor3 = Color3.new(1,i,i)
				end
				TextButton.TextColor3 = Color3.new(1,1,1)
				TextButton.Selected = false
				debounce = false
			end
		end)
	end


	local Config = Configuration
	Config.NeutralColor = BrickColor.new("Dark stone grey");


	local function SetupTouch(Part)
		for _, Item in pairs(Part:GetChildren()) do
			if Item:IsA("BasePart") then
				local Name = string.lower(Item.Name)
				if Name == "hitdetect" or Name == "hitdetectrecolor" then
					Item.Touched:connect(function(NewPart)
						print("Touch")
						if NewPart and NewPart.Parent and NewPart:IsA("BasePart") and NewPart.CanCollide then
							if not (ParentNamesNoDetect[NewPart.Parent.Name] or NamesNoDetect[NewPart.Name]) then
								VehicleSeat.MaxSpeed = 0
								print("Stop")
							else
								local Valid = true
								for _, ChildName in pairs(ChildrenParentNoDetect) do
									if NewPart.Parent:FindFirstChild(ChildName) then
										Valid = false
									end
								end
								if Valid then
									print("Stop")
									VehicleSeat.MaxSpeed = 0
								end
							end
						end 
					end)
				end
			end
			SetupTouch(Item)
		end
	end

	local function Recolor(Part, Color)
		--EnemyValue.Value = Color;
		for _, Item in pairs(Part:GetChildren()) do
			if Item:IsA("BasePart") then
				local Name = string.lower(Item.Name)
				if Name == "recolor" or Name == "hitdetectrecolor" then
					Item.BrickColor = Color
				end
			end
			Recolor(Item, Color)
		end
	end

	Recolor(Boat, Config.NeutralColor)
	SetupTouch(Boat)

	BodyPosition.position = Vector3.new(0, CurrentHeight, 0)


	VehicleSeat.ChildAdded:connect(function(Child)
		spawn(function()
			if Child:IsA("Weld") then
				local Torso = Child.Part1
				if Torso and Torso:IsA("BasePart") then
					local Character, Player = GetCharacter(Torso)
					if Character and Player then
						for _, Item in pairs(Character:GetChildren()) do
							if Item:IsA("Model") and Item ~= Boat then
								--Item:Destroy(); -- Remove all other boats.  
							end
						end

						Boat.Parent = game.Workspace
						VehicleSeat.Anchored = false

						Recolor(Boat, Player.TeamColor)

						repeat wait(0) until Player:FindFirstChild("PlayerGui")
						local Gui = ShipGui:Clone()
						local MainFrame = WaitForChild(Gui, "MainFrame")
						local ShipName = WaitForChild(MainFrame, "ShipName")
						local ShipSpeed = WaitForChild(MainFrame, "ShipSpeed")

						local FireCannon = WaitForChild(MainFrame, "Fire")

						--SetCannonRow(FireCannon, "ShipCannon", Boat.BodyKit)

						ShipName.Text = Config.ShipName
						ShipSpeed.Text = "Shipspeed: 0.000 m/ps";

						local function UpdateHeight()
							print("Updating ship height to "..CurrentHeight)
							BodyPosition.position = Vector3.new(0, CurrentHeight, 0)
						end

						spawn(function()
							while Gui and Gui.Parent do
								wait(0.07)
								ShipSpeed.Text = "Shipspeed: "..RoundNumber(MPStoKPS(VehicleSeat.Velocity.magnitude/20), 0.001).." KPH"
							end
							--for i,v in pairs(script.Parent.Parent:GetDescendants()) do
								--if v.ClassName == "ParticleEmitter" then
									--v.Enabled = false
								--end
							--end
							Player:WaitForChild("canDash").Value = true
							Player:WaitForChild("canRun").Value = true
							--print("Connection disconnected")
						end)

						Player:WaitForChild("canDash").Value = false
						Player:WaitForChild("canRun").Value = false
						Gui.Parent = Player.PlayerGui

						Child.AncestryChanged:connect(function(Child, Parent)
							if not Parent then
								if Gui then
									Gui:Destroy()
								end
								Recolor(Boat, Config.NeutralColor)

								VehicleSeat.TurnSpeed = 0
								VehicleSeat.MaxSpeed = 0
							end
						end)
					end
				end
			end
		end)
	end)

	local RotationX, RotationY, RotationZ = VehicleSeat.CFrame:toEulerAnglesXYZ()

	BodyGyro.cframe = CFrame.Angles(0, RotationY, 0)
	Boat:MakeJoints()

	while true do
		local VehicleSeatThrottle = VehicleSeat.Throttle
		local VehicleSeatMaxSpeed = VehicleSeat.MaxSpeed
		local VehicleSeatSteer = VehicleSeat.Steer

		if VehicleSeatThrottle == 1 then
			if VehicleSeatMaxSpeed < Config.TopSpeed + 0.5 then
				--for i,v in pairs(script.Parent.Parent:GetDescendants()) do
				--	if v.ClassName == "ParticleEmitter" then
						--v.Enabled = true
					--end
				--end
				VehicleSeat.MaxSpeed = VehicleSeatMaxSpeed + VehicleSeat.Torque
			end
		elseif VehicleSeatThrottle == -1  then
			if VehicleSeatMaxSpeed > -Config.TopSpeedReverse - 0.5 then
				--for i,v in pairs(script.Parent.Parent:GetDescendants()) do
					--if v.ClassName == "ParticleEmitter" then
						--if v.Enabled == true then
							--v.Enabled = false
						--end
					--end
				--end
				VehicleSeat.MaxSpeed = VehicleSeatMaxSpeed - VehicleSeat.Torque
			end
		end

		if VehicleSeatSteer == 1 then
			if VehicleSeat.TurnSpeed > -Config.MaxTurn then
				VehicleSeat.TurnSpeed = VehicleSeat.TurnSpeed - Config.TurnSpeedAddition
			end
		elseif VehicleSeatSteer == -1 then
			if VehicleSeat.TurnSpeed < Config.MaxTurn then
				VehicleSeat.TurnSpeed = VehicleSeat.TurnSpeed + Config.TurnSpeedAddition
			end
		elseif VehicleSeatSteer == 0 then
			if VehicleSeat.TurnSpeed < 0 then
				VehicleSeat.TurnSpeed = VehicleSeat.TurnSpeed + Config.TurnSpeedAddition
			elseif VehicleSeat.TurnSpeed > 0 then
				VehicleSeat.TurnSpeed = VehicleSeat.TurnSpeed - Config.TurnSpeedAddition
			end
			if VehicleSeat.TurnSpeed <= Config.TurnStop and VehicleSeat.TurnSpeed >= -Config.TurnStop then
				VehicleSeat.TurnSpeed = 0
			end
		end

		local Pitch = math.abs(VehicleSeat.MaxSpeed) / 10 + 2.5

		if VehicleSeatMaxSpeed > 0 then
			if Config.PlayEngineSound then
				EngineSound.Pitch = Pitch
				EngineSound:Play()
			end

		elseif VehicleSeatMaxSpeed < 0 then
			if Config.PlayEngineSound then
				EngineSound.Pitch = Pitch
				EngineSound:Play()
			end

		else
			EngineSound:Stop()

		end
		--BodyGyro.cframe = CFrame.Angles(0, (AngleY) + VehicleSeat.TurnSpeed, 0) * CFrame.new(Direction.p+Vector3.new(0,0,-1))
		--BodyGyro.cframe             = VehicleSeat.CFrame * CFrame.Angles(0, VehicleSeat.TurnSpeed, 0) --BodyGyro.cframe * CFrame.Angles(0, VehicleSeat.TurnSpeed, 0) -- VehicleSeat.CFrame * CFrame.Angles(0, VehicleSeat.TurnSpeed, 0)
		--BodyGyro.cframe = (VehicleSeat.CFrame.lookVector * CFrame.new(1, 0, 0)) * CFrame.Angles(0, VehicleSeat.TurnSpeed, 0)
		--print("Ship to be angled at "..((AngleY) + VehicleSeat.TurnSpeed)*(180/math.pi).." with AngleY (Actual) at "..(AngleY*(180/math.pi)).." with an increase of "..VehicleSeat.TurnSpeed*(180/math.pi))

		BodyAngularVelocity.angularvelocity = Vector3.new(0, VehicleSeat.TurnSpeed, 0)
		--local Direction = VehicleSeat.CFrame
		--local AngleX, AngleY, AngleZ = Direction:toEulerAnglesXYZ() 
		local Guide = VehicleSeat.CFrame * CFrame.new(-1, 0, 0)

		BodyGyro.cframe = CFrame.new(VehicleSeat.Position, Vector3.new(Guide.X, VehicleSeat.CFrame.Y + (-Config.TiltFactor * (VehicleSeat.TurnSpeed/Config.MaxTurn)), Guide.Z))
		BodyVelocity.velocity       = VehicleSeat.CFrame.lookVector * VehicleSeat.MaxSpeed

		wait(0.1)
	end
end


-- Variables --

local Boat = script.Parent.Parent
local VehicleSeat = script.Parent

local Config = {
	TopSpeed = 30; -- How fast it can go.  (In studs)
	TopSpeedReverse = 15; -- How fast is can go in reverse (In studs)
	MaxTurn = 0.5; -- 1 = complete turn in 1 second 0.5 means complete turn (360) in 2 seconds.  
	TurnSpeedAddition = 0.05; 
	TurnStop = 0.3;
	TiltFactor = 0.1; -- Amount it tilts.  
	ShipName = script.Parent.Parent.Name;
}



SetupBoat(Boat, VehicleSeat, Config)

Thanks in advance!

1 Like

Have it wait outside of any if statements so there is no possible chance of a while true do crash loop.

(sorry for such a late reply lmao)

1 Like

you also forgot a wait in this if statement if what i said above doesnt work

Yea so I did check most of the IF statements for wait and actually went further to use task.wait since its more accurate however it doesn’t seem to actually solve anything. It still makes the memory go up infinitely while driving the ship. It stops going any further after I jump out it. Is it possible for the ship model to cause that memory leak?

no, im not sure whats causing it then.