2025-07-06 20:43:53 +03:00
# include "MainProgram.hpp"
2025-11-17 16:08:42 +03:00
# include "VersionConstants.hpp"
2025-11-20 12:38:28 +03:00
# include <SDL3/SDL_render.h>
# include <SDL3/SDL_video.h>
2025-07-06 20:43:53 +03:00
# include <chrono>
# include <include/core/SkAlphaType.h>
# include <include/core/SkColor.h>
# include <include/core/SkPaint.h>
# include <filesystem>
# include <iostream>
# include <include/core/SkFont.h>
# include <include/core/SkData.h>
# include <include/core/SkSurfaceProps.h>
# include "InputManager.hpp"
# include <cereal/types/vector.hpp>
# include <cereal/types/unordered_map.hpp>
# include <Eigen/Core>
# include <fstream>
# include <cereal/archives/json.hpp>
# include <Helpers/Serializers.hpp>
# include <nlohmann/json.hpp>
# include <include/core/SkSurface.h>
# ifdef USE_SKIA_BACKEND_GRAPHITE
# include <include/gpu/graphite/Surface.h>
# elif USE_SKIA_BACKEND_GANESH
# include <include/gpu/ganesh/GrDirectContext.h>
# include <include/gpu/ganesh/SkSurfaceGanesh.h>
# endif
# ifdef __EMSCRIPTEN__
# include <emscripten.h>
# endif
# include <Helpers/Logger.hpp>
2026-02-27 16:16:51 +03:00
# include "DrawCollision.hpp"
2025-07-06 20:43:53 +03:00
MainProgram : : MainProgram ( ) :
2026-03-01 17:28:43 +03:00
input ( * this ) ,
2025-11-16 10:42:01 +03:00
fonts ( std : : make_shared < FontData > ( ) ) ,
2025-09-06 19:06:30 +03:00
toolbar ( * this )
2025-07-06 20:43:53 +03:00
{
displayName = Random : : get ( ) . alphanumeric_str ( 10 ) ;
Logger : : get ( ) . add_log ( " WORLDFATAL " , [ & ] ( const std : : string & text ) {
* logFile < < " [WORLDFATAL] " < < text < < std : : endl ;
std : : cout < < " [WORLDFATAL] " < < text < < std : : endl ;
logMessages . emplace_front ( Toolbar : : LogMessage { text , Toolbar : : LogMessage : : COLOR_ERROR } ) ;
if ( logMessages . size ( ) = = LOG_SIZE )
logMessages . pop_back ( ) ;
} ) ;
Logger : : get ( ) . add_log ( " USERINFO " , [ & ] ( const std : : string & text ) {
* logFile < < " [USERINFO] " < < text < < std : : endl ;
std : : cout < < " [USERINFO] " < < text < < std : : endl ;
logMessages . emplace_front ( Toolbar : : LogMessage { text , Toolbar : : LogMessage : : COLOR_NORMAL } ) ;
if ( logMessages . size ( ) = = LOG_SIZE )
logMessages . pop_back ( ) ;
} ) ;
Logger : : get ( ) . add_log ( " CHAT " , [ & ] ( const std : : string & text ) {
* logFile < < " [CHAT] " < < text < < std : : endl ;
std : : cout < < " [CHAT] " < < text < < std : : endl ;
} ) ;
2025-10-16 21:22:08 +03:00
}
2025-07-06 20:43:53 +03:00
2025-10-16 21:22:08 +03:00
void MainProgram : : update ( ) {
2025-10-20 17:30:36 +03:00
while ( std : : chrono : : duration_cast < std : : chrono : : duration < float > > ( std : : chrono : : steady_clock : : now ( ) - lastFrameTime ) . count ( ) < 1.0 / fpsLimit ) ;
lastFrameTime = std : : chrono : : steady_clock : : now ( ) ;
2026-03-05 12:03:10 +03:00
input . update ( ) ;
2026-01-18 13:39:49 +03:00
for ( auto & s : setTabsToClose )
std : : erase ( worlds , s . lock ( ) ) ;
2025-10-20 17:30:36 +03:00
2025-12-09 17:20:27 +03:00
std : : erase_if ( worlds , [ & ] ( const std : : shared_ptr < World > & w ) {
return w - > setToDestroy ;
} ) ;
2025-10-20 17:30:36 +03:00
if ( worldIndex > = worlds . size ( ) )
worldIndex = worlds . size ( ) - 1 ;
if ( worlds . size ( ) = = 0 )
new_tab ( {
2026-01-01 22:46:02 +03:00
. isClient = false
2025-10-20 17:30:36 +03:00
} , true ) ;
std : : shared_ptr < World > oldWorld = world ;
world = worlds [ worldIndex ] ;
2026-02-28 22:47:35 +03:00
if ( oldWorld ! = world ) {
if ( oldWorld )
oldWorld - > on_tab_out ( ) ;
}
2025-10-20 17:30:36 +03:00
deltaTime . update_time_since ( ) ;
deltaTime . update_time_point ( ) ;
for ( auto & w : worlds ) {
if ( w = = world )
w - > focus_update ( ) ;
else
w - > unfocus_update ( ) ;
}
toolbar . update ( ) ; // GUI should be setup after the world data has been fully updated for this frame, so that the GUI reflects the current state of the world data
NetLibrary : : update ( ) ;
if ( tabSetToOpen ) {
tabSetToOpen = false ;
new_tab_open ( ) ;
}
2025-07-06 20:43:53 +03:00
return ;
}
2026-01-18 13:39:49 +03:00
bool MainProgram : : app_close_requested ( ) {
return toolbar . app_close_requested ( ) ;
}
2025-07-08 12:18:55 +03:00
void MainProgram : : update_display_names ( ) {
for ( auto & w : worlds ) {
2026-01-01 22:46:02 +03:00
if ( ! w - > netObjMan . is_connected ( ) )
2025-12-17 14:46:27 +03:00
w - > ownClientData - > set_display_name ( displayName ) ;
2025-07-08 12:18:55 +03:00
}
}
2025-07-06 20:43:53 +03:00
void MainProgram : : new_tab ( const World : : OpenWorldInfo & tabInfo , bool createSameThread ) {
newTabToOpenInfo = tabInfo ;
if ( createSameThread )
new_tab_open ( ) ;
else
tabSetToOpen = true ;
}
void MainProgram : : new_tab_open ( ) {
std : : unique_ptr < World > newWorld ;
try {
newWorld = std : : make_unique < World > ( * this , newTabToOpenInfo ) ;
}
catch ( const std : : runtime_error & e ) {
2025-11-21 08:35:47 +03:00
Logger : : get ( ) . log ( " WORLDFATAL " , " Failed to open canvas: " + ( newTabToOpenInfo . filePathSource . has_value ( ) ? newTabToOpenInfo . filePathSource . value ( ) . string ( ) : " NO PATH " ) + " with error: " + e . what ( ) ) ;
2025-07-06 20:43:53 +03:00
return ;
}
worlds . emplace_back ( std : : move ( newWorld ) ) ;
worldIndex = worlds . size ( ) - 1 ;
}
2026-01-18 13:39:49 +03:00
void MainProgram : : set_tab_to_close ( const std : : weak_ptr < World > & tabToClose ) {
setTabsToClose . emplace_back ( tabToClose ) ;
2025-07-06 20:43:53 +03:00
}
2025-10-27 20:19:47 +03:00
void MainProgram : : init_net_library ( ) {
NetLibrary : : init ( configPath / " p2p.json " ) ;
}
2025-07-06 20:43:53 +03:00
void MainProgram : : save_config ( ) {
std : : ofstream f ( configPath / " config.json " ) ;
if ( f . is_open ( ) ) {
using json = nlohmann : : json ;
json j ;
2025-11-17 16:08:42 +03:00
j [ " version " ] = VersionConstants : : CURRENT_VERSION_STRING ;
2025-07-06 20:43:53 +03:00
j [ " settings " ] = toolbar . get_config_json ( ) ;
j [ " window " ] [ " pos " ] = window . writtenPos ;
j [ " window " ] [ " size " ] = window . writtenSize ;
j [ " window " ] [ " maximized " ] = window . maximized ;
j [ " window " ] [ " fullscreen " ] = window . fullscreen ;
j [ " fileselectorpath " ] = toolbar . file_selector_path ( ) ;
2026-01-26 19:08:36 +03:00
j [ " toolConfig " ] = toolConfig ;
2025-07-06 20:43:53 +03:00
f < < std : : setw ( 4 ) < < j ;
f . close ( ) ;
}
toolbar . save_palettes ( ) ;
# ifdef __EMSCRIPTEN__
EM_ASM (
FS . syncfs ( false , ( err ) = > {
if ( err )
console . log ( " Error syncing IDBFS: " , err ) ;
else
console . log ( " Synced to IDBFS " ) ;
} ) ;
) ;
# endif
}
void MainProgram : : load_config ( ) {
std : : ifstream f ( configPath / " config.json " ) ;
toolbar . file_selector_path ( ) = homePath ;
if ( f . is_open ( ) ) {
using json = nlohmann : : json ;
2025-11-17 16:08:42 +03:00
2025-07-06 20:43:53 +03:00
try {
json j ;
f > > j ;
2025-11-17 16:08:42 +03:00
VersionNumber version ( 0 , 0 , 1 ) ;
try {
std : : string versionStr ;
j . at ( " version " ) . get_to ( versionStr ) ;
auto optVersion = version_str_to_version_numbers ( versionStr ) ;
if ( optVersion . has_value ( ) )
version = optVersion . value ( ) ;
}
catch ( . . . ) { }
2025-07-06 20:43:53 +03:00
try {
2025-11-17 16:08:42 +03:00
toolbar . set_config_json ( j [ " settings " ] , version ) ;
2025-07-06 20:43:53 +03:00
}
catch ( . . . ) { }
# ifndef __EMSCRIPTEN__
try {
j . at ( " window " ) . at ( " size " ) . get_to ( window . size ) ;
window . writtenSize = window . size ;
j . at ( " window " ) . at ( " pos " ) . get_to ( window . pos ) ;
window . writtenPos = window . pos ;
j . at ( " window " ) . at ( " maximized " ) . get_to ( window . maximized ) ;
j . at ( " window " ) . at ( " fullscreen " ) . get_to ( window . fullscreen ) ;
}
catch ( . . . ) { }
try {
j . at ( " fileselectorpath " ) . get_to ( toolbar . file_selector_path ( ) ) ;
}
catch ( . . . ) {
toolbar . file_selector_path ( ) = homePath ;
}
# endif
2026-01-26 19:08:36 +03:00
try {
j . at ( " toolConfig " ) . get_to ( toolConfig ) ;
}
catch ( . . . ) { }
2025-07-06 20:43:53 +03:00
f . close ( ) ;
} catch ( . . . ) { }
}
# ifdef __EMSCRIPTEN__
else
toolbar . viewWebVersionWelcome = true ;
# endif
toolbar . load_palettes ( ) ;
toolbar . load_theme ( ) ;
toolbar . load_licenses ( ) ;
2025-10-27 20:19:47 +03:00
NetLibrary : : copy_default_p2p_config_to_path ( configPath / " p2p.json " ) ;
2025-07-06 20:43:53 +03:00
}
2025-11-20 12:38:28 +03:00
void MainProgram : : set_vsync_value ( int vsyncValue ) {
if ( ! SDL_GL_SetSwapInterval ( vsyncValue ) ) {
Logger : : get ( ) . log ( " INFO " , " Vsync value " + std : : to_string ( vsyncValue ) + " not available. Setting to 1 " ) ;
2025-11-20 13:12:41 +03:00
if ( vsyncValue = = - 1 ) {
Logger : : get ( ) . log ( " USERINFO " , " Adaptive VSync not available. Setting Vsync to On " ) ;
vsyncValue = 1 ;
}
2025-11-20 12:38:28 +03:00
SDL_GL_SetSwapInterval ( 1 ) ;
}
window . vsyncValue = vsyncValue ;
}
2025-11-23 12:53:50 +03:00
void MainProgram : : update_scale_and_density ( ) {
2025-11-23 20:51:14 +03:00
# ifdef __EMSCRIPTEN__
window . scale = 1.0f ;
# else
2025-11-23 12:53:50 +03:00
window . scale = window . applyDisplayScale ? SDL_GetWindowDisplayScale ( window . sdlWindow ) : 1.0f ;
2025-11-23 20:51:14 +03:00
# endif
2025-11-23 12:53:50 +03:00
window . density = SDL_GetWindowPixelDensity ( window . sdlWindow ) ;
}
float MainProgram : : get_scale_and_density_factor_gui ( ) {
return window . scale * window . density ;
}
2026-02-25 13:26:35 +03:00
void MainProgram : : draw ( SkCanvas * canvas , std : : shared_ptr < World > worldToDraw , const DrawData & drawData ) {
canvas - > clear ( drawData . transparentBackground ? SkColor4f { 0.0f , 0.0f , 0.0f , 0.0f } : worldToDraw - > canvasTheme . get_back_color ( ) ) ;
worldToDraw - > draw ( canvas , drawData ) ;
if ( ! drawData . takingScreenshot )
toolbar . draw ( canvas ) ;
2025-07-06 20:43:53 +03:00
}
2026-02-16 21:27:42 +03:00
sk_sp < SkSurface > MainProgram : : create_native_surface ( Vector2i resolution , bool isMSAA ) {
# ifdef USE_BACKEND_OPENGLES_3_0
SkImageInfo imgInfo = SkImageInfo : : Make ( resolution . x ( ) , resolution . y ( ) , kRGBA_8888_SkColorType , kPremul_SkAlphaType ) ;
# else
SkImageInfo imgInfo = SkImageInfo : : MakeN32Premul ( resolution . x ( ) , resolution . y ( ) ) ;
# endif
SkSurfaceProps defaultProps ;
# ifdef USE_SKIA_BACKEND_GRAPHITE
auto surfaceToRet = SkSurfaces : : RenderTarget ( window . recorder ( ) , imgInfo , skgpu : : Mipmapped : : kNo , isMSAA ? & window . defaultMSAASurfaceProps : & defaultProps ) ;
# elif USE_SKIA_BACKEND_GANESH
auto surfaceToRet = SkSurfaces : : RenderTarget ( window . ctx . get ( ) , skgpu : : Budgeted : : kNo , imgInfo , isMSAA ? window . defaultMSAASampleCount : 0 , isMSAA ? & window . defaultMSAASurfaceProps : & defaultProps ) ;
# endif
if ( ! surfaceToRet )
throw std : : runtime_error ( " [MainProgram::create_native_surface] Could not make native surface " ) ;
return surfaceToRet ;
}
2026-03-04 11:03:05 +03:00
void MainProgram : : input_drop_file_callback ( const InputManager : : DropCallbackArgs & drop ) {
if ( std : : filesystem : : is_regular_file ( drop . data ) ) {
std : : filesystem : : path droppedFilePath ( drop . data ) ;
if ( droppedFilePath . has_extension ( ) & & droppedFilePath . extension ( ) . string ( ) = = std : : string ( " . " + World : : FILE_EXTENSION ) ) {
new_tab ( {
. isClient = false ,
. filePathSource = droppedFilePath
} , true ) ;
return ;
}
}
if ( world )
world - > input_drop_file_callback ( drop ) ;
}
void MainProgram : : input_drop_text_callback ( const InputManager : : DropCallbackArgs & drop ) {
if ( world )
world - > input_drop_text_callback ( drop ) ;
}
2026-03-02 16:36:34 +03:00
void MainProgram : : input_key_callback ( const InputManager : : KeyCallbackArgs & key ) {
switch ( key . key ) {
case InputManager : : KEY_NOGUI : {
if ( key . down & & ! key . repeat )
drawGui = ! drawGui ;
break ;
}
case InputManager : : KEY_FULLSCREEN : {
if ( key . down & & ! key . repeat )
drawGui = ! drawGui ;
}
}
2026-03-04 12:15:24 +03:00
toolbar . input_key_callback ( key ) ;
2026-03-02 16:36:34 +03:00
if ( world )
world - > input_key_callback ( key ) ;
}
void MainProgram : : input_mouse_button_callback ( const InputManager : : MouseButtonCallbackArgs & button ) {
2026-03-04 12:15:24 +03:00
toolbar . input_mouse_button_callback ( button ) ;
2026-03-02 16:36:34 +03:00
if ( world )
world - > input_mouse_button_callback ( button ) ;
}
void MainProgram : : input_mouse_motion_callback ( const InputManager : : MouseMotionCallbackArgs & motion ) {
2026-03-04 12:15:24 +03:00
toolbar . input_mouse_motion_callback ( motion ) ;
2026-03-02 16:36:34 +03:00
if ( world )
world - > input_mouse_motion_callback ( motion ) ;
}
2026-03-02 23:09:48 +03:00
void MainProgram : : input_mouse_wheel_callback ( const InputManager : : MouseWheelCallbackArgs & wheel ) {
2026-03-04 12:15:24 +03:00
toolbar . input_mouse_wheel_callback ( wheel ) ;
2026-03-02 23:09:48 +03:00
if ( world )
world - > input_mouse_wheel_callback ( wheel ) ;
}
2026-03-03 16:01:21 +03:00
void MainProgram : : input_pure_mouse_button_callback ( const InputManager : : MouseButtonCallbackArgs & button ) {
if ( world )
world - > input_pure_mouse_button_callback ( button ) ;
}
void MainProgram : : input_pure_mouse_motion_callback ( const InputManager : : MouseMotionCallbackArgs & motion ) {
if ( world )
world - > input_pure_mouse_motion_callback ( motion ) ;
}
2026-03-03 14:59:18 +03:00
void MainProgram : : input_pen_button_callback ( const InputManager : : PenButtonCallbackArgs & button ) {
2026-03-03 16:01:21 +03:00
if ( world )
world - > input_pen_button_callback ( button ) ;
2026-03-03 14:59:18 +03:00
}
void MainProgram : : input_pen_touch_callback ( const InputManager : : PenTouchCallbackArgs & touch ) {
2026-03-03 16:01:21 +03:00
if ( world )
world - > input_pen_touch_callback ( touch ) ;
2026-03-03 14:59:18 +03:00
}
void MainProgram : : input_pen_motion_callback ( const InputManager : : PenMotionCallbackArgs & motion ) {
2026-03-03 16:01:21 +03:00
if ( world )
world - > input_pen_motion_callback ( motion ) ;
}
void MainProgram : : input_pen_axis_callback ( const InputManager : : PenAxisCallbackArgs & axis ) {
if ( world )
world - > input_pen_axis_callback ( axis ) ;
2026-03-03 14:59:18 +03:00
}
2026-03-05 12:03:10 +03:00
void MainProgram : : input_multi_finger_touch_callback ( const InputManager : : MultiFingerTouchArgs & touch ) {
if ( world )
world - > input_multi_finger_touch_callback ( touch ) ;
}
void MainProgram : : input_multi_finger_motion_callback ( const InputManager : : MultiFingerMotionArgs & motion ) {
if ( world )
world - > input_multi_finger_motion_callback ( motion ) ;
}
2025-07-06 20:43:53 +03:00
bool MainProgram : : network_being_used ( ) {
for ( auto & w : worlds ) {
2026-01-01 22:46:02 +03:00
if ( w - > netObjMan . is_connected ( ) )
2025-07-06 20:43:53 +03:00
return true ;
}
return false ;
}
bool MainProgram : : net_server_hosted ( ) {
for ( auto & w : worlds ) {
2026-01-01 22:46:02 +03:00
if ( w - > netServer )
2025-07-06 20:43:53 +03:00
return true ;
}
return false ;
}
void MainProgram : : early_destroy ( ) {
for ( auto & w : worlds )
w - > early_destroy ( ) ;
}
MainProgram : : ~ MainProgram ( ) {
NetLibrary : : destroy ( ) ;
}