3 ms·
I'm really curious what you mean by "fully concurrent" and how you accomplish it. I've been deliberating over how to write a game loop for my pet project [1] t
by willismichael 13y ago
I'm really curious what you mean by "fully concurrent" and how you accomplish it. I've been deliberating over how to write a game loop for my pet project [1] that allows multiple threads to concurrently update the world state without causing inconsistencies.
For example, let's take the age-old problem of two people in the game (potentially both AI-controlled) both deciding to pick up an item. If two separate threads are handling the logic, both having a view of the world with the item on the ground, and both of them end up trying to update the world state such that the item ends up in somebody's inventory, what is a clean and elegant way to resolve that?
I've considered using STM, having a ref to each entity in the game, and wrapping each bit of game logic in a transaction, thus rolling back entity updates that conflict with previously committed entity updates. I'm concerned about the performance implications of this, though. I keep imagining it becoming way too contentious and having more rollbacks than completed transactions.
[1] https://github.com/willismichael/terra-incognita https://github.com/willismichael/terra-incognita
- Morgawr 13y agoI've been writing on and off about my engine design on my blog http://morgawr.github.io/ http://morgawr.github.io/ but it's mostly just the introduction, I wanted to finish the actual engine (or, rather, make it usable) and make a small game as a demo in it (so far I only have a pong clone). I don't know how feasible my design is, but my idea is to use Clojure's principles of values/identities and reftypes, every entity in the game is an agent with its own thread (scheduled in a thread pool), inside the agent there are refs (I intended to use atom but for race conditions and synchronized updating, the example you mentioned, I decided to go with refs). Entities can query a global database of entities (tagged, component based) and retrieve their state by dereferencing. If they want to update the state of other entities they can either send a message via the agent (ordered asynchronous update) or act directly onto the inner ref (via transaction) for a synchronized/concurrent update. It's tricky to explain, and it's probably going to give me a lot of troubles along the way but it's mostly a fun experiment for a private project.
- willismichael 13y agoThanks! Same here in terms of "mostly a fun experiment". A while ago Brian Carper blogged about early attempts at developing a little RPG in clojure: http://briancarper.net/blog/520/making-an-rpg-in-clojure-part-one-of-many http://briancarper.net/blog/520/making-an-rpg-in-clojure-par... His post indicated that he initially tried the "every entity is an agent" idea, and had performance problems with it - although maybe his real problem was that the entire world was a single ref, and all of the agents would bash on that ref. Another question, do you have any kind of reverse-lookup table implementation? I see that you're using component-based entities, do you have any way to quickly query your world for entities that match some arbitrary criteria, rather than iterate over all entities in the entire world? I ask because my game idea would involve potentially tens of thousands of entities, and I'm trying to figure out a way to build game logic mini-systems that can quickly find a subset of game entities and perform some function on them. For example "In the spring time find all fruit-bearing trees of a minimum age and spawn unripe fruit on them", or "every 15 seconds find all poisoned characters and deduct 1 health point"
- Morgawr 13y agoalthough maybe his real problem was that the entire world was a single ref, and all of the agents would bash on that ref. Having the whole world inside a ref sounds bad... I mean, I don't really know but if you want to take the godmap approach you really need to pass around the state and apply the parallelism somewhere else (aka in the way the state transformations are applied, like pmap for example), not have entities as external agents that hammer on the poor state. do you have any kind of reverse-lookup table implementation? Yes and no. I don't have anything fancy at the moment, I can query the "database" of entities (aka the individual game screens) for specific tags, lookup operations are very quick in Clojure since the entity's state is a map with tag -> value, the lookup is close to O(1). The downside is that I need to query through all the entities contained in a specific screen (or all screens) to look for the tags I'm looking for, but I don't see another feasible way to do it and maintain concurrency. I could sort entities by tags (what I tried to do on my old and half-failed attempt: https://github.com/Morgawr/CloisterJS https://github.com/Morgawr/CloisterJS ClojureScript engine) but I would lose the advantage of concurrency. If the performance impact gets too much, then I'll try to engineer something around the problem but so far I have more important issues (like, having an engine at all).
- FreezerburnV 13y agoIn regards to your specific question about how to cleanly resolve the two AI problem, I can think of two ways to deal with that situation. 1) Lock-based "update the world state" kind of solution. Whichever thread manages to grab the lock for being able to pick up an item is the winner, and actually gets the item. The world is modified so that the item no longer exists. When the other AI gets the lock and tries to pick up the item, it will get some kind of error letting it know that the item no longer exists. Or it can double-check for the existence of the item, and abort the pick up attempt before an error can be generated. 2) The other resolution I can think of is to have a queue of state updates. Basically, both AI broadcast to a queue that they wish to pick up an item. Whichever AI queues first, gets the item. Some logic runs to give the item to that AI, (thus removing it from the world) and then sees that the second attempt to get the item is no longer valid and aborts the attempt. I think those two ideas make sense at a high level. (I'm sure the nitty gritty is complicated) Also they don't deal with having to roll back state to a previous point in time. (I think) Any thoughts?
- Morgawr 13y agoWith Clojure it's a bit different cause you want to handle non-locking using atoms/refs/agents/whatever. In my engine you can either a) have the first entity send a message to the object it wants to pick up, then have the object handle a queue of transformation functions on its state. The first function that is run on the object's thread is the one that changes its state or b) if you want immediate response have the first entity actually act on the object to pick up, due to atom (or refs) locking/transactions only one of the two entities can act on the object, hence the second one will fail to pick it up.