Serialization works in new version

This commit is contained in:
Yousef Khadadeh 2025-11-16 16:39:12 +03:00
parent 6b3ce01e94
commit 79f7135746
23 changed files with 186 additions and 56 deletions

View file

@ -46,6 +46,7 @@ set(sources "src/main.cpp"
"src/World.cpp"
"src/FileHelpers.cpp"
"src/BookmarkManager.cpp"
"src/VersionConstants.cpp"
"src/ResourceDisplay/ResourceDisplay.cpp"
"src/ResourceDisplay/SvgResourceDisplay.cpp"
"src/ResourceDisplay/ImageResourceDisplay.cpp"

View file

@ -14,7 +14,7 @@ template <class T> std::weak_ptr<T> make_weak_ptr(std::shared_ptr<T> ptr) { retu
#define RELIABLE_COMMAND_CHANNEL "reliableCommand"
#define UNRELIABLE_COMMAND_CHANNEL "unreliableCommand"
#define RESOURCE_COMMAND_CHANNEL "resourceCommand"
#define MAIN_NETLIBRARY_CHANNEL "main00003" // NOTE: Change this value whenever netcode is incompatible with the previous version
#define MAIN_NETLIBRARY_CHANNEL "main00004" // NOTE: Change this value whenever netcode is incompatible with the previous version
typedef uint8_t MessageCommandType;

View file

@ -5,6 +5,18 @@ std::string version_numbers_to_version_str(const VersionNumber& versionNum) {
return std::to_string(versionNum.major) + "." + std::to_string(versionNum.minor) + "." + std::to_string(versionNum.patch);
}
bool operator<=(const VersionNumber& a, const VersionNumber& b) {
return !(a > b);
}
bool operator<(const VersionNumber& a, const VersionNumber& b) {
return !(a >= b);
}
bool operator>=(const VersionNumber& a, const VersionNumber& b) {
return (a > b) || (a == b);
}
bool operator>(const VersionNumber& a, const VersionNumber& b) {
return (a.major > b.major) || (a.major == b.major && (a.minor > b.minor || (a.minor == b.minor && a.patch > b.patch)));
}
@ -13,6 +25,10 @@ bool operator==(const VersionNumber& a, const VersionNumber& b) {
return a.major == b.major && a.minor == b.minor && a.patch == b.patch;
}
bool operator!=(const VersionNumber& a, const VersionNumber& b) {
return a.major != b.major || a.minor != b.minor || a.patch != b.patch;
}
std::optional<VersionNumber> version_str_to_version_numbers(const std::string& versionStr) {
try {
std::vector<std::string> splitStrings = split_string_by_token(versionStr, ".");

View file

@ -10,5 +10,9 @@ struct VersionNumber {
std::optional<VersionNumber> version_str_to_version_numbers(const std::string& versionStr);
std::string version_numbers_to_version_str(const VersionNumber& versionNum);
bool operator<=(const VersionNumber& a, const VersionNumber& b);
bool operator<(const VersionNumber& a, const VersionNumber& b);
bool operator>(const VersionNumber& a, const VersionNumber& b);
bool operator>=(const VersionNumber& a, const VersionNumber& b);
bool operator==(const VersionNumber& a, const VersionNumber& b);
bool operator!=(const VersionNumber& a, const VersionNumber& b);

View file

@ -23,6 +23,14 @@ void DrawBrushStroke::load(cereal::PortableBinaryInputArchive& a) {
a(d->points, d->color, d->hasRoundCaps);
}
void DrawBrushStroke::save_file(cereal::PortableBinaryOutputArchive& a) const {
a(d->points, d->color, d->hasRoundCaps);
}
void DrawBrushStroke::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
a(d->points, d->color, d->hasRoundCaps);
}
DrawComponentType DrawBrushStroke::get_type() const {
return DRAWCOMPONENT_BRUSHSTROKE;
}

View file

@ -25,6 +25,8 @@ class DrawBrushStroke : public DrawComponent {
virtual DrawComponentType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void save_file(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) override;
struct Data {
std::vector<DrawBrushStrokePoint> points;

View file

@ -6,6 +6,7 @@
#include "../CoordSpaceHelper.hpp"
#include "../Server/MainServer.hpp"
#include "../CollabList.hpp"
#include <Helpers/VersionNumber.hpp>
#ifndef IS_SERVER
#include "../DrawData.hpp"
@ -43,6 +44,8 @@ class DrawComponent {
static std::shared_ptr<DrawComponent> allocate_comp_type(DrawComponentType type);
virtual void save(cereal::PortableBinaryOutputArchive& a) const = 0;
virtual void load(cereal::PortableBinaryInputArchive& a) = 0;
virtual void save_file(cereal::PortableBinaryOutputArchive& a) const = 0;
virtual void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) = 0;
virtual DrawComponentType get_type() const = 0;
virtual void get_used_resources(std::unordered_set<ServerClientID>& v) const;
virtual void remap_resource_ids(std::unordered_map<ServerClientID, ServerClientID>& newIDs);

View file

@ -18,6 +18,14 @@ void DrawEllipse::load(cereal::PortableBinaryInputArchive& a) {
a(d.strokeColor, d.fillColor, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
void DrawEllipse::save_file(cereal::PortableBinaryOutputArchive& a) const {
a(d.strokeColor, d.fillColor, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
void DrawEllipse::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
a(d.strokeColor, d.fillColor, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawEllipse::copy() const {
auto a = std::make_shared<DrawEllipse>();

View file

@ -11,6 +11,8 @@ class DrawEllipse : public DrawComponent {
public:
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void save_file(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) override;
virtual DrawComponentType get_type() const override;
// User input data

View file

@ -24,6 +24,14 @@ void DrawImage::load(cereal::PortableBinaryInputArchive& a) {
a(d.p1, d.p2, d.imageID);
}
void DrawImage::save_file(cereal::PortableBinaryOutputArchive& a) const {
a(d.p1, d.p2, d.imageID);
}
void DrawImage::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
a(d.p1, d.p2, d.imageID);
}
void DrawImage::get_used_resources(std::unordered_set<ServerClientID>& v) const {
v.emplace(d.imageID);
}

View file

@ -11,6 +11,8 @@ class DrawImage : public DrawComponent {
public:
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void save_file(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) override;
virtual DrawComponentType get_type() const override;
virtual void get_used_resources(std::unordered_set<ServerClientID>& v) const override;
virtual void remap_resource_ids(std::unordered_map<ServerClientID, ServerClientID>& newIDs) override;

View file

@ -21,6 +21,14 @@ void DrawRectangle::load(cereal::PortableBinaryInputArchive& a) {
a(d.strokeColor, d.fillColor, d.cornerRadius, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
void DrawRectangle::save_file(cereal::PortableBinaryOutputArchive& a) const {
a(d.strokeColor, d.fillColor, d.cornerRadius, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
void DrawRectangle::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
a(d.strokeColor, d.fillColor, d.cornerRadius, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawRectangle::copy() const {
auto a = std::make_shared<DrawRectangle>();

View file

@ -11,6 +11,8 @@ class DrawRectangle : public DrawComponent {
public:
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void save_file(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) override;
virtual DrawComponentType get_type() const override;
// User input data

View file

@ -37,6 +37,29 @@ void DrawTextBox::load(cereal::PortableBinaryInputArchive& a) {
textBox->set_rich_text_data(richText);
}
void DrawTextBox::save_file(cereal::PortableBinaryOutputArchive& a) const {
a(d.p1, d.p2, textBox->get_rich_text_data());
}
void DrawTextBox::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
if(version < VersionNumber(0, 3, 0)) {
bool editing; // Unused
Vector4f textColor;
float textSize;
RichText::TextBox::Cursor cursor; // Unused, Equivalent to old cursor structure, just paragraph index and text index flipped
std::string currentText;
a(editing, d.p1, d.p2, textColor, textSize, cursor, currentText);
textBox->insert({0, 0}, currentText);
textBox->set_text_style_modifier_between({0, 0}, textBox->move(RichText::TextBox::Movement::END, {0, 0}), std::make_shared<ColorTextStyleModifier>(textColor));
textBox->set_text_style_modifier_between({0, 0}, textBox->move(RichText::TextBox::Movement::END, {0, 0}), std::make_shared<SizeTextStyleModifier>(textSize));
}
else {
TextData richText;
a(d.p1, d.p2, richText);
textBox->set_rich_text_data(richText);
}
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawTextBox::copy() const {
auto a = std::make_shared<DrawTextBox>();

View file

@ -16,6 +16,8 @@ class DrawTextBox : public DrawComponent {
virtual DrawComponentType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void save_file(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) override;
// User input data
struct Data {

View file

@ -174,7 +174,7 @@ void DrawingProgram::parallel_loop_all_components(std::function<void(const std::
parallel_loop_container(components.client_list(), func);
}
std::unordered_set<ServerClientID> DrawingProgram::get_used_resources() {
std::unordered_set<ServerClientID> DrawingProgram::get_used_resources() const {
std::unordered_set<ServerClientID> toRet;
for(auto& c : components.client_list())
c->obj->get_used_resources(toRet);
@ -559,6 +559,23 @@ void DrawingProgram::initialize_draw_data(cereal::PortableBinaryInputArchive& a)
compCache.test_rebuild(components.client_list(), true);
}
void DrawingProgram::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
uint64_t compCount;
a(compCount);
for(uint64_t i = 0; i < compCount; i++) {
DrawComponentType t;
a(t);
auto newComp = DrawComponent::allocate_comp_type(t);
a(newComp->id, newComp->coords);
newComp->load_file(a, version);
components.init_emplace_back(newComp);
}
parallel_loop_all_components([&](const auto& c){
c->obj->commit_update(*this, false);
});
compCache.test_rebuild(components.client_list(), true);
}
void DrawingProgram::client_erase_set(std::unordered_set<CollabListType::ObjectInfoPtr> erasedComponents) {
std::unordered_set<ServerClientID> idsToErase;
for(auto& c : erasedComponents)
@ -687,11 +704,12 @@ void DrawingProgram::draw_drag_circle(SkCanvas* canvas, const Vector2f& sPos, co
canvas->drawCircle(sPos.x(), sPos.y(), constantRadius, paintOutline);
}
void DrawingProgram::write_to_file(cereal::PortableBinaryOutputArchive& a) {
void DrawingProgram::save_file(cereal::PortableBinaryOutputArchive& a) const {
auto& cList = components.client_list();
a((uint64_t)cList.size());
for(size_t i = 0; i < cList.size(); i++) {
a(cList[i]->obj->get_type(), cList[i]->obj->id, cList[i]->obj->coords, *cList[i]->obj);
a(cList[i]->obj->get_type(), cList[i]->obj->id, cList[i]->obj->coords);
cList[i]->obj->save_file(a);
}
}

View file

@ -30,10 +30,13 @@ class DrawingProgram {
void update();
void scale_up(const WorldScalar& scaleUpAmount);
void draw(SkCanvas* canvas, const DrawData& drawData);
void initialize_draw_data(cereal::PortableBinaryInputArchive& a);
std::unordered_set<ServerClientID> get_used_resources();
void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version);
std::unordered_set<ServerClientID> get_used_resources() const;
ClientPortionID get_max_id(ServerPortionID serverID);
void write_to_file(cereal::PortableBinaryOutputArchive& a);
void save_file(cereal::PortableBinaryOutputArchive& a) const;
World& world;
bool prevent_undo_or_redo();

View file

@ -48,14 +48,5 @@ enum ClientCommands : MessageCommandType {
CLIENT_CANVAS_SCALE
};
// 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 "INFPNT000003"
#define SAVEFILE_HEADER_V2 "INFPNT000002"
#define SAVEFILE_HEADER_V1 "INFPNT000001"
// Version header
#define VERSION_STRING "0.3.0"
// Arbitrary value. Higher scale up values lead to less scale up events (which can cause stuttering), but leads to larger numbers overall, which can cause a tiny bit of lag and increased memory usage
#define CANVAS_SCALE_UP_STEP WorldScalar("100000000000000000000000000000000000000000000000000")

View file

@ -9,6 +9,7 @@
#include "GUIStuff/Elements/Element.hpp"
#include "GUIStuff/GUIManager.hpp"
#include "InputManager.hpp"
#include "VersionConstants.hpp"
#include "World.hpp"
#include <SDL3/SDL_dialog.h>
#include <algorithm>
@ -193,7 +194,7 @@ void Toolbar::load_licenses() {
std::sort(thirdPartyLicenses.begin(), thirdPartyLicenses.end(), [](const auto& a1, const auto& a2) {
return std::lexicographical_compare(a1.first.begin(), a1.first.end(), a2.first.begin(), a2.first.end());
});
ownLicenseText = "InfiniPaint v" + std::string(VERSION_STRING);
ownLicenseText = "InfiniPaint v" + VersionConstants::CURRENT_VERSION_STRING;
ownLicenseText +=
R"(
@ -583,7 +584,7 @@ void Toolbar::update_notification_check() {
case FileDownloader::DownloadData::Status::SUCCESS: {
updateCheckerData.updateCheckDone = true;
std::optional<VersionNumber> newVersion = version_str_to_version_numbers(updateCheckerData.versionFile->str);
std::optional<VersionNumber> currentVersion = version_str_to_version_numbers(VERSION_STRING);
std::optional<VersionNumber> currentVersion = VersionConstants::CURRENT_VERSION_NUMBER;
if(newVersion.has_value() && currentVersion.has_value()) {
VersionNumber& newV = newVersion.value();
VersionNumber& currentV = currentVersion.value();

20
src/VersionConstants.cpp Normal file
View file

@ -0,0 +1,20 @@
#include "VersionConstants.hpp"
#include <stdexcept>
namespace VersionConstants {
VersionNumber header_to_version_number(const std::string& header) {
static std::unordered_map<std::string, VersionNumber> m;
if(m.empty()) {
m["INFPNT000001"] = VersionNumber(0, 0, 1);
m["INFPNT000002"] = VersionNumber(0, 1, 0);
m["INFPNT000003"] = VersionNumber(0, 2, 0);
m["INFPNT000004"] = VersionNumber(0, 3, 0);
}
auto it = m.find(header);
if(it == m.end())
throw std::runtime_error("[VersionConstants::header_to_version_number] Header " + header + " is invalid");
return it->second;
}
}

16
src/VersionConstants.hpp Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <Helpers/VersionNumber.hpp>
#include <unordered_map>
namespace VersionConstants {
// Map OLDEST version that is compatible with the filetype with a specific header
// This means that, to check for file version, you can do these two checks:
// Check if older than a certain version: if(fileVersion < NEWEST VERSION YOU SHOULDNT RUN THE IF STATEMENT ON)
// Check if newer/equal to a certain version: if(fileVersion >= OLDEST VERSION YOU SHOULD RUN THE IF STATEMENT ON)
VersionNumber header_to_version_number(const std::string& header);
constexpr int SAVEFILE_HEADER_LEN = 12; // DO NOT CHANGE THIS HEADER LENGTH
const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000004"; // Change whenever the save file is incompatible with the previous version
const std::string CURRENT_VERSION_STRING = "0.3.0";
constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 3, 0);
}

View file

@ -7,6 +7,7 @@
#include "MainProgram.hpp"
#include "SharedTypes.hpp"
#include "Toolbar.hpp"
#include "VersionConstants.hpp"
#include "WorldGrid.hpp"
#include "cereal/archives/portable_binary.hpp"
#include <fstream>
@ -273,14 +274,13 @@ void World::save_to_file(const std::filesystem::path& fileName) {
filePath = force_extension_on_path(fileName, FILE_EXTENSION);
std::stringstream f;
f.write(SAVEFILE_HEADER, SAVEFILE_HEADER_LEN);
f.write(VersionConstants::CURRENT_SAVEFILE_HEADER.c_str(), VersionConstants::SAVEFILE_HEADER_LEN);
{
std::stringstream fWorldDataToCompress;
{
cereal::PortableBinaryOutputArchive a(fWorldDataToCompress);
SaveLoadFileOp op{.world = this, .versionStr = SAVEFILE_HEADER};
a(op);
save_file(a);
}
std::vector<char> compressedData(ZSTD_compressBound(fWorldDataToCompress.view().size()));
@ -321,30 +321,26 @@ void World::load_from_file(const std::filesystem::path& fileName, std::string_vi
buffer = byteDataFromFile;
}
if(buffer.size() < SAVEFILE_HEADER_LEN)
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, SAVEFILE_HEADER_LEN);
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(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);
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);
}
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 is not an InfiniPaint canvas (Wrong header)");
ByteMemStream f((char*)uncompressedDataView.data(), uncompressedDataView.size());
cereal::PortableBinaryInputArchive a(f);
SaveLoadFileOp op{.world = this, .versionStr = std::string(fileHeader)};
a(op);
load_file(a, fileVersion);
nextClientID = get_max_id(ownID);
Logger::get().log("USERINFO", "File loaded");
@ -355,34 +351,34 @@ 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());
void World::save_file(cereal::PortableBinaryOutputArchive& a) const {
a(drawData.cam.c, main.window.size.cast<float>().eval());
a(convert_vec3<Vector3f>(canvasTheme.backColor));
drawProg.save_file(a);
a(bMan);
a(gridMan);
rMan.save_strip_unused_resources(a, drawProg.get_used_resources());
}
void World::SaveLoadFileOp::load(cereal::PortableBinaryInputArchive& a) {
void World::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
CoordSpaceHelper coordsToJumpTo;
Vector2f windowSizeToJumpTo;
a(coordsToJumpTo, windowSizeToJumpTo);
if(versionStr == SAVEFILE_HEADER_V1)
world->set_canvas_background_color(DEFAULT_CANVAS_BACKGROUND_COLOR);
if(version < VersionNumber(0, 1, 0))
set_canvas_background_color(DEFAULT_CANVAS_BACKGROUND_COLOR);
else {
Vector3f canvasBackColor;
a(canvasBackColor);
world->set_canvas_background_color(canvasBackColor);
set_canvas_background_color(canvasBackColor);
}
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);
drawData.cam.smooth_move_to(*this, coordsToJumpTo, windowSizeToJumpTo, true);
drawProg.load_file(a, version);
a(bMan);
if(version >= VersionNumber(0, 2, 0))
a(gridMan);
a(rMan);
}
WorldScalar World::calculate_zoom_from_uniform_zoom(WorldScalar uniformZoom, WorldVec oldWindowSize) {

View file

@ -34,12 +34,8 @@ class World {
World(MainProgram& initMain, OpenWorldInfo& worldInfo);
struct SaveLoadFileOp {
World* world;
std::string versionStr;
void save(cereal::PortableBinaryOutputArchive& a) const;
void load(cereal::PortableBinaryInputArchive& a);
};
void save_file(cereal::PortableBinaryOutputArchive& a) const;
void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version);
MainProgram& main;
DrawData drawData;