local T={workspace;Players}local j={}j.__index=j local y={}local Q=Signal.new()local u=Signal.new()local function q(j)for T,y in ipairs(T)do if j:IsDescendantOf(y)then return true end end return false end function j.FromTag(T)return y[T]end function j.ObserveFromTag(T,y)local q=Janitor.new()local m=Janitor.new()q:Add(m)local function H(j)if j._tag==T then y(j,m)end end local function e(j)if j._tag==T then m:Cleanup()end end do local y=j.FromTag(T)if y then task.spawn(H,y)end end q:Add(Q:Connect(H))q:Add(u:Connect(e))return q end function j.Auto(T)local function y(T)local y=require(T)assert(type(y)=="\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(y.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(y.Tag,y,y.RenderPriority,y.RequiredComponents)end for T,j in ipairs(T:GetDescendants())do if j:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then y(j)end end T.DescendantAdded:Connect(function(T)if T:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then y(T)end end)end function j.new(T,m,H,e)assert(type(T)=="\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(T))assert(type(m)=="\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(m))assert(type(m.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(m.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(y[T]==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 x=setmetatable({},j)x._janitor=Janitor.new()x._lifecycleJanitor=Janitor.new()x._tag=T x._class=m x._objects={}x._instancesToObjects={}x._hasHeartbeatUpdate=type(m.HeartbeatUpdate)=="\102\117\110\099\116\105\111\110"x._hasSteppedUpdate=type(m.SteppedUpdate)=="\102\117\110\099\116\105\111\110"x._hasRenderUpdate=type(m.RenderUpdate)=="\102\117\110\099\116\105\111\110"x._hasInit=type(m.Init)=="\102\117\110\099\116\105\111\110"x._hasDeinit=type(m.Deinit)=="\102\117\110\099\116\105\111\110"x._renderPriority=H or Enum.RenderPriority.Last.Value x._requireComponents=e or{}x._lifecycle=false x._nextId=0 x.Added=Signal.new(x._janitor)x.Removed=Signal.new(x._janitor)local S=Janitor.new()x._janitor:Add(S)local function n()local function y(T)for y,Q in ipairs(x._requireComponents)do local u=j.FromTag(Q)if u:GetFromInstance(T)==nil then return false end end return true end S:Add((CollectionService:GetInstanceAddedSignal(T)):Connect(function(T)if q(T)and y(T)then x:_instanceAdded(T)end end))S:Add((CollectionService:GetInstanceRemovedSignal(T)):Connect(function(T)x:_instanceRemoved(T)end))for Q,u in ipairs(x._requireComponents)do local q=j.FromTag(u)S:Add(q.Added:Connect(function(j)if CollectionService:HasTag(j.Instance,T)and y(j.Instance)then x:_instanceAdded(j.Instance)end end))S:Add(q.Removed:Connect(function(j)if CollectionService:HasTag(j.Instance,T)then x:_instanceRemoved(j.Instance)end end))end S:Add(function()x:_stopLifecycle()for T in pairs(x._instancesToObjects)do x:_instanceRemoved(T)end end)do local j=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")for T,Q in ipairs(CollectionService:GetTagged(T))do if q(Q)and y(Q)then local T=j.Event:Connect(function()x:_instanceAdded(Q)end)j:Fire()T:Disconnect()end end j:Destroy()end end if#x._requireComponents==0 then n()else local T={}local function y()for T,j in pairs(T)do if not j then return end end n()end local function Q()S:Cleanup()end for j,y in ipairs(x._requireComponents)do T[y]=false end for u,q in ipairs(x._requireComponents)do x._janitor:Add(j.ObserveFromTag(q,function(j,u)T[q]=true y()u:Add(function()T[q]=false Q()end)end))end end y[T]=x Q:Fire(x)x._janitor:Add(function()y[T]=nil u:Fire(x)end)return x end function j._startHeartbeatUpdate(T)local j=T._objects T._heartbeatUpdate=RunService.Heartbeat:Connect(function(T)for j,y in ipairs(j)do y:HeartbeatUpdate(T)end end)T._lifecycleJanitor:Add(T._heartbeatUpdate)end function j._startSteppedUpdate(T)local j=T._objects T._steppedUpdate=RunService.Stepped:Connect(function(T,y)for T,j in ipairs(j)do j:SteppedUpdate(y)end end)T._lifecycleJanitor:Add(T._steppedUpdate)end function j._startRenderUpdate(T)local j=T._objects T._renderName=T._tag.."\082\101\110\100\101\114\085\112\100\097\116\101"RunService:BindToRenderStep(T._renderName,T._renderPriority,function(T)for j,y in ipairs(j)do y:RenderUpdate(T)end end)T._lifecycleJanitor:Add(function()RunService:UnbindFromRenderStep(T._renderName)end)end function j._startLifecycle(T)T._lifecycle=true if T._hasHeartbeatUpdate then T:_startHeartbeatUpdate()end if T._hasSteppedUpdate then T:_startSteppedUpdate()end if T._hasRenderUpdate then T:_startRenderUpdate()end end function j._stopLifecycle(T)T._lifecycle=false T._lifecycleJanitor:Cleanup()end function j._instanceAdded(j,T)if j._instancesToObjects[T]then return end if not j._lifecycle then j:_startLifecycle()end j._nextId=j._nextId+1 local y=j._tag..tostring(j._nextId)if IS_SERVER then T:SetAttribute(ATTRIBUTE_ID_NAME,y)end local Q=j._class.new(T)Q.Instance=T Q._id=y j._instancesToObjects[T]=Q table.insert(j._objects,Q)if j._hasInit then task.defer(function()if j._instancesToObjects[T]~=Q then return end Q:Init()end)end j.Added:Fire(Q)return Q end function j._instanceRemoved(j,T)if not j._instancesToObjects[T]then return end j._instancesToObjects[T]=nil for y,Q in ipairs(j._objects)do if Q.Instance==T then if j._hasDeinit then Q:Deinit()end if IS_SERVER and(T.Parent and T:GetAttribute(ATTRIBUTE_ID_NAME)~=nil)then T:SetAttribute(ATTRIBUTE_ID_NAME,nil)end j.Removed:Fire(Q)Q:Destroy()Q._destroyed=true TableUtil.FastRemove(j._objects,y)break end end if#j._objects==0 and j._lifecycle then j:_stopLifecycle()end end function j.GetAll(T)return TableUtil.CopyShallow(T._objects)end function j.GetFromInstance(j,T)return j._instancesToObjects[T]end function j.GetFromID(j,T)for j,y in ipairs(j._objects)do if y._id==T then return y end end return nil end function j.Filter(j,T)return TableUtil.Filter(j._objects,T)end function j.WaitFor(y,T,j)local Q=type(T)=="\115\116\114\105\110\103"local function u(j)return Q and j.Instance.Name==T or not Q and j.Instance==T end for T,j in ipairs(y._objects)do if u(j)then return Promise.Resolve(j)end end local q=nil return((Promise.FromEvent(y.Added,function(T)q=T return u(T)end)):Then(function()return q end)):Timeout(j or DEFAULT_WAIT_FOR_TIMEOUT)end function j.Observe(y,T,j)local Q=Janitor.new()local u=Janitor.new()Q:Add(u)Q:Add(y.Added:Connect(function(y)if y.Instance==T then j(y,u)end end))Q:Add(y.Removed:Connect(function(j)if j.Instance==T then u:Cleanup()end end))for y,Q in ipairs(y._objects)do if Q.Instance==T then task.spawn(j,Q,u)break end end return Q end function j.Destroy(T)T._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
		end
		for _,requiredComponent in ipairs(self._requireComponents) do
			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
