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

[WIP] Sectors #2975

Closed
wants to merge 26 commits into from
Closed

[WIP] Sectors #2975

wants to merge 26 commits into from

Conversation

Vizaxo
Copy link
Contributor

@Vizaxo Vizaxo commented Jun 14, 2017

Sectors WIP PR

The first stage of the implementation of sectors as part of my GSOC project, for mentor review.

Vizaxo added 6 commits June 14, 2017 15:47
The EntityManager stores multiple entities both in the global scope,
and the local scope.

The method createSectorEntity is used to create an entity in the
sector scope.
Also makes EntityCache an interface, to seperate the interface from
the implementation.
@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

@skaldarnar skaldarnar requested a review from msteiger June 14, 2017 20:25
Copy link
Member

@skaldarnar skaldarnar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation adds a sector cache for entities (in addition to the global cache). This (hard-coded) separation implies that we duplicate the API to create and manage entities in different caches, i.e., global (backwards compatible methods) and sectors (indicated by method name). This makes some assumptions:

  • there is only one other entity cache, and entities live either in one or the other;
  • even if other scopes were implemented, they would still be statically known. This disallows for dynamic usage of different caches (e.g., sectors for different, unrelated context).

I'm wondering whether we can parameterize the existing methods with some kind of sector identifier (SectorRef) - method overloading should allow to keep it backwards compatible? Alternatively, the sector ref parameter could be an Optional<SectorRef> where Option.empty() defines that the entity is created in global scope...

import org.terasology.math.geom.Quat4f;
import org.terasology.math.geom.Vector3f;

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do a final iteration over the Javadoc in the end. Most of it is pretty self-documenting. However, try to avoid "empty" Javadoc where just the parameter names and such are auto-generated. A general summary of what the EntityCache actually is might be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I will. I didn't want to go documenting lots of stuff now when it's still rapidly changing.


private static final Logger logger = LoggerFactory.getLogger(PojoEntityCache.class);

private Map<Long, BaseEntityRef> entityStore = new MapMaker().weakValues().concurrencyLevel(4).initialCapacity(1000).makeMap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mapping is specific to each cache instance, right? Can entities be in several caches at once, should this be allowed?
I'm not sure whether we need a way to transfer entities from one cache into another. Do you see a use case where this might be necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't considered entities being in multiple caches. It might be useful (e.g. for entities near a sector boundary), but it could cause lots of problems with systems trying to update the same entity at the same time in two different caches (especially if we're thinking about using multi-threading to handle sectors in the future). So I think it's best to have each entity in a single cache.

I think that'll definitely be necessary when we have multiple sectors, so sectors can be moved or split (and entities can change sector if they move around, or find themselves interacting with lots of things outside of their sector).


@Override
public void clear() {
//Todo: should also clear out ids from the EntityManager
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that each entity is only contained in a single cache, is that correct?

@Override
public EntityRef create(String prefabName, Vector3f position) {
if (prefabName != null && !prefabName.isEmpty()) {
Prefab prefab = entityManager.getPrefabManager().getPrefab(prefabName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above in line 103 you check for the prefab to not being null, but here the check is missing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The create() methods below also assume that prefab is not null. Is the assumption (somewhat) safe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I'll add the checks where they're needed.

Some of them are mostly safe because the associated create(String prefabName) does the null check, but I'll add the checks there as well, in case it is called with a null prefab from somewhere else (currently nowhere within the engine calls the create() methods with a Prefab directly, but it doesn't hurt to be safe).

return;
}
EntityRef ref = createEntityRef(entityId);
if (entityManager.getEventSystem() != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduce local field for entityManager.getEventSystem()

long entityId = ref.getId();
entityStore.remove(entityId);
entityManager.remove(entityId);
if (ref instanceof PojoEntityRef) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get around this instanceof somehow? Should it be generally possible to invalidate EntityRef?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why you shouldn't be able to invalidate any EntityRef, even if that doesn't do anything for some implementations of it. I've added that in 15ca85c; I think that getting rid of the instanceof makes it a lot cleaner.

@Override
public EntityRef createEntityWithId(long id, Iterable<Component> components) {
if (!entityManager.registerId(id)) {
return EntityRef.NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "silently" fails creating the entity with the specific id. Is there a chance for the consumer to know that the id was already taken? Does this happen often? Should we log a warning in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An error gets logged by registerId. I'm not sure if it happens often or if it's a problem; this is only currently called for storage/serialization tasks, which already know the ID they're trying to create for from the entity they already had (so presumably they know that it's not invalid).

Most uses would only use the normal create methods, which will guarantee a valid ID. Perhaps a note should be added about this to the documentation?

}

@Override
public EntityBuilder newBuilder(String prefabName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the cost of using a builder? Do reduce code duplication, does it make sense to implement create(prefab) in terms of the builder?

public EntityRef create(String prefabName) {
  newBuilder(prefabName).build()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The builder class says that the whole entity can be setup before creation without any events being sent in-between. It also seems like a good way of creating lots of the same entity, so might be useful for spawning lots of cities, for example.

Implementing some of the create() methods in terms of the builder should reduce code duplication, especially the code to extract components from a prefab.

return builder;
}

public Map<Long, BaseEntityRef> getEntityStore() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we care about mutability of the map?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't considered that; I think an unmodifiable map would be more sensible, and then have a putEntity method so that the EntityManager can put entities into the map (from createEntityRef). But perhaps that should be changed too: maybe the EntityManager should just call the cache's createEntityRef? I think restricting external changes to the internal map would be an improvement; I'll look into the best way of doing it.

}

@Override
//Todo: Depreciated, maybe remove? Not many uses
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should be used instead? This just creates a new EnityRef for an entity with the same set of components. Can we create a new builder from a give entity instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EntityManager says that it's depreciated in favour of EntityRef.copy(). Creating a builder from an existing entity is an interesting idea, especially if entities need to be duplicated multiple times (e.g. maybe for duplicating inventory items).

@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

Vizaxo added 3 commits June 16, 2017 12:43
Also add the putEntity method, to allow the internal store to be
modified by the entity manager.
This introduces a mapping between entityId and cache, so the
EnityManager can properly store/retrieve components from the correct
cache.
@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

Vizaxo added 7 commits June 19, 2017 21:13
Add addPrefab methods to EntityBuilder so that calling functions don't
have to expand the prefabs.
Add the getExistingEntity method to EntityCache and EntityManager,
which will only return an entity if it already exists in the cache.
This allows searching of individual caches for whether an entity has a
component, so the EntityManager doesn't have to access the internal
store of the cache.
This removes a lot of code duplication, and makes the methods easier
to read.
@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

Copy link
Member

@msteiger msteiger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it very hard to review this, due to the large amounts of moved code. Does the actual implementation of the entity manager change? I would try to use other components as fields rather than creating an inheritance tree. Also see https://en.wikipedia.org/wiki/Composition_over_inheritance for details.

Sorry, I'm not very familiar with the GSOC proposal. What exactly are you aiming for in this PR?

There is a temp. storage that buffers entities that are created (in parallel) during world gen. and fed to the the EntityManager as soon as it world gen. is complete. Is it something similar?

* @return
*/
EntityRef create(Prefab prefab, Vector3f position, Quat4f rotation);
default EntityRef createSectorEntity() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that null should not be returned, if possible. What about an empty sector entity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you're right. EntityRef.NULL is the preferred way of returning a null entity, as it behaves sensibly by avoiding NullPointerExceptions.

I'm actually not sure why I implemented this as a default method; the only EntityManager in the engine is the Pojo one, for which I've implemented the method anyway (I expect my thinking was that I didn't want to have to go and implement it in multiple places while the interface is still changing). It's probably best to just remove the default implementation and leave it abstract.

@Vizaxo
Copy link
Contributor Author

Vizaxo commented Jun 28, 2017

@msteiger Thanks for the feedback.

Does the actual implementation of the entity manager change?

The implementation of most of the methods is moved into the EntityCache, and I have been refactoring those methods to reduce duplicate code, but their functionality remains the same. The EntityManager now calls the methods on the global cache for lots of its methods by default, so the interface remains the same if you are just dealing with entities in the global scope.

Sorry, I'm not very familiar with the GSOC proposal. What exactly are you aiming for in this PR?

This issue might help explain it, and I've just posted a blog post (skip to "What is my project?" section) which should outline what I'm aiming for. This first stage will just be to get a basic sector/cache system working; doing things like multi-threading and multi-world will have to come later (after things like re-integrating Gestalt-EntitySystem have been worked out (as mentioned in that issue thread)).

If that's still unclear, please let me know.

I would try to use other components as fields rather than creating an inheritance tree. Also see https://en.wikipedia.org/wiki/Composition_over_inheritance for details.

The caches are implemented as fields; for example, the PojoEntityManager has a global cache, and a PojoSectorManager. The PojoSectorManager in turn has caches for each of its sectors (just one, at the moment).

But I did create an inheritance tree by making the EntityManager and SectorManager implement EntityCache. This is because the managers are caches in their own right, and can be used in place of individual caches when it's appropriate (so the SectorManager is the cache for all sector entities, and only the SectorManager needs to know about how each cache is represented internally).

I initially represented this with pure composition, but the interfaces had to be duplicated because many of the same operations have to be performed on the managers and caches (e.g. creating an entity in that cache).

I think this way of working works in this scenario, because the caches are separate fields, but the inheritance means the interfaces don't have to be duplicated. There might be a possibility for turning the inheritance of EntityManager -> LowLevelEntityManager -> EngineEntityManager (and the equivalent for caches) into a composition relationship, but I can't immediately see much advantage to that, as inheritance seems to fit their relationship well already.

Saying that, i tend to think about this kind of stuff in terms of inheritance, but can certainly see the advantage of favouring composition instead. I might be missing the point here or thinking about it in the wrong way, so I'd like to keep considering alternate ways of doing things if they lead to better code, even if it's not how I naturally think about these kind of problems. If you think this can be better represented without the inheritance in the interfaces, I'm all ears.

@GooeyHub
Copy link
Member

Uh oh, something went wrong with the build. Need to check on that

@GooeyHub
Copy link
Member

Hooray Jenkins reported success with all tests good!

@Cervator
Copy link
Member

Closing as superseded by #3009 which has been merged into the v2 branch :-)

@Cervator Cervator closed this Jul 21, 2017
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

Successfully merging this pull request may close these issues.

5 participants