Add aethex_cloud module - connects engine to Railway gateway

This commit is contained in:
MrPiglr 2026-03-06 16:03:36 -07:00
parent ce88320459
commit 39c99577f5
14 changed files with 1335 additions and 0 deletions

View 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)

View 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);
}
}

View 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

View 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;
}

View 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

View 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;
}
}

View 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

View 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);
}
}

View 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

View 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();
}
}

View 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

View 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"

View 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;
}
}

View 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