Skip to content

Commit

Permalink
Embedding game process in editor
Browse files Browse the repository at this point in the history
  • Loading branch information
Hilderin committed Oct 1, 2024
1 parent 2ab283a commit 4ed7b31
Show file tree
Hide file tree
Showing 35 changed files with 965 additions and 90 deletions.
3 changes: 3 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,9 @@
<constant name="FEATURE_NATIVE_DIALOG_FILE" value="25" enum="Feature">
Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b]
</constant>
<constant name="FEATURE_WINDOW_EMBEDDING" value="25" enum="Feature">
Display server supports embedding a window from another process. [b]Windows, Linux (X11)[/b]
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
Expand Down
13 changes: 13 additions & 0 deletions editor/editor_run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/plugins/game_editor_plugin.h"
#include "editor/run_instances_dialog.h"
#include "main/main.h"
#include "servers/display_server.h"
Expand Down Expand Up @@ -175,12 +176,14 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) {
args.push_back(itos(pos.x) + "," + itos(pos.y));
} break;
case 3: { // force maximized

Vector2 pos = screen_rect.position + screen_rect.size / 2;
args.push_back("--position");
args.push_back(itos(pos.x) + "," + itos(pos.y));
args.push_back("--maximized");
} break;
case 4: { // force fullscreen

Vector2 pos = screen_rect.position + screen_rect.size / 2;
args.push_back("--position");
args.push_back(itos(pos.x) + "," + itos(pos.y));
Expand Down Expand Up @@ -234,6 +237,9 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) {
List<String> instance_args(args);
RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args);
RunInstancesDialog::get_singleton()->apply_custom_features(i);
if (GameEditor::get_singleton()) {
GameEditor::get_singleton()->get_argument_list_for_instance(i, instance_args);
}

