RingOfRaces/modules/godot_remote/godot_remote/GRServer.cpp

1806 lines
59 KiB
C++

/* GRServer.cpp */
#ifndef NO_GODOTREMOTE_SERVER
#include "GRServer.h"
#include "GRNotifications.h"
#include "GRPacket.h"
#include "GodotRemote.h"
#ifndef GDNATIVE_LIBRARY
#include "core/input_map.h"
#include "core/io/pck_packer.h"
#include "core/io/resource_loader.h"
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "core/os/input_event.h"
#include "core/os/thread_safe.h"
#include "main/input_default.h"
#include "scene/main/node.h"
#include "scene/main/scene_tree.h"
#else
#include <Directory.hpp>
#include <File.hpp>
#include <Input.hpp>
#include <InputEvent.hpp>
#include <InputMap.hpp>
#include <Node.hpp>
#include <PCKPacker.hpp>
#include <ResourceLoader.hpp>
#include <SceneTree.hpp>
using namespace godot;
#endif
using namespace GRUtils;
#ifndef GDNATIVE_LIBRARY
void GRServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("_load_settings"), &GRServer::_load_settings);
ClassDB::bind_method(D_METHOD("_remove_resize_viewport", "vp"), &GRServer::_remove_resize_viewport);
ClassDB::bind_method(D_METHOD("_on_grviewport_deleting"), &GRServer::_on_grviewport_deleting);
ClassDB::bind_method(D_METHOD("get_gr_viewport"), &GRServer::get_gr_viewport);
ClassDB::bind_method(D_METHOD("force_update_custom_input_scene"), &GRServer::force_update_custom_input_scene);
ClassDB::bind_method(D_METHOD("set_video_stream_enabled", "value"), &GRServer::set_video_stream_enabled);
ClassDB::bind_method(D_METHOD("set_skip_frames", "frames"), &GRServer::set_skip_frames);
//ClassDB::bind_method(D_METHOD("set_auto_adjust_scale"), &GRServer::set_auto_adjust_scale);
ClassDB::bind_method(D_METHOD("set_jpg_quality", "quality"), &GRServer::set_jpg_quality);
ClassDB::bind_method(D_METHOD("set_render_scale", "scale"), &GRServer::set_render_scale);
ClassDB::bind_method(D_METHOD("set_password", "password"), &GRServer::set_password);
ClassDB::bind_method(D_METHOD("set_custom_input_scene", "_scn"), &GRServer::set_custom_input_scene);
ClassDB::bind_method(D_METHOD("set_custom_input_scene_compressed", "_is_compressed"), &GRServer::set_custom_input_scene_compressed);
ClassDB::bind_method(D_METHOD("set_custom_input_scene_compression_type", "_type"), &GRServer::set_custom_input_scene_compression_type);
ClassDB::bind_method(D_METHOD("is_video_stream_enabled"), &GRServer::is_video_stream_enabled);
ClassDB::bind_method(D_METHOD("get_skip_frames"), &GRServer::get_skip_frames);
//ClassDB::bind_method(D_METHOD("is_auto_adjust_scale"), &GRServer::is_auto_adjust_scale);
ClassDB::bind_method(D_METHOD("get_jpg_quality"), &GRServer::get_jpg_quality);
ClassDB::bind_method(D_METHOD("get_render_scale"), &GRServer::get_render_scale);
ClassDB::bind_method(D_METHOD("get_password"), &GRServer::get_password);
ClassDB::bind_method(D_METHOD("get_custom_input_scene"), &GRServer::get_custom_input_scene);
ClassDB::bind_method(D_METHOD("is_custom_input_scene_compressed"), &GRServer::is_custom_input_scene_compressed);
ClassDB::bind_method(D_METHOD("get_custom_input_scene_compression_type"), &GRServer::get_custom_input_scene_compression_type);
//ADD_PROPERTY(PropertyInfo(Variant::BOOL, "video_stream_enabled"), "set_video_stream_enabled", "is_video_stream_enabled");
//ADD_PROPERTY(PropertyInfo(Variant::INT, "skip_frames"), "set_skip_frames", "get_skip_frames");
////ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_adjust_scale"), "set_auto_adjust_scale", "is_auto_adjust_scale");
//ADD_PROPERTY(PropertyInfo(Variant::INT, "jpg_quality"), "set_jpg_quality", "get_jpg_quality");
//ADD_PROPERTY(PropertyInfo(Variant::INT, "render_scale"), "set_render_scale", "get_render_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "password"), "set_password", "get_password");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_input_scene"), "set_custom_input_scene", "get_custom_input_scene");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_input_scene_compressed"), "set_custom_input_scene_compressed", "is_custom_input_scene_compressed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_input_scene_compression_type"), "set_custom_input_scene_compression_type", "get_custom_input_scene_compression_type");
ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::STRING, "device_id")));
ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::STRING, "device_id")));
ADD_SIGNAL(MethodInfo("client_viewport_orientation_changed", PropertyInfo(Variant::BOOL, "is_vertical")));
ADD_SIGNAL(MethodInfo("client_viewport_aspect_ratio_changed", PropertyInfo(Variant::REAL, "stream_aspect_ratio")));
}
#else
void GRServer::_register_methods() {
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/*
METHOD_REG(GRServer, _internal_call_only_deffered_start);
METHOD_REG(GRServer, _internal_call_only_deffered_stop);
METHOD_REG(GRServer, _internal_call_only_deffered_restart);
METHOD_REG(GRServer, get_avg_ping);
METHOD_REG(GRServer, get_avg_fps);
METHOD_REG(GRServer, get_port);
METHOD_REG(GRServer, set_port);
METHOD_REG(GRServer, start);
METHOD_REG(GRServer, stop);
METHOD_REG(GRServer, get_status);
register_signal<GRServer>("status_changed", "status", GODOT_VARIANT_TYPE_INT);
register_property<GRServer, uint16_t>("port", &GRServer::set_port, &GRServer::get_port, 52341);
*/
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
METHOD_REG(GRServer, _notification);
METHOD_REG(GRServer, _thread_listen);
METHOD_REG(GRServer, _thread_connection);
METHOD_REG(GRServer, _on_grviewport_deleting);
METHOD_REG(GRServer, _load_settings);
METHOD_REG(GRServer, _remove_resize_viewport);
METHOD_REG(GRServer, get_gr_viewport);
METHOD_REG(GRServer, force_update_custom_input_scene);
METHOD_REG(GRServer, set_video_stream_enabled);
METHOD_REG(GRServer, set_skip_frames);
//METHOD_REG(GRServer, set_auto_adjust_scale);
METHOD_REG(GRServer, set_jpg_quality);
METHOD_REG(GRServer, set_render_scale);
METHOD_REG(GRServer, set_password);
METHOD_REG(GRServer, set_custom_input_scene);
METHOD_REG(GRServer, set_custom_input_scene_compressed);
METHOD_REG(GRServer, set_custom_input_scene_compression_type);
METHOD_REG(GRServer, is_video_stream_enabled);
METHOD_REG(GRServer, get_skip_frames);
//METHOD_REG(GRServer, is_auto_adjust_scale);
METHOD_REG(GRServer, get_jpg_quality);
METHOD_REG(GRServer, get_render_scale);
METHOD_REG(GRServer, get_password);
METHOD_REG(GRServer, get_custom_input_scene);
METHOD_REG(GRServer, is_custom_input_scene_compressed);
METHOD_REG(GRServer, get_custom_input_scene_compression_type);
//register_property<GRServer, bool>("video_stream_enabled", &GRServer::set_video_stream_enabled, &GRServer::is_video_stream_enabled, true);
//register_property<GRServer, int>("skip_frames", &GRServer::set_skip_frames, &GRServer::get_skip_frames, 0);
////register_property<GRServer, bool>("auto_adjust_scale", &GRServer::set_auto_adjust_scale, &GRServer::is_auto_adjust_scale, true);
//register_property<GRServer, int>("jpg_quality", &GRServer::set_jpg_quality, &GRServer::get_jpg_quality, 80);
//register_property<GRServer, int>("render_scale", &GRServer::set_render_scale, &GRServer::get_render_scale, 0.3f);
register_property<GRServer, String>("password", &GRServer::set_password, &GRServer::get_password, "");
register_property<GRServer, String>("custom_input_scene", &GRServer::set_custom_input_scene, &GRServer::get_custom_input_scene, "");
register_property<GRServer, bool>("custom_input_scene_compressed", &GRServer::set_custom_input_scene_compressed, &GRServer::is_custom_input_scene_compressed, true);
register_property<GRServer, int>("custom_input_scene_compression_type", &GRServer::set_custom_input_scene_compression_type, &GRServer::get_custom_input_scene_compression_type, 0);
register_signal<GRServer>("client_connected", "device_id", GODOT_VARIANT_TYPE_STRING);
register_signal<GRServer>("client_disconnected", "device_id", GODOT_VARIANT_TYPE_STRING);
register_signal<GRServer>("client_viewport_orientation_changed", "is_vertical", GODOT_VARIANT_TYPE_BOOL);
register_signal<GRServer>("client_viewport_aspect_ratio_changed", "stream_aspect_ratio", GODOT_VARIANT_TYPE_REAL);
}
#endif
void GRServer::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_POSTINITIALIZE:
#ifndef GDNATIVE_LIBRARY
_init();
#endif
break;
case NOTIFICATION_PREDELETE: {
_deinit();
GRDevice::_deinit();
break;
case NOTIFICATION_EXIT_TREE:
if (get_status() == (int)WorkingStatus::STATUS_WORKING) {
_internal_call_only_deffered_stop();
}
break;
}
}
}
void GRServer::set_auto_adjust_scale(bool _val) {
auto_adjust_scale = _val;
}
bool GRServer::is_auto_adjust_scale() {
return auto_adjust_scale;
}
bool GRServer::set_video_stream_enabled(bool val) {
if (resize_viewport && !resize_viewport->is_queued_for_deletion() &&
resize_viewport->is_video_stream_enabled() != val) {
resize_viewport->set_video_stream_enabled(val);
return true;
}
return false;
}
bool GRServer::is_video_stream_enabled() {
if (resize_viewport && !resize_viewport->is_queued_for_deletion())
return resize_viewport->is_video_stream_enabled();
return false;
}
bool GRServer::set_compression_type(ImageCompressionType _type) {
if (resize_viewport && !resize_viewport->is_queued_for_deletion() &&
resize_viewport->get_compression_type() != _type) {
resize_viewport->set_compression_type(_type);
return true;
}
return false;
}
GRDevice::ImageCompressionType GRServer::get_compression_type() {
if (resize_viewport && !resize_viewport->is_queued_for_deletion())
return resize_viewport->get_compression_type();
return ImageCompressionType::COMPRESSION_UNCOMPRESSED;
}
bool GRServer::set_render_scale(float _scale) {
if (resize_viewport && !resize_viewport->is_queued_for_deletion() &&
resize_viewport->get_rendering_scale() != _scale) {
resize_viewport->set_rendering_scale(_scale);
return true;
}
return false;
}
float GRServer::get_render_scale() {
if (resize_viewport && !resize_viewport->is_queued_for_deletion())
return resize_viewport->get_rendering_scale();
return -1;
}
bool GRServer::set_skip_frames(int fps) {
if (resize_viewport && !resize_viewport->is_queued_for_deletion() &&
resize_viewport->get_skip_frames() != fps) {
resize_viewport->set_skip_frames(fps);
return true;
}
return false;
}
int GRServer::get_skip_frames() {
if (resize_viewport && !resize_viewport->is_queued_for_deletion())
return resize_viewport->get_skip_frames();
return -1;
}
bool GRServer::set_jpg_quality(int quality) {
ERR_FAIL_COND_V(quality < 0 || quality > 100, false);
if (resize_viewport && !resize_viewport->is_queued_for_deletion() &&
resize_viewport->get_jpg_quality() != quality) {
resize_viewport->set_jpg_quality(quality);
return true;
}
return false;
}
int GRServer::get_jpg_quality() {
if (resize_viewport && !resize_viewport->is_queued_for_deletion())
return resize_viewport->get_jpg_quality();
return -1;
}
void GRServer::set_password(String _pass) {
password = _pass;
}
String GRServer::get_password() {
return password;
}
void GRServer::set_custom_input_scene(String _scn) {
if (custom_input_scene != _scn) {
custom_input_scene = _scn;
force_update_custom_input_scene();
}
}
String GRServer::get_custom_input_scene() {
return custom_input_scene;
}
void GRServer::set_custom_input_scene_compressed(bool _is_compressed) {
custom_input_pck_compressed = _is_compressed;
}
bool GRServer::is_custom_input_scene_compressed() {
return custom_input_pck_compressed;
}
void GRServer::set_custom_input_scene_compression_type(int _type) {
custom_input_pck_compression_type = ENUM_CONV(Compression::Mode) _type;
}
int GRServer::get_custom_input_scene_compression_type() {
return custom_input_pck_compression_type;
}
void GRServer::_init() {
set_name("GodotRemoteServer");
LEAVE_IF_EDITOR();
tcp_server.instance();
#ifndef GDNATIVE_LIBRARY
#else
GRDevice::_init();
#endif
Mutex_create(connection_mutex);
custom_input_scene_regex_resource_finder.instance();
custom_input_scene_regex_resource_finder->compile(custom_input_scene_regex_resource_finder_pattern);
init_server_utils();
}
void GRServer::_deinit() {
LEAVE_IF_EDITOR();
if (get_status() == (int)WorkingStatus::STATUS_WORKING) {
_internal_call_only_deffered_stop();
}
Mutex_delete(connection_mutex);
custom_input_scene_regex_resource_finder.unref();
deinit_server_utils();
}
void GRServer::_internal_call_only_deffered_start() {
switch ((WorkingStatus)get_status()) {
case WorkingStatus::STATUS_WORKING:
ERR_FAIL_MSG("Can't start already working GodotRemote Server");
case WorkingStatus::STATUS_STARTING:
ERR_FAIL_MSG("Can't start already starting GodotRemote Server");
case WorkingStatus::STATUS_STOPPING:
ERR_FAIL_MSG("Can't start stopping GodotRemote Server");
}
set_status(WorkingStatus::STATUS_STARTING);
_log("Starting GodotRemote server. Version: " + str(GodotRemote::get_singleton()->get_version()), LogLevel::LL_NORMAL);
if (server_thread_listen) {
server_thread_listen->close_thread();
memdelete(server_thread_listen);
server_thread_listen = nullptr;
}
if (!resize_viewport) {
resize_viewport = memnew(GRSViewport);
add_child(resize_viewport);
}
server_thread_listen = memnew(ListenerThreadParamsServer);
server_thread_listen->dev = this;
Thread_start(server_thread_listen->thread_ref, GRServer, _thread_listen, server_thread_listen, this);
set_status(WorkingStatus::STATUS_WORKING);
call_deferred("_load_settings");
GRNotifications::add_notification("Godot Remote Server Status", "Server started", GRNotifications::GRNotifications::NotificationIcon::ICON_SUCCESS, true, 1.f);
}
void GRServer::_internal_call_only_deffered_stop() {
switch ((WorkingStatus)get_status()) {
case WorkingStatus::STATUS_STOPPED:
ERR_FAIL_MSG("Can't stop already stopped GodotRemote Server");
case WorkingStatus::STATUS_STOPPING:
ERR_FAIL_MSG("Can't stop already stopping GodotRemote Server");
case WorkingStatus::STATUS_STARTING:
ERR_FAIL_MSG("Can't stop starting GodotRemote Server");
}
set_status(WorkingStatus::STATUS_STOPPING);
_log("Stopping GodotRemote server", LogLevel::LL_NORMAL);
if (server_thread_listen) {
server_thread_listen->close_thread();
memdelete(server_thread_listen);
server_thread_listen = nullptr;
}
if (resize_viewport)
resize_viewport->set_process(false);
call_deferred("_remove_resize_viewport", resize_viewport);
resize_viewport = nullptr;
_send_queue_resize(0);
set_status(WorkingStatus::STATUS_STOPPED);
GRNotifications::add_notification("Godot Remote Server Status", "Server stopped", GRNotifications::NotificationIcon::ICON_FAIL, true, 1.f);
}
void GRServer::_remove_resize_viewport(Node *node) {
GRSViewport *vp = cast_to<GRSViewport>(node);
if (vp && !vp->is_queued_for_deletion()) {
remove_child(vp);
memdelete(vp);
}
}
void GRServer::_on_grviewport_deleting() {
resize_viewport = nullptr;
}
GRSViewport *GRServer::get_gr_viewport() {
return resize_viewport;
}
void GRServer::force_update_custom_input_scene() {
custom_input_scene_was_updated = false;
}
void GRServer::_adjust_viewport_scale() {
if (!resize_viewport)
return;
const float smooth = 0.8f;
float scale = resize_viewport->auto_scale;
if (!auto_adjust_scale) {
//if (scale == -1.f)
// return;
scale = -1.f;
goto end;
}
if (prev_avg_fps == 0) {
prev_avg_fps = avg_fps;
} else {
prev_avg_fps = (prev_avg_fps * smooth) + (avg_fps * (1.f - smooth));
}
_log(prev_avg_fps, LogLevel::LL_NORMAL);
if (prev_avg_fps < resize_viewport->get_skip_frames() - 4) {
scale -= 0.001f;
} else if (prev_avg_fps > resize_viewport->get_skip_frames() - 1) {
scale += 0.0012f;
}
if (scale < 0.1f)
scale = 0.1f;
if (scale > 1)
scale = 1;
end:
resize_viewport->auto_scale = scale;
prev_avg_fps = 0;
resize_viewport->call_deferred("_update_size");
}
void GRServer::_load_settings() {
using_client_settings = false;
const String title = "Server settings updated";
GRNotifications::add_notification_or_update_line(title, "title", "Loaded default server settings", GRNotifications::NotificationIcon::ICON_NONE, 1.f);
// only updated by server itself
password = GET_PS(GodotRemote::ps_server_password_name);
set_custom_input_scene(GET_PS(GodotRemote::ps_server_custom_input_scene_name));
#ifndef GDNATIVE_LIBRARY
set_custom_input_scene_compressed(GET_PS(GodotRemote::ps_server_custom_input_scene_compressed_name));
set_custom_input_scene_compression_type(ENUM_CONV(Compression::Mode)(int) GET_PS(GodotRemote::ps_server_custom_input_scene_compression_type_name));
#endif
GRNotifications::add_notification_or_update_line(title, "cis", "Custom input scene: " + str(get_custom_input_scene()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
if (!get_custom_input_scene().empty()) {
GRNotifications::add_notification_or_update_line(title, "cisc", "Scene compressed: " + str(is_custom_input_scene_compressed()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
GRNotifications::add_notification_or_update_line(title, "cisct", "Scene compression type: " + str(get_custom_input_scene_compression_type()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
}
// can be updated by client
auto_adjust_scale = GET_PS(GodotRemote::ps_server_auto_adjust_scale_name); // TODO move to viewport
GRNotifications::add_notification_or_update_line(title, "auto_scale", "Auto adjust scale: " + str(auto_adjust_scale), GRNotifications::NotificationIcon::ICON_NONE, 1.f); // TODO not completed
if (resize_viewport && !resize_viewport->is_queued_for_deletion()) {
set_video_stream_enabled((bool)GET_PS(GodotRemote::ps_server_stream_enabled_name));
set_compression_type((ImageCompressionType)(int)GET_PS(GodotRemote::ps_server_compression_type_name));
set_jpg_quality(GET_PS(GodotRemote::ps_server_jpg_quality_name));
set_render_scale(GET_PS(GodotRemote::ps_server_scale_of_sending_stream_name));
set_skip_frames(GET_PS(GodotRemote::ps_server_stream_skip_frames_name));
GRNotifications::add_notification_or_update_line(title, "stream", "Stream enabled: " + str(is_video_stream_enabled()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
GRNotifications::add_notification_or_update_line(title, "compression", "Compression type: " + str(get_render_scale()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
GRNotifications::add_notification_or_update_line(title, "quality", "JPG Quality: " + str(get_jpg_quality()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
GRNotifications::add_notification_or_update_line(title, "skip", "Skip Frames: " + str(get_skip_frames()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
GRNotifications::add_notification_or_update_line(title, "scale", "Scale of stream: " + str(get_render_scale()), GRNotifications::NotificationIcon::ICON_NONE, 1.f);
} else {
_log("Resize viewport not found!", LogLevel::LL_ERROR);
GRNotifications::add_notification("Critical Error", "Resize viewport not found!", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
// notification
GRNotificationPanelUpdatable *np = cast_to<GRNotificationPanelUpdatable>(GRNotifications::get_notification(title));
if (np) {
np->clear_lines();
}
}
void GRServer::_update_settings_from_client(const std::map<int, Variant> settings) {
#define SET_BODY(_func, _id, _text, _type) \
if (_func(value)) { \
GRNotifications::add_notification_or_update_line(title, _id, _text + str((_type value)), GRNotifications::NotificationIcon::ICON_NONE, 1.f); \
using_client_settings = true; \
using_client_settings_recently_updated = true; \
}
#define SET_BODY_CONVERT(_func, _conv, _id, _text, _type) \
if (_func(_conv(value))) { \
GRNotifications::add_notification_or_update_line(title, _id, _text + str((_type value)), GRNotifications::NotificationIcon::ICON_NONE, 1.f); \
using_client_settings = true; \
using_client_settings_recently_updated = true; \
}
const String title = "Server settings updated";
GRNotificationPanelUpdatable *np = cast_to<GRNotificationPanelUpdatable>(GRNotifications::get_notification(title));
if (np) {
np->remove_updatable_line("title");
}
if (!resize_viewport || resize_viewport->is_queued_for_deletion()) {
_log("Resize viewport not found!", LogLevel::LL_ERROR);
GRNotifications::add_notification("Critical Error", "Resize viewport not found!", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
for (auto p : settings) {
Variant key = p.first;
Variant value = p.second;
_log("Trying to set server setting from client with key: " + str((int)key) + " and value: " + str(value), LogLevel::LL_DEBUG);
if (key.get_type() == Variant::INT) {
TypesOfServerSettings k = (TypesOfServerSettings)(int)key;
switch (k) {
case TypesOfServerSettings::SERVER_SETTINGS_USE_INTERNAL:
if ((bool)value) {
call_deferred("_load_settings");
return;
}
break;
case TypesOfServerSettings::SERVER_SETTINGS_VIDEO_STREAM_ENABLED: {
SET_BODY(set_video_stream_enabled, "stream", "Stream enabled: ", (bool));
break;
}
case TypesOfServerSettings::SERVER_SETTINGS_COMPRESSION_TYPE: {
SET_BODY_CONVERT(set_compression_type, (ImageCompressionType)(int), "compression", "Compression type: ", (int));
break;
}
case TypesOfServerSettings::SERVER_SETTINGS_JPG_QUALITY: {
SET_BODY(set_jpg_quality, "quality", "JPG Quality: ", (int));
break;
}
case TypesOfServerSettings::SERVER_SETTINGS_SKIP_FRAMES: {
SET_BODY(set_skip_frames, "skip", "Skip Frames: ", (int));
break;
}
case TypesOfServerSettings::SERVER_SETTINGS_RENDER_SCALE: {
SET_BODY(set_render_scale, "scale", "Scale of stream: ", (float));
break;
}
default:
_log("Unknown server setting with code: " + str((int)k), LogLevel::LL_NORMAL);
break;
}
}
}
#undef SET_BODY
#undef SET_BODY_CONVERT
}
void GRServer::_reset_counters() {
GRDevice::_reset_counters();
prev_avg_fps = 0;
}
//////////////////////////////////////////////
////////////////// STATIC ////////////////////
//////////////////////////////////////////////
void GRServer::_thread_listen(THREAD_DATA p_userdata) {
Thread_set_name("GR_listen_thread");
ListenerThreadParamsServer *this_thread_info = (ListenerThreadParamsServer *)p_userdata;
GRServer *dev = this_thread_info->dev;
Ref<TCP_Server> srv = dev->tcp_server;
OS *os = OS::get_singleton();
ConnectionThreadParamsServer *connection_thread_info = nullptr;
Error err = Error::OK;
bool listening_error_notification_shown = false;
while (!this_thread_info->stop_thread) {
if (!srv->is_listening()) {
err = srv->listen(dev->port);
if (err != Error::OK) {
switch (err) {
case Error::ERR_UNAVAILABLE: {
String txt = "Socket listening unavailable";
_log(txt, LogLevel::LL_ERROR);
if (!listening_error_notification_shown)
GRNotifications::add_notification("Can't start listening", txt, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.25f);
break;
}
case Error::ERR_ALREADY_IN_USE: {
String txt = "Socket already in use";
_log(txt, LogLevel::LL_ERROR);
if (!listening_error_notification_shown)
GRNotifications::add_notification("Can't start listening", txt, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.25f);
break;
}
case Error::ERR_INVALID_PARAMETER: {
String txt = "Invalid listening address";
_log(txt, LogLevel::LL_ERROR);
if (!listening_error_notification_shown)
GRNotifications::add_notification("Can't start listening", txt, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.25f);
break;
}
case Error::ERR_CANT_CREATE: {
String txt = "Can't bind listener";
_log(txt, LogLevel::LL_ERROR);
if (!listening_error_notification_shown)
GRNotifications::add_notification("Can't start listening", txt, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.25f);
break;
}
case Error::FAILED: {
String txt = "Failed to start listening";
_log(txt, LogLevel::LL_ERROR);
if (!listening_error_notification_shown)
GRNotifications::add_notification("Can't start listening", txt, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.25f);
break;
}
}
listening_error_notification_shown = true;
sleep_usec(1000_ms);
continue;
} else {
_log("Start listening port " + str(dev->port), LogLevel::LL_NORMAL);
GRNotifications::add_notification("Start listening", "Start listening on port: " + str(dev->port), GRNotifications::NotificationIcon::ICON_SUCCESS, true, 1.f);
}
}
listening_error_notification_shown = false;
if (connection_thread_info) {
if (connection_thread_info->ppeer.is_null()) {
connection_thread_info->break_connection = true;
}
if (connection_thread_info->finished || connection_thread_info->break_connection) {
_log("Waiting connection thread...", LogLevel::LL_DEBUG);
connection_thread_info->close_thread();
memdelete(connection_thread_info);
connection_thread_info = nullptr;
}
}
if (srv->is_connection_available()) {
Ref<StreamPeerTCP> con = srv->take_connection();
con->set_no_delay(true);
String address = CONNECTION_ADDRESS(con);
Ref<PacketPeerStream> ppeer(memnew(PacketPeerStream));
ppeer->set_stream_peer(con);
ppeer->set_output_buffer_max_size(_grutils_data_server->compress_buffer.size());
if (!connection_thread_info) {
Dictionary ret_data;
GRDevice::AuthResult res = _auth_client(dev, ppeer, ret_data, false);
String dev_id = "";
if (ret_data.has("id")) {
dev_id = ret_data["id"];
}
switch (res) {
case GRDevice::AuthResult::OK:
connection_thread_info = memnew(ConnectionThreadParamsServer);
connection_thread_info->device_id = dev_id;
connection_thread_info->dev = dev;
connection_thread_info->ppeer = ppeer;
dev->custom_input_scene_was_updated = false;
dev->client_connected++;
Thread_start(connection_thread_info->thread_ref, GRServer, _thread_connection, connection_thread_info, dev);
_log("New connection from " + address, LogLevel::LL_NORMAL);
dev->call_deferred("emit_signal", "client_connected", dev_id);
GRNotifications::add_notification("Connected", "Client connected: " + address + "\nDevice ID: " + connection_thread_info->device_id, GRNotifications::NotificationIcon::ICON_SUCCESS, true, 1.f);
break;
case GRDevice::AuthResult::VersionMismatch:
case GRDevice::AuthResult::IncorrectPassword:
case GRDevice::AuthResult::Error:
case GRDevice::AuthResult::RefuseConnection:
case GRDevice::AuthResult::Timeout:
continue;
default:
_log("Unknown error code. Disconnecting. " + address, LogLevel::LL_NORMAL);
continue;
}
} else {
Dictionary ret_data;
GRDevice::AuthResult res = _auth_client(dev, ppeer, ret_data, true);
}
} else {
_log("Waiting...", LogLevel::LL_DEBUG);
sleep_usec(33_ms);
}
}
if (connection_thread_info) {
_log("Closing connection thread...", LogLevel::LL_DEBUG);
connection_thread_info->break_connection = true;
connection_thread_info->close_thread();
memdelete(connection_thread_info);
connection_thread_info = nullptr;
}
dev->tcp_server->stop();
this_thread_info->finished = true;
}
void GRServer::_thread_connection(THREAD_DATA p_userdata) {
ConnectionThreadParamsServer *thread_info = (ConnectionThreadParamsServer *)p_userdata;
Ref<StreamPeerTCP> connection = thread_info->ppeer->get_stream_peer();
Ref<PacketPeerStream> ppeer = thread_info->ppeer;
GRServer *dev = thread_info->dev;
dev->_reset_counters();
dev->_send_queue_resize(0);
GodotRemote *gr = GodotRemote::get_singleton();
OS *os = OS::get_singleton();
Input *input = Input::get_singleton();
Error err = Error::OK;
Input::MouseMode mouse_mode = Input::MOUSE_MODE_VISIBLE;
String address = CONNECTION_ADDRESS(connection);
Thread_set_name("GR_connection " + address);
uint64_t time64 = os->get_ticks_usec();
uint64_t prev_send_settings_time = time64;
uint64_t prev_send_image_time = time64;
uint64_t prev_ping_sending_time = time64;
uint64_t prev_process_image_time = time64;
//uint64_t prev_send_sync_time = time64;
bool ping_sended = false;
bool time_synced = false;
TimeCountInit();
while (!thread_info->break_connection && connection.is_valid() &&
!connection->is_queued_for_deletion() && connection->is_connected_to_host()) {
bool nothing_happens = true;
float fps = Engine::get_singleton()->get_frames_per_second();
if (fps == 0) {
fps = 1;
}
if (dev->resize_viewport && !dev->resize_viewport->is_queued_for_deletion()) {
if (dev->resize_viewport->get_skip_frames())
fps = fps / dev->resize_viewport->get_skip_frames();
if (!dev->resize_viewport->is_processing()) {
dev->resize_viewport->set_process(true);
dev->resize_viewport->force_get_image();
}
}
uint64_t send_data_time_us = uint64_t(1000000.0 / fps);
TimeCount("Cycle start");
///////////////////////////////////////////////////////////////////
// SENDING
bool is_queued_send = false; // this placed here for android compiler
uint64_t start_while_time = os->get_ticks_usec();
// TIME SYNC
//time = os->get_ticks_usec();
//if (time - prev_send_sync_time > 1000_ms) {
if (!time_synced) {
time_synced = true;
nothing_happens = false;
//prev_send_sync_time = time;
Ref<GRPacketSyncTime> pack(memnew(GRPacketSyncTime));
err = ppeer->put_var(pack->get_data());
if ((int)err) {
_log("Can't send sync time data! Code: " + str((int)err), LogLevel::LL_ERROR);
goto end_send;
}
TimeCount("Sync Time Send");
}
// IMAGE
TimeCountReset();
time64 = os->get_ticks_usec();
// if image compressed and data is ready
if (dev->resize_viewport && !dev->resize_viewport->is_queued_for_deletion() &&
dev->resize_viewport->has_compressed_image_data()) {
nothing_happens = false;
Ref<GRPacketImageData> pack(memnew(GRPacketImageData));
auto ips = dev->resize_viewport->get_last_compressed_image_data();
if (!(ips->ret_data.size() == 0) || ips->is_empty) { // if not broken image or force empty image :)
pack->set_is_empty(ips->is_empty);
pack->set_compression_type((int)ips->compression_type);
pack->set_size(Size2((float)ips->width, (float)ips->height));
pack->set_format(ips->format);
pack->set_image_data(ips->ret_data);
pack->set_start_time(os->get_ticks_usec());
pack->set_frametime(send_data_time_us);
err = ppeer->put_var(pack->get_data());
// avg fps
dev->_update_avg_fps(time64 - prev_send_image_time);
dev->_adjust_viewport_scale();
prev_send_image_time = time64;
if ((int)err) {
_log("Can't send image data! Code: " + str((int)err), LogLevel::LL_ERROR);
memdelete(ips);
ips = nullptr;
goto end_send;
}
}
memdelete(ips);
ips = nullptr;
} else {
if (!dev->is_video_stream_enabled()) {
dev->_update_avg_fps(0);
}
}
// SERVER SETTINGS
TimeCountReset();
time64 = os->get_ticks_usec();
if (time64 - prev_send_settings_time > 1000_ms && dev->using_client_settings) {
prev_send_settings_time = time64;
if (dev->using_client_settings_recently_updated) {
dev->using_client_settings_recently_updated = false;
} else {
nothing_happens = false;
Ref<GRPacketServerSettings> pack(memnew(GRPacketServerSettings));
pack->add_setting((int)TypesOfServerSettings::SERVER_SETTINGS_VIDEO_STREAM_ENABLED, dev->is_video_stream_enabled());
pack->add_setting((int)TypesOfServerSettings::SERVER_SETTINGS_COMPRESSION_TYPE, dev->get_compression_type());
pack->add_setting((int)TypesOfServerSettings::SERVER_SETTINGS_JPG_QUALITY, dev->get_jpg_quality());
pack->add_setting((int)TypesOfServerSettings::SERVER_SETTINGS_RENDER_SCALE, dev->get_render_scale());
pack->add_setting((int)TypesOfServerSettings::SERVER_SETTINGS_SKIP_FRAMES, dev->get_skip_frames());
err = ppeer->put_var(pack->get_data());
if ((int)err) {
_log("Send server settings failed with code: " + str((int)err), LogLevel::LL_ERROR);
goto end_send;
}
TimeCount("Send server settings");
}
}
// MOUSE MODE
TimeCountReset();
if (input->get_mouse_mode() != mouse_mode) {
nothing_happens = false;
mouse_mode = input->get_mouse_mode();
Ref<GRPacketMouseModeSync> pack(memnew(GRPacketMouseModeSync));
pack->set_mouse_mode(mouse_mode);
err = ppeer->put_var(pack->get_data());
if ((int)err) {
_log("Send mouse mode sync failed with code: " + str((int)err), LogLevel::LL_ERROR);
goto end_send;
}
TimeCount("Send image data");
}
// PING
TimeCountReset();
time64 = os->get_ticks_usec();
if ((time64 - prev_ping_sending_time) > 100_ms && !ping_sended) {
nothing_happens = false;
ping_sended = true;
Ref<GRPacketPing> pack(memnew(GRPacketPing));
err = ppeer->put_var(pack->get_data());
prev_ping_sending_time = time64;
if ((int)err) {
_log("Send ping failed with code: " + str((int)err), LogLevel::LL_ERROR);
goto end_send;
}
TimeCount("Ping");
}
// CUSTOM INPUT SCENE
TimeCountReset();
if (!dev->custom_input_scene_was_updated) {
dev->custom_input_scene_was_updated = true;
Ref<GRPacketCustomInputScene> pack;
if (!dev->custom_input_scene.empty()) {
pack = dev->_create_custom_input_pack(dev->custom_input_scene, dev->custom_input_pck_compressed, dev->custom_input_pck_compression_type);
} else {
pack.instance();
}
err = ppeer->put_var(pack->get_data());
if ((int)err) {
_log("Send custom input failed with code: " + str((int)err), LogLevel::LL_ERROR);
goto end_send;
}
TimeCount("Custom input");
}
// SEND QUEUE
TimeCountReset();
while (!dev->send_queue.empty() && (os->get_ticks_usec() - start_while_time) <= send_data_time_us / 2) {
is_queued_send = true;
Ref<GRPacket> packet = dev->_send_queue_pop_front();
if (packet.is_valid()) {
err = ppeer->put_var(packet->get_data());
if ((int)err) {
_log("Put data from queue failed with code: " + str((int)err), LogLevel::LL_ERROR);
goto end_send;
}
}
}
if (is_queued_send) {
TimeCount("Send queued data");
}
end_send:
if (!connection->is_connected_to_host()) {
_log("Lost connection after sending!", LogLevel::LL_ERROR);
GRNotifications::add_notification("Error", "Lost connection after sending data!", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
continue;
}
///////////////////////////////////////////////////////////////////
// RECEIVING
TimeCountReset();
uint64_t recv_start_time = os->get_ticks_usec();
while (connection->is_connected_to_host() && ppeer->get_available_packet_count() > 0 &&
(os->get_ticks_usec() - recv_start_time) < send_data_time_us / 2) {
nothing_happens = false;
Variant res;
#ifndef GDNATIVE_LIBRARY
err = (Error)(int)ppeer->get_var(res);
#else
err = Error::OK;
res = ppeer->get_var();
#endif
if ((int)err) {
_log("Can't receive packet!", LogLevel::LL_ERROR);
continue;
}
//_log(str_arr((PoolByteArray)res, true));
Ref<GRPacket> pack = GRPacket::create(res);
if (pack.is_null()) {
_log("Received packet was NULL", LogLevel::LL_ERROR);
continue;
}
GRPacket::PacketType type = pack->get_type();
//_log((int)type);
switch (type) {
case GRPacket::PacketType::SyncTime: {
ERR_PRINT("NOT IMPLEMENTED");
break;
}
case GRPacket::PacketType::ImageData: {
ERR_PRINT("NOT IMPLEMENTED");
break;
}
case GRPacket::PacketType::InputData: {
Ref<GRPacketInputData> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketInputData", LogLevel::LL_ERROR);
break;
}
for (int i = 0; i < data->get_inputs_count(); i++) {
Ref<GRInputData> id = data->get_input_data(i);
GRInputData::InputType ev_type = id->get_type();
if (ev_type >= GRInputData::InputType::_InputEvent) {
Ref<GRInputDataEvent> ied = id;
if (ied.is_null()) {
_log("GRInputDataEvent is null", LogLevel::LL_ERROR);
continue;
}
Ref<InputEvent> ev = ied->construct_event();
if (ev.is_valid()) {
Input::get_singleton()->call_deferred("parse_input_event", ev);
}
} else {
switch (ev_type) {
case GRInputData::InputType::_NoneIT: {
_log("Not valid input type! 0", LogLevel::LL_NORMAL);
break;
}
case GRInputData::InputType::_InputDeviceSensors: {
Ref<GRInputDeviceSensorsData> sd = id;
if (sd.is_null()) {
_log("GRInputDeviceSensorsData is null", LogLevel::LL_ERROR);
continue;
}
auto s = sd->get_sensors();
set_accelerometer(s[0]);
set_gravity(s[1]);
set_gyroscope(s[2]);
set_magnetometer(s[3]);
break;
}
default: {
_log("Not supported input type! " + str((int)ev_type), LogLevel::LL_ERROR);
continue;
break;
}
}
}
}
break;
}
case GRPacket::PacketType::ServerSettings: {
Ref<GRPacketServerSettings> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketServerSettings", LogLevel::LL_ERROR);
break;
}
dev->_update_settings_from_client(data->get_settings());
break;
}
case GRPacket::PacketType::ClientStreamOrientation: {
Ref<GRPacketClientStreamOrientation> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketClientStreamOrientation", LogLevel::LL_ERROR);
break;
}
dev->call_deferred("emit_signal", "client_viewport_orientation_changed", data->is_vertical());
break;
}
case GRPacket::PacketType::ClientStreamAspect: {
Ref<GRPacketClientStreamAspect> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketClientStreamAspect", LogLevel::LL_ERROR);
break;
}
dev->call_deferred("emit_signal", "client_viewport_aspect_ratio_changed", data->get_aspect());
break;
}
case GRPacket::PacketType::CustomUserData: {
Ref<GRPacketCustomUserData> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketCustomUserData", LogLevel::LL_ERROR);
break;
}
dev->call_deferred("emit_signal", "user_data_received", data->get_packet_id(), data->get_user_data());
break;
}
case GRPacket::PacketType::Ping: {
Ref<GRPacketPong> pack(memnew(GRPacketPong));
err = ppeer->put_var(pack->get_data());
if ((int)err) {
_log("Send pong failed with code: " + str((int)err), LogLevel::LL_ERROR);
goto end_recv;
}
break;
}
case GRPacket::PacketType::Pong: {
dev->_update_avg_ping(os->get_ticks_usec() - prev_ping_sending_time);
ping_sended = false;
break;
}
default: {
_log("Not supported packet type! " + str((int)type), LogLevel::LL_WARNING);
break;
}
}
}
TimeCount("End receiving");
end_recv:
if (!connection->is_connected_to_host()) {
_log("Lost connection after receiving!", LogLevel::LL_ERROR);
GRNotifications::add_notification("Error", "Lost connection after receiving data!", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
continue;
}
if (nothing_happens) // for less cpu using
sleep_usec(1_ms);
}
_log("Closing connection thread with address: " + address, LogLevel::LL_DEBUG);
if (dev->resize_viewport)
dev->resize_viewport->set_process(false);
if (connection->is_connected_to_host()) {
GRNotifications::add_notification("Disconnected", "Closing connection with " + address, GRNotifications::NotificationIcon::ICON_FAIL, false, 1.f);
} else {
GRNotifications::add_notification("Disconnected", "Client disconnected: " + address, GRNotifications::NotificationIcon::ICON_FAIL, false, 1.f);
}
if (ppeer.is_valid()) {
ppeer.unref();
}
thread_info->ppeer.unref();
thread_info->break_connection = true;
dev->client_connected--;
dev->_send_queue_resize(0);
dev->call_deferred("_load_settings");
dev->call_deferred("emit_signal", "client_disconnected", thread_info->device_id);
thread_info->finished = true;
}
GRServer::AuthResult GRServer::_auth_client(GRServer *dev, Ref<PacketPeerStream> &ppeer, Dictionary &ret_data, bool refuse_connection) {
// _v - variable definition, _n - dict key, _c - fail condition, _e - error message, _r - return value on fail condition
#define packet_error_check(_t) \
if ((int)err) { \
_log(_t, LogLevel::LL_DEBUG); \
con->disconnect_from_host(); \
return GRDevice::AuthResult::Error; \
}
#define dict_get(_t, _v, _n, _c, _e, _r) \
_t _v; \
if (dict.has(_n)) \
_v = V_CAST(dict[_n], _t); \
else \
goto error_dict; \
if (_c) { \
ppeer->put_var((int)_r); \
_log(_e, LogLevel::LL_DEBUG); \
con->disconnect_from_host(); \
return _r; \
}
#define wait_packet(_n) \
time = (uint32_t)OS::get_singleton()->get_ticks_msec(); \
while (ppeer->get_available_packet_count() == 0) { \
if (OS::get_singleton()->get_ticks_msec() - time > 150) { \
_log("Connection timeout. Refusing " + address + ". Waited: " + str(_n), LogLevel::LL_DEBUG); \
goto timeout; \
} \
if (!con->is_connected_to_host()) { \
return GRDevice::AuthResult::Error; \
} \
sleep_usec(1_ms); \
}
Ref<StreamPeerTCP> con = ppeer->get_stream_peer();
String address = CONNECTION_ADDRESS(con);
uint32_t time = 0;
Error err = Error::OK;
Variant res;
if (!refuse_connection) {
// PUT client can try to connect
err = ppeer->put_var((int)GRDevice::AuthResult::TryToConnect);
packet_error_check("Can't send authorization init packet to " + address + ". Code: " + str((int)err));
// GET auth data
wait_packet("auth_data");
#ifndef GDNATIVE_LIBRARY
err = (Error)(int)ppeer->get_var(res);
packet_error_check("Can't get authorization data from client to " + address + ". Code: " + str((int)err));
#else
err = Error::OK;
res = ppeer->get_var();
#endif
Dictionary dict = res;
if (dict.empty()) {
goto error_dict;
} else {
dict_get(String, id, "id",
id.empty(), "Device ID field is empty or does not exists. " + address,
GRDevice::AuthResult::VersionMismatch);
ret_data["id"] = id;
dict_get(PoolByteArray, ver, "version",
ver.size() == 0, "Version field is empty or does not exists. " + address,
GRDevice::AuthResult::VersionMismatch);
if (!validate_version(ver)) {
_log("Version mismatch", LogLevel::LL_ERROR);
return GRDevice::AuthResult::VersionMismatch;
}
if (!dev->password.empty()) {
dict_get(String, password, "password",
password != dev->password, "Incorrect password. " + address,
GRDevice::AuthResult::IncorrectPassword);
}
}
// PUT auth ok
err = ppeer->put_var((int)GRDevice::AuthResult::OK);
packet_error_check("Can't send final authorization packet from client to " + address + ". Code: " + str((int)err));
return GRDevice::AuthResult::OK;
error_dict:
_log("Got invalid authorization data from client. " + address, LogLevel::LL_NORMAL);
err = ppeer->put_var((int)GRDevice::AuthResult::Error);
packet_error_check("Can't send error code to client " + address + ". Code: " + str((int)err));
con->disconnect_from_host();
return GRDevice::AuthResult::Error;
} else {
// PUT refuse connection
Error err = ppeer->put_var((int)GRDevice::AuthResult::RefuseConnection);
con->disconnect_from_host();
return GRDevice::AuthResult::RefuseConnection;
}
timeout:
con->disconnect_from_host();
return GRDevice::AuthResult::Timeout;
#undef dict_get
#undef wait_packet
#undef packet_error_check
}
Ref<GRPacketCustomInputScene> GRServer::_create_custom_input_pack(String _scene_path, bool compress, ENUM_ARG(Compression::Mode) compression_type) {
Ref<GRPacketCustomInputScene> pack = memnew(GRPacketCustomInputScene);
std::vector<String> files;
_scan_resource_for_dependencies_recursive(_scene_path, files);
#ifndef GDNATIVE_LIBRARY
#else
compress = false;
#endif
if (files.size()) {
String pck_file = _scene_path.get_base_dir() + "tmp.pck";
Ref<PCKPacker> pck = memnew(PCKPacker);
Error err = pck->pck_start(pck_file);
if ((int)err) {
_log("Can't create PCK file. Code: " + str((int)err), LogLevel::LL_ERROR);
} else {
Error add_err = Error::OK;
for (int i = 0; i < files.size(); i++) {
add_err = pck->add_file(files[i], files[i]);
if ((int)add_err) {
_log("Can't add file to PCK. Code: " + str((int)add_err), LogLevel::LL_ERROR);
break;
}
}
if (!(int)add_err) {
err = pck->flush();
if ((int)err) {
_log("Can't flush PCK file. Code: " + str((int)err), LogLevel::LL_ERROR);
} else {
// if OK show which files added
_log(str(files.size()) + " files added to custom input PCK", LogLevel::LL_NORMAL);
_log(str_arr(files, true, 0, ",\n"), LogLevel::LL_DEBUG);
#ifndef GDNATIVE_LIBRARY
FileAccess *file = FileAccess::open(pck_file, FileAccess::ModeFlags::READ, &err);
#else
File *file = memnew(File);
err = file->open(pck_file, File::ModeFlags::READ);
#endif
if ((int)err) {
_log("Can't open PCK file for reading. Code: " + str((int)err), LogLevel::LL_ERROR);
} else {
PoolByteArray arr;
#ifndef GDNATIVE_LIBRARY
err = arr.resize(file->get_len());
if ((int)err) {
_log("Can't resize temp buffer array. Code: " + str((int)err), LogLevel::LL_ERROR);
} else {
#else
{
#endif
#ifndef GDNATIVE_LIBRARY
auto w = arr.write();
int res = file->get_buffer(w.ptr(), arr.size());
release_pva_write(w);
file->close();
if (res != arr.size()) {
#else
arr = file->get_buffer(file->get_len());
int file_length = (int)file->get_len();
file->close();
if (file_length != arr.size()) {
int res = (int)Error::ERR_FILE_CANT_READ;
#endif
_log("PCK was not fully read. " + str(res) + " of " + str(arr.size()), LogLevel::LL_ERROR);
} else {
if (compress) {
PoolByteArray com;
err = compress_bytes(arr, com, compression_type);
if ((int)err) {
_log("Can't compress PCK data. Code: " + str((int)err), LogLevel::LL_ERROR);
}
pack->set_scene_path(_scene_path);
pack->set_scene_data(com);
pack->set_compressed(true);
pack->set_compression_type(compression_type);
pack->set_original_size(arr.size());
} else {
pack->set_scene_path(_scene_path);
pack->set_scene_data(arr);
pack->set_compressed(false);
pack->set_compression_type(0);
}
#ifndef GDNATIVE_LIBRARY
DirAccess *dir = DirAccess::open(_scene_path.get_base_dir());
if (dir) {
dir->remove(pck_file);
memdelete(dir);
}
#else
Directory *dir = memnew(Directory);
if (dir) {
dir->open(_scene_path.get_base_dir());
dir->remove(pck_file);
memdelete(dir);
}
#endif
}
}
}
memdelete(file);
}
}
}
} else {
_log("Files to pack not found! Scene path: " + _scene_path, LogLevel::LL_ERROR);
}
files.clear();
return pack;
}
void GRServer::_scan_resource_for_dependencies_recursive(String _d, std::vector<String> &_arr) {
if (!is_vector_contains(_arr, _d)) {
_arr.push_back(_d);
} else {
return;
}
Error err = Error::OK;
String text = file_get_as_string(_d, &err);
if ((int)err) {
_log("Can't read file as text: " + _d, LogLevel::LL_ERROR);
} else {
String imp = _d + ".import";
text += file_get_as_string(imp, &err);
if ((int)err) {
_log(".import file not found for " + imp, LogLevel::LL_DEBUG);
} else {
if (!is_vector_contains(_arr, imp)) {
_arr.push_back(imp);
}
}
// Check for .md5 files in .import folder
String imp_folder = "res://.import/"; // workaround for GDNative
if (_d.begins_with(imp_folder)) {
String md5 = _d.get_basename() + ".md5";
text += file_get_as_string(md5, &err);
// Not error == OK
if (!(int)err) {
if (!is_vector_contains(_arr, md5)) {
_arr.push_back(md5);
}
}
}
Array res = custom_input_scene_regex_resource_finder->search_all(text);
for (int i = 0; i < res.size(); i++) {
Ref<RegExMatch> rem = res[i];
String path = rem->get_string(1);
path = path.trim_suffix("\\"); // Needed for avoiding escape symbols in build-in scripts
_scan_resource_for_dependencies_recursive(path, _arr);
}
}
}
//////////////////////////////////////////////
////////////// GRSViewport ///////////////////
//////////////////////////////////////////////
void GRSViewport::_processing_thread(THREAD_DATA p_user) {
GRSViewport *vp = (GRSViewport *)p_user;
while (vp->is_thread_active) {
TimeCountInit();
vp->_TS_LOCK_;
if (img_is_empty(vp->last_image)) {
vp->_TS_UNLOCK_;
sleep_usec(1_ms);
continue;
}
ImgProcessingViewportStorage *ips = memnew(ImgProcessingViewportStorage);
Ref<Image> img = vp->last_image;
vp->last_image = newref(Image);
vp->_TS_UNLOCK_;
if (!ips) {
goto end;
}
ips->width = (int)img->get_width();
ips->height = (int)img->get_height();
ips->compression_type = vp->compression_type;
ips->jpg_quality = vp->jpg_quality;
ips->format = img->get_format();
if (!(ips->format == Image::FORMAT_RGBA8 || ips->format == Image::FORMAT_RGB8)) {
img->convert(Image::FORMAT_RGB8);
ips->format = img->get_format();
if (ips->format != Image::FORMAT_RGB8) {
_log("Can't convert stream image to RGB8.", LogLevel::LL_ERROR);
GRNotifications::add_notification("Stream Error", "Can't convert stream image to RGB8.", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
goto end;
}
TimeCount("Image Convert");
}
ips->bytes_in_color = img->get_format() == Image::FORMAT_RGB8 ? 3 : 4;
if (img->get_data().size() == 0)
goto end;
switch (ips->compression_type) {
case GRDevice::ImageCompressionType::COMPRESSION_UNCOMPRESSED: {
ips->ret_data = img->get_data();
TimeCount("Image processed: Uncompressed");
break;
}
case GRDevice::ImageCompressionType::COMPRESSION_JPG: {
if (!img_is_empty(img)) {
Error err = compress_jpg(ips->ret_data, img->get_data(), ips->width, ips->height, ips->bytes_in_color, ips->jpg_quality, GRServer::Subsampling::SUBSAMPLING_H2V2);
if ((int)err) {
_log("Can't compress stream image JPG. Code: " + str((int)err), LogLevel::LL_ERROR);
GRNotifications::add_notification("Stream Error", "Can't compress stream image to JPG. Code: " + str((int)err), GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
}
TimeCount("Image processed: JPG");
break;
}
case GRDevice::ImageCompressionType::COMPRESSION_PNG: {
ips->ret_data = img->save_png_to_buffer();
if (ips->ret_data.size() == 0) {
_log("Can't compress stream image to PNG.", LogLevel::LL_ERROR);
GRNotifications::add_notification("Stream Error", "Can't compress stream image to PNG.", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
TimeCount("Image processed: PNG");
break;
}
default:
_log("Not implemented compression type: " + str((int)ips->compression_type), LogLevel::LL_ERROR);
break;
}
end:
if (vp->video_stream_enabled)
vp->_set_img_data(ips);
}
}
#ifndef GDNATIVE_LIBRARY
void GRSViewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_size"), &GRSViewport::_update_size);
ClassDB::bind_method(D_METHOD("_on_renderer_deleting"), &GRSViewport::_on_renderer_deleting);
ClassDB::bind_method(D_METHOD("set_rendering_scale"), &GRSViewport::set_rendering_scale);
ClassDB::bind_method(D_METHOD("get_rendering_scale"), &GRSViewport::get_rendering_scale);
ADD_PROPERTY(PropertyInfo(Variant::REAL, "rendering_scale", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_rendering_scale", "get_rendering_scale");
}
#else
void GRSViewport::_register_methods() {
METHOD_REG(GRSViewport, _notification);
METHOD_REG(GRSViewport, _processing_thread);
METHOD_REG(GRSViewport, _on_renderer_deleting);
METHOD_REG(GRSViewport, _update_size);
METHOD_REG(GRSViewport, set_rendering_scale);
METHOD_REG(GRSViewport, get_rendering_scale);
register_property<GRSViewport, float>("rendering_scale", &GRSViewport::set_rendering_scale, &GRSViewport::get_rendering_scale, 0.3f, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_RANGE, "0,1,0.001");
}
#endif
void GRSViewport::_set_img_data(ImgProcessingViewportStorage *_data) {
_TS_LOCK_;
if (last_image_data)
memdelete(last_image_data);
last_image_data = _data;
_TS_UNLOCK_;
}
void GRSViewport::_on_renderer_deleting() {
renderer = nullptr;
}
void GRSViewport::_notification(int p_notification) {
TimeCountInit();
switch (p_notification) {
case NOTIFICATION_POSTINITIALIZE:
#ifndef GDNATIVE_LIBRARY
_init();
#endif
break;
case NOTIFICATION_PREDELETE:
_deinit();
break;
case NOTIFICATION_PROCESS: {
frames_from_prev_image++;
if (video_stream_enabled) {
is_empty_image_sended = false;
if (frames_from_prev_image > skip_frames && img_is_empty(last_image)) {
frames_from_prev_image = 0;
if (get_texture().is_null())
break;
auto tmp_image = get_texture()->get_data();
_TS_LOCK_;
last_image = tmp_image;
TimeCount("Get image data from VisualServer");
if (img_is_empty(last_image))
_log("Can't copy viewport image data", LogLevel::LL_ERROR);
_TS_UNLOCK_;
}
} else {
if (!is_empty_image_sended) {
is_empty_image_sended = true;
ImgProcessingViewportStorage *ipsv = memnew(ImgProcessingViewportStorage);
ipsv->width = 0;
ipsv->height = 0;
ipsv->format = Image::Format::FORMAT_RGB8;
ipsv->bytes_in_color = 3;
ipsv->jpg_quality = 1;
ipsv->is_empty = true;
_set_img_data(ipsv);
}
}
break;
}
case NOTIFICATION_ENTER_TREE: {
main_vp = ST()->get_root();
main_vp->connect("size_changed", this, "_update_size");
_update_size();
renderer = memnew(GRSViewportRenderer);
renderer->tex = main_vp->get_texture();
renderer->connect("tree_exiting", this, "_on_renderer_deleting");
add_child(renderer);
break;
}
case NOTIFICATION_EXIT_TREE: {
if (renderer) {
remove_child(renderer);
//renderer->queue_del();
memdelete(renderer);
}
main_vp->disconnect("size_changed", this, "_update_size");
main_vp = nullptr;
break;
}
default:
break;
}
}
void GRSViewport::_update_size() {
float scale = rendering_scale;
if (auto_scale > 0)
scale = auto_scale;
if (main_vp && main_vp->get_texture().is_valid()) {
Vector2 size = main_vp->get_size() * scale;
if (get_size() == size)
return;
if (size.x < 8)
size.x = 8;
else if (size.x > Image::MAX_WIDTH)
size.x = Image::MAX_WIDTH;
if (size.y < 8)
size.y = 8;
else if (size.y > Image::MAX_HEIGHT)
size.y = Image::MAX_HEIGHT;
set_size(size);
}
}
GRSViewport::ImgProcessingViewportStorage *GRSViewport::get_last_compressed_image_data() {
_TS_LOCK_;
auto res = last_image_data;
last_image_data = nullptr;
_TS_UNLOCK_;
return res;
}
bool GRSViewport::has_compressed_image_data() {
return last_image_data;
}
void GRSViewport::force_get_image() {
frames_from_prev_image = skip_frames;
}
void GRSViewport::set_video_stream_enabled(bool val) {
video_stream_enabled = val;
}
bool GRSViewport::is_video_stream_enabled() {
return video_stream_enabled;
}
void GRSViewport::set_rendering_scale(float val) {
rendering_scale = val;
call_deferred("_update_size");
}
float GRSViewport::get_rendering_scale() {
return rendering_scale;
}
void GRSViewport::set_compression_type(GRDevice::ImageCompressionType val) {
compression_type = val;
}
GRDevice::ImageCompressionType GRSViewport::get_compression_type() {
return compression_type;
}
void GRSViewport::set_jpg_quality(int _quality) {
ERR_FAIL_COND(_quality < 0 || _quality > 100);
jpg_quality = _quality;
}
int GRSViewport::get_jpg_quality() {
return jpg_quality;
}
void GRSViewport::set_skip_frames(int skip) {
skip_frames = skip;
}
int GRSViewport::get_skip_frames() {
return skip_frames;
}
void GRSViewport::_init() {
set_name("GRSViewport");
LEAVE_IF_EDITOR();
set_process(false);
rendering_scale = GET_PS(GodotRemote::ps_server_scale_of_sending_stream_name);
set_hdr(false);
set_disable_3d(true);
set_keep_3d_linear(true);
set_usage(Viewport::USAGE_2D);
set_update_mode(Viewport::UPDATE_ALWAYS);
set_disable_input(true);
set_shadow_atlas_quadrant_subdiv(0, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);
set_shadow_atlas_quadrant_subdiv(1, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);
set_shadow_atlas_quadrant_subdiv(2, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);
set_shadow_atlas_quadrant_subdiv(3, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);
_TS_LOCK_;
last_image = newref(Image);
_TS_UNLOCK_;
is_thread_active = true;
Thread_start(_thread_process, GRSViewport, _processing_thread, this, this);
}
void GRSViewport::_deinit() {
LEAVE_IF_EDITOR();
is_thread_active = false;
_close_thread();
_TS_LOCK_;
if (last_image_data)
memdelete(last_image_data);
last_image_data = nullptr;
last_image.unref();
_TS_UNLOCK_;
}
//////////////////////////////////////////////
/////////// GRSViewportRenderer /////////////
//////////////////////////////////////////////
#ifndef GDNATIVE_LIBRARY
void GRSViewportRenderer::_bind_methods() {
}
#else
void GRSViewportRenderer::_register_methods() {
METHOD_REG(GRSViewportRenderer, _notification);
}
#endif
void GRSViewportRenderer::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_POSTINITIALIZE:
#ifndef GDNATIVE_LIBRARY
_init();
#endif
break;
case NOTIFICATION_PREDELETE:
_deinit();
break;
case NOTIFICATION_ENTER_TREE: {
vp = get_viewport();
break;
}
case NOTIFICATION_EXIT_TREE: {
vp = nullptr;
tex.unref();
break;
}
case NOTIFICATION_PROCESS: {
update();
break;
}
case NOTIFICATION_DRAW: {
draw_texture_rect(tex, Rect2(Vector2(), vp->get_size()), false);
break;
}
default:
break;
}
}
void GRSViewportRenderer::_init() {
set_name("GRSViewportRenderer");
LEAVE_IF_EDITOR();
set_process(true);
set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
}
void GRSViewportRenderer::_deinit() {
LEAVE_IF_EDITOR();
}
#endif // !NO_GODOTREMOTE_SERVER