#include "ecs.hpp" #include "bucketed-array.hpp" #include "game-settings.hpp" #include "math-helpers.hpp" #include "physics.hpp" #include "pk.h" #include "window.hpp" #include #include #include constexpr struct { const pk_handle_item_index_T generics = 256; const pk_handle_item_index_T entityPtrs = 256; const pk_handle_item_index_T grBinds = 64; const pk_handle_item_index_T instances = 256; } bcSizes; struct ECS { struct pk_membucket *bkt = nullptr; struct ECSBucketContainers { BucketContainer generics{}; BucketContainer entityPtrs{}; BucketContainer grBinds{}; BucketContainer instances{}; } 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_handle_validate(entHandle, ecs.bc.entityPtrs.pkeHandle, ecs.bc.entityPtrs.limits.itemIndex) == PK_HANDLE_VALIDATION_VALID); ent = ecs.bc.entityPtrs.buckets[entHandle.bucketIndex][entHandle.itemIndex]; } void ECS_Init() { Buckets_Init(ecs.bc.generics, bcSizes.generics); Buckets_Init(ecs.bc.entityPtrs, bcSizes.entityPtrs); Buckets_Init(ecs.bc.grBinds, bcSizes.grBinds); Buckets_Init(ecs.bc.instances, bcSizes.instances); pk_arr_reserve(&entitiesMarkedForRemoval, 16); pk_arr_reserve(&EntitiesToBeRemoved, 16); pk_arr_reserve(&entitiesYetToBeRemoved, 16); pk_arr_reserve(&EntitiesWithExcessInstances, 16); } 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 */ pk_handle newHandle{Buckets_NewHandle(ecs.bc.generics)}; return &ecs.bc.generics.buckets[newHandle.bucketIndex][newHandle.itemIndex]; } EntityHandle ECS_CreateEntity(Entity_Base *entity, Entity_Base *parentEntity) { assert(entity != nullptr); assert(entity->handle == EntityHandle_MAX && "Entity already created!"); EntityHandle entityHandle{Buckets_NewHandle(ecs.bc.entityPtrs)}; entity->handle = entityHandle; if (parentEntity) entity->parentHandle = parentEntity->handle; if (entity->uuid == pk_uuid_max || entity->uuid == pk_uuid_zed) entity->uuid = pk_uuid_new_v7(); ecs.bc.entityPtrs.buckets[entityHandle.bucketIndex][entityHandle.itemIndex] = entity; return entityHandle; } Entity_Base *ECS_GetEntity(EntityHandle handle) { if (pk_handle_validate(handle, ecs.bc.entityPtrs.pkeHandle, ecs.bc.entityPtrs.limits.itemIndex) != PK_HANDLE_VALIDATION_VALID) return nullptr; return ecs.bc.entityPtrs[handle]; } Entity_Base *ECS_GetEntityByUUID(pk_uuid uuid) { for (pk_handle_bucket_index_T b = 0; b <= ecs.bc.entityPtrs.pkeHandle.bucketIndex; ++b) { for (pk_handle_item_index_T i = 0; i < ecs.bc.entityPtrs.pkeHandle.itemIndex; ++i) { Entity_Base *bs = ecs.bc.entityPtrs.buckets[b][i]; if (bs->uuid == uuid) return bs; } } return nullptr; } 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); } void ECS_Tick_Early(double delta) { // these reserves might happen 1 tick early, but that's fine (void)delta; 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; pk_arr_clear(&entitiesMarkedForRemoval); // this has the potential to be slow as balls for (pk_handle_bucket_index_T b = 0; b <= ecs.bc.entityPtrs.pkeHandle.bucketIndex; ++b) { pk_handle_item_index_T entCount = b == ecs.bc.entityPtrs.pkeHandle.bucketIndex ? ecs.bc.entityPtrs.pkeHandle.itemIndex : ecs.bc.entityPtrs.limits.itemIndex; for (pk_handle_item_index_T e = 0; e < entCount; ++e) { Entity_Base *ent = ecs.bc.entityPtrs.buckets[b][e]; if (ent->handle == EntityHandle_MAX) continue; Entity_Base *parentEnt = nullptr; if (ent->parentHandle != EntityHandle_MAX) parentEnt = ecs.bc.entityPtrs.buckets[ent->parentHandle.bucketIndex][ent->parentHandle.itemIndex]; 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); } } } } 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; pk_arr_t updateGrBinds; updateGrBinds.bkt = pkeSettings.mem.bkt; for (long b = 0; b <= ecs.bc.instances.pkeHandle.bucketIndex; ++b) { auto &bkt = ecs.bc.instances.buckets[b]; long count = ecs.bc.instances.pkeHandle.bucketIndex == b ? ecs.bc.instances.pkeHandle.itemIndex : ecs.bc.instances.limits.itemIndex; for (uint32_t i = 0; i < count; ++i) { auto &inst = bkt[i]; if (inst.entHandle == EntityHandle_MAX) continue; auto activationState = inst.bt.rigidBody->getActivationState(); if (activationState == ISLAND_SLEEPING || activationState == DISABLE_SIMULATION || activationState == WANTS_DEACTIVATION) { // do nothing } else { inst.isNeedingUpdated = true; } Entity_Base *ent = ecs.bc.entityPtrs.buckets[inst.entHandle.bucketIndex][inst.entHandle.itemIndex]; 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); continue; } 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; } } } } if (entityRemovalCount > 0 || updateGrBinds.next > 0) { for (pk_handle_bucket_index_T b = 0; b <= ecs.bc.grBinds.pkeHandle.bucketIndex; ++b) { auto &bkt = ecs.bc.grBinds.buckets[b]; long count = ecs.bc.grBinds.pkeHandle.bucketIndex == b ? ecs.bc.grBinds.pkeHandle.itemIndex : ecs.bc.grBinds.limits.itemIndex; for (pk_handle_item_index_T i = 0; i < count; ++i) { auto &grBinds = bkt[i]; if (grBinds.entHandle == EntityHandle_MAX) { continue; } 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.buckets[grBinds.entHandle.bucketIndex][grBinds.entHandle.itemIndex]; 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; } } } } } 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; for (long b = 0; b <= ecs.bc.instances.pkeHandle.bucketIndex; ++b) { auto &bkt = ecs.bc.instances.buckets[b]; long count = ecs.bc.instances.pkeHandle.bucketIndex == b ? ecs.bc.instances.pkeHandle.itemIndex : ecs.bc.instances.limits.itemIndex; for (uint32_t i = 0; i < count; ++i) { auto &inst = bkt[i]; if (inst.isNeedingUpdated == false) continue; if (inst.entHandle == EntityHandle_MAX) continue; if (inst.grBindsHandle == GrBindsHandle_MAX) continue; auto &grBinds = ecs.bc.grBinds.buckets[inst.grBindsHandle.bucketIndex][inst.grBindsHandle.itemIndex]; 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; 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; 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; } } 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{Buckets_NewHandle(ecs.bc.grBinds)}; auto *comp = &ecs.bc.grBinds.buckets[grBindsHandle.bucketIndex][grBindsHandle.itemIndex]; 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_handle_validate(grBindsHandle, ecs.bc.grBinds.pkeHandle, ecs.bc.grBinds.limits.itemIndex) == PK_HANDLE_VALIDATION_VALID); return &ecs.bc.grBinds.buckets[grBindsHandle.bucketIndex][grBindsHandle.itemIndex]; } void ECS_GetGrBinds(Entity_Base *entity, pk_arr_t &arr) { for (pk_handle_bucket_index_T b = 0; b <= ecs.bc.grBinds.pkeHandle.bucketIndex; ++b) { auto &bkt = ecs.bc.grBinds.buckets[b]; long itemCount = ecs.bc.grBinds.pkeHandle.bucketIndex == b ? ecs.bc.grBinds.pkeHandle.itemIndex : ecs.bc.grBinds.limits.itemIndex; for (pk_handle_item_index_T i = 0; i < itemCount; ++i) { CompGrBinds *grBinds = &bkt[i]; if (grBinds->entHandle == entity->handle) { pk_arr_append(&arr, &grBinds); } } } } uint64_t ECS_GetGrBinds_BucketCount() { return ecs.bc.grBinds.pkeHandle.bucketIndex + 1; } CompGrBinds *ECS_GetGrBinds(pk_handle_bucket_index_T bucketIndex, pk_handle_item_index_T &itemCount) { if (bucketIndex == ecs.bc.grBinds.pkeHandle.bucketIndex) { itemCount = ecs.bc.grBinds.pkeHandle.itemIndex; } else { itemCount = ecs.bc.grBinds.limits.itemIndex; } return ecs.bc.grBinds.buckets[bucketIndex]; } CompInstance *ECS_CreateInstance(Entity_Base *entity, pk_uuid uuid, CompGrBinds *entityTypeGrBinds, InstPos *inst_pos) { assert(entity != nullptr && entity != CAFE_BABE(Entity_Base)); InstanceHandle instanceHandle{Buckets_NewHandle(ecs.bc.instances)}; auto *instBkt = ecs.bc.instances.buckets[instanceHandle.bucketIndex]; auto *comp = &instBkt[instanceHandle.itemIndex]; 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_handle_validate(instanceHandle, ecs.bc.instances.pkeHandle, ecs.bc.instances.limits.itemIndex) == PK_HANDLE_VALIDATION_VALID); auto *inst = &ecs.bc.instances.buckets[instanceHandle.bucketIndex][instanceHandle.itemIndex]; return inst; } void ECS_GetInstances(Entity_Base *entity, pk_arr_t &arr) { for (pk_handle_bucket_index_T b = 0; b <= ecs.bc.instances.pkeHandle.bucketIndex; ++b) { auto &bkt = ecs.bc.instances.buckets[b]; long itemCount = ecs.bc.instances.pkeHandle.bucketIndex == b ? ecs.bc.instances.pkeHandle.itemIndex : ecs.bc.instances.limits.itemIndex; for (pk_handle_item_index_T i = 0; i < itemCount; ++i) { CompInstance *inst = &bkt[i]; if (inst->entHandle == entity->handle) { pk_arr_append(&arr, &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; } } uint64_t ECS_GetInstances_BucketCount() { return ecs.bc.instances.pkeHandle.bucketIndex + 1; } CompInstance *ECS_GetInstances(pk_handle_bucket_index_T bucketIndex, pk_handle_item_index_T &itemCount) { if (bucketIndex == ecs.bc.instances.pkeHandle.bucketIndex) { itemCount = ecs.bc.instances.pkeHandle.itemIndex; } else { itemCount = ecs.bc.instances.limits.itemIndex; } return ecs.bc.instances.buckets[bucketIndex]; } void ECS_Teardown() { pk_arr_reset(&EntitiesWithExcessInstances); pk_arr_reset(&entitiesYetToBeRemoved); pk_arr_reset(&EntitiesToBeRemoved); pk_arr_reset(&entitiesMarkedForRemoval); Buckets_Destroy(ecs.bc.instances); Buckets_Destroy(ecs.bc.grBinds); Buckets_Destroy(ecs.bc.entityPtrs); Buckets_Destroy(ecs.bc.generics); }