UUID "Service" for Instances

Have you ever wanted a way to uniquely identify instances?
Have you ever wanted a fast way to check up on an Inst later without holding a reference to it or naming it some unique way?
Then this is the script for you lol.
As far as I can tell this creates no lag. I can not guarantee at this time the server won’t shut down after a week as it remembers all UUID created.

Description:
	Creates a UUID for all instances and is guaranteed 
		to never be the same in the same server.
	The uuid string will never be deleted but the inst referance to it can be.
Useage:
	_G.uuidTable[inst] 
		- returns a string uuid, or nil if not found or inst was destroyed
	_G.uuidTable[string uuid]
		- returns inst, string "null" if inst was Destroyed, or nil if uuid was never created
Notes:
	-Might Take a sec or two to start up 
		if NOT _G.uuidTable == NIL then you are good to go
	-I have no idea if this crosses the client/server boundry, it should, I think.
	-This remembers EVERY UUID CREATED IN THE SERVER
		I do not know if that is a problem for a server that runs for a week
~DrWhoInTARDIS
--[[
	Description:
		Creates a UUID for all instances and is guaranteed 
			to never be the same in the same server.
		The uuid string will never be deleted but the inst referance to it can be.
	Useage:
		_G.uuidTable[inst] 
			- returns a string uuid or nil if not found or inst was destroyed
		_G.uuidTable[string uuid]
			- returns inst, string "null" if inst was Destroyed, or nil if uuid was never created
	Notes:
		-Might Take a sec or two to start up 
			if NOT _G.uuidTable == NIL then you are good to go
		-I have no idea if this crosses the client/server boundry, it should, I think.
		-This remembers EVERY UUID CREATED IN THE SERVER
			I do not know if that is a problem for a server that runs for a week
	~DrWhoInTARDIS
]]

math.randomseed(os.clock())
random = math.random

function uuid() --function from https://gist.github.com/jrus/3197011
	local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
	return string.gsub(template, '[xy]', function (c)
		local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
		return string.format('%x', v)
	end)
end

uuidTable = {
	size = {
		["Insts"] = 0,
		["IDs"] = 0,
		add = function(self,n) 
			for k,v in pairs(self) do 
				if type(v) == type(1) then
					self[k] = self[k] + n
				end
			end
		end,
		sub = function(self,n) 
			self.Insts = self.Insts - n
		end,
	}
}
setmetatable(uuidTable.size, {
	__call = function(t)
		local total = 0
		local outString = ""
		for k,v in pairs(t) do 
			if type(v)==type(1) then
				outString = outString .. " #" .. tostring(k) .. ": " .. tostring(v)
				total = total + v
			end
		end
		return "UUID: Size: " .. tostring(total) .. outString
	end
})
function putUUID(thing) 
	local myUUID = uuid()

	if not uuidTable[myUUID] == nil then
		print("ERROR generating ID!!!! Same UUID as generated!", myUUID)
		putUUID(thing)
	elseif not uuidTable == nil then 
		print("ERROR giving UUID!!! Already has UUID!", thing:GetFullName())
	else
		uuidTable[myUUID] = thing
		uuidTable[thing] = myUUID
		uuidTable.size:add(1)
	end
end

for _,des in ipairs(game:GetDescendants()) do
	local good, message = pcall(putUUID,des)
	--if not good then print(message)end
end

game.DescendantAdded:Connect(function(des)
	local good, message = pcall(putUUID,des)
	--if not good then print(message)end	
end)
game.DescendantRemoving:Connect(function(des)
	local found = uuidTable[des]
	if found == nil then 
		print("ERROR Removing ID!!! Could not find UUID for",des:GetFullName())
	else
		--print("removing",des:GetFullName(),found)
		uuidTable[found] = "null"
		uuidTable[des] = nil
		uuidTable.size:sub(1)
	end
end)

_G.uuidTable = uuidTable

print(uuidTable.size())
while wait(20) do
	print(_G.uuidTable.size())
end
1 Like

I’d highly recommend against using _G or metatables for this. Additionally, you’re not using weak keys, which means that UUIDs for instances that aren’t reachable anymore will be kept around forever. This would be useful if it was possible to retrieve an instance by UUID, but that functionality isn’t provided. Basically, you have a memory leak.

You should probably use a ModuleScript instead. I’ve gone ahead and written a UuidStore example for you, complete with Luau types and documentation. You’re free to use it as long as you keep my name in there somewhere. This features both strong and weak references (weak by default), reference upgrading/downgrading, retrieving an Instance by UUID, and so on. Took me about an hour and 10 minutes.

--!strict

--------------------------------------------------------------------------------
--- UuidStore is a small module to help keep track of Instances through the use
--- of UUIDs (universally-unique identifiers). Although Instances can be stored
--- in any variable just like strings, sometimes strings can be used where
--- Instances cannot, for example when trying to pass certain Instances through
--- RemoteEvents.
---
--- UuidStore provides no synchronization between client and server, so UUIDs
--- are local only to the client (or server), but if one source becomes the
--- authoritative source of UUIDs (like how Instance replication IDs work), you
--- can communicate information about Instances that the other side cannot see,
--- for example ones in ServerStorage or in the local Camera.
---
--- UuidStore provides both strong and weak UUID references, where strong
--- references are guaranteed to always be retrievable later, and weak
--- references are guaranteed not to inhibit garbage collection.
---
--- To retrieve an Instance's UUID, call UuidStore:getUuid(Instance, boolean?).
--- The optional boolean argument can be set to `true` to create a strong
--- reference if the Instance did not already have a UUID. To upgrade existing
--- references, you can use UuidStore:upgradeWeakRef(uuid: string).
---
--- Once you have a UUID, you can retrieve the Instance it refers to by calling
--- the method UuidStorage:getInstanceByUuid(uuid: string). Note that this only
--- works on weak references if the Instance hasn't been garbage-collected; if
--- it has, then the method returns nil.
---
--- Use Luau autocomplete or read the module source for additional functions and
--- documentation.
---
--- Module donated by qwertyexpert to DrWhoInTARDIS
--------------------------------------------------------------------------------

