Entitas-csharp: API Syntax: Renaming Entity.Repace?

Created on 2 Dec 2017  路  17Comments  路  Source: sschmid/Entitas-CSharp

Isssue

In Entitas the proper way to change component values is to replace the component entirely with a new component containing different values using the entity.Replace[ComponentName](values) helper method.

entity.ReplacePosition(10, 100);
entity.ReplaceHealth(entity.health.value - 1);

This makes sense due to the immutable nature of components, however, it's not very intuitive for beginners. Anyone not familiar with the system would wonder why the Position and Health components in the above example must be replaced. Why not just modify the values directly instead? This exact scenario has actually already happened and was discussed in the chat and inspired this possible solution.

Solution 1

Rename entity.Replace to either entity.Update, entity.Set or something similar to better indicate it's purpose rather than function and minimize confusion for newcomers. So we would instead have something like this:

entity.UpdatePosition(10, 100);
entity.UpdateHealth(entity.health.value - 1);

Solution 2

bleedingpixels in the chat suggested the possibility of replacing both entity.Add and entity.Replace with entity.Set. So both adding and "modifying" components would look exactly the same.

entity.SetPosition(10, 100);
entity.SetHealth(entity.health.value - 1);

However, I'm not sure if this would be as intuitive as only renamingReplace itself.

Now the main issue with renaming Replace to something like "Update" is that it now suggests the data is mutable when it is actual not, but is that really an issue? Experienced users will already know it's not and newcomers will be more likely to use it instead of just editing component values directly.

Update: as KumoKairo quoted below, we already have entity.isFlag = true/false which violates the entity.Replace rule.

All 17 comments

Just some followup in the chat. A strong point I'm absolutely agree with:

I was also thinking about the fact that we already have the entity.isFlag = true; helper method for flag components, which I would argue doesn't suggest immutability at all and is just meant to be intuitive like Update . It would also seem that renaming Replace to Update would make even more sense considering the fact that this method also updates all other systems, but maybe that's just me 馃槢
This has got me thinking, isn't something like entity.isMovable = true/false basically a replacement for entity.AddMovable, entity.ReplaceMovable and entity.RemoveMovable? All though I guess Replace in this case would be useless

This bool equal operation goes against the commonReplace pattern and encourages to replace values of components directly

As an interesting side note, I also noticed while watching the Writing High Performance C# Scripts Unite Austin 2017 talk again that the new Unity ECS system uses Set instead of Replace for their own API:

EntityManager.SetComponent(entity, new Component(...));

I like the UpdateXXX renaming. Another minor issue with the code generator is with naming conventions. Say I make a component like this:

public class PositionComponent
{
    public Vector2 Value;
}

The API it generates is:

public partial class GameEntity {

    public PositionComponent position { ... }
    public bool hasPosition { ... }

    public void AddPosition(int newX, int newY) {
        ...
    }

    public void ReplacePosition(int newX, int newY) {
        ...
    }

    public void RemovePosition() {
        ...
    }
}

Then, when used, it looks like: entity.position.Value which does not look right if you are used to Microsofts C# naming conventions where public fields, properties, and methods start with an upper-case.

Could be nice to have some kind of option to disable this such that fields begin with upper-case.

I like talking about details. In this case I don't really see an issue, but I might be biased since we called it Replace since the beginning 4 years ago :) In my opinion Replace makes still sense as it also explains why entities leave a group and re-entering it (well at least in the mental model) when you change a component. By now we probably are all familiar with Groups and Reactive Systems, but in the beginning I managed to explain the functionality by talking about really replacing immutable components.

@IsaiahKelly

Unity ECS system uses Set

Afaik, they are actually setting the component. So it makes sense

@nbreum15

Microsofts C# naming conventions

Welcome to Unity gameObject.transform.position.x ;)

As a result of #508 I plan to expose the code generation templates, so everyone can make tweaks without adding a custom code generator. That means you can easily change method names (e.g. UpdateX) and change upper or lowercase.

@sschmid thanks for the reply.

Afaik, they are actually setting the component. So it makes sense

Yes, they are _setting_ the component to a different value... by _replacing_ it with a new one (watch this). So I'm not sure what your point is exactly? 馃榾

I'm also now leaning towards entity.Set over entity.Update because it could also replace (no pun intended) the component flag boolean. So instead of entity.isMovable = true; you could use entity.SetMovable(); giving us a more consistent API.

However, this is all very much a personal preference kind of thing. Allowing people to create their own API naming conventions would be very helpful too, but I think universal consistency or agreed upon standards are important. If everyone has their own, things could get very confusing fast! So I guess it really comes down to what the community wants. Just throwing some ideas out there anyway. Thanks for considering them.

@IsaiahKelly

Yes, they are setting the component to a different value... by replacing it with a new one

They are actually setting it ;) They're putting the struct data directly in the array, so to be specific, they're overwriting the old data. I've basically built what Unity is building now a year ago, I might have shared some info here and there about it when I talked about my other ECS with components as structs and entity is an int.

Different topic (because I read and here a lot about it recently), but maybe it's a good time to also stress that not only Unity's ECS has been built with performance as a key concern, performance is also one of Entitas key pillars. I also critically compared to my other ECS which has the same performance as the Unity one and Entitas kept up so great that I decided not to further pursue a second ECS.

Yeah, but since Unity is able to implement it on the c++ side, they'll be able to make it super cache friendly. Something that'd be quite challenging when implementing it purely in c#.

Buut they'll also expose native memory access and Entitas can benefit too :)

On topic: me and my team are used to Replace, not sure what would be the benefit of the rename. Naming conventions are just... conventions :)

@kdrzymala The biggest benefit here would not be for experienced users, but beginners. The primary objective being to just make it more intuitive or easily understandable for those coming from a OOP prospective, but whether that's actually a good idea is up for debate.

I would however point out again that we already have entity.isMovable = true; which completely goes against the reasoning for sticking with entity.Replace . The argument for keeping it just seems to be that it's something people are used to, which doesn't sound like a very compelling reason to me, but I might be trying to fix something that isn't actually broken. 馃し鈥嶁檪

@sschmid In my mental model the word "replace" just doesn't seem optimal because it's describing the literal function rather than the purpose, if that makes any sense. In other words, we _replace_ components when we want to _update_ their values. So "replace" is the literal function, but "update" is the intended purpose. Do you see what I mean? Now this is probably very subjective, but that is just how my brain seems to see things anyway.

Also, I would say the difference between Entitas entity.Replace and Unity ECS entity.Set seems trivial to me, but maybe it's just because I don't actually understand the differences? At least there seems to be no difference in how they work from an API perceptive. In both cases you're replacing all values at the same time. The only difference seems to be in how this is handle behind the scenes in memory. So I don't see any functional difference in calling either method "replace" or "set", but maybe that's just because I'm entirely ignorant and have no idea what I'm talking about. 馃槵

My 2 cents: I would really prefer to have a completely 'fluent interface' approach for adding/replacing/removing components on an entity.

Maybe add 'is'/'has' prefix to flag components methods just like we have with properties now would be cool as well:

public class Destructible : IComponent {}
public class Movable : IComponent {}
Entity entity = context.CreateEntity.AddIsDestructible().AddIsMovable();

@ShadeSlider this is how Entitas looked like a few years ago. There was some performance considerations and bottlenecks with this fluent syntax that made Simon to remove it.

@ShadeSlider Thanks for the input, but I don't think including "Is" in the add method makes any sense if you're not actually setting or getting a bool. So I think it would be better to stick with how it works now or just use the standard add method instead.

However, I think replacing all of this with just Entity.Set makes a lot of sense.

So instead of doing this:

    public static GameEntity CreateBlocker(this GameContext context, int x, int y) {
        var entity = context.CreateEntity();
        entity.isGameBoardElement = true;
        entity.AddPosition(new IntVector2(x, y));
        entity.AddAsset(Res.Blocker);
        return entity;
}

You would do this:

    public static GameEntity CreateBlocker(this GameContext context, int x, int y) {
        var entity = context.CreateEntity();
        entity.SetGameBoardElement();
        entity.SetPosition(new IntVector2(x, y));
        entity.SetAsset(Res.Blocker);
        return entity;
}

Then instead of replacing components to update their values you would simply call set again:

void moveDown(GameEntity e, IntVector2 position) {
        var nextRowPos = GameBoardLogic.GetNextEmptyRow(_context, position);
        if (nextRowPos != position.y) {
            e.SetPosition(new IntVector2(position.x, nextRowPos));
        }
}

This all seems like a much simpler, clearer and logical API to me.

