Search Unity

When a joint breaks ... how do you detect which joint broke?

Discussion in 'Physics' started by a436t4ataf, Mar 3, 2020.

  1. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    The only API call I know for this is still broken/buggy in 2019.3 -- https://docs.unity3d.com/ScriptReference/Joint.OnJointBreak.html -- (it is missing the most important information for a callback: which Joint broke??!?).

    Is there a new API call we should be using instead? The last time I had to do this, we had to use multiple hacks to "guess" which joint had broken because of the mising API calls from Unity. Is that still the case? I was hoping that with the latest (re-)integration with new Havok this would be fixed.
     
    Bryarey likes this.
  2. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,510
    The description in the Joint page clarifies it:
    upload_2020-3-4_11-39-43.png
    Therefore you get that event called in a script that is in the same GameObject of the joint. Thus, a simple GetComponent<Joint>() inside the OnJointBreak call gives you the joint that broke.
     
    ksf000 likes this.
  3. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    You realise that you can have more than one joint per GameObject, right? :)
     
    gotiobg, zkenyon and Bryarey like this.
  4. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    e.g. most of my use of joints has two per GO. Maybe you'd advise against that? (that's a separate issue, but ... it's been the only way I've found to reliably implement + maintain a lot of common use-case with Physics/Havok in Unity. Maybe there's a better way these days?)
     
  5. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,510
    Thanks for this. Last time I had checked that out it wasn't possible to have more than one joint in the GameObject. But that was quite a few Unity and PhysX versions ago. :)

    Then I'd try monitoring all the parameters exposed by the Joint component to see if any of them provides some clue about the joint being broken. Maybe verifying the distance between anchor and connectedAnchor?
     
  6. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Yep, that's the kind of "heuristic" code (to solve a problem that has a KNOWN SOLUTION if only unity would expose the API call!) I had from ages ago - but I was sure there must be something less hacky by now!

    The problem is that you'll have to wait N frames (maybe only 1, if you're lucky) of physics before anything manifests as an actual explanation for the broken joint.

    I guess not :( :(.
     
    Edy likes this.
  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    Yeah, that kind of sucks. You can do this for 2D but not for 3D. A while back I had to implement a script that monitored UnityEngine.Object on a GameObject for removal as part of a test project and even though it has an overhead of polling them, it did work. Just relied on the fact that a reference to an Object is set to null when it's destroyed. This means you can gather the objects (Joints in this case) then monitor for null and do whatever you like. In theory you could do this globally and search for them at start but that could get expensive and dynamically created stuff wouldn't work.

    Here I do it in FixedUpdate but you can do it in LateUpdate to get the results of them being destroyed.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Events;
    5.  
    6. public class JointMonitor : MonoBehaviour
    7. {
    8.     [Serializable]
    9.     public class JointRemovedEvent : UnityEvent<Joint> {}
    10.  
    11.     [SerializeField]
    12.     JointRemovedEvent m_RemovedEvent = new JointRemovedEvent();
    13.  
    14.     List<Joint> m_MonitoredJoints;
    15.  
    16.     void Start()
    17.     {
    18.         m_MonitoredJoints = new List<Joint>(1);
    19.         GetComponents<Joint>(m_MonitoredJoints);
    20.     }
    21.  
    22.     void FixedUpdate()
    23.     {
    24.         for (var i = 0; i < m_MonitoredJoints.Count; ++i)
    25.         {
    26.             var joint = m_MonitoredJoints[i];
    27.             if (joint)
    28.                 continue;
    29.        
    30.             m_RemovedEvent.Invoke(joint);
    31.             m_MonitoredJoints.RemoveAt(i);
    32.         }
    33.     }
    34. }
    35.  
     
    halley likes this.
  8. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    LOL so - if I understand correctly - your code is effectively saying "the Unity API just doesn't work - let's replace it with one that does" ?

    Do I need to submit a bug report for this? It seems amazing that no-one has reported it already?
     
  9. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    My code was saying the API isn't giving you what you want so I'll spend some of my time getting you moving forward on your project with a potential workaround even though I'm not responsible for 3D physics at all. You're welcome. ;)

    It seems you want the API changed and nothing else so sure, go ahead and submit a bug report but you should report it as a feature request and it's probably unlikely to change in the near future but I'll let whoever is responsible for that decide.
     
    Last edited: Mar 4, 2020
    Bryarey likes this.
  10. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I'm sorry that it came across badly, I didn't mean to dismiss your code (I thought it was a nice, elegant way to give an interface that provides the missing behaviour in a way that requires the smallest change to downstream code if Unity fixes it in future).

    In the context of the OP: your code snippet (if I'm understanding it correctly) still doesn't fix the problem - like the heuristics and hacks I've used before, it's a "best guess" but it's not on time. We still have to wait multiple frames (At least) and run a "scan the entire scene and all physisc objects, every single frame" just to get the callback information that - by definition - HAS to be in the callback (without that info in the callback, the callback is practically useless except for trivial, toy examples - as shown by your code: we're having to 100% replace the Unity method because the Unity method simply doesn't do what it was designed to do).

    For context in my code: I'm using this in a package used by other developers, and I've seen use cases where they have hundreds of objects with multiple joints each. Unity should be able to handle upwards of tens of thousands of these (given all the optimizations in unity itself, and the 3rd party physics layer) -- but polling that many objects every frame is likely to signifianctly damage performance.

    ...all because the callback method is missing a core parameter. Hence: seems to me like a substantial bug.
     
  11. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    I don't see why. When the simulation steps, it destroys the joint components if required. Why would you have to wait for "several frames" or iterations of that? The actual deletion of objects in Unity are always handled in the late frame stage; doesn't matte if it's physics doing it here or a user script. Just try deleting the component in play-mode, it's the same as what physic does to delete the component.

    Did you see this? I discussed the above here.

    You seem to be arguing your point hard but I don't think it's necessary; it's not being argued against. It's fine that you don't want to use a workaround and yes, I already stated the potential polling overhead so you're "preaching to the choir" here sir, especially as I implemented 2D joints to include this. ;)

    You did that bug report right?
     
    Last edited: Mar 5, 2020
  12. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Yep, I'll make a new bug report, just surprising if it's not already covered.

    Re: frame delay - I may be over worrying on this one, but I've had problems with these heuristic / detecting approaches in the very recent past (and they were "worse / harder to write other code for" with physics issues, because of the different frame rates of physics vs render), usually "fixed" with switchign code into coroutines and inserting a WaitForEndOfFrame / WaitForSeconds(0.01) - with extensive testing showing that the Unity-Object custom null checks (==null) weren't yet updated on the frame where the event happened, even though other parts of Unity engine/APIs were already showing the data as changed.

    EDIT: to be clear: I did a lot of work shifting code around between LateUpdate and FixedUpdate, and monitoring inside Update (with render tick set much faster than physics tick), tryign to narrow down the precise moment inside Unity's frame-cycle where data became valid. Probably spent in total a few weeks just testing the specifics of timing and physics data validity, and there are some edge-cases I never managed to completely eliminate/handle)

    A new bug project is a good excuse to see if I can stimulate those delays in a simple test case (although: my experience so far is that the delays don't always appear in toy projects - although I belived null updates were done synchronously extensive testing on version 2018/2019 (with regression testing as far back as 5.x/2017) showed this wasn't true in practice for larger projects, and there was at least some level of caching/indirection/asynch-execution happening. Main example I had was a project with no multi-threading but Unity showed the results of GameObject.Destroy() sometimes 0 frame late, sometimes 1 frame late, and - very hard to prove this one - some recurring but hard to repro crashes in builds suggested sometimes: 2 frames)
     
  13. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    Really, I'm not following the complexity you're indicating here. Under the hood it's pretty simple for any call to UnityEngine.Object.Destroy. These are all processed after the main loop has finished performing the "LateUpdate" MonoBehaviour callbacks. If you perform this action outside of the "LateUpdate" callback then it'll obviously be processed after the next logical "LateUpdate" which, depending on where you call it, might be later in the loop or the next one. They will be destroyed before rendering.

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    There's no such thing as Unity-Object custom null checks updating. It doesn't need to go fix-up all references. The reference itself is an object and when you access it, it'll see the object gone and translate that into an effective object null. Maybe there was a bug in a specific part of Unity you were using to try to verify your assumptions, not sure, but what I described is how it happens and a lot of stuff depends on that.

    Anyway, this is beyond the scope of the thread so let's leave it here.
     
  14. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    FYI what I saw was:

    Code (CSharp):
    1. public IEnumerator AMethod()
    2. {
    3. Destroy( thing );
    4. // some lines of code, but no threading, nothing async ... and still within the same method
    5. Debug.Log( "is null? "+(thing==null) ); // sometimes true, sometimes false
    6.  
    7. yield return null;
    8.  
    9. Debug.Log( "is null? "+(thing==null) ); // always true, except for some runtime crashes where it came through as false, but I never managed to reproduce in debugger
    10. }
    ...but agreed: that's out of scope for this topic -- it's only of interest as explanation for my fears/concerns around manual detection approaches to the problem. Given your comments, I'll revisit my beliefs there - I thought it was a general experience (I'd spoken to a few other devs who had similar non-determinstic-unless-you-wait-at-least-one-whole-frame experiences with Destroy) but that's independent of the physics/joint callback :)
     
  15. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    a436t4ataf likes this.
  16. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,510
    Hey @MelvMay, now that @yant left the 3D physics team, do you know who should we talk with about 3D physics topics like this?
     
  17. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Wow. It is very possible that some of the instances were interface-typed not class-typed. I wouldn't even have thought to look for that (I had no idea C#'s op-overloading had that constraint! Learn something new every day). It's even possible/probable that in some lines of code they were being referenced as interface instance, and others as class instance - and none of us would have even questioned whether that could make a difference - even within the same method.
     
  18. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    AFAIK there was talk about Sustained Engineering team doing it but honestly, I have no idea.
     
    Edy likes this.
  19. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    524
    Bumping this thread...

    The OnJointBreak (3d) event still seems extremely unreliable for us in 2019.4.7. Quite often in the editor we're not seeing dynamically created 3d joints with breakforces being destroyed even though the PhysX joint simulation clearly shows the joint has been broken, and obviously then the Monobehaviour OnJointBreak callback doesn't get called.

    @MelvMay do you know if anything happened with a bug report around this issue? Is there a recommended way to be able to create joints dynamically in code, set a breakforce, have joints destroyed and receive this event at all? It only seems reliable if the joints are created in the editor, not in code.
     
  20. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    FYI I eventually discovered an actual bug in this, in all versions of 2018.x through 2020.x, and got QA to repro it :). The way Unity's null check is implemented causes it to (100% reproducible) incorrectly return false when it should return true *until the current method returns* after which time it returns true correctly. The cause is C#'s function despatcher + type system and how it handles generics, interacting badly with Unity's custom null check.

    If you use GetComponent<T> then this bug can be demonstrated in 2 lines of code.

    Ultimately Unity decided not to fix the bug (I don't think it can be fixed? I think the null check itself is basically illegal in C# and can't be fully implemented - yet another reason to delete that null check from Unity :D), and QA instead said they would update the docs for GetComponent and push everyone to use TryGetComponent instead (which bypasses this bug, and is superior to GetComponent anyway). Developers can (and will) still hit this bug but at least the most-common example of it is now documented.
     
  21. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    I wouldn't know as I'm not a 3D physics dev. Maybe @yant could help further.
     
  22. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    596
    Hi. Do you have a case# on this? Any kind of a repro? I could take a look if that helps. Anthony.
     
  23. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    524
    Thanks for all of your replies :)

    @a436t4ataf I'm not hitting the problem you're describing that was part of the earlier thread, with GetComponent<> doing strange stuff, but that's an interesting issue to hear about because we make extensive use of GetComponent<> calls.

    @MelvMay thanks for your reply and for bumping @yant.

    @yant thanks for following up on this. I haven't had time to build a repro for what we're seeing yet as I spent yesterday trying to see if it was my problem or Unity 2019.4.7. The end result was I think there's an issue with joints that are made in code and the order that properties for that joint are updated. I'm not seeing the same issue with joints created in the editor that exist when the scene starts.

    Sometimes the dynamically created joint values take effect but the joint destruction callback in a monobehaviour on the gameobject doesn't get called. Other times the PhysX simulation clearly breaks a joint (bodies are no longer connected) but the joint component remains on the gameobject instead of being destroyed. Then if you edit any of the joint component's settings in the inspector the system updates, resulting in the bodies suddenly rejoining or the joint connecting and immediately breaking and the component being deleted.

    After trying to get OnJointBreak calls working for a while I did get something going for an hour, but then it stopped working for no particular reason and I noticed some of this other strange behaviour with joints breaking but components not being deleted. I restarted Unity etc, tried using DefaultScriptOrdering to shuffle script priorities around, and a few other ideas but nothing seemed to restore the working code until I changed the order I was setting the joint's properties when I created it. Originally it felt like some sort of race or something because the problem was hard to pin down.

    I'll file a bug report (will post the case# here) with some code snippets but the basics are that I have two bodies I want to connect at runtime with a breakable hinge joint and then listen for when that joint is broken. I create a hinge joint, add the connected body, set some limits, set some spring, set the breakforces, and then add a small monobehaviour with the OnJointBreak listener. In this order things started to fail in various ways, eg. PhysX sim clearly breaks a joint when a force is applied because the gameobjects fall apart but the hinge joint component still exists in the inspector and the OnJointBreak callback is not called. As soon as I manually edit anything in the joint inspector it seems to update the whole system, exist for a frame, then sometimes get destroyed (probably due to break force but the game objects are now in different positions for the original joint config).

    When I shuffled the part of the hinge joint creation code where I set the connected body to be the last property edited when constructing the joint, everything started working as expected again. This might actually be the proper way to construct a joint, with the connected body being the last thing set. Setting the connected body before updating other properties seemed to be a pathway to blowing things up.

    Also, as this thread and others mention, the OnJointBreak callback isn't very useful if you want to know what joint is actually breaking. Is there any chance of an update that could pass the joint as well? Otherwise it seems we have to start a coroutine and start doing null checks on joint properties to know what has been destroyed.

    Thanks for any tips. I'll try to get a repro submitted soon.
     
    Last edited: Oct 21, 2020
    a436t4ataf likes this.
  24. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    596
    Ok thanks. One clear difference editor/runtime is that when you add joints from code there is no anchor snapping to the surface of colliders -- could it be the root cause in your particular case? Otherwise, looking forward to receiving a case from you. Thanks.
     
  25. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    524
    hi Yant, thanks for your reply. No, I don't think that's the problem but I'll test out our prototype around it. We're clearly seeing different behaviour when the ordering of something (not fully sure what yet) is changed. The PhysX sim is simulating a broken joint, but the Unity front end is showing a joint still exists. Mayhem then ensues.

    Any glimmer of hope on an additional callback that also passes a reference to the joint being broken? Also, while I've got you here, the visualisation of joints in the Editor is very bare-bones and it's really hard to see tiny hinge points and axes etc with current scene gizmo rendering. Do you have any recommendations for visualising the construction of physics and joints etc in the Editor?
     
  26. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    It is a simple bug to reprodce.

    Simply create a joint at runtime with a breakforce.

    Break it in runtime.

    No OnJointBreak message is dispatched.

    I described it here:

    https://forum.unity.com/threads/hinge-joints-not-destroying.973767/#post-6331905
     
  27. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    596
    A report is still worth creating. There could be many issues, but the ones reported take priority for being investigated.
     
    a436t4ataf likes this.
  28. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    524
    @yant Also, is hingeJoint.angle working correctly? We're seeing crazy values (that we're trying to visualise with springs and limits in the scene) that don't seem to make sense. The Unity forums and Answers etc. have a bunch of posts asking similar questions that don't have answers. Querying the joint and rendering some scene handles to try to debug limits and various angles doesn't match the Unity joint handles when 'editing angular limits'.

    We're also seeing this warning (2019.4.7): 'No script asset for HingeJointTool. Check that the definition is in a file of the same name.'
     
    Last edited: Oct 23, 2020
  29. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    596
  30. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    524
    Thanks for confirming that the hinge angle issue is still open, @yant. It's been confusing us over the last few weeks trying to work with it. Has the focus of physics development shifted from PhysX to the newer systems (ie. the new Unity physics implementation and Havok plugin stuff) or is there a chance that longstanding issues like this will get a fix sometime?

    Apologies, I have been away and not had time to make a repro for the coded hinge joint issues yet but I will submit one as soon as I can.
     
  31. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    596
    The shifted focus is a very special question, we have to be precise what that would mean really. I, for one, didn't shift to anywhere in the end.
     
  32. chubshobe

    chubshobe

    Joined:
    Jun 20, 2015
    Posts:
    52
    It wouldn't really make sense for Unity to provide info on which joint broke. What would it be, a reference to a dead object? That might cause other issues. The type of joint which broke? You can also have multiple of the same type of joints on the same GameObject. Either way the best solution is to not have multiple joints on the same GO in this case at least.
     
  33. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    For 2D it's this but this was obviously implemented way after 3D had been for years: OnJointBreak2D. The objects internally are not actually destroyed immediately, they're destroyed at the end of frame. They're just removed from sight.
     
    a436t4ataf and chubshobe like this.
  34. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Right, but ... Unity does this all the time already: null'd references (due to object destruction) aren't truly null until next frame. It's core to the design of the Unity engine and APIs! While it causes a lot of headaches it's also a fundamental feature that provides huge value everywhere we're writing code.

    Well, yes, but ... every unity developer is already working around those issues, and this doesn't add any additional cognitive load - it's more of the same :).

    It's 2021 now. I would really like to see this fixed. I won't be able to use any API fix for another 3 years (because it'll take that long to percolate through to all the LTS versions, so that I can stop writing and supporting workaround hacks), but every year it isn't fixed means the clock gets reset for another 3+ years wait :(.
     
  35. chubshobe

    chubshobe

    Joined:
    Jun 20, 2015
    Posts:
    52
    I've never used joints in 2D so I didn't know about this, thanks. I do prefer the 3D version though, I can't think of a use case where I'd want a reference to a joint which will be destroyed shortly after other than to compare it's instance ID.
     
  36. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    524
    An event/callback emitted when the 3d joint is destroyed that contains a reference to the joint being destroyed is way more preferable than the joint monitoring stuff we ended up with, which basically consisted of starting a coroutine in the callback that waits for end of frame to check which joint turned null and then emitting events for it... pretty messy stuff in 3d that is trivial in 2d.

    It's not that you want to do something with the joint itself, it's more about informing other things that a specific joint has been destroyed.
     
    a436t4ataf likes this.
  37. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Yeah, it's surely the most common use case: updating parts of your game based on WHICH joint broke. Otherwise your game isn't really doing much with the physics - and it's the main reason why this callback is needed in the first place.
     
  38. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
    In short, better to have it and not need it rather than need it and well, you know the rest. ;)
     
  39. zkenyon

    zkenyon

    Joined:
    Mar 27, 2014
    Posts:
    1
    An alternative solution to the ones described here is to put all of your joints in wrapper classes, Something like:


    Code (CSharp):
    1. class JointWatcher : Monobehaviour{
    2. public Joint j;
    3. public WatchJoint (Rigidbody rb, Joint j){ //joint already created at this point.
    4. var jw = rb.gameObject.AddComponent<JointWatcher>();
    5. jw.joint=j;
    6. }
    7. FixedUpdate(){
    8. if (joint == null)
    9. OnJointBreak();
    10. }
    11. void OnJointBreak(){
    12. ...
    13. }
    14. }
     
    Edy likes this.
  40. newlife

    newlife

    Joined:
    Jan 20, 2010
    Posts:
    1,081
    Any news about this issue? Fixes, workaround..
     
  41. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I assume I logged a bug (I log 1 a month on average) but it's possible I didn't get around to it. Worth logging yourself - if it's a dupe you'll find out soon enough. As described above it's simple to create a test case (just have to go through the pain of waiting for the sloooooow UnityEditor to initialize a new project for you).
     
  42. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
  43. alexanderperrin

    alexanderperrin

    Joined:
    Dec 15, 2016
    Posts:
    67
    My workaround for this is just to break the joints myself. I have a 'BreakableJoint' component which references a joint you specify. In FixedUpdate, it reads Joint.currentForce and Joint.currentTorque, compares them to a break force and torque threshold set in the component, then destroys the joint and raises an event with the joint reference when those thresholds are exceeded.

    I've found this to behave in the same way that setting the break force limits on the joint component behave. Perhaps slightly less performant, but not an issue.
     
    a436t4ataf and Edy like this.
  44. newlife

    newlife

    Joined:
    Jan 20, 2010
    Posts:
    1,081
    How do you keep the reference of the joint after you destroy it?
     
  45. alexanderperrin

    alexanderperrin

    Joined:
    Dec 15, 2016
    Posts:
    67
    The reference to the joint is passed via the argument in the event invoked by the BreakableJoint component. This reference is useful in this context, but after the joint is cleaned up by Unity after destruction, the reference will become null.
     
    newlife likes this.