Hi,
MessiahAndrw wrote:Your "process per object" is very much
Agent-oriented programming. I thought I came up with the idea back in university until my professor told me that it has already been done.
Your professor was right.
The idea of "software as cooperating isolated pieces that communicate" dates back at least as far as the
actor model(1973), is mostly what
Alan Kay meant when he invented the term "OOP", is the basis of
highly fault tolerant systems, plays a significant role in
large scale/highly parallel systems (e.g. super-computers), and is mimicked by networking protocols and the modern Internet.
It has had many names, and even though the details (e.g. the specifics of the communication mechanism, and the granularity - large numbers of tiny objects vs. smaller number of large objects) vary significantly, the underlying concept remains the same.
In general there's a compromise between communication costs and parallelism; and this compromise determines granularity. Basically when there's high communication costs you want a small number of large objects (to minimise the communication costs), and when there's negligible communication costs you want a large number of tiny objects (to maximise parallelism).
For my approach, the granularity of objects varies - you can have a process for each individual chicken, or a process for a collection of many chickens, or a process for a large collection of animals. Within each process you have multiple threads, and they could be used as "one thread per sub-object" but they could also be used as "master thread and worker threads" or anything else.
MessiahAndrw wrote:The main problem with this approach is that it's not the most efficient way to handle process-intensive agents. For example, your physics agent tries to created a thread per core, rendering agent tries to create a thread per core, etc. But suddenly, because your have more than one worker thread per core, the OS might end up assigning most of your worker theads from the same agent to theat same core, losing the parallelism advantages.
Several things here...
First; you're assuming that all threads consume as much CPU time as they can. For most threads this isn't the case - they'll sit idle most of the time waiting for a request/message to arrive, then handle that request and send a reply, then go back to being idle. If 1 thread handling 100 requests per second uses 10% of one CPU, then 100 threads each handling 1 request per second uses 10% of one CPU.
Real-time rendering is special because it can easily use all CPU time you throw at it and still not get enough CPU time; but this means it must be able to sacrifice quality (either detail or frame rate or both) when it doesn't get enough CPU time, and that means the scheduler can give it whatever amount of time it likes (e.g. any CPU time other threads didn't use, and/or limit it to a percentage of total CPU time).
You're also assuming that all threads are equally important. They mostly aren't. Something that (e.g.) determines if grass and plants should grow, or fire should spread, or if that chicken far in the distance lays an egg? These aren't very important and can be low priority threads. A keyboard driver? That's very important and needs very high priority. Mostly, thread priorities are used to determine what gets sacrificed/postponed when there isn't enough CPU time.
Finally; you're assuming that any process can use all CPUs. For a distributed system like mine this isn't true - a process can only use CPUs that are within one computer (and processes are typically limited to CPUs within one NUMA domain instead). If physics takes a thread per core on one computer, and renderer takes a thread per core on another computer, then they're not competing for CPU time.
MessiahAndrw wrote:The other disadvantage is that game developers like to keep things in lockstep (which is especially important in network games - think Age Of Empires - instead of broadcasting what thousands of little simulated people are doing, it just sends the high level user actions across the network, with every client running a deterministic simulation in lockstep.)
What you see as a disadvantage, I see as an advantage.
Essentially you're breaking time up into small pieces, and any events that happen within a time period you pretend didn't happen until the end of the time period. For what you're thinking the time periods have to be larger than "round trip network latency"; and for slower networking (Internet) the time periods need to be large (relative to a human's perceptions) which translates directly into "laggy" (or network latency has a direct effect on responsiveness). It is also unfair/racey, in that one player can do an action before the other player does, but both players see the later action occurring first and the earlier action occurring later.
Now imagine what happens if you break time into much smaller pieces, like nanoseconds. When the time period used is less than network latency you can't rely on "committed state" alone (actions that have occurred may not be received yet); and you need to have both "committed state" and "predicted future state" plus the ability to deal with false predictions. This is a little harder. However, it is not unfair/racey, and it does not have the "network latency has a direct effect on responsiveness" relationship. Instead, higher latency means a higher chance of false predictions and more corrections. Depending on how you make predictions and how you correct them, these corrections can be almost entirely unnoticeable even with severe network latency (e.g. packets being dropped due to congestion). Also note that you can still bunch up multiple actions that have occurred at slightly different times and send a single report (it's just that you'd be attaching time stamp to each action rather than having a single time that's "assumed" for all actions).
MessiahAndrw wrote:The mainstream approach to scalable parallellism in high end game engines today is the
thread pool pattern. If the game is running on a 6-core computer, then 6 worker threads are created. There is a pool of tasks, and if a thread isn't processing a worker thread grabs the next task to be done.
Simple example: You have 100 objects in your game with their own update routine, so at the start of the update loop you add your 100 update calls to the task pool, and let the worker threads go crazy.
Yes; this doesn't scale to 2 or more computers and only does a crude/broken approximation of continuous time.
MessiahAndrw wrote:Also, about the idea of having a library of objects is the motivation behind Unity's
Asset Store. You can import high quality trees and road pieces to build a track, import a high quality car model with physics and controls already programmed in, import a Car AI library, slap on a nice menu, and you have a realistic car racing game in under a day.
That's very similar to what I want; except I want it fully standardised (so all assets of a certain type have the same controls, etc) and with meta-data (so that processes can dynamically choose from available assets), and without the separation between "developer community" and "modding community".
Cheers,
Brendan