Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
27d7a94
Update README.md
sunduk Nov 2, 2017
9312fa0
Update README.md
sunduk Nov 2, 2017
46b2047
Update README.md
sunduk Nov 2, 2017
6e51488
Update README.md
sunduk Nov 2, 2017
0911fa4
Update README.md
sunduk May 4, 2018
96236ef
Update README.md
sunduk May 15, 2018
b1426e8
Update README.md
sunduk May 15, 2018
ee36682
apply dotnet core
netpyoung Aug 13, 2019
8c04d52
Merge pull request #11 from netpyoung/6-dotnet-core-support
sunduk Sep 7, 2019
b1984d5
Update README.md
sunduk Oct 18, 2019
ad4e80f
Update README.md
sunduk Mar 19, 2020
fa12b5d
Update README.md
sunduk Mar 19, 2020
f1b65dd
Update README.md
sunduk Mar 19, 2020
efa54f4
Update README.md
sunduk Mar 19, 2020
9b29087
Update README.md
kas-hish Oct 1, 2020
0d3f20e
Update README.md
kas-hish Oct 1, 2020
948e3e6
Merge pull request #12 from kas-hish/master
sunduk Nov 4, 2020
9dcafb1
Fix heartbeat error.
sunduk Jul 24, 2022
4d81a01
Update README.md
sunduk Jul 9, 2024
1d15f77
Add test manual.
sunduk Jul 9, 2024
1dcc88e
Update README.md
sunduk Jul 9, 2024
d8fc7f6
Update README.md
sunduk Jul 9, 2024
5429663
Update README.md
sunduk Jul 9, 2024
5d562fe
Update README.md
sunduk Jul 10, 2024
2ce189a
Fix indent.
sunduk Jul 12, 2024
51da9fd
Fix indent.
sunduk Jul 12, 2024
52e5fbd
Remove unused variables.
sunduk Jul 12, 2024
7646f3b
Remove unused variables.
sunduk Jul 12, 2024
fc27a14
Update .NET to 8.
sunduk Jul 12, 2024
9ad35b9
Add Copilot guidance, local skills, and MCP setup docs
wforney Jul 26, 2026
8fba7dc
Modernize solutions and add TUnit test projects
wforney Jul 26, 2026
e6be80d
Add semantic versioning for build outputs
wforney Jul 26, 2026
ae8db20
Fix synchronous receive recursion
wforney Jul 26, 2026
e38c9ee
Add GitHub Actions build and test workflow
wforney Jul 26, 2026
9545aaa
Document PR assignment workflow
wforney Jul 26, 2026
175687e
Add pull request template
wforney Jul 26, 2026
32fff6c
Fix null token handling in receive callback
Copilot Jul 26, 2026
05d044b
Update GitHub Actions for Node 24
wforney Jul 26, 2026
f310821
Add code coverage reporting to CI
wforney Jul 26, 2026
dbcab4d
Comment coverage on pull requests
wforney Jul 26, 2026
c954569
Add coverage summaries to Actions
wforney Jul 26, 2026
2f407e4
Update artifact actions for Node 24
wforney Jul 26, 2026
523a94f
Harden workflow permissions
wforney Jul 26, 2026
1d58f4e
Bump artifact actions for Node 24
wforney Jul 26, 2026
8649e00
Merge pull request #1 from wforney/review/fix-receive-recursion
wforney Jul 26, 2026
08d7ce0
Rewrite docs for clarity; add main.ck layout file
wforney Jul 26, 2026
139cb23
Modernize codebase: rename, refactor, enforce .NET style
wforney Jul 26, 2026
05db523
Refactor CSampleServer to modern C# style & encapsulation
wforney Jul 26, 2026
d84bd7d
Refactor client/server to modern C# and rename types
wforney Jul 26, 2026
835c426
Refactor game server to modern C# architecture
wforney Jul 27, 2026
1e292f3
Refactor: modern C# style and code consistency
wforney Jul 27, 2026
e240f76
Add comprehensive tests and improve session cleanup logic
wforney Jul 27, 2026
f71bc3c
Ignore .contextkeeper and stop tracking it
wforney Jul 27, 2026
73153d8
Formatting
wforney Jul 27, 2026
ca3efa8
Refactor: translate Korean comments in UserToken.cs to English
wforney Jul 27, 2026
9475b89
Centralize protocol logic in new Protocol project
wforney Aug 2, 2026
4ce38e2
Enable TUnit reflection scanner in all test projects
wforney Aug 2, 2026
ab0e0ff
Align test packet parsing with production flow
wforney Aug 2, 2026
dc37623
Replace Korean comments with English; doc & style cleanup
wforney Aug 2, 2026
ded3516
Refactor: PascalCase Vector2, use var, simplify tests
wforney Aug 2, 2026
1337415
Move InternalsVisibleTo to FreeNet.csproj
wforney Aug 2, 2026
0af4403
Refactor naming, docs, and encapsulation across codebase
wforney Aug 2, 2026
7428ff4
Refactor to use C# events for connection/session handling
wforney Aug 2, 2026
719cec4
Refactor to pipelines & EventHandler model for sessions
wforney Aug 2, 2026
02d3e55
Remove unused Examples folder entry from FreeNet.csproj
wforney Aug 2, 2026
05668e1
Refactor protocol messages: unify names and base class
wforney Aug 2, 2026
0a1ff51
Refactor session event args and simplify message callback
wforney Aug 2, 2026
35de5c2
Refactor logic thread to use Channels; add clean shutdown
wforney Aug 2, 2026
5ea3b7a
Standardize codebase and docs to English; remove Korean text
wforney Aug 2, 2026
3c66342
Remove KOREAN_TEXT_REPORT.md and its contents
wforney Aug 2, 2026
d2703aa
Bump version to 0.2.0 for new minor release
wforney Aug 2, 2026
ad6f036
Add MIT License file with 2026 copyright
wforney Aug 2, 2026
f3ec39a
Rewrite version history, add PIPELINES_MODERNIZATION doc
wforney Aug 2, 2026
ec477d3
Clarify async I/O compatibility in README and favorites
wforney Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .clinerules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Use `AGENTS.md` as the canonical repository guide.

