ECS too verbose

The ECS code I’m reading or tried is far too verbose, tons look like boiler plate so I am giving up for now, since these are the very early days I think it will get better.

What is the plan to make ECS code more readable and accessible?

I think there is plan to integrate it to unity so then you will not be able to read it. :smile:

1 Like

I wouldn’t call it verbose exactly, but it does require a great deal of effort at the moment and is not very intuitive or streamlined, at least not for those coming from a Unity / OOP mindset. The pure ECS approach also currently requires you to essentially rewrite every system yourself, which kind of defeats the whole purpose of using an engine like Unity in the first place!

So I think at this moment the hybrid approach is ideal for beginners and is how I plan to start using this in projects. Since this allows you to gradually transition to this new way of doing things without having to relearn everything all at once, which can be overwhelming.

In the future entities and entity components will be tightly integrated into the editor just like good old classic components. However, if you’re more of a visual design kind of person (like myself) then there seems to be some very exciting editor features in the works. The video below seems to be our very first glimpse of what they’re working on, and I like what I’m seeing so far.

It would also be fantastic if someone from Unity could provide additional information about these new visual editor features they’re working on. (hint hint)

2 Likes

@laurentlavigne It would be great if we can make it concrete. What specific example code (please paste it here) do you feel has too much boiler plate. Lets start with one code example and discuss, see where it takes us.

5 Likes

The API does seem pretty heavy and geared towards low-level concerns, but I suppose if the animating concern for switching to ECS was performance that makes sense as a starting point.

I’m with you though in hoping that they don’t stop there. Most ECS implementations see it as an extension of data oriented design - which it has an obvious affinity with - but it has a lot to offer as a general paradigm: it naturally pushes you towards small modular code; greatly simplifies dependency management; is easily testable; etc. It would be a real missed opportunity if those benefits were locked behind a wall of complexity that made them only accessible to the “engine” programmers on the team.

Side Note: For me at least, having a low-level API for “programmers” and an editor-centric visual interface for “designers” would be great but not sufficient. On small or one-man “indie” teams (which I suspect are well represented in Unity’s user base) those roles blur a lot and it would be great to have a high-level API that allows “programmer/designers” to express and iterate on designs in code without being forced back into MonoBehaviours and having to define things in a somewhat arbitrary mix of code and editor based assets.

EDIT: Actually I take some of the above back. It’s fine if there’s only a low-level API as long as it exposes a lot of the engine and gives us the flexibility to create our own fully featured abstractions on top of it. What I would want to avoid is something along the lines of how now if you want to get access to engine events (e.g. Update, etc.) you have to create an object in the scene and attach a script to forward those events. If we can access everything without having to create objects in the scene, we can take care of the rest.

Oooh that’s a tough one… how do I narrow down a general feeling of 5x character count into one example.

Let’s try this one:

            entityManager.SetComponentData(player, new Position2D {Value = new float2(0.0f, 0.0f)});

And this

        public static void SetupComponentData(EntityManager entityManager)
        {
            var arch = entityManager.CreateArchetype(typeof(EnemySpawnCooldown), typeof(EnemySpawnSystemState));
            var stateEntity = entityManager.CreateEntity(arch);
            var oldState = Random.state;
            Random.InitState(0xaf77);
            entityManager.SetComponentData(stateEntity, new EnemySpawnCooldown { Value = 0.0f });
            entityManager.SetComponentData(stateEntity, new EnemySpawnSystemState
            {
                SpawnedEnemyCount = 0,
                RandomState = Random.state
            });
            Random.state = oldState;
        }

and that

            PostUpdateCommands.CreateEntity(TwoStickBootstrap.BasicEnemyArchetype);
            PostUpdateCommands.SetComponent(new Position2D { Value = spawnPosition });
            PostUpdateCommands.SetComponent(new Heading2D { Value = new float2(0.0f, -1.0f) });
            PostUpdateCommands.SetComponent(default(Enemy));
            PostUpdateCommands.SetComponent(new Health { Value = TwoStickBootstrap.Settings.enemyInitialHealth });
            PostUpdateCommands.SetComponent(new EnemyShootState { Cooldown = 0.5f });
            PostUpdateCommands.SetComponent(new MoveSpeed { speed = TwoStickBootstrap.Settings.enemySpeed });
            PostUpdateCommands.AddSharedComponent(TwoStickBootstrap.EnemyLook);

By the way, why is it math.normalize and not Mathematics.Normalize?

1 Like

Another good example is TransformSystem. Haven’t done a “scientific” count yet but the boilerplate/ code ratio feels like 6:1. That’s without scaling and “proper” hierarchy support.

To be fair, getting the clowns out of the car still leaves you with a bunch of clowns. However, imho, it’s better for the clowns to be working the factory ( boilerplate ) than slowing down your car.

At the end of the day, how many clowns we get out of the car is still up to us. As in, instead of implementing the best performance case ( separating transform components to ?fit within cache lines? and only update what is used ), you could just roll them into one component/ job and be done with it.

I prefer the clowns to be out of sight of consumers. My only worry is how the regular unity devs ( hobbysts, one man indie shows, students, jammers, which prefer human-understandable code and super fast iteration ) will react to the final ECS system. If adoption is low …

Am I misunderstanding you because how is what you showed any more verbose then the a similar method with the old component system? Its 12 more characters and once you start adjusting multiple properties per component and adding multiple components you wind up typing up a lot more code the old way.

var someVar = playerGo.AddComponent(SomeType);
someVar.Value = Vector2.zero;

Personally I load most of my assets from bundles then attach the scripts at runtime and I feel like I have been overall typing up way less code with the ECS than with the old system.

public static void SetupComponentData(EntityManager entityManager)
        {
            var arch = entityManager.CreateArchetype(typeof(EnemySpawnCooldown), typeof(EnemySpawnSystemState));
            var stateEntity = entityManager.CreateEntity(arch);
            var oldState = Random.state;
            Random.InitState(0xaf77);
            entityManager.SetComponentData(stateEntity, new EnemySpawnCooldown { Value = 0.0f });
            entityManager.SetComponentData(stateEntity, new EnemySpawnSystemState
            {
                SpawnedEnemyCount = 0,
                RandomState = Random.state
            });
            Random.state = oldState;
        }

We will introduce ref returns across the ECS api soon. I think that will help with the specific example you posted.
It would then look like this. The weird handling of Random… Is not specific to ECS. I am sure we will come up with new API’s for random, because “determinism by default” implies that the current API + multithreading is not very compatible…

public static void SetupComponentData(EntityManager entityManager)
        {
            var stateEntity = entityManager.CreateEntity(typeof(EnemySpawnCooldown), typeof(EnemySpawnSystemState));
            var oldState = Random.state;
            Random.InitState(0xaf77);

            entityManager.GetComponentData<EnemySpawnSystemState>(stateEntity).RandomState = Random.state;
            Random.state = oldState;
        }
3 Likes

I also generally agree on this. If you create Entity’s from code, it seems like the wrong approach. Thats what prefabs are for. And you can totally instantiate pure entities from a prefab. (It extracts all IComponentData from the prefab)

Naturally in the long run a lot of tooling has to go into making sure you can actually create pure entity scenes / prefabs without going through game objects. We have a team working on that. But it will take time.

11 Likes

Interesting !

Personal opinion (from an ECS beginner :slight_smile: )

  • My expectation would be that the API get’s as simple as possible but not any simpler - in the end it’s C# and it’s OK to type some code (example: while I like the injection, I would also be OK to just type it out with get component groups. I prefer if there is only 1 way of doing the same thing and this way is properly documented)
  • I think the ECS lends itself to some way of a visual user interface with code generation since it consists of fairly standardized blocks (I never used a visual coding tool, but I could see it totally for ECS) - for entities, components, systems and world management
5 Likes

This pops into my head whenever I try to imagine the medium-long term future of ECS & Unity. I hope it ends up making sense and is the way Unity go.

2 Likes

@Joachim_Ante_1 , love the generic and ref return (it’s that “=” I guess), pays off to have upgraded c#
While you have the hedge trimmer, CreateEntity could also go generic. Same for everything that takes type as argument.

CreateEntity<EnemySpawnCooldown, EnemySpawnSystemState>();

That’s an important discussion to have early because jobs confuse the hell outta me when people don’t put read/write attributes, what’s the default in this case, read only? like when you don’t put public in front of a class’ variable? One way to do things and that way is super concise and at the same time clear of intention.

I’m more a type less, read less sorta guy and since I’ve only learned c# from the version before unity 2017, c# always felt too verbose. But with the transition to c# 6, we’re at the cusp of a change similar to when unity API got its generics overloads (you guys & gals remember when you had to pass a type? :hushed:) less code, clear intention

You’re right, going with = in this case is more chatty, I don’t know why I gave this example, must be a temporary allergy to constructors.
Regarding the instantiation and ECS, that’s what I noticed: those like you who build their assets by code have cleaner tighter code with ECS. And reading through the 100K soldier example is crystal clear because the points of entry to the ECS systems are very rigid. It’s just a long process at the moment.

The funny thing with reading the that example is I’d never bother if it were OOP because everybody got their own pasta flavor and following the meandering trail of calls isn’t fun.

Story time: I’m programming microcontrollers nowadays, all in C, it’s all arrays of data, very linear, when you need a thread you call it by hand, very concise functions, it’s relaxing.

What does a GameObject-less prefab look like? That implies some kind of container that can have components (like IComponentData) attached to it, but doesn’t have a transform.
Unity already supports putting ScriptableObjects as a part of a scene file. You have to create custom tools to edit that stuff, though. Is that the route you’re going down, or are you planning on introducing a new concept entirely? Or is this not decided yet?

The important part about Unity that needs to not get lost in all of this is the inspector. No matter how fast the code runs, if I can’t make things my designers and artists can click on and fiddle with the values on in the inspector, it’s not really usable. That’s what the pure approach looks like right now; the demo has a bunch of spaceships on the screen, and the non-programmers on the team doesn’t seem to have any good way to interact with them at runtime, which means that I suddenly become a bottleneck on everything.

3 Likes

I agree completely. For the prefab question, they could flesh out the Archetypes concept?

So in a typical ECS system you start with entities and components. The components on an entity are basically what drives the behavior. Even though it’s systems that perform the work. This is because data driven in the ECS context means behavior is data driven. It was really never intended to describe the benefits you can get in performance related to cpu cachelines and such.

It’s confusing to mix up those two concepts, you need separate terminology. I see a lot of newcomers missing one of the whole key points of ECS because of this.

It’s not at all clear from where the abstractions are that you have two distinct and separate parts of an ECS system. You have your components and how you model them, and the data driven part here is that components drive behavior of entities via which components you attach to an entity.

Which is related to but distinctly different from the systems that use components to implement the behavior functionality.

Judging by the posts people are making, if they were not already familiar with ECS the above is just completely lost on them.

For example, you should be manipulating which components go on which entities via the entities themselves. Not the entity manager. Abstraction is in the wrong place.

It’s little things like that where the abstractions cleanly map to the domain model that really make the difference between an elegant easy to understand api, and well what we have now.

Like the shared components. That’s almost an anti pattern in the context of ECS.

Systems overall seem ok. If anything the needs of systems are leaking out into places they don’t belong.

It does seem that within a system jobs should just be abstracted away. So that maybe a system runs in a job context, but you aren’t creating jobs within systems.

4 Likes

Agree with the terminology part. Say now Component is the traditional component, ComponentData is Component in ECS, ComponentSystem is System but with a “Component” prefix. Please, make it less confusing. :wink:

For example, you should be manipulating which components go on which entities via the entities themselves. Not the entity manager. Abstraction is in the wrong place.

Components do not go on entities. Entities are not containers. That’s a critical difference between game objects and entities.

An entity is an id. Components have an id. Different components which share the same id can be grouped together. There is no “entity” storage. Components are effectively rows in a database, and an entity is just one field in one of those rows.

6 Likes