tibia-rme/source/map_display.h
Eduardo Dantas 1fbc5b9113
perf: otbm load save and preview/map renderization (#188)
Improve OTBM load/save and map view performance

This change improves Remere's Map Editor performance in OTBM load/save paths,
object allocation, binary serialization, tile lookup, and idle map rendering.

Measured impact:

Object pool allocation:
- Slab refill events reduced from 17,830 to 2,230, about 87.5% fewer refills.
- 48-byte class refills reduced from 4,276 to 535, about 87.5% fewer refills.
- 64-byte class refills reduced from 5 to 1, about 80.0% fewer refills.
- 128-byte class refills reduced from 8,776 to 1,097, about 87.5% fewer refills.
- 1024-byte class refills reduced from 4,773 to 597, about 87.5% fewer refills.
- rme::allocatePooledObject sampled CPU share reduced from 20.01% to 14.62%,
  about 26.9% lower sampled share.
- Total pooled allocation calls stayed at 42,559,971 for the measured workload.
- Heap fallback allocations stayed at 0, confirming the hot load path remains pooled.

Latest mixed load/save profile:
- GUI::LoadMap remained the dominant sampled cost at 67.02%.
- IOMapOTBM::loadMap accounted for 60.23% total sampled CPU, with 56.34% in
  the inner load body.
- GUI::SaveMap and Editor::saveMap accounted for 28.16%.
- IOMapOTBM::saveMap accounted for 27.63%.
- BaseMap::forEachTileLocation during save accounted for 23.01%.
- Tile::Tile accounted for 11.53%.
- Tile::addLoadedItem accounted for 8.15%.
- Item::Create accounted for 6.99%.
- QTreeNode::createFloor accounted for 6.71%.
- BinaryNode::advance and BinaryNode::load accounted for 5.37% and 3.81%.

Map view idle and preview rendering:
- Static map-view sampled CPU dropped from 139,767 sampled units to 126 sampled
  units after overlay-only refresh reuse.
- MapCanvas::OnPaint dropped from 46,229 sampled units and 32.20% to 19 sampled
  units and 15.08%.
- GLRenderer::flushCommands dropped from 24,321 sampled units and 16.94% to
  4 sampled units and 3.17%.
- Performance stats refresh changed from a 16 ms scene-dirty timer to a 500 ms
  overlay-only timer.
- Show Preview changed from a 16 ms scene-dirty timer to a 250 ms scene-dirty
  timer.
- Position indicator keeps the 16 ms scene-dirty timer because it is expected
  to animate smoothly while active.

Main changes:
- Added cached floor and tile lookup while loading OTBM map data and spawn files.
- Added direct TileLocation assignment for parser paths that already resolved
  the destination location.
- Added BaseMap::forEachTileLocation for direct save traversal of existing tile
  locations.
- Added a small-object slab allocator for hot Item, Tile, and Floor allocations.
- Added pool owner-thread binding and diagnostics for allocation validation.
- Increased slab sizing to reduce refill pressure in large-map loads.
- Improved binary node writing by batching raw bytes and avoiding redundant cache
  renewal checks.
- Avoided rewriting XML sidecar files when serialized content only differs by
  line endings.
- Fixed invalid ground serialization so placeholder ground id 0 no longer drops
  the rest of the tile contents during save.
- Split map canvas refresh into scene-dirty and overlay-only paths.
- Adjusted animation timer behavior for position indicator, Show Preview, and
  performance stats.
- Scaled tooltip rendering with map zoom, clamped to 55% minimum.
- Kept review and Sonar cleanups away from hot path regressions with targeted
  NOSONAR annotations or FORCEINLINE where needed.
- Added AGENTS.md guidance for future Git, build, and PCH discipline.

Notes:
- The Visual Studio captures are sampling profiles, so percentages represent CPU
  sample share, not direct wall-clock speedup.
- The latest profile is a mixed interaction profile, not a strict load-only or
  save-only benchmark.
- The allocator counters are the strongest before/after measurement in this
  change.
- No OTBM format or map semantics are intended to change.
2026-05-22 15:14:34 -03:00

233 lines
6.3 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Remere's Map Editor is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////
#ifndef RME_DISPLAY_WINDOW_H_
#define RME_DISPLAY_WINDOW_H_
#include "action.h"
#include "tile.h"
#include "monster.h"
#include "npc.h"
class Item;
class Monster;
class Npc;
class MapWindow;
class MapPopupMenu;
class AnimationTimer;
class MapDrawer;
class MapCanvas : public wxGLCanvas {
public:
MapCanvas(MapWindow* parent, Editor &editor, int* attriblist);
virtual ~MapCanvas();
void Reset();
#ifdef __LINUX__
bool DispatchMenuShortcut(wxKeyEvent &event);
#endif
// All events
void OnPaint(wxPaintEvent &event);
void OnEraseBackground(wxEraseEvent &event) { }
void OnMouseMove(wxMouseEvent &event);
void OnMouseLeftRelease(wxMouseEvent &event);
void OnMouseLeftClick(wxMouseEvent &event);
void OnMouseLeftDoubleClick(wxMouseEvent &event);
void OnMouseCenterClick(wxMouseEvent &event);
void OnMouseCenterRelease(wxMouseEvent &event);
void OnMouseRightClick(wxMouseEvent &event);
void OnMouseRightRelease(wxMouseEvent &event);
void OnKeyDown(wxKeyEvent &event);
void OnKeyUp(wxKeyEvent &event);
void OnWheel(wxMouseEvent &event);
void OnGainMouse(wxMouseEvent &event);
void OnLoseMouse(wxMouseEvent &event);
// Mouse events handlers (called by the above)
void OnMouseActionRelease(wxMouseEvent &event);
void OnMouseActionClick(wxMouseEvent &event);
void OnMouseCameraClick(wxMouseEvent &event);
void OnMouseCameraRelease(wxMouseEvent &event);
void OnMousePropertiesClick(wxMouseEvent &event);
void OnMousePropertiesRelease(wxMouseEvent &event);
//
void OnCut(wxCommandEvent &event);
void OnCopy(wxCommandEvent &event);
void OnCopyPosition(wxCommandEvent &event);
void OnCopyItemId(wxCommandEvent &event);
void OnCopyName(wxCommandEvent &event);
void OnBrowseTile(wxCommandEvent &event);
void OnPaste(wxCommandEvent &event);
void OnDelete(wxCommandEvent &event);
// ----
void OnGotoDestination(wxCommandEvent &event);
void OnCopyDestination(wxCommandEvent &event);
void OnRotateItem(wxCommandEvent &event);
void OnSwitchDoor(wxCommandEvent &event);
// ----
void OnSelectRAWBrush(wxCommandEvent &event);
void OnSelectGroundBrush(wxCommandEvent &event);
void OnSelectDoodadBrush(wxCommandEvent &event);
void OnSelectDoorBrush(wxCommandEvent &event);
void OnSelectWallBrush(wxCommandEvent &event);
void OnSelectCarpetBrush(wxCommandEvent &event);
void OnSelectTableBrush(wxCommandEvent &event);
void OnSelectMonsterBrush(wxCommandEvent &event);
void OnSelectSpawnBrush(wxCommandEvent &event);
void OnSelectNpcBrush(wxCommandEvent &event);
void OnSelectSpawnNpcBrush(wxCommandEvent &event);
void OnSelectHouseBrush(wxCommandEvent &event);
void OnSelectMoveTo(wxCommandEvent &event);
// ---
void OnProperties(wxCommandEvent &event);
virtual void Refresh();
virtual void ScreenToMap(int screen_x, int screen_y, int* map_x, int* map_y);
void MouseToMap(int* map_x, int* map_y) {
ScreenToMap(cursor_x, cursor_y, map_x, map_y);
}
virtual void GetScreenCenter(int* map_x, int* map_y);
void StartPasting();
void EndPasting();
void EnterSelectionMode();
void EnterDrawingMode();
virtual void UpdatePositionStatus(int x = -1, int y = -1);
virtual void UpdateZoomStatus();
void ChangeFloor(int new_floor);
int GetFloor() const noexcept {
return floor;
}
double GetZoom() const noexcept {
return zoom;
}
virtual void SetZoom(double value);
virtual void GetViewBox(int* view_scroll_x, int* view_scroll_y, int* screensize_x, int* screensize_y) const;
MapWindow* GetMapWindow() const;
Position GetCursorPosition() const;
void ShowPositionIndicator(const Position &position);
void TakeScreenshot(wxFileName path, wxString format);
protected:
void getTilesToDraw(int mouse_map_x, int mouse_map_y, int floor, PositionVector* tilestodraw, PositionVector* tilestoborder, bool fill = false);
bool floodFill(Map* map, const Position &center, int x, int y, GroundBrush* brush, PositionVector* positions);
private:
void QueueRefresh(bool mark_scene_dirty);
enum {
BLOCK_SIZE = 64
};
inline int getFillIndex(int x, int y) const noexcept {
return ((y % BLOCK_SIZE) * BLOCK_SIZE) + (x % BLOCK_SIZE);
}
static bool processed[BLOCK_SIZE * BLOCK_SIZE];
Tile* lastTile;
Editor &editor;
MapDrawer* drawer;
int keyCode;
// View related
int floor;
double zoom;
int cursor_x;
int cursor_y;
bool dragging;
bool boundbox_selection;
bool screendragging;
bool isPasting() const;
bool drawing;
bool dragging_draw;
bool replace_dragging;
uint8_t* screenshot_buffer;
int drag_start_x;
int drag_start_y;
int drag_start_z;
int last_cursor_map_x;
int last_cursor_map_y;
int last_cursor_map_z;
int last_click_map_x;
int last_click_map_y;
int last_click_map_z;
int last_click_abs_x;
int last_click_abs_y;
int last_click_x;
int last_click_y;
int last_mmb_click_x;
int last_mmb_click_y;
int view_scroll_x;
int view_scroll_y;
uint32_t current_house_id;
wxStopWatch refresh_watch;
MapPopupMenu* popup_menu;
AnimationTimer* animation_timer;
friend class MapDrawer;
friend class AnimationTimer;
DECLARE_EVENT_TABLE()
};
// Right-click popup menu
class MapPopupMenu : public wxMenu {
public:
MapPopupMenu(Editor &editor);
virtual ~MapPopupMenu();
void Update();
protected:
Editor &editor;
};
class AnimationTimer : public wxTimer {
public:
AnimationTimer(MapCanvas* canvas);
void Notify();
void StartRefresh(int interval, bool mark_scene_dirty);
void Stop();
private:
MapCanvas* map_canvas;
bool started = false;
bool mark_scene_dirty = false;
int interval = 0;
};
#endif