Huge meshing speed boost! Crazy performance!
New features:
- Meshing is now insanely fast. Try holding +, the world will expand at supersonic speeds.
Technical details:
- Read about ZeroMQ and replaced my mutexes with a rough messaging system.
- Create a unique_queue which maps keys to elements, and given a key, can move the corresponding element to the front or back of the queue in O(1).
- Came up with a really cool algorithm for sharing world data with other threads without worrying about it being edited. (See extra notes at the bottom.)
Controls:
- F11 fullscreen
- R toggle mouse raw input
- T toggle t-junction fixing
- Mouse look around
- WASD move around
- Shift/space move up/down
- ESC quit
- N toggle noclip
- P cycle polygon mode
- [+ or numpad+] (press once or hold) increase render distance
- [- or numpad-] (press once or hold) decrease render distance (without unloading existing chunks)
- C toggle face culling
- Left-click destroy block
- Right-click place flowing water
- F3 enable debug overlay
- Scroll change block type (active block type visible in debug menu)
Extra notes:
When you hear "meshing speed boost" you may think that my meshing algorithm has been improved, but no; it's the inter-thread communication that has improved. My main thread and meshing thread (only one for now) communicate much more efficiently.
Old way:
-
Main thread creates a "mesh generation request" containing A COPY of a chunk of world data that needs meshing.
-
Main thread locks the "request queue" and sticks the request in there.
-
Meshing thread picks it up, creates the "mesh generation response", and sticks in in the "response queue."
-
Main thread picks it up and updates its list of meshes.
New way:
-
Main thread creates a "mesh generation request" but now with A CONST REFERENCE to the block data.
-
Main thread asynchronously sends the request message through zeromq (basically a super-fast socket) to the meshing thread.
-
Meshing thread receives the request, removes duplicate requests using an O(1) unique_queue data structure, generates the mesh, and sends it back through zeromq.
-
Main thread asynchronously receives mesh and updates its list of meshes.
Without locks and conditional variables, everything is much smoother!
Now you may be thinking "How can you send a reference to the world data? What if someone else modifies that data while you're meshing it?" I came up with a clever way to overcome that problem:
-
The world stores data as shared pointers to chunks of blocks.
-
When creating the "mesh generation request", the world simply duplicates the shared pointers to the data, increasing the reference count of the shared pointers until the meshing thread discards it.
-
When placing or breaking a block into a chunk of data, the world checks the reference count of the shared pointer - if it's 1 (only the world has a copy), the world simply edits the existing data. If it's more than 1 (at least one meshing request is somewhere in the request queue), the world creates a copy of the data and discards the old shared pointer.
Now you may be thinking "What are the downsides of this strategy compared to the other one?"
- So far, none. Using the old method, every time we placed/destroyed a block we would create a copy of the block's chunk data. Now, we only create a copy if the chunk is currently being meshed.