<URL: http://bugs.freeciv.org/Ticket/Display.html?id=40717 >

The point of these patches is to remove cpu expensive
redraws caused by calls to update_map_canvas_visible()
in the functions:

  - popupinfo_popdown_callback()
  - cancel_tile_hiliting()
  - release_right_button()

These were making selection rectangle and popit use
rather sluggish (of course this would only be
noticeable on machines as slow as or slower than mine).

Another benefit of the cleanup is to remove the map
size dependent space cost of goto line drawing data
structures in client/goto.c.

For both problems, hash tables are used for efficient
data access and minimal memory usage. As such, for
S2_1 this patch is dependent on the trunk hash tables
(40716).


-----------------------------------------------------------------------
クソッ、また誰かが地図に落書きしてしまった。
commit e784e6db696301d0d35fc21443430bc8b38e866f
Author: Madeline Book <madeline.b...@gmail.com>
Date:   Fri Feb 13 17:39:22 2009 -0500

    Mapdeco cleanup.
---
 client/goto.c                |  111 +--------------
 client/goto.h                |    1 -
 client/gui-gtk-2.0/mapctrl.c |   32 +---
 client/mapctrl_common.c      |   30 ++--
 client/mapview_common.c      |  328 ++++++++++++++++++++++++++++++++++++++----
 client/mapview_common.h      |   29 +++--
 client/packhand.c            |    2 +-
 client/tilespec.c            |    8 +-
 8 files changed, 349 insertions(+), 192 deletions(-)

diff --git a/client/goto.c b/client/goto.c
index 20eab18..9916e3d 100644
--- a/client/goto.c
+++ b/client/goto.c
@@ -40,21 +40,6 @@
 #define PATH_LOG_LEVEL          LOG_DEBUG
 #define PACKET_LOG_LEVEL        LOG_DEBUG
 
