Przeglądaj źródła

Add ability to switch tile source during execution

Johannes Hofmann 8 lat temu
rodzic
commit
350e8db588
9 zmienionych plików z 122 dodań i 75 usunięć
  1. 5
    5
      src/config.rs
  2. 0
    1
      src/coord.rs
  3. 46
    9
      src/main.rs
  4. 3
    6
      src/map_view_gl.rs
  5. 9
    0
      src/tile.rs
  6. 12
    7
      src/tile_cache.rs
  7. 7
    5
      src/tile_cache_gl.rs
  8. 26
    24
      src/tile_loader.rs
  9. 14
    18
      src/tile_source.rs

+ 5
- 5
src/config.rs Wyświetl plik

@@ -33,15 +33,15 @@ impl Config {
33 33
         toml::from_str(&content).ok()
34 34
     }
35 35
 
36
-    pub fn tile_sources(&self) -> BTreeMap<String, TileSource> {
37
-        let mut map = BTreeMap::new();
36
+    pub fn tile_sources(&self) -> Vec<(String, TileSource)> {
37
+        let mut vec = Vec::with_capacity(self.sources.len());
38 38
 
39 39
         for (id, (name, source)) in self.sources.iter().enumerate() {
40 40
             let mut path = PathBuf::from(&self.tile_cache_dir);
41 41
             //TODO escape name (no slashes or dots)
42 42
             path.push(name);
43 43
 
44
-            map.insert(
44
+            vec.push((
45 45
                 name.clone(),
46 46
                 TileSource::new(
47 47
                     id as u32,
@@ -49,9 +49,9 @@ impl Config {
49 49
                     path,
50 50
                     source.max_zoom,
51 51
                 ),
52
-            );
52
+            ));
53 53
         }
54 54
 
55
-        return map;
55
+        vec
56 56
     }
57 57
 }

+ 0
- 1
src/coord.rs Wyświetl plik

@@ -1,5 +1,4 @@
1 1
 use std::f64::consts::PI;
2
-use tile::Tile;
3 2
 
4 3
 /// A position in map coordinates.
5 4
 /// Valid values for x and y lie in the interval [0.0, 1.0].

+ 46
- 9
src/main.rs Wyświetl plik

@@ -55,7 +55,7 @@ struct InputState {
55 55
     mouse_pressed: bool,
56 56
 }
57 57
 
58
-fn handle_event(event: Event, map: &mut MapViewGl, input_state: &mut InputState) -> Action {
58
+fn handle_event(event: Event, map: &mut MapViewGl, input_state: &mut InputState, sources: &mut TileSources) -> Action {
59 59
     match event {
60 60
         Event::Closed => Action::Close,
61 61
         Event::Awakened => Action::Redraw,
@@ -119,6 +119,14 @@ fn handle_event(event: Event, map: &mut MapViewGl, input_state: &mut InputState)
119 119
                 VirtualKeyCode::Escape => {
120 120
                     Action::Close
121 121
                 },
122
+                VirtualKeyCode::PageUp => {
123
+                    sources.switch_to_prev();
124
+                    Action::Redraw
125
+                },
126
+                VirtualKeyCode::PageDown => {
127
+                    sources.switch_to_next();
128
+                    Action::Redraw
129
+                },
122 130
                 VirtualKeyCode::Left => {
123 131
                     map.move_pixel(-50.0, 0.0);
124 132
                     Action::Redraw
@@ -159,6 +167,7 @@ fn handle_event(event: Event, map: &mut MapViewGl, input_state: &mut InputState)
159 167
 
160 168
 fn main() {
161 169
     let config = config::Config::from_toml("deltamap.toml").unwrap();
170
+    let mut sources = TileSources::new(config.tile_sources()).unwrap();
162 171
 
163 172
     let mut window = glutin::WindowBuilder::new().build().unwrap();
164 173
     window.set_title("DeltaMap");
@@ -168,10 +177,8 @@ fn main() {
168 177
     let proxy = window.create_window_proxy();
169 178
 
170 179
     let cx = context::Context::from_window(&window);
171
-    let mut sources = config.tile_sources();
172 180
     let mut map = map_view_gl::MapViewGl::new(
173 181
         &cx,
174
-        sources.into_iter().next().unwrap().1,
175 182
         window.get_inner_size_pixels().unwrap(),
176 183
         move || { proxy.wakeup_event_loop(); },
177 184
     );
@@ -190,7 +197,7 @@ fn main() {
190 197
 
191 198
         let mut redraw = false;
192 199
 
193
-        match handle_event(event, &mut map, &mut input_state) {
200
+        match handle_event(event, &mut map, &mut input_state, &mut sources) {
194 201
             Action::Close => break 'outer,
195 202
             Action::Redraw => {
196 203
                 redraw = true;
@@ -199,7 +206,7 @@ fn main() {
199 206
         }
200 207
 
201 208
         for event in window.poll_events() {
202
-            match handle_event(event, &mut map, &mut input_state) {
209
+            match handle_event(event, &mut map, &mut input_state, &mut sources) {
203 210
                 Action::Close => break 'outer,
204 211
                 Action::Redraw => {
205 212
                     redraw = true;
@@ -213,10 +220,10 @@ fn main() {
213 220
             if diff + draw_dur * 2 < milli16 {
214 221
                 if let Some(dur) = milli16.checked_sub(draw_dur * 2) {
215 222
                     std::thread::sleep(dur);
216
-                    println!("SLEEP {}", dur.as_secs() as f64 + dur.subsec_nanos() as f64 * 1e-9);
223
+                    println!("SLEEP {}", dur.as_secs() as f64 + f64::from(dur.subsec_nanos()) * 1e-9);
217 224
 
218 225
                     for event in window.poll_events() {
219
-                        match handle_event(event, &mut map, &mut input_state) {
226
+                        match handle_event(event, &mut map, &mut input_state, &mut sources) {
220 227
                             Action::Close => break 'outer,
221 228
                             Action::Redraw => {
222 229
                                 redraw = true;
@@ -230,7 +237,7 @@ fn main() {
230 237
 
231 238
         if redraw {
232 239
             let draw_start = Instant::now();
233
-            map.draw();
240
+            map.draw(sources.current());
234 241
             draw_dur = draw_start.elapsed();
235 242
 
236 243
             let _ = window.swap_buffers();
@@ -238,7 +245,37 @@ fn main() {
238 245
             last_draw = Instant::now();
239 246
 
240 247
             let diff = start_loop.elapsed();
241
-            println!("EVENT LOOP SECS {}", diff.as_secs() as f64 + diff.subsec_nanos() as f64 * 1e-9);
248
+            println!("EVENT LOOP SECS {}", diff.as_secs() as f64 + f64::from(diff.subsec_nanos()) * 1e-9);
249
+        }
250
+    }
251
+}
252
+
253
+struct TileSources {
254
+    current_index: usize,
255
+    sources: Vec<(String, TileSource)>,
256
+}
257
+
258
+impl TileSources {
259
+    pub fn new(sources: Vec<(String, TileSource)>) -> Option<TileSources> {
260
+        if sources.is_empty() {
261
+            None
262
+        } else {
263
+            Some(TileSources {
264
+                current_index: 0,
265
+                sources: sources,
266
+            })
242 267
         }
243 268
     }
269
+
270
+    pub fn current(&self) -> &TileSource {
271
+        &self.sources[self.current_index].1
272
+    }
273
+
274
+    pub fn switch_to_next(&mut self) {
275
+        self.current_index = (self.current_index + 1) % self.sources.len();
276
+    }
277
+
278
+    pub fn switch_to_prev(&mut self) {
279
+        self.current_index = (self.current_index + self.sources.len().saturating_sub(1)) % self.sources.len();
280
+    }
244 281
 }

+ 3
- 6
src/map_view_gl.rs Wyświetl plik

@@ -19,13 +19,12 @@ pub struct MapViewGl<'a> {
19 19
     buf: Buffer<'a>,
20 20
     viewport_size: (u32, u32),
21 21
     map_view: MapView,
22
-    tile_source: TileSource,
23 22
     tile_cache: TileCache,
24 23
     tile_cache_gl: TileCacheGl<'a>,
25 24
 }
26 25
 
27 26
 impl<'a> MapViewGl<'a> {
28
-    pub fn new<F>(cx: &Context, tile_source: TileSource, initial_size: (u32, u32), update_func: F) -> MapViewGl
27
+    pub fn new<F>(cx: &Context, initial_size: (u32, u32), update_func: F) -> MapViewGl
29 28
         where F: Fn() + Sync + Send + 'static,
30 29
     {
31 30
         println!("version: {}", cx.gl_version());
@@ -74,8 +73,6 @@ impl<'a> MapViewGl<'a> {
74 73
                 buf: buf,
75 74
                 viewport_size: initial_size,
76 75
                 map_view: map_view,
77
-                //TODO load templates from config
78
-                tile_source: tile_source,
79 76
                 tile_cache: TileCache::new(move |_tile| update_func()),
80 77
                 tile_cache_gl: TileCacheGl::new(tex, 256),
81 78
             }
@@ -94,12 +91,12 @@ impl<'a> MapViewGl<'a> {
94 91
         }
95 92
     }
96 93
 
97
-    pub fn draw(&mut self) {
94
+    pub fn draw(&mut self, source: &TileSource) {
98 95
         {
99 96
             let visible_tiles = self.map_view.visible_tiles(true);
100 97
             let textured_visible_tiles = self.tile_cache_gl.textured_visible_tiles(
101 98
                 &visible_tiles,
102
-                &self.tile_source,
99
+                source,
103 100
                 &mut self.tile_cache,
104 101
             );
105 102
 

+ 9
- 0
src/tile.rs Wyświetl plik

@@ -7,3 +7,12 @@ pub struct Tile {
7 7
     pub coord: TileCoord,
8 8
     pub source_id: TileSourceId,
9 9
 }
10
+
11
+impl Tile {
12
+    pub fn new(coord: TileCoord, source_id: TileSourceId) -> Tile {
13
+        Tile {
14
+            coord: coord,
15
+            source_id: source_id,
16
+        }
17
+    }
18
+}

+ 12
- 7
src/tile_cache.rs Wyświetl plik

@@ -1,19 +1,20 @@
1 1
 use image;
2 2
 use linked_hash_map::{Entry, LinkedHashMap};
3 3
 use coord::TileCoord;
4
+use tile::Tile;
4 5
 use tile_loader::TileLoader;
5 6
 use tile_source::TileSource;
6 7
 
7 8
 
8 9
 pub struct TileCache {
9 10
     loader: TileLoader,
10
-    map: LinkedHashMap<TileCoord, image::DynamicImage>,
11
+    map: LinkedHashMap<Tile, image::DynamicImage>,
11 12
     max_tiles: usize,
12 13
 }
13 14
 
14 15
 impl TileCache {
15 16
     pub fn new<F>(new_tile_func: F) -> Self
16
-        where F: Fn(TileCoord) + Sync + Send + 'static,
17
+        where F: Fn(Tile) + Sync + Send + 'static,
17 18
     {
18 19
         TileCache {
19 20
             loader: TileLoader::new(move |tile| {
@@ -26,11 +27,13 @@ impl TileCache {
26 27
 
27 28
     pub fn get_sync(
28 29
         &mut self,
29
-        tile: TileCoord,
30
+        tile_coord: TileCoord,
30 31
         source: &TileSource,
31 32
         write_to_file: bool,
32 33
         ) -> Option<&image::DynamicImage>
33 34
     {
35
+        let tile = Tile::new(tile_coord, source.id());
36
+
34 37
         //TODO Return the value from get_refresh with borrowck agreeing that this is OK.
35 38
         self.map.get_refresh(&tile);
36 39
 
@@ -44,14 +47,14 @@ impl TileCache {
44 47
                 Some(entry.into_mut())
45 48
             },
46 49
             Entry::Vacant(entry) => {
47
-                self.loader.get_sync(tile, source, write_to_file).map(|img| entry.insert(img) as &_)
50
+                self.loader.get_sync(tile_coord, source, write_to_file).map(|img| entry.insert(img) as &_)
48 51
             },
49 52
         }
50 53
     }
51 54
 
52 55
     pub fn get_async(
53 56
         &mut self,
54
-        tile: TileCoord,
57
+        tile_coord: TileCoord,
55 58
         source: &TileSource,
56 59
         write_to_file: bool,
57 60
         ) -> Option<&image::DynamicImage>
@@ -66,20 +69,22 @@ impl TileCache {
66 69
             println!("CACHE SIZE: {} tiles", self.map.len());
67 70
         }
68 71
 
72
+        let tile = Tile::new(tile_coord, source.id());
73
+
69 74
         //TODO Return the value from get_refresh with borrowck agreeing that this is OK.
70 75
         self.map.get_refresh(&tile);
71 76
 
72 77
         match self.map.entry(tile) {
73 78
             Entry::Occupied(entry) => Some(entry.into_mut()),
74 79
             Entry::Vacant(_) => {
75
-                self.loader.async_request(tile, source, write_to_file);
80
+                self.loader.async_request(tile_coord, source, write_to_file);
76 81
                 None
77 82
             }
78 83
         }
79 84
     }
80 85
 
81 86
     // Return a tile from the cache but do not use TileLoader.
82
-    pub fn lookup(&mut self, tile: TileCoord, source: &TileSource) -> Option<&image::DynamicImage> {
87
+    pub fn lookup(&mut self, tile: Tile) -> Option<&image::DynamicImage> {
83 88
         //TODO Return the value from get_refresh with borrowck agreeing that this is OK.
84 89
         self.map.get_refresh(&tile);
85 90
 

+ 7
- 5
src/tile_cache_gl.rs Wyświetl plik

@@ -4,6 +4,7 @@ use map_view::VisibleTile;
4 4
 use std::collections::HashMap;
5 5
 use std::collections::hash_map::Entry;
6 6
 use texture::Texture;
7
+use tile::Tile;
7 8
 use tile_cache::TileCache;
8 9
 use tile_source::TileSource;
9 10
 
@@ -37,8 +38,8 @@ pub struct TexturedVisibleTile {
37 38
 pub struct TileCacheGl<'a> {
38 39
     texture: Texture<'a>,
39 40
     tile_size: u32,
40
-    slots_lru: LinkedHashMap<CacheSlot, Option<TileCoord>>, // LRU cache of slots
41
-    tile_to_slot: HashMap<TileCoord, CacheSlot>,
41
+    slots_lru: LinkedHashMap<CacheSlot, Option<Tile>>, // LRU cache of slots
42
+    tile_to_slot: HashMap<Tile, CacheSlot>,
42 43
 }
43 44
 
44 45
 impl<'a> TileCacheGl<'a> {
@@ -69,15 +70,16 @@ impl<'a> TileCacheGl<'a> {
69 70
         CacheSlot { x: 0, y: 0 }
70 71
     }
71 72
 
72
-    pub fn store(&mut self, tile: TileCoord, source: &TileSource, cache: &mut TileCache, load: bool) -> Option<CacheSlot> {
73
+    pub fn store(&mut self, tile_coord: TileCoord, source: &TileSource, cache: &mut TileCache, load: bool) -> Option<CacheSlot> {
73 74
         let mut remove_tile = None;
75
+        let tile = Tile::new(tile_coord, source.id());
74 76
 
75 77
         let slot = match self.tile_to_slot.entry(tile) {
76 78
             Entry::Vacant(entry) => {
77 79
                 let img_option = if load {
78
-                    cache.get_async(tile, source, true)
80
+                    cache.get_async(tile_coord, source, true)
79 81
                 } else {
80
-                    cache.lookup(tile, source)
82
+                    cache.lookup(tile)
81 83
                 };
82 84
 
83 85
                 if let Some(img) = img_option {

+ 26
- 24
src/tile_loader.rs Wyświetl plik

@@ -8,7 +8,8 @@ use std::io::Write;
8 8
 use std::path::{Path, PathBuf};
9 9
 use std::sync::mpsc;
10 10
 use std::thread;
11
-use tile_source::{TileSource, TileSourceId};
11
+use tile::Tile;
12
+use tile_source::TileSource;
12 13
 
13 14
 
14 15
 //TODO remember failed loading attempts
@@ -17,14 +18,14 @@ use tile_source::{TileSource, TileSourceId};
17 18
 pub struct TileLoader {
18 19
     client: Option<Client>,
19 20
     join_handle: thread::JoinHandle<()>,
20
-    request_tx: mpsc::Sender<(TileCoord, TileSourceId, String, PathBuf, bool)>,
21
-    result_rx: mpsc::Receiver<(TileCoord, TileSourceId, Option<DynamicImage>)>,
22
-    pending: HashSet<(TileCoord, TileSourceId)>,
21
+    request_tx: mpsc::Sender<(Tile, String, PathBuf, bool)>,
22
+    result_rx: mpsc::Receiver<(Tile, Option<DynamicImage>)>,
23
+    pending: HashSet<Tile>,
23 24
 }
24 25
 
25 26
 impl TileLoader {
26 27
     pub fn new<F>(notice_func: F) -> Self
27
-        where F: Fn(TileCoord) + Sync + Send + 'static,
28
+        where F: Fn(Tile) + Sync + Send + 'static,
28 29
     {
29 30
         let (request_tx, request_rx) = mpsc::channel();
30 31
         let (result_tx, result_rx) = mpsc::channel();
@@ -39,18 +40,18 @@ impl TileLoader {
39 40
     }
40 41
 
41 42
     fn work<F>(
42
-        request_rx: mpsc::Receiver<(TileCoord, TileSourceId, String, PathBuf, bool)>,
43
-        result_tx: mpsc::Sender<(TileCoord, TileSourceId, Option<DynamicImage>)>,
43
+        request_rx: mpsc::Receiver<(Tile, String, PathBuf, bool)>,
44
+        result_tx: mpsc::Sender<(Tile, Option<DynamicImage>)>,
44 45
         notice_func: F,
45 46
     )
46
-        where F: Fn(TileCoord) + Sync + Send + 'static,
47
+        where F: Fn(Tile) + Sync + Send + 'static,
47 48
     {
48 49
         let mut client_opt = None;
49
-        while let Ok((tile, source_id, url, path, write_to_file)) = request_rx.recv() {
50
+        while let Ok((tile, url, path, write_to_file)) = request_rx.recv() {
50 51
             println!("work {:?}", tile);
51 52
             match image::open(&path) {
52 53
                 Ok(img) => {
53
-                    result_tx.send((tile, source_id, Some(img))).unwrap();
54
+                    result_tx.send((tile, Some(img))).unwrap();
54 55
                     notice_func(tile);
55 56
                     continue;
56 57
                 },
@@ -66,7 +67,7 @@ impl TileLoader {
66 67
                             let mut buf: Vec<u8> = vec![];
67 68
                             response.copy_to(&mut buf).unwrap();
68 69
                             if let Ok(img) = image::load_from_memory(&buf) {
69
-                                result_tx.send((tile, source_id, Some(img))).unwrap();
70
+                                result_tx.send((tile, Some(img))).unwrap();
70 71
                                 notice_func(tile);
71 72
 
72 73
                                 if write_to_file {
@@ -80,36 +81,37 @@ impl TileLoader {
80 81
                     }
81 82
                 },
82 83
             }
83
-            result_tx.send((tile, source_id, None)).unwrap();
84
+            result_tx.send((tile, None)).unwrap();
84 85
         }
85 86
     }
86 87
 
87
-    pub fn async_request(&mut self, tile: TileCoord, source: &TileSource, write_to_file: bool) {
88
-        if tile.zoom > source.max_tile_zoom() {
88
+    pub fn async_request(&mut self, tile_coord: TileCoord, source: &TileSource, write_to_file: bool) {
89
+        if tile_coord.zoom > source.max_tile_zoom() {
89 90
             return;
90 91
         }
91 92
 
92
-        if !self.pending.contains(&(tile, source.id())) {
93
-            self.pending.insert((tile, source.id()));
93
+        let tile = Tile::new(tile_coord, source.id());
94
+
95
+        if !self.pending.contains(&tile) {
96
+            self.pending.insert(tile);
94 97
             self.request_tx.send((
95 98
                 tile,
96
-                source.id(),
97
-                source.remote_tile_url(tile),
98
-                source.local_tile_path(tile),
99
+                source.remote_tile_url(tile_coord),
100
+                source.local_tile_path(tile_coord),
99 101
                 write_to_file
100 102
             )).unwrap();
101 103
         }
102 104
     }
103 105
 
104
-    pub fn async_result(&mut self) -> Option<(TileCoord, DynamicImage)> {
106
+    pub fn async_result(&mut self) -> Option<(Tile, DynamicImage)> {
105 107
         match self.result_rx.try_recv() {
106 108
             Err(_) => None,
107
-            Ok((tile, source_id, None)) => {
108
-                self.pending.remove(&(tile, source_id));
109
+            Ok((tile, None)) => {
110
+                self.pending.remove(&tile);
109 111
                 None
110 112
             },
111
-            Ok((tile, source_id, Some(img))) => {
112
-                self.pending.remove(&(tile, source_id));
113
+            Ok((tile, Some(img))) => {
114
+                self.pending.remove(&tile);
113 115
                 Some((tile, img))
114 116
             },
115 117
         }

+ 14
- 18
src/tile_source.rs Wyświetl plik

@@ -1,5 +1,5 @@
1 1
 use coord::TileCoord;
2
-use std::path::{Path, PathBuf};
2
+use std::path::PathBuf;
3 3
 
4 4
 
5 5
 #[derive(Clone, Debug)]
@@ -36,35 +36,31 @@ impl TileSource {
36 36
         }
37 37
     }
38 38
 
39
-    pub fn local_tile_path(&self, tile: TileCoord) -> PathBuf {
39
+    pub fn local_tile_path(&self, tile_coord: TileCoord) -> PathBuf {
40 40
         let mut path = PathBuf::from(&self.directory);
41
-        path.push(tile.zoom.to_string());
42
-        path.push(tile.x.to_string());
43
-        path.push(tile.y.to_string() + ".png");
41
+        path.push(tile_coord.zoom.to_string());
42
+        path.push(tile_coord.x.to_string());
43
+        path.push(tile_coord.y.to_string() + ".png");
44 44
 
45 45
         path
46 46
     }
47 47
 
48
-    pub fn remote_tile_url(&self, tile: TileCoord) -> String {
49
-        Self::fill_template(&self.url_template, tile)
48
+    pub fn remote_tile_url(&self, tile_coord: TileCoord) -> String {
49
+        Self::fill_template(&self.url_template, tile_coord)
50 50
     }
51 51
 
52 52
     pub fn max_tile_zoom(&self) -> u32 {
53 53
         self.max_zoom
54 54
     }
55 55
 
56
-    fn fill_template(template: &str, tile: TileCoord) -> String {
57
-        let x_str = tile.x.to_string();
58
-        let y_str = tile.y.to_string();
59
-        let z_str = tile.zoom.to_string();
60
-
61
-        //let len = (template.len() + x_str.len() + y_str.len() + z_str.len()).saturating_sub(9);
56
+    fn fill_template(template: &str, tile_coord: TileCoord) -> String {
57
+        let x_str = tile_coord.x.to_string();
58
+        let y_str = tile_coord.y.to_string();
59
+        let z_str = tile_coord.zoom.to_string();
62 60
 
63 61
         //TODO use the regex crate for templates or some other more elegant method
64
-        let string = template.replacen("{x}", &x_str, 1);
65
-        let string = string.replacen("{y}", &y_str, 1);
66
-        let string = string.replacen("{z}", &z_str, 1);
67
-
68
-        return string;
62
+        template.replacen("{x}", &x_str, 1)
63
+                .replacen("{y}", &y_str, 1)
64
+                .replacen("{z}", &z_str, 1)
69 65
     }
70 66
 }