Skip to content

do-i-know-it/YggdrAshill.Ragnarok

Repository files navigation

YggdrAshill.Ragnarok: a lifecycle management framework

GitHub GitHub Release Date GitHub last commit GitHub repo size GitHub code size in bytes

Ragnarok defines how to manage lifecycles of applications for mainly XR (VR/AR/MR), inspired by

This framework isolates definitions from implementations for the lifecycle, allowing you to implement and extend it to adapt to the platforms you use, as shown below.

Dependencies

This framework depends on .NET Standard 2.0.

Installation

Developers should

  1. Go to Release pages.
  2. Download the latest version.

to use this framework.

Usage

Using our framework, you can resolve dependencies as below:

interface ISender
{
    string Send();
}

interface IReceiver
{
    void Receive(string message);
}

class Service
{
    readonly ISender sender;
    readonly IReciever receiver;

    [Inject]
    Service(ISender sender, IReceiver receiver)
    {
        this.sender = sender;
        this.receiver = receiver;
    }

    public void Run()
    {
        var message = sender.Send();
        receiver.Receive(message);
    }
}

using implementations as below:

class ConsoleSender : ISender
{
    readonly string announcement;

    [Inject]
    ConsoleSender(string announcement)
    {
        this.announcement = announcement;
    }

    public string Send()
    {
        Console.Write($"{announcement}:");

        return Console.ReadLine() ?? string.Empty;
    }
}

class ConsoleReceiver : IReceiver
{
    [InjectField]
    readonly string? header;

    public void Receive(string message)
    {
        if (header == null)
        {
            Console.WriteLine($"{message}");
        }
        else
        {
            Console.WriteLine($"{header}: {message}");
        }
    }
}

like:

var context = new DependencyInjectionContext();

// Register ConsoleSender as ISender to instantiate per local scope.
context.Register<ConsoleSender>(Lifetime.Local)
    .WithArgument("announcement", "Enter any text") // Add parameter to inject into constructor.
    .As<ISender>();
// Register ConsoleReceiver as IReceiver to instantiate per global scope.
context.Register<ConsoleReceiver>(Lifetime.Global)
    .WithField("header", "Recieved") // Add parameter to inject into fields.
    .As<IReceiver>();
// Register Service to instantiate per request.
context.Register<Service>(Lifetime.Temporal);

using var scope = context.CreateScope();

// You can resolve an instance of Service and run it.
scope.Resolver.Resolve<Service>().Run();

or using IInstallations as below:

class ServiceInstallation : IInstallation
{
    public void Install(IObjectContainer container)
    {
        container.Register<ISender, ConsoleSender>(Lifetime.Local).WithArgument("announcement", "Enter any text");
        container.Register<IReceiver, ConsoleReceiver>(Lifetime.Global).WithField("header", "Recieved");
        container.Register<Service>(Lifetime.Temporal);
    }
}

like:

var context = new DependencyInjectionContext();

// You can register installation like:
context.Install<ServiceInstallation>();
// or 
context.Install(new ServiceInstallation());

using var scope = context.CreateScope();

// The result is like:
// "Enter any text: This is a test."
// "Recieved: This is a test."
scope.Resolver.Resolve<Service>().Run();

Instantiating collection

In our framework, you can resolve collection as bellow:

interface ILogger
{
    void Log(string message);
}

class ConsoleLogger : ILogger
{
    ...
}

class FileLogger : ILogger
{
    ...
}

like:

var context = new DependencyInjectionContext();

context.Register<ILogger, ConsoleLogger>(Lifetime.Global); // Same as Register<ConsoleLogger>(Lifetime.Global).As<ILogger>();
context.Register<ILogger, FileLogger>(Lifetime.Global);

using var scope = context.CreateScope();

// You can resolve collection like:
var array = scope.Resolver.Resolve<ILogger[]>();
// or
var readOnlyList = scope.Resolver.Resolve<IReadOnlyList<ILogger>>();
// or
var readOnlyCollection = scope.Resolver.Resolve<IReadOnlyCollection<ILogger>>();
// or
var enumerable = scope.Resolver.Resolve<IEnumerable<ILogger>>();

Resolving dependency from sub containers

You can resolve instance using sub containers like:

var context = new DependencyInjectionContext();

context.RegisterFromSubContainer<Service>(subContainer =>
{
    subContainer.Register<ISender, ConsoleSender>(Lifetime.Global).WithArgument("announcement", "Enter any text");
    subContainer.Register<IReceiver, ConsoleReceiver>(Lifetime.Global).WithField("header", "Recieved");
    subContainer.Register<Service>(Lifetime.Temporal);
});

using var scope = context.CreateScope();

// You can resolve an instance of Service.
var service = scope.Resolver.Resolve<Service>();
// but you can't resolve an instance of ISender.
var sender = scope.Resolver.Resolve<ISender>(); // RagnarokNotRegisteredException.

Sub containers (inspired by Zenject/Extenject/UniDi) help you making dependency scopes more smaller.

Resolving factory to create instance on demand

You can resolve instance using sub containers like:

var context = new DependencyInjectionContext();

context.RegisterFactory<Service, ServiceInstallation>(Ownership.External);

using var scope = context.CreateScope();

var facotry = scope.Resolver.Resolve<IFactory<Service>>();
var service = factory.Create();

service.Run();

Registering factories (inspired by Zenject/Extenject/UniDi and VContainer) help you creating instances on demand.

Architecture

Now writing...

Known issues

Please see GitHub Issues.

Future works

Please see GitHub Projects.

License

This framework is under the MIT License, see LICENSE.

Remarks

This framework is a part of YggdrAshill framework.
Other frameworks will be produced soon for YggdrAshill.