AeThex-Engine-Core/engine/modules/aethex_cloud/aethex_cloud.cpp
mrpiglr 190b6b2eab
Some checks are pending
Build AeThex Engine / build-windows (push) Waiting to run
Build AeThex Engine / build-linux (push) Waiting to run
Build AeThex Engine / build-macos (push) Waiting to run
Build AeThex Engine / create-release (push) Blocked by required conditions
Deploy Docsify Documentation / build (push) Waiting to run
Deploy Docsify Documentation / deploy (push) Blocked by required conditions
chore: sync local changes to Forgejo
2026-03-13 00:37:06 -07:00

356 lines
11 KiB
C++

/**************************************************************************/
/* 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_cloud_connected"), &AethexCloud::is_cloud_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 using factory method (abstract class)
websocket = Ref<WebSocketPeer>(WebSocketPeer::create());
}
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_cloud_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;
// Use factory method for abstract HTTPClient class
Ref<HTTPClient> http = Ref<HTTPClient>(HTTPClient::create());
if (http.is_null()) {
result["error"] = "Failed to create HTTP client";
return result;
}
// 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(AETHEX_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 - convert String body to bytes for the API
CharString body_utf8 = body_str.utf8();
err = http->request(p_method, p_endpoint, headers, (const uint8_t *)body_utf8.get_data(), body_utf8.length());
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"] = AETHEX_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) {
Vector<uint8_t> packet;
websocket->get_packet_buffer(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;
}
}