q 0.1.1

Q is a Rust GDExtension crate for Godot, offering a Game Manager, Music Manager, Bevy ECS, and Tokio-powered multi-threading. It simplifies and enhances game development with efficient tools and scalable performance.
Documentation
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, LazyLock};

use crossbeam_channel::{Receiver, Sender, unbounded};
use godot::prelude::*;
use serde_json::Value;

use crate::core::actor::Actor;
use crate::core::ecs::EntityStore;
use crate::core::event::{GameEvent, GameRequest};

struct Channels {
    req_tx: Sender<GameRequest>,
    evt_rx: Receiver<GameEvent>,
    entity_store: Arc<EntityStore>,
    shutdown_flag: Arc<AtomicBool>,
}

static CHANNELS: LazyLock<Channels> = LazyLock::new(|| {
    let (req_tx, req_rx) = unbounded();
    let (evt_tx, evt_rx) = unbounded();
    let entity_store = Arc::new(EntityStore::new());
    let shutdown_flag = Arc::new(AtomicBool::new(false));
    Actor::spawn(entity_store.clone(), req_rx, evt_tx, shutdown_flag.clone());
    Channels {
        req_tx,
        evt_rx,
        entity_store,
        shutdown_flag,
    }
});

#[derive(GodotClass)]
#[class(base = Node)]
pub struct EventBridge {
    base: Base<Node>,
}

#[godot_api]
impl INode for EventBridge {
    fn init(base: Base<Node>) -> Self {
        let _ = &*CHANNELS;
        Self { base }
    }

    fn process(&mut self, _delta: f64) {
        while let Ok(event) = CHANNELS.evt_rx.try_recv() {
            self.emit_event(event);
        }
    }
}

#[godot_api]
impl EventBridge {
    #[signal]
    fn entity_spawned(id: GString, x: f32, y: f32);

    #[signal]
    fn entity_despawned(id: GString);

    #[signal]
    fn position_updated(id: GString, x: f32, y: f32);

    #[signal]
    fn custom_event(event_type: GString, payload: GString);

    #[signal]
    fn error_event(message: GString);

    #[func]
    pub fn send_request(&self, request_type: GString, payload: GString) {
        let request_str = request_type.to_string();
        let payload_str = payload.to_string();
        let request = if let Ok(json) = serde_json::from_str::<Value>(&payload_str) {
            Self::parse_json_request(&request_str, &json, &payload_str)
        } else {
            Self::parse_csv_request(&request_str, &payload_str)
        };
        let _ = CHANNELS.req_tx.send(request);
    }

    fn parse_json_request(request_type: &str, json: &Value, raw: &str) -> GameRequest {
        match request_type {
            "spawn_entity" => {
                let x = json["x"].as_f64().map(|v| v as f32);
                let y = json["y"].as_f64().map(|v| v as f32);
                if let (Some(x), Some(y)) = (x, y) {
                    GameRequest::SpawnEntity { x, y }
                } else {
                    GameRequest::Custom(request_type.to_string(), raw.to_string())
                }
            }
            "despawn_entity" => {
                let id = json["id"]
                    .as_str()
                    .map(|s| s.to_string())
                    .unwrap_or_else(|| raw.to_string());
                GameRequest::DespawnEntity { id }
            }
            "update_position" => {
                let id = json["id"].as_str().map(|s| s.to_string());
                let x = json["x"].as_f64().map(|v| v as f32);
                let y = json["y"].as_f64().map(|v| v as f32);
                if let (Some(id), Some(x), Some(y)) = (id, x, y) {
                    GameRequest::UpdatePosition { id, x, y }
                } else {
                    GameRequest::Custom(request_type.to_string(), raw.to_string())
                }
            }
            "shutdown" => GameRequest::Shutdown,
            _ => GameRequest::Custom(request_type.to_string(), raw.to_string()),
        }
    }

    fn parse_csv_request(request_type: &str, payload: &str) -> GameRequest {
        match request_type {
            "spawn_entity" => {
                let parts: Vec<&str> = payload.split(',').collect();
                if parts.len() == 2
                    && let (Ok(x), Ok(y)) = (parts[0].trim().parse(), parts[1].trim().parse())
                {
                    return GameRequest::SpawnEntity { x, y };
                }
                GameRequest::Custom(request_type.to_string(), payload.to_string())
            }
            "despawn_entity" => GameRequest::DespawnEntity {
                id: payload.to_string(),
            },
            "update_position" => {
                let parts: Vec<&str> = payload.split(',').collect();
                if parts.len() == 3
                    && let (Ok(x), Ok(y)) = (parts[1].trim().parse(), parts[2].trim().parse())
                {
                    return GameRequest::UpdatePosition {
                        id: parts[0].trim().to_string(),
                        x,
                        y,
                    };
                }
                GameRequest::Custom(request_type.to_string(), payload.to_string())
            }
            "shutdown" => GameRequest::Shutdown,
            _ => GameRequest::Custom(request_type.to_string(), payload.to_string()),
        }
    }

    #[func]
    pub fn get_entity_count(&self) -> i64 {
        CHANNELS.entity_store.entity_count() as i64
    }

    #[func]
    pub fn get_all_entity_ids(&self) -> Array<GString> {
        let ids = CHANNELS.entity_store.all_entity_ids();
        let mut arr = Array::new();
        for id in ids {
            arr.push(&GString::from(&id.to_string()));
        }
        arr
    }

    #[func]
    pub fn despawn_all(&self) {
        CHANNELS.entity_store.despawn_all();
    }

    #[func]
    pub fn shutdown(&self) {
        CHANNELS.shutdown_flag.store(true, Ordering::Relaxed);
        let _ = CHANNELS.req_tx.send(GameRequest::Shutdown);
    }

    fn emit_event(&mut self, event: GameEvent) {
        match event {
            GameEvent::EntitySpawned { id, x, y } => {
                self.base_mut().emit_signal(
                    "entity_spawned",
                    &[
                        GString::from(&id).to_variant(),
                        x.to_variant(),
                        y.to_variant(),
                    ],
                );
            }
            GameEvent::EntityDespawned { id } => {
                self.base_mut()
                    .emit_signal("entity_despawned", &[GString::from(&id).to_variant()]);
            }
            GameEvent::PositionUpdated { id, x, y } => {
                self.base_mut().emit_signal(
                    "position_updated",
                    &[
                        GString::from(&id).to_variant(),
                        x.to_variant(),
                        y.to_variant(),
                    ],
                );
            }
            GameEvent::Custom(event_type, payload) => {
                self.base_mut().emit_signal(
                    "custom_event",
                    &[
                        GString::from(&event_type).to_variant(),
                        GString::from(&payload).to_variant(),
                    ],
                );
            }
            GameEvent::Error(message) => {
                self.base_mut()
                    .emit_signal("error_event", &[GString::from(&message).to_variant()]);
            }
        }
    }
}