diff --git a/.mailmap b/.mailmap index fbf10ad63f..8a1a8727b1 100644 --- a/.mailmap +++ b/.mailmap @@ -20,4 +20,5 @@ Tobias Feldballe Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> -Nikhil Ghosh \ No newline at end of file +Nikhil Ghosh +David Wever <56411717+dmwever@users.noreply.github.com> \ No newline at end of file diff --git a/copying.md b/copying.md index 3d97be2f8b..d1434fe4a7 100644 --- a/copying.md +++ b/copying.md @@ -158,6 +158,7 @@ _the openage authors_ are: | Jeremiah Morgan | jere8184 | jeremiahmorgan dawt bham à outlook dawt com | | Tobias Alam | alamt22 | tobiasal à umich dawt edu | | Alex Zhuohao He | ZzzhHe | zhuohao dawt he à outlook dawt com | +| David Wever | dmwever | dmwever à crimson dawt ua dawt edu | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. diff --git a/libopenage/gamestate/map.cpp b/libopenage/gamestate/map.cpp index 8300ce273d..88578fabdb 100644 --- a/libopenage/gamestate/map.cpp +++ b/libopenage/gamestate/map.cpp @@ -49,7 +49,7 @@ Map::Map(const std::shared_ptr &state, auto sector = grid->get_sector(chunk_idx); auto cost_field = sector->get_cost_field(); - cost_field->set_cost(tile_idx, path_cost.second); + cost_field->set_cost(tile_idx, path_cost.second, time::TIME_ZERO); } } } diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index 3da45ceab3..5a44baf113 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "move.h" @@ -37,7 +37,8 @@ namespace openage::gamestate::system { std::vector find_path(const std::shared_ptr &pathfinder, path::grid_id_t grid_id, const coord::phys3 &start, - const coord::phys3 &end) { + const coord::phys3 &end, + const time::time_t &start_time) { auto start_tile = start.to_tile(); auto end_tile = end.to_tile(); @@ -46,6 +47,7 @@ std::vector find_path(const std::shared_ptr &pat grid_id, start_tile, end_tile, + start_time, }; auto tile_path = pathfinder->get_path(request); @@ -122,7 +124,7 @@ const time::time_t Move::move_default(const std::shared_ptrget_map(); auto pathfinder = map->get_pathfinder(); auto grid_id = map->get_grid_id(move_path_grid->get_name()); - auto waypoints = find_path(pathfinder, grid_id, current_pos, destination); + auto waypoints = find_path(pathfinder, grid_id, current_pos, destination, start_time); // use waypoints for movement double total_time = 0; diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 7f85264d01..33c2844ace 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -1,6 +1,7 @@ add_sources(libopenage cost_field.cpp definitions.cpp + field_cache.cpp flow_field.cpp grid.cpp integration_field.cpp diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 3299084548..12889663eb 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "cost_field.h" @@ -13,6 +13,7 @@ namespace openage::path { CostField::CostField(size_t size) : size{size}, + valid_until{time::TIME_MIN}, cells(this->size * this->size, COST_MIN) { log::log(DBG << "Created cost field with size " << this->size << "x" << this->size); } @@ -33,29 +34,34 @@ cost_t CostField::get_cost(size_t idx) const { return this->cells.at(idx); } -void CostField::set_cost(const coord::tile_delta &pos, cost_t cost) { - this->cells[pos.ne + pos.se * this->size] = cost; +void CostField::set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until) { + this->set_cost(pos.ne + pos.se * this->size, cost, valid_until); } -void CostField::set_cost(size_t x, size_t y, cost_t cost) { - this->cells[x + y * this->size] = cost; -} - -void CostField::set_cost(size_t idx, cost_t cost) { - this->cells[idx] = cost; +void CostField::set_cost(size_t x, size_t y, cost_t cost, const time::time_t &valid_until) { + this->set_cost(x + y * this->size, cost, valid_until); } const std::vector &CostField::get_costs() const { return this->cells; } -void CostField::set_costs(std::vector &&cells) { +void CostField::set_costs(std::vector &&cells, const time::time_t &valid_until) { ENSURE(cells.size() == this->cells.size(), "cells vector has wrong size: " << cells.size() << "; expected: " << this->cells.size()); this->cells = std::move(cells); + this->valid_until = valid_until; +} + +bool CostField::is_dirty(const time::time_t &time) const { + return time >= this->valid_until; +} + +void CostField::clear_dirty() { + this->valid_until = time::TIME_MAX; } } // namespace openage::path diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index a1fc87b046..c03b494a84 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -6,6 +6,7 @@ #include #include "pathfinding/types.h" +#include "time/time.h" namespace openage { @@ -64,8 +65,9 @@ class CostField { * * @param pos Coordinates of the cell (relative to field origin). * @param cost Cost to set. + * @param valid_until Time at which the cost value expires. */ - void set_cost(const coord::tile_delta &pos, cost_t cost); + void set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until); /** * Set the cost at a specified position. @@ -73,16 +75,21 @@ class CostField { * @param x X-coordinate of the cell. * @param y Y-coordinate of the cell. * @param cost Cost to set. + * @param valid_until Time at which the cost value expires. */ - void set_cost(size_t x, size_t y, cost_t cost); + void set_cost(size_t x, size_t y, cost_t cost, const time::time_t &valid_until); /** * Set the cost at a specified position. * * @param idx Index of the cell. * @param cost Cost to set. + * @param valid_until Time at which the cost value expires. */ - void set_cost(size_t idx, cost_t cost); + inline void set_cost(size_t idx, cost_t cost, const time::time_t &valid_until) { + this->cells[idx] = cost; + this->valid_until = valid_until; + } /** * Get the cost field values. @@ -95,8 +102,23 @@ class CostField { * Set the cost field values. * * @param cells Cost field values. + * @param valid_until Time at which the cost value expires. */ - void set_costs(std::vector &&cells); + void set_costs(std::vector &&cells, const time::time_t &changed); + + /** + * Check if the cost field is dirty at the specified time. + * + * @param time Time of access to the cost field. + * + * @return Whether the cost field is dirty. + */ + bool is_dirty(const time::time_t &time) const; + + /** + * Clear the dirty flag. + */ + void clear_dirty(); private: /** @@ -104,6 +126,11 @@ class CostField { */ size_t size; + /** + * Time the cost field expires. + */ + time::time_t valid_until; + /** * Cost field values. */ diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 483261ebaf..31f66bb51f 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "demo_0.h" @@ -33,15 +33,17 @@ void path_demo_0(const util::Path &path) { // Cost field with some obstacles auto cost_field = std::make_shared(field_length); - cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE); - cost_field->set_cost(coord::tile_delta{1, 0}, 254); - cost_field->set_cost(coord::tile_delta{4, 3}, 128); - cost_field->set_cost(coord::tile_delta{5, 3}, 128); - cost_field->set_cost(coord::tile_delta{6, 3}, 128); - cost_field->set_cost(coord::tile_delta{4, 4}, 128); - cost_field->set_cost(coord::tile_delta{5, 4}, 128); - cost_field->set_cost(coord::tile_delta{6, 4}, 128); - cost_field->set_cost(coord::tile_delta{1, 7}, COST_IMPASSABLE); + + const time::time_t time = time::TIME_ZERO; + cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE, time); + cost_field->set_cost(coord::tile_delta{1, 0}, 254, time); + cost_field->set_cost(coord::tile_delta{4, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{5, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{6, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{4, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{5, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{6, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{1, 7}, COST_IMPASSABLE, time); log::log(INFO << "Created cost field"); // Create an integration field from the cost field diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 1edeba52a1..61df3e8995 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "demo_1.h" @@ -30,20 +30,30 @@ void path_demo_1(const util::Path &path) { // Initialize the cost field for each sector. for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); - std::vector sector_cost = sectors_cost.at(sector->get_id()); - cost_field->set_costs(std::move(sector_cost)); + + // Read the data from the preconfigured table + std::vector sector_cost = SECTORS_COST.at(sector->get_id()); + + // Set the cost field for the sector + cost_field->set_costs(std::move(sector_cost), time::TIME_MAX); } - // Initialize portals between sectors. + // Initialize portals between sectors util::Vector2s grid_size = grid->get_size(); portal_id_t portal_id = 0; for (size_t y = 0; y < grid_size[1]; y++) { for (size_t x = 0; x < grid_size[0]; x++) { + // For each sector on the grid, we will seatch for portals to the east and south + // sectors and connect them + auto sector = grid->get_sector(x, y); if (x < grid_size[0] - 1) { + // Check the sector to the east auto neighbor = grid->get_sector(x + 1, y); auto portals = sector->find_portals(neighbor, PortalDirection::EAST_WEST, portal_id); + + // Add the portals to the sector and the neighbor for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); @@ -51,8 +61,11 @@ void path_demo_1(const util::Path &path) { portal_id += portals.size(); } if (y < grid_size[1] - 1) { + // Check the sector to the south auto neighbor = grid->get_sector(x, y + 1); auto portals = sector->find_portals(neighbor, PortalDirection::NORTH_SOUTH, portal_id); + + // Add the portals to the sector and the neighbor for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); @@ -62,7 +75,8 @@ void path_demo_1(const util::Path &path) { } } - // Connect portals inside sectors. + // Connect portals that can mutually reach each other + // within the same sector for (auto sector : grid->get_sectors()) { sector->connect_exits(); @@ -81,19 +95,25 @@ void path_demo_1(const util::Path &path) { auto pathfinder = std::make_shared(); pathfinder->add_grid(grid); + // Add a timer to measure the pathfinding speed util::Timer timer; - // Create a path request and get the path + // Create a path request from one end of the grid to the other coord::tile start{2, 26}; coord::tile target{36, 2}; - PathRequest path_request{ grid->get_id(), start, target, + time::TIME_ZERO, }; + + // Initialize the portal nodes of the grid + // This is used for the A* pathfinding search at the beginning grid->init_portal_nodes(); + timer.start(); + // Let the pathfinder search for a path Path path_result = pathfinder->get_path(path_request); timer.stop(); @@ -109,8 +129,11 @@ void path_demo_1(const util::Path &path) { auto render_manager = std::make_shared(qtapp, window, path, grid); log::log(INFO << "Created render manager for pathfinding demo"); + // Callbacks for mouse button events + // Used to set the start and target cells for pathfinding window->add_mouse_button_callback([&](const QMouseEvent &ev) { if (ev.type() == QEvent::MouseButtonRelease) { + // From the mouse position, calculate the position/cell on the grid auto cell_count_x = grid->get_size()[0] * grid->get_sector_size(); auto cell_count_y = grid->get_size()[1] * grid->get_sector_size(); auto window_size = window->get_size(); @@ -127,6 +150,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, + time::TIME_ZERO, }; timer.reset(); @@ -147,6 +171,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, + time::TIME_ZERO, }; timer.reset(); diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index db70efe084..b2d3037eb8 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -182,7 +182,7 @@ class RenderManager1 { // Cost for the sectors in the grid // taken from Figure 23.1 in "Crowd Pathfinding and Steering Using Flow Field Tiles" -const std::vector> sectors_cost = { +const std::vector> SECTORS_COST = { { // clang-format off 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp new file mode 100644 index 0000000000..86f985e3ba --- /dev/null +++ b/libopenage/pathfinding/field_cache.cpp @@ -0,0 +1,32 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "field_cache.h" + +namespace openage::path { + +void FieldCache::add(const cache_key_t cache_key, + const field_cache_t cache_entry) { + this->cache[cache_key] = cache_entry; +} + +bool FieldCache::evict(const cache_key_t cache_key) { + return this->cache.erase(cache_key) != 0; +} + +bool FieldCache::is_cached(const cache_key_t cache_key) const { + return this->cache.contains(cache_key); +} + +std::shared_ptr FieldCache::get_integration_field(const cache_key_t cache_key) const { + return this->cache.at(cache_key).first; +} + +std::shared_ptr FieldCache::get_flow_field(const cache_key_t cache_key) const { + return this->cache.at(cache_key).second; +} + +field_cache_t FieldCache::get(const cache_key_t cache_key) const { + return this->cache.at(cache_key); +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/field_cache.h b/libopenage/pathfinding/field_cache.h new file mode 100644 index 0000000000..b5c39b44de --- /dev/null +++ b/libopenage/pathfinding/field_cache.h @@ -0,0 +1,110 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "pathfinding/types.h" +#include "util/hash.h" + + +namespace openage { +namespace coord { +struct tile_delta; +} // namespace coord + +namespace path { +class IntegrationField; +class FlowField; + +/** + * Cache to store already calculated flow and integration fields for the pathfinding algorithm. + */ +class FieldCache { +public: + FieldCache() = default; + ~FieldCache() = default; + + /** + * Adds a new field entry to the cache. + * + * @param cache_key Cache key for the field entry. + * @param cache_entry Field entry (integration field, flow field). + */ + void add(const cache_key_t cache_key, + const field_cache_t cache_entry); + + /** + * Evict field entry from the cache. + * + * @param cache_key Cache key for the field entry to evict. + * + * @return true if the cache key was found and evicted, false otherwise. + */ + bool evict(const cache_key_t cache_key); + + /** + * Check if there is a cached entry for a specific cache key. + * + * @param cache_key Cache key to check. + * + * @return true if a field entry is found for the cache key, false otherwise. + */ + bool is_cached(const cache_key_t cache_key) const; + + /** + * Get a cached integration field. + * + * @param cache_key Cache key for the field entry. + * + * @return Integration field. + */ + std::shared_ptr get_integration_field(const cache_key_t cache_key) const; + + /** + * Get a cached flow field. + * + * @param cache_key Cache key for the field entry. + * + * @return Flow field. + */ + std::shared_ptr get_flow_field(const cache_key_t cache_key) const; + + /** + * Get a cached field entry. + * + * Contains both integration field and flow field. + * + * @param cache_key Cache key for the field entry. + * + * @return Field entry (integration field, flow field). + */ + field_cache_t get(const cache_key_t cache_key) const; + +private: + /** + * Hash function for the field cache. + */ + struct pair_hash { + template + std::size_t operator()(const std::pair &pair) const { + return util::hash_combine(std::hash{}(pair.first), std::hash{}(pair.second)); + } + }; + + /** + * Cache for already computed fields. + * + * Key is the portal ID and the sector ID from which the field was entered. Fields that are cached are + * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated + * when the field is reused. + */ + std::unordered_map + cache; +}; + +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 265c5285cc..f4dcf324b1 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -1,17 +1,23 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "integrator.h" #include "log/log.h" #include "pathfinding/cost_field.h" +#include "pathfinding/field_cache.h" #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" #include "pathfinding/portal.h" +#include "time/time.h" namespace openage::path { +Integrator::Integrator() : + field_cache{std::make_unique()} { +} + std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, const coord::tile_delta &target, bool with_los) { @@ -36,24 +42,33 @@ std::shared_ptr Integrator::integrate(const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (cost_field->is_dirty(time)) { + log::log(DBG << "Evicting cached integration and flow fields for portal " << portal->get_id() + << " from sector " << other_sector_id); + this->field_cache->evict(cache_key); + } + else if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration field for portal " << portal->get_id() << " from sector " << other_sector_id); + + // retrieve cached integration field + auto cached_integration_field = this->field_cache->get_integration_field(cache_key); + if (with_los) { log::log(SPAM << "Performing LOS pass on cached field"); // Make a copy of the cached field to avoid modifying the cached field - auto integration_field = std::make_shared(*cached->second.first); + auto integration_field = std::make_shared(*cached_integration_field); // Only integrate LOS; leave the rest of the field as is integration_field->integrate_los(cost_field, other, other_sector_id, portal, target); return integration_field; } - return cached->second.first; + return cached_integration_field; } log::log(DBG << "Integrating cost field for portal " << portal->get_id() @@ -99,15 +114,18 @@ std::shared_ptr Integrator::build(const std::shared_ptr &portal, bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached flow field for portal " << portal->get_id() << " from sector " << other_sector_id); + + // retrieve cached flow field + auto cached_flow_field = this->field_cache->get_flow_field(cache_key); + if (with_los) { log::log(SPAM << "Transferring LOS flags to cached flow field"); // Make a copy of the cached flow field - auto flow_field = std::make_shared(*cached->second.second); + auto flow_field = std::make_shared(*cached_flow_field); // Transfer the LOS flags to the flow field flow_field->transfer_dynamic_flags(integration_field); @@ -115,7 +133,7 @@ std::shared_ptr Integrator::build(const std::shared_ptrsecond.second; + return cached_flow_field; } log::log(DBG << "Building flow field for portal " << portal->get_id() @@ -140,17 +158,28 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (cost_field->is_dirty(time)) { + log::log(DBG << "Evicting cached integration and flow fields for portal " << portal->get_id() + << " from sector " << other_sector_id); + this->field_cache->evict(cache_key); + } + else if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration and flow fields for portal " << portal->get_id() << " from sector " << other_sector_id); + + // retrieve cached fields + auto cached_fields = this->field_cache->get(cache_key); + auto cached_integration_field = cached_fields.first; + auto cached_flow_field = cached_fields.second; + if (with_los) { log::log(SPAM << "Performing LOS pass on cached field"); // Make a copy of the cached integration field - auto integration_field = std::make_shared(*cached->second.first); + auto integration_field = std::make_shared(*cached_integration_field); // Only integrate LOS; leave the rest of the field as is integration_field->integrate_los(cost_field, other, other_sector_id, portal, target); @@ -158,7 +187,7 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ log::log(SPAM << "Transferring LOS flags to cached flow field"); // Make a copy of the cached flow field - auto flow_field = std::make_shared(*cached->second.second); + auto flow_field = std::make_shared(*cached_flow_field); // Transfer the LOS flags to the flow field flow_field->transfer_dynamic_flags(integration_field); @@ -166,10 +195,10 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ return std::make_pair(integration_field, flow_field); } - return cached->second; + return std::make_pair(cached_integration_field, cached_flow_field); } - auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, with_los); + auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, time, with_los); auto flow_field = this->build(integration_field, other, other_sector_id, portal); log::log(DBG << "Caching integration and flow fields for portal ID: " << portal->get_id() @@ -182,7 +211,9 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ std::shared_ptr cached_flow_field = std::make_shared(*flow_field); cached_flow_field->reset_dynamic_flags(); - this->field_cache[cache_key] = std::make_pair(cached_integration_field, cached_flow_field); + field_cache_t field_cache = field_cache_t(cached_integration_field, cached_flow_field); + + this->field_cache->add(cache_key, field_cache); return std::make_pair(integration_field, flow_field); } diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index c27d3c1eb1..490d5eaacb 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -1,13 +1,13 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once -#include #include -#include #include "pathfinding/types.h" +#include "pathfinding/field_cache.h" #include "util/hash.h" +#include "time/time.h" namespace openage { @@ -26,7 +26,11 @@ class Portal; */ class Integrator { public: - Integrator() = default; + /** + * Create a new integrator. + */ + Integrator(); + ~Integrator() = default; /** @@ -56,6 +60,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. + * @param time Time of the path request. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field. @@ -65,6 +70,7 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los = true); /** @@ -114,6 +120,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. + * @param time Time of the path request. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field and flow field. @@ -123,30 +130,14 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los = true); private: - /** - * Hash function for the field cache. - */ - struct pair_hash { - template - std::size_t operator()(const std::pair &pair) const { - return util::hash_combine(std::hash{}(pair.first), std::hash{}(pair.second)); - } - }; - /** * Cache for already computed fields. - * - * Key is the portal ID and the sector ID from which the field was entered. Fields that are cached are - * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated - * when the field is reused. */ - std::unordered_map, - get_return_t, - pair_hash> - field_cache; + std::unique_ptr field_cache; }; } // namespace path diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index d1c61ac325..df1b34a599 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -6,6 +6,7 @@ #include "coord/tile.h" #include "pathfinding/types.h" +#include "time/time.h" namespace openage::path { @@ -20,6 +21,8 @@ struct PathRequest { coord::tile start; /// Target position of the path. coord::tile target; + /// Time the request was made. + const time::time_t time; }; /** diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index c99afd5b9d..5a9c785f0c 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -155,6 +155,7 @@ const Path Pathfinder::get_path(const PathRequest &request) { prev_sector_id, portal, target_delta, + request.time, with_los); flow_fields.push_back(std::make_pair(next_sector_id, sector_fields.second)); diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index f50dc209bf..5897bbef84 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "sector.h" diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index a4ba94fa52..d684a399b6 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 5f92496ff8..2ac0960c34 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -10,6 +10,7 @@ #include "pathfinding/integration_field.h" #include "pathfinding/integrator.h" #include "pathfinding/types.h" +#include "time/time.h" namespace openage { @@ -23,7 +24,8 @@ void flow_field() { // | 1 | 1 | 1 | // | 1 | X | 1 | // | 1 | 1 | 1 | - cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}); + const time::time_t time = time::TIME_ZERO; + cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}, time); // Test the different field types { diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 1b48a49654..3229010a91 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -4,6 +4,8 @@ #include #include +#include +#include namespace openage::path { @@ -109,4 +111,17 @@ using sector_id_t = size_t; */ using portal_id_t = size_t; +class FlowField; +class IntegrationField; + +/** + * Cache key for accessing the field cache using a portal id and a sector id. + */ +using cache_key_t = std::pair; + +/** + * Returnable field cache entry pair containing an integration field and a flow field. + */ +using field_cache_t = std::pair, std::shared_ptr>; + } // namespace openage::path