#include "game.hpp" #include "camera.hpp" #include "components.hpp" #include "ecs.hpp" #include "entities.hpp" #include "event.hpp" #include "font.hpp" #include "game-settings.hpp" #include "game-type-defs.hpp" #include "helpers.hpp" #include "imgui.h" #include "level-types.hpp" #include "level.hpp" #include "physics.hpp" #include "player-input.hpp" #include "plugins.hpp" #include "project.hpp" #include "scene.hpp" #include "static-ui.hpp" #include "thread-pool.hpp" #include "window.hpp" #include "pk.h" #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{}; }; pk_arr_t 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_UUID = "UUID: "; 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_UUID = "Cam::UUID: "; 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::ostream &stream, const PkeCamera &cam) { NULL_CHAR_ARR(handleStr, 23); PkeCamera c{}; if (cam.uuid != pk_uuid_zed && cam.uuid != pk_uuid_max) { stream << PKE_FILE_CAMERA_UUID << cam.uuid << std::endl; } 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::ostream &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 (comp.uuid != pk_uuid_zed && comp.uuid != pk_uuid_max) { stream << PKE_FILE_INSTANCE_UUID << comp.uuid << 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(void *handle, void *mapping) { InstMapping *inst_mapping = reinterpret_cast(mapping); return inst_mapping->origHandle == *reinterpret_cast(handle); } void DeserializeCamera(pke_scene *scene, std::istream &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 = pk_arr_find_first_index(&loadFileInstanceMappings, &instanceHandle, FindFirstInstanceHandle); if (targetInstanceHandle != InstanceHandle_MAX) { targetInstanceIndex = pk_arr_find_first_index(&loadFileInstanceMappings, &targetInstanceHandle, FindFirstInstanceHandle); } InstPos instPos; if (instanceIndex == -1) { instPos.mass = 1.f; instPos.posRot.setIdentity(); instPos.scale = btVector3(1.f, 1.f, 1.f); fprintf(stdout, "[DeserializeCamera] Failed to find instance mapping. Is this an outdated parse?\n"); } else { instPos = loadFileInstanceMappings[instanceIndex].newInstance; } auto &rCam = PkeCamera_Register(cam.uuid, instPos); rCam.type = cam.type; rCam.view = cam.view; rCam.isPrimary = cam.isPrimary; rCam.phys.targetInstHandle = targetInstanceHandle; pke_scene_register_camera(scene->scene_handle, rCam.camHandle); if (targetInstanceIndex > -1) { PkeCamera_TargetInstance(rCam.camHandle, ECS_GetInstance(loadFileInstanceMappings[targetInstanceIndex].newInstHandle)); } if (rCam.isPrimary == true) { ActiveCamera = &rCam; } return; } if (strncmp(readLine, PKE_FILE_CAMERA_UUID, strlen(PKE_FILE_CAMERA_TYPE)) == 0) { uint64_t prefixLen = strlen(PKE_FILE_CAMERA_UUID); (readLine + prefixLen) >> cam.uuid; continue; } 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 DeserializeInstance(Entity_Base *parentEntity, std::istream &stream) { CompInstance comp{}; InstMapping mapping { .origHandle = InstanceHandle_MAX, .newEntHandle = EntityHandle_MAX, .newInstHandle = InstanceHandle_MAX, .newInstance = { .posRot = {}, .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::DeserializeInstance] 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::DeserializeInstance] Failed to create instance from save file. No EntTypeCode present.\n"); break; } if (skipEntCreate == false) { if (etPtr != nullptr && etPtr->createInstanceCallback.func != nullptr) { /* TODO 2025-03-27 JCB * We have not yet defined what the appropriate callback signature * for creating an entity instance is. * What should be passed as arguments? What would need to be passed * that couldn't be accessed globally? * Consider changing this callback to trigger after creating a * generic instance, rather than *creating* it. */ // typedef Entity_Base *CreateInst(); // entity = reinterpret_cast(etPtr->createInstanceCallback.func)(); fprintf(stderr, "[%s] Attempted to call EntityType::createInstanceCallback and we have not yet defined a valid function signature", __FILE__); } else { entity = EntityType_CreateGenericInstance(etPtr, parentEntity, &comp, &mapping.newInstance); fprintf(stdout ,"[Game::DeserializeInstance] 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 pk_arr_t instances{}; ECS_GetInstances(entity, instances); assert(instances.next > 0); mapping.newInstHandle = instances[0]->instanceHandle; } pk_arr_append(&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_UUID)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_UUID); (readLine + prefixLen) >> comp.uuid ; 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; pk_handle_bucket_index_T instanceBucketCount = ECS_GetInstances_BucketCount(); for (pk_handle_bucket_index_T b = 0; b < instanceBucketCount; ++b) { pk_handle_item_index_T count; auto *instances = ECS_GetInstances(b, count); for (pk_handle_item_index_T 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; } } pk_handle_bucket_index_T cameraBucketCount = PkeCamera_GetBucketCount(); for (pk_handle_bucket_index_T b = 0; b < cameraBucketCount; ++b) { pk_handle_item_index_T count; auto *cameras = PkeCamera_GetCameras(b, count); for (pk_handle_item_index_T 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 (std::exception &e) { fprintf(stderr, "[%s][Game_SaveSceneFile] Failed to serialize scene file: %s\n", __FILE__, e.what()); failed = false; } catch (...) { fprintf(stderr, "[%s][Game_SaveSceneFile] Failed to serialize scene file, uncaught exception.\n", __FILE__); failed = false; } if (failed == false) { std::ofstream f(sceneFilePath); if (!f.is_open()) { failed = true; } else { f << stream.str(); } f.flush(); f.close(); } 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(const char *sceneFilePath) { std::ifstream f(sceneFilePath); if (!f.is_open()) { fprintf(stderr, "Failed to load requested scene file: '%s'\n", sceneFilePath); return; } // TODO scene name is in the file? pke_scene *scn = pke_scene_get_by_name(sceneFilePath); if (scn == nullptr) { scn = pke_scene_create(sceneFilePath); } memset(readLine, '\0', readLineLength); while (f.getline(readLine, readLineLength)) { if (strcmp(PKE_FILE_OBJ_INSTANCE, readLine) == 0) { DeserializeInstance(scn, f); continue; } if (strcmp(PKE_FILE_OBJ_CAMERA, readLine) == 0) { DeserializeCamera(scn, f); continue; } } f.close(); pk_arr_clear(&loadFileInstanceMappings); } const int64_t consoleBufferCount = 30; const int64_t consoleLineLength = 128; char consoleBuffer[consoleBufferCount][consoleLineLength]; int64_t 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 (int64_t i = consoleBufferIndex + 1; i < consoleBufferCount; ++i) { ImGui::Text("%s", consoleBuffer[i]); } for (int64_t 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); if (pkeSettings.rt.nextLevel != LevelHandle_MAX) { // TODO async this PkeLevel *lvl = PkeLevel_Get(pkeSettings.rt.activeLevel); for (uint32_t i = 0; i < lvl->scene_instances.next; ++i) { // TODO can't instantiate a scene that hasn't been loaded yet /* struct pke_scene *scene = pke_scene_get_by_handle(lvl->scene_instances[i].scene_handle); if (scene != nullptr) { Game_LoadSceneFile(scene->name); } */ } 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); pke_ui_tick(delta); FontType_Tick(delta); for (long i = 0; i < LoadedPkePlugins.next; ++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 { pk_ev_init(); PkeThreads_Init(); AM_Init(); ECS_Init(); Physics_Init(); PkeCamera_Init(); PkeLevel_Init(); pke_scene_master_init(); Game_Init(); CreateWindow(windowProps); EntityType_Init(); PkeInput_Init(); pke_ui_init(); pke_ui_init_bindings(); FontType_Init(); PkeProject_Load(pkeSettings.args.projectPath); if (pkeSettings.args.pluginPath != nullptr) { PkePlugin_Load(pkeSettings.args.pluginPath); } for (long i = 0; i < LoadedPkePlugins.next; ++i) { if (LoadedPkePlugins[i].OnInit != nullptr) { LoadedPkePlugins[i].OnInit(); } } // // at this point, everything is loaded or initialized. // // if we were passed only a scene name, create a faux level. if (!pkeSettings.args.levelName && pkeSettings.args.sceneName) { pkeSettings.rt.nextLevel = PkeLevel_Create("faux-level")->levelHandle; PkeLevel *lvl = PkeLevel_Get(pkeSettings.rt.nextLevel); scene_instance si{}; si.scene_handle = pke_scene_get_by_name(pkeSettings.args.sceneName)->scene_handle; pk_arr_append_t(&lvl->scene_instances, si); } // TODO remove me: temp stuff for testing pk_cstr test_text = cstring_to_pk_cstr("012\n3456789\tThe quick\r\nbrown fox jumped over the lazy dog."); // pk_cstr test_text = cstring_to_pk_cstr("%+-*0123456789$"); // pk_cstr test_text = cstring_to_pk_cstr("$#"); // pk_cstr test_text = cstring_to_pk_cstr("$"); pk_cstr test_text2 = cstring_to_pk_cstr("+0123456\n789\tThe quick brown fox jumped over the lazy dog."); FontTypeIndex font_type_count; FontRenderSettings fr_set; fr_set.char_scale = 9 * 7.0; fr_set.line_height_scale = 1.0; fr_set.char_spacing_scale = 1.0; fr_set.surface_area_size.x = 1920 - (1920 / 4.0); fr_set.surface_area_size.y = 1080 - (1080 / 3.0); fr_set.surface_area_pos.x = 1920 / 4.0; fr_set.surface_area_pos.y = 1080 / 3.0; fr_set.surface_area_type_flags = FONT_RENDER_SURFACE_AREA_TYPE_FLAGS_NONE; if ((FontType_GetFonts(font_type_count)); font_type_count != FontTypeIndex{0}) { FontType_AddStringRender(FontTypeIndex{0}, pk_cstr_to_pk_str(&test_text), &fr_set); } if ((FontType_GetFonts(font_type_count)); font_type_count > FontTypeIndex{1}) { fr_set.surface_area_pos.y *= 2; fr_set.surface_area_size.y = 1080 - fr_set.surface_area_pos.y; FontType_AddStringRender(FontTypeIndex{1}, pk_cstr_to_pk_str(&test_text2), &fr_set); } // TODO remove me: temp stuff for testing pke_ui_box *ui_box = pke_ui_box_new_root(); ui_box->flags = PKE_UI_BOX_FLAG_POSITION_TYPE_DYNAMIC; ui_box->pos_top_left_x = 0.1; ui_box->pos_top_left_y = 0.1; ui_box->max_width = 0.8; ui_box->max_height = 0.8; // TODO remove me: temp stuff for testing pke_ui_box *c_ui_box = pke_ui_box_new_child(ui_box); c_ui_box->flags = PKE_UI_BOX_FLAG_POSITION_TYPE_STATIC; c_ui_box->pos_top_left_x = 20; c_ui_box->pos_top_left_y = 20; c_ui_box->max_width = 3000; c_ui_box->max_height = 3000; 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); pkeSettings.rt.was_framebuffer_resized = false; 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 for (long i = 0; i < LoadedPkePlugins.next; ++i) { if (LoadedPkePlugins[i].OnTeardown) { LoadedPkePlugins[i].OnTeardown(); } } Game_Teardown(); Event_Teardown(); EntityType_Teardown(); FontType_Teardown(); pke_ui_teardown(); PkeInput_Teardown(); pke_scene_master_teardown(); PkeLevel_Teardown(); PkeCamera_Teardown(); Physics_Teardown(); ECS_Teardown(); DestroyWindow(); AM_DebugPrint(); AM_Teardown(); PkeThreads_Teardown(); pk_ev_teardown(); pk_memory_debug_print(); fprintf(stdout, "Game_Main Exiting\n"); } void Game_Init() { pkeSettings.mem.bkt = pk_bucket_create("game-transient", 1UL << 26, true); for (uint64_t i = 0; i < consoleBufferCount; ++i) { memset(consoleBuffer[i], '\0', consoleLineLength); } } void Game_Teardown() { }