You've described almost exactly the state update model used in Ironclad:

https://github.com/mikera/ironclad

I'd strongly suggest taking a look at the Ironclad source if your game is 
anything vaguely turn-based / strategic.

Some comments:
- A big, single immutable data structure for the entire game state is the 
best way to go if you can make it work. I managed this for Ironclad and 
Alchemy, it might be hard for a FPS though.....
- For performance, you may need specialised persistent data structures 
(e.g. Ironclad uses a custom PersistentTreeGrid for maps and unit locations 
etc.). As part of this, I implemented things like efficient area searches 
(for nearby entity detection).
- You'll need to maintain indexes as part of your immutable data structure 
(e.g. a map of entity ID to entity location). This is the only real way to 
avoid expensive traversal of the whole data structure. This is also the way 
to efficiently process events targeted at a specific entity.
- The model of message -> [collection of atomic updates] is good.
- Keeping a queue of incoming events is also a good idea (probably 
essential if your game is networked / multiplayer)
- I would suggest *decoupling* screen redraw from the game event update 
loop. One of the big advantages of immutable game state is that it is easy 
to put rendering on another thread! Same can also apply for AI.
- If you want fluid animations resulting from discrete game events, 
consider doing the animations outside the main game state, e.g. by managing 
sprites within the rendering engine. It's a bit of a hack, but massively 
reduces the number of game state updates required for every little position 
update.

Finally, beware of unnecessarily creating/realising sequences - this 
results in a lot of GC overhead. A common culprit is running a map over a 
vector for example. Don't do this unless you have to - I ended up writing a 
bunch of non-allocating data structure traversal functions to avoid this 
problem (some of these are in https://github.com/mikera/clojure-utils)



On Monday, 20 May 2013 09:02:23 UTC+8, Daniel Wright wrote:
>
> Hello, 
>
> I am trying to structure the game loop for a simple game in clojure, 
> trying to keep things pure as far as possible to get a feel for how this 
> would work in a functional environment.  Currently, I am working with 
> a message-based system, whereby various events create "messages" which I 
> then act on to change state.  For example: 
>
>   1. Read keypresses, generate a message for each keypress and add to 
>      the queue. 
>   2. Read from the network; add any incoming messages to the queue. 
>   3. Add an "update" message to the queue which can be used for generic 
>      update processing: AI, physics, whatever 
>   4. Go through the entities in my world delivering these messages as 
>      appropriate.  Keypress and update messages will be processed by any 
>      entity that implements a handler for them; network messages may be 
>      "directed" so that they only get sent to a specific entity. 
>     (The return value of the functions processing these messages is 
>      itself a vector of messages, such as "update-state" to replace the 
>      current state of an entity (position, etc) with a new state, or 
>      perhaps a message to send information over the network.) 
>   5. Send any outgoing network messages, perform any state updates, etc. 
>   6. Draw the screen, return to 1 and begin the next game loop. 
>
> The issue I'm having is that this system results in rather a lot of 
> looping through every entity in the world.  There are two full loops, 
> delivering the messages in step 4 and updating the state in step 5. 
> Originally I had the message handlers in step 4 return a new state 
> rather than new messages, so I just updated the entities in-place during 
> the first loop, but I found sometimes I wanted to do other things than 
> just update state -- for example send messages over the network, or to 
> another entity in the world.  So it seemed more flexible to return 
> messages, even if some of those messages are directed toward the entity 
> sending it. 
>
> My other issue is that with messages intended to be processed by a 
> particular entity, I can either check that while looping through the 
> whole list of entities (which means for every entity it's not intended 
> for I'm running a wasteful check on the id of a message), or I can put 
> the entities in a map instead of a vector and look them up by some id 
> instead (in which case I'm doing a search for every directed message, on 
> top of the loop I'm already doing through all the entities). 
>
> I've come from a mostly C++ background, so my sense of when I'm doing 
> something really bad isn't very well-tuned in functional languages at 
> the moment.  I write something that "feels" nice and looks pretty, and 
> then I step back and think about what it's actually *doing* and I can't 
> help but think "in C++ this would be unforgivably vile." 
>
> It seems the more I try to push function purity the more I have to loop 
> through some monolithic data structure holding all of my state, since I 
> can't just pass references around and modify them in-place.  Writing the 
> code for the entities themselves is going quite well -- I am keeping 
> their functions pure, not referring to anything outside of the 
> parameters they're passed in, and thus always returning the same result 
> given the same input, and limiting their input to the information they 
> need without giving them access to the entire state of everything -- all 
> of which is great for testing, parallelisation, and all the rest.  It's 
> at the higher level of managing the collection of these entities and 
> their relationships that I wonder whether I am working along the right 
> lines or whether I am in some sense "doing it wrong". 
>
> As an aside, right now I am avoiding storing entity state as atoms and 
> having the update functions modify those atoms because although clojure 
> helps update their values safely it still means the function has side 
> effects, and I'm trying to keep functions as "pure" as possible at least 
> until I can understand the limitations of doing that and see the 
> necessity for using global constructs. 
>
> I have a feeling this is only going to get more complex as I start 
> wanting to make smaller sub-lists that refer to the same entities.  For 
> example my entities may be stored in some tree format in the world 
> state, but I might want to have a list of "all enemies within a certain 
> radius" or whatever just as a convenience for quick access to those 
> entities I'm interested in.  Right now if I updated an entity in this 
> list it would remain not updated in the global state tree... I'm 
> guessing there's no way around holding an atom or similar in both lists 
> and updating that, but here I may be missing something also. 
>
> Sorry for the slightly rambling mail; thoughts/guidance appreciated! 
>
> -Dani. 
>

-- 
-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to