How to read transform position from Transform without high performance impact?

Here’s a situation, I’ve got a bunch of monobehaviours that have entities related to them.
I want to read transform.position from Monobehaviour side, and set it as a float3 data to the entity as a component.

Bruteforce solution works (just iterate collection of them and manually write via ECB to the EntityManager via SetComponentData) but it takes too much time to sync it when numbers of those objects rise.

I’ve searched for TransformAccess examples that use transform for reading position manually, but couldn’t find any up-to-date. Rest of the examples just point towards the full conversion, which I don’t need (only world position is required in my case).

Any suggestions?

Just use the built in CopyTransformFromGameObjectSystem?

1 Like

I used

https://docs.unity3d.com/Packages/com.unity.entities@0.13/api/Unity.Entities.EntityQueryExtensionsForTransformAccessArray.html

and

for my hybrid processing.

This approach is still valid and fastest way if you don’t need any other stuff.

If you need to know what Entity has the transformAccess, using EntityQuery.ToEntityArrayAsync() too. It returns entity list which is exactly same order to TransformAccessArray() so you can grab correct Entity.

1 Like

Is it possible to use TransformAccessArray separately though?

Would something like this:

  • Populate TransformAccessArray with Transform[ ] constructor, or via add;
  • Attach an index of the Transform to the Entity via ComponentData (upon initialization);
  • Pass that TransformAccessArray to the system and use it in a job;
  • Sync a position to the component by fetching TransformAccess by index, and setting component value

work?

Hmmm TransformAccessArray doesn’t have a way to retrieve TransformAccess from it.

If you want to fetch position from Transform to IComponentData, it can be done more simply.

  1. Add Transform component to your entity, which has Position component. (via EntityManager)
  2. In your system, Create EntityQuery which has Transform and Position types.
  3. Create TransformAccessArray() and ToEntityArrayAsync() by using EntityQuery of 2.
  4. Create IJobParallelForTransform job and copy position value yourself. It may be like this:
[BurstCompile]
struct StashTransformJob : IJobParallelForTransform
{
    [DeallocateOnJobCompletion]
    public NativeArray<Entity> Entities;
    [NativeDisableParallelForRestriction]
    public ComponentDataFromEntity<Position> PositionFromEntity;

    public void Execute(int index, TransformAccess transform)
    {
        Entity entity = this.Entities[index];

        Position component = this.PositionFromEntity[entity];

        component.value = transform.Position;

        this.PositionFromEntity[entity] = component;
    }
}
1 Like

Uhhh, isn’t that a reference type?

Ok, how do I do that?

EDIT: WUuuutt its possible to attach managed objects? Wow. I didn’t know that.
It’s EntityManager.AddComponentObject that I was missing this whole time. That is super cool to have.

Thanks, I’ll try the above :slight_smile:

Here is complete example code:

  1. System
using UnityEngine;
using Unity.Entities;
using Unity.Burst;
using Unity.Mathematics;
using UnityEngine.Jobs;
using Unity.Collections;

public class TransformStashSystem : SystemBase
{
    private EntityQuery transformQuery;

    protected override void OnCreate()
    {
        this.transformQuery = this.GetEntityQuery(ComponentType.ReadOnly<Transform>(), ComponentType.ReadWrite<Position>());
    }

    protected override void OnUpdate()
    {
        var entityArray = this.transformQuery.ToEntityArray(Allocator.TempJob);
        var transformArray = this.transformQuery.GetTransformAccessArray();
        var positionFromEntity = this.GetComponentDataFromEntity<Position>();

        this.Dependency = new StashTransformJob()
        {
            Entities = entityArray,
            PositionFromEntity = positionFromEntity,
        }.Schedule(transformArray, this.Dependency);
    }
}

public struct Position : IComponentData
{
    public float3 value;
}

[BurstCompile]
public struct StashTransformJob : IJobParallelForTransform
{
    [DeallocateOnJobCompletion]
    public NativeArray<Entity> Entities;
    [NativeDisableParallelForRestriction]
    public ComponentDataFromEntity<Position> PositionFromEntity;

    public void Execute(int index, TransformAccess transform)
    {
        Entity entity = this.Entities[index];

        Position component = this.PositionFromEntity[entity];

        component.value = transform.position;

        this.PositionFromEntity[entity] = component;
    }
}
  1. Create entity
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;

public class TransformSample : MonoBehaviour
{
    private Entity entity;

    private void OnEnable()
    {
        var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        this.entity = entityManager.CreateEntity();

        entityManager.AddComponent<Position>(this.entity);
        entityManager.AddComponentObject(this.entity, this.transform);
    }

    private void OnDisable()
    {
        var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        entityManager.DestroyEntity(this.entity);
        this.entity = default;
    }
}

These two files contains all you need I think.

Additional tips:
If your transforms share same root transform, it cannot be read as parallel. This is Unity’s restrction so you should manage your transforms does not share same root as possible as for maximum performance.

2 Likes

Works like a charm, thanks again :slight_smile:

Yeah, its unfortunately the case. Lots of points attached to the single root.
Good to know though. Perhaps I’ll remake it someday.

Got a x2-3 performance boost compared to the bruteforce method, not quite zero, but its better than it was before.
Guess that’s the price for using Hybrid & this kind of hierarchy.