Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
August 20, 2014
arrowPress Releases
August 20, 2014
PR Newswire
View All
View All     Submit Event





If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 
The Curtain Problem
by Jamie Fristrom on 06/23/13 11:36:00 pm   Expert Blogs   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

I have a coding example that I think generalizes to a case I see a lot in games. It seems to be a knee-jerk reaction for a lot of coders (including myself, once) to cache state when we don't have to.  Even before I was interested in functional programming I knew that duplicating data was a ripe opportunity for bugs, but it was playing around with Haskell that made me realize just how far you can go to avoid pushing state.

I think one of the reasons we tend to push state and cache calculations is because often it's a little less work to write the code in the first place.  Adding a new flag and setting it is easier than adding a new function.  A more complicated example - came across some code recently where the coder set up an stl map so he could quickly look up a few elements from a larger hierarchy of stuff.  Probably less work than writing the recursive search;  but now we have to maintain the map and the original data.  And then there's performance.  Nobody wants somebody else to look at their code and say, "Hey, why'd you do it this slow way?" 

But the vast majority of the time we're working on code where performance isn't a concern, and doing that recursive search, checking all that data, or whatever once or twice a frame is not a big deal. And it's better to write code that is less likely to break in unusual circumstances, so you don't have to revisit it, adding band-aid after band-aid.

Back on Spidey 2, the designers were in charge of writing the scripts to handle fades, and because they're not programmers by trade, it got a bit funky, though I wouldn't necessarily have done better at the time, because it was one of those problems that seems simple but turns out to have some issues.  Bugs kept cropping up relating to the fade curtain - it would fade to black and stay faded, or it would fade in too soon, or the like.  And we'd go in and find the script that was doing things incorrectly and fix it, and then another bug would crop up a few days later.

The curtain was an example of how state can screw one up.  You might initially set this up ("always use the simplest possible design") as two functions BeginFadingOut and BeginFadingIn which set a boolean isFadingOut.  But then you get two chunks of code (not necessarily even on different threads) that both think they need to use the curtains and you get a mess - one'll start it fading in before the other one's ready.  

Just one example - I don't exactly remember if this was one of the bugs or not, but it could have been - spidey might die, and respawn, but respawn in an exterior when he died in an interior, which means streaming in some new terrain data.  We might have had two scripts, one for respawning, and one for transitioning from an interior to an exterior.  Both might have called the "close curtain" function, then waited for it to close, down their work, and then opened it up.  But lo - the "respawn spidey" script is opening the curtain before the "transition" script has finished streaming in the data, and we get an ugly, visible load.

One thing we tried, then, was incrementing and decrementing a counter.  Better, and when a script failed it was because the script was clearly not using the system correctly, so it became easier to blame the scripts.

What I should have realized when we were doing these spot fixes of offending scripts was that the curtain mechanism itself was to blame.  We were effectively pushing state...duplicating data.  Whether the curtain was fading in or out depended on the state of the game.  The curtain should have been pulling state.  It probably should have looked something like this: 

bool ShouldIBeDown()
{
return AreWeSettingUpACutscene() || IsSpideyRespawning() || AreWeTransitioning();
}

