#include "ecs.hpp" #include "array.hpp" #include "bucketed-array.hpp" #include "game-settings.hpp" #include "math-helpers.hpp" #include "physics.hpp" #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. */ DynArray entitiesMarkedForRemoval{16}; /* * Public list of entities that will be removed next tick * * Entity or child of entity that had ECS_MarkForRemoval called */ DynArray EntitiesToBeRemoved{16}; // public /* * The entities being removed this tick * * Each of these entities has gone a full tick in the "to be removed" state */ DynArray entitiesYetToBeRemoved{0, nullptr}; /* * Entities that have more instances registered than their current * grBinds GPU buffer has space for. * * These need to be resized THIS TICK */ DynArray EntitiesWithExcessInstances{16}; 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); } Entity_Base *ECS_CreateGenericEntity() { 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; ecs.bc.entityPtrs.buckets[entityHandle.bucketIndex][entityHandle.itemIndex] = entity; return entityHandle; } Entity_Base *ECS_GetEntity(EntityHandle handle) { pk_handle_validate(handle, ecs.bc.entityPtrs.pkeHandle, ecs.bc.entityPtrs.limits.itemIndex); return ecs.bc.entityPtrs.buckets[handle.bucketIndex][handle.itemIndex]; } 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"); entitiesMarkedForRemoval.Push(entity); } void ECS_Tick_Early(double delta) { // these reserves might happen 1 tick early, but that's fine (void)delta; bool shouldRun = entitiesMarkedForRemoval.Count() > 0 || EntitiesToBeRemoved.Count() > 0 || entitiesYetToBeRemoved.Count() > 0; entitiesYetToBeRemoved.Reserve(entitiesMarkedForRemoval.Count()); EntitiesToBeRemoved.Resize(entitiesMarkedForRemoval.Count()); memcpy(EntitiesToBeRemoved.GetPtr(), entitiesMarkedForRemoval.GetPtr(), sizeof(void *) * entitiesMarkedForRemoval.Count()); entitiesYetToBeRemoved.Resize(0); if (!shouldRun) return; entitiesMarkedForRemoval.Resize(0); // 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) { entitiesYetToBeRemoved.Push(ent); ent->handle = EntityHandle_MAX; ent->parentHandle = EntityHandle_MAX; ent->isMarkedForRemoval = false; } else if (EntitiesToBeRemoved.Has(ent)) { ent->isMarkedForRemoval = true; } else if (parentEnt != nullptr && EntitiesToBeRemoved.Has(parentEnt)) { ent->isMarkedForRemoval = true; EntitiesToBeRemoved.Push(ent); } } } } struct updateGrBindsAfter { GrBindsHandle grBindsHandle = GrBindsHandle_MAX; uint32_t count = 0; }; bool DynArrayFindGrBinds(updateGrBindsAfter const &after, const GrBindsHandle &handle) { return after.grBindsHandle == handle; } void ECS_Tick(double delta) { int32_t physicsTickCount = Physics_Tick(delta); int64_t entityRemovalCount = entitiesYetToBeRemoved.Count(); if (physicsTickCount == 0 && entityRemovalCount == 0) return; DynArray *updateGrBindsPtr = pk_new>(pkeSettings.mem.bkt); DynArray &updateGrBinds = *updateGrBindsPtr; 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 && entitiesYetToBeRemoved.Has(ent)) { if (inst.grBindsHandle != GrBindsHandle_MAX) { int64_t afterIndex = updateGrBinds.FindFirstIndex(DynArrayFindGrBinds, inst.grBindsHandle); updateGrBindsAfter *after = nullptr; if (afterIndex != -1) { after = &updateGrBinds[afterIndex]; } else { after = &updateGrBinds.Push(); after->grBindsHandle = inst.grBindsHandle; } 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.Count() > 0 && inst.instanceHandle != InstanceHandle_MAX) { int64_t afterIndex = updateGrBinds.FindFirstIndex(DynArrayFindGrBinds, inst.grBindsHandle); if (afterIndex > -1) { auto &after = updateGrBinds[afterIndex]; inst.index -= after.count; inst.isNeedingUpdated = true; } } } } if (entityRemovalCount > 0 || updateGrBinds.Count() > 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; } auto afterIndex = updateGrBinds.FindFirstIndex(DynArrayFindGrBinds, grBinds.grBindsHandle); Entity_Base *ent = ecs.bc.entityPtrs.buckets[grBinds.entHandle.bucketIndex][grBinds.entHandle.itemIndex]; if (entitiesYetToBeRemoved.Has(ent)) { 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 != -1) { auto &after = updateGrBinds[afterIndex]; grBinds.instanceCounter -= after.count; } } } } } struct InstanceBufferCopyChunk { uint64_t startingIndex; uint64_t endingIndex; DynArray *mats = nullptr; VkBufferCopy dstBufferCopy; }; struct InstanceBufferCopy { CompGrBinds *grBinds = nullptr; VkDeviceSize runningSize = 0; DynArray *chunks = nullptr; }; void ECS_Tick_Late(double delta) { // using a pointer here avoids calling the destructor when the object goes out of scope (void)delta; DynArray *bufferUpdatesPtr = pk_new>(pkeSettings.mem.bkt); new (bufferUpdatesPtr) DynArray(0, pkeSettings.mem.bkt); DynArray &bufferUpdates = *bufferUpdatesPtr; 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.Count(); ++u) { if (bufferUpdates[u].grBinds->grBindsHandle == inst.grBindsHandle) { bfrUpdate = &bufferUpdates[u]; } } if (bfrUpdate == nullptr) { bufferUpdates.Push({ .grBinds = &grBinds, }); bfrUpdate = &bufferUpdates[bufferUpdates.Count() - 1]; bfrUpdate->chunks = pk_new>(pkeSettings.mem.bkt); new (bfrUpdate->chunks) DynArray(4, pkeSettings.mem.bkt); } InstanceBufferCopyChunk *chunk = nullptr; for (long ii = 0; ii < bfrUpdate->chunks->Count(); ++ii) { if ((*bfrUpdate->chunks)[ii].endingIndex == inst.index - 1) { chunk = &(*bfrUpdate->chunks)[ii]; chunk->endingIndex += 1; break; } } if (chunk == nullptr) { bfrUpdate->chunks->Push({ .startingIndex = inst.index, .endingIndex = inst.index, .mats = nullptr, .dstBufferCopy = {}, }); chunk = &(*bfrUpdate->chunks)[bfrUpdate->chunks->Count() - 1]; chunk->dstBufferCopy.dstOffset = sizeof(glm::mat4) * inst.index; chunk->mats = pk_new>(pkeSettings.mem.bkt); new (chunk->mats) DynArray(0, pkeSettings.mem.bkt); } 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); chunk->mats->Push(glm::scale(glmMat_posRot, scale)); bfrUpdate->runningSize += sizeof(glm::mat4); inst.isNeedingUpdated = false; } } while (bufferUpdates.Count() > 0) { InstanceBufferCopy &ibc = bufferUpdates[bufferUpdates.Count() - 1]; VkDeviceSize instanceBytes = sizeof(glm::mat4); VkDeviceSize byteCount = ibc.runningSize; VkBuffer transferBuffer; VkDeviceMemory transferMemory; void *deviceData; BeginTransferBuffer(byteCount, transferBuffer, transferMemory, deviceData); VkDeviceSize runningOffset = 0; for (long i = 0; i < ibc.chunks->Count(); ++i) { auto &chunk = (*ibc.chunks)[i]; memcpy(static_cast(deviceData) + runningOffset, chunk.mats->GetPtr(), 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(transferCommandBuffer, &cbbi); for (long i = 0; i < ibc.chunks->Count(); ++i) { vkCmdCopyBuffer(transferCommandBuffer, transferBuffer, ibc.grBinds->instanceBD.buffer, 1, &(*ibc.chunks)[i].dstBufferCopy); } vkEndCommandBuffer(transferCommandBuffer); 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 = &transferCommandBuffer; vkSubmitInfo.signalSemaphoreCount = 0; vkSubmitInfo.pSignalSemaphores = nullptr; vkQueueSubmit(transferQueue, 1, &vkSubmitInfo, nullptr); vkQueueWaitIdle(transferQueue); vkResetCommandBuffer(transferCommandBuffer, 0); } EndTransferBuffer(transferBuffer, transferMemory); bufferUpdates.Pop(); } } 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, PkeArray &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) { auto &grBinds = bkt[i]; if (grBinds.entHandle == entity->handle) { PkeArray_Add(&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, CompGrBinds *entityTypeGrBinds) { 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; if (entityTypeGrBinds != nullptr) { comp->grBindsHandle = entityTypeGrBinds->grBindsHandle; comp->index = entityTypeGrBinds->instanceCounter++; comp->isNeedingUpdated = true; if (entityTypeGrBinds->instanceCounter > entityTypeGrBinds->instanceBufferMaxCount) { EntitiesWithExcessInstances.Push(ECS_GetEntity(entityTypeGrBinds->entHandle)); } } 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, PkeArray &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) { auto &inst = bkt[i]; if (inst.entHandle == entity->handle) { PkeArray_Add(&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() { EntitiesWithExcessInstances.~DynArray(); entitiesYetToBeRemoved.~DynArray(); EntitiesToBeRemoved.~DynArray(); entitiesMarkedForRemoval.~DynArray(); Buckets_Destroy(ecs.bc.instances); Buckets_Destroy(ecs.bc.grBinds); Buckets_Destroy(ecs.bc.entityPtrs); Buckets_Destroy(ecs.bc.generics); }