Skip to content

fix(util): SPID form identity helpers#2119

Merged
doodlum merged 5 commits into
community-shaders:devfrom
Dlizzio:refactor/shared-form-identity-dev
Apr 20, 2026
Merged

fix(util): SPID form identity helpers#2119
doodlum merged 5 commits into
community-shaders:devfrom
Dlizzio:refactor/shared-form-identity-dev

Conversation

@Dlizzio
Copy link
Copy Markdown
Contributor

@Dlizzio Dlizzio commented Apr 14, 2026

Adds shared SPID utilities for load order portable form identification. Replaces the old pipe delimited format. Weather editor widgets and weather manager now save and load using SPID keys instead of EditorIDs or raw FormIDs.

Summary by CodeRabbit

  • Refactor

    • Form identification moved to a standardized, load-order‑portable SPID scheme; weather editor save/load now uses SPIDs and improved save path handling.
    • Form name/key resolution updated to prefer editor IDs and provide stable fallbacks.
  • Bug Fixes

    • Stronger validation and lookup fallbacks for form references to reduce missing/invalid form issues.
  • Chore

    • Shader feature metadata version bumped (1-1-0 → 2-0-0).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d889fd6-7f91-4dfc-8fa8-e4762b9d8cba

📥 Commits

Reviewing files that changed from the base of the PR and between c9c9cf0 and 21f03ba.

📒 Files selected for processing (2)
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • src/Utils/Form.cpp
✅ Files skipped from review due to trivial changes (2)
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • src/Utils/Form.cpp

📝 Walkthrough

Walkthrough

Replaces legacy plugin|hexLocalId identifiers with SPID-style 0xHEX~plugin. Adds SPID parsing/formatting and conversion utilities (FormId↔SPID) with lookup/reconstruction and display/key helpers. WeatherEditor widgets use SPID-based cached save keys and consolidated save-path helpers; WeatherManager delegates key formatting to the new utility.

Changes

Cohort / File(s) Summary
SPID Form Utilities
src/Utils/Form.h, src/Utils/Form.cpp
Removed old identifier helpers; added SpidComponents, ParseSpid, FormatSpid, FormIdToSpid, SpidToFormId, GetFormDisplayName, GetFormEditorID, and GetFormFileKey. Implements parsing, formatting, runtime lookup, reconstruction fallbacks, and editor/file-key helpers.
WeatherEditor Widget
src/WeatherEditor/Widget.h, src/WeatherEditor/Widget.cpp
Introduced cachedSaveKey and GetSaveKey() use (set via GetFormFileKey), switched editor-ID resolution to Util::GetFormEditorID, made GetFolderName() const, added GetSaveFilePath() and centralized save/load/delete path handling.
WeatherManager
src/WeatherManager.cpp
Replaced inline weather key logic with delegation to Util::GetFormFileKey(); simplified implementation and removed duplicate formatting/null-check logic.
Metadata
features/Weather Editor/Shaders/Features/WeatherEditor.ini
Bumped feature metadata version from 1-1-0 to 2-0-0.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Util as Util::SpidToFormId / FormIdToSpid
    participant Handler as TESDataHandler
    participant Lookup as TESForm::LookupByID

    Caller->>Util: SpidToFormId("0xHEX~plugin")
    Util->>Util: ParseSpid -> (localFormId, pluginName)
    Util->>Handler: LookupForm(localFormId, pluginName)
    alt found
        Handler-->>Util: TESForm*
        Util-->>Caller: form->GetFormID()
    else not found
        Util->>Util: Reconstruct full FormID from compile indices
        Util->>Lookup: LookupByID(reconstructedID)
        alt lookup success
            Lookup-->>Util: TESForm*
            Util-->>Caller: reconstructedID
        else lookup fail
            Util-->>Caller: 0 (not found)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • alandtse
  • davo0411

Poem

🐰
I nibble bytes and hop through code,
From hex to SPID I trace the road.
Save keys tucked safe in files that roam,
Editor names find their way home.
Hooray — a portable hop and a nod! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing SPID-based form identity helper utilities to replace the previous identifier scheme.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Automated formatting by clang-format, prettier, and other hooks.
See https://pre-commit.ci for details.
@github-actions
Copy link
Copy Markdown

No actionable suggestions for changed features.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/WeatherEditor/Widget.cpp (1)

244-269: ⚠️ Potential issue | 🟡 Minor

Potential null pointer dereference in GetFolderName().

GetFolderName() accesses form->GetFormType() without a null check. While GetSaveFilePath() may typically be called on valid widgets, HasSavedFile() is const and could theoretically be invoked on a widget with a null form.

Consider adding a guard or documenting the precondition:

🛡️ Proposed defensive fix
 std::string Widget::GetFolderName() const
 {
+	if (!form)
+		return "Other Editor Widgets";
 	switch (form->GetFormType()) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/WeatherEditor/Widget.cpp` around lines 244 - 269, GetFolderName()
dereferences form via form->GetFormType() without checking for null; add a
defensive null check at the top of Widget::GetFolderName() (and optionally in
GetSaveFilePath()) to handle a null form by returning the default folder string
(e.g., "Other Editor Widgets") so callers like HasSavedFile() or other const
callers won't dereference a null pointer; locate the check in the
GetFolderName() method (and adjust GetSaveFilePath() to be resilient if you
prefer) and keep the method const-correct while returning the same default value
used in the current default case.
🧹 Nitpick comments (2)
src/Utils/Form.cpp (2)

38-44: Edge case: localFormId == 0 treated as invalid, but warning logged for empty plugin too.

The condition at line 41 logs a warning and returns 0 if either pluginName.empty() OR localFormId == 0. While FormID 0 is typically the null form, the warning message could be misleading if only the plugin name is empty (which is the more likely failure case from a SPID without ~).

This is minor since both cases are effectively invalid SPIDs, but consider separating the conditions for clearer diagnostics if needed later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Utils/Form.cpp` around lines 38 - 44, In Util::SpidToFormId, the combined
check using ParseSpid(…) for components.pluginName.empty() ||
components.localFormId == 0 produces a single misleading warning; split the
validation into two distinct checks so you log one warning when pluginName is
empty (indicating a malformed SPID) and a separate (or no) warning/handling when
localFormId == 0 (the null FormID case), then return 0 after the appropriate
check so diagnostics reference ParseSpid, components.pluginName and
components.localFormId clearly.

95-115: Consider caching or avoiding repeated calls to GetFormEditorID with map fallback.

The fallback path at lines 104-112 performs a linear scan of the entire EditorID map under a read lock. While this is thread-safe, it has O(n) complexity and could become a performance concern if GetFormEditorID is called frequently (e.g., during widget list rendering or transitions).

The current usage in CacheFormData() caches the result, which mitigates this. Ensure that callers avoid repeated lookups for the same form.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Utils/Form.cpp` around lines 95 - 115, GetFormEditorID currently falls
back to a linear scan of the global EditorID map (using
RE::TESForm::GetAllFormsByEditorID()) which is O(n) and costly if called
frequently; update callers to avoid repeated lookups by caching results and/or
add an internal cache inside Util (e.g., a thread-safe unordered_map keyed by
form pointer or form ID) and have Util::GetFormEditorID check that cache before
doing the expensive map scan; ensure the cache is updated/invalidated where
forms change (reference CacheFormData or the function(s) that mutate form data)
and use RE::BSReadLockGuard only around the fallback scan as currently
implemented.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/WeatherEditor/Widget.h`:
- Around line 46-53: GetSaveFilePath is using cachedSaveKey (returned by
GetSaveKey) to build filenames but the SPID-formatted key can contain characters
like '~' that should be sanitized; fix this by applying
FileSystem::SanitizeFileName to the SPID before it is used in paths—either
sanitize when caching the value stored in cachedSaveKey (where the SPID is
computed) or call SanitizeFileName inside GetSaveFilePath before composing the
file path so the returned path never contains unsafe characters; update
references to cachedSaveKey/GetSaveKey accordingly and ensure you include the
SanitizeFileName symbol from FileSystem.h.

---

Outside diff comments:
In `@src/WeatherEditor/Widget.cpp`:
- Around line 244-269: GetFolderName() dereferences form via form->GetFormType()
without checking for null; add a defensive null check at the top of
Widget::GetFolderName() (and optionally in GetSaveFilePath()) to handle a null
form by returning the default folder string (e.g., "Other Editor Widgets") so
callers like HasSavedFile() or other const callers won't dereference a null
pointer; locate the check in the GetFolderName() method (and adjust
GetSaveFilePath() to be resilient if you prefer) and keep the method
const-correct while returning the same default value used in the current default
case.

---

Nitpick comments:
In `@src/Utils/Form.cpp`:
- Around line 38-44: In Util::SpidToFormId, the combined check using
ParseSpid(…) for components.pluginName.empty() || components.localFormId == 0
produces a single misleading warning; split the validation into two distinct
checks so you log one warning when pluginName is empty (indicating a malformed
SPID) and a separate (or no) warning/handling when localFormId == 0 (the null
FormID case), then return 0 after the appropriate check so diagnostics reference
ParseSpid, components.pluginName and components.localFormId clearly.
- Around line 95-115: GetFormEditorID currently falls back to a linear scan of
the global EditorID map (using RE::TESForm::GetAllFormsByEditorID()) which is
O(n) and costly if called frequently; update callers to avoid repeated lookups
by caching results and/or add an internal cache inside Util (e.g., a thread-safe
unordered_map keyed by form pointer or form ID) and have Util::GetFormEditorID
check that cache before doing the expensive map scan; ensure the cache is
updated/invalidated where forms change (reference CacheFormData or the
function(s) that mutate form data) and use RE::BSReadLockGuard only around the
fallback scan as currently implemented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16b34bc2-cc46-4bec-b9bc-813125fb3961

📥 Commits

Reviewing files that changed from the base of the PR and between 469f0a7 and c9c9cf0.

📒 Files selected for processing (5)
  • src/Utils/Form.cpp
  • src/Utils/Form.h
  • src/WeatherEditor/Widget.cpp
  • src/WeatherEditor/Widget.h
  • src/WeatherManager.cpp

Comment thread src/WeatherEditor/Widget.h
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 14, 2026

✅ A pre-release build is available for this PR:
Download

@alandtse
Copy link
Copy Markdown
Collaborator

Does this break any configs we've previously released? Or is this only impacting dev builds? If this breaks old saves, this is a breaking change and will increment us to 2.0.0.

@Dlizzio
Copy link
Copy Markdown
Contributor Author

Dlizzio commented Apr 15, 2026

Does this break any configs we've previously released? Or is this only impacting dev builds? If this breaks old saves, this is a breaking change and will increment us to 2.0.0.

This is a breaking change. It was largely advised throughout the community to not use the weather editor until the next update because of its unfinished state.

The current implementation is not a proper implementation.

@Dlizzio Dlizzio changed the title feat(util): SPID form identity helpers fix(util): SPID form identity helpers Apr 15, 2026
@SkrubbySkrubInAShrub
Copy link
Copy Markdown
Collaborator

you can bump the weather editor to 2.0.0 just fine
I think the amount of work we have put into it is warranted

Copy link
Copy Markdown
Collaborator

@SkrubbySkrubInAShrub SkrubbySkrubInAShrub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments, code works fine, but here are some plausible edgecases. verify if this is not slop, some of this is what the AI called out so be skeptical.

Comment thread src/Utils/Form.cpp
Comment thread src/Utils/Form.cpp
Comment thread src/WeatherManager.cpp
Comment thread src/Utils/Form.h
Comment thread src/WeatherEditor/Widget.h
@alandtse
Copy link
Copy Markdown
Collaborator

2.0.0 would be the CS version bump. Not the feature

@alandtse
Copy link
Copy Markdown
Collaborator

The major version tells downloaders if they have to manually change anything. If you're telling me the change to spid IDs will break things for prior users, it's a major CS bump.

@alandtse alandtse changed the title fix(util): SPID form identity helpers fix!(util): SPID form identity helpers Apr 15, 2026
@alandtse alandtse changed the title fix!(util): SPID form identity helpers fix(util)!: SPID form identity helpers Apr 15, 2026
@SkrubbySkrubInAShrub
Copy link
Copy Markdown
Collaborator

Can we not try to make a system that upgrades the files?

@davo0411
Copy link
Copy Markdown
Collaborator

davo0411 commented Apr 17, 2026

i understand the semver perspective and importance to adhere to it, but if people see CS 2.0 and its essentially a nothing burger update just bug fixing, people are gonna crack it. especially when as far as im aware there are no mods currently shipping weather editor jsons instead of esps. this is really more of an architectural fix.

@alandtse
Copy link
Copy Markdown
Collaborator

Version numbers don't mean anything. We could bump to 100.0.0 if we really wanted. We really shouldn't care what the random numbers mean. But it's a signal to people on whether something is breaking on their load order. We broke people going from 0.x to 1.x.

If you all really strongly believe we should break weather editing without telling people then, I can accept that. I never understood why we embedded an editor in a shader project so I frankly don't really care about that audience anyway.

@Dlizzio
Copy link
Copy Markdown
Contributor Author

Dlizzio commented Apr 18, 2026

I think for this specific circumstance it is acceptable.

@alandtse alandtse changed the title fix(util)!: SPID form identity helpers fix(util): SPID form identity helpers Apr 18, 2026
…-identity-dev

# Conflicts:
#	features/Weather Editor/Shaders/Features/WeatherEditor.ini
@doodlum doodlum merged commit 24c22be into community-shaders:dev Apr 20, 2026
17 checks passed
InTheBottle added a commit to InTheBottle/skyrim-community-shaders that referenced this pull request Apr 20, 2026
ParticleTroned pushed a commit to ParticleTroned/skyrim-community-shaders that referenced this pull request May 2, 2026
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
(cherry picked from commit 24c22be)
@Dlizzio Dlizzio deleted the refactor/shared-form-identity-dev branch May 3, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants