#include "game.hpp" #include "array.hpp" #include "camera.hpp" #include "components.hpp" #include "dynamic-array.hpp" #include "ecs.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 "thread-pool.hpp" #include "vendor-glm-include.hpp" #include "window.hpp" #include "pk.h" #include #include #include #include #include #include #include #include #include const long readLineLength = 128; char readLine[readLineLength]; struct InstMapping { InstanceHandle origHandle = InstanceHandle_MAX; EntityHandle newEntHandle; InstanceHandle newInstHandle; InstPos newInstance{}; }; PkeArray loadFileInstanceMappings{}; 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_HANDLE = "Inst::InstHandle: "; 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_INSTANCE_SPECIAL_ENTITY_TYPE_CODE_CAMERA = 'C'; 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_INSTANCE_HANDLE = "Cam::InstanceHandle: "; const char *PKE_FILE_CAMERA_TARGET_INSTANCE_HANDLE = "Cam::TargetInstanceHandle: "; const char *PKE_FILE_CAMERA_IS_PRIMARY = "Cam::IsPrimary: "; void SerializeCamera(std::ostringstream &stream, const PkeCamera &cam) { NULL_CHAR_ARR(handleStr, 23); PkeCamera c{}; if (cam.type != c.type) { stream << PKE_FILE_CAMERA_TYPE << int(static_cast(cam.type)) << std::endl; } if (cam.view != c.view) { stream << PKE_FILE_CAMERA_ORIENTATION << int(static_cast(cam.view)) << std::endl; } if (cam.phys.instHandle != InstanceHandle_MAX) { snprintf(handleStr, 22, "0x%08X 0x%08X", cam.phys.instHandle.bucketIndex, cam.phys.instHandle.itemIndex); stream << PKE_FILE_CAMERA_INSTANCE_HANDLE << handleStr << std::endl; } if (cam.phys.targetInstHandle != InstanceHandle_MAX) { snprintf(handleStr, 22, "0x%08X 0x%08X", cam.phys.targetInstHandle.bucketIndex, cam.phys.targetInstHandle.itemIndex); stream << PKE_FILE_CAMERA_TARGET_INSTANCE_HANDLE << handleStr << std::endl; } if (cam.isPrimary != c.isPrimary) { stream << PKE_FILE_CAMERA_IS_PRIMARY << cam.isPrimary << std::endl; } } void SerializeInstance(std::ostringstream &stream, const CompInstance &comp) { NULL_CHAR_ARR(handleStr, 23); EntityType *et = nullptr; if (comp.grBindsHandle != GrBindsHandle_MAX) { et = EntityType_FindByEntityHandle(ECS_GetGrBinds(comp.grBindsHandle)->entHandle); } CompInstance c{}; InstPos baseInst{}; baseInst.posRot = btTransform{}; baseInst.posRot.setIdentity(); baseInst.scale = btVector3(1, 1, 1); baseInst.mass = 1; if (comp.entHandle != InstanceHandle_MAX) { snprintf(handleStr, 22, "0x%08X 0x%08X", comp.entHandle.bucketIndex, comp.entHandle.itemIndex); stream << PKE_FILE_INSTANCE_ENTITY_HANDLE << handleStr << std::endl; } if (comp.instanceHandle != InstanceHandle_MAX) { snprintf(handleStr, 22, "0x%08X 0x%08X", comp.instanceHandle.bucketIndex, comp.instanceHandle.itemIndex); stream << PKE_FILE_INSTANCE_HANDLE << handleStr << std::endl; } if (et != nullptr) { stream << PKE_FILE_INSTANCE_ENTITY_TYPE_CODE << et->entityTypeCode.val << std::endl; } else if (PkeCamera_Get(comp.entHandle)) { stream << PKE_FILE_INSTANCE_ENTITY_TYPE_CODE << PKE_FILE_INSTANCE_SPECIAL_ENTITY_TYPE_CODE_CAMERA << 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; } } bool FindFirstInstanceHandle(const InstMapping &mapping, const InstanceHandle handle) { return mapping.origHandle == handle; } void ParseCamera(PkeLevel *level, std::ifstream &stream) { PkeCamera cam{}; InstanceHandle instanceHandle = InstanceHandle_MAX; InstanceHandle targetInstanceHandle = InstanceHandle_MAX; while (stream.getline(readLine, readLineLength)) { if (strcmp(readLine, PKE_FILE_OBJ_END) == 0) { int64_t instanceIndex = -1, targetInstanceIndex = -1; instanceIndex = PkeArray_FindFirstIndex(&loadFileInstanceMappings, FindFirstInstanceHandle, instanceHandle); if (targetInstanceHandle != InstanceHandle_MAX) { targetInstanceIndex = PkeArray_FindFirstIndex(&loadFileInstanceMappings, FindFirstInstanceHandle, targetInstanceHandle); } InstPos instPos; if (instanceIndex == -1) { instPos.mass = 1.f; instPos.posRot.setIdentity(); instPos.scale = btVector3(1.f, 1.f, 1.f); fprintf(stdout, "[ParseCamera] Failed to find instance mapping. Is this an outdated parse?\n"); } else { instPos = loadFileInstanceMappings.data[instanceIndex].newInstance; } auto &rCam = PkeCamera_Register(instPos); rCam.type = cam.type; rCam.view = cam.view; rCam.isPrimary = cam.isPrimary; rCam.phys.targetInstHandle = targetInstanceHandle; PkeLevel_RegisterCamera(level->levelHandle, rCam.camHandle); if (targetInstanceIndex > -1) { PkeCamera_TargetInstance(rCam.camHandle, ECS_GetInstance(loadFileInstanceMappings.data[targetInstanceIndex].newInstHandle)); } if (rCam.isPrimary == true) { ActiveCamera = &rCam; } return; } 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); PkeCameraView_T handle_t; STR2NUM_ERROR result = str2num(handle_t, readLine + prefixLen); assert(result == STR2NUM_ERROR::SUCCESS); cam.view = PkeCameraView{handle_t}; continue; } if (strstr(readLine, PKE_FILE_CAMERA_INSTANCE_HANDLE)) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_INSTANCE_HANDLE); readLine[prefixLen + 10] = '\0'; STR2NUM_ERROR result1 = str2num(instanceHandle.bucketIndex, readLine + prefixLen); STR2NUM_ERROR result2 = str2num(instanceHandle.itemIndex, readLine + prefixLen + 11); assert(result1 == STR2NUM_ERROR::SUCCESS); assert(result2 == STR2NUM_ERROR::SUCCESS); // TODO add to global list continue; } if (strstr(readLine, PKE_FILE_CAMERA_TARGET_INSTANCE_HANDLE)) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_TARGET_INSTANCE_HANDLE); readLine[prefixLen + 10] = '\0'; STR2NUM_ERROR result1 = str2num(targetInstanceHandle.bucketIndex, readLine + prefixLen); STR2NUM_ERROR result2 = str2num(targetInstanceHandle.itemIndex, readLine + prefixLen + 11); assert(result1 == STR2NUM_ERROR::SUCCESS); assert(result2 == STR2NUM_ERROR::SUCCESS); // TODO find and set 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(Entity_Base *parentEntity, std::ifstream &stream) { CompInstance comp{}; InstMapping mapping { .origHandle = InstanceHandle_MAX, .newEntHandle = EntityHandle_MAX, .newInstHandle = InstanceHandle_MAX, .newInstance = { .scale = btVector3(1.f, 1.f, 1.f), .mass = 1.f, }, }; mapping.newInstance.posRot.setIdentity(); comp.collisionCallback.name[0] = '\0'; NULL_CHAR_ARR(entTypeCode, 21); while (stream.getline(readLine, readLineLength)) { if (strstr(PKE_FILE_OBJ_END, readLine)) { EntityType *etPtr = nullptr; Entity_Base *entity = nullptr; bool skipEntCreate = false; if (strlen(entTypeCode) > 1) { etPtr = EntityType_FindByTypeCode(entTypeCode); if (etPtr == nullptr) { fprintf(stdout, "[Game::ParseInstance] Unknown EntityTypeCode: \"%s\"\n", entTypeCode); break; } } else if (strlen(entTypeCode) == 1) { // handle internally if (entTypeCode[0] == PKE_FILE_INSTANCE_SPECIAL_ENTITY_TYPE_CODE_CAMERA) { skipEntCreate = true; } } else { fprintf(stdout, "[Game::ParseInstance] Failed to create instance from save file. No EntTypeCode present.\n"); break; } if (skipEntCreate == false) { if (etPtr != nullptr && etPtr->createInstanceCallback.func != nullptr) { typedef Entity_Base *CreateInst(); entity = reinterpret_cast(etPtr->createInstanceCallback.func)(); } else { entity = EntityType_CreateGenericInstance(etPtr, parentEntity, &comp, &mapping.newInstance); fprintf(stdout ,"[Game::ParseInstance] Debug: entTypeCode '%s' does not have a registered callback func to handle instance creation.\n", entTypeCode); } mapping.newEntHandle = entity->handle; } if (mapping.newEntHandle != EntityHandle_MAX) { // TODO this is messy PkeArray instances{}; ECS_GetInstances(entity, instances); assert(instances.next > 0); mapping.newInstHandle = instances.data[0]->instanceHandle; } PkeArray_Add(&loadFileInstanceMappings, mapping); break; } if (strstr(readLine, PKE_FILE_INSTANCE_ENTITY_HANDLE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_ENTITY_HANDLE); readLine[prefixLen + 10] = '\0'; STR2NUM_ERROR result1 = str2num(comp.entHandle.bucketIndex, readLine + prefixLen); STR2NUM_ERROR result2 = str2num(comp.entHandle.itemIndex, readLine + prefixLen + 11); assert(result1 == STR2NUM_ERROR::SUCCESS); assert(result2 == STR2NUM_ERROR::SUCCESS); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_HANDLE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_HANDLE); readLine[prefixLen + 10] = '\0'; STR2NUM_ERROR result1 = str2num(mapping.origHandle.bucketIndex, readLine + prefixLen); STR2NUM_ERROR result2 = str2num(mapping.origHandle.itemIndex, readLine + prefixLen + 11); assert(result1 == STR2NUM_ERROR::SUCCESS); assert(result2 == STR2NUM_ERROR::SUCCESS); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_ENTITY_TYPE_CODE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_ENTITY_TYPE_CODE); strncpy(entTypeCode, readLine + prefixLen, 21); 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 != ']'); mapping.newInstance.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 != ']'); mapping.newInstance.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(mapping.newInstance.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(mapping.newInstance.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::ostringstream stream{}; bool failed = false; try { stream << PKE_FILE_BEGIN << std::endl; stream << PKE_FILE_VERSION << std::endl; stream << "" << 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; stream << PKE_FILE_OBJ_INSTANCE << std::endl; SerializeInstance(stream, instance); stream << PKE_FILE_OBJ_END << 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; stream << PKE_FILE_OBJ_CAMERA << std::endl; SerializeCamera(stream, cam); stream << PKE_FILE_OBJ_END << std::endl; } } stream << PKE_FILE_END << std::endl; } catch (...) { failed = true; } if (failed == false) { std::ofstream f(sceneFilePath); if (f.is_open()) { f << stream.str(); f.flush(); f.close(); } else { failed = true; } } if (failed) { NULL_CHAR_ARR(errFileName, 256); strncpy(errFileName, sceneFilePath, 256); strncpy(errFileName + strlen(sceneFilePath), ".err", 256 - strlen(sceneFilePath)); std::ofstream errF(sceneFilePath); if (errF.is_open()) { errF << stream.str(); errF.flush(); errF.close(); fprintf(stderr, "Failed to save scene file '%s', partial output saved to '%s'\n", sceneFilePath, errFileName); } else { fprintf(stderr, "Failed to save scene file '%s' and also failed to write failed output\n", sceneFilePath); } } } void Game_LoadSceneFile(PkeLevel *level, 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); while (f.getline(readLine, readLineLength)) { if (strcmp(PKE_FILE_OBJ_INSTANCE, readLine) == 0) { ParseInstance(level, f); continue; } if (strcmp(PKE_FILE_OBJ_CAMERA, readLine) == 0) { ParseCamera(level, f); continue; } } f.close(); PkeArray_SoftReset(&loadFileInstanceMappings); } 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) { pk_bucket_reset(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(PkeLevel_Get(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_Early() gets called first because it updates the public * `EntitiesToBeRemoved` for all other ticks to use. */ ECS_Tick_Early(delta); EntityType_Tick(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); } } PkeCamera_Tick(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 { PkeThreads_Init(); AM_Init(); ECS_Init(); Physics_Init(); PkeCamera_Init(); PkeLevel_Init(); Game_Init(); CreateWindow(windowProps); EntityType_Init(); PkeProject_Load(pkeSettings.args.projectPath); PkeInput_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 pk_memory_debug_print(); #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(); PkeCamera_Teardown(); Physics_Teardown(); ECS_Teardown(); DestroyWindow(); AM_DebugPrint(); AM_Teardown(); PkeThreads_Teardown(); pk_memory_debug_print(); fprintf(stdout, "Game_Main Exiting\n"); } void Game_Init() { pkeSettings.mem.bkt = pk_bucket_create("game", 1UL << 26, true); for (long i = 0; i < consoleBufferCount; ++i) { memset(consoleBuffer[i], '\0', consoleLineLength); } } void Game_Teardown() { }