ID:2080201
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
Stay tuned for Snippet Sunday #13, where I demonstrate why this is a needed feature.

I've just finished my first pass optimization of Epoch, bringing the loading time down from 30 seconds to 2 seconds on my machine.

The brunt of the overhead in Epoch is "appearance churn". The trouble with appearance churn? It doesn't show up in the profiler. It increases world.cpu, but doesn't show the full effect honestly. Unless you understand how appearances work to any degree of certainty (and I'll admit it's a black box on the how, in some places but I've got a grasp enough to know how to work around it) you just assume that BYOND is slow and there's nothing wrong with your code.

There's been a lot of talk about implementing a new proc for changing multiple appearance values in one go, but I think this is a mistake, because procs can only use assignment operators for their arguments. This means that we'll have to be making a ton of local variables and inducing a ton of access overhead, or worse yet, we'll be creating a ton of lists and then using argslist() to call the proc. This will result in ugly, unreadable syntax becoming the "ideal".

Let's take a look at what I imagine the current suggestion would look like:

changeAppearance(src,"overlays"=overlays.Copy()+'herp.dmi',"maptext"="<span class='combat'>[maptext]",icon='herpyderp.dmi',"icon_state"=icon_state+"1","underlays"=underlays.Copy()+'derp.dmi')


This is super ugly and unmaintainable code. It makes the engine's syntax look like hot garbage from New Jersey.

Suggested Syntax:

Instead, this is what I'm proposing:

var/mutable_appearance/m = src.appearance.values
m.overlays += 'herp.dmi'
m.underlays += 'derp.dmi'
m.maptext = "<span class='combat'>[maptext]"
m.icon_state += "1"
src.appearance = m


This should result in only one appearance lookup and two overlay/underlay lookups.

Notice the parity with the current method of changing appearances:

overlays += 'herp.dmi'
underlays += 'derp.dmi'
maptext = "<span class='combat'>[maptext]"
icon_state += "1"


The above results in two overlay/underlay lookups, and then four appearance lookups.

Converting to this approach would be painless for most projects, as compared to a new proc.


Attacking the problem of appearance churn has already solved a lot of problems for Epoch.

And while the above example only saves three appearance lookups, there are functions in Epoch where I've managed to reduce the churn from 3-9 overlay lookups per object using more complex techniques than the one shown above, and even using list.Copy() liberally, it's still saved us a net of over 20 seconds just on world startup time alone.

That's a speed improvement of 1000% just because I changed a small handful of functions that did nothing more offensive than:

underlays += o1
underlays += o2
underlays += o3
underlays += o4


Who is going to think the correct way to append to an overlay/underlay list is:

underlays = underlays.Copy() + list(o1,o2,o3,o4)


The suggested syntax just makes more sense and preserves a lot of what introductory programmers are already familiar with.


appearance.values is a read-only variable that creates a mutable appearance. The mutable appearance reference does not update the appearance it was copied from. When assigned to an atom.appearance, or added to an overlays/underlays list, it's converted into an appearance then and there, making for a single appearance lookup.

All values in the mutable appearance type are volatile, and therefore don't need to worry about being unique-only. It'd be just like working with an atom, but without all the appearance churn.
Hmm. It should show up in the profiler, although not directly.
Hmm. It should show up in the profiler, although not directly.

Not in my experience. I've seen 100%+ CPU worlds reporting only a few seconds of total CPU over hours.
That confuses me, because appearance churn should mostly happen in procs when appearances are being changed. The only time I would expect otherwise would be when unused appearances are deleted outside of procs. Any churn that happens during procs should always show up in the profiler, because all the profiler does is measure start and end times (with some code built in to handle sleeps, of course).
Oh, also related to this is ID array churn; overlays and underlays use ID arrays internally, and those are unique immutable objects. So it's not just appearances coming and going, but ID arrays.

A mutable appearance would probably have to take this into account as well, and not use ID arrays under the hood.
Ah yes I would love to be able to assemble a complex appearance quickly and efficiently. It's the one thing that we've always found really tricky to optimize, often going so far as caching many appearances close to the final product to reduce churn.

Many of the updates recently have really loosened all the constraints I've found holding back performance in Byond (like string joining which in a way was a similar problem to this, etc etc), and if this can be improved then I have very few complaints left!
I must say I do like the syntax on this.

Batching updates and doing them in one pass would be very nice to have in a form like this.

I'm even evisioning having most code not setting /modifying appearances, just setting state and then having one batch pass every system tick (or perhaps even delayed to every n ticks where n is the largest number we can get away with that doesn't start to incur noticeable visual lag) that does the appearance modifications.