Skip to content
This repository was archived by the owner on Nov 10, 2022. It is now read-only.

Commit b6ad984

Browse files
authored
LCU Events (#64)
* It's generating 1 event per client, that's something! * Generating events for all endpoints, which is better. * Events generated for events, but versions aren't perfect. * Some polishing. * Removed "On" event prefix. * Removed unused code * IT BUILDS * Looking good * whops
1 parent 6588fa3 commit b6ad984

25 files changed

+393
-140
lines changed

Riot Games Client.sln

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actions", "Actions", "{65B5
2727
.github\actions\dotnet-build-and-pack\action.yml = .github\actions\dotnet-build-and-pack\action.yml
2828
EndProjectSection
2929
EndProject
30-
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RiotGames.Client.LeagueClient.WebSockets", "RiotGames.Client.LeagueClient.WebSockets\RiotGames.Client.LeagueClient.WebSockets.shproj", "{DC557719-ED6A-4165-B6C3-942DED1D6DBF}"
30+
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RiotGames.LeagueOfLegends.LeagueClient.Client", "RiotGames.LeagueOfLegends.LeagueClient.Client\RiotGames.LeagueOfLegends.LeagueClient.Client.shproj", "{DC557719-ED6A-4165-B6C3-942DED1D6DBF}"
3131
EndProject
3232
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messages", "Messages", "{3A0453A3-A1EF-4384-94F5-7852DC8C9A90}"
3333
EndProject
@@ -36,7 +36,7 @@ EndProject
3636
Global
3737
GlobalSection(SharedMSBuildProjectFiles) = preSolution
3838
RiotGames.Messaging.Client\RiotGames.Messaging.Client.projitems*{4fd3f114-3ad4-4bcc-9040-676aed72a7cb}*SharedItemsImports = 13
39-
RiotGames.Client.LeagueClient.WebSockets\RiotGames.Client.LeagueClient.WebSockets.projitems*{dc557719-ed6a-4165-b6c3-942ded1d6dbf}*SharedItemsImports = 13
39+
RiotGames.LeagueOfLegends.LeagueClient.Client\RiotGames.LeagueOfLegends.LeagueClient.Client.projitems*{dc557719-ed6a-4165-b6c3-942ded1d6dbf}*SharedItemsImports = 13
4040
EndGlobalSection
4141
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4242
Debug|Any CPU = Debug|Any CPU

RiotGames.Client.CodeGeneration/CodeAnalysisHelpers.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ public static ConstructorDeclarationSyntax InternalConstructorDeclaration(string
3939
.WithModifier(SyntaxKind.InternalKeyword)
4040
.WithBody($"{fieldIdentifier} = {parameterIdentifier + (parameterProperty == null ? null : '.' + parameterProperty)};");
4141

42+
// TODO: Write a better one
43+
public static ConstructorDeclarationSyntax InternalConstructorDeclaration(string identifier, string parameterType, string parameterIdentifier, string fieldIdentifier, string? parameterProperty, string fieldIdentifier2, string parameterProperty2) =>
44+
ConstructorDeclaration(identifier, parameterType, parameterIdentifier)
45+
.WithModifier(SyntaxKind.InternalKeyword)
46+
.WithBody($"{fieldIdentifier} = {parameterIdentifier + '.' + parameterProperty}; {fieldIdentifier2} = {parameterIdentifier + '.' + parameterProperty2};");
47+
4248
public static EnumDeclarationSyntax PublicEnumDeclaration(string identifier, params string[] members) =>
4349
EnumDeclaration(Identifier(identifier)).WithModifiers(SyntaxKind.PublicKeyword.ToTokenList())
4450
.AddMembers(members.Select(m => EnumMemberDeclaration(Identifier(m))).ToArray());

RiotGames.Client.CodeGeneration/FileWriter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static void WriteFile(RiotGamesApi.Client client, FileType fileType, stri
2727

2828
public static void WriteLeagueClientFile(string contents, string? subClass = null)
2929
{
30-
var folder = Path.Combine(GetAssemblyDirectory(), @"../../../../", "RiotGames.Client/LeagueOfLegends/LeagueClient");
30+
var folder = Path.Combine(GetAssemblyDirectory(), @"../../../../", "RiotGames.LeagueOfLegends.LeagueClient.Client");
3131

3232
string? suffix = null;
3333
if (subClass != null)
@@ -38,7 +38,7 @@ public static void WriteLeagueClientFile(string contents, string? subClass = nul
3838

3939
public static void WriteLeagueClientModelsFile(string contents)
4040
{
41-
var folder = Path.Combine(GetAssemblyDirectory(), @"../../../../", "RiotGames.Client/LeagueOfLegends/LeagueClient");
41+
var folder = Path.Combine(GetAssemblyDirectory(), @"../../../../", "RiotGames.LeagueOfLegends.LeagueClient.Client");
4242

4343
File.WriteAllText(Path.Combine(folder, $"LeagueClientModels.g.cs"), contents);
4444
}

RiotGames.Client.CodeGeneration/LeagueClient/LeagueClientEndpointsGenerator.cs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ internal class LeagueClientEndpointsGenerator : OpenApiEndpointGeneratorBase<Lcu
3232

3333
private readonly List<MemberDeclarationSyntax[]> _moduleProperties = new();
3434
private readonly List<ClassDeclarationSyntax> _moduleClasses = new();
35+
private IGrouping<string, KeyValuePair<string, string>>? _eventGroup;
3536

3637
public LeagueClientEndpointsGenerator(string[] enums) : this(LEAGUECLIENT_CLASS_IDENTIFIER, enums) { }
3738

@@ -69,15 +70,19 @@ protected override void AddEndpoint(EndpointDefinition? endpoint)
6970
CancellableReturnAwaitStatement(null, endpoint.Identifier.EndWith("Async"), null, parameterIdentifier.ToCamelCase() + '.' + parameter.Key.ToPascalCase()),
7071
new Dictionary<string, string> { { parameterIdentifier.ToCamelCase(), interfaceIdentifier } });
7172

73+
74+
7275
Class = Class.AddMembers(method);
7376
}
7477
}
7578
}
7679

77-
private void _addGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string?, Path>> groupedPaths, string? className = null)
80+
private void _addGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string?, Path>> groupedPaths, IEnumerable<IGrouping<string, KeyValuePair<string, string>>>? groupedEvents, string? className = null)
7881
{
7982
foreach (var group in groupedPaths.Where(g => g.Key != null))
8083
{
84+
var eventGroup = groupedEvents?.SingleOrDefault(g => g.Key == group.Key);
85+
8186
bool versionSuffix = false;
8287
var versioned = group.GroupBy(P =>
8388
{
@@ -87,6 +92,14 @@ private void _addGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string
8792
else return null;
8893
});
8994

95+
var versionedEvents = eventGroup?.GroupBy(e =>
96+
{
97+
var thirdPart = e.Key.SplitAndRemoveEmptyEntries('_')[2];
98+
if (thirdPart[0] == 'v' && char.IsDigit(thirdPart[1]))
99+
return thirdPart;
100+
else return null;
101+
});
102+
90103
if (versioned.Count() > 1)
91104
{
92105
var firstVersionCount = versioned.First().Count();
@@ -101,7 +114,8 @@ private void _addGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string
101114
// Separate modules
102115
foreach (var versionedModule in versioned)
103116
{
104-
_addGroupsAsNestedClassesWithEndpoints(versionedModule.GroupByModule(),
117+
var versionedModuleEvents = versionedEvents?.Where(ve => ve.Key == versionedModule.Key);
118+
_addGroupsAsNestedClassesWithEndpoints(versionedModule.GroupByModule(), versionedModuleEvents?.SelectMany(e => e)?.GroupByModule(),
105119
group.Key.RemoveChars('{', '}').ToPascalCase() + versionedModule.Key?.ToUpper());
106120
}
107121

@@ -112,14 +126,14 @@ private void _addGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string
112126
var moduleGenerator = new LeagueClientModuleGenerator((className ?? group.Key.RemoveChars('{', '}').ToPascalCase())
113127
.FixGamePrefixes(), Enums, methodVersionSuffix: versionSuffix, generateConstructor: true);
114128

115-
moduleGenerator.AddPathsAsEndpoints(group);
129+
moduleGenerator.AddPathsAsEndpoints(group, eventGroup);
116130
_moduleProperties.Add(moduleGenerator.FieldAndProperty);
117131
_moduleClasses.Add(moduleGenerator.Class);
118132
Console.WriteLine($"League Client: Generated client module for module {moduleGenerator.ModuleName}.");
119133
}
120134
}
121135

122-
public virtual void AddGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string?, Path>> groupedPaths, string? className = null)
136+
public virtual void AddGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string?, Path>> groupedPaths, IEnumerable<IGrouping<string, KeyValuePair<string, string>>> groupedEvents, string? className = null)
123137
{
124138
// Root group
125139
var nullGroup = groupedPaths.FirstOrDefault(g => g.Key == null);
@@ -129,13 +143,14 @@ public virtual void AddGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<
129143

130144
{
131145
var lolGroups = groupedPaths.Where(g => g is { Key: not null, Key: not "lol-tft" } && g.Key.StartsWith("lol-"));
146+
var lolEvents = groupedEvents.Where(g => g.Key.StartsWith("lol-"));
132147
var generator = new LeagueClientEndpointsGenerator("LeagueOfLegendsClient", Enums, true);
133148
generator.Class = generator.Class.AddMembers(
134149
InternalConstructorDeclaration("LeagueOfLegendsClient")
135150
.WithBody(Block())
136151
.WithParameter(LEAGUECLIENTBASE_CLASS_IDENTIFIER, "leagueClient")
137-
.WithBaseConstructorInitializer("leagueClient.HttpClient"));
138-
generator._addGroupsAsNestedClassesWithEndpoints(lolGroups);
152+
.WithBaseConstructorInitializer("leagueClient.HttpClient", "leagueClient.EventRouter"));
153+
generator._addGroupsAsNestedClassesWithEndpoints(lolGroups, lolEvents);
139154
generator._addNestedMembersToClass();
140155
Class = Class.AddMembers(generator.Class);
141156
}
@@ -147,14 +162,14 @@ public virtual void AddGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<
147162
InternalConstructorDeclaration("TeamfightTacticsClient")
148163
.WithBody(Block())
149164
.WithParameter(LEAGUECLIENTBASE_CLASS_IDENTIFIER, "leagueClient")
150-
.WithBaseConstructorInitializer("leagueClient.HttpClient"));
165+
.WithBaseConstructorInitializer("leagueClient.HttpClient", "leagueClient.EventRouter"));
151166
generator.AddPathsAsEndpoints(tftPaths);
152167
Class = Class.AddMembers(generator.Class.AddModifiers(Token(SyntaxKind.PartialKeyword)));
153168
}
154169

