#include "ecs.hpp" #include "game-settings.hpp" #include "math-helpers.hpp" #include "memory.hpp" #include "physics.hpp" #include "vendor/glm_include.hpp" #include "window.hpp" #include #include #include TypeSafeInt_B(EntityHandle); const uint64_t maxBucketItemCount = 256; struct EntityBucket { Entity entities[maxBucketItemCount]; }; struct GrBindsBucket { CompGrBinds compGrBinds[maxBucketItemCount]; }; struct InstanceBucket { CompInstance instances[maxBucketItemCount]; }; /* * 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}; DynArray EntitiesWithExcessInstances{16}; BucketContainer Entities_BucketContainer{}; BucketContainer Comp_GrBinds_BucketContainer{}; BucketContainer Comp_Instance_BucketContainer{}; void ECS_GetEntity_Inner(EntityHandle entHandle, Entity*& ent) { EntityHandle_T entHandle_t{static_cast(entHandle)}; assert(entHandle_t != EntityHandle_T_MAX && "Unknown entity handle"); auto b = Buckets_GetBucketIndex(static_cast(entHandle)); auto e = Buckets_GetItemIndex(static_cast(entHandle)); ent = &Entities_BucketContainer.buckets[b].entities[e]; } void ECS_Init() { Buckets_Init(Entities_BucketContainer); Buckets_Init(Comp_GrBinds_BucketContainer); Buckets_Init(Comp_Instance_BucketContainer); } uint64_t ECS_GetEntities_BucketCount() { return Entities_BucketContainer.bucketCounter + 1; } Entity *ECS_GetEntities(uint64_t bucketIndex, uint64_t &itemCount) { assert(bucketIndex <= Entities_BucketContainer.bucketCounter); itemCount = bucketIndex == Entities_BucketContainer.bucketCounter ? Entities_BucketContainer.itemCounter >> 32 : maxBucketItemCount; return Entities_BucketContainer.buckets[bucketIndex].entities; } EntityHandle ECS_CreateEntity_Inner(EntityHandle parentEntHandle) { EntityHandle_T entityHandle_T{Buckets_NewHandle(maxBucketItemCount, Entities_BucketContainer)}; EntityHandle entityHandle{entityHandle_T}; auto b = Buckets_GetBucketIndex(static_cast(entityHandle)); auto e = Buckets_GetItemIndex(static_cast(entityHandle)); Entity *entity = &Entities_BucketContainer.buckets[b].entities[e]; entity = new (entity) Entity{}; entity->handle = entityHandle; entity->parentHandle = parentEntHandle; return entityHandle; } EntityHandle ECS_CreateEntity(EntityHandle parentEntHandle) { return ECS_CreateEntity_Inner(parentEntHandle); } void ECS_MarkForRemoval(EntityHandle entityHandle) { auto b = Buckets_GetBucketIndex(static_cast(entityHandle)); auto e = Buckets_GetItemIndex(static_cast(entityHandle)); const Entity *ent = &Entities_BucketContainer.buckets[b].entities[e]; assert(ent->isMarkedForRemoval == false && "Entity already marked for removal"); entitiesMarkedForRemoval.Push(entityHandle); } void ECS_Tick_Early(double delta) { // these reserves might happen 1 tick early, but that's fine entitiesYetToBeRemoved.Reserve(entitiesMarkedForRemoval.Count()); EntitiesToBeRemoved.Resize(entitiesMarkedForRemoval.Count()); memcpy(EntitiesToBeRemoved.GetPtr(), entitiesMarkedForRemoval.GetPtr(), sizeof(EntityHandle) * entitiesMarkedForRemoval.Count()); entitiesYetToBeRemoved.Resize(0); for (long b = 0; b <= Entities_BucketContainer.bucketCounter; ++b) { uint64_t entCount = b == Entities_BucketContainer.bucketCounter ? Entities_BucketContainer.itemCounter >> 32 : maxBucketItemCount; for (long e = 0; e < entCount; ++e) { Entity *ent = &Entities_BucketContainer.buckets[b].entities[e]; if (ent->handle == EntityHandle_MAX) continue; if (ent->isMarkedForRemoval) { entitiesYetToBeRemoved.Push(*ent); ent->handle = EntityHandle_MAX; ent->parentHandle = EntityHandle_MAX; ent->isMarkedForRemoval = false; } else if (EntitiesToBeRemoved.Has(ent->handle)) { ent->isMarkedForRemoval = true; } else if (EntitiesToBeRemoved.Has(ent->parentHandle)) { ent->isMarkedForRemoval = true; EntitiesToBeRemoved.Push(ent->handle); } } } for (long e = 0; e < entitiesYetToBeRemoved.Count(); ++e) { Entity &clonedEnt = entitiesYetToBeRemoved[e]; CompGrBinds *grBinds = nullptr; InstanceBucket *instBucket = nullptr; CompInstance *inst = nullptr; uint64_t instBucketIndex = 0; if (clonedEnt.instanceHandle != InstanceHandle_MAX) { InstanceHandle_T instanceHandle_t{static_cast(clonedEnt.instanceHandle)}; auto b = Buckets_GetBucketIndex(instanceHandle_t); auto i = Buckets_GetItemIndex(instanceHandle_t); instBucketIndex = b; instBucket = &Comp_Instance_BucketContainer.buckets[b]; inst = &instBucket->instances[i]; GrBindsHandle_T grBindsHandle_t = static_cast(inst->grBindsHandle); b = Buckets_GetBucketIndex(grBindsHandle_t); i = Buckets_GetItemIndex(grBindsHandle_t); grBinds = &Comp_GrBinds_BucketContainer.buckets[b].compGrBinds[i]; } if (grBinds == nullptr && clonedEnt.grBindsHandle != GrBindsHandle_MAX) { GrBindsHandle_T bindsHandle_t{static_cast(clonedEnt.grBindsHandle)}; auto b = Buckets_GetBucketIndex(bindsHandle_t); auto i = Buckets_GetItemIndex(bindsHandle_t); grBinds = &Comp_GrBinds_BucketContainer.buckets[b].compGrBinds[i]; } if (inst != nullptr) { assert(grBinds != nullptr); for (long bi = instBucketIndex; bi <= Comp_Instance_BucketContainer.bucketCounter; ++bi) { uint64_t instCounter = 0; if (bi == Comp_Instance_BucketContainer.bucketCounter) { instCounter = Comp_Instance_BucketContainer.itemCounter >> 32; } else { instCounter = maxBucketItemCount; } auto &bucket = Comp_Instance_BucketContainer.buckets[bi]; for (long ii = 0; ii < instCounter; ++ii) { if (bucket.instances[ii].entHandle == EntityHandle_MAX) { continue; } if (bucket.instances[ii].grBindsHandle != clonedEnt.grBindsHandle) { continue; } if (bucket.instances[ii].instanceHandle < clonedEnt.instanceHandle) { continue; } bucket.instances[ii].index -= 1; bucket.instances[ii].isNeedingUpdated = true; } } grBinds->instanceCounter -= 1; inst->entHandle = EntityHandle_MAX; inst->grBindsHandle = GrBindsHandle_MAX; inst->index = ECS_UNSET_VAL; inst->instanceHandle = InstanceHandle_MAX; inst->isNeedingUpdated = false; BtDynamicsWorld->removeRigidBody(inst->bt.rigidBody); Pke_Delete(inst->bt.motionState, MemBkt_Bullet); Pke_Delete(inst->bt.rigidBody, MemBkt_Bullet); } else if (grBinds != nullptr) { /* * 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. */ } } entitiesMarkedForRemoval.Resize(0); } void ECS_Tick(double delta) { int32_t physicsTickCount = Physics_Tick(delta); if (physicsTickCount != 0) { for (long b = 0; b <= Comp_Instance_BucketContainer.bucketCounter; ++b) { auto &bkt = Comp_Instance_BucketContainer.buckets[b]; long count = Comp_Instance_BucketContainer.bucketCounter == b ? Comp_Instance_BucketContainer.itemCounter >> 32 : maxBucketItemCount; for (uint32_t i = 0; i < count; ++i) { auto &inst = bkt.instances[i]; if (inst.entHandle == EntityHandle_MAX) continue; auto activationState = inst.bt.rigidBody->getActivationState(); if (activationState == ISLAND_SLEEPING || activationState == DISABLE_SIMULATION || activationState == WANTS_DEACTIVATION) { continue; } inst.isNeedingUpdated = true; } } } } struct InstanceBufferCopyChunk { uint64_t startingIndex; uint64_t endingIndex; DynArray mats{0, nullptr}; VkBufferCopy dstBufferCopy; }; struct InstanceBufferCopy { CompGrBinds *grBinds = nullptr; VkDeviceSize runningSize = 0; DynArray chunks{4}; }; void ECS_Tick_Late(double delta) { DynArray bufferUpdates{0, nullptr}; for (long b = 0; b <= Comp_Instance_BucketContainer.bucketCounter; ++b) { auto &bkt = Comp_Instance_BucketContainer.buckets[b]; long count = Comp_Instance_BucketContainer.bucketCounter == b ? Comp_Instance_BucketContainer.itemCounter >> 32 : maxBucketItemCount; for (uint32_t i = 0; i < count; ++i) { auto &inst = bkt.instances[i]; if (inst.isNeedingUpdated == false) continue; if (inst.entHandle == EntityHandle_MAX) continue; GrBindsHandle_T grBindsHandle_t{static_cast(inst.grBindsHandle)}; auto b_inner = Buckets_GetBucketIndex(grBindsHandle_t); auto bi_inner = Buckets_GetItemIndex(grBindsHandle_t); auto &grBinds = Comp_GrBinds_BucketContainer.buckets[b_inner].compGrBinds[bi_inner]; 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]; } 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, }); chunk = &bfrUpdate->chunks[bfrUpdate->chunks.Count() - 1]; chunk->dstBufferCopy.dstOffset = sizeof(glm::mat4) * inst.index; } 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.Pop(); 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->instanceBuffer, 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); } } CompGrBinds &ECS_CreateGrBinds(EntityHandle entHandle) { assert(entHandle != EntityHandle_MAX); Entity *ent = nullptr; ECS_GetEntity_Inner(entHandle, ent); assert(ent->grBindsHandle == GrBindsHandle_MAX); GrBindsHandle_T newHandle{Buckets_NewHandle(maxBucketItemCount, Comp_GrBinds_BucketContainer)}; ent->grBindsHandle = GrBindsHandle{newHandle}; auto b = Buckets_GetBucketIndex(newHandle); auto i = Buckets_GetItemIndex(newHandle); auto *comp = &Comp_GrBinds_BucketContainer.buckets[b].compGrBinds[i]; comp = new (comp) CompGrBinds{}; comp->entHandle = entHandle; comp->grBindsHandle = ent->grBindsHandle; return *comp; } CompGrBinds *ECS_GetGrBinds(EntityHandle entHandle) { Entity *ent = nullptr; ECS_GetEntity_Inner(entHandle, ent); if (ent->grBindsHandle == GrBindsHandle_MAX) return nullptr; GrBindsHandle_T grBindsHandle_t{static_cast(ent->grBindsHandle)}; auto b = Buckets_GetBucketIndex(grBindsHandle_t); auto i = Buckets_GetItemIndex(grBindsHandle_t); return &Comp_GrBinds_BucketContainer.buckets[b].compGrBinds[i]; } CompGrBinds *ECS_GetGrBinds(GrBindsHandle grBindsHandle) { if (grBindsHandle == GrBindsHandle_MAX) return nullptr; GrBindsHandle_T grBindsHandle_t{static_cast(grBindsHandle)}; auto b = Buckets_GetBucketIndex(grBindsHandle_t); auto i = Buckets_GetItemIndex(grBindsHandle_t); return &Comp_GrBinds_BucketContainer.buckets[b].compGrBinds[i]; } uint64_t ECS_GetGrBinds_BucketCount() { return Comp_GrBinds_BucketContainer.bucketCounter + 1; } CompGrBinds *ECS_GetGrBinds(uint64_t bucketIndex, uint64_t &itemCount) { if (bucketIndex == Comp_GrBinds_BucketContainer.bucketCounter) { itemCount = Comp_GrBinds_BucketContainer.itemCounter >> 32; } else { itemCount = maxBucketItemCount; } return Comp_GrBinds_BucketContainer.buckets[bucketIndex].compGrBinds; } CompInstance &ECS_CreateInstance(EntityHandle entHandle, EntityHandle entityTypeEntityHandle) { assert(entHandle != EntityHandle_MAX); assert(entityTypeEntityHandle != EntityHandle_MAX); Entity *ent = nullptr; Entity *entTypeEnt = nullptr; ECS_GetEntity_Inner(entHandle, ent); ECS_GetEntity_Inner(entityTypeEntityHandle, entTypeEnt); assert(ent->grBindsHandle == GrBindsHandle_MAX); assert(entTypeEnt->grBindsHandle != GrBindsHandle_MAX); InstanceHandle_T newHandle{Buckets_NewHandle(maxBucketItemCount, Comp_Instance_BucketContainer)}; ent->instanceHandle = InstanceHandle{newHandle}; GrBindsHandle_T grBindsHandle_t{static_cast(entTypeEnt->grBindsHandle)}; auto b = Buckets_GetBucketIndex(newHandle); auto i = Buckets_GetItemIndex(newHandle); auto *instBkt = &Comp_Instance_BucketContainer.buckets[b]; auto *comp = &instBkt->instances[i]; comp = new (comp) CompInstance{}; comp->entHandle = entHandle; auto b2 = Buckets_GetBucketIndex(grBindsHandle_t); auto i2 = Buckets_GetItemIndex(grBindsHandle_t); auto *grBinds = &Comp_GrBinds_BucketContainer.buckets[b2].compGrBinds[i2]; comp->grBindsHandle = grBinds->grBindsHandle; comp->instanceHandle = ent->instanceHandle; comp->index = grBinds->instanceCounter++; comp->isNeedingUpdated = true; if (grBinds->instanceCounter > grBinds->instanceBufferMaxCount) { EntitiesWithExcessInstances.Push(grBinds->entHandle); } return *comp; } CompInstance *ECS_GetInstance(EntityHandle entHandle) { Entity *ent = nullptr; ECS_GetEntity_Inner(entHandle, ent); if (ent->instanceHandle == InstanceHandle_MAX) return nullptr; InstanceHandle_T instanceHandle_t{static_cast(ent->instanceHandle)}; auto b = Buckets_GetBucketIndex(instanceHandle_t); auto i = Buckets_GetItemIndex(instanceHandle_t); auto *inst = &Comp_Instance_BucketContainer.buckets[b].instances[i]; return inst; } void ECS_UpdateInstance(EntityHandle entHandle, const InstPos &instPos, bool overridePhysics) { Entity *ent = nullptr; ECS_GetEntity_Inner(entHandle, ent); if (ent->instanceHandle == InstanceHandle_MAX) return; InstanceHandle_T instanceHandle_t{static_cast(ent->instanceHandle)}; auto b = Buckets_GetBucketIndex(instanceHandle_t); auto i = Buckets_GetItemIndex(instanceHandle_t); auto *inst = &Comp_Instance_BucketContainer.buckets[b].instances[i]; if (BtDynamicsWorld && overridePhysics) { btVector3 localInertia(0, 0, 0); inst->bt.rigidBody->getCollisionShape()->calculateLocalInertia(instPos.mass, localInertia); inst->bt.rigidBody->setMassProps(instPos.mass, localInertia); inst->bt.rigidBody->getMotionState()->setWorldTransform(instPos.posRot); inst->bt.rigidBody->setWorldTransform(instPos.posRot); inst->bt.rigidBody->getCollisionShape()->setLocalScaling(instPos.scale); inst->bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); inst->bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); inst->bt.rigidBody->activate(); inst->isNeedingUpdated = true; } } uint64_t ECS_GetInstances_BucketCount() { return Comp_Instance_BucketContainer.bucketCounter + 1; } CompInstance *ECS_GetInstances(uint64_t bucketIndex, uint64_t &itemCount) { if (bucketIndex == Comp_Instance_BucketContainer.bucketCounter) { itemCount = Comp_Instance_BucketContainer.itemCounter >> 32; } else { itemCount = maxBucketItemCount; } return Comp_Instance_BucketContainer.buckets[bucketIndex].instances; } void ECS_Teardown() { EntitiesWithExcessInstances.~DynArray(); entitiesYetToBeRemoved.~DynArray(); EntitiesToBeRemoved.~DynArray(); entitiesMarkedForRemoval.~DynArray(); Buckets_Destroy(Entities_BucketContainer); Buckets_Destroy(Comp_GrBinds_BucketContainer); Buckets_Destroy(Comp_Instance_BucketContainer); }