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 for XMLExporter issues in #2310 #2313

Merged
merged 17 commits into from
Oct 25, 2024

Conversation

JosiahGoeman
Copy link
Contributor

Includes fixes for all issues listed in #2310 and refactors related classes for readability, adherence to style guide, and elimination of duplicate code. Also adds unit tests for implementations of the OutputCapsule and InputCapsule interfaces. Currently one test is commented out because BinaryExporter fails it, see #2312.

JosiahGoeman and others added 13 commits January 4, 2024 05:58
… of nmemAlloc()

jMonkeyEngine#2176
LWJGLBufferAllocator.allocate() now always returns zero-initialized buffers.
Tests all write* and read* methods of OutputCapsule and InputCapsule respectively.
Previously DOMOutputCapsule was writing indices of set bits and DOMInputCapsule was reading values of each bit.  Changed DOMOutputCapsule to match the expected behavior of DOMInputCapsule.
org.w3c.dom.Element.getAttribute() returns an empty string for attributes that are not found.  It looks like DOMInputCapsule.readString() was interpreting an empty string as the attribute not existing, and returning defVal even when the attribute did exist and was an empty string.  Now it checks explicitly whether the attribute exists.
DOMSerializer contains several edge-case issues that were only partially worked around with the encodeString() and decodeString() helper methods.  Java has a robust built-in solution to serializing Document objects, and using that instead fixes several bugs.
Also refactored all primitive array write and read methods to be more readable and reduce duplicate code.
Refactored write and read methods for Savables and 1 and 2 dimensional Savable arrays.  Fixed NullPointerException when writing a 2d Savable array containing a null element in the outer array.
DOMInputCapsule used to throw a NullPointerException when reading an Arraylist containing a null element.  Also refactored list write and read methods to clean up a bit and accidentally also fixed an unrelated bug where reading ArrayList<ByteBuffer> would return a list containing all null elements.
@JosiahGoeman
Copy link
Contributor Author

JosiahGoeman commented Sep 18, 2024

Wait, why did GitHub include the BufferAllocator commit from my previous pr? That commit is already in the target branch.

@stephengold
Copy link
Member

I'm unsure what happened. It might have something to do with creating a pull request in your "master" branch. Does the "files changed" tab look correct to you?

@JosiahGoeman
Copy link
Contributor Author

Yes, the files changed are correct.

@stephengold stephengold linked an issue Sep 18, 2024 that may be closed by this pull request
@stephengold stephengold added the defect Something that is supposed to work, but doesn't. Less severe than a "bug" label Sep 18, 2024
@JosiahGoeman
Copy link
Contributor Author

Alright, I think this should be ready for review.

@stephengold
Copy link
Member

If there's no substantive review or discussion, I'll run some simple tests of my own and then integrate this PR.

@stephengold
Copy link
Member

My tests turned up an issue that's probably related to this change. I'll troubleshoot and post my findings here.

@stephengold
Copy link
Member

The issue I'm seeing is an infinite recursion that occurs while writing a Minie PhysicsVehicle to an XMLExporter. However, I don't fully understand what's going wrong...

The test app looks like this:

        NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
        CollisionShape shape = new SphereCollisionShape(1f);
        PhysicsVehicle body = new PhysicsVehicle(shape, 1f);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        XMLExporter exporter = new XMLExporter();
        try {
            exporter.save(body, outputStream);
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

The infinite recursion is as follows:
PhysicsVehicle.write() line 846 invokes
PhysicsRigidBody.write(). At line 1442, that invokes
DOMOutputCapsule.write(). At line 406, that invokes
RigidBodyMotionState.write(). At line 366, that invokes
DOMOutputCapsule.write(). And at line 406, that invokes
PhysicsVehicle.write() again.

There's only one PhysicsVehicle object in the test app, but it appears DOMOutputCapsule has forgotten it's already in the process of writing that PhysicsVehicle. The cycle repeats many times, until eventually the test app crashes with a StackOverflowError.

The same test app works fine if I replace XMLExporter with BinaryExporter.

@codex128
Copy link
Contributor

I took a quick peek at it, and it looks like PhysicsVehicle contains a PhysicsRigidBody, which it attempts to save. The PhysicsRigidBody contains a RigidBodyMotionState, which it also attempts to save. Finally, the RigidBodyMotionState contains the original PhysicsVehicle, which it also attempts to save, and the process starts all over again.

I don't know why it only fails with XMLExporter, but I'd guess that BinaryExporter maybe has some sort of check for that sort of dependency issue.

@stephengold
Copy link
Member

I'd guess that BinaryExporter maybe has some sort of check for that sort of dependency issue.

The test succeeds with XMLExporter in v3.7.0-stable, which means there was also a check in XMLExporter, and this PR disabled that check. I'm hoping @JosiahGoeman will solve this for us.

@JosiahGoeman
Copy link
Contributor Author

JosiahGoeman commented Oct 24, 2024

I'd guess that BinaryExporter maybe has some sort of check for that sort of dependency issue.

The test succeeds with XMLExporter in v3.7.0-stable, which means there was also a check in XMLExporter, and this PR disabled that check. I'm hoping @JosiahGoeman will solve this for us.

Whoops, I think I see what the issue is. DOMOutputCapsule uses an IdendityHashMap to keep track of unique Savable instances and their corresponding DOM elements, so they only get written once and their reference network is preserved. It looks like in the DOMOutputCapsule.write(Savable value, String name, Savable defVal) method I switched the order of writing the Savable's contents and putting it in the map. So any reference loop will cause infinite recursion. I don't remember there being a reason why I did that, so it might just a matter of switching those calls back the way they were. I'll get on it.

Writing a Savable containing a reference loop caused infinite recursion due to bookkeeping being performed after the recursive call instead of before.  Also added a unit test for this to InputOutputCapsuleTest.
@stephengold
Copy link
Member

Thank you for your quick response.
How about adding a reference loop to jme3-plugins/src/test ?

@JosiahGoeman
Copy link
Contributor Author

Looks like it was indeed that simple. And here I was thinking my test suite was so thorough. -_- I added a test for the reference-loop case.

@stephengold stephengold added this to the Future Release milestone Oct 24, 2024
@stephengold
Copy link
Member

Thanks, @JosiahGoeman .
7f522dc passes my tests as well, so I think this PR is ready for integration---unless someone cares to review it.

@stephengold stephengold merged commit eee4356 into jMonkeyEngine:master Oct 25, 2024
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
defect Something that is supposed to work, but doesn't. Less severe than a "bug"
Projects
None yet
Development

Successfully merging this pull request may close these issues.

XMLExporter has several bugs/inconsistencies with BinaryExporter
3 participants