jeffcampbellmakesgames / Entitas-Redux

An entity-component framework for Unity with code generation and visual debugging
MIT License
102 stars 13 forks source link

[FEATURE REQUEST] Create API for copying one entity to another #24

Closed jeffcampbellmakesgames closed 3 years ago

jeffcampbellmakesgames commented 3 years ago

Is your feature request related to a problem? Please describe. I'd like to have an easy way to copy all of the components on an entity over to another entity. This previously existed, but used reflection at runtime to shallow-copy each member's value. In vanilla Entitas, that implementation is here.

Describe the solution you'd like I'd like for this new CopyTo method to take advantage of the newer component copy methods to copy all components from one entity of a particular context type to another. In this way, it will potentially support deep-copying of components from one entity to another.

jeffcampbellmakesgames commented 3 years ago

@fahall I've pushed up this work on branch feat/entity_copy_to if you'd like to check it out. The end-result of this work is that Entity.CopyTo and Context.CloneEntity are now supported again using the newer component copy to methods under the hood.

Example Auto-Generated Code for Entity

    /// <summary>
    /// Copies all components on this entity to <paramref name="copyToEntity"/>.
    /// </summary>
    public void CopyTo(VisualDebugEntity copyToEntity)
    {
        for (var i = 0; i < VisualDebugComponentsLookup.TotalComponents; ++i)
        {
            if (HasComponent(i))
            {
                if (copyToEntity.HasComponent(i))
                {
                    throw new EntityAlreadyHasComponentException(
                        i,
                        "Cannot copy component '" +
                        VisualDebugComponentsLookup.ComponentNames[i] +
                        "' to " +
                        this +
                        "!",
                        "If replacement is intended, please call CopyTo() with `replaceExisting` set to true.");
                }

                var component = GetComponent(i);
                copyToEntity.CopyComponentTo(component);
            }
        }
    }

    /// <summary>
    /// Copies all components on this entity to <paramref name="copyToEntity"/>; if <paramref name="replaceExisting"/>
    /// is true any of the components that <paramref name="copyToEntity"/> has that this entity has will be replaced,
    /// otherwise they will be skipped.
    /// </summary>
    public void CopyTo(VisualDebugEntity copyToEntity, bool replaceExisting)
    {
        for (var i = 0; i < VisualDebugComponentsLookup.TotalComponents; ++i)
        {
            if (!HasComponent(i))
            {
                continue;
            }

            if (!copyToEntity.HasComponent(i) || replaceExisting)
            {
                var component = GetComponent(i);
                copyToEntity.CopyComponentTo(component);
            }
        }
    }

    /// <summary>
    /// Copies components on this entity at <paramref name="indices"/> in the <see cref="VisualDebugComponentsLookup"/> to
    /// <paramref name="copyToEntity"/>. If <paramref name="replaceExisting"/> is true any of the components that
    /// <paramref name="copyToEntity"/> has that this entity has will be replaced, otherwise they will be skipped.
    /// </summary>
    public void CopyTo(VisualDebugEntity copyToEntity, bool replaceExisting, params int[] indices)
    {
        for (var i = 0; i < indices.Length; ++i)
        {
            var index = indices[i];

            // Validate that the index is within range of the component lookup
            if (index < 0 && index >= VisualDebugComponentsLookup.TotalComponents)
            {
                const string OUT_OF_RANGE_WARNING =
                    "Component Index [{0}] is out of range for [{1}].";

                const string HINT = "Please ensure any CopyTo indices are valid.";

                throw new IndexOutOfLookupRangeException(
                    string.Format(OUT_OF_RANGE_WARNING, index, nameof(VisualDebugComponentsLookup)),
                    HINT);
            }

            if (!HasComponent(index))
            {
                continue;
            }

            if (!copyToEntity.HasComponent(index) || replaceExisting)
            {
                var component = GetComponent(index);
                copyToEntity.CopyComponentTo(component);
            }
        }
    }

Example Auto-Generated Code for Context

    /// <summary>
    /// Creates a new entity and adds copies of all specified components to it. If replaceExisting is true, it will
    /// replace existing components.
    /// </summary>
    public VisualDebugEntity CloneEntity(VisualDebugEntity entity, bool replaceExisting = false, params int[] indices)
    {
        var target = CreateEntity();
        entity.CopyTo(target, replaceExisting, indices);
        return target;
    }
jeffcampbellmakesgames commented 3 years ago

This has been merged to develop