And then, what translucency to draw the curtain (or how high to draw it, if it's a more literal curtain) could have been determined simply by measuring how long ShouldIBeDown had been true.  The functions that respawn Spidey or transition to different areas would wait on IsCurtainDown() before doing their work.

Now, you say, "That breaks encapsulation!"  "That creates circular dependencies!" because the curtain is dependent on transitions and Spidey's respawn system and the cutscene system and they're all dependent on the curtain.  Before, the curtain was a low level service they could all just layer on top of.  I'd say - hey, I'd rather be conflated than buggy, and for this case it kind of makes sense for all these things to be conflated - but what about a more general situation, where some more encapsulation or layering would make sense?

One possibility that's C++ friendly is the curtain's ShouldIBeDown() function could be virtual.  Another possibility, not so C++ friendly, would be to use some kind of lambdas - function pointers or functors or delegates or something like that.  You could add a list of curtain-closing conditions this way.

Speaking of delegates, with Schizoid I was using delegates like candy, and one particularly bone-headed thing I did that I later stopped doing once I realized how bug-prone it was: changing delegates mid-stream.  I had some enemies that would change their minds, and tried to implement that, at first, by changing their whatDoIWantToDo delegate.  This is pushing state - the curtain problem all over again.  Instead of having one function tell the entity to do one thing by pushing one delegate and having another function tell the entity to do the other thing by pushing another delegate, I needed to put that all inside a single function  if( condition() ) doOneThing() else doTheOther().  If I had a rule of thumb for delegates or function pointers now, it would be - you can pass them in constructors but never change them once their object is live.  So that's what I did, and I lived happily ever after.

The value of avoiding state is something that the how-to-program books I've read never seem to discuss. The "Don't Repeat Yourself" chapter of The Pragmatic Programmer touches on it at the same time as it tells you not to copy and paste code, but that's the only one I can think of off the top of my head. Whole engines are built around stateful paradigms: for example, the animations that execute scripts when they get to certain frames in Flash, and the tween-happy Coco2D. And that's crazy to me. Unlike QA, unit tests, pair programming and code reviews, avoiding state keeps bugs out of your code in the first place. I personally think it's more important than avoiding magic numbers or properly declaring things 'private' or 'const'. What do you think?

(Originally printed here.  If you found this article interesting, please help me out on Greenlight.)


Related Jobs

Cloud Imperium Games
Cloud Imperium Games — Santa Monica, California, United States
[08.19.14]

Gameplay Programmer
2K
2K — Novato, California, United States
[08.19.14]

Senior Engine Programmer
The Digital Animation and Visual Effects School
The Digital Animation and Visual Effects School — Orlando, Florida, United States
[08.19.14]

Instructor - Video Game Asset Production
Shiver Entertainment
Shiver Entertainment — South Miami, Florida, United States
[08.19.14]

Senior Technical Director/Artist






Comments


Alexander Symington
profile image
Great blog!

The 'Greenlight' link currently seems to just point to this page. I think this is the correct link(?):-

http://steamcommunity.com/sharedfiles/filedetails/?id=145451414

Tim Zook
profile image
I generally prefer to use lenses (http://twistedoakstudios.com/blog/Post1823_decoupling-shared-cont
rol) or cancellation tokens (http://twistedoakstudios.com/blog/Post1941_perishable-collections
-the-benefits-of-removal-by-lifetime) in the cases that you describe in your examples. You mentioned that you used something like lenses with your curtain system but I didn't feel you had a very good explanation of why this was inherently worse solution than hard-coding the "ShouldIBeDown" function.

Yes, these still may technically be "duplication of state" as you describe, but I generally find them very safe to use and they solve the circular dependencies you mention in an elegant way, imo. But what method you prefer ultimately depends on the requirements of your program.

Devin Horsman
profile image
Here's a presentation on Lenses I put together with some Unity Examples (linked at the end) (https://docs.google.com/presentation/d/1MQfjDcVOCqSQ0RLyEbQKLhAG2sI44wjr KElM3FZbvF8/present#slide=id.p)

Jamie Fristrom
profile image
I mentioned we tried incrementing and decrementing a counter - a poor-man's way of doing lenses. (Never heard it called that before, so I've learned something today.) Lenses would be superior, sure, but still wouldn't solve all our problems (often which were caused by simple mistakes, forgetting to re-open the curtain for example.)

Diet Schnaepp
profile image
I'd love to read more stuff like that. It takes experience to have those standards but even more to phrase them convincingly

Brendan Vance
profile image
I would claim that to "push state" is essentially to introduce a runtime dependency forming a one-way connection between two systems; while it is true that replacing this with function calls (such as that boolean expression in the article) does us one better by moving the dependency from runtime to compile-time, you are correct in pointing out that it's still a dependency and it still requires you to modify a single behaviour in multiple places (in this case it would be two places: Setting a flag somewhere in the system, and then rewriting the ShouldIBeDown() function to check that flag). The more things you have to remember to change in order to make something work, the more painful programming becomes.