if (OS::get_singleton()->is_stdout_verbose()) {
print_line(vformat("Running: %s", exec));
Expand Down Expand Up @@ -286,6 +292,13 @@ void EditorRun::stop() {
running_scene = "";
}

OS::ProcessID EditorRun::get_current_process() const {
if (pids.is_empty()) {
return 0;
}
return pids.get(0);
}

EditorRun::EditorRun() {
status = STATUS_STOP;
running_scene = "";
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_run.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#define EDITOR_RUN_H

#include "core/os/os.h"
#include "servers/display_server.h"

class EditorRun {
public:
Expand All @@ -58,6 +59,7 @@ class EditorRun {
void stop_child_process(OS::ProcessID p_pid);
bool has_child_process(OS::ProcessID p_pid) const;
int get_child_process_count() const { return pids.size(); }
OS::ProcessID get_current_process() const;

EditorRun();
};
Expand Down
32 changes: 32 additions & 0 deletions editor/gui/editor_run_bar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,34 @@ void EditorRunBar::play_custom_scene(const String &p_custom) {
current_mode = RunMode::RUN_CUSTOM;
_run_scene(p_custom);
}
void EditorRunBar::restart() {
if (!is_playing()) {
return;
}

RunMode last_current_mode = current_mode;
String last_run_custom_filename = run_custom_filename;
String last_run_current_filename = run_current_filename;

stop_playing();

switch (last_current_mode) {
case RunMode::RUN_MAIN: {
_run_scene();
} break;
case RunMode::RUN_CUSTOM: {
play_custom_scene(last_run_custom_filename);
} break;
case RunMode::RUN_CURRENT: {
_run_scene(last_run_current_filename);
} break;
case RunMode::STOPPED: {
// Nothing to do.
} break;
}

current_mode = last_current_mode;
}

void EditorRunBar::stop_playing() {
if (editor_run.get_status() == EditorRun::STATUS_STOP) {
Expand Down Expand Up @@ -345,6 +373,10 @@ void EditorRunBar::stop_child_process(OS::ProcessID p_pid) {
}
}

OS::ProcessID EditorRunBar::get_current_process() const {
return editor_run.get_current_process();
}

void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
write_movie_button->set_pressed(p_enabled);
}
Expand Down
2 changes: 2 additions & 0 deletions editor/gui/editor_run_bar.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class EditorRunBar : public MarginContainer {
void play_main_scene(bool p_from_native = false);
void play_current_scene(bool p_reload = false);
void play_custom_scene(const String &p_custom);
void restart();

void stop_playing();
bool is_playing() const;
Expand All @@ -105,6 +106,7 @@ class EditorRunBar : public MarginContainer {

OS::ProcessID has_child_process(OS::ProcessID p_pid) const;
void stop_child_process(OS::ProcessID p_pid);
OS::ProcessID get_current_process() const;

void set_movie_maker_enabled(bool p_enabled);
bool is_movie_maker_enabled() const;
Expand Down
1 change: 1 addition & 0 deletions editor/icons/AutoFocus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions editor/icons/EmbeddedProcess.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions editor/icons/KeepAspect.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
178 changes: 177 additions & 1 deletion editor/plugins/game_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@

#include "game_editor_plugin.h"

#include "core/config/project_settings.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/button.h"
#include "scene/gui/panel.h"
#include "scene/gui/separator.h"

void GameEditorDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
p_session->send_message("scene:runtime_node_select_setup", Array());
Expand Down Expand Up @@ -118,6 +119,12 @@ void GameEditorDebugger::_bind_methods() {

///////

GameEditor *GameEditor::singleton = nullptr;

GameEditor *GameEditor::get_singleton() {
return singleton;
}

void GameEditor::_sessions_changed() {
// The debugger session's `session_started/stopped` signal can be unreliable, so count it manually.
active_sessions = 0;
Expand All @@ -131,6 +138,35 @@ void GameEditor::_sessions_changed() {
_update_debugger_buttons();
}

void GameEditor::_play_pressed() {
OS::ProcessID current_process_id = EditorRunBar::get_singleton()->get_current_process();
if (current_process_id == 0) {
return;
}

if (embedded_button->is_pressed()) {
embedded_process->embed_process(current_process_id);
_update_ui();

if (auto_focus_button->is_pressed()) {
EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_GAME);
}
}
}

void GameEditor::_stop_pressed() {
embedded_process->reset();
_update_ui();
}

void GameEditor::_embedding_completed() {
_update_ui();
}

void GameEditor::_embedding_failed() {
state_label->set_text(TTR("Connection impossible to the game process."));
}

void GameEditor::_update_debugger_buttons() {
bool empty = active_sessions == 0;
suspend_button->set_disabled(empty);
Expand Down Expand Up @@ -167,6 +203,40 @@ void GameEditor::_select_mode_pressed(int p_option) {
debugger->set_select_mode(mode);
}

void GameEditor::_embedded_button_pressed() {
EditorSettings::get_singleton()->set_project_metadata("game_editor", "embedded", embedded_button->is_pressed());

if (EditorRunBar::get_singleton()->is_playing()) {
EditorRunBar::get_singleton()->restart();
}

_update_ui();
}

void GameEditor::_auto_focus_button_pressed() {
EditorSettings::get_singleton()->set_project_metadata("game_editor", "auto_focus", auto_focus_button->is_pressed());
}

void GameEditor::_keep_aspect_button_pressed() {
EditorSettings::get_singleton()->set_project_metadata("game_editor", "keep_aspect", keep_aspect_button->is_pressed());
}

void GameEditor::_update_ui() {
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
state_label->set_text(TTR("Game embedding not available on your OS."));
} else if (embedded_process->is_embedding_completed()) {
state_label->set_text("");
} else if (embedded_process->is_embedding_in_progress()) {
state_label->set_text(TTR("Game starting..."));
} else if (EditorRunBar::get_singleton()->is_playing()) {
state_label->set_text(TTR("Game running not embedded."));
} else if (embedded_button->is_pressed()) {
state_label->set_text(TTR("Press play to start the game."));
} else {
state_label->set_text(TTR("Embedding is disabled."));
}
}

void GameEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
Expand All @@ -180,12 +250,77 @@ void GameEditor::_notification(int p_what) {
select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_icon(get_editor_theme_icon(SNAME("ToolSelect")));
select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_icon(get_editor_theme_icon(SNAME("ListSelect")));

embedded_button->set_icon(get_editor_theme_icon(SNAME("EmbeddedProcess")));
auto_focus_button->set_icon(get_editor_theme_icon(SNAME("AutoFocus")));
keep_aspect_button->set_icon(get_editor_theme_icon(SNAME("KeepAspect")));

panel->set_theme_type_variation("GamePanel");
} break;

case NOTIFICATION_READY: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
// Embedding available.
embedded_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_editor", "embedded", true));
auto_focus_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_editor", "auto_focus", true));
keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_editor", "keep_aspect", true));

EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameEditor::_play_pressed));
EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameEditor::_stop_pressed));
} else {
// Embedding not available.
embedding_separator->hide();
embedded_button->hide();
auto_focus_button->hide();
keep_aspect_button->hide();
keep_aspect_button->hide();
}

_update_ui();
} break;
}
}

