ID:2379074
 
Sometimes you want to hang on to a reference to something without actually intending to keep that thing from deleting itself. Weak references are a good way to do this. Otherwise, referencing another object will require to to manage your references and circularize the relationship.

datum
var/tmp
__weakrefs = 0
proc
__weakref(weakref/r)
if(__weakrefs<=0)
global.__weakrefs[r.id] = list(r)
__weakrefs = 1
else
global.__weakrefs[r.id] += r
++__weakrefs

__weakderef(weakref/r)
if(__weakrefs<=1)
global.__weakrefs -= r.id
__weakrefs = 0
else
global.__weakrefs[r.id] -= r
--__weakrefs
r.id = null

Weakref()
var/weakref/r = new/weakref(src)
__weakref(r)
return r

Del()
if(__weakrefs)
var/ref = "\ref[src]"
var/list/l = global.__weakrefs[ref]
for(var/weakref/r in l)
r.id = null
__weakrefs = 0
global.__weakrefs -= ref
..()

var
list/__weakrefs = list()

weakref
var/tmp
id
proc
ref()
if(id)
return locate(id)
else
return null

New(datum/ref)
if(ref)
id = "\ref[ref]"

Del()
if(id)
ref()?.__weakderef(src)
if(id)
id = null
..()


An example for a potential use of this snippet:

ui_element
var
weakref/owner

New(mob/owner)
src.owner = owner.Weakref()
owner.client.screen += src
loc = null
..()


if you ever need to get the owner, you can do this quickly in a local variable via the weakref datum's ref() proc. Thanks to 512's proc chaining, you can operate directly on the result, or you can cache the result in a local variable:

owner.ref().client.screen += contents


or:

var/mob/m = owner.ref()
m.client.screen += contents


Be aware that ref() can return null if the object you are weak referencing gets deleted, and weakrefs will not be fully destroyed if this is the case. They will simply no longer resolve after the relationship is broken.
This is cool as shit. How does this work?
Alright, so if you aren't familiar with the "\ref[]" text macro, you should look into it.

The idea is that internally, objects are stored in what can be thought of as a big ass table.

In reality, there are a number of different tables. Each object has an internal typeid that determines where that object is stored. This means that obj, mobs, turfs, areas, datums, icons, etc. all have their own unique 1-byte id.

Every item is identified by 4 bytes. the first byte is the typeid, the other 3 are the unique id for that type. Mind you, this isn't the same as a "type". These are internal types. Everything derived from /obj is the same internal type, even if it has a crazy type path in DM.

Since it uses 3 bytes per unique id, that means that you can't have more than 16 million some-odd of any one internal type.

This is the value that the \ref[] text macro returns. It converts that typeid into hexadecimal notation, meaning an item's refid will look something like this:

"0x0C0000FF"

This is called a text ref, or an embedded object reference.


locate() isn't a single function. It does a number of things, depending on how it is used in context.

The basic formats are as follows:

locate(type)
locate(tag)
locate(x,y,z)
locate(textref)

locate(type) in list is actually shorthand for something like this:

proc
locate_type(type,list)
if(!list) list = world.contents
var/datum/found, count = 0, len = length(__filter_list)
while(count++<len)
test = __filter_list[count]
if(test && istype(test,type))
return found
return null


This is an exhaustive search with first-found ejection behavior.

locate(tag) is a really neat use. atoms have something called tags, which when set will add the object to a global registry of objects associated by their tag. So locate(tag) would look more or less like this:

return tagged_objs[sometag]


locate(x,y,z) is actually a map lookup. This would map internally into something more like this:

return world_map[z][y][x]


lastly, the ref lookup would get the value of the first byte to determine which table to look the object up in, and then the other 3 bytes would be used to determine which position in the table the object we want is in. In theory, this is a simple lookup as well:

return object_tables[type][id]



My weak reference datum is a wrapper around a regular old textref. The library stores a global list associating an object with the weak references to it. textrefs are already weak references, but they aren't safe. The reason they aren't safe, is because a weak reference doesn't prevent an object from being deleted, and old reference ids are recycled as soon as possible. So it's entirely possible that the object you are trying to grab by textref doesn't exist, or a totally different, or even worse, a very similar one has taken its place. This can result in some nasty bugs if you aren't aware of it ahead of time.

To make this safe, I've basically told my library to keep track of which objects have been weak referenced. When those objects are deleted (either manually, or by garbage collection, the object knows to look up its own reference id, then tell all of the weakreferences to the object to discard the textref they are hanging on to --because it is now invalid.

This ensures that the object you got a weak reference to will always be the one you get back out of the weakref.
That's really interesting. I've never really actually read the documentation on the ref macro before. I remember using it with Topic a lot and I'm surprised I never looked up what it was before.

Login to reply.