Grids can be saved in files and used online

This commit is contained in:
Yousef Khadadeh 2025-09-24 16:18:20 +03:00
parent 0c0d7d8439
commit 52de64907a
14 changed files with 122 additions and 42 deletions

View file

@ -67,6 +67,9 @@ namespace SCollision {
AABB<S> toRet{min.template cast<S>(), max.template cast<S>()};
return toRet;
}
template <class Archive> void serialize(Archive& a) {
a(min, max);
}
};
template <typename T> class Circle {
public:

View file

@ -22,9 +22,16 @@ class BookmarkManager {
void remove_bookmark(const std::string& name);
void jump_to_bookmark(const std::string& name);
const std::unordered_map<std::string, Bookmark>& bookmark_list();
template <typename Archive> void serialize(Archive& a) {
template <typename Archive> void save(Archive& a) const {
a(bookmarks);
}
template <typename Archive> void load(Archive& a) {
a(bookmarks);
changed = true;
}
const std::vector<std::string>& sorted_names();
private:
bool changed = true;

View file

@ -233,9 +233,9 @@ void DrawingProgram::tool_options_gui() {
}
void DrawingProgram::modify_grid(ServerClientID gridToModifyID) {
switch_to_tool(DrawingProgramToolType::GRIDMODIFY);
GridModifyTool* gridModToolPtr = static_cast<GridModifyTool*>(drawTool.get());
gridModToolPtr->set_grid_id(gridToModifyID);
std::unique_ptr<GridModifyTool> newTool(std::make_unique<GridModifyTool>(*this));
newTool->set_grid_id(gridToModifyID);
switch_to_tool_ptr(std::move(newTool));
}
void DrawingProgram::update() {

View file

@ -171,4 +171,5 @@ void GridModifyTool::draw(SkCanvas* canvas, const DrawData& drawData) {
}
void GridModifyTool::switch_tool(DrawingProgramToolType newTool) {
drawP.world.gridMan.send_grid_info(gridID);
}

View file

@ -1,4 +1,5 @@
#include "GridManager.hpp"
#include "Server/CommandList.hpp"
#include "SharedTypes.hpp"
#include "World.hpp"
#include "MainProgram.hpp"
@ -8,6 +9,19 @@ GridManager::GridManager(World& w):
world(w) {}
void GridManager::init_client_callbacks() {
world.con.client_add_recv_callback(CLIENT_SET_GRID, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID gID;
message(gID);
auto& g = grids[gID];
message(g);
changed = true;
});
world.con.client_add_recv_callback(CLIENT_REMOVE_GRID, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID gID;
message(gID);
grids.erase(gID);
changed = true;
});
}
ServerClientID GridManager::add_default_grid(const std::string& newName) {
@ -22,8 +36,17 @@ ServerClientID GridManager::add_default_grid(const std::string& newName) {
return newID;
}
void GridManager::send_grid_info(ServerClientID gridID) {
auto gridFoundIt = grids.find(gridID);
if(gridFoundIt != grids.end()) {
WorldGrid& g = gridFoundIt->second;
world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_SET_GRID, gridID, g);
}
}
void GridManager::remove_grid(ServerClientID idToRemove) {
grids.erase(idToRemove);
world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_REMOVE_GRID, idToRemove);
changed = true;
}
@ -43,6 +66,15 @@ const std::vector<ServerClientID>& GridManager::sorted_grid_ids() {
return sortedGridIDs;
}
ClientPortionID GridManager::get_max_id(ServerPortionID serverID) const {
ClientPortionID maxClientID = 0;
for(auto& p : grids) {
if(p.first.first == serverID)
maxClientID = std::max(maxClientID, p.first.second);
}
return maxClientID;
}
void GridManager::draw(SkCanvas* canvas, const DrawData& drawData) {
for(auto& [str, g] : grids)
g.draw(*this, canvas, drawData);

View file

@ -10,6 +10,16 @@ class GridManager {
ServerClientID add_default_grid(const std::string& newName);
template <typename Archive> void save(Archive& a) const {
a(grids);
}
template <typename Archive> void load(Archive& a) {
a(grids);
changed = true;
}
void send_grid_info(ServerClientID gridID);
void remove_grid(ServerClientID idToRemove);
void draw(SkCanvas* canvas, const DrawData& drawData);
ClientPortionID get_max_id(ServerPortionID serverID) const;

View file

@ -16,7 +16,7 @@ class ClientData {
Vector3f cursorColor;
std::string displayName;
ServerClientID id = {0, 0};
ServerPortionID serverID = 0;
std::queue<ResourceData> pendingResourceData;
std::queue<ServerClientID> pendingResourceID;

View file

@ -18,7 +18,9 @@ enum ServerCommands : MessageCommandType {
SERVER_CHAT_MESSAGE,
SERVER_NEW_BOOKMARK,
SERVER_REMOVE_BOOKMARK,
SERVER_CANVAS_COLOR
SERVER_CANVAS_COLOR,
SERVER_SET_GRID,
SERVER_REMOVE_GRID
};
enum ClientCommands : MessageCommandType {
@ -38,11 +40,14 @@ enum ClientCommands : MessageCommandType {
CLIENT_CHAT_MESSAGE,
CLIENT_NEW_BOOKMARK,
CLIENT_REMOVE_BOOKMARK,
CLIENT_CANVAS_COLOR
CLIENT_CANVAS_COLOR,
CLIENT_SET_GRID,
CLIENT_REMOVE_GRID
};
// NOTE: Do not change savefile header length
#define SAVEFILE_HEADER_LEN 12
// NOTE: Change these values whenever savefile is incompatible with previous version
#define SAVEFILE_HEADER "INFPNT000002"
#define SAVEFILE_HEADER "INFPNT000003"
#define SAVEFILE_HEADER_V2 "INFPNT000002"
#define SAVEFILE_HEADER_V1 "INFPNT000001"

View file

@ -29,7 +29,7 @@ void ServerData::save(cereal::PortableBinaryOutputArchive& a) const {
a((uint64_t)components.size());
for(uint64_t i = 0; i < components.size(); i++)
a(components[i]->get_type(), components[i]->id, components[i]->coords, *components[i]);
a(bookmarks);
a(bookmarks, grids);
}
void ServerData::load(cereal::PortableBinaryInputArchive& a) {
@ -44,20 +44,7 @@ void ServerData::load(cereal::PortableBinaryInputArchive& a) {
components.emplace_back(newComp);
idToComponentMap.emplace(newComp->id, newComp);
}
a(bookmarks);
}
ClientPortionID ServerData::get_max_id(ServerPortionID serverID) const {
ClientPortionID maxClientID = 0;
for(auto& p : resources) {
if(p.first.first == serverID)
maxClientID = std::max(maxClientID, p.first.second);
}
for(auto& p : components) {
if(p->id.first == serverID)
maxClientID = std::max(maxClientID, p->id.second);
}
return maxClientID;
a(bookmarks, grids);
}
MainServer::MainServer(World& initWorld, const std::string& serverLocalID):
@ -69,6 +56,7 @@ MainServer::MainServer(World& initWorld, const std::string& serverLocalID):
data.idToComponentMap.emplace(data.components.back()->id, data.components.back());
}
data.bookmarks = world.bMan.bookmark_list();
data.grids = world.gridMan.grids;
data.resources = world.rMan.resource_list();
data.canvasBackColor = convert_vec3<Vector3f>(world.canvasTheme.backColor);
@ -91,23 +79,22 @@ MainServer::MainServer(World& initWorld, const std::string& serverLocalID):
if(isDirectConnect)
fileDisplayName = newClient.displayName;
newClient.id.first = Random::get().int_range<ServerPortionID>(1, std::numeric_limits<ServerPortionID>::max());
client->customID = newClient.id.first;
newClient.id.second = data.get_max_id(newClient.id.first);
netServer->send_items_to_all_clients_except(client, RELIABLE_COMMAND_CHANNEL, CLIENT_USER_CONNECT, newClient.id.first, newClient.displayName, newClient.cursorColor);
newClient.serverID = Random::get().int_range<ServerPortionID>(1, std::numeric_limits<ServerPortionID>::max());
client->customID = newClient.serverID;
netServer->send_items_to_all_clients_except(client, RELIABLE_COMMAND_CHANNEL, CLIENT_USER_CONNECT, newClient.serverID, newClient.displayName, newClient.cursorColor);
if(isDirectConnect)
netServer->send_items_to_client(client, RELIABLE_COMMAND_CHANNEL, CLIENT_INITIAL_DATA, isDirectConnect, newClient.id.first, newClient.cursorColor);
netServer->send_items_to_client(client, RELIABLE_COMMAND_CHANNEL, CLIENT_INITIAL_DATA, isDirectConnect, newClient.serverID, newClient.cursorColor);
else {
netServer->send_items_to_client(client, RELIABLE_COMMAND_CHANNEL, CLIENT_INITIAL_DATA, isDirectConnect, newClient.id.first, newClient.cursorColor, newClient.displayName, fileDisplayName, clients, data);
netServer->send_items_to_client(client, RELIABLE_COMMAND_CHANNEL, CLIENT_INITIAL_DATA, isDirectConnect, newClient.serverID, newClient.cursorColor, newClient.displayName, fileDisplayName, clients, data);
for(auto& [id, rData] : data.resources)
netServer->send_items_to_client(client, RESOURCE_COMMAND_CHANNEL, CLIENT_NEW_RESOURCE, id, rData);
}
clients.emplace(newClient.id.first, newClient);
clients.emplace(newClient.serverID, newClient);
});
netServer->add_recv_callback(SERVER_MOVE_MOUSE, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
auto& c = clients[client->customID];
message(c.camCoords, c.windowSize, c.cursorPos);
netServer->send_items_to_all_clients_except(client, UNRELIABLE_COMMAND_CHANNEL, CLIENT_MOVE_MOUSE, c.id.first, c.camCoords, c.windowSize, c.cursorPos);
netServer->send_items_to_all_clients_except(client, UNRELIABLE_COMMAND_CHANNEL, CLIENT_MOVE_MOUSE, c.serverID, c.camCoords, c.windowSize, c.cursorPos);
});
netServer->add_recv_callback(SERVER_PLACE_SINGLE_COMPONENT, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
uint64_t placement;
@ -247,6 +234,19 @@ MainServer::MainServer(World& initWorld, const std::string& serverLocalID):
data.bookmarks.erase(name);
netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_REMOVE_BOOKMARK, name);
});
netServer->add_recv_callback(SERVER_SET_GRID, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
ServerClientID gID;
message(gID);
WorldGrid& g = data.grids[gID];
message(g);
netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_SET_GRID, gID, g);
});
netServer->add_recv_callback(SERVER_REMOVE_GRID, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
ServerClientID gID;
message(gID);
data.grids.erase(gID);
netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_REMOVE_GRID, gID);
});
netServer->add_recv_callback(SERVER_CANVAS_COLOR, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
message(data.canvasBackColor);
netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_CANVAS_COLOR, data.canvasBackColor);

View file

@ -3,6 +3,7 @@
#include "../SharedTypes.hpp"
#include "../BookmarkManager.hpp"
#include "../ResourceManager.hpp"
#include "../WorldGrid.hpp"
class DrawComponent;
@ -12,9 +13,10 @@ class ServerData {
std::unordered_map<ServerClientID, std::shared_ptr<DrawComponent>> idToComponentMap;
std::unordered_map<ServerClientID, ResourceData> resources;
std::unordered_map<std::string, Bookmark> bookmarks;
std::unordered_map<ServerClientID, WorldGrid> grids;
Vector3f canvasBackColor;
void save(cereal::PortableBinaryOutputArchive& a) const;
void write_to_file(cereal::PortableBinaryOutputArchive& a) const;
void load(cereal::PortableBinaryInputArchive& a);
ClientPortionID get_max_id(ServerPortionID serverID) const;
};

View file

@ -587,6 +587,7 @@ void Toolbar::grid_menu(bool justOpened) {
if(gui.svg_icon_button("visibility eye", grid.visible ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", false, entryHeight, false)) {
miniButtonClicked = true;
grid.visible = !grid.visible;
main.world->gridMan.send_grid_info(gridID);
}
if(gui.svg_icon_button("edit pencil", "data/icons/pencil.svg", false, entryHeight, false)) {
miniButtonClicked = true;

View file

@ -5,6 +5,7 @@
#include "Helpers/Networking/ByteStream.hpp"
#include "Server/CommandList.hpp"
#include "MainProgram.hpp"
#include "SharedTypes.hpp"
#include "WorldGrid.hpp"
#include "cereal/archives/portable_binary.hpp"
#include <fstream>
@ -61,6 +62,7 @@ World::World(MainProgram& initMain, OpenWorldInfo& worldInfo):
drawProg.init_client_callbacks();
rMan.init_client_callbacks();
bMan.init_client_callbacks();
gridMan.init_client_callbacks();
init_client_callbacks();
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_INITIAL_DATA, displayName, false);
}
@ -77,9 +79,9 @@ void World::init_client_callbacks() {
message(newBackColor);
set_canvas_background_color(newBackColor, false);
drawProg.initialize_draw_data(message);
message(bMan);
message(bMan, gridMan);
}
nextClientID = std::max(rMan.get_max_id(ownID), drawProg.get_max_id(ownID));
nextClientID = get_max_id(ownID);
clientStillConnecting = false;
});
con.client_add_recv_callback(CLIENT_USER_CONNECT, [&](cereal::PortableBinaryInputArchive& message) {
@ -240,6 +242,7 @@ void World::start_hosting(const std::string& initNetSource, const std::string& s
drawProg.init_client_callbacks();
rMan.init_client_callbacks();
bMan.init_client_callbacks();
gridMan.init_client_callbacks();
init_client_callbacks();
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_INITIAL_DATA, displayName, true);
conType = CONNECTIONTYPE_SERVER;
@ -302,13 +305,13 @@ void World::load_from_file(const std::filesystem::path& fileName, std::string_vi
}
if(buffer.size() < SAVEFILE_HEADER_LEN)
throw std::runtime_error("[World::load_from_file] File too small");
throw std::runtime_error("[World::load_from_file] File is not an InfiniPaint canvas (File too small)");
std::string_view fileHeader = buffer.substr(0, SAVEFILE_HEADER_LEN);
std::string_view uncompressedDataView;
std::vector<char> uncompressedDataVector;
if(fileHeader == SAVEFILE_HEADER) {
if(fileHeader == SAVEFILE_HEADER || fileHeader == SAVEFILE_HEADER_V2) {
uncompressedDataVector.resize(ZSTD_getFrameContentSize(buffer.data() + SAVEFILE_HEADER_LEN, buffer.size() - SAVEFILE_HEADER_LEN));
size_t trueUncompressedSize = ZSTD_decompress(uncompressedDataVector.data(), uncompressedDataVector.size(), buffer.data() + SAVEFILE_HEADER_LEN, buffer.size() - SAVEFILE_HEADER_LEN);
uncompressedDataView = std::string_view(uncompressedDataVector.data(), trueUncompressedSize);
@ -316,7 +319,7 @@ void World::load_from_file(const std::filesystem::path& fileName, std::string_vi
else if(fileHeader == SAVEFILE_HEADER_V1)
uncompressedDataView = std::string_view(buffer.data() + SAVEFILE_HEADER_LEN, buffer.size() - SAVEFILE_HEADER_LEN);
else
throw std::runtime_error("[World::load_from_file] File does not have correct header");
throw std::runtime_error("[World::load_from_file] File is not an InfiniPaint canvas (Wrong header)");
@ -325,17 +328,22 @@ void World::load_from_file(const std::filesystem::path& fileName, std::string_vi
cereal::PortableBinaryInputArchive a(f);
SaveLoadFileOp op{.world = this, .versionStr = std::string(fileHeader)};
a(op);
nextClientID = std::max(rMan.get_max_id(ownID), drawProg.get_max_id(ownID));
nextClientID = get_max_id(ownID);
Logger::get().log("USERINFO", "File loaded");
set_name(filePath.stem().string());
}
ClientPortionID World::get_max_id(ServerPortionID serverID) {
return std::max(std::max(rMan.get_max_id(serverID), drawProg.get_max_id(serverID)), gridMan.get_max_id(serverID));
}
void World::SaveLoadFileOp::save(cereal::PortableBinaryOutputArchive& a) const {
a(world->drawData.cam.c, world->main.window.size.cast<float>().eval());
a(convert_vec3<Vector3f>(world->canvasTheme.backColor));
world->drawProg.write_to_file(a);
a(world->bMan);
a(world->gridMan);
world->rMan.save_strip_unused_resources(a, world->drawProg.get_used_resources());
}
@ -343,16 +351,20 @@ void World::SaveLoadFileOp::load(cereal::PortableBinaryInputArchive& a) {
CoordSpaceHelper coordsToJumpTo;
Vector2f windowSizeToJumpTo;
a(coordsToJumpTo, windowSizeToJumpTo);
if(versionStr == SAVEFILE_HEADER) {
if(versionStr == SAVEFILE_HEADER_V1)
world->set_canvas_background_color(DEFAULT_CANVAS_BACKGROUND_COLOR);
else {
Vector3f canvasBackColor;
a(canvasBackColor);
world->set_canvas_background_color(canvasBackColor);
}
else
world->set_canvas_background_color(DEFAULT_CANVAS_BACKGROUND_COLOR);
world->drawData.cam.smooth_move_to(*world, coordsToJumpTo, windowSizeToJumpTo, true);
world->drawProg.initialize_draw_data(a);
a(world->bMan);
if(versionStr == SAVEFILE_HEADER)
a(world->gridMan);
a(world->rMan);
}

View file

@ -100,6 +100,8 @@ class World {
} canvasTheme;
private:
ClientPortionID get_max_id(ServerPortionID serverID);
void init_client_callbacks();
void set_name(const std::string& n);

View file

@ -2,20 +2,25 @@
#include <include/core/SkCanvas.h>
#include "DrawData.hpp"
#include <include/effects/SkRuntimeEffect.h>
#include <cereal/types/optional.hpp>
class GridManager;
class WorldGrid {
public:
template <class Archive> void serialize(Archive& a) {
a(name, gridType, size, visible, offset, bounds, color, removeDivisionsOutwards, subdivisions);
}
void draw(GridManager& gMan, SkCanvas* canvas, const DrawData& drawData);
std::string name;
enum class GridType : unsigned {
CIRCLE_POINTS = 0,
SQUARE_POINTS,
SQUARE_LINES,
RULED
} gridType = GridType::CIRCLE_POINTS;
std::string name;
WorldScalar size{50000000};
bool visible = true;
WorldVec offset{0, 0};