If we are just going to repeat tired old arguements Ill just repeat my truism, most of the compute intensive work in games boils down to simulation ... and reality is massively parallel.
I missed this truism before, so I just wanted to make a quick note of this.
True, reality may be massively parallel, (or not, it could just be a miraculously fast single thread) but as far as we know reality:
a) Synchronization is free, if something is not synchronized, it's because it's not meant to be
b) Storage is exactly as big as it will ever need to be
c) Storage is accessible exactly as fast as it needs to be (not just speed of light, things may possibly go faster)
d) If phenomenon A and B take 1 second to complete, it doesn't matter if B is 10 times as mathematically complex.
e) Reality has at its base behaviors that may be truly random, no amount of massive parallelism will make that work with an algorithm. (Minor aside, not really important for most things)
f) There is no doubt if two things will interact. If they interact, it's because they interact. If they don't, they (by definition) do not. That's not a viable development method.
g) Whoever's at the controls has not been tempted to hit Ctrl-Alt-Delete
h) The hardware apparently doesn't fail after 20 billion years.
I think you may need to read more on how HashLife actually works. HalfLife can and does skip over steps and it does have an increasing performance slope. Indeed, the first 100-steps may take 1 minute to calculate, the next 1000 steps may take 10 seconds, and the next 1,000,000 steps may be calculated in only 1 second. If you were to display a HashLife simulation in progress, you'd see some regions jumping far forward, and even others appearing to move backwards sometimes. However, in any given region, all of the values are provably correct for the local time of that cell.
I think that depends on how you are displaying the simulation. If you cram all the parallel activity together without taking program flow into account, sure it would look wierd. There's still a meaningful flow. Since the algorithm is not in-place, a read from memory locations by following the return address stack can show a very definite order.
As the program runs, it learns, and as more and more configurations of the grid are encountered, it's speed only increases. Simulating arbitrarily large grids into the far future, paradoxically, is easier than simulating in the near term. And yes, the problem does skip over time steps, literally, in a log_2(t) fashion. It can do so, because the locality of prediction guarantees that many intermediate steps are not needed in order to calculate the smaller predictable grid at time t*2. The only major downside is the memory consumed in running the algorithm.
It does not skip a calculation, the simulation just doesn't repeat itself. At some point in the program's execution, that given time step with the given preconditions was done at least once.
I would also note for practical reasons that the "only major downside" is a pretty major downside, especially since it relies heavily on the hardware and run-time environment to enforce its implicit ordering of calculations. If the algorithm were running in-place, things would have been a lot more complicated. Because it is not in-place, place in memory and in the structure supply what naive time-keeping isn't.
Think this only applies to Life? Let us impose a speed of light on our game world of 'C', and let us impose a "planck time" of t, (e.g. 100Hz 1/100th of a second). Let us divide the game world into partitions with the smallest being C*t in size (if C == 1000 m/s, and t = 1/100th s, then C/t = 1 meter). In any partition NxN in size (N = some multiple of Ct), the state of the partition N/2 x N/2 in size at time t*2 is completely determined by the state at t.
I agree this could be useful in certain situations. The rest of my issues lie in running it in real-time.
It becomes very critical that you tell me what a good C would be.
If it's a game where a human player's time frame is inserted into the game world, C can't be whatever we choose, it must be close enough so that it isn't ridiculous to play, but not so fast that it becomes ridiculous to simulate.
The relationship between C, the partition size, and the minimum time tick determine the size of the workload.
You want a fast enough C, since the player doesn't see everything as moving like it's underwater, but
If C is large, the time-tick must be smaller if you don't want the partition size to grow too large, since that prevents culling in a crowded space, and it forces more unnecessary synchronization.
If the time-tick is too small, you wind up looping through things that almost never change.
Worse, time-ticks are have at least a minimal serializing effect. That means synchronization or praying nothing goes wrong too often, because cleanup code takes real-world time.
But the larger the time-tick, the more you must decrease C or shrink partition size.
If the partition size is smaller, you must decrease C or speed up the time tick to limit propogation.
You want fewer partitions, since the more there are, the more state you have to maintain. But you don't want partitions to have too much going on inside of them, since that ups the computation cost. You can't eliminate excess work if the partitioning is too large to allow for adequate subdivision.
There's another problem.
You don't want subdivisions so small that objects span too many of them, but in a game, not everything is a point-sized object like in HashLife.
A free flowing ( or nearly so) 3d world also has a lot more neighbors than a 2d grid that artificially removes a vast set of complications from the simulation.
It becomes a volume problem, worse, a non-integral volume problem if this is a game world and you do not continue to use the fixed points in space idea behind the Life simulation.
A meter-sized cube is a very coarse subdivision of a game-world. Remember, as the "plank length" of the simulation, any and all changes must be in multiples of a meter.
This also means that things must either move at least 1m per time-slice or not move at all.
So unless the minimal time-slice is more than a second, there is no way to go less than a meter per second, or more than a meter per second.
That paradigm works great when time and space units are infinitestimally small.
We can proceed to memoize results just as was done in HashLife, as long as we can design a suitable hash algorithm (as I showed in another message, such an algorithm has already been created for N-body gravitmetric problems) Physics within the minimal partition size will be run as the base case.
The ceiling for perfect predictability in a game with user input is the number of time steps between system reads of user input. Any more and the viability of the speculative results becomes increasingly suspect the more they speculate past a given IO poll.
If the system can be guaranteed not be be perturbed in a fashion that would not follow from the previous time step, then HashLife's assumption would be correct.
What to do? Hash the combination of possible user inputs (input, action, place in the spatial grid,relationship to any number of other time stamps) as well? Invalidation also needs to progagate, so that means that the size and effectiveness of the memoization cache is inversely related to the number of perturbations to the system per a given number of time steps.
If it is 1 perturbation per time step, and it is maintained for longer than the stride of all the layers, then the number of valid memoized results will be little more than what you would get from an initial run and stopping, then starting, then stopping, then starting.
What is the maximal size of the state representation of a body in an N-body gravimetric problem?
Aside from position, the rest is calculated from the positions of everything else. What happens if you aren't worried about point objects that have no health, and don't have a fixed function that determines their next move in the space of perhaps a half a kilobyte?
Once this is finished, we can simulate game physics in arbitrarily large worlds and query for the state of any game partition at any large time in the future without necessarily having to recompute the entire world, or intervening steps. Note, this does not depend on the base case problem being analytically solvable! (e.g. 3 body problem is insoluable analytically) Life, and many cellular automata do not present analytic solutions.
Are you trying to enter real life now? If N=1 and it takes 1 minute, and N=10 it takes .1 minutes, do you really think N=10^64 will take less time than it takes for an electron to move between the atoms in the processor?
Memoization is a win as long as the cost of the memory access is dwarfed by computational complexity. It can't make the cost of memory access zero.
What about practical constraints? Memoizing an arbitrarily large universe means an arbitrarily large amount of memory. HashLife takes more memory to simulate something, it doesn't scale as insanely as a non-canonized quad tree (canonization being somewhat serializing, by the way), at a bare minimum it takes the minimum number of bits needed to represent the unique state of the system. If a larger universe always takes the same number of bits or less than a smaller one, then something is being lost.
If we can agree that it takes some number of bits X to represent the simulation state, and that nothing is being lost, then X will slowly but inexorably increase. It's trivially true that at some point every bit will be traversed by the simulation as it calculates the next time-step. Unless the time per bit magically is always faster, HashLife does not have an eternal speedup.
I'd expect it to have a kind of nifty-looking time function, though.
Depending on the input, it could go faster or slower based on some factors:
The number of unique canonicalzed nodes: the given simulation is simple and it is probably not sustainable to have all canonical nodes per layer, so this one doesn't strike me as being a dominant factor very often.
At level zero, there are two canonical nodes. Unless of course a simulation is entirely empty. (After a generation, it's still entirely empty, but that's just a silly setup.)
What's cool about the canonicalized tree is that it's an incredibly compressed layer, since the number of leaf nodes in a quad tree go up by powers of four of the number of levels.
At level one, there are at most 16 canonical nodes, and as long as the size of the simulated world is such that the number of non-canonicalized nodes is greater than 16 (plus a fudge factor for the 256 extra bits needed in a 64-bit pointer system to point down to the children), it will be more compact.
At level two, there are at most 16^4 canonical nodes, but that's ~65 thousand for what could be billions of nodes otherwise. I'm very interested in seeing the math concerning what the number of canonical nodes vs problem size.
And so on.
Each successive level added to the top of the tree will have this growth for the maximal number of canonical nodes, though for any problem arbitrarily larger, it is a fixed number against any number of additional nodes.
It's a very nifty algorithm, but it won't magically make all the extra work disappear.
Given problem size N, it cannot be said that every problem size from N+1 to infinity will run faster.
What it does guarantee is that thanks to the limited number of canonical nodes, the number of arithmetic operations is unbelievably greater than the number of lookups.
The number of lookups places a floor that continuously grows, but at a sublinear rate. It never goes permanently down. There are inputs that can cause the numbers to oscillate, but not stay down.
The pointer size:
The pointer size determines the size of the hash space (or a quarter of it), once the number of canonical nodes exceeds the number 4*pointer size, something is guaranteed to collide. Thankfully for HashLife, the simplicity of the problem makes it so that there's plenty of room in the space being hashed to for things to work for a long long time.
If the number of bits needed to represent a given cell increases, the hash function runs into problems.
If the rules became that a cell could have 4 states, dead, ailing, normal, robust.
That's a max of 4 canonical nodes at 0, 256 at 1, 256^4 at 2, and so on.
For a two-bit simulation, it's likely something else would get in the way of performance long before the hash became a problem.
I wonder about a simulation in which an object's bit representation exceeds the pointer size, which means that there will likely be a collision once the number of cells got large enough.
It is far more likely, however, that other problems with holding all of this data will get in the way, though this can be minimized with a memory setup where a given location can store the entirety of a node's contents.
The special cases are the following:
1. objects which don't exist completely within one partition or another, but which span two partitions.
If the objects aren't points, or nearly so, then it is very likely to happen. HashLife's objects don't span anything, they and their unit of space are the same.
If that assumption is not changed, the partitions are just too big, and everything is a cube.
If that assumption is changed, then objects could span more than just two partitions. In a full-fledged 3d environment, it's all partitions in a sphere around the object.
2. partitions which include non-linear or non-local inputs: random events, AI controlled characters, human controlled characters
HashLife already handled #2 a long time ago by introducing hash entry invalidation and dirty bit concepts.
I'm curious to see how it affected performance scaling. Especially if invalidations become too burdensome. Beyond a certain number of future steps, it might be pointless.
#1 may or may not be handled by subdividing the object.
That would involve some kind of synchronization, which would be a variable load depending on how often this occurs.
In any case, I'm not advocating that HashLife's algorithm itself beused to solve the large scale simulation problem in games. For one, it burns memory like crazy. What I am trying to show is that monotonically increasing step-by-step time simulations are not neccessarily needed to get results, and that lazy evaluation and "call by need" may offer alternatives.
HashLife is step-by-step. It's just so simple that you can reuse steps.
That doesn't work too nice once the relationships and outcome spaces explode.
HashLife also does assume an implicit form of synchronization. It's present in the function calls and the use of the "new" operator. It's (very intelligently) sacrificing memory space to save on time spent calculating results, and it's using the work the OS would be doing anyway as its synchronization.
I am also trying to stimulate interest in the idea that regions of the game simulation can be partitioned into local predictable cones of influence (which may not include AI actors or players) which need not be simulated at every step.
That's a good idea, but it's not just a cone, not unless the given object is the only one that can move or interact.
It's more of a warped sphere, where the cone juts out in front and the rest of the sphere (smaller in back) is there to catch if other objects' cones may meet up with the object.
The coarser the time step, the larger the sphere portion becomes.
In my "pot roast" example, going and looking at the pot roast 30 minutes later and seeing it replaced with a cooked fascimile is the equivalent of a semantic hash table lookup (hashtable.get(roast, 30 minutes) -> return pre-simulated cooked roast).
My objection was that without some form of coordination, you do not know the value "30 minutes".
How can you without some kind of timekeeper or synchronizing scheme? Where is the time coming from?
HashLife works great because it rests on the assumption that the system handles all of that already. Unless you think it's a good idea that the "new" operator be allowed to write to an address in the heap without checking to see if there's a valid object there, there is some form of synchronization.
I'm arguing that while lock-step is a form of synchronization, it is not the only form.
As long as a system's resources are not infinite and operations take non-zero time, some of it will be necessary.
Depending on level design, one can mark whether an object is "local" or has non-local effects (aroma of meat cooking, sounds of boiling) and thus the simulator can decide whether it needs to micro-simulate that partition, or whether it can replace it with a cached pre-computed representation later.
It still needs to know which cached representation to use. If that is not known, it does not matter how many precomputed states there are. There is no algorithm that can make garbage into a valid result.
If an algorithm can take a "garbage" input and somehow make a valid computation, it's either lucky coincidence, or by tautology, the input wasn't garbage at all.
The reason HashLife has philosophical implications for our reality is the implication that what you experience as an arrow of time and history of states may be completely illusory and that reality itself does not neccessarily "compute" all states in between. Yes, we can run measurements to measure phenomena at arbitrarily small resolutions, but if the universe works like HashLife, than our perception of having taken 1,000 samples at 1000hz may simply be an illusion and our memory of "now" merely contains the perception of having carried out all of those steps, when in fact, the universe jumped forward 1 second and our memory reflects the correct and consistent history that never in fact occured.
Or if you think there's something to quantum foam theory, it calculates everything and more.
I've already gone into the fact that a simulation, if properly made, does not have any perception of outside time. A player's actions are mapped so that they match what an object would do in the simulation. The problem is that without synchronization of some sort, outside time does filter in.
Some things take longer to simulate than others. Maybe because of instruction count or mathematical complexity, it takes 2 ns in our time for A and 3 ns for B.
If A is not made to wait by something and it is allowed to loop, it will go faster than B. Unless the simulation is defined in such a way that it doesn't matter, something must keep track so that if B needs to interact with A, it interacts with the right version of A.
People have long questioned the continuum vs the discrete conception of time. However, in both conceptions, a monotonically increasing linear order was always assumed.
There is a lot of evidence this is not entirely the case at the quantumn level.
Though as far as games are concerned, this is a non-issue, quantumn mechanics does not make intuitive sense, and it's not really fun either.
When my character tunnels through a wall of a skyscraper and I fall 1000 feet, that's a bug, not quantum mechanics sneaking up on me.
Step T_n depends on Step T_(n-1) according to transition rules or the laws of physics. The philosophical debate engendered by HashLife was whether we even need this assumption, maybe the universe can "jump" or "tunnel" between arbitrarily large configurations, as long as all possible observers in the later configuration agree that the later state was a evolutionary consequence of the former.
It's not really necessary that observers agree even then. Einstein already illustrated the folly of determining simultaneity between three observers signalling across a distance with relativistic speeds.
Time may be entirely local, but thanks to the great speed of light and the matter-based laws of our means of perception and thinking, it's not very often we see odd effects.
Then there's entanglement experiments, which have been shown to possibly violate even the speed of light.
Time of course, would be merely an illusion, an after-though mental explanation by back-predicting the states that could have lead to the current one.
Even if it is, it is a consistently maintained one, even without our need to justify it mentally.
I perceive myself to have had a life history, because information in the state of my brain leads to me believe that the current state (now) could have been the consequence of prior ones (the past). Cause and effect extrapolation. The assumption of continuum, that a continuous stream of time existed between all events in my memory, are just that, an assumption, or an extrapolation.
And I'm sure if we are in a vast simulation, that there are higher beings tearing their n-dimensional hair out trying to simulate it so it's not taking forever for Them or Him or Her or Whatever.
In any case, I bring it up because HashLife is not just a curiousity. When Bill Gosper invented it, people were already playing with the idea of artificial life and intelligence within these grids, and philosophers began to ask "what if?" our universe amounted to nothing more than a big cellular automaton.
As far as I know cellular automatons, there are signs the universe routinely violates propogation restrictions so it may be a bit more complex than that. It's also not proven that the universe is deterministic, or that what is random in the universe is truly random.
If there is true randomness (it might not be, it could just seem that way), then no automaton can be the answer. There is no algorithmic way to arrive at randomness.
If so, what if it evaluates using HashLife techniques? Then many of our most fundamental assumptions are wrong. Sure, sophistry right? Still, it makes for interesting thought experiments.
It doesn't make a single one of our physical assumptions wrong at all. It might make for interesting theological debate, but as long as internal consistency is maintained, it doesn't matter at all how weird things are outside of our simulation.
The idea that our reality is The Reality is almost a non-sequiter to physical laws, which only worry about consequences within their context: a context which is safely insulated from a possible exterior world.