local M={workspace,Players}local J={}J.__index=J local C={}local G=Signal.new()local W=Signal.new()local function V(J)for M,C in ipairs(M)do if J:IsDescendantOf(C)then return true end end return false end function J.FromTag(M)return C[M]end function J.ObserveFromTag(M,C)local V=Janitor.new()local j=Janitor.new()V:Add(j)local function N(J)if J._tag==M then C(J,j)end end local function t(J)if J._tag==M then j:Cleanup()end end do local C=J.FromTag(M)if C then task.spawn(N,C)end end V:Add(G:Connect(N))V:Add(W:Connect(t))return V end function J.Auto(M)local function C(M)local C=require(M)assert(type(C)=="\116\097\098\108\101","\069\120\112\101\099\116\101\100 \116\097\098\108\101 \102\111\114 \099\111\109\112\111\110\101\110\116")assert(type(C.Tag)=="\115\116\114\105\110\103","\069\120\112\101\099\116\101\100 \046\084\097\103 \112\114\111\112\101\114\116\121")J.new(C.Tag,C,C.RenderPriority,C.RequiredComponents)end for M,J in ipairs(M:GetDescendants())do if J:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then C(J)end end M.DescendantAdded:Connect(function(M)if M:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then C(M)end end)end function J.new(M,j,N,t)assert(type(M)=="\115\116\114\105\110\103","\065\114\103\117\109\101\110\116 \035\049 \040\116\097\103\041 \115\104\111\117\108\100 \098\101 \097 \115\116\114\105\110\103\059 \103\111\116 "..type(M))assert(type(j)=="\116\097\098\108\101","\065\114\103\117\109\101\110\116 \035\050 \040\099\108\097\115\115\041 \115\104\111\117\108\100 \098\101 \097 \116\097\098\108\101\059 \103\111\116 "..type(j))assert(type(j.new)=="\102\117\110\099\116\105\111\110","\067\108\097\115\115 \109\117\115\116 \099\111\110\116\097\105\110 \097 \046\110\101\119 \099\111\110\115\116\114\117\099\116\111\114 \102\117\110\099\116\105\111\110")assert(type(j.Destroy)=="\102\117\110\099\116\105\111\110","\067\108\097\115\115 \109\117\115\116 \099\111\110\116\097\105\110 \097 \058\068\101\115\116\114\111\121 \102\117\110\099\116\105\111\110")assert(C[M]==nil,"\067\111\109\112\111\110\101\110\116 \097\108\114\101\097\100\121 \098\111\117\110\100 \116\111 \116\104\105\115 \116\097\103")local v=setmetatable({},J)v._janitor=Janitor.new()v._lifecycleJanitor=Janitor.new()v._tag=M v._class=j v._objects={}v._instancesToObjects={}v._hasHeartbeatUpdate=type(j.HeartbeatUpdate)=="\102\117\110\099\116\105\111\110"v._hasSteppedUpdate=type(j.SteppedUpdate)=="\102\117\110\099\116\105\111\110"v._hasRenderUpdate=type(j.RenderUpdate)=="\102\117\110\099\116\105\111\110"v._hasInit=type(j.Init)=="\102\117\110\099\116\105\111\110"v._hasDeinit=type(j.Deinit)=="\102\117\110\099\116\105\111\110"v._renderPriority=N or Enum.RenderPriority.Last.Value v._requireComponents=t or{}v._lifecycle=false v._nextId=0 v.Added=Signal.new(v._janitor)v.Removed=Signal.new(v._janitor)local Z=Janitor.new()v._janitor:Add(Z)local function L()local function C(M)for C,G in ipairs(v._requireComponents)do local W=J.FromTag(G)if W:GetFromInstance(M)==nil then return false end end return true end Z:Add((CollectionService:GetInstanceAddedSignal(M)):Connect(function(M)if V(M)and C(M)then v:_instanceAdded(M)end end))Z:Add((CollectionService:GetInstanceRemovedSignal(M)):Connect(function(M)v:_instanceRemoved(M)end))for G,W in ipairs(v._requireComponents)do local V=J.FromTag(W)Z:Add(V.Added:Connect(function(J)if CollectionService:HasTag(J.Instance,M)and C(J.Instance)then v:_instanceAdded(J.Instance)end end))Z:Add(V.Removed:Connect(function(J)if CollectionService:HasTag(J.Instance,M)then v:_instanceRemoved(J.Instance)end end))end Z:Add(function()v:_stopLifecycle()for M in pairs(v._instancesToObjects)do v:_instanceRemoved(M)end end)do local J=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")for M,G in ipairs(CollectionService:GetTagged(M))do if V(G)and C(G)then local M=J.Event:Connect(function()v:_instanceAdded(G)end)J:Fire()M:Disconnect()end end J:Destroy()end end if#v._requireComponents==0 then L()else local M={}local function C()for M,J in pairs(M)do if not J then return end end L()end local function G()Z:Cleanup()end for W,V in ipairs(v._requireComponents)do M[V]=false v._janitor:Add(J.ObserveFromTag(V,function(J,W)M[V]=true C()W:Add(function()M[V]=false G()end)end))end end C[M]=v G:Fire(v)v._janitor:Add(function()C[M]=nil W:Fire(v)end)return v end function J._startHeartbeatUpdate(M)local J=M._objects M._heartbeatUpdate=RunService.Heartbeat:Connect(function(M)for J,C in ipairs(J)do C:HeartbeatUpdate(M)end end)M._lifecycleJanitor:Add(M._heartbeatUpdate)end function J._startSteppedUpdate(M)local J=M._objects M._steppedUpdate=RunService.Stepped:Connect(function(M,C)for M,J in ipairs(J)do J:SteppedUpdate(C)end end)M._lifecycleJanitor:Add(M._steppedUpdate)end function J._startRenderUpdate(M)local J=M._objects M._renderName=M._tag.."\082\101\110\100\101\114\085\112\100\097\116\101"RunService:BindToRenderStep(M._renderName,M._renderPriority,function(M)for J,C in ipairs(J)do C:RenderUpdate(M)end end)M._lifecycleJanitor:Add(function()RunService:UnbindFromRenderStep(M._renderName)end)end function J._startLifecycle(M)M._lifecycle=true if M._hasHeartbeatUpdate then M:_startHeartbeatUpdate()end if M._hasSteppedUpdate then M:_startSteppedUpdate()end if M._hasRenderUpdate then M:_startRenderUpdate()end end function J._stopLifecycle(M)M._lifecycle=false M._lifecycleJanitor:Cleanup()end function J._instanceAdded(J,M)if J._instancesToObjects[M]then return end if not J._lifecycle then J:_startLifecycle()end J._nextId=J._nextId+1 local C=J._tag..tostring(J._nextId)if IS_SERVER then M:SetAttribute(ATTRIBUTE_ID_NAME,C)end local G=J._class.new(M)G.Instance=M G._id=C J._instancesToObjects[M]=G table.insert(J._objects,G)if J._hasInit then task.defer(function()if J._instancesToObjects[M]~=G then return end G:Init()end)end J.Added:Fire(G)return G end function J._instanceRemoved(J,M)if not J._instancesToObjects[M]then return end J._instancesToObjects[M]=nil for C,G in ipairs(J._objects)do if G.Instance==M then if J._hasDeinit then G:Deinit()end if IS_SERVER and(M.Parent and M:GetAttribute(ATTRIBUTE_ID_NAME)~=nil)then M:SetAttribute(ATTRIBUTE_ID_NAME,nil)end J.Removed:Fire(G)G:Destroy()G._destroyed=true TableUtil.FastRemove(J._objects,C)break end end if#J._objects==0 and J._lifecycle then J:_stopLifecycle()end end function J.GetAll(M)return TableUtil.CopyShallow(M._objects)end function J.GetFromInstance(J,M)return J._instancesToObjects[M]end function J.GetFromID(J,M)for J,C in ipairs(J._objects)do if C._id==M then return C end end return nil end function J.Filter(J,M)return TableUtil.Filter(J._objects,M)end function J.WaitFor(C,M,J)local G=type(M)=="\115\116\114\105\110\103"local function W(J)return G and J.Instance.Name==M or not G and J.Instance==M end for M,J in ipairs(C._objects)do if W(J)then return Promise.Resolve(J)end end local V=nil return((Promise.FromEvent(C.Added,function(M)V=M return W(M)end)):Then(function()return V end)):Timeout(J or DEFAULT_WAIT_FOR_TIMEOUT)end function J.Observe(C,M,J)local G=Janitor.new()local W=Janitor.new()G:Add(W)G:Add(C.Added:Connect(function(C)if C.Instance==M then J(C,W)end end))G:Add(C.Removed:Connect(function(J)if J.Instance==M then W:Cleanup()end end))for C,G in ipairs(C._objects)do if G.Instance==M then task.spawn(J,G,W)break end end return G end function J.Destroy(M)M._janitor:Destroy()end return J

