#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; 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. */ pk_arr_t entitiesMarkedForRemoval; /* * Public list of entities that will be removed next tick * * Entity or child of entity that had ECS_MarkForRemoval called */ pk_arr_t EntitiesToBeRemoved; /* * 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; } 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() { new (&ecs.bc.generics) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr }; new (&ecs.bc.entityPtrs) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr }; new (&ecs.bc.grBinds) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr }; new (&ecs.bc.instances) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr }; new (&ecs.bc.ev_mgrs) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr }; pk_arr_reserve(&entitiesMarkedForRemoval, 16); pk_arr_reserve(&EntitiesToBeRemoved, 16); pk_arr_reserve(&entitiesYetToBeRemoved, 16); pk_arr_reserve(&EntitiesWithExcessInstances, 16); 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; 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"); 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; pk_arr_clear(&EntitiesToBeRemoved); bool shouldRun = entitiesMarkedForRemoval.next > 0 || EntitiesToBeRemoved.next > 0 || entitiesYetToBeRemoved.next > 0; pk_arr_reserve(&entitiesYetToBeRemoved, entitiesMarkedForRemoval.reserved); pk_arr_reserve(&EntitiesToBeRemoved, entitiesMarkedForRemoval.reserved); memcpy(EntitiesToBeRemoved.data, entitiesMarkedForRemoval.data, sizeof(void *) * entitiesMarkedForRemoval.next); pk_arr_clear(&entitiesYetToBeRemoved); if (!shouldRun) return; EntitiesToBeRemoved.next = entitiesMarkedForRemoval.next; pk_arr_clear(&entitiesMarkedForRemoval); // 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? type_bkt_arr_entities::FN_Iter iter_tmpln; iter_tmpln.func = [](Entity_Base **ent_ptr) { Entity_Base *ent = *ent_ptr; Entity_Base *parentEnt = nullptr; if (ent->parentHandle != EntityHandle_MAX) { parentEnt = ecs.bc.entityPtrs[ent->parentHandle]; } if (ent->isMarkedForRemoval) { pk_arr_append_t(&entitiesYetToBeRemoved, ent); ent->handle = EntityHandle_MAX; ent->parentHandle = EntityHandle_MAX; ent->isMarkedForRemoval = false; } else if (pk_arr_find_first_index(&EntitiesToBeRemoved, ent, ecs_pk_arr_find_first_matching_pointer) != uint32_t(-1)) { ent->isMarkedForRemoval = true; } else if (parentEnt != nullptr && pk_arr_find_first_index(&EntitiesToBeRemoved, parentEnt, ecs_pk_arr_find_first_matching_pointer) != uint32_t(-1)) { ent->isMarkedForRemoval = true; pk_arr_append_t(&EntitiesToBeRemoved, ent); } }; pk_bkt_arr_iterate(&ecs.bc.entityPtrs, type_bkt_arr_entities::FN_Iter::invoke, &iter_tmpln); } 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) { int32_t physicsTickCount = Physics_Tick(delta); uint32_t entityRemovalCount = entitiesYetToBeRemoved.next; if (physicsTickCount == 0 && entityRemovalCount == 0) return; using InstIterFn = pk_tmpln_1; using GrBindsIterFn = pk_tmpln_1; using CompEvMgrIterFn = pk_tmpln_1; InstIterFn inst_iter_cb{}; GrBindsIterFn grbinds_iter_cb{}; CompEvMgrIterFn comp_ev_mgr_iter_cb{}; pk_arr_t updateGrBinds; updateGrBinds.bkt = pkeSettings.mem_bkt.game_transient; inst_iter_cb.func = [entityRemovalCount, &updateGrBinds](CompInstance *arr_obj_data) { CompInstance &inst = *arr_obj_data; auto activationState = inst.bt.rigidBody->getActivationState(); if (activationState == ISLAND_SLEEPING || activationState == DISABLE_SIMULATION || activationState == WANTS_DEACTIVATION) { // no-op } else { inst.isNeedingUpdated = true; } Entity_Base *ent = ecs.bc.entityPtrs[inst.entHandle]; if (entityRemovalCount > 0 && pk_arr_find_first_index(&entitiesYetToBeRemoved, ent, ecs_pk_arr_find_first_matching_pointer) != uint32_t(-1)) { if (inst.grBindsHandle != GrBindsHandle_MAX) { uint32_t afterIndex = pk_arr_find_first_index(&updateGrBinds, &inst.grBindsHandle, ecs_pk_arr_find_by_gr_binds_handle); updateGrBindsAfter *after = nullptr; if (afterIndex != uint32_t(-1)) { after = &updateGrBinds[afterIndex]; } else { struct updateGrBindsAfter tmp{}; tmp.grBindsHandle = inst.grBindsHandle; tmp.count = 0; pk_arr_append_t(&updateGrBinds, tmp); after = &updateGrBinds[updateGrBinds.next-1]; } after->count += 1; } inst.entHandle = EntityHandle_MAX; inst.grBindsHandle = GrBindsHandle_MAX; inst.index = ECS_UNSET_VAL_32; inst.instanceHandle = InstanceHandle_MAX; inst.isNeedingUpdated = false; BtDynamicsWorld->removeRigidBody(inst.bt.rigidBody); pk_delete(inst.bt.motionState, MemBkt_Bullet); pk_delete(inst.bt.rigidBody, MemBkt_Bullet); inst.bt.rigidBody = CAFE_BABE(btRigidBody); inst.bt.motionState = CAFE_BABE(btDefaultMotionState); return; } 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; } } }; pk_bkt_arr_iterate(&ecs.bc.instances, &InstIterFn::invoke, &inst_iter_cb); grbinds_iter_cb.func = [&updateGrBinds](CompGrBinds *arr_obj_data) { CompGrBinds &grBinds = *arr_obj_data; uint32_t afterIndex = pk_arr_find_first_index(&updateGrBinds, &grBinds.grBindsHandle, ecs_pk_arr_find_by_gr_binds_handle); Entity_Base *ent = ecs.bc.entityPtrs[grBinds.entHandle]; if (pk_arr_find_first_index(&entitiesYetToBeRemoved, ent, ecs_pk_arr_find_first_matching_pointer) != uint32_t(-1)) { grBinds.entHandle = EntityHandle_MAX; grBinds.grBindsHandle = GrBindsHandle_MAX; grBinds.vkPipelineLayout = VK_NULL_HANDLE; grBinds.graphicsPipeline = VK_NULL_HANDLE; grBinds.collisionCallback = PkeCallback{}; /* * 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. */ } if (afterIndex != uint32_t(-1)) { auto &after = updateGrBinds[afterIndex]; grBinds.instanceCounter -= after.count; } }; if (entityRemovalCount > 0 || updateGrBinds.next > 0) { pk_bkt_arr_iterate(&ecs.bc.grBinds, &GrBindsIterFn::invoke, &grbinds_iter_cb); } comp_ev_mgr_iter_cb.func = [](pke_component_event *arr_obj_data) { Entity_Base *ent = ecs.bc.entityPtrs[arr_obj_data->entity_handle]; if (pk_arr_find_first_index(&entitiesYetToBeRemoved, ent, ecs_pk_arr_find_first_matching_pointer) != uint32_t(-1)) { pk_ev_destroy_mgr(arr_obj_data->ev_mgr_id); pk_bkt_arr_free_handle(&ecs.bc.ev_mgrs, arr_obj_data->pke_event_handle); } }; if (entityRemovalCount > 0) { pk_bkt_arr_iterate(&ecs.bc.ev_mgrs, &CompEvMgrIterFn::invoke, &comp_ev_mgr_iter_cb); } } 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) { // using a pointer here avoids calling the destructor when the object goes out of scope (void)delta; PKVK_TmpBufferDetails tmpBufferDetails{}; pk_arr_t bufferUpdates; bufferUpdates.bkt = pkeSettings.mem_bkt.game_transient; using InstIterFn = pk_tmpln_1; InstIterFn inst_iter_cb{}; inst_iter_cb.func = [&bufferUpdates](CompInstance *arr_obj_data) { CompInstance &inst = *arr_obj_data; if (inst.isNeedingUpdated == false) return; if (inst.entHandle == EntityHandle_MAX) return; if (inst.grBindsHandle == GrBindsHandle_MAX) return; 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; }; pk_bkt_arr_iterate(&ecs.bc.instances, &InstIterFn::invoke, &inst_iter_cb); 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) { 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. using GrBindsIterFn = pk_tmpln_1; GrBindsIterFn gr_binds_iter_cb{}; gr_binds_iter_cb.func = [&entity, &arr](CompGrBinds *arr_obj_data) { if (arr_obj_data->entHandle == entity->handle) { pk_arr_append(&arr, &arr_obj_data); } }; pk_bkt_arr_iterate(&ecs.bc.grBinds, &GrBindsIterFn::invoke, &gr_binds_iter_cb); } 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); auto *inst = &ecs.bc.instances[instanceHandle]; return inst; } void ECS_GetInstances(Entity_Base *entity, pk_arr_t &arr) { 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. using InstIterFn = pk_tmpln_1; InstIterFn inst_iter_cb{}; inst_iter_cb.func = [&entity, &arr](CompInstance *arr_obj_data) { if (arr_obj_data->entHandle == entity->handle) { pk_arr_append(&arr, &arr_obj_data); } }; pk_bkt_arr_iterate(&ecs.bc.instances, &InstIterFn::invoke, &inst_iter_cb); } 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) }; auto *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) { if (handle == PkeEventMgrHandle_MAX) return nullptr; assert(pk_bkt_arr_handle_validate(&ecs.bc.ev_mgrs, handle) == PK_BKT_ARR_HANDLE_VALIDATION_VALID); auto *ev_mgr = &ecs.bc.ev_mgrs[handle]; return ev_mgr; } void ECS_GetEvs(Entity_Base *entity, pk_arr_t &arr) { 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. using CompEvMgrIterFn = pk_tmpln_1; CompEvMgrIterFn inst_iter_cb{}; inst_iter_cb.func = [&entity, &arr](pke_component_event *arr_obj_data) { if (arr_obj_data->entity_handle == entity->handle) { pk_arr_append_t(&arr, arr_obj_data); } }; pk_bkt_arr_iterate(&ecs.bc.ev_mgrs, &CompEvMgrIterFn::invoke, &inst_iter_cb); } pk_bkt_arr *ECS_GetEvs() { return &ecs.bc.ev_mgrs; } void ECS_Teardown() { pk_arr_reset(&EntitiesWithExcessInstances); pk_arr_reset(&entitiesYetToBeRemoved); pk_arr_reset(&EntitiesToBeRemoved); pk_arr_reset(&entitiesMarkedForRemoval); ecs.bc.ev_mgrs.~pk_bkt_arr_t(); ecs.bc.instances.~pk_bkt_arr_t(); ecs.bc.grBinds.~pk_bkt_arr_t(); ecs.bc.entityPtrs.~pk_bkt_arr_t(); ecs.bc.generics.~pk_bkt_arr_t(); }