Skip to content

Commit

Permalink
localnet fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasteles committed Mar 15, 2024
1 parent 6fe0d83 commit 5370d89
Show file tree
Hide file tree
Showing 34 changed files with 286 additions and 126 deletions.
2 changes: 1 addition & 1 deletion .run/SpaceWar.Lobby (9000).run.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SpaceWar.Lobby (9000)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0/SpaceWar.exe" />
<option name="PROGRAM_PARAMETERS" value="9000" />
<option name="PROGRAM_PARAMETERS" value="-LocalPort 9000" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
Expand Down
2 changes: 1 addition & 1 deletion .run/SpaceWar.Lobby (9001).run.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SpaceWar.Lobby (9001)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0/SpaceWar.exe" />
<option name="PROGRAM_PARAMETERS" value="9001" />
<option name="PROGRAM_PARAMETERS" value="-LocalPort 9001" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
Expand Down
2 changes: 1 addition & 1 deletion .run/SpaceWar.Lobby (9002).run.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SpaceWar.Lobby (9002)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0/SpaceWar.exe" />
<option name="PROGRAM_PARAMETERS" value="9002" />
<option name="PROGRAM_PARAMETERS" value="-LocalPort 9002" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
Expand Down
2 changes: 1 addition & 1 deletion .run/SpaceWar.Lobby (local 9100).run.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SpaceWar.Lobby (local 9100)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0/SpaceWar.exe" />
<option name="PROGRAM_PARAMETERS" value="9100 &quot;http://localhost:9999&quot;" />
<option name="PROGRAM_PARAMETERS" value="-LocalPort 9100 -ServerUrl &quot;http://localhost:9999&quot;" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/samples/SpaceWar.Lobby/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
Expand Down
1 change: 1 addition & 0 deletions samples/LobbyServer/LobbyRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ IOptions<AppSettings> settings
Peer peer = new(userName, remote)
{
PeerId = peerId,
LocalEndpoint = req.LocalEndpoint,
};

LobbyEntry entry = new(peer, req.Mode)
Expand Down
9 changes: 8 additions & 1 deletion samples/LobbyServer/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ public sealed class Peer(string username, IPAddress requestAddress)
public PeerId PeerId { get; init; } = Guid.NewGuid();
public string Username { get; } = username;
public IPAddress RequestAddress { get; } = requestAddress;
public IPEndPoint? LocalEndpoint { get; init; }
public IPEndPoint? Endpoint { get; set; }
public bool Ready { get; private set; }
public bool Connected => Endpoint is not null;

public void ToggleReady() => Ready = !Ready;
}

Expand Down Expand Up @@ -153,7 +155,12 @@ public void Purge(DateTimeOffset now)
}
}

public sealed record EnterLobbyRequest(string LobbyName, string Username, PeerMode Mode);
public sealed record EnterLobbyRequest(
string LobbyName,
string Username,
PeerMode Mode,
IPEndPoint? LocalEndpoint = null
);

public sealed record EnterLobbyResponse(
string Username,
Expand Down
2 changes: 0 additions & 2 deletions samples/LobbyServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@
})
.Configure<ForwardedHeadersOptions>(o => o.ForwardedHeaders = ForwardedHeaders.XForwardedFor)
.AddMemoryCache()
.AddHttpLogging(_ => { })
.AddSingleton(TimeProvider.System)
.AddSingleton<LobbyRepository>();

builder.Services.AddHostedService<UdpListenerService>();

var app = builder.Build();
Console.Title = app.Environment.ApplicationName;
app.UseHttpLogging();
app.UseForwardedHeaders();
app.UseSwagger().UseSwaggerUI();

Expand Down
1 change: 0 additions & 1 deletion samples/LobbyServer/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
Expand Down
54 changes: 48 additions & 6 deletions samples/SpaceWar.Lobby/AppSettings.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,55 @@
using System.Reflection;
using System.Text.Json;

namespace SpaceWar;

[Serializable]
public class AppSettings
{
public string LobbyName = "spacewar";
public string Username = string.Empty;
public int Port = 9000;
public string Username { get; set; } = string.Empty;
public required string LobbyName { get; set; }
public required Uri ServerUrl { get; set; }
public int LocalPort { get; set; }
public int ServerUdpPort { get; set; }

public void ParseArgs(string[] args)
{
if (args is []) return;

var argsDict = args
.Chunk(2)
.Where(a => a[0].StartsWith('-'))
.Select(a => a is [{ } key, { } value]
? (Key: key.TrimStart('-'), Value: value)
: throw new InvalidOperationException("Bad arguments")
).ToDictionary(x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase);

if (argsDict.TryGetValue(nameof(LocalPort), out var portArg) &&
int.TryParse(portArg, out var port) && port > 0)
LocalPort = port;

if (argsDict.TryGetValue(nameof(ServerUdpPort), out var lobbyPortArg) &&
int.TryParse(lobbyPortArg, out var lobbyPort) && lobbyPort > 0)
ServerUdpPort = lobbyPort;

if (argsDict.TryGetValue(nameof(ServerUrl), out var serverUrl) &&
Uri.TryCreate(serverUrl, UriKind.Absolute, out var serverUri))
ServerUrl = serverUri;

if (argsDict.TryGetValue(nameof(Username), out var usernameArg) &&
!string.IsNullOrWhiteSpace(usernameArg))
Username = usernameArg;
}

public int LobbyPort = 8888;
public static AppSettings LoadFromJson(string file)
{
var settingsFile = Path.Combine(
Path.GetDirectoryName(AppContext.BaseDirectory)
?? Directory.GetCurrentDirectory(),
file
);

public Uri LobbyUrl = new("https://lobby-server.fly.dev");
// public readonly Uri LobbyUrl = new("http://localhost:9999");
return JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(settingsFile))
?? throw new InvalidOperationException($"unable to read {file}");
}
}
2 changes: 1 addition & 1 deletion samples/SpaceWar.Lobby/Game1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public Game1(AppSettings appSettings)

protected override void Initialize()
{
Window.Title = $"SpaceWar {settings.Port}";
Window.Title = $"SpaceWar {settings.LocalPort}";
graphics.PreferredBackBufferWidth = Config.InternalWidth;
graphics.PreferredBackBufferHeight = Config.InternalHeight;
graphics.ApplyChanges();
Expand Down
1 change: 1 addition & 0 deletions samples/SpaceWar.Lobby/Models/Peer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public sealed class Peer
public required Guid PeerId { get; init; }
public required string Username { get; init; }
public required IPEndPoint Endpoint { get; init; }
public IPEndPoint? LocalEndpoint { get; init; }
public bool Connected { get; init; }
public bool Ready { get; init; }
}
42 changes: 2 additions & 40 deletions samples/SpaceWar.Lobby/Program.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using SpaceWar;

AppSettings settings = new();
ReadProgramArgs();
var settings = AppSettings.LoadFromJson("appsettings.json");
settings.ParseArgs(args);

using var game = new Game1(settings);
game.Run();


void ReadProgramArgs()
{
if (TryGetConfig(0, "SPACEWAR_PORT", out var portArg) &&
int.TryParse(portArg, out var port))
settings.Port = port;

if (TryGetConfig(1, "SPACEWAR_LOBBY_URL", out var serverUrlArg)
&& Uri.TryCreate(serverUrlArg, UriKind.Absolute, out var serverUrl))
settings.LobbyUrl = serverUrl;

if (TryGetConfig(2, "SPACEWAR_LOBBY_PORT", out var lobbyPortArg)
&& int.TryParse(lobbyPortArg, out var lobbyPort))
settings.LobbyPort = lobbyPort;
}

bool TryGetConfig(int argsIndex, string envName,
[NotNullWhen(true)] out string? argValue)
{
var tempValue = Environment.GetEnvironmentVariable(envName);
if (!string.IsNullOrWhiteSpace(tempValue))
{
argValue = tempValue;
return true;
}

tempValue = args.ElementAtOrDefault(argsIndex);
if (!string.IsNullOrWhiteSpace(tempValue))
{
argValue = tempValue;
return true;
}

argValue = null;
return false;
}
95 changes: 82 additions & 13 deletions samples/SpaceWar.Lobby/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,86 @@
# SpaceWar - NetCode Sample
## Running
Run one of the scripts in [/scripts](/samples/SpaceWar/scripts) to start multiple clients of the game.
> On Windows run the `.cmd` [/scripts/windows](/samples/SpaceWar/scripts/windows)
> On Linux/Mac run the `.sh` files [/scripts/linux](/samples/SpaceWar/scripts/linux)
Each script defines a configuration with up to 4 players, some of them with spectators, they are:
- **start_2players**: start 2 game peer instances.
- **start_2players_1spec:** Start 2 game peer instances with a single spectator on player 1.
- **start_2players_2spec:** Start 2 game peer instances with two spectators. each observing one player.
- **start_3players:** Start 3 game peer instances.
- **start_4players:** Start 4 game peer instances.
- **start_4players_2spec:** Start 4 game peer instances with two spectators. one observing player 1 and the other
observing player 2.
# SpaceWar - NetCode Sample with online Lobby

This shows a basic example of NAT traversal using [UDP hole punching](https://en.wikipedia.org/wiki/UDP_hole_punching)

# How it works?

This enable a P2P connection over the internet, this is possible using
a [middle server](https://github.com/lucasteles/Backdash/tree/master/samples/LobbyServer)
which all clients know.
The server catches the IP address and port of a client and send it to the others.

The current server runs almost as a simple http with json responses. It keeps the lobbies info with sliding expiration
cache.

When a client enters the lobby the server responds with a token of type `Guid`/`UUID`. It is used a very
basic `Authentication` mechanism.

The client use http pooling to get updated information of each lobby member/peer.

When logged-in every client needs to send a `UDP` package with their token to the server. So the
server updates their `IP` and open `Port` using the package headers metadata.

> ⚠️ UDP Hole punching usually **does not** work witch clients behind the same NAT. To mitigate this the server
> also tracks the clients local IPs and ports. So they can check if the peer is at the same network
## Controls

- **Arrows**: Move
- **Left Control**: Fire
- **Enter**: Missile

## Running

### Server

On the [server directory](https://github.com/lucasteles/Backdash/tree/master/samples/LobbyServer) run:

```bash
dotnet run .
```

- Default **HTTP**: `9999`
- Default **UDP** : `8888`

> 💡 Check the swagger `API` docs at http://localhost:9999/swagger
### Clients

On `SpaceWar.Lobby` project directory run

```bash
dotnet run .
```

The default client configuration is defined in this [JSON file](/appsettings.json):

```json
{
"LobbyName": "spacewar",
"LocalPort": 9000,
"ServerUrl": "http://localhost:9999",
"ServerUdpPort": 8888
}
```

You can override the default port via command args:

```sh
dotnet run --project .\LobbyClient -LocalPort 9001
```

> 💡useful for starting clients in different ports

You can also override the server URL and UDP Port configs:

```bash
dotnet run --project .\LobbyClient -ServerUrl "https://lobby-server.fly.dev" -ServerUdpPort 8888
```

Check the the [scripts directory](https://github.com/lucasteles/Backdash/tree/master/samples/SpaceWar.Lobby/scripts)
to run local instances of the server and clients.

> ⚠️ The default configured server is a remote server. To connect to localhost server
> run: `dotnet run . -ServerUrl "http://localhost:9999"`
9 changes: 7 additions & 2 deletions samples/SpaceWar.Lobby/Scenes/BattleSessionScene.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Net;
using Backdash;
using Backdash.Core;
using SpaceWar.Logic;
Expand Down Expand Up @@ -39,11 +40,15 @@ public BattleSessionScene(int port, IReadOnlyList<Player> players,
rollbackSession.AddPlayers(players);
}

public BattleSessionScene(int port, int playerCount, Peer host, IReadOnlyList<Peer> peersInfo)
public BattleSessionScene(
int port, int playerCount,
IPEndPoint host,
IReadOnlyList<Peer> peersInfo
)
{
this.peersInfo = peersInfo;
rollbackSession = RollbackNetcode.CreateSpectatorSession<PlayerInputs, GameState>(
port, host.Endpoint, playerCount, options
port, host, playerCount, options
);
}

Expand Down
2 changes: 1 addition & 1 deletion samples/SpaceWar.Lobby/Scenes/ChooseNameScene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public override void Initialize()
? Environment.UserName
: Config.Username;

username.Append(Regex.Replace(currentUser, "[^a-zA-Z0-9]", "_"));
username.Append(Regex.Replace(currentUser.ToLower(), "[^a-zA-Z0-9]", "_"));
cursorSize = Assets.MainFont.MeasureString(" ");
Window.TextInput += OnTextInput;
keyboard.Update();
Expand Down
Loading

0 comments on commit 5370d89

Please sign in to comment.