-/* For each tile and each direction we store the number of lines going out 
- * of the tile in this direction.  Since each line is undirected, we only 
- * store the 4 lower-numbered directions for each tile; the 4 upper-numbered
- * directions are stored as reverses from the target tile.
- * Notes: 1. This assumes that 
- * - there are 8 directions
- * - out of every two opposite directions (like NORTH and SOUTH) one and 
- *   only one has number less than 4
- * 2. There _can_ be more than one line drawn between two tiles, because of 
- * the waypoints. */
-struct goto_tiles {
-  unsigned char drawn[4];
-};
-static struct goto_tiles *tiles = NULL;
-
 /*
  * The whole path is separated by waypoints into parts.  Each part has its
  * own starting position and requires its own map.  When the unit is unable
@@ -97,10 +82,6 @@ struct goto_map {
 
 static struct goto_map_list *goto_maps = NULL;
 
-#define DRAWN(_tile, dir) (tiles[tile_index(_tile)].drawn[dir])
-
-static void increment_drawn(struct tile *src_tile, enum direction8 dir);
-static void decrement_drawn(struct tile *src_tile, enum direction8 dir);
 static void reset_last_part(struct goto_map *goto_map);
 static void remove_last_part(struct goto_map *goto_map);
 
@@ -148,8 +129,6 @@ void init_client_goto(void)
   free_client_goto();
 
   goto_maps = goto_map_list_new();
-
-  tiles = fc_calloc(MAP_INDEX_SIZE, sizeof(*tiles));
 }
 
 /********************************************************************** 
@@ -164,11 +143,6 @@ void free_client_goto(void)
     goto_map_list_free(goto_maps);
     goto_maps = NULL;
   }
-
-  if (NULL != tiles) {
-    free(tiles);
-    tiles = NULL;
-  }
 }
 
 /**********************************************************************
@@ -224,7 +198,7 @@ static bool update_last_part(struct goto_map *goto_map,
       struct pf_position *a = &p->path->positions[i];
 
       if (is_valid_dir(a->dir_to_next_pos)) {
-	decrement_drawn(a->tile, a->dir_to_next_pos);
+        mapdeco_remove_gotoline(a->tile, a->dir_to_next_pos);
       } else {
 	assert(i < p->path->length - 1
 	       && a->tile == p->path->positions[i + 1].tile);
@@ -241,7 +215,7 @@ static bool update_last_part(struct goto_map *goto_map,
     struct pf_position *a = &new_path->positions[i];
 
     if (is_valid_dir(a->dir_to_next_pos)) {
-      increment_drawn(a->tile, a->dir_to_next_pos);
+      mapdeco_add_gotoline(a->tile, a->dir_to_next_pos);
     } else {
       assert(i < new_path->length - 1
 	     && a->tile == new_path->positions[i + 1].tile);
@@ -1231,87 +1205,6 @@ void send_goto_route(void)
   } goto_map_unit_iterate_end;
 }
 
-/* ================= drawn functions ============================ */
-
-/********************************************************************** 
-  Every line segment has 2 ends; we only keep track of it at one end
-  (the one from which dir i <4). This function returns pointer to the
-  correct char. This function is for internal use only. Use get_drawn
-  when in doubt.
-***********************************************************************/
-static unsigned char *get_drawn_char(struct tile *ptile, enum direction8 dir)
-{
-  struct tile *tile1;
-
-  tile1 = mapstep(ptile, dir);
-
-  if (dir >= 4) {
-    ptile = tile1;
-    dir = DIR_REVERSE(dir);
-  }
-
-  return &DRAWN(ptile, dir);
-}
-
-/**************************************************************************
-  Increments the number of segments at the location, and draws the
-  segment if necessary.
-**************************************************************************/
-static void increment_drawn(struct tile *src_tile, enum direction8 dir)
-{
-  unsigned char *count = get_drawn_char(src_tile, dir);
-
-  freelog(LOG_DEBUG, "increment_drawn(src=(%d,%d) dir=%s)",
-          TILE_XY(src_tile), dir_get_name(dir));
-
-  if (*count < 255) {
-    (*count)++;
-  } else {
-    /* don't overflow unsigned char. */
-    assert(*count < 255);
-  }
-
-  if (*count == 1) {
-    draw_segment(src_tile, dir);
-  }
-}
-
-/**************************************************************************
-  Decrements the number of segments at the location, and clears the
-  segment if necessary.
-**************************************************************************/
-static void decrement_drawn(struct tile *src_tile, enum direction8 dir)
-{
-  unsigned char *count = get_drawn_char(src_tile, dir);
-
-  freelog(LOG_DEBUG, "decrement_drawn(src=(%d,%d) dir=%s)",
-          TILE_XY(src_tile), dir_get_name(dir));
-
-  if (*count > 0) {
-    (*count)--;
-  } else {
-    /* don't underflow unsigned char. */
-    assert(*count > 0);
-  }
-
-  if (*count == 0) {
-    undraw_segment(src_tile, dir);
-  }
-}
-
-/****************************************************************************
-  Return TRUE if there is a line drawn from (x,y) in the given direction.
-  This is used by mapview to determine whether to draw a goto line.
-****************************************************************************/
-bool is_drawn_line(struct tile *ptile, int dir)
-{
-  if (!mapstep(ptile, dir)) {
-    return 0;
-  }
-
-  return (*get_drawn_char(ptile, dir) != 0);
-}
-
 /**************************************************************************
   Find the path to the nearest (fastest to reach) allied city for the
   unit, or NULL if none is reachable.
diff --git a/client/goto.h b/client/goto.h
index 0e1cb11..f390dd3 100644
--- a/client/goto.h
+++ b/client/goto.h
@@ -32,7 +32,6 @@ bool goto_pop_waypoint(void);
 
 bool is_valid_goto_destination(const struct tile *ptile);
 bool is_valid_goto_draw_line(struct tile *dest_tile);
-bool is_drawn_line(struct tile *dest_tile, int dir);
  
 void request_orders_cleared(struct unit *punit);
 void send_goto_path(struct unit *punit, struct pf_path *path,
diff --git a/client/gui-gtk-2.0/mapctrl.c b/client/gui-gtk-2.0/mapctrl.c
index f2ffeb6..f1e5023 100644
--- a/client/gui-gtk-2.0/mapctrl.c
+++ b/client/gui-gtk-2.0/mapctrl.c
@@ -113,12 +113,8 @@ static void popupinfo_positioning_callback(GtkWidget *w, GtkAllocation *alloc,
 static void popit(GdkEventButton *event, struct tile *ptile)
 {
   GtkWidget *p;
-  struct tile *cross_list[2 + 1];
-  struct tile **cross_head = cross_list;
-  int i;
   static struct tmousepos mousepos;
   struct unit *punit;
-  bool is_orders;
 
   if (TILE_UNKNOWN != client_tile_get_known(ptile)) {
     p=gtk_window_new(GTK_WINDOW_POPUP);
@@ -128,21 +124,14 @@ static void popit(GdkEventButton *event, struct tile *ptile)
 
     punit = find_visible_unit(ptile);
 
-    is_orders = show_unit_orders(punit);
-
-    if (punit && punit->goto_tile) {
-      map_deco[tile_index(punit->goto_tile)].crosshair++;
-      *cross_head = punit->goto_tile;
-      cross_head++;
+    if (punit) {
+      mapdeco_set_gotoroute(punit);
+      if (punit->goto_tile) {
+        mapdeco_set_crosshair(punit->goto_tile, TRUE);
+      }
     }
-    map_deco[tile_index(ptile)].crosshair++;
-    *cross_head = ptile;
-    cross_head++;
+    mapdeco_set_crosshair(ptile, TRUE);
 
-    *cross_head = NULL;
-    for (i = 0; cross_list[i]; i++) {
-      put_cross_overlay_tile(cross_list[i]);
-    }
     g_signal_connect(p, "destroy",
 		     G_CALLBACK(popupinfo_popdown_callback), NULL);
 
@@ -168,13 +157,8 @@ static void popit(GdkEventButton *event, struct tile *ptile)
 **************************************************************************/
 void popupinfo_popdown_callback(GtkWidget *w, gpointer data)
 {
-  /* We could just remove the crosshairs that we placed earlier, but
-   * this is easier. */
-  whole_map_iterate(ptile) {
-    map_deco[tile_index(ptile)].crosshair = 0;
-  } whole_map_iterate_end;
-
-  update_map_canvas_visible();
+  mapdeco_clear_crosshairs();
+  mapdeco_clear_gotoroutes();
 }
 
  /**************************************************************************
diff --git a/client/mapctrl_common.c b/client/mapctrl_common.c
index b74150a..448d23e 100644
--- a/client/mapctrl_common.c
+++ b/client/mapctrl_common.c
@@ -112,6 +112,9 @@ void anchor_selection_rectangle(int canvas_x, int canvas_y,
     on the map and hilited in the City List Window.
 
  Later, I'll want to add unit hiliting for mass orders.       -ali
+
+ NB: At the end of this function the current selection rectangle will be
+ erased (by being redrawn).
 **************************************************************************/
 static void define_tiles_within_rectangle(void)
 {
@@ -157,7 +160,7 @@ static void define_tiles_within_rectangle(void)
       if (NULL != tile_city(ptile)
           && tile_owner(ptile) == client.conn.playing) {
 	/* FIXME: handle rectangle_append */
-        map_deco[tile_index(ptile)].hilite = HILITE_CITY;
+        mapdeco_set_highlight(ptile, TRUE);
         tiles_hilited_cities = TRUE;
       }
       unit_list_iterate(ptile->units, punit) {
@@ -181,6 +184,9 @@ static void define_tiles_within_rectangle(void)
   }
   unit_list_free(units);
 
+  /* Clear previous rectangle. */
+  draw_selection_rectangle(rec_corner_x, rec_corner_y, rec_w, rec_h);
+
   /* Hilite in City List Window */
   if (tiles_hilited_cities) {
     hilite_cities_from_canvas();      /* cityrep.c */
@@ -281,7 +287,9 @@ void cancel_selection_rectangle(void)
   if (rectangle_active) {
     rectangle_active = FALSE;
     rbutton_down = FALSE;
-    dirty_rect(rec_corner_x, rec_corner_y, rec_w, rec_h);
+
+    /* Erase the previously drawn selection rectangle. */
+    draw_selection_rectangle(rec_corner_x, rec_corner_y, rec_w, rec_h);
   }
 }
 
@@ -290,7 +298,7 @@ void cancel_selection_rectangle(void)
 **************************************************************************/
 bool is_city_hilited(struct city *pcity)
 {
-  return map_deco[tile_index(pcity->tile)].hilite == HILITE_CITY;
+  return pcity && mapdeco_is_highlight_set(city_tile(pcity));
 }
 
 /**************************************************************************
@@ -300,12 +308,7 @@ void cancel_tile_hiliting(void)
 {
   if (tiles_hilited_cities)  {
     tiles_hilited_cities = FALSE;
-
-    whole_map_iterate(ptile) {
-      map_deco[tile_index(ptile)].hilite = HILITE_NONE;
-    } whole_map_iterate_end;
-
-    update_map_canvas_visible();
+    mapdeco_clear_highlights();
   }
 }
 
@@ -317,7 +320,6 @@ void release_right_button(int canvas_x, int canvas_y)
 {
   if (rectangle_active) {
     define_tiles_within_rectangle();
-    update_map_canvas_visible();
   } else {
     recenter_button_pressed(canvas_x, canvas_y);
   }
@@ -332,22 +334,20 @@ void toggle_tile_hilite(struct tile *ptile)
 {
   struct city *pcity = tile_city(ptile);
 
-  if (map_deco[tile_index(ptile)].hilite == HILITE_CITY) {
-    map_deco[tile_index(ptile)].hilite = HILITE_NONE;
+  if (mapdeco_is_highlight_set(ptile)) {
+    mapdeco_set_highlight(ptile, FALSE);
     if (pcity) {
       toggle_city_hilite(pcity, FALSE); /* cityrep.c */
     }
   }
   else if (NULL != pcity && city_owner(pcity) == client.conn.playing) {
-    map_deco[tile_index(ptile)].hilite = HILITE_CITY;
+    mapdeco_set_highlight(ptile, TRUE);
     tiles_hilited_cities = TRUE;
     toggle_city_hilite(pcity, TRUE);
   }
   else  {
     return;
   }
-
-  refresh_tile_mapcanvas(ptile, FALSE, TRUE);
 }
 
 /**************************************************************************
diff --git a/client/mapview_common.c b/client/mapview_common.c
index 5d12cae..35f6493 100644
--- a/client/mapview_common.c
+++ b/client/mapview_common.c
@@ -19,6 +19,7 @@
 
 /* utility */
 #include "fcintl.h"
+#include "hash.h"
 #include "log.h"
 #include "rand.h"
 #include "support.h"
@@ -45,7 +46,14 @@
 #include "overview_common.h"
 #include "tilespec.h"
 
-struct mapview_decoration *map_deco;
+struct hash_table *mapdeco_highlight_table;
+struct hash_table *mapdeco_crosshair_table;
+
+struct gotoline_counter {
+  int line_count[DIR8_COUNT];
+};
+struct hash_table *mapdeco_gotoline_table;
+
 struct view mapview;
 bool can_slide = TRUE;
 
@@ -1304,8 +1312,8 @@ void update_map_canvas(int canvas_x, int canvas_y, int width, int height)
       continue;
     }
     adjc_dir_iterate(ptile, adjc_tile, dir) {
-      if (is_drawn_line(ptile, dir)) {
-	draw_segment(ptile, dir);
+      if (mapdeco_is_gotoline_set(ptile, dir)) {
+        draw_segment(ptile, dir);
       }
     } adjc_dir_iterate_end;
   } gui_rect_iterate_end;
@@ -1894,22 +1902,6 @@ void draw_segment(struct tile *src_tile, enum direction8 dir)
    * which fails when the size of the mapview approaches that of the map. */
 }
 
-/**************************************************************************
-  Remove the line from src_x, src_y in the given direction, and redraw
-  the change if necessary.
-**************************************************************************/
-void undraw_segment(struct tile *src_tile, enum direction8 dir)
-{
-  struct tile *dst_tile = mapstep(src_tile, dir);
-
-  if (is_drawn_line(src_tile, dir) || !dst_tile) {
-    assert(0);
-    return;
-  }
-  refresh_tile_mapcanvas(src_tile, FALSE, FALSE);
-  refresh_tile_mapcanvas(dst_tile, FALSE, FALSE);
-}
-
 /****************************************************************************
   This function is called to decrease a unit's HP smoothly in battle
   when combat_animation is turned on.
@@ -2120,7 +2112,7 @@ struct city *find_city_or_settler_near_tile(const struct tile *ptile,
        * city (perhaps an UNSEEN city) may be working it!
        */
       
-      if (map_deco[tile_index(pcity->tile)].hilite == HILITE_CITY) {
+      if (mapdeco_is_highlight_set(city_tile(pcity))) {
 	/* rule c */
 	return pcity;
       }
@@ -2547,16 +2539,300 @@ static bool can_do_cached_drawing(void)
   Called when we receive map dimensions.  It initialized the mapview
   decorations.
 **************************************************************************/
-void init_mapview_decorations(void)
+void mapdeco_init(void)
 {
   /* HACK: this must be called on a map_info packet. */
   mapview.can_do_cached_drawing = can_do_cached_drawing();
 
-  map_deco = fc_realloc(map_deco, MAP_INDEX_SIZE * sizeof(*map_deco));
-  whole_map_iterate(ptile) {
-    map_deco[tile_index(ptile)].hilite = HILITE_NONE;
-    map_deco[tile_index(ptile)].crosshair = 0;
-  } whole_map_iterate_end;
+  mapdeco_free();
+  mapdeco_highlight_table = hash_new(hash_fval_keyval, hash_fcmp_keyval);
+  mapdeco_crosshair_table = hash_new(hash_fval_keyval, hash_fcmp_keyval);
+  mapdeco_gotoline_table = hash_new(hash_fval_keyval, hash_fcmp_keyval);
+}
+
+/**************************************************************************
+  Free all memory used for map decorations.
+**************************************************************************/
+void mapdeco_free(void)
+{
+  if (mapdeco_highlight_table) {
+    hash_free(mapdeco_highlight_table);
+    mapdeco_highlight_table = NULL;
+  }
+  if (mapdeco_crosshair_table) {
+    hash_free(mapdeco_crosshair_table);
+    mapdeco_crosshair_table = NULL;
+  }
+  if (mapdeco_gotoline_table) {
+    hash_values_iterate(mapdeco_gotoline_table, pglc) {
+      free(pglc);
+    } hash_values_iterate_end;
+    hash_free(mapdeco_gotoline_table);
+    mapdeco_gotoline_table = NULL;
+  }
+}
+
+/**************************************************************************
+  Set the given tile's map decoration as either highlighted or not,
+  depending on the value of 'highlight'.
+**************************************************************************/
+void mapdeco_set_highlight(const struct tile *ptile, bool highlight)
+{
+  bool changed = FALSE;
+  if (!ptile || !mapdeco_highlight_table) {
+    return;
+  }
+
+  if (highlight) {
+    changed = hash_insert(mapdeco_highlight_table, ptile, NULL);
+  } else {
+    changed = hash_key_exists(mapdeco_highlight_table, ptile);
+    hash_delete_entry(mapdeco_highlight_table, ptile);
+  }
+
+  if (changed) {
+    /* FIXME: Remove the cast. */
+    refresh_tile_mapcanvas((struct tile *) ptile, TRUE, FALSE);
+  }
+}
+
+/**************************************************************************
+  Return TRUE if the given tile is highlighted.
+**************************************************************************/
+bool mapdeco_is_highlight_set(const struct tile *ptile)
+{
+  if (!ptile || !mapdeco_highlight_table) {
+    return FALSE;
+  }
+  return hash_key_exists(mapdeco_highlight_table, ptile);
+}
+
+/**************************************************************************
+  Clears all highlighting. Marks the previously highlighted tiles as
+  needing a mapview update.
+**************************************************************************/
+void mapdeco_clear_highlights(void)
+{
+  if (!mapdeco_highlight_table) {
+    return;
+  }
+
+  hash_keys_iterate(mapdeco_highlight_table, ptile) {
+    refresh_tile_mapcanvas(ptile, TRUE, FALSE);
+  } hash_keys_iterate_end;
+
+  hash_delete_all_entries(mapdeco_highlight_table);
+}
+
+/**************************************************************************
+  Marks the given tile as having a "crosshair" map decoration.
+**************************************************************************/
+void mapdeco_set_crosshair(const struct tile *ptile, bool crosshair)
+{
+  bool changed;
+
+  if (!mapdeco_crosshair_table || !ptile) {
+    return;
+  }
+
+  if (crosshair) {
+    changed = hash_insert(mapdeco_crosshair_table, ptile, NULL);
+  } else {
+    changed = hash_key_exists(mapdeco_crosshair_table, ptile);
+    hash_delete_entry(mapdeco_crosshair_table, ptile);
+  }
+
+  if (changed) {
+    /* FIXME: Remove the cast. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+  }
+}
+
+/**************************************************************************
+  Returns TRUE if there is a "crosshair" decoration set at the given tile.
+**************************************************************************/
+bool mapdeco_is_crosshair_set(const struct tile *ptile)
+{
+  if (!mapdeco_crosshair_table || !ptile) {
+    return FALSE;
+  }
+  return hash_key_exists(mapdeco_crosshair_table, ptile);
+}
+
+/**************************************************************************
+  Clears all previous set tile crosshair decorations. Marks the affected
+  tiles as needing a mapview update.
+**************************************************************************/
+void mapdeco_clear_crosshairs(void)
+{
+  if (!mapdeco_crosshair_table) {
+    return;
+  }
+
+  hash_keys_iterate(mapdeco_crosshair_table, ptile) {
+    refresh_tile_mapcanvas(ptile, FALSE, FALSE);
+  } hash_keys_iterate_end;
+
+  hash_delete_all_entries(mapdeco_crosshair_table);
+}
+
+/**************************************************************************
+  Add a goto line from the given tile 'ptile' in the direction 'dir'. If
+  there was no previously drawn line there, a mapview update is queued
+  for the source and destination tiles.
+**************************************************************************/
+void mapdeco_add_gotoline(const struct tile *ptile, enum direction8 dir)
+{
+  struct gotoline_counter *pglc;
+  const struct tile *ptile_dest;
+  bool changed;
+
+  if (!mapdeco_gotoline_table || !ptile
+      || !(0 <= dir && dir < DIR8_COUNT)) {
+    return;
+  }
+  ptile_dest = mapstep(ptile, dir);
+  if (!ptile_dest) {
+    return;
+  }
+
+  pglc = hash_lookup_data(mapdeco_gotoline_table, ptile);
+  if (!pglc) {
+    pglc = fc_calloc(1, sizeof(*pglc));
+    hash_insert(mapdeco_gotoline_table, ptile, pglc);
+  }
+  changed = (pglc->line_count[dir] < 1);
+  pglc->line_count[dir]++;
+
+  if (changed) {
+    /* FIXME: Remove cast. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    refresh_tile_mapcanvas((struct tile *) ptile_dest, FALSE, FALSE);
+  }
+}
+
+/**************************************************************************
+  Removes a goto line from the given tile 'ptile' going in the direction
+  'dir'. If this was the last line there, a mapview update is queued to
+  erase the drawn line.
+**************************************************************************/
+void mapdeco_remove_gotoline(const struct tile *ptile,
+                             enum direction8 dir)
+{
+  struct gotoline_counter *pglc;
+  bool changed = FALSE;
+
+  if (!mapdeco_gotoline_table || !ptile
+      || !(0 <= dir && dir < DIR8_COUNT)) {
+    return;
+  }
+
+  pglc = hash_lookup_data(mapdeco_gotoline_table, ptile);
+  if (!pglc) {
+    return;
+  }
+
+  pglc->line_count[dir]--;
+  if (pglc->line_count[dir] <= 0) {
+    pglc->line_count[dir] = 0;
+    changed = TRUE;
+  }
+
+  if (changed) {
+    /* FIXME: Remove the casts. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    ptile = mapstep(ptile, dir);
+    if (ptile != NULL) {
+      refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    }
+  }
+}
+
+/**************************************************************************
+  Set the map decorations for the given unit's goto route. A goto route
+  consists of one or more goto lines, with each line being from the center
+  of one tile to the center of another tile.
+**************************************************************************/
+void mapdeco_set_gotoroute(const struct unit *punit)
+{
+  const struct unit_order *porder;
+  const struct tile *ptile;
+  int i, ind;
+
+  if (!punit || !unit_tile(punit) || !unit_has_orders(punit)
+      || punit->orders.length < 1) {
+    return;
+  }
+
+  ptile = unit_tile(punit);
+
+  for (i = 0; ptile != NULL && i < punit->orders.length; i++) {
+    if (punit->orders.index + i >= punit->orders.length
+        && !punit->orders.repeat) {
+      break;
+    }
+
+    ind = (punit->orders.index + i) % punit->orders.length;
+    porder = &punit->orders.list[ind];
+    if (porder->order != ORDER_MOVE) {
+      break;
+    }
+
+    mapdeco_add_gotoline(ptile, porder->dir);
+    ptile = mapstep(ptile, porder->dir);
+  }
+}
+
+/**************************************************************************
+  Returns TRUE if a goto line should be drawn from the given tile in the
+  given direction.
+**************************************************************************/
+bool mapdeco_is_gotoline_set(const struct tile *ptile,
+                             enum direction8 dir)
+{
+  struct gotoline_counter *pglc;
+
+  if (!ptile || !(0 <= dir && dir < DIR8_COUNT)
+      || !mapdeco_gotoline_table) {
+    return FALSE;
+  }
+
+  pglc = hash_lookup_data(mapdeco_gotoline_table, ptile);
+  if (!pglc) {
+    return FALSE;
+  }
+
+  return pglc->line_count[dir] > 0;
+}
+
+/**************************************************************************
+  Clear all goto line map decorations and queues mapview updates for the
+  affected tiles.
+**************************************************************************/
+void mapdeco_clear_gotoroutes(void)
+{
+  const struct tile *ptile;
+  struct gotoline_counter *pglc;
+
+  if (!mapdeco_gotoline_table) {
+    return;
+  }
+
+  hash_iterate(mapdeco_gotoline_table, iter) {
+    ptile = hash_iter_get_key(iter);
+    pglc = hash_iter_get_value(iter);
+
+    /* FIXME: Remove the casts. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    adjc_dir_iterate(ptile, ptile_dest, dir) {
+      if (pglc->line_count[dir] > 0) {
+        refresh_tile_mapcanvas((struct tile *) ptile_dest, FALSE, FALSE);
+      }
+    } adjc_dir_iterate_end;
+
+    free(pglc);
+  } hash_iterate_end;
+  hash_delete_all_entries(mapdeco_gotoline_table);
 }
 
 /**************************************************************************
diff --git a/client/mapview_common.h b/client/mapview_common.h
index afee919..e7240f0 100644
--- a/client/mapview_common.h
+++ b/client/mapview_common.h
@@ -25,15 +25,6 @@
 
 struct canvas_store;		/* opaque type, real type is gui-dep */
 
-struct mapview_decoration {
-  /* For client Area Selection */
-  enum tile_hilite {
-    HILITE_NONE, HILITE_CITY
-  } hilite;
-
-  int crosshair; /* A refcount */
-};
-
 struct view {
   int gui_x0, gui_y0;
   int width, height;		/* Size in pixels. */
@@ -43,7 +34,24 @@ struct view {
   struct canvas *store, *tmp_store;
 };
 
-extern struct mapview_decoration *map_deco;
+void mapdeco_init(void);
+void mapdeco_free(void);
+void mapdeco_set_highlight(const struct tile *ptile, bool highlight);
+bool mapdeco_is_highlight_set(const struct tile *ptile);
+void mapdeco_clear_highlights(void);
+void mapdeco_set_crosshair(const struct tile *ptile, bool crosshair);
+bool mapdeco_is_crosshair_set(const struct tile *ptile);
+void mapdeco_clear_crosshairs(void);
+void mapdeco_set_gotoroute(const struct unit *punit);
+void mapdeco_add_gotoline(const struct tile *ptile,
+                          enum direction8 dir);
+void mapdeco_remove_gotoline(const struct tile *ptile,
+                             enum direction8 dir);
+bool mapdeco_is_gotoline_set(const struct tile *ptile,
+                             enum direction8 dir);
+void mapdeco_clear_gotoroutes(void);
+
+
 extern struct view mapview;
 
 /* HACK: Callers can set this to FALSE to disable sliding.  It should be
@@ -303,7 +311,6 @@ void get_city_mapview_traderoutes(struct city *pcity,
                                   size_t traderoutes_buffer_len,
                                   enum color_std *traderoutes_color);
 
-void init_mapview_decorations(void);
 bool map_canvas_resized(int width, int height);
 void init_mapcanvas_and_overview(void);
 
diff --git a/client/packhand.c b/client/packhand.c
index 1b6801b..2c36ff7 100644
--- a/client/packhand.c
+++ b/client/packhand.c
@@ -1560,7 +1560,7 @@ void handle_map_info(int xsize, int ysize, int topology_id)
 
   map_allocate();
   init_client_goto();
-  init_mapview_decorations();
+  mapdeco_init();
 
   generate_citydlg_dimensions();
 
diff --git a/client/tilespec.c b/client/tilespec.c
index 65be15d..23c7547 100644
--- a/client/tilespec.c
+++ b/client/tilespec.c
@@ -4096,10 +4096,8 @@ static int fill_grid_sprite_array(const struct tileset *t,
       }
     }
 
-    if ((pedge->tile[0]
-	 && map_deco[tile_index(pedge->tile[0])].hilite == HILITE_CITY)
-	|| (pedge->tile[1]
-	    && map_deco[tile_index(pedge->tile[1])].hilite == HILITE_CITY)) {
+    if (mapdeco_is_highlight_set(pedge->tile[0])
+        || mapdeco_is_highlight_set(pedge->tile[1])) {
       ADD_SPRITE_SIMPLE(t->sprites.grid.selected[pedge->type]);
     } else if (!draw_terrain && draw_coastline
 	       && pedge->tile[0] && pedge->tile[1]
@@ -4494,7 +4492,7 @@ int fill_sprite_array(struct tileset *t,
 
   case LAYER_OVERLAYS:
     sprs += fill_city_overlays_sprite_array(t, sprs, ptile, citymode);
-    if (ptile && map_deco[tile_index(ptile)].crosshair > 0) {
+    if (mapdeco_is_crosshair_set(ptile)) {
       ADD_SPRITE_SIMPLE(t->sprites.user.attention);
     }
     break;
commit 47680f1dbbedf8da397e4abba690b0f019be55fb
Author: Madeline Book <madeline.b...@gmail.com>
Date:   Fri Feb 13 17:35:48 2009 -0500

    Mapdeco cleanup.
---
 client/goto.c                |  111 +--------------
 client/goto.h                |    1 -
 client/gui-gtk-2.0/mapctrl.c |   32 +---
 client/mapctrl_common.c      |   30 ++--
 client/mapview_common.c      |  328 ++++++++++++++++++++++++++++++++++++++----
 client/mapview_common.h      |   29 +++--
 client/packhand.c            |    2 +-
 client/tilespec.c            |    8 +-
 8 files changed, 349 insertions(+), 192 deletions(-)

diff --git a/client/goto.c b/client/goto.c
index fd4342b..07d0056 100644
--- a/client/goto.c
+++ b/client/goto.c
@@ -38,21 +38,6 @@
 #define PATH_LOG_LEVEL          LOG_DEBUG
 #define PACKET_LOG_LEVEL        LOG_DEBUG
 
-/* For each tile and each direction we store the number of lines going out 
- * of the tile in this direction.  Since each line is undirected, we only 
- * store the 4 lower-numbered directions for each tile; the 4 upper-numbered
- * directions are stored as reverses from the target tile.
- * Notes: 1. This assumes that 
- * - there are 8 directions
- * - out of every two opposite directions (like NORTH and SOUTH) one and 
- *   only one has number less than 4
- * 2. There _can_ be more than one line drawn between two tiles, because of 
- * the waypoints. */
-struct goto_tiles {
-  unsigned char drawn[4];
-};
-static struct goto_tiles *tiles = NULL;
-
 /*
  * The whole path is separated by waypoints into parts.  Each part has its
  * own starting position and requires its own map.  When the unit is unable
@@ -95,10 +80,6 @@ struct goto_map {
 
 static struct goto_map_list *goto_maps = NULL;
 
-#define DRAWN(ptile, dir) (tiles[(ptile)->index].drawn[dir])
-
-static void increment_drawn(struct tile *src_tile, enum direction8 dir);
-static void decrement_drawn(struct tile *src_tile, enum direction8 dir);
 static void reset_last_part(struct goto_map *goto_map);
 static void remove_last_part(struct goto_map *goto_map);
 
@@ -146,8 +127,6 @@ void init_client_goto(void)
   free_client_goto();
 
   goto_maps = goto_map_list_new();
-
-  tiles = fc_calloc(MAP_INDEX_SIZE, sizeof(*tiles));
 }
 
 /********************************************************************** 
@@ -163,11 +142,6 @@ void free_client_goto(void)
     goto_map_list_free(goto_maps);
     goto_maps = NULL;
   }
-
-  if (NULL != tiles) {
-    free(tiles);
-    tiles = NULL;
-  }
 }
 
 /**********************************************************************
@@ -223,7 +197,7 @@ static bool update_last_part(struct goto_map *goto_map,
       struct pf_position *a = &p->path->positions[i];
 
       if (is_valid_dir(a->dir_to_next_pos)) {
-	decrement_drawn(a->tile, a->dir_to_next_pos);
+        mapdeco_remove_gotoline(a->tile, a->dir_to_next_pos);
       } else {
 	assert(i < p->path->length - 1
 	       && a->tile == p->path->positions[i + 1].tile);
@@ -240,7 +214,7 @@ static bool update_last_part(struct goto_map *goto_map,
     struct pf_position *a = &new_path->positions[i];
 
     if (is_valid_dir(a->dir_to_next_pos)) {
-      increment_drawn(a->tile, a->dir_to_next_pos);
+      mapdeco_add_gotoline(a->tile, a->dir_to_next_pos);
     } else {
       assert(i < new_path->length - 1
 	     && a->tile == new_path->positions[i + 1].tile);
@@ -1222,87 +1196,6 @@ void send_goto_route(void)
   } goto_map_unit_iterate_end;
 }
 
-/* ================= drawn functions ============================ */
-
-/********************************************************************** 
-  Every line segment has 2 ends; we only keep track of it at one end
-  (the one from which dir i <4). This function returns pointer to the
-  correct char. This function is for internal use only. Use get_drawn
-  when in doubt.
-***********************************************************************/
-static unsigned char *get_drawn_char(struct tile *ptile, enum direction8 dir)
-{
-  struct tile *tile1;
-
-  tile1 = mapstep(ptile, dir);
-
-  if (dir >= 4) {
-    ptile = tile1;
-    dir = DIR_REVERSE(dir);
-  }
-
-  return &DRAWN(ptile, dir);
-}
-
-/**************************************************************************
-  Increments the number of segments at the location, and draws the
-  segment if necessary.
-**************************************************************************/
-static void increment_drawn(struct tile *src_tile, enum direction8 dir)
-{
-  unsigned char *count = get_drawn_char(src_tile, dir);
-
-  freelog(LOG_DEBUG, "increment_drawn(src=(%d,%d) dir=%s)",
-          TILE_XY(src_tile), dir_get_name(dir));
-
-  if (*count < 255) {
-    (*count)++;
-  } else {
-    /* don't overflow unsigned char. */
-    assert(*count < 255);
-  }
-
-  if (*count == 1) {
-    draw_segment(src_tile, dir);
-  }
-}
-
-/**************************************************************************
-  Decrements the number of segments at the location, and clears the
-  segment if necessary.
-**************************************************************************/
-static void decrement_drawn(struct tile *src_tile, enum direction8 dir)
-{
-  unsigned char *count = get_drawn_char(src_tile, dir);
-
-  freelog(LOG_DEBUG, "decrement_drawn(src=(%d,%d) dir=%s)",
-          TILE_XY(src_tile), dir_get_name(dir));
-
-  if (*count > 0) {
-    (*count)--;
-  } else {
-    /* don't underflow unsigned char. */
-    assert(*count > 0);
-  }
-
-  if (*count == 0) {
-    undraw_segment(src_tile, dir);
-  }
-}
-
-/****************************************************************************
-  Return TRUE if there is a line drawn from (x,y) in the given direction.
-  This is used by mapview to determine whether to draw a goto line.
-****************************************************************************/
-bool is_drawn_line(struct tile *ptile, int dir)
-{
-  if (!mapstep(ptile, dir)) {
-    return 0;
-  }
-
-  return (*get_drawn_char(ptile, dir) != 0);
-}
-
 /**************************************************************************
   Find the path to the nearest (fastest to reach) allied city for the
   unit, or NULL if none is reachable.
diff --git a/client/goto.h b/client/goto.h
index edf79cd..4702928 100644
--- a/client/goto.h
+++ b/client/goto.h
@@ -32,7 +32,6 @@ bool goto_pop_waypoint(void);
 
 bool is_valid_goto_destination(const struct tile *ptile);
 bool is_valid_goto_draw_line(struct tile *dest_tile);
-bool is_drawn_line(struct tile *dest_tile, int dir);
  
 void request_orders_cleared(struct unit *punit);
 void send_goto_path(struct unit *punit, struct pf_path *path,
diff --git a/client/gui-gtk-2.0/mapctrl.c b/client/gui-gtk-2.0/mapctrl.c
index cbad2b9..6b28f6c 100644
--- a/client/gui-gtk-2.0/mapctrl.c
+++ b/client/gui-gtk-2.0/mapctrl.c
@@ -110,12 +110,8 @@ static void popupinfo_positioning_callback(GtkWidget *w, GtkAllocation *alloc,
 static void popit(GdkEventButton *event, struct tile *ptile)
 {
   GtkWidget *p;
-  struct tile *cross_list[2 + 1];
-  struct tile **cross_head = cross_list;
-  int i;
   static struct tmousepos mousepos;
   struct unit *punit;
-  bool is_orders;
 
   if (client_tile_get_known(ptile) >= TILE_KNOWN_FOGGED) {
     p=gtk_window_new(GTK_WINDOW_POPUP);
@@ -125,21 +121,14 @@ static void popit(GdkEventButton *event, struct tile *ptile)
 
     punit = find_visible_unit(ptile);
 
-    is_orders = show_unit_orders(punit);
-
-    if (punit && punit->goto_tile) {
-      map_deco[punit->goto_tile->index].crosshair++;
-      *cross_head = punit->goto_tile;
-      cross_head++;
+    if (punit) {
+      mapdeco_set_gotoroute(punit);
+      if (punit->goto_tile) {
+        mapdeco_set_crosshair(punit->goto_tile, TRUE);
+      }
     }
-    map_deco[ptile->index].crosshair++;
-    *cross_head = ptile;
-    cross_head++;
+    mapdeco_set_crosshair(ptile, TRUE);
 
-    *cross_head = NULL;
-    for (i = 0; cross_list[i]; i++) {
-      put_cross_overlay_tile(cross_list[i]);
-    }
     g_signal_connect(p, "destroy",
 		     G_CALLBACK(popupinfo_popdown_callback), NULL);
 
@@ -165,13 +154,8 @@ static void popit(GdkEventButton *event, struct tile *ptile)
 **************************************************************************/
 void popupinfo_popdown_callback(GtkWidget *w, gpointer data)
 {
-  /* We could just remove the crosshairs that we placed earlier, but
-   * this is easier. */
-  whole_map_iterate(ptile) {
-    map_deco[ptile->index].crosshair = 0;
-  } whole_map_iterate_end;
-
-  update_map_canvas_visible();
+  mapdeco_clear_crosshairs();
+  mapdeco_clear_gotoroutes();
 }
 
  /**************************************************************************
diff --git a/client/mapctrl_common.c b/client/mapctrl_common.c
index 59758c0..2b4bebc 100644
--- a/client/mapctrl_common.c
+++ b/client/mapctrl_common.c
@@ -106,6 +106,9 @@ void anchor_selection_rectangle(int canvas_x, int canvas_y,
     on the map and hilited in the City List Window.
 
  Later, I'll want to add unit hiliting for mass orders.       -ali
+
+ NB: At the end of this function the current selection rectangle will be
+ erased (by being redrawn).
 **************************************************************************/
 static void define_tiles_within_rectangle(void)
 {
@@ -150,7 +153,7 @@ static void define_tiles_within_rectangle(void)
        */
       if (ptile->city && city_owner(ptile->city) == game.player_ptr) {
 	/* FIXME: handle rectangle_append */
-        map_deco[ptile->index].hilite = HILITE_CITY;
+        mapdeco_set_highlight(ptile, TRUE);
         tiles_hilited_cities = TRUE;
       }
       unit_list_iterate(ptile->units, punit) {
@@ -175,6 +178,9 @@ static void define_tiles_within_rectangle(void)
   unit_list_unlink_all(units);
   unit_list_free(units);
 
+  /* Clear previous rectangle. */
+  draw_selection_rectangle(rec_corner_x, rec_corner_y, rec_w, rec_h);
+
   /* Hilite in City List Window */
   if (tiles_hilited_cities) {
     hilite_cities_from_canvas();      /* cityrep.c */
@@ -275,7 +281,9 @@ void cancel_selection_rectangle(void)
   if (rectangle_active) {
     rectangle_active = FALSE;
     rbutton_down = FALSE;
-    dirty_rect(rec_corner_x, rec_corner_y, rec_w, rec_h);
+
+    /* Erase the previously drawn selection rectangle. */
+    draw_selection_rectangle(rec_corner_x, rec_corner_y, rec_w, rec_h);
   }
 }
 
@@ -284,7 +292,7 @@ void cancel_selection_rectangle(void)
 **************************************************************************/
 bool is_city_hilited(struct city *pcity)
 {
-  return map_deco[pcity->tile->index].hilite == HILITE_CITY;
+  return pcity && mapdeco_is_highlight_set(pcity->tile);
 }
 
 /**************************************************************************
@@ -294,12 +302,7 @@ void cancel_tile_hiliting(void)
 {
   if (tiles_hilited_cities)  {
     tiles_hilited_cities = FALSE;
-
-    whole_map_iterate(ptile) {
-      map_deco[ptile->index].hilite = HILITE_NONE;
-    } whole_map_iterate_end;
-
-    update_map_canvas_visible();
+    mapdeco_clear_highlights();
   }
 }
 
@@ -311,7 +314,6 @@ void release_right_button(int canvas_x, int canvas_y)
 {
   if (rectangle_active) {
     define_tiles_within_rectangle();
-    update_map_canvas_visible();
   } else {
     /* NB: Assumes 'rectangle_append' was set because <SHIFT> was on. */
     if (rectangle_append) {
@@ -335,22 +337,20 @@ void toggle_tile_hilite(struct tile *ptile)
 {
   struct city *pcity = ptile->city;
 
-  if (map_deco[ptile->index].hilite == HILITE_CITY) {
-    map_deco[ptile->index].hilite = HILITE_NONE;
+  if (mapdeco_is_highlight_set(ptile)) {
+    mapdeco_set_highlight(ptile, FALSE);
     if (pcity) {
       toggle_city_hilite(pcity, FALSE); /* cityrep.c */
     }
   }
   else if (pcity && city_owner(pcity) == game.player_ptr) {
-    map_deco[ptile->index].hilite = HILITE_CITY;
+    mapdeco_set_highlight(ptile, TRUE);
     tiles_hilited_cities = TRUE;
     toggle_city_hilite(pcity, TRUE);
   }
   else  {
     return;
   }
-
-  refresh_tile_mapcanvas(ptile, FALSE, TRUE);
 }
 
 /**************************************************************************
diff --git a/client/mapview_common.c b/client/mapview_common.c
index 56dd285..cc4d770 100644
--- a/client/mapview_common.c
+++ b/client/mapview_common.c
@@ -18,6 +18,7 @@
 #include <assert.h>
 
 #include "fcintl.h"
+#include "hash.h"
 #include "log.h"
 #include "rand.h"
 #include "support.h"
@@ -41,7 +42,14 @@
 #include "overview_common.h"
 #include "tilespec.h"
 
-struct mapview_decoration *map_deco;
+struct hash_table *mapdeco_highlight_table;
+struct hash_table *mapdeco_crosshair_table;
+
+struct gotoline_counter {
+  int line_count[DIR8_COUNT];
+};
+struct hash_table *mapdeco_gotoline_table;
+
 struct view mapview;
 bool can_slide = TRUE;
 
@@ -1299,8 +1307,8 @@ void update_map_canvas(int canvas_x, int canvas_y, int width, int height)
       continue;
     }
     adjc_dir_iterate(ptile, adjc_tile, dir) {
-      if (is_drawn_line(ptile, dir)) {
-	draw_segment(ptile, dir);
+      if (mapdeco_is_gotoline_set(ptile, dir)) {
+        draw_segment(ptile, dir);
       }
     } adjc_dir_iterate_end;
   } gui_rect_iterate_end;
@@ -1812,22 +1820,6 @@ void draw_segment(struct tile *src_tile, enum direction8 dir)
    * which fails when the size of the mapview approaches that of the map. */
 }
 
-/**************************************************************************
-  Remove the line from src_x, src_y in the given direction, and redraw
-  the change if necessary.
-**************************************************************************/
-void undraw_segment(struct tile *src_tile, enum direction8 dir)
-{
-  struct tile *dst_tile = mapstep(src_tile, dir);
-
-  if (is_drawn_line(src_tile, dir) || !dst_tile) {
-    assert(0);
-    return;
-  }
-  refresh_tile_mapcanvas(src_tile, FALSE, FALSE);
-  refresh_tile_mapcanvas(dst_tile, FALSE, FALSE);
-}
-
 /****************************************************************************
   This function is called to decrease a unit's HP smoothly in battle
   when combat_animation is turned on.
@@ -2035,7 +2027,7 @@ struct city *find_city_or_settler_near_tile(const struct tile *ptile,
        * causing it to be marked as C_TILE_UNAVAILABLE.
        */
       
-      if (map_deco[pcity->tile->index].hilite == HILITE_CITY) {
+      if (mapdeco_is_highlight_set(pcity->tile)) {
 	/* rule c */
 	return pcity;
       }
@@ -2432,16 +2424,300 @@ static bool can_do_cached_drawing(void)
   Called when we receive map dimensions.  It initialized the mapview
   decorations.
 **************************************************************************/
-void init_mapview_decorations(void)
+void mapdeco_init(void)
 {
   /* HACK: this must be called on a map_info packet. */
   mapview.can_do_cached_drawing = can_do_cached_drawing();
 
-  map_deco = fc_realloc(map_deco, MAP_INDEX_SIZE * sizeof(*map_deco));
-  whole_map_iterate(ptile) {
-    map_deco[ptile->index].hilite = HILITE_NONE;
-    map_deco[ptile->index].crosshair = 0;
-  } whole_map_iterate_end;
+  mapdeco_free();
+  mapdeco_highlight_table = hash_new(hash_fval_keyval, hash_fcmp_keyval);
+  mapdeco_crosshair_table = hash_new(hash_fval_keyval, hash_fcmp_keyval);
+  mapdeco_gotoline_table = hash_new(hash_fval_keyval, hash_fcmp_keyval);
+}
+
+/**************************************************************************
+  Free all memory used for map decorations.
+**************************************************************************/
+void mapdeco_free(void)
+{
+  if (mapdeco_highlight_table) {
+    hash_free(mapdeco_highlight_table);
+    mapdeco_highlight_table = NULL;
+  }
+  if (mapdeco_crosshair_table) {
+    hash_free(mapdeco_crosshair_table);
+    mapdeco_crosshair_table = NULL;
+  }
+  if (mapdeco_gotoline_table) {
+    hash_values_iterate(mapdeco_gotoline_table, pglc) {
+      free(pglc);
+    } hash_values_iterate_end;
+    hash_free(mapdeco_gotoline_table);
+    mapdeco_gotoline_table = NULL;
+  }
+}
+
+/**************************************************************************
+  Set the given tile's map decoration as either highlighted or not,
+  depending on the value of 'highlight'.
+**************************************************************************/
+void mapdeco_set_highlight(const struct tile *ptile, bool highlight)
+{
+  bool changed = FALSE;
+  if (!ptile || !mapdeco_highlight_table) {
+    return;
+  }
+
+  if (highlight) {
+    changed = hash_insert(mapdeco_highlight_table, ptile, NULL);
+  } else {
+    changed = hash_key_exists(mapdeco_highlight_table, ptile);
+    hash_delete_entry(mapdeco_highlight_table, ptile);
+  }
+
+  if (changed) {
+    /* FIXME: Remove the cast. */
+    refresh_tile_mapcanvas((struct tile *) ptile, TRUE, FALSE);
+  }
+}
+
+/**************************************************************************
+  Return TRUE if the given tile is highlighted.
+**************************************************************************/
+bool mapdeco_is_highlight_set(const struct tile *ptile)
+{
+  if (!ptile || !mapdeco_highlight_table) {
+    return FALSE;
+  }
+  return hash_key_exists(mapdeco_highlight_table, ptile);
+}
+
+/**************************************************************************
+  Clears all highlighting. Marks the previously highlighted tiles as
+  needing a mapview update.
+**************************************************************************/
+void mapdeco_clear_highlights(void)
+{
+  if (!mapdeco_highlight_table) {
+    return;
+  }
+
+  hash_keys_iterate(mapdeco_highlight_table, ptile) {
+    refresh_tile_mapcanvas(ptile, TRUE, FALSE);
+  } hash_keys_iterate_end;
+
+  hash_delete_all_entries(mapdeco_highlight_table);
+}
+
+/**************************************************************************
+  Marks the given tile as having a "crosshair" map decoration.
+**************************************************************************/
+void mapdeco_set_crosshair(const struct tile *ptile, bool crosshair)
+{
+  bool changed;
+
+  if (!mapdeco_crosshair_table || !ptile) {
+    return;
+  }
+
+  if (crosshair) {
+    changed = hash_insert(mapdeco_crosshair_table, ptile, NULL);
+  } else {
+    changed = hash_key_exists(mapdeco_crosshair_table, ptile);
+    hash_delete_entry(mapdeco_crosshair_table, ptile);
+  }
+
+  if (changed) {
+    /* FIXME: Remove the cast. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+  }
+}
+
+/**************************************************************************
+  Returns TRUE if there is a "crosshair" decoration set at the given tile.
+**************************************************************************/
+bool mapdeco_is_crosshair_set(const struct tile *ptile)
+{
+  if (!mapdeco_crosshair_table || !ptile) {
+    return FALSE;
+  }
+  return hash_key_exists(mapdeco_crosshair_table, ptile);
+}
+
+/**************************************************************************
+  Clears all previous set tile crosshair decorations. Marks the affected
+  tiles as needing a mapview update.
+**************************************************************************/
+void mapdeco_clear_crosshairs(void)
+{
+  if (!mapdeco_crosshair_table) {
+    return;
+  }
+
+  hash_keys_iterate(mapdeco_crosshair_table, ptile) {
+    refresh_tile_mapcanvas(ptile, FALSE, FALSE);
+  } hash_keys_iterate_end;
+
+  hash_delete_all_entries(mapdeco_crosshair_table);
+}
+
+/**************************************************************************
+  Add a goto line from the given tile 'ptile' in the direction 'dir'. If
+  there was no previously drawn line there, a mapview update is queued
+  for the source and destination tiles.
+**************************************************************************/
+void mapdeco_add_gotoline(const struct tile *ptile, enum direction8 dir)
+{
+  struct gotoline_counter *pglc;
+  const struct tile *ptile_dest;
+  bool changed;
+
+  if (!mapdeco_gotoline_table || !ptile
+      || !(0 <= dir && dir < DIR8_COUNT)) {
+    return;
+  }
+  ptile_dest = mapstep(ptile, dir);
+  if (!ptile_dest) {
+    return;
+  }
+
+  pglc = hash_lookup_data(mapdeco_gotoline_table, ptile);
+  if (!pglc) {
+    pglc = fc_calloc(1, sizeof(*pglc));
+    hash_insert(mapdeco_gotoline_table, ptile, pglc);
+  }
+  changed = (pglc->line_count[dir] < 1);
+  pglc->line_count[dir]++;
+
+  if (changed) {
+    /* FIXME: Remove cast. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    refresh_tile_mapcanvas((struct tile *) ptile_dest, FALSE, FALSE);
+  }
+}
+
+/**************************************************************************
+  Removes a goto line from the given tile 'ptile' going in the direction
+  'dir'. If this was the last line there, a mapview update is queued to
+  erase the drawn line.
+**************************************************************************/
+void mapdeco_remove_gotoline(const struct tile *ptile,
+                             enum direction8 dir)
+{
+  struct gotoline_counter *pglc;
+  bool changed = FALSE;
+
+  if (!mapdeco_gotoline_table || !ptile
+      || !(0 <= dir && dir < DIR8_COUNT)) {
+    return;
+  }
+
+  pglc = hash_lookup_data(mapdeco_gotoline_table, ptile);
+  if (!pglc) {
+    return;
+  }
+
+  pglc->line_count[dir]--;
+  if (pglc->line_count[dir] <= 0) {
+    pglc->line_count[dir] = 0;
+    changed = TRUE;
+  }
+
+  if (changed) {
+    /* FIXME: Remove the casts. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    ptile = mapstep(ptile, dir);
+    if (ptile != NULL) {
+      refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    }
+  }
+}
+
+/**************************************************************************
+  Set the map decorations for the given unit's goto route. A goto route
+  consists of one or more goto lines, with each line being from the center
+  of one tile to the center of another tile.
+**************************************************************************/
+void mapdeco_set_gotoroute(const struct unit *punit)
+{
+  const struct unit_order *porder;
+  const struct tile *ptile;
+  int i, ind;
+
+  if (!punit || !punit->tile || !unit_has_orders(punit)
+      || punit->orders.length < 1) {
+    return;
+  }
+
+  ptile = punit->tile;
+
+  for (i = 0; ptile != NULL && i < punit->orders.length; i++) {
+    if (punit->orders.index + i >= punit->orders.length
+        && !punit->orders.repeat) {
+      break;
+    }
+
+    ind = (punit->orders.index + i) % punit->orders.length;
+    porder = &punit->orders.list[ind];
+    if (porder->order != ORDER_MOVE) {
+      break;
+    }
+
+    mapdeco_add_gotoline(ptile, porder->dir);
+    ptile = mapstep(ptile, porder->dir);
+  }
+}
+
+/**************************************************************************
+  Returns TRUE if a goto line should be drawn from the given tile in the
+  given direction.
+**************************************************************************/
+bool mapdeco_is_gotoline_set(const struct tile *ptile,
+                             enum direction8 dir)
+{
+  struct gotoline_counter *pglc;
+
+  if (!ptile || !(0 <= dir && dir < DIR8_COUNT)
+      || !mapdeco_gotoline_table) {
+    return FALSE;
+  }
+
+  pglc = hash_lookup_data(mapdeco_gotoline_table, ptile);
+  if (!pglc) {
+    return FALSE;
+  }
+
+  return pglc->line_count[dir] > 0;
+}
+
+/**************************************************************************
+  Clear all goto line map decorations and queues mapview updates for the
+  affected tiles.
+**************************************************************************/
+void mapdeco_clear_gotoroutes(void)
+{
+  const struct tile *ptile;
+  struct gotoline_counter *pglc;
+
+  if (!mapdeco_gotoline_table) {
+    return;
+  }
+
+  hash_iterate(mapdeco_gotoline_table, iter) {
+    ptile = hash_iter_get_key(iter);
+    pglc = hash_iter_get_value(iter);
+
+    /* FIXME: Remove the casts. */
+    refresh_tile_mapcanvas((struct tile *) ptile, FALSE, FALSE);
+    adjc_dir_iterate(ptile, ptile_dest, dir) {
+      if (pglc->line_count[dir] > 0) {
+        refresh_tile_mapcanvas((struct tile *) ptile_dest, FALSE, FALSE);
+      }
+    } adjc_dir_iterate_end;
+
+    free(pglc);
+  } hash_iterate_end;
+  hash_delete_all_entries(mapdeco_gotoline_table);
 }
 
 /**************************************************************************
diff --git a/client/mapview_common.h b/client/mapview_common.h
index 6958728..0901a7b 100644
--- a/client/mapview_common.h
+++ b/client/mapview_common.h
@@ -25,15 +25,6 @@
 
 struct canvas_store;		/* opaque type, real type is gui-dep */
 
-struct mapview_decoration {
-  /* For client Area Selection */
-  enum tile_hilite {
-    HILITE_NONE, HILITE_CITY
-  } hilite;
-
-  int crosshair; /* A refcount */
-};
-
 struct view {
   int gui_x0, gui_y0;
   int width, height;		/* Size in pixels. */
@@ -43,7 +34,24 @@ struct view {
   struct canvas *store, *tmp_store;
 };
 
-extern struct mapview_decoration *map_deco;
+void mapdeco_init(void);
+void mapdeco_free(void);
+void mapdeco_set_highlight(const struct tile *ptile, bool highlight);
+bool mapdeco_is_highlight_set(const struct tile *ptile);
+void mapdeco_clear_highlights(void);
+void mapdeco_set_crosshair(const struct tile *ptile, bool crosshair);
+bool mapdeco_is_crosshair_set(const struct tile *ptile);
+void mapdeco_clear_crosshairs(void);
+void mapdeco_set_gotoroute(const struct unit *punit);
+void mapdeco_add_gotoline(const struct tile *ptile,
+                          enum direction8 dir);
+void mapdeco_remove_gotoline(const struct tile *ptile,
+                             enum direction8 dir);
+bool mapdeco_is_gotoline_set(const struct tile *ptile,
+                             enum direction8 dir);
+void mapdeco_clear_gotoroutes(void);
+
+
 extern struct view mapview;
 
 /* HACK: Callers can set this to FALSE to disable sliding.  It should be
@@ -293,7 +301,6 @@ void get_city_mapview_name_and_growth(struct city *pcity,
 				      size_t growth_buffer_len,
 				      enum color_std *grwoth_color);
 
-void init_mapview_decorations(void);
 bool map_canvas_resized(int width, int height);
 void init_mapcanvas_and_overview(void);
 
diff --git a/client/packhand.c b/client/packhand.c
index c5422a0..baa6db6 100644
--- a/client/packhand.c
+++ b/client/packhand.c
@@ -1413,7 +1413,7 @@ void handle_map_info(int xsize, int ysize, int topology_id)
 
   map_allocate();
   init_client_goto();
-  init_mapview_decorations();
+  mapdeco_init();
 
   generate_citydlg_dimensions();
 
diff --git a/client/tilespec.c b/client/tilespec.c
index 606f640..dde0d79 100644
--- a/client/tilespec.c
+++ b/client/tilespec.c
@@ -3885,10 +3885,8 @@ static int fill_grid_sprite_array(const struct tileset *t,
       }
     }
 
-    if ((pedge->tile[0]
-	 && map_deco[pedge->tile[0]->index].hilite == HILITE_CITY)
-	|| (pedge->tile[1]
-	    && map_deco[pedge->tile[1]->index].hilite == HILITE_CITY)) {
+    if (mapdeco_is_highlight_set(pedge->tile[0])
+        || mapdeco_is_highlight_set(pedge->tile[1])) {
       ADD_SPRITE_SIMPLE(t->sprites.grid.selected[pedge->type]);
     } else if (!draw_terrain && draw_coastline
 	       && pedge->tile[0] && pedge->tile[1]
@@ -4263,7 +4261,7 @@ int fill_sprite_array(struct tileset *t,
 
   case LAYER_OVERLAYS:
     sprs += fill_city_overlays_sprite_array(t, sprs, ptile, citymode);
-    if (ptile && map_deco[ptile->index].crosshair > 0) {
+    if (mapdeco_is_crosshair_set(ptile)) {
       ADD_SPRITE_SIMPLE(t->sprites.user.attention);
     }
     break;
_______________________________________________
Freeciv-dev mailing list
Freeciv-dev@gna.org
https://mail.gna.org/listinfo/freeciv-dev

Reply via email to