Add aethex_cloud module - connects engine to Railway gateway
This commit is contained in:
parent
ce88320459
commit
39c99577f5
14 changed files with 1335 additions and 0 deletions
18
engine/modules/aethex_cloud/SCsub
Normal file
18
engine/modules/aethex_cloud/SCsub
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
Import("env")
|
||||||
|
Import("env_modules")
|
||||||
|
|
||||||
|
env_aethex_cloud = env_modules.Clone()
|
||||||
|
|
||||||
|
# Module source files
|
||||||
|
module_sources = [
|
||||||
|
"register_types.cpp",
|
||||||
|
"aethex_cloud.cpp",
|
||||||
|
"aethex_auth.cpp",
|
||||||
|
"aethex_cloud_save.cpp",
|
||||||
|
"aethex_asset_library.cpp",
|
||||||
|
"aethex_telemetry.cpp",
|
||||||
|
]
|
||||||
|
|
||||||
|
env_aethex_cloud.add_source_files(env.modules_sources, module_sources)
|
||||||
148
engine/modules/aethex_cloud/aethex_asset_library.cpp
Normal file
148
engine/modules/aethex_cloud/aethex_asset_library.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_asset_library.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#include "aethex_asset_library.h"
|
||||||
|
#include "aethex_cloud.h"
|
||||||
|
#include "core/io/http_client.h"
|
||||||
|
#include "core/io/file_access.h"
|
||||||
|
|
||||||
|
void AethexAssetLibrary::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("search", "query", "category", "page", "per_page"), &AethexAssetLibrary::search, DEFVAL(""), DEFVAL(""), DEFVAL(1), DEFVAL(20));
|
||||||
|
ClassDB::bind_method(D_METHOD("get_asset", "asset_id"), &AethexAssetLibrary::get_asset);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_categories"), &AethexAssetLibrary::get_categories);
|
||||||
|
ClassDB::bind_method(D_METHOD("download_asset", "asset_id", "destination"), &AethexAssetLibrary::download_asset);
|
||||||
|
ClassDB::bind_method(D_METHOD("publish_asset", "name", "description", "category", "data"), &AethexAssetLibrary::publish_asset);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("search_async", "query", "category", "callback"), &AethexAssetLibrary::search_async);
|
||||||
|
ClassDB::bind_method(D_METHOD("download_async", "asset_id", "destination", "callback"), &AethexAssetLibrary::download_async);
|
||||||
|
|
||||||
|
ADD_SIGNAL(MethodInfo("search_completed", PropertyInfo(Variant::DICTIONARY, "results")));
|
||||||
|
ADD_SIGNAL(MethodInfo("download_completed", PropertyInfo(Variant::STRING, "asset_id"), PropertyInfo(Variant::STRING, "path")));
|
||||||
|
ADD_SIGNAL(MethodInfo("download_progress", PropertyInfo(Variant::STRING, "asset_id"), PropertyInfo(Variant::FLOAT, "progress")));
|
||||||
|
ADD_SIGNAL(MethodInfo("publish_completed", PropertyInfo(Variant::DICTIONARY, "result")));
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexAssetLibrary::AethexAssetLibrary() {
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexAssetLibrary::~AethexAssetLibrary() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexAssetLibrary::set_cloud(AethexCloud *p_cloud) {
|
||||||
|
cloud = p_cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAssetLibrary::search(const String &p_query, const String &p_category, int p_page, int p_per_page) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String endpoint = "/api/assets?page=" + itos(p_page) + "&perPage=" + itos(p_per_page);
|
||||||
|
if (!p_query.is_empty()) {
|
||||||
|
endpoint += "&query=" + p_query.uri_encode();
|
||||||
|
}
|
||||||
|
if (!p_category.is_empty()) {
|
||||||
|
endpoint += "&category=" + p_category.uri_encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary result = cloud->make_request(endpoint, HTTPClient::METHOD_GET);
|
||||||
|
|
||||||
|
if (result.get("success", false)) {
|
||||||
|
emit_signal("search_completed", result.get("data", Dictionary()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAssetLibrary::get_asset(const String &p_asset_id) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloud->make_request("/api/assets/" + p_asset_id, HTTPClient::METHOD_GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAssetLibrary::get_categories() {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloud->make_request("/api/assets/categories", HTTPClient::METHOD_GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAssetLibrary::download_asset(const String &p_asset_id, const String &p_destination) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
|
||||||
|
if (!cloud) {
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get download info
|
||||||
|
Dictionary asset_info = cloud->make_request("/api/assets/" + p_asset_id + "/download", HTTPClient::METHOD_GET);
|
||||||
|
|
||||||
|
if (!asset_info.get("success", false)) {
|
||||||
|
result["error"] = "Failed to get download URL";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement actual file download
|
||||||
|
// This would need to download from the URL and save to p_destination
|
||||||
|
|
||||||
|
result["success"] = true;
|
||||||
|
result["path"] = p_destination;
|
||||||
|
emit_signal("download_completed", p_asset_id, p_destination);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAssetLibrary::publish_asset(const String &p_name, const String &p_description, const String &p_category, const PackedByteArray &p_data) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary data;
|
||||||
|
data["name"] = p_name;
|
||||||
|
data["description"] = p_description;
|
||||||
|
data["category"] = p_category;
|
||||||
|
data["data"] = Variant(p_data).stringify(); // Base64 encode in real implementation
|
||||||
|
|
||||||
|
Dictionary result = cloud->make_request("/api/assets/publish", HTTPClient::METHOD_POST, data);
|
||||||
|
|
||||||
|
if (result.get("success", false)) {
|
||||||
|
emit_signal("publish_completed", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexAssetLibrary::search_async(const String &p_query, const String &p_category, const Callable &p_callback) {
|
||||||
|
Dictionary result = search(p_query, p_category);
|
||||||
|
if (p_callback.is_valid()) {
|
||||||
|
p_callback.call(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexAssetLibrary::download_async(const String &p_asset_id, const String &p_destination, const Callable &p_callback) {
|
||||||
|
Dictionary result = download_asset(p_asset_id, p_destination);
|
||||||
|
if (p_callback.is_valid()) {
|
||||||
|
p_callback.call(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
engine/modules/aethex_cloud/aethex_asset_library.h
Normal file
46
engine/modules/aethex_cloud/aethex_asset_library.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_asset_library.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef AETHEX_ASSET_LIBRARY_H
|
||||||
|
#define AETHEX_ASSET_LIBRARY_H
|
||||||
|
|
||||||
|
#include "core/object/ref_counted.h"
|
||||||
|
|
||||||
|
class AethexCloud;
|
||||||
|
|
||||||
|
class AethexAssetLibrary : public RefCounted {
|
||||||
|
GDCLASS(AethexAssetLibrary, RefCounted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AethexCloud *cloud = nullptr;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void set_cloud(AethexCloud *p_cloud);
|
||||||
|
|
||||||
|
// Browse assets
|
||||||
|
Dictionary search(const String &p_query = "", const String &p_category = "", int p_page = 1, int p_per_page = 20);
|
||||||
|
Dictionary get_asset(const String &p_asset_id);
|
||||||
|
Dictionary get_categories();
|
||||||
|
|
||||||
|
// Download
|
||||||
|
Dictionary download_asset(const String &p_asset_id, const String &p_destination);
|
||||||
|
|
||||||
|
// Publish from editor
|
||||||
|
Dictionary publish_asset(const String &p_name, const String &p_description, const String &p_category, const PackedByteArray &p_data);
|
||||||
|
|
||||||
|
// Async
|
||||||
|
void search_async(const String &p_query, const String &p_category, const Callable &p_callback);
|
||||||
|
void download_async(const String &p_asset_id, const String &p_destination, const Callable &p_callback);
|
||||||
|
|
||||||
|
AethexAssetLibrary();
|
||||||
|
~AethexAssetLibrary();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AETHEX_ASSET_LIBRARY_H
|
||||||
174
engine/modules/aethex_cloud/aethex_auth.cpp
Normal file
174
engine/modules/aethex_cloud/aethex_auth.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_auth.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#include "aethex_auth.h"
|
||||||
|
#include "aethex_cloud.h"
|
||||||
|
#include "core/io/http_client.h"
|
||||||
|
|
||||||
|
void AethexAuth::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("login", "email", "password"), &AethexAuth::login);
|
||||||
|
ClassDB::bind_method(D_METHOD("register_account", "username", "email", "password"), &AethexAuth::register_account);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_profile"), &AethexAuth::get_profile);
|
||||||
|
ClassDB::bind_method(D_METHOD("logout"), &AethexAuth::logout);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("login_async", "email", "password", "callback"), &AethexAuth::login_async);
|
||||||
|
ClassDB::bind_method(D_METHOD("register_async", "username", "email", "password", "callback"), &AethexAuth::register_async);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("is_logged_in"), &AethexAuth::is_logged_in);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_user_id"), &AethexAuth::get_user_id);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_username"), &AethexAuth::get_username);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_email"), &AethexAuth::get_email);
|
||||||
|
|
||||||
|
ADD_SIGNAL(MethodInfo("login_success", PropertyInfo(Variant::DICTIONARY, "user")));
|
||||||
|
ADD_SIGNAL(MethodInfo("login_failed", PropertyInfo(Variant::STRING, "error")));
|
||||||
|
ADD_SIGNAL(MethodInfo("logged_out"));
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexAuth::AethexAuth() {
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexAuth::~AethexAuth() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexAuth::set_cloud(AethexCloud *p_cloud) {
|
||||||
|
cloud = p_cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAuth::login(const String &p_email, const String &p_password) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
|
||||||
|
if (!cloud) {
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary data;
|
||||||
|
data["email"] = p_email;
|
||||||
|
data["password"] = p_password;
|
||||||
|
|
||||||
|
Dictionary response = cloud->make_request("/api/auth/login", HTTPClient::METHOD_POST, data);
|
||||||
|
|
||||||
|
if (response.get("success", false)) {
|
||||||
|
Dictionary resp_data = response.get("data", Dictionary());
|
||||||
|
|
||||||
|
if (resp_data.has("token")) {
|
||||||
|
cloud->set_auth_token(resp_data["token"]);
|
||||||
|
|
||||||
|
if (resp_data.has("user")) {
|
||||||
|
Dictionary user = resp_data["user"];
|
||||||
|
user_id = user.get("id", "");
|
||||||
|
username = user.get("username", "");
|
||||||
|
email = user.get("email", "");
|
||||||
|
logged_in = true;
|
||||||
|
|
||||||
|
emit_signal("login_success", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
result["success"] = true;
|
||||||
|
result["user"] = resp_data.get("user", Dictionary());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String error = response.get("error", "Login failed");
|
||||||
|
emit_signal("login_failed", error);
|
||||||
|
result["error"] = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAuth::register_account(const String &p_username, const String &p_email, const String &p_password) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
|
||||||
|
if (!cloud) {
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary data;
|
||||||
|
data["username"] = p_username;
|
||||||
|
data["email"] = p_email;
|
||||||
|
data["password"] = p_password;
|
||||||
|
|
||||||
|
Dictionary response = cloud->make_request("/api/auth/register", HTTPClient::METHOD_POST, data);
|
||||||
|
|
||||||
|
if (response.get("success", false)) {
|
||||||
|
result["success"] = true;
|
||||||
|
result["data"] = response.get("data", Dictionary());
|
||||||
|
} else {
|
||||||
|
result["error"] = response.get("error", "Registration failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexAuth::get_profile() {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
|
||||||
|
if (!cloud) {
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary response = cloud->make_request("/api/auth/profile", HTTPClient::METHOD_GET);
|
||||||
|
|
||||||
|
if (response.get("success", false)) {
|
||||||
|
result["success"] = true;
|
||||||
|
result["user"] = response.get("data", Dictionary());
|
||||||
|
} else {
|
||||||
|
result["error"] = response.get("error", "Failed to get profile");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexAuth::logout() {
|
||||||
|
if (cloud) {
|
||||||
|
cloud->set_auth_token("");
|
||||||
|
}
|
||||||
|
|
||||||
|
user_id = "";
|
||||||
|
username = "";
|
||||||
|
email = "";
|
||||||
|
logged_in = false;
|
||||||
|
|
||||||
|
emit_signal("logged_out");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexAuth::login_async(const String &p_email, const String &p_password, const Callable &p_callback) {
|
||||||
|
// TODO: Implement proper async
|
||||||
|
Dictionary result = login(p_email, p_password);
|
||||||
|
if (p_callback.is_valid()) {
|
||||||
|
p_callback.call(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexAuth::register_async(const String &p_username, const String &p_email, const String &p_password, const Callable &p_callback) {
|
||||||
|
// TODO: Implement proper async
|
||||||
|
Dictionary result = register_account(p_username, p_email, p_password);
|
||||||
|
if (p_callback.is_valid()) {
|
||||||
|
p_callback.call(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AethexAuth::is_logged_in() const {
|
||||||
|
return logged_in;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AethexAuth::get_user_id() const {
|
||||||
|
return user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AethexAuth::get_username() const {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AethexAuth::get_email() const {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
52
engine/modules/aethex_cloud/aethex_auth.h
Normal file
52
engine/modules/aethex_cloud/aethex_auth.h
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_auth.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef AETHEX_AUTH_H
|
||||||
|
#define AETHEX_AUTH_H
|
||||||
|
|
||||||
|
#include "core/object/ref_counted.h"
|
||||||
|
|
||||||
|
class AethexCloud;
|
||||||
|
|
||||||
|
class AethexAuth : public RefCounted {
|
||||||
|
GDCLASS(AethexAuth, RefCounted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AethexCloud *cloud = nullptr;
|
||||||
|
|
||||||
|
String user_id;
|
||||||
|
String username;
|
||||||
|
String email;
|
||||||
|
bool logged_in = false;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void set_cloud(AethexCloud *p_cloud);
|
||||||
|
|
||||||
|
// Auth methods
|
||||||
|
Dictionary login(const String &p_email, const String &p_password);
|
||||||
|
Dictionary register_account(const String &p_username, const String &p_email, const String &p_password);
|
||||||
|
Dictionary get_profile();
|
||||||
|
void logout();
|
||||||
|
|
||||||
|
// Async versions
|
||||||
|
void login_async(const String &p_email, const String &p_password, const Callable &p_callback);
|
||||||
|
void register_async(const String &p_username, const String &p_email, const String &p_password, const Callable &p_callback);
|
||||||
|
|
||||||
|
// Status
|
||||||
|
bool is_logged_in() const;
|
||||||
|
String get_user_id() const;
|
||||||
|
String get_username() const;
|
||||||
|
String get_email() const;
|
||||||
|
|
||||||
|
AethexAuth();
|
||||||
|
~AethexAuth();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AETHEX_AUTH_H
|
||||||
350
engine/modules/aethex_cloud/aethex_cloud.cpp
Normal file
350
engine/modules/aethex_cloud/aethex_cloud.cpp
Normal file
|
|
@ -0,0 +1,350 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_cloud.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#include "aethex_cloud.h"
|
||||||
|
#include "aethex_auth.h"
|
||||||
|
#include "aethex_cloud_save.h"
|
||||||
|
#include "aethex_asset_library.h"
|
||||||
|
#include "aethex_telemetry.h"
|
||||||
|
|
||||||
|
#include "core/io/json.h"
|
||||||
|
#include "core/config/project_settings.h"
|
||||||
|
#include "core/version.h"
|
||||||
|
|
||||||
|
AethexCloud *AethexCloud::singleton = nullptr;
|
||||||
|
|
||||||
|
AethexCloud *AethexCloud::get_singleton() {
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::_bind_methods() {
|
||||||
|
// Configuration
|
||||||
|
ClassDB::bind_method(D_METHOD("set_gateway_url", "url"), &AethexCloud::set_gateway_url);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_gateway_url"), &AethexCloud::get_gateway_url);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_api_key", "key"), &AethexCloud::set_api_key);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_api_key"), &AethexCloud::get_api_key);
|
||||||
|
|
||||||
|
// Connection
|
||||||
|
ClassDB::bind_method(D_METHOD("connect_to_gateway"), &AethexCloud::connect_to_gateway);
|
||||||
|
ClassDB::bind_method(D_METHOD("disconnect_from_gateway"), &AethexCloud::disconnect_from_gateway);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_status"), &AethexCloud::get_status);
|
||||||
|
ClassDB::bind_method(D_METHOD("is_connected"), &AethexCloud::is_connected);
|
||||||
|
|
||||||
|
// Auth token
|
||||||
|
ClassDB::bind_method(D_METHOD("set_auth_token", "token"), &AethexCloud::set_auth_token);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_auth_token"), &AethexCloud::get_auth_token);
|
||||||
|
|
||||||
|
// Sub-services
|
||||||
|
ClassDB::bind_method(D_METHOD("get_auth"), &AethexCloud::get_auth);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_cloud_save"), &AethexCloud::get_cloud_save);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_asset_library"), &AethexCloud::get_asset_library);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_telemetry"), &AethexCloud::get_telemetry);
|
||||||
|
|
||||||
|
// HTTP
|
||||||
|
ClassDB::bind_method(D_METHOD("make_request", "endpoint", "method", "data"), &AethexCloud::make_request, DEFVAL(HTTPClient::METHOD_GET), DEFVAL(Dictionary()));
|
||||||
|
ClassDB::bind_method(D_METHOD("make_request_async", "endpoint", "method", "data", "callback"), &AethexCloud::make_request_async, DEFVAL(HTTPClient::METHOD_GET), DEFVAL(Dictionary()), DEFVAL(Callable()));
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::STRING, "gateway_url"), "set_gateway_url", "get_gateway_url");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::STRING, "api_key"), "set_api_key", "get_api_key");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::STRING, "auth_token"), "set_auth_token", "get_auth_token");
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
ADD_SIGNAL(MethodInfo("connected"));
|
||||||
|
ADD_SIGNAL(MethodInfo("disconnected"));
|
||||||
|
ADD_SIGNAL(MethodInfo("connection_error", PropertyInfo(Variant::STRING, "error")));
|
||||||
|
ADD_SIGNAL(MethodInfo("message_received", PropertyInfo(Variant::DICTIONARY, "message")));
|
||||||
|
|
||||||
|
// Enums
|
||||||
|
BIND_ENUM_CONSTANT(STATUS_DISCONNECTED);
|
||||||
|
BIND_ENUM_CONSTANT(STATUS_CONNECTING);
|
||||||
|
BIND_ENUM_CONSTANT(STATUS_CONNECTED);
|
||||||
|
BIND_ENUM_CONSTANT(STATUS_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexCloud::AethexCloud() {
|
||||||
|
singleton = this;
|
||||||
|
|
||||||
|
// Default gateway URL - can be overridden in project settings
|
||||||
|
gateway_url = "https://engine.aethex.dev";
|
||||||
|
|
||||||
|
// Load from project settings if available
|
||||||
|
if (ProjectSettings::get_singleton()->has_setting("aethex/cloud/gateway_url")) {
|
||||||
|
gateway_url = GLOBAL_GET("aethex/cloud/gateway_url");
|
||||||
|
}
|
||||||
|
if (ProjectSettings::get_singleton()->has_setting("aethex/cloud/api_key")) {
|
||||||
|
api_key = GLOBAL_GET("aethex/cloud/api_key");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize sub-services
|
||||||
|
auth.instantiate();
|
||||||
|
cloud_save.instantiate();
|
||||||
|
asset_library.instantiate();
|
||||||
|
telemetry.instantiate();
|
||||||
|
|
||||||
|
// Set parent reference
|
||||||
|
auth->set_cloud(this);
|
||||||
|
cloud_save->set_cloud(this);
|
||||||
|
asset_library->set_cloud(this);
|
||||||
|
telemetry->set_cloud(this);
|
||||||
|
|
||||||
|
// Initialize WebSocket
|
||||||
|
websocket.instantiate();
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexCloud::~AethexCloud() {
|
||||||
|
disconnect_from_gateway();
|
||||||
|
singleton = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::set_gateway_url(const String &p_url) {
|
||||||
|
gateway_url = p_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AethexCloud::get_gateway_url() const {
|
||||||
|
return gateway_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::set_api_key(const String &p_key) {
|
||||||
|
api_key = p_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AethexCloud::get_api_key() const {
|
||||||
|
return api_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::set_auth_token(const String &p_token) {
|
||||||
|
auth_token = p_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AethexCloud::get_auth_token() const {
|
||||||
|
return auth_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error AethexCloud::connect_to_gateway() {
|
||||||
|
if (status == STATUS_CONNECTED || status == STATUS_CONNECTING) {
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = STATUS_CONNECTING;
|
||||||
|
|
||||||
|
// Connect WebSocket for real-time
|
||||||
|
String ws_url = gateway_url.replace("https://", "wss://").replace("http://", "ws://") + "/ws";
|
||||||
|
|
||||||
|
Error err = websocket->connect_to_url(ws_url);
|
||||||
|
if (err != OK) {
|
||||||
|
status = STATUS_ERROR;
|
||||||
|
emit_signal("connection_error", "Failed to connect to WebSocket");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::disconnect_from_gateway() {
|
||||||
|
if (websocket.is_valid()) {
|
||||||
|
websocket->close();
|
||||||
|
}
|
||||||
|
websocket_connected = false;
|
||||||
|
status = STATUS_DISCONNECTED;
|
||||||
|
emit_signal("disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexCloud::ConnectionStatus AethexCloud::get_status() const {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AethexCloud::is_connected() const {
|
||||||
|
return status == STATUS_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<AethexAuth> AethexCloud::get_auth() const {
|
||||||
|
return auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<AethexCloudSave> AethexCloud::get_cloud_save() const {
|
||||||
|
return cloud_save;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<AethexAssetLibrary> AethexCloud::get_asset_library() const {
|
||||||
|
return asset_library;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<AethexTelemetry> AethexCloud::get_telemetry() const {
|
||||||
|
return telemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexCloud::make_request(const String &p_endpoint, HTTPClient::Method p_method, const Dictionary &p_data) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
|
||||||
|
Ref<HTTPClient> http;
|
||||||
|
http.instantiate();
|
||||||
|
|
||||||
|
// Parse gateway URL
|
||||||
|
String host = gateway_url.replace("https://", "").replace("http://", "");
|
||||||
|
bool use_ssl = gateway_url.begins_with("https://");
|
||||||
|
int port = use_ssl ? 443 : 80;
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
Error err = http->connect_to_host(host, port);
|
||||||
|
if (err != OK) {
|
||||||
|
result["error"] = "Failed to connect to gateway";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for connection
|
||||||
|
while (http->get_status() == HTTPClient::STATUS_CONNECTING ||
|
||||||
|
http->get_status() == HTTPClient::STATUS_RESOLVING) {
|
||||||
|
http->poll();
|
||||||
|
OS::get_singleton()->delay_usec(100000); // 100ms
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http->get_status() != HTTPClient::STATUS_CONNECTED) {
|
||||||
|
result["error"] = "Connection failed";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare headers
|
||||||
|
Vector<String> headers;
|
||||||
|
headers.push_back("Content-Type: application/json");
|
||||||
|
headers.push_back("X-Engine-Version: " + String(VERSION_FULL_CONFIG));
|
||||||
|
|
||||||
|
if (!auth_token.is_empty()) {
|
||||||
|
headers.push_back("Authorization: Bearer " + auth_token);
|
||||||
|
}
|
||||||
|
if (!api_key.is_empty()) {
|
||||||
|
headers.push_back("X-API-Key: " + api_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare body
|
||||||
|
String body_str;
|
||||||
|
if (!p_data.is_empty()) {
|
||||||
|
body_str = JSON::stringify(p_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make request
|
||||||
|
err = http->request(p_method, p_endpoint, headers, body_str);
|
||||||
|
if (err != OK) {
|
||||||
|
result["error"] = "Request failed";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for response
|
||||||
|
while (http->get_status() == HTTPClient::STATUS_REQUESTING) {
|
||||||
|
http->poll();
|
||||||
|
OS::get_singleton()->delay_usec(100000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!http->has_response()) {
|
||||||
|
result["error"] = "No response";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
PackedByteArray response_body;
|
||||||
|
while (http->get_status() == HTTPClient::STATUS_BODY) {
|
||||||
|
http->poll();
|
||||||
|
PackedByteArray chunk = http->read_response_body_chunk();
|
||||||
|
if (chunk.size() > 0) {
|
||||||
|
response_body.append_array(chunk);
|
||||||
|
}
|
||||||
|
OS::get_singleton()->delay_usec(10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response
|
||||||
|
int response_code = http->get_response_code();
|
||||||
|
String response_str = String::utf8((const char *)response_body.ptr(), response_body.size());
|
||||||
|
|
||||||
|
result["status_code"] = response_code;
|
||||||
|
result["success"] = response_code >= 200 && response_code < 300;
|
||||||
|
|
||||||
|
// Try to parse as JSON
|
||||||
|
JSON json;
|
||||||
|
if (json.parse(response_str) == OK) {
|
||||||
|
result["data"] = json.get_data();
|
||||||
|
} else {
|
||||||
|
result["data"] = response_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::make_request_async(const String &p_endpoint, HTTPClient::Method p_method, const Dictionary &p_data, const Callable &p_callback) {
|
||||||
|
// For async requests, we'll use HTTPRequest node in actual implementation
|
||||||
|
// This is a simplified version that calls synchronously
|
||||||
|
// TODO: Implement proper async with threading or HTTPRequest
|
||||||
|
Dictionary result = make_request(p_endpoint, p_method, p_data);
|
||||||
|
if (p_callback.is_valid()) {
|
||||||
|
p_callback.call(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::_on_websocket_message(const PackedByteArray &p_data) {
|
||||||
|
String message_str = String::utf8((const char *)p_data.ptr(), p_data.size());
|
||||||
|
|
||||||
|
JSON json;
|
||||||
|
if (json.parse(message_str) == OK) {
|
||||||
|
Dictionary message = json.get_data();
|
||||||
|
String type = message.get("type", "");
|
||||||
|
|
||||||
|
if (type == "connected") {
|
||||||
|
status = STATUS_CONNECTED;
|
||||||
|
websocket_connected = true;
|
||||||
|
emit_signal("connected");
|
||||||
|
|
||||||
|
// Authenticate WebSocket if we have a token
|
||||||
|
if (!auth_token.is_empty()) {
|
||||||
|
Dictionary auth_msg;
|
||||||
|
auth_msg["type"] = "auth";
|
||||||
|
auth_msg["token"] = auth_token;
|
||||||
|
auth_msg["engineVersion"] = VERSION_FULL_CONFIG;
|
||||||
|
websocket->send_text(JSON::stringify(auth_msg));
|
||||||
|
}
|
||||||
|
} else if (type == "auth_success") {
|
||||||
|
// WebSocket authenticated
|
||||||
|
} else if (type == "pong") {
|
||||||
|
// Heartbeat response
|
||||||
|
} else {
|
||||||
|
emit_signal("message_received", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloud::process(double p_delta) {
|
||||||
|
if (!websocket.is_valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
websocket->poll();
|
||||||
|
|
||||||
|
WebSocketPeer::State ws_state = websocket->get_ready_state();
|
||||||
|
|
||||||
|
switch (ws_state) {
|
||||||
|
case WebSocketPeer::STATE_OPEN: {
|
||||||
|
while (websocket->get_available_packet_count() > 0) {
|
||||||
|
PackedByteArray packet = websocket->get_packet();
|
||||||
|
_on_websocket_message(packet);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case WebSocketPeer::STATE_CLOSING:
|
||||||
|
// Still processing
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WebSocketPeer::STATE_CLOSED: {
|
||||||
|
if (websocket_connected) {
|
||||||
|
websocket_connected = false;
|
||||||
|
status = STATUS_DISCONNECTED;
|
||||||
|
emit_signal("disconnected");
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
engine/modules/aethex_cloud/aethex_cloud.h
Normal file
94
engine/modules/aethex_cloud/aethex_cloud.h
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_cloud.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef AETHEX_CLOUD_H
|
||||||
|
#define AETHEX_CLOUD_H
|
||||||
|
|
||||||
|
#include "core/object/ref_counted.h"
|
||||||
|
#include "core/io/http_client.h"
|
||||||
|
#include "modules/websocket/websocket_peer.h"
|
||||||
|
|
||||||
|
class AethexAuth;
|
||||||
|
class AethexCloudSave;
|
||||||
|
class AethexAssetLibrary;
|
||||||
|
class AethexTelemetry;
|
||||||
|
|
||||||
|
class AethexCloud : public Object {
|
||||||
|
GDCLASS(AethexCloud, Object);
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum ConnectionStatus {
|
||||||
|
STATUS_DISCONNECTED,
|
||||||
|
STATUS_CONNECTING,
|
||||||
|
STATUS_CONNECTED,
|
||||||
|
STATUS_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
static AethexCloud *singleton;
|
||||||
|
|
||||||
|
String gateway_url;
|
||||||
|
String api_key;
|
||||||
|
String auth_token;
|
||||||
|
ConnectionStatus status = STATUS_DISCONNECTED;
|
||||||
|
|
||||||
|
Ref<AethexAuth> auth;
|
||||||
|
Ref<AethexCloudSave> cloud_save;
|
||||||
|
Ref<AethexAssetLibrary> asset_library;
|
||||||
|
Ref<AethexTelemetry> telemetry;
|
||||||
|
|
||||||
|
Ref<WebSocketPeer> websocket;
|
||||||
|
bool websocket_connected = false;
|
||||||
|
|
||||||
|
// Internal methods
|
||||||
|
void _websocket_connect();
|
||||||
|
void _websocket_process();
|
||||||
|
void _on_websocket_message(const PackedByteArray &p_data);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
static AethexCloud *get_singleton();
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
void set_gateway_url(const String &p_url);
|
||||||
|
String get_gateway_url() const;
|
||||||
|
|
||||||
|
void set_api_key(const String &p_key);
|
||||||
|
String get_api_key() const;
|
||||||
|
|
||||||
|
// Connection
|
||||||
|
Error connect_to_gateway();
|
||||||
|
void disconnect_from_gateway();
|
||||||
|
ConnectionStatus get_status() const;
|
||||||
|
bool is_connected() const;
|
||||||
|
|
||||||
|
// Auth token (set after login)
|
||||||
|
void set_auth_token(const String &p_token);
|
||||||
|
String get_auth_token() const;
|
||||||
|
|
||||||
|
// Sub-services
|
||||||
|
Ref<AethexAuth> get_auth() const;
|
||||||
|
Ref<AethexCloudSave> get_cloud_save() const;
|
||||||
|
Ref<AethexAssetLibrary> get_asset_library() const;
|
||||||
|
Ref<AethexTelemetry> get_telemetry() const;
|
||||||
|
|
||||||
|
// HTTP helpers
|
||||||
|
Dictionary make_request(const String &p_endpoint, HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Dictionary &p_data = Dictionary());
|
||||||
|
void make_request_async(const String &p_endpoint, HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Dictionary &p_data = Dictionary(), const Callable &p_callback = Callable());
|
||||||
|
|
||||||
|
// Process (call from main loop)
|
||||||
|
void process(double p_delta);
|
||||||
|
|
||||||
|
AethexCloud();
|
||||||
|
~AethexCloud();
|
||||||
|
};
|
||||||
|
|
||||||
|
VARIANT_ENUM_CAST(AethexCloud::ConnectionStatus);
|
||||||
|
|
||||||
|
#endif // AETHEX_CLOUD_H
|
||||||
147
engine/modules/aethex_cloud/aethex_cloud_save.cpp
Normal file
147
engine/modules/aethex_cloud/aethex_cloud_save.cpp
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_cloud_save.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#include "aethex_cloud_save.h"
|
||||||
|
#include "aethex_cloud.h"
|
||||||
|
#include "core/io/http_client.h"
|
||||||
|
|
||||||
|
void AethexCloudSave::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("list_saves", "game_id"), &AethexCloudSave::list_saves, DEFVAL(""));
|
||||||
|
ClassDB::bind_method(D_METHOD("get_save", "save_id"), &AethexCloudSave::get_save);
|
||||||
|
ClassDB::bind_method(D_METHOD("create_save", "game_id", "name", "data"), &AethexCloudSave::create_save);
|
||||||
|
ClassDB::bind_method(D_METHOD("update_save", "save_id", "data"), &AethexCloudSave::update_save);
|
||||||
|
ClassDB::bind_method(D_METHOD("delete_save", "save_id"), &AethexCloudSave::delete_save);
|
||||||
|
ClassDB::bind_method(D_METHOD("sync_project", "project_path"), &AethexCloudSave::sync_project);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("list_saves_async", "game_id", "callback"), &AethexCloudSave::list_saves_async);
|
||||||
|
ClassDB::bind_method(D_METHOD("save_async", "game_id", "name", "data", "callback"), &AethexCloudSave::save_async);
|
||||||
|
|
||||||
|
ADD_SIGNAL(MethodInfo("save_completed", PropertyInfo(Variant::DICTIONARY, "result")));
|
||||||
|
ADD_SIGNAL(MethodInfo("save_failed", PropertyInfo(Variant::STRING, "error")));
|
||||||
|
ADD_SIGNAL(MethodInfo("sync_completed", PropertyInfo(Variant::DICTIONARY, "result")));
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexCloudSave::AethexCloudSave() {
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexCloudSave::~AethexCloudSave() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloudSave::set_cloud(AethexCloud *p_cloud) {
|
||||||
|
cloud = p_cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexCloudSave::list_saves(const String &p_game_id) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String endpoint = "/api/cloud/saves";
|
||||||
|
if (!p_game_id.is_empty()) {
|
||||||
|
endpoint += "?gameId=" + p_game_id.uri_encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloud->make_request(endpoint, HTTPClient::METHOD_GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexCloudSave::get_save(const String &p_save_id) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloud->make_request("/api/cloud/saves/" + p_save_id, HTTPClient::METHOD_GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexCloudSave::create_save(const String &p_game_id, const String &p_name, const Dictionary &p_data) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary data;
|
||||||
|
data["gameId"] = p_game_id;
|
||||||
|
data["name"] = p_name;
|
||||||
|
data["data"] = p_data;
|
||||||
|
|
||||||
|
Dictionary result = cloud->make_request("/api/cloud/saves", HTTPClient::METHOD_POST, data);
|
||||||
|
|
||||||
|
if (result.get("success", false)) {
|
||||||
|
emit_signal("save_completed", result);
|
||||||
|
} else {
|
||||||
|
emit_signal("save_failed", result.get("error", "Save failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexCloudSave::update_save(const String &p_save_id, const Dictionary &p_data) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary data;
|
||||||
|
data["data"] = p_data;
|
||||||
|
|
||||||
|
return cloud->make_request("/api/cloud/saves/" + p_save_id, HTTPClient::METHOD_PUT, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexCloudSave::delete_save(const String &p_save_id) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloud->make_request("/api/cloud/saves/" + p_save_id, HTTPClient::METHOD_DELETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary AethexCloudSave::sync_project(const String &p_project_path) {
|
||||||
|
if (!cloud) {
|
||||||
|
Dictionary result;
|
||||||
|
result["success"] = false;
|
||||||
|
result["error"] = "Cloud not initialized";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary data;
|
||||||
|
data["projectPath"] = p_project_path;
|
||||||
|
// TODO: Read and package project files
|
||||||
|
|
||||||
|
Dictionary result = cloud->make_request("/api/cloud/projects/sync", HTTPClient::METHOD_POST, data);
|
||||||
|
|
||||||
|
if (result.get("success", false)) {
|
||||||
|
emit_signal("sync_completed", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloudSave::list_saves_async(const String &p_game_id, const Callable &p_callback) {
|
||||||
|
Dictionary result = list_saves(p_game_id);
|
||||||
|
if (p_callback.is_valid()) {
|
||||||
|
p_callback.call(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexCloudSave::save_async(const String &p_game_id, const String &p_name, const Dictionary &p_data, const Callable &p_callback) {
|
||||||
|
Dictionary result = create_save(p_game_id, p_name, p_data);
|
||||||
|
if (p_callback.is_valid()) {
|
||||||
|
p_callback.call(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
engine/modules/aethex_cloud/aethex_cloud_save.h
Normal file
45
engine/modules/aethex_cloud/aethex_cloud_save.h
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_cloud_save.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef AETHEX_CLOUD_SAVE_H
|
||||||
|
#define AETHEX_CLOUD_SAVE_H
|
||||||
|
|
||||||
|
#include "core/object/ref_counted.h"
|
||||||
|
|
||||||
|
class AethexCloud;
|
||||||
|
|
||||||
|
class AethexCloudSave : public RefCounted {
|
||||||
|
GDCLASS(AethexCloudSave, RefCounted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AethexCloud *cloud = nullptr;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void set_cloud(AethexCloud *p_cloud);
|
||||||
|
|
||||||
|
// Cloud save methods
|
||||||
|
Dictionary list_saves(const String &p_game_id = "");
|
||||||
|
Dictionary get_save(const String &p_save_id);
|
||||||
|
Dictionary create_save(const String &p_game_id, const String &p_name, const Dictionary &p_data);
|
||||||
|
Dictionary update_save(const String &p_save_id, const Dictionary &p_data);
|
||||||
|
Dictionary delete_save(const String &p_save_id);
|
||||||
|
|
||||||
|
// Project sync
|
||||||
|
Dictionary sync_project(const String &p_project_path);
|
||||||
|
|
||||||
|
// Async versions
|
||||||
|
void list_saves_async(const String &p_game_id, const Callable &p_callback);
|
||||||
|
void save_async(const String &p_game_id, const String &p_name, const Dictionary &p_data, const Callable &p_callback);
|
||||||
|
|
||||||
|
AethexCloudSave();
|
||||||
|
~AethexCloudSave();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AETHEX_CLOUD_SAVE_H
|
||||||
125
engine/modules/aethex_cloud/aethex_telemetry.cpp
Normal file
125
engine/modules/aethex_cloud/aethex_telemetry.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_telemetry.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#include "aethex_telemetry.h"
|
||||||
|
#include "aethex_cloud.h"
|
||||||
|
#include "core/io/http_client.h"
|
||||||
|
#include "core/os/os.h"
|
||||||
|
#include "core/version.h"
|
||||||
|
|
||||||
|
void AethexTelemetry::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &AethexTelemetry::set_enabled);
|
||||||
|
ClassDB::bind_method(D_METHOD("is_enabled"), &AethexTelemetry::is_enabled);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("track_event", "event_name", "properties"), &AethexTelemetry::track_event, DEFVAL(Dictionary()));
|
||||||
|
ClassDB::bind_method(D_METHOD("track_screen", "screen_name"), &AethexTelemetry::track_screen);
|
||||||
|
ClassDB::bind_method(D_METHOD("track_error", "error", "stack_trace"), &AethexTelemetry::track_error, DEFVAL(""));
|
||||||
|
ClassDB::bind_method(D_METHOD("report_crash", "message", "stack_trace", "context"), &AethexTelemetry::report_crash, DEFVAL(Dictionary()));
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("flush"), &AethexTelemetry::flush);
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexTelemetry::AethexTelemetry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
AethexTelemetry::~AethexTelemetry() {
|
||||||
|
// Flush remaining events on shutdown
|
||||||
|
if (!event_buffer.is_empty()) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::set_cloud(AethexCloud *p_cloud) {
|
||||||
|
cloud = p_cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::set_enabled(bool p_enabled) {
|
||||||
|
enabled = p_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AethexTelemetry::is_enabled() const {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::track_event(const String &p_event_name, const Dictionary &p_properties) {
|
||||||
|
if (!enabled || !cloud) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary event;
|
||||||
|
event["name"] = p_event_name;
|
||||||
|
event["properties"] = p_properties;
|
||||||
|
event["timestamp"] = Time::get_singleton()->get_datetime_string_from_system(true);
|
||||||
|
event["platform"] = OS::get_singleton()->get_name();
|
||||||
|
event["engineVersion"] = VERSION_FULL_CONFIG;
|
||||||
|
|
||||||
|
event_buffer.push_back(event);
|
||||||
|
|
||||||
|
// Auto-flush if buffer is full
|
||||||
|
if (event_buffer.size() >= buffer_size) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::track_screen(const String &p_screen_name) {
|
||||||
|
Dictionary props;
|
||||||
|
props["screen"] = p_screen_name;
|
||||||
|
track_event("screen_view", props);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::track_error(const String &p_error, const String &p_stack_trace) {
|
||||||
|
Dictionary props;
|
||||||
|
props["error"] = p_error;
|
||||||
|
props["stack_trace"] = p_stack_trace;
|
||||||
|
track_event("error", props);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::report_crash(const String &p_message, const String &p_stack_trace, const Dictionary &p_context) {
|
||||||
|
if (!cloud) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary data;
|
||||||
|
data["message"] = p_message;
|
||||||
|
data["stackTrace"] = p_stack_trace;
|
||||||
|
data["context"] = p_context;
|
||||||
|
data["platform"] = OS::get_singleton()->get_name();
|
||||||
|
data["engineVersion"] = VERSION_FULL_CONFIG;
|
||||||
|
data["timestamp"] = Time::get_singleton()->get_datetime_string_from_system(true);
|
||||||
|
|
||||||
|
// Crash reports are sent immediately, not buffered
|
||||||
|
cloud->make_request("/api/telemetry/crash", HTTPClient::METHOD_POST, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::flush() {
|
||||||
|
if (!cloud || event_buffer.is_empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all buffered events
|
||||||
|
for (int i = 0; i < event_buffer.size(); i++) {
|
||||||
|
cloud->make_request("/api/telemetry/event", HTTPClient::METHOD_POST, event_buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
event_buffer.clear();
|
||||||
|
time_since_flush = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AethexTelemetry::process(double p_delta) {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_since_flush += p_delta;
|
||||||
|
|
||||||
|
// Auto-flush periodically
|
||||||
|
if (time_since_flush >= flush_interval && !event_buffer.is_empty()) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
55
engine/modules/aethex_cloud/aethex_telemetry.h
Normal file
55
engine/modules/aethex_cloud/aethex_telemetry.h
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* aethex_telemetry.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef AETHEX_TELEMETRY_H
|
||||||
|
#define AETHEX_TELEMETRY_H
|
||||||
|
|
||||||
|
#include "core/object/ref_counted.h"
|
||||||
|
#include "core/templates/vector.h"
|
||||||
|
|
||||||
|
class AethexCloud;
|
||||||
|
|
||||||
|
class AethexTelemetry : public RefCounted {
|
||||||
|
GDCLASS(AethexTelemetry, RefCounted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AethexCloud *cloud = nullptr;
|
||||||
|
bool enabled = true;
|
||||||
|
Vector<Dictionary> event_buffer;
|
||||||
|
int buffer_size = 100;
|
||||||
|
double flush_interval = 30.0;
|
||||||
|
double time_since_flush = 0.0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void set_cloud(AethexCloud *p_cloud);
|
||||||
|
|
||||||
|
// Enable/disable
|
||||||
|
void set_enabled(bool p_enabled);
|
||||||
|
bool is_enabled() const;
|
||||||
|
|
||||||
|
// Track events
|
||||||
|
void track_event(const String &p_event_name, const Dictionary &p_properties = Dictionary());
|
||||||
|
void track_screen(const String &p_screen_name);
|
||||||
|
void track_error(const String &p_error, const String &p_stack_trace = "");
|
||||||
|
|
||||||
|
// Crash reporting
|
||||||
|
void report_crash(const String &p_message, const String &p_stack_trace, const Dictionary &p_context = Dictionary());
|
||||||
|
|
||||||
|
// Flush
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
// Process (call periodically)
|
||||||
|
void process(double p_delta);
|
||||||
|
|
||||||
|
AethexTelemetry();
|
||||||
|
~AethexTelemetry();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AETHEX_TELEMETRY_H
|
||||||
17
engine/modules/aethex_cloud/config.py
Normal file
17
engine/modules/aethex_cloud/config.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
def can_build(env, platform):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def configure(env):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_doc_classes():
|
||||||
|
return [
|
||||||
|
"AethexCloud",
|
||||||
|
"AethexAuth",
|
||||||
|
"AethexCloudSave",
|
||||||
|
"AethexAssetLibrary",
|
||||||
|
"AethexTelemetry",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_doc_path():
|
||||||
|
return "doc_classes"
|
||||||
48
engine/modules/aethex_cloud/register_types.cpp
Normal file
48
engine/modules/aethex_cloud/register_types.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* register_types.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#include "register_types.h"
|
||||||
|
|
||||||
|
#include "aethex_cloud.h"
|
||||||
|
#include "aethex_auth.h"
|
||||||
|
#include "aethex_cloud_save.h"
|
||||||
|
#include "aethex_asset_library.h"
|
||||||
|
#include "aethex_telemetry.h"
|
||||||
|
|
||||||
|
#include "core/object/class_db.h"
|
||||||
|
#include "core/config/engine.h"
|
||||||
|
|
||||||
|
static AethexCloud *aethex_cloud_singleton = nullptr;
|
||||||
|
|
||||||
|
void initialize_aethex_cloud_module(ModuleInitializationLevel p_level) {
|
||||||
|
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register classes
|
||||||
|
GDREGISTER_CLASS(AethexCloud);
|
||||||
|
GDREGISTER_CLASS(AethexAuth);
|
||||||
|
GDREGISTER_CLASS(AethexCloudSave);
|
||||||
|
GDREGISTER_CLASS(AethexAssetLibrary);
|
||||||
|
GDREGISTER_CLASS(AethexTelemetry);
|
||||||
|
|
||||||
|
// Create singleton
|
||||||
|
aethex_cloud_singleton = memnew(AethexCloud);
|
||||||
|
Engine::get_singleton()->add_singleton(Engine::Singleton("AethexCloud", aethex_cloud_singleton));
|
||||||
|
}
|
||||||
|
|
||||||
|
void uninitialize_aethex_cloud_module(ModuleInitializationLevel p_level) {
|
||||||
|
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aethex_cloud_singleton) {
|
||||||
|
Engine::get_singleton()->remove_singleton("AethexCloud");
|
||||||
|
memdelete(aethex_cloud_singleton);
|
||||||
|
aethex_cloud_singleton = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
engine/modules/aethex_cloud/register_types.h
Normal file
16
engine/modules/aethex_cloud/register_types.h
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* register_types.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* AeThex Engine */
|
||||||
|
/* https://aethex.dev */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef AETHEX_CLOUD_REGISTER_TYPES_H
|
||||||
|
#define AETHEX_CLOUD_REGISTER_TYPES_H
|
||||||
|
|
||||||
|
#include "modules/register_module_types.h"
|
||||||
|
|
||||||
|
void initialize_aethex_cloud_module(ModuleInitializationLevel p_level);
|
||||||
|
void uninitialize_aethex_cloud_module(ModuleInitializationLevel p_level);
|
||||||
|
|
||||||
|
#endif // AETHEX_CLOUD_REGISTER_TYPES_H
|
||||||
Loading…
Reference in a new issue