How do you combine a hybrid ECS with a pure ECS?

Hybrid makes it easy to move GO, pure is great for heavy lifting so I am trying to adapt influence map AI over to pure.
The hybrid will move the GOs out of a movement vector calculated by the pure influence map AI pure layer. How do I feed the pure with hybrid data, and back to hybrid and how do I limit the refresh rate of the pure ECS layer?
Cheers.

1 Like

Call systems update with frequency which you need. Not understand your question, what you mean in “combine”?

There is no way to limit ECS frequency but you can make your systems wait. You can have a frame counter which incremented every update.Systems act according to that.Like every X frame this system wil run.

Below example shows system called “LimitedSystem” running every frame but only doing work every second (assuming you hit 60 FPS) at 30th frame.

Example

public struct FrameCounterData:IComponentData
{
    public int frameCounter;
}

public class FrameSystem:ComponentSystem
{
    public struct Data
    {
        public readonly int Length;
        public ComponentDataArray<FrameCounterData> data1;
    }

[Inject] private Data data;

    protected override void OnUpdate()
    {
        //You don't need a loop if there is only one FrameCounterData on world.
        for (int i = 0; i < data.Length; i++)
        {
            FrameCounterData frameData=data.data1[0];
            frameData.currentFrame++;

            if(currentFrame >= 60)
            {
                frameData.currentFrame=0;
            }
           
            data.data1[i]=frameData;
        }
   
    }
}

public class LimitedSystem:ComponentSystem //The system you want to limit/wait.
{
    public struct Data
    {
        public readonly int Length;
        public ComponentDataArray<FrameCounterData> data1;
    }

[Inject] private Data data;

    protected override void OnUpdate()
    {
        if(data.data1[0].frameCounter == 30)
            //do stuff   
    }

}

Transfering data between GO and ECS worlds are cumbersome at this point.ComponentSystem and MonoBehaviour can access to EntityManager.With EntityManager you can pull components and thus their data.

Let’s say you wish to show your frameCounter on console.

        var v = manager.GetComponentDataFromEntity<FrameCounterData>();
        Debug.Log(v[e].frameCounter.ToString()); //"e" is entity

This does that and it can be called from MonoBehaviours.Problem is you need to know Entity to pass as parameter.There is EntityManager.GetAllEntities() methode,but it gives all entities and looping through them is not performant.Another option is chunk iteration.But I can’t give an example at this time.Feeding databack to ECS can be done in same whay.You can use EntityManager.SetComponentData() to pass data to components then your systems can process them.

These are simple examples,I hope someone who knows better than me shows a better approach.

1 Like

Can’t find the API for that, how do you do that?

I think there is a way to fill a buffer that crosses the pure/main thread barrier.

I mean manage them manually. All managers has .Update(). Create systems manually, ang manage them manually. (for separating this from default world create your own, and update systems when you need.

        foreach (var manager in myWorld.BehaviourManagers)
        {
            manager.Update();
        }
1 Like

How do I create a world and assign an entitymanager to it? the doc links on world are all dead

In very very basic implementation:

Bootstrap (can be system in other world, not only MonoBehaviour, is just for fast sample, update “Eizenhorn World” every 100 frames)

using Unity.Entities;
using UnityEngine;

public class ManualWorldBootstrap : MonoBehaviour
{
    private World _manualWorld;
    private float _udateCounter;

    void Start()
    {
        _manualWorld = new World("Eizenhorn World");
        _manualWorld.CreateManager<ManualWorldSystem>().Enabled = false;
        ScriptBehaviourUpdateOrder.UpdatePlayerLoop(_manualWorld);
    }

    void Update()
    {
        Debug.Log("Frame tick");
        _udateCounter++;
        if (_udateCounter == 100)
        {
            _udateCounter = 0;
            foreach (var manager in _manualWorld.BehaviourManagers)
            {
                _manualWorld.GetExistingManager<ManualWorldSystem>().Enabled = true;
                manager.Update();
                _manualWorld.GetExistingManager<ManualWorldSystem>().Enabled = false;
            }
        }
    }
}

System (every update creates new entity)

using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

public struct ManualWorldComponent : IComponentData {}

[DisableAutoCreation, AlwaysUpdateSystem]
public class ManualWorldSystem : ComponentSystem
{
    private ComponentGroup _manualWorldGroup;
    protected override void OnCreateManager()
    {
        base.OnCreateManager();
        _manualWorldGroup = GetComponentGroup(typeof(Position), typeof(Rotation), typeof(ManualWorldComponent));
        Debug.Log("Manager created");
    }

    protected override void OnUpdate()
    {
        EntityManager.CreateEntity(typeof(Position), typeof(Rotation), typeof(ManualWorldComponent));
        Debug.Log("<color=green>Manager updated - </color> items in group " + _manualWorldGroup.CalculateLength());
    }
}

Result
3844411--324892--upload_2018-11-1_11-9-46.png

2 Likes

Thanks! Perfect documentation example :slight_smile:
Interesting bit of automagic: as soon as UpdatePlayerLoop is set, all other systems that relied on autoboostrapping stop running. In order to keep them running, World.Active needs to be added to the parameters.

  1. you don’t need to update player loop if you are updating the world manually. it just interfere with the other worlds.
  2. you are setting _manualWorld.GetExistingManager<ManualWorldSystem>().Enabled in the loop. did you mean to set manager.Enabled?

if you instead wanted to only manually update one system, do ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World.AllWorlds); and get rid of the loop (also cache your system instance)

  1. Yep is not needed, just wrote this by inertion as in default wold manager initialisation :slight_smile:

  2. After creating I disable system for preventing OnOpdate calls in system (because system marked AlwaysUpdateSystem, otherwise if you not mark system AlwaysUpdateSystem and in world not exists any entity which can be handled by this system (in ComponentGroup for example), Update on manager not do any things, thus system never run, until entity which can be handled not be created in world, otherwise if entity exists system start runs OnUpdate every frame), and then iterate through all managers in world enable them, update and disable, thus system updates ONLY when I call it, without any early out or empty updates.

ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World.AllWorlds) will not work :slight_smile: You must convert it to Array of World becausle World.AllWorlds is ReadOnlyCollection :slight_smile:
ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World.AllWorlds.ToArray()) is correct.
You not need cache managers, because internally is Dictionary, and just getting manager by Type as key is O1, cache in this case - overhead.
3848857--325465--upload_2018-11-2_15-33-53.png

  1. you are continually enabaling/disabling the same system in the loop, when calling Update on the other systems.
    you either want to disable/manually update one system (in that case you cache it when you create&disable it in Start, then you do not loop all the systems each frame) or the entire world (in that case you need to enable/disable the system you are iterating, not the same system over and over)