155170
groupedPaths = groupedPaths.Where(g => g is { Key: not null, Key: not "lol-tft" } && !g.Key.StartsWith("lol-"));
156171

157-
_addGroupsAsNestedClassesWithEndpoints(groupedPaths, className);
172+
_addGroupsAsNestedClassesWithEndpoints(groupedPaths, null, className);
158173
}
159174

160175
protected override EndpointDefinition? GetMethodObjectToEndpointDefinition(LcuMethodObject getMethodObject, string path, LcuPathObject pathObject)
@@ -183,6 +198,12 @@ public virtual void AddGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<
183198
else
184199
path = "\"" + path + "\"";
185200

201+
if (_eventGroup != null && _eventGroup.Any(e => e.Key.EventEqualsPath(path))) // TODO: Fix match
202+
{
203+
var @event = _eventGroup.First(e => e.Key.EventEqualsPath(path));
204+
Class = Class.AddMembers(LeagueClientEvent.RmsEvent(@event.Key, nameFromPath, returnType));
205+
}
206+
186207
return new EndpointDefinition("Get" + nameFromPath, returnType, "HttpClient", baseMethod, typeArgument, path, null, pathParameters);
187208
}
188209

@@ -204,6 +225,7 @@ public override string GenerateCode()
204225
_addNestedMembersToClass();
205226
var @namespace = NamespaceDeclaration("RiotGames.LeagueOfLegends.LeagueClient");
206227
@namespace = @namespace.AddSystemDynamicUsing();
228+
@namespace = @namespace.AddUsing("RiotGames.Messaging");
207229

208230
@namespace = @namespace.AddMembers(Class);
209231

@@ -251,10 +273,13 @@ public LeagueClientModuleGenerator(string moduleName, string[] enums, bool metho
251273

252274
if (generateConstructor)
253275
{
254-
FieldDeclarationSyntax httpClientField = InternalReadOnlyFieldDeclaration("LeagueClientHttpClient", "HttpClient");
255-
Class = Class.AddMembers(httpClientField);
276+
var httpClientField = InternalReadOnlyFieldDeclaration("LeagueClientHttpClient", "HttpClient");
277+
var eventRouterField = InternalReadOnlyFieldDeclaration("RmsEventRouter", "EventRouter");
278+
Class = Class.AddMembers(httpClientField, eventRouterField);
256279

257-
var constructor = InternalConstructorDeclaration(ClassName, LEAGUECLIENTBASE_CLASS_IDENTIFIER, "leagueClient", "HttpClient", "HttpClient");
280+
var constructor = InternalConstructorDeclaration(ClassName, LEAGUECLIENTBASE_CLASS_IDENTIFIER, "leagueClient",
281+
"HttpClient", "HttpClient",
282+
"EventRouter", "EventRouter");
258283
Class = Class.AddMembers(constructor);
259284
}
260285

@@ -306,9 +331,15 @@ protected override string GetNameFromPath(string path, bool isArrayResponse)
306331

307332
public MemberDeclarationSyntax[] FieldAndProperty => new MemberDeclarationSyntax[] { _fieldDeclaration, _propertyDeclaration };
308333

309-
public override void AddGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string?, Path>> groupedPaths, string? className = null) => throw new NotImplementedException();
334+
public override void AddGroupsAsNestedClassesWithEndpoints(IEnumerable<IGrouping<string?, Path>> groupedPaths, IEnumerable<IGrouping<string, KeyValuePair<string, string>>> groupedEvents, string? className = null) => throw new NotImplementedException();
310335

311336
public override string GenerateCode() => throw new NotImplementedException();
337+
338+
internal void AddPathsAsEndpoints(IGrouping<string?, Path> group, IGrouping<string, KeyValuePair<string, string>>? eventGroup)
339+
{
340+
_eventGroup = eventGroup;
341+
AddPathsAsEndpoints(group);
342+
}
312343
}
313344
}
314345
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Security.Cryptography.X509Certificates;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
11+
12+
namespace RiotGames.Client.CodeGeneration.LeagueClient
13+
{
14+
internal static class LeagueClientEvent
15+
{
16+
public static MemberDeclarationSyntax[] RmsEvent(string topic, string identifier, string typeName)
17+
{
18+
identifier = identifier.RemoveStart("Get");
19+
20+
var privateEventIdentifier = '_' + identifier.ToCamelCase() + "Changed";
21+
var publicEventIdentifier = identifier.ToPascalCase() + "Changed";
22+
23+
24+
var privateEvent = EventFieldDeclaration(List<AttributeListSyntax>(), SyntaxKind.PrivateKeyword.ToTokenList(),
25+
SyntaxFactory.VariableDeclaration(
26+
SyntaxFactory.ParseTypeName($"LeagueClientEventHandler<{typeName}>"),
27+
SyntaxFactory.SeparatedList<VariableDeclaratorSyntax>(new[]
28+
{
29+
SyntaxFactory.VariableDeclarator(privateEventIdentifier)
30+
}
31+
)
32+
));
33+
34+
var publicEvent = EventDeclaration(ParseTypeName($"LeagueClientEventHandler<{typeName}>"),
35+
Identifier(publicEventIdentifier))
36+
.WithModifiers(SyntaxKind.PublicKeyword.ToTokenList())
37+
.WithAccessorList(AccessorList(new SyntaxList<AccessorDeclarationSyntax>(new[]
38+
{
39+
AccessorDeclaration(SyntaxKind.AddAccessorDeclaration, ParseStatement("if (" + privateEventIdentifier + " == null)\r\n" +
40+
" EventRouter.Subscribe(\"" + topic + "\",\r\n"+
41+
"(" + typeName + " args) => " + privateEventIdentifier + "?.Invoke(this, args));\r\n\r\n"+
42+
"" + privateEventIdentifier + " += value;").ToBlock()),
43+
AccessorDeclaration(SyntaxKind.RemoveAccessorDeclaration, ParseStatement("" + privateEventIdentifier + " -= value;\r\n\r\n"+
44+
"if (" + privateEventIdentifier + " == null)\r\n"+
45+
" EventRouter.Unsubscribe(\"" + topic + "\");\r\n").ToBlock())
46+
})));
47+
48+
return new MemberDeclarationSyntax[] {privateEvent, publicEvent};
49+
}
50+
}
51+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net.Http.Json;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
#nullable disable
9+
10+
namespace RiotGames.Client.CodeGeneration.LeagueClient
11+
{
12+
internal class LeagueClientHelp
13+
{
14+
public Dictionary<string, string> Events { get; set; }
15+
public Dictionary<string, string> Types { get; set; }
16+
public Dictionary<string, string> Functions { get; set; }
17+
18+
public static async Task<LeagueClientHelp> DownloadAsync()
19+
{
20+
using var client = new HttpClient();
21+
var schema = await client.GetFromJsonAsync<LeagueClientHelp>("https://www.mingweisamuel.com/lcu-schema/lcu/help.json");
22+
return schema ?? throw new Exception("We didn't get any help.json from the server!");
23+
}
24+
}
25+
}
26+
27+
#nullable restore
Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
6-
using MingweiSamuel;
7-
using MingweiSamuel.Lcu;
8-
9-
namespace RiotGames.Client.CodeGeneration.LeagueClient
10-
{
11-
using Path = KeyValuePair<string, OpenApiPathObject<OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, LcuParameterObject, LcuSchemaObject>>;
12-
using Paths = IEnumerable<KeyValuePair<string, OpenApiPathObject<OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, LcuParameterObject, LcuSchemaObject>>>;
13-
14-
internal static class LeagueClientLinqQueries
15-
{
16-
public static IEnumerable<IGrouping<string?, Path>> GroupByModule(this Paths paths) =>
17-
paths.GroupBy(p => p.Key.Count(c => c == '/') == 1 ? null : p.Key.SplitAndRemoveEmptyEntries('/')[0]);
18-
}
19-
}
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using MingweiSamuel;
7+
using MingweiSamuel.Lcu;
8+
9+
namespace RiotGames.Client.CodeGeneration.LeagueClient
10+
{
11+
using Path = KeyValuePair<string, OpenApiPathObject<OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, LcuParameterObject, LcuSchemaObject>>;
12+
using Paths = IEnumerable<KeyValuePair<string, OpenApiPathObject<OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, OpenApiMethodObject<LcuParameterObject, LcuSchemaObject>, LcuParameterObject, LcuSchemaObject>>>;
13+
14+
internal static class LeagueClientLinqQueries
15+
{
16+
public static IEnumerable<IGrouping<string?, Path>> GroupByModule(this Paths paths) =>
17+
paths.GroupBy(p => p.Key.Count(c => c == '/') == 1 ? null : p.Key.SplitAndRemoveEmptyEntries('/')[0]);
18+
19+
/// <summary>
20+
/// Groups events by module.
21+
/// </summary>
22+
public static IEnumerable<IGrouping<string, KeyValuePair<string, string>>> GroupByModule(this IEnumerable<KeyValuePair<string, string>> events)
23+
{
24+
var jsonApiEvents = events.Where(e => e.Key.StartsWith("OnJsonApiEvent_"));
25+
return jsonApiEvents.GroupBy(e => e.Key.RemoveStart("OnJsonApiEvent_").SplitAndRemoveEmptyEntries('_')[0]);
26+
}
27+
}
28+
29+
internal static class LeagueClientRandomExtensions
30+
{
31+
public static string EventToPath(this string @event) => @event.Replace("OnJsonApiEvent", "").Replace("_", "/");
32+
33+
public static bool EventEqualsPath(this string @event, string path) => @event.EventToPath() == path.Trim('\"');
34+
}
35+
}

0 commit comments

Comments
 (0)