Skip to content

Commit b1b4424

Browse files
committed
Add components for datafeed subgroups
1 parent 4bbcb48 commit b1b4424

8 files changed

Lines changed: 269 additions & 23 deletions

File tree

MonkeyLoader.Resonite.Integration/Configuration/ConfigKeyCustomDataFeedItems.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
using MonkeyLoader.Components;
33
using MonkeyLoader.Configuration;
44
using MonkeyLoader.Resonite.DataFeeds;
5-
using System;
6-
using System.Collections.Generic;
7-
using System.Text;
85

96
namespace MonkeyLoader.Resonite.Configuration
107
{

MonkeyLoader.Resonite.Integration/Configuration/ConfigKeyQuantity.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
using Elements.Quantity;
22
using FrooxEngine;
33
using MonkeyLoader.Configuration;
4-
using System;
5-
using System.Collections.Generic;
64
using System.Diagnostics.CodeAnalysis;
7-
using System.Linq;
8-
using System.Text;
9-
using System.Threading.Tasks;
105

116
namespace MonkeyLoader.Resonite.Configuration
127
{
@@ -50,7 +45,7 @@ public ConfigKeyQuantity(UnitConfiguration defaultConfiguration, UnitConfigurati
5045
}
5146
}
5247

53-
/// <summary>su
48+
/// <summary>
5449
/// Defines the typed definition for a quantified config item.
5550
/// </summary>
5651
/// <typeparam name="T">The type of the config item's value.</typeparam>

MonkeyLoader.Resonite.Integration/Configuration/ConfigKeySessionShare.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22
using FrooxEngine;
33
using MonkeyLoader.Components;
44
using MonkeyLoader.Configuration;
5-
using System;
6-
using System.Collections.Generic;
75
using System.Diagnostics.CodeAnalysis;
8-
using System.Linq;
9-
using System.Text;
10-
using System.Threading.Tasks;
116

127
namespace MonkeyLoader.Resonite.Configuration
138
{
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Elements.Core;
2+
using EnumerableToolkit;
3+
using FrooxEngine;
4+
using MonkeyLoader.Components;
5+
using MonkeyLoader.Configuration;
6+
using MonkeyLoader.Resonite.DataFeeds;
7+
8+
namespace MonkeyLoader.Resonite.Configuration
9+
{
10+
/// <summary>
11+
/// Represents a config key component, which marks keys that should be
12+
/// enumerated in (nested) subgroups for the <see cref="SettingsDataFeed">settings</see>.
13+
/// </summary>
14+
/// <remarks><para>
15+
/// <see cref="LocaleString"/> keys for the subgroups are generated by appending the
16+
/// subgroup's path up to that point to the parent <see cref="ConfigSection"/>'s
17+
/// <see cref="ConfigSection.FullId">FullId</see>, all joined by periods (<c>.</c>).
18+
/// </para><para>
19+
/// This component can be added to multiple config keys, as it does not keep a reference to them.
20+
/// </para></remarks>
21+
public class ConfigKeySubgroup : IConfigKeySubgroup
22+
{
23+
/// <inheritdoc/>
24+
public int Priority { get; }
25+
26+
/// <inheritdoc/>
27+
public Sequence<string> SubgroupPath { get; }
28+
29+
/// <summary>
30+
/// Creates a new <see cref="IConfigKeySubgroup"/> component, which marks keys that should be
31+
/// enumerated in (nested) subgroups for the <see cref="SettingsDataFeed">settings</see>.
32+
/// </summary>
33+
/// <param name="priority">The priority of this subgroup. Higher comes first.</param>
34+
/// <param name="path">The first segment of the subgroup path.</param>
35+
/// <param name="subgroupPath">The further subgroup path elements.</param>
36+
/// <exception cref="ArgumentException">When any subgroup path element is <see cref="string.IsNullOrWhiteSpace(string?)"><see langword="null"/> or whitespace</see>.</exception>
37+
/// <inheritdoc cref="ConfigKeySubgroup"/>
38+
public ConfigKeySubgroup(int priority, string path, params string[]? subgroupPath)
39+
: this(priority, path.Yield().Concat(subgroupPath ?? []))
40+
{ }
41+
42+
/// <param name="priority">The priority of this subgroup. Higher comes first.</param>
43+
/// <param name="subgroupPath">The path that this subgroup should have. Must have at least one element.</param>
44+
/// <exception cref="NullReferenceException">When the <paramref name="subgroupPath"/> is <see langword="null"/>.</exception>
45+
/// <exception cref="ArgumentOutOfRangeException">When the <paramref name="subgroupPath"/> doesn't have at least one element.</exception>
46+
/// <inheritdoc cref="ConfigKeySubgroup(int, string, string[])"/>
47+
public ConfigKeySubgroup(int priority, IEnumerable<string> subgroupPath)
48+
{
49+
ArgumentNullException.ThrowIfNull(subgroupPath);
50+
51+
Priority = priority;
52+
SubgroupPath = subgroupPath.ToArray();
53+
54+
ArgumentOutOfRangeException.ThrowIfZero(SubgroupPath.Length, nameof(subgroupPath));
55+
56+
if (SubgroupPath.Any(string.IsNullOrWhiteSpace))
57+
throw new ArgumentException("Subgroup path must not have any null or whitespace elements!");
58+
}
59+
60+
/// <inheritdoc cref="ConfigKeySubgroup(int, string, string[])"/>
61+
public ConfigKeySubgroup(string path, params string[]? subgroupPath)
62+
: this(0, path.Yield().Concat(subgroupPath ?? []))
63+
{ }
64+
65+
void IComponent<IDefiningConfigKey>.Initialize(IDefiningConfigKey entity)
66+
{ }
67+
}
68+
69+
/// <summary>
70+
/// Defines the interface for config key components, which mark keys that should be
71+
/// enumerated in (nested) subgroups for the <see cref="SettingsDataFeed">settings</see>.
72+
/// </summary>
73+
/// <remarks>
74+
/// <see cref="LocaleString"/> keys for the subgroups are generated by appending the
75+
/// subgroup's path up to that point to the parent <see cref="ConfigSection"/>'s
76+
/// <see cref="ConfigSection.FullId">FullId</see>, all joined by periods (<c>.</c>).
77+
/// </remarks>
78+
public interface IConfigKeySubgroup : IConfigKeyComponent<IDefiningConfigKey>, ISubgroupedDataFeedItem, IPrioritizable
79+
{ }
80+
}

MonkeyLoader.Resonite.Integration/DataFeeds/ICustomDataFeedItems.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using FrooxEngine;
2-
using System;
3-
using System.Collections.Generic;
42

53
namespace MonkeyLoader.Resonite.DataFeeds
64
{
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Elements.Core;
2+
using EnumerableToolkit;
3+
using FrooxEngine;
4+
using MonkeyLoader.Meta;
5+
6+
namespace MonkeyLoader.Resonite.DataFeeds
7+
{
8+
/// <summary>
9+
/// Defines the interface for objects that should be enumerated in (nested) subgroups for their <see cref="IDataFeed">data feed</see>.
10+
/// </summary>
11+
/// <remarks>
12+
/// <see cref="LocaleString"/> keys for the subgroups are generated by appending the
13+
/// subgroup's path up to that point to the parent <see cref="IIdentifiable"/>'s
14+
/// <see cref="IIdentifiable.FullId">FullId</see>, all joined by periods (<c>.</c>).
15+
/// </remarks>
16+
public interface ISubgroupedDataFeedItem
17+
{
18+
/// <summary>
19+
/// Gets the path that the subgroup of this object should have.<br/>
20+
/// Multiple segments are generated as nested subgroups.
21+
/// </summary>
22+
/// <inheritdoc cref="ISubgroupedDataFeedItem"/>
23+
public Sequence<string> SubgroupPath { get; }
24+
}
25+
}

MonkeyLoader.Resonite.Integration/DataFeeds/Settings/SettingsFallbackLocaleGenerator.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
using MonkeyLoader.Meta;
2-
using MonkeyLoader.Patching;
2+
using MonkeyLoader.Resonite.Configuration;
33
using MonkeyLoader.Resonite.Locale;
4-
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Reflection;
8-
using System.Text;
9-
using System.Threading.Tasks;
104

115
namespace MonkeyLoader.Resonite.DataFeeds.Settings
126
{
@@ -57,6 +51,19 @@ private static void GenerateLocaleMessages(FallbackLocaleGenerationEvent eventDa
5751
{
5852
eventData.AddMessage(configKey.GetLocaleKey("Name"), configKey.Id);
5953
eventData.AddMessage(configKey.GetLocaleKey("Description"), configKey.Description ?? "No Description");
54+
55+
if (configKey.Components.TryGet<IConfigKeySubgroup>(out var subgroup))
56+
{
57+
var subgroupKey = configSection.FullId;
58+
59+
foreach (var pathElement in subgroup.SubgroupPath)
60+
{
61+
subgroupKey = $"{subgroupKey}.{pathElement}";
62+
63+
eventData.AddMessage($"{subgroupKey}.Name", pathElement);
64+
eventData.AddMessage($"{subgroupKey}.Description", "No Description");
65+
}
66+
}
6067
}
6168
}
6269
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using Elements.Core;
2+
using EnumerableToolkit;
3+
using FrooxEngine;
4+
using MonkeyLoader.Meta;
5+
6+
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
7+
8+
namespace MonkeyLoader.Resonite.DataFeeds
9+
{
10+
/// <summary>
11+
/// Represents a helper to generate nested <see cref="DataFeedGroup">subgroups</see>
12+
/// for <see cref="ISubgroupedDataFeedItem">subgrouped data feed items</see>.
13+
/// </summary>
14+
/// <remarks><para>
15+
/// The generator uses internal state to track which subgroups have already been created.<br/>
16+
/// As such, it must be recreated for each enumeration of a <see cref="IDataFeed">data feed</see>.
17+
/// </para><para>
18+
/// <see cref="LocaleString"/> keys for the subgroups are generated by appending the
19+
/// subgroup's path up to that point to the <see cref="BaseIdentifiable">BaseIdentifiable</see>'s
20+
/// <see cref="IIdentifiable.FullId">FullId</see>, all joined by periods (<c>.</c>).
21+
/// </para></remarks>
22+
public sealed class SubgroupGenerator
23+
{
24+
private readonly HashSet<Sequence<string>> _createdSubgroups = [];
25+
26+
/// <summary>
27+
/// Gets the group keys for the outer container that the
28+
/// nested <see cref="DataFeedGroup">subgroups</see> should be placed in.
29+
/// </summary>
30+
public IReadOnlyList<string> BaseGroupKeys { get; }
31+
32+
/// <summary>
33+
/// Gets the base <see cref="IIdentifiable">identifiable</see> that's used to
34+
/// generate the <see cref="LocaleString"/>s for the subgroups.
35+
/// </summary>
36+
/// <remarks>
37+
/// <see cref="LocaleString"/> keys for the subgroups are generated by appending the
38+
/// subgroup's path up to that point to the this <see cref="IIdentifiable">identifiable</see>'s
39+
/// <see cref="IIdentifiable.FullId">FullId</see>, all joined by periods (<c>.</c>).
40+
/// </remarks>
41+
public IIdentifiable BaseIdentifiable { get; }
42+
43+
/// <summary>
44+
/// Gets path of the items being enumerated.
45+
/// </summary>
46+
public IReadOnlyList<string> Path { get; }
47+
48+
/// <summary>
49+
/// Creates a new instance of this helper to generate nested <see cref="DataFeedGroup">subgroups</see>
50+
/// for <see cref="ISubgroupedDataFeedItem">subgrouped data feed items</see>.
51+
/// </summary>
52+
/// <param name="baseIdentifiable">The base <see cref="IIdentifiable">identifiable</see> that's used to generate the <see cref="LocaleString"/>s for the subgroups.</param>
53+
/// <param name="path">The path of the items being enumerated.</param>
54+
/// <param name="groupKeys">The group keys for the outer container that the nested <see cref="DataFeedGroup">subgroups</see> should be placed in.</param>
55+
/// <inheritdoc cref="SubgroupGenerator"/>
56+
public SubgroupGenerator(IIdentifiable baseIdentifiable, IReadOnlyList<string> path, IReadOnlyList<string> groupKeys)
57+
{
58+
BaseIdentifiable = baseIdentifiable;
59+
Path = path;
60+
BaseGroupKeys = groupKeys;
61+
}
62+
63+
/// <summary>
64+
/// Enumerates the missing nested subgroups for the given path.
65+
/// </summary>
66+
/// <remarks>
67+
/// Prefer using any of the <c>GetSubgroupEnumerator</c> overloads,
68+
/// since they also return the required group keys to place items in the final subgroup.
69+
/// </remarks>
70+
/// <inheritdoc cref="GetSubgroupEnumerator(Sequence{string}, int, out IReadOnlyList{string})"/>
71+
public async IAsyncEnumerable<DataFeedItem> EnumerateSubgroupsAsync(Sequence<string> subgroupPath, int priority = 0)
72+
{
73+
await Task.CompletedTask;
74+
75+
if (_createdSubgroups.Contains(subgroupPath))
76+
yield break;
77+
78+
for (var i = 0; i < subgroupPath.Length; ++i)
79+
{
80+
var subgroupPathElements = subgroupPath.Take(i + 1).ToArray();
81+
82+
if (!_createdSubgroups.Add(subgroupPathElements))
83+
continue;
84+
85+
var joinedSubgroupPath = string.Join('.', subgroupPathElements);
86+
var label = BaseIdentifiable.GetLocaleString($"{joinedSubgroupPath}.Name");
87+
var description = BaseIdentifiable.GetLocaleString($"{joinedSubgroupPath}.Description");
88+
89+
var subgroup = new DataFeedGroup();
90+
subgroup.InitBase(subgroupPathElements[i], Path, [.. BaseGroupKeys, .. subgroupPathElements[..i]], label);
91+
subgroup.InitSorting(-priority);
92+
subgroup.InitDescription(description);
93+
yield return subgroup;
94+
}
95+
}
96+
97+
/// <summary>
98+
/// Gets an enumerator for the missing nested subgroups for the subgrouped data feed item's path and
99+
/// returns the group keys necessary to place items in the final subgroup,
100+
/// using the item's <see cref="IPrioritizable.Priority">priority</see> too, if available.
101+
/// </summary>
102+
/// <param name="subgroupedItem">
103+
/// The <see cref="ISubgroupedDataFeedItem">subgrouped data feed item</see> containing the
104+
/// <see cref="ISubgroupedDataFeedItem.SubgroupPath">path</see> of the required nested subgroups.<br/>
105+
/// If this is also <see cref="IPrioritizable"/>, that <see cref="IPrioritizable.Priority">priority</see>
106+
/// will be assigned to the newly generated subgroups; otherwise, zero. Higher comes first.
107+
/// </param>
108+
/// <inheritdoc cref="GetSubgroupEnumerator(ISubgroupedDataFeedItem, int, out IReadOnlyList{string})"/>
109+
public IAsyncEnumerable<DataFeedItem> GetSubgroupEnumerator(ISubgroupedDataFeedItem subgroupedItem, out IReadOnlyList<string> groupKeys)
110+
=> GetSubgroupEnumerator(subgroupedItem, (subgroupedItem as IPrioritizable)?.Priority ?? 0, out groupKeys);
111+
112+
/// <summary>
113+
/// Gets an enumerator for the missing nested subgroups for the subgrouped data feed item's path and
114+
/// returns the group keys necessary to place items in the final subgroup.
115+
/// </summary>
116+
/// <param name="subgroupedItem">
117+
/// The <see cref="ISubgroupedDataFeedItem">subgrouped data feed item</see> containing the
118+
/// <see cref="ISubgroupedDataFeedItem.SubgroupPath">path</see> of the required nested subgroups.
119+
/// </param>
120+
/// <inheritdoc cref="GetSubgroupEnumerator(Sequence{string}, int, out IReadOnlyList{string})"/>
121+
public IAsyncEnumerable<DataFeedItem> GetSubgroupEnumerator(ISubgroupedDataFeedItem subgroupedItem, int priority, out IReadOnlyList<string> groupKeys)
122+
{
123+
groupKeys = [.. BaseGroupKeys, .. subgroupedItem.SubgroupPath];
124+
125+
return EnumerateSubgroupsAsync(subgroupedItem.SubgroupPath, priority);
126+
}
127+
128+
/// <summary>
129+
/// Gets an enumerator for the missing nested subgroups for the given path and
130+
/// returns the group keys necessary to place items in the final subgroup.
131+
/// </summary>
132+
/// <param name="subgroupPath">The path of the required nested subgroups.</param>
133+
/// <param name="priority">The optional priority to assign to the newly created subgroups. Higher comes first.</param>
134+
/// <param name="groupKeys">The group keys necessary to place items in the final subgroup.</param>
135+
/// <returns>A sequence of the missing nested subgroups for the given path. Empty if all subgroups have already been generated.</returns>
136+
public IAsyncEnumerable<DataFeedItem> GetSubgroupEnumerator(Sequence<string> subgroupPath, int priority, out IReadOnlyList<string> groupKeys)
137+
{
138+
groupKeys = [.. BaseGroupKeys, .. subgroupPath];
139+
140+
return EnumerateSubgroupsAsync(subgroupPath, priority);
141+
}
142+
143+
/// <inheritdoc cref="GetSubgroupEnumerator(Sequence{string}, int, out IReadOnlyList{string})"/>
144+
public IAsyncEnumerable<DataFeedItem> GetSubgroupEnumerator(Sequence<string> subgroupPath, out IReadOnlyList<string> groupKeys)
145+
=> GetSubgroupEnumerator(subgroupPath, 0, out groupKeys);
146+
}
147+
}
148+
149+
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)

0 commit comments

Comments
 (0)