local RunService = game:GetService("RunService")
local Packages = script.Parent.Parent.Packages
local Log = require(Packages.Log)
local InstanceMap = {}
InstanceMap.__index = InstanceMap
function InstanceMap.new(onInstanceChanged)
local self = {
fromIds = {},
fromInstances = {},
pausedUpdateInstances = {},
instancesToSignal = {},
onInstanceChanged = onInstanceChanged,
}
return setmetatable(self, InstanceMap)
end
function InstanceMap:size()
local size = 0
for _ in pairs(self.fromIds) do
size = size + 1
end
return size
end
function InstanceMap:stop()
for instance in pairs(self.fromInstances) do
self:removeInstance(instance)
end
end
function InstanceMap:__fmtDebug(output)
output:writeLine("InstanceMap {{")
output:indent()
local entries = {}
for id, instance in pairs(self.fromIds) do
local label = string.format("%s (%s)", instance:GetFullName(), instance.ClassName)
table.insert(entries, { id, label })
end
table.sort(entries, function(a, b)
return a[2] < b[2]
end)
for _, entry in ipairs(entries) do
output:writeLine("{}: {}", entry[1], entry[2])
end
output:unindent()
output:write("}")
end
function InstanceMap:insert(id, instance)
self:removeId(id)
self:removeInstance(instance)
self.fromIds[id] = instance
self.fromInstances[instance] = id
self:__connectSignals(instance)
end
function InstanceMap:removeId(id)
local instance = self.fromIds[id]
if instance ~= nil then
self:__disconnectSignals(instance)
self.fromIds[id] = nil
self.fromInstances[instance] = nil
end
end
function InstanceMap:removeInstance(instance)
local id = self.fromInstances[instance]
self:__disconnectSignals(instance)
if id ~= nil then
self.fromInstances[instance] = nil
self.fromIds[id] = nil
end
end
function InstanceMap:destroyInstance(instance)
local id = self.fromInstances[instance]
local descendants = instance:GetDescendants()
instance.Parent = nil
if id ~= nil then
self:removeId(id)
end
for _, descendantInstance in descendants do
self:removeInstance(descendantInstance)
end
end
function InstanceMap:destroyId(id)
local instance = self.fromIds[id]
if instance ~= nil then
self:destroyInstance(instance)
else
self:removeId(id)
end
end
function InstanceMap:pauseInstance(instance)
local id = self.fromInstances[instance]
if id == nil then
return
end
self.pausedUpdateInstances[instance] = true
end
function InstanceMap:unpauseInstance(instance)
self.pausedUpdateInstances[instance] = nil
end
function InstanceMap:unpauseAllInstances()
table.clear(self.pausedUpdateInstances)
end
function InstanceMap:__connectSignals(instance)
if instance:IsA("ValueBase") then
local signals = {
instance:GetPropertyChangedSignal("Name"):Connect(function()
self:__maybeFireInstanceChanged(instance, "Name")
end),
instance:GetPropertyChangedSignal("Value"):Connect(function()
self:__maybeFireInstanceChanged(instance, "Value")
end),
instance:GetPropertyChangedSignal("Parent"):Connect(function()
self:__maybeFireInstanceChanged(instance, "Parent")
end),
}
self.instancesToSignal[instance] = signals
else
self.instancesToSignal[instance] = instance.Changed:Connect(function(propertyName)
self:__maybeFireInstanceChanged(instance, propertyName)
end)
end
end
function InstanceMap:__maybeFireInstanceChanged(instance, propertyName)
Log.trace("{}.{} changed", instance:GetFullName(), propertyName)
if self.pausedUpdateInstances[instance] then
return
end
if self.onInstanceChanged == nil then
return
end
if RunService:IsRunning() then
return
end
self.onInstanceChanged(instance, propertyName)
end
function InstanceMap:__disconnectSignals(instance)
local signals = self.instancesToSignal[instance]
if signals ~= nil then
if typeof(signals) == "table" then
for _, signal in ipairs(signals) do
signal:Disconnect()
end
else
signals:Disconnect()
end
self.instancesToSignal[instance] = nil
end
end
return InstanceMap