infinipaint/src/World.cpp
copilot 1c1d63f376
Some checks are pending
Windows Release / windows-x86_64 (push) Waiting to run
Ship 0.8 fog/freistellen and fix Windows CI for friends
Publish the portable zip to public Forgejo from the private GitHub
builder, and force Ninja so MSVC 195 deps build without CMake 4.2.
2026-10-25 18:07:47 +01:00

728 lines
29 KiB
C++

/*
* InfiniPaint
* Copyright (C) 2025-2026 Yousef Khadadeh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "World.hpp"
#include <Helpers/HsvRgb.hpp>
#include <Helpers/MathExtras.hpp>
#include <Helpers/Networking/ByteStream.hpp>
#include <Helpers/NetworkingObjects/NetObjManager.hpp>
#include <Helpers/NetworkingObjects/NetObjManagerTypeList.hpp>
#include <Helpers/VersionNumber.hpp>
#include <Helpers/NetworkingObjects/NetObjGenericSerializedClass.hpp>
#include <Helpers/NetworkingObjects/DelayUpdateSerializedClassManager.hpp>
#include <cereal/types/unordered_map.hpp>
#include "DrawingProgram/Layers/DrawingProgramLayerListItem.hpp"
#include "DrawingProgram/Layers/FogStripper.hpp"
#include "FingerInputTracker.hpp"
#include "Helpers/NetworkingObjects/NetObjOrderedList.hpp"
#include "Helpers/NetworkingObjects/NetObjTemporaryPtr.decl.hpp"
#include "Helpers/NetworkingObjects/NetObjUnorderedSet.hpp"
#include "CommandList.hpp"
#include "Helpers/StringHelpers.hpp"
#include "MainProgram.hpp"
#include "SharedTypes.hpp"
#include "Toolbar.hpp"
#include "VersionConstants.hpp"
#include "WorldGrid.hpp"
#include <cereal/archives/portable_binary.hpp>
#include <chrono>
#include <include/core/SkFontMetrics.h>
#include <Helpers/Networking/NetLibrary.hpp>
#include <Helpers/Logger.hpp>
#include <cereal/types/vector.hpp>
#include <Helpers/Networking/NetLibrary.hpp>
#include <zstd.h>
#include "CanvasComponents/CanvasComponent.hpp"
#include "CanvasComponents/CanvasComponentContainer.hpp"
#include "CanvasComponents/CanvasComponentAllocator.hpp"
#include "WorldScreenshot.hpp"
#include "FileHelpers.hpp"
#ifdef __EMSCRIPTEN__
#include <EmscriptenHelpers/emscripten_browser_file.h>
#endif
World::World(MainProgram& initMain, const CustomEvents::OpenInfiniPaintFileEvent& worldInfo):
netObjMan(!worldInfo.isClient),
main(initMain),
undo(*this),
rMan(*this),
drawProg(*this),
bMan(*this),
gridMan(*this),
canvasTheme(*this)
{
saveThumbnail = worldInfo.saveThumbnail;
init_net_obj_type_list();
netObjMan.set_netobj_destroy_callback([&undo = undo](const NetworkingObjects::NetObjID& idToDestroy) {
undo.remove_by_netid(idToDestroy);
});
netObjMan.set_netid_reassign_callback([&undo = undo](const NetworkingObjects::NetObjID& oldID, const NetworkingObjects::NetObjID& newID) {
undo.reassign_netid(oldID, newID);
});
netSource = worldInfo.netSource;
drawData.rMan = &rMan;
drawData.main = &main;
drawData.cam.set_viewing_area(main.window.size.cast<float>());
if(worldInfo.isClient)
init_client(worldInfo.netSource);
else {
init_client_data_list();
set_name(name);
if(worldInfo.filePathSource.has_value())
load_from_file(worldInfo.filePathSource.value(), worldInfo.fileDataBuffer);
else
load_empty_canvas(worldInfo.filePathEmptyAutoSaveDir);
#ifdef ENABLE_ORDERED_LIST_TEST
listDebugTest = netObjMan.make_obj<NetworkingObjects::NetObjOrderedList<uint16_t>>();
#endif
}
}
void World::init_client_data_list() {
clients = netObjMan.make_obj<NetworkingObjects::NetObjUnorderedSet<ClientData>>();
serverClientData = ownClientData = clients->emplace_direct(clients, ClientData::InitStruct{
.cursorColor = get_random_cursor_color(),
.displayName = main.conf.displayName
});
init_client_data_list_callbacks();
}
Vector3f World::get_random_cursor_color() {
Vector3f hsv;
hsv[0] = Random::get().real_range(0.0f, 360.0f);
hsv[1] = Random::get().real_range(0.3f, 0.7f);
hsv[2] = 1.0;
return hsv_to_rgb<Vector3f>(hsv);
}
void World::init_client_data_list_callbacks() {
clients->set_insert_callback([&](const NetworkingObjects::NetObjOwnerPtr<ClientData>& objPtr) {
add_chat_message(objPtr->get_display_name(), "joined", Toolbar::ChatMessage::Type::JOIN);
Logger::get().log(Logger::LogType::PHONE_USERINFO, objPtr->get_display_name() + " joined");
});
clients->set_erase_callback([&](const NetworkingObjects::NetObjOwnerPtr<ClientData>& objPtr) {
add_chat_message(objPtr->get_display_name(), "left", Toolbar::ChatMessage::Type::JOIN);
Logger::get().log(Logger::LogType::PHONE_USERINFO, objPtr->get_display_name() + " left");
});
}
void World::init_net_obj_type_list() {
BookmarkListItem::register_class(*this);
WorldGrid::register_class(*this);
NetworkingObjects::register_ordered_list_class<WorldGrid>(netObjMan);
CanvasComponentAllocator::register_class(*this);
CanvasComponentContainer::register_class(*this);
NetworkingObjects::register_ordered_list_class<CanvasComponentContainer>(netObjMan);
DrawingProgramLayerListItem::register_class(*this);
NetworkingObjects::register_ordered_list_class<DrawingProgramLayerListItem>(netObjMan);
canvasTheme.register_class();
ClientData::register_class(*this);
NetworkingObjects::register_unordered_set_class<ClientData>(netObjMan);
#ifdef ENABLE_ORDERED_LIST_TEST
NetworkingObjects::register_generic_serialized_class<uint16_t>(netObjMan);
NetworkingObjects::register_ordered_list_class<uint16_t>(netObjMan);
#endif
}
void World::init_client(const std::string& serverFullID) {
main.init_net_library();
clientStillConnecting = true;
netClient = std::make_shared<NetClient>(serverFullID);
lastKeepAliveSent = std::chrono::steady_clock::now();
NetLibrary::register_client(netClient);
netObjMan.set_client(netClient, SERVER_UPDATE_NETWORK_OBJECT);
set_name("");
rMan.init_client_callbacks();
drawProg.init_client_callbacks();
netClient->add_recv_callback(CLIENT_INITIAL_DATA, [&](cereal::PortableBinaryInputArchive& message) {
std::string fileDisplayName;
NetworkingObjects::NetObjID clientDataObjID, serverDataObjID;
message(fileDisplayName, clientDataObjID, serverDataObjID);
set_name(fileDisplayName);
clients = netObjMan.read_create_message<NetworkingObjects::NetObjUnorderedSet<ClientData>>(message, nullptr);
ownClientData = netObjMan.get_obj_temporary_ref_from_id<ClientData>(clientDataObjID);
serverClientData = netObjMan.get_obj_temporary_ref_from_id<ClientData>(serverDataObjID);
bMan.read_create_message(message);
gridMan.read_create_message(message);
drawProg.read_components_client(message);
canvasTheme.read_create_message(message);
init_client_data_list_callbacks();
drawData.cam.smooth_move_to(*main.world, ownClientData->get_cam_coords(), ownClientData->get_window_size(), true);
#ifdef ENABLE_ORDERED_LIST_TEST
listDebugTest = netObjMan.read_create_message<NetworkingObjects::NetObjOrderedList<uint16_t>>(message, nullptr);
#endif
clientStillConnecting = false;
set_to_layout_gui_if_focus();
});
netClient->add_recv_callback(CLIENT_UPDATE_NETWORK_OBJECT, [&](cereal::PortableBinaryInputArchive& message) {
netObjMan.read_update_message(message, nullptr);
});
netClient->add_recv_callback(CLIENT_KEEP_ALIVE, [&](cereal::PortableBinaryInputArchive& message) {
});
netClient->send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_INITIAL_DATA, main.conf.displayName);
}
void World::send_reliable_multi_command_to_all(const std::function<void()>& captureSendBlock) {
if(netServer)
netServer->send_multi_command_to_all_clients(RELIABLE_COMMAND_CHANNEL, captureSendBlock);
else if(netClient)
netClient->send_multi_command_to_server(RELIABLE_COMMAND_CHANNEL, captureSendBlock);
else
captureSendBlock();
}
void World::focus_update() {
if(!clientStillConnecting) {
delayedUpdateObjectManager.update(netObjMan);
constexpr float SECONDS_TO_SEND_CAMERA_DATA = 0.5f;
timeToSendCameraData.update_time_since();
if(timeToSendCameraData.get_time_since() > SECONDS_TO_SEND_CAMERA_DATA) {
timeToSendCameraData.update_time_point();
ownClientData->set_window_size(ownClientData, main.window.size.cast<float>().eval());
ownClientData->set_camera_coords(ownClientData, drawData.cam.c);
}
ownClientData->set_cursor_pos(ownClientData, main.input.mouse.pos);
drawProg.update();
#ifdef ENABLE_ORDERED_LIST_TEST
list_debug_test_update();
#endif
}
drawData.cam.update_main(*this);
rMan.update();
}
bool World::connection_update() {
if(netServer) {
if(netServer->is_disconnected()) {
Logger::get().log(Logger::LogType::USERINFO, "Host connection failed");
netSource.clear();
netServer = nullptr;
netObjMan.disconnect();
}
else {
netServer->update();
if(std::chrono::steady_clock::now() - lastKeepAliveSent > std::chrono::seconds(2)) {
netServer->send_items_to_all_clients(UNRELIABLE_COMMAND_CHANNEL, CLIENT_KEEP_ALIVE);
lastKeepAliveSent = std::chrono::steady_clock::now();
}
}
}
else if(netClient) {
if(netClient->is_disconnected()) {
Logger::get().log(Logger::LogType::USERINFO, "Client connection failed");
netObjMan.disconnect();
main.set_tab_to_close(this);
netClient = nullptr;
return false;
}
netClient->update();
if(std::chrono::steady_clock::now() - lastKeepAliveSent > std::chrono::seconds(2)) {
netClient->send_items_to_server(UNRELIABLE_COMMAND_CHANNEL, SERVER_KEEP_ALIVE);
lastKeepAliveSent = std::chrono::steady_clock::now();
}
}
return true;
}
bool World::is_focus() {
return main.world.get() == this;
}
void World::set_to_layout_gui_if_focus() {
if(is_focus())
main.g.gui.set_to_layout();
}
void World::undo_with_checks() {
if(!clientStillConnecting && !drawProg.prevent_undo_or_redo())
undo.undo();
}
void World::redo_with_checks() {
if(!clientStillConnecting && !drawProg.prevent_undo_or_redo())
undo.redo();
}
void World::update() {
connection_update();
}
void World::on_tab_out() {
rMan.clear_display_cache();
if(!clientStillConnecting)
drawProg.on_tab_out();
}
void World::input_add_file_to_canvas_callback(const CustomEvents::AddFileToCanvasEvent& addFile) {
if(!clientStillConnecting)
drawProg.input_add_file_to_canvas_callback(addFile);
}
void World::input_paste_callback(const CustomEvents::PasteEvent& paste) {
if(!clientStillConnecting)
drawProg.input_paste_callback(paste);
}
void World::input_android_text_box_input_callback(const CustomEvents::AndroidTextBoxInputEvent& textboxInput) {
if(!clientStillConnecting)
drawProg.input_android_text_box_input_callback(textboxInput);
}
void World::input_text_key_callback(const InputManager::KeyCallbackArgs& key) {
if(!clientStillConnecting)
drawProg.input_text_key_callback(key);
}
void World::input_text_callback(const InputManager::TextCallbackArgs& text) {
if(!clientStillConnecting)
drawProg.input_text_callback(text);
}
void World::input_drop_file_callback(const InputManager::DropCallbackArgs& drop) {
if(!clientStillConnecting)
drawProg.input_drop_file_callback(drop);
}
void World::input_drop_text_callback(const InputManager::DropCallbackArgs& drop) {
if(!clientStillConnecting)
drawProg.input_drop_text_callback(drop);
}
void World::input_key_callback(const InputManager::KeyCallbackArgs& key) {
if(!clientStillConnecting) {
switch(key.key) {
case InputManager::KEY_REDO: {
if(key.down)
redo_with_checks();
break;
}
case InputManager::KEY_UNDO: {
if(key.down)
undo_with_checks();
break;
}
}
drawProg.input_key_callback(key);
drawData.cam.input_key_callback(key);
}
}
void World::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) {
if(!clientStillConnecting) {
drawProg.input_mouse_button_callback(button);
drawData.cam.input_mouse_button_on_canvas_callback(*this, button);
}
}
void World::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) {
if(!clientStillConnecting) {
drawProg.input_mouse_motion_callback(motion);
drawData.cam.input_mouse_motion_callback(*this, motion);
}
}
void World::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel) {
if(!clientStillConnecting)
drawData.cam.input_mouse_wheel_callback(*this, wheel);
}
void World::input_pen_button_callback(const InputManager::PenButtonCallbackArgs& button) {
if(!clientStillConnecting)
drawProg.input_pen_button_callback(button);
}
void World::input_pen_touch_callback(const InputManager::PenTouchCallbackArgs& touch) {
if(!clientStillConnecting)
drawProg.input_pen_touch_callback(touch);
}
void World::input_pen_motion_callback(const InputManager::PenMotionCallbackArgs& motion) {
if(!clientStillConnecting)
drawProg.input_pen_motion_callback(motion);
}
void World::input_pen_axis_callback(const InputManager::PenAxisCallbackArgs& axis) {
if(!clientStillConnecting)
drawProg.input_pen_axis_callback(axis);
}
void World::input_finger_touch_callback(const FingerInput::TouchCallbackArgs& touch) {
if(!clientStillConnecting)
drawData.cam.input_finger_touch_callback(*this, touch);
}
std::optional<InputManager::TextBoxStartInfo> World::get_text_box_start_info() {
if(!clientStillConnecting)
return drawProg.get_text_box_start_info();
return std::nullopt;
}
void World::send_chat_message(const std::string& message) {
if(!clientStillConnecting)
ownClientData->send_chat_message(ownClientData, *this, message);
}
void World::add_chat_message(const std::string& name, const std::string& message, Toolbar::ChatMessage::Type type) {
Logger::get().log(Logger::LogType::CHAT, type == Toolbar::ChatMessage::JOIN ? (name + " " + message) : ("[" + name + "] " + message));
chatMessages.emplace_front(Toolbar::ChatMessage{name, message, type});
if(chatMessages.size() == CHAT_SIZE)
chatMessages.pop_back();
set_to_layout_gui_if_focus();
}
void World::early_destroy() {
//con.early_destroy();
}
void World::set_name(const std::string& n) {
if(n.empty())
name = "New File";
else
name = n;
}
void World::ensure_display_name_unique(std::string& displayName) {
std::vector<std::string> strList;
for(auto& client : clients->get_data())
strList.emplace_back(client->get_display_name());
displayName = ensure_string_unique(strList, displayName);
}
void World::start_hosting(const std::string& initNetSource, const std::string& serverLocalID) {
main.init_net_library();
netServer = std::make_shared<NetServer>(serverLocalID);
lastKeepAliveSent = std::chrono::steady_clock::now();
NetLibrary::register_server(netServer);
netObjMan.set_server(netServer, CLIENT_UPDATE_NETWORK_OBJECT);
netSource = initNetSource;
rMan.init_server_callbacks();
drawProg.init_server_callbacks();
netServer->add_recv_callback(SERVER_INITIAL_DATA, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
ClientData::InitStruct newClientData;
newClientData.cursorColor = get_random_cursor_color();
message(newClientData.displayName);
ensure_display_name_unique(newClientData.displayName);
newClientData.camCoords = ownClientData->get_cam_coords();
newClientData.windowSize = ownClientData->get_window_size();
newClientData.gridSize = ownClientData->get_grid_size();
NetworkingObjects::NetObjTemporaryPtr<ClientData> clientDataObjPtr = clients->emplace_direct(clients, newClientData);
client->customID = clientDataObjPtr.get_net_id().data;
auto ss(std::make_shared<std::stringstream>());
{
cereal::PortableBinaryOutputArchive a(*ss);
a(CLIENT_INITIAL_DATA, name, clientDataObjPtr.get_net_id(), serverClientData.get_net_id());
clients.write_create_message(a);
bMan.bookmarkListRoot.write_create_message(a);
gridMan.grids.write_create_message(a);
drawProg.write_components_server(a);
canvasTheme.write_create_message(a);
#ifdef ENABLE_ORDERED_LIST_TEST
listDebugTest.write_create_message(a);
#endif
}
netServer->send_string_stream_to_client(client, RELIABLE_COMMAND_CHANNEL, ss);
auto visibleResources = FogStripper::compute_visible_resource_ids(drawProg.layerMan, drawProg.layerMan.clientHiddenComponentIds);
for(auto& r : rMan.resource_list()) {
if(!visibleResources.contains(r.get_net_id()))
continue;
netServer->send_items_to_client(client, RESOURCE_COMMAND_CHANNEL, CLIENT_NEW_RESOURCE_ID, r.get_net_id());
netServer->send_items_to_client(client, RESOURCE_COMMAND_CHANNEL, CLIENT_NEW_RESOURCE_DATA, *r);
}
});
netServer->add_recv_callback(SERVER_UPDATE_NETWORK_OBJECT, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
currentNetUpdateClient = client;
netObjMan.read_update_message(message, client);
currentNetUpdateClient = nullptr;
drawProg.layerMan.flush_deferred_rejects();
});
netServer->add_recv_callback(SERVER_KEEP_ALIVE, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
});
netServer->add_disconnect_callback([&](std::shared_ptr<NetServer::ClientData> client) {
NetworkingObjects::NetObjID idToErase;
idToErase.data = client->customID;
clients->erase(clients, idToErase);
});
}
void World::autosave_to_directory(const std::filesystem::path& directoryToSaveAt) {
std::vector<std::string> strList;
try {
strList = glob_path_as_string_list(directoryToSaveAt, ("*" + World::DOT_FILE_EXTENSION).c_str(), 0, [&](const auto& p){ return p.stem().string();});
}
catch(...) {
}
std::string nameToSaveUnder = ensure_string_unique(strList, name);
save_to_file(directoryToSaveAt / std::filesystem::path(nameToSaveUnder + "." + FILE_EXTENSION));
}
void World::save_to_file(const std::filesystem::path& filePathToSaveAt) {
try {
if(main.conf.forceExtensionOnPath)
filePath = force_extension_on_path(filePathToSaveAt, FILE_EXTENSION);
else
filePath = filePathToSaveAt;
std::stringstream f;
f.write(VersionConstants::CURRENT_SAVEFILE_HEADER.c_str(), VersionConstants::SAVEFILE_HEADER_LEN);
{
std::stringstream fWorldDataToCompress;
{
cereal::PortableBinaryOutputArchive a(fWorldDataToCompress);
save_file(a);
}
std::vector<char> compressedData(ZSTD_compressBound(fWorldDataToCompress.view().size()));
size_t trueCompressedSize = ZSTD_compress(compressedData.data(), compressedData.size(), fWorldDataToCompress.view().data(), fWorldDataToCompress.view().size(), ZSTD_CLEVEL_DEFAULT);
std::string_view compressedF(compressedData.data(), trueCompressedSize);
f << compressedF;
}
set_name(filePath.stem().string());
#ifdef __EMSCRIPTEN__
emscripten_browser_file::download(
filePath.string(),
"application/octet-stream",
f.view()
);
#else
if(!SDL_SaveFile(filePath.string().c_str(), f.view().data(), f.view().size()))
throw std::runtime_error("SDL_SaveFile failed with error: " + std::string(SDL_GetError()));
else if(saveThumbnail) {
Vector2f imageCenter{main.window.size.x() * 0.5f, main.window.size.y() * 0.5f};
float imageDim = std::max(main.window.size.x(), main.window.size.y());
Vector2f imageDimVec{imageDim * 0.5f, imageDim * 0.5f};
SCollision::AABB<float> imageBounds{imageCenter - imageDimVec, imageCenter + imageDimVec};
world_take_screenshot(main.world, {
.filePath = filePath.parent_path() / (filePath.stem().string() + ".jpg"),
.type = WorldScreenshotInfo::ScreenshotType::JPG,
.imageSizePixels = {512, 512},
.cameraCoords = drawData.cam.c,
.imageBounds = imageBounds,
.transparentBackground = false,
.displayGrid = false
});
}
#endif
Logger::get().log(Logger::LogType::DESKTOP_USERINFO, "File saved");
undo.set_save_action();
}
catch(const std::exception& e) {
Logger::get().log(Logger::LogType::WORLDFATAL, std::string("Save error: ") + e.what());
}
}
void World::load_empty_canvas(const std::optional<std::filesystem::path>& filePathEmptyAutoSaveDir) {
gridMan.server_init_no_file();
bMan.server_init_no_file();
BookmarkListItem* originBookmark = new BookmarkListItem(netObjMan, "Origin", false, {
.coords = drawData.cam.c,
.windowSize = main.window.size
});
bMan.bookmarkListRoot->get_folder_list()->push_back_and_send_create(bMan.bookmarkListRoot->get_folder_list(), originBookmark);
drawProg.server_init_no_file();
canvasTheme.server_init_no_file();
if(filePathEmptyAutoSaveDir.has_value()) {
bool oldSaveThumbnail = true;
saveThumbnail = false;
autosave_to_directory(filePathEmptyAutoSaveDir.value());
saveThumbnail = oldSaveThumbnail;
}
}
void World::load_from_file(const std::filesystem::path& filePathToLoadFrom, std::string_view buffer) {
filePath = filePathToLoadFrom;
std::string byteDataFromFile;
if(buffer.empty()) {
byteDataFromFile = read_file_to_string(filePath);
buffer = byteDataFromFile;
}
if(buffer.size() < VersionConstants::SAVEFILE_HEADER_LEN)
throw std::runtime_error("[World::load_from_file] File is not an InfiniPaint canvas (File too small)");
std::string_view fileHeader = buffer.substr(0, VersionConstants::SAVEFILE_HEADER_LEN);
VersionNumber fileVersion = VersionConstants::header_to_version_number(std::string(fileHeader));
std::string_view uncompressedDataView;
std::vector<char> uncompressedDataVector;
if(fileVersion < VersionNumber(0, 1, 0))
uncompressedDataView = std::string_view(buffer.data() + VersionConstants::SAVEFILE_HEADER_LEN, buffer.size() - VersionConstants::SAVEFILE_HEADER_LEN);
else {
uncompressedDataVector.resize(ZSTD_getFrameContentSize(buffer.data() + VersionConstants::SAVEFILE_HEADER_LEN, buffer.size() - VersionConstants::SAVEFILE_HEADER_LEN));
size_t trueUncompressedSize = ZSTD_decompress(uncompressedDataVector.data(), uncompressedDataVector.size(), buffer.data() + VersionConstants::SAVEFILE_HEADER_LEN, buffer.size() - VersionConstants::SAVEFILE_HEADER_LEN);
uncompressedDataView = std::string_view(uncompressedDataVector.data(), trueUncompressedSize);
}
ByteMemStream f((char*)uncompressedDataView.data(), uncompressedDataView.size());
Logger::get().log(Logger::LogType::INFO, "Loading file from version " + version_numbers_to_version_str(fileVersion));
cereal::PortableBinaryInputArchive a(f);
load_file(a, fileVersion);
Logger::get().log(Logger::LogType::DESKTOP_USERINFO, "File loaded");
set_name(filePath.stem().string());
}
void World::save_file(cereal::PortableBinaryOutputArchive& a) const {
drawData.cam.save_file(a, *this);
canvasTheme.save_file(a);
drawProg.save_file(a);
bMan.save_file(a);
gridMan.save_file(a);
rMan.save_file(a);
}
void World::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
drawData.cam.load_file(a, version, *this);
canvasTheme.load_file(a, version);
drawProg.load_file(a, version);
bMan.load_file(a, version);
gridMan.load_file(a, version);
rMan.load_file(a, version);
}
WorldScalar World::calculate_zoom_from_uniform_zoom(WorldScalar uniformZoom, WorldVec oldWindowSize) {
WorldScalar a1(main.window.size.x() / static_cast<double>(oldWindowSize.x()));
WorldScalar a2(main.window.size.y() / static_cast<double>(oldWindowSize.y()));
return uniformZoom * ((a1 < a2) ? a1 : a2);
}
void World::draw_other_player_cursors(SkCanvas* canvas, const DrawData& drawData) {
if(clients) {
for(auto& clientDataPtr : clients->get_data()) {
if(clientDataPtr != ownClientData)
clientDataPtr->draw_cursor(canvas, drawData);
}
}
}
void World::scale_up_step() {
if(ownClientData)
ownClientData->scale_up_step(ownClientData, *this);
}
void World::scale_up(const WorldScalar& scaleUpAmount) {
Logger::get().log(Logger::LogType::DESKTOP_USERINFO, "Canvas scaled up");
// drawProg will be sending info on committed objects/modified grids. These objects will NOT be scaled up.
// This means that the scale up message must be sent AFTER the World::scale_up function is called on our end
// if this client is the one responsible for the scale up
drawProg.scale_up(scaleUpAmount);
bMan.scale_up(scaleUpAmount);
gridMan.scale_up(scaleUpAmount);
drawData.cam.scale_up(*this, scaleUpAmount);
undo.scale_up(scaleUpAmount);
}
bool World::should_ask_before_closing() {
// If it's a client that didn't save to a file previously, we can ignore asking before quitting (probably don't intend to save anyway)
// If it's a server or a client, and the previous statement is false, we should always ask before quitting because the data could be modified by someone on the network
// If it's a local file, ask before quitting if we detect any local changes
// If on web version, don't ask before closing
#ifdef __EMSCRIPTEN__
return false;
#else
if(netClient || netServer)
return !(netClient && filePath.empty());
return hasUnsavedLocalChanges;
#endif
}
void World::set_has_unsaved_local_changes(bool newHasUnsavedLocalChangesVal) {
bool oldHasUnsavedLocalChanges = hasUnsavedLocalChanges;
hasUnsavedLocalChanges = newHasUnsavedLocalChangesVal;
if(oldHasUnsavedLocalChanges != hasUnsavedLocalChanges)
main.g.gui.set_to_layout();
}
#ifdef ENABLE_ORDERED_LIST_TEST
void World::list_debug_test_update() {
if(std::chrono::steady_clock::now() < (listDebugTestTimeStart + std::chrono::minutes(1))) {
if(nextSendTime < std::chrono::steady_clock::now() - std::chrono::milliseconds(300)) {
nextSendTime = std::chrono::steady_clock::now();
bool isInsert;
if(listDebugTest->size() > 70)
isInsert = false;
else if(listDebugTest->size() < 20)
isInsert = true;
else
isInsert = Random::get().real_range(0.0f, 1.0f) > 0.5f;
using namespace NetworkingObjects;
if(isInsert) {
std::vector<std::pair<NetObjOrderedListIterator<uint16_t>, NetObjOwnerPtr<uint16_t>>> toInsert;
std::vector<uint32_t> randomIndices;
uint32_t insertAmount = Random::get().int_range(5, 25);
for(uint32_t i = 0; i < insertAmount; i++)
randomIndices.emplace_back(Random::get().int_range<uint32_t>(0, listDebugTest->size() + 10));
randomIndices[1] = randomIndices[0];
std::sort(randomIndices.begin(), randomIndices.end());
for(uint32_t index : randomIndices)
toInsert.emplace_back(listDebugTest->at(index), netObjMan.make_obj_direct<uint16_t>(Random::get().int_range<uint32_t>(10, 100)));
listDebugTest->insert_ordered_list_and_send_create(listDebugTest, toInsert);
}
else {
std::vector<NetObjOrderedListIterator<uint16_t>> toErase;
for(uint32_t i = 0; i < listDebugTest->size(); i++) {
if(Random::get().real_range(0.0f, 1.0f) > 0.8f)
toErase.emplace_back(listDebugTest->at(i));
}
listDebugTest->erase_list(listDebugTest, toErase);
}
}
}
if(netServer && netServer->get_client_list().empty())
listDebugTestTimeStart = std::chrono::steady_clock::now();
}
#endif
void World::draw(SkCanvas* canvas, const DrawData& calledDrawData) {
if(!clientStillConnecting) {
if(calledDrawData.drawGrids)
gridMan.draw_back(canvas, calledDrawData);
drawProg.draw(canvas, calledDrawData);
if(calledDrawData.drawGrids) {
gridMan.draw_front(canvas, calledDrawData);
gridMan.draw_coordinates(canvas, calledDrawData);
}
if(!calledDrawData.takingScreenshot)
draw_other_player_cursors(canvas, calledDrawData);
}
}