What is setUserData and why do I need it?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
ivan
Party member
Posts: 1454
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: What is setUserData and why do I need it?

Post by ivan » 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?

User avatar
pgimeno
Party member
Posts: 1690
Joined: Sun Oct 18, 2015 2:58 pm

Re: What is setUserData and why do I need it?

Post by pgimeno » Sun Jul 14, 2019 11:50 am

You will have a strong reference to the body somewhere, and you can access the associated data using the body as the key to the weak table. The weak key means that the element will be automatically removed from the table when the body disappears.

What raidho and I are trying to say is that setUserData/getUserData is redundant, as it could have been implemented in pure Lua (and it can be implemented for any other object as well that doesn't already support it):

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
...
Now that I think about it, I no longer think it's thread safe. The context that the function expects won't exist in a different thread, so it can't execute safely.

User avatar
ivan
Party member
Posts: 1454
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: What is setUserData and why do I need it?

Post by ivan » Sun Jul 14, 2019 12:30 pm

pgimeno wrote:
Sat Jul 13, 2019 9:41 am
You will have a strong reference to the body somewhere
If you have a strong reference somewhere else in the code - the weak ref will never be collected.
I think the user shouldn't have to store a reference in Lua, Box2D objects are destroyed explicitly anyways.

User avatar
pgimeno
Party member
Posts: 1690
Joined: Sun Oct 18, 2015 2:58 pm

Re: What is setUserData and why do I need it?

Post by pgimeno » Sun Jul 14, 2019 12:49 pm

ivan wrote:
Sun Jul 14, 2019 12:30 pm
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.

User avatar
ivan
Party member
Posts: 1454
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: What is setUserData and why do I need it?

Post by ivan » Sun Jul 14, 2019 12:55 pm

I don't get it, can you show a code example of how that would work?

User avatar
pgimeno
Party member
Posts: 1690
Joined: Sun Oct 18, 2015 2:58 pm

Re: What is setUserData and why do I need it?

Post by pgimeno » Sun Jul 14, 2019 6:59 pm

This example adds setUserData/getUserData capabilities to an Image object and demonstrates them. It also shows the count of elements in the objectData table. When pressing the space bar, the image is deleted and garbage collection is run immediately to show how the weak element is deleted. For some reason, the weak table element isn't removed unless I add two collectgarbage() in a row, but in normal use, that should happen automatically (there just isn't anything going on allocation-wise in this program, so the garbage collector doesn't run unless forced).

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

User avatar
raidho36
Party member
Posts: 1804
Joined: Mon Jun 17, 2013 12:00 pm

Re: What is setUserData and why do I need it?

Post by raidho36 » Sun Jul 14, 2019 9:24 pm

pgimeno wrote:
Sun Jul 14, 2019 11:50 am
Now that I think about it, I no longer think it's thread safe. The context that the function expects won't exist in a different thread, so it can't execute safely.
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.

User avatar
ivan
Party member
Posts: 1454
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: What is setUserData and why do I need it?

Post by ivan » Mon Jul 15, 2019 4:01 am

It's a good try, but I don't see how you can ensure that the user doesn't lose his strong reference. I think this would be very difficult to debug especially for box2d callbacks, etc.
Furthermore, if you are going to hack into the debug.registry you might as well overwrite body.create/destroy and don't use weak keys at all.

User avatar
pgimeno
Party member
Posts: 1690
Joined: Sun Oct 18, 2015 2:58 pm

Re: What is setUserData and why do I need it?

Post by pgimeno » Mon Jul 15, 2019 11:54 am

ivan wrote:
Mon Jul 15, 2019 4:01 am
It's a good try, but I don't see how you can ensure that the user doesn't lose his strong reference. I think this would be very difficult to debug especially for box2d callbacks, etc.
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
--]]
Notice how the body object is the same, yet the internal LÖVE reference does not count as a strong reference. I consider this to be a bug in LÖVE. Indeed the __gc metamethod of Body is called twice. That should not happen.

ivan wrote:
Mon Jul 15, 2019 4:01 am
Furthermore, if you are going to hack into the debug.registry you might as well overwrite body.create/destroy and don't use weak keys at all.
That was purely for illustrative purposes.

User avatar
raidho36
Party member
Posts: 1804
Joined: Mon Jun 17, 2013 12:00 pm

Re: What is setUserData and why do I need it?

Post by raidho36 » Mon Jul 15, 2019 9:41 pm

It does counts internal references as strong ones, however it doesn't actually keep them. Box2D's internal references are not counted as strong ones because then it would be impossible for physics objects to get garbage collected.

Not sure about calling __gc twice though. Only the source code investigation could tell.

Post Reply

Who is online

Users browsing this forum: Google [Bot] and 7 guests