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


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 event = self._instance:GetPropertyChangedSignal(key)
	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