void GameEditor::get_argument_list_for_instance(int p_idx, List<String> &r_list) {
if (p_idx != 0 || !embedded_button->is_pressed() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
return;
}

// Remove duplicates/unwanted parameters.
List<String, DefaultAllocator>::Element *position_item = r_list.find("--position");
if (position_item) {
r_list.erase(position_item->next());
r_list.erase(position_item);
}
List<String, DefaultAllocator>::Element *resolution_item = r_list.find("--resolution");
if (resolution_item) {
r_list.erase(resolution_item->next());
r_list.erase(resolution_item);
}
List<String, DefaultAllocator>::Element *screen_item = r_list.find("--screen");
if (screen_item) {
r_list.erase(screen_item->next());
r_list.erase(screen_item);
}
r_list.erase("-f");
r_list.erase("--fullscreen");
r_list.erase("-m");
r_list.erase("--maximized");
r_list.erase("-t");
r_list.erase("--always-on-top");

// Add editor window native id so the started game can directly set it's parent to it.
r_list.push_back("--wid");
r_list.push_back(itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id())));

Rect2i rect = embedded_process->get_screen_rect();
r_list.push_back("--position");
r_list.push_back(itos(rect.position.x) + "," + itos(rect.position.y));
r_list.push_back("--resolution");
r_list.push_back(itos(rect.size.x) + "x" + itos(rect.size.y));
}

GameEditor::GameEditor(Ref<GameEditorDebugger> p_debugger) {
singleton = this;
debugger = p_debugger;

// Add some margin to the sides for better aesthetics.
Expand Down Expand Up @@ -252,14 +387,55 @@ GameEditor::GameEditor(Ref<GameEditorDebugger> p_debugger) {
select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->connect(SceneStringName(pressed), callable_mp(this, &GameEditor::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_LIST));
select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked."));

embedding_separator = memnew(VSeparator);
main_menu_hbox->add_child(embedding_separator);

embedded_button = memnew(Button);
main_menu_hbox->add_child(embedded_button);
embedded_button->set_toggle_mode(true);
embedded_button->set_theme_type_variation("FlatButton");
embedded_button->set_tooltip_text(TTR("Activate the game embedding mode."));
embedded_button->connect(SceneStringName(pressed), callable_mp(this, &GameEditor::_embedded_button_pressed));

auto_focus_button = memnew(Button);
main_menu_hbox->add_child(auto_focus_button);
auto_focus_button->set_toggle_mode(true);
auto_focus_button->set_theme_type_variation("FlatButton");
auto_focus_button->set_tooltip_text(TTR("Focus the game editor on project run."));
auto_focus_button->connect(SceneStringName(pressed), callable_mp(this, &GameEditor::_auto_focus_button_pressed));

keep_aspect_button = memnew(Button);
main_menu_hbox->add_child(keep_aspect_button);
keep_aspect_button->set_toggle_mode(true);
keep_aspect_button->set_theme_type_variation("FlatButton");
keep_aspect_button->set_tooltip_text(TTR("Keep aspect ratio of the embedded game."));
keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameEditor::_keep_aspect_button_pressed));

panel = memnew(Panel);
add_child(panel);
panel->set_v_size_flags(SIZE_EXPAND_FILL);

embedded_process = memnew(EmbeddedProcess);
panel->add_child(embedded_process);
embedded_process->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
embedded_process->connect(SNAME("embedding_failed"), callable_mp(this, &GameEditor::_embedding_failed));
embedded_process->connect(SNAME("embedding_completed"), callable_mp(this, &GameEditor::_embedding_completed));

state_label = memnew(Label());
panel->add_child(state_label);
state_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
state_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
state_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
state_label->set_anchors_and_offsets_preset(PRESET_FULL_RECT);

p_debugger->connect("session_started", callable_mp(this, &GameEditor::_sessions_changed));
p_debugger->connect("session_stopped", callable_mp(this, &GameEditor::_sessions_changed));
}

GameEditor::~GameEditor() {
singleton = nullptr;
}

///////

void GameEditorPlugin::make_visible(bool p_visible) {
Expand Down
Loading

0 comments on commit 4ed7b31

Please sign in to comment.