aframevr / aframe-inspector

:mag: Visual inspector tool for A-Frame. Hit *<ctrl> + <alt> + i* on any A-Frame scene.
https://aframe.io/aframe-inspector/examples/
MIT License
654 stars 201 forks source link

React UI disappear when selecting a entity with texture on aframe 1.6.0 (works ok on aframe 1.5.0) #722

Closed vincentfretin closed 3 months ago

vincentfretin commented 3 months ago

Tested on the index.html from this repo, on aframe 1.5.0 no issue, on aframe 1.6.0, when selecting an entity with a texture, the cube or the floor, we get the stacktrace

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
    at checkForNestedUpdates (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:27287:11)
    at scheduleUpdateOnFiber (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:25470:3)
    at Object.enqueueSetState (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:14067:7)
    at Component.setState (webpack-internal:///./node_modules/react/cjs/react.development.js:355:16)
    at TextureWidget.setValue (webpack-internal:///./src/components/widgets/TextureWidget.js:206:12)
    at TextureWidget.componentDidUpdate (webpack-internal:///./src/components/widgets/TextureWidget.js:145:14)
    at commitLayoutEffectOnFiber (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:23333:28)
    at commitLayoutMountEffects_complete (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:24683:9)
    at commitLayoutEffects_begin (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:24669:7)
    at commitLayoutEffects (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:24607:3)

and the React UI disappear.

in TextureWidget.js:206:12

    console.log(newValue, this.state.value);
    if (newValue && newValue !== this.state.value) {
      this.setValue(newValue);
    }

on aframe 1.6.0, it gives <img id="crateImg" src="https://aframe.io/sample-assets/assets/images/wood/crate.gif" crossorigin="true">, '#crateImg'

on aframe 1.5.0, it gives #crateImg, #crateImg

vincentfretin commented 3 months ago

on aframe 1.5.0 entity.components.material.attrValue gives {src: '#crateImg'}

on aframe 1.6.0 it gives {src: img#crateImg} so the resolved selector Double checking with your @mrxz to see if this new behavior is expected. and how we can get the unresolved src selector here to fix the issue.

vincentfretin commented 3 months ago

https://github.com/aframevr/aframe-inspector/blob/9302f2a44452f133d3d10b236d83a27291c0f4b1/src/components/components/PropertyRow.js#L117 is getting the value with entity.getDOMAttribute('material').src so that's the resolved selector.

and in https://github.com/aframevr/aframe-inspector/blob/9302f2a44452f133d3d10b236d83a27291c0f4b1/src/components/widgets/TextureWidget.js#L97 it's getting the value in a different way, so that's already fishy. It should just take again this.props.value.

Even if I rewrite the react component to use

componentDidUpdate(prevProps) {
    // This will be triggered typically when the element is changed directly with
    // element.setAttribute.
    if (!Object.is(this.props.value, prevProps.value)) {
      this.setValue(this.props.value);
    }
  }

similar to the other widgets, I get an error when selecting a new texture from the modal and a different error from 1.6.0 and master, but that may be a different issue.

mrxz commented 3 months ago

Double checking with your @mrxz to see if this new behavior is expected. and how we can get the unresolved src selector here to fix the issue.

@vincentfretin This is expected, parsing of properties now happens earlier and is stored to avoid repeated parsing as that just waste cycles and often includes allocations. The selector and asset based types are tricky in that regard, as parsing is non-deterministic (selector might match different elements at a later point) and there isn't always a trivial stringification to go back.

For selector and selectorAll they are not stored parsed on .attrValue, meaning .attrValue and getDOMAttribute() should both return the raw selector string. For assets, only id based elements are allowed, so it's possible to construct the string value through "#" + attrValue.id, though probably best to utilise the stringify method on the schema for this. (As an aside, .attrValue is strictly speaking internal and should not be relied on, meaning getDOMAttribute() being the proper way to access its value, though in practice there is no real difference at this point)

Either way, there is no guarantee that a string was used in the first place. It's perfectly valid to directly feed an HTMLImageElement into properties expecting a selector or map using .setAttribute. This also holds true for 1.5.0 and earlier. Not sure what the best approach for the inspector is when it encounters such a situation.

vincentfretin commented 3 months ago

Thanks @mrxz for the insights.

For selector and selectorAll they are not stored parsed on .attrValue, meaning .attrValue and getDOMAttribute() should both return the raw selector string.

This is not what I see here, selector type used on material src is giving the HTMLImageElement, not #crateImg

I see now that getDOMAttribute() is just component.attrValue[this.props.name] really. The new componentDidUpdate I wrote above is fixing the error. Now I need to investigate another error in ModalTextures.js when clicking on another texture.

mrxz commented 3 months ago

This is not what I see here, selector type used on material src is giving the HTMLImageElement, not #crateImg

That's because src isn't a selector type, but an asset type (map to be precise: src/shaders/standard.js#L50). You can only use id selectors with those (for example #crateImg works, but a-assets > img:first-child won't work), and these are stored parsed on .attrValue, but because of that limitation should be reversible to a string using their id.

Now I need to investigate another error in ModalTextures.js when clicking on another texture.

There is a bug in 1.6.0 (fixed on master: https://github.com/aframevr/aframe/pull/5529) that can cause issues when changing textures. Depending on the state of the texture in Three, replacing the texture might fail or could otherwise cause issues when disposing. So I'd first try to debug the issue with A-Frame master to rule out the above bug.

vincentfretin commented 3 months ago

On aframe 1.6.0, the threejs error is indeed https://github.com/aframevr/aframe/pull/5529 and I don't have it on master. The other issue doesn't seem related to aframe-inspector code, all the code is correct as far as I can tell. The issue is in aframe I can reproduce it in the console, replacing crateImg to floorImg texture on the cube: document.querySelector('.boxClass').setAttribute('material', {src: '#floorImg'}) gives

src-loader.js:154
HEAD http://localhost:3333/examples/[object%20HTMLImageElement] 404 (Not Found)

that's here https://github.com/aframevr/aframe/blob/9436d4b5c555cb924c3f1d5fdf977b5f0a7624e2/src/utils/src-loader.js#L154

vincentfretin commented 3 months ago

That's probably a regression from https://github.com/aframevr/aframe/pull/5454

vincentfretin commented 3 months ago

or maybe not, your changes didn't touch the validateSrc function.

mrxz commented 3 months ago

Not sure, I wasn't able reproduce the issue in a plain A-Frame project. Placing a break point in updateProperties in component.js, reveals that after calling .setAttribute there are two updates:

So something in the inspector probably triggers this second update.

mrxz commented 3 months ago

Oh... found it. It's related to the fact that the example scene has debug="true". This forces the flushToDOM logic on every update. The stringify method for assets is just the default one causing it to become [object HTMLImageElement]. Going to create a fix for it.

vincentfretin commented 3 months ago

@mrxz I just tested again my branch https://github.com/aframevr/aframe-inspector/pull/723 and latest master, I shouldn't have to update the link in index.html, the cache should have expired by now, but just to be sure I tested with latest https://cdn.jsdelivr.net/gh/aframevr/aframe@3cad96337fa22be307c7408452336fa82e1181ca/dist/aframe-master.min.js that includes https://github.com/aframevr/aframe/pull/5544 but I still see src-loader.js:154 HEAD http://localhost:3333/examples/[object%20HTMLImageElement] 404 (Not Found) when I select the cube with wood texture and change it to grass texture. Am I missing something?

mrxz commented 3 months ago

@vincentfretin It seems that the master builds aren't being updated any more (Notice the last commit date here: https://github.com/aframevr/aframe/tree/master/dist). While the build bot still makes changes and commits, it doesn't actually update the files in dist/ (e.g. commit: https://github.com/aframevr/aframe/commit/3cad96337fa22be307c7408452336fa82e1181ca)

@dmarcos Any idea what is up with the build bot?

dmarcos commented 3 months ago

I was messing around with node versions and stuff and had to reboot the server and I might had screwed things up. Sorry. I’ll look into it

vincentfretin commented 3 months ago

I guess you played with the node version because of aframe-site, you should probably merge https://github.com/aframevr/aframe-site/pull/539 so you can use a more recent version of node on the aws machine instead of using nvm install v6.

vincentfretin commented 3 months ago

@mrxz With aframe master build I copied over manually, it seems there is an edge case that is not handled. You can execute in the console document.querySelector('.boxClass').setAttribute('material', 'src', '') this is what is executed when you empty the src input on the right panel (I added console.log in the updateEntity function to verify that), gives GET http://localhost:3333/examples/undefined 404 (Not Found) and the cube is black (missing texture) instead of becoming white (the selected color) This worked properly on aframe 1.5.0.

mrxz commented 3 months ago

@vincentfretin Found the bug, there's indeed an edge case when "unsetting" a value. These are now recorded as undefined on the internal attrValue, which behaves virtually identical to an empty string, except when stringifying through flushToDOM. I've created a PR to skip over undefined values when stringifying (https://github.com/aframevr/aframe/pull/5549), as even prior to 1.6.0 it's possible to end up with undefined values (albeit considerable less common).

This does mean a small change in behaviour where flushToDOM no longer serializes empty values (e.g. material="src: "), but that seems like a clear improvement to me. There shouldn't be a difference between not setting a property, and setting it and subsequently "unsetting" it IMO.

The test coverage of flushToDOM and the debug component could use some work. Especially debug seems to only react to setAttribute changes, missing changes caused by things like mixin updates or removeAttribute on individual properties.