local UuidStore = {}

UuidStore.strongInstances = {} :: {[Instance]: string}
UuidStore.strongUuids = {} :: {[string]: Instance}
UuidStore.weakInstances = setmetatable({} :: {[Instance]: string}, {__mode = 'k'})
UuidStore.weakUuids = {} :: {[string]: boolean}

local hex = {[0] = '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}

local function generateUuid(): string
	return string.gsub('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', '[xy]', function(c)
		return hex[if c == 'x' then math.random(0, 0xf) else math.random(8, 0xb)]
	end)
end

--------------------------------------------------------------------------------
--- Generates a new UUID which is guaranteed not to collide with any existing
--- UUIDs in the UuidStore.
---
--- Multiple calls to this function MAY return the same UUID unless the returned
--- UUIDs are used in the store.
--------------------------------------------------------------------------------
function UuidStore.generateUuid(self: typeof(UuidStore)): string
	local uuid = generateUuid()
	while self.strongUuids[uuid] or self.weakUuids[uuid] do uuid = generateUuid() end
	return uuid
end

--------------------------------------------------------------------------------
--- Registers a new UUID in the store, for example if you are receiving UUIDs
--- from a server and need to track them locally. This is an advanced function
--- that can corrupt state, especially if this UUID exists in the store already.
--- Take care only to call it when you're sure this UUID and Instance have BOTH
--- never been seen before.
--------------------------------------------------------------------------------
function UuidStore.registerUuid(self: typeof(UuidStore), instance: Instance, uuid: string, strong: boolean)
	if strong then
		self.strongInstances[instance], self.strongUuids[uuid] = uuid, instance
	else
		self.weakInstances[instance], self.weakUuids[uuid] = uuid, true
	end
end

--------------------------------------------------------------------------------
--- Gets the UUID of an Instance. This method can never fail and will commit a
--- new UUID to the store if one is not already found.
---
--- The optional `strong` argument dictates whether to use a strong reference or
--- not, which will inhibit garbage collection but guarantee that
--- getInstanceByUuid will always be able to find the Instance.
--------------------------------------------------------------------------------
function UuidStore.getUuid(self: typeof(UuidStore), instance: Instance, strong: boolean?): string
	local found = self.strongInstances[instance] or self.weakInstances[instance]

	if not found then
		local strong = strong == true
		local uuid = self:generateUuid()
		self:registerUuid(instance, uuid, strong)
		found = uuid
	end

	return found
end

--------------------------------------------------------------------------------
--- Returns the Instance with the specified UUID. This method MAY return nil iff
--- the Instance only had a weak reference and was garbage-collected since its
--- UUID was generated.
--------------------------------------------------------------------------------
function UuidStore.getInstanceByUuid(self: typeof(UuidStore), uuid: string): Instance?
	local found = self.strongUuids[uuid] or self.weakUuids[uuid]
	if typeof(found) == 'Instance' then return found end -- if found something in strong, return it
	if found == nil then return nil end -- if found nothing, return nil

	-- found is probably true which means there MAY be a weak reference in weakInstances

	for instance, instanceUuid in pairs(self.weakInstances) do
		if instanceUuid == uuid then return instance end
	end

	return nil
end

--------------------------------------------------------------------------------
--- Returns whether or not the specified UUID holds a strong reference to an
--- Instance. A return value of false does not necessarily mean that the UUID is
--- in the store.
--------------------------------------------------------------------------------
function UuidStore.uuidIsStrong(self: typeof(UuidStore), uuid: string): boolean
	return self.strongUuids[uuid] ~= nil
end

--------------------------------------------------------------------------------
--- Returns whether or not the specified UUID holds or held a weak reference to
--- an Instance. A return value of false does not necessarily mean that the UUID
--- is in the store, and a return value of true does not necessarily mean that
--- the Instance hasn't been garbage-collected.
--------------------------------------------------------------------------------
function UuidStore.uuidIsWeak(self: typeof(UuidStore), uuid: string): boolean
	return self.weakUuids[uuid] == true
end

--------------------------------------------------------------------------------
--- Downgrades a UUID's strong reference to a weak reference. This is a no-op if
--- the reference is already weak. This enables the Instance to be
--- garbage-collected.
--------------------------------------------------------------------------------
function UuidStore.downgradeStrongRef(self: typeof(UuidStore), uuid: string)
	local instance = self.strongUuids[uuid]

	if instance then
		self.weakInstances[instance], self.weakUuids[uuid] = uuid, true
		self.strongInstances[instance], self.strongUuids[uuid] = nil, nil
	end
end

--------------------------------------------------------------------------------
--- Upgrades a UUID's weak reference to a strong reference. This is a no-op if
--- the reference is already strong or if the Instance has been
--- garbage-collected. Returns false if the reference is weak and the Instance
--- has been garbage-collected.
--------------------------------------------------------------------------------
function UuidStore.upgradeWeakRef(self: typeof(UuidStore), uuid: string): boolean
	local instance = if self.weakUuids[uuid] then self:getInstanceByUuid(uuid) else nil

	if instance then
		self.strongInstances[instance], self.strongUuids[uuid] = uuid, instance
		self.weakInstances[instance], self.weakUuids[uuid] = nil, nil
		return true
	end

	return UuidStore:uuidIsStrong(uuid)
end

return UuidStore
1 Like