#Identifying non-null MissingException refs

1 messages · Page 1 of 1 (latest)

wary yacht
#

  public interface ILocatable
    {
        public void RegisterToLocatorService();
        public void UnregisterFromLocatorService();
    }

    public interface ISystemLocatorService
    {
        public List<ILocatable> LocatableSystems { get; }
        public Dictionary<Type, ILocatable> LocatableByType { get; }
        
        public void RegisterLocatable(ILocatable locatable);
        public void UnregisterLocatable(ILocatable locatable);
        
        public T GetLocatableOfType<T>() where T : ILocatable; // Get First
        public void SetFromLocatableOfType<T>(out T output) where T : ILocatable;
        public List<T> GetAllLocatablesOfType<T>(int index) where T : ILocatable; // Get All
        
        //Events
        public event Action<ILocatable> LocatableAdded;
        public event Action<ILocatable> LocatableRemoved;
    }

___ IMPLEMENTATIONS OF THE SERVICE ___

       public List<ILocatable> LocatableSystems { get; } = new List<ILocatable>();
       public Dictionary<Type, ILocatable> LocatableByType { get; } = new Dictionary<Type, ILocatable>();

       private async void SanitizeLocatedSystems(string scene)
        {
            
            LocatableSystems.RemoveAll(locatable => locatable == null); // HERE LIES THE ISSUE

            var keysToRemove = new List<Type>();
            foreach (var kvp in LocatableByType)
            {
                if (kvp.Value == null)
                {
                    keysToRemove.Add(kvp.Key);
                }
            }

            foreach (var key in keysToRemove)
            {
                LocatableByType.Remove(key);
            }
        }

#

I am calling SanitizeLocatedSystems() as a response to any scene being Unloaded , but when I debug this code, the null checks fail event though Rider shows the red "MissingReferenceException" error on both the dictionary values and the list entry.
If I call the Unregister method on object's OnDestroy , the cleanup works as intended, but I'd like to do a full cleanup when a scene is done unloading.

#

Is this "easily" doable or am I just gonna get stuck fighting against Unity's lifecycles?

iron steeple
#

you could probably check the instance's type to see if it's UnityEngine.Object, then cast to that and do the comparison there. Unity overloads the equals and equality operators to do additional checking for the fake null used by the editor and not-actually-null-but-native-side-destroyed states that happen when you hold a reference to a C# shell object. You lose that by storing these as interfaces, so your null check is only checking for actual null

wary yacht
#

I see, will attempt later and report back

#

Thanks for the help

wary yacht
#

Had to do

kvp.Value is UnityEngine.Object obj && obj == null

to get it to work

dense ember
# wary yacht Is this "easily" doable or am I just gonna get stuck fighting against Unity's li...

this kind of system becomes way more robust (if you're registering MonoBehaviours) if the services are installed by a bootstrapper and bound to a gameobject in the scene they live in, which calls Unregister in its OnDestroy(). or if the systems (un)register themselves in Awake/Destroy. Your locator, or some other thing that points to it, needs to be a global (public static) thing anyway.

wary yacht
#

This system is meant to later be expandable, for example as a "Get the closest XYZ component to this location"

#

or
Get XYZ if it belongs to WFG type entity

dense ember
#

well, you're likely overloading your locator with many features that need complex runtime solvers and solving problems you probably want to solve by a service you get from the locator, not the locator itself

#

do you actually have a specific need for 'automatic' DI?

#

and do you like the tradeoffs?

#

a nice simple compromise would be


public class MyServiceImpl : MonoBehaviour, IMyService {
  void Awake() {
    // by convention only access Services during Init
    Services.Register<IMyService>(this);
  }

  void OnDestroy {
    Services.Unregister(this);
  }
}

// usage in other class

void Start() {
  // by convention clients always assume Services outlive their users.
   _lookup = Services.Get<MyLookup>();
}

void Foo() {
  _lookup.GetNearby(somePosition);
}
wary yacht
#

I mostly inject services and I do like that architecture over static instances (as it also allows me to easily have Global configs for Input, Game Rules, etc...) and I do have a pretty setup workflow

And I don't think I'm overloading the locator because what it's doing is JUST locating stuff (or rather registering and then relaying that information, more like a Cataloger I guess).

I'm using this system currently more for re-linking UI stuff that loses references with scene loading and unloading, but I see more usages for it without dirtying it's single purpose

wary yacht
#

not verbatum

#

but really close

dense ember
#

i find that pattern to be a good compromise that minimises downsides when used with a strong convention that prevents Services.Get access outside init.

#

your UI example is a good use case. and really anything you want to separate out for workflow reasons. Beats a mess of static public instances any day.

wary yacht
#

exactly
I used to do static event Action<T> TExistsNow

#

but it's...

#

ugly