Having trouble with cyclic tables

My hitbox system doesn’t work because it apparently has a cyclic table references and I can’t find out where

My code:

local Hitbox = {}
local Metatable = {__index = Hitbox}

local ClientCast = require(script:WaitForChild("ClientCast"))
local AttachmentFunctions = require(script:WaitForChild("AttachmentFunctions"))
local RunService = game:GetService("RunService")

type self = {
	Size : Vector3,
	CFrame : CFrame,
	HitboxPart : Part,
	Caster : any,
	DebounceWaitTime : number,
	SanityCheckRange : number,
	onTouch : (Humanoid) -> nil?,
	
	Debounces : {Humanoid}
}

export type HitboxType = typeof(setmetatable({} :: self, Metatable))


function Hitbox.new (cFrame : CFrame, size : Vector3, onTouch : (Humanoid) -> nil?, autoCalcDensity : boolean?, lifetime : number?): HitboxType
	local self = setmetatable({} :: self, Metatable)
	
	self.CFrame = cFrame
	self.Size = size
	
	local HitboxPart = Instance.new("Part")
	
	HitboxPart.CFrame = self.CFrame
	HitboxPart.Size = self.Size
	HitboxPart.Parent = workspace
	HitboxPart.Name = "HitboxPart"
	HitboxPart.Color = Color3.new(1, 0.431373, 0.431373)
	
	if RunService:IsStudio() then
		HitboxPart.Transparency = 0.5
	else
		HitboxPart.Transparency = 1
	end
	
	HitboxPart.Anchored = true
	HitboxPart.CanCollide = false
	HitboxPart.Massless = true
	
	self.HitboxPart = HitboxPart
	
	local oldDensity
	if autoCalcDensity then
		oldDensity = AttachmentFunctions.CalculateSuitableDensity(HitboxPart)
	end
	AttachmentFunctions.GenerateAttachmentsVolume(HitboxPart)
	
	if oldDensity then
		AttachmentFunctions.Density = oldDensity
	end
	print(HitboxPart)
	
	task.wait(0.005)
	
	local Caster = ClientCast.new(HitboxPart, RaycastParams.new())
	
	self.Caster = Caster
	self.Debounces = {}
	self.DebounceWaitTime = 0.5
	self.SanityCheckRange = 5
	self.onTouch = onTouch
	
	self.Caster.HumanoidCollided:Connect (function(raycastResult : RaycastResult, humanoid : Humanoid)
		
		if self:GetOwner() ~= nil then
			warn("Hitbox has a player named ", self:GetOwner().Name)
			
			local owner = self:GetOwner()
			local character = owner.Character

			local hrp = character:WaitForChild("HumanoidRootPart") :: BasePart
			
			if (humanoid.RootPart.Position.Magnitude - hrp.Position.Magnitude) > self.SanityCheckRange then
				warn("Most likely an exploiter")
				return
			end
			
		else
			warn("Hitbox doesn't have a player")
		end
		
		if not self.Debounces[humanoid] and onTouch ~= nil then
			
			self.Debounces[humanoid] = true

			self.onTouch(humanoid)

			-- wait until stop touching
			task.wait(1.25)
			self.Debounces[humanoid] = false
		end
	end)

	if lifetime then
		self:Start()
		
		task.delay(lifetime, function()
			self:Destroy()
		end)
	end
	
	return self
end

function Hitbox.SetOwner (self : HitboxType, player : Player?)
	self.Caster:SetOwner (player)
	
	if player then
		local newRaycastParams = RaycastParams.new()
		newRaycastParams.FilterDescendantsInstances = {player.Character}
		newRaycastParams.FilterType = Enum.RaycastFilterType.Exclude
		
		self.Caster:EditRaycastParams (newRaycastParams)
	end
end

function Hitbox.EditRaycastParams (self : HitboxType, newRaycastParams : RaycastParams)
	self.Caster:EditRaycastParams (newRaycastParams)
end

function Hitbox.SetSize (self : HitboxType, size : Vector3)
	self.Size = size
	self.HitboxPart.Size = size
end

function Hitbox.SetCFrame (self : HitboxType, cframe : CFrame)
	self.CFrame = cframe
	self.HitboxPart.CFrame = self.CFrame
end

function Hitbox.GetOwner (self : HitboxType) : Player?
	return self.Caster:GetOwner ()
end

function Hitbox.Start (self : HitboxType)
	self.Caster:Start()
end

function Hitbox.Stop (self : HitboxType)
	self.Caster:Stop()
end

function Hitbox.WeldTo (self : HitboxType, part : BasePart)
	self.HitboxPart.CFrame = part.CFrame
	
	local weld = Instance.new("WeldConstraint")
	
	weld.Parent = self.HitboxPart
	weld.Name = "HitboxWeld"
	weld.Part0 = part
	weld.Part1 = self.HitboxPart
	
	self.HitboxPart.Anchored = false
end

function Hitbox.Unweld (self : HitboxType)
	if self.HitboxPart:FindFirstChild("HitboxWeld") then
		self.HitboxPart:FindFirstChild("HitboxWeld"):Destroy()
		self.HitboxPart.Anchored = true
	end
