FlightGear next
tilemgr.cxx
Go to the documentation of this file.
1// tilemgr.cxx -- routines to handle dynamic management of scenery tiles
2//
3// Written by Curtis Olson, started January 1998.
4//
5// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20//
21// $Id$
22
23
24#ifdef HAVE_CONFIG_H
25# include <config.h>
26#endif
27
28#include <algorithm>
29#include <functional>
30
31#include <osgViewer/Viewer>
32#include <osgDB/Registry>
33
34#include <simgear/constants.h>
35#include <simgear/debug/logstream.hxx>
36#include <simgear/structure/exception.hxx>
37#include <simgear/scene/model/modellib.hxx>
38#include <simgear/scene/util/SGReaderWriterOptions.hxx>
39#include <simgear/scene/tgdb/VPBTechnique.hxx>
40#include <simgear/scene/tgdb/VPBLineFeatureRenderer.hxx>
41#include <simgear/scene/tsync/terrasync.hxx>
42#include <simgear/misc/strutils.hxx>
43#include <simgear/scene/material/matlib.hxx>
44
45#include <Main/fg_props.hxx>
46#include <Main/globals.hxx>
50#include <Viewer/renderer.hxx>
51#include <Viewer/splash.hxx>
52
53#include "scenery.hxx"
54#include "SceneryPager.hxx"
55#include "tilemgr.hxx"
56
58
59
60#ifdef SG_TORRENT
61
62#include <simgear/io/torrent.hxx>
63
64static bool s_torrentRuntimeEnabled = false;
65static std::mutex s_torrentMutex;
66static std::vector<std::string> s_torrentScenerySuffixes;
67
68
69struct torrentStatus
70{
71 std::shared_ptr<libtorrent::torrent_info> torrent_info;
72 bool finished = false;
73 bool ok = false; // Only valid if .finished is true.
74};
75
76static std::map<std::string, torrentStatus> s_torrentDirToStatus;
77
78static std::ostream& operator<<(std::ostream& out, const torrentStatus& status)
79{
80 return out << "torrentStatus {"
81 << " finished=" << status.finished
82 << " ok=" << status.ok
83 << "}";
84}
85
86static void torrentScheduleTileCallback(const std::string& dir, bool ok)
87{
88 SG_LOG(SG_TERRAIN, SG_ALERT, "torrentScheduleTileCallback(): dir=" << dir << " ok=" << ok);
89 std::unique_lock lock(s_torrentMutex);
90
91 assert(!s_torrentDirToStatus[dir].finished);
92
93 s_torrentDirToStatus[dir].torrent_info = nullptr;
94 s_torrentDirToStatus[dir].finished = true;
95 s_torrentDirToStatus[dir].ok = ok;
96}
97
98static bool torrentIsSyncing(const std::string& path)
99{
100 SG_LOG( SG_TERRAIN, SG_DEBUG, "torrentIsSyncing(): path=" << path);
101 std::unique_lock lock(s_torrentMutex);
102 // Need to look at active torrents and see whether they include <path>
103 // (which will be a unique leafname, for example `2941832.stg`).
104 for (auto it: s_torrentDirToStatus)
105 {
106 SG_LOG( SG_TERRAIN, SG_DEBUG, "torrentIsSyncing(): it.first=" << it.first);
107 const torrentStatus& status = it.second;
108 if (status.finished)
109 {
110 continue;
111 }
112 if (status.torrent_info)
113 {
114 const libtorrent::file_storage& file_storage = status.torrent_info->files();
115 for (int i=0; i<file_storage.num_files(); ++i)
116 {
117 SG_LOG( SG_TERRAIN, SG_DEBUG, "torrentIsSyncing():"
118 " file_storage.file_name(i)=" << file_storage.file_name(i)
119 );
120 if (file_storage.file_name(i) == path)
121 {
122 SG_LOG(SG_TERRAIN, SG_DEBUG, "torrentIsSyncing():"
123 << " returning true"
124 << " path=" << path
125 );
126 return true;
127 }
128 }
129 }
130 else
131 {
132 /* This could end up downloading <path>, we don't know yet. */
133 return true;
134 }
135 }
136 SG_LOG(SG_TERRAIN, SG_DEBUG, "torrentIsSyncing():"
137 << " returning false"
138 << " path=" << path
139 );
140 return false;
141}
142
143static void torrentInfoCallback(const std::string& dir, std::shared_ptr<libtorrent::torrent_info> torrent_info)
144{
145 std::unique_lock lock(s_torrentMutex);
146 SG_LOG(SG_TERRAIN, SG_DEBUG, "torrentInfoCallback():" << " dir=" << dir);
147 assert(!s_torrentDirToStatus[dir].torrent_info);
148 s_torrentDirToStatus[dir].torrent_info = torrent_info;
149}
150
151static void torrentScheduleTile(const SGBucket& bucket)
152{
153 std::string basePath = bucket.gen_base_path();
154 SG_LOG( SG_TERRAIN, SG_DEBUG, "torrentScheduleTile(): basePath=" << basePath);
155 assert(!s_torrentScenerySuffixes.empty());
156 std::string url_base = "http://us1mirror.flightgear.org/terrasync/ws2";
157 for (const std::string& scenerySuffix : s_torrentScenerySuffixes)
158 {
159 std::string dir = scenerySuffix + "/" + basePath;
160 size_t p = dir.rfind("/");
161 assert(p != std::string::npos);
162 std::string dir_parent = dir.substr(0, p);
163 std::string torrent_url = url_base + "/" + dir + ".torrent";
164 SG_LOG( SG_TERRAIN, SG_DEBUG, "torrentScheduleTile():"
165 << " basePath=" << basePath
166 << " scenerySuffix=" << scenerySuffix
167 << " torrent_url=" << torrent_url
168 );
169 std::unique_lock lock(s_torrentMutex);
170 auto it = s_torrentDirToStatus.find(dir);
171 if (it == s_torrentDirToStatus.end())
172 {
173 s_torrentDirToStatus[dir] = torrentStatus();
174 simgear::Torrent* torrent = globals->get_subsystem<simgear::Torrent>();
175 SGPath scenery_dir = fgGetString("/sim/terrasync/scenery-dir");
176 SG_LOG( SG_TERRAIN, SG_DEBUG, "/sim/terrasync/scenery-dir=" << scenery_dir);
177 assert(scenery_dir.str() != "");
178 /* Need to temporarily drop lock in case add_torrent_url() calls
179 our callback before returning. */
180 lock.unlock();
181 SGPath torrent_path = scenery_dir / (dir + ".torrent");
182 SGPath out_path = scenery_dir / dir_parent;
183 torrent->add_torrent_url(
184 torrent_url,
185 torrent_path,
186 out_path,
187 std::bind(torrentScheduleTileCallback, dir, std::placeholders::_1),
188 std::bind(torrentInfoCallback, dir, std::placeholders::_1)
189 );
190 lock.lock();
191 }
192 else
193 {
194 SG_LOG( SG_TERRAIN, SG_DEBUG, "torrentScheduleTile(): not downloading because already trying/tried:"
195 << " dir=" << dir
196 << " it->second=" << it->second
197 );
198 }
199 }
200
201}
202
203#endif
204
205class FGTileMgr::TileManagerListener : public SGPropertyChangeListener
206{
207public:
209 _manager(manager),
210 _useVBOsProp(fgGetNode("/sim/rendering/use-vbos", true)),
211 _enableCacheProp(fgGetNode("/sim/tile-cache/enable", true)),
212 _pagedLODMaximumProp(fgGetNode("/sim/rendering/max-paged-lod", true)),
213 _lodDetailed(fgGetNode("/sim/rendering/static-lod/detailed", true)),
214 _lodRoughDelta(fgGetNode("/sim/rendering/static-lod/rough-delta", true)),
215 _lodBareDelta(fgGetNode("/sim/rendering/static-lod/bare-delta", true)),
216 _lodRough(fgGetNode("/sim/rendering/static-lod/rough", true)),
217 _lodBare(fgGetNode("/sim/rendering/static-lod/bare", true))
218 {
219 _useVBOsProp->addChangeListener(this, true);
220
221 _enableCacheProp->addChangeListener(this, true);
222 if (_enableCacheProp->getType() == simgear::props::NONE) {
223 _enableCacheProp->setBoolValue(true);
224 }
225
226 if (_pagedLODMaximumProp->getType() == simgear::props::NONE) {
227 // not set, use OSG default / environment value variable
228 osg::ref_ptr<osgViewer::View> view(globals->get_renderer()->getView());
229 int current = view->getDatabasePager()->getTargetMaximumNumberOfPageLOD();
230 _pagedLODMaximumProp->setIntValue(current);
231 }
232 _pagedLODMaximumProp->addChangeListener(this, true);
233 _lodDetailed->addChangeListener(this, true);
234 _lodBareDelta->addChangeListener(this, true);
235 _lodRoughDelta->addChangeListener(this, true);
236 }
237
239 {
240 _useVBOsProp->removeChangeListener(this);
241 _enableCacheProp->removeChangeListener(this);
242 _pagedLODMaximumProp->removeChangeListener(this);
243 _lodDetailed->removeChangeListener(this);
244 _lodBareDelta->removeChangeListener(this);
245 _lodRoughDelta->removeChangeListener(this);
246 }
247
248 virtual void valueChanged(SGPropertyNode* prop)
249 {
250 if (prop == _useVBOsProp) {
251 bool useVBOs = prop->getBoolValue();
252 _manager->_options->setPluginStringData("SimGear::USE_VBOS",
253 useVBOs ? "ON" : "OFF");
254 } else if (prop == _enableCacheProp) {
255 _manager->_enableCache = prop->getBoolValue();
256 } else if (prop == _pagedLODMaximumProp) {
257 int v = prop->getIntValue();
258 osg::ref_ptr<osgViewer::View> view(globals->get_renderer()->getView());
259 if (view) {
260 osgDB::DatabasePager* pager = view->getDatabasePager();
261 if (pager) pager->setTargetMaximumNumberOfPageLOD(v);
262 }
263 } else if (prop == _lodDetailed || prop == _lodBareDelta || prop == _lodRoughDelta) {
264 // compatibility with earlier versions; set the static lod ranges appropriately as otherwise (bad) self managed
265 // LOD on scenery with range animations doesn't work.
266 // see also /sim/rendering/enable-range-lod-animations - which is false by default in > 2019.2 which also fixes
267 // the scenery but in a more efficient way.
268 _lodRough->setDoubleValue(_lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue());
269 _lodBare->setDoubleValue(_lodRough->getDoubleValue() + _lodBareDelta->getDoubleValue());
270 }
271
272 flightgear::addSentryBreadcrumb("Property:" + prop->getNameString() + " is now " +
273 prop->getStringValue(),
274 "info");
275 }
276
277private:
278 FGTileMgr* _manager;
279 SGPropertyNode_ptr _useVBOsProp,
280 _enableCacheProp,
281 _pagedLODMaximumProp,
282 _lodDetailed,
283 _lodRoughDelta,
284 _lodBareDelta,
285 _lodRough,
286 _lodBare
287 ;
288};
289
291 state( Start ),
292 last_state( Running ),
293 scheduled_visibility(100.0),
294 _visibilityMeters(fgGetNode("/environment/visibility-m", true)),
295 _lodDetailed(fgGetNode("/sim/rendering/static-lod/detailed", true)),
296 _lodRoughDelta(fgGetNode("/sim/rendering/static-lod/rough-delta", true)),
297 _lodBareDelta(fgGetNode("/sim/rendering/static-lod/bare-delta", true)),
298 _disableNasalHooks(fgGetNode("/sim/temp/disable-scenery-nasal", true)),
299 _scenery_loaded(fgGetNode("/sim/sceneryloaded", true)),
300 _scenery_override(fgGetNode("/sim/sceneryloaded-override", true)),
301 _pager_file_queue_size(fgGetNode("/sim/rendering/statistics/database-pager/file-queue-size", true)),
302 _pager_compile_queue_size(fgGetNode("/sim/rendering/statistics/database-pager/compile-queue-size", true)),
303 _pager_merge_queue_size(fgGetNode("/sim/rendering/statistics/database-pager/merge-queue-size", true)),
304 _pager_min_merge_time(fgGetNode("/sim/rendering/statistics/database-pager/min-merge-time", true)),
305 _pager_mean_merge_time(fgGetNode("/sim/rendering/statistics/database-pager/mean-merge-time", true)),
306 _pager_max_merge_time(fgGetNode("/sim/rendering/statistics/database-pager/max-merge-time", true)),
307 _pager_active_lod_count(fgGetNode("/sim/rendering/statistics/database-pager/active-paged-lod-count", true)),
308 _pager(FGScenery::getPagerSingleton()),
309 _enableCache(true),
310 _use_vpb(false)
311{
312 const char* torrent_enabled_path = "/sim/torrent/enabled";
313 SGPropertyNode* torrent_enabled_node = fgGetNode(torrent_enabled_path);
314 if (torrent_enabled_node)
315 {
316 #ifdef SG_TORRENT
317 s_torrentRuntimeEnabled = torrent_enabled_node->getBoolValue();
318 #else
319 SG_LOG(SG_TERRAIN, SG_ALERT,
320 "This Flightgear build does not support torrents;"
321 " ignoring property "
322 << torrent_enabled_path << "='" << torrent_enabled_node->getStringValue() << "'"
323 );
324 #endif
325 }
326}
327
328
332
333// Initialize the Tile Manager subsystem
335{
336 reinit();
337}
338
340{
341 _listener.reset();
342
343 FGScenery* scenery = globals->get_scenery();
344 if (scenery && scenery->get_terrain_branch()) {
345 osg::Group* group = scenery->get_terrain_branch();
346 group->removeChildren(0, group->getNumChildren());
347 }
348 // clear OSG cache
349 osgDB::Registry::instance()->clearObjectCache();
350 state = Start; // need to init again
351}
352
354{
355 SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
356 auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
357
358 // drops the previous options reference
359 _options = new simgear::SGReaderWriterOptions;
360 _listener.reset(new TileManagerListener(this));
361
363 _options->setPropertyNode(globals->get_props());
364
365 osgDB::FilePathList &fp = _options->getDatabasePathList();
366 const PathList &sc = globals->get_fg_scenery();
367 fp.clear();
368 for (auto it = sc.begin(); it != sc.end(); ++it) {
369 fp.push_back(it->utf8Str());
370 }
371 _options->setPluginStringData("SimGear::FG_ROOT", globals->get_fg_root().utf8Str());
372
373 if (terraSync) {
374 _options->setPluginStringData("SimGear::TERRASYNC_ROOT", globals->get_terrasync_dir().utf8Str());
375 }
376
377 if (!_disableNasalHooks->getBoolValue())
378 _options->setModelData(new FGNasalModelDataProxy);
379
380 double detailed = fgGetDouble("/sim/rendering/static-lod/detailed", SG_OBJECT_RANGE_DETAILED);
381 double rough = fgGetDouble("/sim/rendering/static-lod/rough-delta", SG_OBJECT_RANGE_ROUGH) + detailed;
382 double bare = fgGetDouble("/sim/rendering/static-lod/bare", SG_OBJECT_RANGE_BARE) + rough;
383 double tile_min_expiry = fgGetDouble("/sim/rendering/plod-minimum-expiry-time-secs", SG_TILE_MIN_EXPIRY);
384 flightgear::addSentryBreadcrumb("PLod-minimum-expiry time=" + std::to_string(tile_min_expiry), "info");
385
386 _use_vpb = fgGetBool("/scenery/use-vpb");
387
388 _options->setPluginStringData("SimGear::LOD_RANGE_BARE", std::to_string(bare));
389 _options->setPluginStringData("SimGear::LOD_RANGE_ROUGH", std::to_string(rough));
390 _options->setPluginStringData("SimGear::LOD_RANGE_DETAILED", std::to_string(detailed));
391 _options->setPluginStringData("SimGear::PAGED_LOD_EXPIRY", std::to_string(tile_min_expiry));
392
393 string_list scenerySuffixes;
394 for (auto node : fgGetNode("/sim/rendering/", true)->getChildren("scenery-path-suffix")) {
395 if (node->getBoolValue("enabled", true)) {
396 scenerySuffixes.push_back(node->getStringValue("name"));
397 }
398 }
399
400 if (scenerySuffixes.empty()) {
401 // if preferences didn't load, use some default
402 scenerySuffixes = {"Objects", "Terrain"}; // defaut values
403 }
404
405 #ifdef SG_TORRENT
406 s_torrentScenerySuffixes = scenerySuffixes;
407 #endif
408 if (terraSync) {
409 terraSync->setSceneryPathSuffixes(scenerySuffixes);
410 }
411 _options->setSceneryPathSuffixes(scenerySuffixes);
412
413 if (state != Start)
414 {
415 // protect against multiple scenery reloads and properly reset flags,
416 // otherwise aircraft fall through the ground while reloading scenery
417 if (_scenery_loaded->getBoolValue() == false) {
418 SG_LOG( SG_TERRAIN, SG_INFO, "/sim/sceneryloaded already false, avoiding duplicate re-init of tile manager" );
419 return;
420 }
421 }
422
423 _scenery_loaded->setBoolValue(false);
424 fgSetDouble("/sim/startup/splash-alpha", 1.0);
425
427
428 // remove all old scenery nodes from scenegraph and clear cache
429 osg::Group* group = globals->get_scenery()->get_terrain_branch();
430 group->removeChildren(0, group->getNumChildren());
431 tile_cache.init();
432
433 // clear OSG cache, except on initial start-up
434 if (state != Start)
435 {
436 osgDB::Registry::instance()->clearObjectCache();
437 }
438
439 state = Inited;
440
441 previous_bucket.make_bad();
442 current_bucket.make_bad();
443 scheduled_visibility = 100.0;
444
445 // force an update now
446 update(0.0);
447}
448
450{
451 _options->setMaterialLib(globals->get_matlib());
452}
453
454/* schedule a tile for loading, keep request for given amount of time.
455 * Returns true if tile is already loaded. */
456bool FGTileMgr::sched_tile( const SGBucket& b, double priority, bool current_view, double duration)
457{
458 // see if tile already exists in the cache
459 STGTileEntry *t = tile_cache.get_stg_tile( b );
460 if (!t)
461 {
462 // create a new entry
463 t = new STGTileEntry( b );
464 SG_LOG( SG_TERRAIN, SG_INFO, "sched_tile: new STG tile entry for:" << b );
465
466 // insert the tile into the cache, update will generate load request
467 if ( tile_cache.insert_tile( t ) )
468 {
469 // Attach to scene graph
470
471 t->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
472 } else
473 {
474 // insert failed (cache full with no available entries to
475 // delete.) Try again later
476 delete t;
477 return false;
478 }
479
480 SG_LOG( SG_TERRAIN, SG_DEBUG, " New tile cache size " << (int)tile_cache.get_size() );
481 }
482
483 // update tile's properties
484 tile_cache.request_tile(t,priority,current_view,duration);
485
486 if (_use_vpb) {
487 VPBTileEntry *v = tile_cache.get_vpb_tile( b );
488
489 if (!v)
490 {
491 // create a new entry
492 v = new VPBTileEntry( b, _options );
493 SG_LOG( SG_TERRAIN, SG_INFO, "sched_tile: new VPB tile entry for:" << b );
494
495 // insert the tile into the cache, update will generate load request
496 if ( tile_cache.insert_tile( v ) )
497 {
498 // Attach to scene graph
500 } else {
501 // insert failed (cache full with no available entries to
502 // delete.) Try again later
503 delete v;
504 return false;
505 }
506
507 SG_LOG( SG_TERRAIN, SG_DEBUG, " New tile cache size " << (int)tile_cache.get_size() );
508 }
509
510 // update tile's properties. We ensure the top level VPB tiles have maximum priority.
511 // The LoD system will take care of appropriate prioritization of the subtiles
512 tile_cache.request_tile(v, 1.0, current_view, duration);
513 }
514
515 return t->is_loaded();
516}
517
518/* schedule needed buckets for the current view position for loading,
519 * keep request for given amount of time */
520void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis)
521{
522 // sanity check (unfortunately needed!)
523 if (!curr_bucket.isValid() )
524 {
525 SG_LOG( SG_TERRAIN, SG_ALERT,
526 "Attempting to schedule tiles for invalid bucket" );
527 return;
528 }
529
530 double tile_width = curr_bucket.get_width_m();
531 double tile_height = curr_bucket.get_height_m();
532 SG_LOG( SG_TERRAIN, SG_INFO,
533 "scheduling needed tiles for " << curr_bucket
534 << ", tile-width-m:" << tile_width << ", tile-height-m:" << tile_height);
535
536
537 // cout << "tile width = " << tile_width << " tile_height = "
538 // << tile_height << endl;
539 // starting with 2018.3 we will use deltas rather than absolutes as it is more intuitive for the user
540 // and somewhat easier to visualise
541 double maxTileRange = _lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue() + _lodBareDelta->getDoubleValue();
542
543 double tileRangeM = std::min(vis, maxTileRange);
544 int xrange = (int)(tileRangeM / tile_width) + 1;
545 int yrange = (int)(tileRangeM / tile_height) + 1;
546 if ( xrange < 1 ) { xrange = 1; }
547 if ( yrange < 1 ) { yrange = 1; }
548
549 // make the cache twice as large to avoid losing terrain when switching
550 // between aircraft and tower views
551 tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
552 // cout << "xrange = " << xrange << " yrange = " << yrange << endl;
553 // cout << "max cache size = " << tile_cache.get_max_cache_size()
554 // << " current cache size = " << tile_cache.get_size() << endl;
555
556 // clear flags of all tiles belonging to the previous view set
557 tile_cache.clear_current_view();
558
559 // update timestamps, so all tiles scheduled now are *newer* than any tile previously loaded
560 osg::FrameStamp* framestamp
562 tile_cache.set_current_time(framestamp->getReferenceTime());
563
564 SGBucket b;
565
566 int x, y;
567 auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
568
569 /* schedule all tiles, use distance-based loading priority,
570 * so tiles are loaded in innermost-to-outermost sequence. */
571 SGGeod centerPos = curr_bucket.get_center();
572
573 for ( x = -xrange; x <= xrange; ++x )
574 {
575 for ( y = -yrange; y <= yrange; ++y )
576 {
577 SGBucket b = curr_bucket.sibling(x, y);
578 SGGeod bPos = b.get_center();
579
580 double d = SGGeodesy::distanceM(centerPos, bPos);
581
582 // Priority goes out to 2xtileRangeM because we round up the xrange/yrange above, so d is sometimes > tileRangeM.
583 double priority = (2.0 * tileRangeM - d) / (2.0 * tileRangeM);
584 SG_LOG(SG_TERRAIN, SG_DEBUG, " Scheduling Tile STG file " << b.get_center_lat() << ", " << b.get_center_lon() << " distance " << d << " priority: " << priority);
585 sched_tile( b, priority, true, 0.0 );
586
587 #ifdef SG_TORRENT
588 if (s_torrentRuntimeEnabled) {
589 torrentScheduleTile(b);
590 }
591 else
592 #endif
593 if (terraSync) {
594 terraSync->scheduleTile(b);
595 }
596 }
597 }
598}
599
604void FGTileMgr::update_queues(bool& isDownloadingScenery)
605{
606 osg::FrameStamp* framestamp = globals->get_renderer()->getFrameStamp();
607 double current_time = framestamp->getReferenceTime();
608 double vis = _visibilityMeters->getDoubleValue();
609 TileEntry *e;
610 int loading=0;
611 int sz=0;
612
613 tile_cache.set_current_time( current_time );
614 tile_cache.reset_traversal();
615
616 while ( ! tile_cache.at_end() )
617 {
618 e = tile_cache.get_current();
619 if ( e )
620 {
621 // Prepare the ssg nodes corresponding to each tile.
622 // Set the ssg transform and update it's range selector
623 // based on current visibilty
624 e->prep_ssg_node(vis);
625
626 if (!e->is_loaded()) {
627 bool nonExpiredOrCurrent = !e->is_expired(current_time) || e->is_current_view();
628 bool downloading = isTileDirSyncing(e->tileFileName);
629 isDownloadingScenery |= downloading;
630 if ( !downloading && nonExpiredOrCurrent) {
631 // schedule tile for loading with osg pager
632 _pager->queueRequest(e->tileFileName,
633 e->getNode(),
634 e->get_priority(),
635 framestamp,
637 _options.get());
638 loading++;
639 }
640 } // of tile not loaded case
641 } else {
642 SG_LOG(SG_TERRAIN, SG_ALERT, "Warning: empty tile in cache!");
643 }
644 tile_cache.next();
645 sz++;
646 }
647
648 int drop_count = sz - tile_cache.get_max_cache_size();
649 bool dropTiles = false;
650 if (_enableCache) {
651 dropTiles = ( drop_count > 0 ) && ((loading==0)||(drop_count > 10));
652 } else {
653 dropTiles = true;
654 drop_count = sz; // no limit on tiles to drop
655 }
656
657 if (dropTiles)
658 {
659 long drop_index = _enableCache ? tile_cache.get_drop_tile() :
660 tile_cache.get_first_expired_tile();
661 while ( drop_index > -1 )
662 {
663 // schedule tile for deletion with osg pager
664 TileEntry* old = tile_cache.get_tile(drop_index);
665 SG_LOG(SG_TERRAIN, SG_DEBUG, "Dropping:" << old->get_tile_bucket());
666
667 tile_cache.clear_entry(drop_index);
668
669 if (_use_vpb) {
670 // Clear out any VPB data - e.g. roads
671 simgear::VPBLineFeatureRenderer::unloadFeatures(old->get_tile_bucket());
672 }
673
674 osg::ref_ptr<osg::Object> subgraph = old->getNode();
676 delete old;
677 // zeros out subgraph ref_ptr, so subgraph is owned by
678 // the pager and will be deleted in the pager thread.
679 _pager->queueDeleteRequest(subgraph);
680
681 if (!_enableCache)
682 drop_index = tile_cache.get_first_expired_tile();
683 // limit tiles dropped to drop_count
684 else if (--drop_count > 0)
685 drop_index = tile_cache.get_drop_tile();
686 else
687 drop_index = -1;
688 }
689 } // of dropping tiles loop
690}
691
692// given the current lon/lat (in degrees), fill in the array of local
693// chunks. If the chunk isn't already in the cache, then read it from
694// disk.
696{
697 double vis = _visibilityMeters->getDoubleValue();
698 schedule_tiles_at(globals->get_view_position(), vis);
699
700 bool waitingOnTerrasync = false;
701 update_queues(waitingOnTerrasync);
702
703 if (_pager) {
704 // Update various useful statistics
705 _pager_file_queue_size->setIntValue(_pager->getFileRequestListSize());
706 _pager_compile_queue_size->setIntValue(_pager->getDataToCompileListSize());
707 _pager_merge_queue_size->setIntValue(_pager->getDataToMergeListSize());
708 _pager_active_lod_count->setIntValue(_pager->getActivePagedLODCount());
709 _pager_min_merge_time->setFloatValue(_pager->getMinimumTimeToMergeTile());
710 _pager_mean_merge_time->setFloatValue(_pager->getAverageTimeToMergeTiles());
711 _pager_max_merge_time->setFloatValue(_pager->getMaximumTimeToMergeTile());
712 }
713
714 // scenery loading check, triggers after each sim (tile manager) reinit
715 if (!_scenery_loaded->getBoolValue())
716 {
717 bool fdmInited = fgGetBool("sim/fdm-initialized");
718 bool positionFinalized = fgGetBool("sim/position-finalized");
719 bool sceneryOverride = _scenery_override->getBoolValue();
720
721
722 // we are done if final position is set and the scenery & FDM are done.
723 // scenery-override can ignore the last two, but not position finalization.
724 if (positionFinalized && (sceneryOverride || (isSceneryLoaded() && fdmInited)))
725 {
726 _scenery_loaded->setBoolValue(true);
728 }
729 else
730 {
731 if (!positionFinalized) {
732 fgSplashProgress("finalize-position");
733 } else if (waitingOnTerrasync) {
734 fgSplashProgress("downloading-scenery");
735 } else {
736 fgSplashProgress("loading-scenery");
737 }
738
739 // be nice to loader threads while waiting for initial scenery, reduce to 20fps
740 SGTimeStamp::sleepForMSec(50);
741 }
742 }
743}
744
745// schedule tiles for the viewer bucket
746// (FDM/AI/groundcache/... should use "schedule_scenery" instead)
747void FGTileMgr::schedule_tiles_at(const SGGeod& location, double range_m)
748{
749 // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
750 // << longitude << " " << latitude );
751
752 current_bucket = SGBucket( location );
753
754 // schedule more tiles when visibility increased considerably
755 // TODO Calculate tile size - instead of using fixed value (5000m)
756 if (range_m - scheduled_visibility > 5000.0)
757 previous_bucket.make_bad();
758
759 // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
760 // << current_bucket );
761 fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
762
763 // do tile load scheduling.
764 // Note that we need keep track of both viewer buckets and fdm buckets.
765 if ( state == Running ) {
766 if (last_state != state)
767 {
768 SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
769 }
770 if (current_bucket != previous_bucket) {
771 // We've moved to a new bucket, we need to schedule any
772 // needed tiles for loading.
773 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr: at " << location << ", scheduling needed for:" << current_bucket
774 << ", visibility=" << range_m);
775 scheduled_visibility = range_m;
776 schedule_needed(current_bucket, range_m);
777 }
778
779 // save bucket
780 previous_bucket = current_bucket;
781 } else if ( state == Start || state == Inited ) {
782 SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Start || Inited" );
783 // do not update bucket yet (position not valid in initial loop)
784 state = Running;
785 previous_bucket.make_bad();
786 }
787 last_state = state;
788}
789
795bool FGTileMgr::schedule_scenery(const SGGeod& position, double range_m, double duration)
796{
797 // sanity check (unfortunately needed!)
798 if (!position.isValid())
799 return false;
800
801 bool available = true;
802
803 SGBucket bucket(position);
804 available = sched_tile( bucket, 1.0, false, duration);
805
806 if ((!available)&&(duration==0.0)) {
807 SG_LOG( SG_TERRAIN, SG_DEBUG, "schedule_scenery: Scheduling tile at bucket:" << bucket << " return false" );
808 return false;
809 }
810
811 SGVec3d cartPos = SGVec3d::fromGeod(position);
812
813 // Traverse all tiles required to be there for the given visibility.
814 double tile_width = bucket.get_width_m();
815 double tile_height = bucket.get_height_m();
816 double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
817 double max_dist = tile_r + range_m;
818 double max_dist2 = max_dist*max_dist;
819
820 int xrange = (int)fabs(range_m / tile_width) + 1;
821 int yrange = (int)fabs(range_m / tile_height) + 1;
822
823 for ( int x = -xrange; x <= xrange; ++x )
824 {
825 for ( int y = -yrange; y <= yrange; ++y )
826 {
827 // We have already checked for the center tile.
828 if ( x != 0 || y != 0 )
829 {
830 SGBucket b = bucket.sibling(x, y );
831 if (!b.isValid()) {
832 continue;
833 }
834
835 double distance2 = distSqr(cartPos, SGVec3d::fromGeod(b.get_center()));
836 // Do not ask if it is just the next tile but way out of range.
837 if (distance2 <= max_dist2)
838 {
839 float priority = (max_dist2 - distance2) / max_dist2;
840 available &= sched_tile( b, priority, false, duration );
841 if ((!available)&&(duration==0.0))
842 return false;
843 }
844 }
845 }
846 }
847
848 return available;
849}
850
851// Returns true if tiles around current view position have been loaded
853{
854 double range_m = 100.0;
855 if (scheduled_visibility < range_m)
856 range_m = scheduled_visibility;
857
858 return schedule_scenery(globals->get_view_position(), range_m, 0.0);
859}
860
861bool FGTileMgr::isTileDirSyncing(const std::string& tileFileName) const
862{
863 auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
864 if (!terraSync) {
865 return false;
866 }
867
868 // if Models is syncing, also wait for it, since otherwise
869 // we get load errors
870 if (terraSync->isDataDirPending("Models")) {
871 return true;
872 }
873
874 #ifdef SG_TORRENT
875 if (s_torrentRuntimeEnabled) {
876 return torrentIsSyncing(tileFileName);
877 }
878 #endif
879 std::string nameWithoutExtension = tileFileName.substr(0, tileFileName.size() - 4);
880 long int bucketIndex = simgear::strutils::to_int(nameWithoutExtension);
881 SGBucket bucket(bucketIndex);
882
883 return terraSync->isTileDirPending(bucket.gen_base_path());
884}
#define p(x)
#define i(x)
virtual FGRenderer * get_renderer() const
Definition globals.cxx:572
FGScenery * get_scenery() const
Definition globals.cxx:950
Thread-safe proxy for FGNasalModelData.
osg::FrameStamp * getFrameStamp() const
Definition renderer.cxx:800
osg::Group * get_terrain_branch() const
Definition scenery.hxx:130
TileManagerListener(FGTileMgr *manager)
Definition tilemgr.cxx:208
virtual void valueChanged(SGPropertyNode *prop)
Definition tilemgr.cxx:248
bool isSceneryLoaded()
Definition tilemgr.cxx:852
void reinit()
Definition tilemgr.cxx:353
bool schedule_scenery(const SGGeod &position, double range_m, double duration=0.0)
Schedules scenery for given position.
Definition tilemgr.cxx:795
void shutdown()
Definition tilemgr.cxx:339
void init()
Definition tilemgr.cxx:334
void materialLibChanged()
Definition tilemgr.cxx:449
friend class TileManagerListener
Definition tilemgr.hxx:77
void update(double dt)
Definition tilemgr.cxx:695
STGTileEntry * get_stg_tile(const SGBucket &b) const
bool insert_tile(STGTileEntry *e)
Create a new tile and enqueue it for loading.
std::string tileFileName
Definition tileentry.hxx:58
void prep_ssg_node(float vis)
Definition tileentry.cxx:91
bool is_expired(double current_time) const
Return false if the tile entry is still needed, otherwise return true indicating that the tile is no ...
float get_priority() const
osg::ref_ptr< osg::Referenced > & getDatabaseRequest()
void addToSceneGraph(osg::Group *terrain_branch)
Add terrain mesh and ground lighting to scene graph.
bool is_current_view() const
const SGBucket & get_tile_bucket() const
Return the "bucket" for this tile.
bool is_loaded() const
Return true if the tile entry is loaded, otherwise return false indicating that the loading thread is...
void removeFromSceneGraph()
disconnect terrain mesh and ground lighting nodes from scene graph for this tile.
osg::LOD * getNode() const
return the scenegraph node for the terrain
static int status
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
bool fgSetInt(const char *name, int val)
Set an int value for a property.
Definition fg_props.cxx:568
FGGlobals * globals
Definition globals.cxx:142
std::vector< SGPath > PathList
Definition globals.hxx:37
std::vector< std::string > string_list
Definition globals.hxx:36
void addSentryBreadcrumb(const std::string &, const std::string &)
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
Definition proptest.cpp:31
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
std::ostream & operator<<(std::ostream &out, const FGFrameInfo &frame_info)
static osg::ref_ptr< SceneryPager > pager
Definition scenery.cxx:558
void fgSplashProgress(const char *identifier, unsigned int percent)
Set progress information.
Definition splash.cxx:863