CalmUI
Store

C++ Store

Write game values from C++ and respond to committed changes.

Use calmui::CalmUiStore from Store.h after loading your package. The example below assumes store is the loaded Store and field is a valid boolean field ID from your generated schema. Check writes before committing:

Object members are checked against their declared schema, including nested objects inside keyed array rows. An object container alone does not make an undeclared member or a wrong nested scalar type valid.

if (store.BeginUpdate()) {
  if (!store.SetBool(field, true)) {
    store.Rollback();
  } else {
    const bool saved = store.Commit();
    // If saved is false, inspect store.LastError().
  }
}

Also: SetInt, SetFloat, SetString, SetObjectField, ReplaceArray, keyed insert/update/remove/move, and Read. Whole objects use SetObject. Typed reads are ReadBool, ReadInt, ReadFloat, ReadString, ReadObject, and ReadArray.

Use SubscribeScoped to watch selected fields and automatically unsubscribe when its owner is destroyed, SubscribeAllScoped for every committed batch, or Subscribe, SubscribeAll, UpdateDependencies, and Unsubscribe for explicit lifetime management. Each patch includes its kind, field/member IDs, key, old/new operation data, and old/new index. Equal values do not produce a patch: republishing an unchanged array or object every tick costs one comparison and notifies nobody.

Writes are cheap enough to publish whole rows. Member validation uses tables the Store builds once from the schema, field lookups are binary searches, and a patch's previous value is moved out of the committed state at Commit instead of being copied. A 64-row keyed ReplaceArray with 50 members per row against a 1,550-field schema takes about 0.5 ms (Release, CalmUiStoreTests).

UCalmUiLocalPlayerSubsystem::GetLocalStore() exposes the player Store to native game code. UCalmUiEngineSubsystem::GetGlobalStore() exposes the process-wide Store. Both must be mutated on the Unreal Game Thread; code running elsewhere must queue its transaction with AsyncTask(ENamedThreads::GameThread, ...).

Reconfigure preserves compatible fields. Removing a field or changing its type, scope, member status, or array key is breaking and is rejected unless the new schema version is higher. A versioned migration keeps compatible values and drops only incompatible ones.

Explicit save/load code can use SaveSnapshot and RestoreSnapshot. Snapshots store sorted field IDs plus their typed values and carry the schema version and hash. Restore is transactional: incompatible values roll back the entire operation with LastError, while a newer version may omit fields removed by a versioned migration. Persist the resulting engine-neutral structure in the game's own SaveGame format; serialization policy remains game-owned.

Use the Unreal player Store

This example assumes a loaded package defining Player.name at player.name and action MainMenu.NewGame, with the matching generated header available to your module. Replace the generated names with your project's names. Include CalmUiLocalPlayerSubsystem.h and use a valid local Player Controller. The callback shows where to implement your game's new-game behavior:

#include <CalmUiSchema.generated.h>
#include <calmui/Actions.h>
#include <calmui/Store.h>

UCalmUiLocalPlayerSubsystem* ui =
    PlayerController->GetLocalPlayer()->GetSubsystem<UCalmUiLocalPlayerSubsystem>();
calmui::CalmUiStore* store = ui->GetLocalStore();

calmui::CalmUiStoreTransaction update(*store);
if (!store->SetString(CalmUiFields::Player_name, "Alex")) {
    return; // The transaction cancels its pending changes.
}
const bool saved = update.Commit();
if (!saved) return;

const calmui::ActionRegistrationId action = ui->GetActionDispatcher()->Register(
    CalmUiActions::MainMenu_NewGame,
    [](const calmui::Value&, const calmui::ActionContext& context) {
      // Add the game's new-game logic here. Returning true alone does not start a game.
      // context.LocalPlayer identifies the requesting player.
      return true;
    });

Keep the registration handle and unregister it during teardown. Global fields come from UCalmUiEngineSubsystem::GetGlobalStore(); do not write them through the per-player Store.

On this page