Re: What is setUserData and why do I need it?
Posted: Sun Jul 14, 2019 3:22 am
Well if I already have a strong reference elsewhere it sort of defeats the purpose - somebody has to manually modify those refs, right?
Code: Select all
local objectData = setmetatable({}, {__mode='k'})
local function setUserData(self, data)
objectData[self] = data
end
local function getUserData(self)
return objectData[self]
end
local reg = debug.getregistry()
reg.Body.setUserData = setUserData
reg.Body.getUserData = getUserData
reg.Fixture.setUserData = setUserData
reg.Fixture.getUserData = getUserData
...
-- it can also be used for other objects:
reg.Image.setUserData = setUserData
reg.Image.getUserData = getUserData
reg.Canvas.setUserData = setUserData
reg.Canvas.getUserData = getUserData
...
If you have a strong reference somewhere else in the code - the weak ref will never be collected.
That's the point. With this method, the user data will be associated to the object for as long as the object is alive. If you destroy the object, the associated data will be automatically destroyed as well, which is the whole point. That's what lets you implement setUserData/getUserData in Lua, and why I consider them redundant in the physics classes.
Code: Select all
local objectData = setmetatable({}, {__mode='k'})
local function setUserData(self, data)
objectData[self] = data
end
local function getUserData(self)
return objectData[self]
end
local reg = debug.getregistry()
reg.Image.setUserData = setUserData
reg.Image.getUserData = getUserData
local function drawImg(img, x, y)
local data = img:getUserData()
love.graphics.draw(img, x, y, data.angle, data.scale, data.scale,
data.ox, data.oy)
end
local img = love.graphics.newImage('image.jpg')
img:setUserData({angle = math.rad(45), scale = 0.5,
ox = img:getWidth()/2, oy = img:getHeight()/2})
local function numElems(t)
local count = 0
for k, v in next, objectData do
count = count + 1
end
return count
end
function love.draw()
if img then
drawImg(img, love.graphics.getWidth()/2, love.graphics.getHeight()/2)
end
love.graphics.print(tostring(numElems(objectData)))
end
function love.keypressed(k)
if k == "space" then
img = nil
collectgarbage()
collectgarbage()
end
return k == "escape" and love.event.quit()
end
The other thread will simply have its own separate userdata storage, on account of residing in a totally separate Lua state. It's perfectly thread-safe.
I think I get what you mean. I assume that the user will have strong references to the objects they want to manipulate, but that may not be the case. The internal Box2D references don't count as strong Lua references, and the weakref can be destroyed even if the object is alive. Here's a demonstration:
Code: Select all
local objectData = setmetatable({}, {__mode='k'})
local world = love.physics.newWorld(0, 0, true)
print("Before creating body, world contains "..#world:getBodies().." bodies")
local body = love.physics.newBody(world, 0, 0)
objectData[body] = {}
print("Body reference: " .. tostring(body))
print("After creating body, world contains "..#world:getBodies().." bodies")
local function numElems(t)
local count = 0
for k, v in next, objectData do
count = count + 1
end
return count
end
local oldgc = debug.getregistry().Body.__gc
debug.getregistry().Body.__gc = function(...)
print("GC run", ...)
oldgc(...)
end
print("Before GC, obj.data table contains "..numElems(objectData).." elements")
body = nil
collectgarbage()
collectgarbage()
print("After GC, obj.data table contains "..numElems(objectData).." elements")
print("Body reference: " .. tostring(world:getBodies()[1]))
love.event.quit()
--[[ Output:
Before creating body, world contains 0 bodies
Body reference: Body: 0x55fb73722640
After creating body, world contains 1 bodies
Before GC, obj.data table contains 1 elements
GC run Body: 0x55fb73722640
After GC, obj.data table contains 0 elements
Body reference: Body: 0x55fb73722640
GC run Body: 0x55fb73722640
--]]
That was purely for illustrative purposes.