Skip to content

Add FieldLoader/Saver support for ImmutableArray, FrozenSet, FrozenDictionary.#22168

Merged
pchote merged 2 commits into
OpenRA:bleedfrom
RoosterDragon:traitinfo-immutable
Dec 8, 2025
Merged

Add FieldLoader/Saver support for ImmutableArray, FrozenSet, FrozenDictionary.#22168
pchote merged 2 commits into
OpenRA:bleedfrom
RoosterDragon:traitinfo-immutable

Conversation

@RoosterDragon
Copy link
Copy Markdown
Member

As config is often meant to be loaded and then never modified, it is helpful to support collections that are read-only after creation. This allows various classes that load config from YAML and elsewhere to deserialize straight into read-only collections and enforce invariants around data mutation.

Change classes that use FieldLoader to use read-only collections.

Fixes #22033

@RoosterDragon RoosterDragon force-pushed the traitinfo-immutable branch 2 times, most recently from b810a0a to 7fa4276 Compare November 8, 2025 18:44
Copy link
Copy Markdown
Contributor

@anvilvapre anvilvapre left a comment

Choose a reason for hiding this comment

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

untested.

curious if it would be noticeable faster.

curious if/how it would cause for more efficient dictionaries.

@RoosterDragon
Copy link
Copy Markdown
Member Author

There's an overhead at load time - about twice the cost to create a read-only collection over a mutable one.

The Frozen sets/dicts will improve lookup performance, since that's what those types do. That said most of our sets/dicts from config have so few elements I don't imagine it'll be noticable.

Overall, this PR is likely neutral performance wise. The main benefit of the PR is to express that these various collections cannot be modified, which will help prevent bugs.

Copy link
Copy Markdown
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

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

If we added hotloading, I assume we'd do it by replacing the immutable collections? Updating the readonly variables?

Copy link
Copy Markdown
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

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

I wonder if this breaks documentation

Comment thread OpenRA.Mods.Common/EditorBrushes/EditorMarkerLayerBrush.cs Outdated
Comment thread OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs
.SelectMany(resourceTypeInfo => resourceTypeInfo.AllowedTerrainTypes
.Select(terrainName => (resourceTypeInfo, terrainInfo.GetTerrainIndex(terrainName))))
.ToImmutableHashSet();
.ToHashSet();
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.

freeze?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This set won't live very long, so I've chosen to use a regular set to avoid the additional overhead of creating a frozen one.

But both are valid enough options.

var permittedEndBrushes = Brushes.End.ToImmutableHashSet();
var permittedStartBrushes = Brushes.Start.ToHashSet();
var permittedInnerBrushes = Brushes.Inner.ToHashSet();
var permittedEndBrushes = Brushes.End.ToHashSet();
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.

freeze?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These are also short lived.

Comment thread OpenRA.Mods.Common/Traits/World/ClearMapGenerator.cs
@PunkPun
Copy link
Copy Markdown
Member

PunkPun commented Nov 23, 2025

maybe to wait for #22163 before merge as that adds newer dotnet versions add a collection expression for empty frozen sets?

@RoosterDragon
Copy link
Copy Markdown
Member Author

RoosterDragon commented Nov 24, 2025

If we added hotloading, I assume we'd do it by replacing the immutable collections? Updating the readonly variables?

If depends where hotloading wants to inject the "actually we might change this at runtime" concept, since currently everything can variously assume the trait info never changes and cache things based on that.

I'd be tempted to use "TraitInfos are immutable, but we can swap in a new TraitInfo at runtime". This allows the TraitInfo itself to cache whatever things it likes for performance. Then traits can reference a traitinfo but must always perform live lookups against the info, in case a new traitinfo info gets swapped in later.

This would avoid an issue that you have even with mutable collections, where if the trait captures the collection into an enumerable or something, swapping out a new one won't help anyway.

e.g. var filtered = info.SomeCollections.Where(...); captures info.SomeCollection, so swapping in a new SomeCollection won't help, as filtered still uses the old one.

I wonder if this breaks documentation

I've made some fixes and run a diff before/after - docs are the same except for the InternalTypes (the backing C# types) changing. Friendly descriptions are available for the new types.

maybe to wait for #22163 before merge as that adds newer dotnet versions add a collection expression for empty frozen sets?

It'll be cleaner with that for sure. But the style rule can do the cleanup for us even if we merge this first.

@pchote
Copy link
Copy Markdown
Member

pchote commented Nov 24, 2025

My preference / suggestion would be to restrict hot-loading to the full Ruleset/World to avoid any issues with cached state.

Copy link
Copy Markdown
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

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

Chat localisations have regressed

Screenshot 2025-12-01 at 16 50 14

Otherwise surface level testing didn't reveal anything else

…ctionary.

As config is often meant to be loaded and then never modified, it is helpful to support collections that are read-only after creation. This allows various classes that load config from YAML and elsewhere to deserialize straight into read-only collections and enforce invariants around data mutation.
@RoosterDragon
Copy link
Copy Markdown
Member Author

Chat localisations have regressed

Nice spot, fixed.

Copy link
Copy Markdown
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

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

👍

Copy link
Copy Markdown
Member

@pchote pchote left a comment

Choose a reason for hiding this comment

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

This touches a lot of code, so lets take this now to reduce the chance of bitrot / rebase collisions.

@pchote pchote merged commit 649e7e8 into OpenRA:bleed Dec 8, 2025
2 checks passed
@pchote
Copy link
Copy Markdown
Member

pchote commented Dec 8, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make TraitInfo fields immutable

4 participants