-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: order chunks on demand #4773
Changes from 8 commits
29cce78
fbc652a
f637a98
1907954
a037898
1f6db11
c0aefbe
d890029
a3cd2ea
a67f4a1
58ed505
52ea11f
87e8511
d738be2
a229fea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
package org.terasology.engine.world.chunks.localChunkProvider; | ||
|
||
import com.google.common.collect.Maps; | ||
import org.joml.Vector3f; | ||
import org.joml.Vector3i; | ||
import org.joml.Vector3ic; | ||
import org.junit.jupiter.api.AfterEach; | ||
|
@@ -13,11 +14,11 @@ | |
import org.terasology.engine.entitySystem.entity.EntityManager; | ||
import org.terasology.engine.entitySystem.entity.EntityRef; | ||
import org.terasology.engine.entitySystem.event.Event; | ||
import org.terasology.engine.logic.location.LocationComponent; | ||
import org.terasology.engine.world.BlockEntityRegistry; | ||
import org.terasology.engine.world.block.BeforeDeactivateBlocks; | ||
import org.terasology.engine.world.block.Block; | ||
import org.terasology.engine.world.block.BlockManager; | ||
import org.terasology.engine.world.block.BlockRegion; | ||
import org.terasology.engine.world.block.OnActivatedBlocks; | ||
import org.terasology.engine.world.block.OnAddedBlocks; | ||
import org.terasology.engine.world.chunks.Chunk; | ||
|
@@ -36,17 +37,16 @@ | |
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.Future; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
import static org.mockito.Mockito.atLeast; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.when; | ||
|
||
class LocalChunkProviderTest { | ||
|
||
private static final int WAIT_CHUNK_IS_READY_IN_SECONDS = 30; | ||
private static final int WAIT_CHUNK_IS_READY_IN_SECONDS = 5; | ||
|
||
private LocalChunkProvider chunkProvider; | ||
private EntityManager entityManager; | ||
|
@@ -58,6 +58,8 @@ class LocalChunkProviderTest { | |
private Block blockAtBlockManager; | ||
private TestStorageManager storageManager; | ||
private TestWorldGenerator generator; | ||
private RelevanceSystem relevanceSystem; | ||
private EntityRef playerEntity; | ||
|
||
@BeforeEach | ||
public void setUp() { | ||
|
@@ -81,36 +83,33 @@ public void setUp() { | |
chunkCache); | ||
chunkProvider.setBlockEntityRegistry(blockEntityRegistry); | ||
chunkProvider.setWorldEntity(worldEntity); | ||
chunkProvider.setRelevanceSystem(new RelevanceSystem(chunkProvider)); // workaround. initialize loading pipeline | ||
relevanceSystem = new RelevanceSystem(chunkProvider); | ||
chunkProvider.setRelevanceSystem(relevanceSystem); // workaround. initialize loading pipeline | ||
} | ||
|
||
@AfterEach | ||
public void tearDown() { | ||
chunkProvider.shutdown(); | ||
} | ||
|
||
private Future<Chunk> requestCreatingOrLoadingArea(Vector3ic chunkPosition, int radius) { | ||
Future<Chunk> chunkFuture = chunkProvider.createOrLoadChunk(chunkPosition); | ||
BlockRegion extentsRegion = new BlockRegion( | ||
chunkPosition.x() - radius, chunkPosition.y() - radius, chunkPosition.z() - radius, | ||
chunkPosition.x() + radius, chunkPosition.y() + radius, chunkPosition.z() + radius); | ||
|
||
extentsRegion.iterator().forEachRemaining(pos -> { | ||
if (!pos.equals(chunkPosition)) { // remove center. we takes future for it already. | ||
chunkProvider.createOrLoadChunk(pos); | ||
} | ||
}); | ||
return chunkFuture; | ||
private void requestCreatingOrLoadingArea(Vector3ic chunkPosition, int radius) { | ||
playerEntity = mock(EntityRef.class); | ||
when(playerEntity.exists()).thenReturn(true); | ||
when(playerEntity.getComponent(LocationComponent.class)).thenReturn(new LocationComponent(new Vector3f(chunkPosition))); | ||
Vector3i distance = new Vector3i(radius * 2, radius * 2, radius * 2); | ||
relevanceSystem.addRelevanceEntity(playerEntity, distance, null); | ||
} | ||
|
||
private Future<Chunk> requestCreatingOrLoadingArea(Vector3ic chunkPosition) { | ||
return requestCreatingOrLoadingArea(chunkPosition, 1); | ||
private void requestCreatingOrLoadingArea(Vector3ic chunkPosition) { | ||
requestCreatingOrLoadingArea(chunkPosition, 1); | ||
} | ||
|
||
@Test | ||
void testGenerateSingleChunk() throws InterruptedException, ExecutionException, TimeoutException { | ||
Vector3i chunkPosition = new Vector3i(0, 0, 0); | ||
requestCreatingOrLoadingArea(chunkPosition).get(WAIT_CHUNK_IS_READY_IN_SECONDS, TimeUnit.SECONDS); | ||
requestCreatingOrLoadingArea(chunkPosition); | ||
chunkProvider.notifyRelevanceChanged(); | ||
Thread.sleep(WAIT_CHUNK_IS_READY_IN_SECONDS * 1000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll want to find a way to write these tests without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's annoying that they take so long now. Unfortunately there aren't any futures to wait on anymore, but I guess we could add an object to wait on in the ChunkProcessingPipeline, or use a loop. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to ask about that: Why did you remove the futures? My guess would have been because you only call the methods involved from a thread dedicated to doing that work, so they're all blocking and don't return until the result is available. But the number of times the tests rely on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, there's just no need for futures currently. Everything is just scheduled for after the chunk is generated via stages. Futures are only needed in the tests, because the rest of the code just submits chunks for generation asynchronously. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added another object which is notified when generation finishes, which fixes the tests. |
||
chunkProvider.update(); | ||
|
||
final ArgumentCaptor<Event> eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); | ||
|
@@ -139,7 +138,9 @@ void testGenerateSingleChunkWithBlockLifeCycle() throws InterruptedException, Ex | |
Vector3i chunkPosition = new Vector3i(0, 0, 0); | ||
blockAtBlockManager.setLifecycleEventsRequired(true); | ||
blockAtBlockManager.setEntity(mock(EntityRef.class)); | ||
requestCreatingOrLoadingArea(chunkPosition).get(WAIT_CHUNK_IS_READY_IN_SECONDS, TimeUnit.SECONDS); | ||
requestCreatingOrLoadingArea(chunkPosition); | ||
chunkProvider.notifyRelevanceChanged(); | ||
Thread.sleep(WAIT_CHUNK_IS_READY_IN_SECONDS * 1000); | ||
chunkProvider.update(); | ||
|
||
final ArgumentCaptor<Event> worldEventCaptor = ArgumentCaptor.forClass(Event.class); | ||
|
@@ -181,7 +182,9 @@ void testLoadSingleChunk() throws InterruptedException, ExecutionException, Time | |
generator.createChunk(chunk, null); | ||
storageManager.add(chunk); | ||
|
||
requestCreatingOrLoadingArea(chunkPosition).get(WAIT_CHUNK_IS_READY_IN_SECONDS, TimeUnit.SECONDS); | ||
requestCreatingOrLoadingArea(chunkPosition); | ||
chunkProvider.notifyRelevanceChanged(); | ||
Thread.sleep(WAIT_CHUNK_IS_READY_IN_SECONDS * 1000); | ||
chunkProvider.update(); | ||
|
||
Assertions.assertTrue(((TestChunkStore) storageManager.loadChunkStore(chunkPosition)).isEntityRestored(), | ||
|
@@ -206,7 +209,9 @@ void testLoadSingleChunkWithBlockLifecycle() throws InterruptedException, Execut | |
blockAtBlockManager.setLifecycleEventsRequired(true); | ||
blockAtBlockManager.setEntity(mock(EntityRef.class)); | ||
|
||
requestCreatingOrLoadingArea(chunkPosition).get(WAIT_CHUNK_IS_READY_IN_SECONDS, TimeUnit.SECONDS); | ||
requestCreatingOrLoadingArea(chunkPosition); | ||
chunkProvider.notifyRelevanceChanged(); | ||
Thread.sleep(WAIT_CHUNK_IS_READY_IN_SECONDS * 1000); | ||
chunkProvider.update(); | ||
|
||
Assertions.assertTrue(((TestChunkStore) storageManager.loadChunkStore(chunkPosition)).isEntityRestored(), | ||
|
@@ -249,7 +254,11 @@ void testUnloadChunkAndDeactivationBlock() throws InterruptedException, TimeoutE | |
blockAtBlockManager.setLifecycleEventsRequired(true); | ||
blockAtBlockManager.setEntity(mock(EntityRef.class)); | ||
|
||
requestCreatingOrLoadingArea(chunkPosition).get(WAIT_CHUNK_IS_READY_IN_SECONDS, TimeUnit.SECONDS); | ||
requestCreatingOrLoadingArea(chunkPosition); | ||
chunkProvider.notifyRelevanceChanged(); | ||
Thread.sleep(WAIT_CHUNK_IS_READY_IN_SECONDS * 1000); | ||
relevanceSystem.removeRelevanceEntity(playerEntity); | ||
chunkProvider.notifyRelevanceChanged(); | ||
|
||
//Wait BeforeDeactivateBlocks event | ||
Assertions.assertTimeoutPreemptively(Duration.of(WAIT_CHUNK_IS_READY_IN_SECONDS, ChronoUnit.SECONDS), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a way more reasonable time to wait for chunks to be ready 👍