ps. dictionary lookup is still a lookup (plus arg checking), and _manualSystem is less typing than _manualWorld.GetExistingManager<ManualWorldSystem>()

Oh I understand which you mean, you right, absolutely looked through this moment thx, of course I mean Enable\Disable current system in loop :slight_smile: And without player loop is not needed

O1 that says it all, I repeat, this is an overhead.

Here fixed variation, thanks @M_R for pointing to some incorrect places :slight_smile:

using Unity.Entities;
using UnityEngine;

public class ManualWorldBootstrap : MonoBehaviour
{
    private World _manualWorld;
    private float _udateCounter;

    void Start()
    {
        _manualWorld                                            = new World("Eizenhorn World");
        _manualWorld.CreateManager<ManualWorldSystem>();
    }


    void Update()
    {
        Debug.Log("Frame tick");
        _udateCounter++;
        if (_udateCounter == 100)
        {
            _udateCounter = 0;
            foreach (var manager in _manualWorld.BehaviourManagers)
            {
                manager.Update();
            }
        }
    }
}
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

public struct ManualWorldComponent : IComponentData {}

[DisableAutoCreation, AlwaysUpdateSystem]
public class ManualWorldSystem : ComponentSystem
{
    private ComponentGroup _manualWorldGroup;
    protected override void OnCreateManager()
    {
        base.OnCreateManager();
        _manualWorldGroup = GetComponentGroup(typeof(Position), typeof(Rotation), typeof(ManualWorldComponent));
        Debug.Log("Manager created");
    }

    public void UpdateManually()
    {
        OnUpdate();
    }

    protected override void OnUpdate()
    {
        EntityManager.CreateEntity(typeof(Position), typeof(Rotation), typeof(ManualWorldComponent));
        Debug.Log("<color=green>Manager updated - </color> items in group " + _manualWorldGroup.CalculateLength());
    }
}
1 Like

Code?

Your update code seems a bit odd. Why not do it in a frame-rate independent way instead, using either delta time, cooroutines or maybe even the InvokeRepeating function?

Sample Code

using UnityEngine;

public class ManualWorldBootstrap : MonoBehaviour
{
    private World _manualWorld;
    private float _udateCounter;

    void Start ()
    {
        _manualWorld = new World("Eizenhorn World");
        _manualWorld.CreateManager<ManualWorldSystem> ();
        InvokeRepeating("UpdateWorld", 1, 1);
    }

    void UpdateWorld ()
    {
        foreach (var manager in _manualWorld.BehaviourManagers)
        {
            manager.Update ();
        }

        Debug.Log ("World updated!");
    }
}
  1. Invoke is reflection, and garbage, and under hood it’s do same thing.
  2. Coroutines - too, generate garbage.
    Simple timer use only stack and not generate garbage. Also using in monobehaviour is just sample, you can do this in other ComponentSystem/JobComponentSystem in other World, where you not inherit monobehaviour and don’t have invoke or coroutines.
1 Like

All true but my main point was that your sample code is not frame-rate independent and I was simply giving multiple alternatives. Garbage collection is also not really an issue for such a simple function/example, and if it is you probably woudn’t use a Monobehaviour to begin with. You also completely ignored the delta time suggestion, which is what I would use, but InvokeRepeating is simplest so it works great as a sample.

And Invoke it’s ugly like SendMessage, It’s bad practice

Now return to first quote. WARNING: Infinite loop detected! :hushed:

We seem to both agree it’s just a sample and doesn’t really matter. So… maybe we can just forget about this whole thing? I’m sorry to derail the tread. I should have never made the original comment.

Oh of course, may be I incorrect understand your agreement, laguage barrier :slight_smile: Anyway, no problem, forget about our discussion :slight_smile:

1 Like