local DESCENDANT_WHITELIST = {workspace, Players}

local Component = {}
Component.__index = Component

local componentsByTag = {}

local componentByTagCreated = Signal.new()
local componentByTagDestroyed = Signal.new()


local function IsDescendantOfWhitelist(instance)
	for _,v in ipairs(DESCENDANT_WHITELIST) do
		if (instance:IsDescendantOf(v)) then
			return true
		end
	end
	return false
end


function Component.FromTag(tag)
	return componentsByTag[tag]
end


function Component.ObserveFromTag(tag, observer)
	local janitor = Janitor.new()
	local observeJanitor = Janitor.new()
	janitor:Add(observeJanitor)
	local function OnCreated(component)
		if (component._tag == tag) then
			observer(component, observeJanitor)
		end
	end
	local function OnDestroyed(component)
		if (component._tag == tag) then
			observeJanitor:Cleanup()
		end
	end
	do
		local component = Component.FromTag(tag)
		if (component) then
			task.spawn(OnCreated, component)
		end
	end
	janitor:Add(componentByTagCreated:Connect(OnCreated))
	janitor:Add(componentByTagDestroyed:Connect(OnDestroyed))
	return janitor
end


function Component.Auto(folder)
	local function Setup(moduleScript)
		local m = require(moduleScript)
		assert(type(m) == "table", "Expected table for component")
		assert(type(m.Tag) == "string", "Expected .Tag property")
		Component.new(m.Tag, m, m.RenderPriority, m.RequiredComponents)
	end
	for _,v in ipairs(folder:GetDescendants()) do
		if (v:IsA("ModuleScript")) then
			Setup(v)
		end
	end
	folder.DescendantAdded:Connect(function(v)
		if (v:IsA("ModuleScript")) then
			Setup(v)
		end
	end)
