All-in-One Solution for Indie Game Development · Empowering Indie Developers' Dreams
📖 Documentation · 🚀 Quick Start · 💬 QQ Group: 467608841
🌐 Language: English | 简体中文 | 繁體中文 | 日本語 | 한국어
GameFrameX Server is a high-performance game server framework built on C# .NET 10.0, featuring an Actor Model architecture with hot-update support. The framework enforces strict separation between persistent state and business logic, designed specifically for multiplayer online game scenarios.
Design Philosophy: Simplicity over complexity
- Actor Model — Lock-free high-concurrency architecture based on TPL DataFlow, avoiding traditional lock contention through message passing
- State-Logic Separation — Strict separation between persistent data (Apps layer) and hot-updatable business logic (Hotfix layer)
- Zero-Downtime Hot Updates — Replace business logic assemblies at runtime without restarting the server
- Multi-Protocol Networking — Support for TCP, WebSocket, and HTTP protocols with built-in message encoding/decoding and compression
- MongoDB Persistence — Transparent ORM mapping based on CacheState with automatic serialization/deserialization
- Source Generator — Roslyn-based Agent code generation for automatic Actor message queue scheduling
- Config Table System — Integrated Luban config generation with JSON hot-reloading
- OpenTelemetry — Built-in Prometheus metrics export, health checks, and performance monitoring
┌──────────────────────────────────────────────────────────────┐
│ Client Layer │
├──────────────────────────────────────────────────────────────┤
│ Network Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ TCP │ │WebSocket │ │ HTTP │ │ KCP │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Hotfix Layer (Hot-updatable) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ComponentAgent │ HttpHandler │ EventHandler │ │
│ └──────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Apps Layer (Non-hot-updatable) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ State │ │Component │ │ActorType │ │
│ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Framework Layer (NuGet Packages) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Core │ │ NetWork │ │ DataBase │ │ Monitor │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Database Layer │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ MongoDB │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
| Dependency | Version |
|---|---|
| .NET SDK | 10.0+ |
| MongoDB | 4.x+ |
| IDE | Visual Studio 2022 / JetBrains Rider / VS Code |
# 1. Clone the repository
git clone https://github.com/GameFrameX/Server_main.git
cd Server_main
# 2. Restore dependencies
dotnet restore
# 3. Build
dotnet build
# 4. Run the game server
dotnet run --project GameFrameX.Launcher \
--ServerType=Game \
--ServerId=1000 \
--APMPort=29090| Endpoint | URL | Description |
|---|---|---|
| Health Check | http://localhost:29090/health |
Service health status |
| Metrics | http://localhost:29090/metrics |
Prometheus metrics |
| Test API | http://localhost:28080/game/api/test |
HTTP connectivity test |
Server_main/
├── GameFrameX.Launcher/ # Application entry point (Executable)
│ ├── Program.cs # Bootstrap: register state types & protocol messages
│ └── StartUp/
│ └── AppStartUpGame.cs # Game server startup flow
│
├── GameFrameX.Hotfix/ # Hot-update layer (Business logic, runtime-replaceable)
│ ├── Logic/
│ │ ├── Http/ # HTTP request handlers
│ │ │ ├── TestHttpHandler.cs
│ │ │ ├── ReloadHttpHandler.cs
│ │ │ ├── Player/ # Account login, online queries
│ │ │ └── Bag/ # Item sending
│ │ ├── Player/
│ │ │ ├── Bag/ # Bag component agent
│ │ │ ├── Login/ # Login/logout logic
│ │ │ └── Pet/ # Pet component agent
│ │ ├── Account/ # Account component agent
│ │ └── Server/ # Server global component agent
│ └── StartUp/ # Hot-update startup flow
│ ├── AppStartUpHotfixGameByEntry.cs # Entry point
│ ├── AppStartUpHotfixGameByMain.cs # Network/connection management
│ ├── AppStartUpHotfixGameByHeart.cs # Heartbeat handling
│ └── AppStartUpHotfixGameByGateWay.cs # Gateway communication
│
├── GameFrameX.Apps/ # Application state layer (Non-hot-updatable)
│ ├── ActorType.cs # Actor type enum definition
│ ├── Account/Login/ # Login state + component
│ ├── Player/
│ │ ├── Bag/ # Bag state + component
│ │ ├── Player/ # Player state + component
│ │ └── Pet/ # Pet state + component
│ ├── Server/ # Server global state + component
│ └── Common/
│ ├── Session/ # Session management (SessionManager)
│ ├── Event/ # Event ID definitions
│ └── EventData/ # Event arguments
│
├── GameFrameX.Config/ # Config table system (Luban generated)
│ ├── ConfigComponent.cs # Config loading singleton
│ ├── Tables/ # Generated config table classes
│ ├── TablesItem/ # Config data models
│ └── json/ # JSON config data files
│
├── GameFrameX.Proto/ # Network protocol definitions
│ ├── Basic_10.cs # Basic protocols (heartbeat, etc.)
│ ├── Bag_100.cs # Bag protocols
│ ├── User_300.cs # User/account protocols
│ └── BuiltIn/ # Built-in system protocols
│
├── GameFrameX.CodeGenerator/ # Roslyn source generator
│ ├── AgentGenerator.cs # Agent wrapper code generation
│ └── AgentTemplate.cs # Code templates
│
├── Server.sln # Visual Studio solution
├── Dockerfile # Docker multi-stage build
├── docker-compose.yml # Docker Compose orchestration
└── LICENSE # MIT + Apache 2.0 dual license
GameFrameX uses a three-layer separation pattern that enables hot-updating business logic without server downtime.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ State │────▶│ Component │◀────│ ComponentAgent│
│ (Data Def) │ │ (Shell) │ │ (Logic) │
│ Apps Layer │ │ Apps Layer │ │ Hotfix Layer │
│ Not Hot-fix │ │ Not Hot-fix │ │ Hot-fixable │
└──────────────┘ └──────────────┘ └──────────────┘
Inherit CacheState to define persistent data structures. The framework handles MongoDB serialization automatically.
// GameFrameX.Apps/Player/Bag/Entity/BagState.cs
public sealed class BagState : CacheState
{
public Dictionary<int, BagItemState> List { get; set; } = new();
}
public sealed class BagItemState
{
public long ItemId { get; set; }
public long Count { get; set; }
}Inherit StateComponent<TState> to serve as the bridge between state and logic.
// GameFrameX.Apps/Player/Bag/Component/BagComponent.cs
[ComponentType(GlobalConst.ActorTypePlayer)]
public class BagComponent : StateComponent<BagState> { }Inherit StateComponentAgent<TComponent, TState> to write hot-updatable business code.
// GameFrameX.Hotfix/Logic/Player/Bag/BagComponentAgent.cs
public class BagComponentAgent : StateComponentAgent<BagComponent, BagState>
{
public async Task OnAddBagItem(INetWorkChannel netWorkChannel,
ReqAddItem message, RespAddItem response)
{
// Validate item config exists
foreach (var item in message.ItemDic)
{
if (!ConfigComponent.Instance.GetConfig<TbItemConfig>()
.TryGet(item.Key, out var _))
{
response.ErrorCode = (int)OperationStatusCode.NotFound;
return;
}
}
await UpdateChanged(netWorkChannel, message.ItemDic);
}
public async Task<BagState> UpdateChanged(INetWorkChannel netWorkChannel,
Dictionary<int, long> itemDic)
{
var bagState = OwnerComponent.State;
var notify = new NotifyBagInfoChanged();
foreach (var item in itemDic)
{
if (bagState.List.TryGetValue(item.Key, out var value))
{
value.Count += item.Value;
}
else
{
bagState.List[item.Key] = new BagItemState
{
Count = item.Value, ItemId = item.Key
};
}
}
await netWorkChannel.WriteAsync(notify);
await OwnerComponent.WriteStateAsync(); // Auto-persist to MongoDB
return bagState;
}
}Inherit BaseHttpHandler and register with [HttpMessageMapping].
// GameFrameX.Hotfix/Logic/Http/TestHttpHandler.cs
[HttpMessageMapping(typeof(TestHttpHandler))]
[HttpMessageResponse(typeof(HttpTestResponse))]
[Description("Test API endpoint")]
public sealed class TestHttpHandler : BaseHttpHandler
{
public override Task<string> Action(string ip, string url,
Dictionary<string, object> parameters)
{
var response = new HttpTestResponse { Message = "hello" };
return Task.FromResult(HttpJsonResult.SuccessString(response));
}
}The framework provides two RPC handler base classes with automatic ComponentAgent injection:
// Player-level message (bound to a specific player Actor)
[MessageMapping(typeof(ReqAddItem))]
internal sealed class AddItemHandler
: PlayerRpcComponentHandler<BagComponentAgent, ReqAddItem, RespAddItem>
{
protected override async Task ActionAsync(ReqAddItem request, RespAddItem response)
{
await ComponentAgent.OnAddBagItem(NetWorkChannel, request, response);
}
}
// Global-level message (bound to the server Actor)
[MessageMapping(typeof(ReqHeartBeat))]
internal sealed class HeartBeatHandler
: GlobalRpcComponentHandler<ServerComponentAgent, ReqHeartBeat, RespHeartBeat>
{
protected override Task ActionAsync(ReqHeartBeat request, RespHeartBeat response)
{
return Task.CompletedTask;
}
}Use the [Event] attribute to bind event IDs. The framework automatically dispatches to the corresponding ComponentAgent.
[Event(EventId.PlayerLogin)]
internal sealed class PlayerLoginEventHandler : EventListener<PlayerComponentAgent>
{
protected override Task HandleEvent(PlayerComponentAgent agent, GameEventArgs args)
{
return agent.OnLogin();
}
}The Roslyn source generator automatically handles Actor message queue scheduling. Control invocation behavior through attributes:
| Attribute | Description | Use Case |
|---|---|---|
[Service] |
Default mode; method calls are enqueued to the Actor message queue | All business methods |
[ThreadSafe] |
Skip message queue, invoke directly (requires thread-safe implementation) | Read-only operations, stateless computation |
[Discard] |
Fire-and-forget, do not await the return value | Logging, statistics where results are not needed |
[TimeOut(ms)] |
Set timeout for message queue invocation | Long operations requiring timeout control |
public class ServerComponentAgent : StateComponentAgent<ServerComponent, ServerState>
{
// Enqueued to Actor message queue
[Service]
public virtual Task<bool> IsOnline(long roleId) { ... }
// Skip message queue, thread-safe direct call
[Service]
[ThreadSafe]
public virtual long FirstStartTime()
{
return State.FirstStartTime;
}
// Fire-and-forget, non-blocking
[Service]
[Discard]
public virtual ValueTask AddOnlineRole(long roleId)
{
OwnerComponent.OnlineSet.Add(roleId);
return ValueTask.CompletedTask;
}
}Use the ConfigComponent singleton to access Luban-generated config tables:
var config = ConfigComponent.Instance.GetConfig<TbItemConfig>();
// Safe query with TryGet
if (config.TryGet(itemId, out var itemConfig))
{
// Use itemConfig.Name, itemConfig.Type, etc.
}Use the GameDb static class for MongoDB CRUD operations:
// Query
var state = await GameDb.FindAsync<LoginState>(
m => m.UserName == userName && m.Password == password);
// Add or update
await GameDb.AddOrUpdateAsync(loginState);
// List query
var list = await GameDb.FindListAsync<LoginState>(m => m.Id != 0);
// Delete
var count = await GameDb.DeleteAsync(state);The hot-update system allows replacing business logic without stopping the server.
- Apps Layer (
GameFrameX.Apps): Contains state definitions and component shells — not hot-updatable - Hotfix Layer (
GameFrameX.Hotfix): Contains all business logic — hot-updatable - Hotfix assembly outputs to the
hotfix/directory, loaded at runtime byHotfixManager
# Trigger via HTTP endpoint (with version number)
curl "http://localhost:28080/game/api/Reload?version=1.0.1"| Parameter | Description | Default | Example |
|---|---|---|---|
ServerType |
Server type | — | Game |
ServerId |
Unique server ID | — | 1000 |
InnerPort |
TCP internal port | — | 29100 |
HttpPort |
HTTP service port | 0 |
28080 |
WsPort |
WebSocket service port | 0 |
29110 |
MetricsPort |
Prometheus metrics port | 0 |
29090 |
DataBaseUrl |
MongoDB connection string | — | mongodb://localhost:27017 |
DataBaseName |
Database name | — | gameframex |
dotnet GameFrameX.Launcher.dll \
--ServerType=Game \
--ServerId=1000 \
--InnerPort=29100 \
--HttpPort=28080 \
--WsPort=29110 \
--MetricsPort=29090 \
--DataBaseUrl=mongodb://127.0.0.1:27017 \
--DataBaseName=game_dbdocker-compose up --build| Port | Protocol | Description |
|---|---|---|
29090 |
HTTP | APM metrics / Health check |
29100 |
TCP | Game client connections |
29110 |
WebSocket | WebSocket connections |
28080 |
HTTP | HTTP API |
Message IDs are calculated by module ID shift: (moduleId << 16) + seqId
| Module | ID Range | File | Description |
|---|---|---|---|
| System | -10 ~ -1 | Player_-10.cs, Service_-3.cs |
Built-in system protocols |
| Basic | 10 | Basic_10.cs |
Heartbeat, server ready notification |
| Bag | 100 | Bag_100.cs |
Item CRUD, use, compose |
| User | 300 | User_300.cs |
Login, register, character list |
| Component | Technology |
|---|---|
| Runtime | .NET 10.0 |
| Database | MongoDB |
| Networking | SuperSocket |
| Serialization | protobuf-net |
| Config Generation | Luban |
| Code Generation | Roslyn Source Generator |
| Monitoring | OpenTelemetry + Prometheus |
| Object Mapping | Mapster |
| Containerization | Docker + Docker Compose |
- Fork this repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add some feature') - Push to the branch (
git push origin feature/amazing-feature) - Create a Pull Request
This project is dual-licensed under the MIT License and Apache License 2.0. See the LICENSE file for details.
