Skip to content

Commit

Permalink
Editor autosave (#1554)
Browse files Browse the repository at this point in the history
* Autosave feature + recovery dialog

* Editor autosave: forgot to actually make the setting effective :)

* Changed auto-save recovery dialog message

Co-authored-by: Semphris <semphris@protonmail.com>
  • Loading branch information
Semphriss and Semphris committed Nov 6, 2020
1 parent 916f8bc commit 44adc43
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 26 deletions.
75 changes: 57 additions & 18 deletions src/editor/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
#include "sdk/integration.hpp"
#include "sprite/sprite_manager.hpp"
#include "supertux/game_manager.hpp"
#include "supertux/gameconfig.hpp"
#include "supertux/globals.hpp"
#include "supertux/level.hpp"
#include "supertux/level_parser.hpp"
#include "supertux/menu/menu_storage.hpp"
Expand All @@ -54,7 +56,6 @@
#include "supertux/world.hpp"
#include "util/file_system.hpp"
#include "util/reader_mapping.hpp"
#include "util/string_util.hpp"
#include "video/compositor.hpp"
#include "video/drawing_context.hpp"
#include "video/surface.hpp"
Expand All @@ -78,7 +79,7 @@ Editor::Editor() :
m_level(),
m_world(),
m_levelfile(),
m_test_levelfile(),
m_autosave_levelfile(),
m_quit_request(false),
m_newlevel_request(false),
m_reload_request(false),
Expand All @@ -100,7 +101,8 @@ Editor::Editor() :
m_bgr_surface(Surface::from_file("images/background/antarctic/arctis2.png")),
m_undo_manager(new UndoManager),
m_ignore_sector_change(false),
m_level_first_loaded(false)
m_level_first_loaded(false),
m_time_since_last_save(0.f)
{
auto toolbox_widget = std::make_unique<EditorToolboxWidget>(*this);
auto layers_widget = std::make_unique<EditorLayersWidget>(*this);
Expand Down Expand Up @@ -152,6 +154,25 @@ Editor::draw(Compositor& compositor)
void
Editor::update(float dt_sec, const Controller& controller)
{
// Auto-save (interval)
if (m_level) {
m_time_since_last_save += dt_sec;
if (m_time_since_last_save >= static_cast<float>(std::max(
g_config->editor_autosave_frequency, 1)) * 60.f) {
m_time_since_last_save = 0.f;
std::string backup_filename = get_autosave_from_levelname(m_levelfile);
std::string directory = get_level_directory();

// Set the test level file even though we're not testing, so that
// if the user quits the editor without ever testing, it'll delete
// the autosave file anyways
m_autosave_levelfile = FileSystem::join(directory, backup_filename);
m_level->save(m_autosave_levelfile);
}
} else {
m_time_since_last_save = 0.f;
}

// Pass all requests
if (m_reload_request) {
reload_level();
Expand Down Expand Up @@ -209,12 +230,31 @@ Editor::update(float dt_sec, const Controller& controller)
}
}

void
Editor::remove_autosave_file()
{
// Clear the auto-save file
if (!m_autosave_levelfile.empty())
{
// Try to remove the test level using the PhysFS file system
if (physfsutil::remove(m_autosave_levelfile) != 0)
{
// This file is not inside any PhysFS mounts,
// try to remove this using normal file system
// methods.
FileSystem::remove(m_autosave_levelfile);
}
}
}

void
Editor::save_level()
{
m_undo_manager->reset_index();
m_level->save(m_world ? FileSystem::join(m_world->get_basedir(), m_levelfile) :
m_levelfile);
m_time_since_last_save = 0.f;
remove_autosave_file();
}

std::string
Expand Down Expand Up @@ -242,7 +282,7 @@ Editor::test_level(const boost::optional<std::pair<std::string, Vector>>& test_p

Tile::draw_editor_images = false;
Compositor::s_render_lighting = true;
std::string backup_filename = m_levelfile + "~";
std::string backup_filename = get_autosave_from_levelname(m_levelfile);
std::string directory = get_level_directory();

// This is jank to get an owned World pointer, GameManager/World
Expand All @@ -254,8 +294,10 @@ Editor::test_level(const boost::optional<std::pair<std::string, Vector>>& test_p
current_world = owned_world.get();
}

m_test_levelfile = FileSystem::join(directory, backup_filename);
m_level->save(m_test_levelfile);
m_autosave_levelfile = FileSystem::join(directory, backup_filename);
m_level->save(m_autosave_levelfile);
m_time_since_last_save = 0.f;

if (!m_level->is_worldmap())
{
Integration::set_level(m_level->get_name().c_str());
Expand All @@ -266,7 +308,7 @@ Editor::test_level(const boost::optional<std::pair<std::string, Vector>>& test_p
{
Integration::set_worldmap(m_level->get_name().c_str());
Integration::set_status(TESTING_WORLDMAP);
GameManager::current()->start_worldmap(*current_world, "", m_test_levelfile);
GameManager::current()->start_worldmap(*current_world, "", m_autosave_levelfile);
}

m_leveltested = true;
Expand Down Expand Up @@ -472,6 +514,12 @@ Editor::reload_level()
StringUtil::has_suffix(m_levelfile, ".stwm"),
true));
ReaderMapping::s_translations_enabled = true;

// Autosave files : Once the level is loaded, make sure
// to use the regular file
m_levelfile = get_levelname_from_autosave(m_levelfile);
m_autosave_levelfile = FileSystem::join(get_level_directory(),
get_autosave_from_levelname(m_levelfile));
}

void
Expand All @@ -481,6 +529,8 @@ Editor::quit_editor()

auto quit = [this] ()
{
remove_autosave_file();

//Quit level editor
m_world = nullptr;
m_levelfile = "";
Expand Down Expand Up @@ -573,17 +623,6 @@ Editor::setup()

// Reactivate the editor after level test
if (m_leveltested) {
if (!m_test_levelfile.empty())
{
// Try to remove the test level using the PhysFS file system
if (physfsutil::remove(m_test_levelfile) != 0)
{
// This file is not inside any PhysFS mounts,
// try to remove this using normal file system
// methods.
FileSystem::remove(m_test_levelfile);
}
}
m_leveltested = false;
Tile::draw_editor_images = true;
m_level->reactivate();
Expand Down
18 changes: 17 additions & 1 deletion src/editor/editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "util/currenton.hpp"
#include "util/file_system.hpp"
#include "util/log.hpp"
#include "util/string_util.hpp"
#include "video/surface_ptr.hpp"

class GameObject;
Expand All @@ -48,6 +49,17 @@ class Editor final : public Screen,
public:
static bool is_active();

private:
static bool is_autosave_file(const std::string& filename) {
return StringUtil::has_suffix(filename, "~");
}
static std::string get_levelname_from_autosave(const std::string& filename) {
return is_autosave_file(filename) ? filename.substr(0, filename.size() - 1) : filename;
}
static std::string get_autosave_from_levelname(const std::string& filename) {
return is_autosave_file(filename) ? filename : filename + "~";
}

public:
static bool s_resaving_in_progress;

Expand Down Expand Up @@ -93,6 +105,8 @@ class Editor final : public Screen,

bool is_testing_level() const { return m_leveltested; }

void remove_autosave_file();

/** Checks whether the level can be saved and does not contain
obvious issues (currently: check if main sector and a spawn point
named "main" is present) */
Expand Down Expand Up @@ -145,7 +159,7 @@ class Editor final : public Screen,
std::unique_ptr<World> m_world;

std::string m_levelfile;
std::string m_test_levelfile;
std::string m_autosave_levelfile;

public:
bool m_quit_request;
Expand Down Expand Up @@ -179,6 +193,8 @@ class Editor final : public Screen,
bool m_ignore_sector_change;

bool m_level_first_loaded;

float m_time_since_last_save;

private:
Editor(const Editor&) = delete;
Expand Down
8 changes: 6 additions & 2 deletions src/gui/dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@
#include "video/video_system.hpp"
#include "video/viewport.hpp"

Dialog::Dialog(bool passive) :
Dialog::Dialog(bool passive, bool auto_clear_dialogs) :
m_text(),
m_buttons(),
m_selected_button(),
m_cancel_button(-1),
m_passive(passive),
m_clear_diags(auto_clear_dialogs),
m_text_size()
{
}
Expand Down Expand Up @@ -258,7 +259,10 @@ Dialog::on_button_click(int button) const
{
m_buttons[button].callback();
}
MenuManager::instance().set_dialog({});
if (m_clear_diags || button == m_cancel_button)
{
MenuManager::instance().set_dialog({});
}
}

/* EOF */
3 changes: 2 additions & 1 deletion src/gui/dialog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ class Dialog
int m_selected_button;
int m_cancel_button;
bool m_passive;
bool m_clear_diags;

Sizef m_text_size;

public:
Dialog(bool passive = false);
Dialog(bool passive = false, bool auto_clear_dialogs = true);
virtual ~Dialog();

void set_text(const std::string& text);
Expand Down
7 changes: 6 additions & 1 deletion src/supertux/gameconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Config::Config() :
enable_discord(false),
discord_hide_editor(false),
#endif
editor_autosave_frequency(5),
repository_url()
{
}
Expand Down Expand Up @@ -93,6 +94,8 @@ Config::load()
#endif
}

config_mapping.get("editor_autosave_frequency", editor_autosave_frequency);

EditorOverlayWidget::autotile_help = !developer_mode;

if (is_christmas()) {
Expand Down Expand Up @@ -209,7 +212,9 @@ Config::save()
#endif
}
writer.end_list("integrations");


writer.write("editor_autosave_frequency", editor_autosave_frequency);

if (is_christmas()) {
writer.write("christmas", christmas_mode);
}
Expand Down
2 changes: 2 additions & 0 deletions src/supertux/gameconfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class Config final
bool discord_hide_editor;
#endif

int editor_autosave_frequency;

std::string repository_url;

bool is_christmas() const {
Expand Down
6 changes: 5 additions & 1 deletion src/supertux/level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include <physfs.h>
#include <numeric>

#include <boost/algorithm/string/predicate.hpp>

Level* Level::s_current = nullptr;

Level::Level(bool worldmap) :
Expand Down Expand Up @@ -89,7 +91,9 @@ Level::save(const std::string& filepath, bool retry)

Writer writer(filepath);
save(writer);
log_warning << "Level saved as " << filepath << "." << std::endl;
log_warning << "Level saved as " << filepath << "."
<< (boost::algorithm::ends_with(filepath, "~") ? " [Autosave]" : "")
<< std::endl;
} catch(std::exception& e) {
if (retry) {
std::stringstream msg;
Expand Down
32 changes: 30 additions & 2 deletions src/supertux/menu/editor_level_select_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,44 @@ EditorLevelSelectMenu::create_item(bool worldmap)
}
}

void
EditorLevelSelectMenu::open_level(const std::string& filename)
{
auto editor = Editor::current();
editor->set_level(filename);
MenuManager::instance().clear_menu_stack();
}

void
EditorLevelSelectMenu::menu_action(MenuItem& item)
{
auto editor = Editor::current();
World* world = editor->get_world();
if (item.get_id() >= 0)
{
editor->set_level(m_levelset->get_level_filename(item.get_id()));

MenuManager::instance().clear_menu_stack();
std::string file_name = m_levelset->get_level_filename(item.get_id());
std::string file_name_full = FileSystem::join(editor->get_level_directory(), file_name);

if (PHYSFS_exists((file_name_full + "~").c_str())) {
auto dialog = std::make_unique<Dialog>(/* passive = */ false, /* auto_clear_dialogs = */ false);
dialog->set_text(_("An auto-save recovery file was found. Would you like to restore the recovery\nfile and resume where you were before the editor crashed?"));
dialog->clear_buttons();
dialog->add_default_button(_("Yes"), [this, file_name] {
open_level(file_name + "~");
MenuManager::instance().set_dialog({});
});
dialog->add_button(_("No"), [this, file_name] {
Dialog::show_confirmation(_("This will delete the auto-save file. Are you sure?"), [this, file_name] {
open_level(file_name);
});
});
dialog->add_cancel_button(_("Cancel"));
MenuManager::instance().set_dialog(std::move(dialog));
} else {
open_level(file_name);
}

} else {
switch (item.get_id()) {
case -1:
Expand Down
2 changes: 2 additions & 0 deletions src/supertux/menu/editor_level_select_menu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class EditorLevelSelectMenu final : public Menu

void menu_action(MenuItem& item) override;

void open_level(const std::string& filename);

private:
void initialize();
void create_level();
Expand Down
2 changes: 2 additions & 0 deletions src/supertux/menu/editor_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "gui/menu_manager.hpp"
#include "supertux/level.hpp"
#include "supertux/gameconfig.hpp"
#include "supertux/globals.hpp"
#include "supertux/menu/menu_storage.hpp"
#include "util/gettext.hpp"
#include "video/compositor.hpp"
Expand Down Expand Up @@ -68,6 +69,7 @@ EditorMenu::EditorMenu()
add_toggle(-1, _("Render Light"), &Compositor::s_render_lighting);
add_toggle(-1, _("Autotile Mode"), &EditorOverlayWidget::autotile_mode);
add_toggle(-1, _("Enable Autotile Help"), &EditorOverlayWidget::autotile_help);
add_intfield(_("Autosave Frequency"), &(g_config->editor_autosave_frequency));

add_submenu(worldmap ? _("Worldmap Settings") : _("Level Settings"),
MenuStorage::EDITOR_LEVEL_MENU);
Expand Down

0 comments on commit 44adc43

Please sign in to comment.