[ForkJoinPool.commonPool-worker-5] WARN com.rvandoosselaer.blocks.ChunkCache - Chunk(location=Vec3i[15, 6, 13], empty=false, full=false) is evicted from the cache, but it’s node is still attached to parent Root Node (Node).
It then calls chunk.cleanup() on the Chunk. Which then causes nullpointerexceptions elsewhere because all the fields in Chunk have been set to null.
I built a priority queue wrapper for this problem some time last year.
The idea was to add items with an optimistic priority.
The optimistic priority is calculated as ‘total player distance travelled’ + ‘distance to item’. (Or in other words: The currently lowest possible total player distance travelled to reach the item.)
After taking an item from the queue, i recompute its optimistic priority, and readd it to the queue if the next (peeked) item can be better. This is repeated until the item cannot be further improved. (edit: or until the difference is below a tolerance threshold)
The cache I use is caffeine. The eviction mechanism of this cache is handled in separated threads. When your cache size is equal or almost equal as the actual size of the grid of chunks you want to display, it can occur that the chunk is evicted from cache before it’s node was detached due to racing conditions.
If you call ChunkManager#remove the chunk might get evicted before it’s detached.
The same behaviour can happen when the chunk mesh (re-)generation is running, but the chunk isn’t in the grid anymore and get’s detached. The mesh generation task can then throw a NPE as the chunk was cleaned up. All those errors are handled gracefully, but are still logged.
I already created a ticket long ago to look into this. Once a chunk is removed it should cancel all it’s running tasks. It already removes it’s future tasks, but not the already started ones.
I just add CCD. Before, ball go trough if shooting from exact angle and exact place. So far ball not go trough anymore. I don’t know other way to test it but keep shooting the ball.
When we do physics, and somehow it makes holes so some physics for example balls can go through, the holes will be the same everytimes we run the physics. Balls only go through in the same place and from same angle same place (or in tiny average). But we can’t predict where the holes will be made. I mean the blocks is made of quads with the same size. that’s my guessing.
If you haven’t already done so, it might be helpful to enable Minie’s debug visualization by (for instance) replacing
stateManager.attach(new BulletAppState());
with
BulletAppState bas = new BulletAppState();
bas.setDebugEnabled(true);
stateManager.attach(bas);
Doing so should allow you to quickly determine whether there are gaps in the physics objects or places where they otherwise fail to match the block faces.
Question: what are the implications of setting ChunkManager.triggerAdjacentChunkUpdates to false? (instead of true)
After changing it from true to false, the performance of my program seems to have improved, as the meshes of adjacent chunks don’t have to be recomputed. My program still seems to work correctly (as far as I can tell).
Is there a disadvantage to setting this to false?
The mesh generation algorithm looks beyond the boundaries of a chunk. It can happen however that at the time of the chunk mesh generation, the adjacent chunks are not ready yet and the algorithm ‘wrongfully’ thinks that there is no adjacent block.
This flag is a ‘bazooka’ solution to counter this behaviour. When the mesh generation of a chunk is done, it triggers a mesh recalculation of the adjacent chunks.
The performance impact is that the mesh queue will be a lot longer as the mesh of a chunk will be regenerated a few times. The generation logic on its own, is not different. Meaning that the generation time will be the same, if the flag is set or not.
In some of the example applications this flag is set. When running the example and enabling wireframe mode, you can clearly see that the shared meshes between the chunks will be removed as more chunks are generated.
OK so it sounds like it is OK (or even preferable) to set triggerAdjacentChunkUpdates to false.
Another question, this time about the Pager on startup.
I just realised that the Pager doesn’t attach any chunks to the scene graph until all chunks in the grid have been generated. So on startup you get a long delay while the system is generating all the chunks in the surrounding grid before it adds any of them to the scene graph. You suggested earlier that I add some kind of wait screen to inform the user while this is going on.
Would it not be better to attach chunks to the scene graph as soon as they become available? Instead of waiting for the entire grid to be generated first? That way there is basically no delay for the user, as chunks become visible on screen as soon as they are generated.
To give you a better idea of what I am talking about, I added these lines in Pager.ChunkPagerListener:
This is wrong. Each update call, one page can be attached, updated and detached. There is no wait-till-the-full-grid-is-available logic.
When a page that should be attached is not available, it is requested and put back at the end of the queue.
As attaching and detaching a lot of meshes can have a serious performance impact (all buffers need to be send to the gpu) I choose to only attach/detach 1 page per update call.
In your code, you immediatly attach a page when it becomes available. This might work in your setup, but in other configurations it will not work.
You should also be careful to make sure that you attach the pages on the main thread. If your chunkmanager is managed on a different thread, so will be the callback of the listener. This will throw exceptions as you are editing the scenegraph outside the main thread.
There is no wait-till-the-full-grid-is-available logic.
When a page that should be attached is not available, it is requested and put back at the end of the queue.
You are correct that there is no explicit logic. However, putting a page “back at the end of the queue” means that every other page already on the queue (i.e. every other page in the grid) has to be sent to the ChunkGenerator first, before this page will come back to the head of the queue and be attached to the scene graph.
I choose to only attach/detach 1 page per update call.
OK. In that case, perhaps there should be 2 queues in the pager; one for sending pages to the chunk generator (which would not have to follow this rule of 1 page per update call) and another queue for attaching generated pages to the the scene graph (which would process 1 page per update)
In my case, the grid is 8000 chunks (31 x 31 x 5). I have to wait for 8000 updates (at the minimum) before the first page is attached to the scene graph. This means I am waiting 25 seconds for anything to appear on the screen, vs the scene appearing almost instantly with the code change I made. And this penalty has to be paid both on startup and any time the player moves to a completely new location (e.g. if your game had some kind of “teleport” function)
Thanks for the heads up on the threading. I will have a look.
I am a bit confused as to how the Chunk Manager’s cache and repository works. The wiki states that a world that has a size of 9, 5, 9 (in chunks) will have a cache of size 9 * 5 * 9, which is 405. From what I understand, while the cache has a total capacity of the world size, it only actively contains the chunks that are being paged by the Chunk Pager(the size of which was specified before). So, if that chunk isn’t in memory(it’s not being loaded by the player or pager) then it is not in the cache. Is this correct? On top of that, what is a repository? Is it a file that contains all chunk data? When does a chunk become available in the cache? When is it removed? getChunk() only works if its in the cache I believe, but what does requestChunk() do? Its there to access chunks not currently in memory, I think, but does requestChunk() “read” the repository(the world file I assume) for that chunk data since it’s not in memory? Sorry for the long post, I am just a bit confused.
Also, would you happen to know any tutorials / docs for JayFella’s FastNoise? I’m a bit confused on some parts of your endless runner but I assume that’s because I don’t understand the FastNoise library (I am confused on what the layers are, frequency, hardfloor, waterheight, the usage of Math.max(why it is required)), I assume most of this should be resolved once I understand FastNoise.
This is the minimum default size of the cache, when you don’t specify a size yourself. This is the amount of chunks the cache will keep in it’s memory. The pager and chunkmanager use the same BlocksConfig#grid parameter to configure their default settings.
This way the chunkmanager will only keep the bare minimum amount of chunks in the cache that are required to render the grid by the pager.
You can create the ChunkManager with a larger cache than the size of the grid. When you move over the boundaries of a chunk and the pager request new ‘edge’ chunks, the chunks can be immediately loaded from the cache instead of being loaded from a repository or being generated first.
A repository is an abstraction (interface) to load and save chunks. It can be whatever you need for your project. I supply a FileRepository implementation that saves and loads chunks from a file repository on disk.
When it is requested using ChunkManager#requestChunk. Depending on how you configured your ChunkManager, it can be loaded from a ChunkRepository, generated from a ChunkGenerator or just instantiated as an empty chunk.
Either manually when ChunkManager#removeChunk is called or it is automatically removed during cache maintenance. Cache maintenance is triggered using an interval in the update loop, you should not care about this.
ChunkManager#getChunk will fetch a chunk from the cache and return it. When it is not in the cache, it can be requested using ChunkManager#requestChunk.
You should really see the ChunkManager as a helper for managing chunks, so you don’t need to do this. You can supply it with a repository (to save and load chunks from) and/or with a generator (to generate chunks). You should even not care about the ChunkCache, the ChunkManager does this for you.
When you need a chunk, you can simply request it from the ChunkManager and it will take care of the loading/generation/mesh generation/… etc for you.
Best to summon @jayfella for this.
If I remember correctly, I created a lemur toolbox to change the parameters on the fly to see what happens.
The documentation is sparse, agreed, but the terms you describe are generic noise equations.
A layer is an additional noise value that is calculated with the existing layers. In terms of terrain you would typically start with “big” layers such as continents, mountains, etc and work your way down to finer details with each layer.
the “hardfloor” value is sea level, where the terrain “plateaus”.
The use of Math.max is … to limit a value to a maximum value.
I guess this is a basic video of how I created some visually appealling terrain. I think I recorded it with FRAPS, which didn’t record the javafx combobox overlays.