Adding Components
Adding components to Sovereign Engine is somewhat more involved as compared to other well-known ECS frameworks due to the distributed and persisted nature of Sovereign’s ECS. This guide covers the basic steps of adding a new component type and optionally persisting it in the database.
Adding A New Component Type
The following steps must be performed when adding a new component to Sovereign:
Determine whether the component is client-side only, server-side only, or common to all. Most components are common to all unless there is a specific reason to limit the component to one or the other.
Tip
Keep in mind that not all common and server-side components need to be persisted in the database. It is perfectly acceptable to have a component which is known to the server but only exists in memory. For example, the
Velocity
component is not persisted to the database. Therefore, do not consider the need for persistence when deciding the scope of a component.Add a new component type to the appropriate section in the
ComponentType
enum.Create a new component collection class derived from
BaseComponentCollection<T>
(orBaseTagCollection
for tags) in the appropriate Components namespace as listed in the table below:Scope
Namespace
Common
Sovereign.EngineCore.Components
Client
Sovereign.ClientCore.Components
Server
Sovereign.ServerCore.Components
If the base type
T
of the component collection has not been used for another component before, you may need to add new component operators to theComponentOperators
class in order to dervice fromBaseComponentCollection<T>
.Update
IEntityBuilder
with methods for adding and removing the new component from an entity. For common-scoped components, the new methods should be implemented inAbstractEntityBuilder
, while client-only methods and server-only methods should be implemented inClientEntityBuilder
andServerEntityBuilder
respectively (with empty methods for out-of-scope builders). You will also need to updateClientEntityFactory
andServerEntityFactory
to pass the component collection to the newly created builders as needed.Tip
Not all components are applicable to template entities. If the new component type is not applicable to template entities, ensure that the
IEntityBuilder
implementations check forAbstractEntityBuilder
’sisTemplate
field and avoid adding the component to template entities.
Replicating Components Over the Network
Note
This section applies only to common- and server-scoped components.
Many common-scoped components need to be replicated from server to client as part of entity synchronization. This may be done through the following steps:
Add a new nullable property to the end of
EntityDefinition
for the component value. Ensure that theKey
attribute is correctly set to ensure that the field is properly serialized and deserialized.If necessary, update
EntityDefinitionValidator
with any validation logic required for the new component.Update
EntityDefinitionProcessor
to call the appropriateIEntityBuilder
methods based on the value of the new property you added toEntityDefinition
.Updated
EntitySynchronizer
to set the new property you added toEntityDefinition
as needed.
Displaying Component Values in Entity Debugger
Note
This section applies only to common- and client-scoped components.
Common-scoped and client-scoped component values should be displayed in the client-side entity debugger windows. To do this, make the following changes in the client:
In the
EntityDebugGui.Render()
method, add a new call toAddComponentRow
for the new component.If the component applies to player characters, update
PlayerDebugGui.Render()
to add a new call toAddComponentRow
for the new component.
Persisting Components in Database
Note
This section applies only to common- and server-scoped components.
Persisting a component in the database requires several changes:
Update the SQL migration scripts with a table for the new component as well as any required indices. Also update the
EntityWithComponents
view to add columns for the new component.Update
IPersistenceProvider
with add/modify/remove queries for your new component.Update
SqlitePersistenceProvider
to implement the methods you added toIPersistenceProvider
. For simple (non-struct/class) types you can most likely useSimpleSqliteAddComponentQuery
andSimpleSqliteModifyComponentQuery
. For more complex types, check to see if a reusable query type already exists; otherwise you will need to create your own (seeVector3SqliteAddComponentQuery
for an example).Add a new state tracker derived from
BaseStateTracker
for the component to theSovereign.Persistence.State.Trackers
namespace. Add the tracker to theStateTrackerInstaller
andTrackerManager
classes as well.Update
StateBuffer
to add a newStructBuffer<StateUpdate<T>>
field to store state updates for the components, whereT
is the component value type. Add a new update method toStateBuffer
to the component, then call this from your new state tracker. Also update theStateBuffer.Reset()
method to callClear()
on the newStructBuffer
field. Finally, update theDoSynchronize(IPersistenceProvider)
method to callSynchronizeComponent
with the newly addedStructBuffer
and add/modify/delete queries.Update
SqliteRetrieveEntityQuery
andSqliteRetrieveRangeQuery
to retrieve the new component when fetching entities from the database.Update
EntityProcessor
to process the new component from theEntityWithComponents
by adding a newProcessX(IDataReader, IEntityBuilder)
method (whereX
is the component name) and calling it from theProcessSingleEntity(IDataReader)
method.