Skip to content
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

test(ChunkMeshWorker): initial attempt at using reactor-test #4987

Merged

Conversation

keturn
Copy link
Member

@keturn keturn commented Jan 20, 2022

Use reactor-test methods to write these tests of ChunkMeshWorker's Flux. Targets #4786.

Key Points to Review:

  • Are the tests legible? Can you tell what they are supposed to be testing and what they expect?
  • I tried to centralize the responsibility for calling mesh.dispose by putting that in Chunk#setMesh : 72b0f67 (#4987), does that make sense?
    • Is that an appropriate use of AtomicReference? Or is it giving me a false sense of security without preventing any real race condition?

@keturn
Copy link
Member Author

keturn commented Jan 21, 2022

@keturn
Copy link
Member Author

keturn commented Jan 21, 2022

They are beginning to look readable.
As it is now, being able to pass a different source flux there did not improve testability.
Every usage of setMesh had this, so it seemed safer to make Chunk responsible for this operation.
@keturn keturn marked this pull request as ready for review February 6, 2022 17:50
@keturn
Copy link
Member Author

keturn commented Feb 6, 2022

There are still some tests left to complete, but as this is a PR-to-a-PR and I think the tests are more or less legible now, I think this is ready for review.

@keturn keturn added Category: Test/QA Requests, Issues and Changes targeting tests and quality assurance Topic: Concurrency Requests, issues, and changes relating to threading, concurrency, parallel execution, etc. labels Feb 6, 2022
ChunkView chunkView = worldProvider.getLocalView(chunk.getPosition());
if (chunkView != null && chunkView.isValidView() /* && chunkMeshProcessing.remove(chunk.getPosition()) */) {
if (chunkView != null && chunkView.isValidView()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FIXME (or test): omitting the chunkMeshProcessing part of this if condition changes something about the reliability of ChunkMeshWorker.remove, I think. Because now there can be things that were removed and are no longer in chunkMeshProcessing list, yet they may still run this code.

Is that harmful? Unsure.

@keturn
Copy link
Member Author

keturn commented Feb 6, 2022

Brain dumping on ChunkMeshWorkerTest as it's been a couple weeks:

ChunkMeshWorker has a few collections of state that say something about the view of the world or work-in-progress

  • Set chunkMeshProcessing
  • List chunksInProximityOfCamera

in addition to

  • Chunk.isDirty

Those are the things I'm concerned with having tests for, to make sure we're updating them correctly and they are being used effectively.

One of the things chunkMeshProcessing is being used for is avoiding duplicate work:
if two events throw the same chunk (or location) in the queue twice before the first is processed,
we only need handle it once. Doing it twice would be redundant at best,
as it would be working from the same data at approximately the same time.

It might also be serving as a lock to avoid race conditions?

Things with mutable state that might get racy:

  • a Chunk has block data
  • a Chunk has a Mesh, which may be replaced or removed
  • a ChunkMesh may or may not have some initialization data (vertexElements) and its own buffers and GL buffers which manages.

Some things are not a big deal if they are temporarily inconsistent, like a slight lag in reflecting the current block data.

Other things scare me more, like trying to regenerate ChunkMesh VBOs at the same time another thread is uploading its buffers.

@keturn
Copy link
Member Author

keturn commented Feb 6, 2022

ChunkMesh tests

@keturn
Copy link
Member Author

keturn commented Feb 18, 2022

I got to talk this through with @outofculture. The conclusion we came to (which isn't 100% solid but seems like the intended design) is that ChunkMeshWorker always makes new ChunkMesh objects. And the ChunkMesh that ends up on RenderableChunk.mesh is, in effect, a read-only ChunkMesh.

(In practice, it looks like LodChunkProvider does do chunk.mesh.updateMesh() on a previous mesh, but do LoD chunks get mixed in with other chunks?)

If that's the case, I think I can replace some of the test cases I'm worried about writing with notes to use read-only views.

@keturn
Copy link
Member Author

keturn commented Feb 18, 2022

Thoughts on separating out the mutable methods of ChunkMesh are in #4998. It seems like it's probably viable, but some FallingBlocks-related interfaces make it outside the scope of including in this PR.

worker.add(chunk2);
worker.update();
})
.expectNext(chunk1, chunk2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this care about the order? if so, we could include that in the test name, e.g. testMultipleChunksOrdered or something similar

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logically, I don't want to assert order here, but it looks like those arguments to expectNext are ordered. Hmm.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the thing I'm most concerned with from this review.

I came up with something that's probably technically correct, but the readability is horrible. Hopefully someone will answer this post with better ideas: How to make order-independent assertions on Flux output?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The author answered my post and I've made some changes accordingly.

I'm torn, because I think using the getChunksThatResultFrom helper I added works well for these cases where you just want the final output and examine all if it together.

And I think StepVerifier is the right sort of tool to use for some of these cases where we're worried about how things change if you add more work while other things are still in progress.

But having some tests written one way and some tests written the other, and having these two different ways of getting a ChunkMeshWorker—I don't feel good about it.

I think, though, that after I add some documentation for the new function, it'll be Close Enough.

The goal here was to get tests that can keep us in line as we rework this code, and I expect the details of how we test will change as we do that.

})
.expectNextCount(1).as("expect only one result")
.then(() -> {
// adding it again and doing another update should still not change
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need another assertion after this? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verify at the end will fail if another thing comes through that's not expected, with a message like

expected: timeout(5s);
actual: onNext(DummyChunk{chunkPos=( 1.240E+2 4.560E+2 7.890E+2), dirty=false, mesh=Mock for ChunkMesh, hashCode: 1832543155, ready=true})

worker.remove(chunk);
worker.update();
})
// chunk was removed, no events expected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do something like expectNextCount(0) here to assert this?

worker.update();
})
.then(() -> {
worker.remove(chunk); // second time calling remove on the same chunk
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we consider the following a different test case?

worker.add(chunk);
worker.remove(chunk);
worker.remove(chunk);
worker.update();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see one, no. Are you concerned we need one?

worker.remove(chunk); // second time calling remove on the same chunk
worker.update();
})
// chunk was removed, no events expected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do something like expectNextCount(0) here to assert this?

worker.remove(position0);
worker.update();
})
// chunk was removed, no events expected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

@pollend
Copy link
Member

pollend commented May 6, 2022

the ways lod's work is kind of not great tbh. not sure if it should be generated as part of a chunk and cached together with the chunk. having to regenerate the chunk seems like it adds quite a lot of bandwidth to the gpu.

@keturn keturn merged commit cfb307c into refactor/migrate-chunk-mesh-generation-flowable May 7, 2022
@keturn keturn deleted the test/chunkmeshworker branch May 7, 2022 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Test/QA Requests, Issues and Changes targeting tests and quality assurance Topic: Concurrency Requests, issues, and changes relating to threading, concurrency, parallel execution, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants