But Abdiel: how do you propose the engine would handle infinite terrain then?
We actually had a quite in-depth discussion about this with a few friends (also programmers like me) about half a year ago. I don't remember all the details here on the spot, but here are the most significant bits:
- Decouple static data ("world") from dynamic "entities". Assume that World almost never changes, if it does, only as a result of some Entity and only on a miniature scale (few blocks at a time).
- World resides in a database. Entities are loaded or unloaded into memory. The only time pieces of World are loaded into the server's memory at all is when it is being changed (+- some collision detection). There is absolutely no reason to keep hundreds of chunks of static solid rock in memory, ever.
- When client explores an area, send them updated World data once. From there on only the client takes care of rendering World, the server doesn't need to care about it.
This alone would reduce the amount of data that the server needs to store in memory at one time by a few orders of magnitude. Let's look at Entities next:
- Every Entity (in vanilla) has a very simple and predictable behavior. A furnace smelts one block every 10 seconds, a minecart moves 10 blocks a second, a repeater sends a redstone signal four ticks after it receives one. Forgetting redstone for a while (I'll get to that in a bit), we can predict the state of any one entity in any moment, if we know its state now.
- Simulate running entities only when a client is around to observe them. What exactly this means depends on what's computationally feasible, but also on the type of the entity. Worst case, simulate an entity whenever a client is in a given range.
- Example: A minecart should be moving whenever a player looks at it. But a furnace doesn't need to be smelting while a player is not looking at the GUI. It is enough to remember when the smelting started, and how many items and fuel are there in the furnace. Whenever a player opens the GUI, it is trivial (as in, a few instructions) to calculate what state the smelting is in.
- Entities sometimes cause various actions to happen, we'll call that Triggers. A furnace that finishes smelting changes its texture. A plant grows. Etc.
- We may need to update the game state (and World in some cases) whenever a Trigger happens regardless of whether an Entity is being actively simulated. This can be easily done by a technique called Discrete Simulation (look it up, or just trust me that it's simple).
- Whenever a client comes within range or starts observing an Entity, the Entity may need to "catch up" with its internal state, if it was not being simulated. E.g. as I mentioned before, the furnace needs to update the number of smelted items. For most vanilla entities, if we know when was the entity last simulated, and what state we left it off, we can calculate the state it is in easily. (Again for a furnace, take the time since the last simulation, divide by 10 seconds, subtract that number of inputs and add that number of outputs +- some math with fuel.)
- The simplest, but computationally most expensive form of catching up, is to simply simulate the entity for the number of ticks it had been unobserved. Again, a discrete simulation ensures that we don't actually have to perform as many actual computations even in this case.
The last thing that's being a problem is redstone, as it is easily capable of emitting loads of updates if left running. Our collective opinion was that we should optimize for small systems, with up to few hundred redstone blocks, and not for people building fully functional computers and stuff (sorry.) Unfortunately, there is not much that can be optimized here. You can treat an uninterrupted line of redstone as a single entity. There are a few known algorithms for identifying patterns, such as "understanding" what a redstone clock is, but those are for the most part theoretical and not easily applicable to such a complex situation. However, we could modify game mechanics a little: add simple circuits as individual blocks, like Redpower does. These circuits are much more predictable than a random mess of wires and torches, and we could perform the "catch up" on them without having to simulate every tick.
So, to sum up, the server would keep in memory and work with:
- Small pieces of World, mostly for collision detection and performing updates. Think, uh, a 8^3 or 16^3 area around every client, less than that for mobs. (In practice this would actually probably be more to speed up hard drive access, but it would be 3D areas, not stupidly tall chunks.)
- New pieces of World that are being freshly generated or sent to clients.
- Entities that are currently being observed by clients, which are performing some action (minecarts) and are not just waiting for a Trigger (growing reeds).
- Some specific complex behaviors for which we can't perform the "catch up" in reasonable time - i.e. complex redstone circuitry, flowing water, etc.
- (The above can be sped up by heuristically grouping entities by location, and performing many ticks for each location at once. It is much better, performance-wise, to run one area for 100 ticks, then another, independent area for 100 ticks, than trying to simulate 100 ticks in both areas once. As long as all the required simulation is done by the time the entities are observed, when exactly we calculate the changes doesn't matter.)
For a vanilla game, this reduces the load on a server by a factor of thousands. We could easily run an entire vanilla world like this, regardless of whether people are online or around any machinery.
Now, mods throw a huge wrench in all of this by being able to run practically any code with the machines. When we were talking about this, we didn't consider mods at all. With arbitrary code running in the machines, certain parts of the game would have to be constantly loaded, as there is no way to do the catch-up without simulating every tick. A (partial) solution is to provide an API to give modders a chance to add this functionality. We know that a macerator processes an item in X ticks. We could analyze simple and certain complex pipe networks and machine automation in terms of graph flows (again, look it up). But integrating all the current mods' functionality into this framework would certainly require a lot of effort and probably also re-thinking of basic concepts. The big question is (and I do not have an answer to it), whether the computation resources saved by not keeping World in memory, and running incremental updates and discrete simulations of machines whenever possible would be enough to allow us to simply run all the remaining stuff continuously.
Obviously this whole post is just a bunch of random ideas I remember from a conversation long ago. It is not intended to be anything close to a complete concept, much less any practical suggestion for an architectural change. There are huge holes in it, there are questions for which I do not have answers. But the point remains: Minecraft started as Notch's personal little project to demonstrate his coding skills. The core mechanics were not designed with best programming practices in mind, nor thinking about all the crazy stuff modders do with it these days. Looking back at it now, there are many places where huge improvements could be made. But with the heaps of code and basic designs that would have to be changed, and more code in mods that would have to adapt to this, making such a huge change is almost unthinkable. There is some low-hanging fruit to be picked up (more granular "chunks", not loading static World when not needed, discrete simulation of simple machinery), and that's mostly what I had in mind in my first post.
Please forgive the little off-topic rant, and the wall of text.
I really do not mind if you stopped reading somewhere in the middle.