switch to golang wrapper for enet

This commit is contained in:
Travis Shears 2026-04-13 13:50:21 +02:00
parent 4420b4e061
commit ee3b8d3490
Signed by: travisshears
GPG key ID: CB9BF1910F3F7469
10 changed files with 188 additions and 194 deletions

50
backend/.gitignore vendored
View file

@ -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

View file

@ -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>

View file

@ -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"]

View file

@ -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.");
}
}
}
}
}

View file

@ -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
View 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"

View 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
View 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
View 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
View 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()
}