Data-Driven Service Flow

This page documents the recommended data-driven architecture used across Luna Systems:

  • ServiceLocator for runtime resolution
  • Data package catalogs (ICatalog, IAssetCatalog<T>)
  • CatalogKey fields for serializable references
  • IDataSerializer/Newtonsoft for JSON serialization

The concrete example below uses Inventory sample assets/code from Packages/com.cupkekgames.luna/Samples~/Library, but the same pattern applies to other systems.

Big Picture

1) Authoring Model (Serializable Data First)

Inventory is the example model:

  • InventoryItemDefinitionSO : DataSO<InventoryItemDefinition>
  • InventoryItemDefinition : IData
  • InventoryItemDefinition.IconKey : CatalogKey
  • InventoryItem.Key : CatalogKey
  • InventoryItemReference.ItemKey : CatalogKey

This keeps references stable for JSON/saves and avoids direct hard references inside runtime data models.

1.5) Static feature definitions vs runtime feature state

IFeature on data models (e.g. InventoryItemDefinition.Features) is authored, static behavior/config: equip rules, tooltip layout, cooldown duration, etc.

Mutable data that belongs to a specific runtime instance (last use time, durability remaining, charges left) must not live on shared definitions. Use IFeatureStateData from the Data package and store a [SerializeReference] List<IFeatureStateData> on the runtime entity (for example InventoryItem).

  • Each state type implements IFeatureStateData.CloneState() for deep copy when cloning the owner (e.g. stack split).
  • Per-field serialization is decided on the state class: [SerializeField] for persisted values, [NonSerialized] for session-only fields (e.g. Time.time-based cooldown timestamps that should reset on load).
  • Features read/write state through the runtime context they already receive (e.g. ItemTooltipContext.Item.GetState<T>()), using definition fields for config and state fields for what changes at runtime.

Example (inventory consumable): ConsumableFeature holds CooldownDuration on the definition; ConsumableCooldownState on the stack holds LastUsedTime.

Example (tier override): ItemTierFeature.Tier is the authored default; ItemTierRuntimeState.TierOverride on the stack overrides tooltip and slot USS when set (empty override falls back to the definition).

The same split applies to other systems (abilities, quests, etc.)—Inventory is not a special case in the architecture.

When serializing polymorphic lists with Newtonsoft and TypeNameHandling, register concrete IFeatureStateData types in your SerializationTypeProviderSO known-types list.

2) Catalog IDs (Example Setup)

Sample constants:

  • InventoryConstants.ItemsCatalogId = "Items"
  • InventoryConstants.IconsCatalogId = "ItemIcon"

AssetCatalog<T> registers itself to ServiceLocator for:

  • ICatalog (key listing)
  • IAssetCatalog (untyped value lookup)
  • IAssetCatalog<T> (typed value lookup)

All are keyed by catalog id and use append: true, so multiple catalog assets can contribute to one id.

Concrete sample assets

From Luna Demo Service Registry Items.asset:

  • Item Provider Equipments.asset (_catalogId: Items)
  • Item Provider Potions.asset (_catalogId: Items)
  • ItemSpriteDatabase.asset (_catalogId: ItemIcon)
  • ItemTypeKeyProvider.asset (_catalogId: ItemType)
  • EquipmentTypeKeyProvider.asset (_catalogId: EquipmentType)
  • ItemTierKeyProvider.asset (_catalogId: ItemTier)
  • CupkekGames Data Serializer Registrar.asset (registers IDataSerializer)

Result: definitions can be split across multiple catalogs while still resolving through one logical key space (Items).

3) Runtime Resolution Path (Example)

InventoryItemDatabaseSO is the bridge that gameplay/UI code uses through IInventoryItemDatabase:

  1. GetItemDefinition(CatalogKey) validates catalog id (defaults to Items if empty)
  2. Iterates ServiceLocator.GetAll<IAssetCatalog<InventoryItemDefinitionSO>>("Items")
  3. Returns the first matching InventoryItemDefinitionSO.Data
  4. For icons, resolves IAssetCatalog<Sprite> by IconKey.Catalog (fallback ItemIcon)

This is why both Items and ItemIcon catalogs must be registered before inventory UI/gameplay runs.

4) Serialization Path (DataSO + IDataSerializer)

DataSO<T> uses ServiceLocator.Get<IDataSerializer>() for:

  • ToJson()
  • LoadFromJson()

In samples, DataSerializerRegistrar registers NewtonsoftDataSerializer as IDataSerializer.

Important requirement

NewtonsoftDataSerializer also depends on ServiceLocator.Get<SerializationManager>(). So if you call DataSO JSON APIs at runtime, you must register a SerializationManager too (for example through SerializationManagerRegistrar in the Newtonsoft sample registry).

In short:

  • Required for DataSO JSON methods: IDataSerializer + SerializationManager
  • Required for catalog-backed runtime lookups: relevant catalogs registered under expected ids

5) Minimal Setup Checklist

To apply this pattern in your own system:

  1. Register one or more catalogs for your domain ids
  2. Register services that resolve/bridge those catalogs to runtime APIs
  3. Register IDataSerializer (for example via DataSerializerRegistrar)
  4. Register SerializationManager if using Newtonsoft JSON calls (ToJson, LoadFromJson, save/load)
  5. Verify with Service Locator Debug window that all services and keys are present

6) Common Failure Modes

  • No IAssetCatalog<...> registered with key '...'
    • Missing catalog registration.
  • IAssetCatalog<...>('...') not found in ServiceLocator
    • Wrong/missing catalog id registration.
  • SerializationManager must be initialized before use
    • IDataSerializer exists, but SerializationManager was not registered/initialized.

Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL