Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Simple Injector in a game that relies heavily on dynamic assemblies and reflection? #425

Closed
PJB3005 opened this issue May 30, 2017 · 14 comments
Labels

Comments

@PJB3005
Copy link

PJB3005 commented May 30, 2017

So I'm not sure whether SimpleInjector is right for my use case or not. I want to use dependency injection, but due to the nature of my project, I feel like the lock after first use of a container might be a blocker. Is there a better way to solve my problem?

My first problem is as follows. I want to use DI in a multiplayer game. The issue is, I need to load content from an assembly at runtime, and I absolutely cannot do it before causing the container to lock. Of course I still want this dynamic content to be able to use DI, and access the singletons and alike inside my main engine container.

Is it possible to maybe make a second container for the dynamic content, that still has access to the main container?

My second problem is different. I'm using a lot of "get a list of all subtypes of X and instantiate them" for things like entity types. Now, because the container isn't generally accessible to the objects within it, how would I instantiate these types, while still allowing them to take advantage of DI?

Should I add a type that has a reference to the container into the container itself, so certain objects can get the container if they have to?

Thanks in advance.

@dotnetjunkie
Copy link
Collaborator

dotnetjunkie commented May 30, 2017

In my experience, there is always a better solution than updating an existing container, and for that reason the container is locked at first use.

Do note that this doesn't mean that it becomes impossible for registrations to get added, however, it is simply impossible to do this using the registration API (i.e. RegisterXXX). A safe way of 'adding' registrations is by the use of the ResolveUnregisteredType event. Whether this is the correct solution for you, is hard to say. You'll need to supply me with more information about your specific case.

Can you describe what types are dynamically loaded and give some examples of this. I would like to know:

  • Do they all share the same interface?
  • All different interfaces?
  • Are they registered as collection?
  • Why do you require such assembly to be loaded dynamically after the container has been constructed?

Please show examples of the design.

Now, because the container isn't generally accessible to the entities within it, how would I instantiate these types, while still allowing them to take advantage of DI?

That is more easy to answer. Entities (or other data-centric objects) should not be built by a DI Container. Entities should be newed up directly in code and should not be constructed using dependencies that are resolved from a container. Instead, if data-centric objects have behavior that requires so called Volatile Dependencies (the things that containers resolve), use Method Injection and pass such dependency on to a method that requires the dependency.

I happen to be writing a book that describes this subject in more detail. Here's an example from chapter 3 of the book that shows a Product entity that uses Method Injection in its ApplyDiscountFor method:

public class Product
{
    public string Name { get; set; } 
    public decimal UnitPrice { get; set; }  
    public bool IsFeatured { get; set; } 

    public DiscountedProduct ApplyDiscountFor(IUserContext user)
    {
        bool preferred = user.IsInRole(Role.PreferredCustomer);

        decimal discount = preferred ? .95m : 1.00m;

        return new DiscountedProduct(
            name: this.Name, 
            unitPrice: this.UnitPrice * discount);
    }
}

In the chapter, the Product is used by a ProductService which looks like this:

public class ProductService : IProductService
{
    private readonly IProductRepository repository;
    private readonly IUserContext userContext;

    public ProductService(IProductRepository repository, IUserContext userContext)  
    {
        this.repository = repository;
        this.userContext = userContext;
    }

    public IEnumerable<DiscountedProduct> GetFeaturedProducts()
    {
        return
            from product in this.repository.GetFeaturedProducts()   
            select product.ApplyDiscountFor(this.userContext);  
    }
}

The ProductService is a component that uses Constructor Injection to get the IUserContext Dependency. The ProductService is constructed by the DI Container, while it uses Method Injection to pass the IUserContext on to a manually created Product class.

Should I add a type that has a reference to the container into the container itself, so certain objects can get the container if they have to?

