Skip to content

Commit

Permalink
Merge pull request #18 from hadashiA/ku/register-system
Browse files Browse the repository at this point in the history
Support Injection of ComponentSystemBase (ECS)
  • Loading branch information
hadashiA authored Jul 20, 2020
2 parents be87965 + fe96f75 commit b6f84d0
Show file tree
Hide file tree
Showing 24 changed files with 905 additions and 40 deletions.
315 changes: 314 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Extra small code size. Few internal types.
- Application can freely create nested Lifetime Scope
- Dispatch your custom types to Unity's PlayerLoopSystem. (It is greatly influenced by Zenject)
- ECS Integration
- Immutable Container.

![](docs/unity_performance_test_result.png)
Expand Down Expand Up @@ -44,6 +45,7 @@ Following is a deep profiler Unity result sample.
- [Registering](#registering)
- [Controlling Scope and Lifetime](#controlling-scope-and-lifetime)
- [Dispatching Unity Lifecycle](#dispatching-unity-lifecycle)
- [Integrating with ECS](#integrating-with-ecs)
- [Optimization](#optimization)
- [Best Practices and Recommendations](#best-practices-and-recommendations)

Expand Down Expand Up @@ -135,7 +137,7 @@ using VContainer.Unity;

namespace MyGame
{
public sealed class GameLifetimeScope : LifetimeScope
public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
Expand Down Expand Up @@ -958,6 +960,317 @@ And
Note:
- [Unity - Manual: Order of Execution for Event Functions](https://docs.unity3d.com/Manual/ExecutionOrder.html)
## Integrating with ECS

VContainer supports integration between Unity's ECS (Entity Component System) and regular C# World.
( This is an experimental feature. Any feedback is wellcome! :0 )


:warning: Currently, this feature requires Unity 2019.3 or later versions.

### Setup

ECS features for VContainer is enabled if the project has the `com.unity.entities` package installed.

- Currently, ECS is a preview version. You may need the settings: `[Windows] -> [Package Manager]` and `[Advanced] -> [Show preview packages]`.
- Select package of `Entities` and Press `[Install]`.

If the `com.unity.entities` package exists, the `VCONTAINER_ECS_INTEGRATION` symbol is defined and the following features are enabled.

### When using Unity's default World

By default, ECS will automatically instantiate classes that inherits the `ComponentSystemBase` defined in your project, add it to the default world, and running on it.

In this mode, you can use method injection to ECS `System`.
( The constructor is automatically used by Unity, so it cannot be used.)

```csharp
class SystemA : SystemBase
{
[Inject]
public void Construct(Settings settings)
{
// ...
}

protected override void OnUpdate()
{
// ...
}

}
```

```csharp
// Inject the `System` to Unity's default World
builder.RegisterSystemFromDefaultWorld<SystemA>();

// builder.RegisterSystemFromDefaultWorld<SystemB>();
// builder.RegisterSystemFromDefaultWorld<SystemC>();
// ...
// Other dependencies can be injected into the System.
// ...
builder.RegisterInstance(settings);
// ...
```

(Optional) The above can also be declared by grouping as below:

```csharp
builder.UseDefaultWorld(systems =>
{
systems.Add<SystemA>();

// systems.Add<SystemB>();
// systems.Add<SystemC>();
// ...
});
```

Internaly, this is an automation of the following processes:

```csharp
var system = World.DefaultGameObjectInjectionWorld.GetExistingSystem<SystemA>();
system.Construct(settings);
```

Note:
- In default (`UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP is **not** used), The SystemGroup to which the System belongs can be controlled by Attribute ( e.g: `[UpdateInGroup(typeof(SystemGroupType))]` .


#### Example of setup entities (with Default World)

```csharp
public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.UseDefaultWorld(systems =>
{
systems.Add<SystemA>();
})
}
}
```

```csharp
public class SystemA : SystemBase
{
[Inject]
public void Construct(Foo foo)
{
// Setup entities...
var archtype = World.EntityManager.CreateArchetype(typeof(ComponentDataA));
World.EntityManager.CreateEntity(archtype);
}
protected override void OnUpdate()
{
Entities.ForEach((ref ComponentDataA data) =>
{
// Do something...
})
.Schedule();
}
}
```

### When to use your custom world

ECS also allows you to create and register your own system.

There are two ways to disable Unity's automatic system bootstrap.

- By setting the define symbol `UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP` will disable all World and System auto-bootstrap.
- Or, By adding the `[DisableAutoCreation]` attribute to the class definition to disable auto bootstrap per system.

For System that have auto bootstrap disabled, constructor injection can be used.

```csharp
public class SystemA : SystemBase
{
readonly ServiceA serviceA;

// Constructor injection
public SampleSystem(ServiceA serviceA)
{
this.serviceA = serviceA;
}

protected override void OnUpdate()
{
// ...
}
}
```

To use this System, you need to set up World yourself.

VContainer supports to instantiate of World and configuration.

```csharp
// Register of new World under the control of VContainer.
builder.RegisterNewWorld("My World 1", Lifetime.Scoped);

// Register System by specifying the name of World to be added.
builder.RegisterSystemIntoWorld<SystemA>("My World 1");
// builder.RegisterSystemIntoWorld<SystemB>("My World 1");
// builder.RegisterSystemIntoWorld<SystemC>("My World 1");
// Other dependencies can be injected into the System.
// ...
builder.Register<ServiceA>(Lifetime.Singleton);
// ...
```

(Optional) The above can also be declared by grouping as below:

```csharp
builder.UseNewWorld("My World 1", Lifetime.Scoped, systems =>
{
systems.Add<SystemA>();

// systems.Add<SystemB>();
// systems.Add<SystemC>();
// ...
});
```

Internaly, If you use above methods, the following setup will be performed automatically:

```csharp
// When resolving world ...
var world = new World("My World 1");

world.CreateSystem<InitializationSystemGroup>();
world.CreateSystem<SimulationSystemGroup>();
world.CreateSystem<PresentationSystemGroup>();

ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world, PlayerLoop.GetCurrentPlayerLoop());

// Resolving dependencies ...
var systemA = new SystemA(new ServiceA());
world.AddSystem(systemA);

var systemGroup = (ComponentSystemGroup)world.GetOrCreateSystem<SimulationSystemGroup>();
systemGroup.AddSystemToUpdateList(systemA);


// After container build ...
foreach (var system in world.Systems)
{
if (system is ComponentSystemGroup group)
group.SortSystems();
}
```

Note:
- Currently, VContainer is registering the World using `ScriptBehaviourUpdateOrder.UpdatePlayerLoop`.
- This is an alias that registers 3 SystemGroups to PlayerLoop, so VContainer also creates these SystemGroups internally.


By default, VContainer will register System to `SimulationSystemGroup`. If you want to change this, you can use `.IntoGroup<T>()`:

```csharp
// Example
builder.RegisterSystemIntoWorld<SystemA>("My World 1")
.IntoGroup<PresentationSystemGroup>();
```

#### Lifeime of World and Systems

`RegisterNewWorld(...)` or `UseNewWorld(...)` can accept Lifetime as an argument.

- This new World is placed under the control of VContainer.
- World holds System. Therefore, the lifetime of System is the same as World.
- If `Lifetime.Scoped` is specified, when the scope is destroyed, Dispose of all the systems belonging to that World will be called.


```csharp
builder.RegisterNewWorld("My World 1", Lifetime.Scoped);
builder.RegisterSystemIntoWorld("My World 1");
```

```csharp
public class SystemA : SystemBase, IDisposable
{
protected override void OnUpdate()
{
// ...
}

// Called when scope is disposed.
public void Dispose()
{
// ...
}
}
```

#### Example of setup entities (with Custom World)

```csharp
public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.UseNewWorld("My World 1", Lifetime.Scoped, systems =>
{
systems.Add<SystemA>();
})
}
}
```

```csharp
public class SystemA : SystemBase
{
public void SystemA(Foo foo)
{
// Injected dependencies...
}

protected override void OnCreate()
{
// Setup entities...
var archtype = World.EntityManager.CreateArchetype(typeof(ComponentDataA));
World.EntityManager.CreateEntity(archtype);
}

protected override void OnUpdate()
{
Entities.ForEach((ref ComponentDataA data) =>
{
// Do something...
})
.Schedule();
}
}
```

### Resolving `World`

```csharp
// When only one world is registered:
class ClassA
{
public ClassA(World world) { /* ... */ }
}

// When multiple worlds is registered
class ClassA
{
public ClassA(IEnumerable<World> worlds)
{
var world = worlds.First(x => x.Name == "My new world");
// ...
}
}
```


## Comparing VContainer to Zenject

Zenject is awesome. but VContainer is:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public sealed class ScriptTemplateProcessor : UnityEditor.AssetModificationProce
"using VContainer;\n" +
"using VContainer.Unity;\n" +
"\n" +
"public sealed class #SCRIPTNAME# : LifetimeScope\n" +
"public class #SCRIPTNAME# : LifetimeScope\n" +
"{\n" +
" protected override void Configure(IContainerBuilder builder)\n" +
" {\n" +
Expand Down
2 changes: 1 addition & 1 deletion VContainer/Assets/VContainer/Runtime/Internal/IInjector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace VContainer
{
interface IInjector
public interface IInjector
{
void Inject(object instance, IObjectResolver resolver, IReadOnlyList<IInjectParameter> parameters);
object CreateInstance(IObjectResolver resolver, IReadOnlyList<IInjectParameter> parameters);
Expand Down
14 changes: 7 additions & 7 deletions VContainer/Assets/VContainer/Runtime/Registration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ public CollectionRegistration(Type elementType)

public void Add(IRegistration registration)
{
foreach (var x in registrations)
{
if (x.ImplementationType == registration.ImplementationType)
{
throw new VContainerException(registration.ImplementationType, $"Conflict implementation type : {registration}");
}
}
// foreach (var x in registrations)
// {
// if (x.ImplementationType == registration.ImplementationType)
// {
// throw new VContainerException(registration.ImplementationType, $"Conflict implementation type : {registration}");
// }
// }
registrations.Add(registration);
}

Expand Down
Loading

0 comments on commit b6f84d0

Please sign in to comment.