diff --git a/src/linkgraph/linkgraph_gui.cpp b/src/linkgraph/linkgraph_gui.cpp index 21105981cb..8c9be0953a 100644 --- a/src/linkgraph/linkgraph_gui.cpp +++ b/src/linkgraph/linkgraph_gui.cpp @@ -17,6 +17,7 @@ #include "../date_func.h" #include "../viewport_func.h" #include "../smallmap_gui.h" +#include "../zoom_func.h" #include "../core/geometry_func.hpp" #include "../widgets/link_graph_legend_widget.h" @@ -24,6 +25,8 @@ #include "../3rdparty/cpp-btree/btree_map.h" +#include + #include "../safeguards.h" /** @@ -40,25 +43,53 @@ const uint8 LinkGraphOverlay::LINK_COLOURS[] = { * Get a DPI for the widget we will be drawing to. * @param dpi DrawPixelInfo to fill with the desired dimensions. */ -void LinkGraphOverlay::GetWidgetDpi(DrawPixelInfo *dpi) const +void LinkGraphOverlay::GetWidgetDpi(DrawPixelInfo *dpi, uint margin) const { const NWidgetBase *wi = this->window->GetWidget(this->widget_id); - dpi->left = dpi->top = 0; - dpi->width = wi->current_x; - dpi->height = wi->current_y; + dpi->left = dpi->top = -margin; + dpi->width = wi->current_x + 2 * margin; + dpi->height = wi->current_y + 2 * margin; +} + +bool LinkGraphOverlay::CacheStillValid() const +{ + if (this->window->viewport) { + const ViewPort *vp = this->window->viewport; + Rect region { vp->virtual_left, vp->virtual_top, + vp->virtual_left + vp->virtual_width, vp->virtual_top + vp->virtual_height }; + return (region.left >= this->cached_region.left && + region.right <= this->cached_region.right && + region.top >= this->cached_region.top && + region.bottom <= this->cached_region.bottom); + } else { + return true; + } } /** * Rebuild the cache and recalculate which links and stations to be shown. */ -void LinkGraphOverlay::RebuildCache() +void LinkGraphOverlay::RebuildCache(bool incremental) { - this->cached_links.clear(); - this->cached_stations.clear(); + if (!incremental) { + this->cached_links.clear(); + this->cached_stations.clear(); + } if (this->company_mask == 0) return; DrawPixelInfo dpi; - this->GetWidgetDpi(&dpi); + bool cache_all = false; + if (this->window->viewport) { + const ViewPort *vp = this->window->viewport; + const int pixel_margin = 256; + const int vp_margin = ScaleByZoom(pixel_margin, vp->zoom); + this->GetWidgetDpi(&dpi, pixel_margin); + this->cached_region = Rect({ vp->virtual_left - vp_margin, vp->virtual_top - vp_margin, + vp->virtual_left + vp->virtual_width + vp_margin, vp->virtual_top + vp->virtual_height + vp_margin }); + } else { + this->GetWidgetDpi(&dpi); + cache_all = true; + } struct LinkCacheItem { Point from_pt; @@ -66,8 +97,23 @@ void LinkGraphOverlay::RebuildCache() LinkProperties prop; }; btree::btree_map, LinkCacheItem> link_cache_map; + std::vector incremental_station_exclude; + std::vector> incremental_link_exclude; - auto AddLinks = [&](const Station *from, const Station *to, Point from_pt, Point to_pt) { + if (incremental) { + incremental_station_exclude.reserve(this->cached_stations.size()); + for (StationSupplyList::iterator i(this->cached_stations.begin()); i != this->cached_stations.end(); ++i) { + incremental_station_exclude.push_back(i->id); + } + std::sort(incremental_station_exclude.begin(), incremental_station_exclude.end()); + incremental_link_exclude.reserve(this->cached_links.size()); + for (LinkList::iterator i(this->cached_links.begin()); i != this->cached_links.end(); ++i) { + incremental_link_exclude.push_back(std::make_pair(i->from_id, i->to_id)); + } + std::sort(incremental_link_exclude.begin(), incremental_link_exclude.end()); + } + + auto AddLinks = [&](const Station *from, const Station *to, Point from_pt, Point to_pt, btree::btree_map, LinkCacheItem>::iterator insert_iter) { LinkCacheItem *item = nullptr; CargoID c; FOR_EACH_SET_CARGO_ID(c, this->cargo_mask) { @@ -81,11 +127,10 @@ void LinkGraphOverlay::RebuildCache() ConstEdge edge = lg[ge.node][to->goods[c].node]; if (edge.Capacity() > 0) { if (!item) { - item = &link_cache_map[std::make_pair(from->index, to->index)]; - if (item->prop.capacity == 0) { - item->from_pt = from_pt; - item->to_pt = to_pt; - } + auto iter = link_cache_map.insert(insert_iter, std::make_pair(std::make_pair(from->index, to->index), LinkCacheItem())); + item = &(iter->second); + item->from_pt = from_pt; + item->to_pt = to_pt; } this->AddStats(lg.Monthly(edge.Capacity()), lg.Monthly(edge.Usage()), ge.flows.GetFlowVia(to->index), from->owner == OWNER_NONE || to->owner == OWNER_NONE, @@ -98,6 +143,8 @@ void LinkGraphOverlay::RebuildCache() FOR_ALL_STATIONS(sta) { if (sta->rect.IsEmpty()) continue; + if (incremental && std::binary_search(incremental_station_exclude.begin(), incremental_station_exclude.end(), sta->index)) continue; + Point pta = this->GetStationMiddle(sta); StationID from = sta->index; @@ -114,9 +161,8 @@ void LinkGraphOverlay::RebuildCache() for (ConstEdgeIterator i = from_node.Begin(); i != from_node.End(); ++i) { StationID to = lg[i->first].Station(); assert(from != to); - if (!Station::IsValidID(to) || link_cache_map.count(std::make_pair(from, to))) { - continue; - } + if (!Station::IsValidID(to)) continue; + const Station *stb = Station::Get(to); assert(sta != stb); @@ -124,19 +170,28 @@ void LinkGraphOverlay::RebuildCache() if (stb->owner != OWNER_NONE && sta->owner != OWNER_NONE && !HasBit(this->company_mask, stb->owner)) continue; if (stb->rect.IsEmpty()) continue; + if (incremental && std::binary_search(incremental_station_exclude.begin(), incremental_station_exclude.end(), to)) continue; + if (incremental && std::binary_search(incremental_link_exclude.begin(), incremental_link_exclude.end(), std::make_pair(from, to))) continue; + + auto key = std::make_pair(from, to); + auto iter = link_cache_map.lower_bound(key); + if (iter != link_cache_map.end() && !(link_cache_map.key_comp()(key, iter->first))) { + continue; + } + Point ptb = this->GetStationMiddle(stb); - if (!this->IsLinkVisible(pta, ptb, &dpi)) continue; + if (!cache_all && !this->IsLinkVisible(pta, ptb, &dpi)) continue; - AddLinks(sta, stb, pta, ptb); + AddLinks(sta, stb, pta, ptb, iter); } } - if (this->IsPointVisible(pta, &dpi)) { + if (cache_all || this->IsPointVisible(pta, &dpi)) { this->cached_stations.push_back({ from, supply, pta }); } } - this->cached_links.reserve(link_cache_map.size()); + this->cached_links.reserve(this->cached_links.size() + link_cache_map.size()); for (auto &iter : link_cache_map) { this->cached_links.push_back({ iter.first.first, iter.first.second, iter.second.from_pt, iter.second.to_pt, iter.second.prop }); } diff --git a/src/linkgraph/linkgraph_gui.h b/src/linkgraph/linkgraph_gui.h index a57e573989..bffcc1b913 100644 --- a/src/linkgraph/linkgraph_gui.h +++ b/src/linkgraph/linkgraph_gui.h @@ -68,7 +68,8 @@ public: window(w), widget_id(wid), cargo_mask(cargo_mask), company_mask(company_mask), scale(scale) {} - void RebuildCache(); + void RebuildCache(bool incremental = false); + bool CacheStillValid() const; void Draw(const DrawPixelInfo *dpi); void SetCargoMask(CargoTypes cargo_mask); void SetCompanyMask(uint32 company_mask); @@ -86,6 +87,7 @@ protected: uint32 company_mask; ///< Bitmask of companies to be displayed. LinkList cached_links; ///< Cache for links to reduce recalculation. StationSupplyList cached_stations; ///< Cache for stations to be drawn. + Rect cached_region; ///< Region covered by cached_links and cached_stations. uint scale; ///< Width of link lines. uint64 last_update_number = 0; ///< Last window update number @@ -97,7 +99,7 @@ protected: void DrawContent(Point pta, Point ptb, const LinkProperties &cargo) const; bool IsLinkVisible(Point pta, Point ptb, const DrawPixelInfo *dpi, int padding = 0) const; bool IsPointVisible(Point pt, const DrawPixelInfo *dpi, int padding = 0) const; - void GetWidgetDpi(DrawPixelInfo *dpi) const; + void GetWidgetDpi(DrawPixelInfo *dpi, uint margin = 0) const; static void AddStats(uint new_cap, uint new_usg, uint new_flow, bool new_shared, LinkProperties &cargo); static void DrawVertex(int x, int y, int size, int colour, int border_colour); diff --git a/src/main_gui.cpp b/src/main_gui.cpp index a36ac23789..d227ac208d 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -156,7 +156,7 @@ bool DoZoomInOutWindow(ZoomStateChange how, Window *w) /* Update the windows that have zoom-buttons to perhaps disable their buttons */ w->InvalidateData(); if (how != ZOOM_NONE) { - RebuildViewportOverlay(w); + RebuildViewportOverlay(w, false); } return true; } diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index 2853bf25fc..283dd01baa 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -617,8 +617,6 @@ void SmallMapWindow::SetZoomLevel(ZoomLevelChange change, const Point *zoom_pt) Point new_tile = this->PixelToTile(zoom_pt->x, zoom_pt->y, &sub); this->SetNewScroll(this->scroll_x + (tile.x - new_tile.x) * TILE_SIZE, this->scroll_y + (tile.y - new_tile.y) * TILE_SIZE, sub); - } else if (this->map_type == SMT_LINKSTATS) { - this->overlay->RebuildCache(); } this->SetWidgetDisabledState(WID_SM_ZOOM_IN, this->zoom == zoomlevels[MIN_ZOOM_INDEX]); this->SetWidgetDisabledState(WID_SM_ZOOM_OUT, this->zoom == zoomlevels[MAX_ZOOM_INDEX]); @@ -1538,7 +1536,6 @@ void SmallMapWindow::SetNewScroll(int sx, int sy, int sub) this->scroll_x = sx; this->scroll_y = sy; this->subscroll = sub; - if (this->map_type == SMT_LINKSTATS) this->overlay->RebuildCache(); } /* virtual */ void SmallMapWindow::OnScroll(Point delta) diff --git a/src/smallmap_gui.h b/src/smallmap_gui.h index 71e7b54522..ac2e7db41c 100644 --- a/src/smallmap_gui.h +++ b/src/smallmap_gui.h @@ -91,7 +91,7 @@ protected: static const uint LEGEND_BLOB_WIDTH = 8; ///< Width of the coloured blob in front of a line text in the #WID_SM_LEGEND widget. static const uint INDUSTRY_MIN_NUMBER_OF_COLUMNS = 2; ///< Minimal number of columns in the #WID_SM_LEGEND widget for the #SMT_INDUSTRY legend. - static const uint FORCE_REFRESH_PERIOD = 0x1F; ///< map is redrawn after that many ticks + static const uint FORCE_REFRESH_PERIOD = 0x5F; ///< map is redrawn after that many ticks static const uint BLINK_PERIOD = 0x0F; ///< highlight blinking interval uint min_number_of_columns; ///< Minimal number of columns in legends. diff --git a/src/viewport.cpp b/src/viewport.cpp index 66477c223f..d49e6c94c0 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -429,7 +429,7 @@ static void DoSetViewportPosition(const Window *w, int left, int top, int width, } } -static void SetViewportPosition(Window *w, int x, int y) +static void SetViewportPosition(Window *w, int x, int y, bool force_update_overlay) { ViewPort *vp = w->viewport; int old_left = vp->virtual_left; @@ -440,6 +440,8 @@ static void SetViewportPosition(Window *w, int x, int y) vp->virtual_left = x; vp->virtual_top = y; + if (force_update_overlay || IsViewportOverlayOutsideCachedRegion(w)) RebuildViewportOverlay(w, true); + /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower) * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL) */ @@ -2823,7 +2825,7 @@ void UpdateViewportPosition(Window *w) w->viewport->scrollpos_x = pt.x; w->viewport->scrollpos_y = pt.y; - SetViewportPosition(w, pt.x, pt.y); + SetViewportPosition(w, pt.x, pt.y, false); } else { /* Ensure the destination location is within the map */ ClampViewportToMap(vp, w->viewport->dest_scrollpos_x, w->viewport->dest_scrollpos_y); @@ -2850,8 +2852,7 @@ void UpdateViewportPosition(Window *w) if (_scrolling_viewport == w) UpdateActiveScrollingViewport(w); - SetViewportPosition(w, w->viewport->scrollpos_x, w->viewport->scrollpos_y); - if (update_overlay) RebuildViewportOverlay(w); + SetViewportPosition(w, w->viewport->scrollpos_x, w->viewport->scrollpos_y, update_overlay); } } @@ -3438,13 +3439,24 @@ bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click) return result; } -void RebuildViewportOverlay(Window *w) +void RebuildViewportOverlay(Window *w, bool incremental) { if (w->viewport->overlay != NULL && w->viewport->overlay->GetCompanyMask() != 0 && w->viewport->overlay->GetCargoMask() != 0) { - w->viewport->overlay->RebuildCache(); - w->SetDirty(); + w->viewport->overlay->RebuildCache(incremental); + if (!incremental) w->SetDirty(); + } +} + +bool IsViewportOverlayOutsideCachedRegion(Window *w) +{ + if (w->viewport->overlay != NULL && + w->viewport->overlay->GetCompanyMask() != 0 && + w->viewport->overlay->GetCargoMask() != 0) { + return !w->viewport->overlay->CacheStillValid(); + } else { + return false; } } @@ -3477,7 +3489,7 @@ bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant) if (instant) { w->viewport->scrollpos_x = pt.x; w->viewport->scrollpos_y = pt.y; - RebuildViewportOverlay(w); + RebuildViewportOverlay(w, true); } w->viewport->dest_scrollpos_x = pt.x; diff --git a/src/viewport_func.h b/src/viewport_func.h index d7d0159e3f..161aa9ebee 100644 --- a/src/viewport_func.h +++ b/src/viewport_func.h @@ -80,7 +80,8 @@ bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant = false); void UpdateActiveScrollingViewport(Window *w); -void RebuildViewportOverlay(Window *w); +void RebuildViewportOverlay(Window *w, bool incremental); +bool IsViewportOverlayOutsideCachedRegion(Window *w); bool ScrollMainWindowToTile(TileIndex tile, bool instant = false); bool ScrollMainWindowTo(int x, int y, int z = -1, bool instant = false);