Architectural musings

Started by
16 comments, last by ApochPiQ 6 years, 10 months ago
That's a fair observation.

I partly avoided the subject because it's highly subjective, and partly because it seemed somewhat obvious to me that every game is going to do things a tiny bit differently.

As a starting point, I'd say it's definitely true that you want a centralized point of control for data sources. The hard part is that it is highly situational; what makes sense for one game object may not make sense for another.

But it is absolutely the case that setting a value source (versus just updating one's value) is a tricky question. I'll have a think on ways to express the options and pros/cons.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Advertisement
I have something in an engine I'm working on called a DataFlowExpression. It chains together Observable<T> data sources and propagates change events. Observable is just a combination of a value and a 'Changed' event. It's mainly used for the asynchronous 'is this system ready yet? if it's already ready, just do something now' pattern, except without any of the modern frameworks available (I'm stuck with Unity's old Mono libs).

But if you don't want to immediately propagate change, then you've effectively just got an expression tree/first class function, with or without memoization depending on how much optimization you need.

If all you want to solve is the coordination of "who gets control of the camera with what priority, in what circumstances, and in what mixture", you make a top-level class which watches all of the camera data sources, decides how to combine that data, and drives the camera.
Sure, the solution for cameras is pretty straightforward.

But how would you, for instance, integrate with a physics engine?

Or control color elements for a shader?

And so on.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

I'm not sure which parts of a physics engine you'd want to use it for. Can you clarify?

For shader colors, it seems like a simple DataFlowExpression would work, but my gut feeling is that it would end up getting slow if you started using them for an entire system.

The complexities I usually run into with event-driven programming are:
- Sometimes you want bidirectional data flow, and this causes you to pollute everything with 'if (raising) return; raising = true; raise(); raising = false'. Definitely feels like a design issue when that happens.
- Sometimes events are raised too frequently and you just want them in a batch). An extremely common case is adding multiple items to a list, but only wanting to raise an event at the end. You need either an AddSingle or AddMultiple distinction, or just have AddMultiple but be forced to use a collection when passing a single item.
- It can be cumbersome to unsubscribe everything in the event handling structure when you want to shut part of it down.
- You end up with a lot of tiny first-class functions everywhere that are not cache-friendly and potentially using a significant amount of memory if you are really liberal with their use.

I prefer "get it when you need it" programming any time you're doing something every frame. Event-driven is nice when something is happening infrequently enough that the "scattered" nature of the event calls isn't any worse than what you'd normally be doing.
I wouldn't event-drive the vast majority of value sources. They're really much better as reactive programming units, or intermediaries between systems.

For example, say I have an NPC game object. The position and velocity of the NPC could be controlled by a physical simulation.

In other words, physics produces position and velocity data for the NPC.

Rendering might consume that data to display the NPC.

There are as many ways to pipe together this producer/consumer relationship as there are game engines.

My proposal is simply to put a buffer zone between the producer and the consumer.


The decoupling and interchangeability of producers and consumers is the key win. The implementation details are mostly irrelevant for the purposes of what I'm advocating for.

But I suppose the hangup at the moment is that, by leaving the implementation details unspecified, I'm not really giving anyone a clear picture of the idea.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

But I suppose the hangup at the moment is that, by leaving the implementation details unspecified, I'm not really giving anyone a clear picture of the idea.


I think so; without specific details the buffer just seems to boil down to the concept of an 'interface'. If you require a concrete "third party", then it sounds like the Mediator pattern.

I don't disagree that there are any number of implementations, but without providing any details it's difficult to analyze further and discuss specific pros and cons. I've often found that the process of translating a concept into an implementation itself can give you a wealth of insight, simply because a tangible representation of an idea can shed light on hidden flaws or benefits that you didn't even realize existed before you had something physical to examine and tinker with. That's partly why I posted basic, high-level implementation details for (my interpretation of) your idea, to at least provide a reference that others could discuss, build upon, tear apart, etc., in concrete form. That, and because your post validated some of my own musings, so thanks!

I really like the idea of separating an algorithm from its state. It often makes it easy to have code "self contained". If there's little to no state management, then suddenly the "self-contained" aspect becomes easy to get.

Some contrived examples:


Compressor c = new Compressor( init_params );
c.SetupThingies( );

c.AddInput( data );
c.Compress( );

compressed_data = c.Decompress( );

becomes


Compress( in, out );
Decompress( out, in );

---


Physics p = new Physics( init_params );
b = p.AddBody( );
b.AddCollider( p.MakeShape( ) );

...

p.AddCallback( CollisionHappened );

void CollisionHappened( )
{
    play_sound( );
}

becomes


if ( Hit( a, b ) )
{
    play_sound( );
}

---

The bigger OOP-styled, or heavy hitting "retained mode", libraries often have too many unneeded features. Usually the game wants something specific and has to come up with very odd glue code to wrap things or conform to the library's design. If an API is carefully designed with minimal exposed dependencies and state, and is self-contained, it frees up the game to take on its most natural shape.

But I suppose the hangup at the moment is that, by leaving the implementation details unspecified, I'm not really giving anyone a clear picture of the idea.


I think so; without specific details the buffer just seems to boil down to the concept of an 'interface'. If you require a concrete "third party", then it sounds like the Mediator pattern.


I think it's part Mediator and part Strategy, but that's kind of academic.


I'm still contemplating how best to demonstrate this without having to basically write a game :-P

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

This topic is closed to new replies.

Advertisement