@IsaiahKelly Can you explain a bit more on the "immutable nature of components"? Any reference? Because I have some code modifying the value of a component directly in systems. Sometimes when you have some large components, you need to change some fields alone.

@zoujyjs Don't know of any good references here, sorry. It's something that's really lacking at the moment with Entitas. As for your use case, my first guess would be that your component is probably too big and you just need to create a new separate smaller component and system to handle this one type of data.

However, as far as I understand it, you should always use entity.replace whenever you want to change any value on a component because modifying the values directly will not update other systems that work on them. Causing them to become outmoded and/or preventing them from reacting to state changes. Now if you don't really need other systems to know about the changes then I guess you can get away with it, but it's very likely to cause you problems later on if you need to know about these changes.

"Immutable"

The Entitas API treats component data as if it's immutable, but components are actually recycled behind the scenes to avoid garbage collection. I think the logic behind this facade is to force people to use entity.replace whenever they want to change values, so systems can always get notified of changes, but I'm wondering now if this is actually having the opposite effect. Since the concept of replacing things you want to update seems strange and sometimes a little annoying, so people just end up modifying the values directly because it feels more natural or easier.

However, forcing you to replace components entirely, every time you want to change a value, does make people think about what data their components store a might help encourage them to follow the single responsibility principle more often.

Again in my mind just calling it _set_ or _update_ instead of _replace_ seems like it might resolve some of these issues, but who knows, this might ultimately be just a personal preference.

In any case we probably need a dedicated section on this subject in the wiki because it seems to be a topic discussed in the chat often and is not very well understood or explained to newcomers.

@IsaiahKelly Very helpful! It's kind of like a functional programming model where systems read states(components) and map it to new ones. But say I have a CameraComponent which contains a reference to the camera instance and parameters of camera.

// not an entitas syntax component, just for example
struct CameraComponent {
    ICamera *camera;
    ...parameters
    bool fixed;
}

Specifically, bool CameraComponen::fixed is a state indicates should camera be fixed for now maybe when the avatar is doing some animation play when you don't want camera following the animation.

I've got CameraFollowingSystem that will find a entity with PlayerComponent and check if not fixed then set the camera attitude to follow the avatar.

Another FooSystem will set CameraComponent::fixed = true when the avatar is playing some animation.

It's a very common situation where a system will only need to change one field. Also a very common situation where you need some adhoc data to change the system behavior (e.g. an camera offset when you use difference avatar with different body shape). So should I separate the all these data into a new Component? like a FixedComponent ? And add/remove it to/from avatar in FooSystem ? Because it's weird when I have other fields like "should the camera fixed on y axis" which is a parameter of camera, and now I have to add this as a component to an entity (camera is not an entity).

@zoujyjs Deciding how your data should be broken up is no simple task and something of an art form. There are also much more qualified people than me that can probably give you much better advice, but I'll try to give you my own thoughts on the subject anyway.

Yes, in your example I would probably move the fixed bool field to a component like MovableComponent and just add/remove this component to indicate a camera entity is movable. Then the CameraFollowingSystem would only process entities that have both a camera and movable component attached to them.

For more complex constraints, like fixed individual axis, you could create a more complex component to handle all these use cases like this:

public sealed class ConstraintComponent : IComponent {

    public bool lockX;
    public bool lockY;
    public bool lockZ;
}

Then you could use this component for both cameras and other entities that are constrained in a particular way. This component could also be structured differently and it might be more helpful to use an enumerator instead, e. g. constraint = ConstraintType.All;

I think the key principle here is to keep your components as generic as possible, so they can be easily reused in other systems and entity combinations. Maybe thinking of components as a kind of advanced variable would help. You'll notice that both component examples above could easily be reused with other entity types and are not unique to the concept of a camera at all.

Hope this helps you, but I'm afraid we've steered the topic off the road and over a cliff 馃榿. If you have more question on the subject then please feel free to ask in chat or open a new issue on the subject. Thanks!

@IsaiahKelly Your thoughts really enlightened me. I'll try doing some refactoring work after.
Thanks!

Stopped my plans to expose the code generation templates to focus on other things. I hope it's also ok that I won't change the current replace api. But thanks for the discussion

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CCludts picture CCludts  路  3Comments

jakovd picture jakovd  路  3Comments

yuchting picture yuchting  路  4Comments

FNGgames picture FNGgames  路  5Comments

yanjingzhaisun picture yanjingzhaisun  路  4Comments