-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[WIP] Sectors #2975
Conversation
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.
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
There was a problem hiding this 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; | ||
|
||
/** |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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()
}
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
Hooray Jenkins reported success with all tests good! |
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.
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
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.
Hooray Jenkins reported success with all tests good! |
Also updates the implementation of said method to be a more concise stream-based operation.
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
There was a problem hiding this 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() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@msteiger Thanks for the feedback.
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.
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.
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. |
Uh oh, something went wrong with the build. Need to check on that |
Hooray Jenkins reported success with all tests good! |
Closing as superseded by #3009 which has been merged into the v2 branch :-) |
Sectors WIP PR
The first stage of the implementation of sectors as part of my GSOC project, for mentor review.