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 dashmap::DashMap;
use godot::classes::file_access::ModeFlags;
use godot::prelude::*;
use godot::tools::GFile;
use serde::{Deserialize, Serialize};
use serde_json::Value;

pub trait AbstractDataMap: Serialize + for<'de> Deserialize<'de> + Sized {
    fn to_variant_map(&self) -> DashMap<String, Variant> {
        let map = DashMap::new();
        if let Value::Object(obj) =
            serde_json::to_value(self).unwrap_or_else(|_| Value::Object(Default::default()))
        {
            for (key, value) in obj {
                let variant = match value {
                    Value::String(s) => Variant::from(s),
                    Value::Number(n) => {
                        if let Some(f) = n.as_f64() {
                            Variant::from(f as f32)
                        } else {
                            Variant::from(n.as_i64().unwrap_or(0))
                        }
                    }
                    Value::Bool(b) => Variant::from(b),
                    _ => Variant::from(""),
                };
                map.insert(key, variant);
            }
        }
        map
    }

    fn from_variant_map(map: &DashMap<String, Variant>) -> Option<Self> {
        let mut json_map = serde_json::Map::new();
        for entry in map.iter() {
            let json_value = if let Ok(s) = entry.value().try_to::<String>() {
                Value::String(s)
            } else if let Ok(f) = entry.value().try_to::<f32>() {
                Value::Number(serde_json::Number::from_f64(f as f64).unwrap())
            } else if let Ok(i) = entry.value().try_to::<i64>() {
                Value::Number(serde_json::Number::from(i))
            } else if let Ok(b) = entry.value().try_to::<bool>() {
                Value::Bool(b)
            } else {
                Value::Null
            };
            json_map.insert(entry.key().clone(), json_value);
        }
        serde_json::from_value(Value::Object(json_map)).ok()
    }

    fn to_json(&self) -> String {
        serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string())
    }

    fn from_json(json: &str) -> Option<Self> {
        serde_json::from_str(json).ok()
    }

    fn to_save_gfile_json(&self, file_path: &str) -> bool {
        let json_string = self.to_json();
        if let Ok(mut file) = GFile::open(file_path, ModeFlags::WRITE) {
            let _ = file.write_gstring_line(&GString::from(&json_string));
            true
        } else {
            godot_error!("Failed to save data to file: {}", file_path);
            false
        }
    }

    fn from_load_gfile_json(file_path: &str) -> Option<Self> {
        let file = GFile::open(file_path, ModeFlags::READ);

        match file {
            Ok(mut file) => {
                if let Ok(json_string) = file.read_gstring_line() {
                    return Self::from_json(&json_string.to_string());
                } else {
                    godot_error!("[AbstractDataMap] ERROR: Failed to read JSON string.");
                }
            }
            Err(e) => {
                if e.to_string().contains("ERR_FILE_NOT_FOUND") {
                    godot_warn!("[AbstractDataMap] File not found, returning None.");
                } else {
                    godot_error!(
                        "[AbstractDataMap] ERROR: Failed to open file {}: {:?}",
                        file_path,
                        e
                    );
                }
            }
        }

        None
    }
}