An ECS-driven, stateful, bidirectional server framework for Rust
Ecson is an experimental web server framework built for applications where real-time performance and persistent state management are critical. By combining tokio's async I/O with the blazing-fast data-oriented design of bevy_ecs, you can build multiplayer game backends, real-time collaboration tools, and spatial simulations — without a single Arc<Mutex<T>>.
⚠️ This project is experimental. It is not recommended for production use. The API may change without notice.
Traditional async web frameworks (axum, Actix-web) excel at building stateless CRUD APIs. But when you need thousands of persistent WebSocket connections continuously sharing and mutating state, you quickly hit the wall of global shared state.
// The painful pattern you know too well
let state = Arc::new(Mutex::new(AppState::new()));
// Lock contention, deadlocks, thread starvation...
let mut s = state.lock().await;Ecson tackles this problem with ECS (Entity Component System) — an architecture pattern battle-tested in game development, now applied to server-side programming.
| Traditional Concept | Ecson Equivalent |
|---|---|
| Client connection | Entity |
| User state / attributes | Component |
| Business logic | System |
Each connection is spawned as an entity in a global ECS world. The ECS scheduler automatically runs non-conflicting systems in parallel across threads. No locks required.
Good fits
- Multiplayer game backends
- Live whiteboards and real-time collaboration tools
- Metaverse and spatial simulations
- Chat systems with presence management
Poor fits
- REST APIs and simple HTTP servers
- Stateless CRUD applications
cargo new my-ecson-server
cd my-ecson-server
cargo add ecsonEdit src/main.rs:
use ecson::prelude::*;
fn echo_system(
mut ev_recv: MessageReader<MessageReceived>,
mut ev_send: MessageWriter<SendMessage>,
) {
for msg in ev_recv.read() {
ev_send.write(SendMessage {
target: msg.entity,
payload: msg.payload.clone(),
});
}
}
fn main() {
EcsonApp::new()
.add_plugins(EcsonWebSocketPlugin::new("127.0.0.1:8080"))
.add_systems(Update, echo_system)
.run();
}cargo run
# EcsonApp Started🚀Try it in your browser console:
const ws = new WebSocket("ws://127.0.0.1:8080");
ws.onmessage = (e) => console.log("Received:", e.data);
ws.onopen = () => ws.send("Hello, Ecson!");
// → Received: Hello, Ecson!Ecson ships with a set of plug-and-play plugins for common use cases. A full-featured room-based chat server, for example, is just a few lines:
use ecson::prelude::*;
use ecson::plugins::chat::ChatFullPlugin;
fn main() {
EcsonApp::new()
.add_plugins(EcsonWebSocketPlugin::new("127.0.0.1:8080"))
.add_plugins(ChatFullPlugin)
.run();
}| Plugin | What it does | State |
|---|---|---|
ChatFullPlugin |
Room-based chat with /nick, /join, /list commands |
🟢 |
HeartbeatPlugin |
Periodic ping/pong with automatic timeout disconnection | 🟢 |
LobbyPlugin |
Matchmaking lobby management with LobbyReadyEvent |
🔴 |
PresencePlugin |
Client presence state (Online / Away / Busy) | 🔴 |
RateLimitPlugin |
Per-client message rate limiting with configurable actions | 🟢 |
SnapshotPlugin |
Periodic state snapshots broadcast to subscribed clients | 🔴 |
Spatial2DPlugin / Spatial3DPlugin |
Spatial position management with AOI (Area of Interest) notifications | 🔴 |
| Plugin | Protocol | Use case |
|---|---|---|
EcsonWebSocketPlugin |
WS | General purpose, dev & production |
EcsonWebSocketTlsPlugin |
WSS | Production with TLS certificates |
EcsonWebSocketTlsDevPlugin |
WSS (self-signed) | Development / testing |
EcsonWebTransportDevPlugin |
WebTransport | Low-latency games, position sync |
EcsonTcpPlugin |
TCP | |
EcsonUdpPlugin |
UDP | Realtime games |
cargo run --example echo
cargo run --example broadcast_chat
cargo run --example room_chatFrontend test HTML files are available in examples/frontend/. Open them directly in your browser to interact with the running server.
Ecson uses the tracing crate internally. Initialize a subscriber in your application to see logs:
fn main() {
tracing_subscriber::fmt::init();
EcsonApp::new()
// ...
.run();
}ecson/
├── Cargo.toml
├── crates/
│ ├── ecson_core/ ← EcsonApp, Plugin trait, schedule labels
│ ├── ecson_ecs/ ← ECS types, built-in plugins
│ ├── ecson_network/ ← WebSocket, WebTransport, TLS
│ └── ecson_macros/ ← derive macros
└── ecson/ ← Public facade crate (re-exports everything)
└── examples/
Users should only depend on the ecson crate. Internal crates are not intended for direct use.
- bevy_ecs
- tokio / tokio-tungstenite / tokio-rustls / tokio-util
- wtransport
- tracing / tracing-subscriber
- futures-util
- rcgen / rustls / rustls-pemfile
Bug fixes, documentation improvements, and new examples are all welcome. For large feature additions or breaking design changes, please open an Issue first to discuss. See CONTRIBUTING.md for details.
Requirements: Rust 1.85 or later (edition = "2024")
This project stands on the shoulders of Tokio and Bevy ECS. We're also grateful for the following tools used during development: