switch to golang wrapper for enet
This commit is contained in:
parent
4420b4e061
commit
ee3b8d3490
10 changed files with 188 additions and 194 deletions
50
backend/.gitignore
vendored
50
backend/.gitignore
vendored
|
|
@ -1,50 +0,0 @@
|
||||||
# Build results
|
|
||||||
bin/
|
|
||||||
obj/
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
|
|
||||||
# Visual Studio
|
|
||||||
.vs/
|
|
||||||
.vscode/
|
|
||||||
*.sln.iml
|
|
||||||
*.sln.docstates
|
|
||||||
*.user
|
|
||||||
*.userprefs
|
|
||||||
*.userosscache
|
|
||||||
*.sln.user
|
|
||||||
|
|
||||||
# ReSharper
|
|
||||||
.idea/
|
|
||||||
*.resharper
|
|
||||||
*.resharper.user
|
|
||||||
*.DotSettings.user
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
# Local environment
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
appsettings.Development.json
|
|
||||||
|
|
||||||
# NuGet
|
|
||||||
.nuget/
|
|
||||||
*.nupkg
|
|
||||||
*.snupkg
|
|
||||||
packages/
|
|
||||||
|
|
||||||
# Rider
|
|
||||||
.idea/
|
|
||||||
*.sln.iml
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# macOS
|
|
||||||
.DS_Store
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<RootNamespace>Love2DBackend</RootNamespace>
|
|
||||||
<AssemblyName>Backend</AssemblyName>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,14 +1,44 @@
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
# Build stage
|
||||||
WORKDIR /build
|
FROM golang:1.25-bookworm AS builder
|
||||||
COPY Backend.csproj .
|
|
||||||
RUN dotnet restore
|
|
||||||
COPY . .
|
|
||||||
RUN dotnet build -c Release
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/runtime:10.0
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /build/bin/Release/net10.0 .
|
|
||||||
ENV ENET_HOST=0.0.0.0
|
# Install dependencies including libenet-dev from Debian repos
|
||||||
ENV ENET_PORT=7777
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
EXPOSE 7777/udp
|
git \
|
||||||
ENTRYPOINT ["./Backend"]
|
gcc \
|
||||||
|
pkg-config \
|
||||||
|
libenet-dev \
|
||||||
|
sqlite3 \
|
||||||
|
libsqlite3-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy go mod and sum files
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY ./*.go .
|
||||||
|
|
||||||
|
# Enable CGO for go-sqlite3 and go-enet
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
|
||||||
|
RUN go build -o main .
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install runtime dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libenet7 \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /app/main .
|
||||||
|
|
||||||
|
EXPOSE 8095
|
||||||
|
|
||||||
|
CMD ["./main"]
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Love2DBackend
|
|
||||||
{
|
|
||||||
class Program
|
|
||||||
{
|
|
||||||
static async Task Main(string[] args)
|
|
||||||
{
|
|
||||||
Console.WriteLine("LÖVE2D Backend - UDP Server");
|
|
||||||
Console.WriteLine("Starting server on port 7777...");
|
|
||||||
|
|
||||||
using (var udpServer = new UdpClient(7777))
|
|
||||||
{
|
|
||||||
Console.WriteLine("Server listening on 0.0.0.0:7777");
|
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
|
||||||
|
|
||||||
// Handle Ctrl+C gracefully
|
|
||||||
Console.CancelKeyPress += (s, e) =>
|
|
||||||
{
|
|
||||||
e.Cancel = true;
|
|
||||||
cts.Cancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!cts.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
var result = await udpServer.ReceiveAsync(cts.Token);
|
|
||||||
var message = Encoding.UTF8.GetString(result.Buffer);
|
|
||||||
var clientEndpoint = result.RemoteEndPoint;
|
|
||||||
|
|
||||||
Console.WriteLine($"[{clientEndpoint}] {message}");
|
|
||||||
|
|
||||||
// Echo back
|
|
||||||
var response = Encoding.UTF8.GetBytes($"Echo: {message}");
|
|
||||||
await udpServer.SendAsync(response, response.Length, clientEndpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Server stopped.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +1,30 @@
|
||||||
# ENET Gaming Backend
|
# Löve2D ENET Gaming Backend
|
||||||
|
|
||||||
A multiplayer game server written in C# using ENet for networking.
|
A multiplayer game server written in GO Lang wrapping ENet for networking.
|
||||||
Designed to support mutiple of my LÖVE2D game experiments at once.
|
Designed to support mutiple of my LÖVE2D game experiments at once.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ LÖVE2D Game Clients │
|
|
||||||
│ (Fennel/Lua with Love2D Framework) │
|
|
||||||
└────────────────────────────────┬────────────────────────────┘
|
|
||||||
│ (UDP via ENet)
|
|
||||||
│
|
|
||||||
┌────────────┴────────────┐
|
|
||||||
│ ENet-CSharp Server │
|
|
||||||
│ (Multiplayer Backend) │
|
|
||||||
└────────────┬────────────┘
|
|
||||||
│
|
|
||||||
┌────────────┴────────────┐
|
|
||||||
│ Game State Manager │
|
|
||||||
│ Session Storage │
|
|
||||||
│ Player Coordination │
|
|
||||||
└────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- **Language**: C# (.NET)
|
- Language: Go Lang
|
||||||
- **Networking**: [ENet-CSharp](https://github.com/nxrighthere/ENet-CSharp) - reliable UDP multiplayer protocol
|
- Networking:
|
||||||
- **Deployment**: Docker + Nomad
|
- https://github.com/codecat/go-enet
|
||||||
- **Port**: `7777` (UDP, configurable)
|
- http://enet.bespin.org/index.html
|
||||||
|
- Docker image
|
||||||
|
- Nomad deployment
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Prerequisites
|
## Local Development
|
||||||
|
|
||||||
- .NET 8.0 or later
|
```shell
|
||||||
- ENet-CSharp (vendored or via NuGet)
|
$ docker compose up --build
|
||||||
- Docker (for containerization)
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
dotnet build
|
|
||||||
dotnet run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The server will start and listen on `127.0.0.1:7777` by default.
|
## Prod Deployment
|
||||||
|
|
||||||
### Testing Connection
|
```shell
|
||||||
|
$ ./build.sh
|
||||||
With a LÖVE2D client configured to connect to `localhost:7777`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
love ../two_player_cleaning_game
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
Then go to homelab repo and deploy the nomad job
|
||||||
|
|
||||||
Server behavior is controlled via environment variables:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ENET_HOST=0.0.0.0 # Bind address
|
|
||||||
ENET_PORT=7777 # UDP port
|
|
||||||
```
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Learn X In Y C#](https://learnxinyminutes.com/csharp/)
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- [ ] Set up C# project structure
|
|
||||||
- [ ] Integrate ENet-CSharp dependency
|
|
||||||
- [ ] Implement basic server startup
|
|
||||||
- [ ] Define message protocol
|
|
||||||
- [ ] Create player session management
|
|
||||||
- [ ] Build Docker image
|
|
||||||
- [ ] Test Nomad deployment
|
|
||||||
- [ ] Implement game state synchronization
|
|
||||||
|
|
|
||||||
16
backend/build.sh
Executable file
16
backend/build.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export AWS_PROFILE=personal
|
||||||
|
export AWS_REGION=eu-central-1
|
||||||
|
|
||||||
|
REPO_NAME="love2d-backend-homelabstack"
|
||||||
|
|
||||||
|
if ! aws ecr describe-repositories --repository-names "$REPO_NAME" >/dev/null 2>&1; then
|
||||||
|
aws ecr create-repository --repository-name "$REPO_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 -t 853019563312.dkr.ecr.eu-central-1.amazonaws.com/gemini-capsule-homelabstack:latest --push .
|
||||||
|
|
||||||
|
echo "Docker image built and pushed to AWS ECR"
|
||||||
8
backend/docker-compose.yml
Normal file
8
backend/docker-compose.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: love2d-backend-dev
|
||||||
|
ports:
|
||||||
|
- "8095:8095"
|
||||||
16
backend/go.mod
Normal file
16
backend/go.mod
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
module love2d_backend
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/codecat/go-libs v0.0.0-20210906174629-ffa6674c8e05
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/codecat/go-enet v0.0.0-20250728072647-ae229138f138
|
||||||
|
github.com/fatih/color v1.19.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
|
)
|
||||||
15
backend/go.sum
Normal file
15
backend/go.sum
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
github.com/codecat/go-enet v0.0.0-20250728072647-ae229138f138 h1:qQaqly9oVi84i4nLWmM551z7kcFZIy5koCLoPwCarGY=
|
||||||
|
github.com/codecat/go-enet v0.0.0-20250728072647-ae229138f138/go.mod h1:sXkhNvv8T1d/aPEwipUzk7rGWwGJ3uJST2P/Z0zE0L4=
|
||||||
|
github.com/codecat/go-libs v0.0.0-20210906174629-ffa6674c8e05 h1:JSfDXHJvrIpQ8Agy//yoIlGpfIprTCDUytmf68fd/Lc=
|
||||||
|
github.com/codecat/go-libs v0.0.0-20210906174629-ffa6674c8e05/go.mod h1:xJW98cHEb+Kbuu0qmoKzExh3blthZqojIYOFo27VgvE=
|
||||||
|
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||||
|
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
76
backend/main.go
Normal file
76
backend/main.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
enet "github.com/codecat/go-enet"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := uint16(8095)
|
||||||
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
// Initialize enet
|
||||||
|
slog.Info("Initializing E-Net Server", "port", port)
|
||||||
|
enet.Initialize()
|
||||||
|
|
||||||
|
// Create a host listening on 0.0.0.0:8095
|
||||||
|
host, err := enet.NewHost(enet.NewListenAddress(port), 32, 1, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Couldn't create host", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("Server started", "port", port)
|
||||||
|
|
||||||
|
// The event loop
|
||||||
|
for true {
|
||||||
|
// Wait until the next event
|
||||||
|
ev := host.Service(1000)
|
||||||
|
|
||||||
|
// Do nothing if we didn't get any event
|
||||||
|
if ev.GetType() == enet.EventNone {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ev.GetType() {
|
||||||
|
case enet.EventConnect: // A new peer has connected
|
||||||
|
slog.Info("New peer connected", "address", ev.GetPeer().GetAddress())
|
||||||
|
|
||||||
|
case enet.EventDisconnect: // A connected peer has disconnected
|
||||||
|
slog.Info("Peer disconnected", "address", ev.GetPeer().GetAddress())
|
||||||
|
|
||||||
|
case enet.EventReceive: // A peer sent us some data
|
||||||
|
// Get the packet
|
||||||
|
packet := ev.GetPacket()
|
||||||
|
|
||||||
|
// We must destroy the packet when we're done with it
|
||||||
|
defer packet.Destroy()
|
||||||
|
|
||||||
|
// Get the bytes in the packet
|
||||||
|
packetBytes := packet.GetData()
|
||||||
|
|
||||||
|
// Respond "pong" to "ping"
|
||||||
|
if string(packetBytes) == "ping" {
|
||||||
|
ev.GetPeer().SendString("pong", ev.GetChannelID(), enet.PacketFlagReliable)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect the peer if they say "bye"
|
||||||
|
if string(packetBytes) == "bye" {
|
||||||
|
slog.Info("Peer sent bye, disconnecting")
|
||||||
|
ev.GetPeer().Disconnect(0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the host when we're done with it
|
||||||
|
host.Destroy()
|
||||||
|
|
||||||
|
// Uninitialize enet
|
||||||
|
enet.Deinitialize()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue