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"
2026-01-05 12:43:40 +03:00
# include "Helpers/NetworkingObjects/NetObjOrderedList.hpp"
2025-12-17 14:46:27 +03:00
# include "Helpers/NetworkingObjects/NetObjTemporaryPtr.decl.hpp"
# include "Helpers/NetworkingObjects/NetObjUnorderedSet.hpp"
# include "CommandList.hpp"
2026-01-18 13:39:49 +03:00
# include "Helpers/StringHelpers.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>
2026-01-05 12:43:40 +03:00
# include <chrono>
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 ) ,
2026-01-02 17:11:32 +03:00
undo ( * this ) ,
2025-07-06 20:43:53 +03:00
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 ( ) ;
2026-01-02 17:11:32 +03:00
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 ) ;
} ) ;
2025-12-10 17:23:05 +03:00
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 ) ;
2026-01-05 12:43:40 +03:00
# ifdef ENABLE_ORDERED_LIST_TEST
listDebugTest = netObjMan . make_obj < NetworkingObjects : : NetObjOrderedList < uint16_t > > ( ) ;
# endif
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 ) ;
2026-01-05 12:43:40 +03:00
# ifdef ENABLE_ORDERED_LIST_TEST
NetworkingObjects : : register_generic_serialized_class < uint16_t > ( netObjMan ) ;
NetworkingObjects : : register_ordered_list_class < uint16_t > ( netObjMan ) ;
# endif
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 ) ;
2026-01-05 12:43:40 +03:00
# ifdef ENABLE_ORDERED_LIST_TEST
listDebugTest = netObjMan . read_create_message < NetworkingObjects : : NetObjOrderedList < uint16_t > > ( message , nullptr ) ;
# endif
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-02-27 16:16:51 +03:00
mousePreviousWorldVec = drawData . cam . c . from_space ( main . input . mouse . pos ) ;
mouseWorldMove = drawData . cam . c . from_space ( main . input . mouse . move ) ;
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 ( ) ;
2026-01-05 12:43:40 +03:00
# ifdef ENABLE_ORDERED_LIST_TEST
list_debug_test_update ( ) ;
# endif
2025-07-06 20:43:53 +03:00
}
drawData . cam . update_main ( * this ) ;
rMan . update ( ) ;
2025-07-27 20:08:46 +03:00
}
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 ( ) ;
}
}
}
2026-02-28 22:47:35 +03:00
bool World : : is_focus ( ) {
return main . world . get ( ) = = this ;
}
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
}
2026-02-28 22:47:35 +03:00
void World : : on_tab_out ( ) {
2026-02-19 22:29:24 +03:00
rMan . clear_display_cache ( ) ;
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
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 ) ;
2026-03-02 23:09:48 +03:00
drawData . cam . input_key_callback ( key ) ;
2026-03-02 16:36:34 +03:00
}
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
void World : : input_mouse_button_callback ( const InputManager : : MouseButtonCallbackArgs & button ) {
2026-03-02 23:09:48 +03:00
if ( ! clientStillConnecting ) {
2026-03-02 16:36:34 +03:00
drawProg . input_mouse_button_callback ( button ) ;
2026-03-02 23:09:48 +03:00
}
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
void World : : input_mouse_motion_callback ( const InputManager : : MouseMotionCallbackArgs & motion ) {
2026-03-02 23:09:48 +03:00
if ( ! clientStillConnecting ) {
2026-03-02 16:36:34 +03:00
drawProg . input_mouse_motion_callback ( motion ) ;
2026-03-02 23:09:48 +03:00
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 ) ;
}
2026-02-19 22:29:24 +03:00
}
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 ) {
2026-01-18 13:39:49 +03:00
std : : vector < std : : string > strList ;
for ( auto & client : clients - > get_data ( ) )
strList . emplace_back ( client - > get_display_name ( ) ) ;
displayName = ensure_string_unique ( strList , displayName ) ;
2025-12-17 14:46:27 +03:00
}
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 ) ;
2026-01-05 12:43:40 +03:00
# ifdef ENABLE_ORDERED_LIST_TEST
listDebugTest . write_create_message ( a ) ;
# endif
2026-01-01 22:46:02 +03:00
}
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
}
2026-01-18 13:39:49 +03:00
void World : : autosave_to_directory ( const std : : filesystem : : path & directoryToSaveAt ) {
std : : vector < std : : string > strList ;
for ( auto & entry : std : : filesystem : : directory_iterator ( directoryToSaveAt ) ) {
if ( entry . path ( ) . has_stem ( ) & & entry . path ( ) . has_extension ( ) & & entry . path ( ) . extension ( ) = = ( " . " + FILE_EXTENSION ) )
2026-02-03 14:31:33 +03:00
strList . emplace_back ( entry . path ( ) . stem ( ) . string ( ) ) ;
2026-01-18 13:39:49 +03:00
}
std : : string nameToSaveUnder = ensure_string_unique ( strList , name ) ;
save_to_file ( directoryToSaveAt / std : : filesystem : : path ( nameToSaveUnder + " . " + FILE_EXTENSION ) ) ;
}
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 " ) ;
2026-01-17 20:11:13 +03:00
undo . set_save_action ( ) ;
2025-07-06 20:43:53 +03:00
}
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 ) ;
2026-01-02 17:11:32 +03:00
undo . scale_up ( scaleUpAmount ) ;
2025-09-30 11:45:03 +03:00
}
2026-01-18 13:39:49 +03:00
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
2026-02-08 20:54:44 +03:00
# ifdef __EMSCRIPTEN__
2026-01-18 13:39:49 +03:00
return false ;
# else
if ( netClient | | netServer )
return ! ( netClient & & filePath . empty ( ) ) ;
return hasUnsavedLocalChanges ;
# endif
}
2026-01-05 12:43:40 +03:00
# ifdef ENABLE_ORDERED_LIST_TEST
void World : : list_debug_test_update ( ) {
if ( std : : chrono : : steady_clock : : now ( ) < ( listDebugTestTimeStart + std : : chrono : : minutes ( 1 ) ) ) {
2026-01-05 22:01:54 +03:00
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 ) ;
2026-01-05 12:43:40 +03:00
}
}
}
if ( netServer & & netServer - > get_client_list ( ) . empty ( ) )
listDebugTestTimeStart = std : : chrono : : steady_clock : : now ( ) ;
}
# endif
2026-02-25 13:26:35 +03:00
void World : : draw ( SkCanvas * canvas , const DrawData & calledDrawData ) {
2026-01-09 21:56:58 +03:00
if ( ! clientStillConnecting ) {
2026-02-25 13:26:35 +03:00
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 ) ;
2026-01-09 21:56:58 +03:00
}
2026-02-25 13:29:46 +03:00
if ( ! calledDrawData . takingScreenshot )
2026-02-25 13:26:35 +03:00
draw_other_player_cursors ( canvas , calledDrawData ) ;
2025-10-01 21:31:16 +03:00
}
2025-09-06 19:06:30 +03:00
}