end


function Component.new(tag, class, renderPriority, requireComponents)

	assert(type(tag) == "string", "Argument #1 (tag) should be a string; got " .. type(tag))
	assert(type(class) == "table", "Argument #2 (class) should be a table; got " .. type(class))
	assert(type(class.new) == "function", "Class must contain a .new constructor function")
	assert(type(class.Destroy) == "function", "Class must contain a :Destroy function")
	assert(componentsByTag[tag] == nil, "Component already bound to this tag")

	local self = setmetatable({}, Component)

	self._janitor = Janitor.new()
	self._lifecycleJanitor = Janitor.new()
	self._tag = tag
	self._class = class
	self._objects = {}
	self._instancesToObjects = {}
	self._hasHeartbeatUpdate = (type(class.HeartbeatUpdate) == "function")
	self._hasSteppedUpdate = (type(class.SteppedUpdate) == "function")
	self._hasRenderUpdate = (type(class.RenderUpdate) == "function")
	self._hasInit = (type(class.Init) == "function")
	self._hasDeinit = (type(class.Deinit) == "function")
	self._renderPriority = renderPriority or Enum.RenderPriority.Last.Value
	self._requireComponents = requireComponents or {}
	self._lifecycle = false
	self._nextId = 0

	self.Added = Signal.new(self._janitor)
	self.Removed = Signal.new(self._janitor)

	local observeJanitor = Janitor.new()
	self._janitor:Add(observeJanitor)

	local function ObserveTag()

		local function HasRequiredComponents(instance)
			for _,reqComp in ipairs(self._requireComponents) do
				local comp = Component.FromTag(reqComp)
				if (comp:GetFromInstance(instance) == nil) then
					return false
				end
			end
			return true
		end

		observeJanitor:Add(CollectionService:GetInstanceAddedSignal(tag):Connect(function(instance)
			if (IsDescendantOfWhitelist(instance) and HasRequiredComponents(instance)) then
				self:_instanceAdded(instance)
			end
		end))

		observeJanitor:Add(CollectionService:GetInstanceRemovedSignal(tag):Connect(function(instance)
			self:_instanceRemoved(instance)
		end))

		for _,reqComp in ipairs(self._requireComponents) do
			local comp = Component.FromTag(reqComp)
			observeJanitor:Add(comp.Added:Connect(function(obj)
				if (CollectionService:HasTag(obj.Instance, tag) and HasRequiredComponents(obj.Instance)) then
					self:_instanceAdded(obj.Instance)
				end
			end))
			observeJanitor:Add(comp.Removed:Connect(function(obj)
				if (CollectionService:HasTag(obj.Instance, tag)) then
					self:_instanceRemoved(obj.Instance)
				end
			end))
		end

		observeJanitor:Add(function()
			self:_stopLifecycle()
			for instance in pairs(self._instancesToObjects) do
				self:_instanceRemoved(instance)
			end
		end)

		do
			local b = Instance.new("BindableEvent")
			for _,instance in ipairs(CollectionService:GetTagged(tag)) do
				if (IsDescendantOfWhitelist(instance) and HasRequiredComponents(instance)) then
					local c = b.Event:Connect(function()
						self:_instanceAdded(instance)
					end)
					b:Fire()
					c:Disconnect()
				end
			end
			b:Destroy()
		end

	end

	if (#self._requireComponents == 0) then
		ObserveTag()
	else
		-- Only observe tag when all required components are available:
		local tagsReady = {}
		local function Check()
			for _,ready in pairs(tagsReady) do
				if (not ready) then
					return
				end
			end
			ObserveTag()
		end
		local function Cleanup()
			observeJanitor:Cleanup()
		end
		for _,requiredComponent in ipairs(self._requireComponents) do
			tagsReady[requiredComponent] = false
			self._janitor:Add(Component.ObserveFromTag(requiredComponent, function(_component, janitor)
				tagsReady[requiredComponent] = true
				Check()
				janitor:Add(function()
					tagsReady[requiredComponent] = false
					Cleanup()
				end)
			end))
		end
	end

	componentsByTag[tag] = self
	componentByTagCreated:Fire(self)
	self._janitor:Add(function()
		componentsByTag[tag] = nil
		componentByTagDestroyed:Fire(self)
	end)

	return self

end


function Component:_startHeartbeatUpdate()
	local all = self._objects
	self._heartbeatUpdate = RunService.Heartbeat:Connect(function(dt)
		for _,v in ipairs(all) do
			v:HeartbeatUpdate(dt)
		end
	end)
	self._lifecycleJanitor:Add(self._heartbeatUpdate)
end


function Component:_startSteppedUpdate()
	local all = self._objects
	self._steppedUpdate = RunService.Stepped:Connect(function(_, dt)
		for _,v in ipairs(all) do
			v:SteppedUpdate(dt)
		end
	end)
	self._lifecycleJanitor:Add(self._steppedUpdate)
end


function Component:_startRenderUpdate()
	local all = self._objects
	self._renderName = (self._tag .. "RenderUpdate")
	RunService:BindToRenderStep(self._renderName, self._renderPriority, function(dt)
		for _,v in ipairs(all) do
			v:RenderUpdate(dt)
		end
	end)
	self._lifecycleJanitor:Add(function()
		RunService:UnbindFromRenderStep(self._renderName)
	end)
end


function Component:_startLifecycle()
	self._lifecycle = true
	if (self._hasHeartbeatUpdate) then
		self:_startHeartbeatUpdate()
	end
	if (self._hasSteppedUpdate) then
		self:_startSteppedUpdate()
	end
	if (self._hasRenderUpdate) then
		self:_startRenderUpdate()
	end
end


function Component:_stopLifecycle()
	self._lifecycle = false
	self._lifecycleJanitor:Cleanup()
end


function Component:_instanceAdded(instance)
	if (self._instancesToObjects[instance]) then return end
	if (not self._lifecycle) then
		self:_startLifecycle()
	end
	self._nextId = (self._nextId + 1)
	local id = (self._tag .. tostring(self._nextId))
	if (IS_SERVER) then
		instance:SetAttribute(ATTRIBUTE_ID_NAME, id)
	end
	local obj = self._class.new(instance)
	obj.Instance = instance
	obj._id = id
	self._instancesToObjects[instance] = obj
	table.insert(self._objects, obj)
	if (self._hasInit) then
		task.defer(function()
			if (self._instancesToObjects[instance] ~= obj) then return end
			obj:Init()
		end)
	end
	self.Added:Fire(obj)
	return obj
end


function Component:_instanceRemoved(instance)
	if (not self._instancesToObjects[instance]) then return end
	self._instancesToObjects[instance] = nil
	for i,obj in ipairs(self._objects) do
		if (obj.Instance == instance) then
			if (self._hasDeinit) then
				obj:Deinit()
			end
			if (IS_SERVER and instance.Parent and instance:GetAttribute(ATTRIBUTE_ID_NAME) ~= nil) then
				instance:SetAttribute(ATTRIBUTE_ID_NAME, nil)
			end
			self.Removed:Fire(obj)
			obj:Destroy()
			obj._destroyed = true
			TableUtil.FastRemove(self._objects, i)
			break
		end
	end
	if (#self._objects == 0 and self._lifecycle) then
		self:_stopLifecycle()
	end
end


function Component:GetAll()
	return TableUtil.CopyShallow(self._objects)
end


function Component:GetFromInstance(instance)
	return self._instancesToObjects[instance]
end


function Component:GetFromID(id)
	for _,v in ipairs(self._objects) do
		if (v._id == id) then
			return v
		end
	end
	return nil
end


function Component:Filter(filterFunc)
	return TableUtil.Filter(self._objects, filterFunc)
end


function Component:WaitFor(instance, timeout)
	local isName = (type(instance) == "string")
	local function IsInstanceValid(obj)
		return ((isName and obj.Instance.Name == instance) or ((not isName) and obj.Instance == instance))
	end
	for _,obj in ipairs(self._objects) do
		if (IsInstanceValid(obj)) then
			return Promise.Resolve(obj)
		end
	end
	local lastObj = nil
	return Promise.FromEvent(self.Added, function(obj)
		lastObj = obj
		return IsInstanceValid(obj)
	end):Then(function()
		return lastObj
	end):Timeout(timeout or DEFAULT_WAIT_FOR_TIMEOUT)
end


function Component:Observe(instance, observer)
	local janitor = Janitor.new()
	local observeJanitor = Janitor.new()
	janitor:Add(observeJanitor)
	janitor:Add(self.Added:Connect(function(obj)
		if (obj.Instance == instance) then
			observer(obj, observeJanitor)
		end
	end))
	janitor:Add(self.Removed:Connect(function(obj)
		if (obj.Instance == instance) then
			observeJanitor:Cleanup()
		end
	end))
	for _,obj in ipairs(self._objects) do
		if (obj.Instance == instance) then
			task.spawn(observer, obj, observeJanitor)
			break
		end
	end
	return janitor
end


function Component:Destroy()
	self._janitor:Destroy()
end


return Component
