ID:1897218
 
Not a bug
BYOND Version:508
Operating System:Windows 7 Ultimate 64-bit
Web Browser:Chrome 43.0.2357.132
Applies to:DM Language
Status: Not a bug

This is not a bug. It may be an incorrect use of syntax or a limitation in the software. For further discussion on the matter, please consult the BYOND forums.
Descriptive Problem Summary:
animate() is extremely expensive, using 5x as much CPU as directly setting the variables.

Expected Results:
animate() would have almost no impact on performance, as it should be client sided.

Actual Results:
animate() takes almost 5x as much CPU as setting the variables directly, taken from profiling.

Does the problem occur:
Every time? Or how often? Every time
In other games? N/A
In other user accounts? N/A
On other computers? Yes

When does the problem NOT occur?
Setting the variables directly.

Did the problem NOT occur in any earlier versions? If so, what was the last version that worked? (Visit http://www.byond.com/download/build to download old versions for testing.)
Untested

Workarounds:
Set the variables directly.

Animate() actually does more than a small bit of work on the server. It creates an appearance for every frame of the animation where there are changes and then communicates them to interested clients. Can you show the code that you are using to demonstrate the problem? I've never had this problem and I've used animate() extensively.
That's... horribly inneficient.

The code in this case is here: https://github.com/d3athrow/vgstation13/blob/Bleeding-Edge/ code/modules/lighting/lighting_overlay.dm#L57

I have heard from somebody else that disabling lighting transitions (which use animate()) increases performance of said proc by 5 times.

to disable transitions properly: https://github.com/d3athrow/vgstation13/blob/Bleeding-Edge/ code/setup.dm#L1157
turn that define to 0
That's... horribly inneficient.

That's how BYOND works. Without appearances, it simply can't know what visual state atoms are in at all.
Doesn't animate() instantly apply the affect on variables, only have it animated for the clients viewing the animation?
Question: Why are lighting overlays movable atoms?

Why not use areas? It's absolutely going to be significantly faster in the first place owing to the fact that areas can be in multiple places at one time.
In response to PJB3005
PJB3005 wrote:
Doesn't animate() instantly apply the affect on variables, only have it animated for the clients viewing the animation?

Yes and no. Animate (This is my understanding, so if I'm a little off, I'm sure Lummox is going to correct me shortly) creates a series of appearances at a per-frame basis, which will be flipped through on the client-side for any interested parties. I'm not certain, but I think animations are communicated globally, not just to any clients who can see them.
The lighting system used to have a high resolution setting, which cannot be done with areas AFAIK, however this was taken out because it sort of killed any client that tried to view higher resolution lighting.

Also attempting to use areas would require subarea division which frankly isn't very good, as sorting through area contents and such gets a lot more annoying.
Well, I think part of the issue is that you are queueing up probably on the order of hundreds of animations at a time by using movables, rather than using areas for lighting.

I understand that transitioning lighting away from movables is likely to severely impact you guys' codebase, seeing as any variant of SS13 is an absolutely monstrous undertaking, but animating potentially hundreds of atoms per frame is going to seriously impact performance.

Were you to use an area, only one atom would need to be communicated per lighting level.

As for limiting the damage to existing implementations, you might check out a proof of concept I knocked together regarding exactly this problem a few months back. My roofing library actually overrides area behavior in order to subdivide areas into multiple components while still preserving their contiguous behavior. Perhaps you guys could apply a similar concept to the problem you are facing right now in order to reduce the impact of your lighting engine a bit:

Post/Tutorial: http://www.byond.com/forum/?post=1797736

Rooflib Demo: http://www.byond.com/developer/Ter13/RoofingDemo?tab=index

Rooflib: http://www.byond.com/developer/Ter13/RoofLib?tab=index

I know it's a serious wall of text, but it really is packed with some of the best information on how appearances work that's available at the moment (barring Lummox's post, of course, appearances had never been discussed in depth when I was writing this), and it also demonstrates some high-efficiency techniques for taking advantage of the oddities that surround appearances, images, areas, and the map initialization process.
1. We used to use DAL which was area subdivision based, but we replaced it for this new system.

2. Using areas for lighting would require subdividing the areas, you're still animating on a tile basis so I don't see how that would effect performance.

3. I just tested it, animate INSTANTLY sets the vars, the animation is only visual.
I just tested it, animate INSTANTLY sets the vars, the animation is only visual.

Yes, that's definitely what the reference has to say on the subject. I'm more talking about whether the appearances are generated on the server and communicated to the client, or whether they are generated independently on the client.

IIRC, Lummox mentioned something about them being generated per-step. I'm just struggling to find where he said it. Anyway, obviously my suggestions aren't particularly helpful, so I'll duck on out and let Lummox correct my probable (read: almost certain) misconceptions on the internals of how this all works.

Apologies for interloping and best of luck.
More a question for lummox than anything else:
Are matrix operations done on the clientside? If so, could you use those instead? Do matrix operations even let you pass a colour var?
To clear everything up (and I'm closing this as a report since it's not a bug):

When animate() sets each var it's given in each call (except ones like time, loop, etc. that are only used by the proc itself), it works exactly the same way setting that var on the host object does: by creating a new appearance (or reusing an old one, which still requires a lookup). In fact it calls the exact same routine that's used to set a var. So animate() will basically take the same amount of work as setting all those vars at once, but with small overhead for creating the animation step and checking if any clients have already seen the source object and should therefore be notified. Ter hit the problem on the nose: the sheer number of objects being animated is the problem. But once you've created any animations, the ones you made should have little to no impact on server performance (except in the current webclient); the work is all done during the animate() calls.

If you're setting a lot of vars at each step, one option might be to cache the appearances you need so you only have to set appearance. 508 will allow that. It's not a perfect solution because even setting appearance directly will involve lookups (long story, but I think that's something I could maybe optimize since the vars that setting appearance doesn't alter happen to be non-animation vars), but it's something.

Now as for how this works on the client end, whenever animations are present on an object known to the client, the map will be redrawn every tick. (Future optimization: icons won't be re-sorted, unless layer or pixel positions are animating.) Any atom or image with an active animation will calculate its current appearance. It does so by taking its current appearance, finding the animation step it's currently on (by time index) and how far it is between steps, interpolating the from and to appearances, and then applying the values that are animation-relevant. (The client keeps track, when it receives an animation, of which values are changing between the from and to options; the master animation keeps bit flags indicating which values will change throughout the life of the animation.)

To answer your question, Amidaychi, matrix operations can be done on either end, depending on what you're doing. If you're doing something like M1*M2, that's server-side. The interpolation done within an animation step is client-side. Matrix operations don't involve a color var.
Lummox JR resolved issue (Not a bug)
One addendum: There is one inefficiency currently built into animate() calls, where if an animation is interrupted with a new animation, a transition is calculated. Normally this is something only the client would worry about, but the current (non-DSified) webclient relies on the server to do the animation for it, and therefore transition has to be calculated even if there are no webclients connected. If animations are never interrupted by new ones (not new steps, but whole new animations), then no transition is calculated.
But once you've created any animations, the ones you made should have little to no impact on server performance (except in the current webclient)

Maybe this is not the right place for this question, but is there a way to share the appearances generated by animate() across all of the lighting atoms?
In response to HarpyEagle
That's effectively what I was suggesting. Something like this for the first object:

var/animations = list()
animate(O, ...)
animations += O.appearance
animate(...)
animations += O.appearance
...

And then later:

animate(O2, appearance = animations[1], ...)
animate(appearance = animations[2], ...)
...

The only downside is, setting the appearance var won't save you anything if it's a single var being changed. Setting atom.appearance doesn't directly change the appearance to that value because it leaves dir, verbs, screen_loc, and density alone, so there's still a lookup involved.

At the end of the day the important thing is to recognize that any vars you change in animate() are in fact actual var changes, and incur all the same overhead you would have from setting them without animate(). The only difference is, once the animation is setup you don't have to interpolate them yourself on every tick (which would be mondo expensive and difficult to sync) because the client will do it for you.
Does animate() use the cpu to create the appearance steps even if no one is observing the animation?
Well considering tests of performance with and without animate were mostly with most of the animations not visible to any clients (hint, 1 player on the server), I'd say it does
In response to Clusterfack
Yes, the appearance is always created. As I said, it works by changing vars the normal way, which will create a new appearance.