local K=require(script.Parent.Logging)local e="\067\104\097\110\103\101\046"local B={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 M={}M.__index=M function M.new(K)local e=setmetatable({_suspendedEventQueue={};_connections={};_listeners={},_status=B.Disabled;_isResuming=false,_instance=K},M)return e end function M.connectEvent(B,K,e)B:_connect(K,B._instance[K],e)end function M.connectPropertyChange(M,K,B)local x,u=pcall(function()return M._instance:GetPropertyChangedSignal(K)end)if not x 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(K),u),0)end M:_connect(e..K,u,B)end function M._connect(x,K,e,M)if M==nil then if x._connections[K]~=nil then x._connections[K]:Disconnect()x._connections[K]=nil end x._listeners[K]=nil else if x._connections[K]==nil then x._connections[K]=e:Connect(function(...)if x._status==B.Enabled then x._listeners[K](x._instance,...)elseif x._status==B.Suspended then local e=select("\035",...)table.insert(x._suspendedEventQueue,{K;e,...})end end)end x._listeners[K]=M end end function M.suspend(K)K._status=B.Suspended end function M.resume(e)if e._isResuming then return end e._isResuming=true local M=1 while M<=#e._suspendedEventQueue do local B=e._suspendedEventQueue[M]local x=e._listeners[B[1]]local u=B[2]if x~=nil then local M=coroutine.create(x)local P,J=coroutine.resume(M,e._instance,unpack(B,3,2+u))if not P then K.warn("\037\115",J)end end M=M+1 end e._isResuming=false e._status=B.Enabled e._suspendedEventQueue={}end return M
--[[
	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