Skip to content

Commit

Permalink
Merge pull request #47 from mewlist/unload-scene-anyway
Browse files Browse the repository at this point in the history
Unload scene context anyway
  • Loading branch information
mewlist authored Feb 23, 2024
2 parents a3b1be9 + 36626fe commit 93d21f5
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 51 deletions.
4 changes: 3 additions & 1 deletion Runtime/Context/SceneContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public static bool TryGetSceneContext(Scene scene, out SceneContext sceneSceneCo
}


public override Scene Scene => gameObject.scene;
private Scene scene;
public override Scene Scene => scene;

private bool isReverseLoaded;
public override bool IsReverseLoaded => isReverseLoaded;
Expand All @@ -37,6 +38,7 @@ public static bool TryGetSceneContext(Scene scene, out SceneContext sceneSceneCo

protected override async void Awake()
{
scene = gameObject.scene;
SceneContextMap[Scene] = this;

base.Awake();
Expand Down
33 changes: 22 additions & 11 deletions Runtime/Context/SceneContextLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ public class SceneContextLoader : MonoBehaviour, IAsyncDisposable
{
private SceneLoader SceneLoader { get; } = new();
private IContext Context { get; set; }
private List<IContext> ChildSceneContexts { get; } = new();
public IReadOnlyList<IContext> ReadonlyChildSceneContexts => ChildSceneContexts;
private List<SceneContext> ChildSceneContexts { get; } = new();
public IReadOnlyList<SceneContext> ReadonlyChildSceneContexts => ChildSceneContexts;
public float Progression => SceneLoader.Progression;
private TaskQueue TaskQueue { get; } = new();
private bool Disposed { get; set; }

private async void OnDestroy()
{
Disposed = true;
TaskQueue.Dispose();
await UnloadAllScenesAsync();
await UnloadAllScenesInternalAsync();
await SceneLoader.DisposeAsync();
TaskQueue.Dispose();
}

public void SetContext(IContext context)
Expand Down Expand Up @@ -78,34 +78,45 @@ private async ValueTask<SceneContext> LoadAsyncInternal(UnifiedScene unifiedScen
return sceneContext;
}

public async ValueTask UnloadAsync(IContext sceneContext)
public async ValueTask UnloadAsync(SceneContext sceneContext)
{
await TaskQueue.EnqueueAsync(async _ =>
{
await UnloadAsyncInternal(sceneContext);
});
}

private async ValueTask UnloadAsyncInternal(IContext sceneContext)
private async ValueTask UnloadAsyncInternal(SceneContext context)
{
if (!ChildSceneContexts.Contains(sceneContext)) return;
if (!ChildSceneContexts.Contains(context)) return;

foreach (var childSceneContext in ChildSceneContexts.ToArray())
await childSceneContext.SceneContextLoader.UnloadAllScenesAsync();

ChildSceneContexts.Remove(sceneContext);
ChildSceneContexts.Remove(context);

var scene = context.Scene;

sceneContext.Dispose();
context.Dispose();

await SceneLoader.UnloadAsync(sceneContext.Scene);
if (scene.IsValid())
await SceneLoader.UnloadAsync(scene);
}

public async ValueTask UnloadAllScenesAsync()
{
if (Disposed) return;
await UnloadAllScenesInternalAsync();
}

private async ValueTask UnloadAllScenesInternalAsync()
{
if (!ChildSceneContexts.Any()) return;

await Task.WhenAll(ChildSceneContexts.ToArray().Select(x => UnloadAsync(x).AsTask()));
await Task.WhenAll(
ChildSceneContexts
.ToArray()
.Select(x => UnloadAsync(x).AsTask()));
}

public void AddChild(SceneContext target)
Expand Down
9 changes: 6 additions & 3 deletions Writerside~/topics/context_space/close_context_space.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ var sceneContext = await sceneContextLoader.LoadAsync(firstScene, active: true);
await sceneContextLoader.UnloadAsync(sceneContext);
```

> これ以外の方法で直接シーンを閉じたときでも、シーンコンテクスト自体は自動的に閉じられますが、
> Addressables でロードしたシーンをアンロードする場合は、そのハンドルが解放されなくなってしまうので、必ず、シーンローダーを使うか、コンテクストの破棄をするようにしてください。
{style="warning"}
## SceneManager でアンロードする

通常の Unity が提供するシーンのアンロード機能を使って、シーンコンテクストを閉じても構いません。

```C#
SceneManager.UnloadSceneAsync(targetScene);
```

## ゲームオブジェクトコンテクスト空間: Destroy() を呼び出す

Expand Down
23 changes: 13 additions & 10 deletions Writerside~/topics/context_space/close_context_space_en.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Closing the Context Space

There are several ways to close the context space.
Please choose the appropriate method according to your situation.
There are several ways to close a context space.
Choose the method that suits your situation.

## Calling Dispose
## Call Dispose

By calling ```Dispose()``` on the context, you can close the context.
Objects belonging to their own context space are registered in the DI container as ```IContext```.
Expand All @@ -23,24 +23,27 @@ public async Task DisposeSceneContext()
}
```

## Scene Context Space: Calling SceneContextLoader.UnloadAsync()
## Scene Context Space: Call SceneContextLoader.UnloadAsync()

In the case of scene context, by keeping the SceneContext when loading the scene, you can close that context.
In the case of a scene context, you can close the context by holding the SceneContext when you load the scene.

```C#
var sceneContext = await sceneContextLoader.LoadAsync(firstScene, active: true);
...
await sceneContextLoader.UnloadAsync(sceneContext);
```

> Even when the scene is closed directly by methods other than this, the scene context itself is automatically closed, but
> when unloading a scene loaded with Addressables, the handle will not be released, so always use the scene loader or dispose of the context.
{style="warning"}
## Unload with SceneManager

You can also close the scene context using the scene unload function provided by Unity.

## GameObject Context Space: Calling Destroy()
```C#
SceneManager.UnloadSceneAsync(targetScene);
```

## GameObject Context Space: Call Destroy()

When an object with a GameObject context attached is destroyed, that context is closed.
When you destroy an object with a GameObjectContext attached, the context is closed.

```C#
Destroy(gameObjectContext);
Expand Down
21 changes: 13 additions & 8 deletions Writerside~/topics/injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,28 @@ public class SomeClass
}
```

## OnInjected() コールバック
## OnInjected コールバック


```OnInjected()``` というメソッドは(引数なし・public) は、
```OnInjected``` 属性を付けたメソッドは(引数なし・public) は、
インスタンスの注入が完了したタイミングで、自動的に呼び出されます。
```Task```, ```ValueTask``` の戻り値を持つ非同期関数であっても問題ありません。
```Task```, ```ValueTask```, ```UniTask``` の戻り値を持つ非同期関数であっても問題ありません。

特に、MonoBehaviour を継承したコンポーネントの場合、Awake や Start などのライフサイクルメソッドと、
インジェクションが行われるタイミングが前後する可能性があります。
```OnInjected()``` 、インジェクションが完了した次のフレームで呼び出されるようになっているため、
初期化順を安定化させる目的で使用できます
```OnInjected``` コールバックは、インジェクションが完了した次のフレームで呼び出されるようになっているため、
コンポーネントの初期化を安定させる目的で使用します

```C#
public class SomeClass
{
// [Inject] 属性をつけたメソッドは、インスタンスの生成後、DIコンテナによって呼び出され SomeDependency を引数に渡します
// [Inject] 属性を付与したメソッドは、インスタンスの生成後、DIコンテナによって呼び出され SomeDependency を引数に渡します
[Inject]
public InjectMethod(SomeDependency dependency)
{
...
}

// インスタンスの注入が完了したタイミングで、自動的に呼び出されます
// [OnInjected] 属性を付与したメソッドはインスタンスの注入が完了したタイミングで、自動的に呼び出されます
[OnInjected]
public void OnInjected()
{
Expand All @@ -106,6 +105,11 @@ public class SomeClass
}
```

> 特定のコンテクストを要求するようなオブジェクトを別のコンテクストでも動作できるようにします。
> インジェクションがスキップされた場合に既定値を設定し、オブジェクトの動作を保証し、デバッグ効率を上げられるでしょう。
> また、コンテクストによって、動作が切り替わるようなオブジェクトの実現にも役立ちます。
{style="note"}

## コンポーネントへの動的インジェクション (DynamicInjectable)

コンテクスト空間の生成後、通常の Instantiate を使うなど、Factory などを通さずにコンポーネントを生成したときは、
Expand All @@ -118,3 +122,4 @@ public class SomeClass
```Object.Instantiate``` で、インスタンスが生成されたタイミングでも、 同オブジェクトにアタッチされている
IInjectableComponent を継承したコンポーネントへのインジェクションを行うことができます。

DI コンテナを使いながらも、Unity の通常のコーディングフローに従った開発をサポートします。
40 changes: 22 additions & 18 deletions Writerside~/topics/injection_en.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Injection

This section explains where dependencies are injected.
This section explains where to inject dependencies.

## Constructor Injection

Expand All @@ -9,7 +9,7 @@ Constructor injection is a method of injecting dependencies into the arguments o
```C#
public class SomeClass
{
// When SomeClass is instantiated, SomeDependency is automatically injected
// When SomeClass is created, SomeDependency is automatically injected
public SomeClass(SomeDependency dependency)
{
...
Expand All @@ -24,7 +24,7 @@ Method injection is a method of injecting dependencies by calling a method with
```C#
public class SomeClass
{
// Methods with the [Inject] attribute are called by the DI container after the instance is created, passing SomeDependency as an argument
// A method with the [Inject] attribute is called by the DI container after the instance is created, passing SomeDependency as an argument
[Inject]
public InjectMethod(SomeDependency dependency)
{
Expand All @@ -35,48 +35,47 @@ public class SomeClass

## Field Injection

Field injection is a method of injecting dependencies into ```public``` fields with the ```[Inject]``` attribute.
Field injection is a method of injecting dependencies into a ```public``` field with the ```[Inject]``` attribute.

```C#
public class SomeClass
{
// Inject SomeDependency into a public field with the [Inject] attribute
// Injects SomeDependency into a public field with the [Inject] attribute
[Inject] public SomeDependency dependency;
...
}
```

## Property Injection

Property injection is a method of injecting dependencies into properties with a ```public``` setter and the ```[Inject]``` attribute.
Property injection is a method of injecting dependencies into a property with a ```public``` setter and the ```[Inject]``` attribute.

```C#
public class SomeClass
{
// Inject SomeDependency into a property with a public setter and the [Inject] attribute
// Injects SomeDependency into a property with a public setter and the [Inject] attribute
[Inject] public SomeDependency Dependency {get; set;}
...
}
```

## OnInjected() Callback
## OnInjected Callback

A method with the ```OnInjected``` attribute (no arguments, public) is automatically called when the instance's injection is complete. It's no problem even if it's an asynchronous function with return values of ```Task```, ```ValueTask```, or ```UniTask```.

The method ```OnInjected()``` (no arguments, public) is automatically called when the injection of an instance is completed. It can be an asynchronous function with a return value of ```Task``` or ```ValueTask```.

In particular, for components that inherit from MonoBehaviour, the timing of lifecycle methods such as Awake or Start and the timing of injections may change. ```OnInjected()``` is designed to be called on the frame after injection is complete, so it can be used to stabilize the initialization order.
Especially for components inheriting MonoBehaviour, the timing of lifecycle methods like Awake or Start and the injection may overlap. The ```OnInjected``` callback is designed to be called in the frame after the injection is complete, used to stabilize the initialization of the component.

```C#
public class SomeClass
{
// Methods with the [Inject] attribute are called by the DI container after the instance is created, passing SomeDependency as an argument
// A method with the [Inject] attribute is called by the DI container after the instance is created, passing SomeDependency as an argument
[Inject]
public InjectMethod(SomeDependency dependency)
{
...
}

// This is automatically called when the injection of an instance is completed
// A method with the [OnInjected] attribute is automatically called when the instance's injection is complete
[OnInjected]
public void OnInjected()
{
Expand All @@ -87,23 +86,28 @@ public class SomeClass

## Optional Injection

By specifying the ```[Optional]``` attribute as an argument, if the dependency cannot be resolved, the injection can be skipped. The ```default``` value of the type is passed to skipped arguments.
By specifying the ```[Optional]``` attribute for an argument, you can skip the injection if the dependency cannot be resolved. The ```default``` value of the type is passed to skipped arguments.

```C#
public class SomeClass
{
// If the dependency on SomeDependency cannot be resolved, null is passed
// If SomeDependency cannot be resolved, null is passed
public SomeClass([Optional] SomeDependency dependency)
{
...
}
}
```

> This allows an object that requires a specific context to function in another context. By setting default values when the injection is skipped, you can ensure the object's operation and improve debugging efficiency. It's also useful for realizing objects whose operation switches depending on the context.
{style="note"}

## Dynamic Injection to Components (DynamicInjectable)

Normally, injections are not performed on components created without going through a Factory, such as using the regular Instantiate, after the context space is created.
After the creation of the context space, when a component is created without going through a Factory, such as using a regular Instantiate, the component is not normally injected.

In such cases, you can perform the injection by creating instances using a Factory, but it's also a hassle to prepare a Factory every time.

In such cases, you can perform injections by using a Factory to create instances, but it can be a hassle to prepare a Factory every time.
In such cases, if you attach the ```DynamicInjectable``` component to the GameObject in advance, you can inject into the components that inherit IInjectableComponent attached to the same object, even when instances are created with ```Object.Instantiate```.

In such cases, you can attach the ```DynamicInjectable``` component to the GameObject in advance, and perform injections to components that inherit IInjectableComponent attached to the same object, even at the timing of instance creation with ```Object.Instantiate```.
This supports development following the regular coding flow of Unity while using the DI container.

0 comments on commit 93d21f5

Please sign in to comment.