Repository-specific constraints:
- Packet size header (`Defines.HEADERSIZE`) and `CPacket` push/pop order are compatibility-critical.
- `IPeer` binding (`token.set_peer(this)`) and session lifecycle callbacks must be preserved.
- Threading behavior depends on `CNetworkService(use_logicthread)`; validate changes against the selected mode.
6 changes: 6 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Follow `AGENTS.md` for repository-specific guidance.

Priorities:
1. Keep FreeNet packet protocol/framing behavior unchanged unless explicitly requested.
2. Maintain compatibility between `FreeNet/`, `CSampleServer/`, `CSampleClient/`, and `viruswar/server/GameServer/`.
3. Use existing build/run commands from `AGENTS.md`; no new toolchain assumptions.
385 changes: 385 additions & 0 deletions .editorconfig

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Summary

Briefly describe the change and why it matters.

## What changed

-

## Why

-

## Testing

- `dotnet build .\FreeNet.slnx -c Debug`
- `dotnet test .\FreeNet.slnx -c Debug`
- `dotnet build .\viruswar\server\viruswar_server.slnx -c Debug` (if applicable)
- `dotnet test .\viruswar\server\viruswar_server.slnx -c Debug` (if applicable)

## Risk

-

## Related

- Issue:
- PR:

## Checklist

- [ ] I assigned this PR to the current user
- [ ] I added appropriate labels
- [ ] I set a milestone if one applies
- [ ] I verified the relevant build/test commands
76 changes: 76 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copilot instructions for FreeNet

## Build, test, and lint

### Build
- Restore/build the main solution:
- `dotnet restore .\FreeNet.slnx`
- `dotnet build .\FreeNet.slnx -c Debug`
- Build the VirusWar server solution:
- `dotnet build .\viruswar\server\viruswar_server.slnx -c Debug`

### Run samples
- Start sample server:
- `dotnet run --project .\CSampleServer\CSampleServer.csproj`
- Start sample client:
- `dotnet run --project .\CSampleClient\CSampleClient.csproj`

### Tests
- Run all tests:
- `dotnet test .\FreeNet.slnx -c Debug`
- Run VirusWar server tests:
- `dotnet test .\viruswar\server\viruswar_server.slnx -c Debug`
- Run a single test project:
- `dotnet test .\FreeNet.Tests\FreeNet.Tests.csproj -c Debug`
- Test runner is Microsoft Testing Platform (MTP) via `global.json`.
- Manual/load validation flow is still documented in `README.md` and `TestManual.md` for socket stress scenarios.

### Lint/format
- No dedicated lint configuration is present in the repository (no `.editorconfig`, no lint scripts, no analyzer config files).

## High-level architecture

