ID:2205087
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
Currently, appearances are basically a concept in BYOND you can't really do much about except for batching your appearance updates and shit. The apperance var is a reference to a /image object instead of an actual image, and changing it gives a runtime error, something about having a null loc.

I don't think it would hurt to allow developers to access appearances, and it would help a lot if we could manage appearances ourselves. Here's what I think needs to happen:

1. Make an actual type for appearances, and make the appearance var actually have the type. Give the type a reference_count var or something like that with the number of atoms currently using that appearance.

2. Make it so when appearances are modified, the appearances are properly updated on all clients.

3. Add a world.appearances var that contains a list of all the appearances. (Okay, this is probably optional)

This would allow developers to interact with appearances in a meaningful way and optimize code which would be otherwise unoptimisable.
I think this would be pretty cool. Instead of looping through a bunch of atoms with the same appearance to set their appearance, you just change the appearance that they all refer to (although, this affects everything with the same appearance, not just the intended targets...).

This would allow things like Hazordhu's seasons to change all seasonal objects from one appearance to another with a single assignment (per appearance), rather than having to do it gradually to avoid stutter.
Monster860 wrote:
Currently, appearances are basically a concept in BYOND you can't really do much about except for batching your appearance updates and shit. The apperance var is a reference to a /image object instead of an actual image, and changing it gives a runtime error, something about having a null loc.

Mutable appearances derive from /image, largely to simplify issues of var access; but mutable_appearance.appearance, just like the appearance var of any atom or image, is a regular immutable appearance referenc and is not itself an /image or a derivative thereof.

I don't think it would hurt to allow developers to access appearances, and it would help a lot if we could manage appearances ourselves. Here's what I think needs to happen:

1. Make an actual type for appearances, and make the appearance var actually have the type. Give the type a reference_count var or something like that with the number of atoms currently using that appearance.

This doesn't make any sense. What it sounds like you're asking is for the appearance of every atom to be mutable in such a way that if you alter the vars of its appearance, you'll change that appearance for every object that uses it. That's not only totally undesirable, but it would still require removing the appearance from its sorted Bag and re-adding it to adjust its sort order properly. There's literally no upside to that.

2. Make it so when appearances are modified, the appearances are properly updated on all clients.

But that only makes sense if all appearances become mutable, which itself makes no sense at all.

3. Add a world.appearances var that contains a list of all the appearances. (Okay, this is probably optional)

There's no value in that at all.

This would allow developers to interact with appearances in a meaningful way and optimize code which would be otherwise unoptimisable.

I'm really not sure what you mean. Making all appearances mutable would most definitely not improve optimization.
How exactly does this sorting work, and what is the purpose of it? Is it like a red-black tree, is it a binary search?
Appearances are sorted into a self-balancing red-black tree.

The reason for this is that appearances are immutable objects. (And that's a good thing, because it means turfs and other objects can be created very easily and held in memory with minimal data. It also cuts way back on the data that the client needs to be sent.) Having multiple IDs reference the exact same appearance would be way less than ideal, so when a new appearance is about to be created, it's looked up. If it's in the list already, the existing ID is used instead.

The /mutable_appearance object was created as a compromise, allowing multiple vars to be changed at once without churn. An Appearance object is kept in memory but it isn't assigned an ID (the part that requires a lookup) until the mutable appearance is used for something.
I've noticed that there is a lot of overhead when a new appearance is created, however if the appearance already exists this overhead is reduced by a lot.
If the appearance already exists, then only a lookup is done; otherwise, it has to be added to the tree. The overhead is small enough that most run-of-the-mill appearance changes don't really cause a problem, but changing a bunch at once does.
Why does this insertion get very long for large projects compared to small projects if you are using a red/black tree which should be very fast?
Searching the tree is always a necessity, so the depth could be an issue. But more likely it has to do with the number and frequency of insertions and deletions. Depth times frequency is roughly what you're looking at for complexity.

With /mutable_appearance, you basically are setting up a situation where this only has to be done once if you're changing a bunch of appearance values (name, maptext, icon, etc.), rather than once for each value. Thus a lot of that churn is eliminated.

Normally, if you're changing a few values in a row, that's where churn comes in. For instance, let's say you're setting pixel_x/y on an image, and images unlike movables do not keep their pixel offsets separate.

1) Start with appearance A. Create a copy of it (Q) and change its pixel_x.
2) Try to assign an ID to appearance object Q, by searching the appearance tree. If the ID is not found, add Q to the tree. The ID, new or pre-existing, is B.
3) Assign B to the image. Increment the refcount for B and decrement it for A.
4) Create a copy of B (R) and change its pixel_y.
5) Try to assign an ID to R. The ID will be called C.
6) Assign C to the image. Increment the refcount for C and decrement it for B.

Normally those steps are pretty fast, but if you're doing a lot of them at once that's where it adds up. With /mutable_appearance you get to do a shortcut.

1) Create a /mutable_appearance object, Q, that's a copy of A.
2) Change Q.pixel_x.
3) Change Q.pixel_y.
4) Finalize Q by finding an ID, B, via a tree search.
5) Assign B to the image. Increment B, decrement A.

While that doesn't look faster on paper, you're eliminating at least one tree search, one possible addition, and one possible deletion. The more vars you change at once, the better the savings.

/mutable_appearance keeps track of whether it's been finalized or not. If you change any values it returns to an indeterminate state where once it gets assigned to an object, it gets finalized again. (It might be easier to think of it in terms of "open" and "closed".)
Wouldn't it be preferable to mark appearances as "dirty" and flush them between server frames?
In response to Somepotato
We're talking apples and oranges here. Every atom or image has an appearance, and that appearance is a simple ID value corresponding to an immutable appearance that holds all the real info.
In response to Lummox JR
Lummox JR wrote:
We're talking apples and oranges here. Every atom or image has an appearance, and that appearance is a simple ID value corresponding to an immutable appearance that holds all the real info.

It'd work if you kept track of how many things used an appearance (not necessarily refcount, not sure what your use of it is here), eg if only 1 thing did you'd be able to get away with flagging as dirty, which would happen if more than 1 appearance variable were set on an obj creating a (luck permitting) unique appearance.
Any other variables set on the new unique appearance would flag it as dirty for a rebuild for assigning it a new ID at the beginning of the next server tick along with its associated atom.
In response to Somepotato
Somepotato wrote:
Lummox JR wrote:
We're talking apples and oranges here. Every atom or image has an appearance, and that appearance is a simple ID value corresponding to an immutable appearance that holds all the real info.

It'd work if you kept track of how many things used an appearance (not necessarily refcount, not sure what your use of it is here), eg if only 1 thing did you'd be able to get away with flagging as dirty, which would happen if more than 1 appearance variable were set on an obj creating a (luck permitting) unique appearance.
Any other variables set on the new unique appearance would flag it as dirty for a rebuild for assigning it a new ID at the beginning of the next server tick along with its associated atom.

And then you're assigning a bunch of IDs in one shot on one tick, just before the map update, instead of during a proc where that can be more spread out.

One way or another, the ID has to be assigned.
You'd end up getting better performance if you mark them dirty because of how it'd reduce the redundant copying. The ID definitely has to be assigned but a proper queue might not be the worst way to go about it
In response to Somepotato
I don't understand where you're coming from on that. You can't just mark an appearance dirty and give it an ID later; the actual data has to be changed somewhere. But the actual data only exists in one place: in a tree of unique appearances. (There's also a shadow array that can be used for direct access by ID, but that's just pointers.)

If you're talking about assigning a temporary appearance, or rather a pointer to one, to an atom/image when changing its vars, that's going to be just like using /mutable_appearance automatically but with a lot of branching logic checks that would make performance worse; using /mutable_appearance selectively where it's beneficial would perform much better. And finalizing those temporary appearances would have to happen at map sending time, which means you're giving the CPU a big hit at a point where the timing ought to be at its most reliable. (And none of that addresses turfs, which are a different animal and would complicate the handling of this enormously.)

I'm not saying there aren't inefficiencies in the current system, or there's no room for improvement, but this proposal wouldn't be that. It delays something that has to happen anyway to a time when it's better not to have it happen, and for each appearance var change it introduces more of both branching and heap allocations.
I wasn't fully aware with how appearances were handled in the backend. It'd require quite the rewrite of a lot of code to make it applicably useful in this manner so yeah that makes sense.