Skip to content

Commit b015ee9

Browse files
committed
Replace int flag Limit attribute logic with Maximum attribute
- Maximum is more consistent since we already have the Minimum attribute - The bit extent is calculated from the Min to Max (inclusive) range of values - Do not waste bits on nullable int flags since null is not a supported input for any int field - Set Magic Container minimum to 1 to remove the bug for release
1 parent d7de082 commit b015ee9

7 files changed

Lines changed: 84 additions & 86 deletions

File tree

CoreSourceGenerator/FlagsSerializeGenerator.cs

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ private static bool IsPartialClass(SyntaxNode node)
8989
DefaultValue = GetDefaultValue(f),
9090
IsEnum = f.Type.TypeKind == TypeKind.Enum,
9191
EnumSymbol = f.Type.TypeKind == TypeKind.Enum ? f.Type as INamedTypeSymbol : null,
92-
Limit = GetCustomLimit(f),
9392
Minimum = GetCustomMinimum(f),
93+
Maximum = GetCustomMaximum(f),
9494
CustomSerializerName = GetCustomFlagSerializer(f),
9595
}).ToList();
9696

@@ -153,29 +153,29 @@ private static bool HasReactiveAttribute(IFieldSymbol field)
153153
.Any(attr => attr.AttributeClass?.Name.StartsWith("Reactive") ?? false);
154154
}
155155

156-
private static int? GetCustomLimit(IFieldSymbol property)
156+
private static int? GetCustomMinimum(IFieldSymbol property)
157157
{
158158
var customAttr = property.GetAttributes()
159-
.FirstOrDefault(attr => attr.AttributeClass?.Name.StartsWith("Limit") ?? false);
159+
.FirstOrDefault(attr => attr.AttributeClass?.Name.StartsWith("Minimum") ?? false);
160160

161161
if (customAttr?.ConstructorArguments.Length > 0 &&
162-
customAttr.ConstructorArguments[0].Value is int limit)
162+
customAttr.ConstructorArguments[0].Value is int minimum)
163163
{
164-
return limit;
164+
return minimum;
165165
}
166166

167167
return null;
168168
}
169169

170-
private static int? GetCustomMinimum(IFieldSymbol property)
170+
private static int? GetCustomMaximum(IFieldSymbol property)
171171
{
172172
var customAttr = property.GetAttributes()
173-
.FirstOrDefault(attr => attr.AttributeClass?.Name.StartsWith("Minimum") ?? false);
173+
.FirstOrDefault(attr => attr.AttributeClass?.Name.StartsWith("Maximum") ?? false);
174174

175175
if (customAttr?.ConstructorArguments.Length > 0 &&
176-
customAttr.ConstructorArguments[0].Value is int minimum)
176+
customAttr.ConstructorArguments[0].Value is int maximum)
177177
{
178-
return minimum;
178+
return maximum;
179179
}
180180

181181
return null;
@@ -393,15 +393,21 @@ private static void GenerateSerializerList(StringBuilder sb, List<SerializedFiel
393393

394394
private static string GetSerializeCall(SerializedFieldInfo field)
395395
{
396-
var limitExpression = field.Limit != null ? $"{field.Limit}" : "null";
397-
var minExpression = $"{field.Minimum ?? 0}";
398396
var output = new StringBuilder();
399-
if (field.FieldType is "int" or "int?" && field.Limit == null)
400-
output.AppendLine($"#error Numeric type {field.FieldName} must have a `Limit` attribute!");
397+
if (field.FieldType is "int" or "int?")
398+
{
399+
if (field.Minimum == null)
400+
{
401+
output.AppendLine($"#error Numeric type {field.FieldName} must have a `Minimum` attribute!");
402+
}
403+
if (field.Maximum == null)
404+
{
405+
output.AppendLine($"#error Numeric type {field.FieldName} must have a `Maximum` attribute!");
406+
}
407+
}
401408
output.Append(field.FieldType switch
402409
{
403-
"int" or "System.Int32" => $"SerializeInt(flags, \"{field.FieldName}\", {field.FieldName}, false, {limitExpression}, {minExpression})",
404-
"int?" or "System.Int32?" => $"SerializeInt(flags, \"{field.FieldName}\", {field.FieldName}, true, {limitExpression}, {minExpression})",
410+
"int" or "int?" => $"SerializeInt(flags, \"{field.FieldName}\", {field.FieldName}, {field.Minimum}, {field.Maximum})",
405411
"bool" or "System.Boolean" => $"SerializeBool(flags, \"{field.FieldName}\", {field.FieldName}, false)",
406412
"bool?" or "System.Boolean?" => $"SerializeBool(flags, \"{field.FieldName}\", {field.FieldName}, true)",
407413
var type when field.IsEnum => $"SerializeEnum<{type}>(flags, \"{field.FieldName}\", {field.FieldName})",
@@ -412,16 +418,11 @@ private static string GetSerializeCall(SerializedFieldInfo field)
412418

413419
private static string GetDeserializeCall(SerializedFieldInfo field)
414420
{
415-
var limitExpression = field.Limit != null ? $"{field.Limit}" : "null";
416-
var minExpression = $"{field.Minimum ?? 0}";
417421
var output = new StringBuilder();
418422
var propName = GetPropertyName(field.FieldName);
419-
if (field.FieldType is "int" or "int?" && field.Limit == null)
420-
output.AppendLine($"#error Numeric type {field.FieldName} must have a `Limit` attribute!");
421423
output.Append(field.FieldType switch
422424
{
423-
"int" or "System.Int32" => $"{propName} = DeserializeInt(flags, \"{field.FieldName}\", {limitExpression}, {minExpression})",
424-
"int?" or "System.Int32?" => $"{propName} = DeserializeNullableInt(flags, \"{field.FieldName}\", {limitExpression}, {minExpression})",
425+
"int" or "int?" => $"{propName} = DeserializeInt(flags, \"{field.FieldName}\", {field.Minimum}, {field.Maximum})",
425426
"bool" or "System.Boolean" => $"{propName} = DeserializeBool(flags, \"{field.FieldName}\")",
426427
"bool?" or "System.Boolean?" => $"{propName} = DeserializeNullableBool(flags, \"{field.FieldName}\")",
427428
var type when field.IsEnum => $"{propName} = DeserializeEnum<{type}>(flags, \"{field.FieldName}\")",
@@ -582,7 +583,7 @@ public class SerializedFieldInfo
582583
public string? DefaultValue { get; set; }
583584
public bool IsEnum { get; set; }
584585
public INamedTypeSymbol? EnumSymbol { get; set; }
585-
public int? Limit { get; set; }
586+
public int? Maximum { get; set; }
586587
public int? Minimum { get; set; }
587588
public string? CustomSerializerName { get; set; }
588589
}

CrossPlatformUI/Views/Tabs/StartView.axaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
Height="72"
113113
Value="{Binding Config.StartingMagicContainersMin}"
114114
FormatString="N0" ParsingNumberStyle="Integer"
115-
ClipValueToMinMax="True" Minimum="0" Maximum="{Binding Config.StartingMagicContainersMax}" assists:TextFieldAssist.Hints="Start Min"/>
115+
ClipValueToMinMax="True" Minimum="1" Maximum="{Binding Config.StartingMagicContainersMax}" assists:TextFieldAssist.Hints="Start Min"/>
116116
<NumericUpDown Grid.Column="1"
117117
Height="72"
118118
Value="{Binding Config.StartingMagicContainersMax}"

RandomizerCore/Flags/FlagBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public FlagBuilder Append(int val, int extent, int? minimum = null)
113113
throw new ArgumentException("Value is greater than extent in FlagBuilder.Append(int, int)");
114114
}
115115

116-
BitArray argBits = new BitArray([value]);
116+
BitArray argBits = new([value]);
117117
for (int i = BitOperations.Log2((uint)extent - 1); i >= 0; i--)
118118
{
119119
bits.Add(argBits[i]);

RandomizerCore/Flags/FlagReader.cs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -134,20 +134,17 @@ public bool ReadBool()
134134
/// Reads an integer value using a fixed number of bits.
135135
///
136136
/// The number of bits read will be the minimum number of bits needed
137-
/// to represent values in the range <c>[minimum, minimum + extent - 1]</c>.
137+
/// to represent values in the range <c>[0, extent - 1]</c>.
138138
/// </summary>
139139
/// <param name="extent">
140140
/// The number of distinct values that may be represented.
141141
/// </param>
142-
/// <param name="minimum">
143-
/// Optional minimum value added to the decoded result.
144-
/// </param>
145142
/// <returns>
146143
/// The decoded integer value.
147144
/// </returns>
148145
/// <remarks>
149146
/// <example>
150-
/// Examples (with <c>minimum = 0</c>):
147+
/// Examples:
151148
/// <list type="bullet">
152149
/// <item><description>
153150
/// <c>extent = 4</c> → values 0–3, uses 2 bits
@@ -158,35 +155,31 @@ public bool ReadBool()
158155
/// </list>
159156
/// </example>
160157
///
161-
/// The same <paramref name="extent"/> and <paramref name="minimum"/> values
158+
/// The same <paramref name="extent"/> value
162159
/// must be used during encoding to ensure correct decoding.
163160
/// </remarks>
164-
public int ReadInt(int extent, int? minimum = null)
161+
public int ReadInt(int extent)
165162
{
166-
var min = minimum ?? 0;
167-
return Take(BitOperations.Log2((uint)extent - 1) + 1) + min;
163+
return Take(BitOperations.Log2((uint)(extent - 1)) + 1);
168164
}
169165

170166
/// <summary>
171167
/// Reads a nullable integer value using a fixed number of bits.
172168
///
173169
/// The number of bits read will be the minimum number of bits needed
174170
/// to represent values in the range
175-
/// <c>[minimum, minimum + extent - 1]</c>, where the value
176-
/// <c>minimum + extent</c> is reserved as a sentinel representing <c>null</c>.
171+
/// <c>[0, extent - 1]</c>, where the value
172+
/// <c>extent</c> is reserved as a sentinel representing <c>null</c>.
177173
/// </summary>
178174
/// <param name="extent">
179175
/// The number of distinct non-null values that may be represented.
180176
/// </param>
181-
/// <param name="minimum">
182-
/// Optional minimum value added to non-null decoded results.
183-
/// </param>
184177
/// <returns>
185178
/// The decoded integer value, or <c>null</c>.
186179
/// </returns>
187180
/// <remarks>
188181
/// <example>
189-
/// Examples (with <c>minimum = 0</c>):
182+
/// Examples:
190183
/// <list type="bullet">
191184
/// <item><description>
192185
/// <c>extent = 3</c> → values 0–2 or null, uses 2 bits
@@ -197,17 +190,16 @@ public int ReadInt(int extent, int? minimum = null)
197190
/// </list>
198191
/// </example>
199192
///
200-
/// The same <paramref name="extent"/> and <paramref name="minimum"/> values
193+
/// The same <paramref name="extent"/> value
201194
/// must be used during encoding to ensure correct decoding.
202195
/// </remarks>
203-
public int? ReadNullableInt(int extent, int? minimum = null)
196+
public int? ReadNullableInt(int extent)
204197
{
205-
var min = minimum ?? 0;
206198
int result = (byte)Take(BitOperations.Log2((uint)extent) + 1);
207199
if (result == extent)
208200
{
209201
return null;
210202
}
211-
return result + min;
203+
return result;
212204
}
213205
}

RandomizerCore/Flags/LimitAttribute.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace Z2Randomizer.RandomizerCore.Flags;
4+
5+
internal class MaximumAttribute : Attribute
6+
{
7+
public int Maximum { get; }
8+
public MaximumAttribute(int maximum)
9+
{
10+
Maximum = maximum;
11+
}
12+
}

RandomizerCore/RandomizerConfiguration.cs

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -123,23 +123,23 @@ public sealed partial class RandomizerConfiguration : INotifyPropertyChanged
123123
private StartingResourceLimit startSpellsLimit;
124124

125125
[Reactive]
126-
[Limit(8)]
127126
[Minimum(1)]
127+
[Maximum(8)]
128128
private int? startingHeartContainersMin;
129129

130130
[Reactive]
131-
[Limit(8)]
132131
[Minimum(1)]
132+
[Maximum(8)]
133133
private int? startingHeartContainersMax;
134134

135135
[Reactive]
136-
[Limit(8)]
137-
[Minimum(0)]
136+
[Minimum(1)]
137+
[Maximum(8)]
138138
private int? startingMagicContainersMin;
139139

140140
[Reactive]
141-
[Limit(8)]
142-
[Minimum(0)]
141+
[Minimum(1)]
142+
[Maximum(8)]
143143
private int? startingMagicContainersMax;
144144

145145
[Reactive]
@@ -152,18 +152,18 @@ public sealed partial class RandomizerConfiguration : INotifyPropertyChanged
152152
private StartingLives startingLives;
153153

154154
[Reactive]
155-
[Limit(8)]
156155
[Minimum(1)]
156+
[Maximum(8)]
157157
private int startingAttackLevel;
158158

159159
[Reactive]
160-
[Limit(8)]
161160
[Minimum(1)]
161+
[Maximum(8)]
162162
private int startingMagicLevel;
163163

164164
[Reactive]
165-
[Limit(8)]
166165
[Minimum(1)]
166+
[Maximum(8)]
167167
private int startingLifeLevel;
168168

169169
[Reactive]
@@ -402,11 +402,13 @@ private bool palaceStylesAnyMetastyleSelected()
402402
private PalaceItemRoomCount palaceItemRoomCount;
403403

404404
[Reactive]
405-
[Limit(7)]
405+
[Minimum(0)]
406+
[Maximum(6)]
406407
private int palacesToCompleteMin;
407408

408409
[Reactive]
409-
[Limit(7)]
410+
[Minimum(0)]
411+
[Maximum(6)]
410412
[ConditionallyIncludeInFlags]
411413
[DefaultValue(6)]
412414
private int palacesToCompleteMax;
@@ -436,18 +438,18 @@ private bool palaceStylesAnyMetastyleSelected()
436438
private bool shuffleLifeExperience;
437439

438440
[Reactive]
439-
[Limit(8)]
440441
[Minimum(1)]
442+
[Maximum(8)]
441443
private int attackLevelCap;
442444

443445
[Reactive]
444-
[Limit(8)]
445446
[Minimum(1)]
447+
[Maximum(8)]
446448
private int magicLevelCap;
447449

448450
[Reactive]
449-
[Limit(8)]
450451
[Minimum(1)]
452+
[Maximum(8)]
451453
private int lifeLevelCap;
452454

453455
[Reactive]
@@ -828,20 +830,21 @@ private bool DeserializeBool(FlagReader flags, string name)
828830
{
829831
return flags.ReadNullableBool();
830832
}
831-
private int DeserializeInt(FlagReader flags, string name, int limit, int? minimum)
833+
private int DeserializeInt(FlagReader flags, string name, int minimum, int maximum)
832834
{
833-
int min = minimum ?? 0;
834-
return flags.ReadInt(limit) + min;
835+
var extent = maximum - minimum + 1;
836+
return flags.ReadInt(extent) + minimum;
835837
}
836-
private int? DeserializeNullableInt(FlagReader flags, string name, int limit, int? minimum)
838+
private int? DeserializeNullableInt(FlagReader flags, string name, int minimum, int maximum)
837839
{
838-
return flags.ReadNullableInt(limit, minimum);
840+
var extent = maximum - minimum + 1;
841+
return flags.ReadNullableInt(extent) + minimum;
839842
}
840843

841844
private T DeserializeEnum<T>(FlagReader flags, string name) where T: Enum
842845
{
843-
var limit = GetEnumCount<T>();
844-
var index = flags.ReadInt(limit);
846+
var extent = GetEnumCount<T>();
847+
var index = flags.ReadInt(extent);
845848
return GetEnumFromIndex<T>(index)!;
846849
}
847850

@@ -858,8 +861,11 @@ private void SerializeBool(FlagBuilder flags, string name, bool? val, bool isNul
858861
}
859862
}
860863

861-
private void SerializeInt(FlagBuilder flags, string name, int? val, bool isNullable, int limit, int? minimum)
864+
private void SerializeInt(FlagBuilder flags, string name, int? val, int minimum, int maximum)
862865
{
866+
// We don't actually have any real nullable ints in the flags.
867+
// Enums would probably always be a better option anyway.
868+
/*
863869
// limit is checked for null in the flags source generator
864870
if (isNullable)
865871
{
@@ -869,22 +875,21 @@ private void SerializeInt(FlagBuilder flags, string name, int? val, bool isNulla
869875
}
870876
flags.Append(val, limit, minimum);
871877
}
872-
else
878+
*/
879+
int extent = maximum - minimum + 1;
880+
int value = val ?? minimum;
881+
if (value < minimum || value > maximum)
873882
{
874-
var value = val!.Value;
875-
if (value < minimum || value > minimum + limit)
876-
{
877-
logger.Warn($"Property ({name}) was out of range.");
878-
}
879-
flags.Append(value, limit, minimum);
883+
logger.Warn($"Property ({name}={value}) is out of range.");
880884
}
885+
flags.Append(value, extent, minimum);
881886
}
882887

883888
private void SerializeEnum<T>(FlagBuilder flags, string name, T? val) where T: Enum
884889
{
885890
var index = GetEnumIndex<T>(val);
886-
var limit = GetEnumCount<T>();
887-
flags.Append(index, limit);
891+
var extent = GetEnumCount<T>();
892+
flags.Append(index, extent);
888893
}
889894

890895
private void SerializeCustom<Serializer, T>(FlagBuilder flags, string name, T? val) where Serializer : IFlagSerializer where T : class

0 commit comments

Comments
 (0)