Entity Rewrite Possibilities
#1
Entity Inheritance Structure

Forward Forward note

It seems like the post editor was being really weird, I have no clue why. Please excuse some blank bullet points, those are just bugs and there isn't meant to be anything there.

Forward note

This is list of plans proposed by me (Lazauya) on how to replace the current entity scheme. I have been informed that the current enity scheme is too rigid, and that "modules" should replace much of the logic. I tried picking out the modules that would apply to any entity, but not necessarily every entity. This means that for much of the entities, the module functions will be very sparse or non-existent. While I think the modules I outlined are already pretty exhaustive, I would like to see discussion on what should change/go/be added.

The issues that definitely still need discussion and clarification:
  • Minecarts - where should the logic for minecarts staying on rails lie? i.e., which callback. Is a new callback required?
     
    • current thoughts: split it up along, block intersection, placement, and pathfinding
       
  • Boats - where should the logic for boats staying on water/sinking lie? i.e., which callback. Is a new callback required?
     
    • current thoughts: split it up along, block intersection, placement, and pathfinding
       
  • NBT data - how should NBT data be stored in memory?
     
    • current thoughts: see plans
       

All of these plans differ on how/where the NBT data for the entity is stored. I chose to focus on that, as it seems this will either make Cuberite very efficient or end up in a huge disaster if done wrong. The current system is very memory efficient, but it is also apparently very inflexible. Reaching the current level of memory efficiency/speed probably won't be possible with completely flat class structures, but hopefully one of these plans will provide inspiration for the system that will come close to the current level while being flexible. Again, none of these plans are likely to be the final entity handling system, and discussion is required moving forward.

Plan 1

Overview

There is 1 class, cEntity.
The entity has pluggable functions dictating behavior, and contains fields for all potential NBT data attached to this entity.

Class structure
  • cEntity : cEntity
    • damage = function | function called when damage is applied to this entity
    • use = function | function called when the player tries using the entity; e.g. boats, villagers
    • entityIntersection = function | function that dictates behavior when this entity intersects with another entity
    • blockIntersection = function | function that dictates behavior when this entity intersects with a block
    • burning = function | function dictates behavior of entity when it is caught on fire
    • pathfinding = function | function that dictates if/how this entity seeks out new positions
    • placement = function | function that dictates how this entities coordinates exist; e.g. minecarts on rails? paintings, leads, etc.
    • gravity = function | function that dictates if/how gravity affects this entity
    • statusEffect = function | function that dictates how status effects affect this entity
    • Entity data = multi-field | NBT data of any kind of entity; Just include fields for all possible tags, even those that wouldn't be used

Pros:
  • Most extensible?
  • Fastest?

Cons:
  • Memory intensive
Plan 2
Overview

There is 1 parent class and 6 children.
The parent class has the same function fields as Plan 1.
The parent class houses NBT data that is common to all entities
Each child class houses the fields for the NBT data associated with any entity of the child type; e.g. cProjectile has fields specific to arrows, fields specific to 

Class structure
  • cEntity : cEntity
    • damage = function | function called when damage is applied to this entity
    • use = function | function called when the player tries using the entity; e.g. boats, villagers
    • entityIntersection = function | function that dictates behavior when this entity intersects with another entity
    • blockIntersection = function | function that dictates behavior when this entity intersects with a block
    • burning = function | function dictates behavior of entity when it is caught on fire
    • pathfinding = function | function that dictates if/how this entity seeks out new positions
    • placement = function | function that dictates how this entities coordinates exist; e.g. minecarts on rails? paintings, leads, etc.
    • gravity = function | function that dictates if/how gravity affects this entity
    • statusEffect = function | function that dictates how status effects affect this entity
    • Entity data = multi-field | NBT data shared by all entities; only include fields that are shared by all entities
  • cProjectile : cEntity
    • Projectile specific data = multi-field | the specific NBT data of any projectile
  • cMob : cEntity
    • Mob specific data = multi-field | the specific NBT data of any mob
  • cVehicle : cEntity
    • Vehicle specific data = multi-field | the specific NBT data of any vehicle
  • cBlockTileEntity : cEntity (tnt, falling block)
    • Dynamic tile specific data = multi-field | the specific NBT data of any dynamic tile
  • cItemEntity : cEntity (xp, item entities)
    • Item specific data = multi-field | the specific NBT data of any item entity
  • cMiscEntity : cEntity (leash knot, end crystal, painting, etc.)
    • Misc specific data = multi-field | the specific NBT data of any misc

Pros:
  • Less memory intensive than 2
  • Fast

Cons:
  • Memory intensive?
Plan 3

Overview

There is 1 class, cEntity.
The entity has pluggable function fields dictating behavior, and contains a dictionary of dictionaries to store NBT data.

Class structure
  • cEntity : cEntity
    • damage = function | function called when damage is applied to this entity
    • use = function | function called when the player tries using the entity; e.g. boats, villagers
    • entityIntersection = function | function that dictates behavior when this entity intersects with another entity
    • blockIntersection = function | function that dictates behavior when this entity intersects with a block
    • burning = function | function dictates behavior of entity when it is caught on fire
    • pathfinding = function | function that dictates if/how this entity seeks out new positions
    • placement = function | function that dictates how this entities coordinates exist; e.g. minecarts on rails? paintings, leads, etc.
    • gravity = function | function that dictates if/how gravity affects this entity
    • statusEffect = function | function that dictates how status effects affect this entity
    • Entity data = dictionary | a hash map that stores NBT data

Pros:
  • Very memory efficient
  • Easy to implement

Cons:
  • Slow
Plan 4

Overview

There is 1 class, cEntity.
The entity has pluggable function fields dictating behavior, and contains a custom NBT tree structure to store NBT data. This would basically be structs within structs; each struct represents a possible tag, and each struct contains the possible fields for those tags.

Class structure
  • cEntity : cEntity
    • damage = function | function called when damage is applied to this entity
    • use = function | function called when the player tries using the entity; e.g. boats, villagers
    • entityIntersection = function | function that dictates behavior when this entity intersects with another entity
    • blockIntersection = function | function that dictates behavior when this entity intersects with a block
    • burning = function | function dictates behavior of entity when it is caught on fire
    • pathfinding = function | function that dictates if/how this entity seeks out new positions
    • placement = function | function that dictates how this entities coordinates exist; e.g. minecarts on rails? paintings, leads, etc.
    • gravity = function | function that dictates if/how gravity affects this entity
    • statusEffect = function | function that dictates how status effects affect this entity
    • Entity data = dictionary | a hash map that stores NBT data

Pros:
  • Very memory efficient
  • Faster than 3

Cons:
  • Slower than 1/2
  • Harder to implement
Reply
Thanks given by:
#2
How about this:

Single class, cEntity. Contains fields for plugging "subsystems" - a subsystem for pathfinding, a subsystem for physics, a subsystem for handling interactions, a subsystem dictating the graphical look of the entity, a subsystem for handling sounds, etc. Basically what your interaction functions do, I'd put into a subsystem (an interface, a class with virtual functions, descendants implement actual behavior). Each of these subsystems can also load and save specific NBT data; only the subsystem knows what NBT data it can load or save.
Each cEntity also has a dictionary / hashmap for storing custom values.
Reply
Thanks given by:
#3
(04-06-2020, 07:27 PM)xoft Wrote: How about this:

Single class, cEntity. Contains fields for plugging "subsystems" - a subsystem for pathfinding, a subsystem for physics, a subsystem for handling interactions, a subsystem dictating the graphical look of the entity, a subsystem for handling sounds, etc. Basically what your interaction functions do, I'd put into a subsystem (an interface, a class with virtual functions, descendants implement actual behavior). Each of these subsystems can also load and save specific NBT data; only the subsystem knows what NBT data it can load or save.
Each cEntity also has a dictionary / hashmap for storing custom values.
So by "load and save" NBT data, you mean that the data an entity needs to use is only loaded into memory when the subsystem needs it? Or at least, it will be fetched from the chunk region file every time (which could or could not already be loaded). That's not what happens currently as far as I can tell by the code. This seems like it would be very inefficient. I was going to bring up the point of multiple subsystems needing the same piece of data, which this would seem to fix, but if the load-save method isn't very efficient, I think that having each component deal with its own NBT data just isn't a very simple solution. There it seems like there needs to be faster-to-access source of data than just the chunks.

Also, what do you mean by "custom values"? Do you mean non-default NBT data?

I think this is a generally a good idea; I can definitely see how things like path finding should have some kind of internal state that just can't be easily encapsulated in a function. So I will revise the proposal to:
  • cSubSys : none
    • operator()
  • cEntity : none
    • use = cSubSys | subsystem called when the player tries using the entity; e.g. boats, villagers
    • entityIntersection = cSubSys | subsystem that dictates behavior when this entity intersects with another entity
    • blockIntersection = cSubSys | subsystem that dictates behavior when this entity intersects with a block
    • burning = cSubSys | subsystem dictates behavior of entity when it is caught on fire
    • pathfinding = cSubSys | subsystem that dictates if/how this entity seeks out new positions
    • placement = cSubSys | subsystem that dictates how this entities coordinates exist; e.g. minecarts on rails? paintings, leads, etc.
    • gravity = cSubSys | subsystem that dictates if/how gravity affects this entity
    • statusEffect = cSubSys | subsystem that dictates how status effects affect this entity
    • Entity data = ??? | unclear
I think the NBT is going to end up being the largest part of this system. The current system is very tidy and fast with how it handles data in memory, so approaching that would be nice.
Reply
Thanks given by:
#4
I'm going to be using "component", "module", and "subsystem" interchangeably from now on.

The question of where the data should be stored in the class is still very important.

Should any data be stored inside the entity class, or are we just going to store everything in chunks and let modules access the chunks? I don't believe it's wise to have individual modules responsible for their own NBT data. It seems like a lot of the modules would need to access the same data, like position/rotation. Perhaps we can just group modules that need direct access to that information, and call those modules when we need to modify that NBT data, like the module for burning calling the module for damage. As I'm writing this, it seems like a promising solution but more research is required. The downside to this would be that it may be slower than if we let the modules access the data directly, but I don't see a particularly big performance hit. Besides, we already abstract away access right now as far as I can tell, this would just be adding an extra layer or two of function calls at most.

On a different note, here's an alternate idea for storing the NBT data. We have a root struct, say cNBT (or sNBT if you use those naming conventions). We then can create a cEntityNBT, which all further entity NBT classes should inherit from. We also should extend the cNBT class with the necessary data for each type of entity; we have cProjectileNBT, cMobNBT, cBreedableMobNBT, etc. Within each type, we further subdivide it into cArrowNBT, cVillagerNBT, etc, for the entity-specific NBT data. cArrowNBT can inherit from cEntity and cProjectileNBT, while cVillagerNBT can inherit from cEntityNBT, cMobNBT, cBreedableMobNBT. The obvious downside to this seems to be that this may not be as generic as was initially hoped. The plus side is that this is still a super generic way to do things, and it promises to be very very fast; each access to the data would only need to be a single dereference. And of course, you could abstract it away to make it thread-safe.

Feedback is welcomed.
Reply
Thanks given by:
#5
I guess I wasn't too clear. I think the entity should have its data stored either in the entity class itself, or in the individual subsystem classes (whatever makes more sense for the particular kind of data), but it is each subsystem's responsibility to know how to save and load that data from / to NBT, *when serializing the entity*. So when the entity gets loaded, its subsystems load their internal data from the entity's NBT, and when it's saved, the subsystems save the data. They store the data within themselves, as they see fit. If there is need to share the data between multiple subsystems, that needs individual consideration. In a general case, such a sharing means the subsystems are not designed properly.

From this, you can see that a subsystem cannot be a class with a single operator(), but instead many virtual methods. For example:
/** The interface of the high-level AI subclasses */
class cEntity::IBehavior
{
  /** Called when aAttacker has attacked this entity. */
  virtual void OnAttacked(const cEntity & aAttacker) = 0;

  /** Called when sight of the tracked target was lost (such as when targeting a player, or hunting) */
  virtual void OnLostSightOfTarget(const cEntity & aTarget) = 0;

  /** Called when a new entity comes into the sight range.
  This doesn't mean the entity can be seen, it is only close enough to actually check the line-of-sight, if desired. */
  virtual void OnEntityInSightRange(const cEntity & aEntity) = 0;

  // ...

  /** Saves the behavior's data to the NBT */
  virtual void SaveToNBT(...) = 0;

  /** Loads the behavior's data to the NTB. */
  virtual void LoadFromNBT(...) = 0;
}
Reply
Thanks given by:




Users browsing this thread: 1 Guest(s)