- `FreeNet/` is the core networking library (TCP, async receive/send, pooling, packet framing).
- `CNetworkService` is the main composition root:
- owns `SocketAsyncEventArgs` pools and shared receive buffers,
- starts `CListener`,
- creates `CUserToken` sessions per connection,
- wires session lifecycle callbacks and heartbeat checks.
- `CUserToken` is the per-connection transport/session object:
- receives raw bytes and delegates framing to `CMessageResolver`,
- manages outbound queue batching via `SocketAsyncEventArgs.BufferList`,
- handles system protocols for close/heartbeat.
- `CMessageResolver` reconstructs complete packets from stream fragments using a fixed 4-byte length header (`Defines.HEADERSIZE = 4`).
- Message dispatch has two modes selected by `CNetworkService(use_logicthread)`:
- `false`: packet handling runs on IO completion threads.
- `true`: packets are queued through `CLogicMessageEntry` + `CDoubleBufferingQueue` and processed on one logic thread.
- `IPeer` is the application-facing session contract; sample/game servers implement it to process protocol messages and cleanup on disconnect.
- App/sample surfaces:
- `CSampleServer/` and `CSampleClient/` are minimal integration examples.
- `viruswar/server/GameServer/` is a fuller game-server usage of the library.
- `documents/*.png` diagrams in README are part of the intended architecture documentation and should stay consistent with networking/dispatch behavior.

## Key conventions in this codebase

- Naming convention is legacy C-style:
- classes typically start with `C` (for example `CUserToken`, `CNetworkService`),
- interfaces start with `I`,
- many methods use lower-case snake/camel hybrids (`on_message`, `on_removed`, `session_created_callback`).
- Packet protocol contract is strict:
- create with `CPacket.create(protocolId)`,
- `push(...)` payload fields in order,
- call `record_size()` before sending (or rely on helpers that do it immediately before send),
- parse with `pop_*` in exactly the same order.
- Protocol IDs `<= 0` are reserved for system-level behavior (`SYS_CLOSE_REQ`, `SYS_CLOSE_ACK`, heartbeat control) and should not be reused by game/application protocols.
- `IPeer` implementations are expected to bind themselves in constructors via `token.set_peer(this)`.
- `session_created_callback` can be invoked concurrently from IO paths; shared state mutations (for example user lists) are expected to be locked.
- For non-FreeNet test clients, heartbeat may need to be disabled in sample server (`service.disable_heartbeat()`), matching repository README guidance.
- For quick transport validation, sample server includes an optional echo path in `CSampleServer/CGameUser.cs` (commented toggle).
- Keep protocol enums and parser usage aligned across client/server projects (`CSampleServer/protocol.cs`, `CSampleClient/protocol.cs`, `viruswar/server/GameServer/protocol.cs`).
- NuGet package versions are centrally managed via `Directory.Packages.props`; add/update versions there rather than per-project.

## Pull request workflow

- When creating PRs, assign them to the current user/requester.
- Add the most appropriate labels for the change.
- Set a milestone when one applies.
19 changes: 19 additions & 0 deletions .github/mcp-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# MCP setup for this repository

Use `.mcp.json.example` as a starter configuration for MCP-enabled clients.

## Preference

Use whichever method minimizes token usage first (CLI, MCP, or built-in tools), while preserving correctness. Use MCP when it is the lower-token practical path or when explicitly requested.

## Included servers

- `github`: repository/issue/PR context and automation
- `filesystem-freenet`: constrained file access rooted to this repository

## Quick setup

1. Copy `.mcp.json.example` to your client's MCP config location or rename to `.mcp.json` if your client reads it from repository root.
2. Set a token in your client/environment:
- `GITHUB_PERSONAL_ACCESS_TOKEN` with repo access as needed.
3. Start/reload your MCP client.
11 changes: 11 additions & 0 deletions .github/prompts/manual-smoke-test.prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Manual smoke test prompt

Run a minimal manual smoke check for the sample client/server path:

1. Start server:
- `dotnet run --project .\CSampleServer\CSampleServer.csproj`
2. Start client in a separate terminal:
- `dotnet run --project .\CSampleClient\CSampleClient.csproj`
3. Send one chat line from client and confirm one `CHAT_MSG_ACK` response.

If the scenario uses a non-FreeNet client lacking heartbeat support, toggle server heartbeat off via the commented `service.disable_heartbeat()` line in `CSampleServer\Program.cs` as documented in `README.md`.
17 changes: 17 additions & 0 deletions .github/prompts/protocol-change.prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Protocol-safe change prompt

Make the requested protocol/networking change in this repository while preserving wire compatibility and thread-safety guarantees.