end

function Hitbox.Destroy (self : HitboxType)
	self.Caster:Destroy()
	
	self.HitboxPart:Destroy()
	setmetatable(self, nil)
end

return Hitbox

This is what client cast is: ClientCast - A Client-based, Idiosyncratic Hitbox System!

and this is the attachment functions module:

local AttachmentFunctions = {}
AttachmentFunctions.Density = 1.5

local RunService = game:GetService("RunService")

-- Higher values mean less attachments
-- Less attachments means more attachments

function AttachmentFunctions.GenerateAttachmentsDominantAxis (part : Part)
	-- negative is start pos is end
	
	local lAxis = math.max (part.Size.X, part.Size.Y, part.Size.Z) -- Find which axis is the largest
	
	if lAxis == part.Size.X then
		local sPos = (part.Position - Vector3.new(part.Size.X / 2)).X
		local ePos = (part.Position + Vector3.new(part.Size.X / 2)).X
		
		for x = sPos, ePos, AttachmentFunctions.Density do
			local attachment = Instance.new("Attachment")
			
			attachment.Parent = part
			attachment.WorldPosition = Vector3.new(x, part.Position.Y, part.Position.Z)
			attachment.Name = "DmgPoint"
			
			if RunService:IsStudio() then
				attachment.Visible = true
			end

		end
		
	elseif lAxis == part.Size.Y then
		
		local sPos = (part.Position - Vector3.new(0, part.Size.Y / 2, 0)).Y
		local ePos = (part.Position + Vector3.new(0, part.Size.Y / 2, 0)).Y

		for y = sPos, ePos, AttachmentFunctions.Density do
			local attachment = Instance.new("Attachment")

			attachment.Parent = part
			attachment.WorldPosition = Vector3.new(part.Position.X, y, part.Position.Z)
			attachment.Name = "DmgPoint"
			
			if RunService:IsStudio() then
				attachment.Visible = true
			end

		end
		
	elseif lAxis == part.Size.Z then
		
		local sPos = (part.Position - Vector3.new(0, part.Size.Z / 2, 0)).Z
		local ePos = (part.Position + Vector3.new(0, part.Size.Z / 2, 0)).Z

		for z = sPos, ePos, AttachmentFunctions.Density do
			local attachment = Instance.new("Attachment")

			attachment.Parent = part
			attachment.WorldPosition = Vector3.new(part.Position.X, part.Position.Y, z)
			attachment.Name = "DmgPoint"
			
			if RunService:IsStudio() then
				attachment.Visible = true
			end
			
		end
	
	end
end

function AttachmentFunctions.CalculateSuitableDensity (part : Part)
	local oldDensity = AttachmentFunctions.Density

	local lAxis = math.max (part.Size.X, part.Size.Y, part.Size.Z) -- Find which axis is the largest

	if lAxis == part.Size.X then
		Density = part.Size.X / 20

	elseif lAxis == part.Size.Y then
		Density = part.Size.Y / 20

	elseif lAxis == part.Size.Z then
		Density = part.Size.Z / 20

	end

	return oldDensity
end

function AttachmentFunctions.GenerateCenterAttachment (part : Part)

	local attachment = Instance.new("Attachment")
	attachment.Parent = part
	attachment.WorldPosition = Vector3.new(part.Position.X, part.Position.Y, part.Position.Z)
	attachment.Name = "CenterPoint"
	
	if RunService:IsStudio() then
		attachment.Visible = true
	end
	
end

function AttachmentFunctions.GenerateAttachmentsVolume (part : Part)
	
	local count = 0
	
	local hX, hY, hZ = part.Size.X / 2, part.Size.Y / 2, part.Size.Z / 2
	local estimatedAttCount = hX * hY * hZ
	
	for x = -hX, hX, AttachmentFunctions.Density do
		for y = -hY, hY, AttachmentFunctions.Density do
			for z = -hZ, hZ, AttachmentFunctions.Density do
			
				local attachment = Instance.new("Attachment")
			
				attachment.Parent = part
				attachment.Position = Vector3.new(x, y, z)
				attachment.Name = "DmgPoint"
				
				if RunService:IsStudio() then
					attachment.Visible = true
				end
				
				count += 1

			end

		end

	end
	
	print("estimation: ", estimatedAttCount)
	print("actual count: ", count)
end

return AttachmentFunctions

And I’m sending these hitbox objects over bindable events.
Any help is appreciated

bump 30charlimitbypass

The issue is not that something is wrong with ur hitbox logic its that ur object oriented design uses a common lua pattern that creates cyclic table references In ur code hitbox instances use a metatable with __index that points back to the hitbox table so a cycle is created This is normal in lua and the garbage collector handles it but some tools or serializers may complain if they try to traverse the tables To fix that u can break the cycle before serializing by removing the metatable or write custom serialization logic

Here is an example of how u might break the cycle before serialization:

setmetatable(hitboxInstance, nil)