Behaviour of Instance.new()

Recently I wrote an module function that gets an instance while preventing dupes of it and saves performance:

MainFramework.GetInstance = function (Type,Name,Parent,PropertyList)
    local InstanceToGet = Parent:FindFirstChild(Name) or Instance.new(Type)
    InstanceToGet.Parent = Parent
    InstanceToGet.Name = Name

    for property,value in pairs(PropertyList) do
        InstanceToGet[property] = value
    end

    return InstanceToGet
end

So for example:

--requiring the module as main--
local part1 = main.GetInstance("Part","Part1",workspace,{Position=Vector3.new(0,10,0),Anchored=true})
--creates a part named Part1 in workspace with position of 0,10,0 and anchored, saved to var part1--
main.GetInstance("Part","Part1",workspace,{Anchored=false})
--wont create new part as Part1 already exist in workspace, but changed property Anchored to false--
local part2 = main.GetInstance("Part","Part2",workspace)
--creates Part2 in workspace with default properties and saved into part2--

This works very well to prevent dupes while making sure I have the instance I needed, but something very basic is still not yet solved and has been in my mind for quite a while already, that is:

If I created an instance using Instance.new but did not parent it to anything. Where is it stored? Will it cost me performance and memory? What is it’s lifetime in game? How will dupes created like this behave?

If anyone can answer my question I will appreciate it alot since I think documentation of Instance.new() is not quite sufficient on roblox…

1 Like

When you create an Instance without a parent it’s stored under nil. This means that it lives outside of the game environment and any game engine rendering/physics won’t be applied to it. If you create that type of instance you can only manipulate it as long you have a reference to it(returning it, setting a tag, etc) because you can’t directly fetch it using a path(nil isn’t indexable). As long you have access to it you can set its parent to a normal parent, a good practice is to configure a part before setting its parent, so other scripts looking for such configurations won’t need to wait for them to be set(they just wait for the object to be added to a parent).

Also, exploiters have code executors that allow them to fetch instances parented under nil, so this practice should not be used for game security.

Such objects most likely won’t impact performance because as mentioned above the game engine doesn’t consider them(no physics, no rendering, no collisions, etc). However, they may fill up the memory over time if they aren’t correctly handled/destroyed. I’m not entirely sure how the garbage collector handles said objects, perhaps it could be deleting objects under nil itself if it notices there are no references to them/they aren’t accessible.

Also since you can’t directly check for instances under nil, you will have to use something else other than FindFirstChild to check for duplicates. Perhaps you could add a tag to said objects and have an edge case like this:

local CollectionService = game:GetService("CollectionService")
local InstanceToGet = nil

if Parent then
	InstanceToGet = Parent:FindFirstChild(Name)
else
	local objects = CollectionService:GetTagged("NilObject")
	for _, object in pairs(objects) do
		if object.Name == Name then 
			InstanceToGet = object
			break
		end
	end
end
InstanceToGet = InstanceToGet or Instance.new(Type)

local function AncestryChanged(child: Instance, parent: Instance?)
	local found = table.find(child:GetTags(), "NilObject")
	if parent and found then
		child:RemoveTag("NilObject")
	elseif not parent and not found then
		child:AddTag("NilObject")
	end
end

InstanceToGet.AncestryChanged:Connect(AncestryChanged)
AncestryChanged(InstanceToGet, Parent)
--rest of your code
2 Likes

Thanks for explaining in details and examples! Definitely going to check out tags and collection service since I haven’t really gone into them other than part tagging. Thanks

1 Like

That is correct. Simply doing something like List[Object] = nil or List[Index] = nil and making sure that the instance isn’t referenced in any variables of any script (called a “strong reference”) will lead to it eventually getting removed completely, however something to note is that event connections are also references:

MyPart.Touched:Connect(function(...)
    -- something
end)

-- MyPart is never removed because the .Touched event
-- connection keeps a reference to it
MyPart.Parent = nil

This can be solved in two ways:

MyPart:Destroy()

:Destroy disconnects all connected event connections and thus removes the reference, but you could also disconnect the events manually:

local TouchedEvent = MyPart.Touched:Connect(function(...)
    -- some code
end)

MyPart.Parent = nil

-- Disconnects the connection and removes the reference
TouchedEvent:Disconnect()

There is a more in-depth explanation on this matter here:

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.