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
Velocitycomponent 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
ComponentTypeenum.Create a new component collection class derived from
BaseComponentCollection<T>(orBaseTagCollectionfor tags) in the appropriate Components namespace as listed in the table below:Scope
Namespace
Common
Sovereign.EngineCore.ComponentsClient
Sovereign.ClientCore.ComponentsServer
Sovereign.ServerCore.ComponentsIf the base type
Tof the component collection has not been used for another component before, you may need to add new component operators to theComponentOperatorsclass in order to dervice fromBaseComponentCollection<T>.Update
IEntityBuilderwith 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 inClientEntityBuilderandServerEntityBuilderrespectively (with empty methods for out-of-scope builders). You will also need to updateClientEntityFactoryandServerEntityFactoryto 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
IEntityBuilderimplementations check forAbstractEntityBuilder’sisTemplatefield 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
EntityDefinitionfor the component value. Ensure that theKeyattribute is correctly set to ensure that the field is properly serialized and deserialized. Note that tag properties are not nullable in this struct.If necessary, update
EntityDefinitionValidatorwith any validation logic required for the new component.Update
EntityDefinitionProcessorto call the appropriateIEntityBuildermethods based on the value of the new property you added toEntityDefinition.Update
EntityDefinitionGeneratorto set the new property you added toEntityDefinitionas 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 toAddComponentRowfor the new component.If the component applies to player characters, update
PlayerDebugGui.Render()to add a new call toAddComponentRowfor the new component.
Adding Components to Template Entity Editor
Note
This section applies only to common-scoped components.
Block Template Editor
For common-scoped components which apply to blocks and are persisted, the Block Template Editor should be updated to allow editing of the component in block templates. To do this, make the following changes in the client:
In the
BlockTemplateEditorTabclass, add a private field of the same type as the component value. Name the fieldinput{ComponentName}where{ComponentName}is the name of the component. This field will store the value of the component while it is being edited.In the
BlockTemplateEditorTab.RenderComponentTable()method, determine if the component belongs to one of the existing categories (under aImGui.CollapsingHeadercall). If not, create a new category where the component editor will live along with a table to hold the fields belonging to the category. Follow the existing examples.In the category, add a row for editing the component. Follow the existing examples. Use the field you added in step 1 as the
refargument to the input field.In the
InsertNew()method, set the default value for the component in new templates if desired.In the
Save()method, update the corresponding field in theselectedDefinitionstruct.In the
Select(int)method, update the input field from theselectedDefinitionstruct.
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
EntityWithComponentsview to add columns for the new component.Update
IPersistenceProviderwith add/modify/remove queries for your new component.Update
SqlitePersistenceProviderto implement the methods you added toIPersistenceProvider. For simple (non-struct/class) types you can most likely useSimpleSqliteAddComponentQueryandSimpleSqliteModifyComponentQuery. For more complex types, check to see if a reusable query type already exists; otherwise you will need to create your own (seeVector3SqliteAddComponentQueryfor an example).Add a new state tracker derived from
BaseStateTrackerfor the component to theSovereign.Persistence.State.Trackersnamespace. Add the tracker to theStateTrackerInstallerandTrackerManagerclasses as well.Update
StateBufferto add a newStructBuffer<StateUpdate<T>>field to store state updates for the components, whereTis the component value type. Add a new update method toStateBufferto the component, then call this from your new state tracker. Also update theStateBuffer.Reset()method to callClear()on the newStructBufferfield. Finally, update theDoSynchronize(IPersistenceProvider)method to callSynchronizeComponentwith the newly addedStructBufferand add/modify/delete queries.Update
SqliteRetrieveEntityQueryandSqliteRetrieveRangeQueryto retrieve the new component when fetching entities from the database. For components which may be held by template entities, also updateSqliteRetrieveAllTemplatesQuery.Update
EntityProcessorto process the new component from theEntityWithComponentsby adding a newProcessX(IDataReader, IEntityBuilder)method (whereXis the component name) and calling it from theProcessSingleEntity(IDataReader)method.
Binding Components to Scripting Engine
Note
This section applies only to common- and server-scoped components.
For many components, it is useful to have a binding of the component collection to
the scripting engine so that scripts may read and write the component value. To do this,
simply add the [ScriptableComponents] attribute to the component collection class
with a single parameter corresponding to the name that will be assigned to the binding.
By standard Lua conventions, this name should be all lowercase. Refer to an existing
bound component collection (e.g. NameComponentCollection) for examples.