Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
1

How do I delete an object that I made? (Lua object-oriented programming)

Asked by 4 years ago

I have a metatable (named Item) and I can't seem to find anything on google for this, but I want to add a function that will let me delete the item. I've looked at plenty of search results, and I either didn't understand them very well or it didn't work for me.

Here's my code:


local Item = {} Item.__index = Item function Item.new(ID, Amount) local NewItem = {} setmetatable(NewItem, Item) NewItem.ID = ID NewItem.Amount = Amount return NewItem end function Item:Delete() --not sure what to put here exactly --I've tried removing all of self's keys but that didn't do anything end local Apple = Item.new("apple", 1) print(Apple) --a table Apple:Delete() print(Apple) --still a table

I found that removing all references to a table effectively deletes it (or at least the garbage collector will delete it). I would do Apple = nil to remove the reference (I think?) but 1. I need to know for sure that the table is gone, and it's not gonna sit there taking up memory forever, and 2. I can't do that inside the :Delete() function. (self = nil does nothing)

1 answer

Log in to vote
1
Answered by 4 years ago
Edited 4 years ago

You are correct, when you remove all references to a table, it is deleted. This is taken care of by Lua's garbage collection:

However, there are reasons you might want some sort of cleanup function:

  • If your object subscribes to any events that will still exist when you want your object to be deleted, it needs to unsubscribe from them (or else the object can't be cleaned up because the event still holds a reference to it)
  • If your object added itself to a collection of (in your example) apples (perhaps so some system can track how many apples are in the game at the moment and iterate through them as necessary), naturally you'll need to remove it from that collection (unless that collection uses weak keys/values)
  • If you know the object will stay in memory for a while longer (ex maybe apples are referenced by an AppleTree which you intend to stay in memory for a longer period of time) and each Apple contains a reference to a large table or resource (that is specific to that apple), you should remove the references to that larger table/resource to allow it to be garbage collected.

For all other cases, you can expect that it will be removed from memory as needed. (In regular Lua you can force garbage collection to occur immediately with the debug library's collectgarbage, but Roblox has removed this functionality.)

For example, say these items are part of a player's inventory, and you have a dictionary tracking each player's inventory like playerInventory[player] = inventoryList. So long as you perform playerInventory[player] = nil when the player leaves the server, you don't need to do anything special to each inventory item, so long as the items don't do any of the above points.

If you wish to ensure that a piece of code isn't going to leak memory, you can use tables with weak keys/values to test, such as these functions I've written for that purpose:

local function waitForGarbageCollection() -- Creates garbage and waits for Lua to clean it up
    local t = setmetatable({}, {__mode="v"})
    t[1] = {}
    while t[1] do wait() end
end
local function LeaksMemory(create, cleanup)
    --  create:function() must return 1+ values.
    --  cleanup:function(...) -- optional; given what create returned
    --  Prints status message (with indices of leaked values if needed).
    local t = setmetatable({}, {__mode = "v"})
    local numValues
    do -- put create's values in temporary scope so they can be garbage collected
        local newValues = {create()}
        numValues = #newValues
        if numValues == 0 then error("create returned 0 values") end
        for i = 1, #newValues do
            t[i] = newValues[i]
        end
        if cleanup then cleanup(unpack(newValues)) end
    end
    waitForGarbageCollection()
    local numLeaked = 0
    local report = ": "
    for i = 1, numValues do
        if t[i] ~= nil then
            numLeaked = numLeaked + 1
            if numLeaked == 1 then
                report = report .. i
            else
                report = report .. ", " .. i
            end
        end
    end
    print(numValues > 1
        and string.format("%d/%d leaked%s", numLeaked, numValues, numLeaked > 0 and report or "")
        or numLeaked == 1 and "leaked" or "no leak")
end

-- Example usage:

local Item = {}
Item.__index = Item
function Item.new(ID, Amount)
    local NewItem = {}
    setmetatable(NewItem, Item)

    NewItem.ID = ID or 1
    NewItem.Amount = Amount or 0

    NewItem:Connect()

    return NewItem
end
function Item:Inc()
    self.Amount = self.Amount + 1
end
function Item:Connect()
    self.con = game:GetService("RunService").Heartbeat:Connect(function()
        self:Inc()
    end)
end
function Item:Destroy()
    if self.con then
        self.con:Disconnect()
    end
end

LeaksMemory(Item.new) -- leaked
LeaksMemory(Item.new, Item.Destroy) -- no leak

-- Note: LeaksMemory isn't a perfect indicator, as it assumes you'll give it a reference to everything that might be left in memory. For instance, if we change the Connect function to not refer to self...:

local a = 0
function Item:Connect()
    self.con = game:GetService("RunService").Heartbeat:Connect(function()
        a = a + 1
    end)
end
LeaksMemory(Item.new) -- no leak

-- ... you can see that it claims there is no leak when there really is one. If you can return a reference to everything that might be left in memory (from the function you pass in to LeaksMemory), you can get the full story:
function Item:Connect()
    local function func() a = a + 1 end
    self.con = game:GetService("RunService").Heartbeat:Connect(func)
    return func
end
function Item.new(ID, Amount)
    local NewItem = {}
    setmetatable(NewItem, Item)

    NewItem.ID = ID or 1
    NewItem.Amount = Amount or 0

    return NewItem, NewItem:Connect()
end
LeaksMemory(Item.new) -- 1/2 leaked: 2
LeaksMemory(Item.new, Item.Destroy) -- 0/2 leaked

-- The "1/2 leaked: 2" means that 1 of 2 items were leaked (and it was the item in the 2nd index that remained in memory), but calling Destroy ensured that even the function was removed from memory
0
Thanks for the explanation! I'm a little confused on the whole Item:Connect() part, do I need it or is it just an example? Also, shouldn't setting all the things inside an item to nil make it get garbage collected? Professor_Boxtrot 136 — 4y
0
Right, the whole "Item:Connect" part is illustrating an example of a memory leak. If you have an object that doesn't do any of the things I mentioned in the bullet points, you don't need any cleanup code, it'll happen automatically - setting an item's fields to "nil" just wastes time in that case. chess123mate 5873 — 4y
0
If it connected to an event (as in Item:Connect), setting item.con to nil won't help -- that simply removes item's reference to 'con', but the event also has a reference to con that you can't get rid of unless you Destroy the event (not possible for events like Heartbeat) or call :Disconnect on the connection. chess123mate 5873 — 4y
Ad

Answer this question