2025-07-06 20:43:53 +03:00
|
|
|
#include "World.hpp"
|
2025-12-10 17:23:05 +03:00
|
|
|
#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>
|
2025-12-16 12:02:08 +03:00
|
|
|
#include <cereal/types/unordered_map.hpp>
|
2025-12-25 17:18:28 +03:00
|
|
|
#include "DrawingProgram/Layers/DrawingProgramLayerListItem.hpp"
|
2025-12-17 14:46:27 +03:00
|
|
|
#include "Helpers/NetworkingObjects/NetObjTemporaryPtr.decl.hpp"
|
|
|
|
|
#include "Helpers/NetworkingObjects/NetObjUnorderedSet.hpp"
|
|
|
|
|
#include "CommandList.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
#include "MainProgram.hpp"
|
2025-09-24 16:18:20 +03:00
|
|
|
#include "SharedTypes.hpp"
|
2025-10-16 09:38:58 +03:00
|
|
|
#include "Toolbar.hpp"
|
2025-11-16 16:39:12 +03:00
|
|
|
#include "VersionConstants.hpp"
|
2025-09-12 09:54:49 +03:00
|
|
|
#include "WorldGrid.hpp"
|
2025-12-14 18:57:40 +03:00
|
|
|
#include <cereal/archives/portable_binary.hpp>
|
2025-07-06 20:43:53 +03:00
|
|
|
#include <fstream>
|
|
|
|
|
#include "FileHelpers.hpp"
|
|
|
|
|
#include "FontData.hpp"
|
|
|
|
|
#include <include/core/SkFontMetrics.h>
|
|
|
|
|
#include <Helpers/Networking/NetLibrary.hpp>
|
|
|
|
|
#include <Helpers/Logger.hpp>
|
|
|
|
|
#include <cereal/types/vector.hpp>
|
|
|
|
|
#include <Helpers/Networking/NetLibrary.hpp>
|
2025-07-28 18:21:09 +03:00
|
|
|
#include <zstd.h>
|
2025-12-15 16:49:51 +03:00
|
|
|
#include "CanvasComponents/CanvasComponent.hpp"
|
2025-12-14 18:57:40 +03:00
|
|
|
#include "CanvasComponents/CanvasComponentContainer.hpp"
|
|
|
|
|
#include "CanvasComponents/CanvasComponentAllocator.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
|
#include <EmscriptenHelpers/emscripten_browser_file.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
World::World(MainProgram& initMain, OpenWorldInfo& worldInfo):
|
2026-01-01 22:46:02 +03:00
|
|
|
netObjMan(!worldInfo.isClient),
|
2025-07-06 20:43:53 +03:00
|
|
|
main(initMain),
|
|
|
|
|
rMan(*this),
|
|
|
|
|
drawProg(*this),
|
2025-09-06 19:06:30 +03:00
|
|
|
bMan(*this),
|
2025-12-16 23:06:46 +03:00
|
|
|
gridMan(*this),
|
|
|
|
|
canvasTheme(*this)
|
2025-07-06 20:43:53 +03:00
|
|
|
{
|
2025-12-10 17:23:05 +03:00
|
|
|
init_net_obj_type_list();
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
netSource = worldInfo.netSource;
|
|
|
|
|
|
|
|
|
|
drawData.rMan = &rMan;
|
|
|
|
|
drawData.main = &main;
|
|
|
|
|
|
2026-01-01 22:46:02 +03:00
|
|
|
if(worldInfo.isClient)
|
|
|
|
|
init_client(worldInfo.netSource);
|
|
|
|
|
else {
|
|
|
|
|
init_client_data_list();
|
|
|
|
|
if(worldInfo.filePathSource.has_value())
|
|
|
|
|
load_from_file(worldInfo.filePathSource.value(), worldInfo.fileDataBuffer);
|
|
|
|
|
else
|
|
|
|
|
load_empty_canvas();
|
|
|
|
|
set_name(name);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-12-17 14:46:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::init_client_data_list() {
|
2026-01-01 22:46:02 +03:00
|
|
|
clients = netObjMan.make_obj<NetworkingObjects::NetObjUnorderedSet<ClientData>>();
|
|
|
|
|
ownClientData = clients->emplace_direct(clients, ClientData::InitStruct{
|
|
|
|
|
.cursorColor = get_random_cursor_color(),
|
|
|
|
|
.displayName = main.displayName
|
|
|
|
|
});
|
|
|
|
|
init_client_data_list_callbacks();
|
2025-12-17 14:46:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
clients->set_erase_callback([&](const NetworkingObjects::NetObjOwnerPtr<ClientData>& objPtr) {
|
|
|
|
|
add_chat_message(objPtr->get_display_name(), "left", Toolbar::ChatMessage::Type::JOIN);
|
|
|
|
|
});
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
2025-12-10 17:23:05 +03:00
|
|
|
void World::init_net_obj_type_list() {
|
2025-12-24 12:42:27 +03:00
|
|
|
BookmarkListItem::register_class(*this);
|
2025-12-31 16:30:56 +03:00
|
|
|
WorldGrid::register_class(*this);
|
2025-12-13 13:03:27 +03:00
|
|
|
NetworkingObjects::register_ordered_list_class<WorldGrid>(netObjMan);
|
2025-12-24 12:42:27 +03:00
|
|
|
CanvasComponentAllocator::register_class(*this);
|
2025-12-31 16:30:56 +03:00
|
|
|
CanvasComponentContainer::register_class(*this);
|
2025-12-15 16:49:51 +03:00
|
|
|
NetworkingObjects::register_ordered_list_class<CanvasComponentContainer>(netObjMan);
|
2025-12-25 17:18:28 +03:00
|
|
|
DrawingProgramLayerListItem::register_class(*this);
|
|
|
|
|
NetworkingObjects::register_ordered_list_class<DrawingProgramLayerListItem>(netObjMan);
|
2025-12-16 23:06:46 +03:00
|
|
|
canvasTheme.register_class();
|
2025-12-17 14:46:27 +03:00
|
|
|
ClientData::register_class(*this);
|
|
|
|
|
NetworkingObjects::register_unordered_set_class<ClientData>(netObjMan);
|
2025-12-16 23:06:46 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-01 22:46:02 +03:00
|
|
|
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, SERVER_UPDATE_MANY_NETWORK_OBJECTS);
|
|
|
|
|
set_name("");
|
2025-12-10 17:23:05 +03:00
|
|
|
|
2025-12-17 14:46:27 +03:00
|
|
|
rMan.init_client_callbacks();
|
2025-12-31 11:42:16 +03:00
|
|
|
drawProg.init_client_callbacks();
|
2026-01-01 22:46:02 +03:00
|
|
|
netClient->add_recv_callback(CLIENT_INITIAL_DATA, [&](cereal::PortableBinaryInputArchive& message) {
|
2025-12-17 14:46:27 +03:00
|
|
|
std::string fileDisplayName;
|
|
|
|
|
NetworkingObjects::NetObjID clientDataObjID;
|
|
|
|
|
message(fileDisplayName, clientDataObjID);
|
|
|
|
|
set_name(fileDisplayName);
|
2025-12-23 07:53:29 +03:00
|
|
|
bMan.bookmarkListRoot = netObjMan.read_create_message<BookmarkListItem>(message, nullptr);
|
2025-12-17 14:46:27 +03:00
|
|
|
gridMan.grids = netObjMan.read_create_message<NetworkingObjects::NetObjOrderedList<WorldGrid>>(message, nullptr);
|
|
|
|
|
drawProg.read_components_client(message);
|
|
|
|
|
canvasTheme.read_create_message(message);
|
|
|
|
|
clients = netObjMan.read_create_message<NetworkingObjects::NetObjUnorderedSet<ClientData>>(message, nullptr);
|
|
|
|
|
init_client_data_list_callbacks();
|
2025-12-17 15:50:14 +03:00
|
|
|
ownClientData = netObjMan.get_obj_temporary_ref_from_id<ClientData>(clientDataObjID);
|
2025-12-17 14:46:27 +03:00
|
|
|
drawData.cam.smooth_move_to(*main.world, ownClientData->get_cam_coords(), ownClientData->get_window_size(), true);
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
clientStillConnecting = false;
|
|
|
|
|
});
|
2026-01-01 22:46:02 +03:00
|
|
|
netClient->add_recv_callback(CLIENT_UPDATE_NETWORK_OBJECT, [&](cereal::PortableBinaryInputArchive& message) {
|
2025-12-17 14:46:27 +03:00
|
|
|
netObjMan.read_update_message(message, nullptr);
|
2025-09-30 16:36:09 +03:00
|
|
|
});
|
2026-01-01 22:46:02 +03:00
|
|
|
netClient->add_recv_callback(CLIENT_UPDATE_MANY_NETWORK_OBJECTS, [&](cereal::PortableBinaryInputArchive& message) {
|
2025-12-23 14:10:33 +03:00
|
|
|
netObjMan.read_many_update_message(message, nullptr);
|
|
|
|
|
});
|
2026-01-01 22:46:02 +03:00
|
|
|
netClient->add_recv_callback(CLIENT_KEEP_ALIVE, [&](cereal::PortableBinaryInputArchive& message) {
|
2025-07-06 20:43:53 +03:00
|
|
|
});
|
2026-01-01 22:46:02 +03:00
|
|
|
|
|
|
|
|
netClient->send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_INITIAL_DATA, main.displayName);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::focus_update() {
|
2026-01-01 22:46:02 +03:00
|
|
|
connection_update();
|
|
|
|
|
if(setToDestroy)
|
2025-12-09 17:20:27 +03:00
|
|
|
return;
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
if(!clientStillConnecting) {
|
2025-12-18 23:37:37 +03:00
|
|
|
delayedUpdateObjectManager.update(netObjMan);
|
2025-11-30 11:47:57 +03:00
|
|
|
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();
|
2025-12-17 14:46:27 +03:00
|
|
|
ownClientData->set_window_size(ownClientData, main.window.size.cast<float>().eval());
|
|
|
|
|
ownClientData->set_camera_coords(ownClientData, drawData.cam.c);
|
2025-11-30 11:47:57 +03:00
|
|
|
}
|
2025-12-17 14:46:27 +03:00
|
|
|
ownClientData->set_cursor_pos(ownClientData, main.input.mouse.pos);
|
2025-07-06 20:43:53 +03:00
|
|
|
drawProg.update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drawData.cam.update_main(*this);
|
|
|
|
|
|
|
|
|
|
rMan.update();
|
|
|
|
|
|
|
|
|
|
mousePreviousWorldVec = drawData.cam.c.from_space(main.input.mouse.pos);
|
|
|
|
|
mouseWorldMove = drawData.cam.c.from_space(main.input.mouse.move);
|
|
|
|
|
|
2025-07-27 20:08:46 +03:00
|
|
|
if(main.input.key(InputManager::KEY_UNDO).repeat)
|
|
|
|
|
undo_with_checks();
|
|
|
|
|
else if(main.input.key(InputManager::KEY_REDO).repeat)
|
|
|
|
|
redo_with_checks();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 22:46:02 +03:00
|
|
|
void World::connection_update() {
|
|
|
|
|
if(netServer) {
|
|
|
|
|
if(netServer->is_disconnected()) {
|
|
|
|
|
Logger::get().log("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("USERINFO", "Client connection failed");
|
|
|
|
|
setToDestroy = true;
|
|
|
|
|
netObjMan.disconnect();
|
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 20:08:46 +03:00
|
|
|
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();
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::unfocus_update() {
|
2026-01-01 22:46:02 +03:00
|
|
|
connection_update();
|
|
|
|
|
if(setToDestroy)
|
|
|
|
|
return;
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::send_chat_message(const std::string& message) {
|
2025-12-17 14:46:27 +03:00
|
|
|
if(!clientStillConnecting)
|
|
|
|
|
ownClientData->send_chat_message(ownClientData, *this, message);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
2025-10-16 09:38:58 +03:00
|
|
|
void World::add_chat_message(const std::string& name, const std::string& message, Toolbar::ChatMessage::Type type) {
|
|
|
|
|
Logger::get().log("CHAT", type == Toolbar::ChatMessage::JOIN ? (name + " " + message) : ("[" + name + "] " + message));
|
|
|
|
|
chatMessages.emplace_front(Toolbar::ChatMessage{name, message, type});
|
2025-07-06 20:43:53 +03:00
|
|
|
if(chatMessages.size() == CHAT_SIZE)
|
|
|
|
|
chatMessages.pop_back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WorldVec World::get_mouse_world_pos() {
|
|
|
|
|
return mousePreviousWorldVec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WorldVec World::get_mouse_world_move() {
|
|
|
|
|
return mouseWorldMove;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::early_destroy() {
|
|
|
|
|
//con.early_destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::set_name(const std::string& n) {
|
|
|
|
|
if(n.empty())
|
|
|
|
|
name = "New File";
|
|
|
|
|
else
|
|
|
|
|
name = n;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 14:46:27 +03:00
|
|
|
void World::ensure_display_name_unique(std::string& displayName) {
|
|
|
|
|
for(;;) {
|
|
|
|
|
bool isUnique = true;
|
|
|
|
|
for(auto& client : clients->get_data()) {
|
|
|
|
|
if(client->get_display_name() == displayName) {
|
|
|
|
|
size_t leftParenthesisIndex = displayName.find_last_of('(');
|
|
|
|
|
size_t rightParenthesisIndex = displayName.find_last_of(')');
|
|
|
|
|
bool failToIncrement = true;
|
|
|
|
|
if(leftParenthesisIndex != std::string::npos && rightParenthesisIndex != std::string::npos && leftParenthesisIndex < rightParenthesisIndex) {
|
|
|
|
|
std::string numStr = displayName.substr(leftParenthesisIndex + 1, rightParenthesisIndex - leftParenthesisIndex - 1);
|
|
|
|
|
bool isAllDigits = true;
|
|
|
|
|
for(char c : numStr) {
|
|
|
|
|
if(!isdigit(c)) {
|
|
|
|
|
isAllDigits = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(isAllDigits) {
|
|
|
|
|
try {
|
|
|
|
|
int s = std::stoi(numStr);
|
|
|
|
|
s++;
|
|
|
|
|
displayName = displayName.substr(0, leftParenthesisIndex);
|
|
|
|
|
displayName += "(" + std::to_string(s) + ")";
|
|
|
|
|
failToIncrement = false;
|
|
|
|
|
}
|
|
|
|
|
catch(...) {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(failToIncrement)
|
|
|
|
|
displayName += " (2)";
|
|
|
|
|
isUnique = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(isUnique)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
void World::start_hosting(const std::string& initNetSource, const std::string& serverLocalID) {
|
2025-10-27 20:19:47 +03:00
|
|
|
main.init_net_library();
|
2025-12-17 14:46:27 +03:00
|
|
|
netServer = std::make_shared<NetServer>(serverLocalID);
|
2026-01-01 22:46:02 +03:00
|
|
|
lastKeepAliveSent = std::chrono::steady_clock::now();
|
2025-12-17 14:46:27 +03:00
|
|
|
NetLibrary::register_server(netServer);
|
2025-12-23 14:10:33 +03:00
|
|
|
netObjMan.set_server(netServer, CLIENT_UPDATE_NETWORK_OBJECT, CLIENT_UPDATE_MANY_NETWORK_OBJECTS);
|
2025-07-06 20:43:53 +03:00
|
|
|
netSource = initNetSource;
|
2026-01-01 22:46:02 +03:00
|
|
|
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());
|
|
|
|
|
bMan.bookmarkListRoot.write_create_message(a);
|
|
|
|
|
gridMan.grids.write_create_message(a);
|
|
|
|
|
drawProg.write_components_server(a);
|
|
|
|
|
canvasTheme.write_create_message(a);
|
|
|
|
|
clients.write_create_message(a);
|
|
|
|
|
}
|
|
|
|
|
netServer->send_string_stream_to_client(client, RELIABLE_COMMAND_CHANNEL, ss);
|
|
|
|
|
for(auto& r : rMan.resource_list()) {
|
|
|
|
|
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) {
|
|
|
|
|
netObjMan.read_update_message(message, client);
|
|
|
|
|
});
|
|
|
|
|
netServer->add_recv_callback(SERVER_UPDATE_MANY_NETWORK_OBJECTS, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
netObjMan.read_many_update_message(message, client);
|
|
|
|
|
});
|
|
|
|
|
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);
|
|
|
|
|
});
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-21 08:35:47 +03:00
|
|
|
void World::save_to_file(const std::filesystem::path& filePathToSaveAt) {
|
2025-07-06 20:43:53 +03:00
|
|
|
try {
|
2025-11-21 08:35:47 +03:00
|
|
|
filePath = force_extension_on_path(filePathToSaveAt, FILE_EXTENSION);
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
std::stringstream f;
|
2025-11-16 16:39:12 +03:00
|
|
|
f.write(VersionConstants::CURRENT_SAVEFILE_HEADER.c_str(), VersionConstants::SAVEFILE_HEADER_LEN);
|
2025-07-28 18:21:09 +03:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
std::stringstream fWorldDataToCompress;
|
|
|
|
|
{
|
|
|
|
|
cereal::PortableBinaryOutputArchive a(fWorldDataToCompress);
|
2025-11-16 16:39:12 +03:00
|
|
|
save_file(a);
|
2025-07-28 18:21:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
set_name(filePath.stem().string());
|
|
|
|
|
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
|
emscripten_browser_file::download(
|
|
|
|
|
filePath.string(),
|
|
|
|
|
"application/octet-stream",
|
|
|
|
|
f.view()
|
|
|
|
|
);
|
2025-07-28 18:21:09 +03:00
|
|
|
#else
|
|
|
|
|
std::ofstream fi(filePath, std::ios::out | std::ios::binary);
|
|
|
|
|
fi << f.view();
|
|
|
|
|
fi.close();
|
2025-07-06 20:43:53 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
Logger::get().log("USERINFO", "File saved");
|
|
|
|
|
}
|
|
|
|
|
catch(const std::exception& e) {
|
|
|
|
|
Logger::get().log("WORLDFATAL", std::string("Save error: ") + e.what());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 22:46:02 +03:00
|
|
|
void World::load_empty_canvas() {
|
|
|
|
|
gridMan.server_init_no_file();
|
|
|
|
|
bMan.server_init_no_file();
|
|
|
|
|
drawProg.server_init_no_file();
|
|
|
|
|
canvasTheme.server_init_no_file();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-21 08:35:47 +03:00
|
|
|
void World::load_from_file(const std::filesystem::path& filePathToLoadFrom, std::string_view buffer) {
|
|
|
|
|
filePath = force_extension_on_path(filePathToLoadFrom, FILE_EXTENSION);
|
2025-07-06 20:43:53 +03:00
|
|
|
|
2025-07-28 18:21:09 +03:00
|
|
|
std::string byteDataFromFile;
|
2025-07-20 15:24:44 +03:00
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
if(buffer.empty()) {
|
2025-11-21 08:35:47 +03:00
|
|
|
byteDataFromFile = read_file_to_string(filePath);
|
2025-07-28 18:21:09 +03:00
|
|
|
buffer = byteDataFromFile;
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-07-28 18:21:09 +03:00
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
if(buffer.size() < VersionConstants::SAVEFILE_HEADER_LEN)
|
2025-09-24 16:18:20 +03:00
|
|
|
throw std::runtime_error("[World::load_from_file] File is not an InfiniPaint canvas (File too small)");
|
2025-07-28 18:21:09 +03:00
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
std::string_view fileHeader = buffer.substr(0, VersionConstants::SAVEFILE_HEADER_LEN);
|
|
|
|
|
VersionNumber fileVersion = VersionConstants::header_to_version_number(std::string(fileHeader));
|
2025-07-28 18:21:09 +03:00
|
|
|
std::string_view uncompressedDataView;
|
|
|
|
|
std::vector<char> uncompressedDataVector;
|
|
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
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);
|
2025-07-28 18:21:09 +03:00
|
|
|
uncompressedDataView = std::string_view(uncompressedDataVector.data(), trueUncompressedSize);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-07-28 18:21:09 +03:00
|
|
|
|
|
|
|
|
ByteMemStream f((char*)uncompressedDataView.data(), uncompressedDataView.size());
|
|
|
|
|
|
2025-11-17 16:08:42 +03:00
|
|
|
Logger::get().log("INFO", "Loading file from version " + version_numbers_to_version_str(fileVersion));
|
|
|
|
|
|
2025-07-28 18:21:09 +03:00
|
|
|
cereal::PortableBinaryInputArchive a(f);
|
2025-11-16 16:39:12 +03:00
|
|
|
load_file(a, fileVersion);
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
Logger::get().log("USERINFO", "File loaded");
|
|
|
|
|
set_name(filePath.stem().string());
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
void World::save_file(cereal::PortableBinaryOutputArchive& a) const {
|
2026-01-01 22:46:02 +03:00
|
|
|
drawData.cam.save_file(a, *this);
|
|
|
|
|
canvasTheme.save_file(a);
|
2025-11-16 16:39:12 +03:00
|
|
|
drawProg.save_file(a);
|
2026-01-01 22:46:02 +03:00
|
|
|
bMan.save_file(a);
|
|
|
|
|
gridMan.save_file(a);
|
2026-01-01 23:28:26 +03:00
|
|
|
rMan.save_file(a);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
void World::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
|
2026-01-01 22:46:02 +03:00
|
|
|
drawData.cam.load_file(a, version, *this);
|
|
|
|
|
canvasTheme.load_file(a, version);
|
2025-11-16 16:39:12 +03:00
|
|
|
drawProg.load_file(a, version);
|
2026-01-01 22:46:02 +03:00
|
|
|
bMan.load_file(a, version);
|
|
|
|
|
gridMan.load_file(a, version);
|
2026-01-01 23:28:26 +03:00
|
|
|
rMan.load_file(a, version);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 19:06:30 +03:00
|
|
|
void World::draw_other_player_cursors(SkCanvas* canvas, const DrawData& drawData) {
|
2025-12-17 14:46:27 +03:00
|
|
|
if(clients) {
|
|
|
|
|
for(auto& clientDataPtr : clients->get_data()) {
|
|
|
|
|
if(clientDataPtr != ownClientData)
|
|
|
|
|
clientDataPtr->draw_cursor(canvas, drawData);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-06 19:06:30 +03:00
|
|
|
|
2025-09-30 16:36:09 +03:00
|
|
|
void World::scale_up_step() {
|
2025-12-31 16:30:56 +03:00
|
|
|
if(ownClientData)
|
|
|
|
|
ownClientData->scale_up_step(ownClientData, *this);
|
2025-09-30 16:36:09 +03:00
|
|
|
}
|
|
|
|
|
|
2025-09-30 11:45:03 +03:00
|
|
|
void World::scale_up(const WorldScalar& scaleUpAmount) {
|
|
|
|
|
Logger::get().log("USERINFO", "Canvas scaled up");
|
|
|
|
|
bMan.scale_up(scaleUpAmount);
|
|
|
|
|
gridMan.scale_up(scaleUpAmount);
|
|
|
|
|
drawData.cam.scale_up(*this, scaleUpAmount);
|
2025-09-30 16:36:09 +03:00
|
|
|
// drawProg will be sending info on committed objects/modified grids. These objects will be scaled up already.
|
|
|
|
|
// This means that the scale up message must be send BEFORE 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);
|
|
|
|
|
|
|
|
|
|
undo.clear(); // Do last to make sure that any undo generated while scaling up is removed, as it contains outdated scale info
|
2025-09-30 11:45:03 +03:00
|
|
|
}
|
|
|
|
|
|
2025-09-06 19:06:30 +03:00
|
|
|
void World::draw(SkCanvas* canvas) {
|
|
|
|
|
drawData.refresh_draw_optimizing_values();
|
|
|
|
|
if(drawData.drawGrids)
|
2025-10-01 21:31:16 +03:00
|
|
|
gridMan.draw_back(canvas, drawData);
|
2025-09-06 19:06:30 +03:00
|
|
|
drawProg.draw(canvas, drawData);
|
2025-10-01 21:31:16 +03:00
|
|
|
if(drawData.drawGrids) {
|
|
|
|
|
gridMan.draw_front(canvas, drawData);
|
2025-09-25 10:21:47 +03:00
|
|
|
gridMan.draw_coordinates(canvas, drawData);
|
2025-10-01 21:31:16 +03:00
|
|
|
}
|
2025-09-06 19:06:30 +03:00
|
|
|
draw_other_player_cursors(canvas, drawData);
|
|
|
|
|
}
|