I've thought about this problem a lot, and the approach I favour is not especially efficient but is certainly more humane. Essentially it is an anonymous multiple delegation pattern that works as follows. If you want to operate on something (like, say, the state of the curtain) you would request a handle from that curtain; this handle is an object of some sort containing a flag representing whether the curtain should be lowered or not, and both the curtain system as well as the things it serves retain a reference to it. Let's suppose the curtain system works by lowering itself any time any of its handles want it to be lowered, raising only when everything wants it unlowered or no handles currently exist; this way you would never experience that ugly pop-in.

The advantage here is that none of the components using the curtain need to know of or worry about any other components that use it; they have only the ability to specify whatever they need to about what THEY want from the curtain, leaving the business of managing those various interests to the curtain system. The curtain system, for its part, doesn't care what components are using it; it knows only about the handles it dispatches and works with those alone.

You can take this a step further by adding a finish() function to both ends; anything that has a reference to the handle (including the curtain system itself) can declare it finished, and an event system of some sort (callbacks, selectors, anonymous function references, whatever) can notify all interested parties when the handle is no longer valid and doesn't affect the curtain system anymore.

This is similar to a counter system, except it affords our theoretical curtain the ability to safely decrement whenever it has to AND it affords other systems the choice of retaining the handle or 'firing and forgetting' as appropriate (useful when we consider other scenarios, like handles for temporary behaviours that will terminate themselves after a certain period of time or some other, unrelated event). The systems that use handles have a convenient, universal, highly-readable way of remembering the modifications they make (they can store the handle itself, handle its cleanup in a callback, and then do things like inspect it and check for null rather than creating some new flag that says 'hasIncrementedCounter' or something and messing with that).

Jamie Fristrom
profile image
In the "stateless curtain" system none of the components using the curtain need to know of or worry about other components either - because none of the game-state components use the curtain at all; the curtain uses them. (But purely in a read-only capacity.)

So I prefer the simplicity of the curtain-dependent-on-game-state approach for this, but that's because in my hypothetical scenario the curtain is purely cosmetic. If we had an in-game curtain, door, or light operated by various toggle switches throughout the level, where state is unavoidable, sign me up for your thing.

Marc Audouy
profile image
I agree with Brendan, that there are more advantages to handle the curtain as a separate standalone service, you can get the benefit of encapsulation without the bugs if you design your service with multiple concurrent users in mind.
a very simple implementation of the handles he mentionned would be store a list of the objects which requested the curtain to be lowered, when an object "opens" it again it is removed from the list, when the list is empty the curtain is re-opened. the "objects" can be anything, as long as they're distinct: a cutscene manager, a player state machine, etc. it's basically "named" reference counting, it is trivial to implement and easy to debug.
but in general there's never a magic bullet, being stateless can be great, it's just that in my experience performance is always more of an issue than memory cost of some extra flags, at least in videos games, and pushing states is nearly always more efficient.

Kaj Eijlers
profile image
Isn't this the exact description of model-view? The curtain's visibility is a view on your gamestate, and the query in this case is (isSettingUpCutScene() || isRespawning() || isTransitioning()), and a change in that bool should cause the curtain to switch state?
Caching should come in if any of those queries are expensive, then you could indeed keep a running value for that bool by reacting to a change in its subqueries and trigger an event when the total query changes, it's how you would optimize a pure functional implementation. It'll be no more expensive than maintaining state and much easier to maintain.
But barring that optimization, to me this is a very pure example of model-view.

Jamie Fristrom
profile image
Absolutely. I've been calling it document - view but it's the same thing and it's beautiful. I really should have talked about it in that light more - when you stop and think about it, it's kind of silly (though all-too common) for our game state code to be dependent on UI code, but that's what's going on when we have game threads closing and opening a curtain. We should be able to delete your UI code and the game should still be able to function correctly but invisibly.


none
 
Comment: