-
Notifications
You must be signed in to change notification settings - Fork 7
Prefab patching #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Prefab patching #52
Changes from all commits
f4ac41c
2b0f842
ad38751
6fe6fdc
3967ebe
b4b0c7a
25b3fa4
03c96e4
ae9c9f0
5292e80
9ed941a
f1d2b20
79d719e
51a8bd5
ef3d3fd
0fd2ad2
226beff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| using PatchManager.PrefabPatching; | ||
| using UnityEditor; | ||
|
|
||
| namespace PatchManager.Editor; | ||
|
|
||
| [InitializeOnLoad] | ||
| internal static class PrefabPatchPlayModeCleanup | ||
| { | ||
| static PrefabPatchPlayModeCleanup() | ||
| { | ||
| EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; | ||
| EditorApplication.playModeStateChanged += OnPlayModeStateChanged; | ||
| } | ||
|
|
||
| private static void OnPlayModeStateChanged(PlayModeStateChange state) | ||
| { | ||
| if (state == PlayModeStateChange.ExitingPlayMode) | ||
| PrefabPatchRuntime.ReleaseSessionResources(); | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| # Declarative prefab patches | ||
|
|
||
| Patch Manager prefab patches modify stock Addressable prefabs before the game | ||
| instantiates them. Visual prefab-variant authoring, C#, and Lua all generate the | ||
| same versioned JSON manifest and use the same resolver, ordering rules, cache, | ||
| and runtime composer. | ||
|
|
||
| The schema is type-agnostic for Unity `Component` types. An added component is | ||
| identified by its assembly-qualified CLR type and contains: | ||
|
|
||
| - every serialized value, using Unity `SerializedProperty` paths; | ||
| - every Unity object reference as a separate fixup; | ||
| - a stable patch-local component ID. | ||
|
|
||
| The runtime constructs all patch-owned objects and components beneath an | ||
| inactive root, applies collection sizes and serialized values, and then resolves | ||
| references. This permits references between sibling objects, between arbitrary | ||
| components, into nested serialized arrays/lists, to stock prefab objects, and | ||
| to mod or stock Addressables. It also prevents `OnEnable` from observing a | ||
| partially hydrated effective prefab. | ||
|
|
||
| This is still the initial prerelease schema (`schemaVersion: 1`, | ||
| `composerVersion: 1`). There is no compatibility or migration layer. Recompile | ||
| local experimental manifests from their authoring variants whenever the schema | ||
| changes. | ||
|
|
||
| Compiled JSON contains the local `patchName`, operations, dependencies, and | ||
| target identity. It does not serialize a mod ID or mod version. Each SpaceWarp | ||
| descriptor declares an `addressable_prefab_patch_label`; Patch Manager queries | ||
| that label in both editor Play Mode and players and assigns the descriptor's | ||
| `swinfo` ID to every returned manifest. The manifest asset's own Addressables | ||
| address is not patch identity. The fully-qualified `mod-id:patch-name` exists | ||
| only in the resolved runtime model. | ||
|
|
||
| KSP2UnityTools-generated mods default to `<mod-id>_prefab_patches`. Redux uses | ||
| `redux_prefab_patches`. Script TextAssets use the parallel | ||
| `<mod-id>_patches`/`redux_patches` convention. Loose Lua files remain supported. | ||
|
|
||
| ## Visual authoring | ||
|
|
||
| In Redux SDK: | ||
|
|
||
| 1. Import the stock prefab with BundleKit's Linked Addressables Browser. | ||
| 2. Right-click the linked prefab and choose **Redux SDK > Create Prefab Patch | ||
| from Linked Prefab**. | ||
| 3. Edit the generated prefab variant normally. | ||
| 4. Add `PrefabPatchAuthoringObjectId` to every newly added GameObject and give | ||
| each one a stable ID. | ||
| 5. Save the variant. With auto-compile enabled, its `.prefabpatch.json` manifest | ||
| is rebuilt immediately. | ||
|
|
||
| New `GameObject`, `RectTransform`, UGUI, TMP, game, and mod component types use | ||
| the same general compiler. The compiler records the complete serialized | ||
| component state; it does not select from a component whitelist. Local | ||
| GameObject/component references are translated to patch-owned IDs. Linked game | ||
| assets and mod-owned Addressables remain address references and are loaded | ||
| before composition. | ||
|
|
||
| ## C# authoring | ||
|
|
||
| Register C# patches before `PrefabPatchRuntime.CloseRegistration()`. Handwritten | ||
| patches target a stock Addressables key and select objects by their ordinary | ||
| prefab hierarchy path: | ||
|
|
||
| ```csharp | ||
| using PatchManager.PrefabPatching; | ||
| using PatchManager.CSharpPatching; | ||
| using UnityEngine.UI; | ||
|
|
||
| var background = PrefabPatchBuilder.ComponentAt<Image>( | ||
| "KSP2UIWindow/Root/Window-App/Background" | ||
| ); | ||
|
|
||
| Patching.Mod.PatchPrefab("toolbar", "SomeWindow.prefab") | ||
| .SetValue( | ||
| "tint-background", | ||
| background, | ||
| "m_Color.r", | ||
| PrefabPatchValue.FromFloat(0.25) | ||
| ) | ||
| .Register(); | ||
| ``` | ||
|
|
||
| `PrefabPatchBuilder` also exposes value/reference writes, active/suppress, | ||
| add/remove component, ordering, dependency, conflict, and configuration-input | ||
| methods. `PatchObject` and `PatchComponent` create stable targets for objects | ||
| introduced by this or a required patch. Addressable references are created with | ||
| `Addressable`. | ||
|
|
||
| Operation order is the fluent call order. This is significant when a later | ||
| operation targets an object or component introduced earlier in the same patch. | ||
|
|
||
| ## Lua authoring | ||
|
|
||
| Lua uses `PM:Prefab`. Tables use the camel-case names from the public JSON | ||
| schema; empty Lua tables are normalized to empty arrays where the model expects | ||
| a collection. | ||
|
|
||
| ```lua | ||
| local patch = PM:Prefab("toolbar", "SomeWindow.prefab") | ||
| :Needs("some-required-mod") | ||
| :SetComponent( | ||
| "tint-background", | ||
| "KSP2UIWindow/Root/Window-App/Background", | ||
| "UnityEngine.UI.Image", | ||
| "m_Color.r", | ||
| 0.25 | ||
| ) | ||
|
|
||
| patch:Register() | ||
| ``` | ||
|
|
||
| The returned builder supports `Early`, `Late`, `First`, `Last`, dependency and | ||
| ordering methods, `Set`, `Reference`, `Active`, `Suppress`, `AddObject`, | ||
| `AddComponent`, `RemoveComponent`, `Build`, and `Register`. `Build` is useful | ||
| for tools/tests; normal mods call `Register`. `GameObject(path)` and | ||
| `Component(path, type, ordinal)` return key-first targets that can be passed to | ||
| any generic operation method; `SetComponent` is shorthand for the common | ||
| scalar-component case. | ||
|
|
||
| Visual, C#, and Lua manifests all identify the stock prefab by its Addressables | ||
| key. Compiled visual manifests additionally retain a structural fingerprint | ||
| and sibling-index runtime locators, but do not serialize catalog, bundle, CAB, | ||
| path-ID, or full structural-description metadata. Imperative patches resolve | ||
| named hierarchy paths at runtime. Duplicate child names at one hierarchy level | ||
| are rejected as ambiguous rather than resolved arbitrarily. | ||
|
|
||
| Patch-owned targets use the local owning patch name plus stable | ||
| object/component ID. An explicit `other-mod:patch-name` is used only when | ||
| targeting another mod's required patch. | ||
|
|
||
| ## Runtime and compatibility behavior | ||
|
|
||
| - Prefab discovery, plan-cache, ordering, diagnostics, and composition failures | ||
| are written as a `Prefab Patches` section in the existing | ||
| `pm_summary.log`; prefab updates preserve the ordinary JSON-patch summary. | ||
| - Composition occurs once per resolved stock prefab and the effective prefab is | ||
| cached for repeated provider requests. | ||
| - Resolver ordering, required/conflicting mods and patches, and field conflicts | ||
| are identical for all three frontends. | ||
| - The player loads Addressable references before applying the plan. Target | ||
| references are resolved locally after every object/component has been | ||
| created. | ||
| - A missing CLR component type, missing Addressable, stale stock fingerprint, | ||
| invalid property path, or unknown project tag fails the patch with a precise | ||
| diagnostic instead of silently dropping data. | ||
| - Patch manifests and mod-owned assets are distributable. Linked stock game | ||
| assets remain external references and are not copied into a mod build by the | ||
| prefab patch schema. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again - why split the docs here? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using PatchManager.PrefabPatching; | ||
|
|
||
| namespace PatchManager.CSharpPatching | ||
| { | ||
| /// <summary> | ||
| /// Mod-scoped C# frontend for declarative prefab patches. | ||
| /// </summary> | ||
| public static class PrefabPatchingCSharpPatching | ||
| { | ||
| /// <summary> | ||
| /// Creates a prefab patch owned by the calling mod's swinfo identity. | ||
| /// </summary> | ||
| /// <summary> | ||
| /// Creates a prefab patch targeting a stock Addressables key. | ||
| /// Canonical bundle and CAB metadata are not part of imperative | ||
| /// authoring. | ||
| /// </summary> | ||
| public static PrefabPatchBuilder PatchPrefab( | ||
| this PmScope scope, | ||
| string name, | ||
| string address | ||
| ) => new(scope.ModId, name, address); | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need this here - nobody will read the documentation here
This needs to go on the modding docs page - and be elaborated upon more like how the other patch manager docs are