API Design
Public C++ handler contract in KDataForgeApi and the KDataForge registration surface used by third-party mods.
Third-party handler types derive against KDataForgeApi. Registering those handlers calls
UKDFSubsystem, which lives in the main KDataForge module, so a registering mod links both
modules. Other implementation details may change between versions.
Stability guarantees
Section titled “Stability guarantees”IKDFDataEditorHandler, UKDFHandlerBase, FKDFNode, EKDFStage, EKDFOp, the context structs,
and FKDFPatchRecord form the public handler contract. Additive changes are preferred. The main
module exposes the registration and integration methods described below.
The YAML schema is versioned through the documentation rather than C++ compatibility rules. Breaking schema changes belong in the changelog.
The KDataForgeApi contract
Section titled “The KDataForgeApi contract”Implement IKDFDataEditorHandler, or normally derive from UKDFHandlerBase, for one YAML root
type. The interface is C++ only because UKDFDataEditorHandler declares
CannotImplementInterfaceInBlueprint.
class IKDFDataEditorHandler{ virtual FName GetRootType() const = 0; virtual EKDFStage GetStage() const = 0; virtual int32 GetPriority() const; virtual bool ValidateDocument(const FKDFNode&, FKDFValidationContext&) = 0; virtual bool ApplyDocument(const FKDFNode&, FKDFApplyContext&) = 0; virtual bool ExportObject(UObject*, FKDFNode&, FKDFExportContext&); // optional virtual UClass* GetEditorPanelClass() const; // optional};There is no RevertDocument virtual in the current interface. The framework parser handles raw
YAML loading. ApplyDocument performs writes, ValidateDocument performs structural checks,
ExportObject supports editor export, and GetEditorPanelClass can provide a custom panel.
FKDFNode, the document model
Section titled “FKDFNode, the document model”Handlers receive an order-preserving map, sequence, and scalar tree rather than YAML text. Key
helpers include Find, FindShared, GetString, GetBool, GetInt, GetFloat, their TryGet*
variants, and the MakeMap, MakeSequence, and MakeScalar builders used by exporters.
Contexts
Section titled “Contexts”Contexts carry mGameInstance, mSourceFile, and a diagnostic sink with AddInfo, AddWarning,
and AddError. Diagnostics are non-fatal. FKDFApplyContext adds bDryRun, mPatchRecord, and
mAppliedOpCount.
Template providers and config values
Section titled “Template providers and config values”KDataForgeApi also exports the extension point for {prefix:body}
templates: IKDFTemplateProvider
(GetPrefix, GetPhase, Resolve), the request and result structs, and the thread-safe
FKDFTemplateProviderRegistry (Register, RegisterWithSpelling, Unregister, Find,
GetPrefixes, GetPrefixSpellings). The typed value
that providers return and that the Blueprint
Config API exchanges is FKDFConfigValue (with
EKDFConfigValueType and FKDFConfigKeyedValue); both live in KDFConfigTypes.h.
There is no bLiveReload flag in the handler API. KDataForge scans YAML during GameInstance
initialization and again when an authorized host runs /kdf reload. Reload restores captured
top-level property baselines before rebuilding and applying the handler pipeline. Frozen SML
registrations and irreversible world changes still require a restart.
Registration
Section titled “Registration”Register during your game instance module’s CONSTRUCTION phase. Explicit subsystem
initialization is required because SML can dispatch module lifecycle phases before custom
GameInstance subsystems have been created.
#include "Module/GameInstanceModuleManager.h"#include "Subsystems/KDFSubsystem.h"
UGameInstanceModuleManager* Modules = GetGameInstance()->GetSubsystem<UGameInstanceModuleManager>();if (Modules != nullptr){ Modules->EnsureSubsystemInitialized(UKDFSubsystem::StaticClass()); if (UKDFSubsystem* KDF = UKDFSubsystem::Get(GetGameInstance())) { KDF->RegisterHandler(NewObject<UMyHandler>(KDF)); }}- Add
KDataForgeApifor handler types andKDataForgefor registration to your module dependencies. - The subsystem retains handler objects against garbage collection.
- Registration after the initial load logs a warning. The documents apply on the next session.
- Multiple handlers may claim the same root type. They all run in priority order.
Optional soft integration
Section titled “Optional soft integration”If you only want your mod’s content to be editable, you do not need a custom handler. Reflected
properties are already reachable through built-in cdo documents
using target, allAssetsOfClass, and matchTag. Recommend a mod-presence condition:
conditions: hasMod: YourModRefMain-module surface
Section titled “Main-module surface”Handler registration already links the main module. Handlers that generate content or integrate with the editor can also use these stable, additive entry points:
| API | Purpose |
|---|---|
GetDynamicContent().GetOrCreateClass / GetOrCreateAsset | Create save-stable generated classes or UDataAsset instances |
QueueContentRegistration / QueueSinkTableRegistration | Queue per-world SML content registration before the freeze |
RegisterActorPatch / GetActorPatches / ClearActorPatches | Manage spawn-time FKDFActorPatch rules consumed by UKDFActorPatchSubsystem |
RegisterNodePurge / GetNodePurges / ClearNodePurges | Manage FKDFNodePurge rules consumed by UKDFResourceNodeSubsystem |
RegisterContentRemoval / GetContentRemovals / ClearContentRemovals | Manage exact-class FKDFContentRemoval rules for recipe, schematic, and research content |
MarkDataAssetsChanged | Request optional KAPI or RefinedRDApi consumer rescans after initial load |
ApplyEditorOp / RestorePropertyText | Apply editor writes and restore canonical property text for undo |
ExportObjectToYaml | Export an object to full or changes-only cdo YAML |
OnToggleEditorRequested() | Multicast hook used by the in-game editor UI |
FKDFPatchUtil::ApplyOpsToObject | Apply a parsed properties: sequence with pipeline semantics |
UKDFContentRemoveSubsystem | Apply recipe cleanup for one world and query exact removal rules/counts |
Runtime rule lifecycle
Section titled “Runtime rule lifecycle”The three Register* rule families above are retained on UKDFSubsystem for the GameInstance.
Registering a rule with the same target class and source file replaces the previous rule. Clearing
a rule array only stops later consumers; it does not undo actor writes, restore destroyed resource
nodes, or put removed content back into an already-initialized manager.
RegisterActorPatchis normally driven byapplyToSpawnedActors: true. At world begin play,UKDFActorPatchSubsystemapplies matching rules to existing actors; its spawn delegate handles later actors. A rule registered after that initial sweep affects future spawns. CallApplyPatchesToActorexplicitly when a late integration also needs an existing actor updated.RegisterNodePurgeis normally driven bytype: resourcenode. World initialization, actor-spawn, streamed-level, and deferred sweeps consume the rules. There is no public on-demand whole-world sweep, and destroyed level nodes can already have persistent vanilla save tombstones.RegisterContentRemovalis normally driven by document-levelremove:. Schematic and research filtering happens whileRegisterQueuedContentForWorlddrains the SML registry before its freeze. Recipe rules are different: native add hooks prevent later grants, andUKDFContentRemoveSubsystem::ApplyWorldRemovalscan re-run its idempotent save-restored-recipe sweep when the rule set grows. Calling it late does not retroactively remove a schematic or research tree from a registry that already initialized.
UKDFContentRemoveSubsystem::Get(WorldContext) exists only for Game and PIE worlds. Its
IsRecipeRemoved and IsContentRemoved queries use exact class identity, not ancestry.
GetAppliedRemovalCount reports resolvable rules seen by the last application pass, not the number
of manager entries changed. WereRemovalsApplied becomes true after the first non-empty application
pass and clearing the GameInstance rule list does not reset that per-world history flag.
FKDFRecipeRemoval exposes lower-level HasAnyRemovalRules, IsRecipeRemoved, and the server-only
SweepSaveRestoredRecipes helper. Normal integrations should use UKDFContentRemoveSubsystem so
world timing and reporting stay centralized. Native recipe-add prevention is compiled for packaged
runtime builds, not Unreal Editor PIE; PIE still exercises the initialization sweep.
Treat unlisted implementation details in the main module as internal.
Versioning
Section titled “Versioning”KDataForgeApi follows the mod’s CI version. Handler ABI additions should be backwards compatible.
Breaking schema changes must update the YAML Schema
and the changelog.
