Skip to content

Implement menu source generation#76

Open
dplochcoder wants to merge 4 commits intomainfrom
gen
Open

Implement menu source generation#76
dplochcoder wants to merge 4 commits intomainfrom
gen

Conversation

@dplochcoder
Copy link
Copy Markdown
Collaborator

Summary of Changes

This enables quick and easy generation of menus for classes intended to be serialized with Json, as opposed to data specified through BepInEx configuration files. It does not yet have feature parity with MenuElementFactory but it is close and I think the generated API is much nicer to use than runtime reflection. Attributes exist to plug in custom elements, sub menus, and to clamp numeric values to bounded ranges.

I don't know if the packaging set up will work with nuget; I copied stuff from https://github.com/BadMagic100/DataDrivenConstants/blob/main/DataDrivenConstants.Package/DataDrivenConstants.Package.csproj. I've also never written a Roslyn analyzer before so I probably have some stuff wrong, but it at least all works in the test project and the diagnostics seem to be working too.

Checklist

  • No change is too small for a release, so pick one:
    • I have updated the package version following semantic versioning
    • There is another change actively WIP that will update the version (link issue or PR here)
    • This PR does not update user-facing code/config
    • I'm not sure how to set the version and would like the reviewer's help

@dplochcoder dplochcoder requested a review from BadMagic100 April 28, 2026 05:08
Comment thread Silksong.ModMenuTesting/Tests/GeneratorTest.cs
@flibber-hk
Copy link
Copy Markdown
Member

Suppose I have a menu generated with this procedure, and I want to modify the values of the underlying data object in code. What is the recommended way to do this? In HK.MC I believe the answer was to modify the menu elements directly - is the expectation here that they have to hold a reference to the generated menu and check if it exists before deciding whether to modify the data on the generated menu or on the data directly?

it may or may not be the case that the answer to this question is simply an article

@BadMagic100 BadMagic100 self-assigned this Apr 28, 2026
@dplochcoder
Copy link
Copy Markdown
Collaborator Author

Suppose I have a menu generated with this procedure, and I want to modify the values of the underlying data object in code. What is the recommended way to do this? In HK.MC I believe the answer was to modify the menu elements directly - is the expectation here that they have to hold a reference to the generated menu and check if it exists before deciding whether to modify the data on the generated menu or on the data directly?

it may or may not be the case that the answer to this question is simply an article

I wrote an article. The answer depends on what sort of syncing behaviour your actually want between the menu and the underlying data; if the answer is "always in-sync" then you want to respond to every value change with menu.ExportTo(underlyingDataSource) and you want to follow-up every data change with menu?.ApplyFrom(underlyingDataSource). You shouldn't ever need to modify the menu element's values directly.

@dplochcoder dplochcoder force-pushed the gen branch 2 times, most recently from 07d94a1 to cc44d03 Compare April 28, 2026 18:49
Comment thread docs/articles/generation.md Outdated
Copy link
Copy Markdown
Member

@BadMagic100 BadMagic100 left a comment

Choose a reason for hiding this comment

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

I would really strongly recommend creating a roslyn test project to verify the generated sources as well.

For locally testing the nuget packaging, you can set the package version as a prerelease and build, then set up a local package source with dotnet nuget add source and then take the prerelease version as a dependency. If you don't need the extra assurance you can also just change the extension of the package to .zip and inspect the content (nuget has an online package browser you can use to compare against other packages)

Comment thread docs/articles/generation.md

Each declared element on your custom menu class can be changed directly, via setting the property to a new element. If you don't want a property to be managed by the custom menu, annotate it with @"Silksong.ModMenu.Models.ModMenuIgnoreAttribute" in the data class.

You can also succinctly specify minimums and maximums for numeric properties, using @"Silksong.ModMenu.Generator.ModMenuRangeAttribute". If you have a special type or you want to use a custom element for a specific property, use the @"Silksong.ModMenu.Generator.ElementFactoryAttribute`1" with an @"Silksong.ModMenu.Generator.IElementFactory`2" implementing class.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a reason that these are a different set of attributes than the ones used for bepinex configs (particularly, for element customization)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

They're not compatible. MenuElementGenerator is a delegate, not an Attribute, it requires a ConfigEntryBase which is not available here, and even if it were Attributes cannot contain function-refs, only Types, so it would need to be packaged into a new interface anyway. AcceptableValueRange is also not an Attribute.

/// Attribute to mark a field or property of a data class as requiring its own custom sub-menu, of the parameterized type.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class SubMenuAttribute<T> : Attribute
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The doc article could probably do with an explanation of how to generate submenus

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It does, it's the last section of the doc. Sub-menus are generated the same way normal menus are generated, so it's not a very long section.

Comment thread Silksong.ModMenu/Silksong.ModMenu.csproj Outdated
Comment thread Silksong.ModMenuAnalyzers/MenuProperty.cs
Comment thread Silksong.ModMenuAnalyzers/ModMenuGenerator.cs
Comment thread Silksong.ModMenuAnalyzers/ModMenuGenerator.cs Outdated

/// Custom menu class generated for {className}.
[System.CodeDom.Compiler.GeneratedCode(""ModMenuGenerator"", {VERSION.MakeLiteral()})]
public class {menuClassName} : Silksong.ModMenu.Generator.ICustomMenu<{className}>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is there a particular reason to not have this implement INotifyPropertyChange in addition/instead/have ICustomMenu derive from that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't really understand the suggestion here, at least the 'instead' part. All of the elements have different types and sub menus get different code, so making this work in a generic fashion seems like it would require some awful reflection at minimum.

Comment thread Silksong.ModMenuAnalyzers/Silksong.ModMenuAnalyzers.csproj Outdated
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.

3 participants