Merge branch 'main' into 'feat/network-manager'
This commit is contained in:
+18
-2
@@ -104,5 +104,21 @@ $RECYCLE.BIN/
|
||||
## Jenv
|
||||
.java-version
|
||||
|
||||
## VSCode
|
||||
.vscode
|
||||
## diagrams.net / draw.io
|
||||
# Draw.io temporary files
|
||||
*.drawio.bkp
|
||||
*.drawio.tmp
|
||||
|
||||
# Auto-save files
|
||||
*.drawio.autosave
|
||||
|
||||
# Exported images (optional – nur wenn du Exporte nicht versionieren willst)
|
||||
*.drawio.png
|
||||
*.drawio.svg
|
||||
*.drawio.pdf
|
||||
|
||||
## IntelliJ
|
||||
.idea
|
||||
|
||||
## bin
|
||||
bin/
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"vscjava.vscode-java-pack",
|
||||
"mhutchie.git-graph",
|
||||
"gitlab.gitlab-workflow",
|
||||
]
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Export drawio to SVG",
|
||||
"type": "shell",
|
||||
"command": "./scripts/export-drawio.sh",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Export puml to SVG",
|
||||
"type": "shell",
|
||||
"command": "./scripts/export-plantuml.sh",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -35,6 +35,8 @@ This protocol handles game state synchronization, player actions, lobby and glob
|
||||
│ └── test/ # Unit tests
|
||||
├── documents/ # Project resources and documentation
|
||||
│ ├── images/ # Images for README and documentation
|
||||
│ ├── diary # Diary files for each organizational meetup
|
||||
│ ├── blog # Blog files covering project-related topics
|
||||
│ ├── docs # Documentation
|
||||
│ ├── milestones/ # Milestone deliverables (6 milestones)
|
||||
│ └── (Other resources) # Additional project materials
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
+8
-1
@@ -23,10 +23,17 @@ repositories {
|
||||
|
||||
javafx {
|
||||
version = "25.0.2"
|
||||
modules = ['javafx.controls', 'javafx.fxml', 'javafx.base']
|
||||
modules = ['javafx.controls', 'javafx.fxml', 'javafx.base', 'javafx.graphics', 'javafx.web']
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Source: https://mvnrepository.com/artifact/org.apache.logging.log4j
|
||||
implementation("org.apache.logging.log4j:log4j-api:2.25.3")
|
||||
runtimeOnly("org.apache.logging.log4j:log4j-core:2.25.3")
|
||||
|
||||
// Source: https://mvnrepository.com/artifact/org.fusesource.jansi/jansi
|
||||
runtimeOnly("org.fusesource.jansi:jansi:2.4.2")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Blog
|
||||
|
||||
## Purpose
|
||||
This folder contains a collection of blog posts documenting the development process of our application. These blogs serve two key purposes:
|
||||
|
||||
1. **For External Audiences**
|
||||
|
||||
The blogs provide insights into the internal program flow and the interaction between components without requiring readers to dive into the source code. They offer a mixed-level overview of how different parts of the system work together.
|
||||
|
||||
2. **For Our Team**
|
||||
|
||||
The blogs serve as a record of our decision-making process. By documenting what decisions were made and why, we can track the evolution of our design choices and understand the reasoning behind them. This is invaluable for maintaining consistency across the team.
|
||||
|
||||
But keep in mind that these blogs **do not replace** the detailed source code documentation.
|
||||
|
||||
## Format
|
||||
All blog posts follow the naming convention: `<YYYY-MM-DD>_<Title>.md`
|
||||
|
||||
## Blog Posts
|
||||
|
||||
### General
|
||||
*(No posts yet)*
|
||||
|
||||
### Client
|
||||
*(No posts yet)*
|
||||
|
||||
### Server
|
||||
*(No posts yet)*
|
||||
@@ -0,0 +1,86 @@
|
||||
# Server-Side Networking — How It All Fits Together
|
||||
|
||||
This doc is meant to give a solid mixed-level understanding of how our server-side networking works. No deep dives — just a clear picture of what's happening under the hood when a client connects and sends a command.
|
||||
|
||||
> **Note:** This is v1. Annotations and Reflection-based dispatching are on the roadmap but not covered here yet.
|
||||
|
||||
> **Disclamer:** This document has been written by Claude. I modified certain parts and verified its contents for correctness.
|
||||
|
||||
## Our Protocol at a Glance
|
||||
|
||||
Our protocol is inspired by **POP3** - a classic, text-based protocol that communicates over a raw TCP connection. The idea is simple: the client sends a command as a plain-text string, and the server responds with either a success or an error.
|
||||
|
||||
A typical exchange looks something like this:
|
||||
|
||||
```
|
||||
Client → GET_DELTA SINCE=42
|
||||
Server → +OK
|
||||
PLAYER_FOLDED playerId=3
|
||||
POT 240
|
||||
NEXT_TURN playerId=1
|
||||
.
|
||||
```
|
||||
|
||||
Responses start with `+OK` on success or `-ERR` when something goes wrong. Commands are short, uppercase strings - sometimes followed by arguments.
|
||||
|
||||
We don't use HTTP, there's no JSON body, no headers. Just a raw socket, a text stream, and a clearly defined set of commands.
|
||||
|
||||
## Core Components & Their Roles
|
||||
|
||||

|
||||
|
||||
Here's a quick rundown of the main building blocks:
|
||||
|
||||
### `NetworkManager`
|
||||
This is the entry point. It binds to a specific port and listens for incoming TCP connections.
|
||||
Once a client connects, it creates a `Session` adds it to the `SessionManager` and goes back to waiting. Its not the responsibility of the `NetworkManager` to recieve and send data from and to each connected client.
|
||||
|
||||
### `Session`
|
||||
Every client that connects has its own `Session` instance running on its own thread. This component owns the lifecycle of that connection: it reads incoming data, passes it along for processing, and writes responses back.
|
||||
|
||||
### `SessionId`
|
||||
The `SessionId` is used to uniquely identify each session. Essentially its a wrapper arround the UUID type.
|
||||
|
||||
### `SessionManager`
|
||||
The `SessionManager` stores all currently active sessions. It allows for the retrieval of a specific session by its id.
|
||||
|
||||
### `ProtocolParser`
|
||||
Raw text (encapsulated in a `RawRequest`) coming off the socket isn't immediately useful — the `ProtocolParser` turns it with the help of the `Tokenizer` into a `PrimitiveRequest`.
|
||||
|
||||
### `RawRequest`
|
||||
The `RawRequest` object is created after a message has been recieved.
|
||||
It holds both a reference to the `Session` that recieved the message and the message itself.
|
||||
|
||||
### `PrimitiveRequest`
|
||||
The `PrimitiveRequest` object is created by the `ProtocolParser` after the content of the `RawRequest` has been tokenized by the `Tokenizer`.
|
||||
The command is stored in its own attribute and the arguments are accessible as a dictionary.
|
||||
|
||||
### `Tokenizer`
|
||||
The job of the `tokenizer` is it, to take the raw string and turn it into tokens.
|
||||
For example, the command `CHAT_LOBBY GAME=12 MESSAGE='All-in?'` will be decoded as `"CHAT_LOBBY", "GAME", "=", "12", "MESSAGE", "=", "All-in?"`.
|
||||
It is unaware about the meaning.
|
||||
|
||||
### `CommandParser` (per command)
|
||||
Takes in the `PrimitiveRequest` and checks if all required fields are provided with the correct value.
|
||||
Creates a command specific `Request` object containing command arguments in a structured manner.
|
||||
|
||||
### `Request` (per command)
|
||||
Has fields common along each implementation such as a reference to the `Session` that recieved the request.
|
||||
Furthermore each implementation has fields unique to each command. For example, the `ChatLobbyRequest` has fields for the game and the message.
|
||||
|
||||
### `CommandRouter`
|
||||
By identifying the type of class of the `Request`, the `CommandRouter` routes the request to the matching `CommandHandler`.
|
||||
|
||||
### `CommandHandler` (per command)
|
||||
Each supported command has its own handler — a small, focused class that contains the logic for that specific command. They have access to the domain, containing inner parts of the game itself.
|
||||
|
||||
### `Response` (per response)
|
||||
The interface has two sub-interfaces for either failed `ErrorResponse` or successfull `SuccessResponse` execution of commands.
|
||||
|
||||
Each step has exactly one responsibility. Increasing the ability to test and extend different components later on.
|
||||
|
||||
## Concurrency - Handling Multiple Clients
|
||||
|
||||
Every incoming connection spawns a new **Thread**. This means multiple clients can be served simultaneously without blocking each other.
|
||||
|
||||
As each `Session` runs entirely on its own thread, there's no shared mutable state between sessions, with exception of the `SessionManager` and other key components explained later.
|
||||
@@ -0,0 +1,96 @@
|
||||
@startuml
|
||||
skinparam classAttributeIconSize 0
|
||||
skinparam packageStyle rectangle
|
||||
skinparam linetype ortho
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
' Network Layer
|
||||
class NetworkManager {
|
||||
+ start(port: int)
|
||||
}
|
||||
|
||||
class Session {
|
||||
- id: SessionId
|
||||
+ Session(id: SessionId)
|
||||
+ getId(): SessionId
|
||||
+ run()
|
||||
+ send(response: String)
|
||||
}
|
||||
|
||||
class SessionId {
|
||||
- value: UUID
|
||||
+ SessionId()
|
||||
+ SessionId(value: UUID)
|
||||
}
|
||||
|
||||
class SessionManager {
|
||||
- sessions: Map<UUID, Session>
|
||||
+ add(session: Session)
|
||||
+ getById(id: UUID): Session
|
||||
+ remove(id: UUID)
|
||||
}
|
||||
|
||||
NetworkManager --> Session : creates
|
||||
NetworkManager --> SessionManager : adds session to
|
||||
Session --> SessionId : identified by
|
||||
Session --> RawRequest : creates
|
||||
Session ..> Response : sends
|
||||
SessionManager o-- Session : manages
|
||||
|
||||
' Protocol Layer
|
||||
class RawRequest {
|
||||
- session: Session
|
||||
- rawMessage: String
|
||||
}
|
||||
|
||||
class Tokenizer {
|
||||
+ tokenize(input: String): List<String>
|
||||
}
|
||||
|
||||
class ProtocolParser {
|
||||
+ parse(raw: RawRequest): PrimitiveRequest
|
||||
}
|
||||
|
||||
class PrimitiveRequest {
|
||||
- command: String
|
||||
- arguments: Map<String, String>
|
||||
}
|
||||
|
||||
RawRequest --> ProtocolParser : passed to
|
||||
ProtocolParser --> Tokenizer : uses
|
||||
ProtocolParser --> PrimitiveRequest : produces
|
||||
|
||||
' Command Layer
|
||||
interface CommandParser <<per command>> {
|
||||
+ parse(primitive: PrimitiveRequest): Request
|
||||
}
|
||||
|
||||
interface Request <<per command>> {
|
||||
- session: Session
|
||||
}
|
||||
|
||||
class CommandRouter {
|
||||
+ route(request: Request)
|
||||
}
|
||||
|
||||
interface CommandHandler <<per command>> {
|
||||
+ handle(request: Request)
|
||||
}
|
||||
|
||||
PrimitiveRequest --> CommandParser : passed to
|
||||
CommandParser --> Request : produces
|
||||
CommandRouter --> CommandHandler : dispatches to
|
||||
CommandRouter ..> Request : receives
|
||||
|
||||
' Response Layer
|
||||
interface Response {
|
||||
+ encode(): String
|
||||
}
|
||||
|
||||
interface SuccessResponse
|
||||
|
||||
interface ErrorResponse
|
||||
|
||||
Response <|-- SuccessResponse
|
||||
Response <|-- ErrorResponse
|
||||
@enduml
|
||||
File diff suppressed because one or more lines are too long
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check for draw.io installation
|
||||
DRAWIO_PATH=""
|
||||
check_drawio_installed() {
|
||||
if command -v drawio &>/dev/null; then
|
||||
DRAWIO_PATH=$(command -v drawio)
|
||||
return 0 # drawio via PATH
|
||||
elif [[ -x "/Applications/draw.io.app/Contents/MacOS/draw.io" ]]; then
|
||||
DRAWIO_PATH="/Applications/draw.io.app/Contents/MacOS/draw.io"
|
||||
return 0 # For macOS check at default path
|
||||
else
|
||||
return 1 # not found
|
||||
fi
|
||||
}
|
||||
|
||||
if ! check_drawio_installed; then
|
||||
echo "Error: 'drawio' was not found. Make sure, that the draw.io cli is installed."
|
||||
echo "https://www.diagrams.net/"
|
||||
exit 1
|
||||
else
|
||||
echo "Draw.io installation found at: $DRAWIO_PATH"
|
||||
fi
|
||||
|
||||
# Compare source (draw.io document) and exported file svg on mtime
|
||||
needs_export() {
|
||||
local drawio_file="$1"
|
||||
local svg_file="$2"
|
||||
|
||||
# If no file found, export
|
||||
if [[ ! -f "$svg_file" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$drawio_file" -nt "$svg_file" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# If (re-)export is necessary export with drawio cli
|
||||
export_file() {
|
||||
local drawio_file="$1"
|
||||
local svg_file="${drawio_file%.drawio}.svg"
|
||||
|
||||
if needs_export "$drawio_file" "$svg_file"; then
|
||||
echo "↻ Exporting: $drawio_file"
|
||||
echo "$svg_file"
|
||||
echo "$drawio_file"
|
||||
|
||||
"$DRAWIO_PATH" --export --format svg --transparent --output "$svg_file" "$drawio_file"
|
||||
else
|
||||
echo "✓ Up-to-date: $drawio_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Iterate over all .drawio files
|
||||
main() {
|
||||
# Source - https://stackoverflow.com/a/9612232
|
||||
# Posted by Kevin, modified by community. See post 'Timeline' for change history
|
||||
# Retrieved 2026-03-09, License - CC BY-SA 4.0
|
||||
find . -name '*.drawio' -print0 |
|
||||
while IFS= read -r -d '' line; do
|
||||
export_file "$line"
|
||||
done
|
||||
|
||||
echo "✓ Exported all files"
|
||||
}
|
||||
|
||||
# Start script at entry point
|
||||
main
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check for plantuml installation
|
||||
PLANTUML_PATH=""
|
||||
check_plantuml_installed() {
|
||||
if command -v plantuml &>/dev/null; then
|
||||
PLANTUML_PATH=$(command -v plantuml)
|
||||
return 0 # plantuml via PATH
|
||||
else
|
||||
return 1 # not found
|
||||
fi
|
||||
}
|
||||
|
||||
if ! check_plantuml_installed; then
|
||||
echo "Error: 'plantuml' was not found. Make sure, that plantuml is installed."
|
||||
echo "https://plantuml.com/starting"
|
||||
exit 1
|
||||
else
|
||||
echo "PlantUML installation found at: $PLANTUML_PATH"
|
||||
fi
|
||||
|
||||
# Compare source (.puml document) and exported file svg on mtime
|
||||
needs_export() {
|
||||
local puml_file="$1"
|
||||
local svg_file="$2"
|
||||
|
||||
# If no file found, export
|
||||
if [[ ! -f "$svg_file" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$puml_file" -nt "$svg_file" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# If (re-)export is necessary export with plantuml cli
|
||||
export_file() {
|
||||
local puml_file="$1"
|
||||
local svg_file="${puml_file%.puml}.svg"
|
||||
|
||||
if needs_export "$puml_file" "$svg_file"; then
|
||||
echo "↻ Exporting: $puml_file"
|
||||
"$PLANTUML_PATH" -tsvg "$puml_file"
|
||||
else
|
||||
echo "✓ Up-to-date: $puml_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Iterate over all .puml files
|
||||
main() {
|
||||
# Source - https://stackoverflow.com/a/9612232
|
||||
# Posted by Kevin, modified by community. See post 'Timeline' for change history
|
||||
# Retrieved 2026-03-09, License - CC BY-SA 4.0
|
||||
find . -name '*.puml' -print0 |
|
||||
while IFS= read -r -d '' line; do
|
||||
export_file "$line"
|
||||
done
|
||||
|
||||
echo "✓ Exported all files"
|
||||
}
|
||||
|
||||
# Start script at entry point
|
||||
main
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn">
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout disableAnsi="false" pattern=
|
||||
"%style{%d{HH:mm:ss.SSS}}{bright,black} %highlight{%-8level}{
|
||||
FATAL=bright red,
|
||||
ERROR=red,
|
||||
WARN=yellow,
|
||||
INFO=blue,
|
||||
DEBUG=cyan,
|
||||
TRACE=white
|
||||
} %style{%-20logger{20}}{magenta} %msg%n"
|
||||
/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="debug">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
||||
Reference in New Issue
Block a user