Skip to content

Instantly share code, notes, and snippets.

@zeux
Last active September 5, 2024 20:24

Revisions

  1. zeux revised this gist Sep 1, 2022. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions gctracker.lua
    Original file line number Diff line number Diff line change
    @@ -19,6 +19,7 @@ PERFORMANCE OF THIS SOFTWARE.

    export type GCTracker = {
    -- track the lifetime of object obj; update will call dtor when obj is dead
    -- note: dtor should not reference obj directly or transitively since tracker keeps a strong reference to it
    track: (obj: any, dtor: () -> ()) -> any,
    -- forget previously tracked object; note, this needs to be passed the token that was returned by track
    forget: (token: any) -> (),
  2. zeux revised this gist Sep 1, 2022. No changes.
  3. zeux revised this gist Sep 1, 2022. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions gctracker.lua
    Original file line number Diff line number Diff line change
    @@ -17,7 +17,7 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    PERFORMANCE OF THIS SOFTWARE.
    ]]--

    type GCTracker = {
    export type GCTracker = {
    -- track the lifetime of object obj; update will call dtor when obj is dead
    track: (obj: any, dtor: () -> ()) -> any,
    -- forget previously tracked object; note, this needs to be passed the token that was returned by track
    @@ -94,4 +94,6 @@ local function GCTracker(): GCTracker
    end

    return self
    end
    end

    return { new = GCTracker }
  4. zeux revised this gist Sep 1, 2022. 1 changed file with 74 additions and 96 deletions.
    170 changes: 74 additions & 96 deletions gctracker.lua
    Original file line number Diff line number Diff line change
    @@ -18,102 +18,80 @@ PERFORMANCE OF THIS SOFTWARE.
    ]]--

    type GCTracker = {
    -- track the lifetime of object obj; update will call dtor when obj is dead
    track: (obj: any, dtor: () -> ()) -> any,
    -- forget previously tracked object; note, this needs to be passed the token that was returned by track
    forget: (token: any) -> (),
    -- update tracker, calling destructors for dead objects; if n is specified, do at most n iterations to amortize cost
    update: (n: number?) -> ()
    -- track the lifetime of object obj; update will call dtor when obj is dead
    track: (obj: any, dtor: () -> ()) -> any,
    -- forget previously tracked object; note, this needs to be passed the token that was returned by track
    forget: (token: any) -> (),
    -- update tracker, calling destructors for dead objects; if n is specified, do at most n iterations to amortize cost
    update: (n: number?) -> ()
    }

    local function GCTracker(): GCTracker
    -- key: token
    -- value: tracked object (weak)
    local tobj = {}
    setmetatable(tobj, { __mode = "vs" })

    -- key: token
    -- value: destructor
    local tdtor = {}

    local self = { lasttoken = nil }

    function self.track(obj, dtor)
    assert(type(dtor) == "function")

    local token = newproxy()

    tobj[token] = obj
    tdtor[token] = dtor

    return token
    end

    function self.forget(token)
    assert(type(token) == "userdata")
    assert(tdtor[token] ~= nil)

    tobj[token] = nil
    tdtor[token] = nil
    end

    function self.update(n: number?)
    assert(n == nil or type(n) == "number")

    if n then
    local lt = self.lasttoken
    if lt ~= nil and tdtor[lt] == nil then
    lt = nil
    end

    for i=1,n do
    local k, v = next(tdtor, lt)

    if k == nil then
    lt = nil
    break
    end

    if tobj[k] == nil then
    pcall(v)
    tdtor[k] = nil
    end

    lt = k
    end

    self.lasttoken = lt
    else
    for k,v in tdtor do
    if tobj[k] == nil then
    pcall(v)
    tdtor[k] = nil
    end
    end
    end
    end

    return self
    end

    --[[ Example, requires collectgarbage() (Luau REPL)
    local tracker = GCTracker()
    tracker.track({ x = 1 }, function() print("he's dead jim") end)
    local T = {x=2}
    tracker.track(T, function() print("he's also dead jim") end)
    tracker.update(1)
    tracker.update(2)
    print("first")
    collectgarbage()
    tracker.update()
    T = nil
    print("second")
    collectgarbage()
    tracker.update()
    ]]--
    -- key: token
    -- value: tracked object (weak)
    local tobj = {}
    setmetatable(tobj, { __mode = "vs" })

    -- key: token
    -- value: destructor
    local tdtor = {}

    local self = { lasttoken = nil }

    function self.track(obj, dtor)
    assert(type(dtor) == "function")

    local token = newproxy()

    tobj[token] = obj
    tdtor[token] = dtor

    return token
    end

    function self.forget(token)
    assert(type(token) == "userdata")
    assert(tdtor[token] ~= nil)

    tobj[token] = nil
    tdtor[token] = nil
    end

    function self.update(n: number?)
    assert(n == nil or type(n) == "number")

    if n then
    local lt = self.lasttoken
    if lt ~= nil and tdtor[lt] == nil then
    lt = nil
    end

    for i=1,n do
    local k, v = next(tdtor, lt)

    if k == nil then
    lt = nil
    break
    end

    if tobj[k] == nil then
    pcall(v)
    tdtor[k] = nil
    end

    lt = k
    end

    self.lasttoken = lt
    else
    for k,v in tdtor do
    if tobj[k] == nil then
    pcall(v)
    tdtor[k] = nil
    end
    end
    end
    end

    return self
    end
  5. zeux revised this gist Sep 1, 2022. No changes.
  6. zeux created this gist Sep 1, 2022.
    119 changes: 119 additions & 0 deletions gctracker.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,119 @@
    --!strict

    --[[
    BSD Zero Clause License
    Copyright (c) 2022 Arseny Kapoulkine
    Permission to use, copy, modify, and/or distribute this software for any
    purpose with or without fee is hereby granted.
    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
    AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    PERFORMANCE OF THIS SOFTWARE.
    ]]--

    type GCTracker = {
    -- track the lifetime of object obj; update will call dtor when obj is dead
    track: (obj: any, dtor: () -> ()) -> any,
    -- forget previously tracked object; note, this needs to be passed the token that was returned by track
    forget: (token: any) -> (),
    -- update tracker, calling destructors for dead objects; if n is specified, do at most n iterations to amortize cost
    update: (n: number?) -> ()
    }

    local function GCTracker(): GCTracker
    -- key: token
    -- value: tracked object (weak)
    local tobj = {}
    setmetatable(tobj, { __mode = "vs" })

    -- key: token
    -- value: destructor
    local tdtor = {}

    local self = { lasttoken = nil }

    function self.track(obj, dtor)
    assert(type(dtor) == "function")

    local token = newproxy()

    tobj[token] = obj
    tdtor[token] = dtor

    return token
    end

    function self.forget(token)
    assert(type(token) == "userdata")
    assert(tdtor[token] ~= nil)

    tobj[token] = nil
    tdtor[token] = nil
    end

    function self.update(n: number?)
    assert(n == nil or type(n) == "number")

    if n then
    local lt = self.lasttoken
    if lt ~= nil and tdtor[lt] == nil then
    lt = nil
    end

    for i=1,n do
    local k, v = next(tdtor, lt)

    if k == nil then
    lt = nil
    break
    end

    if tobj[k] == nil then
    pcall(v)
    tdtor[k] = nil
    end

    lt = k
    end

    self.lasttoken = lt
    else
    for k,v in tdtor do
    if tobj[k] == nil then
    pcall(v)
    tdtor[k] = nil
    end
    end
    end
    end

    return self
    end

    --[[ Example, requires collectgarbage() (Luau REPL)
    local tracker = GCTracker()
    tracker.track({ x = 1 }, function() print("he's dead jim") end)
    local T = {x=2}
    tracker.track(T, function() print("he's also dead jim") end)
    tracker.update(1)
    tracker.update(2)
    print("first")
    collectgarbage()
    tracker.update()
    T = nil
    print("second")
    collectgarbage()
    tracker.update()
    ]]--