question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Auto-Establish Component References in the Same GameObject

See original GitHub issue

Summary

With a special (optional) RequiredComponent-Attribute for fields you needn’t assign the required component in the OnInitialize-Method manually.

Example:

[RequiredComponent]
private Spaceship ship;

public void OnUpdate()
{
    ship.DoStuff();
}

Analysis

  • Type less code
  • Do we need an new RequiredComponentAttribute-Class just for fields?

Issue Analytics

  • State:open
  • Created 7 years ago
  • Comments:11 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
deanljohnsoncommented, Jan 16, 2018

Personally, I would only want this feature if it worked in the last way you mentioned (runs when adding or removing a component). Even the metadata version seems like a lot of code to be ran at runtime behind the scenes with no obvious indication to users of its impact. I could see people assuming it would have far less overhead - expecting Duality to work more magic behind the scenes. Seems risky especially with this feature being so convenient that users could end up using it very often.

Couldn’t the procedure you mentioned for the metadata implementation be simplified? If the RequiredComponent attribute already necessitates a similar process, it seems like you could re-use the GetType and maybe the Dictionary lookup if the data for the RequiredComponent and AutoRef attributes were stored together. If the AutoRef is allowed to be null this does have the design flaw of mixing hard requirements and optional setup. Maybe name the attribute RequiredRef rather than RequiredComponent? Or changes the semantics of the implementation to be “component initialization steps” rather “component requirements”. Anyways, if the additional GetType and Dictionary lookup can be shared with RequiredComponent, the additional overhead of this attribute would start to get quite close to the manual implementation and less of an issue. This would make this issue a much more substantial change since it’d be touching the RequiredComponent stuff.

Problem with the RequiredRef idea: what would be the meaning/expectation if RequiredRef was used without RequiredComponent? We can’t prevent this at build time and so it would have to be allowed.

As long as AutoRef is just syntactic sugar for GetComponent inside OnInit I can’t think of a compelling reason to use it over GetComponent. And if it was syntactic sugar, I can see many people preferring the manual way just to be explicit about their initialization steps. The AutoRef attribute would be moving the code relevant to initialization to multiple places.

Another less appealing but far easier to implement implementation would be to have the attribute be an editor only feature - when a component is added to an object in the editor that has the AutoRef property, it automatically fetches a component of the requested type and serializes the reference. At runtime the AutoRef attribute would do nothing.

0reactions
Barsonaxcommented, Apr 26, 2018

Below you can find the code for a basic injectioncontainer that will inject values based on either a constant value or a expression.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressionTrees
{
    public class InjectionContainer
    {
        private readonly Dictionary<Type, Expression> _dependencies = new Dictionary<Type, Expression>();
        private readonly Dictionary<Type, Action<object>> _injectionCache = new Dictionary<Type, Action<object>>();

        /// <summary>
        /// Adds the dependency. Everytime this dependency is needed the same instance will be injected.
        /// </summary>
        /// <typeparam name="TDependency"></typeparam>
        /// <typeparam name="TInstance"></typeparam>
        /// <param name="instance"></param>
        public void AddDependency<TDependency, TInstance>(TInstance instance)
            where TInstance : TDependency
        {
            var expression = Expression.Constant(instance);
            _dependencies.Add(typeof(TDependency), expression);
        }

        /// <summary>
        /// Adds the dependency. Everytime this dependency is needed a new instance will be created and injected.
        /// </summary>
        /// <typeparam name="TDependency"></typeparam>
        /// <typeparam name="TInstance"></typeparam>
        /// <param name="expression">This expression is used to create a new instance</param>
        public void AddDependency<TDependency, TInstance>(Expression<Func<TInstance>> expression)
            where TInstance : TDependency
        {
            _dependencies.Add(typeof(TDependency), expression);
        }

        public void InjectDependencies(object instance)
        {
            var type = instance.GetType();
            if (!_injectionCache.TryGetValue(type, out var action))
            {
                action = GenerateInjectionExpression(type);
                _injectionCache.Add(type, action);
            }
            action.Invoke(instance);
        }

        public Action<object> GenerateInjectionExpression(Type type)
        {
            var instanceParameter = Expression.Parameter(typeof(object));

            var body = new List<Expression>();
            var instanceCasted = Expression.Variable(type, "instanceCasted");
            body.Add(instanceCasted);
            body.Add(Expression.Assign(instanceCasted, Expression.Convert(instanceParameter, type)));
            foreach (var methodInfo in type.GetRuntimeMethods())
            {
                if (methodInfo.CustomAttributes.All(x => x.AttributeType != typeof(InjectAttribute))) continue;
                var parameterTypes = methodInfo.GetParameters();
                var parameterExpressions = GetParameterExpressions(parameterTypes);
                body.Add(Expression.Call(instanceCasted, methodInfo, parameterExpressions));
            }

            var block = Expression.Block(new[] { instanceCasted }, body);
            var expressionTree = Expression.Lambda<Action<object>>(block, instanceParameter);

            var action = expressionTree.Compile();
            return action;
        }

        private Expression[] GetParameterExpressions(ParameterInfo[] parameterTypes)
        {
            var parameterExpressions = new Expression[parameterTypes.Length];
            for (int i = 0; i < parameterTypes.Length; i++)
            {
                if (_dependencies.TryGetValue(parameterTypes[i].ParameterType, out var dependency))
                {
                    switch (dependency)
                    {
                        case ConstantExpression constantExpression:
                            parameterExpressions[i] = constantExpression;
                            break;
                        case LambdaExpression lambdaExpression:
                            parameterExpressions[i] = lambdaExpression.Body;
                            break;
                    }
                }
                else
                {
                    throw new Exception($"No configured dependency found for {parameterTypes[i].ParameterType}");
                }
            }

            return parameterExpressions;
        }
    }
}

Read more comments on GitHub >

github_iconTop Results From Across the Web

Referencing a script on a GameObject in a MonoBehaviour ...
First select the game object with the components you need to connect to your other script. Then lock one of the inspectors with...
Read more >
Using GetComponent to Reference a Game Object's ...
Using GetComponent to Reference a Game Object's Component in Unity 3D · Comments34.
Read more >
The GameObject-Component Relationship
The Transform Component is critical to all GameObjects, so each GameObject has one. But GameObjects can contain other Components as well. The Main...
Read more >
Best practice for getting references to specific child objects ...
So by using GetComponentInChildren you can find that component on the first child that has it. In case you actually need the game...
Read more >
unity game engine - Access component from another script
Drag&Drop the according GameObject in the field it automatically gets the according component reference.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found