# Plexor Core
**Plexor Core** is the foundational library of the **Plexo** architecture—a language-agnostic distributed system and message processing framework inspired by biological neural networks.
This crate provides the essential abstractions and the central routing logic ("Plexus") that decouples signal definition from transport implementation.
## Features
- **Biological Metaphor**: Intuitive concepts like Neurons (signals), Axons (transmitters), and Synapses (receivers).
- **Transport Agnostic**: The core logic is independent of the underlying network layer. Transports (ZeroMQ, HTTP, etc.) are pluggable "Ganglia".
- **Type-Safe Routing**: Strongly typed signals within the process, with safe erasure for dynamic routing.
- **Asynchronous**: Built on `tokio` and `futures` for high-performance async message passing.
## Motivation
Building distributed systems often involves a significant amount of "drudge work" that pollutes application logic:
- **Transport Boilerplate**: Writing the same ZMQ socket loops, HTTP pollers, or WebSocket handlers over and over.
- **Manual Routing**: Hardcoding which service handles which message type and manually managing those connections.
- **Implicit Contracts**: Relying on fragile naming conventions or complex glue code to bridge different parts of a system.
- **Cross-Cutting Concerns**: Re-implementing tracing, correlation IDs, and error propagation for every new network boundary.
Plexor solves this by decoupling your **Signals** from your **Transports**:
1. **Define your Signals (Neurons)**: You focus on the data and the codec once.
2. **Define your Logic (Reactants)**: Your code reacts to signals arriving at a **Synapse**, regardless of whether they came from a local thread or a ZMQ socket 1,000 miles away.
3. **Dynamic Wiring**: Using the **Plexus** (the hub) and **Ganglia** (the bridges), you can "infuse" different transport layers into your system at runtime.
The biological metaphor provides a mental model where the network is a living organism: you don't call "endpoints," you fire "Axons" into a "Plexus," and the system handles the heavy lifting of routing and encoding automatically.
## Core Concepts
- **Neuron**: The fundamental unit of identity. It defines the schema (data structure), the codec (how it's encoded), and its unique namespace in the network.
- **Plexus**: The "brain" of the system. It interconnects multiple Ganglia and manages the complex routing logic required to move signals between them.
- **Axon**: Your primary interface for "firing" a signal. It's a high-level, strongly-typed handle used to transmit messages into the Plexus.
- **Synapse & Reactant**: The receiving end. A Synapse filters for specific Neurons, and a Reactant is the logic that "triggers" when that signal arrives.
- **Ganglion**: A node in the network. It can be a transport bridge (ZMQ, HTTP, etc.) or a processing hub. Ganglia handle the physical transmission of signals.
## Usage
### In-Process Example
This example demonstrates a simple local message passing setup using the built-in `TextCodec`.
*Note: This example assumes you have `tokio` in your dependencies.*
```rust
use plexor_core::neuron::{Neuron, NeuronImpl};
use plexor_core::plexus::Plexus;
use plexor_core::axon::{Axon, AxonImpl};
use plexor_core::reactant::{Reactant, ReactantError};
use plexor_core::payload::Payload;
use plexor_core::erasure::reactant::ReactantErased;
use plexor_core::namespace::NamespaceImpl;
use plexor_core::codec::TextCodec;
use std::sync::Arc;
#[derive(Clone)]
struct MyReactant;
impl Reactant<String, TextCodec> for MyReactant {
fn react(&self, payload: Arc<Payload<String, TextCodec>>) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), ReactantError>> + Send + 'static>> {
Box::pin(async move {
println!("Received: {}", payload.value);
Ok(())
})
}
fn erase(self: Box<Self>) -> Arc<dyn ReactantErased + Send + Sync + 'static> {
plexor_core::erasure::reactant::erase_reactant(self)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Define Identity
let ns = Arc::new(NamespaceImpl { delimiter: ".", parts: vec!["hello", "world"] });
let neuron = NeuronImpl::<String, TextCodec>::new_arc(ns);
// 2. Create the Plexus (The Brain)
let plexus_arc = Plexus::new_shared(vec![], vec![]).await;
// 3. Register a Reactant (The Receiver)
{
let mut p = plexus_arc.lock().await;
// Adapt ensures the Plexus knows how to route this specific Neuron
p.adapt(neuron.clone()).await?;
// Link the Reactant to the Neuron
p.react(neuron.name(), vec![Box::new(MyReactant).erase()], vec![]).await?;
}
// 4. Create an Axon (The Transmitter) and Fire!
let mut axon = AxonImpl::new(neuron, plexus_arc);
axon.transmit("Hello, Plexor!".to_string()).await?;
Ok(())
}
```
## Implementation Details
### Type Erasure & Unsafe
To achieve a **Universal Plexus** capable of routing an infinite variety of user-defined types without compile-time knowledge, Plexor utilizes type erasure and `unsafe` transmutes.
- **Dynamic Type Mapping**: Particularly for **External Ganglia**, incoming byte streams must be dynamically mapped to concrete Rust types at runtime based on string-based identities.
- **Safety**: All `unsafe` operations are strictly guarded by `TypeId` equality verification at runtime. We only step outside the safe sandbox once we have absolute proof that the memory layout matches the expected type.
- **Rationale**: This approach overcomes the limitations of standard `Any` downcasting in complex generic contexts across trait boundaries, which is a common hurdle in building pluggable distributed frameworks.
## Ecosystem
To build a distributed system, combine `plexor-core` with other crates in the ecosystem:
- **Codecs**:
- `plexor-codec-serde-json`: JSON serialization.
- `plexor-codec-capnp`: Cap'n Proto serialization.
- **Transports (WIP)**:
- `plexor-zmq`: ZeroMQ transport.
- `plexor-http`: HTTP/SSE transport.
- `plexor-websocket`: WebSocket transport.
## License
Mozilla Public License, version 2.0.