General Topic: I have a variable set at Start then I lose it immediately

I have lists to track which of my agents are selected to take action. Just recently, a nasty WTF showed itself. Somehow, during Start, just after I set a leader agent the data vanishes. Console records the operation succeeded, then a nanosecond later, Genie-Poof!!!

It is a short script, independent of any outside manipulation other than when the RankedUnit class registers with this class.

public class Selector : MonoBehaviour
{
    public static Selector Instance { get; private set; }

    InputControl control = null;
    RankedUnit leader = null;
    List<RankedUnit> rankedUnitList = new();
    List<RankedUnit> selectedList = new();

    private void Awake()
    {
        if (Instance != null)
        {
            Debug.LogError("There's more than one Selector instance. " + transform + " :: " +
               Instance + "Redundent instance destroyed.");
            Destroy(gameObject);
            return;
        }
        Instance = this;

        control = new(); control.Enable();
    }

    private void Start()
    {
        OrderUnitsByRank();
      
        if (leader == null)
        {
            SetLeader(rankedUnitList[0]);
        }
    
        foreach (RankedUnit unit in rankedUnitList)
        {
            if (selectedList.Contains(unit) == false)
            {
                selectedList.Add(unit);
            }
        }

        Debug.Log("leader = " + leader);
        /* Success */
    }

    public void RegisterUnit(RankedUnit unit)
    {
        rankedUnitList.Add(unit);
    }

    void OrderUnitsByRank()
    {
        rankedUnitList.Sort((leftHand, rightHand) => leftHand.rank.CompareTo(rightHand.rank));
        selectedList.Sort((leftHand, rightHand) => leftHand.currentRank.CompareTo(rightHand.currentRank));
    }

    public void UnregisterUnit(RankedUnit unit)
    { rankedUnitList.Remove(unit); }

    public void SetLeader(RankedUnit player)
    {
        leader = player;
        if (selectedList.Contains(player) == false)
        { selectedList.Add(player); }
    }

    public RankedUnit GetLeader()
    {
        if (leader == null)
        {
            Debug.Log("Major Tom?....");
        }
        /* Fail */
        return leader;
    }

    private void OnDisable()
    {
        control.Disable();
        control = null;
        instance = null;
    }
}

That’s about it.

It might help to know which course/project this goes with. I’m assuming the error is occurring when an outside script is calling GetLeader().

Sorry Bro. I just untagged it five minutes ago because It wasn’t getting any notice in Unity.RPG. I felt it that it may justify a general help designation.

To your reply, GetLeader is called by PlayerController.cs. Units register in Awake from RankedUnit. PlayerControllers all look to see if they wear the leader badge in Start. (after a forced delay to avoid rushing) but none get information. I checked the source, Here, and discovered even the Selector class doesn’t seem to know what is going on.

What I wasn’t seeing was anything that ties it to the RPG course…

It feels like there’s a potential race condition going on… but I’m not sure when or where the RankedUnit(?) registers with Selector.

It used to be that all the Awakes() ran, and then all the OnEnables() ran, meaning that you could set up a Singleton in Awake() and start accessing it in other scripts OnEnable(), but now Awake() => OnEnable() is run on each script, so it’s possible to get a race condition registering with a Singleton in Awake() (which is one reason I prefer FindObjectByType instead of a Singleton, because you would be able to register as long as you used = new(); in the declaration for the Lists, which you have.

But that doesnt’ seem like what’s going on, as you’re saying that the Leader has been set properly at the end of Start(), and then whatever’s asking for the Leader is coming up null, presumably after the Debug in Start().

In short, I’m not seeing a reason why leader would not be set…

One thing that does concern me is that you’re caching state. Here’s how I’m reading the order of events:

  • Units register with the Selector.Instance
  • In Start() all the units are sorted by their rank, leaving the “leader” in slot[0].
  • At no other time does the leader change… but… what if the rank changes?

In this case, you’re caching state, which is something that as a rule should be frowned upon if we can avoid it.
Assuming that the leader is supposed to change if another RankedUnit overtakes him, then this might be a better workflow…

  • Change Start() to be RankAndSelectLeader() (still private)
    Make this change to GetLeader()
    public RankedUnit GetLeader()
    {
        RankAndSetLeader();
        return rankedUnitList[0];
    }

In this case, you’re caching state, which is something that as a rule should be frowned upon if we can avoid it.

Word.

Relevance / This is stemming from a something I was working on few weeks back. That formation movement chapter of my life that we refer to as the Crucible. You were there (Brian) for a bit of it. All was rosy, but occasional out of index errors got me into a rebuild of the Selector. It was flawed and pegged for refactoring. The assumption that the leader was properly stored all along was incorrect.

I am investigating that flawed logic here, now, for all of you to enjoy. I am stumped.

I chose to follow a different method. It wasn’t too big of a deal to work around this little obstacle, I was/am just confused by what I was seeing. The only thing that makes any sense is that a race was being run.

I want to avoid altering the execution order of my scripts. I will do everything in my power not to swap my scripts around like a hand of Rummy. With that conviction, I chose to move the Who-Need-To-Know logic out of this class and oopsidentally (Real word. Right hand to God,) found a system I prefer. I know that real world programming requires real world solutions but for this guy, starting down a path of stacking the load order of my scripts looks like a future full of confusion. I don’t trust my competence to avoid unexpectedly overwritten or ignored data. I say, “If you don’t feel comfortable shooting the gun, put the gun down.”

In the end the problem is solved. My curiosity remains.

I am attentive to your, Brain, above recommendation about getting and setting (caching) my references. Your comments helped me format a better initialization system.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms