visibility_service.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #include "visibility_service.h"
  2. #include "../core/component.h"
  3. #include "../core/world.h"
  4. #include "../systems/owner_registry.h"
  5. #include <algorithm>
  6. #include <cmath>
  7. namespace Game::Map {
  8. namespace {
  9. constexpr float kDefaultVisionRange = 12.0f;
  10. bool inBoundsStatic(int x, int z, int width, int height) {
  11. return x >= 0 && x < width && z >= 0 && z < height;
  12. }
  13. int indexStatic(int x, int z, int width) { return z * width + x; }
  14. int worldToGridStatic(float worldCoord, float half, float tileSize) {
  15. float gridCoord = worldCoord / tileSize + half;
  16. return static_cast<int>(std::floor(gridCoord + 0.5f));
  17. }
  18. } // namespace
  19. VisibilityService &VisibilityService::instance() {
  20. static VisibilityService s_instance;
  21. return s_instance;
  22. }
  23. void VisibilityService::initialize(int width, int height, float tileSize) {
  24. std::unique_lock lock(m_cellsMutex);
  25. m_width = std::max(1, width);
  26. m_height = std::max(1, height);
  27. m_tileSize = std::max(0.0001f, tileSize);
  28. m_halfWidth = m_width * 0.5f - 0.5f;
  29. m_halfHeight = m_height * 0.5f - 0.5f;
  30. const int count = m_width * m_height;
  31. m_cells.assign(count, static_cast<std::uint8_t>(VisibilityState::Unseen));
  32. m_version.store(1, std::memory_order_release);
  33. m_generation.store(0, std::memory_order_release);
  34. m_initialized = true;
  35. }
  36. void VisibilityService::reset() {
  37. if (!m_initialized)
  38. return;
  39. std::unique_lock lock(m_cellsMutex);
  40. std::fill(m_cells.begin(), m_cells.end(),
  41. static_cast<std::uint8_t>(VisibilityState::Unseen));
  42. m_version.fetch_add(1, std::memory_order_release);
  43. }
  44. bool VisibilityService::update(Engine::Core::World &world, int playerId) {
  45. if (!m_initialized)
  46. return false;
  47. bool integrated = integrateCompletedJob();
  48. if (!m_jobActive.load(std::memory_order_acquire)) {
  49. auto sources = gatherVisionSources(world, playerId);
  50. auto payload = composeJobPayload(sources);
  51. startAsyncJob(std::move(payload));
  52. }
  53. return integrated;
  54. }
  55. void VisibilityService::computeImmediate(Engine::Core::World &world,
  56. int playerId) {
  57. if (!m_initialized)
  58. return;
  59. auto sources = gatherVisionSources(world, playerId);
  60. auto payload = composeJobPayload(sources);
  61. auto result = executeJob(std::move(payload));
  62. if (result.changed) {
  63. std::unique_lock lock(m_cellsMutex);
  64. m_cells = std::move(result.cells);
  65. m_version.fetch_add(1, std::memory_order_release);
  66. }
  67. }
  68. std::vector<VisibilityService::VisionSource>
  69. VisibilityService::gatherVisionSources(Engine::Core::World &world,
  70. int playerId) const {
  71. std::vector<VisionSource> sources;
  72. auto entities = world.getEntitiesWith<Engine::Core::TransformComponent>();
  73. const float rangePadding = m_tileSize * 0.5f;
  74. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  75. for (auto *entity : entities) {
  76. auto *transform = entity->getComponent<Engine::Core::TransformComponent>();
  77. auto *unit = entity->getComponent<Engine::Core::UnitComponent>();
  78. if (!transform || !unit)
  79. continue;
  80. if (unit->ownerId != playerId &&
  81. !ownerRegistry.areAllies(playerId, unit->ownerId))
  82. continue;
  83. if (unit->health <= 0)
  84. continue;
  85. const float visionRange = std::max(unit->visionRange, kDefaultVisionRange);
  86. const int centerX = worldToGrid(transform->position.x, m_halfWidth);
  87. const int centerZ = worldToGrid(transform->position.z, m_halfHeight);
  88. if (!inBounds(centerX, centerZ))
  89. continue;
  90. const int cellRadius =
  91. std::max(1, static_cast<int>(std::ceil(visionRange / m_tileSize)));
  92. const float expandedRangeSq =
  93. (visionRange + rangePadding) * (visionRange + rangePadding);
  94. sources.push_back({centerX, centerZ, cellRadius, expandedRangeSq});
  95. }
  96. return sources;
  97. }
  98. VisibilityService::JobPayload VisibilityService::composeJobPayload(
  99. const std::vector<VisionSource> &sources) const {
  100. std::shared_lock lock(m_cellsMutex);
  101. const auto gen = const_cast<std::atomic<std::uint64_t> &>(m_generation)
  102. .fetch_add(1, std::memory_order_relaxed);
  103. return JobPayload{m_width, m_height, m_tileSize, m_cells, sources, gen};
  104. }
  105. void VisibilityService::startAsyncJob(JobPayload &&payload) {
  106. m_jobActive.store(true, std::memory_order_release);
  107. m_pendingJob = std::async(std::launch::async, executeJob, std::move(payload));
  108. }
  109. bool VisibilityService::integrateCompletedJob() {
  110. if (!m_jobActive.load(std::memory_order_acquire))
  111. return false;
  112. if (m_pendingJob.wait_for(std::chrono::seconds(0)) !=
  113. std::future_status::ready)
  114. return false;
  115. auto result = m_pendingJob.get();
  116. m_jobActive.store(false, std::memory_order_release);
  117. if (result.changed) {
  118. std::unique_lock lock(m_cellsMutex);
  119. m_cells = std::move(result.cells);
  120. m_version.fetch_add(1, std::memory_order_release);
  121. return true;
  122. }
  123. return false;
  124. }
  125. VisibilityService::JobResult VisibilityService::executeJob(JobPayload payload) {
  126. const int count = payload.width * payload.height;
  127. std::vector<std::uint8_t> currentVisible(count, 0);
  128. const float halfWidth = payload.width * 0.5f - 0.5f;
  129. const float halfHeight = payload.height * 0.5f - 0.5f;
  130. for (const auto &src : payload.sources) {
  131. for (int dz = -src.cellRadius; dz <= src.cellRadius; ++dz) {
  132. const int gz = src.centerZ + dz;
  133. if (!inBoundsStatic(src.centerX, gz, payload.width, payload.height))
  134. continue;
  135. const float worldDz = dz * payload.tileSize;
  136. for (int dx = -src.cellRadius; dx <= src.cellRadius; ++dx) {
  137. const int gx = src.centerX + dx;
  138. if (!inBoundsStatic(gx, gz, payload.width, payload.height))
  139. continue;
  140. const float worldDx = dx * payload.tileSize;
  141. const float distSq = worldDx * worldDx + worldDz * worldDz;
  142. if (distSq <= src.expandedRangeSq) {
  143. const int idx = indexStatic(gx, gz, payload.width);
  144. currentVisible[idx] = 1;
  145. }
  146. }
  147. }
  148. }
  149. bool changed = false;
  150. for (int idx = 0; idx < count; ++idx) {
  151. const std::uint8_t nowVisible = currentVisible[idx];
  152. if (nowVisible) {
  153. if (payload.cells[idx] !=
  154. static_cast<std::uint8_t>(VisibilityState::Visible)) {
  155. payload.cells[idx] =
  156. static_cast<std::uint8_t>(VisibilityState::Visible);
  157. changed = true;
  158. }
  159. } else {
  160. if (payload.cells[idx] ==
  161. static_cast<std::uint8_t>(VisibilityState::Visible)) {
  162. payload.cells[idx] =
  163. static_cast<std::uint8_t>(VisibilityState::Explored);
  164. changed = true;
  165. }
  166. }
  167. }
  168. return JobResult{std::move(payload.cells), payload.generation, changed};
  169. }
  170. VisibilityState VisibilityService::stateAt(int gridX, int gridZ) const {
  171. if (!m_initialized || !inBounds(gridX, gridZ))
  172. return VisibilityState::Visible;
  173. std::shared_lock lock(m_cellsMutex);
  174. return static_cast<VisibilityState>(m_cells[index(gridX, gridZ)]);
  175. }
  176. bool VisibilityService::isVisibleWorld(float worldX, float worldZ) const {
  177. if (!m_initialized)
  178. return true;
  179. const int gx = worldToGrid(worldX, m_halfWidth);
  180. const int gz = worldToGrid(worldZ, m_halfHeight);
  181. if (!inBounds(gx, gz))
  182. return false;
  183. std::shared_lock lock(m_cellsMutex);
  184. return m_cells[index(gx, gz)] ==
  185. static_cast<std::uint8_t>(VisibilityState::Visible);
  186. }
  187. bool VisibilityService::isExploredWorld(float worldX, float worldZ) const {
  188. if (!m_initialized)
  189. return true;
  190. const int gx = worldToGrid(worldX, m_halfWidth);
  191. const int gz = worldToGrid(worldZ, m_halfHeight);
  192. if (!inBounds(gx, gz))
  193. return false;
  194. std::shared_lock lock(m_cellsMutex);
  195. const auto state = m_cells[index(gx, gz)];
  196. return state == static_cast<std::uint8_t>(VisibilityState::Visible) ||
  197. state == static_cast<std::uint8_t>(VisibilityState::Explored);
  198. }
  199. std::vector<std::uint8_t> VisibilityService::snapshotCells() const {
  200. std::shared_lock lock(m_cellsMutex);
  201. return m_cells;
  202. }
  203. bool VisibilityService::inBounds(int x, int z) const {
  204. return x >= 0 && x < m_width && z >= 0 && z < m_height;
  205. }
  206. int VisibilityService::index(int x, int z) const { return z * m_width + x; }
  207. int VisibilityService::worldToGrid(float worldCoord, float half) const {
  208. float gridCoord = worldCoord / m_tileSize + half;
  209. return static_cast<int>(std::floor(gridCoord + 0.5f));
  210. }
  211. } // namespace Game::Map