#include "game.hpp" #include "camera.hpp" #include "components.hpp" #include "dynamic-array.hpp" #include "entities.hpp" #include "game-settings.hpp" #include "helpers.hpp" #include "imgui.h" #include "level-types.hpp" #include "level.hpp" #include "math-helpers.hpp" #include "physics.hpp" #include "player-input.hpp" #include "plugins.hpp" #include "project.hpp" #include "vendor/glm_include.hpp" #include "window.hpp" #include #include #include #include #include #include #include #include const long readLineLength = 128; char readLine[readLineLength]; const char *levelName = "demo-level"; const char *PKE_FILE_BEGIN = ":PKFB:"; const char *PKE_FILE_END = ":PKFE:"; const char *PKE_FILE_VERSION = ":0:"; const char *PKE_FILE_OBJ_END = ""; const char *PKE_FILE_OBJ_INSTANCE = "Instance:"; const char *PKE_FILE_OBJ_CAMERA = "Camera:"; const char *PKE_FILE_INSTANCE_ENTITY_HANDLE = "EntityHandle: "; const char *PKE_FILE_INSTANCE_ENTITY_TYPE_CODE = "EntityTypeCode: "; const char *PKE_FILE_INSTANCE_POS_POS = "InstPos::Pos: "; const char *PKE_FILE_INSTANCE_POS_ROT = "InstPos::Rot: "; const char *PKE_FILE_INSTANCE_POS_SCALE = "InstPos::Scale: "; const char *PKE_FILE_INSTANCE_PHYSICS_MASS = "InstPos::Mass: "; const char *PKE_FILE_INSTANCE_PHYSICS_COLLISION_LAYER = "InstPos::CollisionLayer: "; const char *PKE_FILE_INSTANCE_PHYSICS_COLLISION_MASK = "InstPos::CollisionMask: "; const char *PKE_FILE_INSTANCE_COLLISION_CALLBACK_SIGNATURE = "Inst::CollisionCallbackSignature: "; const char *PKE_FILE_CAMERA_POS = "Cam::Pos: "; const char *PKE_FILE_CAMERA_ROT = "Cam::Rot: "; const char *PKE_FILE_CAMERA_TARGET = "Cam::Target: "; const char *PKE_FILE_CAMERA_TYPE = "Cam::Type: "; const char *PKE_FILE_CAMERA_ORIENTATION = "Cam::Orientation: "; const char *PKE_FILE_CAMERA_IS_PRIMARY = "Cam::IsPrimary: "; void SerializeCamera(std::ofstream &stream, const PkeCamera &cam) { PkeCamera c{}; if (cam.pos != c.pos) { stream << PKE_FILE_CAMERA_POS << "[" << std::setw(10) << cam.pos[0] << "," << std::setw(10) << cam.pos[1] << "," << std::setw(10) << cam.pos[2] << "]" << std::endl; } if (cam.rot != c.rot) { stream << PKE_FILE_CAMERA_ROT << "[" << std::setw(10) << cam.rot[0] << "," << std::setw(10) << cam.rot[1] << "," << std::setw(10) << cam.rot[2] << "," << std::setw(10) << cam.rot[3] << "]" << std::endl; } if (cam.target != c.target) { stream << PKE_FILE_CAMERA_TARGET << "[" << std::setw(10) << cam.target[0] << "," << std::setw(10) << cam.target[1] << "," << std::setw(10) << cam.target[2] << "]" << std::endl; } if (cam.type != c.type) { stream << PKE_FILE_CAMERA_TYPE << int(static_cast(cam.type)) << std::endl; } if (cam.orientation != c.orientation) { stream << PKE_FILE_CAMERA_ORIENTATION << int(static_cast(cam.orientation)) << std::endl; } if (cam.isPrimary != c.isPrimary) { stream << PKE_FILE_CAMERA_IS_PRIMARY << cam.isPrimary << std::endl; } } void SerializeInstance(std::ofstream &stream, const CompInstance &comp) { char handleStr[19] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; snprintf(handleStr, 19, "0x%016lX", comp.entHandle.hash); EntityType *et = nullptr; // EntityTypeDetails *etd = nullptr; for (long i = 0; i < GlobalEntityTypes.Count(); ++i) { for (int64_t k = 0; k < GlobalEntityTypes[i].detailsCount; ++k) { if (GlobalEntityTypes[i].details[k].grBindsHandle == comp.grBindsHandle) { et = &GlobalEntityTypes[i]; // etd = &GlobalEntityTypes[i].details[k]; break; } } } assert(et != nullptr); CompInstance c{}; InstPos baseInst{}; baseInst.posRot = btTransform{}; baseInst.posRot.setIdentity(); baseInst.scale = btVector3(1, 1, 1); baseInst.mass = 1; if (comp.entHandle != c.entHandle) stream << PKE_FILE_INSTANCE_ENTITY_HANDLE << handleStr << std::endl; stream << PKE_FILE_INSTANCE_ENTITY_TYPE_CODE << et->entityTypeCode << std::endl; btTransform trans; comp.bt.motionState->getWorldTransform(trans); btVector3 scale = comp.bt.rigidBody->getCollisionShape()->getLocalScaling(); btScalar mass = comp.bt.rigidBody->getMass(); PhysicsCollision collisionLayer{static_cast(comp.bt.rigidBody->getBroadphaseProxy()->m_collisionFilterGroup)}; PhysicsCollision collisionMask{static_cast(comp.bt.rigidBody->getBroadphaseProxy()->m_collisionFilterMask)}; if (trans != baseInst.posRot) { btVector3 pos = trans.getOrigin(); btQuaternion rot = trans.getRotation(); stream << PKE_FILE_INSTANCE_POS_POS << "[" << std::setw(10) << pos[0] << "," << std::setw(10) << pos[1] << "," << std::setw(10) << pos[2] << "]" << std::endl << PKE_FILE_INSTANCE_POS_ROT << "[" << std::setw(10) << rot[0] << "," << std::setw(10) << rot[1] << "," << std::setw(10) << rot[2] << "," << std::setw(10) << rot[3] << "]" << std::endl; } if (scale != baseInst.scale) stream << PKE_FILE_INSTANCE_POS_SCALE << "[" << std::setw(10) << scale[0] << "," << std::setw(10) << scale[1] << "," << std::setw(10) << scale[2] << "]" << std::endl; if (mass != baseInst.mass) { stream << PKE_FILE_INSTANCE_PHYSICS_MASS << mass << std::endl; } if (collisionLayer != c.physicsLayer) { stream << PKE_FILE_INSTANCE_PHYSICS_COLLISION_LAYER << static_cast(collisionLayer) << std::endl; } if (collisionMask != c.physicsMask) { stream << PKE_FILE_INSTANCE_PHYSICS_COLLISION_MASK << static_cast(collisionMask) << std::endl; } if (comp.collisionCallback.name[0] != '\0') { stream << PKE_FILE_INSTANCE_COLLISION_CALLBACK_SIGNATURE << comp.collisionCallback.name << std::endl; } } void ParseCamera(LevelHandle levelHandle, std::ifstream &stream) { PkeCamera cam{}; while (stream.getline(readLine, readLineLength)) { if (strcmp(readLine, PKE_FILE_OBJ_END) == 0) { auto &rCam = PkeCamera_Register(); rCam.pos = cam.pos; rCam.rot = cam.rot; rCam.target = cam.target; rCam.type = cam.type; rCam.orientation = cam.orientation; rCam.isPrimary = cam.isPrimary; if (levelHandle != LevelHandle_MAX) { PkeLevel_RegisterCamera(levelHandle, rCam.handle); } if (rCam.isPrimary == true) { ActiveCamera = &rCam; } return; } if (strncmp(readLine, PKE_FILE_CAMERA_POS, strlen(PKE_FILE_CAMERA_POS)) == 0) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_POS); char *startingChar = strchr(readLine + prefixLen, '[') + 1; assert(startingChar != nullptr); char *pEnd = nullptr; long index = 0; do { assert(index < 3); STR2NUM_ERROR result = str2num(cam.pos[index], startingChar, pEnd); assert(result == STR2NUM_ERROR::SUCCESS); startingChar = pEnd + 1; ++index; } while (*pEnd != ']'); } if (strncmp(readLine, PKE_FILE_CAMERA_ROT, strlen(PKE_FILE_CAMERA_ROT)) == 0) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_ROT); char *startingChar = strchr(readLine + prefixLen, '[') + 1; assert(startingChar != nullptr); char *pEnd = nullptr; long index = 0; do { assert(index < 4); STR2NUM_ERROR result = str2num(cam.rot[index], startingChar, pEnd); assert(result == STR2NUM_ERROR::SUCCESS); startingChar = pEnd + 1; ++index; } while (*pEnd != ']'); } if (strncmp(readLine, PKE_FILE_CAMERA_TARGET, strlen(PKE_FILE_CAMERA_TARGET)) == 0) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_TARGET); char *startingChar = strchr(readLine + prefixLen, '[') + 1; assert(startingChar != nullptr); char *pEnd = nullptr; long index = 0; do { assert(index < 3); STR2NUM_ERROR result = str2num(cam.target[index], startingChar, pEnd); assert(result == STR2NUM_ERROR::SUCCESS); startingChar = pEnd + 1; ++index; } while (*pEnd != ']'); } if (strncmp(readLine, PKE_FILE_CAMERA_TYPE, strlen(PKE_FILE_CAMERA_TYPE)) == 0) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_TYPE); PkeCameraType_T handle_t; STR2NUM_ERROR result = str2num(handle_t, readLine + prefixLen); assert(result == STR2NUM_ERROR::SUCCESS); cam.type = PkeCameraType{handle_t}; continue; } if (strncmp(readLine, PKE_FILE_CAMERA_ORIENTATION, strlen(PKE_FILE_CAMERA_ORIENTATION)) == 0) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_ORIENTATION); PkeCameraOrientation_T handle_t; STR2NUM_ERROR result = str2num(handle_t, readLine + prefixLen); assert(result == STR2NUM_ERROR::SUCCESS); cam.orientation = PkeCameraOrientation{handle_t}; continue; } if (strncmp(readLine, PKE_FILE_CAMERA_IS_PRIMARY, strlen(PKE_FILE_CAMERA_IS_PRIMARY)) == 0) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_IS_PRIMARY); uint8_t isPrimary; STR2NUM_ERROR result = str2num(isPrimary, readLine + prefixLen); assert(result == STR2NUM_ERROR::SUCCESS); cam.isPrimary = bool(isPrimary); continue; } } } void ParseInstance(EntityHandle parentEntHandle, std::ifstream &stream) { CompInstance comp{}; InstPos instPos{}; instPos.posRot = btTransform{}; instPos.posRot.setIdentity(); instPos.scale = btVector3(1, 1, 1); instPos.mass = 1.f; comp.collisionCallback.name[0] = '\0'; char entTypeCode[21]; memset(reinterpret_cast(entTypeCode), '\0', 21); while (stream.getline(readLine, readLineLength)) { if (strstr(PKE_FILE_OBJ_END, readLine)) { if (entTypeCode[0] == '\0') { printf("[Game::ParseInstance] Failed to create instance from save file. No EntTypeCode present.\n"); break; } int64_t existingEntityTypeIndex = EntityType_FindByTypeCode(entTypeCode); if (existingEntityTypeIndex == -1) { printf("[Game::ParseInstance] Failed to create instance from save file. Unknown EntityTypeCode: \"%s\"", entTypeCode); break; } const auto &et = GlobalEntityTypes[existingEntityTypeIndex]; for (int64_t i = 0; i < et.detailsCount; ++i) { auto &etd = et.details[i]; auto entHandle = ECS_CreateEntity(parentEntHandle); auto &compInst = ECS_CreateInstance(entHandle, etd.entityHandle); strncpy(compInst.collisionCallback.name, comp.collisionCallback.name, 16); PkePlugin_SetSignatureFunc(&compInst.collisionCallback); compInst.physicsLayer = comp.physicsLayer; compInst.physicsMask = comp.physicsMask; btVector3 localInertia(0, 0, 0); etd.bt.shape->calculateLocalInertia(instPos.mass, localInertia); compInst.bt.motionState = Pke_New(MemBkt_Bullet); new (compInst.bt.motionState) btDefaultMotionState(instPos.posRot); compInst.bt.rigidBody = Pke_New(MemBkt_Bullet); new (compInst.bt.rigidBody) btRigidBody(instPos.mass, compInst.bt.motionState, etd.bt.shape, localInertia); compInst.bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); compInst.bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); compInst.bt.rigidBody->getCollisionShape()->setLocalScaling(instPos.scale); BtDynamicsWorld->addRigidBody(compInst.bt.rigidBody); auto *broadphaseProxy = compInst.bt.rigidBody->getBroadphaseProxy(); broadphaseProxy->m_collisionFilterGroup = static_cast(comp.physicsLayer); broadphaseProxy->m_collisionFilterMask = static_cast(comp.physicsMask); compInst.bt.rigidBody->setUserPointer(reinterpret_cast(compInst.entHandle.hash)); } break; } if (strstr(readLine, PKE_FILE_INSTANCE_ENTITY_HANDLE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_ENTITY_HANDLE); STR2NUM_ERROR result = str2num(comp.entHandle.hash, readLine + prefixLen); assert(result == STR2NUM_ERROR::SUCCESS); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_ENTITY_TYPE_CODE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_ENTITY_TYPE_CODE); uint64_t len = strlen(readLine + prefixLen); memcpy(entTypeCode, readLine + prefixLen, len); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_POS_POS)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_POS_POS); char *startingChar = strchr(readLine + prefixLen, '[') + 1; assert(startingChar != nullptr); char *pEnd = nullptr; long index = 0; btVector3 pos; do { assert(index < 3); STR2NUM_ERROR result = str2num(pos[index], startingChar, pEnd); assert(result == STR2NUM_ERROR::SUCCESS); startingChar = pEnd + 1; ++index; } while (*pEnd != ']'); instPos.posRot.setOrigin(pos); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_POS_ROT)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_POS_ROT); char *startingChar = strchr(readLine + prefixLen, '[') + 1; assert(startingChar != nullptr); char *pEnd = nullptr; long index = 0; btQuaternion rot; do { assert(index < 4); STR2NUM_ERROR result = str2num(rot[index], startingChar, pEnd); assert(result == STR2NUM_ERROR::SUCCESS); startingChar = pEnd + 1; ++index; } while (*pEnd != ']'); instPos.posRot.setRotation(rot); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_POS_SCALE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_POS_SCALE); char *startingChar = strchr(readLine + prefixLen, '[') + 1; assert(startingChar != nullptr); char *pEnd = nullptr; long index = 0; do { assert(index < 3); STR2NUM_ERROR result = str2num(instPos.scale[index], startingChar, pEnd); assert(result == STR2NUM_ERROR::SUCCESS); startingChar = pEnd + 1; ++index; } while (*pEnd != ']'); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_PHYSICS_MASS)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_PHYSICS_MASS); STR2NUM_ERROR result = str2num(instPos.mass, readLine + prefixLen); assert(result == STR2NUM_ERROR::SUCCESS); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_PHYSICS_COLLISION_LAYER)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_PHYSICS_COLLISION_LAYER); PhysicsCollision_T val = static_cast(comp.physicsLayer); STR2NUM_ERROR result = str2num(val, readLine + prefixLen); comp.physicsLayer = PhysicsCollision{val}; assert(result == STR2NUM_ERROR::SUCCESS); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_PHYSICS_COLLISION_MASK)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_PHYSICS_COLLISION_MASK); PhysicsCollision_T val = static_cast(comp.physicsMask); STR2NUM_ERROR result = str2num(val, readLine + prefixLen); comp.physicsMask = PhysicsCollision{val}; assert(result == STR2NUM_ERROR::SUCCESS); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_COLLISION_CALLBACK_SIGNATURE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_COLLISION_CALLBACK_SIGNATURE); strncpy(comp.collisionCallback.name, readLine + prefixLen, 16); continue; } } } void Game_SaveSceneFile(const char *sceneFilePath) { std::ofstream f(sceneFilePath); assert(f.is_open()); f << PKE_FILE_BEGIN << std::endl; f << PKE_FILE_VERSION << std::endl; f << "" << std::endl; int64_t cameraBucketCount = PkeCamera_GetBucketCount(); for (long b = 0; b < cameraBucketCount; ++b) { int64_t count; auto *cameras = PkeCamera_GetCameras(b, count); for (long i = 0; i < count; ++i) { const auto &cam = cameras[i]; if (cam.handle == CameraHandle_MAX) continue; f << PKE_FILE_OBJ_CAMERA << std::endl; SerializeCamera(f, cam); f << PKE_FILE_OBJ_END << std::endl; } } int64_t instanceBucketCount = ECS_GetInstances_BucketCount(); for (long b = 0; b < instanceBucketCount; ++b) { uint64_t count; auto *instances = ECS_GetInstances(b, count); for (long i = 0; i < count; ++i) { const auto &instance = instances[i]; if (instance.entHandle == EntityHandle_MAX) continue; f << PKE_FILE_OBJ_INSTANCE << std::endl; SerializeInstance(f, instance); f << PKE_FILE_OBJ_END << std::endl; } } f << PKE_FILE_END << std::endl; f.flush(); f.close(); } void Game_LoadSceneFile(LevelHandle levelHandle, const char *sceneFilePath) { std::ifstream f(sceneFilePath); if (!f.is_open()) { fprintf(stderr, "Failed to load requested scene file: %s\n", sceneFilePath); return; } memset(readLine, '\0', readLineLength); EntityHandle parentEntHandle = EntityHandle_MAX; if (levelHandle != LevelHandle_MAX) { parentEntHandle = ECS_CreateEntity(); PkeLevel_RegisterWrappingEntity(levelHandle, parentEntHandle); } while (f.getline(readLine, readLineLength)) { if (strcmp(PKE_FILE_OBJ_CAMERA, readLine) == 0) { ParseCamera(levelHandle, f); continue; } if (strcmp(PKE_FILE_OBJ_INSTANCE, readLine) == 0) { ParseInstance(parentEntHandle, f); continue; } } f.close(); } const uint64_t consoleBufferCount = 30; const uint64_t consoleLineLength = 128; char consoleBuffer[consoleBufferCount][consoleLineLength]; long consoleBufferIndex = 0; void Game_RecordImGui() { static bool scrollToBottom = true; if (!ImGui::Begin("Console", &pkeSettings.editorSettings.isShowingConsole)) { ImGui::End(); return; } ImVec2 region = ImGui::GetContentRegionAvail(); region.y -= 27; if (ImGui::BeginListBox("##ConsoleHistory", region)) { for (long i = consoleBufferIndex + 1; i < consoleBufferCount; ++i) { ImGui::Text("%s", consoleBuffer[i]); } for (long i = 0; i < consoleBufferIndex; ++i) { ImGui::Text("%s", consoleBuffer[i]); } if (scrollToBottom) ImGui::SetScrollHereY(1); scrollToBottom = false; ImGui::EndListBox(); } ImGui::Separator(); if (ImGui::InputText("##ConsoleInput", consoleBuffer[consoleBufferIndex], consoleLineLength, ImGuiInputTextFlags_EnterReturnsTrue)) { // TODO parse and execute. scrollToBottom = true; consoleBufferIndex = (consoleBufferIndex + 1) % consoleBufferCount; memset(consoleBuffer[consoleBufferIndex], '\0', consoleLineLength); } auto focusedFlags = (ImGuiFocusedFlags_ChildWindows); if (ImGui::IsWindowFocused(focusedFlags) && !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered() && !ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGui::IsMouseClicked(ImGuiMouseButton_Left, true)) { ImGui::SetKeyboardFocusHere(-1); } ImGui::End(); } void Game_Tick(double delta) { Pke_ResetBucket(pkeSettings.mem.bkt); // TODO this should be removed in favor of storing the scene details inside a level definition if (pkeSettings.rt.shouldLoadScene && pkeSettings.rt.sceneName) { pkeSettings.rt.shouldLoadScene = false; if (pkeSettings.rt.activeLevel != LevelHandle_MAX) { pkeSettings.rt.previousLevel = pkeSettings.rt.activeLevel; } pkeSettings.rt.nextLevel = PkeLevel_Create(pkeSettings.rt.sceneName); } if (pkeSettings.rt.nextLevel != LevelHandle_MAX) { // TODO async this Game_LoadSceneFile(pkeSettings.rt.nextLevel, pkeSettings.rt.sceneName); pkeSettings.rt.activeLevel = pkeSettings.rt.nextLevel; pkeSettings.rt.nextLevel = LevelHandle_MAX; } if (pkeSettings.rt.previousLevel != LevelHandle_MAX) { PkeLevel_Remove(pkeSettings.rt.previousLevel); pkeSettings.rt.previousLevel = LevelHandle_MAX; } /* * ECS_Tick() gets called first because it updates the public * `EntitiesToBeRemoved` for all other ticks to use. */ ECS_Tick_Early(delta); ECS_Tick(delta); PkeInput_Tick(delta); const auto pluginCount = LoadedPkePlugins.Count(); for (long i = 0; i < pluginCount; ++i) { if (LoadedPkePlugins[i].OnTick != nullptr) { LoadedPkePlugins[i].OnTick(delta); } } EntityType_Tick_Late(delta); ECS_Tick_Late(delta); } void Game_Main(PKEWindowProperties windowProps, const char *executablePath) { pkeSettings.executablePath = executablePath; fprintf(stdout, "Game_Main Entering\n"); try { AM_Init(); PkeCamera_Init(); Physics_Init(); Game_Init(); ECS_Init(); PkeProject_Load(pkeSettings.args.projectPath); CreateWindow(windowProps); PkeInput_Init(); EntityType_Init(); if (pkeSettings.args.pluginPath != nullptr) { PkePlugin_Load(pkeSettings.args.pluginPath); } const long pluginCount = LoadedPkePlugins.Count(); for (long i = 0; i < pluginCount; ++i) { if (LoadedPkePlugins[i].OnInit != nullptr) { LoadedPkePlugins[i].OnInit(); } } GameTimePoint lastTimePoint = pkeSettings.steadyClock.now(); double deltaTillNextRender = pkeSettings.deltaPerFrame; GameTimePoint lastLogTimePoint = pkeSettings.steadyClock.now(); int64_t tickCount = 0; int64_t renderCount = 0; int64_t nsAhead = 0.0; while (pkeSettings.isGameRunning) { glfwPollEvents(); int64_t nsAheadHolder = 0.0; if (nsAhead > 0) { nsAheadHolder = nsAhead; std::this_thread::sleep_for(GameTimeDuration(nsAhead)); nsAhead = 0; } if (vidMode.refreshRate != pkeSettings.targetFPS) { pkeSettings.targetFPS = vidMode.refreshRate; pkeSettings.deltaPerFrame = 1 / double(pkeSettings.targetFPS); } GameTimePoint currentTimePoint = pkeSettings.steadyClock.now(); double deltaThisTick = ((currentTimePoint - lastTimePoint).count() - nsAheadHolder) / NANO_DENOM_DOUBLE; deltaThisTick = std::min(deltaThisTick, pkeSettings.minimumDeltaPerFrame); lastTimePoint = currentTimePoint; deltaTillNextRender -= deltaThisTick; bool shouldRender = pkeSettings.graphicsSettings.isFramerateUnlocked || pkeSettings.graphicsSettings.isWaitingForVsync || deltaTillNextRender <= 0.0; if (shouldRender == false && (deltaTillNextRender > 0.0 && deltaTillNextRender - (deltaThisTick * 2.0) <= 0.0)) { /* * We are ahead of the render schedule * && the current tick's speed would put us behind schedule next tick. * Simulate the extra time we are ahead and prepare to sleep the difference * before the next tick. */ nsAhead = std::floor(deltaTillNextRender * NANO_DENOM_DOUBLE); deltaThisTick += deltaTillNextRender; shouldRender = true; } tickCount += 1; Game_Tick(deltaThisTick); if (shouldRender) { Render(); renderCount += 1; double msBehind = deltaTillNextRender * -1000; int64_t behindCount = 0; while (deltaTillNextRender < pkeSettings.deltaPerFrame) { behindCount += 1; deltaTillNextRender += pkeSettings.deltaPerFrame; } if (behindCount > 2) { fprintf(stderr, "[PKE::main] late render - simulated ahead: %fms - delta behind: %fms - missed frames:%ld\n", nsAheadHolder / (NANO_DENOM_DOUBLE / 1000), msBehind, behindCount - 1); fflush(stderr); } } if ((currentTimePoint - lastLogTimePoint).count() > std::chrono::nanoseconds::period::den) { lastLogTimePoint = currentTimePoint; fprintf(stdout, "TPS: ~%ld - actual:%ld - presents:%ld\n", int64_t(1 / deltaThisTick), tickCount, renderCount); fflush(stdout); tickCount = 0; renderCount = 0; } pkeSettings.isGameRunning = !glfwWindowShouldClose(window); } vkDeviceWaitIdle(vkDevice); } catch (const std::exception &exc) { fprintf(stderr, "Game_Main EXCEPTION: %s\n", exc.what()); } catch (const char *err) { fprintf(stderr, "Game_Main UNHANDLED EXCEPTION: %s\n", err); } catch (...) { fprintf(stderr, "Game_Main UNHANDLED EXCEPTION\n"); } fprintf(stdout, "Game_Main SHUTDOWN INITIATED\n"); #ifndef NDEBUG Pke_DebugPrint(); #endif const auto pluginCount = LoadedPkePlugins.Count(); for (long i = 0; i < pluginCount; ++i) { if (LoadedPkePlugins[i].OnTeardown) { LoadedPkePlugins[i].OnTeardown(); } } Game_Teardown(); Event_Teardown(); EntityType_Teardown(); PkeInput_Teardown(); Physics_Teardown(); ECS_Teardown(); PkeCamera_Teardown(); AM_DebugPrint(); AM_Teardown(); DestroyWindow(); Pke_DebugPrint(); fprintf(stdout, "Game_Main Exiting\n"); } void Game_Init() { pkeSettings.mem.bkt = Pke_BeginTransientBucket(1UL << 26); for (long i = 0; i < consoleBufferCount; ++i) { memset(consoleBuffer[i], '\0', consoleLineLength); } } void Game_Teardown() { }