Thatcher Ulrich
2010-07-12 04:06:00 UTC
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
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
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
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
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
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.
Camera+EntityList -> [VisibilityChecker] -> EntityList
EntityList -> [EntityRender] -> DirectX
EntityList -> [EntityPrinter] -> Console
etc.
In C++, you could for example implement the very high level architecture
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
http://lists.midnightryder.com/listinfo.cgi/sweng-gamedev-midnightryder.comarchitecture/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
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
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
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.
Camera+EntityList -> [VisibilityChecker] -> EntityList
EntityList -> [EntityRender] -> DirectX
EntityList -> [EntityPrinter] -> Console
etc.
In C++, you could for example implement the very high level architecture
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