local n={AutoSaveProfiles=30,RobloxWriteCooldown=7,ForceLoadMaxSteps=8,AssumeDeadSessionLock=1800;IssueCountForCriticalState=5,IssueLast=120,CriticalStateLast=120,MetaTagsUpdatedValues={ProfileCreateTime=true,SessionLoadCount=true;ActiveSession=true;ForceLoadSession=true;LastUpdate=true}}local M do local n={}local h=nil local function P(n,...)local M=h h=nil n(...)h=M end local function L(...)P(...)while true do P(coroutine.yield())end end local V={}V.__index=V function V.Disconnect(n)if n._is_connected==false then return end n._is_connected=false n._script_signal._listener_count-=1 if n._script_signal._head==n then n._script_signal._head=n._next else local M=n._script_signal._head while M~=nil and M._next~=n do M=M._next end if M~=nil then M._next=n._next end end if n._disconnect_listener~=nil then if not h then h=coroutine.create(L)end task.spawn(h,n._disconnect_listener,n._disconnect_param)n._disconnect_listener=nil end end local f={}f.__index=f function f.Connect(P,n,M,h)local L={_listener=n;_script_signal=P;_disconnect_listener=M;_disconnect_param=h,_next=P._head,_is_connected=true}setmetatable(L,V)P._head=L P._listener_count+=1 return L end function f.GetListenerCount(n)return n._listener_count end function f.Fire(n,...)local M=n._head while M~=nil do if M._is_connected==true then if not h then h=coroutine.create(L)end task.spawn(h,M._listener,...)end M=M._next end end function f.FireUntil(M,n,...)local h=M._head while h~=nil do if h._is_connected==true then h._listener(...)if n()~=true then return end end h=h._next end end function n.NewScriptSignal()return{_head=nil,_listener_count=0,Connect=f.Connect;GetListenerCount=f.GetListenerCount;Fire=f.Fire;FireUntil=f.FireUntil}end M={NewScriptSignal=n.NewScriptSignal;ConnectToOnClose=function(n,M)if(game:GetService("\082\117\110\083\101\114\118\105\099\101")):IsStudio()==false or M==true then game:BindToClose(n)end end}end


local SETTINGS = {

	AutoSaveProfiles = 30, -- Seconds (This value may vary - ProfileService will split the auto save load evenly in the given time)
	RobloxWriteCooldown = 7, -- Seconds between successive DataStore calls for the same key
	ForceLoadMaxSteps = 8, -- Steps taken before ForceLoad request steals the active session for a profile
	AssumeDeadSessionLock = 30 * 60, -- (seconds) If a profile hasn't been updated for 30 minutes, assume the session lock is dead
		-- As of writing, os.time() is not completely reliable, so we can only assume session locks are dead after a significant amount of time.
	
	IssueCountForCriticalState = 5, -- Issues to collect to announce critical state
	IssueLast = 120, -- Seconds
	CriticalStateLast = 120, -- Seconds
	
	MetaTagsUpdatedValues = { -- Technical stuff - do not alter
		ProfileCreateTime = true,
		SessionLoadCount = true,
		ActiveSession = true,
		ForceLoadSession = true,
		LastUpdate = true,
	},
	
}

local Madwork -- Standalone Madwork reference for portable version of ProfileService
do

	local MadworkScriptSignal = {}

	local FreeRunnerThread = nil
	
	local function AcquireRunnerThreadAndCallEventHandler(fn, ...)
		local acquired_runner_thread = FreeRunnerThread
		FreeRunnerThread = nil
		fn(...)
		FreeRunnerThread = acquired_runner_thread
	end
	
	local function RunEventHandlerInFreeThread(...)
		AcquireRunnerThreadAndCallEventHandler(...)
		while true do
			AcquireRunnerThreadAndCallEventHandler(coroutine.yield())
		end
	end
	
	-- ScriptConnection object:

	local ScriptConnection = {
		--[[
			_listener = listener,
			_script_signal = script_signal,
			_disconnect_listener = disconnect_listener,
			_disconnect_param = disconnect_param,
			
			_next = next_script_connection,
			_is_connected = is_connected,
		--]]
	}
	ScriptConnection.__index = ScriptConnection

	function ScriptConnection:Disconnect()

		if self._is_connected == false then
			return
		end

		self._is_connected = false
		self._script_signal._listener_count -= 1

		if self._script_signal._head == self then
			self._script_signal._head = self._next
		else
			local prev = self._script_signal._head
			while prev ~= nil and prev._next ~= self do
				prev = prev._next
			end
			if prev ~= nil then
				prev._next = self._next
			end
		end

		if self._disconnect_listener ~= nil then
			if not FreeRunnerThread then
				FreeRunnerThread = coroutine.create(RunEventHandlerInFreeThread)
			end
			task.spawn(FreeRunnerThread, self._disconnect_listener, self._disconnect_param)
			self._disconnect_listener = nil
		end

	end
	
	-- ScriptSignal object:

	local ScriptSignal = {
		--[[
			_head = nil,
			_listener_count = 0,
		--]]
	}
	ScriptSignal.__index = ScriptSignal

	function ScriptSignal:Connect(listener, disconnect_listener, disconnect_param) --> [ScriptConnection]

		local script_connection = {
			_listener = listener,
			_script_signal = self,
			_disconnect_listener = disconnect_listener,
			_disconnect_param = disconnect_param,

			_next = self._head,
			_is_connected = true,
		}
		setmetatable(script_connection, ScriptConnection)

		self._head = script_connection
		self._listener_count += 1

		return script_connection

	end

	function ScriptSignal:GetListenerCount() --> [number]
		return self._listener_count
	end

	function ScriptSignal:Fire(...)
		local item = self._head
		while item ~= nil do
			if item._is_connected == true then
				if not FreeRunnerThread then
					FreeRunnerThread = coroutine.create(RunEventHandlerInFreeThread)
				end
				task.spawn(FreeRunnerThread, item._listener, ...)
			end
			item = item._next
		end
	end

	function ScriptSignal:FireUntil(continue_callback, ...)
		local item = self._head
		while item ~= nil do
			if item._is_connected == true then
				item._listener(...)
				if continue_callback() ~= true then
					return
				end
			end
			item = item._next
		end
	end

	function MadworkScriptSignal.NewScriptSignal() --> [ScriptSignal]
		return {
			_head = nil,
			_listener_count = 0,
			Connect = ScriptSignal.Connect,
			GetListenerCount = ScriptSignal.GetListenerCount,
			Fire = ScriptSignal.Fire,
			FireUntil = ScriptSignal.FireUntil,
		}
	end

	-- Madwork framework namespace:
	
	Madwork = {
		NewScriptSignal = MadworkScriptSignal.NewScriptSignal,
		ConnectToOnClose = function(task, run_in_studio_mode)
			if game:GetService("RunService"):IsStudio() == false or run_in_studio_mode == true then
				game:BindToClose(task)
			end
		end,
	}

end
