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

Fix size hint and values read with sparse iterator #411

Merged
merged 7 commits into from
Jan 11, 2024

Conversation

derwiath
Copy link
Contributor

@derwiath derwiath commented Jan 8, 2024

In #313 @wsw0108 shows that collecting positions specified using a sparse accessor triggers a panic (attempt to subtract with overflow) inside of size_hint() of SparseIter.

In this PR I have rewritten the code in the issue as two unit tests, one that verifies size_hint() and one that verifies that the positions are actually correct. Both tests fail when using latest main.

The SimpleSparseAccessor.gltf test asset contains 14 positions specified in a sparse accessor where three of them is overrideen by values specified in the sparse section of the accessor. Look at the data layout image here for an overview: gltf-test/tutorialModels/SimpleSparseAccessor.

The two tests pass after delegating the size_hint() call to the iterator for the base accessor, it knows how many items are left.

The gltf specification on Sparse Accessors mentions that a sparse accessor may not have a bufferView set

When accessor.bufferView is undefined, the sparse accessor is initialized as an array of zeros of size (size of the accessor element) * (accessor.count) bytes.

For SparseIter, base will not be set and size_hint() cannot rely on it anymore. The solution is simple, simply pass accessor.count to SparseIter so that it can use it to know how many items there are. Then it simply deducts self.counter from it as that variable keeps track of how many items have been read from the iterator.

The asset tests/box_sparse.gltf contains a sparse accessor without a base buffer view. This is used in two unit tests that the behavior is correct, even for these sorts of accessors.

@derwiath derwiath changed the title Fix issues with sparse accessor Fix size hint and values read with sparse iterator Jan 8, 2024
Currently this fails due to a faulty size_hint() implementation in
SparseIter.

For the test asset it claims that 3 items will be read from the
iterator, when the correct answer is 14. 3 is the number of positions
overriden by the sparse values, while the total number of positions is
14.

The bug itself will be fixed in an upcoming change.
The `collect()` in this test currently triggers a
`attempt to subtract with overflow` panic inside
`SparseIter::size_hint()`.

This will be fixed in a following commmit.
The fix is to delegate size hint to `self.base` as it knows how many
values are left to consume.

Note that `base` is only set if `accessor.bufferView` is set. If it
isn't then the sparse iterator actually has no clue on how many items
are left. But it at least knows that there are at least `self.values`
items left.
This issue will be improved in a follow up PR.

The problem with the old code was that when `self.values.len()`
is less than `self.counter`, a panic was triggered:
`attempt to subtract with overflow`.

To understand why we need to understand what the `values` and `counter`
variables.

`self.values` is an iterator over the sparse values that overwrites
values from the base accessor. Each call to `SparseIter::next()` will
consume one item and `self.values.len()` consequently decrease by 1.

`self.counter` holds how many items have been consumed or seen another
way the number of successfull calls to `SparseIter::next()`. It starts
at 0 and increase until all values in the base accessor has been
consumed.

Now you hopefully see that the old implementation was just plain wrong.
For this test case, the iterator gives a size hint of 1 output value.
The correct answer is found in `accessor.count`, which is `2`.
This test triggers an infinite loop, as `next()` in `SparseIter`
does not have any end condition whenever the base buffer view
is not set in a sparse accessor.
When `accessor.bufferView` is unset the sparse iterator should
use T::zero() for `accessor.count` items as base. Then possibly
overwriting them with the values in specified by the accessor.sparse
section.

The sparse iterator is passed `accessor.count` here, so that it knows
when it should stop generating items. Before it continued until
the end of times.
@alteous
Copy link
Member

alteous commented Jan 11, 2024

Thanks for fixing this!

@alteous alteous merged commit defae93 into gltf-rs:main Jan 11, 2024
1 check passed
@derwiath derwiath deleted the derwiath/issue-313 branch January 12, 2024 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants