sigtrapgames / SmartData

A designer-friendly, editor-driven Unity3D framework for connecting data and events.
MIT License
271 stars 14 forks source link

Listeners not being unbound after scene change #12

Closed DanielPhk closed 5 years ago

DanielPhk commented 5 years ago

Basically I have a few scenes that are almost identical. I use a SmartFloat to get the value from a Slider, directly on the OnValueChange of the Slider. After I switch scenes the script(which is present in both scenes) that references the SmartFloat works as expected but I get a null reference from that same script (which has been destroyed because of the switch to another scene). Note that I didn't bind a listener to it, just accessing the value.

So because everything is working as expected on scene switch, it leads me to believe that there's still a listener bound to the script that has been destroyed on the previous scene...

I know I could write a custom script to bind the listenter to the SmartFloat's update and unbind it on destroy instead of accessing it's value directly, but it seems a listener is persisting from the last scene even though I am just accessing it by value.

SixWays commented 5 years ago

Hi, could you post some script and/or screens of your scene setup? A little hard to tell what exactly your code is doing here.

DanielPhk commented 5 years ago

Hi,

Sorry for the long wait, was really busy ;)

I've attached a repro project.

Simple instructions to reproduce on the README file.

Thanks!

SmartDataBug.zip

sigtrapgames commented 5 years ago

Sorry on my end too - only just got around to this! So there's a couple of things - there IS a bug here, but it's not what you're thinking.

UnbindOnDestroy doesn't unbind all calls to BindListener - that's up to you to manually unbind (cache the returned IRelayBinding and call Enable(false) at end-of-life). What it actually does is unbinds the UnityEvent attached to the SmartRef. So it's expected behaviour that you'll get a nullref in the following scene if you haven't manually unbound your listener. That said - I think I'll change the name to UnbindUnityEventOnDestroy or something to make that clearer since at the moment it does read like you've read it! So apologies for that.

It has uncovered a bug though - turns out, calling UnbindOnDestroy within the first two frames doesn't actually do anything! At least, not in editor - it works fine in builds. UnbindOnDestroy checks whether you're in play mode - if not it ignores it. Which would be fine except the reason for that it to avoid SmartRefs from trying to do stuff during OnAfterDeserialized, which Unity disallows. So it can't actually check Application.isPlaying (again, disallowed during deserialization), and has to rely on subscribing to EditorApplication.playModeStateChanged. Which - drum roll please - it turns out gets called AFTER TWO WHOLE FRAMES OF BEING IN PLAY MODE. Thanks Unity.

So, tl;dr I have to do that slightly differently now!