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