├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── asset_manager ├── ntshengn_asset_manager.cpp └── ntshengn_asset_manager.h ├── ecs ├── ntshengn_ecs.cpp └── ntshengn_ecs.h ├── exception_handler ├── ntshengn_exception_handler.h ├── ntshengn_exception_handler_linux_freebsd.cpp ├── ntshengn_exception_handler_linux_freebsd.h ├── ntshengn_exception_handler_windows.cpp └── ntshengn_exception_handler_windows.h ├── frame_limiter ├── ntshengn_frame_limiter.cpp └── ntshengn_frame_limiter.h ├── job_system ├── ntshengn_job_system.cpp └── ntshengn_job_system.h ├── main.cpp ├── module_loader ├── ntshengn_module_loader.h ├── ntshengn_module_loader_linux_freebsd.h └── ntshengn_module_loader_windows.h ├── networking ├── ntshengn_networking.cpp ├── ntshengn_networking.h ├── ntshengn_networking_client_socket.cpp ├── ntshengn_networking_client_socket.h ├── ntshengn_networking_server_socket.cpp └── ntshengn_networking_server_socket.h ├── ntshengn_core.cpp ├── ntshengn_core.h ├── profiler ├── ntshengn_profiler.cpp └── ntshengn_profiler.h ├── scene_manager ├── ntshengn_scene_manager.cpp └── ntshengn_scene_manager.h ├── script_manager_loader ├── ntshengn_script_manager_loader.h ├── ntshengn_script_manager_loader_linux_freebsd.h └── ntshengn_script_manager_loader_windows.h ├── scripting ├── ntshengn_scripting.cpp └── ntshengn_scripting.h └── utils └── ntshengn_core_defines.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | coverage*[.json, .xml, .info] 146 | 147 | # Visual Studio code coverage results 148 | *.coverage 149 | *.coveragexml 150 | 151 | # NCrunch 152 | _NCrunch_* 153 | .*crunch*.local.xml 154 | nCrunchTemp_* 155 | 156 | # MightyMoose 157 | *.mm.* 158 | AutoTest.Net/ 159 | 160 | # Web workbench (sass) 161 | .sass-cache/ 162 | 163 | # Installshield output folder 164 | [Ee]xpress/ 165 | 166 | # DocProject is a documentation generator add-in 167 | DocProject/buildhelp/ 168 | DocProject/Help/*.HxT 169 | DocProject/Help/*.HxC 170 | DocProject/Help/*.hhc 171 | DocProject/Help/*.hhk 172 | DocProject/Help/*.hhp 173 | DocProject/Help/Html2 174 | DocProject/Help/html 175 | 176 | # Click-Once directory 177 | publish/ 178 | 179 | # Publish Web Output 180 | *.[Pp]ublish.xml 181 | *.azurePubxml 182 | # Note: Comment the next line if you want to checkin your web deploy settings, 183 | # but database connection strings (with potential passwords) will be unencrypted 184 | *.pubxml 185 | *.publishproj 186 | 187 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 188 | # checkin your Azure Web App publish settings, but sensitive information contained 189 | # in these scripts will be unencrypted 190 | PublishScripts/ 191 | 192 | # NuGet Packages 193 | *.nupkg 194 | # NuGet Symbol Packages 195 | *.snupkg 196 | # The packages folder can be ignored because of Package Restore 197 | **/[Pp]ackages/* 198 | # except build/, which is used as an MSBuild target. 199 | !**/[Pp]ackages/build/ 200 | # Uncomment if necessary however generally it will be regenerated when needed 201 | #!**/[Pp]ackages/repositories.config 202 | # NuGet v3's project.json files produces more ignorable files 203 | *.nuget.props 204 | *.nuget.targets 205 | 206 | # Microsoft Azure Build Output 207 | csx/ 208 | *.build.csdef 209 | 210 | # Microsoft Azure Emulator 211 | ecf/ 212 | rcf/ 213 | 214 | # Windows Store app package directories and files 215 | AppPackages/ 216 | BundleArtifacts/ 217 | Package.StoreAssociation.xml 218 | _pkginfo.txt 219 | *.appx 220 | *.appxbundle 221 | *.appxupload 222 | 223 | # Visual Studio cache files 224 | # files ending in .cache can be ignored 225 | *.[Cc]ache 226 | # but keep track of directories ending in .cache 227 | !?*.[Cc]ache/ 228 | 229 | # Others 230 | ClientBin/ 231 | ~$* 232 | *~ 233 | *.dbmdl 234 | *.dbproj.schemaview 235 | *.jfm 236 | *.pfx 237 | *.publishsettings 238 | orleans.codegen.cs 239 | 240 | # Including strong name files can present a security risk 241 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 242 | #*.snk 243 | 244 | # Since there are multiple workflows, uncomment next line to ignore bower_components 245 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 246 | #bower_components/ 247 | 248 | # RIA/Silverlight projects 249 | Generated_Code/ 250 | 251 | # Backup & report files from converting an old project file 252 | # to a newer Visual Studio version. Backup files are not needed, 253 | # because we have git ;-) 254 | _UpgradeReport_Files/ 255 | Backup*/ 256 | UpgradeLog*.XML 257 | UpgradeLog*.htm 258 | ServiceFabricBackup/ 259 | *.rptproj.bak 260 | 261 | # SQL Server files 262 | *.mdf 263 | *.ldf 264 | *.ndf 265 | 266 | # Business Intelligence projects 267 | *.rdl.data 268 | *.bim.layout 269 | *.bim_*.settings 270 | *.rptproj.rsuser 271 | *- [Bb]ackup.rdl 272 | *- [Bb]ackup ([0-9]).rdl 273 | *- [Bb]ackup ([0-9][0-9]).rdl 274 | 275 | # Microsoft Fakes 276 | FakesAssemblies/ 277 | 278 | # GhostDoc plugin setting file 279 | *.GhostDoc.xml 280 | 281 | # Node.js Tools for Visual Studio 282 | .ntvs_analysis.dat 283 | node_modules/ 284 | 285 | # Visual Studio 6 build log 286 | *.plg 287 | 288 | # Visual Studio 6 workspace options file 289 | *.opt 290 | 291 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 292 | *.vbw 293 | 294 | # Visual Studio LightSwitch build output 295 | **/*.HTMLClient/GeneratedArtifacts 296 | **/*.DesktopClient/GeneratedArtifacts 297 | **/*.DesktopClient/ModelManifest.xml 298 | **/*.Server/GeneratedArtifacts 299 | **/*.Server/ModelManifest.xml 300 | _Pvt_Extensions 301 | 302 | # Paket dependency manager 303 | .paket/paket.exe 304 | paket-files/ 305 | 306 | # FAKE - F# Make 307 | .fake/ 308 | 309 | # CodeRush personal settings 310 | .cr/personal 311 | 312 | # Python Tools for Visual Studio (PTVS) 313 | __pycache__/ 314 | *.pyc 315 | 316 | # Cake - Uncomment if you are using it 317 | # tools/** 318 | # !tools/packages.config 319 | 320 | # Tabs Studio 321 | *.tss 322 | 323 | # Telerik's JustMock configuration file 324 | *.jmconfig 325 | 326 | # BizTalk build output 327 | *.btp.cs 328 | *.btm.cs 329 | *.odx.cs 330 | *.xsd.cs 331 | 332 | # OpenCover UI analysis results 333 | OpenCover/ 334 | 335 | # Azure Stream Analytics local run output 336 | ASALocalRun/ 337 | 338 | # MSBuild Binary and Structured Log 339 | *.binlog 340 | 341 | # NVidia Nsight GPU debugger configuration file 342 | *.nvuser 343 | 344 | # MFractors (Xamarin productivity tool) working folder 345 | .mfractor/ 346 | 347 | # Local History for Visual Studio 348 | .localhistory/ 349 | 350 | # BeatPulse healthcheck temp database 351 | healthchecksdb 352 | 353 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 354 | MigrationBackup/ 355 | 356 | # Ionide (cross platform F# VS Code tools) working folder 357 | .ionide/ 358 | 359 | # Fody - auto-generated XML schema 360 | FodyWeavers.xsd 361 | 362 | # NutshellEngine specific ignore 363 | /build/ 364 | /Common/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | set(NAME NutshellEngine) 3 | project(${NAME} VERSION 0.0.1 LANGUAGES C CXX) 4 | 5 | include(FetchContent) 6 | 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | 10 | set(NTSHENGN_DEFINES "") 11 | set(NTSHENGN_LIBRARIES "") 12 | 13 | if(CMAKE_SYSTEM_NAME STREQUAL "Linux") 14 | list(APPEND NTSHENGN_LIBRARIES ${CMAKE_DL_LIBS}) 15 | endif() 16 | 17 | set(NTSHENGN_COMMON_PATH "NtshEngn_Common_NOTFOUND" CACHE PATH "The directory containing NutshellEngine's common resources.") 18 | if(NOT ${NTSHENGN_COMMON_PATH} STREQUAL "NtshEngn_Common_NOTFOUND" AND EXISTS ${NTSHENGN_COMMON_PATH}) 19 | message(STATUS "Copy NutshellEngine\'s common resources from ${NTSHENGN_COMMON_PATH} to ${CMAKE_CURRENT_SOURCE_DIR}/Common") 20 | file(COPY ${NTSHENGN_COMMON_PATH}/ DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/Common) 21 | 22 | add_subdirectory(Common) 23 | else() 24 | message(STATUS "Copy NutshellEngine\'s common resources from Git repository to ${CMAKE_CURRENT_SOURCE_DIR}/Common") 25 | FetchContent_Declare( 26 | Common 27 | GIT_REPOSITORY https://github.com/Team-Nutshell/NutshellEngine-Common 28 | GIT_TAG main 29 | SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Common" 30 | ) 31 | 32 | FetchContent_MakeAvailable(Common) 33 | endif() 34 | 35 | set(ASSET_MANAGER_SOURCES 36 | asset_manager/ntshengn_asset_manager.cpp) 37 | set(ASSET_MANAGER_HEADERS 38 | asset_manager/ntshengn_asset_manager.h) 39 | 40 | set(CORE_SOURCES 41 | ntshengn_core.cpp) 42 | set(CORE_HEADERS 43 | ntshengn_core.h) 44 | 45 | set(ECS_SOURCES 46 | ecs/ntshengn_ecs.cpp) 47 | set(ECS_HEADERS 48 | ecs/ntshengn_ecs.h) 49 | 50 | set(EXCEPTION_HANDLER_SOURCES "") 51 | set(EXCEPTION_HANDLER_HEADERS 52 | exception_handler/ntshengn_exception_handler.h) 53 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 54 | list(APPEND EXCEPTION_HANDLER_SOURCES 55 | exception_handler/ntshengn_exception_handler_windows.cpp) 56 | list(APPEND EXCEPTION_HANDLER_HEADERS 57 | exception_handler/ntshengn_exception_handler_windows.h) 58 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") 59 | list(APPEND EXCEPTION_HANDLER_SOURCES 60 | exception_handler/ntshengn_exception_handler_linux_freebsd.cpp) 61 | list(APPEND EXCEPTION_HANDLER_HEADERS 62 | exception_handler/ntshengn_exception_handler_linux_freebsd.h) 63 | endif() 64 | 65 | set(FRAME_LIMITER_SOURCES 66 | frame_limiter/ntshengn_frame_limiter.cpp) 67 | set(FRAME_LIMITER_HEADERS 68 | frame_limiter/ntshengn_frame_limiter.h) 69 | 70 | set(JOB_SYSTEM_SOURCES 71 | job_system/ntshengn_job_system.cpp) 72 | set(JOB_SYSTEM_HEADERS 73 | job_system/ntshengn_job_system.h) 74 | 75 | set(MODULE_LOADER_HEADERS 76 | module_loader/ntshengn_module_loader.h) 77 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 78 | list(APPEND MODULE_LOADER_HEADERS 79 | module_loader/ntshengn_module_loader_windows.h) 80 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") 81 | list(APPEND MODULE_LOADER_HEADERS 82 | module_loader/ntshengn_module_loader_linux_freebsd.h) 83 | endif() 84 | 85 | set(NETWORKING_SOURCES 86 | networking/ntshengn_networking.cpp 87 | networking/ntshengn_networking_client_socket.cpp 88 | networking/ntshengn_networking_server_socket.cpp) 89 | set(NETWORKING_HEADERS 90 | networking/ntshengn_networking.h 91 | networking/ntshengn_networking_client_socket.h 92 | networking/ntshengn_networking_server_socket.h) 93 | 94 | set(PROFILER_SOURCES 95 | profiler/ntshengn_profiler.cpp) 96 | set(PROFILER_HEADERS 97 | profiler/ntshengn_profiler.h) 98 | 99 | set(SCENE_MANAGER_SOURCES 100 | scene_manager/ntshengn_scene_manager.cpp) 101 | set(SCENE_MANAGER_HEADERS 102 | scene_manager/ntshengn_scene_manager.h) 103 | 104 | set(SCRIPT_MANAGER_LOADER_HEADERS 105 | script_manager_loader/ntshengn_script_manager_loader.h) 106 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 107 | list(APPEND SCRIPT_MANAGER_LOADER_HEADERS 108 | script_manager_loader/ntshengn_script_manager_loader_windows.h) 109 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") 110 | list(APPEND SCRIPT_MANAGER_LOADER_HEADERS 111 | script_manager_loader/ntshengn_script_manager_loader_linux_freebsd.h) 112 | endif() 113 | 114 | set(SCRIPTING_SOURCES 115 | scripting/ntshengn_scripting.cpp) 116 | set(SCRIPTING_HEADERS 117 | scripting/ntshengn_scripting.h) 118 | 119 | set(UTILS_HEADERS 120 | utils/ntshengn_core_defines.h) 121 | 122 | set(SOURCES ${ASSET_MANAGER_SOURCES} ${CORE_SOURCES} ${ECS_SOURCES} ${EXCEPTION_HANDLER_SOURCES} ${FRAME_LIMITER_SOURCES} ${JOB_SYSTEM_SOURCES} ${NETWORKING_SOURCES} ${PROFILER_SOURCES} ${SCENE_MANAGER_SOURCES} ${SCRIPTING_SOURCES}) 123 | set(HEADERS ${ASSET_MANAGER_HEADERS} ${CORE_HEADERS} ${ECS_HEADERS} ${EXCEPTION_HANDLER_HEADERS} ${FRAME_LIMITER_HEADERS} ${JOB_SYSTEM_HEADERS} ${MODULE_LOADER_HEADERS} ${NETWORKING_HEADERS} ${PROFILER_HEADERS} ${SCENE_MANAGER_HEADERS} ${SCRIPT_MANAGER_LOADER_HEADERS} ${SCRIPTING_HEADERS} ${UTILS_HEADERS}) 124 | 125 | add_executable(${PROJECT_NAME} main.cpp ${SOURCES} ${HEADERS}) 126 | 127 | target_link_libraries(${PROJECT_NAME} 128 | PUBLIC 129 | NutshellEngine::Common 130 | ${NTSHENGN_LIBRARIES}) 131 | 132 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 133 | target_link_libraries(${PROJECT_NAME} 134 | PRIVATE 135 | Dbghelp.lib) 136 | endif() 137 | 138 | if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") 139 | set(THREADS_PREFER_PTHREAD_FLAG TRUE) 140 | find_package(Threads REQUIRED) 141 | target_link_libraries(${PROJECT_NAME} 142 | PRIVATE 143 | Threads::Threads) 144 | endif() 145 | 146 | target_compile_options(${PROJECT_NAME} 147 | PUBLIC 148 | $<$:/W4 /WX> 149 | $<$>:-Wall -Werror>) 150 | 151 | target_compile_definitions(${PROJECT_NAME} 152 | PUBLIC 153 | ${NTSHENGN_DEFINES} 154 | $<$:NTSHENGN_DEBUG> 155 | $<$:NTSHENGN_RELEASE> 156 | $<$:NTSHENGN_RELEASE> 157 | $<$:NTSHENGN_DEBUG>) 158 | 159 | set_target_properties(${PROJECT_NAME} 160 | PROPERTIES 161 | VS_DEBUGGER_WORKING_DIRECTORY "$(OutDir)") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 Team Nutshell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NutshellEngine 2 | ![NutshellEngine](https://www.team-nutshell.dev/nutshellengine/assets/images/nutshellengine-logo-full-dark-theme.png) 3 | 4 | **NutshellEngine** is a **modular game engine** designed to **enable game and engine developers to work together** efficiently by providing **easy to replace modules** implementing the game engine’s principal systems such as rendering, physics and audio. 5 | 6 | This repository contains **NutshellEngine's core**. 7 | 8 | ## [**NutshellEngine's website**](https://www.team-nutshell.dev/nutshellengine/) 9 | ## [**NutshellEngine's documentation**](https://www.team-nutshell.dev/nutshellengine-docs/) 10 | 11 | ## NutshellEngine's repositories 12 | - [**NutshellEngine**](https://github.com/Team-Nutshell/NutshellEngine): Core of NutshellEngine, manages the modules and various systems. 13 | - [**Application**](https://github.com/Team-Nutshell/NutshellEngine-Application): Base of NutshellEngine's applications. 14 | - [**Editor**](https://github.com/Team-Nutshell/NutshellEngine-Editor): NutshellEngine's editor. 15 | - [**Graphics Module**](https://github.com/Team-Nutshell/NutshellEngine-GraphicsModule): Module managing NutshellEngine's graphics engine. 16 | - [**Physics Module**](https://github.com/Team-Nutshell/NutshellEngine-PhysicsModule): Module managing NutshellEngine's physics engine. 17 | - [**Window Module**](https://github.com/Team-Nutshell/NutshellEngine-WindowModule): Module managing NutshellEngine's windows and inputs. 18 | - [**Audio Module**](https://github.com/Team-Nutshell/NutshellEngine-AudioModule): Module managing NutshellEngine's audio system. 19 | - [**Asset Loader Module**](https://github.com/Team-Nutshell/NutshellEngine-AssetLoaderModule): Module loading assets used by NutshellEngine's applications and modules. -------------------------------------------------------------------------------- /asset_manager/ntshengn_asset_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_asset_manager.h" 2 | #include "../utils/ntshengn_defines.h" 3 | #include "../utils/ntshengn_utils_bimap.h" 4 | #include "../utils/ntshengn_utils_file.h" 5 | #include "../utils/ntshengn_utils_json.h" 6 | #include "../utils/ntshengn_utils_math.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | NtshEngn::Model* NtshEngn::AssetManager::createModel(const std::string& modelName) { 16 | if (m_models.find(modelName) == m_models.end()) { 17 | Model newModel; 18 | m_models[modelName] = newModel; 19 | Model* model = &m_models[modelName]; 20 | 21 | m_modelNames[model] = modelName; 22 | 23 | return model; 24 | } 25 | else { 26 | NTSHENGN_ASSET_MANAGER_WARNING("Model name \"" + modelName + "\" already exists."); 27 | 28 | return nullptr; 29 | } 30 | } 31 | 32 | NtshEngn::Model* NtshEngn::AssetManager::loadModel(const std::string& filePath) { 33 | if (!std::filesystem::exists(filePath)) { 34 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load model file \"" + filePath + "\" (file does not exist)."); 35 | 36 | return nullptr; 37 | } 38 | 39 | std::string normalizedPath = getNormalizedPath(filePath); 40 | 41 | if (m_models.find(normalizedPath) != m_models.end()) { 42 | if (m_modelLastModified.find(normalizedPath) != m_modelLastModified.end()) { 43 | if (m_modelLastModified[normalizedPath] == std::filesystem::last_write_time(filePath)) { 44 | return &m_models[normalizedPath]; 45 | } 46 | } 47 | } 48 | 49 | Model newModel; 50 | if (File::extension(filePath) == "ntmd") { 51 | loadModelNtmd(filePath, newModel); 52 | } 53 | else { 54 | if (m_assetLoaderModule) { 55 | newModel = m_assetLoaderModule->loadModel(filePath); 56 | } 57 | } 58 | 59 | if (newModel.primitives.size() != 0) { 60 | m_models[normalizedPath] = newModel; 61 | Model* model = &m_models[normalizedPath]; 62 | 63 | m_modelNames[model] = normalizedPath; 64 | m_modelLastModified[normalizedPath] = std::filesystem::last_write_time(filePath); 65 | 66 | return model; 67 | } 68 | else { 69 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load model file \"" + filePath + "\"."); 70 | 71 | return nullptr; 72 | } 73 | } 74 | 75 | NtshEngn::Material* NtshEngn::AssetManager::createMaterial(const std::string& materialName) { 76 | if (m_materials.find(materialName) == m_materials.end()) { 77 | Material newMaterial; 78 | m_materials[materialName] = newMaterial; 79 | Material* material = &m_materials[materialName]; 80 | 81 | m_materialNames[material] = materialName; 82 | 83 | return material; 84 | } 85 | else { 86 | NTSHENGN_ASSET_MANAGER_WARNING("Material name \"" + materialName + "\" already exists."); 87 | 88 | return nullptr; 89 | } 90 | } 91 | 92 | NtshEngn::Material* NtshEngn::AssetManager::loadMaterial(const std::string& filePath) { 93 | if (!std::filesystem::exists(filePath)) { 94 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load material file \"" + filePath + "\" (file does not exist)."); 95 | 96 | return nullptr; 97 | } 98 | 99 | std::string normalizedPath = getNormalizedPath(filePath); 100 | 101 | if (m_materials.find(normalizedPath) != m_materials.end()) { 102 | if (m_materialLastModified.find(normalizedPath) != m_materialLastModified.end()) { 103 | if (m_materialLastModified[normalizedPath] == std::filesystem::last_write_time(filePath)) { 104 | return &m_materials[normalizedPath]; 105 | } 106 | } 107 | } 108 | 109 | Material newMaterial; 110 | if (File::extension(filePath) == "ntml") { 111 | loadMaterialNtml(filePath, newMaterial); 112 | } 113 | else { 114 | if (m_assetLoaderModule) { 115 | newMaterial = m_assetLoaderModule->loadMaterial(filePath); 116 | } 117 | } 118 | 119 | m_materials[normalizedPath] = newMaterial; 120 | Material* material = &m_materials[normalizedPath]; 121 | 122 | m_materialNames[material] = normalizedPath; 123 | m_materialLastModified[normalizedPath] = std::filesystem::last_write_time(filePath); 124 | 125 | return material; 126 | } 127 | 128 | NtshEngn::Image* NtshEngn::AssetManager::createImage(const std::string& imageName) { 129 | if (m_images.find(imageName) == m_images.end()) { 130 | Image newImage; 131 | m_images[imageName] = newImage; 132 | Image* image = &m_images[imageName]; 133 | 134 | m_imageNames[image] = imageName; 135 | 136 | return image; 137 | } 138 | else { 139 | NTSHENGN_ASSET_MANAGER_WARNING("Image name \"" + imageName + "\" already exists."); 140 | 141 | return nullptr; 142 | } 143 | } 144 | 145 | NtshEngn::Image* NtshEngn::AssetManager::loadImage(const std::string& filePath) { 146 | if (!std::filesystem::exists(filePath)) { 147 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load image file \"" + filePath + "\" (file does not exist)."); 148 | 149 | return nullptr; 150 | } 151 | 152 | std::string normalizedPath = getNormalizedPath(filePath); 153 | 154 | if (m_images.find(normalizedPath) != m_images.end()) { 155 | if (m_imageLastModified.find(normalizedPath) != m_imageLastModified.end()) { 156 | if (m_imageLastModified[normalizedPath] == std::filesystem::last_write_time(filePath)) { 157 | return &m_images[normalizedPath]; 158 | } 159 | } 160 | } 161 | 162 | Image newImage; 163 | if (File::extension(filePath) == "ntim") { 164 | loadImageNtim(filePath, newImage); 165 | } 166 | else { 167 | if (m_assetLoaderModule) { 168 | newImage = m_assetLoaderModule->loadImage(filePath); 169 | } 170 | } 171 | 172 | if (newImage.width != 0) { 173 | m_images[normalizedPath] = newImage; 174 | Image* image = &m_images[normalizedPath]; 175 | 176 | m_imageNames[image] = normalizedPath; 177 | m_imageLastModified[normalizedPath] = std::filesystem::last_write_time(filePath); 178 | 179 | return image; 180 | } 181 | else { 182 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load image file \"" + filePath + "\"."); 183 | 184 | return nullptr; 185 | } 186 | } 187 | 188 | NtshEngn::Font* NtshEngn::AssetManager::createFont(const std::string& fontName) { 189 | if (m_fonts.find(fontName) == m_fonts.end()) { 190 | Font newFont; 191 | m_fonts[fontName] = newFont; 192 | Font* font = &m_fonts[fontName]; 193 | 194 | m_fontNames[font] = fontName; 195 | 196 | return font; 197 | } 198 | else { 199 | NTSHENGN_ASSET_MANAGER_WARNING("Image name \"" + fontName + "\" already exists."); 200 | 201 | return nullptr; 202 | } 203 | } 204 | 205 | NtshEngn::Font* NtshEngn::AssetManager::loadFont(const std::string& filePath, float fontHeight) { 206 | if (!std::filesystem::exists(filePath)) { 207 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load font file \"" + filePath + "\" (file does not exist)."); 208 | 209 | return nullptr; 210 | } 211 | 212 | if (fontHeight <= 0.0f) { 213 | NTSHENGN_ASSET_MANAGER_WARNING("Font height for font file \"" + filePath + "\" should be greater than 0."); 214 | 215 | return nullptr; 216 | } 217 | 218 | std::string normalizedPath = getNormalizedPath(filePath); 219 | 220 | if (m_fonts.find(normalizedPath) != m_fonts.end()) { 221 | if (m_fontLastModified.find(normalizedPath) != m_fontLastModified.end()) { 222 | if (m_fontLastModified[normalizedPath] == std::filesystem::last_write_time(filePath)) { 223 | return &m_fonts[normalizedPath]; 224 | } 225 | } 226 | } 227 | 228 | Font newFont; 229 | if (m_assetLoaderModule) { 230 | newFont = m_assetLoaderModule->loadFont(filePath, fontHeight); 231 | } 232 | 233 | if (newFont.image) { 234 | m_fonts[normalizedPath] = newFont; 235 | Font* font = &m_fonts[normalizedPath]; 236 | 237 | m_fontNames[font] = normalizedPath; 238 | m_fontLastModified[normalizedPath] = std::filesystem::last_write_time(filePath); 239 | 240 | return font; 241 | } 242 | else { 243 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load font file \"" + filePath + "\"."); 244 | 245 | return nullptr; 246 | } 247 | } 248 | 249 | NtshEngn::Sound* NtshEngn::AssetManager::createSound(const std::string& soundName) { 250 | if (m_sounds.find(soundName) == m_sounds.end()) { 251 | Sound newSound; 252 | m_sounds[soundName] = newSound; 253 | Sound* sound = &m_sounds[soundName]; 254 | 255 | m_soundNames[sound] = soundName; 256 | 257 | return sound; 258 | } 259 | else { 260 | NTSHENGN_ASSET_MANAGER_WARNING("Sound name \"" + soundName + "\" already exists."); 261 | 262 | return nullptr; 263 | } 264 | } 265 | 266 | NtshEngn::Sound* NtshEngn::AssetManager::loadSound(const std::string& filePath) { 267 | if (!std::filesystem::exists(filePath)) { 268 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load sound file \"" + filePath + "\" (file does not exist)."); 269 | 270 | return nullptr; 271 | } 272 | 273 | std::string normalizedPath = getNormalizedPath(filePath); 274 | 275 | if (m_sounds.find(normalizedPath) != m_sounds.end()) { 276 | if (m_soundLastModified.find(normalizedPath) != m_soundLastModified.end()) { 277 | if (m_soundLastModified[normalizedPath] == std::filesystem::last_write_time(filePath)) { 278 | return &m_sounds[normalizedPath]; 279 | } 280 | } 281 | } 282 | 283 | Sound newSound; 284 | if (File::extension(filePath) == "ntsd") { 285 | loadSoundNtsd(filePath, newSound); 286 | } 287 | else { 288 | if (m_assetLoaderModule) { 289 | newSound = m_assetLoaderModule->loadSound(filePath); 290 | } 291 | } 292 | 293 | if (newSound.size != 0) { 294 | m_sounds[normalizedPath] = newSound; 295 | Sound* sound = &m_sounds[normalizedPath]; 296 | 297 | m_soundNames[sound] = normalizedPath; 298 | m_soundLastModified[normalizedPath] = std::filesystem::last_write_time(filePath); 299 | 300 | return sound; 301 | } 302 | else { 303 | NTSHENGN_ASSET_MANAGER_WARNING("Could not load sound file \"" + filePath + "\"."); 304 | 305 | return nullptr; 306 | } 307 | } 308 | 309 | void NtshEngn::AssetManager::destroyModel(const std::string& modelName) { 310 | if (m_models.find(modelName) != m_models.end()) { 311 | m_modelNames.erase(&m_models[modelName]); 312 | if (m_modelLastModified.find(modelName) != m_modelLastModified.end()) { 313 | m_modelLastModified.erase(modelName); 314 | } 315 | m_models.erase(modelName); 316 | } 317 | else { 318 | NTSHENGN_ASSET_MANAGER_WARNING("Could not destroy model named \"" + modelName + "\" (name not found)."); 319 | } 320 | } 321 | 322 | void NtshEngn::AssetManager::destroyMaterial(const std::string& materialName) { 323 | if (m_materials.find(materialName) != m_materials.end()) { 324 | m_materialNames.erase(&m_materials[materialName]); 325 | if (m_materialLastModified.find(materialName) != m_materialLastModified.end()) { 326 | m_materialLastModified.erase(materialName); 327 | } 328 | m_materials.erase(materialName); 329 | } 330 | else { 331 | NTSHENGN_ASSET_MANAGER_WARNING("Could not destroy material named \"" + materialName + "\" (name not found)."); 332 | } 333 | } 334 | 335 | void NtshEngn::AssetManager::destroyImage(const std::string& imageName) { 336 | if (m_images.find(imageName) != m_images.end()) { 337 | m_imageNames.erase(&m_images[imageName]); 338 | if (m_imageLastModified.find(imageName) != m_imageLastModified.end()) { 339 | m_imageLastModified.erase(imageName); 340 | } 341 | m_images.erase(imageName); 342 | } 343 | else { 344 | NTSHENGN_ASSET_MANAGER_WARNING("Could not destroy image named \"" + imageName + "\" (name not found)."); 345 | } 346 | } 347 | 348 | void NtshEngn::AssetManager::destroyFont(const std::string& fontName) { 349 | if (m_fonts.find(fontName) != m_fonts.end()) { 350 | m_fontNames.erase(&m_fonts[fontName]); 351 | if (m_fontLastModified.find(fontName) != m_fontLastModified.end()) { 352 | m_fontLastModified.erase(fontName); 353 | } 354 | m_fonts.erase(fontName); 355 | } 356 | else { 357 | NTSHENGN_ASSET_MANAGER_WARNING("Could not destroy font named \"" + fontName + "\" (name not found)."); 358 | } 359 | } 360 | 361 | void NtshEngn::AssetManager::destroySound(const std::string& soundName) { 362 | if (m_sounds.find(soundName) != m_sounds.end()) { 363 | m_soundNames.erase(&m_sounds[soundName]); 364 | if (m_soundLastModified.find(soundName) != m_soundLastModified.end()) { 365 | m_soundLastModified.erase(soundName); 366 | } 367 | m_sounds.erase(soundName); 368 | } 369 | else { 370 | NTSHENGN_ASSET_MANAGER_WARNING("Could not destroy sound named \"" + soundName + "\" (name not found)."); 371 | } 372 | } 373 | 374 | NtshEngn::Model* NtshEngn::AssetManager::findModelByName(const std::string& modelName) { 375 | if (m_models.find(modelName) != m_models.end()) { 376 | return &m_models[modelName]; 377 | } 378 | else { 379 | return nullptr; 380 | } 381 | } 382 | 383 | std::string NtshEngn::AssetManager::getModelName(const Model* model) { 384 | if (m_modelNames.find(const_cast(model)) != m_modelNames.end()) { 385 | return m_modelNames[const_cast(model)]; 386 | } 387 | else { 388 | return ""; 389 | } 390 | } 391 | 392 | NtshEngn::Material* NtshEngn::AssetManager::findMaterialByName(const std::string& materialName) { 393 | if (m_materials.find(materialName) != m_materials.end()) { 394 | return &m_materials[materialName]; 395 | } 396 | else { 397 | return nullptr; 398 | } 399 | } 400 | 401 | std::string NtshEngn::AssetManager::getMaterialName(const Material* material) { 402 | if (m_materialNames.find(const_cast(material)) != m_materialNames.end()) { 403 | return m_materialNames[const_cast(material)]; 404 | } 405 | else { 406 | return ""; 407 | } 408 | } 409 | 410 | NtshEngn::Image* NtshEngn::AssetManager::findImageByName(const std::string& imageName) { 411 | if (m_images.find(imageName) != m_images.end()) { 412 | return &m_images[imageName]; 413 | } 414 | else { 415 | return nullptr; 416 | } 417 | } 418 | 419 | std::string NtshEngn::AssetManager::getImageName(const Image* image) { 420 | if (m_imageNames.find(const_cast(image)) != m_imageNames.end()) { 421 | return m_imageNames[const_cast(image)]; 422 | } 423 | else { 424 | return ""; 425 | } 426 | } 427 | 428 | NtshEngn::Font* NtshEngn::AssetManager::findFontByName(const std::string& fontName) { 429 | if (m_fonts.find(fontName) != m_fonts.end()) { 430 | return &m_fonts[fontName]; 431 | } 432 | else { 433 | return nullptr; 434 | } 435 | } 436 | 437 | std::string NtshEngn::AssetManager::getFontName(const Font* font) { 438 | if (m_fontNames.find(const_cast(font)) != m_fontNames.end()) { 439 | return m_fontNames[const_cast(font)]; 440 | } 441 | else { 442 | return ""; 443 | } 444 | } 445 | 446 | NtshEngn::Sound* NtshEngn::AssetManager::findSoundByName(const std::string& soundName) { 447 | if (m_sounds.find(soundName) != m_sounds.end()) { 448 | return &m_sounds[soundName]; 449 | } 450 | else { 451 | return nullptr; 452 | } 453 | } 454 | 455 | std::string NtshEngn::AssetManager::getSoundName(const Sound* sound) { 456 | if (m_soundNames.find(const_cast(sound)) != m_soundNames.end()) { 457 | return m_soundNames[const_cast(sound)]; 458 | } 459 | else { 460 | return ""; 461 | } 462 | } 463 | 464 | void NtshEngn::AssetManager::calculateTangents(Mesh& mesh) { 465 | std::vector tan1(mesh.vertices.size()); 466 | std::vector tan2(mesh.vertices.size()); 467 | for (size_t i = 0; i < mesh.indices.size(); i += 3) { 468 | const NtshEngn::Vertex& vertex0 = mesh.vertices[mesh.indices[i]]; 469 | const NtshEngn::Vertex& vertex1 = mesh.vertices[mesh.indices[i + 1]]; 470 | const NtshEngn::Vertex& vertex2 = mesh.vertices[mesh.indices[i + 2]]; 471 | 472 | const Math::vec3 dPos1 = vertex1.position - vertex0.position; 473 | const Math::vec3 dPos2 = vertex2.position - vertex0.position; 474 | 475 | const Math::vec2 dUV1 = vertex1.uv - vertex0.uv; 476 | const Math::vec2 dUV2 = vertex2.uv - vertex0.uv; 477 | 478 | const float r = 1.0f / (dUV1.x * dUV2.y - dUV1.y * dUV2.x); 479 | 480 | const Math::vec3 uDir = (dPos1 * dUV2.y - dPos2 * dUV1.y) * r; 481 | const Math::vec3 vDir = (dPos2 * dUV1.x - dPos1 * dUV2.x) * r; 482 | 483 | tan1[mesh.indices[i]] += uDir; 484 | tan1[mesh.indices[i + 1]] += uDir; 485 | tan1[mesh.indices[i + 2]] += uDir; 486 | 487 | tan2[mesh.indices[i]] += vDir; 488 | tan2[mesh.indices[i + 1]] += vDir; 489 | tan2[mesh.indices[i + 2]] += vDir; 490 | } 491 | 492 | for (size_t i = 0; i < mesh.vertices.size(); i++) { 493 | const Math::vec3 n = mesh.vertices[i].normal; 494 | const Math::vec3 t = tan1[i]; 495 | 496 | const Math::vec3 tangent = Math::normalize(t - n * Math::dot(n, t)); 497 | const float tangentHandedness = (Math::dot(Math::cross(n, t), tan2[i]) < 0.0f) ? -1.0f : 1.0f; 498 | 499 | mesh.vertices[i].tangent = { tangent.x, tangent.y, tangent.z, tangentHandedness }; 500 | } 501 | } 502 | 503 | std::array NtshEngn::AssetManager::calculateAABB(const Mesh& mesh) { 504 | Math::vec3 min = Math::vec3(std::numeric_limits::max()); 505 | Math::vec3 max = Math::vec3(std::numeric_limits::lowest()); 506 | for (const NtshEngn::Vertex& vertex : mesh.vertices) { 507 | if (vertex.position[0] < min.x) { 508 | min.x = vertex.position[0]; 509 | } 510 | if (vertex.position[0] > max.x) { 511 | max.x = vertex.position[0]; 512 | } 513 | 514 | if (vertex.position[1] < min.y) { 515 | min.y = vertex.position[1]; 516 | } 517 | if (vertex.position[1] > max.y) { 518 | max.y = vertex.position[1]; 519 | } 520 | 521 | if (vertex.position[2] < min.z) { 522 | min.z = vertex.position[2]; 523 | } 524 | if (vertex.position[2] > max.z) { 525 | max.z = vertex.position[2]; 526 | } 527 | } 528 | 529 | const float epsilon = 0.0001f; 530 | 531 | if (min.x == max.x) { 532 | min.x -= epsilon; 533 | max.x += epsilon; 534 | } 535 | 536 | if (min.y == max.y) { 537 | min.y -= epsilon; 538 | max.y += epsilon; 539 | } 540 | 541 | if (min.z == max.z) { 542 | min.z -= epsilon; 543 | max.z += epsilon; 544 | } 545 | 546 | return { min, max }; 547 | } 548 | 549 | void NtshEngn::AssetManager::setAssetLoaderModule(AssetLoaderModuleInterface* assetLoaderModule) { 550 | m_assetLoaderModule = assetLoaderModule; 551 | } 552 | 553 | void NtshEngn::AssetManager::loadMeshNtmh(const std::string& filePath, Mesh& mesh) { 554 | const std::unordered_map stringToMeshTopology { 555 | { "TriangleList", MeshTopology::TriangleList }, 556 | { "TriangleStrip", MeshTopology::TriangleStrip }, 557 | { "LineList", MeshTopology::LineList }, 558 | { "LineStrip", MeshTopology::LineStrip }, 559 | { "PointList", MeshTopology::PointList }, 560 | { "Unknown", MeshTopology::Unknown } 561 | }; 562 | 563 | JSON json; 564 | const JSON::Node& meshRoot = json.read(filePath); 565 | 566 | bool hasNormals = false; 567 | bool hasUvs = false; 568 | bool hasTangents = false; 569 | 570 | if (meshRoot.contains("vertices")) { 571 | for (size_t i = 0; i < meshRoot["vertices"].size(); i++) { 572 | const JSON::Node& vertexNode = meshRoot["vertices"][i]; 573 | 574 | Vertex vertex; 575 | if (vertexNode.contains("position")) { 576 | vertex.position = { vertexNode["position"][0].getNumber(), vertexNode["position"][1].getNumber(), vertexNode["position"][2].getNumber() }; 577 | } 578 | 579 | if (vertexNode.contains("normal")) { 580 | vertex.normal = { vertexNode["normal"][0].getNumber(), vertexNode["normal"][1].getNumber(), vertexNode["normal"][2].getNumber() }; 581 | hasNormals = true; 582 | } 583 | 584 | if (vertexNode.contains("uv")) { 585 | vertex.uv = { vertexNode["uv"][0].getNumber(), vertexNode["uv"][1].getNumber() }; 586 | hasUvs = true; 587 | } 588 | 589 | if (vertexNode.contains("color")) { 590 | vertex.color = { vertexNode["color"][0].getNumber(), vertexNode["color"][1].getNumber(), vertexNode["color"][2].getNumber() }; 591 | } 592 | 593 | if (vertexNode.contains("tangent")) { 594 | vertex.tangent = { vertexNode["tangent"][0].getNumber(), vertexNode["tangent"][1].getNumber(), vertexNode["tangent"][2].getNumber(), vertexNode["tangent"][3].getNumber() }; 595 | hasTangents = true; 596 | } 597 | 598 | if (vertexNode.contains("joints")) { 599 | vertex.joints = std::array({ static_cast(vertexNode["joints"][0].getNumber()), static_cast(vertexNode["joints"][1].getNumber()), static_cast(vertexNode["joints"][2].getNumber()), static_cast(vertexNode["joints"][3].getNumber()) }); 600 | } 601 | 602 | if (vertexNode.contains("weights")) { 603 | vertex.weights = { vertexNode["weights"][0].getNumber(), vertexNode["weights"][1].getNumber(), vertexNode["weights"][2].getNumber(), vertexNode["weights"][3].getNumber() }; 604 | } 605 | 606 | mesh.vertices.push_back(vertex); 607 | } 608 | } 609 | 610 | if (meshRoot.contains("indices")) { 611 | for (size_t i = 0; i < meshRoot["indices"].size(); i++) { 612 | mesh.indices.push_back(static_cast(meshRoot["indices"][i].getNumber())); 613 | } 614 | } 615 | else { 616 | // Calculate indices 617 | mesh.indices.resize(mesh.vertices.size()); 618 | std::iota(mesh.indices.begin(), mesh.indices.end(), 0); 619 | } 620 | 621 | // Calculate tangents 622 | if ((!hasTangents) && (hasNormals && hasUvs)) { 623 | calculateTangents(mesh); 624 | } 625 | 626 | if (meshRoot.contains("topology")) { 627 | mesh.topology = stringToMeshTopology.at(meshRoot["topology"].getString()); 628 | } 629 | } 630 | 631 | void NtshEngn::AssetManager::loadImageSamplerNtsp(const std::string& filePath, ImageSampler& imageSampler) { 632 | const std::unordered_map stringToImageSamplerFilter{ 633 | { "Linear", ImageSamplerFilter::Linear }, 634 | { "Nearest", ImageSamplerFilter::Nearest }, 635 | { "Unknown", ImageSamplerFilter::Unknown } 636 | }; 637 | const std::unordered_map stringToImageSamplerAddressMode{ 638 | { "Repeat", ImageSamplerAddressMode::Repeat }, 639 | { "MirroredRepeat", ImageSamplerAddressMode::MirroredRepeat }, 640 | { "ClampToEdge", ImageSamplerAddressMode::ClampToEdge }, 641 | { "ClampToBorder", ImageSamplerAddressMode::ClampToBorder }, 642 | { "Unknown", ImageSamplerAddressMode::Unknown } 643 | }; 644 | const std::unordered_map stringToImageSamplerBorderColor{ 645 | { "FloatTransparentBlack", ImageSamplerBorderColor::FloatTransparentBlack }, 646 | { "IntTransparentBlack", ImageSamplerBorderColor::IntTransparentBlack }, 647 | { "FloatOpaqueBlack", ImageSamplerBorderColor::FloatOpaqueBlack }, 648 | { "IntOpaqueBlack", ImageSamplerBorderColor::IntOpaqueBlack }, 649 | { "FloatOpaqueWhite", ImageSamplerBorderColor::FloatOpaqueWhite }, 650 | { "IntOpaqueWhite", ImageSamplerBorderColor::IntOpaqueWhite }, 651 | { "Unknown", ImageSamplerBorderColor::Unknown } 652 | }; 653 | 654 | JSON json; 655 | const JSON::Node& imageSamplerRoot = json.read(filePath); 656 | 657 | if (imageSamplerRoot.contains("magFilter")) { 658 | imageSampler.magFilter = stringToImageSamplerFilter.at(imageSamplerRoot["magFilter"].getString()); 659 | } 660 | 661 | if (imageSamplerRoot.contains("minFilter")) { 662 | imageSampler.minFilter = stringToImageSamplerFilter.at(imageSamplerRoot["minFilter"].getString()); 663 | } 664 | 665 | if (imageSamplerRoot.contains("mipmapFilter")) { 666 | imageSampler.mipmapFilter = stringToImageSamplerFilter.at(imageSamplerRoot["mipmapFilter"].getString()); 667 | } 668 | 669 | if (imageSamplerRoot.contains("addressModeU")) { 670 | imageSampler.addressModeU = stringToImageSamplerAddressMode.at(imageSamplerRoot["addressModeU"].getString()); 671 | } 672 | 673 | if (imageSamplerRoot.contains("addressModeV")) { 674 | imageSampler.addressModeV = stringToImageSamplerAddressMode.at(imageSamplerRoot["addressModeV"].getString()); 675 | } 676 | 677 | if (imageSamplerRoot.contains("addressModeW")) { 678 | imageSampler.addressModeW = stringToImageSamplerAddressMode.at(imageSamplerRoot["addressModeW"].getString()); 679 | } 680 | 681 | if (imageSamplerRoot.contains("borderColor")) { 682 | imageSampler.borderColor = stringToImageSamplerBorderColor.at(imageSamplerRoot["borderColor"].getString()); 683 | } 684 | 685 | if (imageSamplerRoot.contains("anisotropyLevel")) { 686 | imageSampler.anisotropyLevel = imageSamplerRoot["anisotropyLevel"].getNumber(); 687 | } 688 | } 689 | 690 | void NtshEngn::AssetManager::loadMaterialNtml(const std::string& filePath, Material& material) { 691 | JSON json; 692 | const JSON::Node& materialRoot = json.read(filePath); 693 | 694 | if (materialRoot.contains("diffuse")) { 695 | const JSON::Node& diffuseNode = materialRoot["diffuse"]; 696 | 697 | if (diffuseNode.contains("texture")) { 698 | const JSON::Node& diffuseTextureNode = diffuseNode["texture"]; 699 | 700 | if (diffuseTextureNode.contains("imagePath")) { 701 | material.diffuseTexture.image = loadImage(diffuseTextureNode["imagePath"].getString()); 702 | } 703 | 704 | if (diffuseTextureNode.contains("imageSamplerPath")) { 705 | loadImageSamplerNtsp(diffuseTextureNode["imageSamplerPath"].getString(), material.diffuseTexture.imageSampler); 706 | } 707 | } 708 | else if (diffuseNode.contains("color")) { 709 | const JSON::Node& diffuseColorNode = diffuseNode["color"]; 710 | 711 | std::string mapKey = "srgb " + std::to_string(diffuseColorNode[0].getNumber()) + " " + std::to_string(diffuseColorNode[1].getNumber()) + " " + std::to_string(diffuseColorNode[2].getNumber()) + " " + std::to_string(diffuseColorNode[3].getNumber()); 712 | 713 | Image* image = findImageByName(mapKey); 714 | if (!image) { 715 | image = createImage(mapKey); 716 | image->width = 1; 717 | image->height = 1; 718 | image->format = ImageFormat::R8G8B8A8; 719 | image->colorSpace = ImageColorSpace::SRGB; 720 | image->data = { static_cast(round(255.0f * diffuseColorNode[0].getNumber())), static_cast(round(255.0f * diffuseColorNode[1].getNumber())), static_cast(round(255.0f * diffuseColorNode[2].getNumber())), static_cast(round(255.0f * diffuseColorNode[3].getNumber())) }; 721 | } 722 | 723 | material.diffuseTexture.image = image; 724 | } 725 | } 726 | 727 | if (materialRoot.contains("normal")) { 728 | const JSON::Node& normalNode = materialRoot["normal"]; 729 | 730 | if (normalNode.contains("texture")) { 731 | const JSON::Node& normalTextureNode = normalNode["texture"]; 732 | 733 | if (normalTextureNode.contains("imagePath")) { 734 | material.normalTexture.image = loadImage(normalTextureNode["imagePath"].getString()); 735 | } 736 | 737 | if (normalTextureNode.contains("imageSamplerPath")) { 738 | loadImageSamplerNtsp(normalTextureNode["imageSamplerPath"].getString(), material.normalTexture.imageSampler); 739 | } 740 | } 741 | } 742 | 743 | if (materialRoot.contains("metalness")) { 744 | const JSON::Node& metalnessNode = materialRoot["metalness"]; 745 | 746 | if (metalnessNode.contains("texture")) { 747 | const JSON::Node& metalnessTextureNode = metalnessNode["texture"]; 748 | 749 | if (metalnessTextureNode.contains("imagePath")) { 750 | material.metalnessTexture.image = loadImage(metalnessTextureNode["imagePath"].getString()); 751 | } 752 | 753 | if (metalnessTextureNode.contains("imageSamplerPath")) { 754 | loadImageSamplerNtsp(metalnessTextureNode["imageSamplerPath"].getString(), material.metalnessTexture.imageSampler); 755 | } 756 | } 757 | else if (metalnessNode.contains("value")) { 758 | const JSON::Node& metalnessValueNode = metalnessNode["value"]; 759 | uint8_t metalnessValue = static_cast(round(255.0f * metalnessValueNode.getNumber())); 760 | 761 | std::string mapKey = "linear " + std::to_string(metalnessValueNode.getNumber()) + " " + std::to_string(metalnessValueNode.getNumber()) + " " + std::to_string(metalnessValueNode.getNumber()) + " " + std::to_string(metalnessValueNode.getNumber()); 762 | 763 | Image* image = findImageByName(mapKey); 764 | if (!image) { 765 | image = createImage(mapKey); 766 | image->width = 1; 767 | image->height = 1; 768 | image->format = ImageFormat::R8G8B8A8; 769 | image->colorSpace = ImageColorSpace::Linear; 770 | image->data = { metalnessValue, metalnessValue, metalnessValue, metalnessValue }; 771 | } 772 | 773 | material.metalnessTexture.image = image; 774 | } 775 | } 776 | 777 | if (materialRoot.contains("roughness")) { 778 | const JSON::Node& roughnessNode = materialRoot["roughness"]; 779 | 780 | if (roughnessNode.contains("texture")) { 781 | const JSON::Node& roughnessTextureNode = roughnessNode["texture"]; 782 | 783 | if (roughnessTextureNode.contains("imagePath")) { 784 | material.roughnessTexture.image = loadImage(roughnessTextureNode["imagePath"].getString()); 785 | } 786 | 787 | if (roughnessTextureNode.contains("imageSamplerPath")) { 788 | loadImageSamplerNtsp(roughnessTextureNode["imageSamplerPath"].getString(), material.roughnessTexture.imageSampler); 789 | } 790 | } 791 | else if (roughnessNode.contains("value")) { 792 | const JSON::Node& roughnessValueNode = roughnessNode["value"]; 793 | uint8_t roughnessValue = static_cast(round(255.0f * roughnessValueNode.getNumber())); 794 | 795 | std::string mapKey = "linear " + std::to_string(roughnessValueNode.getNumber()) + " " + std::to_string(roughnessValueNode.getNumber()) + " " + std::to_string(roughnessValueNode.getNumber()) + " " + std::to_string(roughnessValueNode.getNumber()); 796 | 797 | Image* image = findImageByName(mapKey); 798 | if (!image) { 799 | image = createImage(mapKey); 800 | image->width = 1; 801 | image->height = 1; 802 | image->format = ImageFormat::R8G8B8A8; 803 | image->colorSpace = ImageColorSpace::Linear; 804 | image->data = { roughnessValue, roughnessValue, roughnessValue, roughnessValue }; 805 | } 806 | 807 | material.roughnessTexture.image = image; 808 | } 809 | } 810 | 811 | if (materialRoot.contains("occlusion")) { 812 | const JSON::Node& occlusionNode = materialRoot["occlusion"]; 813 | 814 | if (occlusionNode.contains("texture")) { 815 | const JSON::Node& occlusionTextureNode = occlusionNode["texture"]; 816 | 817 | if (occlusionTextureNode.contains("imagePath")) { 818 | material.occlusionTexture.image = loadImage(occlusionTextureNode["imagePath"].getString()); 819 | } 820 | 821 | if (occlusionTextureNode.contains("imageSamplerPath")) { 822 | loadImageSamplerNtsp(occlusionTextureNode["imageSamplerPath"].getString(), material.occlusionTexture.imageSampler); 823 | } 824 | } 825 | else if (occlusionNode.contains("value")) { 826 | const JSON::Node& occlusionValueNode = occlusionNode["value"]; 827 | uint8_t occlusionValue = static_cast(round(255.0f * occlusionValueNode.getNumber())); 828 | 829 | std::string mapKey = "linear " + std::to_string(occlusionValueNode.getNumber()) + " " + std::to_string(occlusionValueNode.getNumber()) + " " + std::to_string(occlusionValueNode.getNumber()) + " " + std::to_string(occlusionValueNode.getNumber()); 830 | 831 | Image* image = findImageByName(mapKey); 832 | if (!image) { 833 | image = createImage(mapKey); 834 | image->width = 1; 835 | image->height = 1; 836 | image->format = ImageFormat::R8G8B8A8; 837 | image->colorSpace = ImageColorSpace::Linear; 838 | image->data = { occlusionValue, occlusionValue, occlusionValue, occlusionValue }; 839 | } 840 | 841 | material.occlusionTexture.image = image; 842 | } 843 | } 844 | 845 | if (materialRoot.contains("emissive")) { 846 | const JSON::Node& emissiveNode = materialRoot["emissive"]; 847 | 848 | if (emissiveNode.contains("texture")) { 849 | const JSON::Node& emissiveTextureNode = emissiveNode["texture"]; 850 | 851 | if (emissiveTextureNode.contains("imagePath")) { 852 | material.emissiveTexture.image = loadImage(emissiveTextureNode["imagePath"].getString()); 853 | } 854 | 855 | if (emissiveTextureNode.contains("imageSamplerPath")) { 856 | loadImageSamplerNtsp(emissiveTextureNode["imageSamplerPath"].getString(), material.emissiveTexture.imageSampler); 857 | } 858 | } 859 | else if (emissiveNode.contains("color")) { 860 | const JSON::Node& emissiveColorNode = emissiveNode["color"]; 861 | 862 | std::string mapKey = "srgb " + std::to_string(emissiveColorNode[0].getNumber()) + " " + std::to_string(emissiveColorNode[1].getNumber()) + " " + std::to_string(emissiveColorNode[2].getNumber()) + " " + std::to_string(1.0f); 863 | 864 | Image* image = findImageByName(mapKey); 865 | if (!image) { 866 | image = createImage(mapKey); 867 | image->width = 1; 868 | image->height = 1; 869 | image->format = ImageFormat::R8G8B8A8; 870 | image->colorSpace = ImageColorSpace::SRGB; 871 | image->data = { static_cast(round(255.0f * emissiveColorNode[0].getNumber())), static_cast(round(255.0f * emissiveColorNode[1].getNumber())), static_cast(round(255.0f * emissiveColorNode[2].getNumber())), 255 }; 872 | } 873 | 874 | material.emissiveTexture.image = image; 875 | } 876 | 877 | if (emissiveNode.contains("factor")) { 878 | material.emissiveFactor = emissiveNode["factor"].getNumber(); 879 | } 880 | } 881 | 882 | if (materialRoot.contains("alphaCutoff")) { 883 | material.alphaCutoff = materialRoot["alphaCutoff"].getNumber(); 884 | } 885 | 886 | if (materialRoot.contains("indexOfRefraction")) { 887 | material.indexOfRefraction = materialRoot["indexOfRefraction"].getNumber(); 888 | } 889 | 890 | if (materialRoot.contains("useTriplanarMapping")) { 891 | material.useTriplanarMapping = materialRoot["useTriplanarMapping"].getBoolean(); 892 | } 893 | 894 | if (materialRoot.contains("scaleUV")) { 895 | material.scaleUV.x = materialRoot["scaleUV"][0].getNumber(); 896 | material.scaleUV.y = materialRoot["scaleUV"][1].getNumber(); 897 | } 898 | 899 | if (materialRoot.contains("offsetUV")) { 900 | material.offsetUV.x = materialRoot["offsetUV"][0].getNumber(); 901 | material.offsetUV.y = materialRoot["offsetUV"][1].getNumber(); 902 | } 903 | } 904 | 905 | void NtshEngn::AssetManager::loadModelNtmd(const std::string& filePath, Model& model) { 906 | JSON json; 907 | const JSON::Node& modelRoot = json.read(filePath); 908 | 909 | if (modelRoot.contains("primitives")) { 910 | for (size_t i = 0; i < modelRoot["primitives"].size(); i++) { 911 | const JSON::Node& primitiveNode = modelRoot["primitives"][i]; 912 | 913 | ModelPrimitive primitive; 914 | if (primitiveNode.contains("meshPath")) { 915 | loadMeshNtmh(primitiveNode["meshPath"].getString(), primitive.mesh); 916 | } 917 | 918 | if (primitiveNode.contains("materialPath")) { 919 | loadMaterialNtml(primitiveNode["materialPath"].getString(), primitive.material); 920 | } 921 | 922 | model.primitives.push_back(primitive); 923 | } 924 | } 925 | } 926 | 927 | void NtshEngn::AssetManager::loadImageNtim(const std::string& filePath, Image& image) { 928 | const std::unordered_map stringToImageFormat{ 929 | { "R8", ImageFormat::R8 }, 930 | { "R8G8", ImageFormat::R8G8 }, 931 | { "R8G8B8", ImageFormat::R8G8B8 }, 932 | { "R8G8B8A8", ImageFormat::R8G8B8A8 }, 933 | { "R16", ImageFormat::R16 }, 934 | { "R16G16", ImageFormat::R16G16 }, 935 | { "R16G16B16", ImageFormat::R16G16B16 }, 936 | { "R16G16B16A16", ImageFormat::R16G16B16A16 }, 937 | { "R32", ImageFormat::R32 }, 938 | { "R32G32", ImageFormat::R32G32 }, 939 | { "R32G32B32", ImageFormat::R32G32B32 }, 940 | { "R32G32B32A32", ImageFormat::R32G32B32A32 }, 941 | { "Unknown", ImageFormat::Unknown } 942 | }; 943 | const std::unordered_map stringToImageColorSpace{ 944 | { "Linear", ImageColorSpace::Linear }, 945 | { "SRGB", ImageColorSpace::SRGB }, 946 | { "Unknown", ImageColorSpace::Unknown } 947 | }; 948 | 949 | JSON json; 950 | const JSON::Node& imageRoot = json.read(filePath); 951 | 952 | if (imageRoot.contains("width")) { 953 | image.width = static_cast(imageRoot["width"].getNumber()); 954 | } 955 | if (imageRoot.contains("height")) { 956 | image.height = static_cast(imageRoot["height"].getNumber()); 957 | } 958 | if (imageRoot.contains("format")) { 959 | image.format = stringToImageFormat.at(imageRoot["format"].getString()); 960 | } 961 | if (imageRoot.contains("colorSpace")) { 962 | image.colorSpace = stringToImageColorSpace.at(imageRoot["colorSpace"].getString()); 963 | } 964 | if (imageRoot.contains("data")) { 965 | for (size_t i = 0; i < imageRoot["data"].size(); i++) { 966 | image.data.push_back(static_cast(imageRoot["data"][i].getNumber())); 967 | } 968 | } 969 | } 970 | 971 | void NtshEngn::AssetManager::loadSoundNtsd(const std::string& filePath, Sound& sound) { 972 | JSON json; 973 | const JSON::Node& soundRoot = json.read(filePath); 974 | 975 | if (soundRoot.contains("channels")) { 976 | sound.channels = static_cast(soundRoot["channels"].getNumber()); 977 | } 978 | 979 | if (soundRoot.contains("sampleRate")) { 980 | sound.sampleRate = static_cast(soundRoot["sampleRate"].getNumber()); 981 | } 982 | 983 | if (soundRoot.contains("bitsPerSample")) { 984 | sound.bitsPerSample = static_cast(soundRoot["bitsPerSample"].getNumber()); 985 | } 986 | 987 | if (soundRoot.contains("size")) { 988 | sound.size = static_cast(soundRoot["size"].getNumber()); 989 | } 990 | 991 | if (soundRoot.contains("data")) { 992 | for (size_t i = 0; i < soundRoot["data"].size(); i++) { 993 | sound.data.push_back(static_cast(soundRoot["data"][i].getNumber())); 994 | } 995 | } 996 | } 997 | 998 | std::string NtshEngn::AssetManager::getNormalizedPath(const std::string& filePath) { 999 | if (filePath.empty()) { 1000 | return ""; 1001 | } 1002 | 1003 | std::string normalizedPath = std::filesystem::canonical(filePath).string(); 1004 | std::replace(normalizedPath.begin(), normalizedPath.end(), '\\', '/'); 1005 | std::string currentPath = std::filesystem::current_path().string(); 1006 | std::replace(currentPath.begin(), currentPath.end(), '\\', '/'); 1007 | if (normalizedPath.substr(0, currentPath.size()) == currentPath) { 1008 | normalizedPath = normalizedPath.substr(currentPath.size() + 1); 1009 | } 1010 | 1011 | return normalizedPath; 1012 | } 1013 | -------------------------------------------------------------------------------- /asset_manager/ntshengn_asset_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/asset_manager/ntshengn_asset_manager_interface.h" 3 | #include "../../Common/modules/ntshengn_asset_loader_module_interface.h" 4 | #include "../resources/ntshengn_resources_audio.h" 5 | #include "../resources/ntshengn_resources_graphics.h" 6 | #include 7 | #include 8 | 9 | namespace NtshEngn { 10 | 11 | class AssetManager : public AssetManagerInterface { 12 | public: 13 | Model* createModel(const std::string& modelName); 14 | Model* loadModel(const std::string& filePath); 15 | 16 | Material* createMaterial(const std::string& materialName); 17 | Material* loadMaterial(const std::string& filePath); 18 | 19 | Image* createImage(const std::string& imageName); 20 | Image* loadImage(const std::string& filePath); 21 | 22 | Font* createFont(const std::string& fontName); 23 | Font* loadFont(const std::string& filePath, float fontHeight); 24 | 25 | Sound* createSound(const std::string& soundName); 26 | Sound* loadSound(const std::string& filePath); 27 | 28 | void destroyModel(const std::string& modelName); 29 | void destroyMaterial(const std::string& materialName); 30 | void destroyImage(const std::string& imageName); 31 | void destroyFont(const std::string& fontName); 32 | void destroySound(const std::string& soundName); 33 | 34 | Model* findModelByName(const std::string& modelName); 35 | std::string getModelName(const Model* model); 36 | 37 | Material* findMaterialByName(const std::string& materialName); 38 | std::string getMaterialName(const Material* material); 39 | 40 | Image* findImageByName(const std::string& imageName); 41 | std::string getImageName(const Image* image); 42 | 43 | Font* findFontByName(const std::string& fontName); 44 | std::string getFontName(const Font* font); 45 | 46 | Sound* findSoundByName(const std::string& soundName); 47 | std::string getSoundName(const Sound* sound); 48 | 49 | void calculateTangents(Mesh& mesh); 50 | std::array calculateAABB(const Mesh& mesh); 51 | 52 | public: 53 | void setAssetLoaderModule(AssetLoaderModuleInterface* assetLoaderModule); 54 | 55 | private: 56 | void loadMeshNtmh(const std::string& filePath, Mesh& mesh); 57 | 58 | void loadImageSamplerNtsp(const std::string& filePath, ImageSampler& imageSampler); 59 | 60 | void loadMaterialNtml(const std::string& filePath, Material& material); 61 | 62 | void loadModelNtmd(const std::string& filePath, Model& model); 63 | 64 | void loadImageNtim(const std::string& filePath, Image& image); 65 | 66 | void loadSoundNtsd(const std::string& filePath, Sound& sound); 67 | 68 | std::string getNormalizedPath(const std::string& filePath); 69 | 70 | private: 71 | AssetLoaderModuleInterface* m_assetLoaderModule; 72 | 73 | std::unordered_map m_models; 74 | std::unordered_map m_materials; 75 | std::unordered_map m_images; 76 | std::unordered_map m_fonts; 77 | std::unordered_map m_sounds; 78 | 79 | std::unordered_map m_modelNames; 80 | std::unordered_map m_materialNames; 81 | std::unordered_map m_imageNames; 82 | std::unordered_map m_fontNames; 83 | std::unordered_map m_soundNames; 84 | 85 | std::unordered_map m_modelLastModified; 86 | std::unordered_map m_materialLastModified; 87 | std::unordered_map m_imageLastModified; 88 | std::unordered_map m_fontLastModified; 89 | std::unordered_map m_soundLastModified; 90 | }; 91 | 92 | } -------------------------------------------------------------------------------- /ecs/ntshengn_ecs.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_ecs.h" 2 | 3 | void NtshEngn::ECS::init() { 4 | m_entityManager = std::make_unique(); 5 | m_componentManager = std::make_unique(); 6 | m_systemManager = std::make_unique(); 7 | } 8 | 9 | NtshEngn::Entity NtshEngn::ECS::createEntity() { 10 | Entity newEntity = m_entityManager->createEntity(); 11 | addComponent(newEntity, Transform{}); 12 | 13 | return newEntity; 14 | } 15 | 16 | NtshEngn::Entity NtshEngn::ECS::createEntity(const std::string& name) { 17 | Entity newEntity = m_entityManager->createEntity(name); 18 | addComponent(newEntity, Transform{}); 19 | 20 | return newEntity; 21 | } 22 | 23 | void NtshEngn::ECS::destroyEntity(Entity entity) { 24 | ComponentMask entityComponents = m_entityManager->getComponents(entity); 25 | m_systemManager->entityDestroyed(entity, entityComponents); 26 | m_entityManager->destroyEntity(entity); 27 | m_componentManager->entityDestroyed(entity); 28 | } 29 | 30 | void NtshEngn::ECS::destroyAllEntities() { 31 | while (!m_entityManager->getExistingEntities().empty()) { 32 | destroyEntity(*m_entityManager->getExistingEntities().rbegin()); 33 | } 34 | } 35 | 36 | void NtshEngn::ECS::destroyNonPersistentEntities() { 37 | const std::set& existingEntities = m_entityManager->getExistingEntities(); 38 | const std::set& persistentEntities = m_entityManager->getPersistentEntities(); 39 | std::set nonPersistentEntities; 40 | std::set_difference(existingEntities.begin(), existingEntities.end(), persistentEntities.begin(), persistentEntities.end(), std::inserter(nonPersistentEntities, nonPersistentEntities.begin())); 41 | while (!nonPersistentEntities.empty()) { 42 | Entity entityToDestroy = *nonPersistentEntities.rbegin(); 43 | nonPersistentEntities.erase(entityToDestroy); 44 | destroyEntity(entityToDestroy); 45 | } 46 | } 47 | 48 | bool NtshEngn::ECS::entityExists(Entity entity) { 49 | return m_entityManager->entityExists(entity); 50 | } 51 | 52 | void NtshEngn::ECS::setEntityName(Entity entity, const std::string& name) { 53 | m_entityManager->setEntityName(entity, name); 54 | } 55 | 56 | bool NtshEngn::ECS::entityHasName(Entity entity) { 57 | return m_entityManager->entityHasName(entity); 58 | } 59 | 60 | std::string NtshEngn::ECS::getEntityName(Entity entity) { 61 | return m_entityManager->getEntityName(entity); 62 | } 63 | 64 | NtshEngn::Entity NtshEngn::ECS::findEntityByName(const std::string& name) { 65 | return m_entityManager->findEntityByName(name); 66 | } 67 | 68 | void NtshEngn::ECS::setEntityPersistence(Entity entity, bool persistent) { 69 | m_entityManager->setEntityPersistence(entity, persistent); 70 | } 71 | 72 | bool NtshEngn::ECS::isEntityPersistent(Entity entity) { 73 | return m_entityManager->isEntityPersistent(entity); 74 | } 75 | 76 | void NtshEngn::ECS::addEntityToEntityGroup(Entity entity, const std::string& entityGroupName) { 77 | m_entityManager->addEntityToEntityGroup(entity, entityGroupName); 78 | } 79 | 80 | void NtshEngn::ECS::removeEntityFromEntityGroup(Entity entity, const std::string& entityGroupName) { 81 | m_entityManager->removeEntityFromEntityGroup(entity, entityGroupName); 82 | } 83 | 84 | bool NtshEngn::ECS::entityGroupExists(const std::string& entityGroupName) { 85 | return m_entityManager->entityGroupExists(entityGroupName); 86 | } 87 | 88 | bool NtshEngn::ECS::isEntityInEntityGroup(Entity entity, const std::string& entityGroupName) { 89 | return m_entityManager->isEntityInEntityGroup(entity, entityGroupName); 90 | } 91 | 92 | std::set NtshEngn::ECS::getEntitiesInEntityGroup(const std::string& entityGroupName) { 93 | return m_entityManager->getEntitiesInEntityGroup(entityGroupName); 94 | } 95 | 96 | std::set NtshEngn::ECS::getEntityGroupsOfEntity(Entity entity) { 97 | return m_entityManager->getEntityGroupsOfEntity(entity); 98 | } 99 | -------------------------------------------------------------------------------- /ecs/ntshengn_ecs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/ecs/ntshengn_ecs_interface.h" 3 | 4 | namespace NtshEngn { 5 | 6 | class ECS : public ECSInterface { 7 | public: 8 | void init(); 9 | 10 | // Entity 11 | Entity createEntity(); 12 | Entity createEntity(const std::string& name); 13 | 14 | void destroyEntity(Entity entity); 15 | void destroyAllEntities(); 16 | void destroyNonPersistentEntities(); 17 | 18 | bool entityExists(Entity entity); 19 | 20 | void setEntityName(Entity entity, const std::string& name); 21 | bool entityHasName(Entity entity); 22 | std::string getEntityName(Entity entity); 23 | Entity findEntityByName(const std::string& name); 24 | 25 | void setEntityPersistence(Entity entity, bool persistent); 26 | bool isEntityPersistent(Entity entity); 27 | 28 | void addEntityToEntityGroup(Entity entity, const std::string& entityGroupName); 29 | void removeEntityFromEntityGroup(Entity entity, const std::string& entityGroupName); 30 | 31 | bool entityGroupExists(const std::string& entityGroupName); 32 | 33 | bool isEntityInEntityGroup(Entity entity, const std::string& entityGroupName); 34 | std::set getEntitiesInEntityGroup(const std::string& entityGroupName); 35 | std::set getEntityGroupsOfEntity(Entity entity); 36 | }; 37 | 38 | } -------------------------------------------------------------------------------- /exception_handler/ntshengn_exception_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if defined(NTSHENGN_OS_WINDOWS) 3 | #include "ntshengn_exception_handler_windows.h" 4 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 5 | #include "ntshengn_exception_handler_linux_freebsd.h" 6 | #endif -------------------------------------------------------------------------------- /exception_handler/ntshengn_exception_handler_linux_freebsd.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_exception_handler_linux_freebsd.h" 2 | 3 | void NtshEngn::ExceptionHandler::setExceptionHandler() { 4 | m_signalStack.ss_size = SIGSTKSZ; 5 | m_signalStack.ss_flags = 0; 6 | m_signalStack.ss_sp = mmap(NULL, SIGSTKSZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 7 | if (m_signalStack.ss_sp != MAP_FAILED) { 8 | if (sigaltstack(&m_signalStack, 0) < 0) { 9 | NTSHENGN_CORE_ERROR("Could not sigaltstack."); 10 | } 11 | } 12 | else { 13 | NTSHENGN_CORE_ERROR("Could not mmap the signal stack."); 14 | } 15 | 16 | struct sigaction sigAction = {}; 17 | sigAction.sa_sigaction = signalHandler; 18 | sigemptyset(&sigAction.sa_mask); 19 | sigAction.sa_flags = SA_SIGINFO | SA_ONSTACK; 20 | 21 | if (sigaction(SIGILL, &sigAction, NULL) != 0) { 22 | NTSHENGN_CORE_ERROR("Could not the set the action for signal SIGILL."); 23 | } 24 | if (sigaction(SIGSEGV, &sigAction, NULL) != 0) { 25 | NTSHENGN_CORE_ERROR("Could not the set the action for signal SIGSEGV."); 26 | } 27 | if (sigaction(SIGBUS, &sigAction, NULL) != 0) { 28 | NTSHENGN_CORE_ERROR("Could not the set the action for signal SIGBUS."); 29 | } 30 | if (sigaction(SIGABRT, &sigAction, NULL) != 0) { 31 | NTSHENGN_CORE_ERROR("Could not the set the action for signal SIGABRT."); 32 | } 33 | if (sigaction(SIGTRAP, &sigAction, NULL) != 0) { 34 | NTSHENGN_CORE_ERROR("Could not the set the action for signal SIGTRAP."); 35 | } 36 | if (sigaction(SIGFPE, &sigAction, NULL) != 0) { 37 | NTSHENGN_CORE_ERROR("Could not the set the action for signal SIGFPE."); 38 | } 39 | } 40 | 41 | void NtshEngn::ExceptionHandler::unsetExceptionHandler() { 42 | munmap(m_signalStack.ss_sp, SIGSTKSZ); 43 | } -------------------------------------------------------------------------------- /exception_handler/ntshengn_exception_handler_linux_freebsd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../utils/ntshengn_core_defines.h" 3 | #include "../Common/utils/ntshengn_defines.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace NtshEngn { 13 | 14 | class ExceptionHandler { 15 | public: 16 | static std::string signalString(int signo, int si_code) { 17 | switch (signo) { 18 | case SIGILL: 19 | switch (si_code) { 20 | case ILL_ILLOPC: 21 | return "SIGILL - ILLOPC"; 22 | 23 | case ILL_ILLOPN: 24 | return "SIGILL - ILLOPN"; 25 | 26 | case ILL_ILLADR: 27 | return "SIGILL - ILLADR"; 28 | 29 | case ILL_ILLTRP: 30 | return "SIGILL - ILLTRP"; 31 | 32 | case ILL_PRVOPC: 33 | return "SIGILL - PRVOPC"; 34 | 35 | case ILL_PRVREG: 36 | return "SIGILL - PRVREG"; 37 | 38 | case ILL_COPROC: 39 | return "SIGILL - COPROC"; 40 | 41 | case ILL_BADSTK: 42 | return "SIGILL - BADSTK"; 43 | 44 | default: 45 | return "SIGILL"; 46 | } 47 | 48 | case SIGSEGV: 49 | return "SIGSEGV"; 50 | 51 | case SIGBUS: 52 | return "SIGBUS"; 53 | 54 | case SIGABRT: 55 | return "SIGABRT"; 56 | 57 | case SIGTRAP: 58 | return "SIGTRAP"; 59 | 60 | case SIGFPE: 61 | switch (si_code) { 62 | case FPE_INTOVF: 63 | return "SIGFPE - INTOVF"; 64 | 65 | case FPE_INTDIV: 66 | return "SIGFPE - INTDIV"; 67 | 68 | case FPE_FLTOVF: 69 | return "SIGFPE - FLTOVF"; 70 | 71 | case FPE_FLTDIV: 72 | return "SIGFPE - FLTDIV"; 73 | 74 | case FPE_FLTUND: 75 | return "SIGFPE - FLTUND"; 76 | 77 | default: 78 | return "SIGFPE"; 79 | } 80 | 81 | default: 82 | return "Unknown Signal"; 83 | } 84 | } 85 | 86 | static std::string stackTrace() { 87 | std::string message; 88 | 89 | const size_t MAX_TRACE_SIZE = 64; 90 | void* stackTraces[MAX_TRACE_SIZE]; 91 | int traceSize = backtrace(stackTraces, MAX_TRACE_SIZE); 92 | char** symbols = backtrace_symbols(stackTraces, traceSize); 93 | 94 | std::vector> addresses; 95 | for (int i = 3; i < (traceSize - 1); i++) { 96 | std::string symbolsString = std::string(symbols[i]); 97 | size_t openParenthesisPosition = symbolsString.find("("); 98 | size_t closedParenthesisPosition = symbolsString.find(')', openParenthesisPosition); 99 | addresses.push_back({ std::filesystem::canonical(symbolsString.substr(0, openParenthesisPosition)), symbolsString.substr(openParenthesisPosition + 1, closedParenthesisPosition - (openParenthesisPosition + 1)) }); 100 | } 101 | 102 | for (size_t i = 0; i < addresses.size(); i++) { 103 | std::string command = "addr2line -f -p -C -e" + addresses[i].first + " " + addresses[i].second; 104 | FILE* fp = popen(command.c_str(), "r"); 105 | char stdOutBuffer[4096]; 106 | std::string addr2Line; 107 | while (fgets(stdOutBuffer, 4096, fp) != NULL) { 108 | addr2Line += std::string(stdOutBuffer); 109 | } 110 | 111 | pclose(fp); 112 | 113 | size_t messageCursor = 0; 114 | size_t previousMessageCursor = 0; 115 | size_t cppPosition = addr2Line.find(".cpp", messageCursor); 116 | size_t hPosition = addr2Line.find(".h", messageCursor); 117 | while ((cppPosition != std::string::npos) || (hPosition != std::string::npos)) { 118 | previousMessageCursor = messageCursor; 119 | 120 | if (cppPosition < hPosition) { 121 | messageCursor = cppPosition + 4; 122 | } 123 | else { 124 | messageCursor = hPosition + 2; 125 | } 126 | 127 | std::string untilExtension = addr2Line.substr(previousMessageCursor, messageCursor - previousMessageCursor); 128 | 129 | size_t lastSpacePosition = untilExtension.find_last_of(' ', messageCursor); 130 | 131 | message += untilExtension.substr(0, lastSpacePosition + 1); 132 | message += std::filesystem::path(untilExtension.substr(lastSpacePosition)).filename(); 133 | 134 | cppPosition = addr2Line.find(".cpp", messageCursor); 135 | hPosition = addr2Line.find(".h", messageCursor); 136 | } 137 | message += addr2Line.substr(messageCursor); 138 | } 139 | 140 | if (symbols) { 141 | free(symbols); 142 | } 143 | 144 | return message; 145 | } 146 | 147 | static void signalHandler(int signo, siginfo_t* info, void* other) { 148 | NTSHENGN_UNUSED(other); 149 | 150 | std::string message; 151 | message += "Signal:\n"; 152 | message += "Code: " + signalString(signo, info->si_code); 153 | message += "\nStack Trace:\n" + stackTrace(); 154 | message.pop_back(); 155 | 156 | NTSHENGN_CORE_ERROR(message); 157 | } 158 | 159 | public: 160 | void setExceptionHandler(); 161 | void unsetExceptionHandler(); 162 | 163 | private: 164 | stack_t m_signalStack; 165 | }; 166 | 167 | } 168 | -------------------------------------------------------------------------------- /exception_handler/ntshengn_exception_handler_windows.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_exception_handler_windows.h" 2 | 3 | void NtshEngn::ExceptionHandler::setExceptionHandler() { 4 | SetUnhandledExceptionFilter(unhandledExceptionFilter); 5 | } 6 | 7 | void NtshEngn::ExceptionHandler::unsetExceptionHandler() { 8 | SetUnhandledExceptionFilter(NULL); 9 | } -------------------------------------------------------------------------------- /exception_handler/ntshengn_exception_handler_windows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../utils/ntshengn_core_defines.h" 3 | #include 4 | #include 5 | #undef far 6 | #undef near 7 | #include 8 | #include 9 | 10 | namespace NtshEngn { 11 | 12 | class ExceptionHandler { 13 | public: 14 | static std::string exceptionCodeString(DWORD exceptionCode) { 15 | switch (exceptionCode) { 16 | case EXCEPTION_ACCESS_VIOLATION: 17 | return "EXCEPTION_ACCESS_VIOLATION"; 18 | 19 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: 20 | return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; 21 | 22 | case EXCEPTION_BREAKPOINT: 23 | return "EXCEPTION_BREAKPOINT"; 24 | 25 | case EXCEPTION_DATATYPE_MISALIGNMENT: 26 | return "EXCEPTION_DATATYPE_MISALIGNMENT"; 27 | 28 | case EXCEPTION_FLT_DENORMAL_OPERAND: 29 | return "EXCEPTION_FLT_DENORMAL_OPERAND"; 30 | 31 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: 32 | return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; 33 | 34 | case EXCEPTION_FLT_INEXACT_RESULT: 35 | return "EXCEPTION_FLT_INEXACT_RESULT"; 36 | 37 | case EXCEPTION_FLT_INVALID_OPERATION: 38 | return "EXCEPTION_FLT_INVALID_OPERATION"; 39 | 40 | case EXCEPTION_FLT_OVERFLOW: 41 | return "EXCEPTION_FLT_OVERFLOW"; 42 | 43 | case EXCEPTION_FLT_STACK_CHECK: 44 | return "EXCEPTION_FLT_STACK_CHECK"; 45 | 46 | case EXCEPTION_FLT_UNDERFLOW: 47 | return "EXCEPTION_FLT_UNDERFLOW"; 48 | 49 | case EXCEPTION_ILLEGAL_INSTRUCTION: 50 | return "EXCEPTION_ILLEGAL_INSTRUCTION"; 51 | 52 | case EXCEPTION_IN_PAGE_ERROR: 53 | return "EXCEPTION_IN_PAGE_ERROR"; 54 | 55 | case EXCEPTION_INT_DIVIDE_BY_ZERO: 56 | return "EXCEPTION_INT_DIVIDE_BY_ZERO"; 57 | 58 | case EXCEPTION_INT_OVERFLOW: 59 | return "EXCEPTION_INT_OVERFLOW"; 60 | 61 | case EXCEPTION_INVALID_DISPOSITION: 62 | return "EXCEPTION_INVALID_DISPOSITION"; 63 | 64 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: 65 | return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; 66 | 67 | case EXCEPTION_PRIV_INSTRUCTION: 68 | return "EXCEPTION_PRIV_INSTRUCTION"; 69 | 70 | case EXCEPTION_SINGLE_STEP: 71 | return "EXCEPTION_SINGLE_STEP"; 72 | 73 | case EXCEPTION_STACK_OVERFLOW: 74 | return "EXCEPTION_STACK_OVERFLOW"; 75 | 76 | default: 77 | return "Unknown Exception"; 78 | } 79 | } 80 | 81 | static std::string stackTrace(PCONTEXT contextRecord) { 82 | std::string message; 83 | 84 | SymInitialize(GetCurrentProcess(), 0, true); 85 | 86 | STACKFRAME frame = { 0 }; 87 | frame.AddrPC.Offset = contextRecord->Rip; 88 | frame.AddrPC.Mode = AddrModeFlat; 89 | frame.AddrStack.Offset = contextRecord->Rsp; 90 | frame.AddrStack.Mode = AddrModeFlat; 91 | frame.AddrFrame.Offset = contextRecord->Rbp; 92 | frame.AddrFrame.Mode = AddrModeFlat; 93 | 94 | DWORD displacement = 0; 95 | IMAGEHLP_LINE line; 96 | 97 | while (StackWalk(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &frame, contextRecord, 0, SymFunctionTableAccess, SymGetModuleBase, 0)) { 98 | if (SymGetLineFromAddr(GetCurrentProcess(), frame.AddrPC.Offset, &displacement, &line)) { 99 | message += std::filesystem::path(line.FileName).filename().string() + " - Line: " + std::to_string(line.LineNumber) + "\n"; 100 | } 101 | } 102 | 103 | SymCleanup(GetCurrentProcess()); 104 | 105 | return message; 106 | } 107 | 108 | static LONG WINAPI unhandledExceptionFilter(struct _EXCEPTION_POINTERS* lpExceptionInfo) { 109 | std::string message; 110 | message += "Exception:\n"; 111 | message += "Code: " + exceptionCodeString(lpExceptionInfo->ExceptionRecord->ExceptionCode); 112 | if (lpExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW) { 113 | message += "\nStack Trace:\n" + stackTrace(lpExceptionInfo->ContextRecord); 114 | message.pop_back(); 115 | } 116 | 117 | NTSHENGN_CORE_ERROR(message); 118 | 119 | return EXCEPTION_EXECUTE_HANDLER; 120 | } 121 | 122 | public: 123 | void setExceptionHandler(); 124 | void unsetExceptionHandler(); 125 | }; 126 | 127 | } -------------------------------------------------------------------------------- /frame_limiter/ntshengn_frame_limiter.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_frame_limiter.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void NtshEngn::FrameLimiter::wait(double frameStart) { 7 | using namespace std::chrono_literals; 8 | 9 | if (m_maxFPS != 0) { 10 | const double currentTime = std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); 11 | const double maxFPSToMilliseconds = 1000.0 / static_cast(m_maxFPS); 12 | const double deltaTime = currentTime - frameStart; 13 | if (deltaTime < maxFPSToMilliseconds) { 14 | if ((maxFPSToMilliseconds - deltaTime) > 2.0) { 15 | std::chrono::duration sleepTime = (maxFPSToMilliseconds - deltaTime - 3.0) * 1ms; 16 | std::this_thread::sleep_for(sleepTime); 17 | } 18 | while ((std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count() - frameStart) < maxFPSToMilliseconds); 19 | } 20 | } 21 | } 22 | 23 | void NtshEngn::FrameLimiter::setMaxFPS(uint32_t maxFPS) { 24 | m_maxFPS = maxFPS; 25 | } 26 | 27 | uint32_t NtshEngn::FrameLimiter::getMaxFPS() { 28 | return m_maxFPS; 29 | } -------------------------------------------------------------------------------- /frame_limiter/ntshengn_frame_limiter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/frame_limiter/ntshengn_frame_limiter_interface.h" 3 | 4 | namespace NtshEngn { 5 | 6 | class FrameLimiter : public FrameLimiterInterface { 7 | public: 8 | void wait(double frameStart); 9 | 10 | void setMaxFPS(uint32_t maxFPS); 11 | uint32_t getMaxFPS(); 12 | 13 | private: 14 | uint32_t m_maxFPS = 0; 15 | }; 16 | 17 | } -------------------------------------------------------------------------------- /job_system/ntshengn_job_system.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_job_system.h" 2 | #include 3 | 4 | void NtshEngn::JobSystem::init() { 5 | m_numThreads = std::max(1u, std::thread::hardware_concurrency()); 6 | m_sharedData.currentJobs.store(0); 7 | m_sharedData.running = true; 8 | 9 | for (uint32_t threadID = 0; threadID < m_numThreads; threadID++) { 10 | m_threads.emplace_back([&sharedData = m_sharedData]() { 11 | std::function job; 12 | 13 | while (sharedData.running) { 14 | if (sharedData.jobQueue.pop_front(job)) { 15 | job(); 16 | sharedData.currentJobs.fetch_sub(1); 17 | } 18 | else { 19 | std::unique_lock lock(sharedData.wakeMutex); 20 | sharedData.wakeCondition.wait(lock); 21 | } 22 | } 23 | }); 24 | } 25 | } 26 | 27 | void NtshEngn::JobSystem::destroy() { 28 | m_sharedData.running = false; 29 | m_sharedData.wakeCondition.notify_all(); 30 | for (uint32_t threadID = 0; threadID < m_numThreads; threadID++) { 31 | m_threads[threadID].join(); 32 | } 33 | } 34 | 35 | void NtshEngn::JobSystem::execute(const std::function& job) { 36 | m_sharedData.jobQueue.push_back(job); 37 | 38 | m_sharedData.currentJobs.fetch_add(1); 39 | 40 | m_sharedData.wakeCondition.notify_one(); 41 | } 42 | 43 | void NtshEngn::JobSystem::dispatch(uint32_t jobCount, uint32_t jobsPerWorker, const std::function& job) { 44 | if ((jobCount == 0) || (jobsPerWorker == 0)) { 45 | return; 46 | } 47 | 48 | const uint32_t workerCount = (jobCount + jobsPerWorker - 1) / jobsPerWorker; 49 | 50 | m_sharedData.currentJobs.fetch_add(workerCount); 51 | 52 | for (uint32_t workerIndex = 0; workerIndex < workerCount; workerIndex++) { 53 | const uint32_t workerJobOffset = workerIndex * jobsPerWorker; 54 | const uint32_t workerJobEnd = std::min(workerJobOffset + jobsPerWorker, jobCount); 55 | 56 | std::function dispatchJobForWorker = [workerJobOffset, workerJobEnd, workerIndex, job]() { 57 | JobDispatchArguments dispatchArguments; 58 | dispatchArguments.workerIndex = workerIndex; 59 | 60 | for (uint32_t jobIndex = workerJobOffset; jobIndex < workerJobEnd; jobIndex++) { 61 | dispatchArguments.jobIndex = jobIndex; 62 | 63 | job(dispatchArguments); 64 | } 65 | }; 66 | 67 | m_sharedData.jobQueue.push_back(dispatchJobForWorker); 68 | 69 | m_sharedData.wakeCondition.notify_one(); 70 | } 71 | } 72 | 73 | bool NtshEngn::JobSystem::isBusy() { 74 | return m_sharedData.currentJobs.load() != 0; 75 | } 76 | 77 | void NtshEngn::JobSystem::wait() { 78 | while (isBusy()) { 79 | std::this_thread::yield(); 80 | } 81 | } 82 | 83 | uint32_t NtshEngn::JobSystem::getNumThreads() const { 84 | return m_numThreads; 85 | } -------------------------------------------------------------------------------- /job_system/ntshengn_job_system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/job_system/ntshengn_job_system_interface.h" 3 | #include "../utils/ntshengn_utils_thread_safe_queue.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace NtshEngn { 12 | 13 | struct JobSharedData { 14 | ThreadSafeQueue> jobQueue; 15 | std::condition_variable wakeCondition; 16 | std::mutex wakeMutex; 17 | std::atomic currentJobs; 18 | bool running; 19 | }; 20 | 21 | class JobSystem : public JobSystemInterface { 22 | public: 23 | void init(); 24 | void destroy(); 25 | 26 | void execute(const std::function& job); 27 | void dispatch(uint32_t jobCount, uint32_t jobsPerWorker, const std::function& job); 28 | 29 | bool isBusy(); 30 | void wait(); 31 | 32 | uint32_t getNumThreads() const; 33 | 34 | private: 35 | uint32_t m_numThreads = 0; 36 | std::vector m_threads; 37 | JobSharedData m_sharedData; 38 | }; 39 | 40 | } -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #if defined(NTSHENGN_OS_WINDOWS) && defined(NTSHENGN_RELEASE) 2 | #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") 3 | #endif 4 | 5 | #include "ntshengn_core.h" 6 | #if defined(NTSHENGN_DEBUG) 7 | #include "exception_handler/ntshengn_exception_handler.h" 8 | #endif 9 | 10 | int main() { 11 | #if defined(NTSHENGN_DEBUG) 12 | NtshEngn::ExceptionHandler exceptionHandler; 13 | exceptionHandler.setExceptionHandler(); 14 | #endif 15 | 16 | NtshEngn::Core core; 17 | core.run("assets/options/options.ntop"); 18 | 19 | #if defined(NTSHENGN_DEBUG) 20 | exceptionHandler.unsetExceptionHandler(); 21 | #endif 22 | 23 | return 0; 24 | } -------------------------------------------------------------------------------- /module_loader/ntshengn_module_loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if defined(NTSHENGN_OS_WINDOWS) 3 | #include "ntshengn_module_loader_windows.h" 4 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 5 | #include "ntshengn_module_loader_linux_freebsd.h" 6 | #endif -------------------------------------------------------------------------------- /module_loader/ntshengn_module_loader_linux_freebsd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../utils/ntshengn_core_defines.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef void* createModule_t(); 9 | typedef void destroyModule_t(void*); 10 | 11 | namespace NtshEngn { 12 | 13 | class ModuleLoader { 14 | public: 15 | template 16 | T* loadModule(const std::string& modulePath) { 17 | NTSHENGN_CORE_INFO("Loading module from: \"" + modulePath + "\"."); 18 | 19 | m_moduleLibraries[typeid(T).name()] = dlopen(modulePath.c_str(), RTLD_LAZY); 20 | if (!m_moduleLibraries[typeid(T).name()]) { 21 | NTSHENGN_CORE_WARNING("Could not load the dynamic library: " + std::string(dlerror())); 22 | return nullptr; 23 | } 24 | 25 | createModule_t* createModule = (createModule_t*)dlsym(m_moduleLibraries[typeid(T).name()], "createModule"); 26 | if (!createModule) { 27 | NTSHENGN_CORE_ERROR("Could not load symbol \"createModule\" from dynamic library: " + std::string(dlerror())); 28 | } 29 | 30 | T* module = static_cast(createModule()); 31 | 32 | NTSHENGN_CORE_INFO("Loaded module \"" + module->getName() + "\"."); 33 | 34 | return module; 35 | } 36 | 37 | template 38 | void unloadModule(T* module) { 39 | NTSHENGN_CORE_INFO("Unloading module \"" + module->getName() + "\"."); 40 | 41 | destroyModule_t* destroyModule = (destroyModule_t*)dlsym(m_moduleLibraries[typeid(T).name()], "destroyModule"); 42 | if (!destroyModule) { 43 | NTSHENGN_CORE_ERROR("Could not load symbol \"destroyModule\": " + std::string(dlerror())); 44 | } 45 | 46 | destroyModule(module); 47 | 48 | if (dlclose(m_moduleLibraries[typeid(T).name()]) != 0) { 49 | NTSHENGN_CORE_ERROR("Could not unload the dynamic library: " + std::string(dlerror())); 50 | } 51 | } 52 | 53 | private: 54 | std::unordered_map m_moduleLibraries; 55 | }; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /module_loader/ntshengn_module_loader_windows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../utils/ntshengn_core_defines.h" 3 | #include 4 | #undef far 5 | #undef near 6 | #include 7 | #include 8 | #include 9 | 10 | typedef void* (__stdcall* createModule_t)(); 11 | typedef void (__stdcall* destroyModule_t)(void*); 12 | 13 | namespace NtshEngn { 14 | 15 | class ModuleLoader { 16 | public: 17 | template 18 | T* loadModule(const std::string& modulePath) { 19 | NTSHENGN_CORE_INFO("Loading module from: " + modulePath + "."); 20 | 21 | m_moduleLibraries[typeid(T).name()] = LoadLibrary(modulePath.c_str()); 22 | if (!m_moduleLibraries[typeid(T).name()]) { 23 | NTSHENGN_CORE_WARNING("Could not load the dynamic library. Error code: " + std::to_string(GetLastError())); 24 | return nullptr; 25 | } 26 | 27 | createModule_t createModule = (createModule_t)GetProcAddress(m_moduleLibraries[typeid(T).name()], "createModule"); 28 | if (!createModule) { 29 | NTSHENGN_CORE_ERROR("Could not load symbol \"createModule\" from dynamic library. Error code: " + std::to_string(GetLastError())); 30 | } 31 | 32 | T* module = static_cast(createModule()); 33 | 34 | NTSHENGN_CORE_INFO("Loaded module \"" + module->getName() + "\"."); 35 | 36 | return module; 37 | } 38 | 39 | template 40 | void unloadModule(T* module) { 41 | NTSHENGN_CORE_INFO("Unloading module " + module->getName() + "."); 42 | 43 | destroyModule_t destroyModule = (destroyModule_t)GetProcAddress(m_moduleLibraries[typeid(T).name()], "destroyModule"); 44 | if (!destroyModule) { 45 | NTSHENGN_CORE_ERROR("Could not load symbol \"destroyModule\"."); 46 | } 47 | 48 | destroyModule(module); 49 | 50 | if (!FreeLibrary(m_moduleLibraries[typeid(T).name()])) { 51 | NTSHENGN_CORE_ERROR("Could not unload the dynamic library."); 52 | } 53 | } 54 | 55 | private: 56 | std::unordered_map m_moduleLibraries; 57 | }; 58 | 59 | } -------------------------------------------------------------------------------- /networking/ntshengn_networking.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_networking.h" 2 | #include "ntshengn_networking_server_socket.h" 3 | #include "ntshengn_networking_client_socket.h" 4 | #if defined(NTSHENGN_OS_WINDOWS) 5 | #include 6 | #pragma comment(lib, "ws2_32.lib") 7 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 8 | #include 9 | #include 10 | #include 11 | #define SOCKET int 12 | #define INVALID_SOCKET -1 13 | #endif 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | void NtshEngn::Networking::init() { 20 | #if defined(NTSHENGN_OS_WINDOWS) 21 | WSADATA data; 22 | if (WSAStartup(MAKEWORD(2, 0), &data) != 0) { 23 | NTSHENGN_NETWORKING_WARNING("Could not start WSA."); 24 | } 25 | #endif 26 | } 27 | 28 | void NtshEngn::Networking::update() { 29 | // Servers 30 | for (const std::unique_ptr& server : m_servers) { 31 | server->update(); 32 | } 33 | 34 | // Clients 35 | for (const std::unique_ptr& client : m_clients) { 36 | client->update(); 37 | } 38 | } 39 | 40 | void NtshEngn::Networking::destroy() { 41 | // Servers 42 | for (const std::unique_ptr& server : m_servers) { 43 | server->destroy(); 44 | } 45 | 46 | m_servers.clear(); 47 | 48 | // Clients 49 | for (const std::unique_ptr& client : m_clients) { 50 | client->destroy(); 51 | } 52 | 53 | m_clients.clear(); 54 | 55 | #if defined(NTSHENGN_OS_WINDOWS) 56 | if (WSACleanup() != 0) { 57 | NTSHENGN_NETWORKING_WARNING("Could not cleanup WSA."); 58 | } 59 | #endif 60 | } 61 | 62 | NtshEngn::ServerSocketInterface* NtshEngn::Networking::createServerSocket(uint16_t port, NetworkType networkType) { 63 | if (networkType == NetworkType::UDP) { 64 | return createServerSocketUDP(port); 65 | } 66 | else if (networkType == NetworkType::TCP) { 67 | return createServerSocketTCP(port); 68 | } 69 | 70 | return nullptr; 71 | } 72 | 73 | NtshEngn::ClientSocketInterface* NtshEngn::Networking::createClientSocket(NetworkType networkType) { 74 | if (networkType == NetworkType::UDP) { 75 | return createClientSocketUDP(); 76 | } 77 | else if (networkType == NetworkType::TCP) { 78 | return createClientSocketTCP(); 79 | } 80 | 81 | return nullptr; 82 | } 83 | 84 | void NtshEngn::Networking::closeServerSocket(ServerSocketInterface* serverSocket) { 85 | serverSocket->destroy(); 86 | 87 | std::forward_list>::iterator prev = m_servers.before_begin(); 88 | for (std::forward_list>::iterator it = m_servers.begin(); it != m_servers.end(); it++) { 89 | if (serverSocket == (*it).get()) { 90 | m_servers.erase_after(prev); 91 | return; 92 | } 93 | 94 | prev = it; 95 | } 96 | } 97 | 98 | void NtshEngn::Networking::closeClientSocket(ClientSocketInterface* clientSocket) { 99 | clientSocket->destroy(); 100 | 101 | std::forward_list>::iterator prev = m_clients.before_begin(); 102 | for (std::forward_list>::iterator it = m_clients.begin(); it != m_clients.end(); it++) { 103 | if (clientSocket == (*it).get()) { 104 | m_clients.erase_after(prev); 105 | return; 106 | } 107 | 108 | prev = it; 109 | } 110 | } 111 | 112 | NtshEngn::ServerSocketInterface* NtshEngn::Networking::createServerSocketUDP(uint16_t port) { 113 | sockaddr_in sockaddrIn; 114 | sockaddrIn.sin_family = AF_INET; 115 | sockaddrIn.sin_port = htons(port); 116 | sockaddrIn.sin_addr.s_addr = htonl(INADDR_ANY); 117 | 118 | SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 119 | if (sock == INVALID_SOCKET) { 120 | NTSHENGN_NETWORKING_WARNING("[SERVER / UDP] Error creating socket for server."); 121 | } 122 | #if defined(NTSHENGN_OS_WINDOWS) 123 | unsigned long nonBlockingSocket = 1; 124 | int nonBlocking = ioctlsocket(sock, FIONBIO, &nonBlockingSocket); 125 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 126 | int nonBlocking = fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); 127 | #endif 128 | if (nonBlocking != 0) { 129 | NTSHENGN_NETWORKING_WARNING("[SERVER / UDP] Error while making the server socket non-blocking."); 130 | } 131 | 132 | if (bind(sock, reinterpret_cast(&sockaddrIn), static_cast(sizeof(sockaddr_in))) == 0) { 133 | NTSHENGN_NETWORKING_INFO("[SERVER / UDP] Server opened on port " + std::to_string(port) + "."); 134 | } 135 | else { 136 | NTSHENGN_NETWORKING_WARNING("[SERVER / UDP] Could not bind socket to port " + std::to_string(port) + " when opening server."); 137 | } 138 | 139 | m_servers.push_front(std::make_unique(static_cast(sock), port, NetworkType::UDP)); 140 | 141 | return m_servers.front().get(); 142 | } 143 | 144 | NtshEngn::ServerSocketInterface* NtshEngn::Networking::createServerSocketTCP(uint16_t port) { 145 | sockaddr_in sockaddrIn; 146 | sockaddrIn.sin_family = AF_INET; 147 | sockaddrIn.sin_port = htons(port); 148 | sockaddrIn.sin_addr.s_addr = htonl(INADDR_ANY); 149 | 150 | SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 151 | if (sock == INVALID_SOCKET) { 152 | NTSHENGN_NETWORKING_WARNING("[SERVER / TCP] Error creating socket for server."); 153 | } 154 | #if defined(NTSHENGN_OS_WINDOWS) 155 | unsigned long nonBlockingSocket = 1; 156 | int nonBlocking = ioctlsocket(sock, FIONBIO, &nonBlockingSocket); 157 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 158 | int nonBlocking = fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); 159 | #endif 160 | if (nonBlocking != 0) { 161 | NTSHENGN_NETWORKING_WARNING("[SERVER / TCP] Error while making the server socket non-blocking."); 162 | } 163 | 164 | if (bind(sock, reinterpret_cast(&sockaddrIn), static_cast(sizeof(sockaddr_in))) == 0) { 165 | NTSHENGN_NETWORKING_INFO("[SERVER / TCP] Server opened on port " + std::to_string(port) + "."); 166 | } 167 | else { 168 | NTSHENGN_NETWORKING_WARNING("[SERVER / TCP] Could not bind socket to port " + std::to_string(port) + " when opening server."); 169 | } 170 | 171 | if (listen(sock, SOMAXCONN) == 0) { 172 | NTSHENGN_NETWORKING_INFO("[SERVER / TCP] Listening on socket."); 173 | } 174 | 175 | m_servers.push_front(std::make_unique(static_cast(sock), port, NetworkType::TCP)); 176 | 177 | return m_servers.front().get(); 178 | } 179 | 180 | NtshEngn::ClientSocketInterface* NtshEngn::Networking::createClientSocketUDP() { 181 | SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 182 | if (sock == INVALID_SOCKET) { 183 | NTSHENGN_NETWORKING_WARNING("[CLIENT / UDP] Error creating socket for client."); 184 | } 185 | #if defined(NTSHENGN_OS_WINDOWS) 186 | unsigned long nonBlockingSocket = 1; 187 | int nonBlocking = ioctlsocket(sock, FIONBIO, &nonBlockingSocket); 188 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 189 | int nonBlocking = fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); 190 | #endif 191 | if (nonBlocking != 0) { 192 | NTSHENGN_NETWORKING_WARNING("[CLIENT / UDP] Error while making the client socket non-blocking."); 193 | } 194 | 195 | NTSHENGN_NETWORKING_INFO("[CLIENT / UDP] Created client socket."); 196 | 197 | m_clients.push_front(std::make_unique(static_cast(sock), NetworkType::UDP)); 198 | 199 | return m_clients.front().get(); 200 | } 201 | 202 | NtshEngn::ClientSocketInterface* NtshEngn::Networking::createClientSocketTCP() { 203 | SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 204 | if (sock == INVALID_SOCKET) { 205 | NTSHENGN_NETWORKING_WARNING("[CLIENT / TCP] Error creating socket for client."); 206 | } 207 | #if defined(NTSHENGN_OS_WINDOWS) 208 | unsigned long nonBlockingSocket = 1; 209 | int nonBlocking = ioctlsocket(sock, FIONBIO, &nonBlockingSocket); 210 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 211 | int nonBlocking = fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); 212 | #endif 213 | if (nonBlocking != 0) { 214 | NTSHENGN_NETWORKING_WARNING("[CLIENT / TCP] Error while making the client socket non-blocking."); 215 | } 216 | 217 | NTSHENGN_NETWORKING_INFO("[CLIENT / TCP] Created client socket."); 218 | 219 | m_clients.push_front(std::make_unique(static_cast(sock), NetworkType::TCP)); 220 | 221 | return m_clients.front().get(); 222 | } -------------------------------------------------------------------------------- /networking/ntshengn_networking.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/networking/ntshengn_networking_interface.h" 3 | #include 4 | #include 5 | 6 | namespace NtshEngn { 7 | 8 | class Networking : public NetworkingInterface { 9 | public: 10 | void init(); 11 | void update(); 12 | void destroy(); 13 | 14 | ServerSocketInterface* createServerSocket(uint16_t port, NetworkType networkType); 15 | ClientSocketInterface* createClientSocket(NetworkType networkType); 16 | 17 | void closeServerSocket(ServerSocketInterface* serverSocket); 18 | void closeClientSocket(ClientSocketInterface* clientSocket); 19 | 20 | private: 21 | ServerSocketInterface* createServerSocketUDP(uint16_t port); 22 | ServerSocketInterface* createServerSocketTCP(uint16_t port); 23 | 24 | ClientSocketInterface* createClientSocketUDP(); 25 | ClientSocketInterface* createClientSocketTCP(); 26 | 27 | private: 28 | std::forward_list> m_servers; 29 | std::forward_list< std::unique_ptr> m_clients; 30 | }; 31 | 32 | } -------------------------------------------------------------------------------- /networking/ntshengn_networking_client_socket.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_networking_client_socket.h" 2 | #if defined(NTSHENGN_OS_WINDOWS) 3 | #include 4 | #include 5 | #pragma comment(lib, "ws2_32.lib") 6 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #define SOCKET int 13 | #define SOCKET_ERROR -1 14 | #define WSAGetLastError() errno 15 | #define WSAECONNRESET ECONNRESET 16 | #define WSAEWOULDBLOCK EWOULDBLOCK 17 | #define closesocket close 18 | #endif 19 | #include 20 | 21 | NtshEngn::ClientSocket::ClientSocket(NtshEngn::Socket socket, NtshEngn::NetworkType networkType): 22 | m_socket(socket), m_networkType(networkType) {} 23 | 24 | void NtshEngn::ClientSocket::update() { 25 | if (m_networkType == NetworkType::UDP) { 26 | updateUDP(); 27 | } 28 | else if (m_networkType == NetworkType::TCP) { 29 | updateTCP(); 30 | } 31 | } 32 | 33 | void NtshEngn::ClientSocket::destroy() { 34 | if (m_networkType == NetworkType::UDP) { 35 | destroyUDP(); 36 | } 37 | else if (m_networkType == NetworkType::TCP) { 38 | destroyTCP(); 39 | } 40 | } 41 | 42 | void NtshEngn::ClientSocket::connectToServer(const std::string& ipAddress, uint16_t port) { 43 | if (m_networkType == NetworkType::UDP) { 44 | connectToServerUDP(ipAddress, port); 45 | } 46 | else if (m_networkType == NetworkType::TCP) { 47 | connectToServerTCP(ipAddress, port); 48 | } 49 | } 50 | 51 | void NtshEngn::ClientSocket::sendDataToServer(const void* data, size_t dataSize) { 52 | if (m_connectedServer.ipAddress == "") { 53 | NTSHENGN_NETWORKING_WARNING("[CLIENT] Client is not connected to any server."); 54 | return; 55 | } 56 | 57 | if (m_networkType == NetworkType::UDP) { 58 | sendDataToServerUDP(data, dataSize); 59 | } 60 | else if (m_networkType == NetworkType::TCP) { 61 | sendDataToServerTCP(data, dataSize); 62 | } 63 | } 64 | 65 | void NtshEngn::ClientSocket::setServerDisconnectCallback(std::function callback) { 66 | m_serverDisconnectCallback = callback; 67 | } 68 | 69 | void NtshEngn::ClientSocket::setDataReceivedCallback(std::function callback) { 70 | m_dataReceivedCallback = callback; 71 | } 72 | 73 | const NtshEngn::ConnectedServer& NtshEngn::ClientSocket::getConnectedServer() { 74 | return m_connectedServer; 75 | } 76 | 77 | void NtshEngn::ClientSocket::updateUDP() { 78 | std::array buffer; 79 | 80 | sockaddr_in serverSockaddr; 81 | serverSockaddr.sin_family = AF_INET; 82 | serverSockaddr.sin_port = htons(m_connectedServer.port); 83 | inet_pton(AF_INET, m_connectedServer.ipAddress.c_str(), &serverSockaddr.sin_addr.s_addr); 84 | socklen_t sockaddrSize = static_cast(sizeof(sockaddr_in)); 85 | int receive = recvfrom(static_cast(m_socket), buffer.data(), NTSHENGN_NETWORKING_BUFFER_SIZE, 0, reinterpret_cast(&serverSockaddr), &sockaddrSize); 86 | if ((receive != 0) && (receive != SOCKET_ERROR)) { 87 | uint16_t disconnectHeader = (static_cast(buffer[1]) << 8) + static_cast(buffer[0]); 88 | if ((receive == sizeof(uint16_t)) && (disconnectHeader == NTSHENGN_NETWORKING_HEADER_DISCONNECTION)) { 89 | // Server disconnection 90 | if (m_serverDisconnectCallback) { 91 | m_serverDisconnectCallback(); 92 | } 93 | 94 | NTSHENGN_NETWORKING_INFO("[CLIENT / UDP] Server has disconnected."); 95 | } 96 | else { 97 | // Receive data 98 | if (m_dataReceivedCallback) { 99 | m_dataReceivedCallback(buffer.data(), receive); 100 | } 101 | } 102 | } 103 | } 104 | 105 | void NtshEngn::ClientSocket::updateTCP() { 106 | std::array buffer; 107 | 108 | if (m_connectedServer.ipAddress != "") { 109 | int receive = recv(static_cast(m_socket), buffer.data(), NTSHENGN_NETWORKING_BUFFER_SIZE, 0); 110 | if ((receive != 0) && (receive != SOCKET_ERROR)) { 111 | // Receive data 112 | if (m_dataReceivedCallback) { 113 | m_dataReceivedCallback(buffer.data(), receive); 114 | } 115 | } 116 | else { 117 | if ((receive == 0) || (WSAGetLastError() == WSAECONNRESET)) { 118 | // Server disconnection 119 | if (m_serverDisconnectCallback) { 120 | m_serverDisconnectCallback(); 121 | } 122 | 123 | m_connectedServer.ipAddress = ""; 124 | 125 | NTSHENGN_NETWORKING_INFO("[CLIENT / TCP] Client has been disconnected from server."); 126 | } 127 | } 128 | } 129 | } 130 | 131 | void NtshEngn::ClientSocket::destroyUDP() { 132 | uint16_t disconnectData = NTSHENGN_NETWORKING_HEADER_DISCONNECTION; 133 | sendDataToServer(&disconnectData, sizeof(uint16_t)); 134 | 135 | if (closesocket(static_cast(m_socket)) == 0) { 136 | NTSHENGN_NETWORKING_INFO("[CLIENT / UDP] Client has been closed."); 137 | } 138 | else { 139 | NTSHENGN_NETWORKING_INFO("[CLIENT / UDP] Could not close client."); 140 | } 141 | } 142 | 143 | void NtshEngn::ClientSocket::destroyTCP() { 144 | if (closesocket(static_cast(m_socket)) == 0) { 145 | NTSHENGN_NETWORKING_INFO("[CLIENT / TCP] Client has been closed."); 146 | } 147 | else { 148 | NTSHENGN_NETWORKING_INFO("[CLIENT / TCP] Could not close client."); 149 | } 150 | } 151 | 152 | void NtshEngn::ClientSocket::connectToServerUDP(const std::string& ipAddress, uint16_t port) { 153 | m_connectedServer.ipAddress = ipAddress; 154 | m_connectedServer.port = port; 155 | 156 | uint16_t connectData = NTSHENGN_NETWORKING_HEADER_CONNECTION; 157 | sendDataToServer(&connectData, sizeof(uint16_t)); 158 | 159 | NTSHENGN_NETWORKING_INFO("[CLIENT / UDP] Registered information on server at IP address " + ipAddress + "."); 160 | } 161 | 162 | void NtshEngn::ClientSocket::connectToServerTCP(const std::string& ipAddress, uint16_t port) { 163 | sockaddr_in sockaddrIn; 164 | sockaddrIn.sin_family = AF_INET; 165 | sockaddrIn.sin_port = htons(port); 166 | inet_pton(AF_INET, ipAddress.c_str(), &sockaddrIn.sin_addr.s_addr); 167 | 168 | if (connect(static_cast(m_socket), reinterpret_cast(&sockaddrIn), static_cast(sizeof(sockaddr_in))) == 0) { 169 | NTSHENGN_NETWORKING_INFO("[CLIENT / TCP] Connected to server at IP address " + ipAddress + " and port " + std::to_string(port) + "."); 170 | } 171 | else { 172 | if (WSAGetLastError() == WSAEWOULDBLOCK) { 173 | NTSHENGN_NETWORKING_INFO("[CLIENT / TCP] Attempting to connect to server at IP address " + ipAddress + " and port " + std::to_string(port) + "."); 174 | } 175 | else { 176 | NTSHENGN_NETWORKING_WARNING("[CLIENT / TCP] Could not connect to server at IP address " + ipAddress + " and port " + std::to_string(port) + "."); 177 | } 178 | } 179 | 180 | m_connectedServer.ipAddress = ipAddress; 181 | m_connectedServer.port = port; 182 | } 183 | 184 | void NtshEngn::ClientSocket::sendDataToServerUDP(const void* data, size_t dataSize) { 185 | sockaddr_in serverSockaddr; 186 | serverSockaddr.sin_family = AF_INET; 187 | serverSockaddr.sin_port = htons(m_connectedServer.port); 188 | inet_pton(AF_INET, m_connectedServer.ipAddress.c_str(), &serverSockaddr.sin_addr.s_addr); 189 | if (sendto(static_cast(m_socket), static_cast(data), static_cast(dataSize), 0, reinterpret_cast(&serverSockaddr), static_cast(sizeof(sockaddr_in))) == SOCKET_ERROR) { 190 | NTSHENGN_NETWORKING_WARNING("[CLIENT / UDP] Error sending data to server."); 191 | } 192 | } 193 | 194 | void NtshEngn::ClientSocket::sendDataToServerTCP(const void* data, size_t dataSize) { 195 | if (send(static_cast(m_socket), static_cast(data), static_cast(dataSize), 0) < 0) { 196 | if (WSAGetLastError() == WSAECONNRESET) { 197 | if (m_serverDisconnectCallback) { 198 | m_serverDisconnectCallback(); 199 | } 200 | 201 | m_connectedServer.ipAddress = ""; 202 | 203 | NTSHENGN_NETWORKING_WARNING("[CLIENT / TCP] Server has disconnected."); 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /networking/ntshengn_networking_client_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/networking/ntshengn_networking_client_socket_interface.h" 3 | #include "ntshengn_networking_common.h" 4 | #include 5 | #include 6 | 7 | namespace NtshEngn { 8 | 9 | class ClientSocket : public ClientSocketInterface { 10 | public: 11 | ClientSocket(Socket socket, NetworkType networkType); 12 | 13 | void update(); 14 | void destroy(); 15 | 16 | void connectToServer(const std::string& ipAddress, uint16_t port); 17 | 18 | void sendDataToServer(const void* data, size_t dataSize); 19 | 20 | void setServerDisconnectCallback(std::function callback); 21 | void setDataReceivedCallback(std::function callback); 22 | 23 | const ConnectedServer& getConnectedServer(); 24 | 25 | private: 26 | void updateUDP(); 27 | void updateTCP(); 28 | 29 | void destroyUDP(); 30 | void destroyTCP(); 31 | 32 | void connectToServerUDP(const std::string& ipAddress, uint16_t port); 33 | void connectToServerTCP(const std::string& ipAddress, uint16_t port); 34 | 35 | void sendDataToServerUDP(const void* data, size_t dataSize); 36 | void sendDataToServerTCP(const void* data, size_t dataSize); 37 | 38 | private: 39 | Socket m_socket; 40 | NetworkType m_networkType; 41 | ConnectedServer m_connectedServer; 42 | 43 | std::function m_serverDisconnectCallback; 44 | std::function m_dataReceivedCallback; 45 | }; 46 | 47 | } -------------------------------------------------------------------------------- /networking/ntshengn_networking_server_socket.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_networking_server_socket.h" 2 | #include "../../Common/utils/ntshengn_defines.h" 3 | #if defined(NTSHENGN_OS_WINDOWS) 4 | #include 5 | #include 6 | #pragma comment(lib, "ws2_32.lib") 7 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #define SOCKET int 14 | #define SOCKET_ERROR -1 15 | #define INVALID_SOCKET -1 16 | #define WSAGetLastError() errno 17 | #define WSAECONNRESET ECONNRESET 18 | #define WSAEWOULDBLOCK EWOULDBLOCK 19 | #define closesocket close 20 | #endif 21 | #include 22 | #include 23 | #include 24 | 25 | NtshEngn::ServerSocket::ServerSocket(Socket socket, uint16_t port, NetworkType networkType): 26 | m_socket(socket), m_port(port), m_networkType(networkType) {} 27 | 28 | void NtshEngn::ServerSocket::update() { 29 | if (m_networkType == NetworkType::UDP) { 30 | updateUDP(); 31 | } 32 | else if (m_networkType == NetworkType::TCP) { 33 | updateTCP(); 34 | } 35 | } 36 | 37 | void NtshEngn::ServerSocket::destroy() { 38 | if (m_networkType == NetworkType::UDP) { 39 | destroyUDP(); 40 | } 41 | else if (m_networkType == NetworkType::TCP) { 42 | destroyTCP(); 43 | } 44 | } 45 | 46 | void NtshEngn::ServerSocket::sendDataToClient(ConnectedClientID clientID, const void* data, size_t dataSize) { 47 | NTSHENGN_ASSERT(m_connectedClients.find(clientID) != m_connectedClients.end(), "No client with ID " + std::to_string(clientID) + " is connected to the server."); 48 | 49 | if (m_networkType == NetworkType::UDP) { 50 | sendDataToClientUDP(clientID, data, dataSize); 51 | } 52 | else if (m_networkType == NetworkType::TCP) { 53 | sendDataToClientTCP(clientID, data, dataSize); 54 | } 55 | } 56 | 57 | void NtshEngn::ServerSocket::setClientConnectCallback(std::function callback) { 58 | m_clientConnectCallback = callback; 59 | } 60 | 61 | void NtshEngn::ServerSocket::setClientDisconnectCallback(std::function callback) { 62 | m_clientDisconnectCallback = callback; 63 | } 64 | 65 | void NtshEngn::ServerSocket::setDataReceivedCallback(std::function callback) { 66 | m_dataReceivedCallback = callback; 67 | } 68 | 69 | uint16_t NtshEngn::ServerSocket::getPort() { 70 | return m_port; 71 | } 72 | 73 | const std::unordered_map& NtshEngn::ServerSocket::getConnectedClients() { 74 | return m_connectedClients; 75 | } 76 | 77 | void NtshEngn::ServerSocket::updateUDP() { 78 | std::array buffer; 79 | 80 | sockaddr_in newSockaddr; 81 | socklen_t sockaddrSize = static_cast(sizeof(sockaddr_in)); 82 | int receive = recvfrom(static_cast(m_socket), buffer.data(), NTSHENGN_NETWORKING_BUFFER_SIZE, 0, reinterpret_cast(&newSockaddr), &sockaddrSize); 83 | while ((receive != 0) && (receive != SOCKET_ERROR)) { 84 | std::array clientIPArray; 85 | const char* ipAddress = inet_ntop(AF_INET, &newSockaddr.sin_addr, clientIPArray.data(), clientIPArray.size()); 86 | std::string clientIP(clientIPArray.data(), strlen(ipAddress)); 87 | uint16_t clientPort = ntohs(newSockaddr.sin_port); 88 | 89 | ConnectedClientID currentConnectedClientID = std::numeric_limits::max(); 90 | for (auto& client : m_connectedClients) { 91 | if ((client.second.ipAddress == clientIP) && (client.second.port == clientPort)) { 92 | currentConnectedClientID = client.first; 93 | break; 94 | } 95 | } 96 | 97 | uint16_t header = (static_cast(buffer[1]) << 8) + static_cast(buffer[0]); 98 | if (receive == sizeof(uint16_t)) { 99 | if (header == NTSHENGN_NETWORKING_HEADER_CONNECTION) { 100 | if (currentConnectedClientID == std::numeric_limits::max()) { 101 | // Client connection 102 | ConnectedClient connectedClient; 103 | connectedClient.ipAddress = clientIP; 104 | connectedClient.port = clientPort; 105 | m_connectedClients[m_connectedClientID++] = connectedClient; 106 | 107 | currentConnectedClientID = m_connectedClientID - 1; 108 | 109 | if (m_clientConnectCallback) { 110 | m_clientConnectCallback(currentConnectedClientID); 111 | } 112 | 113 | NTSHENGN_NETWORKING_INFO("[SERVER / UDP] New client with ConnectedClientID " + std::to_string(currentConnectedClientID) + " connected to server."); 114 | } 115 | } 116 | else if (header == NTSHENGN_NETWORKING_HEADER_DISCONNECTION) { 117 | // Client disconnection 118 | m_connectedClients.erase(currentConnectedClientID); 119 | 120 | if (m_clientDisconnectCallback) { 121 | m_clientDisconnectCallback(currentConnectedClientID); 122 | } 123 | 124 | NTSHENGN_NETWORKING_INFO("[SERVER / UDP] Client with ConnectedClientID " + std::to_string(currentConnectedClientID) + " disconnected from server."); 125 | } 126 | } 127 | else { 128 | // Receive data 129 | if (m_dataReceivedCallback) { 130 | m_dataReceivedCallback(currentConnectedClientID, buffer.data(), receive); 131 | } 132 | } 133 | 134 | receive = recvfrom(static_cast(m_socket), buffer.data(), NTSHENGN_NETWORKING_BUFFER_SIZE, 0, reinterpret_cast(&newSockaddr), &sockaddrSize); 135 | } 136 | } 137 | 138 | void NtshEngn::ServerSocket::updateTCP() { 139 | std::array buffer; 140 | 141 | sockaddr_in newClientSockaddr; 142 | socklen_t sockaddrSize = static_cast(sizeof(sockaddr_in)); 143 | SOCKET newClientSocket = accept(static_cast(m_socket), reinterpret_cast(&newClientSockaddr), &sockaddrSize); 144 | while ((newClientSocket != INVALID_SOCKET)) { 145 | // Client connection 146 | ConnectedClient connectedClient; 147 | connectedClient.socket = newClientSocket; 148 | connectedClient.port = ntohs(newClientSockaddr.sin_port); 149 | std::array clientIPArray; 150 | const char* ipAddress = inet_ntop(AF_INET, &newClientSockaddr.sin_addr, clientIPArray.data(), clientIPArray.size()); 151 | std::string clientIP(clientIPArray.data(), strlen(ipAddress)); 152 | connectedClient.ipAddress = clientIP; 153 | m_connectedClients[m_connectedClientID++] = connectedClient; 154 | 155 | if (m_clientConnectCallback) { 156 | m_clientConnectCallback(m_connectedClientID - 1); 157 | } 158 | 159 | NTSHENGN_NETWORKING_INFO("[SERVER / TCP] New client with ConnectedClientID " + std::to_string(m_connectedClientID - 1) + " connected to server."); 160 | 161 | newClientSocket = accept(static_cast(m_socket), reinterpret_cast(&newClientSockaddr), &sockaddrSize); 162 | } 163 | 164 | for (auto it = m_connectedClients.begin(); it != m_connectedClients.end(); ) { 165 | ConnectedClient client = it->second; 166 | bool clientDisconnected = false; 167 | 168 | SOCKET clientSocket = client.socket; 169 | int receive = recv(clientSocket, buffer.data(), NTSHENGN_NETWORKING_BUFFER_SIZE, 0); 170 | if ((receive != 0) && (receive != SOCKET_ERROR)) { 171 | // Receive data 172 | if (m_dataReceivedCallback) { 173 | m_dataReceivedCallback(it->first, buffer.data(), receive); 174 | } 175 | } 176 | else { 177 | if ((receive == 0) || (WSAGetLastError() == WSAECONNRESET)) { 178 | // Client disconnection 179 | ConnectedClientID disconnectedClientID = it->first; 180 | 181 | it = m_connectedClients.erase(it); 182 | clientDisconnected = true; 183 | 184 | if (m_clientDisconnectCallback) { 185 | m_clientDisconnectCallback(disconnectedClientID); 186 | } 187 | 188 | NTSHENGN_NETWORKING_INFO("[SERVER / TCP] Client with ConnectedClientID " + std::to_string(disconnectedClientID) + " disconnected from server."); 189 | } 190 | } 191 | 192 | if (!clientDisconnected) { 193 | it++; 194 | } 195 | } 196 | } 197 | 198 | void NtshEngn::ServerSocket::destroyUDP() { 199 | for (auto& it : m_connectedClients) { 200 | uint16_t disconnectData = NTSHENGN_NETWORKING_HEADER_DISCONNECTION; 201 | sendDataToClient(it.first, &disconnectData, sizeof(uint16_t)); 202 | } 203 | 204 | if (closesocket(static_cast(m_socket)) == 0) { 205 | NTSHENGN_NETWORKING_INFO("[SERVER / UDP] Server on port " + std::to_string(m_port) + " closed."); 206 | } 207 | else { 208 | NTSHENGN_NETWORKING_INFO("[SERVER / UDP] Could not close server on port " + std::to_string(m_port) + "."); 209 | } 210 | } 211 | 212 | void NtshEngn::ServerSocket::destroyTCP() { 213 | if (closesocket(static_cast(m_socket)) == 0) { 214 | NTSHENGN_NETWORKING_INFO("[SERVER / TCP] Server on port " + std::to_string(m_port) + " closed."); 215 | } 216 | else { 217 | NTSHENGN_NETWORKING_INFO("[SERVER / TCP] Could not close server on port " + std::to_string(m_port) + "."); 218 | } 219 | } 220 | 221 | void NtshEngn::ServerSocket::sendDataToClientUDP(ConnectedClientID clientID, const void* data, size_t dataSize) { 222 | sockaddr_in clientSockaddr; 223 | clientSockaddr.sin_family = AF_INET; 224 | clientSockaddr.sin_port = htons(m_connectedClients[clientID].port); 225 | inet_pton(AF_INET, m_connectedClients[clientID].ipAddress.c_str(), &clientSockaddr.sin_addr.s_addr); 226 | if (sendto(static_cast(m_socket), static_cast(data), static_cast(dataSize), 0, reinterpret_cast(&clientSockaddr), static_cast(sizeof(sockaddr_in))) == SOCKET_ERROR) { 227 | NTSHENGN_NETWORKING_WARNING("[SERVER / UDP] Error sending data to client."); 228 | } 229 | } 230 | 231 | void NtshEngn::ServerSocket::sendDataToClientTCP(ConnectedClientID clientID, const void* data, size_t dataSize) { 232 | if (send(m_connectedClients[clientID].socket, static_cast(data), static_cast(dataSize), 0) < 0) { 233 | if (WSAGetLastError() == WSAECONNRESET) { 234 | if (m_clientDisconnectCallback) { 235 | m_clientDisconnectCallback(clientID); 236 | } 237 | 238 | m_connectedClients.erase(clientID); 239 | 240 | NTSHENGN_NETWORKING_INFO("[SERVER / TCP] Client with ClientID " + std::to_string(clientID) + " has disconnected."); 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /networking/ntshengn_networking_server_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/networking/ntshengn_networking_server_socket_interface.h" 3 | #include "ntshengn_networking_common.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace NtshEngn { 9 | 10 | class ServerSocket : public ServerSocketInterface { 11 | public: 12 | ServerSocket(Socket socket, uint16_t port, NetworkType networkType); 13 | 14 | void update(); 15 | void destroy(); 16 | 17 | void sendDataToClient(ConnectedClientID clientID, const void* data, size_t dataSize); 18 | 19 | void setClientConnectCallback(std::function callback); 20 | void setClientDisconnectCallback(std::function callback); 21 | void setDataReceivedCallback(std::function callback); 22 | 23 | uint16_t getPort(); 24 | const std::unordered_map& getConnectedClients(); 25 | 26 | private: 27 | void updateUDP(); 28 | void updateTCP(); 29 | 30 | void destroyUDP(); 31 | void destroyTCP(); 32 | 33 | void sendDataToClientUDP(ConnectedClientID clientID, const void* data, size_t dataSize); 34 | void sendDataToClientTCP(ConnectedClientID clientID, const void* data, size_t dataSize); 35 | 36 | private: 37 | Socket m_socket; 38 | uint16_t m_port; 39 | NetworkType m_networkType; 40 | std::unordered_map m_connectedClients; 41 | ConnectedClientID m_connectedClientID = 0; 42 | 43 | std::function m_clientConnectCallback; 44 | std::function m_clientDisconnectCallback; 45 | std::function m_dataReceivedCallback; 46 | }; 47 | 48 | } -------------------------------------------------------------------------------- /ntshengn_core.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_core.h" 2 | #include 3 | #include 4 | 5 | void NtshEngn::Core::run(const std::string& optionsFilePath) { 6 | std::string windowTitle = "NutshellEngine"; 7 | std::string windowIconImagePath = ""; 8 | std::string firstScenePath = ""; 9 | 10 | if (std::filesystem::exists(optionsFilePath)) { 11 | JSON json; 12 | const JSON::Node& optionsRoot = json.read(optionsFilePath); 13 | 14 | if (optionsRoot.contains("windowTitle")) { 15 | windowTitle = optionsRoot["windowTitle"].getString(); 16 | } 17 | 18 | if (optionsRoot.contains("windowIconImagePath")) { 19 | windowIconImagePath = optionsRoot["windowIconImagePath"].getString(); 20 | } 21 | 22 | if (optionsRoot.contains("maxFPS")) { 23 | m_frameLimiter.setMaxFPS(static_cast(optionsRoot["maxFPS"].getNumber())); 24 | } 25 | 26 | if (optionsRoot.contains("firstScenePath")) { 27 | firstScenePath = optionsRoot["firstScenePath"].getString(); 28 | } 29 | 30 | if (optionsRoot.contains("startProfiling")) { 31 | if (optionsRoot["startProfiling"].getBoolean()) { 32 | m_profiler.start(windowTitle); 33 | } 34 | } 35 | } 36 | else { 37 | NTSHENGN_CORE_WARNING("Options file \"" + optionsFilePath + "\" does not exist."); 38 | } 39 | 40 | // Initialize 41 | m_profiler.startBlock("Init"); 42 | init(); 43 | m_profiler.endBlock(); 44 | 45 | Image* iconImage = nullptr; 46 | if (windowIconImagePath != "") { 47 | iconImage = m_assetManager.loadImage(windowIconImagePath); 48 | } 49 | 50 | if (m_windowModule && m_windowModule->isWindowOpen(m_windowModule->getMainWindowID())) { 51 | m_windowModule->setWindowTitle(m_windowModule->getMainWindowID(), windowTitle); 52 | 53 | if (iconImage) { 54 | m_windowModule->setWindowIcon(m_windowModule->getMainWindowID(), *iconImage); 55 | m_assetManager.destroyImage(m_assetManager.getImageName(iconImage)); 56 | } 57 | } 58 | 59 | if (firstScenePath != "") { 60 | m_sceneManager.goToScene(firstScenePath); 61 | } 62 | 63 | // Loop 64 | m_profiler.startBlock("Update"); 65 | update(); 66 | m_profiler.endBlock(); 67 | 68 | // Destroy 69 | m_profiler.startBlock("Destroy"); 70 | destroy(); 71 | m_profiler.endBlock(); 72 | 73 | if (m_profiler.isRunning()) { 74 | NTSHENGN_CORE_INFO("Profiling session: \n" + ProfilerResultNode::to_string(m_profiler.end())); 75 | } 76 | } 77 | 78 | NtshEngn::GraphicsModuleInterface* NtshEngn::Core::getGraphicsModule() { 79 | return m_graphicsModule; 80 | } 81 | 82 | NtshEngn::PhysicsModuleInterface* NtshEngn::Core::getPhysicsModule() { 83 | return m_physicsModule; 84 | } 85 | 86 | NtshEngn::WindowModuleInterface* NtshEngn::Core::getWindowModule() { 87 | return m_windowModule; 88 | } 89 | 90 | NtshEngn::AudioModuleInterface* NtshEngn::Core::getAudioModule() { 91 | return m_audioModule; 92 | } 93 | 94 | NtshEngn::ECS* NtshEngn::Core::getECS() { 95 | return &m_ecs; 96 | } 97 | 98 | NtshEngn::AssetManager* NtshEngn::Core::getAssetManager() { 99 | return &m_assetManager; 100 | } 101 | 102 | NtshEngn::FrameLimiter* NtshEngn::Core::getFrameLimiter() { 103 | return &m_frameLimiter; 104 | } 105 | 106 | NtshEngn::JobSystem* NtshEngn::Core::getJobSystem() { 107 | return &m_jobSystem; 108 | } 109 | 110 | NtshEngn::Profiler* NtshEngn::Core::getProfiler() { 111 | return &m_profiler; 112 | } 113 | 114 | NtshEngn::Networking* NtshEngn::Core::getNetworking() { 115 | return &m_networking; 116 | } 117 | 118 | NtshEngn::SceneManager* NtshEngn::Core::getSceneManager() { 119 | return &m_sceneManager; 120 | } 121 | 122 | void NtshEngn::Core::init() { 123 | // Load modules 124 | m_profiler.startBlock("Load modules"); 125 | loadModules(); 126 | m_profiler.endBlock(); 127 | 128 | // Load scripts 129 | m_profiler.startBlock("Load scripts"); 130 | loadScripts(); 131 | m_profiler.endBlock(); 132 | 133 | // Pass System Modules 134 | passSystemModules(); 135 | 136 | // Pass Asset Loader Module 137 | passAssetLoaderModule(); 138 | 139 | // Pass Script Manager 140 | passScriptManager(); 141 | 142 | // Initialize ECS 143 | initializeECS(); 144 | 145 | // Pass Asset Manager 146 | passAssetManager(); 147 | 148 | // Pass Frame Limiter 149 | passFrameLimiter(); 150 | 151 | // Initialize Job System 152 | initializeJobSystem(); 153 | 154 | // Pass Profiler 155 | passProfiler(); 156 | 157 | // Initialize Networking 158 | initializeNetworking(); 159 | 160 | // Pass Scene Manager 161 | passSceneManager(); 162 | 163 | // Initialize System Modules 164 | m_profiler.startBlock("Init Window Module"); 165 | NTSHENGN_POINTER_EXECUTE(m_windowModule, init()); 166 | m_profiler.endBlock(); 167 | NTSHENGN_POINTER_EXECUTE(m_windowModule, openWindow(1280, 720, "")); 168 | m_profiler.startBlock("Init Graphics Module"); 169 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, init()); 170 | m_profiler.endBlock(); 171 | m_profiler.startBlock("Init Physics Module"); 172 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, init()); 173 | m_profiler.endBlock(); 174 | m_profiler.startBlock("Init Audio Module"); 175 | NTSHENGN_POINTER_EXECUTE(m_audioModule, init()); 176 | m_profiler.endBlock(); 177 | } 178 | 179 | void NtshEngn::Core::update() { 180 | double lastFrame = std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); 181 | bool applicationClose = false; 182 | while (!applicationClose) { 183 | double currentFrame = std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); 184 | float dt = static_cast((currentFrame - lastFrame) / 1000.0); 185 | 186 | // Update 187 | m_profiler.startBlock("Frame"); 188 | 189 | m_profiler.startBlock("Update Networking"); 190 | m_networking.update(); 191 | m_profiler.endBlock(); 192 | m_profiler.startBlock("Update Window Module"); 193 | NTSHENGN_POINTER_EXECUTE(m_windowModule, update(dt)); 194 | m_profiler.endBlock(); 195 | m_profiler.startBlock("Update Scripting"); 196 | m_scripting.update(dt); 197 | m_profiler.endBlock(); 198 | m_profiler.startBlock("Update Physics Module"); 199 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, update(dt)); 200 | m_profiler.endBlock(); 201 | m_profiler.startBlock("Update Audio Module"); 202 | NTSHENGN_POINTER_EXECUTE(m_audioModule, update(dt)); 203 | m_profiler.endBlock(); 204 | m_profiler.startBlock("Update Graphics Module"); 205 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, update(dt)); 206 | m_profiler.endBlock(); 207 | 208 | applicationClose = m_windowModule ? !m_windowModule->isWindowOpen(m_windowModule->getMainWindowID()) : true; 209 | 210 | m_profiler.startBlock("Wait Frame Limiter"); 211 | m_frameLimiter.wait(currentFrame); 212 | m_profiler.endBlock(); 213 | 214 | m_profiler.endBlock(); 215 | 216 | lastFrame = currentFrame; 217 | } 218 | } 219 | 220 | void NtshEngn::Core::destroy() { 221 | // Destroy Networking 222 | m_networking.destroy(); 223 | 224 | // Destroy Job System 225 | m_jobSystem.destroy(); 226 | 227 | // Destroy System Modules 228 | m_profiler.startBlock("Destroy Graphics Module"); 229 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, destroy()); 230 | m_profiler.endBlock(); 231 | m_profiler.startBlock("Destroy Physics Module"); 232 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, destroy()); 233 | m_profiler.endBlock(); 234 | m_profiler.startBlock("Destroy Window Module"); 235 | NTSHENGN_POINTER_EXECUTE(m_windowModule, destroy()); 236 | m_profiler.endBlock(); 237 | m_profiler.startBlock("Destroy Audio Module"); 238 | NTSHENGN_POINTER_EXECUTE(m_audioModule, destroy()); 239 | m_profiler.endBlock(); 240 | 241 | // Unload scripts 242 | m_profiler.startBlock("Unload scripts"); 243 | unloadScripts(); 244 | m_profiler.endBlock(); 245 | 246 | // Unload modules 247 | m_profiler.startBlock("Unload modules"); 248 | unloadModules(); 249 | m_profiler.endBlock(); 250 | } 251 | 252 | void NtshEngn::Core::loadModules() { 253 | #if defined(NTSHENGN_OS_WINDOWS) 254 | const std::string graphicsModulePath = "./modules/NutshellEngine-GraphicsModule.dll"; 255 | const std::string physicsModulePath = "./modules/NutshellEngine-PhysicsModule.dll"; 256 | const std::string windowModulePath = "./modules/NutshellEngine-WindowModule.dll"; 257 | const std::string audioModulePath = "./modules/NutshellEngine-AudioModule.dll"; 258 | const std::string assetLoaderModulePath = "./modules/NutshellEngine-AssetLoaderModule.dll"; 259 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 260 | const std::string graphicsModulePath = "./modules/libNutshellEngine-GraphicsModule.so"; 261 | const std::string physicsModulePath = "./modules/libNutshellEngine-PhysicsModule.so"; 262 | const std::string windowModulePath = "./modules/libNutshellEngine-WindowModule.so"; 263 | const std::string audioModulePath = "./modules/libNutshellEngine-AudioModule.so"; 264 | const std::string assetLoaderModulePath = "./modules/libNutshellEngine-AssetLoaderModule.so"; 265 | #endif 266 | 267 | if (std::filesystem::exists(std::filesystem::current_path().string() + "/" + graphicsModulePath)) { 268 | m_graphicsModule = m_moduleLoader.loadModule(graphicsModulePath); 269 | } 270 | if (std::filesystem::exists(std::filesystem::current_path().string() + "/" + physicsModulePath)) { 271 | m_physicsModule = m_moduleLoader.loadModule(physicsModulePath); 272 | } 273 | if (std::filesystem::exists(std::filesystem::current_path().string() + "/" + windowModulePath)) { 274 | m_windowModule = m_moduleLoader.loadModule(windowModulePath); 275 | } 276 | if (std::filesystem::exists(std::filesystem::current_path().string() + "/" + audioModulePath)) { 277 | m_audioModule = m_moduleLoader.loadModule(audioModulePath); 278 | } 279 | if (std::filesystem::exists(std::filesystem::current_path().string() + "/" + assetLoaderModulePath)) { 280 | m_assetLoaderModule = m_moduleLoader.loadModule(assetLoaderModulePath); 281 | } 282 | } 283 | 284 | void NtshEngn::Core::unloadModules() { 285 | if (m_graphicsModule) { 286 | m_moduleLoader.unloadModule(m_graphicsModule); 287 | } 288 | if (m_physicsModule) { 289 | m_moduleLoader.unloadModule(m_physicsModule); 290 | } 291 | if (m_windowModule) { 292 | m_moduleLoader.unloadModule(m_windowModule); 293 | } 294 | if (m_audioModule) { 295 | m_moduleLoader.unloadModule(m_audioModule); 296 | } 297 | if (m_assetLoaderModule) { 298 | m_moduleLoader.unloadModule(m_assetLoaderModule); 299 | } 300 | } 301 | 302 | void NtshEngn::Core::loadScripts() { 303 | #if defined(NTSHENGN_OS_WINDOWS) 304 | const std::string scriptsPath = "./NutshellEngine-Scripts.dll"; 305 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 306 | const std::string scriptsPath = "./libNutshellEngine-Scripts.so"; 307 | #endif 308 | 309 | if (std::filesystem::exists(std::filesystem::current_path().string() + "/" + scriptsPath)) { 310 | m_scriptManager = m_scriptManagerLoader.loadScriptManager(scriptsPath); 311 | } 312 | } 313 | 314 | void NtshEngn::Core::unloadScripts() { 315 | if (m_scriptManager) { 316 | m_scriptManagerLoader.unloadScriptManager(m_scriptManager); 317 | } 318 | } 319 | 320 | void NtshEngn::Core::passSystemModules() { 321 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, setSystemModules(m_graphicsModule, m_physicsModule, m_windowModule, m_audioModule)); 322 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, setSystemModules(m_graphicsModule, m_physicsModule, m_windowModule, m_audioModule)); 323 | m_scripting.setSystemModules(m_graphicsModule, m_physicsModule, m_windowModule, m_audioModule); 324 | NTSHENGN_POINTER_EXECUTE(m_windowModule, setSystemModules(m_graphicsModule, m_physicsModule, m_windowModule, m_audioModule)); 325 | NTSHENGN_POINTER_EXECUTE(m_audioModule, setSystemModules(m_graphicsModule, m_physicsModule, m_windowModule, m_audioModule)); 326 | } 327 | 328 | void NtshEngn::Core::passAssetLoaderModule() { 329 | m_assetManager.setAssetLoaderModule(m_assetLoaderModule); 330 | } 331 | 332 | void NtshEngn::Core::passScriptManager() { 333 | m_scripting.setScriptManager(m_scriptManager); 334 | 335 | m_sceneManager.setScriptManager(m_scriptManager); 336 | } 337 | 338 | void NtshEngn::Core::initializeECS() { 339 | m_ecs.init(); 340 | 341 | passECS(); 342 | 343 | m_ecs.registerComponent(); 344 | m_ecs.registerComponent(); 345 | m_ecs.registerComponent(); 346 | m_ecs.registerComponent(); 347 | m_ecs.registerComponent(); 348 | m_ecs.registerComponent(); 349 | m_ecs.registerComponent(); 350 | m_ecs.registerComponent(); 351 | 352 | if (m_graphicsModule) { 353 | m_ecs.registerSystem(m_graphicsModule); 354 | m_ecs.setSystemComponents(m_graphicsModule->getComponentMask()); 355 | } 356 | 357 | if (m_physicsModule) { 358 | m_ecs.registerSystem(m_physicsModule); 359 | m_ecs.setSystemComponents(m_physicsModule->getComponentMask()); 360 | } 361 | 362 | m_ecs.registerSystem(&m_scripting); 363 | ComponentMask scriptingComponents; 364 | scriptingComponents.set(m_ecs.getComponentID()); 365 | m_ecs.setSystemComponents(scriptingComponents); 366 | 367 | if (m_windowModule) { 368 | m_ecs.registerSystem(m_windowModule); 369 | m_ecs.setSystemComponents(m_windowModule->getComponentMask()); 370 | } 371 | 372 | if (m_audioModule) { 373 | m_ecs.registerSystem(m_audioModule); 374 | m_ecs.setSystemComponents(m_audioModule->getComponentMask()); 375 | } 376 | } 377 | 378 | void NtshEngn::Core::passECS() { 379 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, setECS(&m_ecs)); 380 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, setECS(&m_ecs)); 381 | m_scripting.setECS(&m_ecs); 382 | NTSHENGN_POINTER_EXECUTE(m_windowModule, setECS(&m_ecs)); 383 | NTSHENGN_POINTER_EXECUTE(m_audioModule, setECS(&m_ecs)); 384 | 385 | m_sceneManager.setECS(&m_ecs); 386 | } 387 | 388 | void NtshEngn::Core::passAssetManager() { 389 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, setAssetManager(&m_assetManager)); 390 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, setAssetManager(&m_assetManager)); 391 | m_scripting.setAssetManager(&m_assetManager); 392 | NTSHENGN_POINTER_EXECUTE(m_windowModule, setAssetManager(&m_assetManager)); 393 | NTSHENGN_POINTER_EXECUTE(m_audioModule, setAssetManager(&m_assetManager)); 394 | NTSHENGN_POINTER_EXECUTE(m_assetLoaderModule, setAssetManager(&m_assetManager)); 395 | 396 | m_sceneManager.setAssetManager(&m_assetManager); 397 | } 398 | 399 | void NtshEngn::Core::passFrameLimiter() { 400 | m_scripting.setFrameLimiter(&m_frameLimiter); 401 | } 402 | 403 | void NtshEngn::Core::initializeJobSystem() { 404 | m_jobSystem.init(); 405 | 406 | passJobSystem(); 407 | } 408 | 409 | void NtshEngn::Core::passJobSystem() { 410 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, setJobSystem(&m_jobSystem)); 411 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, setJobSystem(&m_jobSystem)); 412 | m_scripting.setJobSystem(&m_jobSystem); 413 | NTSHENGN_POINTER_EXECUTE(m_windowModule, setJobSystem(&m_jobSystem)); 414 | NTSHENGN_POINTER_EXECUTE(m_audioModule, setJobSystem(&m_jobSystem)); 415 | } 416 | 417 | void NtshEngn::Core::passProfiler() { 418 | NTSHENGN_POINTER_EXECUTE(m_graphicsModule, setProfiler(&m_profiler)); 419 | NTSHENGN_POINTER_EXECUTE(m_physicsModule, setProfiler(&m_profiler)); 420 | m_scripting.setProfiler(&m_profiler); 421 | NTSHENGN_POINTER_EXECUTE(m_windowModule, setProfiler(&m_profiler)); 422 | NTSHENGN_POINTER_EXECUTE(m_audioModule, setProfiler(&m_profiler)); 423 | NTSHENGN_POINTER_EXECUTE(m_assetLoaderModule, setProfiler(&m_profiler)); 424 | } 425 | 426 | void NtshEngn::Core::initializeNetworking() { 427 | m_networking.init(); 428 | 429 | passNetworking(); 430 | } 431 | 432 | void NtshEngn::Core::passNetworking() { 433 | m_scripting.setNetworking(&m_networking); 434 | } 435 | 436 | void NtshEngn::Core::passSceneManager() { 437 | m_scripting.setSceneManager(&m_sceneManager); 438 | } -------------------------------------------------------------------------------- /ntshengn_core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Common/utils/ntshengn_defines.h" 3 | #include "Common/utils/ntshengn_enums.h" 4 | #include "Common/utils/ntshengn_utils_json.h" 5 | #include "Common/modules/ntshengn_graphics_module_interface.h" 6 | #include "Common/modules/ntshengn_physics_module_interface.h" 7 | #include "Common/modules/ntshengn_window_module_interface.h" 8 | #include "Common/modules/ntshengn_audio_module_interface.h" 9 | #include "Common/modules/ntshengn_asset_loader_module_interface.h" 10 | #include "ecs/ntshengn_ecs.h" 11 | #include "asset_manager/ntshengn_asset_manager.h" 12 | #include "job_system/ntshengn_job_system.h" 13 | #include "profiler/ntshengn_profiler.h" 14 | #include "frame_limiter/ntshengn_frame_limiter.h" 15 | #include "scene_manager/ntshengn_scene_manager.h" 16 | #include "Common/script/ntshengn_script_manager_interface.h" 17 | #include "networking/ntshengn_networking.h" 18 | #include "utils/ntshengn_core_defines.h" 19 | #include "module_loader/ntshengn_module_loader.h" 20 | #include "script_manager_loader/ntshengn_script_manager_loader.h" 21 | #include "scripting/ntshengn_scripting.h" 22 | #include 23 | 24 | namespace NtshEngn { 25 | 26 | class Core { 27 | public: 28 | void run(const std::string& optionsFilePath); 29 | 30 | GraphicsModuleInterface* getGraphicsModule(); 31 | PhysicsModuleInterface* getPhysicsModule(); 32 | WindowModuleInterface* getWindowModule(); 33 | AudioModuleInterface* getAudioModule(); 34 | 35 | ECS* getECS(); 36 | 37 | AssetManager* getAssetManager(); 38 | 39 | FrameLimiter* getFrameLimiter(); 40 | 41 | JobSystem* getJobSystem(); 42 | 43 | Profiler* getProfiler(); 44 | 45 | Networking* getNetworking(); 46 | 47 | SceneManager* getSceneManager(); 48 | 49 | private: 50 | void init(); 51 | void update(); 52 | void destroy(); 53 | 54 | void loadModules(); 55 | void unloadModules(); 56 | 57 | void loadScripts(); 58 | void unloadScripts(); 59 | 60 | void passSystemModules(); 61 | void passAssetLoaderModule(); 62 | 63 | void passScriptManager(); 64 | 65 | void initializeECS(); 66 | void passECS(); 67 | 68 | void passAssetManager(); 69 | 70 | void passFrameLimiter(); 71 | 72 | void initializeJobSystem(); 73 | void passJobSystem(); 74 | 75 | void passProfiler(); 76 | 77 | void initializeNetworking(); 78 | void passNetworking(); 79 | 80 | void passSceneManager(); 81 | 82 | private: 83 | GraphicsModuleInterface* m_graphicsModule = nullptr; 84 | PhysicsModuleInterface* m_physicsModule = nullptr; 85 | WindowModuleInterface* m_windowModule = nullptr; 86 | AudioModuleInterface* m_audioModule = nullptr; 87 | 88 | AssetLoaderModuleInterface* m_assetLoaderModule = nullptr; 89 | 90 | ScriptManagerInterface* m_scriptManager = nullptr; 91 | Scripting m_scripting; 92 | 93 | ECS m_ecs; 94 | 95 | ModuleLoader m_moduleLoader; 96 | 97 | ScriptManagerLoader m_scriptManagerLoader; 98 | 99 | AssetManager m_assetManager; 100 | 101 | FrameLimiter m_frameLimiter; 102 | 103 | JobSystem m_jobSystem; 104 | 105 | Profiler m_profiler; 106 | 107 | Networking m_networking; 108 | 109 | SceneManager m_sceneManager; 110 | }; 111 | 112 | } -------------------------------------------------------------------------------- /profiler/ntshengn_profiler.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_profiler.h" 2 | #include "../../Common/utils/ntshengn_defines.h" 3 | #include 4 | #include 5 | #include 6 | 7 | void NtshEngn::Profiler::start(const std::string& sessionName) { 8 | if (!m_isRunning) { 9 | m_rootNode.name = sessionName; 10 | m_rootNode.start = std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); 11 | 12 | m_currentNode = &m_rootNode; 13 | 14 | m_isRunning = true; 15 | } 16 | } 17 | 18 | NtshEngn::ProfilerResultNode NtshEngn::Profiler::getResults() { 19 | ProfilerResultNode resultNode; 20 | 21 | if (m_isRunning) { 22 | resultNode.name = m_rootNode.name; 23 | resultNode.count = 1; 24 | resultNode.totalTime = std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - m_rootNode.start; 25 | 26 | for (const Node* child : m_rootNode.children) { 27 | resultNode.children.push_back(child->getResults()); 28 | } 29 | } 30 | 31 | return resultNode; 32 | } 33 | 34 | NtshEngn::ProfilerResultNode NtshEngn::Profiler::end() { 35 | ProfilerResultNode resultNode = getResults(); 36 | m_profilerNodes.clear(); 37 | m_isRunning = false; 38 | 39 | return resultNode; 40 | } 41 | 42 | bool NtshEngn::Profiler::isRunning() const { 43 | return m_isRunning; 44 | } 45 | 46 | void NtshEngn::Profiler::startBlock(const std::string& blockName) { 47 | if (m_isRunning) { 48 | if (m_profilerNodes.find(blockName) == m_profilerNodes.end()) { 49 | Node newProfilerNode; 50 | newProfilerNode.name = blockName; 51 | newProfilerNode.parent = m_currentNode; 52 | m_profilerNodes[blockName] = newProfilerNode; 53 | 54 | m_currentNode->children.push_back(&m_profilerNodes[blockName]); 55 | } 56 | Node* profilerNode = &m_profilerNodes[blockName]; 57 | 58 | m_currentNode = profilerNode; 59 | 60 | profilerNode->start = std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); 61 | } 62 | } 63 | 64 | void NtshEngn::Profiler::endBlock() { 65 | if (m_isRunning) { 66 | if (m_currentNode != &m_rootNode) { 67 | const double blockTime = std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - m_currentNode->start; 68 | 69 | m_currentNode->times.push_back(blockTime); 70 | 71 | m_currentNode = m_currentNode->parent; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /profiler/ntshengn_profiler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/profiler/ntshengn_profiler_interface.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace NtshEngn { 10 | 11 | class Profiler : public ProfilerInterface { 12 | private: 13 | struct Node { 14 | std::string name = ""; 15 | 16 | std::vector times; 17 | 18 | double start; 19 | 20 | Node* parent = nullptr; 21 | std::vector children; 22 | 23 | ProfilerResultNode getResults() const { 24 | ProfilerResultNode resultNode; 25 | resultNode.name = name; 26 | resultNode.count = static_cast(times.size()); 27 | resultNode.totalTime = std::accumulate(times.begin(), times.end(), 0.0); 28 | resultNode.meanTime = resultNode.totalTime / static_cast(resultNode.count); 29 | resultNode.minTimeIndex = static_cast(std::distance(times.begin(), std::min_element(times.begin(), times.end()))); 30 | resultNode.minTime = times[resultNode.minTimeIndex]; 31 | resultNode.maxTimeIndex = static_cast(std::distance(times.begin(), std::max_element(times.begin(), times.end()))); 32 | resultNode.maxTime = times[resultNode.maxTimeIndex]; 33 | 34 | std::vector sortedTimes = times; 35 | std::sort(sortedTimes.begin(), sortedTimes.end()); 36 | resultNode.medianTime = sortedTimes[resultNode.count / 2]; 37 | 38 | for (const Node* child : children) { 39 | resultNode.children.push_back(child->getResults()); 40 | } 41 | 42 | return resultNode; 43 | } 44 | }; 45 | 46 | public: 47 | void start(const std::string& sessionName); 48 | 49 | ProfilerResultNode getResults(); 50 | 51 | ProfilerResultNode end(); 52 | 53 | bool isRunning() const; 54 | 55 | void startBlock(const std::string& blockName); 56 | void endBlock(); 57 | 58 | private: 59 | std::unordered_map m_profilerNodes; 60 | Node m_rootNode; 61 | Node* m_currentNode = nullptr; 62 | 63 | bool m_isRunning = false; 64 | }; 65 | 66 | } -------------------------------------------------------------------------------- /scene_manager/ntshengn_scene_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_scene_manager.h" 2 | #if defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 3 | #pragma GCC diagnostic push 4 | #pragma GCC diagnostic ignored "-Wfree-nonheap-object" 5 | #endif 6 | #include "../utils/ntshengn_utils_json.h" 7 | #if defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 8 | #pragma GCC diagnostic pop 9 | #endif 10 | #include "../utils/ntshengn_utils_math.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | void NtshEngn::SceneManager::goToScene(const std::string& filePath) { 18 | m_currentScenePath = filePath; 19 | 20 | JSON json; 21 | const JSON::Node& sceneRoot = json.read(filePath); 22 | 23 | m_ecs->destroyNonPersistentEntities(); 24 | 25 | if (sceneRoot.contains("entities")) { 26 | const JSON::Node& entitiesNode = sceneRoot["entities"]; 27 | for (size_t i = 0; i < entitiesNode.size(); i++) { 28 | const JSON::Node& entityNode = entitiesNode[i]; 29 | 30 | Entity entity = m_ecs->createEntity(); 31 | 32 | if (entityNode.contains("name")) { 33 | std::string entityName = entityNode["name"].getString(); 34 | m_ecs->setEntityName(entity, entityName); 35 | } 36 | 37 | if (entityNode.contains("isPersistent")) { 38 | bool entityPersistent = entityNode["isPersistent"].getBoolean(); 39 | m_ecs->setEntityPersistence(entity, entityPersistent); 40 | } 41 | 42 | if (entityNode.contains("entityGroups")) { 43 | const JSON::Node& entityGroupsNode = entityNode["entityGroups"]; 44 | for (size_t j = 0; j < entityGroupsNode.size(); j++) { 45 | m_ecs->addEntityToEntityGroup(entity, entityGroupsNode[j].getString()); 46 | } 47 | } 48 | 49 | // Renderable 50 | if (entityNode.contains("renderable")) { 51 | const JSON::Node& renderableNode = entityNode["renderable"]; 52 | 53 | Renderable renderable; 54 | if (renderableNode.contains("modelPath")) { 55 | const JSON::Node& modelPathNode = renderableNode["modelPath"]; 56 | 57 | Model* model = m_assetManager->loadModel(modelPathNode.getString()); 58 | 59 | if (renderableNode.contains("primitiveIndex")) { 60 | const JSON::Node& primitiveIndexNode = renderableNode["primitiveIndex"]; 61 | 62 | uint32_t primitiveIndex = static_cast(primitiveIndexNode.getNumber()); 63 | 64 | if (model && (primitiveIndex < model->primitives.size())) { 65 | renderable.mesh = &model->primitives[primitiveIndex].mesh; 66 | renderable.material = model->primitives[primitiveIndex].material; 67 | } 68 | } 69 | } 70 | 71 | if (renderableNode.contains("materialPath")) { 72 | const JSON::Node& materialPathNode = renderableNode["materialPath"]; 73 | 74 | Material* material = m_assetManager->loadMaterial(materialPathNode.getString()); 75 | 76 | renderable.material = *material; 77 | } 78 | 79 | m_ecs->addComponent(entity, renderable); 80 | } 81 | 82 | // Transform 83 | if (entityNode.contains("transform")) { 84 | const JSON::Node& transformNode = entityNode["transform"]; 85 | 86 | Transform& transform = m_ecs->getComponent(entity); 87 | if (transformNode.contains("position")) { 88 | const JSON::Node& positionNode = transformNode["position"]; 89 | 90 | transform.position = { positionNode[0].getNumber(), positionNode[1].getNumber(), positionNode[2].getNumber() }; 91 | } 92 | 93 | if (transformNode.contains("rotation")) { 94 | const JSON::Node& rotationNode = transformNode["rotation"]; 95 | 96 | transform.rotation = { Math::toRad(rotationNode[0].getNumber()), Math::toRad(rotationNode[1].getNumber()), Math::toRad(rotationNode[2].getNumber()) }; 97 | } 98 | 99 | if (transformNode.contains("scale")) { 100 | const JSON::Node& scaleNode = transformNode["scale"]; 101 | 102 | transform.scale = { scaleNode[0].getNumber(), scaleNode[1].getNumber(), scaleNode[2].getNumber() }; 103 | } 104 | } 105 | 106 | // Camera 107 | if (entityNode.contains("camera")) { 108 | const JSON::Node& cameraNode = entityNode["camera"]; 109 | 110 | Camera camera; 111 | if (cameraNode.contains("forward")) { 112 | const JSON::Node& forwardNode = cameraNode["forward"]; 113 | 114 | camera.forward = { forwardNode[0].getNumber(), forwardNode[1].getNumber(), forwardNode[2].getNumber() }; 115 | } 116 | 117 | if (cameraNode.contains("up")) { 118 | const JSON::Node& upNode = cameraNode["up"]; 119 | 120 | camera.up = { upNode[0].getNumber(), upNode[1].getNumber(), upNode[2].getNumber() }; 121 | } 122 | 123 | if (cameraNode.contains("projectionType")) { 124 | const JSON::Node& projectionTypeNode = cameraNode["projectionType"]; 125 | 126 | if (projectionTypeNode.getString() == "Perspective") { 127 | camera.projectionType = CameraProjectionType::Perspective; 128 | } 129 | else if (projectionTypeNode.getString() == "Orthographic") { 130 | camera.projectionType = CameraProjectionType::Orthographic; 131 | } 132 | } 133 | 134 | if (cameraNode.contains("fov")) { 135 | const JSON::Node& fovNode = cameraNode["fov"]; 136 | 137 | camera.fov = Math::toRad(fovNode.getNumber()); 138 | } 139 | 140 | if (cameraNode.contains("left")) { 141 | const JSON::Node& leftNode = cameraNode["left"]; 142 | 143 | camera.left = leftNode.getNumber(); 144 | } 145 | 146 | if (cameraNode.contains("right")) { 147 | const JSON::Node& rightNode = cameraNode["right"]; 148 | 149 | camera.right = rightNode.getNumber(); 150 | } 151 | 152 | if (cameraNode.contains("bottom")) { 153 | const JSON::Node& bottomNode = cameraNode["bottom"]; 154 | 155 | camera.bottom = bottomNode.getNumber(); 156 | } 157 | 158 | if (cameraNode.contains("top")) { 159 | const JSON::Node& topNode = cameraNode["top"]; 160 | 161 | camera.top = topNode.getNumber(); 162 | } 163 | 164 | if (cameraNode.contains("nearPlane")) { 165 | const JSON::Node& nearPlaneNode = cameraNode["nearPlane"]; 166 | 167 | camera.nearPlane = nearPlaneNode.getNumber(); 168 | } 169 | 170 | if (cameraNode.contains("farPlane")) { 171 | const JSON::Node& farPlaneNode = cameraNode["farPlane"]; 172 | 173 | camera.farPlane = farPlaneNode.getNumber(); 174 | } 175 | 176 | m_ecs->addComponent(entity, camera); 177 | } 178 | 179 | // Light 180 | if (entityNode.contains("light")) { 181 | const JSON::Node& lightNode = entityNode["light"]; 182 | 183 | Light light; 184 | if (lightNode.contains("type")) { 185 | const JSON::Node& typeNode = lightNode["type"]; 186 | 187 | if (typeNode.getString() == "Directional") { 188 | light.type = LightType::Directional; 189 | } 190 | else if (typeNode.getString() == "Point") { 191 | light.type = LightType::Point; 192 | } 193 | else if (typeNode.getString() == "Spot") { 194 | light.type = LightType::Spot; 195 | } 196 | else if (typeNode.getString() == "Ambient") { 197 | light.type = LightType::Ambient; 198 | } 199 | } 200 | 201 | if (lightNode.contains("color")) { 202 | const JSON::Node& colorNode = lightNode["color"]; 203 | 204 | light.color = { colorNode[0].getNumber(), colorNode[1].getNumber(), colorNode[2].getNumber() }; 205 | } 206 | 207 | if (lightNode.contains("intensity")) { 208 | const JSON::Node& intensityNode = lightNode["intensity"]; 209 | 210 | light.intensity = intensityNode.getNumber(); 211 | } 212 | 213 | if (lightNode.contains("direction")) { 214 | const JSON::Node& directionNode = lightNode["direction"]; 215 | 216 | light.direction = { directionNode[0].getNumber(), directionNode[1].getNumber(), directionNode[2].getNumber() }; 217 | } 218 | 219 | if (lightNode.contains("cutoff")) { 220 | const JSON::Node& cutoffNode = lightNode["cutoff"]; 221 | 222 | light.cutoff = { Math::toRad(cutoffNode[0].getNumber()), Math::toRad(cutoffNode[1].getNumber()) }; 223 | } 224 | 225 | if (lightNode.contains("distance")) { 226 | const JSON::Node& distanceNode = lightNode["distance"]; 227 | 228 | light.distance = distanceNode.getNumber(); 229 | } 230 | 231 | m_ecs->addComponent(entity, light); 232 | } 233 | 234 | // Rigidbody 235 | if (entityNode.contains("rigidbody")) { 236 | const JSON::Node& rigidbodyNode = entityNode["rigidbody"]; 237 | 238 | Rigidbody rigidbody; 239 | if (rigidbodyNode.contains("isStatic")) { 240 | const JSON::Node& isStaticNode = rigidbodyNode["isStatic"]; 241 | 242 | rigidbody.isStatic = isStaticNode.getBoolean(); 243 | } 244 | 245 | if (rigidbodyNode.contains("isAffectedByConstants")) { 246 | const JSON::Node& isAffectedByConstantsNode = rigidbodyNode["isAffectedByConstants"]; 247 | 248 | rigidbody.isAffectedByConstants = isAffectedByConstantsNode.getBoolean(); 249 | } 250 | 251 | if (rigidbodyNode.contains("lockRotation")) { 252 | const JSON::Node& lockRotationNode = rigidbodyNode["lockRotation"]; 253 | 254 | rigidbody.lockRotation = lockRotationNode.getBoolean(); 255 | } 256 | 257 | if (rigidbodyNode.contains("mass")) { 258 | const JSON::Node& massNode = rigidbodyNode["mass"]; 259 | 260 | rigidbody.mass = massNode.getNumber(); 261 | } 262 | 263 | if (rigidbodyNode.contains("inertia")) { 264 | const JSON::Node& inertiaNode = rigidbodyNode["inertia"]; 265 | 266 | rigidbody.inertia = inertiaNode.getNumber(); 267 | } 268 | 269 | if (rigidbodyNode.contains("restitution")) { 270 | const JSON::Node& restitutionNode = rigidbodyNode["restitution"]; 271 | 272 | rigidbody.restitution = restitutionNode.getNumber(); 273 | } 274 | 275 | if (rigidbodyNode.contains("staticFriction")) { 276 | const JSON::Node& staticFrictionNode = rigidbodyNode["staticFriction"]; 277 | 278 | rigidbody.staticFriction = staticFrictionNode.getNumber(); 279 | } 280 | 281 | if (rigidbodyNode.contains("dynamicFriction")) { 282 | const JSON::Node& dynamicFrictionNode = rigidbodyNode["dynamicFriction"]; 283 | 284 | rigidbody.dynamicFriction = dynamicFrictionNode.getNumber(); 285 | } 286 | 287 | m_ecs->addComponent(entity, rigidbody); 288 | } 289 | 290 | // Collidable 291 | if (entityNode.contains("collidable")) { 292 | const JSON::Node& collidableNode = entityNode["collidable"]; 293 | 294 | Collidable collidable; 295 | if (collidableNode.contains("type")) { 296 | const JSON::Node& typeNode = collidableNode["type"]; 297 | 298 | if (typeNode.getString() == "Box") { 299 | collidable.collider = std::make_unique(); 300 | ColliderBox* colliderBox = static_cast(collidable.collider.get()); 301 | 302 | if (collidableNode.contains("center")) { 303 | const JSON::Node& centerNode = collidableNode["center"]; 304 | 305 | colliderBox->center = { centerNode[0].getNumber(), centerNode[1].getNumber(), centerNode[2].getNumber() }; 306 | } 307 | 308 | if (collidableNode.contains("halfExtent")) { 309 | const JSON::Node& halfExtentNode = collidableNode["halfExtent"]; 310 | 311 | colliderBox->halfExtent = { halfExtentNode[0].getNumber(), halfExtentNode[1].getNumber(), halfExtentNode[2].getNumber() }; 312 | } 313 | 314 | if (collidableNode.contains("rotation")) { 315 | const JSON::Node& rotationNode = collidableNode["rotation"]; 316 | 317 | colliderBox->rotation = { Math::toRad(rotationNode[0].getNumber()), Math::toRad(rotationNode[1].getNumber()), Math::toRad(rotationNode[2].getNumber()) }; 318 | } 319 | 320 | if (!collidableNode.contains("center") && !collidableNode.contains("halfExtent") && !collidableNode.contains("rotation")) { 321 | // Default box collider 322 | colliderBox->center = Math::vec3(0.0f, 0.0f, 0.0f); 323 | colliderBox->halfExtent = Math::vec3(0.5f, 0.5f, 0.5f); 324 | colliderBox->rotation = Math::vec3(0.0f, 0.0f, 0.0f); 325 | } 326 | } 327 | else if (typeNode.getString() == "Sphere") { 328 | collidable.collider = std::make_unique(); 329 | ColliderSphere* colliderSphere = static_cast(collidable.collider.get()); 330 | 331 | if (collidableNode.contains("center")) { 332 | const JSON::Node& centerNode = collidableNode["center"]; 333 | 334 | colliderSphere->center = { centerNode[0].getNumber(), centerNode[1].getNumber(), centerNode[2].getNumber() }; 335 | } 336 | 337 | if (collidableNode.contains("radius")) { 338 | const JSON::Node& radiusNode = collidableNode["radius"]; 339 | 340 | colliderSphere->radius = radiusNode.getNumber(); 341 | } 342 | 343 | if (!collidableNode.contains("center") && !collidableNode.contains("radius")) { 344 | // Default sphere collider 345 | colliderSphere->center = Math::vec3(0.0f, 0.0f, 0.0f); 346 | colliderSphere->radius = 0.5f; 347 | } 348 | } 349 | else if (typeNode.getString() == "Capsule") { 350 | collidable.collider = std::make_unique(); 351 | ColliderCapsule* colliderCapsule = static_cast(collidable.collider.get()); 352 | 353 | if (collidableNode.contains("base")) { 354 | const JSON::Node& baseNode = collidableNode["base"]; 355 | 356 | colliderCapsule->base = { baseNode[0].getNumber(), baseNode[1].getNumber(), baseNode[2].getNumber() }; 357 | } 358 | 359 | if (collidableNode.contains("tip")) { 360 | const JSON::Node& tipNode = collidableNode["tip"]; 361 | 362 | colliderCapsule->tip = { tipNode[0].getNumber(), tipNode[1].getNumber(), tipNode[2].getNumber() }; 363 | } 364 | 365 | if (collidableNode.contains("radius")) { 366 | const JSON::Node& radiusNode = collidableNode["radius"]; 367 | 368 | colliderCapsule->radius = radiusNode.getNumber(); 369 | } 370 | 371 | if (!collidableNode.contains("base") && !collidableNode.contains("tip") && !collidableNode.contains("radius")) { 372 | // Default capsule collider 373 | colliderCapsule->base = Math::vec3(0.0f, 0.25f, 0.0f); 374 | colliderCapsule->tip = Math::vec3(0.0f, 0.75f, 0.0f); 375 | colliderCapsule->radius = 0.25f; 376 | } 377 | } 378 | } 379 | 380 | m_ecs->addComponent(entity, collidable); 381 | } 382 | 383 | // SoundListener 384 | if (entityNode.contains("soundListener")) { 385 | const JSON::Node& soundListenerNode = entityNode["soundListener"]; 386 | 387 | SoundListener soundListener; 388 | if (soundListenerNode.contains("forward")) { 389 | const JSON::Node& forwardNode = soundListenerNode["forward"]; 390 | 391 | soundListener.forward = { forwardNode[0].getNumber(), forwardNode[1].getNumber(), forwardNode[2].getNumber() }; 392 | } 393 | 394 | if (soundListenerNode.contains("up")) { 395 | const JSON::Node& upNode = soundListenerNode["up"]; 396 | 397 | soundListener.up = { upNode[0].getNumber(), upNode[1].getNumber(), upNode[2].getNumber() }; 398 | } 399 | 400 | m_ecs->addComponent(entity, soundListener); 401 | } 402 | 403 | // Scriptable 404 | if (entityNode.contains("scriptable")) { 405 | const JSON::Node& scriptableNode = entityNode["scriptable"]; 406 | 407 | if (scriptableNode.contains("scriptName")) { 408 | if (scriptableNode["scriptName"].getString() != "") { 409 | const JSON::Node& scriptNameNode = scriptableNode["scriptName"]; 410 | 411 | Scriptable scriptable = m_scriptManager->createScriptable(scriptNameNode.getString()); 412 | 413 | if (scriptableNode.contains("editableVariables")) { 414 | const JSON::Node& editableScriptVariablesNode = scriptableNode["editableVariables"]; 415 | 416 | auto& editableScriptVariables = scriptable.script->editableScriptVariables; 417 | for (auto& editableScriptVariable : editableScriptVariables) { 418 | if (editableScriptVariablesNode.contains(editableScriptVariable.first)) { 419 | const JSON::Node& editableScriptVariableNode = editableScriptVariablesNode[editableScriptVariable.first]; 420 | if (editableScriptVariable.second.type == EditableScriptVariableType::Boolean) { 421 | if (editableScriptVariableNode.getType() == JSON::Type::Boolean) { 422 | *static_cast(editableScriptVariable.second.address) = editableScriptVariableNode.getBoolean(); 423 | } 424 | } 425 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Int8) { 426 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 427 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 428 | } 429 | } 430 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Int16) { 431 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 432 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 433 | } 434 | } 435 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Int32) { 436 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 437 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 438 | } 439 | } 440 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Int64) { 441 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 442 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 443 | } 444 | } 445 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Uint8) { 446 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 447 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 448 | } 449 | } 450 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Uint16) { 451 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 452 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 453 | } 454 | } 455 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Uint32) { 456 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 457 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 458 | } 459 | } 460 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Uint64) { 461 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 462 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 463 | } 464 | } 465 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Float32) { 466 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 467 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 468 | } 469 | } 470 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Float64) { 471 | if (editableScriptVariableNode.getType() == JSON::Type::Number) { 472 | *static_cast(editableScriptVariable.second.address) = static_cast(editableScriptVariableNode.getNumber()); 473 | } 474 | } 475 | else if (editableScriptVariable.second.type == EditableScriptVariableType::String) { 476 | if (editableScriptVariableNode.getType() == JSON::Type::String) { 477 | *static_cast(editableScriptVariable.second.address) = editableScriptVariableNode.getString(); 478 | } 479 | } 480 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Vector2) { 481 | if ((editableScriptVariableNode.getType() == JSON::Type::Array) && (editableScriptVariableNode.size() == 2)) { 482 | if (editableScriptVariableNode[0].getType() == JSON::Type::Number) { 483 | static_cast(editableScriptVariable.second.address)->x = static_cast(editableScriptVariableNode[0].getNumber()); 484 | } 485 | if (editableScriptVariableNode[1].getType() == JSON::Type::Number) { 486 | static_cast(editableScriptVariable.second.address)->y = static_cast(editableScriptVariableNode[1].getNumber()); 487 | } 488 | } 489 | } 490 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Vector3) { 491 | if ((editableScriptVariableNode.getType() == JSON::Type::Array) && (editableScriptVariableNode.size() == 3)) { 492 | if (editableScriptVariableNode[0].getType() == JSON::Type::Number) { 493 | static_cast(editableScriptVariable.second.address)->x = static_cast(editableScriptVariableNode[0].getNumber()); 494 | } 495 | if (editableScriptVariableNode[1].getType() == JSON::Type::Number) { 496 | static_cast(editableScriptVariable.second.address)->y = static_cast(editableScriptVariableNode[1].getNumber()); 497 | } 498 | if (editableScriptVariableNode[2].getType() == JSON::Type::Number) { 499 | static_cast(editableScriptVariable.second.address)->z = static_cast(editableScriptVariableNode[2].getNumber()); 500 | } 501 | } 502 | } 503 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Vector4) { 504 | if ((editableScriptVariableNode.getType() == JSON::Type::Array) && (editableScriptVariableNode.size() == 4)) { 505 | if (editableScriptVariableNode[0].getType() == JSON::Type::Number) { 506 | static_cast(editableScriptVariable.second.address)->x = static_cast(editableScriptVariableNode[0].getNumber()); 507 | } 508 | if (editableScriptVariableNode[1].getType() == JSON::Type::Number) { 509 | static_cast(editableScriptVariable.second.address)->y = static_cast(editableScriptVariableNode[1].getNumber()); 510 | } 511 | if (editableScriptVariableNode[2].getType() == JSON::Type::Number) { 512 | static_cast(editableScriptVariable.second.address)->z = static_cast(editableScriptVariableNode[2].getNumber()); 513 | } 514 | if (editableScriptVariableNode[3].getType() == JSON::Type::Number) { 515 | static_cast(editableScriptVariable.second.address)->w = static_cast(editableScriptVariableNode[3].getNumber()); 516 | } 517 | } 518 | } 519 | else if (editableScriptVariable.second.type == EditableScriptVariableType::Quaternion) { 520 | if ((editableScriptVariableNode.getType() == JSON::Type::Array) && (editableScriptVariableNode.size() == 4)) { 521 | if (editableScriptVariableNode[0].getType() == JSON::Type::Number) { 522 | static_cast(editableScriptVariable.second.address)->a = static_cast(editableScriptVariableNode[0].getNumber()); 523 | } 524 | if (editableScriptVariableNode[1].getType() == JSON::Type::Number) { 525 | static_cast(editableScriptVariable.second.address)->b = static_cast(editableScriptVariableNode[1].getNumber()); 526 | } 527 | if (editableScriptVariableNode[2].getType() == JSON::Type::Number) { 528 | static_cast(editableScriptVariable.second.address)->c = static_cast(editableScriptVariableNode[2].getNumber()); 529 | } 530 | if (editableScriptVariableNode[3].getType() == JSON::Type::Number) { 531 | static_cast(editableScriptVariable.second.address)->d = static_cast(editableScriptVariableNode[3].getNumber()); 532 | } 533 | } 534 | } 535 | } 536 | } 537 | } 538 | 539 | m_ecs->addComponent(entity, scriptable); 540 | } 541 | } 542 | } 543 | } 544 | } 545 | } 546 | 547 | std::string NtshEngn::SceneManager::getCurrentScenePath() { 548 | return m_currentScenePath; 549 | } 550 | 551 | void NtshEngn::SceneManager::setECS(ECS* ecs) { 552 | m_ecs = ecs; 553 | } 554 | 555 | void NtshEngn::SceneManager::setAssetManager(AssetManager* assetManager) { 556 | m_assetManager = assetManager; 557 | } 558 | 559 | void NtshEngn::SceneManager::setScriptManager(ScriptManagerInterface* scriptManager) { 560 | m_scriptManager = scriptManager; 561 | } 562 | -------------------------------------------------------------------------------- /scene_manager/ntshengn_scene_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Common/scene_manager/ntshengn_scene_manager_interface.h" 3 | #include "../asset_manager/ntshengn_asset_manager.h" 4 | #include "../ecs/ntshengn_ecs.h" 5 | #include "../script/ntshengn_script_manager_interface.h" 6 | #include 7 | 8 | namespace NtshEngn { 9 | 10 | class SceneManager : public SceneManagerInterface { 11 | public: 12 | void goToScene(const std::string& filePath); 13 | 14 | std::string getCurrentScenePath(); 15 | 16 | public: 17 | void setECS(ECS* ecs); 18 | 19 | void setAssetManager(AssetManager* assetManager); 20 | 21 | void setScriptManager(ScriptManagerInterface* scriptManager); 22 | 23 | private: 24 | AssetManager* m_assetManager; 25 | 26 | ECS* m_ecs; 27 | 28 | ScriptManagerInterface* m_scriptManager; 29 | 30 | std::string m_currentScenePath = ""; 31 | }; 32 | 33 | } -------------------------------------------------------------------------------- /script_manager_loader/ntshengn_script_manager_loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if defined(NTSHENGN_OS_WINDOWS) 3 | #include "ntshengn_script_manager_loader_windows.h" 4 | #elif defined(NTSHENGN_OS_LINUX) || defined(NTSHENGN_OS_FREEBSD) 5 | #include "ntshengn_script_manager_loader_linux_freebsd.h" 6 | #endif -------------------------------------------------------------------------------- /script_manager_loader/ntshengn_script_manager_loader_linux_freebsd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../utils/ntshengn_core_defines.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef void* createScriptManager_t(); 9 | typedef void destroyScriptManager_t(void*); 10 | 11 | namespace NtshEngn { 12 | 13 | class ScriptManagerLoader { 14 | public: 15 | ScriptManagerInterface* loadScriptManager(const std::string& scriptsPath) { 16 | NTSHENGN_CORE_INFO("Loading script manager from: \"" + scriptsPath + "\"."); 17 | 18 | m_scriptManagerLibrary = dlopen(scriptsPath.c_str(), RTLD_LAZY); 19 | if (!m_scriptManagerLibrary) 20 | { 21 | NTSHENGN_CORE_WARNING("Could not load the dynamic library."); 22 | return nullptr; 23 | } 24 | 25 | createScriptManager_t* createScriptManager = (createScriptManager_t*)dlsym(m_scriptManagerLibrary, "createScriptManager"); 26 | const char* dlsymError = dlerror(); 27 | if (dlsymError) { 28 | NTSHENGN_CORE_ERROR("Could not load symbol \"createScriptManager\" from dynamic library: " + std::string(dlsymError)); 29 | } 30 | 31 | ScriptManagerInterface* scriptManager = static_cast(createScriptManager()); 32 | 33 | NTSHENGN_CORE_INFO("Loaded script manager."); 34 | 35 | return scriptManager; 36 | } 37 | 38 | void unloadScriptManager(ScriptManagerInterface* scriptManager) { 39 | NTSHENGN_CORE_INFO("Unloading script manager."); 40 | 41 | destroyScriptManager_t* destroyScriptManager = (destroyModule_t*)dlsym(m_scriptManagerLibrary, "destroyScriptManager"); 42 | const char* dlsymError = dlerror(); 43 | if (dlsymError) { 44 | NTSHENGN_CORE_ERROR("Could not load symbol \"destroyScriptManager\": " + std::string(dlsymError)); 45 | } 46 | 47 | destroyScriptManager(scriptManager); 48 | 49 | if (dlclose(m_scriptManagerLibrary) != 0) { 50 | NTSHENGN_CORE_ERROR("Could not unload the dynamic library: " + std::string(dlsymError)); 51 | } 52 | } 53 | 54 | private: 55 | void* m_scriptManagerLibrary; 56 | }; 57 | 58 | } -------------------------------------------------------------------------------- /script_manager_loader/ntshengn_script_manager_loader_windows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../utils/ntshengn_core_defines.h" 3 | #include 4 | #undef far 5 | #undef near 6 | #include 7 | #include 8 | #include 9 | 10 | typedef void* (__stdcall* createScriptManager_t)(); 11 | typedef void (__stdcall* destroyScriptManager_t)(void*); 12 | 13 | namespace NtshEngn { 14 | 15 | class ScriptManagerInterface; 16 | 17 | class ScriptManagerLoader { 18 | public: 19 | ScriptManagerInterface* loadScriptManager(const std::string& scriptsPath) { 20 | NTSHENGN_CORE_INFO("Loading script manager from: " + scriptsPath + "."); 21 | 22 | m_scriptManagerLibrary = LoadLibrary(scriptsPath.c_str()); 23 | if (!m_scriptManagerLibrary) { 24 | NTSHENGN_CORE_WARNING("Could not load the dynamic library. Error code: " + std::to_string(GetLastError())); 25 | return nullptr; 26 | } 27 | 28 | createScriptManager_t createScriptManager = (createScriptManager_t)GetProcAddress(m_scriptManagerLibrary, "createScriptManager"); 29 | if (!createScriptManager) { 30 | NTSHENGN_CORE_ERROR("Could not load symbol \"createScriptManager\" from dynamic library. Error code: " + std::to_string(GetLastError())); 31 | } 32 | 33 | ScriptManagerInterface* scriptManager = static_cast(createScriptManager()); 34 | 35 | NTSHENGN_CORE_INFO("Loaded script manager."); 36 | 37 | return scriptManager; 38 | } 39 | 40 | void unloadScriptManager(ScriptManagerInterface* scriptManager) { 41 | NTSHENGN_CORE_INFO("Unloading script manager."); 42 | 43 | destroyScriptManager_t destroyScriptManager = (destroyScriptManager_t)GetProcAddress(m_scriptManagerLibrary, "destroyScriptManager"); 44 | if (!destroyScriptManager) { 45 | NTSHENGN_CORE_ERROR("Could not load symbol destroyScriptManager."); 46 | } 47 | 48 | destroyScriptManager(scriptManager); 49 | 50 | if (!FreeLibrary(m_scriptManagerLibrary)) { 51 | NTSHENGN_CORE_ERROR("Could not unload the dynamic library."); 52 | } 53 | } 54 | 55 | private: 56 | HINSTANCE m_scriptManagerLibrary; 57 | }; 58 | 59 | } -------------------------------------------------------------------------------- /scripting/ntshengn_scripting.cpp: -------------------------------------------------------------------------------- 1 | #include "ntshengn_scripting.h" 2 | 3 | void NtshEngn::Scripting::update(float dt) { 4 | for (auto& entityScript : m_entityScriptsJustInitialized) { 5 | if (m_entityScriptsToDestroy.find(entityScript.first) == m_entityScriptsToDestroy.end()) { 6 | if (!entityScript.second) { 7 | const Scriptable& entityScriptable = m_ecs->getComponent(entityScript.first); 8 | Script* script = static_cast(entityScriptable.script); 9 | script->update(dt); 10 | } 11 | else { 12 | entityScript.second = false; 13 | } 14 | } 15 | } 16 | 17 | for (auto destroyedEntity : m_entityScriptsToDestroy) { 18 | m_entityScriptsJustInitialized.erase(destroyedEntity); 19 | } 20 | m_entityScriptsToDestroy.clear(); 21 | } 22 | 23 | void NtshEngn::Scripting::setSystemModules(GraphicsModuleInterface* graphicsModule, PhysicsModuleInterface* physicsModule, WindowModuleInterface* windowModule, AudioModuleInterface* audioModule) { 24 | m_graphicsModule = graphicsModule; 25 | m_physicsModule = physicsModule; 26 | m_windowModule = windowModule; 27 | m_audioModule = audioModule; 28 | } 29 | 30 | void NtshEngn::Scripting::setScriptManager(ScriptManagerInterface* scriptManager) { 31 | m_scriptManager = scriptManager; 32 | } 33 | 34 | void NtshEngn::Scripting::setECS(ECS* ecs) { 35 | m_ecs = ecs; 36 | } 37 | 38 | void NtshEngn::Scripting::setAssetManager(AssetManager* assetManager) { 39 | m_assetManager = assetManager; 40 | } 41 | 42 | void NtshEngn::Scripting::setFrameLimiter(FrameLimiter* frameLimiter) { 43 | m_frameLimiter = frameLimiter; 44 | } 45 | 46 | void NtshEngn::Scripting::setJobSystem(JobSystem* jobSystem) { 47 | m_jobSystem = jobSystem; 48 | } 49 | 50 | void NtshEngn::Scripting::setProfiler(Profiler* profiler) { 51 | m_profiler = profiler; 52 | } 53 | 54 | void NtshEngn::Scripting::setNetworking(Networking* networking) { 55 | m_networking = networking; 56 | } 57 | 58 | void NtshEngn::Scripting::setSceneManager(SceneManager* sceneManager) { 59 | m_sceneManager = sceneManager; 60 | } 61 | 62 | void NtshEngn::Scripting::onEntityComponentAdded(Entity entity, Component componentID) { 63 | if (componentID == m_ecs->getComponentID()) { 64 | const Scriptable& entityScriptable = m_ecs->getComponent(entity); 65 | 66 | Script* script = static_cast(entityScriptable.script); 67 | script->setEntityID(entity); 68 | script->setSystemModules(m_graphicsModule, m_physicsModule, m_windowModule, m_audioModule); 69 | script->setScriptManager(m_scriptManager); 70 | script->setECS(m_ecs); 71 | script->setAssetManager(m_assetManager); 72 | script->setFrameLimiter(m_frameLimiter); 73 | script->setJobSystem(m_jobSystem); 74 | script->setProfiler(m_profiler); 75 | script->setNetworking(m_networking); 76 | script->setSceneManager(m_sceneManager); 77 | // A new entity with a script with the same ID as an old entity with a script that has been destroyed this frame has been created 78 | if (m_entityScriptsToDestroy.find(entity) != m_entityScriptsToDestroy.end()) { 79 | m_entityScriptsToDestroy.erase(entity); 80 | } 81 | m_entityScriptsJustInitialized[entity] = true; 82 | 83 | script->init(); 84 | } 85 | } 86 | 87 | void NtshEngn::Scripting::onEntityComponentRemoved(Entity entity, Component componentID) { 88 | if (componentID == m_ecs->getComponentID()) { 89 | const Scriptable& scriptable = m_ecs->getComponent(entity); 90 | Script* script = static_cast(scriptable.script); 91 | script->destroy(); 92 | m_scriptManager->destroyScript(script); 93 | 94 | m_entityScriptsToDestroy.insert(entity); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scripting/ntshengn_scripting.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../ecs/ntshengn_ecs.h" 3 | #include "../asset_manager/ntshengn_asset_manager.h" 4 | #include "../job_system/ntshengn_job_system.h" 5 | #include "../profiler/ntshengn_profiler.h" 6 | #include "../frame_limiter/ntshengn_frame_limiter.h" 7 | #include "../networking/ntshengn_networking.h" 8 | #include "../scene_manager/ntshengn_scene_manager.h" 9 | #include "../Common/script/ntshengn_script.h" 10 | #include "../Common/script/ntshengn_script_manager_interface.h" 11 | #include 12 | #include 13 | 14 | namespace NtshEngn { 15 | 16 | class Scripting : public System { 17 | public: 18 | void update(float dt); 19 | 20 | void setSystemModules(GraphicsModuleInterface* graphicsModule, PhysicsModuleInterface* physicsModule, WindowModuleInterface* windowModule, AudioModuleInterface* audioModule); 21 | void setScriptManager(ScriptManagerInterface* scriptManager); 22 | void setECS(ECS* ecs); 23 | void setAssetManager(AssetManager* assetManager); 24 | void setFrameLimiter(FrameLimiter* frameLimiter); 25 | void setJobSystem(JobSystem* jobSystem); 26 | void setProfiler(Profiler* profiler); 27 | void setNetworking(Networking* networking); 28 | void setSceneManager(SceneManager* sceneManager); 29 | 30 | public: 31 | void onEntityComponentAdded(Entity entity, Component componentID); 32 | void onEntityComponentRemoved(Entity entity, Component componentID); 33 | 34 | private: 35 | GraphicsModuleInterface* m_graphicsModule = nullptr; 36 | PhysicsModuleInterface* m_physicsModule = nullptr; 37 | WindowModuleInterface* m_windowModule = nullptr; 38 | AudioModuleInterface* m_audioModule = nullptr; 39 | 40 | ECS* m_ecs = nullptr; 41 | 42 | AssetManager* m_assetManager = nullptr; 43 | 44 | FrameLimiter* m_frameLimiter = nullptr; 45 | 46 | JobSystem* m_jobSystem = nullptr; 47 | 48 | Profiler* m_profiler = nullptr; 49 | 50 | Networking* m_networking = nullptr; 51 | 52 | SceneManager* m_sceneManager = nullptr; 53 | 54 | private: 55 | ScriptManagerInterface* m_scriptManager; 56 | std::unordered_map m_entityScriptsJustInitialized; 57 | std::set m_entityScriptsToDestroy; 58 | }; 59 | 60 | } -------------------------------------------------------------------------------- /utils/ntshengn_core_defines.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #if defined(NTSHENGN_DEBUG) 5 | #define NTSHENGN_CORE_INFO(message) \ 6 | do { \ 7 | std::cerr << "\33[1m\33[39mCORE \33[34mINFO\33[39m\33[0m: " + std::string(message) << std::endl; \ 8 | } while(0) 9 | #else 10 | #define NTSHENGN_CORE_INFO(message) \ 11 | do { \ 12 | } while(0) 13 | #endif 14 | 15 | #if defined(NTSHENGN_DEBUG) 16 | #define NTSHENGN_CORE_WARNING(message) \ 17 | do { \ 18 | std::cerr << "\33[1m\33[39mCORE \33[93mWARNING\33[39m\33[0m: " + std::string(message) << std::endl; \ 19 | } while(0) 20 | #else 21 | #define NTSHENGN_CORE_WARNING(message) \ 22 | do { \ 23 | } while(0) 24 | #endif 25 | 26 | #if defined(NTSHENGN_DEBUG) 27 | #define NTSHENGN_CORE_ERROR(message) \ 28 | do { \ 29 | std::cerr << "\33[1m\33[39mCORE \33[31mERROR\33[39m\33[0m: " + std::string(message) << std::endl; \ 30 | exit(1); \ 31 | } while(0) 32 | #else 33 | #define NTSHENGN_CORE_ERROR(message) \ 34 | do { \ 35 | exit(1); \ 36 | } while(0) 37 | #endif --------------------------------------------------------------------------------