Skip to content

Commit

Permalink
Allocation-less command system
Browse files Browse the repository at this point in the history
  • Loading branch information
jvyden committed Sep 2, 2023
1 parent 064289f commit 5cbd185
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 21 deletions.
23 changes: 12 additions & 11 deletions Refresh.GameServer/Services/CommandService.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Services;
using JetBrains.Annotations;
Expand Down Expand Up @@ -44,34 +45,34 @@ public void StopPublishing(ObjectId id)
/// <summary>
/// Parse a command string into a command object
/// </summary>
/// <param name="str">Command string</param>
/// <param name="input">Command string</param>
/// <returns>Parsed command</returns>
/// <exception cref="FormatException">When the command is in an invalid format</exception>
[Pure]
public Command ParseCommand(string str)
public Command ParseCommand(ReadOnlySpan<char> input)
{
//Ensure the command string starts with a slash
if (str[0] != '/')
// Ensure the command string starts with a slash
if (input[0] != '/')
{
throw new FormatException("Commands must start with `/`");
}

int idx = str.IndexOf(" ", StringComparison.Ordinal);
int index = input.IndexOf(' ');

//If idx is 1, the command name is blank
// If index is 1, the command name is blank
// ReSharper disable once ConvertIfStatementToSwitchStatement
if (idx == 1)
if (index == 1)
{
throw new FormatException("Blank command name");
}

//If theres no space after, or if the space is the last character, then there are no arguments
if (idx == -1 || idx == str.Length - 1)
if (index == -1 || index == input.Length - 1)
{
return new Command(idx == str.Length - 1 ? str[1..idx] : str[1..], null);
return new Command(index == input.Length - 1 ? input[1..index] : input[1..], null);
}

return new Command(str[1..idx], str[(idx + 1)..]);
return new Command(input[1..index], input[(index + 1)..]);
}

public void HandleCommand(Command command, GameDatabaseContext database, GameUser user)
Expand All @@ -84,7 +85,7 @@ public void HandleCommand(Command command, GameDatabaseContext database, GameUse
throw new Exception("User not provided for force match command");
}

GameUser? target = database.GetUserByUsername(command.Arguments);
GameUser? target = database.GetUserByUsername(command.Arguments.ToString());

if (target != null)
{
Expand Down
12 changes: 11 additions & 1 deletion Refresh.GameServer/Types/Commands/Command.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
namespace Refresh.GameServer.Types.Commands;

public record Command(string Name, string? Arguments);
public ref struct Command
{
public Command(ReadOnlySpan<char> name, ReadOnlySpan<char> arguments)
{
this.Name = name;
this.Arguments = arguments;
}

public ReadOnlySpan<char> Name { get; init; }
public ReadOnlySpan<char> Arguments { get; init; }
}
1 change: 1 addition & 0 deletions Refresh.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=lolcatftw/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mmpicks/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mutuals/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=noargs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=npdata/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=NPEA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=NPEG/@EntryIndexedValue">True</s:Boolean>
Expand Down
32 changes: 23 additions & 9 deletions RefreshTests.GameServer/Tests/Commands/CommandParseTests.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
using System.Buffers;
using Bunkum.HttpServer;
using NotEnoughLogs;
using NotEnoughLogs.Loggers;
using Refresh.GameServer.Services;
using Refresh.GameServer.Types.Commands;

namespace RefreshTests.GameServer.Tests.Commands;

public class CommandParseTests : GameServerTest
{
#pragma warning disable NUnit2045
private void ParseTest(CommandService service, ReadOnlySpan<char> input, ReadOnlySpan<char> expectedName, ReadOnlySpan<char> expectedArguments)
{
Command command = service.ParseCommand(input);
Assert.That(command.Name.SequenceEqual(expectedName), Is.True, $"Expected '{command.Name}' to equal '{expectedName}'");
Assert.That(command.Arguments.SequenceEqual(expectedArguments), Is.True, $"Expected '{command.Arguments}' to equal '{expectedArguments}'");
}
#pragma warning restore NUnit2045

[Test]
public void ParsingTest()
{
LoggerContainer<BunkumContext> logger = new();
using LoggerContainer<BunkumContext> logger = new();
logger.RegisterLogger(new ConsoleLogger());
CommandService service = new(logger, new MatchService(logger));

Assert.That(service.ParseCommand("/parse test"), Is.EqualTo(new Command("parse", "test")));
Assert.That(service.ParseCommand("/noargs"), Is.EqualTo(new Command("noargs", null)));
Assert.That(service.ParseCommand("/noargs "), Is.EqualTo(new Command("noargs", null)));
ParseTest(service, "/parse test", "parse", "test");
ParseTest(service, "/noargs", "noargs", "");
ParseTest(service, "/noargs ", "noargs", "");
}

[Test]
public void NoSlashThrows()
{
LoggerContainer<BunkumContext> logger = new();
using LoggerContainer<BunkumContext> logger = new();
logger.RegisterLogger(new ConsoleLogger());
CommandService service = new(logger, new MatchService(logger));

Assert.That(() => service.ParseCommand("parse test"), Throws.Exception);
Assert.That(() => _ = service.ParseCommand("parse test"), Throws.InstanceOf<FormatException>());
}

[Test]
public void BlankCommandThrows()
{
LoggerContainer<BunkumContext> logger = new();
using LoggerContainer<BunkumContext> logger = new();
logger.RegisterLogger(new ConsoleLogger());
CommandService service = new(logger, new MatchService(logger));

Assert.That(() => service.ParseCommand("/ test"), Throws.Exception);
Assert.That(() => _ = service.ParseCommand("/ test"), Throws.InstanceOf<FormatException>());
}
}

0 comments on commit 5cbd185

Please sign in to comment.