local p=require(script.Parent.Logging)local G="\067\104\097\110\103\101\046"local g={Disabled="\068\105\115\097\098\108\101\100";Suspended="\083\117\115\112\101\110\100\101\100";Enabled="\069\110\097\098\108\101\100"}local t={}t.__index=t function t.new(p)local G=setmetatable({_suspendedEventQueue={},_connections={};_listeners={},_status=g.Disabled,_isResuming=false;_instance=p},t)return G end function t.connectEvent(g,p,G)g:_connect(p,g._instance[p],G)end function t.connectPropertyChange(t,p,g)local a,m=pcall(function()return t._instance:GetPropertyChangedSignal(p)end)if not a then error(("\067\097\110\110\111\116 \103\101\116 \099\104\097\110\103\101\100 \115\105\103\110\097\108 \111\110 \112\114\111\112\101\114\116\121 \037\113\058 \037\115"):format(tostring(p),m),0)end t:_connect(G..p,m,g)end function t._connect(a,p,G,t)if t==nil then if a._connections[p]~=nil then a._connections[p]:Disconnect()a._connections[p]=nil end a._listeners[p]=nil else if a._connections[p]==nil then a._connections[p]=G:Connect(function(...)if a._status==g.Enabled then a._listeners[p](a._instance,...)elseif a._status==g.Suspended then local G=select("\035",...)table.insert(a._suspendedEventQueue,{p,G,...})end end)end a._listeners[p]=t end end function t.suspend(p)p._status=g.Suspended end function t.resume(G)if G._isResuming then return end G._isResuming=true local t=1 while t<=#G._suspendedEventQueue do local g=G._suspendedEventQueue[t]local a=G._listeners[g[1]]local m=g[2]if a~=nil then local t=coroutine.create(a)local Z,J=coroutine.resume(t,G._instance,unpack(g,3,2+m))if not Z then p.warn("\037\115",J)end end t=t+1 end G._isResuming=false G._status=g.Enabled G._suspendedEventQueue={}end return t
--[[
	A manager for a single host virtual node's connected events.
]]

local Logging = require(script.Parent.Logging)

local CHANGE_PREFIX = "Change."

local EventStatus = {
	-- No events are processed at all; they're silently discarded
	Disabled = "Disabled",

	-- Events are stored in a queue; listeners are invoked when the manager is resumed
	Suspended = "Suspended",

	-- Event listeners are invoked as the events fire
	Enabled = "Enabled",
}

local SingleEventManager = {}
SingleEventManager.__index = SingleEventManager

function SingleEventManager.new(instance)
	local self = setmetatable({
		-- The queue of suspended events
		_suspendedEventQueue = {},

		-- All the event connections being managed
		-- Events are indexed by a string key
		_connections = {},

		-- All the listeners being managed
		-- These are stored distinctly from the connections
		-- Connections can have their listeners replaced at runtime
		_listeners = {},

		-- The suspension status of the manager
		-- Managers start disabled and are "resumed" after the initial render
		_status = EventStatus.Disabled,

		-- If true, the manager is processing queued events right now.
		_isResuming = false,

		-- The Roblox instance the manager is managing
		_instance = instance,
	}, SingleEventManager)

	return self
end

function SingleEventManager:connectEvent(key, listener)
	self:_connect(key, self._instance[key], listener)
end

function SingleEventManager:connectPropertyChange(key, listener)
	local success, event = pcall(function()
		return self._instance:GetPropertyChangedSignal(key)
	end)

	if not success then
		error(("Cannot get changed signal on property %q: %s"):format(tostring(key), event), 0)
	end

	self:_connect(CHANGE_PREFIX .. key, event, listener)
end

function SingleEventManager:_connect(eventKey, event, listener)
	-- If the listener doesn't exist we can just disconnect the existing connection
	if listener == nil then
		if self._connections[eventKey] ~= nil then
			self._connections[eventKey]:Disconnect()
			self._connections[eventKey] = nil
		end

		self._listeners[eventKey] = nil
	else
		if self._connections[eventKey] == nil then
			self._connections[eventKey] = event:Connect(function(...)
				if self._status == EventStatus.Enabled then
					self._listeners[eventKey](self._instance, ...)
				elseif self._status == EventStatus.Suspended then
					-- Store this event invocation to be fired when resume is
					-- called.

					local argumentCount = select("#", ...)
					table.insert(self._suspendedEventQueue, { eventKey, argumentCount, ... })
				end
			end)
		end

		self._listeners[eventKey] = listener
	end
end

function SingleEventManager:suspend()
	self._status = EventStatus.Suspended
end

function SingleEventManager:resume()
	-- If we're already resuming events for this instance, trying to resume
	-- again would cause a disaster.
	if self._isResuming then
		return
	end

	self._isResuming = true

	local index = 1

	-- More events might be added to the queue when evaluating events, so we
	-- need to be careful in order to preserve correct evaluation order.
	while index <= #self._suspendedEventQueue do
		local eventInvocation = self._suspendedEventQueue[index]
		local listener = self._listeners[eventInvocation[1]]
		local argumentCount = eventInvocation[2]

		-- The event might have been disconnected since suspension started; in
		-- this case, we drop the event.
		if listener ~= nil then
			-- Wrap the listener in a coroutine to catch errors and handle
			-- yielding correctly.
			local listenerCo = coroutine.create(listener)
			local success, result = coroutine.resume(
				listenerCo,
				self._instance,
				unpack(eventInvocation, 3, 2 + argumentCount)
			)

			-- If the listener threw an error, we log it as a warning, since
			-- there's no way to write error text in Roblox Lua without killing
			-- our thread!
			if not success then
				Logging.warn("%s", result)
			end
		end

		index = index + 1
	end

	self._isResuming = false
	self._status = EventStatus.Enabled
	self._suspendedEventQueue = {}
end

return SingleEventManager