Your question is too broad. In general, it is okay for components to depend upon the container as long as those components are defined inside the Composition Root. Code outside the Composition Root (normal application code) should absolutely not depend on the container or an abstraction of any sort. Doing so leads to the Service Locator anti-pattern. Typically you should rely on Constructor Injection for injection of Dependencies into components (the classes that contain the application's behavior) and Method Injection for injection of Dependencies into data-centric objects, where injection happens at runtime, outside the composition root and outside sight of a container.

@dotnetjunkie dotnetjunkie changed the title Using SimpleInjector in a game that relies heavily on dynamic assemblies and reflection? Using Simple Injector in a game that relies heavily on dynamic assemblies and reflection? May 30, 2017
@PJB3005
Copy link
Author

PJB3005 commented May 30, 2017

The assemblies that would be loaded would be game content and the alike. Of course to keep things powerful, this content should be able to work mostly freely and of course define its own interfaces and types entirely. The reasson we need to have the container constructed before loading it is because it happens on connection to a server (it's a multiplayer game), but things like the main menu obviously need to load in before you're able to have a "connect" button.

To give a simplified example of my use case, consider the following example. The game would load all children of the Entity type from the dynamic assembly and spawn them.

public class Enemy : Entity
{
    // Called every second 
    public override void Update()
    {
        MoveRandom();

        if (health <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        IStatTracker tracker = // <???>
        tracker.EntityKilled(this);

        // ...
    }
}

Now, the code calling Update() on every entity in the game can't know that this specific entity will need to have access to IStatTracker, heck IStatTracker might not even be defined in the engine and be entirely existant in the dynamic assembly. This is the problem.

So while there is some overlap and that overlap will allow me communicate between the main and dynamic assembly, the dynamic assembly has a mind of its own.

Doing so leads to the Service Locator anti-pattern.

Yep, which is actually what we currently have and I'm trying to phase out.

@PJB3005
Copy link
Author

PJB3005 commented May 30, 2017

Some extra clarification: this IStatTracker would maybe still depend on something like an ILogger in the main engine, and the entity wouldn't really be updated from the composition root (which would be pretty dirty either way).

@dotnetjunkie
Copy link
Collaborator

I understand that you have seperate assemblies that contain part of the game content and logic, but you yet have to explain why you need it to be loaded lazily, instead of during application start-up. It's also unclear to me whether you wish to have plug-in like behavior, where new assemblies are added while the application is running.

Do note that content, and other data-related stuff are of no interest to the DI container, like I described above. Entities should not be initialized by, nor registered in the container. You might however have some sort of provider registered in the container that is able to provide the application with this data-related stuff.

But again, lazy loading is possible, but how to implement it depends on what your application requires. Your basic options are:

  • Use ResolveUnregisteredType to ensure types get loaded on first resolve.
  • Hide loading of types behind an provider/factory type of abstraction. The provider can create InstanceProducer instances on demand (and cache them).
  • Have each assembly or module have its own isolated set of dependencies (each Composition Root) and possibly its own Container instance.

@PJB3005
Copy link
Author

PJB3005 commented May 31, 2017

but you yet have to explain why you need it to be loaded lazily, instead of during application start-up.

Because we can't actually know which assembly to load into the game until the user connects to a multiplayer server, at which point things like the main menu, texture manager, game loop, etc... have all already initialized and these would of course have locked the container by now.

It's also unclear to me whether you wish to have plug-in like behavior, where new assemblies are added while the application is running.

Yes, effectively.

Use ResolveUnregisteredType to ensure types get loaded on first resolve.

Wouldn't this basically be taking over the job of the container for the types inside the dynamic assembly? Seems like kind of a waste in my ears.

Hide loading of types behind an provider/factory type of abstraction. The provider can create InstanceProducer instances on demand (and cache them).

Well, wouldn't you not be able to add them to the container since it's locked?

Have each assembly or module have its own isolated set of dependencies (each Composition Root) and possibly its own Container instance.

That's what I meant with "Should I add a type that has a reference to the container into the container itself, so certain objects can get the container if they have to?" in the original issue. Personally this feels like the best solution.

But unless I'm misunderstanding what you're saying, that still doesn't solve the "what if this entity wants access to the IFooBar". Both method and constructor injection are off the table.

@dotnetjunkie
Copy link
Collaborator

Because we can't actually know which assembly to load into the game until the user connects to a multiplayer server

Is your problem related to the client application or the game server? Does the server need to load assemblies based on a particular client? Does this mean you have many service instances? I can imagine one instance per game, with multiple players working on that game.

Can you explain it's impossible for you to load all assemblies upon start-up? What is it that makes your application so different that pre-loading isn't an option? Does every client have its own set of assemblies? Do you create an assembly per client? Does each client require different implemented behavior? Since this is a multi-player game server, how does that work when you have multiple clients connecting to the server instance that require different behaviors?

Do note however that being able to make registrations at runtime typically only artificially solves your problem, because it causes all kinds of hard to spot and hard to fix problems. For instance, how do handle thread-safety? You will likely want to be able to handle requests concurrently in your game server. Most containers don't guarantee register operations to be thread-safe and even if they do, you will have to do some locking yourself to prevent problems, which again might cause performance problems, especially in a high-performance game server.

So it might seem to you that Simple Injector makes your life harder right now, but I would argue that it actually prevents you from making it almost impossible to solve problems that arise in the future because of changing an already built-up container. Do note that other DI Containers for .NET are actually now following Simple Injector in this respect. Just read at how Autofac is "deprecating the ability to update an Autofac container" and how Ninject introduced a new IReadonlyKernel in v4 to prevent updates being made to the container.

Well, wouldn't you not be able to add them to the container since it's locked?

Like I said before, the only thing that is blocked is calling RegisterXXX. You can still create InstanceProducer instances, as long as you don't try to add them to the container. For instance, this is completely valid code:

var container = new Container();
container.Register<ILogger, FileLogger>();

// Verift locks the container
container.Verify();

// GetInstance locks the container as well
var logger = container.GetInstance<ILogger>();

// Create an independent InstanceProducer. It can depend on stuff inside the container.
// example: class ServImpl : IService { public ServImpl(ILogger logger) { } }
var producer = Lifestyle.Transient.CreateProducer<IService, ServImpl>(container);

// Create a new ServImpl with an ILogger dependency
IService service = producer.GetInstance();

In other words, a provider can create new InstanceProducer instances on the fly when it is called. It can take care of the assembly loading and everything it needs to get to that particular implementation type.

what if this entity wants access to the IFooBar

I addressed this previously:

  • Entities should not resolve services (like IFooBar) from a container or an abstraction; that's the Service Locator anti-pattern.
  • Neither should entities get services injected into their constructor.
  • Instead, use Method Injection to pass on dependencies that a particular entity method requires.

Both method and constructor injection are off the table.

Seems unreasonable to state that Method Injection is off the table, since everything else is a bad practice. Can you explain why you think that Method Injection is not suitable in your case?

@PJB3005
Copy link
Author

PJB3005 commented May 31, 2017

Is your problem related to the client application or the game server? Does the server need to load assemblies based on a particular client? Does this mean you have many service instances? I can imagine one instance per game, with multiple players working on that game.

Both the client and server will be loading an assembly or two, but it'd be different assemblies. On the server we can guarantee that the assemblies are loaded before the container is made (on startup), but the client every player uses to actually play the game can't. Players have a single installation, and the server sends over the assembly for the client to load on connect, so each server would have separate and individual content. The client can't really connect until after you display your main menu and load things and open a window etc...

Method injection isn't possible because we can't know ahead of time what a specific entity type will want when you call generic methods like something that gets called on absolutely every entity in the game. The only real option you have at that point is to pass in literally everything, which would just ruin the point and basically be an even more unmaintainable version of the service locator.

Say you have a generic virtual method Update that gets called on every Entity every second. The player, enemies, walls, it's all entities of varying types. Player wants to check health and display game over using a global interface manager if you die, Enemy want to check health and delete themselves if they die, and give the player some points. You literally cannot know what something would want, and any sort of reflection based thing would either be ugly, confusing, slow or hard to maintain and write, especially when these kinds of operations would be all over the codebase to improve maintainability and to keep things in their own files.

Also, I'm aware that everybody is saying "modifying the container is bad", and I can see why, but I need a workable and maintainable alternative before I can accept that mentality. That's why I made this question. I'm fully willing to use full DI but not if it means making my code filled to the brim with slow and hard to understand reflection everywhere.

@dotnetjunkie
Copy link
Collaborator

Say you have a generic virtual method Update that gets called on every Entity every second. The player, enemies, walls, it's all entities of varying types.

It seems to me that your Update method is doing too much. You say it might need many dependencies, but that's a clear smell IMO. Instead, extract the bulk of its logic and dependencies into an Aggregate Service. This new service can be injected using Method Injection and this allows you to add dependencies to that new component (using Constructor Injection) later on, without it to impact neither the entity nor the services calling that entity.

@PJB3005
Copy link
Author

PJB3005 commented May 31, 2017

While that's nice in theory, it's not exactly practical in my experience when you've got a largely complex complex codebase and thousands of types (no joke) that all have custom dependencies and need to do something on click, interaction, keyboard event, ...

The amount of complexity, hard to follow and debug code and all kinds of nonsense just confuses me to think about.

@dotnetjunkie
Copy link
Collaborator

So what did you do before you tried to integrate with Simple Injector and how did that work out?

@dotnetjunkie
Copy link
Collaborator

So, if I understand, the problem is primarily in the client application, since after it started, it will download its assemblies from the server.

As I see it, this means that you are actually downloading the application itself. In other words, you could see the code that shows the menu, start-screen and downloads the dll as a different application. A shell that is responsible of starting the real application.

In this respect, I see no problem in having either a start-up container (or use no container at all at that time) for the shell application, and a second application that gets populated after the assemblies have been downloaded.

As a matter of fact, if you take a look at ASP.NET Core, you will see that they have the exact same approach. The WebHostBuilder that is created in the application's Main entry point method, internally uses a DI Container instance to be able to build object graphs. The Startup class that the WebHostBuilder creates on your behalf however uses its own container instance, where registrations are not shared. In other words, the Main is the Shell and the Startup class presents the application's Composition Root.

@PJB3005
Copy link
Author

PJB3005 commented May 31, 2017

We're still building the engine, and due to a long history the entire codebase is an inconsistent mess. Parts of it are using a static service locator inconsistently and inefficiently while the other parts are just impossible to follow messes. I can only say "it hasn't ever been fired up".

What I do know however, is that the "an entity is an individual" works, it's been proven and modern game engines like Unity and UE4 use it. Code stays easy to follow and intuitive, yet it's powerful when used right.

The client "reload" approach works I suppose but it's still not exactly that clean in practice.

@dotnetjunkie
Copy link
Collaborator

Well if you are absolutely sure that you need to be able to update an existing container and the suggestions provided here don't work in your particular, you will have to pick a different tool to do the job. You Always pick the right tool for the job.

@PJB3005
Copy link
Author

PJB3005 commented May 31, 2017

I'll see I guess.

Thanks!

@PJB3005 PJB3005 closed this as completed May 31, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants