Entity Model
This document explains the reusable runtime entity model: entity type descriptors, generated property accessors, prototype entities, inner-entity ownership, entity events, and the property storage model that other runtime systems build on.
Use it when changing Source/Common/Entity.*, EntityProperties.*, EntityProtos.*, Properties.*, PropertiesSerializator.*, ProtoManager.*, metadata annotations, or code that persists/synchronizes entity state.
Ownership model
The engine owns the entity runtime and metadata/property mechanics. An embedding game project owns concrete prototype files, content IDs, scripts, and gameplay rules that use those mechanics.
Keep this document focused on reusable engine behavior. Put project-specific item/critter/map/location definitions and balancing notes in the embedding project’s docs.
Runtime entity types
Source/Common/Entity.h declares the core entity taxonomy through ///@ ExportEntity annotations:
Game— global engine/game state entity.Player— player-side entity/view contract.Location— world location entity with prototypes and time events.Map— map entity/view contract with prototypes and time events.Critter— character/NPC/player body entity with prototypes and time events.Item— item entity/view contract with prototypes, statics, abstract variants, and time events.
EntityTypeDesc stores metadata discovered/generated for each entity type:
- whether the type is exported or global;
- whether it has prototypes, statics, abstract entities, or holder entries;
- the
PropertyRegistratorused by that type; - exported methods and events;
- holder-entry sync/persistence policy.
For generated metadata and registration flow, see GeneratedApiAndMetadata.md.
Entity base class
Entity is the shared base for all runtime/prototype entities. It owns:
- a
Propertiesinstance; - optional event callback lists;
- optional time-event data;
- optional inner-entity holder entries;
- destroying/destroyed state flags;
- intrusive-style reference counting through
AddRef()/Release().
Important accessors and mutation paths include:
- identity/type:
GetName(),GetId(),IsGlobal(),GetTypeName(),GetTypeNamePlural(); - property access:
GetProperties(),GetPropertiesForEdit(),GetValueAsInt(),GetValueAsAny(),SetValueAsInt(),SetValueAsAny(); - raw data snapshots:
StoreData(),RestoreData(),SetValueFromData(); - lifecycle state:
IsDestroying(),IsDestroyed(),MarkAsDestroying(),MarkAsDestroyed(); - ownership graph:
AddInnerEntity(),RemoveInnerEntity(),ClearInnerEntities(); - event dispatch:
SubscribeEvent(),UnsubscribeEvent(),FireEvent().
Do not bypass Properties when changing entity state. Property callbacks, overlay data, sync flags, persistence flags, and script-visible accessors depend on the property layer seeing the mutation.
Generated property wrappers
FO_ENTITY_PROPERTY(type, Name) expands into a small typed accessor surface:
GetPropertyName()returns the registeredProperty*by generated registration index;GetName()reads the typed value fromProperties;SetName()writes throughProperties::SetValue();IsNonEmptyName()checks whether raw property data exists.
Source/Common/EntityProperties.h defines the generated property wrapper classes:
GamePropertiesPlayerPropertiesItemPropertiesCritterPropertiesMapPropertiesLocationProperties
EntityProperties itself contributes common persistent fields:
CustomHolderIdCustomHolderEntryExplicitlyPersistent
The generated wrapper classes are thin over Properties; the real storage, type information, sync/persistence flags, callbacks, and serialization decisions live in Property, Properties, and PropertyRegistrator.
Property runtime
Source/Common/Properties.h defines four central pieces:
PropertyRawData— temporary typed/raw buffer used by getters/setters and raw restore paths.Property— metadata for one property: name/component, base type, collection shape, sync flags, mutability, persistence, nullability, callbacks, and registration index.Properties— per-entity value storage, base/overlay relation, raw snapshot/restore, text import/export, typed get/set helpers, and hash resolution.PropertyRegistrator— per-entity-type registry that creates properties from metadata tokens and tracks lookup, groups, components, data layout, and public/protected/private data spaces.
Property flags are load-bearing:
Common,ServerOnly,ClientOnlyroute side visibility.Synced,OwnerSync,PublicSync,NoSyncroute network replication behavior.ModifiableByClientandModifiableByAnyClientgate client-originated changes.Virtual,Mutable,Persistent,Historical,Nullable, andTemporaryinfluence storage, callbacks, persistence, and script contracts.
When changing property metadata, update runtime docs and script/nullability docs together if the change affects script-visible signatures. See Nullability.md.
Base properties and overlays
A Properties instance can have base properties. This is used heavily by prototype-derived runtime entities:
- base data provides inherited/default values;
- overlay entries store values that differ from the base or need explicit local data;
CompareData()can ignore temporary properties when comparing snapshots;StoreData()can include or exclude protected data depending on sync/persistence needs;RemoveSyncedOverlayEntries()and related overlay helpers keep replicated state compact.
This means a runtime entity is not simply a flat map from property name to value. When debugging, inspect whether the value is coming from base properties, own POD/complex storage, or overlay data.
Prototypes
Source/Common/EntityProtos.h defines prototype entities:
ProtoEntity— base entity withGetProtoId()andCollectionName.EntityWithProto— mix-in for runtime entities that hold a reference to aProtoEntity.ProtoItem,ProtoCritter,ProtoMap,ProtoLocation— typed prototype entities with their generated property wrappers.ProtoCustomEntity— custom prototype entity path.
Source/Common/ProtoManager.* owns prototype lookup and loading:
GetProtoItem(),GetProtoCritter(),GetProtoMap(),GetProtoLocation();- generic
GetProtoEntity(type, pid)andGetProtoEntities(type); AddProto()for adding constructed prototypes;LoadFromResources()for loading baked/resource-backed prototype data.
Prototype loading is adjacent to resource baking. For baker-side proto handling, see BakingPipeline.md.
Inner entities and holders
Entities can hold other entities under named entries. Holder metadata lives in EntityTypeDesc::HolderEntryDesc:
TargetType— what entity type the entry can hold;Sync—NoSync,OwnerSync, orPublicSync;Persistent— whether holder membership participates in persistence.
The common persistent fields CustomHolderId and CustomHolderEntry let custom entities record holder relationships. EntityManagerApi provides custom-entity creation, lookup, and destruction hooks:
CreateCustomInnerEntity()CreateCustomEntity()GetCustomEntity()DestroyEntity()
When changing holder behavior, inspect server/client entity managers and persistence paths in addition to Entity.*.
Events and time events
FO_ENTITY_EVENT(Name, Args...) creates an EntityEventWrapper member. Event callbacks are priority-ordered and return Entity::EventResult:
ContinueChainStopChain
EntityEventWrapper::Fire() builds native call data differently for global and non-global entities: non-global events inject the entity as the first argument.
Entity::TimeEventData stores scheduled script callbacks, fire time, repeat duration, and script data. Entities that support time events are declared with the HasTimeEvents metadata flag in the ExportEntity annotations.
Serialization relationships
Entity state is serialized through property data, not by hand-copying entity fields:
- raw binary property snapshots:
Entity::StoreData()/RestoreData()andProperties::StoreData()/RestoreData(); - full property data:
Properties::StoreAllData()/RestoreAllData(); - text/document conversion:
Properties::SaveToText(),ApplyFromText(), andPropertiesSerializator.*.
Persistence backends store AnyData::Document records. For database commit/recovery details, see Persistence.md.
Tests to inspect
Relevant tests include:
Source/Tests/Test_EntityLifecycle.cppSource/Tests/Test_EntityProtos.cppSource/Tests/Test_LocationAndEntityMgmt.cppSource/Tests/Test_ScriptEntityOps.cpp- property/metadata tests such as
Test_Properties.cppandTest_EngineMetadata.cppwhen available in the checkout.
Change routing
- Entity base, events, holders, time-event storage:
Source/Common/Entity.*. - Generated property wrapper classes:
Source/Common/EntityProperties.*. - Prototype entity classes:
Source/Common/EntityProtos.*. - Prototype lookup/loading:
Source/Common/ProtoManager.*. - Property storage and flags:
Source/Common/Properties.*. - Property document/text conversion:
Source/Common/PropertiesSerializator.*. - Generated metadata and registration: GeneratedApiAndMetadata.md.
- Persistence: Persistence.md.
- Network replication and command buffers: Networking.md.
Validation checklist
- Build the smallest target that compiles generated entity/property code.
- Run entity lifecycle/prototype tests relevant to the changed type.
- Run property/metadata tests when property flags, registration, or serialization changes.
- If a property is synced, validate client/server replication paths and update Networking.md if behavior changes.
- If a property is persistent, validate database save/load paths and update Persistence.md if behavior changes.
- If a property or method is script-visible, validate generated script API and update Nullability.md where applicable.