VitalRouter 는 자식 스코프에서도 라우터를 새로 만들지 않는다
public class BattleLifetimeScope : LifetimeScope // 씬마다 만들어지는 자식 스코프
{
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterVitalRouter(routing =>
{
routing.Map<BattlePresenter>();
routing.Filters.Add<LoggingInterceptor>();
});
}
}
씬 스코프에서 이렇게 등록하면 씬마다 라우터가 하나씩 생기고, 씬을 내리면 같이 사라질 것 같습니다. 그런데 씬을 몇 번 오가면 커맨드 하나에 로그가 여러 번 찍힙니다.
VitalRouter 의 VContainer 연동은 부모 스코프에 Router 가 있으면 새로 만들지 않고 그것을 씁니다. 그리고 스코프를 닫을 때 라우트만 떼고 인터셉터는 떼지 않아서, 자식이 붙인 인터셉터가 부모 라우터에 쌓입니다.
부모에 라우터가 있으면 그것을 쓴다
// VitalRouter.Unity VContainerExtensions.cs (줄임)
if (routing.Isolated || !builder.Exists(typeof(Router), findParentScopes: true))
builder.Register<Router>(Lifetime.Singleton).AsImplementedInterfaces().AsSelf();
| 조건 | 자식 스코프가 쓰는 라우터 |
|---|---|
부모에 Router 가 없다 |
이 스코프에 새로 만든다 |
부모에 있고 Isolated = false (기본) |
부모의 라우터를 그대로 쓴다 |
Isolated = true |
이 스코프 전용 라우터를 새로 만든다 |
기본값이 공유라서 씬 스코프의 Presenter 들은 루트의 라우터 하나를 탑니다. 스코프를 나눴다고 라우터가 나뉘지는 않습니다. 컨테이너가 다 만들어진 뒤에는 그 라우터에 AddFilter 로 인터셉터를 붙이고, Map<T>() 한 클래스의 생성된 MapTo 를 리플렉션으로 찾아 부릅니다.
닫을 때 라우트만 떼고 인터셉터는 남긴다
// 스코프가 만들어질 때
foreach (var interceptorType in routing.Filters.Types)
router.AddFilter((ICommandInterceptor)container.Resolve(interceptorType));
// 스코프가 닫힐 때
builder.RegisterDisposeCallback(container =>
{
for (var i = 0; i < routes.Count; i++)
routes[i].UnmapRoutesMethod.Invoke(instance, null); // 라우트만 뗀다. RemoveFilter 가 없다
});
붙이는 쪽과 떼는 쪽이 짝이 맞지 않습니다. routing.Ordering 도 결국 순서 제어 인터셉터를 붙이는 것이라 같습니다.
자식 스코프를 다섯 번 만들고 닫으며, 닫은 뒤 발행 한 번에 인터셉터가 돈 횟수를 셌습니다.
| 몇 번째 로드 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 인터셉터가 돈 횟수 | 1 | 2 | 3 | 4 | 5 |
Mono 와 IL2CPP 플레이어가 같았습니다. 닫힌 스코프가 만든 인스턴스를 계속 부르지만 예외는 나지 않아서, 깨지지 않고 조용히 쌓입니다.
Isolated 를 켜면 부모에 아무것도 붙지 않는다
routing.Isolated = true 를 켜면 이 스코프 전용 라우터를 새로 만들어 거기에 붙입니다. 같은 구성으로 세 번 불러온 뒤 부모로 발행하니 인터셉터가 한 번도 돌지 않았습니다.
builder.RegisterVitalRouter(routing =>
{
routing.Isolated = true;
routing.Map<BattlePresenter>();
routing.Filters.Add<LoggingInterceptor>();
});
주의할 점
1. 씬 스코프에서 인터셉터나 순서 제어를 쓰면 Isolated 를 켠다
안 지키면 생기는 문제. 다섯 번 불러오면 발행 한 번에 로그가 다섯 번 찍히고, 권한 확인을 다섯 번 하고, 순서 제어가 다섯 겹이 됩니다. 스코프를 닫아도 줄지 않으니 오래 플레이할수록 발행이 무거워집니다.
이렇게 쓴다. 셋 중 하나를 고릅니다. 씬 스코프에서 routing.Isolated = true 를 켜거나, 부모 라우터를 꼭 공유해야 하면 스코프를 닫을 때 직접 router.RemoveFilter(...) 를 부르거나, 인터셉터는 루트 스코프에서 한 번만 등록하고 자식 스코프는 Map 만 합니다.
2. Isolated 로 나누면 커맨드를 어느 라우터로 보낼지 같이 정한다
안 지키면 생기는 문제. Isolated 스코프의 Presenter 는 그 스코프 전용 라우터에 연결됩니다. 부모에서 Router 를 주입받아 발행하던 코드는 이제 그 Presenter 에 닿지 않고, 씬 안에서 발행한 커맨드도 부모 쪽 구독자에게 가지 않습니다.
이렇게 쓴다. 씬 안에서만 오가는 커맨드와 씬을 넘나드는 커맨드를 나누고, 넘나드는 커맨드는 부모 라우터를 따로 주입받아 발행합니다.
3. 씬에 있는 MonoBehaviour 는 Map 이 아니라 MapComponent 로 연결한다
안 지키면 생기는 문제. Map<T>() 에 MonoBehaviour 를 넘기면 씬의 컴포넌트가 아니라 새 GameObject 에 붙인 것이 만들어져 라우터에 연결됩니다. 씬에 배치해 둔 Presenter 는 커맨드를 받지 못하고 보이지 않는 복제본이 대신 받습니다. [Routes] 를 빠뜨린 클래스를 Map 하면 컴파일은 되고 스코프를 만드는 순간 MapTo 가 없다는 예외가 납니다.
이렇게 쓴다. 씬의 오브젝트는 MapComponent(instance) 나 MapComponentInHierarchy<T>() 로 연결하고, 연결하는 클래스는 [Routes] 를 붙인 partial class 로 만듭니다.
정리
VitalRouter 의 VContainer 연동은 자식 스코프에서도 부모의 라우터를 공유하고, 닫을 때 인터셉터를 떼지 않습니다.
Isolated가 아니면 부모에Router가 있는 한 새로 만들지 않습니다.- 자식이 붙인 인터셉터는 스코프를 닫아도 남아, 씬을 불러온 횟수만큼 발행마다 돕니다.
Isolated = true면 전용 라우터에 붙어 부모에는 아무것도 남지 않고, 대신 커맨드가 갈 라우터를 나눠 정해야 합니다.