State Object / Process Object design
Thatcher Ulrich <[email protected]> Mon, 12 Jul 2010 00:06:00 -0400
| Newsgroups | gmane.games.devel.sweng |
|---|---|
| Message-ID | <[email protected]> |
This sounds really interesting and enticing but I still have a lot of questions: * you mention that SO's can inherit from a common interface in order to reuse PO's -- but doesn't that undermine their state-only-ness? * it sounds like this would result in a lot of dynamic allocation of SO's and SO collections, that would not occur in a more conventional OO implementation. Is that true, and is it a problem? * what about a complicated persistent data structure like a spatial index, that refers to other objects in the game? Is this allowed in an SO? Is it an exception to this pattern? * is there a more detailed writeup or example system (I.e. with source code available for study) that illustrates this? Thanks for any insights! -T On Jun 3, 2010 11:01 AM, "Morten Brodersen" <[email protected]> wrote: > There are obviously a lot of people on this list interested in software > architecture/design. Especially how to do it in a pragmatic/effective > way with language such as C++. > > During the 15+ years I have been working in the games industry (and the > 30 years I have been writing software), I had a lot of opportunities to > experiment with software architectures for games (from the engine all > the way up). Some architectures worked well, others less so :) But after > many interesting experiments I ended up with a non-complex way to design > software that works well for small and large scale applications. And it > (surprisingly) works well not only for OO style languages but also for > functional style languages (and multi-threaded applications!). > > I have no idea whether the approach will work for anybody else. I am > definitely not making that claim. But hey it might be interesting for > you so here it is: > > Think about your application as consisting of 2 types of objects: State > Objects and Process Objects. > > A State Object (SO) handles a state (surprise). It can be anything from > a texture to a database to a display to a list of integers. In an OO > language, SO's will typically be implemened as classes/objects with the > usual set/get/add/remove methods. In a functional language, SO's are > typically implemented as (immutable) values. > > The Process Objects (PO) takes as input one or more input SO's and > transforms/outputs it to one or more output SO's. In an OO language, > PO's are typically implemented as classes/objects/static > methods/functions. The PO's might use temporary data while processing > input to output but it normally doesn't maintain a state. In a > functional language PO's are typically implemented as functions. > > Large scale applications are constructed by joining SO's and PO's > together in a graph structure: > > S0 -> [P0] -> S0 -> [P0] -> SO -> [PO] -> SO > > I can of course only show a linear chain in this email so imagine a > number of boxes (SO's) and spheres (PO's) spread around on a piece of > paper and lines going from (input) SO's to PO's and from PO's to > (output) SO's. The graph typically have loops making it possible to (for > example) feed the output of a game tick into the next game tick. > > The SO/PO approach can be used at any abstraction level. Here is (part > of) a very high level architecture for a game: > > GameConfig -> [GameStateMaker] -> GameState > GameConfig+TestConfig -> [GameStateForTesting] -> GameState > NetworkMsg -> [GameStateFromNetworkMsg] -> GameState > GameState+Time -> [GameTicker] -> GameState > GameState -> [GameTester] -> GameTestReport > GameState -> [GameSaver] -> GameFile > GameFile -> [GameLoader] -> GameState > GameState -> [GameStateToNetworkMsg] -> NetworkMsg > GameState -> [GameStateVerifier] -> GameStateErrors > GameStateErrors -> [GameStateErrorPrinter] -> Console > GameStateErrors -> [GameStateWindowMaker] -> Window > etc. > > And some low level ones: > > Camera+EntityList -> [VisibilityChecker] -> EntityList > EntityList -> [EntityRender] -> DirectX > EntityList -> [EntityPrinter] -> Console > etc. > > In C++, you could for example implement the very high level architecture > this way: > > static void Game::tick(GameState* gs, float time) { ... } > > The reason why the SO/PO approach works well is because the application > ends up consisting of a flat collection of self contained SO's that are > easy to understand, test and reuse. And a collection of PO's that also > are easy to understand because they simply transform the input SO's to > the output SO's without relying on any internal state. > > And because the application is broken down into a large number of SO's, > you can typically add new functionality by adding an extra > serial/parallel/concurrent transformation step to the architecture > without having to touch the already working code. You usually do not end > up in the "grab the banana and you get the whole jungle" sitation that > is typical of C++ software. > > If a number of SO's inherit from the same SO interface, you can easily > pick the best SO for the job without having to touch any of the > surrounding PO code that use it. > > If SO's are organized as lists of simple values/objects, you get the > cache performance advantage discuss previously in this email list. > > If some of the PO's are implemented as threads with the input/output > SO's implemented as semaphore protected data/queues, you get a cleanly > architectured multi-core/threaded application. > > Morten > > _______________________________________________ > Sweng-Gamedev mailing list > [email protected] > http://lists.midnightryder.com/listinfo.cgi/sweng-gamedev-midnightryder.com _______________________________________________ Sweng-Gamedev mailing list [email protected] http://lists.midnightryder.com/listinfo.cgi/sweng-gamedev-midnightryder.com