2026-05-11 23:06:39 +03:00
/*
* InfiniPaint
* Copyright ( C ) 2025 - 2026 Yousef Khadadeh
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*/
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 <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"
2026-04-28 17:07:28 +03:00
# include "WorldScreenshot.hpp"
2026-05-20 16:01:55 +03:00
# include "FileHelpers.hpp"
2025-07-06 20:43:53 +03:00
# ifdef __EMSCRIPTEN__
# include <EmscriptenHelpers/emscripten_browser_file.h>
# endif
2026-04-11 16:36:24 +03:00
World : : World ( MainProgram & initMain , const CustomEvents : : OpenInfiniPaintFileEvent & 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
{
2026-04-28 17:07:28 +03:00
saveThumbnail = worldInfo . saveThumbnail ;
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-04-09 20:44:45 +03:00
drawData . cam . set_viewing_area ( main . window . size . cast < float > ( ) ) ;
2025-07-06 20:43:53 +03:00
2026-01-01 22:46:02 +03:00
if ( worldInfo . isClient )
init_client ( worldInfo . netSource ) ;
else {
init_client_data_list ( ) ;
2026-04-27 16:48:44 +03:00
set_name ( name ) ;
2026-01-01 22:46:02 +03:00
if ( worldInfo . filePathSource . has_value ( ) )
load_from_file ( worldInfo . filePathSource . value ( ) , worldInfo . fileDataBuffer ) ;
else
2026-04-27 16:48:44 +03:00
load_empty_canvas ( worldInfo . filePathEmptyAutoSaveDir ) ;
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 > > ( ) ;
2026-05-30 20:57:50 +03:00
serverClientData = ownClientData = clients - > emplace_direct ( clients , ClientData : : InitStruct {
2026-01-01 22:46:02 +03:00
. cursorColor = get_random_cursor_color ( ) ,
2026-03-31 13:17:58 +03:00
. displayName = main . conf . displayName
2026-01-01 22:46:02 +03:00
} ) ;
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 ) ;
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : PHONE_USERINFO , objPtr - > get_display_name ( ) + " joined " ) ;
2025-12-17 14:46:27 +03:00
} ) ;
clients - > set_erase_callback ( [ & ] ( const NetworkingObjects : : NetObjOwnerPtr < ClientData > & objPtr ) {
add_chat_message ( objPtr - > get_display_name ( ) , " left " , Toolbar : : ChatMessage : : Type : : JOIN ) ;
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : PHONE_USERINFO , objPtr - > get_display_name ( ) + " left " ) ;
2025-12-17 14:46:27 +03:00
} ) ;
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 ) ;
2026-06-02 21:57:53 +03:00
netObjMan . set_client ( netClient , SERVER_UPDATE_NETWORK_OBJECT ) ;
2026-01-01 22:46:02 +03:00
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 ;
2026-05-30 20:57:50 +03:00
NetworkingObjects : : NetObjID clientDataObjID , serverDataObjID ;
message ( fileDisplayName , clientDataObjID , serverDataObjID ) ;
2025-12-17 14:46:27 +03:00
set_name ( fileDisplayName ) ;
2026-05-30 20:57:50 +03:00
clients = netObjMan . read_create_message < NetworkingObjects : : NetObjUnorderedSet < ClientData > > ( message , nullptr ) ;
ownClientData = netObjMan . get_obj_temporary_ref_from_id < ClientData > ( clientDataObjID ) ;
serverClientData = netObjMan . get_obj_temporary_ref_from_id < ClientData > ( serverDataObjID ) ;
2026-04-09 12:00:36 +03:00
bMan . read_create_message ( message ) ;
gridMan . read_create_message ( message ) ;
2025-12-17 14:46:27 +03:00
drawProg . read_components_client ( message ) ;
canvasTheme . read_create_message ( message ) ;
init_client_data_list_callbacks ( ) ;
drawData . cam . smooth_move_to ( * main . world , ownClientData - > get_cam_coords ( ) , ownClientData - > get_window_size ( ) , true ) ;
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-04-09 12:00:36 +03:00
set_to_layout_gui_if_focus ( ) ;
2025-07-06 20:43:53 +03:00
} ) ;
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_KEEP_ALIVE , [ & ] ( cereal : : PortableBinaryInputArchive & message ) {
2025-07-06 20:43:53 +03:00
} ) ;
2026-01-01 22:46:02 +03:00
2026-03-31 13:17:58 +03:00
netClient - > send_items_to_server ( RELIABLE_COMMAND_CHANNEL , SERVER_INITIAL_DATA , main . conf . displayName ) ;
2025-07-06 20:43:53 +03:00
}
2026-06-02 21:57:53 +03:00
void World : : send_reliable_multi_command_to_all ( const std : : function < void ( ) > & captureSendBlock ) {
if ( netServer )
netServer - > send_multi_command_to_all_clients ( RELIABLE_COMMAND_CHANNEL , captureSendBlock ) ;
else if ( netClient )
netClient - > send_multi_command_to_server ( RELIABLE_COMMAND_CHANNEL , captureSendBlock ) ;
else
captureSendBlock ( ) ;
}
2025-07-06 20:43:53 +03:00
void World : : focus_update ( ) {
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-04-05 20:26:03 +03:00
bool World : : connection_update ( ) {
2026-01-01 22:46:02 +03:00
if ( netServer ) {
if ( netServer - > is_disconnected ( ) ) {
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : USERINFO , " Host connection failed " ) ;
2026-01-01 22:46:02 +03:00
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 ( ) ) {
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : USERINFO , " Client connection failed " ) ;
2026-01-01 22:46:02 +03:00
netObjMan . disconnect ( ) ;
2026-04-05 20:26:03 +03:00
main . set_tab_to_close ( this ) ;
2026-05-14 11:53:36 +03:00
netClient = nullptr ;
2026-04-05 20:26:03 +03:00
return false ;
2026-01-01 22:46:02 +03:00
}
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-04-05 20:26:03 +03:00
return true ;
2026-01-01 22:46:02 +03:00
}
2026-02-28 22:47:35 +03:00
bool World : : is_focus ( ) {
return main . world . get ( ) = = this ;
}
2026-04-09 12:00:36 +03:00
void World : : set_to_layout_gui_if_focus ( ) {
if ( is_focus ( ) )
main . g . gui . set_to_layout ( ) ;
}
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
}
2026-04-12 12:36:00 +03:00
void World : : update ( ) {
2026-01-01 22:46:02 +03:00
connection_update ( ) ;
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-04-12 17:31:32 +03:00
if ( ! clientStillConnecting )
drawProg . on_tab_out ( ) ;
2026-02-28 22:47:35 +03:00
}
2026-04-11 16:36:24 +03:00
void World : : input_add_file_to_canvas_callback ( const CustomEvents : : AddFileToCanvasEvent & addFile ) {
if ( ! clientStillConnecting )
drawProg . input_add_file_to_canvas_callback ( addFile ) ;
}
void World : : input_paste_callback ( const CustomEvents : : PasteEvent & paste ) {
2026-04-04 17:52:43 +03:00
if ( ! clientStillConnecting )
drawProg . input_paste_callback ( paste ) ;
}
2026-05-11 23:06:39 +03:00
void World : : input_android_text_box_input_callback ( const CustomEvents : : AndroidTextBoxInputEvent & textboxInput ) {
if ( ! clientStillConnecting )
drawProg . input_android_text_box_input_callback ( textboxInput ) ;
}
2026-04-04 17:52:43 +03:00
void World : : input_text_key_callback ( const InputManager : : KeyCallbackArgs & key ) {
if ( ! clientStillConnecting )
drawProg . input_text_key_callback ( key ) ;
}
void World : : input_text_callback ( const InputManager : : TextCallbackArgs & text ) {
if ( ! clientStillConnecting )
drawProg . input_text_callback ( text ) ;
}
2026-03-04 11:03:05 +03:00
void World : : input_drop_file_callback ( const InputManager : : DropCallbackArgs & drop ) {
if ( ! clientStillConnecting )
drawProg . input_drop_file_callback ( drop ) ;
}
void World : : input_drop_text_callback ( const InputManager : : DropCallbackArgs & drop ) {
if ( ! clientStillConnecting )
drawProg . input_drop_text_callback ( drop ) ;
}
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-03 22:31:48 +03:00
if ( ! clientStillConnecting ) {
2026-03-02 16:36:34 +03:00
drawProg . input_mouse_button_callback ( button ) ;
2026-03-03 22:31:48 +03:00
drawData . cam . input_mouse_button_on_canvas_callback ( * this , button ) ;
}
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 ) {
2026-03-03 16:01:21 +03:00
if ( ! clientStillConnecting )
2026-03-02 23:09:48 +03:00
drawData . cam . input_mouse_wheel_callback ( * this , wheel ) ;
2026-03-03 16:01:21 +03:00
}
void World : : input_pen_button_callback ( const InputManager : : PenButtonCallbackArgs & button ) {
if ( ! clientStillConnecting )
drawProg . input_pen_button_callback ( button ) ;
}
void World : : input_pen_touch_callback ( const InputManager : : PenTouchCallbackArgs & touch ) {
if ( ! clientStillConnecting )
drawProg . input_pen_touch_callback ( touch ) ;
}
void World : : input_pen_motion_callback ( const InputManager : : PenMotionCallbackArgs & motion ) {
if ( ! clientStillConnecting )
drawProg . input_pen_motion_callback ( motion ) ;
}
void World : : input_pen_axis_callback ( const InputManager : : PenAxisCallbackArgs & axis ) {
if ( ! clientStillConnecting )
drawProg . input_pen_axis_callback ( axis ) ;
2026-02-19 22:29:24 +03:00
}
2026-03-05 21:09:30 +03:00
void World : : input_multi_finger_touch_callback ( const InputManager : : MultiFingerTouchCallbackArgs & touch ) {
2026-03-05 12:03:10 +03:00
if ( ! clientStillConnecting )
drawData . cam . input_multi_finger_touch_callback ( * this , touch ) ;
}
2026-03-05 21:09:30 +03:00
void World : : input_multi_finger_motion_callback ( const InputManager : : MultiFingerMotionCallbackArgs & motion ) {
2026-03-05 12:03:10 +03:00
if ( ! clientStillConnecting )
drawData . cam . input_multi_finger_motion_callback ( * this , motion ) ;
}
2026-05-07 21:48:47 +03:00
std : : optional < InputManager : : TextBoxStartInfo > World : : get_text_box_start_info ( ) {
if ( ! clientStillConnecting )
return drawProg . get_text_box_start_info ( ) ;
return std : : nullopt ;
}
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 ) {
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : CHAT , type = = Toolbar : : ChatMessage : : JOIN ? ( name + " " + message ) : ( " [ " + name + " ] " + message ) ) ;
2025-10-16 09:38:58 +03:00
chatMessages . emplace_front ( Toolbar : : ChatMessage { name , message , type } ) ;
2025-07-06 20:43:53 +03:00
if ( chatMessages . size ( ) = = CHAT_SIZE )
chatMessages . pop_back ( ) ;
2026-04-09 12:00:36 +03:00
set_to_layout_gui_if_focus ( ) ;
2025-07-06 20:43:53 +03:00
}
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 ) ;
2026-06-02 21:57:53 +03:00
netObjMan . set_server ( netServer , CLIENT_UPDATE_NETWORK_OBJECT ) ;
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 ) ;
2026-05-30 20:57:50 +03:00
a ( CLIENT_INITIAL_DATA , name , clientDataObjPtr . get_net_id ( ) , serverClientData . get_net_id ( ) ) ;
clients . write_create_message ( a ) ;
2026-01-01 22:46:02 +03:00
bMan . bookmarkListRoot . write_create_message ( a ) ;
gridMan . grids . write_create_message ( a ) ;
drawProg . write_components_server ( a ) ;
canvasTheme . 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_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 ;
2026-04-27 16:48:44 +03:00
try {
strList = glob_path_as_string_list ( directoryToSaveAt , ( " * " + World : : DOT_FILE_EXTENSION ) . c_str ( ) , 0 , [ & ] ( const auto & p ) { return p . stem ( ) . string ( ) ; } ) ;
}
catch ( . . . ) {
2026-01-18 13:39:49 +03:00
}
std : : string nameToSaveUnder = ensure_string_unique ( strList , name ) ;
2026-05-12 15:19:43 +03:00
save_to_file ( directoryToSaveAt / std : : filesystem : : path ( nameToSaveUnder + " . " + FILE_EXTENSION ) ) ;
2026-01-18 13:39:49 +03:00
}
2026-05-18 09:45:17 +03:00
void World : : save_to_file ( const std : : filesystem : : path & filePathToSaveAt ) {
2025-07-06 20:43:53 +03:00
try {
2026-05-20 16:01:55 +03:00
if ( main . conf . forceExtensionOnPath )
filePath = force_extension_on_path ( filePathToSaveAt , FILE_EXTENSION ) ;
else
filePath = filePathToSaveAt ;
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
2026-04-11 11:17:26 +03:00
if ( ! SDL_SaveFile ( filePath . string ( ) . c_str ( ) , f . view ( ) . data ( ) , f . view ( ) . size ( ) ) )
2026-03-05 15:03:05 +03:00
throw std : : runtime_error ( " SDL_SaveFile failed with error: " + std : : string ( SDL_GetError ( ) ) ) ;
2026-05-18 09:45:17 +03:00
else if ( saveThumbnail ) {
2026-04-28 17:07:28 +03:00
Vector2f imageCenter { main . window . size . x ( ) * 0.5f , main . window . size . y ( ) * 0.5f } ;
float imageDim = std : : max ( main . window . size . x ( ) , main . window . size . y ( ) ) ;
Vector2f imageDimVec { imageDim * 0.5f , imageDim * 0.5f } ;
SCollision : : AABB < float > imageBounds { imageCenter - imageDimVec , imageCenter + imageDimVec } ;
world_take_screenshot ( main . world , {
. filePath = filePath . parent_path ( ) / ( filePath . stem ( ) . string ( ) + " .jpg " ) ,
. type = WorldScreenshotInfo : : ScreenshotType : : JPG ,
. imageSizePixels = { 512 , 512 } ,
. cameraCoords = drawData . cam . c ,
. imageBounds = imageBounds ,
. transparentBackground = false ,
. displayGrid = false
} ) ;
}
2025-07-06 20:43:53 +03:00
# endif
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : DESKTOP_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 ) {
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : WORLDFATAL , std : : string ( " Save error: " ) + e . what ( ) ) ;
2025-07-06 20:43:53 +03:00
}
}
2026-04-27 16:48:44 +03:00
void World : : load_empty_canvas ( const std : : optional < std : : filesystem : : path > & filePathEmptyAutoSaveDir ) {
2026-01-01 22:46:02 +03:00
gridMan . server_init_no_file ( ) ;
bMan . server_init_no_file ( ) ;
drawProg . server_init_no_file ( ) ;
canvasTheme . server_init_no_file ( ) ;
2026-04-27 16:48:44 +03:00
2026-05-13 14:31:35 +03:00
if ( filePathEmptyAutoSaveDir . has_value ( ) ) {
bool oldSaveThumbnail = true ;
saveThumbnail = false ;
2026-04-27 16:48:44 +03:00
autosave_to_directory ( filePathEmptyAutoSaveDir . value ( ) ) ;
2026-05-13 14:31:35 +03:00
saveThumbnail = oldSaveThumbnail ;
}
2026-01-01 22:46:02 +03:00
}
2025-11-21 08:35:47 +03:00
void World : : load_from_file ( const std : : filesystem : : path & filePathToLoadFrom , std : : string_view buffer ) {
2026-03-05 15:03:05 +03:00
filePath = filePathToLoadFrom ;
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 ( ) ) ;
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : INFO , " Loading file from version " + version_numbers_to_version_str ( fileVersion ) ) ;
2025-11-17 16:08:42 +03:00
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
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : DESKTOP_USERINFO , " File loaded " ) ;
2025-07-06 20:43:53 +03:00
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 ) {
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : DESKTOP_USERINFO , " Canvas scaled up " ) ;
2026-05-30 16:28:22 +03:00
// drawProg will be sending info on committed objects/modified grids. These objects will NOT be scaled up.
// This means that the scale up message must be sent AFTER the World::scale_up function is called on our end
// if this client is the one responsible for the scale up
drawProg . scale_up ( scaleUpAmount ) ;
2025-09-30 11:45:03 +03:00
bMan . scale_up ( scaleUpAmount ) ;
gridMan . scale_up ( scaleUpAmount ) ;
drawData . cam . scale_up ( * this , 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-04-05 20:26:03 +03:00
void World : : set_has_unsaved_local_changes ( bool newHasUnsavedLocalChangesVal ) {
bool oldHasUnsavedLocalChanges = hasUnsavedLocalChanges ;
hasUnsavedLocalChanges = newHasUnsavedLocalChangesVal ;
if ( oldHasUnsavedLocalChanges ! = hasUnsavedLocalChanges )
main . g . gui . set_to_layout ( ) ;
}
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
}