Namespace Serenity.StructuredPersistence.Infrastructure.Editor.Authoring
Classes
SettingsAssetAmbiguityNotice
The one sentence every settings tool says when the project holds two or more of its own copies of a settings asset the runtime loads only one of: which asset, how many, where each one is, and the way forward — pick the one to update in the tool, then delete or merge the rest. It never names the settings type's code name nor a field a caller assigns, and never offers to create another copy: a singleton tool cannot.
A shipped copy is never among the paths: ResolveSettingsContext<TSettings>(string, string) only reports an ambiguity between the project's own copies, because the runtime loads a project copy over a shipped one without a word.
SettingsAssetAuthoring
Shared, engine-side (non-IMGUI) plumbing every single-settings-asset creation wizard needs: resolving
the project-wide settings asset the way the runtime picks it (explicit path, the project's own copy over a
shipped one, an ambiguity between two project copies, or none), Assets-path shape
validation, recursive folder creation with a rollback-friendly created-paths trail, and a hardened
asset-write check. Extracted from Serenity.GameProgress.Infrastructure.Editor.Builder.GameProgressCreator
and Serenity.GameSave.Infrastructure.Editor.Builder.GameSaveCreator, which were byte-for-byte
duplicating this exact logic. Serenity.Leaderboard.Infrastructure.Editor.Builder.LeaderboardCreator
exposes ResolveSettingsContext publicly like every other adopter, and resolves-or-creates its
settings asset through ResolveOrCreateAtPath<TSettings>(SettingsResolution<TSettings>, string, string, string, SerenityAssetCreationTransaction, out bool); its
board-uniqueness and view/menu-option resolution live on top of both and this shared helper knows
nothing about them.
SettingsAssetAuthoring.SettingsResolution
Non-generic counterpart of SettingsAssetAuthoring.SettingsResolution<TSettings> for a caller that only
knows the settings type by its simple name — e.g. an attribute-declared installable system whose
assembly cannot reference the concrete settings type (see SerenityInstallableSystemAttribute).
SettingsAssetAuthoring.SettingsResolution<TSettings>
Where a settings asset of type TSettings was found, if anywhere.
SettingsAssetByIdDraftCommit
The by-Id-in-folder sibling of SettingsAssetDraftCommit: the one shared "resolve-or-create the settings asset by Id within a folder, then commit a draft's every field onto it" sequence for a creator whose asset resolves by Id within a folder rather than by an explicit or project-wide-singleton path (SettingsAssetDraftCommit covers that case instead). Serenity.InputGateway.Installation.Editor.Builder.PlayerInputCreator and Serenity.GameMode.Installation.Editor.Builder.GameModeSettingsCreator both call this for every asset type they author, so an update-in-place run always writes onto a draft seeded from the asset an existing Id resolves to, never onto hardcoded field defaults.
Every caller still owns its OWN Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction, its own commit/rollback, its own Addressables module label, and any cross-asset wiring (a Routing asset's reference to its Device Filter/Join Gate) — this collaborator only resolves the settings asset and writes the draft onto it, exactly the part that never differs between callers.
SettingsAssetDraftCommit
The one shared "resolve-or-create the settings asset, then commit a draft's every field onto it" sequence
behind every draft-based settings wizard. CreateFromDraft(TSettings, string, string, bool)
calls this directly; so do the three hand-rolled creators whose own pre-resolution steps (a
package-content move, per-key definition authoring, Id/key trimming) do not fit that engine's generic
shape and therefore cannot instantiate it —
Serenity.SystemConfiguration.Infrastructure.Editor.Builder.SystemConfigurationSettingsCreator,
Serenity.GameCamera.Infrastructure.Editor.Builder.GameCameraSettingsCreator and
Serenity.GameGraphics.Installation.Settings.Editor.Builder.GameGraphicsSettingsCreator.
Every caller still owns its OWN Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction, its own commit/rollback, its own Addressables module label, and any step that has to run before or after this one (a move, extra assets, RecordOnto(SerenityCreationResult)) — this collaborator only resolves the settings asset and writes the draft onto it, exactly the part that never differs between callers.
SettingsAssetRelocation
Shared move-out-of-package engine for a by-type settings creator that adopts SerenityWizardTargetAsset.MoveOutOfPackage: validating a requested move as errors a caller can surface before its own Create ever runs (mirroring Serenity.Global.Infrastructure.Editor.SerenityPackageContentRelocator.MoveIntoFolder(System.String,System.String,Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction)'s own guards), and performing the move itself inside the caller's own Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction. MoveIntoPlannedFolder(string, string, SerenityAssetCreationTransaction) asserts the relocated path lands exactly where the caller's planned settings asset path said it would — see Serenity.Global.Infrastructure.Editor.SerenityWizardTargetAsset.PlannedTargetPath(System.String,System.String)'s doc for why the two must always agree BY CONSTRUCTION, not by coincidence.
A caller's own Create wants MoveIfRequested(string, string, SerenityAssetCreationTransaction), not MoveIntoPlannedFolder(string, string, SerenityAssetCreationTransaction)
directly: it folds the "was a move even requested" branch and the SettingsAssetRelocation.PendingMove bookkeeping
a caller would otherwise repeat around the move into one call, one held value, and one
RecordOnto(SerenityCreationResult) call after commit — GameGraphicsSettingsCreator,
LeaderboardCreator and LocalizationSettingsCreator all adopt it identically: one
Validate(string, string, SerenityCreationValidationResult) call inside their own Validate, one MoveIfRequested(string, string, SerenityAssetCreationTransaction) call as the
first step inside their own Create (before settings-context resolution runs), one
RecordOnto(SerenityCreationResult) call right after their transaction commits.
SettingsAssetResolution
Resolve-or-create for a settings asset identified BY ID WITHIN A FOLDER — the shape every creator that authors many assets of one type needs, and the counterpart to ResolveSettingsContext<TSettings>(string, string), which answers the opposite question: where is the ONE project-wide settings asset?
Replaces the byte-identical private helper WaveCreator, SequencePlayerCreator and
PlayerInputCreator each declared, plus the concrete twins in
InitializationPipelineProfileCreator and LoggingProfileCreator. Those five differed only
in whether the fallback file name arrived as a parameter or as a class constant.
Lives beside SettingsAssetAuthoring rather than beside
Serenity.Global.Infrastructure.Editor.SerenityAssetIdLookup because it needs
EnsureProjectFolderExists(string, ICollection<string>) and
VerifyAssetWasWrittenToDisk(string), and this assembly already depends on
the one those two lookups live in — the reverse would be a cycle. Reaching past
EnsureProjectFolderExists to the EnsureFolder it wraps would drop its
Assets-path-shape guard, which for some callers is the only validation the path gets.
ResolveOrCreateAtPath<TSettings>(SettingsResolution<TSettings>, string, string, string, SerenityAssetCreationTransaction, out bool) is the by-type twin: resolve-or-create for the ONE
project-wide settings asset a wizard resolves via
ResolveSettingsContext<TSettings>(string, string), identified by an explicit or
fallback path rather than by Id within a folder. It is extracted from the byte-identical private helper
GameGraphicsSettingsCreator, LocalizationSettingsCreator, LoggingProfileCreator and
GameProgressCreator each declare today; the adopter PRs migrate them to it. Like ResolveOrCreateByIdInFolder<TSettings>(string, string, string, SerenityAssetCreationTransaction, out bool) it
ensures the folder lazily — only on the create path, never when an existing asset is returned — but it
constrains only UnityEngine.ScriptableObject, not IFoundationSettings, so it never
assigns an Id or a Guid; the caller seeds both on the returned asset afterward, exactly like the four
copies do.
SingletonSettingsCreationEngine<TSettings, TInput, TResult>
Template-method engine shared by every "create or update the ONE project-wide settings asset of this module" wizard: identity validation, target validation (folder shape and path collision), the transaction-backed creation with rollback, Addressables registration under the module's label, and the in-place update that restores the asset when a step after it fails. A concrete engine supplies only what genuinely differs between modules — its label, its default asset name, how it reads in a message, and which fields the input writes onto the asset.
Extracted from the ten near-identical *SettingsCreator classes (MenuServiceSettingsCreator
and ModalServiceSettingsCreator matched 85-94% line for line; five of the ten declared a
byte-identical ResolveNewAssetPath). ModalCreator is deliberately not one of them: it
authors many drafts, not one singleton, and belongs to
ResolveOrCreateByIdInFolder<TSettings>(string, string, string, SerenityAssetCreationTransaction, out bool)'s shape instead.
Adopt it the way BatchSoundEffectImporter adopts
BatchAudioAssetImportEngine{TItem,TInput,TResult,TAsset}: keep the module's
public static class as the caller-facing surface, hold one private sealed class Engine
instance behind it, and forward each public member in one line — the windows and tests that already
call the static creator never change.
The Id and the Guid are the engine's business, not a subclass's: it writes Id on both the create and the update path and seeds a fresh Guid exactly once, on creation. ApplyFields(TInput, TSettings) is therefore only ever handed the module's own fields to write, which is why the same override serves both paths.
SingletonSettingsInput
The request shape every project-wide-singleton settings wizard shares: which existing asset to edit
(if any), where a brand-new one goes, what it is called, the identifier written onto it, and the
Addressables policy. Base for each module's own Input type, which adds only its own fields —
the counterpart to SingletonSettingsResult and the input half of
SingletonSettingsCreationEngine<TSettings, TInput, TResult>.
SingletonSettingsResult
The outcome shape every project-wide-singleton settings wizard shares, on top of what
Serenity.Global.Infrastructure.Editor.SerenityCreationResult already carries: where the settings asset ended up, and whether
the run edited one that already existed instead of writing a new one. Base for each module's own
Result type — the result half of
SingletonSettingsCreationEngine<TSettings, TInput, TResult>.
StructuredBackendAssetInstaller
Writes one structured backend definition to disk for a creation run: builds the asset, parks whatever already stands at its path when the run is a "Replace Existing Backend", creates the asset, proves it reached disk, and registers it with the run's Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction.
The one implementation of a pipeline that used to live, byte-identical, in four creators
(GameProgressCreator, GameSaveCreator, LeaderboardCreator,
LoggingProfileCreator) as a MoveExistingBackendToTempPathIfNeeded /
CleanUpMovedBackends / RestoreMovedBackends trio over a private
List<(TempPath, OriginalPath)> ledger. That duplication was the highest-blast-radius kind:
the ledger holds the user's existing backend asset — the only copy of it, moved rather than copied — so
a rollback-ordering fix applied to one copy left the other three still able to lose it. The park half
lives here; the unwind half lives on the transaction, as
Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction.TrackReplacedAsset(System.String,System.String), because only the transaction knows
when the original path is free again.
Parking rather than deleting is the point of the flow: a same-name replace must not destroy the old backend before the whole run is known to succeed, and UnityEditor.AssetDatabase.MoveAsset(System.String,System.String) relocates it under its own GUID, so an in-memory reference a creator captured for its rollback stays valid.
StructuredBackendAuthoringFactory
Engine-side (non-IMGUI) half of the shared backend authoring step: per-kind connection defaults,
validation, and shared backend asset construction. Extracted from
Serenity.Leaderboard.Infrastructure.Editor.Builder.LeaderboardCreator so the GameSave and
GameProgress creation wizards do not each duplicate this logic a second and third time; Leaderboard's
own copy is left untouched to avoid touching its already-tested code.
StructuredBackendAuthoringFields
Every connection field any StructuredBackendAuthoringKind backend asset can carry.
A wizard window holds one of these as a [SerializeField] and hands it to
StructuredBackendAuthoringGui/StructuredBackendAuthoringFactory, so the
shared backend authoring step (cards, fields, validation, asset construction) is written once
instead of duplicated per consumer wizard (GameSave, GameProgress). Table/collection name is
deliberately NOT here — it belongs to the consumer's own settings asset, not to the backend.
StructuredBackendAuthoringGui
The shared IMGUI half of the backend authoring step: the four backend cards, the kind-specific
fields, and the scripting-define/secret-in-plaintext warnings — extracted from
CreateLeaderboardWindow's Backend step so the GameSave and GameProgress wizards draw the
identical UI without duplicating it. Pure presentation over StructuredBackendAuthoringFields;
all validation and asset construction live in StructuredBackendAuthoringFactory.
Structs
SettingsAssetDraftCommitOutcome
Outcome of ResolveAndCommit<TSettings>(TSettings, string, string, string, SerenityAssetCreationTransaction): where the settings asset ended up, and whether it was an update rather than a fresh create — everything a caller needs to fill in its own SingletonSettingsResult-derived result, including whether the commit wrote the asset at all.
SettingsAssetRelocation.PendingMove
A move MoveIfRequested(string, string, SerenityAssetCreationTransaction) already performed, held by the caller across its own try/commit block until RecordOnto(SerenityCreationResult) can run — recording earlier would report a move that a LATER step's failure then rolled back. None is both the "nothing was requested" sentinel and this type's own default value, so a caller that never assigns one still gets a safe no-op from RecordOnto(SerenityCreationResult).
StructuredBackendAuthoringFactory.KindDefaults
Per-kind connection defaults offered by a wizard's Backend step, keyed by StructuredBackendAuthoringKind.
Enums
StructuredBackendAuthoringKind
Editor-only authoring choice shared by every structured-store consumer's creation wizard
(GameSave, GameProgress): which shared UnityStructuredBackendDefinition subtype the
wizard creates and assigns to the consumer's settings asset. Purely a wizard concern — the
runtime always dispatches on the backend asset's concrete type, never on this enum. Every kind
creates an explicit backend asset, including PlayerPrefs.