#include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ext.h" #include #include #include #include #include #include bool ext::ready = false; std::vector ext::arguments; uf::Serializer ext::config; namespace { struct { uf::String input; std::ofstream output; struct { std::string output = "log/output.txt"; } filenames; } io; struct { spec::Time::time_t epoch; uf::Timer<> sys = uf::Timer<>(false); uf::Timer<> delta = uf::Timer<>(false); double prevTime = 0; double curTime = 0; double deltaTime = 0; size_t frames = 0; double limiter = 1.0 / 144.0; } times; uf::Serializer& config = ext::config; } namespace { std::string getConfig() { struct { bool initialized = false; uf::Serializer file; uf::Serializer fallback; uf::Serializer merged; } static config; if ( config.initialized ) return config.merged; struct { bool exists = false; std::string filename = "./data/config.json"; } file; /* Read from file */ { file.exists = config.file.readFromFile(file.filename); } /* Initialize default configuration */ { config.fallback["window"]["terminal"]["ncurses"] = true; config.fallback["window"]["terminal"]["visible"] = true; config.fallback["window"]["title"] = "Grimgram"; config.fallback["window"]["icon"] = ""; config.fallback["window"]["size"]["x"] = 0; config.fallback["window"]["size"]["y"] = 0; config.fallback["window"]["visible"] = true; // config.fallback["window"]["fullscreen"] = false; config.fallback["window"]["mode"] = "windowed"; config.fallback["window"]["cursor"]["visible"] = true; config.fallback["window"]["cursor"]["center"] = false; config.fallback["window"]["keyboard"]["repeat"] = true; config.fallback["engine"]["scenes"]["start"] = "StartMenu"; config.fallback["engine"]["scenes"]["max lights"] = 32; config.fallback["engine"]["hook"]["mode"] = "Readable"; config.fallback["engine"]["limiters"]["framerate"] = 60; config.fallback["engine"]["limiters"]["deltaTime"] = 120; config.fallback["engine"]["threads"]["workers"] = "auto"; config.fallback["engine"]["threads"]["frame limiter"] = 144; config.fallback["engine"]["memory pool"]["size"] = "512 MiB"; config.fallback["engine"]["memory pool"]["globalOverride"] = false; config.fallback["engine"]["memory pool"]["subPools"] = true; } /* Merge */ if ( file.exists ){ config.merged = config.file; config.merged.merge( config.fallback, true ); } else { config.merged = config.fallback; } /* Write default to file */ if ( false ) { config.merged.writeToFile(file.filename); } config.initialized = true; return config.merged; } } void EXT_API ext::load() { ext::config = getConfig(); } void EXT_API ext::initialize() { /* Arguments */ { bool modified = false; auto& arguments = ::config["arguments"]; for ( auto& arg : ext::arguments ) { // store raw argument int i = arguments.size(); arguments[i] = arg; // parse --key=value { std::regex regex("^--(.+?)=(.+?)$"); std::smatch match; if ( std::regex_search( arg, match, regex ) ) { std::string keyString = match[1].str(); std::string valueString = match[2].str(); auto keys = uf::string::split(keyString, "."); uf::Serializer value; value.deserialize(valueString); Json::Value* traversal = &::config; for ( auto& key : keys ) { traversal = &((*traversal)[key]); } *traversal = value; modified = true; } } } uf::iostream << "Arguments: " << uf::Serializer(arguments) << "\n"; if ( modified ) uf::iostream << "New config: " << ::config << "\n"; } /* Seed */ { srand(time(NULL)); } /* Open output file */ { io.output.open(io.filenames.output); } /* Initialize timers */ { times.epoch = spec::time.getTime(); times.sys.start(); times.delta.start(); times.prevTime = times.sys.elapsed().asDouble(); times.curTime = times.sys.elapsed().asDouble(); } /* Read persistent data */ { // #include "./inits/persistence.inl" } /* Lua */ { ext::lua::main = ::config["engine"]["ext"]["lua"]["main"].as(); for ( auto it = ::config["engine"]["ext"]["lua"]["modules"].begin(); it != ::config["engine"]["ext"]["lua"]["modules"].end(); ++it ) { std::string key = it.key().as(); ext::lua::modules[key] = ::config["engine"]["ext"]["lua"]["modules"][key].as(); } ext::lua::initialize(); } /* Parse config */ { /* Set memory pool sizes */ { auto deduceSize = []( const Json::Value& value )->size_t{ if ( value.is() ) return value.as(); if ( value.is() ) { std::string str = value.as(); std::regex regex("^(\\d+) ?((?:K|M|G)?(?:i?B)?)$"); std::smatch match; if ( std::regex_search( str, match, regex ) ) { size_t requested = std::stoi( match[1].str() ); std::string prefix = match[2].str(); switch ( prefix.at(0) ) { case 'K': return requested * 1024; case 'M': return requested * 1024 * 1024; case 'G': return requested * 1024 * 1024 * 1024; } return requested; } } return 0; }; { size_t size = deduceSize( ::config["engine"]["memory pool"]["size"] ); uf::MemoryPool::globalOverride = ::config["engine"]["memory pool"]["globalOverride"].as(); uf::iostream << "Requesting " << (int) size << " bytes for global memory pool: " << &uf::MemoryPool::global << "\n"; uf::MemoryPool::global.initialize( size ); uf::MemoryPool::subPool = ::config["engine"]["memory pool"]["subPools"].as(); if ( size <= 0 || uf::MemoryPool::subPool ) { { size_t size = deduceSize( ::config["engine"]["memory pool"]["pools"]["component"] ); uf::iostream << "Requesting " << (int) size << " bytes for component memory pool: " << &uf::component::memoryPool << "\n"; uf::component::memoryPool.initialize( size ); } { size_t size = deduceSize( ::config["engine"]["memory pool"]["pools"]["userdata"] ); uf::iostream << "Requesting " << (int) size << " bytes for userdata memory pool: " << &uf::userdata::memoryPool << "\n"; uf::userdata::memoryPool.initialize( size ); } { size_t size = deduceSize( ::config["engine"]["memory pool"]["pools"]["entity"] ); uf::iostream << "Requesting " << (int) size << " bytes for entity memory pool: " << &uf::Entity::memoryPool << "\n"; uf::Entity::memoryPool.initialize( size ); } } } } if ( ::config["engine"]["limiters"]["framerate"].as() == "auto" && ::config["window"]["refresh rate"].is() ) { double scale = 1.0; size_t refreshRate = ::config["window"]["refresh rate"].as(); ::config["engine"]["limiters"]["framerate"] = refreshRate * scale; uf::iostream << "Setting framerate cap to " << (int) refreshRate * scale << "\n"; } if ( ::config["engine"]["threads"]["frame limiter"].as() == "auto" && ::config["window"]["refresh rate"].is() ) { double scale = 2.0; size_t refreshRate = ::config["window"]["refresh rate"].as(); ::config["engine"]["threads"]["frame limiter"] = refreshRate * scale; uf::iostream << "Setting thread frame limiter to " << (int) refreshRate * scale << "\n"; } /* Frame limiter */ { double limit = ::config["engine"]["limiters"]["framerate"].as(); if ( limit != 0 ) ::times.limiter = 1.0 / ::config["engine"]["limiters"]["framerate"].as(); else ::times.limiter = 0; } /* Thread frame limiter */ { double limit = ::config["engine"]["threads"]["frame limiter"].as(); if ( limit != 0 ) uf::thread::limiter = 1.0 / ::config["engine"]["threads"]["frame limiter"].as(); else uf::thread::limiter = 0; } /* Max delta time */{ double limit = ::config["engine"]["limiters"]["deltaTime"].as(); if ( limit != 0 ) uf::physics::time::clamp = 1.0 / ::config["engine"]["limiters"]["deltaTime"].as(); else uf::physics::time::clamp = 0; } // Mute audio if ( ::config["engine"]["audio"]["mute"].is() ) uf::Audio::mute = ::config["engine"]["audio"]["mute"].as(); // Set worker threads if ( ::config["engine"]["threads"]["workers"].as() == "auto" ) { auto threads = std::max( 1, (int) std::thread::hardware_concurrency() - 1 ); ::config["engine"]["threads"]["workers"] = threads; uf::iostream << "Using " << threads << " worker threads" << "\n"; } uf::thread::workers = ::config["engine"]["threads"]["workers"].as(); // Enable valiation layer uf::renderer::settings::validation = ::config["engine"]["ext"]["vulkan"]["validation"]["enabled"].as(); if ( ::config["engine"]["ext"]["vulkan"]["framebuffer"]["msaa"].is() ) uf::renderer::settings::msaa = ::config["engine"]["ext"]["vulkan"]["framebuffer"]["msaa"].as(); if ( ::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"].is() ) { float scale = ::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"].as(); uf::renderer::settings::width *= scale; uf::renderer::settings::height *= scale; } else if ( ext::json::isArray( ::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"] ) ) { uf::renderer::settings::width = ::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"][0].as(); uf::renderer::settings::height = ::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"][1].as(); std::string filter = ::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"][2].as(); if ( uf::string::lowercase( filter ) == "nearest" ) uf::renderer::settings::swapchainUpscaleFilter = VK_FILTER_NEAREST; else if ( uf::string::lowercase( filter ) == "linear" ) uf::renderer::settings::swapchainUpscaleFilter = VK_FILTER_LINEAR; } if ( ::config["engine"]["debug"]["entity"]["delete children on destroy"].is() ) uf::Entity::deleteChildrenOnDestroy = ::config["engine"]["debug"]["entity"]["delete children on destroy"].as(); if ( ::config["engine"]["debug"]["entity"]["delete components on destroy"].is() ) uf::Entity::deleteComponentsOnDestroy = ::config["engine"]["debug"]["entity"]["delete components on destroy"].as(); for ( int i = 0; i < ::config["engine"]["ext"]["vulkan"]["validation"]["filters"].size(); ++i ) { uf::renderer::settings::validationFilters.push_back( ::config["engine"]["ext"]["vulkan"]["validation"]["filters"][i].as() ); } for ( int i = 0; i < ::config["engine"]["ext"]["vulkan"]["extensions"]["device"].size(); ++i ) { uf::renderer::settings::requestedDeviceExtensions.push_back( ::config["engine"]["ext"]["vulkan"]["extensions"]["device"][i].as() ); } for ( int i = 0; i < ::config["engine"]["ext"]["vulkan"]["extensions"]["instance"].size(); ++i ) { uf::renderer::settings::requestedInstanceExtensions.push_back( ::config["engine"]["ext"]["vulkan"]["extensions"]["instance"][i].as() ); } for ( int i = 0; i < ::config["engine"]["ext"]["vulkan"]["features"].size(); ++i ) { uf::renderer::settings::requestedDeviceFeatures.push_back( ::config["engine"]["ext"]["vulkan"]["features"][i].as() ); } uf::renderer::settings::experimental::rebuildOnTickBegin = ::config["engine"]["ext"]["vulkan"]["experimental"]["rebuild on tick begin"].as(); uf::renderer::settings::experimental::waitOnRenderEnd = ::config["engine"]["ext"]["vulkan"]["experimental"]["wait on render end"].as(); uf::renderer::settings::experimental::individualPipelines = ::config["engine"]["ext"]["vulkan"]["experimental"]["individual pipelines"].as(); uf::renderer::settings::experimental::multithreadedCommandRecording = ::config["engine"]["ext"]["vulkan"]["experimental"]["multithreaded command recording"].as(); uf::renderer::settings::experimental::deferredReconstructPosition = ::config["engine"]["ext"]["vulkan"]["experimental"]["deferred reconstruct position"].as(); uf::renderer::settings::experimental::deferredAliasOutputToSwapchain = ::config["engine"]["ext"]["vulkan"]["experimental"]["deferred alias output to swapchain"].as(); ext::openvr::enabled = ::config["engine"]["ext"]["vr"]["enable"].as(); ext::openvr::swapEyes = ::config["engine"]["ext"]["vr"]["swap eyes"].as(); if ( ::config["engine"]["ext"]["vr"]["dominatEye"].is() ) ext::openvr::dominantEye = ::config["engine"]["ext"]["vr"]["dominatEye"].as(); else if ( ::config["engine"]["ext"]["vr"]["dominatEye"].as() == "left" ) ext::openvr::dominantEye = 0; else if ( ::config["engine"]["ext"]["vr"]["dominatEye"].as() == "right" ) ext::openvr::dominantEye = 1; ext::openvr::driver.manifest = ::config["engine"]["ext"]["vr"]["manifest"].as(); if ( ext::openvr::enabled ) { ::config["engine"]["render modes"]["stereo deferred"] = true; } } /* Create initial scene (kludge) */ { uf::Scene& scene = uf::instantiator::instantiate(); //new uf::Scene; uf::scene::scenes.push_back(&scene); auto& metadata = scene.getComponent(); metadata["system"]["config"] = ::config; } /* Initialize Vulkan */ { // uf::renderer::width = ::config["window"]["size"]["x"].as(); // uf::renderer::height = ::config["window"]["size"]["y"].as(); // setup render mode if ( ::config["engine"]["render modes"]["gui"].as() ) { auto* renderMode = new uf::renderer::RenderTargetRenderMode; uf::renderer::addRenderMode( renderMode, "Gui" ); renderMode->blitter.descriptor.subpass = 1; } if ( ::config["engine"]["render modes"]["multiview stereo deferred"].as() ) uf::renderer::addRenderMode( new uf::renderer::MultiviewStereoscopicDeferredRenderMode, "" ); else if ( ::config["engine"]["render modes"]["stereo deferred"].as() ) uf::renderer::addRenderMode( new uf::renderer::StereoscopicDeferredRenderMode, "" ); else if ( ::config["engine"]["render modes"]["deferred"].as() ) uf::renderer::addRenderMode( new uf::renderer::DeferredRenderMode, "" ); if ( ext::openvr::enabled ) { ext::openvr::initialize(); uint32_t width, height; ext::openvr::recommendedResolution( width, height ); auto& renderMode = uf::renderer::getRenderMode("Stereoscopic Deferred", true); renderMode.width = width; renderMode.height = height; uf::iostream << "Recommended VR Resolution: " << renderMode.width << ", " << renderMode.height << "\n"; if ( ::config["engine"]["ext"]["vr"]["scale"].is() ) { float scale = ::config["engine"]["ext"]["vr"]["scale"].as(); renderMode.width *= scale; renderMode.height *= scale; uf::iostream << "VR Resolution: " << renderMode.width << ", " << renderMode.height << "\n"; } } uf::renderer::initialize(); } /* */ { pod::Thread& threadMain = uf::thread::has("Main") ? uf::thread::get("Main") : uf::thread::create( "Main", false ); pod::Thread& threadPhysics = uf::thread::has("Physics") ? uf::thread::get("Physics") : uf::thread::create( "Physics", true ); } /* Discord */ if ( ::config["engine"]["ex"]["discord"]["enabled"].as() ) { ext::discord::initialize(); } /* Add hooks */ { uf::hooks.addHook( "game:Scene.Load", [&](const std::string& event)->std::string{ auto function = [event]() -> int { uf::Serializer json = event; uf::renderer::synchronize(); uf::hooks.call("game:Scene.Cleanup"); // uf::scene::unloadScene(); auto& scene = uf::scene::loadScene( json["scene"].as() ); auto& metadata = scene.getComponent(); metadata["system"]["config"] = ::config; return 0; }; uf::Serializer json = event; if ( json["immediate"].as() ) function(); else uf::thread::add( uf::thread::get("Main"), function, true ); return "true"; }); uf::hooks.addHook( "game:Scene.Cleanup", [&](const std::string& event)->std::string{ for ( auto* scene : uf::scene::scenes ) { if ( scene->hasComponent() ) { scene->getComponent().destroy(); } } uf::scene::unloadScene(); /* for ( auto* scene : uf::scene::scenes ) { scene->destroy(); delete scene; } uf::scene::scenes.clear(); */ return "true"; }); uf::hooks.addHook( "system:Quit", [&](const std::string& event)->std::string{ // uf::iostream << "system:Quit: " << event << "\n"; ext::ready = false; return "true"; }); } /* Initialize root scene*/ { uf::Serializer payload; payload["scene"] = ::config["engine"]["scenes"]["start"]; payload["immediate"] = true; uf::hooks.call("game:Scene.Load", payload); } ext::ready = true; uf::iostream << "EXT took " << times.sys.elapsed().asDouble() << " seconds to initialize!" << "\n"; { uf::thread::add( uf::thread::fetchWorker(), [&]() -> int { auto& scene = uf::scene::getCurrentScene(); auto& assetLoader = scene.getComponent(); assetLoader.processQueue(); return 0;}, false ); } } void EXT_API ext::tick() { /* Timer */ { times.prevTime = times.curTime; times.curTime = times.sys.elapsed().asDouble(); times.deltaTime = times.curTime - times.prevTime; } /* Print World Tree */ { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); if ( uf::Window::isKeyPressed("U") && timer.elapsed().asDouble() >= 1 ) { timer.reset(); std::function filter = []( uf::Entity* entity, int indent ) { for ( int i = 0; i < indent; ++i ) uf::iostream << "\t"; uf::iostream << entity->getName() << ": " << entity->getUid(); if ( entity->hasComponent>() ) { pod::Transform<> t = uf::transform::flatten(entity->getComponent>()); uf::iostream << " (" << t.position.x << ", " << t.position.y << ", " << t.position.z << ") (" << t.orientation.x << ", " << t.orientation.y << ", " << t.orientation.z << ", " << t.orientation.w << ")"; } uf::iostream << "\n"; }; for ( uf::Scene* scene : uf::scene::scenes ) { if ( !scene ) continue; uf::iostream << "Scene: " << scene->getName() << ": " << scene << "\n"; scene->process(filter, 1); } } } /* Print World Tree */ { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); if ( uf::Window::isKeyPressed("U") && timer.elapsed().asDouble() >= 1 ) { timer.reset(); std::function filter = []( uf::Entity* entity, int indent ) { for ( int i = 0; i < indent; ++i ) uf::iostream << "\t"; uf::iostream << entity->getName() << ": " << entity->getUid() << " ["; for ( auto& behavior : entity->getBehaviors() ) { uf::iostream << uf::instantiator::behaviors->names[behavior.type] << ", "; } uf::iostream << "]\n"; }; for ( uf::Scene* scene : uf::scene::scenes ) { if ( !scene ) continue; uf::iostream << "Scene: " << scene->getName() << ": " << scene << "\n"; scene->process(filter, 1); } uf::Serializer instantiator; { int i = 0; for ( auto& pair : uf::instantiator::objects->names ) { instantiator["objects"][i++] = pair.second; } } { int i = 0; for ( auto& pair : uf::instantiator::behaviors->names ) { instantiator["behaviors"][i++] = pair.second; } } uf::iostream << instantiator << "\n"; } } /* Print Entity Information */ { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); if ( uf::Window::isKeyPressed("P") && timer.elapsed().asDouble() >= 1 ) { timer.reset(); // uf::iostream << uf::renderer::allocatorStats() << "\n"; if ( uf::MemoryPool::global.size() > 0 ) uf::iostream << "Global Memory Pool:\n" << uf::MemoryPool::global.stats() << "\n"; if ( uf::Entity::memoryPool.size() > 0 ) uf::iostream << "Entity Memory Pool:\n" << uf::Entity::memoryPool.stats() << "\n"; if ( uf::component::memoryPool.size() > 0 ) uf::iostream << "Components Memory Pool:\n" << uf::component::memoryPool.stats() << "\n"; if ( uf::userdata::memoryPool.size() > 0 ) uf::iostream << "Userdata Memory Pool:\n" << uf::userdata::memoryPool.stats() << "\n"; } } /* Attempt to reset VR position */ { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); if ( uf::Window::isKeyPressed("Z") && timer.elapsed().asDouble() >= 1 ) { timer.reset(); uf::hooks.call("VR:Seat.Reset"); } } /* Print controller position */ if ( false ) { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); if ( uf::Window::isKeyPressed("Z") && timer.elapsed().asDouble() >= 1 ) { timer.reset(); auto& scene = uf::scene::getCurrentScene(); auto& controller = scene.getController(); auto& camera = controller.getComponent(); auto& t = camera.getTransform(); //controller->getComponent>(); uf::iostream << "Viewport position: (" << t.position.x << ", " << t.position.y << ", " << t.position.z << ") (" << t.orientation.x << ", " << t.orientation.y << ", " << t.orientation.z << ", " << t.orientation.w << ")"; uf::iostream << "\n"; if ( false ) { // uf::Entity* light = scene.findByUid(scene.loadChildUid("/light.json", true)); uf::Object& light = scene.loadChild("/light.json", true); auto& lTransform = light.getComponent>(); auto& lMetadata = light.getComponent(); lTransform.position = t.position; lTransform.orientation = t.orientation; if ( !ext::json::isArray( lMetadata["light"] ) ) { lMetadata["light"]["color"][0] = (rand() % 100) / 100.0; lMetadata["light"]["color"][1] = (rand() % 100) / 100.0; lMetadata["light"]["color"][2] = (rand() % 100) / 100.0; } auto& sMetadata = scene.getComponent(); sMetadata["light"]["should"] = true; } } } /* Limit tickrate */ { // static uf::Timer timer(false); // if ( !timer.running() ) timer.start(); // if ( timer.elapsed().asDouble() >= uf::thread::limiter ) { timer.reset(); { /* Update physics timer */ { uf::physics::tick(); } /* Update entities */ { uf::scene::tick(); } } } /* Tick Main Thread Queue */ { pod::Thread& thread = uf::thread::has("Main") ? uf::thread::get("Main") : uf::thread::create( "Main", false, true ); uf::thread::process( thread ); } /* Garbage collection */ if ( ::config["engine"]["debug"]["garbage collection"]["enabled"].as() ) { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); double every = ::config["engine"]["debug"]["garbage collection"]["every"].as(); uint8_t mode = ::config["engine"]["debug"]["garbage collection"]["mode"].as(); bool announce = ::config["engine"]["debug"]["garbage collection"]["announce"].as(); if ( timer.elapsed().asDouble() >= every ) { timer.reset(); size_t collected = uf::instantiator::collect( mode ); if ( announce && collected > 0 ) { uf::iostream << "GC collected " << (int) collected << " unused entities" << "\n"; } } } /* Update vulkan */ { uf::renderer::tick(); } /* Discord */ if ( ::config["engine"]["ext"]["discord"]["enable"].as() ) { ext::discord::tick(); } /* OpenVR */ if ( ext::openvr::context ) { ext::openvr::tick(); } /* FPS Print */ if ( ::config["engine"]["debug"]["framerate"]["print"].as() ) { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); ++::times.frames; double every = ::config["engine"]["debug"]["framerate"]["every"].as(); double time = 0; if ( (time = timer.elapsed().asDouble()) >= every ) { timer.reset(); // uf::iostream << "Framerate: " << (1.0/times.deltaTime) << " FPS | Frametime: " << (times.deltaTime * 1000) << "ms" << "\n"; uf::iostream << "System: " << (every * 1000.0/::times.frames) << " ms/frame | Time: " << time << " | Frames: " << ::times.frames << " | FPS: " << ::times.frames / time << "\n"; ::times.frames = 0; } } /* Frame limiter of sorts I guess */ if ( ::times.limiter > 0 ) { static uf::Timer timer(false); if ( !timer.running() ) timer.start(); auto elapsed = timer.elapsed().asMilliseconds(); long long sleep = (::times.limiter * 1000) - elapsed; if ( sleep > 0 ) { if ( ::config["engine"]["debug"]["framerate"]["print"].as() ) { uf::iostream << "Frame limiting: " << elapsed << "ms exceeds limit, sleeping for " << elapsed << "ms" << "\n"; } std::this_thread::sleep_for(std::chrono::milliseconds(sleep)); } timer.reset(); } } void EXT_API ext::render() { // uf::scene::render(); uf::renderer::render(); /* OpenVR */ if ( ext::openvr::context ) { ext::openvr::submit(); } } void EXT_API ext::terminate() { /* Kill threads */ { uf::thread::terminate(); } /* OpenVR */ if ( ext::openvr::context ) { ext::openvr::terminate(); } uf::scene::destroy(); /* Close vulkan */ { uf::renderer::destroy(); } /* Flush input buffer */ { io.output << io.input << "\n"; for ( const auto& str : uf::iostream.getHistory() ) io.output << str << "\n"; io.output << "\nTerminated after " << times.sys.elapsed().asDouble() << " seconds" << "\n"; io.output.close(); } /* Write persistent data */ if ( false ) { struct { bool exists = false; std::string filename = "./data/persistent.json"; } file; struct { uf::Serializer file; } config; /* Read from file */ { file.exists = config.file.readFromFile(file.filename); } /* Write persistent data */ { config.file.writeToFile(file.filename); } } } #if 0 std::string EXT_API ext::getConfig() { struct { bool initialized = false; uf::Serializer file; uf::Serializer fallback; uf::Serializer merged; } static config; if ( config.initialized ) return config.merged; struct { bool exists = false; std::string filename = "./data/config.json"; } file; /* Read from file */ { file.exists = config.file.readFromFile(file.filename); } /* Initialize default configuration */ { config.fallback["window"]["terminal"]["ncurses"] = true; config.fallback["window"]["terminal"]["visible"] = true; config.fallback["window"]["title"] = "Grimgram"; config.fallback["window"]["icon"] = ""; config.fallback["window"]["size"]["x"] = 0; config.fallback["window"]["size"]["y"] = 0; config.fallback["window"]["visible"] = true; // config.fallback["window"]["fullscreen"] = false; config.fallback["window"]["mode"] = "windowed"; config.fallback["window"]["cursor"]["visible"] = true; config.fallback["window"]["cursor"]["center"] = false; config.fallback["window"]["keyboard"]["repeat"] = true; config.fallback["engine"]["scenes"]["start"] = "StartMenu"; config.fallback["engine"]["scenes"]["max lights"] = 32; config.fallback["engine"]["hook"]["mode"] = "Readable"; config.fallback["engine"]["limiters"]["framerate"] = 60; config.fallback["engine"]["limiters"]["deltaTime"] = 120; config.fallback["engine"]["threads"]["workers"] = "auto"; config.fallback["engine"]["threads"]["frame limiter"] = 144; config.fallback["engine"]["memory pool"]["size"] = "512 MiB"; config.fallback["engine"]["memory pool"]["globalOverride"] = false; config.fallback["engine"]["memory pool"]["subPools"] = true; } /* Merge */ if ( file.exists ){ config.merged = config.file; config.merged.merge( config.fallback, true ); } else { config.merged = config.fallback; } /* Write default to file */ if ( false ) { config.merged.writeToFile(file.filename); } config.initialized = true; return config.merged; } #endif