Note: this project is not FOSS despite being open source. I have to decide on the terms of use first, as I don't want to support scam MMOs. If you would like to use this for whatever reason, feel free to message me and I'll likely give you a FOSS license.
Chimera is a collection of mostly-separate Rust crates that help replace the boilerplate involved in making an MMO. It is essentially a network-exposed Entity Component System. Chimera manages all client/server syncing along with all entity-to-entity messaging. Entities are essentially just state machines that expose pure functions & impure functions.
Entities can only communicate using messages and act on other entities via effects. Effects are invoked asynchronously to prevent dead locks. Entities create their in-game representation by constructing a node tree, which is sent to the clients by the chimera-networking crate. The chimera-reify-gdext crate can then convert this node tree into the proper game objects, which can communicate with the server over message dispatches.
#[derive(Debug)]
pub struct A;
impl StaticallyTypedEntity for A {
const TYPE: &'static str = "A";
}
impl A {
pub fn new(_: &EntityList) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self))
}
}
#[entity(singleton)]
impl Entity for A {
fn get_id(&self) -> chimera_ecs::prelude::Id {
Self::get_singleton_id()
}
fn get_type(&self) -> CowStr {
CowStr::Borrowed(Self::TYPE)
}
fn on_pushed(&mut self) -> Effect {
// BMessage is automatically generated by the #[entity] macro
// If B wasn't a singleton, we'd have to specify the entity Id
// Message factories have one method per #[action] fn
BMessage::say_hello(false)
}
fn on_popped(&mut self) -> Effect {
BMessage::say_goodbye(true)
}
// Actions & queries must return an Effect or ()
// The shim code is automatically generated by the #[entity] macro
// Queries must return something that's Serializable & Deserializable for the query to be exposed to the network
// Actions & queries can specify #[action(no_serialize)] or #[query(no_serialize)] to make the action/query local only (within the process)
#[query]
fn get_name(&self) -> Effect<String> {
Effect::value("Entity A".to_owned())
}
// Action params must also be Serializable & Deserializable for them to be network-callable
#[action]
fn tell(&mut self, message: String) {
println!(message);
}
}
#[derive(Debug)]
pub struct B {
said_hello: bool,
waved: bool,
dispatch: Arc<EntityDispatch>,
}
impl StaticallyTypedEntity for B {
const TYPE: &'static str = "B";
}
impl B {
pub fn new(e: &EntityList) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self {
said_hello: false,
waved: false,
dispatch: e.open_dispatch(Self::get_singleton_id()),
}))
}
}
#[entity(singleton)]
impl Entity for B {
fn get_id(&self) -> chimera_ecs::prelude::Id {
Self::get_singleton_id()
}
fn get_type(&self) -> CowStr {
CowStr::Borrowed(Self::TYPE)
}
#[action]
fn say_hello(&mut self, waved: bool) -> Effect {
self.said_hello = true;
self.waved = waved;
if waved {
// AQuery is also generated by the #[entity] macro
// Query factories have one method per #[query] fn
AQuery::get_name().and_then(|name: String| AMessage::tell(format!("Hello, {name}!")))
} else {
Effect::none()
}
}
#[action]
fn say_goodbye(&mut self, waved: bool) -> Effect {
self.said_hello = false;
self.waved = waved;
if waved {
AQuery::get_name().and_then(|name: String| AMessage::tell(format!("Goodbye, {name}!")))
} else {
Effect::none()
}
}
#[action(dispatchable)]
fn bar(&mut self, foo: i32) {
// the game client can call dispatch.send to send this message to the server
}
fn render(&self) -> Result<Option<Node>> {
Ok(Some(Node {
scene: "res://B.tscn".to_owned(),
props: str_hash_map!({
// data is automatically sent (only deltas are sent over the network)
// props that change frequently have a special Variable prop
// A value prop can be anything that implements serde::Serialize
has_said_hello => self.said_hello.as_value()?,
dispatch => self.dispatch.as_dispatch()?,
}),
..Default::default()
}))
}
}
Chimera can currently act as a single-node game server. The sharding crate is currently in progress.
Chimera currently only has a pre-made godot 4 node reifier. Support for unreal engine and unity is planned, but those crates will be worked on after sharding is usable.