Constraints to respect:
- Packet framing uses a 4-byte size header (`Defines.HEADERSIZE = 4`).
- Application protocol IDs must remain `> 0`; values `<= 0` are reserved for system flow (close/heartbeat).
- `CPacket.push(...)` and `CPacket.pop_*()` order must stay exactly aligned.
- `IPeer` implementations must continue to bind with `token.set_peer(this)`.
- Validate both dispatch modes where relevant:
- IO-thread path (`new CNetworkService(false)`)
- Logic-thread path (`new CNetworkService(true)`)

When touching protocol definitions, update matching enums/handlers in:
- `CSampleServer\protocol.cs`
- `CSampleClient\protocol.cs`
- `viruswar\server\GameServer\protocol.cs` (if applicable to shared behavior)
175 changes: 175 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
name: build-test

on:
push:
branches:
- master
pull_request:
branches:
- master

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-test:
runs-on: ubuntu-latest

permissions:
contents: read

strategy:
fail-fast: false
matrix:
include:
- name: main
solution: FreeNet.slnx
- name: viruswar
solution: viruswar/server/viruswar_server.slnx

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
cache: true
cache-dependency-path: |
Directory.Packages.props
**/*.csproj
**/*.slnx

- name: Restore ${{ matrix.name }}
run: dotnet restore ${{ matrix.solution }}

- name: Build ${{ matrix.name }}
run: dotnet build ${{ matrix.solution }} -c Debug --no-restore

- name: Test ${{ matrix.name }}
run: dotnet test ${{ matrix.solution }} -c Debug --no-build --results-directory ./TestResults/${{ matrix.name }} --coverage --coverage-output coverage.cobertura.xml --coverage-output-format cobertura

- name: Upload coverage for ${{ matrix.name }}
uses: actions/upload-artifact@v7
with:
name: coverage-${{ matrix.name }}
path: TestResults/${{ matrix.name }}/coverage.cobertura.xml

- name: Publish coverage summary for ${{ matrix.name }}
run: |
{
echo "## Coverage (${{ matrix.name }})"
echo ""
echo "- Artifact: \`coverage-${{ matrix.name }}\`"
echo "- Report: \`TestResults/${{ matrix.name }}/coverage.cobertura.xml\`"
} >> "$GITHUB_STEP_SUMMARY"

coverage-comment:
runs-on: ubuntu-latest
needs: build-test
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: read
issues: write
pull-requests: write

steps:
- name: Download coverage artifacts
uses: actions/download-artifact@v8
with:
pattern: coverage-*
path: coverage

- name: Comment coverage summary
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const path = require('path');

function findCoverageFiles(dir) {
const results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findCoverageFiles(fullPath));
} else if (entry.isFile() && entry.name === 'coverage.cobertura.xml') {
results.push(fullPath);
}
}
return results;
}

function readCoverageSummary(filePath) {
const xml = fs.readFileSync(filePath, 'utf8');
const match = xml.match(/line-rate="([0-9.]+)"/);
if (!match) {
throw new Error(`Could not find line-rate in ${filePath}`);
}

const lineRate = Number.parseFloat(match[1]);
const artifactName = path.basename(path.dirname(filePath));
return {
filePath,
artifactName,
lineRate,
percent: (lineRate * 100).toFixed(2),
};
}

const coverageFiles = findCoverageFiles('coverage');
const summaries = coverageFiles.map(readCoverageSummary).sort((a, b) => a.filePath.localeCompare(b.filePath));
const lines = summaries.map(summary => `- \`${summary.artifactName}\`: ${summary.percent}%`);
const body = [
'## Coverage report',
'',
...lines,
'',
`Workflow run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
].join('\n');

const issue_number = context.payload.pull_request.number;
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
});

const marker = '<!-- coverage-report -->';
const existing = comments.data.find(comment => comment.user?.type === 'Bot' && comment.body?.includes(marker));

const fullBody = `${marker}\n${body}`;
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: fullBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body: fullBody,
});
}

- name: Publish aggregated coverage summary
run: |
{
echo "## Coverage summary"
echo ""
echo "| Artifact | Coverage |"
echo "| --- | ---: |"
for file in coverage/**/coverage.cobertura.xml; do
artifact=$(basename "$(dirname "$file")")
percent=$(python -c "import re, sys; from pathlib import Path; xml = Path(sys.argv[1]).read_text(encoding='utf-8'); m = re.search(r'line-rate=\"([0-9.]+)\"', xml); print(f'{float(m.group(1)) * 100:.2f}%')" "$file")
echo "| $artifact | $percent |"
done
} >> "$GITHUB_STEP_SUMMARY"
Loading