Skip to content

NullReferenceException in GripperBase.SetColliderSize when Pick() runs from OnEnable (before Start) #133

Description

@Preliy

Description

GripperBase.SetColliderSize throws a NullReferenceException when Pick() is invoked during the OnEnable phase — before GripperBase.Start() has cached the BoxCollider reference.

This happens whenever a Cylinder end-position event (OnLimitMinEvent / OnLimitMaxEvent) is wired in the Inspector to GripperBase.Pick() and the gripper has Dynamic Size enabled. Property.Subscribe fires the handler immediately with the current value, so the whole pick chain executes inside Cylinder.OnEnable().

Stack trace

NullReferenceException: Object reference not set to an instance of an object
OC.MaterialFlow.GripperBase.SetColliderSize (System.Boolean isGripped) (at Runtime/Scripts/MaterialFlow/GripperBase.cs:185)
OC.MaterialFlow.GripperBase.OnIsActiveChanged (System.Boolean value) (at Runtime/Scripts/MaterialFlow/GripperBase.cs:148)
OC.Property`1[T].set_Value (T value) (at Runtime/Scripts/Internal/Property.cs:42)
OC.MaterialFlow.GripperBase.Pick () (at Runtime/Scripts/MaterialFlow/GripperBase.cs:96)
UnityEngine.Events.InvokableCall.Invoke ()
UnityEngine.Events.UnityEvent`1[T0].Invoke (T0 arg0)
OC.Property`1[T].Subscribe (System.Action`1[T] action) (at Runtime/Scripts/Internal/Property.cs:118)
OC.Components.Cylinder.OnEnable () (at Runtime/Scripts/Components/Cylinder.cs:82)

Steps to reproduce

  1. Create a Cylinder and a Gripper (or GripperFixedJoint) in the same scene.
  2. On the gripper, enable Dynamic Size and set a non-zero Additional Collider Size.
  3. In the Inspector, wire the cylinder's OnLimitMaxEvent (or OnLimitMinEvent) to GripperBase.Pick().
  4. Make sure the cylinder starts at that limit, so the property already holds true.
  5. Enter Play mode.

Expected: the gripper picks and resizes its collider without errors.

Actual: NullReferenceException on the first frame; the collider is never resized and _isActive is left true with an inconsistent collider state.

Repro is intermittent — it depends on whether GripperBase.OnEnable (which subscribes OnIsActiveChanged) happened to run before Cylinder.OnEnable. Unity does not guarantee that order between components.

Root cause

  • GripperBase.Start() (GripperBase.cs:62-67) is the only place that calls GetReferences() and caches _collider, _initColliderSize, _initColliderCenter.
  • Property.Subscribe (Property.cs:115-119) invokes the action immediately with the current value, so Cylinder.OnEnable can drive gripper logic during the enable phase.
  • Unity guarantees all OnEnable calls precede any Start, so the pick path reaches SetColliderSize with _collider == null (GripperBase.cs:185).

Related defect in the same file

GripperBase.OnDisable() (GripperBase.cs:55-60) uses += where it should use -=:

private new void OnDisable()
{
    base.OnDisable();
    _isActive.OnValueChanged += OnIsActiveChanged;   // should be -=
    _isPicked.OnValueChanged += OnIsPickedChanged;   // should be -=
}

Handlers are therefore never unsubscribed and are duplicated on every disable/enable cycle, causing OnIsActiveChangedEvent / OnIsPickedChangedEvent to fire multiple times per state change.

Suggested fix

  1. Move the reference/initial-size caching from Start() into Awake() so _collider is valid before any OnEnable runs.
  2. Correct OnDisable() to unsubscribe with -=.
  3. Optionally, guard SetColliderSize against a null _collider as defense in depth.

Environment

  • Package: com.open-commissioning.core (observed at commit b842bcd4aae0)
  • Unity: 6.3 (6000.3)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions