RingOfRaces/modules/godot_remote/godot_remote/GRClient.cpp

2002 lines
67 KiB
C++

/* GRClient.cpp */
#ifndef NO_GODOTREMOTE_CLIENT
#include "GRClient.h"
#include "GRNotifications.h"
#include "GRPacket.h"
#include "GRResources.h"
#include "GodotRemote.h"
#ifndef GDNATIVE_LIBRARY
#include "core/input_map.h"
#include "core/io/file_access_pack.h"
#include "core/io/ip.h"
#include "core/io/resource_loader.h"
#include "core/io/tcp_server.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/gui/control.h"
#include "scene/main/node.h"
#include "scene/main/scene_tree.h"
#include "scene/main/viewport.h"
#include "scene/resources/material.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/texture.h"
#else
#include <ClassDB.hpp>
#include <Control.hpp>
#include <Directory.hpp>
#include <File.hpp>
#include <GlobalConstants.hpp>
#include <IP.hpp>
#include <Input.hpp>
#include <InputMap.hpp>
#include <Material.hpp>
#include <Node.hpp>
#include <PackedScene.hpp>
#include <ProjectSettings.hpp>
#include <RandomNumberGenerator.hpp>
#include <ResourceLoader.hpp>
#include <SceneTree.hpp>
#include <TCP_Server.hpp>
#include <Texture.hpp>
#include <Thread.hpp>
#include <Viewport.hpp>
using namespace godot;
#define BUTTON_WHEEL_UP GlobalConstants::BUTTON_WHEEL_UP
#define BUTTON_WHEEL_DOWN GlobalConstants::BUTTON_WHEEL_DOWN
#define BUTTON_WHEEL_LEFT GlobalConstants::BUTTON_WHEEL_LEFT
#define BUTTON_WHEEL_RIGHT GlobalConstants::BUTTON_WHEEL_RIGHT
#endif
enum class DeletingVarName {
CONTROL_TO_SHOW_STREAM,
TEXTURE_TO_SHOW_STREAM,
INPUT_COLLECTOR,
CUSTOM_INPUT_SCENE,
};
using namespace GRUtils;
#ifndef GDNATIVE_LIBRARY
void GRClient::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_texture_from_image", "image"), &GRClient::_update_texture_from_image);
ClassDB::bind_method(D_METHOD("_update_stream_texture_state", "state"), &GRClient::_update_stream_texture_state);
ClassDB::bind_method(D_METHOD("_force_update_stream_viewport_signals"), &GRClient::_force_update_stream_viewport_signals);
ClassDB::bind_method(D_METHOD("_viewport_size_changed"), &GRClient::_viewport_size_changed);
ClassDB::bind_method(D_METHOD("_load_custom_input_scene", "_data"), &GRClient::_load_custom_input_scene);
ClassDB::bind_method(D_METHOD("_remove_custom_input_scene"), &GRClient::_remove_custom_input_scene);
ClassDB::bind_method(D_METHOD("_on_node_deleting", "var_name"), &GRClient::_on_node_deleting);
ClassDB::bind_method(D_METHOD("set_control_to_show_in", "control_node", "position_in_node"), &GRClient::set_control_to_show_in, DEFVAL(0));
ClassDB::bind_method(D_METHOD("set_custom_no_signal_texture", "texture"), &GRClient::set_custom_no_signal_texture);
ClassDB::bind_method(D_METHOD("set_custom_no_signal_vertical_texture", "texture"), &GRClient::set_custom_no_signal_vertical_texture);
ClassDB::bind_method(D_METHOD("set_custom_no_signal_material", "material"), &GRClient::set_custom_no_signal_material);
ClassDB::bind_method(D_METHOD("set_address_port", "ip", "port"), &GRClient::set_address_port);
ClassDB::bind_method(D_METHOD("set_address", "ip"), &GRClient::set_address);
ClassDB::bind_method(D_METHOD("set_server_setting", "setting", "value"), &GRClient::set_server_setting);
ClassDB::bind_method(D_METHOD("disable_overriding_server_settings"), &GRClient::disable_overriding_server_settings);
ClassDB::bind_method(D_METHOD("get_custom_input_scene"), &GRClient::get_custom_input_scene);
ClassDB::bind_method(D_METHOD("get_address"), &GRClient::get_address);
ClassDB::bind_method(D_METHOD("is_stream_active"), &GRClient::is_stream_active);
ClassDB::bind_method(D_METHOD("is_connected_to_host"), &GRClient::is_connected_to_host);
ADD_SIGNAL(MethodInfo("custom_input_scene_added"));
ADD_SIGNAL(MethodInfo("custom_input_scene_removed"));
ADD_SIGNAL(MethodInfo("stream_state_changed", PropertyInfo(Variant::INT, "state", PROPERTY_HINT_ENUM)));
ADD_SIGNAL(MethodInfo("connection_state_changed", PropertyInfo(Variant::BOOL, "is_connected")));
ADD_SIGNAL(MethodInfo("mouse_mode_changed", PropertyInfo(Variant::INT, "mouse_mode")));
ADD_SIGNAL(MethodInfo("server_settings_received", PropertyInfo(Variant::DICTIONARY, "settings")));
// SETGET
ClassDB::bind_method(D_METHOD("set_capture_on_focus", "val"), &GRClient::set_capture_on_focus);
ClassDB::bind_method(D_METHOD("set_capture_when_hover", "val"), &GRClient::set_capture_when_hover);
ClassDB::bind_method(D_METHOD("set_capture_pointer", "val"), &GRClient::set_capture_pointer);
ClassDB::bind_method(D_METHOD("set_capture_input", "val"), &GRClient::set_capture_input);
ClassDB::bind_method(D_METHOD("set_connection_type", "type"), &GRClient::set_connection_type);
ClassDB::bind_method(D_METHOD("set_target_send_fps", "fps"), &GRClient::set_target_send_fps);
ClassDB::bind_method(D_METHOD("set_stretch_mode", "mode"), &GRClient::set_stretch_mode);
ClassDB::bind_method(D_METHOD("set_texture_filtering", "is_filtered"), &GRClient::set_texture_filtering);
ClassDB::bind_method(D_METHOD("set_password", "password"), &GRClient::set_password);
ClassDB::bind_method(D_METHOD("set_device_id", "id"), &GRClient::set_device_id);
ClassDB::bind_method(D_METHOD("set_viewport_orientation_syncing", "is_syncing"), &GRClient::set_viewport_orientation_syncing);
ClassDB::bind_method(D_METHOD("set_viewport_aspect_ratio_syncing", "is_syncing"), &GRClient::set_viewport_aspect_ratio_syncing);
ClassDB::bind_method(D_METHOD("set_server_settings_syncing", "is_syncing"), &GRClient::set_server_settings_syncing);
ClassDB::bind_method(D_METHOD("is_capture_on_focus"), &GRClient::is_capture_on_focus);
ClassDB::bind_method(D_METHOD("is_capture_when_hover"), &GRClient::is_capture_when_hover);
ClassDB::bind_method(D_METHOD("is_capture_pointer"), &GRClient::is_capture_pointer);
ClassDB::bind_method(D_METHOD("is_capture_input"), &GRClient::is_capture_input);
ClassDB::bind_method(D_METHOD("get_connection_type"), &GRClient::get_connection_type);
ClassDB::bind_method(D_METHOD("get_target_send_fps"), &GRClient::get_target_send_fps);
ClassDB::bind_method(D_METHOD("get_stretch_mode"), &GRClient::get_stretch_mode);
ClassDB::bind_method(D_METHOD("get_texture_filtering"), &GRClient::get_texture_filtering);
ClassDB::bind_method(D_METHOD("get_password"), &GRClient::get_password);
ClassDB::bind_method(D_METHOD("get_device_id"), &GRClient::get_device_id);
ClassDB::bind_method(D_METHOD("is_viewport_orientation_syncing"), &GRClient::is_viewport_orientation_syncing);
ClassDB::bind_method(D_METHOD("is_viewport_aspect_ratio_syncing"), &GRClient::is_viewport_aspect_ratio_syncing);
ClassDB::bind_method(D_METHOD("is_server_settings_syncing"), &GRClient::is_server_settings_syncing);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_on_focus"), "set_capture_on_focus", "is_capture_on_focus");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_when_hover"), "set_capture_when_hover", "is_capture_when_hover");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_pointer"), "set_capture_pointer", "is_capture_pointer");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_input"), "set_capture_input", "is_capture_input");
ADD_PROPERTY(PropertyInfo(Variant::INT, "connection_type", PROPERTY_HINT_ENUM, "WiFi,ADB"), "set_connection_type", "get_connection_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "target_send_fps", PROPERTY_HINT_RANGE, "1,1000"), "set_target_send_fps", "get_target_send_fps");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Fill,Keep Aspect"), "set_stretch_mode", "get_stretch_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "texture_filtering"), "set_texture_filtering", "get_texture_filtering");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "password"), "set_password", "get_password");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "device_id"), "set_device_id", "get_device_id");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "viewport_orientation_syncing"), "set_viewport_orientation_syncing", "is_viewport_orientation_syncing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "viewport_aspect_ratio_syncing"), "set_viewport_aspect_ratio_syncing", "is_viewport_aspect_ratio_syncing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_settings_syncing"), "set_server_settings_syncing", "is_server_settings_syncing");
BIND_ENUM_CONSTANT(CONNECTION_ADB);
BIND_ENUM_CONSTANT(CONNECTION_WiFi);
BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT);
BIND_ENUM_CONSTANT(STRETCH_FILL);
BIND_ENUM_CONSTANT(STREAM_NO_SIGNAL);
BIND_ENUM_CONSTANT(STREAM_ACTIVE);
BIND_ENUM_CONSTANT(STREAM_NO_IMAGE);
}
#else
void GRClient::_register_methods() {
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/*
METHOD_REG(GRClient::_internal_call_only_deffered_start);
METHOD_REG(GRClient::_internal_call_only_deffered_stop);
METHOD_REG(GRClient::_internal_call_only_deffered_restart);
METHOD_REG(GRClient::get_avg_ping);
METHOD_REG(GRClient::get_avg_fps);
METHOD_REG(GRClient::get_port);
METHOD_REG(GRClient::set_port);
METHOD_REG(GRClient::start);
METHOD_REG(GRClient::stop);
METHOD_REG(GRClient::get_status);
register_signal<GRClient>("status_changed", "status", GODOT_VARIANT_TYPE_INT);
register_property<GRClient, uint16_t>("port", &GRClient::set_port, &GRClient::get_port, 52341);
*/
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
METHOD_REG(GRClient, _notification);
METHOD_REG(GRClient, _thread_connection);
METHOD_REG(GRClient, _thread_image_decoder);
METHOD_REG(GRClient, _update_texture_from_image);
METHOD_REG(GRClient, _update_stream_texture_state);
METHOD_REG(GRClient, _force_update_stream_viewport_signals);
METHOD_REG(GRClient, _viewport_size_changed);
METHOD_REG(GRClient, _load_custom_input_scene);
METHOD_REG(GRClient, _remove_custom_input_scene);
METHOD_REG(GRClient, _on_node_deleting);
METHOD_REG(GRClient, set_control_to_show_in);
METHOD_REG(GRClient, set_custom_no_signal_texture);
METHOD_REG(GRClient, set_custom_no_signal_vertical_texture);
METHOD_REG(GRClient, set_custom_no_signal_material);
METHOD_REG(GRClient, set_address_port);
METHOD_REG(GRClient, set_address);
METHOD_REG(GRClient, set_server_setting);
METHOD_REG(GRClient, disable_overriding_server_settings);
METHOD_REG(GRClient, get_custom_input_scene);
METHOD_REG(GRClient, get_address);
METHOD_REG(GRClient, is_stream_active);
METHOD_REG(GRClient, is_connected_to_host);
register_signal<GRClient>("custom_input_scene_added", Dictionary::make());
register_signal<GRClient>("custom_input_scene_removed", Dictionary::make());
register_signal<GRClient>("stream_state_changed", "state", GODOT_VARIANT_TYPE_INT);
register_signal<GRClient>("connection_state_changed", "is_connected", GODOT_VARIANT_TYPE_BOOL);
register_signal<GRClient>("mouse_mode_changed", "mouse_mode", GODOT_VARIANT_TYPE_INT);
register_signal<GRClient>("server_settings_received", "settings", GODOT_VARIANT_TYPE_DICTIONARY);
// SETGET
METHOD_REG(GRClient, set_capture_on_focus);
METHOD_REG(GRClient, set_capture_when_hover);
METHOD_REG(GRClient, set_capture_pointer);
METHOD_REG(GRClient, set_capture_input);
METHOD_REG(GRClient, set_connection_type);
METHOD_REG(GRClient, set_target_send_fps);
METHOD_REG(GRClient, set_stretch_mode);
METHOD_REG(GRClient, set_texture_filtering);
METHOD_REG(GRClient, set_password);
METHOD_REG(GRClient, set_device_id);
METHOD_REG(GRClient, set_viewport_orientation_syncing);
METHOD_REG(GRClient, set_viewport_aspect_ratio_syncing);
METHOD_REG(GRClient, set_server_settings_syncing);
METHOD_REG(GRClient, is_capture_on_focus);
METHOD_REG(GRClient, is_capture_when_hover);
METHOD_REG(GRClient, is_capture_pointer);
METHOD_REG(GRClient, is_capture_input);
METHOD_REG(GRClient, get_connection_type);
METHOD_REG(GRClient, get_target_send_fps);
METHOD_REG(GRClient, get_stretch_mode);
METHOD_REG(GRClient, get_texture_filtering);
METHOD_REG(GRClient, get_password);
METHOD_REG(GRClient, get_device_id);
METHOD_REG(GRClient, is_viewport_orientation_syncing);
METHOD_REG(GRClient, is_viewport_aspect_ratio_syncing);
METHOD_REG(GRClient, is_server_settings_syncing);
register_property<GRClient, bool>("capture_on_focus", &GRClient::set_capture_on_focus, &GRClient::is_capture_on_focus, false);
register_property<GRClient, bool>("capture_when_hover", &GRClient::set_capture_when_hover, &GRClient::is_capture_when_hover, false);
register_property<GRClient, bool>("capture_pointer", &GRClient::set_capture_pointer, &GRClient::is_capture_pointer, true);
register_property<GRClient, bool>("capture_input", &GRClient::set_capture_input, &GRClient::is_capture_input, true);
register_property<GRClient, int>("connection_type", &GRClient::set_connection_type, &GRClient::get_connection_type, CONNECTION_WiFi, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_ENUM, "WiFi,ADB");
register_property<GRClient, int>("target_send_fps", &GRClient::set_target_send_fps, &GRClient::get_target_send_fps, 60, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_RANGE, "1,1000");
register_property<GRClient, int>("stretch_mode", &GRClient::set_stretch_mode, &GRClient::get_stretch_mode, STRETCH_KEEP_ASPECT, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_ENUM, "Fill,Keep Aspect");
register_property<GRClient, bool>("texture_filtering", &GRClient::set_texture_filtering, &GRClient::get_texture_filtering, true);
register_property<GRClient, String>("password", &GRClient::set_password, &GRClient::get_password, "");
register_property<GRClient, String>("device_id", &GRClient::set_device_id, &GRClient::get_device_id, "");
register_property<GRClient, bool>("viewport_orientation_syncing", &GRClient::set_viewport_orientation_syncing, &GRClient::is_viewport_orientation_syncing, true);
register_property<GRClient, bool>("viewport_aspect_ratio_syncing", &GRClient::set_viewport_aspect_ratio_syncing, &GRClient::is_viewport_aspect_ratio_syncing, true);
register_property<GRClient, bool>("server_settings_syncing", &GRClient::set_server_settings_syncing, &GRClient::is_server_settings_syncing, true);
}
#endif
void GRClient::_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:
is_deleting = true;
if (get_status() == (int)WorkingStatus::STATUS_WORKING) {
_internal_call_only_deffered_stop();
}
break;
}
}
}
void GRClient::_init() {
set_name("GodotRemoteClient");
LEAVE_IF_EDITOR();
#ifndef GDNATIVE_LIBRARY
#else
GRDevice::_init();
#endif
#ifndef GDNATIVE_LIBRARY
Math::randomize();
device_id = str(Math::randd() * Math::rand()).md5_text().substr(0, 6);
#else
RandomNumberGenerator *rng = memnew(RandomNumberGenerator);
rng->randomize();
device_id = str(rng->randf() * rng->randf()).md5_text().substr(0, 6);
memdelete(rng);
#endif
Mutex_create(connection_mutex);
#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
no_signal_image.instance();
GetPoolVectorFromBin(tmp_no_signal, GRResources::Bin_NoSignalPNG);
no_signal_image->load_png_from_buffer(tmp_no_signal);
no_signal_vertical_image.instance();
GetPoolVectorFromBin(tmp_no_signal_vert, GRResources::Bin_NoSignalVerticalPNG);
no_signal_vertical_image->load_png_from_buffer(tmp_no_signal_vert);
Ref<Shader> shader = newref(Shader);
shader->set_code(GRResources::Txt_CRT_Shader);
no_signal_mat.instance();
no_signal_mat->set_shader(shader);
#endif
}
void GRClient::_deinit() {
LEAVE_IF_EDITOR();
is_deleting = true;
if (get_status() == (int)WorkingStatus::STATUS_WORKING) {
_internal_call_only_deffered_stop();
}
set_control_to_show_in(nullptr, 0);
Mutex_delete(connection_mutex);
#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
no_signal_mat.unref();
no_signal_image.unref();
no_signal_vertical_image.unref();
#endif
}
void GRClient::_internal_call_only_deffered_start() {
switch ((WorkingStatus)get_status()) {
case WorkingStatus::STATUS_WORKING:
ERR_FAIL_MSG("Can't start already working GodotRemote Client");
case WorkingStatus::STATUS_STARTING:
ERR_FAIL_MSG("Can't start already starting GodotRemote Client");
case WorkingStatus::STATUS_STOPPING:
ERR_FAIL_MSG("Can't start stopping GodotRemote Client");
}
_log("Starting GodotRemote client. Version: " + str(GodotRemote::get_singleton()->get_version()), LogLevel::LL_NORMAL);
set_status(WorkingStatus::STATUS_STARTING);
if (thread_connection) {
Mutex_lock(connection_mutex);
thread_connection->break_connection = true;
thread_connection->stop_thread = true;
Mutex_unlock(connection_mutex);
thread_connection->close_thread();
memdelete(thread_connection);
thread_connection = nullptr;
}
thread_connection = memnew(ConnectionThreadParamsClient);
thread_connection->dev = this;
thread_connection->peer.instance();
Thread_start(thread_connection->thread_ref, GRClient, _thread_connection, thread_connection, this);
call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
set_status(WorkingStatus::STATUS_WORKING);
}
void GRClient::_internal_call_only_deffered_stop() {
switch ((WorkingStatus)get_status()) {
case WorkingStatus::STATUS_STOPPED:
ERR_FAIL_MSG("Can't stop already stopped GodotRemote Client");
case WorkingStatus::STATUS_STOPPING:
ERR_FAIL_MSG("Can't stop already stopping GodotRemote Client");
case WorkingStatus::STATUS_STARTING:
ERR_FAIL_MSG("Can't stop starting GodotRemote Client");
}
_log("Stopping GodotRemote client", LogLevel::LL_DEBUG);
set_status(WorkingStatus::STATUS_STOPPING);
_remove_custom_input_scene();
if (thread_connection) {
Mutex_lock(connection_mutex);
thread_connection->break_connection = true;
thread_connection->stop_thread = true;
Mutex_unlock(connection_mutex);
thread_connection->close_thread();
memdelete(thread_connection);
thread_connection = nullptr;
}
_send_queue_resize(0);
call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
set_status(WorkingStatus::STATUS_STOPPED);
}
void GRClient::set_control_to_show_in(Control *ctrl, int position_in_node) {
if (tex_shows_stream && !tex_shows_stream->is_queued_for_deletion()) {
tex_shows_stream->dev = nullptr;
tex_shows_stream->queue_del();
tex_shows_stream = nullptr;
}
if (input_collector && !input_collector->is_queued_for_deletion()) {
input_collector->dev = nullptr;
input_collector->queue_del();
input_collector = nullptr;
}
if (control_to_show_in && !control_to_show_in->is_queued_for_deletion() &&
control_to_show_in->is_connected("resized", this, "_viewport_size_changed")) {
control_to_show_in->disconnect("resized", this, "_viewport_size_changed");
control_to_show_in->disconnect("tree_exiting", this, "_on_node_deleting");
}
_remove_custom_input_scene();
control_to_show_in = ctrl;
if (control_to_show_in && !control_to_show_in->is_queued_for_deletion()) {
control_to_show_in->connect("resized", this, "_viewport_size_changed");
tex_shows_stream = memnew(GRTextureRect);
input_collector = memnew(GRInputCollector);
tex_shows_stream->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::TEXTURE_TO_SHOW_STREAM }));
input_collector->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::INPUT_COLLECTOR }));
control_to_show_in->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::CONTROL_TO_SHOW_STREAM }));
tex_shows_stream->set_name("GodotRemoteStreamSprite");
input_collector->set_name("GodotRemoteInputCollector");
tex_shows_stream->set_expand(true);
tex_shows_stream->set_anchor(MARGIN_RIGHT, 1.f);
tex_shows_stream->set_anchor(MARGIN_BOTTOM, 1.f);
tex_shows_stream->dev = this;
tex_shows_stream->this_in_client = &tex_shows_stream;
control_to_show_in->add_child(tex_shows_stream);
control_to_show_in->move_child(tex_shows_stream, position_in_node);
control_to_show_in->add_child(input_collector);
input_collector->set_tex_rect(tex_shows_stream);
input_collector->dev = this;
input_collector->this_in_client = &input_collector;
signal_connection_state = StreamState::STREAM_ACTIVE; // force execute update function
call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
call_deferred("_force_update_stream_viewport_signals"); // force update if client connected faster than scene loads
}
}
void GRClient::_on_node_deleting(int var_name) {
switch ((DeletingVarName)var_name) {
case DeletingVarName::CONTROL_TO_SHOW_STREAM:
control_to_show_in = nullptr;
set_control_to_show_in(nullptr, 0);
break;
case DeletingVarName::TEXTURE_TO_SHOW_STREAM:
tex_shows_stream = nullptr;
break;
case DeletingVarName::INPUT_COLLECTOR:
input_collector = nullptr;
break;
case DeletingVarName::CUSTOM_INPUT_SCENE:
custom_input_scene = nullptr;
break;
default:
break;
}
}
void GRClient::set_custom_no_signal_texture(Ref<Texture> custom_tex) {
custom_no_signal_texture = custom_tex;
call_deferred("_update_stream_texture_state", signal_connection_state);
}
void GRClient::set_custom_no_signal_vertical_texture(Ref<Texture> custom_tex) {
custom_no_signal_vertical_texture = custom_tex;
call_deferred("_update_stream_texture_state", signal_connection_state);
}
void GRClient::set_custom_no_signal_material(Ref<Material> custom_mat) {
custom_no_signal_material = custom_mat;
call_deferred("_update_stream_texture_state", signal_connection_state);
}
bool GRClient::is_capture_on_focus() {
if (input_collector && !input_collector->is_queued_for_deletion())
return input_collector->is_capture_on_focus();
return false;
}
void GRClient::set_capture_on_focus(bool value) {
if (input_collector && !input_collector->is_queued_for_deletion())
input_collector->set_capture_on_focus(value);
}
bool GRClient::is_capture_when_hover() {
if (input_collector && !input_collector->is_queued_for_deletion())
return input_collector->is_capture_when_hover();
return false;
}
void GRClient::set_capture_when_hover(bool value) {
if (input_collector && !input_collector->is_queued_for_deletion())
input_collector->set_capture_when_hover(value);
}
bool GRClient::is_capture_pointer() {
if (input_collector && !input_collector->is_queued_for_deletion())
return input_collector->is_capture_pointer();
return false;
}
void GRClient::set_capture_pointer(bool value) {
if (input_collector && !input_collector->is_queued_for_deletion())
input_collector->set_capture_pointer(value);
}
bool GRClient::is_capture_input() {
if (input_collector && !input_collector->is_queued_for_deletion())
return input_collector->is_capture_input();
return false;
}
void GRClient::set_capture_input(bool value) {
if (input_collector && !input_collector->is_queued_for_deletion())
input_collector->set_capture_input(value);
}
void GRClient::set_connection_type(ENUM_ARG(ConnectionType) type) {
con_type = (ConnectionType)type;
}
ENUM_ARG(GRClient::ConnectionType)
GRClient::get_connection_type() {
return con_type;
}
void GRClient::set_target_send_fps(int fps) {
ERR_FAIL_COND(fps <= 0);
send_data_fps = fps;
}
int GRClient::get_target_send_fps() {
return send_data_fps;
}
void GRClient::set_stretch_mode(ENUM_ARG(StretchMode) stretch) {
stretch_mode = (StretchMode)stretch;
call_deferred("_update_stream_texture_state", signal_connection_state);
}
ENUM_ARG(GRClient::StretchMode)
GRClient::get_stretch_mode() {
return stretch_mode;
}
void GRClient::set_texture_filtering(bool is_filtering) {
is_filtering_enabled = is_filtering;
}
bool GRClient::get_texture_filtering() {
return is_filtering_enabled;
}
ENUM_ARG(GRClient::StreamState)
GRClient::get_stream_state() {
return signal_connection_state;
}
bool GRClient::is_stream_active() {
return signal_connection_state;
}
String GRClient::get_address() {
return (String)server_address;
}
bool GRClient::set_address(String ip) {
return set_address_port(ip, port);
}
bool GRClient::set_address_port(String ip, uint16_t _port) {
bool all_ok = false;
#ifndef GDNATIVE_LIBRARY
IP_Address adr;
#else
String adr;
#endif
if (ip.is_valid_ip_address()) {
adr = ip;
if (adr.is_valid_ip()) {
server_address = ip;
port = _port;
restart();
all_ok = true;
} else {
_log("Address is invalid: " + ip, LogLevel::LL_ERROR);
GRNotifications::add_notification("Resolve Address Error", "Address is invalid: " + ip, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
} else {
adr = IP::get_singleton()->resolve_hostname(adr);
if (adr.is_valid_ip()) {
_log("Resolved address for " + ip + "\n" + adr, LogLevel::LL_DEBUG);
server_address = ip;
port = _port;
restart();
all_ok = true;
} else {
_log("Can't resolve address for " + ip, LogLevel::LL_ERROR);
GRNotifications::add_notification("Resolve Address Error", "Can't resolve address: " + ip, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
}
return all_ok;
}
void GRClient::set_input_buffer(int mb) {
input_buffer_size_in_mb = mb;
restart();
}
void GRClient::set_viewport_orientation_syncing(bool is_syncing) {
_viewport_orientation_syncing = is_syncing;
if (is_syncing) {
if (input_collector && !input_collector->is_queued_for_deletion()) {
_force_update_stream_viewport_signals();
}
}
}
bool GRClient::is_viewport_orientation_syncing() {
return _viewport_orientation_syncing;
}
void GRClient::set_viewport_aspect_ratio_syncing(bool is_syncing) {
_viewport_aspect_ratio_syncing = is_syncing;
if (is_syncing) {
call_deferred("_viewport_size_changed"); // force update screen aspect
}
}
bool GRClient::is_viewport_aspect_ratio_syncing() {
return _viewport_aspect_ratio_syncing;
}
void GRClient::set_server_settings_syncing(bool is_syncing) {
_server_settings_syncing = is_syncing;
}
bool GRClient::is_server_settings_syncing() {
return _server_settings_syncing;
}
void GRClient::set_password(String _pass) {
password = _pass;
}
String GRClient::get_password() {
return password;
}
void GRClient::set_device_id(String _id) {
ERR_FAIL_COND(_id.empty());
device_id = _id;
}
String GRClient::get_device_id() {
return device_id;
}
bool GRClient::is_connected_to_host() {
if (thread_connection && thread_connection->peer.is_valid()) {
return thread_connection->peer->is_connected_to_host() && is_connection_working;
}
return false;
}
Node *GRClient::get_custom_input_scene() {
return custom_input_scene;
}
void GRClient::_force_update_stream_viewport_signals() {
is_vertical = ScreenOrientation::NONE;
if (!control_to_show_in || control_to_show_in->is_queued_for_deletion()) {
return;
}
call_deferred("_viewport_size_changed"); // force update screen aspect ratio
}
void GRClient::_load_custom_input_scene(Ref<GRPacketCustomInputScene> _data) {
_remove_custom_input_scene();
if (_data->get_scene_path().empty() || _data->get_scene_data().size() == 0) {
_log("Scene not specified or data is empty. Removing custom input scene", LogLevel::LL_DEBUG);
return;
}
if (!control_to_show_in) {
_log("Not specified control to show", LogLevel::LL_ERROR);
return;
}
Error err = Error::OK;
#ifndef GDNATIVE_LIBRARY
FileAccess *file = FileAccess::open(custom_input_scene_tmp_pck_file, FileAccess::ModeFlags::WRITE, &err);
#else
File *file = memnew(File);
err = file->open(custom_input_scene_tmp_pck_file, File::ModeFlags::WRITE);
#endif
if ((int)err) {
_log("Can't open temp file to store custom input scene: " + custom_input_scene_tmp_pck_file + ", code: " + str((int)err), LogLevel::LL_ERROR);
} else {
PoolByteArray scene_data;
if (_data->is_compressed()) {
err = decompress_bytes(_data->get_scene_data(), _data->get_original_size(), scene_data, _data->get_compression_type());
} else {
scene_data = _data->get_scene_data();
}
if ((int)err) {
_log("Can't decompress or set scene_data: Code: " + str((int)err), LogLevel::LL_ERROR);
} else {
#ifndef GDNATIVE_LIBRARY
auto r = scene_data.read();
file->store_buffer(r.ptr(), scene_data.size());
release_pva_read(r);
#else
file->store_buffer(scene_data);
#endif
file->close();
#ifndef GDNATIVE_LIBRARY
if (PackedData::get_singleton()->is_disabled()) {
err = Error::FAILED;
} else {
#if VERSION_MINOR >= 2 && VERSION_PATCH >= 4
err = PackedData::get_singleton()->add_pack(custom_input_scene_tmp_pck_file, true, 0);
#else
err = PackedData::get_singleton()->add_pack(custom_input_scene_tmp_pck_file, true);
#endif
}
#else
err = ProjectSettings::get_singleton()->load_resource_pack(custom_input_scene_tmp_pck_file, true, 0) ? Error::OK : Error::FAILED;
#endif
if ((int)err) {
_log("Can't load PCK file: " + custom_input_scene_tmp_pck_file, LogLevel::LL_ERROR);
} else {
#ifndef GDNATIVE_LIBRARY
Ref<PackedScene> pck = ResourceLoader::load(_data->get_scene_path(), "", false, &err);
#else
Ref<PackedScene> pck = ResourceLoader::get_singleton()->load(_data->get_scene_path(), "", false);
err = pck->can_instance() ? Error::OK : Error::FAILED;
#endif
if ((int)err) {
_log("Can't load scene file: " + _data->get_scene_path() + ", code: " + str((int)err), LogLevel::LL_ERROR);
} else {
custom_input_scene = pck->instance();
if (!custom_input_scene) {
_log("Can't instance scene from PCK file: " + custom_input_scene_tmp_pck_file + ", scene: " + _data->get_scene_path(), LogLevel::LL_ERROR);
} else {
control_to_show_in->add_child(custom_input_scene);
custom_input_scene->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::CUSTOM_INPUT_SCENE }));
_reset_counters();
emit_signal("custom_input_scene_added");
}
}
}
}
}
if (file) {
memdelete(file);
}
}
void GRClient::_remove_custom_input_scene() {
if (custom_input_scene && !custom_input_scene->is_queued_for_deletion()) {
custom_input_scene->queue_del();
custom_input_scene = nullptr;
emit_signal("custom_input_scene_removed");
Error err = Error::OK;
#ifndef GDNATIVE_LIBRARY
DirAccess *dir = DirAccess::open(custom_input_scene_tmp_pck_file.get_base_dir(), &err);
#else
Directory *dir = memnew(Directory);
dir->open(custom_input_scene_tmp_pck_file.get_base_dir());
#endif
if ((int)err) {
_log("Can't open folder: " + custom_input_scene_tmp_pck_file.get_base_dir(), LogLevel::LL_ERROR);
} else {
if (dir && dir->file_exists(custom_input_scene_tmp_pck_file)) {
err = dir->remove(custom_input_scene_tmp_pck_file);
if ((int)err) {
_log("Can't delete file: " + custom_input_scene_tmp_pck_file + ". Code: " + str((int)err), LogLevel::LL_ERROR);
}
}
}
if (dir) {
memdelete(dir);
}
}
}
void GRClient::_viewport_size_changed() {
if (!control_to_show_in || control_to_show_in->is_queued_for_deletion()) {
return;
}
if (_viewport_orientation_syncing) {
Vector2 size = control_to_show_in->get_size();
ScreenOrientation tmp_vert = size.x < size.y ? ScreenOrientation::VERTICAL : ScreenOrientation::HORIZONTAL;
if (tmp_vert != is_vertical) {
is_vertical = tmp_vert;
Mutex_lock(send_queue_mutex);
Ref<GRPacketClientStreamOrientation> packet = _find_queued_packet_by_type<Ref<GRPacketClientStreamOrientation> >();
if (packet.is_valid()) {
packet->set_vertical(is_vertical == ScreenOrientation::VERTICAL);
Mutex_unlock(send_queue_mutex);
goto ratio_sync;
}
Mutex_unlock(send_queue_mutex);
if (packet.is_null()) {
packet.instance();
packet->set_vertical(is_vertical == ScreenOrientation::VERTICAL);
send_packet(packet);
}
}
}
ratio_sync:
if (_viewport_aspect_ratio_syncing) {
Vector2 size = control_to_show_in->get_size();
Mutex_lock(send_queue_mutex);
Ref<GRPacketClientStreamAspect> packet = _find_queued_packet_by_type<Ref<GRPacketClientStreamAspect> >();
if (packet.is_valid()) {
packet->set_aspect(size.x / size.y);
Mutex_unlock(send_queue_mutex);
return;
}
Mutex_unlock(send_queue_mutex);
if (packet.is_null()) {
packet.instance();
packet->set_aspect(size.x / size.y);
send_packet(packet);
}
}
}
void GRClient::_update_texture_from_image(Ref<Image> img) {
if (tex_shows_stream && !tex_shows_stream->is_queued_for_deletion()) {
if (img.is_valid()) {
Ref<ImageTexture> tex = tex_shows_stream->get_texture();
if (tex.is_valid()) {
tex->create_from_image(img);
} else {
tex.instance();
tex->create_from_image(img);
tex_shows_stream->set_texture(tex);
}
uint32_t new_flags = Texture::FLAG_MIPMAPS | (is_filtering_enabled ? Texture::FLAG_FILTER : 0);
if (tex->get_flags() != new_flags) {
tex->set_flags(new_flags);
}
} else {
tex_shows_stream->set_texture(nullptr);
}
}
}
void GRClient::_update_stream_texture_state(ENUM_ARG(StreamState) _stream_state) {
if (is_deleting)
return;
if (tex_shows_stream && !tex_shows_stream->is_queued_for_deletion()) {
switch (_stream_state) {
case StreamState::STREAM_NO_SIGNAL: {
tex_shows_stream->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
if (custom_no_signal_texture.is_valid() || custom_no_signal_vertical_texture.is_valid()) {
tex_shows_stream->set_texture(no_signal_is_vertical ?
(custom_no_signal_vertical_texture.is_valid() ? custom_no_signal_vertical_texture : custom_no_signal_texture) :
(custom_no_signal_texture.is_valid() ? custom_no_signal_texture : custom_no_signal_vertical_texture));
}
#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
else {
_update_texture_from_image(no_signal_is_vertical ? no_signal_vertical_image : no_signal_image);
}
#endif
if (custom_no_signal_material.is_valid()) {
tex_shows_stream->set_material(custom_no_signal_material);
}
#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
else {
tex_shows_stream->set_material(no_signal_mat);
}
#endif
break;
}
case StreamState::STREAM_ACTIVE: {
tex_shows_stream->set_stretch_mode(stretch_mode == StretchMode::STRETCH_KEEP_ASPECT ? TextureRect::STRETCH_KEEP_ASPECT_CENTERED : TextureRect::STRETCH_SCALE);
tex_shows_stream->set_material(nullptr);
break;
}
case StreamState::STREAM_NO_IMAGE:
tex_shows_stream->set_stretch_mode(TextureRect::STRETCH_SCALE);
tex_shows_stream->set_material(nullptr);
tex_shows_stream->set_texture(nullptr);
break;
default:
_log("Wrong stream state!", LogLevel::LL_ERROR);
break;
}
if (signal_connection_state != _stream_state) {
call_deferred("emit_signal", "stream_state_changed", _stream_state);
signal_connection_state = (StreamState)_stream_state;
}
}
}
void GRClient::_reset_counters() {
GRDevice::_reset_counters();
sync_time_client = 0;
sync_time_server = 0;
}
void GRClient::set_server_setting(ENUM_ARG(TypesOfServerSettings) param, Variant value) {
Mutex_lock(send_queue_mutex);
Ref<GRPacketServerSettings> packet = _find_queued_packet_by_type<Ref<GRPacketServerSettings> >();
if (packet.is_valid()) {
packet->add_setting(param, value);
Mutex_unlock(send_queue_mutex);
return;
}
Mutex_unlock(send_queue_mutex);
if (packet.is_null()) {
packet.instance();
packet->add_setting(param, value);
send_packet(packet);
}
}
void GRClient::disable_overriding_server_settings() {
set_server_setting(TypesOfServerSettings::SERVER_SETTINGS_USE_INTERNAL, true);
}
//////////////////////////////////////////////
////////////////// STATIC ////////////////////
//////////////////////////////////////////////
void GRClient::_thread_connection(THREAD_DATA p_userdata) {
ConnectionThreadParamsClient *con_thread = (ConnectionThreadParamsClient *)p_userdata;
GRClient *dev = con_thread->dev;
Ref<StreamPeerTCP> con = con_thread->peer;
OS *os = OS::get_singleton();
Thread_set_name("GRemote_connection");
GRDevice::AuthResult prev_auth_error = GRDevice::AuthResult::OK;
const String con_error_title = "Connection Error";
while (!con_thread->stop_thread) {
if (os->get_ticks_usec() - dev->prev_valid_connection_time > 1000_ms) {
dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
dev->call_deferred("_remove_custom_input_scene");
}
if (con->get_status() == StreamPeerTCP::STATUS_CONNECTED || con->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
con->disconnect_from_host();
}
dev->_send_queue_resize(0);
#ifndef GDNATIVE_LIBRARY
IP_Address adr;
#else
String adr;
#endif
if (dev->con_type == CONNECTION_ADB) {
#ifndef GDNATIVE_LIBRARY
adr = IP_Address("127.0.0.1");
#else
adr = "127.0.0.1";
#endif
} else {
if (dev->server_address.is_valid_ip_address()) {
adr = dev->server_address;
if (adr.is_valid_ip()) {
} else {
_log("Address is invalid: " + dev->server_address, LogLevel::LL_ERROR);
if (prev_auth_error != GRDevice::AuthResult::Error)
GRNotifications::add_notification("Resolve Address Error", "Address is invalid: " + dev->server_address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
prev_auth_error = GRDevice::AuthResult::Error;
}
} else {
adr = IP::get_singleton()->resolve_hostname(adr);
if (adr.is_valid_ip()) {
_log("Resolved address for " + dev->server_address + "\n" + adr, LogLevel::LL_DEBUG);
} else {
_log("Can't resolve address for " + dev->server_address, LogLevel::LL_ERROR);
if (prev_auth_error != GRDevice::AuthResult::Error)
GRNotifications::add_notification("Resolve Address Error", "Can't resolve address: " + dev->server_address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
prev_auth_error = GRDevice::AuthResult::Error;
}
}
}
String address = (String)adr + ":" + str(dev->port);
Error err = con->connect_to_host(adr, dev->port);
_log("Connecting to " + address, LogLevel::LL_DEBUG);
if ((int)err) {
switch (err) {
case Error::FAILED:
_log("Failed to open socket or can't connect to host", LogLevel::LL_ERROR);
break;
case Error::ERR_UNAVAILABLE:
_log("Socket is unavailable", LogLevel::LL_ERROR);
break;
case Error::ERR_INVALID_PARAMETER:
_log("Host address is invalid", LogLevel::LL_ERROR);
break;
case Error::ERR_ALREADY_EXISTS:
_log("Socket already in use", LogLevel::LL_ERROR);
break;
}
sleep_usec(250_ms);
continue;
}
while (con->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
sleep_usec(1_ms);
}
if (con->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
_log("Connection timed out with " + address, LogLevel::LL_DEBUG);
if (prev_auth_error != GRDevice::AuthResult::Timeout) {
GRNotifications::add_notification(con_error_title, "Connection timed out: " + address, GRNotifications::NotificationIcon::ICON_WARNING, true, 1.f);
prev_auth_error = GRDevice::AuthResult::Timeout;
}
sleep_usec(200_ms);
continue;
}
con->set_no_delay(true);
bool long_wait = false;
Ref<PacketPeerStream> ppeer = newref(PacketPeerStream);
ppeer->set_stream_peer(con);
ppeer->set_input_buffer_max_size(dev->input_buffer_size_in_mb * 1024 * 1024);
GRDevice::AuthResult res = _auth_on_server(dev, ppeer);
switch (res) {
case GRDevice::AuthResult::OK: {
_log("Successful connected to " + address, LogLevel::LL_NORMAL);
dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_IMAGE);
con_thread->break_connection = false;
con_thread->peer = con;
con_thread->ppeer = ppeer;
dev->is_connection_working = true;
dev->call_deferred("emit_signal", "connection_state_changed", true);
dev->call_deferred("_force_update_stream_viewport_signals"); // force update screen aspect ratio and orientation
GRNotifications::add_notification("Connected", "Connected to " + address, GRNotifications::NotificationIcon::ICON_SUCCESS, true, 1.f);
_connection_loop(con_thread);
con_thread->peer.unref();
con_thread->ppeer.unref();
dev->is_connection_working = false;
dev->call_deferred("emit_signal", "connection_state_changed", false);
dev->call_deferred("emit_signal", "mouse_mode_changed", Input::MouseMode::MOUSE_MODE_VISIBLE);
break;
}
case GRDevice::AuthResult::Error:
if (res != prev_auth_error)
GRNotifications::add_notification(con_error_title, "Can't connect to " + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
long_wait = true;
break;
case GRDevice::AuthResult::Timeout:
if (res != prev_auth_error)
GRNotifications::add_notification(con_error_title, "Timeout\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
long_wait = true;
break;
case GRDevice::AuthResult::RefuseConnection:
if (res != prev_auth_error)
GRNotifications::add_notification(con_error_title, "Connection refused\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
long_wait = true;
break;
case GRDevice::AuthResult::VersionMismatch:
GRNotifications::add_notification(con_error_title, "Version mismatch\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
long_wait = true;
break;
case GRDevice::AuthResult::IncorrectPassword:
GRNotifications::add_notification(con_error_title, "Incorrect password\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
long_wait = true;
break;
case GRDevice::AuthResult::PasswordRequired:
GRNotifications::add_notification(con_error_title, "Required password but it's not implemented.... " + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
break;
default:
if (res != prev_auth_error)
GRNotifications::add_notification(con_error_title, "Unknown error code: " + str((int)res) + "\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
_log("Unknown error code: " + str((int)res) + ". Disconnecting. " + address, LogLevel::LL_NORMAL);
break;
}
((Ref<StreamPeerTCP>)ppeer->get_stream_peer())->disconnect_from_host();
ppeer->set_output_buffer_max_size(0);
ppeer->set_input_buffer_max_size(0);
prev_auth_error = res;
if (con->is_connected_to_host()) {
con->disconnect_from_host();
}
if (long_wait) {
sleep_usec(888_ms);
}
}
dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
_log("Connection thread stopped", LogLevel::LL_DEBUG);
con_thread->finished = true;
}
void GRClient::_connection_loop(ConnectionThreadParamsClient *con_thread) {
GRClient *dev = con_thread->dev;
Ref<StreamPeerTCP> connection = con_thread->peer;
Ref<PacketPeerStream> ppeer = con_thread->ppeer;
// Data sync with _img_thread
ImgProcessingStorageClient *ipsc = memnew(ImgProcessingStorageClient);
ipsc->dev = dev;
Thread_define(_img_thread);
Thread_start(_img_thread, GRClient, _thread_image_decoder, ipsc, dev);
OS *os = OS::get_singleton();
Error err = Error::OK;
String address = CONNECTION_ADDRESS(connection);
dev->_reset_counters();
//Array stream_queue; // Ref<GRPacketImageData>
std::vector<Ref<GRPacketImageData> > stream_queue; // Ref<GRPacketImageData>
uint64_t time64 = os->get_ticks_usec();
uint64_t prev_cycle_time = 0;
uint64_t prev_send_input_time = time64;
uint64_t prev_ping_sending_time = time64;
uint64_t next_image_required_frametime = time64;
uint64_t prev_display_image_time = time64 - 16_ms;
bool ping_sended = false;
TimeCountInit();
while (!con_thread->break_connection && !con_thread->stop_thread && connection->is_connected_to_host()) {
Mutex_lock(dev->connection_mutex);
TimeCount("Cycle start");
uint64_t cycle_start_time = os->get_ticks_usec();
bool nothing_happens = true;
uint64_t start_while_time = 0;
dev->prev_valid_connection_time = time64;
int send_data_time_us = (1000000 / dev->send_data_fps);
///////////////////////////////////////////////////////////////////
// SENDING
bool is_queued_send = false; // this placed here for android compiler
// INPUT
TimeCountReset();
time64 = os->get_ticks_usec();
if ((time64 - prev_send_input_time) > send_data_time_us) {
prev_send_input_time = time64;
nothing_happens = false;
if (dev->input_collector) {
Ref<GRPacketInputData> pack = dev->input_collector->get_collected_input_data();
if (pack.is_valid()) {
err = ppeer->put_var(pack->get_data());
if ((int)err) {
_log("Put input data failed with code: " + str((int)err), LogLevel::LL_ERROR);
goto end_send;
}
} else {
_log("Can't get input data from input collector", LogLevel::LL_ERROR);
}
TimeCount("Input send");
}
}
// 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 send");
}
// SEND QUEUE
start_while_time = os->get_ticks_usec();
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);
Mutex_unlock(dev->connection_mutex);
continue;
}
///////////////////////////////////////////////////////////////////
// RECEIVING
// Send to processing one of buffered images
time64 = os->get_ticks_usec();
TimeCountReset();
if (!ipsc->_is_processing_img && !stream_queue.empty() && time64 >= next_image_required_frametime) {
nothing_happens = false;
Ref<GRPacketImageData> pack = stream_queue.front();
stream_queue.erase(stream_queue.begin());
if (pack.is_null()) {
_log("Queued image data is null", LogLevel::LL_ERROR);
goto end_img_process;
}
uint64_t frametime = pack->get_frametime() > 1000_ms ? 1000_ms : pack->get_frametime();
next_image_required_frametime = time64 + frametime - prev_cycle_time;
dev->_update_avg_fps(time64 - prev_display_image_time);
prev_display_image_time = time64;
if (pack->get_is_empty()) {
dev->_update_avg_fps(0);
dev->call_deferred("_update_texture_from_image", Ref<Image>());
dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_IMAGE);
} else {
ipsc->tex_data = pack->get_image_data();
ipsc->compression_type = (ImageCompressionType)pack->get_compression_type();
ipsc->size = pack->get_size();
ipsc->format = pack->get_format();
ipsc->_is_processing_img = true;
}
pack.unref();
TimeCount("Get image from queue");
}
end_img_process:
// check if image displayed less then few seconds ago. if not then remove texture
const double image_loss_time = 1.5;
if (os->get_ticks_usec() > int64_t(prev_display_image_time + uint64_t(1000_ms * image_loss_time))) {
if (dev->signal_connection_state != StreamState::STREAM_NO_IMAGE) {
dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_IMAGE);
dev->_reset_counters();
}
}
if (stream_queue.size() > 10) {
//for (int i = 0; i<stream_queue.size(); i++)
//{
// ((Ref<GRPacketImageData>)stream_queue[i]).unref();
//}
stream_queue.clear();
}
// Get some packets
TimeCountReset();
start_while_time = os->get_ticks_usec();
while (ppeer->get_available_packet_count() > 0 && (os->get_ticks_usec() - start_while_time) <= send_data_time_us / 2) {
nothing_happens = false;
#ifndef GDNATIVE_LIBRARY
Variant buf;
err = ppeer->get_var(buf);
#else
Variant buf = ppeer->get_var();
#endif
if ((int)err)
goto end_recv;
Ref<GRPacket> pack = GRPacket::create(buf);
if (pack.is_null()) {
_log("Incorrect GRPacket", LogLevel::LL_ERROR);
continue;
}
GRPacket::PacketType type = pack->get_type();
switch (type) {
case GRPacket::PacketType::SyncTime: {
Ref<GRPacketSyncTime> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketSyncTime", LogLevel::LL_ERROR);
continue;
}
dev->sync_time_client = os->get_ticks_usec();
dev->sync_time_server = data->get_time();
break;
}
case GRPacket::PacketType::ImageData: {
Ref<GRPacketImageData> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketImageData", LogLevel::LL_ERROR);
continue;
}
stream_queue.push_back(data);
break;
}
case GRPacket::PacketType::ServerSettings: {
if (!dev->_server_settings_syncing) {
continue;
}
Ref<GRPacketServerSettings> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketServerSettings", LogLevel::LL_ERROR);
continue;
}
dev->call_deferred("emit_signal", "server_settings_received", map_to_dict(data->get_settings()));
break;
}
case GRPacket::PacketType::MouseModeSync: {
Ref<GRPacketMouseModeSync> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketMouseModeSync", LogLevel::LL_ERROR);
continue;
}
dev->call_deferred("emit_signal", "mouse_mode_changed", data->get_mouse_mode());
break;
}
case GRPacket::PacketType::CustomInputScene: {
Ref<GRPacketCustomInputScene> data = pack;
if (data.is_null()) {
_log("Incorrect GRPacketCustomInputScene", LogLevel::LL_ERROR);
continue;
}
dev->call_deferred("_load_custom_input_scene", data);
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_NORMAL);
break;
}
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:
Mutex_unlock(dev->connection_mutex);
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)
sleep_usec(1_ms);
prev_cycle_time = os->get_ticks_usec() - cycle_start_time;
}
dev->_send_queue_resize(0);
stream_queue.clear();
if (connection->is_connected_to_host()) {
_log("Lost connection to " + address, LogLevel::LL_ERROR);
GRNotifications::add_notification("Disconnected", "Closing connection to " + address, GRNotifications::NotificationIcon::ICON_FAIL, true, 1.f);
} else {
_log("Closing connection to " + address, LogLevel::LL_ERROR);
GRNotifications::add_notification("Disconnected", "Lost connection to " + address, GRNotifications::NotificationIcon::ICON_FAIL, true, 1.f);
}
ipsc->_thread_closing = true;
Thread_close(_img_thread);
memdelete(ipsc);
_log("Closing connection", LogLevel::LL_NORMAL);
con_thread->break_connection = true;
}
void GRClient::_thread_image_decoder(THREAD_DATA p_userdata) {
ImgProcessingStorageClient *ipsc = (ImgProcessingStorageClient *)p_userdata;
GRClient *dev = ipsc->dev;
Error err = Error::OK;
while (!ipsc->_thread_closing) {
if (!ipsc->_is_processing_img) {
sleep_usec(1_ms);
continue;
}
Ref<Image> img(memnew(Image));
ImageCompressionType type = ipsc->compression_type;
TimeCountInit();
switch (type) {
case ImageCompressionType::COMPRESSION_UNCOMPRESSED: {
#ifndef GDNATIVE_LIBRARY
img->create(ipsc->size.x, ipsc->size.y, false, (Image::Format)ipsc->format, ipsc->tex_data);
#else
img->create_from_data((int)ipsc->size.x, (int)ipsc->size.y, false, (Image::Format)ipsc->format, ipsc->tex_data);
#endif
if (img_is_empty(img)) { // is NOT OK
err = Error::FAILED;
_log("Incorrect uncompressed image data.", LogLevel::LL_ERROR);
GRNotifications::add_notification("Stream Error", "Incorrect uncompressed image data.", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
break;
}
case ImageCompressionType::COMPRESSION_JPG: {
err = img->load_jpg_from_buffer(ipsc->tex_data);
if ((int)err || img_is_empty(img)) { // is NOT OK
_log("Can't decode JPG image.", LogLevel::LL_ERROR);
GRNotifications::add_notification("Stream Error", "Can't decode JPG image. Code: " + str((int)err), GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
break;
}
case ImageCompressionType::COMPRESSION_PNG: {
err = img->load_png_from_buffer(ipsc->tex_data);
if ((int)err || img_is_empty(img)) { // is NOT OK
_log("Can't decode PNG image.", LogLevel::LL_ERROR);
GRNotifications::add_notification("Stream Error", "Can't decode PNG image. Code: " + str((int)err), GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
}
break;
}
default:
_log("Not implemented image decoder type: " + str((int)type), LogLevel::LL_ERROR);
break;
}
if (!(int)err) { // is OK
TimeCount("Create Image Time");
dev->call_deferred("_update_texture_from_image", img);
if (dev->signal_connection_state != StreamState::STREAM_ACTIVE) {
dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_ACTIVE);
}
}
ipsc->_is_processing_img = false;
}
}
GRDevice::AuthResult GRClient::_auth_on_server(GRClient *dev, Ref<PacketPeerStream> &ppeer) {
#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. Disconnecting. Waited: " + str(_n), LogLevel::LL_DEBUG); \
goto timeout; \
} \
if (!con->is_connected_to_host()) { \
return GRDevice::AuthResult::Error; \
} \
sleep_usec(1_ms); \
}
#define packet_error_check(_t) \
if ((int)err) { \
_log(_t, LogLevel::LL_DEBUG); \
return GRDevice::AuthResult::Error; \
}
Ref<StreamPeerTCP> con = ppeer->get_stream_peer();
String address = CONNECTION_ADDRESS(con);
uint32_t time = 0;
Error err = Error::OK;
Variant ret;
// GET first packet
wait_packet("first_packet");
#ifndef GDNATIVE_LIBRARY
err = ppeer->get_var(ret);
packet_error_check("Can't get first authorization packet from server. Code: " + str((int)err));
#else
err = Error::OK;
ret = ppeer->get_var();
#endif
if ((int)ret == (int)GRDevice::AuthResult::RefuseConnection) {
_log("Connection refused", LogLevel::LL_ERROR);
return GRDevice::AuthResult::RefuseConnection;
}
if ((int)ret == (int)GRDevice::AuthResult::TryToConnect) {
Dictionary data;
data["id"] = dev->device_id;
data["version"] = get_gr_version();
data["password"] = dev->password;
// PUT auth data
err = ppeer->put_var(data);
packet_error_check("Can't put authorization data to server. Code: " + str((int)err));
// GET result
wait_packet("result");
#ifndef GDNATIVE_LIBRARY
err = ppeer->get_var(ret);
packet_error_check("Can't get final authorization packet from server. Code: " + str((int)err));
#else
err = Error::OK;
ret = ppeer->get_var();
#endif
if ((int)ret == (int)GRDevice::AuthResult::OK) {
return GRDevice::AuthResult::OK;
} else {
GRDevice::AuthResult r = (GRDevice::AuthResult)(int)ret;
switch (r) {
case GRDevice::AuthResult::Error:
_log("Can't connect to server", LogLevel::LL_ERROR);
return r;
case GRDevice::AuthResult::VersionMismatch:
_log("Version mismatch", LogLevel::LL_ERROR);
return r;
case GRDevice::AuthResult::IncorrectPassword:
_log("Incorrect password", LogLevel::LL_ERROR);
return r;
}
}
}
return GRDevice::AuthResult::Error;
timeout:
con->disconnect_from_host();
_log("Connection timeout. Disconnecting", LogLevel::LL_NORMAL);
return GRDevice::AuthResult::Timeout;
#undef wait_packet
#undef packet_error_check
}
//////////////////////////////////////////////
///////////// INPUT COLLECTOR ////////////////
//////////////////////////////////////////////
void GRInputCollector::_update_stream_rect() {
if (!dev || dev->get_status() != GRDevice::WorkingStatus::STATUS_WORKING)
return;
if (texture_rect && !texture_rect->is_queued_for_deletion()) {
switch (dev->get_stretch_mode()) {
case GRClient::StretchMode::STRETCH_KEEP_ASPECT: {
Ref<Texture> tex = texture_rect->get_texture();
if (tex.is_null())
goto fill;
Vector2 pos = texture_rect->get_global_position();
Vector2 outer_size = texture_rect->get_size();
Vector2 inner_size = tex->get_size();
float asp_rec = outer_size.x / outer_size.y;
float asp_tex = inner_size.x / inner_size.y;
if (asp_rec > asp_tex) {
float width = outer_size.y * asp_tex;
stream_rect = Rect2(Vector2(pos.x + (outer_size.x - width) / 2, pos.y), Vector2(width, outer_size.y));
return;
} else {
float height = outer_size.x / asp_tex;
stream_rect = Rect2(Vector2(pos.x, pos.y + (outer_size.y - height) / 2), Vector2(outer_size.x, height));
return;
}
break;
}
case GRClient::StretchMode::STRETCH_FILL:
default:
fill:
stream_rect = Rect2(texture_rect->get_global_position(), texture_rect->get_size());
return;
}
}
if (parent && !parent->is_queued_for_deletion()) {
stream_rect = Rect2(parent->get_global_position(), parent->get_size());
}
return;
}
void GRInputCollector::_collect_input(Ref<InputEvent> ie) {
Ref<GRInputDataEvent> data = GRInputDataEvent::parse_event(ie, stream_rect);
if (data.is_valid()) {
_TS_LOCK_;
collected_input_data.push_back(data);
_TS_UNLOCK_;
}
}
void GRInputCollector::_release_pointers() {
{
auto buttons = mouse_buttons.keys();
for (int i = 0; i < buttons.size(); i++) {
if (mouse_buttons[buttons[i]]) {
Ref<InputEventMouseButton> iemb(memnew(InputEventMouseButton));
iemb->set_button_index(buttons[i]);
iemb->set_pressed(false);
buttons[i] = false;
_collect_input(iemb);
}
}
buttons.clear();
}
{
auto touches = screen_touches.keys();
for (int i = 0; i < touches.size(); i++) {
if (screen_touches[touches[i]]) {
Ref<InputEventScreenTouch> iest(memnew(InputEventScreenTouch));
iest->set_index(touches[i]);
iest->set_pressed(false);
touches[i] = false;
_collect_input(iest);
}
}
touches.clear();
}
}
#ifndef GDNATIVE_LIBRARY
void GRInputCollector::_bind_methods() {
ClassDB::bind_method(D_METHOD("_input", "input_event"), &GRInputCollector::_input);
ClassDB::bind_method(D_METHOD("is_capture_on_focus"), &GRInputCollector::is_capture_on_focus);
ClassDB::bind_method(D_METHOD("set_capture_on_focus", "value"), &GRInputCollector::set_capture_on_focus);
ClassDB::bind_method(D_METHOD("is_capture_when_hover"), &GRInputCollector::is_capture_when_hover);
ClassDB::bind_method(D_METHOD("set_capture_when_hover", "value"), &GRInputCollector::set_capture_when_hover);
ClassDB::bind_method(D_METHOD("is_capture_pointer"), &GRInputCollector::is_capture_pointer);
ClassDB::bind_method(D_METHOD("set_capture_pointer", "value"), &GRInputCollector::set_capture_pointer);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_on_focus"), "set_capture_on_focus", "is_capture_on_focus");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_when_hover"), "set_capture_when_hover", "is_capture_when_hover");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_pointer"), "set_capture_pointer", "is_capture_pointer");
}
#else
void GRInputCollector::_register_methods() {
METHOD_REG(GRInputCollector, _notification);
METHOD_REG(GRInputCollector, _input);
METHOD_REG(GRInputCollector, is_capture_on_focus);
METHOD_REG(GRInputCollector, set_capture_on_focus);
METHOD_REG(GRInputCollector, is_capture_when_hover);
METHOD_REG(GRInputCollector, set_capture_when_hover);
METHOD_REG(GRInputCollector, is_capture_pointer);
METHOD_REG(GRInputCollector, set_capture_pointer);
register_property<GRInputCollector, bool>("capture_on_focus", &GRInputCollector::set_capture_on_focus, &GRInputCollector::is_capture_on_focus, false);
register_property<GRInputCollector, bool>("capture_when_hover", &GRInputCollector::set_capture_when_hover, &GRInputCollector::is_capture_when_hover, true);
register_property<GRInputCollector, bool>("capture_pointer", &GRInputCollector::set_capture_pointer, &GRInputCollector::is_capture_pointer, true);
}
#endif
void GRInputCollector::_input(Ref<InputEvent> ie) {
if (!parent || (capture_only_when_control_in_focus && !parent->has_focus()) ||
(dev && dev->get_status() != GRDevice::WorkingStatus::STATUS_WORKING) ||
!dev->is_stream_active() || !is_inside_tree()) {
return;
}
_TS_LOCK_;
if (collected_input_data.size() >= 256) {
collected_input_data.resize(0);
}
_TS_UNLOCK_;
_update_stream_rect();
if (ie.is_null()) {
_log("InputEvent is null", LogLevel::LL_ERROR);
return;
}
{
Ref<InputEventMouseButton> iemb = ie;
if (iemb.is_valid()) {
int idx = (int)iemb->get_button_index();
if ((!stream_rect.has_point(iemb->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer) {
if (idx == BUTTON_WHEEL_UP || idx == BUTTON_WHEEL_DOWN ||
idx == BUTTON_WHEEL_LEFT || idx == BUTTON_WHEEL_RIGHT) {
return;
} else {
if (iemb->is_pressed() || !((bool)mouse_buttons[idx]))
return;
}
}
mouse_buttons[idx] = iemb->is_pressed();
goto end;
}
}
{
Ref<InputEventMouseMotion> iemm = ie;
if (iemm.is_valid()) {
if ((!stream_rect.has_point(iemm->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
return;
goto end;
}
}
{
Ref<InputEventScreenTouch> iest = ie;
if (iest.is_valid()) {
int idx = (int)iest->get_index();
if ((!stream_rect.has_point(iest->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer) {
if (iest->is_pressed() || !((bool)screen_touches[idx]))
return;
}
screen_touches[idx] = iest->is_pressed();
goto end;
}
}
{
Ref<InputEventScreenDrag> iesd = ie;
if (iesd.is_valid()) {
if ((!stream_rect.has_point(iesd->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
return;
goto end;
}
}
{
Ref<InputEventMagnifyGesture> iemg = ie;
if (iemg.is_valid()) {
if ((!stream_rect.has_point(iemg->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
return;
goto end;
}
}
{
Ref<InputEventPanGesture> iepg = ie;
if (iepg.is_valid()) {
if ((!stream_rect.has_point(iepg->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
return;
goto end;
}
}
end:
_collect_input(ie);
}
void GRInputCollector::_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: {
parent = cast_to<Control>(get_parent());
break;
}
case NOTIFICATION_EXIT_TREE: {
parent = nullptr;
break;
}
case NOTIFICATION_PROCESS: {
_TS_LOCK_;
auto w = sensors.write();
w[0] = Input::get_singleton()->get_accelerometer();
w[1] = Input::get_singleton()->get_gravity();
w[2] = Input::get_singleton()->get_gyroscope();
w[3] = Input::get_singleton()->get_magnetometer();
release_pva_write(w);
_TS_UNLOCK_;
break;
}
}
}
bool GRInputCollector::is_capture_on_focus() {
return capture_only_when_control_in_focus;
}
void GRInputCollector::set_capture_on_focus(bool value) {
capture_only_when_control_in_focus = value;
}
bool GRInputCollector::is_capture_when_hover() {
return capture_pointer_only_when_hover_control;
}
void GRInputCollector::set_capture_when_hover(bool value) {
capture_pointer_only_when_hover_control = value;
}
bool GRInputCollector::is_capture_pointer() {
return !dont_capture_pointer;
}
void GRInputCollector::set_capture_pointer(bool value) {
if (!value) {
_release_pointers();
}
dont_capture_pointer = !value;
}
bool GRInputCollector::is_capture_input() {
return is_processing_input();
}
void GRInputCollector::set_capture_input(bool value) {
set_process_input(value);
}
void GRInputCollector::set_tex_rect(TextureRect *tr) {
texture_rect = tr;
}
Ref<GRPacketInputData> GRInputCollector::get_collected_input_data() {
Ref<GRPacketInputData> res(memnew(GRPacketInputData));
Ref<GRInputDeviceSensorsData> s(memnew(GRInputDeviceSensorsData));
_TS_LOCK_;
s->set_sensors(sensors);
collected_input_data.push_back(s);
res->set_input_data(collected_input_data);
collected_input_data.resize(0);
_TS_UNLOCK_;
return res;
}
void GRInputCollector::_init() {
LEAVE_IF_EDITOR();
_TS_LOCK_;
parent = nullptr;
set_process(true);
set_process_input(true);
sensors.resize(4);
_TS_UNLOCK_;
}
void GRInputCollector::_deinit() {
LEAVE_IF_EDITOR();
_TS_LOCK_;
sensors.resize(0);
collected_input_data.resize(0);
if (this_in_client)
*this_in_client = nullptr;
mouse_buttons.clear();
screen_touches.clear();
_TS_UNLOCK_;
}
//////////////////////////////////////////////
/////////////// TEXTURE RECT /////////////////
//////////////////////////////////////////////
void GRTextureRect::_tex_size_changed() {
if (dev) {
Vector2 v = get_size();
bool is_vertical = v.x < v.y;
if (is_vertical != dev->no_signal_is_vertical) {
dev->no_signal_is_vertical = is_vertical;
dev->_update_stream_texture_state(dev->signal_connection_state); // update texture
}
}
}
#ifndef GDNATIVE_LIBRARY
void GRTextureRect::_bind_methods() {
ClassDB::bind_method(D_METHOD("_tex_size_changed"), &GRTextureRect::_tex_size_changed);
}
#else
void GRTextureRect::_register_methods() {
METHOD_REG(GRTextureRect, _notification);
METHOD_REG(GRTextureRect, _tex_size_changed);
}
#endif
void GRTextureRect::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_POSTINITIALIZE:
#ifndef GDNATIVE_LIBRARY
_init();
#endif
break;
case NOTIFICATION_PREDELETE:
_deinit();
break;
}
}
void GRTextureRect::_init() {
LEAVE_IF_EDITOR();
connect("resized", this, "_tex_size_changed");
}
void GRTextureRect::_deinit() {
if (this_in_client)
*this_in_client = nullptr;
LEAVE_IF_EDITOR();
disconnect("resized", this, "_tex_size_changed");
}
#endif // !NO_GODOTREMOTE_CLIENT