#include "ecs.hpp" #include "game-settings.hpp" #include "math-helpers.hpp" #include "physics.hpp" #include "pk.h" #include "window.hpp" #include #include #include typedef pk_bkt_arr_t type_bkt_arr_entities ; struct ECS { struct pk_membucket *bkt = nullptr; pk_ev_mgr_id_T ev_mgr_id; struct ECSBucketContainers { pk_bkt_arr_t generics{}; type_bkt_arr_entities entityPtrs{}; pk_bkt_arr_t grBinds{}; pk_bkt_arr_t instances{}; pk_bkt_arr_t ev_mgrs{}; } bc; } ecs; /* * Entities that have been marked for removal by calling ECS_MarkForRemoval * * Used to build the other "removal" lists. * * Each ECS module is REQUIRED to manage the lifetimes of its objects. * Each module MUST wait for an entity to be flagged "IsMarkedForRemoval". * Even though the module itself is sometimes the source of reporting the * entity to be cleaned up, it must wait to free resources until marked. * Do note that this means that it can take several ticks for all resources * of a level to get freed. * Ex: box with text; text isn't marked for removal until after box. * (This happens when deserializing, the FontRender doesn't know about its parent") */ pk_arr_t entitiesMarkedForRemoval{}; /* * The entities being removed this tick * * Each of these entities has gone a full tick in the "to be removed" state */ pk_arr_t entitiesYetToBeRemoved{}; /* * Entities that have more instances registered than their current * grBinds GPU buffer has space for. * * These need to be resized THIS TICK */ pk_arr_t EntitiesWithExcessInstances; bool ecs_pk_arr_find_first_matching_pointer(void *search_ptr, void *list_ptr) { return search_ptr == list_ptr; } bool ecs_pk_arr_find_first_matching_handle(void *search_ptr, void *list_ptr) { return *reinterpret_cast(search_ptr) == *reinterpret_cast(list_ptr); } void ECS_GetEntity_Inner(EntityHandle entHandle, Entity_Base*& ent) { assert(pk_bkt_arr_handle_validate(&ecs.bc.entityPtrs, entHandle) == PK_BKT_ARR_HANDLE_VALIDATION_VALID); ent = ecs.bc.entityPtrs[entHandle]; } void ECS_Init() { ecs.bkt = pk_mem_bucket_create("pk_bkt_arr ecs", 1024 * 1024, PK_MEMBUCKET_FLAG_NONE); new (&ecs.bc.generics) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr, ecs.bkt, ecs.bkt }; new (&ecs.bc.entityPtrs) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr, ecs.bkt, ecs.bkt }; new (&ecs.bc.grBinds) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr, ecs.bkt, ecs.bkt }; new (&ecs.bc.instances) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr, ecs.bkt, ecs.bkt }; new (&ecs.bc.ev_mgrs) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr, ecs.bkt, ecs.bkt }; pk_arr_reserve(&entitiesMarkedForRemoval, 16); pk_arr_reserve(&entitiesYetToBeRemoved, 16); pk_arr_reserve(&EntitiesWithExcessInstances, 16); ecs.ev_mgr_id = pk_ev_create_mgr(); } Entity_Base *ECS_CreateGenericEntity() { /* 2025-03-26 - JCB * The only place this is called immediately calls ECS_CreateEntity afterwards. * There is no need to generate a uuid */ GenericEntityHandle newHandle{pk_bkt_arr_new_handle(&ecs.bc.generics)}; Entity_Base *ent = &ecs.bc.generics[newHandle]; new (ent) Entity_Base{}; return ent; } EntityHandle ECS_CreateEntity(Entity_Base *entity, Entity_Base *parentEntity) { assert(entity != nullptr); assert(entity->handle == EntityHandle_MAX && "Entity already created!"); EntityHandle entityHandle{pk_bkt_arr_new_handle(&ecs.bc.entityPtrs)}; entity->handle = entityHandle; entity->parentHandle = EntityHandle_MAX; if (parentEntity != nullptr) { entity->parentHandle = parentEntity->handle; } if (entity->uuid == pk_uuid_max || entity->uuid == pk_uuid_zed) entity->uuid = pk_uuid_new_v7(); ecs.bc.entityPtrs[entityHandle] = entity; fprintf(stdout, "[ECS] %hhu:%hhu - " pk_uuid_printf_format " Entity Created (registered).\n", entity->handle.b, entity->handle.i, pk_uuid_printf_var(entity->uuid)); fflush(stdout); return entityHandle; } Entity_Base *ECS_GetEntity(EntityHandle handle) { if (pk_bkt_arr_handle_validate(&ecs.bc.entityPtrs, handle) != PK_BKT_ARR_HANDLE_VALIDATION_VALID) return nullptr; return ecs.bc.entityPtrs[handle]; } Entity_Base *ECS_GetEntityByUUID(pk_uuid uuid) { auto entity_ptr_find_cb = [](void *user_data, const void *arr_user_data, const void *arr_obj_data) { (void)user_data; const pk_uuid &uuid = *reinterpret_cast(arr_user_data);; const Entity_Base *ent = *reinterpret_cast(arr_obj_data); return (ent->uuid == uuid); }; EntityHandle handle { pk_bkt_arr_find_first_handle(&ecs.bc.entityPtrs, entity_ptr_find_cb, NULL, &uuid) }; if (handle == EntityHandle_MAX) return nullptr; return ecs.bc.entityPtrs[handle]; } void ECS_MarkForRemoval(Entity_Base *entity) { assert(entity->handle != EntityHandle_MAX && "Attempting to remove invalid entity"); assert(entity->isMarkedForRemoval == false && "Entity already marked for removal"); fprintf(stdout, "[ECS] %hhu:%hhu - " pk_uuid_printf_format " Entity Marked For Removal.\n", entity->handle.b, entity->handle.i, pk_uuid_printf_var(entity->uuid)); fflush(stdout); pk_arr_append_t(&entitiesMarkedForRemoval, entity); } pk_bkt_arr *ECS_GetEntities() { return &ecs.bc.entityPtrs; }; void ECS_Tick_Early(double delta) { // these reserves might happen 1 tick early, but that's fine (void)delta; bool b, any; pk_iter_t iter_ent_obj{}; pk_iter_t iter_ent{}; pk_iter_t iter_grbinds{}; pk_iter_t iter_comp_ev{}; pk_iter_t iter_inst{}; Entity_Base *parentEnt; bool shouldRun = entitiesMarkedForRemoval.next > 0; if (!shouldRun) return; // this has the potential to be slow as balls // TODO 2025-05-29 JCB PERF // Consider requiring components to register their entity containers. // Then loop through each container individually. // TODO 2025-05-29 JCB // I'm replacing BucketContainer with pk_bkt_arr. // BucketContainer could not free slots, while pk_bkt_arr can. // Before, we unintentionally had everything sequential, with parents first. // That may or may not have implications about this logic. // Might need to do several passes? pk_arr_reserve(&entitiesYetToBeRemoved, entitiesMarkedForRemoval.reserved); b = pk_arr_iter_begin(&entitiesMarkedForRemoval, &iter_ent); while (b == true) { (*iter_ent)->isMarkedForRemoval = true; pk_arr_append_t(&entitiesYetToBeRemoved, (*iter_ent)->handle); fprintf(stdout, "[ECS] %hhu:%hhu - " pk_uuid_printf_format " Entity Flagged For Removal.\n", (*iter_ent)->handle.b, (*iter_ent)->handle.i, pk_uuid_printf_var((*iter_ent)->uuid)); fflush(stdout); b = pk_arr_iter_increment(&entitiesMarkedForRemoval, &iter_ent); } pk_arr_clear(&entitiesMarkedForRemoval); do { any = false; b = pk_bkt_arr_iter_begin(&ecs.bc.entityPtrs, &iter_ent); while (b == true) { if ((*iter_ent)->isMarkedForRemoval == true) { b = pk_bkt_arr_iter_increment(&ecs.bc.entityPtrs, &iter_ent); continue; } if ((*iter_ent)->parentHandle == EntityHandle_MAX) { b = pk_bkt_arr_iter_increment(&ecs.bc.entityPtrs, &iter_ent); continue; } parentEnt = ecs.bc.entityPtrs[(*iter_ent)->parentHandle]; if (pk_arr_find_first_index(&entitiesYetToBeRemoved, &parentEnt->handle, ecs_pk_arr_find_first_matching_handle) != uint32_t(-1)) { any = true; (*iter_ent)->isMarkedForRemoval = true; pk_arr_append_t(&entitiesYetToBeRemoved, (*iter_ent)->handle); fprintf(stdout, "[ECS] %hhu:%hhu - " pk_uuid_printf_format " Entity Flagged For Removal (child).\n", (*iter_ent)->handle.b, (*iter_ent)->handle.i, pk_uuid_printf_var((*iter_ent)->uuid)); fflush(stdout); } b = pk_bkt_arr_iter_increment(&ecs.bc.entityPtrs, &iter_ent); } } while (any == true); b = pk_bkt_arr_iter_begin(&ecs.bc.grBinds, &iter_grbinds); while (b == true) { Entity_Base *ent = ecs.bc.entityPtrs[iter_grbinds->entHandle]; if (ent->isMarkedForRemoval) { /* * 2023-09-05 JB note - the Vulkan assets (device memory, buffers, * pipeline layout, and descriptor set) are unloaded elsewhere, just, * as they were created elsewhere. * 2025-09-08 JCB note - reminder that the bindings */ pk_bkt_arr_free_handle(&ecs.bc.grBinds, iter_grbinds->grBindsHandle); new (iter_grbinds.data) CompGrBinds{}; } b = pk_bkt_arr_iter_increment(&ecs.bc.grBinds, &iter_grbinds); } b = pk_bkt_arr_iter_begin(&ecs.bc.ev_mgrs, &iter_comp_ev); while (b == true) { Entity_Base *ent = ecs.bc.entityPtrs[iter_comp_ev->entity_handle]; if (ent->isMarkedForRemoval) { if (iter_comp_ev->ev_id != pk_ev_id_T_MAX) { pk_ev_unregister_ev(iter_comp_ev->ev_mgr_id, iter_comp_ev->ev_id); } pk_bkt_arr_free_handle(&ecs.bc.ev_mgrs, iter_comp_ev->pke_event_handle); new (iter_comp_ev.data) pke_component_event{}; } b = pk_bkt_arr_iter_increment(&ecs.bc.ev_mgrs, &iter_comp_ev); } b = pk_bkt_arr_iter_begin(&ecs.bc.instances, &iter_inst); while (b == true) { Entity_Base *ent = ecs.bc.entityPtrs[iter_inst->entHandle]; if (ent->isMarkedForRemoval) { BtDynamicsWorld->removeRigidBody(iter_inst->bt.rigidBody); pk_delete(iter_inst->bt.motionState, MemBkt_Bullet); pk_delete(iter_inst->bt.rigidBody, MemBkt_Bullet); iter_inst->bt.rigidBody = CAFE_BABE(btRigidBody); iter_inst->bt.motionState = CAFE_BABE(btDefaultMotionState); pk_bkt_arr_free_handle(&ecs.bc.instances, iter_inst->instanceHandle); new (iter_inst.data) CompInstance{}; } b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); } b = pk_bkt_arr_iter_begin(&ecs.bc.generics, &iter_ent_obj); while (b == true) { if (iter_ent_obj->isMarkedForRemoval == true) { pk_bkt_arr_free_handle(&ecs.bc.generics, pk_bkt_arr_handle { iter_ent_obj.id.bkt.b, iter_ent_obj.id.bkt.i }); } b = pk_bkt_arr_iter_increment(&ecs.bc.generics, &iter_ent_obj); } } struct updateGrBindsAfter { GrBindsHandle grBindsHandle = GrBindsHandle_MAX; uint32_t count = 0; }; bool ecs_pk_arr_find_by_gr_binds_handle(void *search_val, void *list_val) { assert(search_val != nullptr); assert(list_val != nullptr); GrBindsHandle &search_handle = *reinterpret_cast(search_val); struct updateGrBindsAfter &list_ref = *reinterpret_cast(list_val); return search_handle == list_ref.grBindsHandle; } void ECS_Tick(double delta) { bool b; pk_iter_t iter_inst{}; pk_iter_t iter_grbinds{}; int32_t physicsTickCount = Physics_Tick(delta); uint32_t entityRemovalCount = entitiesYetToBeRemoved.next; if (physicsTickCount == 0 && entityRemovalCount == 0) return; pk_arr_t updateGrBinds; updateGrBinds.bkt = pkeSettings.mem_bkt.game_transient; b = pk_bkt_arr_iter_begin(&ecs.bc.instances, &iter_inst); while (b == true) { Entity_Base *ent = ecs.bc.entityPtrs[iter_inst->entHandle]; if (ent->isMarkedForRemoval) { b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); continue; } CompInstance &inst = *iter_inst; auto activationState = inst.bt.rigidBody->getActivationState(); if (activationState == ISLAND_SLEEPING || activationState == DISABLE_SIMULATION || activationState == WANTS_DEACTIVATION) { // no-op } else { inst.isNeedingUpdated = true; } if (updateGrBinds.next > 0 && inst.instanceHandle != InstanceHandle_MAX) { uint32_t afterIndex = pk_arr_find_first_index(&updateGrBinds, &inst.grBindsHandle, ecs_pk_arr_find_by_gr_binds_handle); if (afterIndex != uint32_t(-1)) { auto &after = updateGrBinds[afterIndex]; inst.index -= after.count; inst.isNeedingUpdated = true; } } b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); } b = pk_bkt_arr_iter_begin(&ecs.bc.grBinds, &iter_grbinds); while ((entityRemovalCount > 0 || updateGrBinds.next > 0) && b == true) { CompGrBinds &grBinds = *iter_grbinds; Entity_Base *ent = ecs.bc.entityPtrs[grBinds.entHandle]; if (ent->isMarkedForRemoval) { b = pk_bkt_arr_iter_increment(&ecs.bc.grBinds, &iter_grbinds); continue; } uint32_t afterIndex = pk_arr_find_first_index(&updateGrBinds, &grBinds.grBindsHandle, ecs_pk_arr_find_by_gr_binds_handle); if (afterIndex != uint32_t(-1)) { auto &after = updateGrBinds[afterIndex]; grBinds.instanceCounter -= after.count; } b = pk_bkt_arr_iter_increment(&ecs.bc.grBinds, &iter_grbinds); } } struct InstanceBufferCopyChunk { uint64_t startingIndex; uint64_t endingIndex; pk_arr_t mats; VkBufferCopy dstBufferCopy; }; struct InstanceBufferCopy { CompGrBinds *grBinds = nullptr; VkDeviceSize runningSize = 0; pk_arr_t chunks; }; void ECS_Tick_Late(double delta) { (void)delta; bool b; pk_iter_t iter_ent_handle{}; pk_iter_t iter_inst{}; PKVK_TmpBufferDetails tmpBufferDetails{}; pk_arr_t bufferUpdates; bufferUpdates.bkt = pkeSettings.mem_bkt.game_transient; b = pk_bkt_arr_iter_begin(&ecs.bc.instances, &iter_inst); while (b == true) { CompInstance &inst = *iter_inst; if (inst.isNeedingUpdated == false) { b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); continue; } if (inst.entHandle == EntityHandle_MAX) { b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); continue; } if (inst.grBindsHandle == GrBindsHandle_MAX) { b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); continue; } auto &grBinds = ecs.bc.grBinds[inst.grBindsHandle]; InstanceBufferCopy *bfrUpdate = nullptr; for (long u = 0; u < bufferUpdates.next; ++u) { if (bufferUpdates[u].grBinds->grBindsHandle == inst.grBindsHandle) { bfrUpdate = &bufferUpdates[u]; } } if (bfrUpdate == nullptr) { InstanceBufferCopy tmp{}; tmp.grBinds = &grBinds; tmp.chunks.bkt = pkeSettings.mem_bkt.game_transient; pk_arr_append_t(&bufferUpdates, tmp); bfrUpdate = &bufferUpdates[bufferUpdates.next-1]; pk_arr_reserve(&bfrUpdate->chunks, 4); } InstanceBufferCopyChunk *chunk = nullptr; for (long ii = 0; ii < bfrUpdate->chunks.next; ++ii) { if (bfrUpdate->chunks[ii].endingIndex == inst.index - 1) { chunk = &bfrUpdate->chunks[ii]; chunk->endingIndex += 1; break; } } if (chunk == nullptr) { InstanceBufferCopyChunk tmp{}; tmp.startingIndex = inst.index; tmp.endingIndex = inst.index; tmp.mats.bkt = pkeSettings.mem_bkt.game_transient; tmp.dstBufferCopy = {}; pk_arr_append_t(&bfrUpdate->chunks, tmp); chunk = &bfrUpdate->chunks[bfrUpdate->chunks.next-1]; chunk->dstBufferCopy.dstOffset = sizeof(glm::mat4) * inst.index; pk_arr_reserve(&chunk->mats, 4); } btTransform btMatrix_posRot; inst.bt.motionState->getWorldTransform(btMatrix_posRot); float openglMatrix[16]; btMatrix_posRot.getOpenGLMatrix(openglMatrix); glm::mat4 glmMat_posRot = glm::make_mat4(openglMatrix); glm::vec3 scale; BulletToGlm(inst.bt.rigidBody->getCollisionShape()->getLocalScaling(), scale); pk_arr_append_t(&chunk->mats, glm::scale(glmMat_posRot, scale)); bfrUpdate->runningSize += sizeof(glm::mat4); inst.isNeedingUpdated = false; b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); } b = pk_arr_iter_begin(&entitiesYetToBeRemoved, &iter_ent_handle); while (b == true) { pk_bkt_arr_free_handle(&ecs.bc.entityPtrs, *iter_ent_handle); fprintf(stdout, "[ECS] %hhu:%hhu - Entity Removed.\n", iter_ent_handle->b, iter_ent_handle->i); fflush(stdout); b = pk_arr_iter_increment(&entitiesYetToBeRemoved, &iter_ent_handle); } pk_arr_clear(&entitiesYetToBeRemoved); while (bufferUpdates.next > 0) { InstanceBufferCopy &ibc = bufferUpdates[bufferUpdates.next - 1]; VkDeviceSize instanceBytes = sizeof(glm::mat4); VkDeviceSize byteCount = ibc.runningSize; PKVK_BeginBuffer(transferFamilyIndex, byteCount, tmpBufferDetails); VkDeviceSize runningOffset = 0; for (long i = 0; i < ibc.chunks.next; ++i) { auto &chunk = ibc.chunks[i]; memcpy(static_cast(tmpBufferDetails.deviceData) + runningOffset, chunk.mats.data, byteCount); chunk.dstBufferCopy.srcOffset = runningOffset; chunk.dstBufferCopy.size = instanceBytes * (chunk.endingIndex - chunk.startingIndex + 1); runningOffset += chunk.dstBufferCopy.size; } assert(runningOffset == ibc.runningSize); { VkCommandBufferBeginInfo cbbi; cbbi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cbbi.pNext = nullptr; cbbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; cbbi.pInheritanceInfo = nullptr; vkBeginCommandBuffer(tmpBufferDetails.cmdBuffer, &cbbi); for (long i = 0; i < ibc.chunks.next; ++i) { vkCmdCopyBuffer(tmpBufferDetails.cmdBuffer, tmpBufferDetails.buffer, ibc.grBinds->instanceBD.buffer, 1, &ibc.chunks[i].dstBufferCopy); } vkEndCommandBuffer(tmpBufferDetails.cmdBuffer); VkSubmitInfo vkSubmitInfo{}; vkSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; vkSubmitInfo.pNext = nullptr; vkSubmitInfo.waitSemaphoreCount = 0; vkSubmitInfo.pWaitSemaphores = nullptr; vkSubmitInfo.pWaitDstStageMask = nullptr; vkSubmitInfo.commandBufferCount = 1; vkSubmitInfo.pCommandBuffers = &tmpBufferDetails.cmdBuffer; vkSubmitInfo.signalSemaphoreCount = 0; vkSubmitInfo.pSignalSemaphores = nullptr; vkQueueSubmit(tmpBufferDetails.queue, 1, &vkSubmitInfo, nullptr); vkQueueWaitIdle(tmpBufferDetails.queue); vkResetCommandBuffer(tmpBufferDetails.cmdBuffer, 0); } PKVK_EndBuffer(tmpBufferDetails); pk_arr_remove_at(&bufferUpdates, bufferUpdates.next-1); } } void ECS_HandleCollision(CompInstance *lhsInst, CompInstance *rhsInst) { assert(lhsInst != nullptr); assert(rhsInst != nullptr); CompGrBinds *lhsGrBinds = ECS_GetGrBinds(lhsInst->grBindsHandle); CompGrBinds *rhsGrBinds = ECS_GetGrBinds(rhsInst->grBindsHandle); void (*lhsColFunc)(CompInstance*, CompInstance*) = nullptr; void (*rhsColFunc)(CompInstance*, CompInstance*) = nullptr; if (lhsInst && lhsInst->collisionCallback.func != nullptr) { lhsColFunc = reinterpret_cast(lhsInst->collisionCallback.func); } if (lhsColFunc == nullptr && lhsGrBinds && lhsGrBinds->collisionCallback.func) { lhsColFunc = reinterpret_cast(lhsGrBinds->collisionCallback.func); } if (rhsInst && rhsInst->collisionCallback.func != nullptr) { rhsColFunc = reinterpret_cast(rhsInst->collisionCallback.func); } if (rhsColFunc == nullptr && rhsGrBinds && rhsGrBinds->collisionCallback.func) { rhsColFunc = reinterpret_cast(rhsGrBinds->collisionCallback.func); } if (lhsColFunc) { lhsColFunc(lhsInst, rhsInst); } if (rhsColFunc) { rhsColFunc(lhsInst, rhsInst); } } CompGrBinds *ECS_CreateGrBinds(Entity_Base *entity) { assert(entity != nullptr && entity != CAFE_BABE(Entity_Base)); GrBindsHandle grBindsHandle{pk_bkt_arr_new_handle(&ecs.bc.grBinds)}; auto *comp = &ecs.bc.grBinds[grBindsHandle]; comp = new (comp) CompGrBinds{}; comp->entHandle = entity->handle; comp->grBindsHandle = grBindsHandle; return comp; } CompGrBinds *ECS_GetGrBinds(GrBindsHandle grBindsHandle) { if (grBindsHandle == GrBindsHandle_MAX) return nullptr; assert(pk_bkt_arr_handle_validate(&ecs.bc.grBinds, grBindsHandle) == PK_BKT_ARR_HANDLE_VALIDATION_VALID); return &ecs.bc.grBinds[grBindsHandle]; } void ECS_GetGrBinds(Entity_Base *entity, pk_arr_t &arr) { bool b; pk_iter_t iter_grbinds{}; if (entity == nullptr) return; // 2025-05-29 JCB PERF // There's gotta be a better way to do this than looping ALL GrBinds... // Let's leave it until it shows up in performance tests. b = pk_bkt_arr_iter_begin(&ecs.bc.grBinds, &iter_grbinds); while (b == true) { if (iter_grbinds->entHandle == entity->handle) { pk_arr_append_t(&arr, iter_grbinds); } b = pk_bkt_arr_iter_increment(&ecs.bc.grBinds, &iter_grbinds); } } pk_bkt_arr *ECS_GetGrBinds() { return &ecs.bc.grBinds; } CompInstance *ECS_CreateInstance(Entity_Base *entity, pk_uuid uuid, CompGrBinds *entityTypeGrBinds, InstPos *inst_pos) { assert(entity != nullptr && entity != CAFE_BABE(Entity_Base)); InstanceHandle instanceHandle{pk_bkt_arr_new_handle(&ecs.bc.instances)}; auto *comp = &ecs.bc.instances[instanceHandle]; new (comp) CompInstance{}; comp->entHandle = entity->handle; comp->instanceHandle = instanceHandle; comp->uuid = uuid; if (comp->uuid == pk_uuid_zed || comp->uuid == pk_uuid_max) { comp->uuid = pk_uuid_new_v7(); } if (entityTypeGrBinds != nullptr) { comp->grBindsHandle = entityTypeGrBinds->grBindsHandle; comp->index = entityTypeGrBinds->instanceCounter++; comp->isNeedingUpdated = true; if (entityTypeGrBinds->instanceCounter > entityTypeGrBinds->instanceBufferMaxCount) { pk_arr_append_t(&EntitiesWithExcessInstances, ECS_GetEntity(entityTypeGrBinds->entHandle)); } } else if (inst_pos != nullptr) { // TODO leaky comp->bt.collision_shape = pk_new(MemBkt_Bullet); new (comp->bt.collision_shape) btSphereShape(1.0); btVector3 localInertia(0, 0, 0); comp->bt.collision_shape->calculateLocalInertia(inst_pos->mass, localInertia); comp->bt.motionState = pk_new(MemBkt_Bullet); new (comp->bt.motionState) btDefaultMotionState(inst_pos->posRot); comp->bt.rigidBody = pk_new(MemBkt_Bullet); new (comp->bt.rigidBody) btRigidBody(inst_pos->mass, comp->bt.motionState, comp->bt.collision_shape, localInertia); comp->bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); comp->bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); comp->bt.rigidBody->getCollisionShape()->setLocalScaling(inst_pos->scale); BtDynamicsWorld->addRigidBody(comp->bt.rigidBody); comp->bt.rigidBody->getBroadphaseProxy()->m_collisionFilterGroup = static_cast(comp->physicsLayer); comp->bt.rigidBody->getBroadphaseProxy()->m_collisionFilterMask = static_cast(comp->physicsMask); comp->bt.rigidBody->setUserPointer(reinterpret_cast(comp)); } return comp; } CompInstance *ECS_GetInstance(InstanceHandle instanceHandle ) { if (instanceHandle == InstanceHandle_MAX) return nullptr; assert(pk_bkt_arr_handle_validate(&ecs.bc.instances, instanceHandle) == PK_BKT_ARR_HANDLE_VALIDATION_VALID); return &ecs.bc.instances[instanceHandle]; } void ECS_GetInstances(Entity_Base *entity, pk_arr_t &arr) { bool b; pk_iter_t iter_inst{}; if (entity == nullptr) return; // 2025-05-29 JCB PERF // There's gotta be a better way to do this than looping ALL GrBinds... // Let's leave it until it shows up in performance tests. b = pk_bkt_arr_iter_begin(&ecs.bc.instances, &iter_inst); while (b == true) { if (iter_inst->entHandle == entity->handle) { pk_arr_append_t(&arr, iter_inst); } b = pk_bkt_arr_iter_increment(&ecs.bc.instances, &iter_inst); } } void ECS_UpdateInstance(CompInstance *instance, const InstPos &instPos, bool overridePhysics) { if (BtDynamicsWorld && overridePhysics) { btVector3 localInertia(0, 0, 0); instance->bt.rigidBody->getCollisionShape()->calculateLocalInertia(instPos.mass, localInertia); instance->bt.rigidBody->setMassProps(instPos.mass, localInertia); instance->bt.rigidBody->getMotionState()->setWorldTransform(instPos.posRot); instance->bt.rigidBody->setWorldTransform(instPos.posRot); instance->bt.rigidBody->getCollisionShape()->setLocalScaling(instPos.scale); instance->bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); instance->bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); instance->bt.rigidBody->activate(); instance->isNeedingUpdated = true; } } pk_bkt_arr *ECS_GetInstances() { return &ecs.bc.instances; } pke_component_event *ECS_CreateEv(Entity_Base *entity, pk_uuid uuid) { assert(entity != nullptr && entity != CAFE_BABE(Entity_Base)); PkeEventHandle pke_ev_handle { pk_bkt_arr_new_handle(&ecs.bc.ev_mgrs) }; pke_component_event *comp = &ecs.bc.ev_mgrs[pke_ev_handle]; new (comp) pke_component_event{}; comp->entity_handle = entity->handle; comp->pke_event_handle = pke_ev_handle; comp->uuid = uuid; if (comp->uuid == pk_uuid_zed || comp->uuid == pk_uuid_max) { comp->uuid = pk_uuid_new_v7(); } comp->ev_id = pk_ev_register_ev(comp->ev_mgr_id, entity); return comp; } pke_component_event *ECS_GetEv(PkeEventHandle handle) { pke_component_event *comp_ev = nullptr; if (handle == PkeEventHandle_MAX) return comp_ev; assert(pk_bkt_arr_handle_validate(&ecs.bc.ev_mgrs, handle) == PK_BKT_ARR_HANDLE_VALIDATION_VALID); comp_ev = &ecs.bc.ev_mgrs[handle]; return comp_ev; } void ECS_GetEvs(Entity_Base *entity, pk_arr_t &arr) { bool b; pk_iter_t iter_comp_ev{}; if (entity == nullptr) return; // 2025-05-29 JCB PERF // There's gotta be a better way to do this than looping // Let's leave it until it shows up in performance tests. b = pk_bkt_arr_iter_begin(&ecs.bc.ev_mgrs, &iter_comp_ev); while(b == true) { if (iter_comp_ev->entity_handle == entity->handle) { pk_arr_append_t(&arr, iter_comp_ev); } b = pk_bkt_arr_iter_increment(&ecs.bc.ev_mgrs, &iter_comp_ev); } } pk_bkt_arr *ECS_GetEvs() { return &ecs.bc.ev_mgrs; } void ECS_Teardown() { pk_ev_destroy_mgr(ecs.ev_mgr_id); pk_arr_reset(&EntitiesWithExcessInstances); pk_arr_reset(&entitiesYetToBeRemoved); pk_arr_reset(&entitiesMarkedForRemoval); pk_bkt_arr_teardown(&ecs.bc.ev_mgrs); pk_bkt_arr_teardown(&ecs.bc.instances); pk_bkt_arr_teardown(&ecs.bc.grBinds); pk_bkt_arr_teardown(&ecs.bc.entityPtrs); pk_bkt_arr_teardown(&ecs.bc.generics); pk_mem_bucket_destroy(ecs.bkt); }