Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Many to one #118

Open
onehundredfeet opened this issue Jul 2, 2021 · 11 comments
Open

Many to one #118

onehundredfeet opened this issue Jul 2, 2021 · 11 comments

Comments

@onehundredfeet
Copy link

Sorry if this has been asked before.

what is the best way to do a one entity to many component relationship where one entity ‘owns’ many components of a certain type? Ideally one where you can easily iterate over an entities set of related components.

unity uses some chunked system to make an array component or something like that. I can’t find anything in the Default ecs other than using an entity multit-map.

@Doraku
Copy link
Owner

Doraku commented Jul 2, 2021

hum EntityMultiMap is the inverse of what you want I think, many entities for one component value. For an entity to hold multiple components of the same type, you would have to use a collection as component type: entity.Set(new List<MyComponent>{ ... }) and create your query accordingly With<List<MyComponent>>(). Would that work for you? I don't know what could be added to make it easier.

@onehundredfeet
Copy link
Author

onehundredfeet commented Jul 2, 2021 via email

@Doraku
Copy link
Owner

Doraku commented Jul 2, 2021

Yeah the allocation thing is why I didn't want to handle it ^^" That's more or less how the old child/parent relationship worked before, I was so displeased with the implementation that I just removed it completely.
I may have an idea on how to do it properly, in the internal storage components have some extra infos (parent entity id, number of parents). Maybe it is possible to construct a linked list of components by adding a "next id" on the internal storage to keep cache coherence and minimise allocation while keeping fast add/remove.

@onehundredfeet
Copy link
Author

onehundredfeet commented Jul 2, 2021 via email

@Doraku
Copy link
Owner

Doraku commented Jul 2, 2021

Components are not disposed even if they implement IDisposable once removed (seemed a little too much magic to me and not always what we want) but you can add it by using https://github.com/Doraku/DefaultEcs/blob/master/documentation/api/World_SubscribeComponentRemoved_T_(ComponentRemovedHandler_T_).md
Your callback should be called even when the entity or the world is disposed. The problem is if you share the same pool between multiple entities :/

@onehundredfeet
Copy link
Author

onehundredfeet commented Jul 2, 2021 via email

@Doraku
Copy link
Owner

Doraku commented Jul 11, 2021

Not with the way it works for now. The component only store the first entity that references it and the total number of references, so I don't need an other collection that could allocate. We could check for every entities but then it would be too costly to use in hot paths. If my idea to handle multiple components of the same type on a single entity works it should be possible to add something for that.
In the meantime I guess you could use EntityMultiMap if your component is a class or the struct values are unique.

@onehundredfeet
Copy link
Author

onehundredfeet commented Jul 11, 2021 via email

@Doraku
Copy link
Owner

Doraku commented Sep 5, 2021

Some thinking I have done on this issue:
It should be possible to use this type

namespace DefaultEcs.Technical
{
    internal struct ComponentLink
    {
        #region Fields

        public int EntityId;
        public int ReferenceCount;

        #endregion

        #region Initialisation

        public ComponentLink(int entityId)
        {
            EntityId = entityId;
            ReferenceCount = 1;
        }

        #endregion
    }
}

by adding a new field to save the next component, allowing us to have a linked list of component for an entity. They would still be in the same array and it should be possible to tweak the ComponentPool<T>.Sort method to put components next to one other, keeping the benefit of reducing cache miss when enumerating the components of an entity.
Some design to keep things simple:

  • having one or multiple components of the same types does not change anything composition wise, it is still viewed as the entity being composed of this particular component type, you can't disable a specific component value, you can only disable the full component type like before.
  • new api on entity: Add<T>(in T value) this would add the component as a linked list node (or just call Set if it is the first)
  • from the query point of view, only the first component value would considered for Map and MultiMap (and the futur SortedSet)
  • new api on entity: Remove<T>(int index) not sure about this one
  • new api on entity: GetAll<T>() to return an enumerable of ref T

The main problem with this scenario is when entities share component. Normally when you remove the component from one entity, the others sharing it keep the value. But because this would be a linked list, if we remove the first component for one entity, the others will still point to the first component of the linked list. The reference count would be a mess to keep updated to. I wondering if it would be better to forbid component sharing when you have multiple values.

Just typing it all to help thinking.

@onehundredfeet
Copy link
Author

I think this is an excellent solution.

As a start, I think choosing between sharing and multiples is a fine choice. If you use one, you can’t use the other.

@onehundredfeet
Copy link
Author

onehundredfeet commented Sep 6, 2021

I’ve started using Haxe in addition to c#. If you should ever feel like you are looking for a new project, may I suggest porting your ecs to haxe? Your ECS is the best IMO and haxe is a better language than c# for game dev.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants