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);
}
}
#Identifying non-null MissingException refs
1 messages · Page 1 of 1 (latest)
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?
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
Worked like a charm, with an (weird?) exception:
For the list removal doing
X as UnityEngine.Object == null
``` it works and removes , but for the dictionary value it doesn't
Had to do
kvp.Value is UnityEngine.Object obj && obj == null
to get it to work
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.
yhea, this is pretty much what I have currently, I'm injecting the service via DI and then unregistering OnDestroy for the Monobehaviours or from the controller layer if not MB
But I wanted to attempt a more "fire and forget" approach (although I understand it has it's limitations, clearly
)
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
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);
}
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
This is what I currently have
not verbatum
but really close
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.