I have next structure:
TestFacade
is a GameObject
that gets pooled.
public class TestFacade : MonoBehaviour, IPoolable<IMemoryPool>, IDisposable
{
public class Factory : PlaceholderFactory<TestFacade> { }
public class Pool : MonoPoolableMemoryPool<IMemoryPool, TestFacade> { }
public Guid ID;
private TestComponent _component;
private TestRegistry _registry;
private IMemoryPool _pool;
[Inject]
public void Construct(TestRegistry registry)
{
_component = new TestComponent(this);
_registry = registry;
ID = Guid.NewGuid();
}
public void OnSpawned(IMemoryPool pool)
{
_component.TestMethod();
_pool = pool;
_registry.Add(ID, this);
}
public void OnDespawned()
{
_pool = null;
_registry.Remove(ID);
}
public void Dispose()
{
_pool.Despawn(this);
}
}
Logicaly, TestComponent
doesn't exist without TestFacade
, but I couldn't "bind" it (don't know how), so I create it myself in TestFacade
making it coupled to TestFacade
.
public class TestComponent
{
private readonly TestFacade _testFacade;
public TestComponent(TestFacade testFacade)
{
_testFacade = testFacade;
}
public void TestMethod()
{
Debug.Log(_testFacade.ID);
// Do something
}
}
TestFacade
fill this registry to keep a record of spawned TestFacade
's.
public class TestRegistry
{
private readonly Dictionary<Guid, TestFacade> _objects;
public IReadOnlyDictionary<Guid, TestFacade> Objects => _objects;
public TestRegistry()
{
_objects = new Dictionary<Guid, TestFacade>();
}
public void Add(in Guid id, TestFacade test)
{
_objects.Add(id, test);
}
public void Remove(in Guid id)
{
_objects.Remove(id);
}
}
TestSpawner
handles spawning/despawning of TestFacade
using TestRegistry
and TestFacade.Factory
(it seems logical, right?).
public class TestSpawner : IInitializable, IDisposable
{
private readonly TestFacade.Factory _testFactory;
private readonly TestRegistry _registry;
public TestSpawner(TestFacade.Factory testFactory, TestRegistry registry)
{
_testFactory = testFactory;
_registry = registry;
}
public ref Guid Spawn()
{
var testFacade = _testFactory.Create();
return ref testFacade.ID;
}
public void Despawn(in Guid id)
{
_registry.Objects[id].Dispose();
}
public void DespawnAll()
{
foreach (var testFacade in _registry.Objects.Values)
{
testFacade.Dispose();
}
}
public void Initialize()
{
// 1
}
public void Dispose()
{
// 2
}
And this is just a test script. All it does is requests to spawn new TestFacade
every second using injected TestSpawner
. Is this right usage of IoC principle and DI Container?
public class TestScript : MonoBehaviour
{
[Inject]
private TestSpawner _testSpawner;
private Guid _id;
private bool set;
private void Start()
{
StartCoroutine(RemoveEachTime());
}
private IEnumerator RemoveEachTime()
{
yield return new WaitForSeconds(1f);
while (true)
{
if (set)
{
_testSpawner.Despawn(in _id);
set = false;
}
else
{
_id = _testSpawner.Spawn();
set = true;
}
yield return new WaitForSeconds(1f);
}
}
}
I have two scenes: Scene1
and Scene2
.
Scene1
simply loads Scene2
.
Scene2
has SceneContext
where I install all this logic.
public class TestSceneInstaller : MonoInstaller
{
[Inject] private Settings _settings;
public override void InstallBindings()
{
Container.Bind<TestRegistry>().AsSingle();
Container.BindInterfacesAndSelfTo<TestSpawner>().AsSingle();
Container.BindFactory<TestFacade, TestFacade.Factory>()
.FromPoolableMemoryPool<TestFacade, TestFacade.Pool>(poolBinder => poolBinder
.WithInitialSize(2)
.FromComponentInNewPrefab(_settings.TestFacadePrefab)
.UnderTransformGroup("TestObjects"));
}
[Serializable]
public class Settings
{
public GameObject TestFacadePrefab;
}
}
I've decided to use Memory Profiler to check what happens in memory.
My logic is that:
Scene1
loadsScene2
and unloads itselfScene2
installs all this logicTestScript
runs some logicScene2
loadsScene1
and unloads itself- All
TestFacade
'sGameObjects
got destroyed, which means
TestComponent
gets destroyed too because it is not referenced byTestFacade
anymore (right?). And basically whole structure should be destroyed? Nobody referencesTestSpawner
so it should also go. Same goes forTestRegistry
.
But, when I load Scene2
again I notice that now I have 2 of everything.
Same happens 3rd time...
Did I messed something up? Is the way I'm thinking wrong?
Unity leak analysis
Memory Profiler tells me that TestFacade
is a Leaked Managed Shell.
P.S. Also, I wouldn't mind any feedback regarding this structure, how I handle everything, e.g., usage of Guid
struct as ID and passing it by ref
to not copy it over and over again, etc.