Question on fixing the race condition lesson

So I have been following along the course for the game, adding code and scripts and asking for help when I am in trouble. But this seems backwards. I know my mover script is updated to everything it has been used so far but the code shown in the lecture makes it seem like this is something I should have looked at sooner.

Or could we have used a race condition example with another section of code?

Here is my mover script now as it is currently setup:

``using System;
using System.Collections;
using System.Collections.Generic;
using RPG.Core;
using UnityEngine;
using UnityEngine.AI;
using RPG.Saving;
using RPG.Attributes;

namespace RPG.Movement
{
public class Mover : MonoBehaviour, IAction, ISaveable
{
[SerializeField] Transform target;
[SerializeField] float maxSpeed = 6f;

    NavMeshAgent navMeshAgent;
    Health health;

    // Start is called before the first frame update
    void Start()
    {
        navMeshAgent = GetComponent<NavMeshAgent>();
        health = GetComponent<Health>();
    }

    // Update is called once per frame
    void Update()
    {
        navMeshAgent.enabled = !health.IsDead();
        UpdateAnimator();
    }
    
    public void StartMoveAction(Vector3 destination, float speedFraction)
    {
        GetComponent<ActionScheduler>().StartAction(this);
        MoveTo(destination, speedFraction);
    }

    public void MoveTo(Vector3 destination, float speedFraction)
    {
        navMeshAgent.destination = destination;
        navMeshAgent.speed = maxSpeed * Mathf.Clamp01(speedFraction);
        navMeshAgent.isStopped = false;
    }

    public void Cancel() 
    {
        navMeshAgent.isStopped = true;
    }

    private void UpdateAnimator()
    {
        Vector3 velocity = navMeshAgent.velocity;
        Vector3 localVelocity = transform.InverseTransformDirection(velocity);
        float speed = localVelocity.z;
        GetComponent<Animator>().SetFloat("forwardSpeed", speed);
    }

    [System.Serializable]
    struct MoverSaveData
    {
        public SerializableVector3 position;
        public SerializableVector3 rotation;
    }

    public object CaptureState()
    {
        Dictionary<string, object> data = new Dictionary<string, object>();
        data["position"] = new SerializableVector3(transform.position);
        data["rotation"] = new SerializableVector3(transform.eulerAngles);
        return data;
    }

    public void RestoreState(object state)
    {
        Dictionary<string, object> data = (Dictionary<string, object>) state;
        navMeshAgent.enabled = false;
        transform.position = ((SerializableVector3)data["position"]).ToVector();
        transform.eulerAngles = ((SerializableVector3)data["rotation"]).ToVector();
        navMeshAgent.enabled = true;
        GetComponent<ActionScheduler>().CancelCurrentAction();
    }
}

}
``
But at the start of this lesson the code seems to be missing the code that was entered in before. Unless things had adjusted and changed off screen or I missed on removing checks I am wondering what the reason for this was. Or am I just confusing myself?

Race conditions exist when one component relies on another component for data or action. If the component that is the dependency is not properly initialized, then the code that depends on it may cause an error when referencing.

We don’t walk you through each of the possible race conditions, but just give a few examples.
It happens that Mover is a class that actually isn’t likely to suffer from a race condition, because nothing calls Mover until the Update phase, which means that the assignments to navMeshAgent and health are technically ok. This is not always the case, however, and in many other classes leaving these assignments in start is a recipe for disaster, so it’s best to be in the habit of assigning variable references in Awake(). (In this case, just change Start() to Awake()). If you’re always in the habit of assigning references in Awake() but not accessing them until Start() or later, then you’ve dramatically reduced the likelyhood that you’ll have a race conditions.

Sometimes, even that isn’t enough, and for these occassions, we’ve created the LazyValue, but what if I told you that my personal version of the project doesn’t have a single LazyValue in it (I don’t even have a copy of the class), but I still avoid race conditions.

Here are my simple rules for avoiding race conditions.

  • Awake() → Assign external references here, but do not act upon them. You can subscribe to events here. Any external component referenced in RestoreState() must be initialized here.
  • OnEnable()/OnDisable() This is where you generally should be subscribing to events. If you subscribed in Awake() do not subscribe here. Anything you subscribe in OnEnable should be unsubscribed in OnDisable.
  • Start() → You should now be able to retrieve data from classes that you have cached in Awake().
  • Update() → (And methods called from Update()) You should now be able to access other components safely.
2 Likes

Privacy & Terms