@@ -15,14 +15,16 @@ public class CategoryStates : ManagedState {
15
15
16
16
private static readonly Logger Logger = Logger . GetLogger < CategoryStates > ( ) ;
17
17
18
- private const string STATE_FILE = "categories.txt" ;
18
+ private const string STATE_FILE = "categories.txt" ;
19
+ private const string INVERTEDSTATE_FILE = "invcategories.txt" ;
19
20
20
21
private const double INTERVAL_SAVESTATE = 5000 ; // 5.0 seconds
21
22
private const double INTERVAL_UPDATEINACTIVECATEGORIES = 100 ; // 0.1 seconds
22
23
23
24
private HashSet < string > _inactiveCategories = new ( StringComparer . OrdinalIgnoreCase ) ;
24
25
25
- private readonly SafeList < PathingCategory > _rawInactiveCategories = new ( ) ;
26
+ private readonly SafeList < PathingCategory > _rawInactiveCategories = new ( ) ; // Contains all categories which have been explicitly unchecked.
27
+ private readonly SafeList < PathingCategory > _rawInvertedCategories = new ( ) ; // Contains all categories which have been explicitly checked and by default are toggled off.
26
28
27
29
private double _lastSaveState = 0 ;
28
30
private double _lastInactiveCategoriesCalculation = 0 ;
@@ -32,115 +34,168 @@ public class CategoryStates : ManagedState {
32
34
33
35
public CategoryStates ( IRootPackState packState ) : base ( packState ) { /* NOOP */ }
34
36
35
- private async Task LoadState ( ) {
36
- string categoryStatesPath = Path . Combine ( DataDirUtil . GetSafeDataDir ( DataDirUtil . COMMON_STATE ) , STATE_FILE ) ;
37
+ private async Task LoadCategoryState ( string stateFileName , SafeList < PathingCategory > rawCategoriesList , PathingCategory rootCategory ) {
38
+ string categoryStatePath = Path . Combine ( DataDirUtil . GetSafeDataDir ( DataDirUtil . COMMON_STATE ) , stateFileName ) ;
37
39
38
- if ( ! File . Exists ( categoryStatesPath ) ) return ;
40
+ if ( ! File . Exists ( categoryStatePath ) )
41
+ return ; // Early skip if this state file doesn't exist yet.
39
42
40
43
string [ ] recordedCategories = Array . Empty < string > ( ) ;
41
44
42
45
try {
43
- recordedCategories = await FileUtil . ReadLinesAsync ( categoryStatesPath ) ;
46
+ recordedCategories = await FileUtil . ReadLinesAsync ( categoryStatePath ) ;
44
47
} catch ( Exception e ) {
45
- Logger . Error ( e , $ "Failed to read { STATE_FILE } ({ categoryStatesPath } ).") ;
48
+ Logger . Error ( e , $ "Failed to read { STATE_FILE } ({ categoryStatePath } ).") ;
46
49
}
47
-
48
- _rawInactiveCategories . Clear ( ) ;
49
-
50
- var rootCategory = _rootPackState . RootCategory ;
51
50
52
- if ( rootCategory == null ) return ; // Early skip if the pack is already getting repopulated.
51
+ rawCategoriesList . Clear ( ) ;
53
52
54
53
foreach ( string categoryNamespace in recordedCategories ) {
55
54
// TODO: Consider the case where a category no longer exists - this will create it.
56
- // Luckily, it shouldn't display anyways because it will not have a displayname .
57
- _rawInactiveCategories . Add ( rootCategory . GetOrAddCategoryFromNamespace ( categoryNamespace ) ) ;
55
+ // We end up ignoring it, though, as it is known that it was not pulled from a pack based on the LoadedFromPack property .
56
+ rawCategoriesList . Add ( rootCategory . GetOrAddCategoryFromNamespace ( categoryNamespace ) ) ;
58
57
}
59
-
60
- _calculationDirty = true ;
61
58
}
62
59
63
- private async Task SaveState ( GameTime gameTime ) {
64
- if ( ! _stateDirty ) return ;
60
+ private async Task LoadStates ( ) {
61
+ var rootCategory = _rootPackState . RootCategory ;
65
62
66
- Logger . Debug ( $ "Saving { nameof ( CategoryStates ) } state.") ;
63
+ if ( rootCategory == null )
64
+ return ; // Early skip if the pack is already getting repopulated.
65
+
66
+ Logger . Debug ( $ "Loading { nameof ( CategoryStates ) } state.") ;
67
+
68
+ await LoadCategoryState ( STATE_FILE , _rawInactiveCategories , rootCategory ) ;
69
+ await LoadCategoryState ( INVERTEDSTATE_FILE , _rawInvertedCategories , rootCategory ) ;
67
70
68
- PathingCategory [ ] inactiveCategories = _rawInactiveCategories . ToArray ( ) ;
71
+ _calculationDirty = true ;
72
+ }
73
+
74
+ private async Task SaveCategoryState ( string stateFileName , SafeList < PathingCategory > rawCategoriesList ) {
75
+ PathingCategory [ ] toggledCategories = rawCategoriesList . ToArray ( ) ;
69
76
70
- string categoryStatesPath = Path . Combine ( DataDirUtil . GetSafeDataDir ( DataDirUtil . COMMON_STATE ) , STATE_FILE ) ;
77
+ string categoryStatePath = Path . Combine ( DataDirUtil . GetSafeDataDir ( DataDirUtil . COMMON_STATE ) , stateFileName ) ;
71
78
72
79
try {
73
- await FileUtil . WriteLinesAsync ( categoryStatesPath , inactiveCategories . Select ( c => c . Namespace ) ) ;
80
+ await FileUtil . WriteLinesAsync ( categoryStatePath , toggledCategories . Select ( c => c . Namespace ) ) ;
74
81
} catch ( Exception e ) {
75
- Logger . Error ( e , $ "Failed to write { STATE_FILE } ({ categoryStatesPath } ).") ;
82
+ Logger . Error ( e , $ "Failed to write { stateFileName } ({ categoryStatePath } ).") ;
76
83
}
84
+ }
85
+
86
+ private async Task SaveStates ( GameTime gameTime ) {
87
+ if ( ! _stateDirty ) return ;
88
+
89
+ Logger . Debug ( $ "Saving { nameof ( CategoryStates ) } state.") ;
90
+
91
+ await SaveCategoryState ( STATE_FILE , _rawInactiveCategories ) ; // Standard categories.
92
+ await SaveCategoryState ( INVERTEDSTATE_FILE , _rawInvertedCategories ) ; // Inverted categories.
77
93
78
94
_stateDirty = false ;
79
95
}
80
96
97
+ private void AddAllSubCategories ( HashSet < string > categories , PathingCategory topCategory ) {
98
+ var remainingCategories = new Queue < PathingCategory > ( topCategory ) ;
99
+
100
+ while ( remainingCategories . Count > 0 ) {
101
+ var category = remainingCategories . Dequeue ( ) ;
102
+
103
+ categories . Add ( category . Namespace ) ;
104
+
105
+ foreach ( var subCategory in category ) {
106
+ remainingCategories . Enqueue ( subCategory ) ;
107
+ }
108
+ }
109
+ }
110
+
81
111
private void CalculateOptimizedCategoryStates ( GameTime gameTime ) {
82
112
if ( ! _calculationDirty ) return ;
83
113
84
- PathingCategory [ ] inactiveCategories = _rawInactiveCategories . ToArray ( ) ;
114
+ if ( _rootPackState . RootCategory == null ) return ;
85
115
86
- var preCalcInactiveCategories = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
116
+ PathingCategory [ ] inactiveCategories = _rawInactiveCategories . ToArray ( ) ;
117
+ PathingCategory [ ] activeInvertedCategories = _rawInvertedCategories . ToArray ( ) ;
87
118
88
- foreach ( var inactiveCategory in inactiveCategories ) {
89
- AddAllSubCategories ( preCalcInactiveCategories , inactiveCategory ) ;
90
- }
119
+ var remainingCategories = new Queue < PathingCategory > ( ) ;
120
+ remainingCategories . Enqueue ( _rootPackState . RootCategory ) ;
91
121
92
- _inactiveCategories = preCalcInactiveCategories ;
122
+ var preCalcInactiveCategories = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
93
123
94
- _calculationDirty = false ;
95
- }
124
+ while ( remainingCategories . Count > 0 ) {
125
+ var category = remainingCategories . Dequeue ( ) ;
96
126
97
- private void AddAllSubCategories ( HashSet < string > categories , PathingCategory currentCategory ) {
98
- categories . Add ( currentCategory . Namespace ) ;
127
+ if ( inactiveCategories . Contains ( category ) // Standard toggled categories.
128
+ || ( ! category . DefaultToggle && ! activeInvertedCategories . Contains ( category ) ) ) { // Inverted toggled categories.
129
+ preCalcInactiveCategories . Add ( category . Namespace ) ;
130
+ AddAllSubCategories ( preCalcInactiveCategories , category ) ;
131
+ continue ;
132
+ }
99
133
100
- foreach ( var subCategory in currentCategory ) {
101
- AddAllSubCategories ( categories , subCategory ) ;
134
+ foreach ( var subCategory in category ) {
135
+ remainingCategories . Enqueue ( subCategory ) ;
136
+ }
102
137
}
138
+
139
+ _inactiveCategories = preCalcInactiveCategories ;
140
+ _calculationDirty = false ;
103
141
}
104
142
105
143
protected override async Task < bool > Initialize ( ) {
106
- await LoadState ( ) ;
144
+ await LoadStates ( ) ;
107
145
108
146
return true ;
109
147
}
110
148
111
149
public override async Task Reload ( ) {
112
150
_inactiveCategories . Clear ( ) ;
113
151
_rawInactiveCategories . Clear ( ) ;
152
+ _rawInvertedCategories . Clear ( ) ;
114
153
115
- await LoadState ( ) ;
154
+ await LoadStates ( ) ;
116
155
}
117
156
118
157
public override void Update ( GameTime gameTime ) {
119
158
UpdateCadenceUtil . UpdateWithCadence ( CalculateOptimizedCategoryStates , gameTime , INTERVAL_UPDATEINACTIVECATEGORIES , ref _lastInactiveCategoriesCalculation ) ;
120
- UpdateCadenceUtil . UpdateAsyncWithCadence ( SaveState , gameTime , INTERVAL_SAVESTATE , ref _lastSaveState ) ;
159
+ UpdateCadenceUtil . UpdateAsyncWithCadence ( SaveStates , gameTime , INTERVAL_SAVESTATE , ref _lastSaveState ) ;
121
160
}
122
161
123
162
public override async Task Unload ( ) {
124
- await SaveState ( null ) ;
163
+ await SaveStates ( null ) ;
125
164
}
126
165
127
166
public bool GetNamespaceInactive ( string categoryNamespace ) {
128
167
return _inactiveCategories . Contains ( categoryNamespace ) ;
129
168
}
130
169
170
+ private bool GetCategoryInactive ( PathingCategory category , SafeList < PathingCategory > rawCategoriesList ) {
171
+ return rawCategoriesList . Contains ( category ) ;
172
+ }
173
+
131
174
public bool GetCategoryInactive ( PathingCategory category ) {
132
- return _rawInactiveCategories . Contains ( category ) ;
175
+ if ( category . DefaultToggle ) {
176
+ return GetCategoryInactive ( category , _rawInactiveCategories ) ;
177
+ } else {
178
+ return ! GetCategoryInactive ( category , _rawInvertedCategories ) ;
179
+ }
133
180
}
134
181
135
- public void SetInactive ( PathingCategory category , bool isInactive ) {
136
- _rawInactiveCategories . Remove ( category ) ;
182
+ private void SetInactive ( PathingCategory category , bool isInactive , SafeList < PathingCategory > rawCategoriesList ) {
183
+ rawCategoriesList . Remove ( category ) ;
137
184
138
185
if ( isInactive ) {
139
- _rawInactiveCategories . Add ( category ) ;
186
+ rawCategoriesList . Add ( category ) ;
140
187
}
141
188
142
- _stateDirty = true ;
143
- _calculationDirty = true ;
189
+ _stateDirty = true ; // Ensure that we save the new state.
190
+ _calculationDirty = true ; // Ensure that the hashset is recalculated.
191
+ }
192
+
193
+ public void SetInactive ( PathingCategory category , bool isInactive ) {
194
+ if ( category . DefaultToggle ) {
195
+ SetInactive ( category , isInactive , _rawInactiveCategories ) ;
196
+ } else {
197
+ SetInactive ( category , ! isInactive , _rawInvertedCategories ) ;
198
+ }
144
199
}
145
200
146
201
public void SetInactive ( string categoryNamespace , bool isInactive ) => SetInactive ( _rootPackState . RootCategory . GetOrAddCategoryFromNamespace ( categoryNamespace ) , isInactive ) ;
0 commit comments