Compare commits

...
142 Commits
Author SHA1 Message Date
Erik Hofman c19cf094a7 Backport the code to be able to display the region name of the file that holds the selected material. 2021-05-27 15:55:02 +02:00
Automatic Release Builder 279179e88d new version: 2020.3.9 2021-05-27 11:09:42 +01:00
James Turner 0ddd3e7f2f Add sg_location to all exceptions, add report flag
Move sg_location member up to the base clase, since it’s potentially
useful in more types.

Allow suppression of the report callback when the exception is thrown
2021-05-05 15:00:23 +01:00
James Turner fe96298be5 Thread-safe error reporting in lowlevel.cxx
Use an exception rather than polling a flag, for checking errors
during BTG reading. This should allow us to give a correctly
identified error, at exactly the point the read fails.
2021-05-05 14:58:37 +01:00
James Turner c6351292dd TerraSync: add a warning file to the root dir.
Try to discourage users from adding custom content underneath the
Terrasync dir, since it can be over-written.
2021-04-23 12:15:34 +01:00
James Turner e2caad3b0b TerraSync: better reporting of permissions failures removing files
Log a ‘failed to remove orphan’ error as an error with the repository,
instead of failing the entire sync
2021-04-23 12:15:34 +01:00
James Turner a095ab684c ASan: fix a leak in GZ extraction 2021-04-23 11:55:02 +01:00
James Turner fe41a03180 Asan: fix leaks in Catalog code 2021-04-23 11:54:56 +01:00
Automatic Release Builder 332d9dfadb new version: 2020.3.8 2021-03-24 11:08:07 +00:00
Automatic Release Builder c05802a498 new version: 2020.3.8 2021-03-24 11:04:18 +00:00
James Turner 0970bb1be2 Fix for local particle update
See issue at:
https://sourceforge.net/p/flightgear/codetickets/2568/
2021-03-24 09:59:13 +00:00
James Turner c9d83fab6c TerraSync: allow an explicit osm2city server
Fixes an error case where a manual TerraSync server is specified; we
would attempt to use an empty string as the OSM2City server, with
hilarious consequences.

Sentry-Id: FLIGHTGEAR-NCZ
2021-03-16 20:14:34 +00:00
James Turner edcce32f24 Remove stray include of std::filesystem 2021-03-07 10:53:36 +00:00
Automatic Release Builder 68d265f0e7 Set correct version files 2021-03-04 22:02:11 +00:00
Automatic Release Builder b985bb5757 new version: 2020.3.7 2021-03-01 12:11:33 +00:00
legoboyvdlp R f030816385 TerraSync: counter to fetch number of bytes which have been extracted from a tarball file 2021-03-01 09:56:48 +00:00
James Turner 6f9f694eff HTTPRepository: improving handling of archives
Avoid hard-coding the archive extension, and ensure the extracted
archive directory is not orphaned on update. Finally, use the
literal filename in the .dirindex when computing the hash, rather
than adding a .tgz extension.
2021-03-01 09:56:48 +00:00
James Turner a0d7f0e172 TerraSync: allow separate OSM2City server
Lookup OSM2City using a separate service profile, and use this server
for requests for OSM2City suffix dirs.
2021-03-01 09:51:24 +00:00
James Turner 9f98e438cb Fix HTTPClient reset() behaviour
Ensure all data members are correctly re-initialzied when doing a reset.

This shoed up as negative ‘bytes downloaded’ counts after a TerraSync
abandon and retry.
2021-02-22 11:29:05 +00:00
James Turner f029ca7b64 Particles: replace use of ParticleSystemUpdater
Extend our own particle manager to replace the OSG particle system
updater. This fixes thread-safety and also timing (better match to
simulation dt values). We also use weak pointers (observer_ptr in
OSG terminology) to ensure particle systems are released once their
frame is gone.
2021-02-22 11:29:05 +00:00
James Turner ca6c6dd6d3 Text-animation: fix missing encoding specification
Ensure we can pass full UTF-8 strings into text animations. Will consider
for back-port after discussion on the devel list.

Ticket-Id: https://sourceforge.net/p/flightgear/codetickets/2512/
2021-02-22 11:10:37 +00:00
Automatic Release Builder 4ba4ea5602 Adding code to see if it improves crash on computeHash.
Also add a test case to verify behaviour on empty files is correct.

Sentry-Id: FLIGHTGEAR-AY2
2021-02-04 10:28:08 +00:00
Automatic Release Builder ca5f66da9f new version: 2020.3.6 2021-01-20 13:01:25 +00:00
James Turner 92d053d850 Tolerate blank lines in buildings lists 2021-01-20 11:35:33 +00:00
Colin Geniet 001ae80723 Animations: Fix spin for Compositor lights
The 'spin' animation has a strange behaviour:
it pushes a temporary rotation matrix on the transformation stack during
cull traversal, and removes it thereafter.
Thus, the rotation matrix is missing outside of cull traversal, e.g.
when the position of Compositor lights is computed.

The same issue was fixed for the 'rotate' animation by commit
e202d4e4a5
(this mentions broken 'picking' animations as a different manifestation
of the same issue).

Fix this by setting the angle of the persistent SGRotateTransform,
instead of creating a temporary rotation matrix.
2021-01-15 15:45:26 +00:00
James Turner 58d9a6d0b5 Sentry: adjust reporting of missing shaders
Reduce noise on the backend when a shader is missing, by special-
casing how we report it.
2021-01-05 12:58:46 +00:00
Scott Giese 261316cb45 Support Boost v1.75
Resolved a breaking change.
2021-01-02 21:57:54 -06:00
Stuart Buchanan 2ad4d2e672 Fix hang on "Loading Scenery" over the sea
Previously attempting to start on an ocean tile with static
models present cause FG to spin on "Loading Scenery".

This was caused by not creating a BVH for an ocean tile in
the specific case where there was STG models, due to a PagedLOD
node being inserted in the scene graph.  So
FG never thought the scenery was loaded sufficient to place
the aircraft.

By explicitly creating a BVH for ocean tiles the problem is
fixed.

Candidate for 2020.X
2021-01-01 22:41:47 +00:00
James Turner 1d9fa929fa Launhcer: fix crashes adding a catalog
Traversing a container which is modified causes crashes, take a copy
during traversal for firePackageStatus.

Sentry-Id: FLIGHTGEAR-CJF
Sentry-Id: FLIGHTGEAR-CJ5
2021-01-01 22:41:42 +00:00
James Turner 26bb6236a0 Effect builder: catch allocation failures
Fail on bad_alloc of reading texture images, instead of crashing.
2020-12-28 15:08:49 +00:00
Scott Giese b2acf5a623 fix: sky and cockpit to turn black
Metar sky condition without base layer height (e.g. FEW///) is the cause of this issue.
Randomize the base layer height when the actual height is unknown.
2020-12-26 10:53:49 -06:00
James Turner bea88bb3a9 Fix BTG error reporting
Use gzerror, not strerror, for these codes.
2020-12-21 15:31:36 +00:00
Automatic Release Builder def2af2bdd new version: 2020.3.5 2020-12-17 13:48:00 +00:00
Fernando García Liñán 6f6d705f22 Add support for anisotropic filtering in Canvas 2020-12-17 13:00:01 +00:00
Richard Harrison e9c33104d3 DDS-TC add option to exclude all Canvas orignated images. 2020-12-17 12:59:26 +00:00
Richard Harrison 985897f8ba Emesary : change to use scoped lock
- scoped locks using lock_guard are better
- because of this the exception handler can also be removed;
  although this was originally intended to manage the locks
  it never did work properly with C++ exceptions.
2020-12-17 12:57:53 +00:00
Richard Harrison 4251b28e88 Emesary: add missing property initialisation to constructor 2020-12-17 12:56:49 +00:00
James Turner 76540a211c SGGeod: add static constructor of an invalid Geod
Use this to allow explicitly initializing a value which isValid
will return false for.
2020-12-17 12:55:30 +00:00
James Turner 904fc5a7dd Fix Nasal GC errors on tests/reset
Ensure the Context temps are cleared, and when recycling an naCode,
ensure old values are cleared explicily.

Sentry-Id: FLIGHTGEAR-Y
2020-12-17 12:55:25 +00:00
Stuart Buchanan 4aebc159d5 Use ref_ptr for ReaderWriterSPT 2020-12-07 17:18:32 +00:00
Richard Harrison f89227fc1a Remove mutex lock on realizeTechniques
This was causing a deadlock - and I can't quite remember what problem it was intended to solve so it is best to remove it.
2020-12-07 17:17:44 +00:00
James Turner f50f383cc0 Reporting of std::bad_alloc in Subsystem::update
Trying to trace down our bad-alloc exception, starting with the simplest
place for now.
2020-12-07 17:16:58 +00:00
James Turner 706ab387de Add reporting callback option to SimGear
Allows us to trigger an error logging callback explicitly, which can
be used to drive Sentry.io on the FlightGear side.
2020-12-07 17:16:47 +00:00
James Turner cddfdb7d1d TerraSync: stronger fix for handling 0-byte files
Change logic so we create an empty file for such cases, i.e exactly
matching the repository. This simplifies logic in downstream code,
compared with not creating a local file.

Add a test-case to cover this

Modify TerraSync to detect a failure of Airports_archive downloading,
and fall back to file-by-file updating.
2020-12-07 17:15:51 +00:00
James Turner 3c64578848 HTTPRepository: don’t crash on empty files
Fix some additional crash cases around 0-length files
2020-12-07 17:15:38 +00:00
James Turner e1fe9b45e0 Unzip: adjust error reporting mechanism
Don’t use local exception throw+catch to report failures in extracting
a zip archive, since this generates noise in Sentry.
2020-12-07 17:15:30 +00:00
James Turner 8fdc1d306f State-machines: don’t require name for transitions
Allow anonymous transitions, since the name is purely informational
(unlike for states).

Sentry-Id: FLIGHTGEAR-9H
2020-12-07 17:15:17 +00:00
Automatic Release Builder 6e0c39bb68 new version: 2020.3.4 2020-11-30 11:17:52 +00:00
Julian Smith f20b416cfe simgear/props/props.cxx: use rmutex to protect SGPropertyNodeListeners.
Also added asserts to check _num_iterators always >= 0.
2020-11-29 20:16:46 +00:00
Scott Giese 47e06b5216 METAR: mitigate wind sensor failures 2020-11-29 16:25:01 +00:00
James Turner bfcdf22705 TerraSync: fix crashes with null file return
Not sure how this is happening, but, check for a null file object
in FileGetRequest::onDone.
2020-11-29 16:23:44 +00:00
Stuart Buchanan d0db407faa Set minimum expiry time on STG nodes. 2020-11-29 16:23:40 +00:00
Automatic Release Builder d95b1c0441 new version: 2020.3.3 2020-11-12 11:14:23 +00:00
James Turner 837ba86d57 DNSClient: own requests, and cancel them on timeout
Fixes crashes where a request times-out, but then is completed by
UDN sometime afterwards, with a free-d object. Have the DNS::Client own
requests, and be able to retrieve the udns_query to cancel them, in 
the timeout case.

Fixes a couple of Sentry reports.
2020-11-12 09:39:58 +00:00
Automatic Release Builder 0cb1b463e1 Catalogs: fix ownership of new Catalogs
When doing the initial download of a Catalog, ensure we still keep
an owning ref to it.
2020-11-11 21:18:57 +00:00
Automatic Release Builder 8a772c8edd new version: 2020.3.2 2020-11-04 22:08:28 +00:00
James Turner 03bdad0a10 Try to capture cause of BTG load failures 2020-11-04 11:52:58 +00:00
James Turner 537776e1f8 TerraSync: fix local-file copying
Avoid downloading data corresponding to files shipped in FGData.
2020-11-04 11:22:48 +00:00
James Turner f34a4a304e Untar: log error details when output create fails
Trying to understand why writing to the output files fails for some
users.

Sentry-Id: FLIGHTGEAR-SS
2020-11-03 21:26:03 +00:00
James Turner a59c4e2c8b TerraSync: make tarball extraction asynchronous 2020-11-03 17:49:19 +00:00
Julian Smith 46f4967f6e Allow use of old zlib-1.2.3 on OpenBSD.
As of 2020-08-01, OpenBSD's system zlib is 1.2.3 which doesn't
have gzoffset(). However we can get away with this by making
gzfilebuf::approxOffset() always return zero.
2020-11-01 11:12:00 +00:00
James Turner 9305417207 Terrasync: tarball extraction, use larger buffer
Use a 1MB buffer, 2kByte is very 1979 :)
2020-11-01 11:11:31 +00:00
James Turner 11da8b33f9 TerraSync: switch to per-directory hash caching
Avoid a central hash cache becoming enormous, now we use the selective
download scheme for the tile dirs.

Hash name is changed to co-exist with older versions.
2020-11-01 11:11:27 +00:00
James Turner c7b320eb55 Fix Airports/ initial sync 2020-11-01 11:11:23 +00:00
James Turner 72b2eb0ebf TerraSync: avoid 404s to probe missing tiles
Use the top-level dirIndex files to determine if a 1x1 tile dir
exists, instead of proving the server via a 404. This reduces the
number of requests we make considerably, which is … important.
2020-11-01 11:11:19 +00:00
James Turner ec3829addb TerraSync: validate local dirs incrementally
Add a process() method to HTTPRepository, and use this to
incrementally validate subdirs after the .dirIndex is received. This 
avoids large pauses of the TerraSync thread, when all of Airports/
is validated at once.
2020-11-01 11:11:15 +00:00
James Turner 96bafef3f3 TerraSync: use an unordered_map for the hash cache
Linear-scan is a bit slow in debug builds, for the large Airports/ tree;
switch to an unordered_map.

Will back-port to the LTS once tested a bit more.
2020-11-01 11:11:12 +00:00
James Turner 3ff3bd0a6c Props: allow flushing the atomic change listener
Trying to narrow down causes of the ‘unregister listeners crashes on
shutdown’ reports.
2020-11-01 11:11:07 +00:00
Automatic Release Builder 78d073a0f0 new version: 2020.3.1 2020-10-26 09:09:22 +00:00
Scott Giese d62796c19d METAR: When Wind unit not specified, default to knots.
FLIGHTGEAR-F6 resolved
2020-10-26 08:58:06 +00:00
Scott Giese 7ea7ff43fc METAR Wind Sensor Failure.
FLIGHTGEAR-F9 resolved.
2020-10-26 08:57:24 +00:00
James Turner dafd185595 Fix for crash reported by Michael Danilov
In case uDNS returns a NULL txt pointer, don’t try to create a
std::string from it, since this will crash,

See: https://sourceforge.net/p/flightgear/codetickets/2398/
2020-10-26 08:54:18 +00:00
Automatic Release Builder 1568ed8b97 Catalog migration: migrate packages too
When doing a catalog migration to a new ID (eg, 2018 -> 2020), also
mark the installed packages for installation, on the new catalog.

Related to this, when manually removing a catalog, record this fact,
so we don’t re-add it automatically due to migration.

Add unit-tests covering both of these cases.
2020-10-22 21:27:47 +01:00
Automatic Release Builder 444e2ffb2d Sound: readWAV: avoid common exceptions.
Avoid exceptions for the common ‘file not found’ case, and instead
return false / nullptr. Erik says it’s fine.
2020-10-15 17:00:30 +01:00
Automatic Release Builder 32ccdaec6f Update version file 2020-10-13 22:27:38 +01:00
Automatic Release Builder bd9f04d980 TerraSync: retry after socket failures
Assume socket failures are intermittent, up to some maximum count
(currently configured as 16). Add a test case to cover this.
2020-10-13 11:38:35 +01:00
Automatic Release Builder 9c530d6978 TerraSync: Rate-limit hash-cache writes
This helps with IO-limited performance on Windows
2020-10-07 12:23:33 +01:00
Automatic Release Builder 05094510be SGPath: optimise exists() on Windows
For a very common existence only check, use a dedicated win32 API
function, to save some time.
2020-10-07 12:21:49 +01:00
Automatic Release Builder 41e43eeba0 TerraSync: fix Windows behaviour
SGDir on Windows returns dot-files by default, which includes our
hash-cache, causing it to be orphaned.
2020-10-06 23:51:06 +01:00
Automatic Release Builder f95cbd703a Add a hasInstance to ResourceManager
This is needed to allow order-independent shutdown, due to the dumb
ownership behaviour for providers.
2020-10-06 11:34:03 +01:00
Automatic Release Builder e39036a635 SGFile::computeHash: check malloc result
Trying to fix a reported crash, in this code. malloc() returning null
seems unlikely but worth checking for. ALso use a unique_ptr with
a custom deleter to avoid a leak if we throw.

Sentry-Id: FLIGHTGEAR-3Y
2020-10-06 11:33:17 +01:00
Automatic Release Builder 1171d57b72 XMLSound: Avoid using exceptions for missing file
Reducing how noisy our exception reporting is, by using simple return
value from SGXMLSound::init.
2020-10-05 14:21:37 +01:00
Automatic Release Builder 852058150b TerraSync: fast start Airports/ sync
If TerraSync/Airports is missing, download Airport_archive.tgz instead.
In all other cases update as normal. This has the effect, that fresh
installs make many fewer requests to TerraSync servers.
2020-10-05 11:58:03 +01:00
Automatic Release Builder 9be955262e Terrasync: re-add persistent update cache
Re-add (with some tweaks) the persistent tile-cache code, so that
TerraSync checks the server at most once per 24 hour period, for a
given repository path.

(Disbale the cache by setting /sim/terrasync/enable-persistent-cache=0)
2020-10-01 10:00:22 +01:00
Automatic Release Builder bfa411e9b7 Packages: fix circular refernece bug
Packages had a strong back-pointer to their catalog, creating a circular
reference loop. Break this so catalogs & packages are freed on exit.
2020-09-30 15:16:41 +01:00
Automatic Release Builder dab015742a Catalogs: fix enabling/disabling
Relates to bug Michael found in the launcher:
https://sourceforge.net/p/flightgear/codetickets/2380/
2020-09-30 14:38:35 +01:00
Automatic Release Builder 0ab81d36b9 TerraSync: tweak warnings around checksum failures
Trying to reduce log spam when there is a server-side failure.
2020-09-29 17:43:18 +01:00
Automatic Release Builder 4d905135e8 Set optimisation flags for RelwithDebInfo
Ensure we use-O3 for AppImage (RelWithDebInfo) builds
2020-09-27 11:15:28 +01:00
Automatic Release Builder afad224ca0 Fix level of terrasync start/stop messages 2020-09-25 13:54:36 +01:00
Automatic Release Builder 99c159d46e Harden Repo::computeHashForPath
Check for some error cases in computeHashForPath, since this showed up
in some crash reports.
2020-09-23 22:08:55 +01:00
Automatic Release Builder 733efd08dd Exceptions: optional callback for exception throws.
This is to allow recording an error+stacktrace whenever an exception
is thrown, since that’s the point when the stack is interesting.
2020-09-22 15:26:33 +01:00
James Turner 3753c62783 Add MANDATORY_INFO log priority.
New log-level to avoid using ‘ALERT’ for expected mandatory messages,
since I want to do error collection on real alerts in a future commit.

As part of this, split logstream.hxx into separate header files, and
add a new virtual hook to LogCallback which takes the complete log-entry
2020-09-18 14:49:33 +01:00
James Turner 3e804605b7 ASan: make copied files in log entries safe at init.
Ensure that if we copy file-names of log entries while startup
logging is active, we free them, but only once startup logging is
disabled, or on shutdown. This is needed to avoid crashes when
we use file-name copying for the Qt message handler.
2020-09-18 14:49:33 +01:00
Stuart Buchanan 35b1d321fe Check correctly for failed BUILDING_LIST
Previous additional checks for a value BUILDING_LIST entry
used an enum incorrectly, resulting in all BUILDING_LIST entries
failing.  Now fixed to check bad() / fail() which are bool.
2020-09-09 21:59:24 +01:00
James Turner 6ab7f68f4b Bindings: don’t cache the command pointer
Caching it complicates add/remove command logic, so making a simple
fix for now, which can be back-ported to 2020.2; ideally we would
cache the pointer but have an invalidation scheme, but that’s
considerably more work and risk.

Relates to Sentry crash:
https://sentry.io/organizations/flightgear/issues/1858764364
2020-09-06 15:23:52 +01:00
James Turner b2e149a737 Refactor SGBuildingBin
Trying to trace a crash that was reported, but along the way fix a leak
of BuildingBins. Also avoid allocating some std::strings on the heap.
2020-09-06 15:23:48 +01:00
Erik Hofman e28c4fa5ca Mipmapping requires a power-of-two destination buffer even if the source isn't. Neglecting this causes a massive bufferoverflow of the destination buffer. So for now we do not mipmap non-power-of-two textures and throw a warning. This affects at least the PC-9M and F-16, presumable many others. The effect will be a black texture when zoomed out and the proper texture when zooming in close enough. 2020-09-06 15:23:37 +01:00
Automatic Release Builder 61dc19f635 Fix for SGTexturedTriangleBin leaks
Adapted from next commit
0d5552851b
2020-09-06 15:23:21 +01:00
Automatic Release Builder 60634bc445 Fix include that dropped when squashing. 2020-08-26 14:21:45 +01:00
Hans Kunkell b3e93eaf6e new method: makeStringSafeForPropertyName, replaces invalid chars 2020-08-26 10:37:54 +01:00
James Turner 36dca92c2b HTTPRepo: Fix ownership of HTTPDirectory 2020-08-26 10:37:54 +01:00
James Turner 5a1ed52d7c Particles: rewrite global manager for thread-safety
Avoid re-registering the global node callback each time a new particle
animation is loaded, and use mutexes to protected all shared data.
2020-08-26 10:37:54 +01:00
James Turner fd191b51ce Avoid a race in MatModels loading
Add a mutex to ensure an SGMatModels only loads its models once. Caught
be ASan, hurrah.
2020-08-26 10:37:54 +01:00
James Turner 6167159795 API to reset the shared tree geometry 2020-08-26 10:37:54 +01:00
James Turner 672afdbc34 Fix for sprintf of nil numeric in Nasal
Spotted by valgrind, w00t. If you used a numerical formatter, such
as f,d or g, with. a nil value, we would read uninitialised data, and
generally get confused.
2020-08-26 10:37:54 +01:00
James Turner 27e61b3dec Ensure SGTerraSync::unbind drops all properties
Avoid some noise on reset, by dropping /all/ our SGPropertyNodes
2020-08-26 10:37:54 +01:00
legoboyvdlp R f1d00c9b40 Drizzle is considered to be light rain. Backportable to 2020.2. Fixes ticket 1792 2020-08-26 10:37:54 +01:00
James Turner 1b00ece8c4 SGBuildingBin: avoid read of un-inited memory
Where the building line is not as long as expected, ensure we read
valid memory.
2020-08-26 10:37:54 +01:00
James Turner d0f24229b2 HTTP: Use curl_multi_wait everywhere
Also check the result of the curl methods, in case they fail
2020-08-26 10:37:54 +01:00
Richard Harrison ab8795f6dc Directional lighting fix
getLights modified to use points rather than triangles based on the configuration.

This fixes point lights (with directional disabled) on AMD
2020-08-26 10:37:42 +01:00
James Turner 2fe60c9635 Terraysnc: fix incorrect handling of ‘entirely ocean’ tiles
We had some confusion between two error codes, meaning we would log an
error (and treat as a failure) a pure ocean 1x1 area, even though this
was entirely correct and expected behaviour.

Terrasync.cxx expected REPO_ERROR_NOT_FOUND, we need to convert the
file-level error to that, and avoid logging a warning.
2020-08-25 21:10:54 +01:00
James Turner 27a3ee3bce TerraSync: restart after max-errors is exceeded.
When we trip the max-error count for a session, back off for a period
of time and then retry (selecting a new TerraSync server).
2020-08-25 21:02:39 +01:00
James Turner 0721db3acd TerraSync: handle reinit better
Fix various cases where re-init could get things blocked. Remove the
duplicate storage of the active paths; now we always check the primary
data, and hence it can’t be out of sync.

Also remove the obsolete persistent cache code.

Fixes some of the issues discussed in:
https://sourceforge.net/p/flightgear/codetickets/2308/

Further improvements still to come, especially to retry on a better
schedule for intermittent connections.
2020-08-25 21:02:16 +01:00
Richard Harrison 24b58cbe21 XMLSound bugfixes
- using expressions for pitch and volume didn't work
- multiple volume elements not computed properly

Discussion here: https://web.archive.org/web/20200730232940/http://fguk.me/forum/development-hangar/8360-supersonic-audio?start=40#46287

          <volume>
                <property>/a/v1</property>
                <factor>0.1</factor>
            </volume>
            <volume>
                <property>/a/v2</property>
                <offset>1</offset>
                <factor>-3</factor>
            </volume>
            <volume>
                <property>/a/v3</property>
                <offset>1</offset>
                <factor>-1</factor>
            </volume>

evaluates as follows:

update(): Have 3 volume entries
 --> 0:prop /a[0]/v1[0] => 0.110000 factor = 0.100000 v=0.011000 offset 0.000000, now 0.000000, v=0.011000 vol=0.011000 ==> 0.011000
 --> 1:prop /a[0]/v2[0] => 0.120000 factor = 3.000000 v=0.360000 -ve offset 1.000000 1.360000 = 0.014960  ==> 0.014960
 --> 2:prop /a[0]/v3[0] => 0.130000 factor = 1.000000 v=0.130000 -ve offset 1.000000 1.130000 = 0.016905  ==> 0.016905
2020-08-04 11:58:27 +01:00
Richard Harrison 5b3274e688 Exclude images with less than 16 bits per pixel from the DDS Texture Cache 2020-08-04 11:58:13 +01:00
James Turner fcd75cfae5 Logging: add log overload which copies filename
This is needed to fix a Valgrind error in the test-cases, due to logging
Nasal filenames which are GCed.
2020-08-04 11:58:05 +01:00
James Turner 6387a1d6d0 Fix compilation for some MSVC versions 2020-07-14 13:59:14 +01:00
Julian Smith fc4ce2528b Renamed version -> simgear-version to avoid breaking clang++ on OpenBSD.
It seems that clang++ headers #include <version>, which found simgear/version
because we need to put singear/ in include path for some code to compile.
2020-07-14 13:59:09 +01:00
James Turner 18d2bfcd8b HTTP: allow CAINFO to be set
Env var is SIMGEAR_TLS_CERT_PATH
2020-07-14 13:58:43 +01:00
James Turner 6738a3aa2b Helper to map string to log priority 2020-06-26 10:31:16 +01:00
Julian Smith a8c1bef0bf Avoid various gcc warnings. 2020-06-26 10:30:58 +01:00
James Turner 4faf0ea468 API to reset the resource manager, and remove
providers.

Needed to fix a bug with parsing -set.xml files in the launcher;
correctly loading XML needs the resource paths to be defined.
2020-06-26 10:30:47 +01:00
Lars Toenning 80cc09fe90 Fix compiler warning of different signedness 2020-06-26 10:30:37 +01:00
James Turner ca8dbb985e Fix zero-interval repeat on pick/knob animation
See:
https://sourceforge.net/p/flightgear/codetickets/2241/

Note did not adjust bug in knob-animation ignoring repeatable flag
(always marked repeatable) since this might break some aircraft.
2020-06-15 16:13:39 +01:00
James Turner 751cdc32a8 new version: 2020.2.1 2020-06-15 12:33:50 +01:00
Julian Smith 1d978429f5 simgear/props/props.cxx: Avoid possible SEGV e.g. if listener removes itself.
simgear/props/props_test.cxx: test for self-unregister.

SGPropertyNode::removeChangeListener() used to modify the _listeners vector or
even delete it and set _listeners to NULL, if a listener was removed.

This could cause a SEGV if removeChangeListener() was called from
a listener, because listeners are called from methods such as
SGPropertyNode::fireValueChanged() which iterate through _listeners and don't
expect it to be changed to NULL.

The fix here is to keep a track of whether we are iterating through the
_listeners vector; if we are, SGPropertyNode::removeChangeListener() sets the
entry to NULL, instead of removing it.

We have a new internal function forEachListener() which takes
care of the iterating details - incrementing/decrementing the new
_listeners->_num_iterators count and skipping NULL entries. Before returning,
if _num_iterators is now zero it removes any NULL entries in the vector of
listeners.

The nListeners() member is no longer inline, because it needs to ignore NULL
entries in the vector. And _listeners now points to an internally-defined
struct which contains a new _num_iterators as well as the vector of listeners.

Added self-unregister test to simgear/props/props_test.cxx.
2020-06-13 18:11:49 +01:00
James Turner 2b2e3ae5c4 Fix CMake value 2020-06-05 13:53:54 +01:00
James Turner 0e3ac7e078 Set CMake OpenGL VND policy 2020-06-05 13:32:43 +01:00
James Turner 01ab3b2385 New SGGeodesy::advance, taking degrees 2020-05-30 16:27:42 +01:00
James Turner 11cf87951c SIMD flags: ensure we respect other compiler flags
Ensure we only ever extend the currently set compile flags when
ENABLE_SIMD is requested
2020-05-28 10:46:18 +01:00
James Turner 7c004a4c90 Fix compilation with Boost >= 1.73 2020-05-28 10:45:47 +01:00
James Turner a48693d273 Kill explicit setting of compiler flags in Debug
Use the CMake defaults for this.
2020-05-24 14:32:13 +01:00
James Turner 12d57a6373 Adjust RelWithDebInfo optimisation for Clang
This should give a bit of speed boost to macOS official builds.
2020-05-13 14:23:00 +01:00
James Turner c7c8fc7777 Better RelWithDebInfo flags for CL.exe 2020-05-08 14:31:44 +01:00
James Turner 6e054e57ef Windows: ensure RelWithDebInfo is fast
Default CMake RelWithDebInfo pessimizes inlining
2020-05-07 14:13:54 +01:00
James Turner 1d89a76d13 Old-style texture compression: better check
Make even more sure new-style texture-compression is active, before
deciding which old-style mode to use.
2020-05-07 11:47:25 +01:00
James Turner 65925cccdf Enable old-style texture compression
Thanks to Dany for tracking this down!
2020-05-05 20:40:29 +01:00
James Turner 898559ab31 Add copyPropertiesIf helper 2020-05-03 12:21:36 +01:00
James Turner ad3621e23b Add Canvas::Image.dirtyPixels() 2020-04-28 09:36:40 +01:00
James Turner 57b0f70a55 Nasal: improve message for non-object member access
Based on some discussion on this ticket:

https://sourceforge.net/p/flightgear/codetickets/2186/

Make this message slightly clearer.
2020-04-28 09:36:40 +01:00
Erik Hofman 8d6e543cc2 Only use readwav for OpenAL 2020-04-28 09:41:11 +02:00
James Turner bd93fb279b new version: 2020.2.0 2020-04-27 09:29:55 +01:00
124 changed files with 5972 additions and 3089 deletions
+49 -32
View File
@@ -10,6 +10,12 @@ if(COMMAND cmake_policy)
if(POLICY CMP0067) if(POLICY CMP0067)
cmake_policy(SET CMP0067 NEW) cmake_policy(SET CMP0067 NEW)
endif() endif()
# OpenGL VND policy : use the old definition for now, until we can audit this
if(POLICY CMP0072)
cmake_policy(SET CMP0072 OLD)
endif()
if(POLICY CMP0093) if(POLICY CMP0093)
cmake_policy(SET CMP0093 NEW) cmake_policy(SET CMP0093 NEW)
endif() endif()
@@ -43,7 +49,7 @@ set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_CXX_STANDARD_REQUIRED YES)
# read 'version' file into a variable (stripping any newlines or spaces) # read 'version' file into a variable (stripping any newlines or spaces)
file(READ version versionFile) file(READ simgear-version versionFile)
string(STRIP ${versionFile} SIMGEAR_VERSION) string(STRIP ${versionFile} SIMGEAR_VERSION)
project(SimGear VERSION ${SIMGEAR_VERSION} LANGUAGES C CXX) project(SimGear VERSION ${SIMGEAR_VERSION} LANGUAGES C CXX)
@@ -165,6 +171,11 @@ if (MSVC)
else (EXISTS ${TEST_3RDPARTY_DIR}) else (EXISTS ${TEST_3RDPARTY_DIR})
set(MSVC_3RDPARTY_ROOT NOT_FOUND CACHE PATH "Location where the third-party dependencies are extracted") set(MSVC_3RDPARTY_ROOT NOT_FOUND CACHE PATH "Location where the third-party dependencies are extracted")
endif (EXISTS ${TEST_3RDPARTY_DIR}) endif (EXISTS ${TEST_3RDPARTY_DIR})
# override CMake default RelWithDebInfo flags. This is important to ensure
# good performance
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/Zi /O2 /Ob2 /D NDEBUG")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "/Zi /O2 /Ob2 /D NDEBUG")
else (MSVC) else (MSVC)
set(MSVC_3RDPARTY_ROOT NOT_FOUND CACHE PATH "Location where the third-party dependencies are extracted") set(MSVC_3RDPARTY_ROOT NOT_FOUND CACHE PATH "Location where the third-party dependencies are extracted")
endif (MSVC) endif (MSVC)
@@ -222,7 +233,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD
endif() endif()
find_package(Boost REQUIRED) find_package(Boost REQUIRED)
set (BOOST_CXX_FLAGS "-DBOOST_BIMAP_DISABLE_SERIALIZATION") set (BOOST_CXX_FLAGS "-DBOOST_BIMAP_DISABLE_SERIALIZATION -DBOOST_NO_STDLIB_CONFIG")
include(BoostTestTargets) include(BoostTestTargets)
if(SIMGEAR_HEADLESS) if(SIMGEAR_HEADLESS)
@@ -267,7 +278,15 @@ else()
endif() endif()
endif(SIMGEAR_HEADLESS) endif(SIMGEAR_HEADLESS)
find_package(ZLIB 1.2.4 REQUIRED) if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
# As of 2020-08-01, OpenBSD's system zlib is slightly old, but it's usable
# with a workaround in simgear/io/iostreams/gzfstream.cxx.
find_package(ZLIB 1.2.3 REQUIRED)
else()
find_package(ZLIB 1.2.4 REQUIRED)
endif()
find_package(LibLZMA REQUIRED)
find_package(CURL REQUIRED) find_package(CURL REQUIRED)
if (SYSTEM_EXPAT) if (SYSTEM_EXPAT)
@@ -412,16 +431,8 @@ if(CMAKE_COMPILER_IS_GNUCXX)
message(WARNING "GCC 4.4 will be required soon, please upgrade") message(WARNING "GCC 4.4 will be required soon, please upgrade")
endif() endif()
if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (X86 OR X86_64)
set(CMAKE_C_FLAGS set(SIMD_COMPILER_FLAGS "-msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
"${CMAKE_C_FLAGS} -O0 -fno-omit-frame-pointer -fno-inline")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -O0 -fno-omit-frame-pointer -fno-inline")
elseif (ENABLE_SIMD)
if (X86 OR X86_64)
set(CMAKE_C_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
endif()
endif() endif()
# certain GCC versions don't provide the atomic builds, and hence # certain GCC versions don't provide the atomic builds, and hence
@@ -430,6 +441,10 @@ if(CMAKE_COMPILER_IS_GNUCXX)
check_cxx_source_compiles( check_cxx_source_compiles(
"int main() { unsigned mValue; return __sync_add_and_fetch(&mValue, 1); }" "int main() { unsigned mValue; return __sync_add_and_fetch(&mValue, 1); }"
GCC_ATOMIC_BUILTINS_FOUND) GCC_ATOMIC_BUILTINS_FOUND)
# override CMake default RelWithDebInfo flags.
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
endif(CMAKE_COMPILER_IS_GNUCXX) endif(CMAKE_COMPILER_IS_GNUCXX)
if (CLANG) if (CLANG)
@@ -440,17 +455,11 @@ if (CLANG)
# fix Boost compilation :( # fix Boost compilation :(
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
if (CMAKE_BUILD_TYPE STREQUAL "Debug") # override CMake default RelWithDebInfo flags.
set(CMAKE_C_FLAGS set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
"${CMAKE_C_FLAGS} -O0 -fno-omit-frame-pointer -fno-inline-functions") set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -O0 -fno-omit-frame-pointer -fno-inline-functions") set(SIMD_COMPILER_FLAGS "-msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
elseif (ENABLE_SIMD)
if (X86 OR X86_64)
set(CMAKE_C_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
endif()
endif()
endif() endif()
if (ENABLE_OPENMP) if (ENABLE_OPENMP)
@@ -482,14 +491,8 @@ if(WIN32)
if(MSVC) if(MSVC)
set(MSVC_FLAGS "-DWIN32 -DNOMINMAX -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -D__CRT_NONSTDC_NO_WARNINGS /MP") set(MSVC_FLAGS "-DWIN32 -DNOMINMAX -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -D__CRT_NONSTDC_NO_WARNINGS /MP")
if(ENABLE_SIMD) if (X86)
if (X86) set(SIMD_COMPILER_FLAGS "/arch:SSE /arch:SSE2")
SET(CMAKE_C_FLAGS_RELEASE "/O2 /arch:SSE /arch:SSE2")
SET(CMAKE_CXX_FLAGS_RELEASE "/O2 /arch:SSE /arch:SSE2")
else()
SET(CMAKE_C_FLAGS_RELEASE "/O2")
SET(CMAKE_CXX_FLAGS_RELEASE "/O2")
endif()
endif() endif()
if (NOT OSG_FSTREAM_EXPORT_FIXED) if (NOT OSG_FSTREAM_EXPORT_FIXED)
@@ -513,6 +516,20 @@ if(WIN32)
set( RT_LIBRARY "winmm" ) set( RT_LIBRARY "winmm" )
endif(WIN32) endif(WIN32)
# append the SIMD flags if requested
if (ENABLE_SIMD)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SIMD_COMPILER_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SIMD_COMPILER_FLAGS}")
# set for multi-configuration generators
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${SIMD_COMPILER_FLAGS}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${SIMD_COMPILER_FLAGS}")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${SIMD_COMPILER_FLAGS}")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${SIMD_COMPILER_FLAGS}")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS_C} ${MSVC_FLAGS}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS_C} ${MSVC_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_CXX} ${MSVC_FLAGS} ${BOOST_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_CXX} ${MSVC_FLAGS} ${BOOST_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}")
+124
View File
@@ -0,0 +1,124 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindLibLZMA
-----------
Find LZMA compression algorithm headers and library.
Imported Targets
^^^^^^^^^^^^^^^^
This module defines :prop_tgt:`IMPORTED` target ``LibLZMA::LibLZMA``, if
liblzma has been found.
Result variables
^^^^^^^^^^^^^^^^
This module will set the following variables in your project:
``LIBLZMA_FOUND``
True if liblzma headers and library were found.
``LIBLZMA_INCLUDE_DIRS``
Directory where liblzma headers are located.
``LIBLZMA_LIBRARIES``
Lzma libraries to link against.
``LIBLZMA_HAS_AUTO_DECODER``
True if lzma_auto_decoder() is found (required).
``LIBLZMA_HAS_EASY_ENCODER``
True if lzma_easy_encoder() is found (required).
``LIBLZMA_HAS_LZMA_PRESET``
True if lzma_lzma_preset() is found (required).
``LIBLZMA_VERSION_MAJOR``
The major version of lzma
``LIBLZMA_VERSION_MINOR``
The minor version of lzma
``LIBLZMA_VERSION_PATCH``
The patch version of lzma
``LIBLZMA_VERSION_STRING``
version number as a string (ex: "5.0.3")
#]=======================================================================]
find_path(LIBLZMA_INCLUDE_DIR lzma.h )
if(NOT LIBLZMA_LIBRARY)
find_library(LIBLZMA_LIBRARY_RELEASE NAMES lzma liblzma NAMES_PER_DIR PATH_SUFFIXES lib)
find_library(LIBLZMA_LIBRARY_DEBUG NAMES lzmad liblzmad NAMES_PER_DIR PATH_SUFFIXES lib)
include(SelectLibraryConfigurations)
select_library_configurations(LIBLZMA)
else()
file(TO_CMAKE_PATH "${LIBLZMA_LIBRARY}" LIBLZMA_LIBRARY)
endif()
if(LIBLZMA_INCLUDE_DIR AND EXISTS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h")
file(STRINGS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h" LIBLZMA_HEADER_CONTENTS REGEX "#define LZMA_VERSION_[A-Z]+ [0-9]+")
string(REGEX REPLACE ".*#define LZMA_VERSION_MAJOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MAJOR "${LIBLZMA_HEADER_CONTENTS}")
string(REGEX REPLACE ".*#define LZMA_VERSION_MINOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MINOR "${LIBLZMA_HEADER_CONTENTS}")
string(REGEX REPLACE ".*#define LZMA_VERSION_PATCH ([0-9]+).*" "\\1" LIBLZMA_VERSION_PATCH "${LIBLZMA_HEADER_CONTENTS}")
set(LIBLZMA_VERSION_STRING "${LIBLZMA_VERSION_MAJOR}.${LIBLZMA_VERSION_MINOR}.${LIBLZMA_VERSION_PATCH}")
unset(LIBLZMA_HEADER_CONTENTS)
endif()
# We're using new code known now as XZ, even library still been called LZMA
# it can be found in http://tukaani.org/xz/
# Avoid using old codebase
if (LIBLZMA_LIBRARY)
include(CheckLibraryExists)
set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
set(CMAKE_REQUIRED_QUIET ${LibLZMA_FIND_QUIETLY})
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY})
elseif(LIBLZMA_LIBRARY_RELEASE)
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_RELEASE})
elseif(LIBLZMA_LIBRARY_DEBUG)
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_DEBUG})
endif()
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_auto_decoder "" LIBLZMA_HAS_AUTO_DECODER)
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_easy_encoder "" LIBLZMA_HAS_EASY_ENCODER)
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_lzma_preset "" LIBLZMA_HAS_LZMA_PRESET)
unset(LIBLZMA_LIBRARY_check)
set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE})
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibLZMA REQUIRED_VARS LIBLZMA_LIBRARY
LIBLZMA_INCLUDE_DIR
LIBLZMA_HAS_AUTO_DECODER
LIBLZMA_HAS_EASY_ENCODER
LIBLZMA_HAS_LZMA_PRESET
VERSION_VAR LIBLZMA_VERSION_STRING
)
mark_as_advanced( LIBLZMA_INCLUDE_DIR LIBLZMA_LIBRARY )
if (LIBLZMA_FOUND)
set(LIBLZMA_LIBRARIES ${LIBLZMA_LIBRARY})
set(LIBLZMA_INCLUDE_DIRS ${LIBLZMA_INCLUDE_DIR})
if(NOT TARGET LibLZMA::LibLZMA)
add_library(LibLZMA::LibLZMA UNKNOWN IMPORTED)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${LIBLZMA_INCLUDE_DIR}
IMPORTED_LINK_INTERFACE_LANGUAGES C)
if(LIBLZMA_LIBRARY_RELEASE)
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
IMPORTED_LOCATION_RELEASE "${LIBLZMA_LIBRARY_RELEASE}")
endif()
if(LIBLZMA_LIBRARY_DEBUG)
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
IMPORTED_LOCATION_DEBUG "${LIBLZMA_LIBRARY_DEBUG}")
endif()
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
IMPORTED_LOCATION "${LIBLZMA_LIBRARY}")
endif()
endif()
endif ()
+1
View File
@@ -1,6 +1,7 @@
include(CMakeFindDependencyMacro) include(CMakeFindDependencyMacro)
find_dependency(ZLIB) find_dependency(ZLIB)
find_dependency(LibLZMA)
find_dependency(Threads) find_dependency(Threads)
# OSG # OSG
+1
View File
@@ -0,0 +1 @@
2020.3.9
+4 -1
View File
@@ -167,7 +167,10 @@ target_link_libraries(SimGearCore PRIVATE
${CMAKE_THREAD_LIBS_INIT} ${CMAKE_THREAD_LIBS_INIT}
${COCOA_LIBRARY} ${COCOA_LIBRARY}
${CURL_LIBRARIES} ${CURL_LIBRARIES}
${WINSOCK_LIBRARY}) ${WINSOCK_LIBRARY}
${SHLWAPI_LIBRARY}
LibLZMA::LibLZMA
)
if(SYSTEM_EXPAT) if(SYSTEM_EXPAT)
target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES}) target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES})
+4
View File
@@ -691,6 +691,10 @@ namespace canvas
{ {
_sampling_dirty = true; _sampling_dirty = true;
} }
else if( name == "anisotropy" )
{
_texture.setMaxAnisotropy( node->getFloatValue() );
}
else if( name == "additive-blend" ) else if( name == "additive-blend" )
{ {
_texture.useAdditiveBlend( node->getBoolValue() ); _texture.useAdditiveBlend( node->getBoolValue() );
+6
View File
@@ -202,6 +202,12 @@ namespace canvas
updateSampling(); updateSampling();
} }
//----------------------------------------------------------------------------
void ODGauge::setMaxAnisotropy(float anis)
{
texture->setMaxAnisotropy(anis);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void ODGauge::setRender(bool render) void ODGauge::setRender(bool render)
{ {
+2
View File
@@ -109,6 +109,8 @@ namespace canvas
int coverage_samples = 0, int coverage_samples = 0,
int color_samples = 0 ); int color_samples = 0 );
void setMaxAnisotropy(float anis);
/** /**
* Enable/Disable updating the texture (If disabled the contents of the * Enable/Disable updating the texture (If disabled the contents of the
* texture remains with the outcome of the last rendering pass) * texture remains with the outcome of the last rendering pass)
+11 -3
View File
@@ -850,7 +850,7 @@ namespace canvas
void fillRow(GLubyte* row, GLuint pixel, GLuint width, GLuint pixelBytes) void fillRow(GLubyte* row, GLuint pixel, GLuint width, GLuint pixelBytes)
{ {
GLubyte* dst = row; GLubyte* dst = row;
for (int x = 0; x < width; ++x) { for (GLuint x = 0; x < width; ++x) {
memcpy(dst, &pixel, pixelBytes); memcpy(dst, &pixel, pixelBytes);
dst += pixelBytes; dst += pixelBytes;
} }
@@ -947,10 +947,18 @@ SGRect<int> intersectRect(const SGRect<int>& a, const SGRect<int>& b)
} }
image->setColor(color, x, y); image->setColor(color, x, y);
auto c = getCanvas().lock();
c->enableRendering(true); // force a repaint
} }
void Image::dirtyPixels()
{
osg::ref_ptr<osg::Image> image = _texture->getImage();
if (!image)
return;
image->dirty();
auto c = getCanvas().lock();
c->enableRendering(true); // force a repaint
}
void Image::allocateImage() void Image::allocateImage()
{ {
osg::Image* image = new osg::Image; osg::Image* image = new osg::Image;
+5
View File
@@ -115,6 +115,11 @@ namespace canvas
void setPixel(int x, int y, const osg::Vec4& color); void setPixel(int x, int y, const osg::Vec4& color);
/**
* mark the image pixels as modified, so the canvas is re-painted
*/
void dirtyPixels();
osg::ref_ptr<osg::Image> getImage() const; osg::ref_ptr<osg::Image> getImage() const;
// void setRow(int row, int offset, ) // void setRow(int row, int offset, )
+1 -1
View File
@@ -73,7 +73,7 @@ namespace canvas
int stretch, int stretch,
uint8_t alignment ) uint8_t alignment )
{ {
ItemData item_data = {0}; ItemData item_data = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
item_data.layout_item = item; item_data.layout_item = item;
item_data.stretch = std::max(0, stretch); item_data.stretch = std::max(0, stretch);
+3
View File
@@ -218,6 +218,9 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
#define SG_OBJECT_RANGE_ROUGH 9000.0 #define SG_OBJECT_RANGE_ROUGH 9000.0
#define SG_OBJECT_RANGE_DETAILED 1500.0 #define SG_OBJECT_RANGE_DETAILED 1500.0
/** Minimum expiry time of PagedLOD within the Tile. Overridden by /sim/rendering/plod-minimum-expiry-time-secs **/
#define SG_TILE_MIN_EXPIRY 180.0
/** Radius of scenery tiles in m **/ /** Radius of scenery tiles in m **/
#define SG_TILE_RADIUS 14000.0 #define SG_TILE_RADIUS 14000.0
+1 -1
View File
@@ -26,7 +26,7 @@
#include <vector> #include <vector>
#include <memory> // for std::unique_ptr #include <memory> // for std::unique_ptr
#include <simgear/debug/logstream.hxx> #include <simgear/debug/LogCallback.hxx>
namespace simgear namespace simgear
{ {
+9 -2
View File
@@ -1,7 +1,14 @@
include (SimGearComponent) include (SimGearComponent)
set(HEADERS debug_types.h logstream.hxx BufferedLogCallback.hxx OsgIoCapture.hxx) set(HEADERS debug_types.h
set(SOURCES logstream.cxx BufferedLogCallback.cxx) logstream.hxx BufferedLogCallback.hxx OsgIoCapture.hxx
LogCallback.hxx LogEntry.hxx
ErrorReportingCallback.hxx)
set(SOURCES logstream.cxx BufferedLogCallback.cxx
LogCallback.cxx LogEntry.cxx
ErrorReportingCallback.cxx
)
simgear_component(debug debug "${SOURCES}" "${HEADERS}") simgear_component(debug debug "${SOURCES}" "${HEADERS}")
+47
View File
@@ -0,0 +1,47 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "ErrorReportingCallback.hxx"
using std::string;
namespace simgear {
static ErrorReportCallback static_callback;
void setErrorReportCallback(ErrorReportCallback cb)
{
static_callback = cb;
}
void reportError(const std::string& msg, const std::string& more)
{
if (!static_callback)
return;
static_callback(msg, more, false);
}
void reportFatalError(const std::string& msg, const std::string& more)
{
if (!static_callback)
return;
static_callback(msg, more, true);
}
} // namespace simgear
+31
View File
@@ -0,0 +1,31 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#pragma once
#include <functional>
#include <string>
namespace simgear {
void reportError(const std::string& msg, const std::string& more = {});
void reportFatalError(const std::string& msg, const std::string& more = {});
using ErrorReportCallback = std::function<void(const std::string& msg, const std::string& more, bool isFatal)>;
void setErrorReportCallback(ErrorReportCallback cb);
} // namespace simgear
+116
View File
@@ -0,0 +1,116 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "LogCallback.hxx"
using namespace simgear;
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) : m_class(c),
m_priority(p)
{
}
void LogCallback::operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage)
{
// override me
}
bool LogCallback::doProcessEntry(const LogEntry& e)
{
return false;
}
void LogCallback::processEntry(const LogEntry& e)
{
if (doProcessEntry(e))
return; // derived class used the new API
// call the old API
(*this)(e.debugClass, e.debugPriority, e.file, e.line, e.message);
}
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
{
if ((c & m_class) != 0 && p >= m_priority)
return true;
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
return true;
return false;
}
void LogCallback::setLogLevels(sgDebugClass c, sgDebugPriority p)
{
m_priority = p;
m_class = c;
}
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
{
switch (p) {
case SG_DEV_ALERT:
case SG_ALERT:
return "ALRT";
case SG_BULK: return "BULK";
case SG_DEBUG: return "DBUG";
case SG_MANDATORY_INFO:
case SG_INFO:
return "INFO";
case SG_POPUP: return "POPU";
case SG_DEV_WARN:
case SG_WARN:
return "WARN";
default: return "UNKN";
}
}
const char* LogCallback::debugClassToString(sgDebugClass c)
{
switch (c) {
case SG_NONE: return "none";
case SG_TERRAIN: return "terrain";
case SG_ASTRO: return "astro";
case SG_FLIGHT: return "flight";
case SG_INPUT: return "input";
case SG_GL: return "opengl";
case SG_VIEW: return "view";
case SG_COCKPIT: return "cockpit";
case SG_GENERAL: return "general";
case SG_MATH: return "math";
case SG_EVENT: return "event";
case SG_AIRCRAFT: return "aircraft";
case SG_AUTOPILOT: return "autopilot";
case SG_IO: return "io";
case SG_CLIPPER: return "clipper";
case SG_NETWORK: return "network";
case SG_ATC: return "atc";
case SG_NASAL: return "nasal";
case SG_INSTR: return "instruments";
case SG_SYSTEMS: return "systems";
case SG_AI: return "ai";
case SG_ENVIRONMENT: return "environment";
case SG_SOUND: return "sound";
case SG_NAVAID: return "navaid";
case SG_GUI: return "gui";
case SG_TERRASYNC: return "terrasync";
case SG_PARTICLES: return "particles";
case SG_HEADLESS: return "headless";
case SG_OSG: return "OSG";
default: return "unknown";
}
}
+56
View File
@@ -0,0 +1,56 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#pragma once
#include <string>
#include "LogEntry.hxx"
#include "debug_types.h"
namespace simgear {
class LogCallback
{
public:
virtual ~LogCallback() = default;
// newer API: return true if you handled the message, otherwise
// the old API will be called
virtual bool doProcessEntry(const LogEntry& e);
// old API, kept for compatability
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage);
void setLogLevels(sgDebugClass c, sgDebugPriority p);
void processEntry(const LogEntry& e);
protected:
LogCallback(sgDebugClass c, sgDebugPriority p);
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
static const char* debugClassToString(sgDebugClass c);
static const char* debugPriorityToString(sgDebugPriority p);
private:
sgDebugClass m_class;
sgDebugPriority m_priority;
};
} // namespace simgear
+45
View File
@@ -0,0 +1,45 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "LogEntry.hxx"
#include <cstring> // for strdup
namespace simgear {
LogEntry::~LogEntry()
{
if (freeFilename) {
free(const_cast<char*>(file));
}
}
LogEntry::LogEntry(const LogEntry& c) : debugClass(c.debugClass),
debugPriority(c.debugPriority),
originalPriority(c.originalPriority),
file(c.file),
line(c.line),
message(c.message),
freeFilename(c.freeFilename)
{
if (c.freeFilename) {
file = strdup(c.file);
}
}
} // namespace simgear
+54
View File
@@ -0,0 +1,54 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#pragma once
#include <string>
#include "debug_types.h"
namespace simgear {
/**
* storage of a single log entry. This is used to pass log entries from
* the various threads to the logging thread, and also to store the startup
* entries
*/
class LogEntry
{
public:
LogEntry(sgDebugClass c, sgDebugPriority p,
sgDebugPriority op,
const char* f, int l, const std::string& msg) : debugClass(c), debugPriority(p), originalPriority(op),
file(f), line(l),
message(msg)
{
}
LogEntry(const LogEntry& c);
LogEntry& operator=(const LogEntry& c) = delete;
~LogEntry();
const sgDebugClass debugClass;
const sgDebugPriority debugPriority;
const sgDebugPriority originalPriority;
const char* file;
const int line;
const std::string message;
bool freeFilename = false; ///< if true, we own, and therefore need to free, the memory pointed to by 'file'
};
} // namespace simgear
+1 -2
View File
@@ -2,8 +2,7 @@
#include <osg/Notify> #include <osg/Notify>
using namespace osg; #include <simgear/debug/logstream.hxx>
/** /**
* merge OSG output into our logging system, so it gets recorded to file, * merge OSG output into our logging system, so it gets recorded to file,
+12 -9
View File
@@ -1,3 +1,5 @@
#pragma once
/** \file debug_types.h /** \file debug_types.h
* Define the various logging classes and priorities * Define the various logging classes and priorities
*/ */
@@ -52,16 +54,17 @@ typedef enum {
* appended, or the priority Nasal reports to compiled code will change. * appended, or the priority Nasal reports to compiled code will change.
*/ */
typedef enum { typedef enum {
SG_BULK = 1, // For frequent messages SG_BULK = 1, // For frequent messages
SG_DEBUG, // Less frequent debug type messages SG_DEBUG, // Less frequent debug type messages
SG_INFO, // Informatory messages SG_INFO, // Informatory messages
SG_WARN, // Possible impending problem SG_WARN, // Possible impending problem
SG_ALERT, // Very possible impending problem SG_ALERT, // Very possible impending problem
SG_POPUP, // Severe enough to alert using a pop-up window SG_POPUP, // Severe enough to alert using a pop-up window
// SG_EXIT, // Problem (no core) // SG_EXIT, // Problem (no core)
// SG_ABORT // Abandon ship (core) // SG_ABORT // Abandon ship (core)
SG_DEV_WARN, // Warning for developers, translated to other priority SG_DEV_WARN, // Warning for developers, translated to other priority
SG_DEV_ALERT // Alert for developers, translated SG_DEV_ALERT, // Alert for developers, translated
} sgDebugPriority;
SG_MANDATORY_INFO // information, but should always be shown
} sgDebugPriority;
+67 -131
View File
@@ -35,6 +35,7 @@
#include <simgear/threads/SGThread.hxx> #include <simgear/threads/SGThread.hxx>
#include <simgear/threads/SGQueue.hxx> #include <simgear/threads/SGQueue.hxx>
#include "LogCallback.hxx"
#include <simgear/io/iostreams/sgstream.hxx> #include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx> #include <simgear/misc/strutils.hxx>
@@ -47,86 +48,9 @@
#include <io.h> #include <io.h>
#endif #endif
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
namespace simgear
{
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
m_class(c),
m_priority(p)
{
}
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
{
if ((c & m_class) != 0 && p >= m_priority)
return true;
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
return true;
return false;
}
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
{
m_priority = p;
m_class = c;
}
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
{
switch (p) {
case SG_ALERT: return "ALRT";
case SG_BULK: return "BULK";
case SG_DEBUG: return "DBUG";
case SG_INFO: return "INFO";
case SG_POPUP: return "POPU";
case SG_WARN: return "WARN";
default: return "UNKN";
}
}
const char* LogCallback::debugClassToString(sgDebugClass c)
{
switch (c) {
case SG_NONE: return "none";
case SG_TERRAIN: return "terrain";
case SG_ASTRO: return "astro";
case SG_FLIGHT: return "flight";
case SG_INPUT: return "input";
case SG_GL: return "opengl";
case SG_VIEW: return "view";
case SG_COCKPIT: return "cockpit";
case SG_GENERAL: return "general";
case SG_MATH: return "math";
case SG_EVENT: return "event";
case SG_AIRCRAFT: return "aircraft";
case SG_AUTOPILOT: return "autopilot";
case SG_IO: return "io";
case SG_CLIPPER: return "clipper";
case SG_NETWORK: return "network";
case SG_ATC: return "atc";
case SG_NASAL: return "nasal";
case SG_INSTR: return "instruments";
case SG_SYSTEMS: return "systems";
case SG_AI: return "ai";
case SG_ENVIRONMENT:return "environment";
case SG_SOUND: return "sound";
case SG_NAVAID: return "navaid";
case SG_GUI: return "gui";
case SG_TERRASYNC: return "terrasync";
case SG_PARTICLES: return "particles";
case SG_HEADLESS: return "headless";
case SG_OSG: return "OSG";
default: return "unknown";
}
}
} // of namespace simgear
//////////////////////////////////////////////////////////////////////////////
class FileLogCallback : public simgear::LogCallback class FileLogCallback : public simgear::LogCallback
{ {
@@ -139,8 +63,8 @@ public:
logTimer.stamp(); logTimer.stamp();
} }
virtual void operator()(sgDebugClass c, sgDebugPriority p, void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& message) const char* file, int line, const std::string& message) override
{ {
if (!shouldLog(c, p)) return; if (!shouldLog(c, p)) return;
@@ -196,8 +120,8 @@ public:
} }
#endif #endif
virtual void operator()(sgDebugClass c, sgDebugPriority p, void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage) const char* file, int line, const std::string& aMessage) override
{ {
if (!shouldLog(c, p)) return; if (!shouldLog(c, p)) return;
//fprintf(stderr, "%s\n", aMessage.c_str()); //fprintf(stderr, "%s\n", aMessage.c_str());
@@ -226,8 +150,8 @@ public:
{ {
} }
virtual void operator()(sgDebugClass c, sgDebugPriority p, void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage) const char* file, int line, const std::string& aMessage) override
{ {
if (!shouldLog(c, p)) return; if (!shouldLog(c, p)) return;
@@ -242,28 +166,6 @@ public:
class logstream::LogStreamPrivate : public SGThread class logstream::LogStreamPrivate : public SGThread
{ {
private: private:
/**
* storage of a single log entry. This is used to pass log entries from
* the various threads to the logging thread, and also to store the startup
* entries
*/
class LogEntry
{
public:
LogEntry(sgDebugClass c, sgDebugPriority p,
const char* f, int l, const std::string& msg) :
debugClass(c), debugPriority(p), file(f), line(l),
message(msg)
{
}
const sgDebugClass debugClass;
const sgDebugPriority debugPriority;
const char* file;
const int line;
const std::string message;
};
/** /**
* RAII object to pause the logging thread if it's running, and restart it. * RAII object to pause the logging thread if it's running, and restart it.
* used to safely make configuration changes. * used to safely make configuration changes.
@@ -401,13 +303,20 @@ public:
~LogStreamPrivate() ~LogStreamPrivate()
{ {
removeCallbacks(); removeCallbacks();
// house-keeping, avoid leak warnings if we exit before disabling
// startup logging
{
std::lock_guard<std::mutex> g(m_lock);
clearStartupEntriesLocked();
}
} }
std::mutex m_lock; std::mutex m_lock;
SGBlockingQueue<LogEntry> m_entries; SGBlockingQueue<simgear::LogEntry> m_entries;
// log entries posted during startup // log entries posted during startup
std::vector<LogEntry> m_startupEntries; std::vector<simgear::LogEntry> m_startupEntries;
bool m_startupLogging = false; bool m_startupLogging = false;
typedef std::vector<simgear::LogCallback*> CallbackVec; typedef std::vector<simgear::LogCallback*> CallbackVec;
@@ -430,6 +339,8 @@ public:
// test suite mode. // test suite mode.
bool m_testMode = false; bool m_testMode = false;
std::vector<std::string> _popupMessages;
void startLog() void startLog()
{ {
std::lock_guard<std::mutex> g(m_lock); std::lock_guard<std::mutex> g(m_lock);
@@ -447,14 +358,19 @@ public:
{ {
std::lock_guard<std::mutex> g(m_lock); std::lock_guard<std::mutex> g(m_lock);
m_startupLogging = on; m_startupLogging = on;
m_startupEntries.clear(); clearStartupEntriesLocked();
} }
} }
virtual void run() void clearStartupEntriesLocked()
{
m_startupEntries.clear();
}
void run() override
{ {
while (1) { while (1) {
LogEntry entry(m_entries.pop()); simgear::LogEntry entry(m_entries.pop());
// special marker entry detected, terminate the thread since we are // special marker entry detected, terminate the thread since we are
// making a configuration change or quitting the app // making a configuration change or quitting the app
if ((entry.debugClass == SG_NONE) && entry.file && !strcmp(entry.file, "done")) { if ((entry.debugClass == SG_NONE) && entry.file && !strcmp(entry.file, "done")) {
@@ -470,8 +386,7 @@ public:
} }
// submit to each installed callback in turn // submit to each installed callback in turn
for (simgear::LogCallback* cb : m_callbacks) { for (simgear::LogCallback* cb : m_callbacks) {
(*cb)(entry.debugClass, entry.debugPriority, cb->processEntry(entry);
entry.file, entry.line, entry.message);
} }
} // of main thread loop } // of main thread loop
} }
@@ -486,7 +401,7 @@ public:
// log a special marker value, which will cause the thread to wakeup, // log a special marker value, which will cause the thread to wakeup,
// and then exit // and then exit
log(SG_NONE, SG_ALERT, "done", -1, ""); log(SG_NONE, SG_ALERT, "done", -1, "", false);
} }
join(); join();
@@ -501,9 +416,8 @@ public:
// we clear startup entries not using this, so always safe to run // we clear startup entries not using this, so always safe to run
// this code, container will simply be empty // this code, container will simply be empty
for (auto entry : m_startupEntries) { for (const auto& entry : m_startupEntries) {
(*cb)(entry.debugClass, entry.debugPriority, cb->processEntry(entry);
entry.file, entry.line, entry.message);
} }
} }
@@ -531,9 +445,9 @@ public:
PauseThread pause(this); PauseThread pause(this);
m_logPriority = p; m_logPriority = p;
m_logClass = c; m_logClass = c;
for (auto cb : m_consoleCallbacks) { for (auto cb : m_consoleCallbacks) {
cb->setLogLevels(c, p); cb->setLogLevels(c, p);
} }
} }
bool would_log( sgDebugClass c, sgDebugPriority p ) const bool would_log( sgDebugClass c, sgDebugPriority p ) const
@@ -551,14 +465,17 @@ public:
} }
void log( sgDebugClass c, sgDebugPriority p, void log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg) const char* fileName, int line, const std::string& msg,
bool freeFilename)
{ {
p = translatePriority(p); auto tp = translatePriority(p);
if (!m_fileLine) { if (!m_fileLine) {
/* This prevents output of file:line in StderrLogCallback. */ /* This prevents output of file:line in StderrLogCallback. */
line = -line; line = -line;
} }
LogEntry entry(c, p, fileName, line, msg);
simgear::LogEntry entry(c, tp, p, fileName, line, msg);
entry.freeFilename = freeFilename;
m_entries.push(entry); m_entries.push(entry);
} }
@@ -589,8 +506,8 @@ logstream::logstream()
logstream::~logstream() logstream::~logstream()
{ {
popup_msgs.clear();
d->stop(); d->stop();
d.reset();
} }
void void
@@ -625,7 +542,14 @@ void
logstream::log( sgDebugClass c, sgDebugPriority p, logstream::log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg) const char* fileName, int line, const std::string& msg)
{ {
d->log(c, p, fileName, line, msg); d->log(c, p, fileName, line, msg, false);
}
void
logstream::logCopyingFilename( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg)
{
d->log(c, p, strdup(fileName), line, msg, true);
} }
@@ -686,17 +610,18 @@ void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName,
void void
logstream::popup( const std::string& msg) logstream::popup( const std::string& msg)
{ {
popup_msgs.push_back(msg); std::lock_guard<std::mutex> g(d->m_lock);
d->_popupMessages.push_back(msg);
} }
std::string std::string
logstream::get_popup() logstream::get_popup()
{ {
std::string rv = ""; std::string rv;
if (!popup_msgs.empty()) std::lock_guard<std::mutex> g(d->m_lock);
{ if (!d->_popupMessages.empty()) {
rv = popup_msgs.front(); rv = d->_popupMessages.front();
popup_msgs.erase(popup_msgs.begin()); d->_popupMessages.erase(d->_popupMessages.begin());
} }
return rv; return rv;
} }
@@ -704,7 +629,8 @@ logstream::get_popup()
bool bool
logstream::has_popup() logstream::has_popup()
{ {
return (popup_msgs.size() > 0) ? true : false; std::lock_guard<std::mutex> g(d->m_lock);
return !d->_popupMessages.empty();
} }
bool bool
@@ -737,6 +663,16 @@ logstream::set_log_classes( sgDebugClass c)
d->setLogLevels(c, d->m_logPriority); d->setLogLevels(c, d->m_logPriority);
} }
sgDebugPriority logstream::priorityFromString(const std::string& s)
{
if (s == "bulk") return SG_BULK;
if (s == "debug") return SG_DEBUG;
if (s == "info") return SG_INFO;
if (s == "warn") return SG_WARN;
if (s == "alert") return SG_ALERT;
throw std::invalid_argument("Couldn't parse log prioirty:" + s);
}
logstream& logstream&
sglog() sglog()
+14 -22
View File
@@ -37,27 +37,8 @@ class SGPath;
namespace simgear namespace simgear
{ {
class LogCallback
{
public:
virtual ~LogCallback() {}
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage) = 0;
void setLogLevels(sgDebugClass c, sgDebugPriority p);
protected:
LogCallback(sgDebugClass c, sgDebugPriority p);
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
static const char* debugClassToString(sgDebugClass c);
static const char* debugPriorityToString(sgDebugPriority p);
private:
sgDebugClass m_class;
sgDebugPriority m_priority;
};
class LogCallback;
/** /**
* Helper force a console on platforms where it might optional, when * Helper force a console on platforms where it might optional, when
* we need to show a console. This basically means Windows at the * we need to show a console. This basically means Windows at the
@@ -105,6 +86,11 @@ public:
sgDebugPriority get_log_priority() const; sgDebugPriority get_log_priority() const;
/**
@brief convert a string value to a log prioirty.
throws std::invalid_argument if the string is not valid
*/
static sgDebugPriority priorityFromString(const std::string& s);
/** /**
* set developer mode on/off. In developer mode, SG_DEV_WARN messags * set developer mode on/off. In developer mode, SG_DEV_WARN messags
* are treated as warnings. In normal (non-developer) mode they are * are treated as warnings. In normal (non-developer) mode they are
@@ -124,6 +110,14 @@ public:
void log( sgDebugClass c, sgDebugPriority p, void log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg); const char* fileName, int line, const std::string& msg);
// overload of above, which can transfer ownership of the file-name.
// this is unecesary overhead when logging from C++, since __FILE__ points
// to constant data, but it's needed when the filename is Nasal data (for
// example) since during shutdown the filename is freed by Nasal GC
// asynchronously with the logging thread.
void logCopyingFilename( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg);
/** /**
* output formatted hex dump of memory block * output formatted hex dump of memory block
*/ */
@@ -185,8 +179,6 @@ private:
// constructor // constructor
logstream(); logstream();
std::vector<std::string> popup_msgs;
class LogStreamPrivate; class LogStreamPrivate;
std::unique_ptr<LogStreamPrivate> d; std::unique_ptr<LogStreamPrivate> d;
+47 -61
View File
@@ -45,14 +45,6 @@ namespace simgear
std::atomic<int> receiveDepth; std::atomic<int> receiveDepth;
std::atomic<int> sentMessageCount; std::atomic<int> sentMessageCount;
void UnlockList()
{
_lock.unlock();
}
void LockList()
{
_lock.lock();
}
public: public:
Transmitter() : receiveDepth(0), sentMessageCount(0) Transmitter() : receiveDepth(0), sentMessageCount(0)
{ {
@@ -69,20 +61,17 @@ namespace simgear
// most recently registered recipients should process the messages/events first. // most recently registered recipients should process the messages/events first.
virtual void Register(IReceiver& r) virtual void Register(IReceiver& r)
{ {
LockList(); std::lock_guard<std::mutex> scopeLock(_lock);
recipient_list.push_back(&r); recipient_list.push_back(&r);
r.OnRegisteredAtTransmitter(this); r.OnRegisteredAtTransmitter(this);
if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end()) if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end())
deleted_recipients.remove(&r); deleted_recipients.remove(&r);
UnlockList();
} }
// Removes an object from receving message from this transmitter // Removes an object from receving message from this transmitter
virtual void DeRegister(IReceiver& R) virtual void DeRegister(IReceiver& R)
{ {
LockList(); std::lock_guard<std::mutex> scopeLock(_lock);
//printf("Remove %x\n", &R);
if (recipient_list.size()) if (recipient_list.size())
{ {
if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end()) if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end())
@@ -93,7 +82,6 @@ namespace simgear
deleted_recipients.push_back(&R); deleted_recipients.push_back(&R);
} }
} }
UnlockList();
} }
// Notify all registered recipients. Stop when receipt status of abort or finished are received. // Notify all registered recipients. Stop when receipt status of abort or finished are received.
@@ -107,69 +95,68 @@ namespace simgear
ReceiptStatus return_status = ReceiptStatusNotProcessed; ReceiptStatus return_status = ReceiptStatusNotProcessed;
sentMessageCount++; sentMessageCount++;
try
std::vector<IReceiver*> temp;
{ {
LockList(); std::lock_guard<std::mutex> scopeLock(_lock);
if (receiveDepth == 0) if (receiveDepth == 0)
deleted_recipients.clear(); deleted_recipients.clear();
receiveDepth++; receiveDepth++;
std::vector<IReceiver*> temp(recipient_list.size());
int idx = 0; int idx = 0;
for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++) for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++)
{ {
temp[idx++] = *i; temp.push_back(*i);
} }
UnlockList(); }
int tempSize = temp.size(); int tempSize = temp.size();
for (int index = 0; index < tempSize; index++) for (int index = 0; index < tempSize; index++)
{
IReceiver* R = temp[index];
{ {
IReceiver* R = temp[index]; std::lock_guard<std::mutex> scopeLock(_lock);
LockList();
if (deleted_recipients.size()) if (deleted_recipients.size())
{ {
if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end()) if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end())
{ {
UnlockList();
continue; continue;
} }
} }
UnlockList();
if (R)
{
ReceiptStatus rstat = R->Receive(M);
switch (rstat)
{
case ReceiptStatusFail:
return_status = ReceiptStatusFail;
break;
case ReceiptStatusPending:
return_status = ReceiptStatusPending;
break;
case ReceiptStatusPendingFinished:
return rstat;
case ReceiptStatusNotProcessed:
break;
case ReceiptStatusOK:
if (return_status == ReceiptStatusNotProcessed)
return_status = rstat;
break;
case ReceiptStatusAbort:
return ReceiptStatusAbort;
case ReceiptStatusFinished:
return ReceiptStatusOK;
}
}
} }
if (R)
{
ReceiptStatus rstat = R->Receive(M);
switch (rstat)
{
case ReceiptStatusFail:
return_status = ReceiptStatusFail;
break;
case ReceiptStatusPending:
return_status = ReceiptStatusPending;
break;
case ReceiptStatusPendingFinished:
return rstat;
case ReceiptStatusNotProcessed:
break;
case ReceiptStatusOK:
if (return_status == ReceiptStatusNotProcessed)
return_status = rstat;
break;
case ReceiptStatusAbort:
return ReceiptStatusAbort;
case ReceiptStatusFinished:
return ReceiptStatusOK;
}
}
} }
catch (...)
{
throw;
// return_status = ReceiptStatusAbort;
}
receiveDepth--; receiveDepth--;
return return_status; return return_status;
} }
@@ -177,9 +164,8 @@ namespace simgear
// number of currently registered recipients // number of currently registered recipients
virtual int Count() virtual int Count()
{ {
LockList(); std::lock_guard<std::mutex> scopeLock(_lock);
return recipient_list.size(); return recipient_list.size();
UnlockList();
} }
// number of sent messages. // number of sent messages.
+36 -9
View File
@@ -46,6 +46,7 @@
#include <sstream> #include <sstream>
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_random.h>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
#include "metar.hxx" #include "metar.hxx"
@@ -631,30 +632,41 @@ bool SGMetar::scanWind()
int dir; int dir;
if (!strncmp(m, "VRB", 3)) if (!strncmp(m, "VRB", 3))
m += 3, dir = -1; m += 3, dir = -1;
else if (!strncmp(m, "///", 3)) // direction not measurable
m += 3, dir = -1;
else if (!scanNumber(&m, &dir, 3)) else if (!scanNumber(&m, &dir, 3))
return false; return false;
int i; int i;
if (!scanNumber(&m, &i, 2, 3)) if (!strncmp(m, "//", 2)) // speed not measurable
m += 2, i = -1;
else if (!scanNumber(&m, &i, 2, 3))
return false; return false;
double speed = i; double speed = i;
double gust = NaN; double gust = NaN;
if (*m == 'G') { if (*m == 'G') {
m++; m++;
if (!scanNumber(&m, &i, 2, 3)) if (!strncmp(m, "//", 2)) // speed not measurable
m += 2, i = -1;
else if (!scanNumber(&m, &i, 2, 3))
return false; return false;
gust = i;
if (i != -1)
gust = i;
} }
double factor; double factor;
if (!strncmp(m, "KT", 2)) if (!strncmp(m, "KT", 2))
m += 2, factor = SG_KT_TO_MPS; m += 2, factor = SG_KT_TO_MPS;
else if (!strncmp(m, "KMH", 3)) else if (!strncmp(m, "KMH", 3)) // invalid Km/h
m += 3, factor = SG_KMH_TO_MPS; m += 3, factor = SG_KMH_TO_MPS;
else if (!strncmp(m, "KPH", 3)) // ?? else if (!strncmp(m, "KPH", 3)) // invalid Km/h
m += 3, factor = SG_KMH_TO_MPS; m += 3, factor = SG_KMH_TO_MPS;
else if (!strncmp(m, "MPS", 3)) else if (!strncmp(m, "MPS", 3))
m += 3, factor = 1.0; m += 3, factor = 1.0;
else if (!strncmp(m, " ", 1)) // default to Knots
factor = SG_KT_TO_MPS;
else else
return false; return false;
if (!scanBoundary(&m)) if (!scanBoundary(&m))
@@ -674,18 +686,28 @@ bool SGMetar::scanVariability()
{ {
char *m = _m; char *m = _m;
int from, to; int from, to;
if (!scanNumber(&m, &from, 3))
if (!strncmp(m, "///", 3)) // direction not measurable
m += 3, from = -1;
else if (!scanNumber(&m, &from, 3))
return false; return false;
if (*m++ != 'V') if (*m++ != 'V')
return false; return false;
if (!scanNumber(&m, &to, 3))
if (!strncmp(m, "///", 3)) // direction not measurable
m += 3, to = -1;
else if (!scanNumber(&m, &to, 3))
return false; return false;
if (!scanBoundary(&m)) if (!scanBoundary(&m))
return false; return false;
_m = m; _m = m;
_wind_range_from = from; _wind_range_from = from;
_wind_range_to = to; _wind_range_to = to;
_grpcount++; _grpcount++;
return true; return true;
} }
@@ -952,6 +974,8 @@ bool SGMetar::scanWeather()
weather += string(a->text) + " "; weather += string(a->text) + " ";
if (!strcmp(a->id, "RA")) if (!strcmp(a->id, "RA"))
_rain = w.intensity; _rain = w.intensity;
else if (!strcmp(a->id, "DZ"))
_rain = LIGHT;
else if (!strcmp(a->id, "HA")) else if (!strcmp(a->id, "HA"))
_hail = w.intensity; _hail = w.intensity;
else if (!strcmp(a->id, "SN")) else if (!strcmp(a->id, "SN"))
@@ -1045,9 +1069,12 @@ bool SGMetar::scanSkyCondition()
return false; return false;
m += i; m += i;
if (!strncmp(m, "///", 3)) // vis not measurable (e.g. because of heavy snowing) if (!strncmp(m, "///", 3)) { // vis not measurable (e.g. because of heavy snowing)
m += 3, i = -1; m += 3, i = -1;
else if (scanBoundary(&m)) { sg_srandom_time();
// randomize the base height to avoid the black sky issue
i = 50 + static_cast<int>(sg_random() * 250.0); // range [5,000, 30,000]
} else if (scanBoundary(&m)) {
_m = m; _m = m;
return true; // ignore single OVC/BKN/... return true; // ignore single OVC/BKN/...
} else if (!scanNumber(&m, &i, 3)) } else if (!scanNumber(&m, &i, 3))
+26
View File
@@ -76,12 +76,38 @@ void test_sensor_failure_cloud()
SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON); SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON);
} }
void test_sensor_failure_wind()
{
SGMetar m1("2020/10/23 16:55 LIVD 231655Z /////KT 9999 OVC025 10/08 Q1020 RMK OVC VIS MIN 9999 BLU");
SG_CHECK_EQUAL(m1.getWindDir(), -1);
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), -1, TEST_EPSILON);
SGMetar m2("2020/10/21 16:55 LIVD 211655Z /////KT CAVOK 07/03 Q1023 RMK SKC VIS MIN 9999 BLU");
SG_CHECK_EQUAL(m2.getWindDir(), -1);
SG_CHECK_EQUAL_EP2(m2.getWindSpeed_kt(), -1, TEST_EPSILON);
SGMetar m3("2020/11/17 16:00 CYAZ 171600Z 14040G//KT 10SM -RA OVC012 12/11 A2895 RMK NS8 VIA CYXY SLP806 DENSITY ALT 900FT");
SG_CHECK_EQUAL(m3.getWindDir(), 140);
SG_CHECK_EQUAL_EP2(m3.getWindSpeed_kt(), 40, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m3.getGustSpeed_kt(), SGMetarNaN, TEST_EPSILON);
}
void test_wind_unit_not_specified()
{
SGMetar m1("2020/10/23 11:58 KLSV 231158Z 05010G14 10SM CLR 16/M04 A2992 RMK SLPNO WND DATA ESTMD ALSTG/SLP ESTMD 10320 20124 5//// $");
SG_CHECK_EQUAL(m1.getWindDir(), 50);
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), 10.0, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getGustSpeed_kt(), 14.0, TEST_EPSILON);
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
try { try {
test_basic(); test_basic();
test_sensor_failure_weather(); test_sensor_failure_weather();
test_sensor_failure_cloud(); test_sensor_failure_cloud();
test_sensor_failure_wind();
test_wind_unit_not_specified();
} catch (sg_exception& e) { } catch (sg_exception& e) {
cerr << "got exception:" << e.getMessage() << endl; cerr << "got exception:" << e.getMessage() << endl;
return -1; return -1;
+90
View File
@@ -0,0 +1,90 @@
// Copyright (C) 2021 James Turner - <james@flightgear.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#pragma once
#include "untar.hxx"
namespace simgear {
class ArchiveExtractorPrivate
{
public:
ArchiveExtractorPrivate(ArchiveExtractor* o) : outer(o)
{
assert(outer);
}
virtual ~ArchiveExtractorPrivate() = default;
typedef enum {
INVALID = 0,
READING_HEADER,
READING_FILE,
READING_PADDING,
READING_PAX_GLOBAL_ATTRIBUTES,
READING_PAX_FILE_ATTRIBUTES,
PRE_END_OF_ARCHVE,
END_OF_ARCHIVE,
ERROR_STATE, ///< states above this are error conditions
BAD_ARCHIVE,
BAD_DATA,
FILTER_STOPPED
} State;
State state = INVALID;
ArchiveExtractor* outer = nullptr;
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
virtual void flush() = 0;
SGPath extractRootPath()
{
return outer->_rootPath;
}
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
{
return outer->filterPath(pathToExtract);
}
bool isSafePath(const std::string& p) const
{
if (p.empty()) {
return false;
}
// reject absolute paths
if (p.at(0) == '/') {
return false;
}
// reject paths containing '..'
size_t doubleDot = p.find("..");
if (doubleDot != std::string::npos) {
return false;
}
// on POSIX could use realpath to sanity check
return true;
}
};
} // namespace simgear
+3
View File
@@ -35,10 +35,12 @@ set(SOURCES
sg_socket.cxx sg_socket.cxx
sg_socket_udp.cxx sg_socket_udp.cxx
HTTPClient.cxx HTTPClient.cxx
HTTPTestApi_private.hxx
HTTPFileRequest.cxx HTTPFileRequest.cxx
HTTPMemoryRequest.cxx HTTPMemoryRequest.cxx
HTTPRequest.cxx HTTPRequest.cxx
HTTPRepository.cxx HTTPRepository.cxx
HTTPRepository_private.hxx
untar.cxx untar.cxx
) )
@@ -81,6 +83,7 @@ add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
add_executable(test_repository test_repository.cxx) add_executable(test_repository test_repository.cxx)
target_link_libraries(test_repository ${TEST_LIBS}) target_link_libraries(test_repository ${TEST_LIBS})
target_compile_definitions(test_repository PUBLIC BUILDING_TESTSUITE)
add_test(http_repository ${EXECUTABLE_OUTPUT_PATH}/test_repository) add_test(http_repository ${EXECUTABLE_OUTPUT_PATH}/test_repository)
add_executable(test_untar test_untar.cxx) add_executable(test_untar test_untar.cxx)
+86 -43
View File
@@ -61,6 +61,10 @@ public:
struct dns_ctx * ctx; struct dns_ctx * ctx;
static size_t instanceCounter; static size_t instanceCounter;
using RequestVec = std::vector<Request_ptr>;
RequestVec _activeRequests;
}; };
size_t Client::ClientPrivate::instanceCounter = 0; size_t Client::ClientPrivate::instanceCounter = 0;
@@ -78,6 +82,11 @@ Request::~Request()
{ {
} }
void Request::cancel()
{
_cancelled = true;
}
bool Request::isTimeout() const bool Request::isTimeout() const
{ {
return (time(NULL) - _start) > _timeout_secs; return (time(NULL) - _start) > _timeout_secs;
@@ -114,18 +123,20 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
{ {
SRVRequest * r = static_cast<SRVRequest*>(data); SRVRequest * r = static_cast<SRVRequest*>(data);
if (result) { if (result) {
r->cname = result->dnssrv_cname; if (!r->isCancelled()) {
r->qname = result->dnssrv_qname; r->cname = result->dnssrv_cname;
r->ttl = result->dnssrv_ttl; r->qname = result->dnssrv_qname;
for (int i = 0; i < result->dnssrv_nrr; i++) { r->ttl = result->dnssrv_ttl;
SRVRequest::SRV_ptr srv(new SRVRequest::SRV); for (int i = 0; i < result->dnssrv_nrr; i++) {
r->entries.push_back(srv); SRVRequest::SRV_ptr srv(new SRVRequest::SRV);
srv->priority = result->dnssrv_srv[i].priority; r->entries.push_back(srv);
srv->weight = result->dnssrv_srv[i].weight; srv->priority = result->dnssrv_srv[i].priority;
srv->port = result->dnssrv_srv[i].port; srv->weight = result->dnssrv_srv[i].weight;
srv->target = result->dnssrv_srv[i].name; srv->port = result->dnssrv_srv[i].port;
srv->target = result->dnssrv_srv[i].name;
}
std::sort(r->entries.begin(), r->entries.end(), sortSRV);
} }
std::sort( r->entries.begin(), r->entries.end(), sortSRV );
free(result); free(result);
} }
r->setComplete(); r->setComplete();
@@ -134,11 +145,16 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
void SRVRequest::submit( Client * client ) void SRVRequest::submit( Client * client )
{ {
// if service is defined, pass service and protocol // if service is defined, pass service and protocol
if (!dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(), _service.empty() ? NULL : _protocol.c_str(), 0, dnscbSRV, this )) { auto q = dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(),
_service.empty() ? NULL : _protocol.c_str(),
0, dnscbSRV, this);
if (!q) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn()); SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return; return;
} }
_start = time(NULL); _start = time(NULL);
_query = q;
} }
TXTRequest::TXTRequest( const std::string & dn ) : TXTRequest::TXTRequest( const std::string & dn ) :
@@ -151,17 +167,24 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
{ {
TXTRequest * r = static_cast<TXTRequest*>(data); TXTRequest * r = static_cast<TXTRequest*>(data);
if (result) { if (result) {
r->cname = result->dnstxt_cname; if (!r->isCancelled()) {
r->qname = result->dnstxt_qname; r->cname = result->dnstxt_cname;
r->ttl = result->dnstxt_ttl; r->qname = result->dnstxt_qname;
for (int i = 0; i < result->dnstxt_nrr; i++) { r->ttl = result->dnstxt_ttl;
//TODO: interprete the .len field of dnstxt_txt? for (int i = 0; i < result->dnstxt_nrr; i++) {
string txt = string((char*)result->dnstxt_txt[i].txt); //TODO: interprete the .len field of dnstxt_txt?
r->entries.push_back( txt ); auto rawTxt = reinterpret_cast<char*>(result->dnstxt_txt[i].txt);
string_list tokens = simgear::strutils::split( txt, "=", 1 ); if (!rawTxt) {
if( tokens.size() == 2 ) { continue;
r->attributes[tokens[0]] = tokens[1]; }
}
const string txt{rawTxt};
r->entries.push_back(txt);
string_list tokens = simgear::strutils::split(txt, "=", 1);
if (tokens.size() == 2) {
r->attributes[tokens[0]] = tokens[1];
}
}
} }
free(result); free(result);
} }
@@ -171,11 +194,13 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
void TXTRequest::submit( Client * client ) void TXTRequest::submit( Client * client )
{ {
// protocol and service an already encoded in DN so pass in NULL for both // protocol and service an already encoded in DN so pass in NULL for both
if (!dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this )) { auto q = dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this);
if (!q) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn()); SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return; return;
} }
_start = time(NULL); _start = time(NULL);
_query = q;
} }
@@ -190,27 +215,29 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
{ {
NAPTRRequest * r = static_cast<NAPTRRequest*>(data); NAPTRRequest * r = static_cast<NAPTRRequest*>(data);
if (result) { if (result) {
r->cname = result->dnsnaptr_cname; if (!r->isCancelled()) {
r->qname = result->dnsnaptr_qname; r->cname = result->dnsnaptr_cname;
r->ttl = result->dnsnaptr_ttl; r->qname = result->dnsnaptr_qname;
for (int i = 0; i < result->dnsnaptr_nrr; i++) { r->ttl = result->dnsnaptr_ttl;
if( !r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service ) for (int i = 0; i < result->dnsnaptr_nrr; i++) {
continue; if (!r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service)
continue;
//TODO: case ignore and result flags may have more than one flag //TODO: case ignore and result flags may have more than one flag
if( !r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags ) if (!r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags)
continue; continue;
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR); NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
r->entries.push_back(naptr); r->entries.push_back(naptr);
naptr->order = result->dnsnaptr_naptr[i].order; naptr->order = result->dnsnaptr_naptr[i].order;
naptr->preference = result->dnsnaptr_naptr[i].preference; naptr->preference = result->dnsnaptr_naptr[i].preference;
naptr->flags = result->dnsnaptr_naptr[i].flags; naptr->flags = result->dnsnaptr_naptr[i].flags;
naptr->service = result->dnsnaptr_naptr[i].service; naptr->service = result->dnsnaptr_naptr[i].service;
naptr->regexp = result->dnsnaptr_naptr[i].regexp; naptr->regexp = result->dnsnaptr_naptr[i].regexp;
naptr->replacement = result->dnsnaptr_naptr[i].replacement; naptr->replacement = result->dnsnaptr_naptr[i].replacement;
}
std::sort(r->entries.begin(), r->entries.end(), sortNAPTR);
} }
std::sort( r->entries.begin(), r->entries.end(), sortNAPTR );
free(result); free(result);
} }
r->setComplete(); r->setComplete();
@@ -218,11 +245,13 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
void NAPTRRequest::submit( Client * client ) void NAPTRRequest::submit( Client * client )
{ {
if (!dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this )) { auto q = dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this);
if (!q) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn()); SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return; return;
} }
_start = time(NULL); _start = time(NULL);
_query = q;
} }
@@ -237,6 +266,7 @@ Client::Client() :
void Client::makeRequest(const Request_ptr& r) void Client::makeRequest(const Request_ptr& r)
{ {
d->_activeRequests.push_back(r);
r->submit(this); r->submit(this);
} }
@@ -247,6 +277,19 @@ void Client::update(int waitTimeout)
return; return;
dns_ioevent(d->ctx, now); dns_ioevent(d->ctx, now);
// drop our owning ref to completed requests,
// and cancel any which timed out
auto it = std::remove_if(d->_activeRequests.begin(), d->_activeRequests.end(),
[this](const Request_ptr& r) {
if (r->isTimeout()) {
dns_cancel(d->ctx, reinterpret_cast<struct dns_query*>(r->_query));
return true;
}
return r->isComplete();
});
d->_activeRequests.erase(it, d->_activeRequests.end());
} }
} // of namespace DNS } // of namespace DNS
+14 -4
View File
@@ -40,28 +40,38 @@ namespace DNS
{ {
class Client; class Client;
using UDNSQueryPtr = void*;
class Request : public SGReferenced class Request : public SGReferenced
{ {
public: public:
Request( const std::string & dn ); Request( const std::string & dn );
virtual ~Request(); virtual ~Request();
std::string getDn() const { return _dn; } const std::string& getDn() const { return _dn; }
int getType() const { return _type; } int getType() const { return _type; }
bool isComplete() const { return _complete; } bool isComplete() const { return _complete; }
bool isTimeout() const; bool isTimeout() const;
void setComplete( bool b = true ) { _complete = b; } void setComplete( bool b = true ) { _complete = b; }
bool isCancelled() const { return _cancelled; }
virtual void submit( Client * client) = 0; virtual void submit( Client * client) = 0;
void cancel();
std::string cname; std::string cname;
std::string qname; std::string qname;
unsigned ttl; unsigned ttl;
protected: protected:
friend class Client;
UDNSQueryPtr _query = nullptr;
std::string _dn; std::string _dn;
int _type; int _type;
bool _complete; bool _complete;
time_t _timeout_secs; time_t _timeout_secs;
time_t _start; time_t _start;
bool _cancelled = false;
}; };
typedef SGSharedPtr<Request> Request_ptr; typedef SGSharedPtr<Request> Request_ptr;
@@ -69,7 +79,7 @@ class NAPTRRequest : public Request
{ {
public: public:
NAPTRRequest( const std::string & dn ); NAPTRRequest( const std::string & dn );
virtual void submit( Client * client ); void submit(Client* client) override;
struct NAPTR : SGReferenced { struct NAPTR : SGReferenced {
int order; int order;
@@ -92,7 +102,7 @@ class SRVRequest : public Request
public: public:
SRVRequest( const std::string & dn ); SRVRequest( const std::string & dn );
SRVRequest( const std::string & dn, const string & service, const string & protocol ); SRVRequest( const std::string & dn, const string & service, const string & protocol );
virtual void submit( Client * client ); void submit(Client* client) override;
struct SRV : SGReferenced { struct SRV : SGReferenced {
int priority; int priority;
@@ -112,7 +122,7 @@ class TXTRequest : public Request
{ {
public: public:
TXTRequest( const std::string & dn ); TXTRequest( const std::string & dn );
virtual void submit( Client * client ); void submit(Client* client) override;
typedef std::vector<string> TXT_list; typedef std::vector<string> TXT_list;
typedef std::map<std::string,std::string> TXT_Attribute_map; typedef std::map<std::string,std::string> TXT_Attribute_map;
+85 -84
View File
@@ -37,7 +37,6 @@
#include <simgear/simgear_config.h> #include <simgear/simgear_config.h>
#include <curl/multi.h>
#include <simgear/io/sg_netChat.hxx> #include <simgear/io/sg_netChat.hxx>
@@ -47,6 +46,9 @@
#include <simgear/timing/timestamp.hxx> #include <simgear/timing/timestamp.hxx>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
#include "HTTPClient_private.hxx"
#include "HTTPTestApi_private.hxx"
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
#include "version.h" #include "version.h"
#else #else
@@ -64,62 +66,23 @@ namespace HTTP
extern const int DEFAULT_HTTP_PORT = 80; extern const int DEFAULT_HTTP_PORT = 80;
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded"; const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
class Connection; void Client::ClientPrivate::createCurlMulti() {
typedef std::multimap<std::string, Connection*> ConnectionDict; curlMulti = curl_multi_init();
typedef std::list<Request_ptr> RequestList; // see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
// we request HTTP 1.1 pipelining
class Client::ClientPrivate curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
{
public:
CURLM* curlMulti;
void createCurlMulti()
{
curlMulti = curl_multi_init();
// see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
// we request HTTP 1.1 pipelining
curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
#if (LIBCURL_VERSION_MINOR >= 30) #if (LIBCURL_VERSION_MINOR >= 30)
curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long) maxConnections); curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS,
curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH, (long)maxConnections);
(long) maxPipelineDepth); curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH,
curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS, (long)maxPipelineDepth);
(long) maxHostConnections); curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS,
(long)maxHostConnections);
#endif #endif
} }
typedef std::map<Request_ptr, CURL*> RequestCurlMap; Client::Client()
RequestCurlMap requests;
std::string userAgent;
std::string proxy;
int proxyPort;
std::string proxyAuth;
unsigned int maxConnections;
unsigned int maxHostConnections;
unsigned int maxPipelineDepth;
RequestList pendingRequests;
SGTimeStamp timeTransferSample;
unsigned int bytesTransferred;
unsigned int lastTransferRate;
uint64_t totalBytesDownloaded;
};
Client::Client() :
d(new ClientPrivate)
{ {
d->proxyPort = 0;
d->maxConnections = 4;
d->maxHostConnections = 4;
d->bytesTransferred = 0;
d->lastTransferRate = 0;
d->timeTransferSample.stamp();
d->totalBytesDownloaded = 0;
d->maxPipelineDepth = 5;
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
static bool didInitCurlGlobal = false; static bool didInitCurlGlobal = false;
static std::mutex initMutex; static std::mutex initMutex;
@@ -129,7 +92,7 @@ Client::Client() :
didInitCurlGlobal = true; didInitCurlGlobal = true;
} }
d->createCurlMulti(); reset();
} }
Client::~Client() Client::~Client()
@@ -161,6 +124,27 @@ void Client::setMaxPipelineDepth(unsigned int depth)
#endif #endif
} }
void Client::reset()
{
if (d.get()) {
curl_multi_cleanup(d->curlMulti);
}
d.reset(new ClientPrivate);
d->proxyPort = 0;
d->maxConnections = 4;
d->maxHostConnections = 4;
d->bytesTransferred = 0;
d->lastTransferRate = 0;
d->timeTransferSample.stamp();
d->totalBytesDownloaded = 0;
d->maxPipelineDepth = 5;
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
d->tlsCertificatePath = SGPath::fromEnv("SIMGEAR_TLS_CERT_PATH");
d->createCurlMulti();
}
void Client::update(int waitTimeout) void Client::update(int waitTimeout)
{ {
if (d->requests.empty()) { if (d->requests.empty()) {
@@ -171,32 +155,22 @@ void Client::update(int waitTimeout)
} }
int remainingActive, messagesInQueue; int remainingActive, messagesInQueue;
#if defined(SG_MAC) int numFds;
// Mac 10.8 libCurl lacks this, let's keep compat for now CURLMcode mc = curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
fd_set curlReadFDs, curlWriteFDs, curlErrorFDs; if (mc != CURLM_OK) {
int maxFD; SG_LOG(SG_IO, SG_WARN, "curl_multi_wait failed:" << curl_multi_strerror(mc));
curl_multi_fdset(d->curlMulti, return;
&curlReadFDs,
&curlWriteFDs,
&curlErrorFDs,
&maxFD);
struct timeval timeout;
long t;
curl_multi_timeout(d->curlMulti, &t);
if ((t < 0) || (t > waitTimeout)) {
t = waitTimeout;
} }
timeout.tv_sec = t / 1000; mc = curl_multi_perform(d->curlMulti, &remainingActive);
timeout.tv_usec = (t % 1000) * 1000; if (mc == CURLM_CALL_MULTI_PERFORM) {
::select(maxFD, &curlReadFDs, &curlWriteFDs, &curlErrorFDs, &timeout); // we could loop here, but don't want to get blocked
#else // also this shouldn't ocurr in any modern libCurl
int numFds; curl_multi_perform(d->curlMulti, &remainingActive);
curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds); } else if (mc != CURLM_OK) {
#endif SG_LOG(SG_IO, SG_WARN, "curl_multi_perform failed:" << curl_multi_strerror(mc));
curl_multi_perform(d->curlMulti, &remainingActive); return;
}
CURLMsg* msg; CURLMsg* msg;
while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) { while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) {
@@ -221,12 +195,23 @@ void Client::update(int waitTimeout)
assert(it->second == e); assert(it->second == e);
d->requests.erase(it); d->requests.erase(it);
if (msg->data.result == 0) { bool doProcess = true;
req->responseComplete(); if (d->testsuiteResponseDoneCallback) {
} else { doProcess =
SG_LOG(SG_IO, SG_WARN, "CURL Result:" << msg->data.result << " " << curl_easy_strerror(msg->data.result)); !d->testsuiteResponseDoneCallback(msg->data.result, req);
req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result)); }
}
if (doProcess) {
if (msg->data.result == 0) {
req->responseComplete();
} else {
SG_LOG(SG_IO, SG_WARN,
"CURL Result:" << msg->data.result << " "
<< curl_easy_strerror(msg->data.result));
req->setFailure(msg->data.result,
curl_easy_strerror(msg->data.result));
}
}
curl_multi_remove_handle(d->curlMulti, e); curl_multi_remove_handle(d->curlMulti, e);
curl_easy_cleanup(e); curl_easy_cleanup(e);
@@ -285,6 +270,11 @@ void Client::makeRequest(const Request_ptr& r)
curl_easy_setopt(curlRequest, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curlRequest, CURLOPT_FOLLOWLOCATION, 1);
if (!d->tlsCertificatePath.isNull()) {
const auto utf8 = d->tlsCertificatePath.utf8Str();
curl_easy_setopt(curlRequest, CURLOPT_CAINFO, utf8.c_str());
}
if (!d->proxy.empty()) { if (!d->proxy.empty()) {
curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str()); curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str());
curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort); curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort);
@@ -552,6 +542,17 @@ void Client::clearAllConnections()
d->createCurlMulti(); d->createCurlMulti();
} }
/////////////////////////////////////////////////////////////////////
void TestApi::setResponseDoneCallback(Client *cl, ResponseDoneCallback cb) {
cl->d->testsuiteResponseDoneCallback = cb;
}
void TestApi::markRequestAsFailed(Request_ptr req, int curlCode,
const std::string &message) {
req->setFailure(curlCode, message);
}
} // of namespace HTTP } // of namespace HTTP
} // of namespace simgear } // of namespace simgear
+5 -1
View File
@@ -24,7 +24,8 @@
#ifndef SG_HTTP_CLIENT_HXX #ifndef SG_HTTP_CLIENT_HXX
#define SG_HTTP_CLIENT_HXX #define SG_HTTP_CLIENT_HXX
#include <memory> // for std::unique_ptr #include <functional>
#include <memory> // for std::unique_ptr
#include <stdint.h> // for uint_64t #include <stdint.h> // for uint_64t
#include <simgear/io/HTTPFileRequest.hxx> #include <simgear/io/HTTPFileRequest.hxx>
@@ -47,6 +48,8 @@ public:
void update(int waitTimeout = 0); void update(int waitTimeout = 0);
void reset();
void makeRequest(const Request_ptr& r); void makeRequest(const Request_ptr& r);
void cancelRequest(const Request_ptr& r, std::string reason = std::string()); void cancelRequest(const Request_ptr& r, std::string reason = std::string());
@@ -123,6 +126,7 @@ private:
friend class Connection; friend class Connection;
friend class Request; friend class Request;
friend class TestApi;
class ClientPrivate; class ClientPrivate;
std::unique_ptr<ClientPrivate> d; std::unique_ptr<ClientPrivate> d;
+68
View File
@@ -0,0 +1,68 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#pragma once
#include <list>
#include <map>
#include "HTTPClient.hxx"
#include "HTTPRequest.hxx"
#include <simgear/timing/timestamp.hxx>
#include <curl/multi.h>
namespace simgear {
namespace HTTP {
typedef std::list<Request_ptr> RequestList;
using ResponseDoneCallback =
std::function<bool(int curlResult, Request_ptr req)>;
class Client::ClientPrivate {
public:
CURLM *curlMulti;
void createCurlMulti();
typedef std::map<Request_ptr, CURL *> RequestCurlMap;
RequestCurlMap requests;
std::string userAgent;
std::string proxy;
int proxyPort;
std::string proxyAuth;
unsigned int maxConnections;
unsigned int maxHostConnections;
unsigned int maxPipelineDepth;
RequestList pendingRequests;
SGTimeStamp timeTransferSample;
unsigned int bytesTransferred;
unsigned int lastTransferRate;
uint64_t totalBytesDownloaded;
SGPath tlsCertificatePath;
// only used by unit-tests / test-api, but
// only costs us a pointe here to declare it.
ResponseDoneCallback testsuiteResponseDoneCallback;
};
} // namespace HTTP
} // namespace simgear
File diff suppressed because it is too large Load Diff
+74 -37
View File
@@ -20,6 +20,7 @@
#ifndef SG_IO_HTTP_REPOSITORY_HXX #ifndef SG_IO_HTTP_REPOSITORY_HXX
#define SG_IO_HTTP_REPOSITORY_HXX #define SG_IO_HTTP_REPOSITORY_HXX
#include <functional>
#include <memory> #include <memory>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
@@ -32,49 +33,85 @@ class HTTPRepoPrivate;
class HTTPRepository class HTTPRepository
{ {
public: public:
enum ResultCode { enum ResultCode {
REPO_NO_ERROR = 0, REPO_NO_ERROR = 0,
REPO_ERROR_NOT_FOUND, REPO_ERROR_NOT_FOUND,
REPO_ERROR_SOCKET, REPO_ERROR_SOCKET,
SVN_ERROR_XML, SVN_ERROR_XML,
SVN_ERROR_TXDELTA, SVN_ERROR_TXDELTA,
REPO_ERROR_IO, REPO_ERROR_IO,
REPO_ERROR_CHECKSUM, REPO_ERROR_CHECKSUM,
REPO_ERROR_FILE_NOT_FOUND, REPO_ERROR_FILE_NOT_FOUND,
REPO_ERROR_HTTP, REPO_ERROR_HTTP,
REPO_ERROR_CANCELLED, REPO_ERROR_CANCELLED,
REPO_PARTIAL_UPDATE REPO_PARTIAL_UPDATE ///< repository is working, but file-level failures
///< occurred
};
HTTPRepository(const SGPath &root, HTTP::Client *cl);
virtual ~HTTPRepository();
virtual SGPath fsBase() const;
virtual void setBaseUrl(const std::string &url);
virtual std::string baseUrl() const;
virtual HTTP::Client *http() const;
virtual void update();
virtual bool isDoingSync() const;
/**
@brief call this periodically to progress non-network tasks
*/
void process();
virtual ResultCode failure() const;
virtual size_t bytesToDownload() const;
virtual size_t bytesDownloaded() const;
virtual size_t bytesToExtract() const;
/**
* optionally provide the location of an installer copy of this
* repository. When a file is missing it will be copied from this tree.
*/
void setInstalledCopyPath(const SGPath &copyPath);
static std::string resultCodeAsString(ResultCode code);
enum class SyncAction { Add, Update, Delete, UpToDate };
enum EntryType { FileType, DirectoryType, TarballType };
struct SyncItem {
const std::string directory; // relative path in the repository
const EntryType type;
const std::string filename;
const SyncAction action;
const SGPath pathOnDisk; // path the entry does / will have
};
using SyncPredicate = std::function<bool(const SyncItem &item)>;
void setFilter(SyncPredicate sp);
struct Failure {
SGPath path;
ResultCode error;
}; };
HTTPRepository(const SGPath& root, HTTP::Client* cl); using FailureVec = std::vector<Failure>;
virtual ~HTTPRepository();
virtual SGPath fsBase() const;
virtual void setBaseUrl(const std::string& url);
virtual std::string baseUrl() const;
virtual HTTP::Client* http() const;
virtual void update();
virtual bool isDoingSync() const;
virtual ResultCode failure() const;
virtual size_t bytesToDownload() const;
virtual size_t bytesDownloaded() const;
/** /**
* optionally provide the location of an installer copy of this * @brief return file-level failures
* repository. When a file is missing it will be copied from this tree.
*/ */
void setInstalledCopyPath(const SGPath& copyPath); FailureVec failures() const;
static std::string resultCodeAsString(ResultCode code);
private: private:
bool isBare() const; bool isBare() const;
std::unique_ptr<HTTPRepoPrivate> _d; std::unique_ptr<HTTPRepoPrivate> _d;
+127
View File
@@ -0,0 +1,127 @@
// HTTPRepository.cxx -- plain HTTP TerraSync remote client
//
// Copyright (C) 20126 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
// USA.
#pragma once
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/misc/sg_path.hxx>
#include "HTTPRepository.hxx"
namespace simgear {
class HTTPDirectory;
using HTTPDirectory_ptr = std::unique_ptr<HTTPDirectory>;
class HTTPRepoGetRequest : public HTTP::Request {
public:
HTTPRepoGetRequest(HTTPDirectory *d, const std::string &u)
: HTTP::Request(u), _directory(d) {}
virtual void cancel();
size_t contentSize() const { return _contentSize; }
void setContentSize(size_t sz) { _contentSize = sz; }
protected:
HTTPDirectory *_directory;
size_t _contentSize = 0;
};
using RepoRequestPtr = SGSharedPtr<HTTPRepoGetRequest>;
class HTTPRepoPrivate {
public:
HTTPRepository::FailureVec failures;
int maxPermittedFailures = 16;
HTTPRepoPrivate(HTTPRepository* parent)
: p(parent)
{
}
~HTTPRepoPrivate();
HTTPRepository *p; // link back to outer
HTTP::Client* http = nullptr;
std::string baseUrl;
SGPath basePath;
bool isUpdating = false;
HTTPRepository::ResultCode status = HTTPRepository::REPO_NO_ERROR;
HTTPDirectory_ptr rootDir;
size_t totalDownloaded = 0;
size_t bytesToExtract = 0;
size_t bytesExtracted = 0;
HTTPRepository::SyncPredicate syncPredicate;
HTTP::Request_ptr updateFile(HTTPDirectory *dir, const std::string &name,
size_t sz);
HTTP::Request_ptr updateDir(HTTPDirectory *dir, const std::string &hash,
size_t sz);
void failedToGetRootIndex(HTTPRepository::ResultCode st);
void failedToUpdateChild(const SGPath &relativePath,
HTTPRepository::ResultCode fileStatus);
void updatedChildSuccessfully(const SGPath &relativePath);
void checkForComplete();
typedef std::vector<RepoRequestPtr> RequestVector;
RequestVector queuedRequests, activeRequests;
void makeRequest(RepoRequestPtr req);
enum class RequestFinish { Done, Retry };
void finishedRequest(const RepoRequestPtr &req, RequestFinish retryRequest);
HTTPDirectory *getOrCreateDirectory(const std::string &path);
bool deleteDirectory(const std::string &relPath, const SGPath &absPath);
typedef std::vector<HTTPDirectory_ptr> DirectoryVector;
DirectoryVector directories;
void scheduleUpdateOfChildren(HTTPDirectory *dir);
SGPath installedCopyPath;
int countDirtyHashCaches() const;
void flushHashCaches();
enum ProcessResult { ProcessContinue, ProcessDone, ProcessFailed };
using RepoProcessTask = std::function<ProcessResult(HTTPRepoPrivate *repo)>;
void addTask(RepoProcessTask task);
std::deque<RepoProcessTask> pendingTasks;
};
} // namespace simgear
+33 -10
View File
@@ -56,6 +56,15 @@ Request::~Request()
} }
void Request::prepareForRetry() {
setReadyState(UNSENT);
_willClose = false;
_connectionCloseHeader = false;
_responseStatus = 0;
_responseLength = 0;
_receivedBodyBytes = 0;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Request* Request::done(const Callback& cb) Request* Request::done(const Callback& cb)
{ {
@@ -193,13 +202,16 @@ void Request::onDone()
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void Request::onFail() void Request::onFail()
{ {
SG_LOG // log if we FAIELD< but not if we CANCELLED
( if (_ready_state == FAILED) {
SG_IO, SG_LOG
SG_INFO, (
"request failed:" << url() << " : " SG_IO,
<< responseCode() << "/" << responseReason() SG_INFO,
); "request failed:" << url() << " : "
<< responseCode() << "/" << responseReason()
);
}
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -344,12 +356,17 @@ void Request::setSuccess(int code)
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void Request::setFailure(int code, const std::string& reason) void Request::setFailure(int code, const std::string& reason)
{ {
// we use -1 for cancellation, don't be noisy in that case
if (code >= 0) {
SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason); SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason);
}
_responseStatus = code; _responseStatus = code;
_responseReason = reason; _responseReason = reason;
if( !isComplete() ) if( !isComplete() ) {
setReadyState(FAILED); setReadyState(code < 0 ? CANCELLED : FAILED);
}
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -373,6 +390,12 @@ void Request::setReadyState(ReadyState state)
_cb_fail(this); _cb_fail(this);
} }
else if (state == CANCELLED )
{
onFail(); // do this for compatability
onAlways();
_cb_fail(this);
}
else else
return; return;
@@ -402,7 +425,7 @@ bool Request::serverSupportsPipelining() const
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
bool Request::isComplete() const bool Request::isComplete() const
{ {
return _ready_state == DONE || _ready_state == FAILED; return _ready_state == DONE || _ready_state == FAILED || _ready_state == CANCELLED;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
+9 -4
View File
@@ -54,7 +54,8 @@ public:
HEADERS_RECEIVED, HEADERS_RECEIVED,
LOADING, LOADING,
DONE, DONE,
FAILED FAILED,
CANCELLED
}; };
virtual ~Request(); virtual ~Request();
@@ -207,7 +208,9 @@ public:
*/ */
bool serverSupportsPipelining() const; bool serverSupportsPipelining() const;
protected: virtual void prepareForRetry();
protected:
Request(const std::string& url, const std::string method = "GET"); Request(const std::string& url, const std::string method = "GET");
virtual void requestStart(); virtual void requestStart();
@@ -221,12 +224,14 @@ protected:
virtual void onFail(); virtual void onFail();
virtual void onAlways(); virtual void onAlways();
void setFailure(int code, const std::string& reason);
void setSuccess(int code); void setSuccess(int code);
private: void setFailure(int code, const std::string &reason);
private:
friend class Client; friend class Client;
friend class Connection; friend class Connection;
friend class ContentDecoder; friend class ContentDecoder;
friend class TestApi;
Request(const Request&); // = delete; Request(const Request&); // = delete;
Request& operator=(const Request&); // = delete; Request& operator=(const Request&); // = delete;
+45
View File
@@ -0,0 +1,45 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#pragma once
#include <functional>
#include "HTTPRequest.hxx"
namespace simgear {
namespace HTTP {
class Client;
using ResponseDoneCallback =
std::function<bool(int curlResult, Request_ptr req)>;
/**
* @brief this API is for unit-testing HTTP code.
* Don't use it for anything else. It's for unit-testing.
*/
class TestApi {
public:
// alow test suite to manipulate requests to simulate network errors;
// without this, it's hard to provoke certain failures in a loop-back
// network sitation.
static void setResponseDoneCallback(Client *cl, ResponseDoneCallback cb);
static void markRequestAsFailed(Request_ptr req, int curlCode,
const std::string &message);
};
} // namespace HTTP
} // namespace simgear
+4 -1
View File
@@ -185,6 +185,9 @@ gzfilebuf::setcompressionstrategy( int comp_strategy )
z_off_t z_off_t
gzfilebuf::approxOffset() { gzfilebuf::approxOffset() {
#ifdef __OpenBSD__
z_off_t res = 0;
#else
z_off_t res = gzoffset(file); z_off_t res = gzoffset(file);
if (res == -1) { if (res == -1) {
@@ -201,7 +204,7 @@ gzfilebuf::approxOffset() {
SG_LOG( SG_GENERAL, SG_ALERT, errMsg ); SG_LOG( SG_GENERAL, SG_ALERT, errMsg );
throw sg_io_exception(errMsg); throw sg_io_exception(errMsg);
} }
#endif
return res; return res;
} }
+70 -42
View File
@@ -23,28 +23,41 @@
// $Id$ // $Id$
#ifdef HAVE_CONFIG_H #include <simgear_config.h>
# include <simgear_config.h>
#endif
#include <string.h> // for memcpy() #include <string.h> // for memcpy()
#include <errno.h>
#include "lowlevel.hxx" #include <simgear/structure/exception.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_path.hxx>
#include "lowlevel.hxx"
static int read_error = false ; thread_local SGPath thread_gzPath;
static int write_error = false ;
void sgClearReadError() { read_error = false; } void setThreadLocalSimgearReadPath(const SGPath& path)
void sgClearWriteError() { write_error = false; } {
int sgReadError() { return read_error ; } thread_gzPath = path;
int sgWriteError() { return write_error ; } }
static std::string gzErrorMessage(gzFile fd)
{
int errNum = 0;
const char *gzMsg = gzerror(fd, &errNum);
if (errNum == Z_ERRNO) {
return simgear::strutils::error_string(errno);
} else {
return {gzMsg};
}
}
void sgReadChar ( gzFile fd, char *var ) void sgReadChar ( gzFile fd, char *var )
{ {
if ( gzread ( fd, var, sizeof(char) ) != sizeof(char) ) { if ( gzread ( fd, var, sizeof(char) ) != sizeof(char) ) {
read_error = true ; throw sg_io_exception("sgReadChar: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
} }
@@ -52,7 +65,7 @@ void sgReadChar ( gzFile fd, char *var )
void sgWriteChar ( gzFile fd, const char var ) void sgWriteChar ( gzFile fd, const char var )
{ {
if ( gzwrite ( fd, (void *)(&var), sizeof(char) ) != sizeof(char) ) { if ( gzwrite ( fd, (void *)(&var), sizeof(char) ) != sizeof(char) ) {
write_error = true ; throw sg_io_exception("sgWriteChar: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -61,7 +74,8 @@ void sgReadFloat ( gzFile fd, float *var )
{ {
union { float v; uint32_t u; } buf; union { float v; uint32_t u; } buf;
if ( gzread ( fd, &buf.u, sizeof(float) ) != sizeof(float) ) { if ( gzread ( fd, &buf.u, sizeof(float) ) != sizeof(float) ) {
read_error = true ; throw sg_io_exception("sgReadFloat: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( &buf.u ); sgEndianSwap( &buf.u );
@@ -78,7 +92,7 @@ void sgWriteFloat ( gzFile fd, const float var )
sgEndianSwap( &buf.u ); sgEndianSwap( &buf.u );
} }
if ( gzwrite ( fd, (void *)(&buf.u), sizeof(float) ) != sizeof(float) ) { if ( gzwrite ( fd, (void *)(&buf.u), sizeof(float) ) != sizeof(float) ) {
write_error = true ; throw sg_io_exception("sgWriteFloat: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -87,7 +101,8 @@ void sgReadDouble ( gzFile fd, double *var )
{ {
union { double v; uint64_t u; } buf; union { double v; uint64_t u; } buf;
if ( gzread ( fd, &buf.u, sizeof(double) ) != sizeof(double) ) { if ( gzread ( fd, &buf.u, sizeof(double) ) != sizeof(double) ) {
read_error = true ; throw sg_io_exception("sgReadDouble: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( &buf.u ); sgEndianSwap( &buf.u );
@@ -104,7 +119,7 @@ void sgWriteDouble ( gzFile fd, const double var )
sgEndianSwap( &buf.u ); sgEndianSwap( &buf.u );
} }
if ( gzwrite ( fd, (void *)(&buf.u), sizeof(double) ) != sizeof(double) ) { if ( gzwrite ( fd, (void *)(&buf.u), sizeof(double) ) != sizeof(double) ) {
write_error = true ; throw sg_io_exception("sgWriteDouble: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -112,7 +127,8 @@ void sgWriteDouble ( gzFile fd, const double var )
void sgReadUInt ( gzFile fd, unsigned int *var ) void sgReadUInt ( gzFile fd, unsigned int *var )
{ {
if ( gzread ( fd, var, sizeof(unsigned int) ) != sizeof(unsigned int) ) { if ( gzread ( fd, var, sizeof(unsigned int) ) != sizeof(unsigned int) ) {
read_error = true ; throw sg_io_exception("sgReadUInt: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( (uint32_t *)var); sgEndianSwap( (uint32_t *)var);
@@ -128,7 +144,7 @@ void sgWriteUInt ( gzFile fd, const unsigned int var )
if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned int) ) if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned int) )
!= sizeof(unsigned int) ) != sizeof(unsigned int) )
{ {
write_error = true ; throw sg_io_exception("sgWriteUInt: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -136,7 +152,8 @@ void sgWriteUInt ( gzFile fd, const unsigned int var )
void sgReadInt ( gzFile fd, int *var ) void sgReadInt ( gzFile fd, int *var )
{ {
if ( gzread ( fd, var, sizeof(int) ) != sizeof(int) ) { if ( gzread ( fd, var, sizeof(int) ) != sizeof(int) ) {
read_error = true ; throw sg_io_exception("sgReadInt: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( (uint32_t *)var); sgEndianSwap( (uint32_t *)var);
@@ -150,7 +167,7 @@ void sgWriteInt ( gzFile fd, const int var )
sgEndianSwap( (uint32_t *)&var); sgEndianSwap( (uint32_t *)&var);
} }
if ( gzwrite ( fd, (void *)(&var), sizeof(int) ) != sizeof(int) ) { if ( gzwrite ( fd, (void *)(&var), sizeof(int) ) != sizeof(int) ) {
write_error = true ; throw sg_io_exception("sgWriteInt: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -158,7 +175,8 @@ void sgWriteInt ( gzFile fd, const int var )
void sgReadLong ( gzFile fd, int32_t *var ) void sgReadLong ( gzFile fd, int32_t *var )
{ {
if ( gzread ( fd, var, sizeof(int32_t) ) != sizeof(int32_t) ) { if ( gzread ( fd, var, sizeof(int32_t) ) != sizeof(int32_t) ) {
read_error = true ; throw sg_io_exception("sgReadLong: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( (uint32_t *)var); sgEndianSwap( (uint32_t *)var);
@@ -174,7 +192,7 @@ void sgWriteLong ( gzFile fd, const int32_t var )
if ( gzwrite ( fd, (void *)(&var), sizeof(int32_t) ) if ( gzwrite ( fd, (void *)(&var), sizeof(int32_t) )
!= sizeof(int32_t) ) != sizeof(int32_t) )
{ {
write_error = true ; throw sg_io_exception("sgWriteLong: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -182,7 +200,8 @@ void sgWriteLong ( gzFile fd, const int32_t var )
void sgReadLongLong ( gzFile fd, int64_t *var ) void sgReadLongLong ( gzFile fd, int64_t *var )
{ {
if ( gzread ( fd, var, sizeof(int64_t) ) != sizeof(int64_t) ) { if ( gzread ( fd, var, sizeof(int64_t) ) != sizeof(int64_t) ) {
read_error = true ; throw sg_io_exception("sgReadLongLong: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( (uint64_t *)var); sgEndianSwap( (uint64_t *)var);
@@ -198,7 +217,7 @@ void sgWriteLongLong ( gzFile fd, const int64_t var )
if ( gzwrite ( fd, (void *)(&var), sizeof(int64_t) ) if ( gzwrite ( fd, (void *)(&var), sizeof(int64_t) )
!= sizeof(int64_t) ) != sizeof(int64_t) )
{ {
write_error = true ; throw sg_io_exception("sgWriteLongLong: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -206,7 +225,8 @@ void sgWriteLongLong ( gzFile fd, const int64_t var )
void sgReadUShort ( gzFile fd, unsigned short *var ) void sgReadUShort ( gzFile fd, unsigned short *var )
{ {
if ( gzread ( fd, var, sizeof(unsigned short) ) != sizeof(unsigned short) ){ if ( gzread ( fd, var, sizeof(unsigned short) ) != sizeof(unsigned short) ){
read_error = true ; throw sg_io_exception("sgReadUShort: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( (uint16_t *)var); sgEndianSwap( (uint16_t *)var);
@@ -222,7 +242,7 @@ void sgWriteUShort ( gzFile fd, const unsigned short var )
if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned short) ) if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned short) )
!= sizeof(unsigned short) ) != sizeof(unsigned short) )
{ {
write_error = true ; throw sg_io_exception("sgWriteUShort: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -230,7 +250,8 @@ void sgWriteUShort ( gzFile fd, const unsigned short var )
void sgReadShort ( gzFile fd, short *var ) void sgReadShort ( gzFile fd, short *var )
{ {
if ( gzread ( fd, var, sizeof(short) ) != sizeof(short) ) { if ( gzread ( fd, var, sizeof(short) ) != sizeof(short) ) {
read_error = true ; throw sg_io_exception("sgReadShort: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
sgEndianSwap( (uint16_t *)var); sgEndianSwap( (uint16_t *)var);
@@ -244,7 +265,7 @@ void sgWriteShort ( gzFile fd, const short var )
sgEndianSwap( (uint16_t *)&var); sgEndianSwap( (uint16_t *)&var);
} }
if ( gzwrite ( fd, (void *)(&var), sizeof(short) ) != sizeof(short) ) { if ( gzwrite ( fd, (void *)(&var), sizeof(short) ) != sizeof(short) ) {
write_error = true ; throw sg_io_exception("sgWriteShort: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -252,7 +273,8 @@ void sgWriteShort ( gzFile fd, const short var )
void sgReadFloat ( gzFile fd, const unsigned int n, float *var ) void sgReadFloat ( gzFile fd, const unsigned int n, float *var )
{ {
if ( gzread ( fd, var, sizeof(float) * n ) != (int)(sizeof(float) * n) ) { if ( gzread ( fd, var, sizeof(float) * n ) != (int)(sizeof(float) * n) ) {
read_error = true ; throw sg_io_exception("sgReadFloat array: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
for ( unsigned int i = 0; i < n; ++i ) { for ( unsigned int i = 0; i < n; ++i ) {
@@ -276,14 +298,15 @@ void sgWriteFloat ( gzFile fd, const unsigned int n, const float *var )
if ( gzwrite ( fd, (void *)var, sizeof(float) * n ) if ( gzwrite ( fd, (void *)var, sizeof(float) * n )
!= (int)(sizeof(float) * n) ) != (int)(sizeof(float) * n) )
{ {
write_error = true ; throw sg_io_exception("sgWriteFloat array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
void sgReadDouble ( gzFile fd, const unsigned int n, double *var ) void sgReadDouble ( gzFile fd, const unsigned int n, double *var )
{ {
if ( gzread ( fd, var, sizeof(double) * n ) != (int)(sizeof(double) * n) ) { if ( gzread ( fd, var, sizeof(double) * n ) != (int)(sizeof(double) * n) ) {
read_error = true ; throw sg_io_exception("sgReadDouble array: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
for ( unsigned int i = 0; i < n; ++i ) { for ( unsigned int i = 0; i < n; ++i ) {
@@ -307,7 +330,7 @@ void sgWriteDouble ( gzFile fd, const unsigned int n, const double *var )
if ( gzwrite ( fd, (void *)var, sizeof(double) * n ) if ( gzwrite ( fd, (void *)var, sizeof(double) * n )
!= (int)(sizeof(double) * n) ) != (int)(sizeof(double) * n) )
{ {
write_error = true ; throw sg_io_exception("sgWriteDouble array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -315,7 +338,8 @@ void sgReadBytes ( gzFile fd, const unsigned int n, void *var )
{ {
if ( n == 0) return; if ( n == 0) return;
if ( gzread ( fd, var, n ) != (int)n ) { if ( gzread ( fd, var, n ) != (int)n ) {
read_error = true ; throw sg_io_exception("sgReadBytes: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
} }
@@ -323,7 +347,7 @@ void sgWriteBytes ( gzFile fd, const unsigned int n, const void *var )
{ {
if ( n == 0) return; if ( n == 0) return;
if ( gzwrite ( fd, (void *)var, n ) != (int)n ) { if ( gzwrite ( fd, (void *)var, n ) != (int)n ) {
write_error = true ; throw sg_io_exception("sgWriteBytes: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -333,7 +357,8 @@ void sgReadUShort ( gzFile fd, const unsigned int n, unsigned short *var )
if ( gzread ( fd, var, sizeof(unsigned short) * n ) if ( gzread ( fd, var, sizeof(unsigned short) * n )
!= (int)(sizeof(unsigned short) * n) ) != (int)(sizeof(unsigned short) * n) )
{ {
read_error = true ; throw sg_io_exception("sgReadUShort array: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
for ( unsigned int i = 0; i < n; ++i ) { for ( unsigned int i = 0; i < n; ++i ) {
@@ -357,7 +382,7 @@ void sgWriteUShort ( gzFile fd, const unsigned int n, const unsigned short *var
if ( gzwrite ( fd, (void *)var, sizeof(unsigned short) * n ) if ( gzwrite ( fd, (void *)var, sizeof(unsigned short) * n )
!= (int)(sizeof(unsigned short) * n) ) != (int)(sizeof(unsigned short) * n) )
{ {
write_error = true ; throw sg_io_exception("sgWriteUShort array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -368,7 +393,8 @@ void sgReadShort ( gzFile fd, const unsigned int n, short *var )
if ( gzread ( fd, var, sizeof(short) * n ) if ( gzread ( fd, var, sizeof(short) * n )
!= (int)(sizeof(short) * n) ) != (int)(sizeof(short) * n) )
{ {
read_error = true ; throw sg_io_exception("sgReadShort array: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
for ( unsigned int i = 0; i < n; ++i ) { for ( unsigned int i = 0; i < n; ++i ) {
@@ -392,7 +418,7 @@ void sgWriteShort ( gzFile fd, const unsigned int n, const short *var )
if ( gzwrite ( fd, (void *)var, sizeof(short) * n ) if ( gzwrite ( fd, (void *)var, sizeof(short) * n )
!= (int)(sizeof(short) * n) ) != (int)(sizeof(short) * n) )
{ {
write_error = true ; throw sg_io_exception("sgWriteShort array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -402,7 +428,8 @@ void sgReadUInt ( gzFile fd, const unsigned int n, unsigned int *var )
if ( gzread ( fd, var, sizeof(unsigned int) * n ) if ( gzread ( fd, var, sizeof(unsigned int) * n )
!= (int)(sizeof(unsigned int) * n) ) != (int)(sizeof(unsigned int) * n) )
{ {
read_error = true ; throw sg_io_exception("sgReadUInt array: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
for ( unsigned int i = 0; i < n; ++i ) { for ( unsigned int i = 0; i < n; ++i ) {
@@ -426,7 +453,7 @@ void sgWriteUInt ( gzFile fd, const unsigned int n, const unsigned int *var )
if ( gzwrite ( fd, (void *)var, sizeof(unsigned int) * n ) if ( gzwrite ( fd, (void *)var, sizeof(unsigned int) * n )
!= (int)(sizeof(unsigned int) * n) ) != (int)(sizeof(unsigned int) * n) )
{ {
write_error = true ; throw sg_io_exception("sgWriteUInt array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
@@ -437,7 +464,8 @@ void sgReadInt ( gzFile fd, const unsigned int n, int *var )
if ( gzread ( fd, var, sizeof(int) * n ) if ( gzread ( fd, var, sizeof(int) * n )
!= (int)(sizeof(int) * n) ) != (int)(sizeof(int) * n) )
{ {
read_error = true ; throw sg_io_exception("sgReadInt array: GZRead failed:" + gzErrorMessage(fd),
sg_location{thread_gzPath}, nullptr, false);
} }
if ( sgIsBigEndian() ) { if ( sgIsBigEndian() ) {
for ( unsigned int i = 0; i < n; ++i ) { for ( unsigned int i = 0; i < n; ++i ) {
@@ -461,7 +489,7 @@ void sgWriteInt ( gzFile fd, const unsigned int n, const int *var )
if ( gzwrite ( fd, (void *)var, sizeof(int) * n ) if ( gzwrite ( fd, (void *)var, sizeof(int) * n )
!= (int)(sizeof(int) * n) ) != (int)(sizeof(int) * n) )
{ {
write_error = true ; throw sg_io_exception("sgWriteInt array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
} }
} }
+8 -4
View File
@@ -35,6 +35,9 @@
#include <simgear/math/SGMath.hxx> #include <simgear/math/SGMath.hxx>
// forward decls
class SGPath;
// Note that output is written in little endian form (and converted as // Note that output is written in little endian form (and converted as
// necessary for big endian machines) // necessary for big endian machines)
@@ -121,9 +124,10 @@ inline void sgWriteGeod ( gzFile fd, const SGGeod& var ) {
sgWriteDouble( fd, var.getElevationM() ); sgWriteDouble( fd, var.getElevationM() );
} }
void sgClearReadError(); /**
void sgClearWriteError(); @ error aid: allow calling code to specify which file path we're reading from, so that erros we
int sgReadError(); throw from sgReadXXXX can have a valid location set.
int sgWriteError(); */
void setThreadLocalSimgearReadPath(const SGPath& path);
#endif // _SG_LOWLEVEL_HXX #endif // _SG_LOWLEVEL_HXX
+347 -355
View File
@@ -42,6 +42,7 @@
#include <simgear/bucket/newbucket.hxx> #include <simgear/bucket/newbucket.hxx>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/math/SGGeometry.hxx> #include <simgear/math/SGGeometry.hxx>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
@@ -453,10 +454,6 @@ void SGBinObject::read_object( gzFile fp,
} }
} }
if ( sgReadError() ) {
throw sg_exception("Error reading object properties");
}
size_t indexCount = std::bitset<32>((int)idx_mask).count(); size_t indexCount = std::bitset<32>((int)idx_mask).count();
if (indexCount == 0) { if (indexCount == 0) {
throw sg_exception("object index mask has no bits set"); throw sg_exception("object index mask has no bits set");
@@ -464,18 +461,10 @@ void SGBinObject::read_object( gzFile fp,
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
if ( sgReadError() ) {
throw sg_exception("Error reading element size");
}
buf.resize( nbytes ); buf.resize( nbytes );
char *ptr = buf.get_ptr(); char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
if ( sgReadError() ) {
throw sg_exception("Error reading element bytes");
}
int_list vs; int_list vs;
int_list ns; int_list ns;
int_list cs; int_list cs;
@@ -502,306 +491,309 @@ void SGBinObject::read_object( gzFile fp,
// read a binary file and populate the provided structures. // read a binary file and populate the provided structures.
bool SGBinObject::read_bin( const SGPath& file ) { bool SGBinObject::read_bin( const SGPath& file )
SGVec3d p; {
int i, k; gzFile fp = NULL;
size_t j;
unsigned int nbytes; try {
sgSimpleBuffer buf( 32768 ); // 32 Kb SGVec3d p;
int i, k;
size_t j;
unsigned int nbytes;
sgSimpleBuffer buf( 32768 ); // 32 Kb
// zero out structures // zero out structures
gbs_center = SGVec3d(0, 0, 0); gbs_center = SGVec3d(0, 0, 0);
gbs_radius = 0.0; gbs_radius = 0.0;
wgs84_nodes.clear(); wgs84_nodes.clear();
normals.clear(); normals.clear();
texcoords.clear(); texcoords.clear();
pts_v.clear(); pts_v.clear();
pts_n.clear(); pts_n.clear();
pts_c.clear(); pts_c.clear();
pts_tcs.clear(); pts_tcs.clear();
pts_vas.clear(); pts_vas.clear();
pt_materials.clear(); pt_materials.clear();
tris_v.clear(); tris_v.clear();
tris_n.clear(); tris_n.clear();
tris_c.clear(); tris_c.clear();
tris_tcs.clear(); tris_tcs.clear();
tris_vas.clear(); tris_vas.clear();
tri_materials.clear(); tri_materials.clear();
strips_v.clear(); strips_v.clear();
strips_n.clear(); strips_n.clear();
strips_c.clear(); strips_c.clear();
strips_tcs.clear(); strips_tcs.clear();
strips_vas.clear(); strips_vas.clear();
strip_materials.clear(); strip_materials.clear();
fans_v.clear(); fans_v.clear();
fans_n.clear(); fans_n.clear();
fans_c.clear(); fans_c.clear();
fans_tcs.clear(); fans_tcs.clear();
fans_vas.clear(); fans_vas.clear();
fan_materials.clear(); fan_materials.clear();
gzFile fp = gzFileFromSGPath(file, "rb"); gzFile fp = gzFileFromSGPath(file, "rb");
if ( fp == NULL ) { if ( fp == NULL ) {
SGPath withGZ = file; SGPath withGZ = file;
withGZ.concat(".gz"); withGZ.concat(".gz");
fp = gzFileFromSGPath(withGZ, "rb"); fp = gzFileFromSGPath(withGZ, "rb");
if (fp == nullptr) { if (fp == nullptr) {
SG_LOG( SG_EVENT, SG_ALERT, throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file), {}, false);
"ERROR: opening " << file << " or " << withGZ << " for reading!"); }
throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file));
} }
}
setThreadLocalSimgearReadPath(file);
sgClearReadError(); // read headers
unsigned int header;
sgReadUInt( fp, &header );
// read headers
unsigned int header;
sgReadUInt( fp, &header );
if ( ((header & 0xFF000000) >> 24) == 'S' &&
((header & 0x00FF0000) >> 16) == 'G' ) {
// read file version if ( ((header & 0xFF000000) >> 24) == 'S' &&
version = (header & 0x0000FFFF); ((header & 0x00FF0000) >> 16) == 'G' ) {
} else {
// close the file before we return
gzclose(fp);
throw sg_io_exception("Bad BTG magic/version", sg_location(file));
}
// read creation time // read file version
unsigned int foo_calendar_time; version = (header & 0x0000FFFF);
sgReadUInt( fp, &foo_calendar_time ); } else {
throw sg_io_exception("Bad BTG magic/version", sg_location(file), {}, false);
}
#if 0 // read creation time
time_t calendar_time = foo_calendar_time; unsigned int foo_calendar_time;
// The following code has a global effect on the host application sgReadUInt( fp, &foo_calendar_time );
// and can screws up the time elsewhere. It should be avoided
// unless you need this for debugging in which case you should
// disable it again once the debugging task is finished.
struct tm *local_tm;
local_tm = localtime( &calendar_time );
char time_str[256];
strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
SG_LOG( SG_EVENT, SG_DEBUG, "File created on " << time_str);
#endif
// read number of top level objects #if 0
int nobjects; time_t calendar_time = foo_calendar_time;
if ( version >= 10) { // version 10 extends everything to be 32-bit // The following code has a global effect on the host application
sgReadInt( fp, &nobjects ); // and can screws up the time elsewhere. It should be avoided
} else if ( version >= 7 ) { // unless you need this for debugging in which case you should
uint16_t v; // disable it again once the debugging task is finished.
sgReadUShort( fp, &v ); struct tm *local_tm;
nobjects = v; local_tm = localtime( &calendar_time );
} else { char time_str[256];
int16_t v; strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
sgReadShort( fp, &v ); SG_LOG( SG_EVENT, SG_DEBUG, "File created on " << time_str);
nobjects = v; #endif
}
SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin Total objects to read = " << nobjects); // read number of top level objects
int nobjects;
if ( sgReadError() ) { if ( version >= 10) { // version 10 extends everything to be 32-bit
throw sg_io_exception("Error reading BTG file header", sg_location(file)); sgReadInt( fp, &nobjects );
}
// read in objects
for ( i = 0; i < nobjects; ++i ) {
// read object header
char obj_type;
uint32_t nproperties, nelements;
sgReadChar( fp, &obj_type );
if ( version >= 10 ) {
sgReadUInt( fp, &nproperties );
sgReadUInt( fp, &nelements );
} else if ( version >= 7 ) { } else if ( version >= 7 ) {
uint16_t v; uint16_t v;
sgReadUShort( fp, &v ); sgReadUShort( fp, &v );
nproperties = v; nobjects = v;
sgReadUShort( fp, &v );
nelements = v;
} else { } else {
int16_t v; int16_t v;
sgReadShort( fp, &v ); sgReadShort( fp, &v );
nproperties = v; nobjects = v;
sgReadShort( fp, &v );
nelements = v;
} }
SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin object " << i << SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin Total objects to read = " << nobjects);
" = " << (int)obj_type << " props = " << nproperties <<
" elements = " << nelements);
if ( obj_type == SG_BOUNDING_SPHERE ) {
// read bounding sphere properties
read_properties( fp, nproperties );
// read bounding sphere elements // read in objects
for ( j = 0; j < nelements; ++j ) { for ( i = 0; i < nobjects; ++i ) {
sgReadUInt( fp, &nbytes ); // read object header
buf.resize( nbytes ); char obj_type;
buf.reset(); uint32_t nproperties, nelements;
char *ptr = buf.get_ptr(); sgReadChar( fp, &obj_type );
sgReadBytes( fp, nbytes, ptr ); if ( version >= 10 ) {
gbs_center = buf.readVec3d(); sgReadUInt( fp, &nproperties );
gbs_radius = buf.readFloat(); sgReadUInt( fp, &nelements );
} else if ( version >= 7 ) {
uint16_t v;
sgReadUShort( fp, &v );
nproperties = v;
sgReadUShort( fp, &v );
nelements = v;
} else {
int16_t v;
sgReadShort( fp, &v );
nproperties = v;
sgReadShort( fp, &v );
nelements = v;
} }
} else if ( obj_type == SG_VERTEX_LIST ) {
// read vertex list properties
read_properties( fp, nproperties );
// read vertex list elements SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin object " << i <<
for ( j = 0; j < nelements; ++j ) { " = " << (int)obj_type << " props = " << nproperties <<
sgReadUInt( fp, &nbytes ); " elements = " << nelements);
buf.resize( nbytes );
buf.reset(); if ( obj_type == SG_BOUNDING_SPHERE ) {
char *ptr = buf.get_ptr(); // read bounding sphere properties
sgReadBytes( fp, nbytes, ptr ); read_properties( fp, nproperties );
int count = nbytes / (sizeof(float) * 3);
wgs84_nodes.reserve( count ); // read bounding sphere elements
for ( k = 0; k < count; ++k ) { for ( j = 0; j < nelements; ++j ) {
SGVec3f v = buf.readVec3f(); sgReadUInt( fp, &nbytes );
// extend from float to double, hmmm buf.resize( nbytes );
wgs84_nodes.push_back( SGVec3d(v[0], v[1], v[2]) ); buf.reset();
char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr );
gbs_center = buf.readVec3d();
gbs_radius = buf.readFloat();
} }
} } else if ( obj_type == SG_VERTEX_LIST ) {
} else if ( obj_type == SG_COLOR_LIST ) { // read vertex list properties
// read color list properties read_properties( fp, nproperties );
read_properties( fp, nproperties );
// read color list elements // read vertex list elements
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
buf.resize( nbytes ); buf.resize( nbytes );
buf.reset(); buf.reset();
char *ptr = buf.get_ptr(); char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
int count = nbytes / (sizeof(float) * 4); int count = nbytes / (sizeof(float) * 3);
colors.reserve(count); wgs84_nodes.reserve( count );
for ( k = 0; k < count; ++k ) { for ( k = 0; k < count; ++k ) {
colors.push_back( buf.readVec4f() ); SGVec3f v = buf.readVec3f();
// extend from float to double, hmmm
wgs84_nodes.push_back( SGVec3d(v[0], v[1], v[2]) );
}
} }
} } else if ( obj_type == SG_COLOR_LIST ) {
} else if ( obj_type == SG_NORMAL_LIST ) { // read color list properties
// read normal list properties read_properties( fp, nproperties );
read_properties( fp, nproperties );
// read normal list elements // read color list elements
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
buf.resize( nbytes ); buf.resize( nbytes );
buf.reset(); buf.reset();
unsigned char *ptr = (unsigned char *)(buf.get_ptr()); char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
int count = nbytes / 3; int count = nbytes / (sizeof(float) * 4);
normals.reserve( count ); colors.reserve(count);
for ( k = 0; k < count; ++k ) {
for ( k = 0; k < count; ++k ) { colors.push_back( buf.readVec4f() );
SGVec3f normal( (ptr[0]) / 127.5 - 1.0, }
(ptr[1]) / 127.5 - 1.0,
(ptr[2]) / 127.5 - 1.0);
normals.push_back(normalize(normal));
ptr += 3;
} }
} } else if ( obj_type == SG_NORMAL_LIST ) {
} else if ( obj_type == SG_TEXCOORD_LIST ) { // read normal list properties
// read texcoord list properties read_properties( fp, nproperties );
read_properties( fp, nproperties );
// read texcoord list elements // read normal list elements
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
buf.resize( nbytes ); buf.resize( nbytes );
buf.reset(); buf.reset();
char *ptr = buf.get_ptr(); unsigned char *ptr = (unsigned char *)(buf.get_ptr());
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
int count = nbytes / (sizeof(float) * 2); int count = nbytes / 3;
texcoords.reserve(count); normals.reserve( count );
for ( k = 0; k < count; ++k ) {
texcoords.push_back( buf.readVec2f() ); for ( k = 0; k < count; ++k ) {
SGVec3f normal( (ptr[0]) / 127.5 - 1.0,
(ptr[1]) / 127.5 - 1.0,
(ptr[2]) / 127.5 - 1.0);
normals.push_back(normalize(normal));
ptr += 3;
}
} }
} } else if ( obj_type == SG_TEXCOORD_LIST ) {
} else if ( obj_type == SG_VA_FLOAT_LIST ) { // read texcoord list properties
// read vertex attribute (float) properties read_properties( fp, nproperties );
read_properties( fp, nproperties );
// read vertex attribute list elements // read texcoord list elements
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
buf.resize( nbytes ); buf.resize( nbytes );
buf.reset(); buf.reset();
char *ptr = buf.get_ptr(); char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
int count = nbytes / (sizeof(float)); int count = nbytes / (sizeof(float) * 2);
va_flt.reserve(count); texcoords.reserve(count);
for ( k = 0; k < count; ++k ) { for ( k = 0; k < count; ++k ) {
va_flt.push_back( buf.readFloat() ); texcoords.push_back( buf.readVec2f() );
}
} }
} } else if ( obj_type == SG_VA_FLOAT_LIST ) {
} else if ( obj_type == SG_VA_INTEGER_LIST ) { // read vertex attribute (float) properties
// read vertex attribute (integer) properties read_properties( fp, nproperties );
read_properties( fp, nproperties );
// read vertex attribute list elements // read vertex attribute list elements
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
buf.resize( nbytes ); buf.resize( nbytes );
buf.reset(); buf.reset();
char *ptr = buf.get_ptr(); char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
int count = nbytes / (sizeof(unsigned int)); int count = nbytes / (sizeof(float));
va_int.reserve(count); va_flt.reserve(count);
for ( k = 0; k < count; ++k ) { for ( k = 0; k < count; ++k ) {
va_int.push_back( buf.readInt() ); va_flt.push_back( buf.readFloat() );
}
} }
} } else if ( obj_type == SG_VA_INTEGER_LIST ) {
} else if ( obj_type == SG_POINTS ) { // read vertex attribute (integer) properties
// read point elements read_properties( fp, nproperties );
read_object( fp, SG_POINTS, nproperties, nelements,
pts_v, pts_n, pts_c, pts_tcs,
pts_vas, pt_materials );
} else if ( obj_type == SG_TRIANGLE_FACES ) {
// read triangle face properties
read_object( fp, SG_TRIANGLE_FACES, nproperties, nelements,
tris_v, tris_n, tris_c, tris_tcs,
tris_vas, tri_materials );
} else if ( obj_type == SG_TRIANGLE_STRIPS ) {
// read triangle strip properties
read_object( fp, SG_TRIANGLE_STRIPS, nproperties, nelements,
strips_v, strips_n, strips_c, strips_tcs,
strips_vas, strip_materials );
} else if ( obj_type == SG_TRIANGLE_FANS ) {
// read triangle fan properties
read_object( fp, SG_TRIANGLE_FANS, nproperties, nelements,
fans_v, fans_n, fans_c, fans_tcs,
fans_vas, fan_materials );
} else {
// unknown object type, just skip
read_properties( fp, nproperties );
// read elements // read vertex attribute list elements
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
// cout << "element size = " << nbytes << endl; buf.resize( nbytes );
if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); } buf.reset();
char *ptr = buf.get_ptr(); char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
int count = nbytes / (sizeof(unsigned int));
va_int.reserve(count);
for ( k = 0; k < count; ++k ) {
va_int.push_back( buf.readInt() );
}
}
} else if ( obj_type == SG_POINTS ) {
// read point elements
read_object( fp, SG_POINTS, nproperties, nelements,
pts_v, pts_n, pts_c, pts_tcs,
pts_vas, pt_materials );
} else if ( obj_type == SG_TRIANGLE_FACES ) {
// read triangle face properties
read_object( fp, SG_TRIANGLE_FACES, nproperties, nelements,
tris_v, tris_n, tris_c, tris_tcs,
tris_vas, tri_materials );
} else if ( obj_type == SG_TRIANGLE_STRIPS ) {
// read triangle strip properties
read_object( fp, SG_TRIANGLE_STRIPS, nproperties, nelements,
strips_v, strips_n, strips_c, strips_tcs,
strips_vas, strip_materials );
} else if ( obj_type == SG_TRIANGLE_FANS ) {
// read triangle fan properties
read_object( fp, SG_TRIANGLE_FANS, nproperties, nelements,
fans_v, fans_n, fans_c, fans_tcs,
fans_vas, fan_materials );
} else {
// unknown object type, just skip
read_properties( fp, nproperties );
// read elements
for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes );
// cout << "element size = " << nbytes << endl;
if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
char *ptr = buf.get_ptr();
sgReadBytes( fp, nbytes, ptr );
}
} }
} }
if ( sgReadError() ) { gzclose(fp);
throw sg_io_exception("Error while reading object", sg_location(file, i)); fp = NULL;
} catch (std::exception&) {
if (fp) {
// close the file
gzclose(fp);
} }
throw; // re-throw
} }
// close the file
gzclose(fp);
return true; return true;
} }
@@ -975,112 +967,112 @@ bool SGBinObject::write_bin_file(const SGPath& file)
return false; return false;
} }
sgClearWriteError(); try {
SG_LOG(SG_IO, SG_DEBUG, "points size = " << pts_v.size()
<< " pt_materials = " << pt_materials.size() );
SG_LOG(SG_IO, SG_DEBUG, "triangles size = " << tris_v.size()
<< " tri_materials = " << tri_materials.size() );
SG_LOG(SG_IO, SG_DEBUG, "strips size = " << strips_v.size()
<< " strip_materials = " << strip_materials.size() );
SG_LOG(SG_IO, SG_DEBUG, "fans size = " << fans_v.size()
<< " fan_materials = " << fan_materials.size() );
SG_LOG(SG_IO, SG_DEBUG, "points size = " << pts_v.size() SG_LOG(SG_IO, SG_DEBUG, "nodes = " << wgs84_nodes.size() );
<< " pt_materials = " << pt_materials.size() ); SG_LOG(SG_IO, SG_DEBUG, "colors = " << colors.size() );
SG_LOG(SG_IO, SG_DEBUG, "triangles size = " << tris_v.size() SG_LOG(SG_IO, SG_DEBUG, "normals = " << normals.size() );
<< " tri_materials = " << tri_materials.size() ); SG_LOG(SG_IO, SG_DEBUG, "tex coords = " << texcoords.size() );
SG_LOG(SG_IO, SG_DEBUG, "strips size = " << strips_v.size()
<< " strip_materials = " << strip_materials.size() );
SG_LOG(SG_IO, SG_DEBUG, "fans size = " << fans_v.size()
<< " fan_materials = " << fan_materials.size() );
SG_LOG(SG_IO, SG_DEBUG, "nodes = " << wgs84_nodes.size() ); version = 10;
SG_LOG(SG_IO, SG_DEBUG, "colors = " << colors.size() ); bool shortMaterialsRanges =
SG_LOG(SG_IO, SG_DEBUG, "normals = " << normals.size() ); (max_object_size(pt_materials) < VERSION_7_MATERIAL_LIMIT) &&
SG_LOG(SG_IO, SG_DEBUG, "tex coords = " << texcoords.size() ); (max_object_size(fan_materials) < VERSION_7_MATERIAL_LIMIT) &&
(max_object_size(strip_materials) < VERSION_7_MATERIAL_LIMIT) &&
(max_object_size(tri_materials) < VERSION_7_MATERIAL_LIMIT);
version = 10; if ((wgs84_nodes.size() < 0xffff) &&
bool shortMaterialsRanges = (normals.size() < 0xffff) &&
(max_object_size(pt_materials) < VERSION_7_MATERIAL_LIMIT) && (texcoords.size() < 0xffff) &&
(max_object_size(fan_materials) < VERSION_7_MATERIAL_LIMIT) && shortMaterialsRanges) {
(max_object_size(strip_materials) < VERSION_7_MATERIAL_LIMIT) && version = 7; // use smaller indices if possible
(max_object_size(tri_materials) < VERSION_7_MATERIAL_LIMIT); }
if ((wgs84_nodes.size() < 0xffff) && // write header magic
(normals.size() < 0xffff) &&
(texcoords.size() < 0xffff) &&
shortMaterialsRanges) {
version = 7; // use smaller indices if possible
}
// write header magic /** Magic Number for our file format */
#define SG_FILE_MAGIC_NUMBER ( ('S'<<24) + ('G'<<16) + version )
/** Magic Number for our file format */ sgWriteUInt( fp, SG_FILE_MAGIC_NUMBER );
#define SG_FILE_MAGIC_NUMBER ( ('S'<<24) + ('G'<<16) + version ) time_t calendar_time = time(NULL);
sgWriteLong( fp, (int32_t)calendar_time );
sgWriteUInt( fp, SG_FILE_MAGIC_NUMBER ); // calculate and write number of top level objects
time_t calendar_time = time(NULL); int nobjects = 5; // gbs, vertices, colors, normals, texcoords
sgWriteLong( fp, (int32_t)calendar_time ); nobjects += count_objects(pt_materials);
nobjects += count_objects(tri_materials);
nobjects += count_objects(strip_materials);
nobjects += count_objects(fan_materials);
// calculate and write number of top level objects SG_LOG(SG_IO, SG_DEBUG, "total top level objects = " << nobjects);
int nobjects = 5; // gbs, vertices, colors, normals, texcoords
nobjects += count_objects(pt_materials);
nobjects += count_objects(tri_materials);
nobjects += count_objects(strip_materials);
nobjects += count_objects(fan_materials);
SG_LOG(SG_IO, SG_DEBUG, "total top level objects = " << nobjects); if (version == 7) {
sgWriteUShort( fp, (uint16_t) nobjects );
} else {
sgWriteInt( fp, nobjects );
}
if (version == 7) { // write bounding sphere
sgWriteUShort( fp, (uint16_t) nobjects ); write_header( fp, SG_BOUNDING_SPHERE, 0, 1);
} else { sgWriteUInt( fp, sizeof(double) * 3 + sizeof(float) ); // nbytes
sgWriteInt( fp, nobjects ); sgWritedVec3( fp, gbs_center );
} sgWriteFloat( fp, gbs_radius );
// write bounding sphere // dump vertex list
write_header( fp, SG_BOUNDING_SPHERE, 0, 1); write_header( fp, SG_VERTEX_LIST, 0, 1);
sgWriteUInt( fp, sizeof(double) * 3 + sizeof(float) ); // nbytes sgWriteUInt( fp, wgs84_nodes.size() * sizeof(float) * 3 ); // nbytes
sgWritedVec3( fp, gbs_center ); for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) {
sgWriteFloat( fp, gbs_radius ); sgWriteVec3( fp, toVec3f(wgs84_nodes[i] - gbs_center));
}
// dump vertex list // dump vertex color list
write_header( fp, SG_VERTEX_LIST, 0, 1); write_header( fp, SG_COLOR_LIST, 0, 1);
sgWriteUInt( fp, wgs84_nodes.size() * sizeof(float) * 3 ); // nbytes sgWriteUInt( fp, colors.size() * sizeof(float) * 4 ); // nbytes
for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) { for ( i = 0; i < (int)colors.size(); ++i ) {
sgWriteVec3( fp, toVec3f(wgs84_nodes[i] - gbs_center)); sgWriteVec4( fp, colors[i]);
} }
// dump vertex color list // dump vertex normal list
write_header( fp, SG_COLOR_LIST, 0, 1); write_header( fp, SG_NORMAL_LIST, 0, 1);
sgWriteUInt( fp, colors.size() * sizeof(float) * 4 ); // nbytes sgWriteUInt( fp, normals.size() * 3 ); // nbytes
for ( i = 0; i < (int)colors.size(); ++i ) { char normal[3];
sgWriteVec4( fp, colors[i]); for ( i = 0; i < (int)normals.size(); ++i ) {
} SGVec3f p = normals[i];
normal[0] = (unsigned char)((p.x() + 1.0) * 127.5);
normal[1] = (unsigned char)((p.y() + 1.0) * 127.5);
normal[2] = (unsigned char)((p.z() + 1.0) * 127.5);
sgWriteBytes( fp, 3, normal );
}
// dump vertex normal list // dump texture coordinates
write_header( fp, SG_NORMAL_LIST, 0, 1); write_header( fp, SG_TEXCOORD_LIST, 0, 1);
sgWriteUInt( fp, normals.size() * 3 ); // nbytes sgWriteUInt( fp, texcoords.size() * sizeof(float) * 2 ); // nbytes
char normal[3]; for ( i = 0; i < (int)texcoords.size(); ++i ) {
for ( i = 0; i < (int)normals.size(); ++i ) { sgWriteVec2( fp, texcoords[i]);
SGVec3f p = normals[i]; }
normal[0] = (unsigned char)((p.x() + 1.0) * 127.5);
normal[1] = (unsigned char)((p.y() + 1.0) * 127.5);
normal[2] = (unsigned char)((p.z() + 1.0) * 127.5);
sgWriteBytes( fp, 3, normal );
}
// dump texture coordinates write_objects(fp, SG_POINTS, pts_v, pts_n, pts_c, pts_tcs, pts_vas, pt_materials);
write_header( fp, SG_TEXCOORD_LIST, 0, 1); write_objects(fp, SG_TRIANGLE_FACES, tris_v, tris_n, tris_c, tris_tcs, tris_vas, tri_materials);
sgWriteUInt( fp, texcoords.size() * sizeof(float) * 2 ); // nbytes write_objects(fp, SG_TRIANGLE_STRIPS, strips_v, strips_n, strips_c, strips_tcs, strips_vas, strip_materials);
for ( i = 0; i < (int)texcoords.size(); ++i ) { write_objects(fp, SG_TRIANGLE_FANS, fans_v, fans_n, fans_c, fans_tcs, fans_vas, fan_materials);
sgWriteVec2( fp, texcoords[i]);
}
write_objects(fp, SG_POINTS, pts_v, pts_n, pts_c, pts_tcs, pts_vas, pt_materials); // close the file
write_objects(fp, SG_TRIANGLE_FACES, tris_v, tris_n, tris_c, tris_tcs, tris_vas, tri_materials); gzclose(fp);
write_objects(fp, SG_TRIANGLE_STRIPS, strips_v, strips_n, strips_c, strips_tcs, strips_vas, strip_materials); fp = NULL;
write_objects(fp, SG_TRIANGLE_FANS, fans_v, fans_n, fans_c, fans_tcs, fans_vas, fan_materials); } catch (std::exception&) {
if (fp) {
// close the file gzclose(fp);
gzclose(fp); }
if ( sgWriteError() ) {
cout << "Error while writing file " << file << endl;
return false; return false;
} }
return true; return true;
} }
+18 -6
View File
@@ -71,21 +71,33 @@ SGFile::~SGFile() {
std::string SGFile::computeHash() std::string SGFile::computeHash()
{ {
if (!file_name.exists()) if (!file_name.exists())
return std::string(); return {};
simgear::sha1nfo info; simgear::sha1nfo info;
sha1_init(&info); sha1_init(&info);
char* buf = static_cast<char*>(malloc(1024 * 1024));
// unique_ptr with custom deleter for exception safety
const int bufSize = 1024 * 1024;
std::unique_ptr<char, std::function<void(char*)>> buf{static_cast<char*>(malloc(bufSize)),
[](char* p) { free(p); }};
if (!buf) {
// @TODO report out of memory error
SG_LOG(SG_IO, SG_ALERT, "Failed to malloc buffer for SHA1 check:" << file_name);
return {};
}
size_t readLen; size_t readLen;
SGBinaryFile f(file_name); SGBinaryFile f(file_name);
if (!f.open(SG_IO_IN)) { if (!f.open(SG_IO_IN)) {
throw sg_io_exception("Couldn't open file for compute hash", file_name); SG_LOG(SG_IO, SG_ALERT, "SGFile::computeHash: Failed to open " << file_name);
return {};
} }
while ((readLen = f.read(buf, 1024 * 1024)) > 0) { while ((readLen = f.read(buf.get(), bufSize)) > 0) {
sha1_write(&info, buf, readLen); sha1_write(&info, buf.get(), readLen);
} }
f.close(); f.close();
free(buf);
std::string hashBytes((char*)sha1_result(&info), HASH_LENGTH); std::string hashBytes((char*)sha1_result(&info), HASH_LENGTH);
return simgear::strutils::encodeHex(hashBytes); return simgear::strutils::encodeHex(hashBytes);
} }
Binary file not shown.
+189 -16
View File
@@ -1,16 +1,18 @@
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <errno.h>
#include <fcntl.h>
#include <functional>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <sstream> #include <sstream>
#include <errno.h>
#include <fcntl.h>
#include <simgear/simgear_config.h> #include <simgear/simgear_config.h>
#include "test_HTTP.hxx"
#include "HTTPRepository.hxx"
#include "HTTPClient.hxx" #include "HTTPClient.hxx"
#include "HTTPRepository.hxx"
#include "HTTPTestApi_private.hxx"
#include "test_HTTP.hxx"
#include <simgear/misc/strutils.hxx> #include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_hash.hxx> #include <simgear/misc/sg_hash.hxx>
@@ -25,8 +27,14 @@
using namespace simgear; using namespace simgear;
using TestApi = simgear::HTTP::TestApi;
std::string dataForFile(const std::string& parentName, const std::string& name, int revision) std::string dataForFile(const std::string& parentName, const std::string& name, int revision)
{ {
if (name == "zeroByteFile") {
return {};
}
std::ostringstream os; std::ostringstream os;
// random content but which definitely depends on our tree location // random content but which definitely depends on our tree location
// and revision. // and revision.
@@ -45,6 +53,9 @@ std::string hashForData(const std::string& d)
return strutils::encodeHex(sha1_result(&info), HASH_LENGTH); return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
} }
class TestRepoEntry;
using AccessCallback = std::function<void(TestRepoEntry &entry)>;
class TestRepoEntry class TestRepoEntry
{ {
public: public:
@@ -70,7 +81,8 @@ public:
int requestCount; int requestCount;
bool getWillFail; bool getWillFail;
bool returnCorruptData; bool returnCorruptData;
std::unique_ptr<SGCallback> accessCallback;
AccessCallback accessCallback;
void clearRequestCounts(); void clearRequestCounts();
@@ -270,8 +282,8 @@ public:
return; return;
} }
if (entry->accessCallback.get()) { if (entry->accessCallback) {
(*entry->accessCallback)(); entry->accessCallback(*entry);
} }
if (entry->getWillFail) { if (entry->getWillFail) {
@@ -282,20 +294,29 @@ public:
entry->requestCount++; entry->requestCount++;
std::string content; std::string content;
bool closeSocket = false;
size_t contentSize = 0;
if (entry->returnCorruptData) { if (entry->returnCorruptData) {
content = dataForFile("!$£$!" + entry->parent->name, content = dataForFile("!$£$!" + entry->parent->name,
"corrupt_" + entry->name, "corrupt_" + entry->name,
entry->revision); entry->revision);
contentSize = content.size();
} else { } else {
content = entry->data(); content = entry->data();
contentSize = content.size();
} }
std::stringstream d; std::stringstream d;
d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n"; d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
d << "Content-Length:" << content.size() << "\r\n"; d << "Content-Length:" << contentSize << "\r\n";
d << "\r\n"; // final CRLF to terminate the headers d << "\r\n"; // final CRLF to terminate the headers
d << content; d << content;
push(d.str().c_str()); push(d.str().c_str());
if (closeSocket) {
closeWhenDone();
}
} else { } else {
sendErrorResponse(404, false, ""); sendErrorResponse(404, false, "");
} }
@@ -392,6 +413,7 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
cl->update(); cl->update();
testServer.poll(); testServer.poll();
repo->process();
if (!repo->isDoingSync()) { if (!repo->isDoingSync()) {
return; return;
} }
@@ -401,6 +423,16 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
std::cerr << "timed out" << std::endl; std::cerr << "timed out" << std::endl;
} }
void runForTime(HTTP::Client *cl, HTTPRepository *repo, int msec = 15) {
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < msec) {
cl->update();
testServer.poll();
repo->process();
SGTimeStamp::sleepForMSec(1);
}
}
void testBasicClone(HTTP::Client* cl) void testBasicClone(HTTP::Client* cl)
{ {
std::unique_ptr<HTTPRepository> repo; std::unique_ptr<HTTPRepository> repo;
@@ -418,6 +450,7 @@ void testBasicClone(HTTP::Client* cl)
verifyFileState(p, "fileA"); verifyFileState(p, "fileA");
verifyFileState(p, "dirA/subdirA/fileAAA"); verifyFileState(p, "dirA/subdirA/fileAAA");
verifyFileState(p, "dirC/subdirA/subsubA/fileCAAA"); verifyFileState(p, "dirC/subdirA/subsubA/fileCAAA");
verifyFileState(p, "dirA/subdirA/zeroByteFile");
global_repo->findEntry("fileA")->revision++; global_repo->findEntry("fileA")->revision++;
global_repo->findEntry("dirB/subdirA/fileBAA")->revision++; global_repo->findEntry("dirB/subdirA/fileBAA")->revision++;
@@ -618,9 +651,19 @@ void testAbandonCorruptFiles(HTTP::Client* cl)
repo->setBaseUrl("http://localhost:2000/repo"); repo->setBaseUrl("http://localhost:2000/repo");
repo->update(); repo->update();
waitForUpdateComplete(cl, repo.get()); waitForUpdateComplete(cl, repo.get());
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) { if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
std::cerr << "Got failure state:" << repo->failure() << std::endl; std::cerr << "Got failure state:" << repo->failure() << std::endl;
throw sg_exception("Bad result from corrupt files test"); throw sg_exception("Bad result from corrupt files test");
}
auto failedFiles = repo->failures();
if (failedFiles.size() != 1) {
throw sg_exception("Bad result from corrupt files test");
}
if (failedFiles.front().path.utf8Str() != "dirB/subdirG/fileBGA") {
throw sg_exception("Bad path from corrupt files test:" +
failedFiles.front().path.utf8Str());
} }
repo.reset(); repo.reset();
@@ -657,15 +700,21 @@ void testServerModifyDuringSync(HTTP::Client* cl)
repo.reset(new HTTPRepository(p, cl)); repo.reset(new HTTPRepository(p, cl));
repo->setBaseUrl("http://localhost:2000/repo"); repo->setBaseUrl("http://localhost:2000/repo");
global_repo->findEntry("dirA/fileAA")->accessCallback.reset(make_callback(&modifyBTree)); global_repo->findEntry("dirA/fileAA")->accessCallback =
[](const TestRepoEntry &r) {
std::cout << "Modifying sub-tree" << std::endl;
global_repo->findEntry("dirB/subdirA/fileBAC")->revision++;
global_repo->defineFile("dirB/subdirZ/fileBZA");
global_repo->findEntry("dirB/subdirB/fileBBB")->revision++;
};
repo->update(); repo->update();
waitForUpdateComplete(cl, repo.get()); waitForUpdateComplete(cl, repo.get());
global_repo->findEntry("dirA/fileAA")->accessCallback.reset(); global_repo->findEntry("dirA/fileAA")->accessCallback = AccessCallback{};
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) { if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
throw sg_exception("Bad result from modify during sync test"); throw sg_exception("Bad result from modify during sync test");
} }
std::cout << "Passed test modify server during sync" << std::endl; std::cout << "Passed test modify server during sync" << std::endl;
@@ -755,6 +804,124 @@ void testCopyInstalledChildren(HTTP::Client* cl)
std::cout << "passed Copy installed children" << std::endl; std::cout << "passed Copy installed children" << std::endl;
} }
void testRetryAfterSocketFailure(HTTP::Client *cl) {
global_repo->clearRequestCounts();
global_repo->clearFailFlags();
std::unique_ptr<HTTPRepository> repo;
SGPath p(simgear::Dir::current().path());
p.append("http_repo_retry_after_socket_fail");
simgear::Dir pd(p);
if (pd.exists()) {
pd.removeChildren();
}
repo.reset(new HTTPRepository(p, cl));
repo->setBaseUrl("http://localhost:2000/repo");
int aaFailsRemaining = 2;
int subdirBAFailsRemaining = 2;
TestApi::setResponseDoneCallback(
cl, [&aaFailsRemaining, &subdirBAFailsRemaining](int curlResult,
HTTP::Request_ptr req) {
if (req->url() == "http://localhost:2000/repo/dirA/fileAA") {
if (aaFailsRemaining == 0)
return false;
--aaFailsRemaining;
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
return true;
} else if (req->url() ==
"http://localhost:2000/repo/dirB/subdirA/.dirindex") {
if (subdirBAFailsRemaining == 0)
return false;
--subdirBAFailsRemaining;
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
return true;
} else {
return false;
}
});
repo->update();
waitForUpdateComplete(cl, repo.get());
if (repo->failure() != HTTPRepository::REPO_NO_ERROR) {
throw sg_exception("Bad result from retry socket failure test");
}
verifyFileState(p, "dirA/fileAA");
verifyFileState(p, "dirB/subdirA/fileBAA");
verifyFileState(p, "dirB/subdirA/fileBAC");
verifyRequestCount("dirA/fileAA", 3);
verifyRequestCount("dirB/subdirA", 3);
verifyRequestCount("dirB/subdirA/fileBAC", 1);
}
void testPersistentSocketFailure(HTTP::Client *cl) {
global_repo->clearRequestCounts();
global_repo->clearFailFlags();
std::unique_ptr<HTTPRepository> repo;
SGPath p(simgear::Dir::current().path());
p.append("http_repo_persistent_socket_fail");
simgear::Dir pd(p);
if (pd.exists()) {
pd.removeChildren();
}
repo.reset(new HTTPRepository(p, cl));
repo->setBaseUrl("http://localhost:2000/repo");
TestApi::setResponseDoneCallback(
cl, [](int curlResult, HTTP::Request_ptr req) {
const auto url = req->url();
if (url.find("http://localhost:2000/repo/dirB") == 0) {
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
return true;
}
return false;
});
repo->update();
waitForUpdateComplete(cl, repo.get());
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
throw sg_exception("Bad result from retry socket failure test");
}
verifyFileState(p, "dirA/fileAA");
verifyRequestCount("dirA/fileAA", 1);
verifyRequestCount("dirD/fileDA", 1);
verifyRequestCount("dirD/subdirDA/fileDAA", 1);
verifyRequestCount("dirD/subdirDB/fileDBA", 1);
}
void testHashOnEmptyFile()
{
std::unique_ptr<HTTPRepository> repo;
SGPath p(simgear::Dir::current().path());
p.append("sgfile_compute_hash");
simgear::Dir pd(p);
if (pd.exists()) {
pd.removeChildren();
} else {
pd.create(0700);
}
SGPath fPath = p / "test_empty_file";
SGFile file(fPath);
file.open(SG_IO_OUT);
file.close();
const auto hash = file.computeHash();
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
sglog().setLogLevels( SG_ALL, SG_INFO ); sglog().setLogLevels( SG_ALL, SG_INFO );
@@ -770,6 +937,8 @@ int main(int argc, char* argv[])
global_repo->defineFile("dirA/fileAC"); global_repo->defineFile("dirA/fileAC");
global_repo->defineFile("dirA/subdirA/fileAAA"); global_repo->defineFile("dirA/subdirA/fileAAA");
global_repo->defineFile("dirA/subdirA/fileAAB"); global_repo->defineFile("dirA/subdirA/fileAAB");
global_repo->defineFile("dirA/subdirA/zeroByteFile");
global_repo->defineFile("dirB/subdirA/fileBAA"); global_repo->defineFile("dirB/subdirA/fileBAA");
global_repo->defineFile("dirB/subdirA/fileBAB"); global_repo->defineFile("dirB/subdirA/fileBAB");
global_repo->defineFile("dirB/subdirA/fileBAC"); global_repo->defineFile("dirB/subdirA/fileBAC");
@@ -800,6 +969,10 @@ int main(int argc, char* argv[])
cl.clearAllConnections(); cl.clearAllConnections();
testCopyInstalledChildren(&cl); testCopyInstalledChildren(&cl);
testRetryAfterSocketFailure(&cl);
testPersistentSocketFailure(&cl);
testHashOnEmptyFile();
std::cout << "all tests passed ok" << std::endl; std::cout << "all tests passed ok" << std::endl;
return 0; return 0;
+31 -2
View File
@@ -32,7 +32,7 @@ void testTarGz()
uint8_t* buf = (uint8_t*) alloca(8192); uint8_t* buf = (uint8_t*) alloca(8192);
size_t bufSize = f.read((char*) buf, 8192); size_t bufSize = f.read((char*) buf, 8192);
SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::TarData); SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::GZData);
f.close(); f.close();
} }
@@ -174,6 +174,34 @@ void testPAXAttributes()
} }
void testExtractXZ()
{
SGPath p = SGPath(SRC_DIR);
p.append("test.tar.xz");
SGBinaryFile f(p);
f.open(SG_IO_IN);
SGPath extractDir = simgear::Dir::current().path() / "test_extract_xz";
simgear::Dir pd(extractDir);
pd.removeChildren();
ArchiveExtractor ex(extractDir);
uint8_t* buf = (uint8_t*)alloca(128);
while (!f.eof()) {
size_t bufSize = f.read((char*)buf, 128);
ex.extractBytes(buf, bufSize);
}
ex.flush();
SG_VERIFY(ex.isAtEndOfArchive());
SG_VERIFY(ex.hasError() == false);
SG_VERIFY((extractDir / "testDir/hello.c").exists());
SG_VERIFY((extractDir / "testDir/foo.txt").exists());
}
int main(int ac, char ** av) int main(int ac, char ** av)
{ {
testTarGz(); testTarGz();
@@ -181,7 +209,8 @@ int main(int ac, char ** av)
testFilterTar(); testFilterTar();
testExtractStreamed(); testExtractStreamed();
testExtractZip(); testExtractZip();
testExtractXZ();
// disabled to avoiding checking in large PAX archive // disabled to avoiding checking in large PAX archive
// testPAXAttributes(); // testPAXAttributes();
+438 -397
View File
@@ -31,87 +31,18 @@
#include <simgear/sg_inlines.h> #include <simgear/sg_inlines.h>
#include <simgear/io/sg_file.hxx> #include <simgear/io/sg_file.hxx>
#include <simgear/misc/sg_dir.hxx> #include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/io/iostreams/sgstream.hxx> #include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <simgear/package/unzip.h> #include <simgear/package/unzip.h>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
#include "ArchiveExtractor_private.hxx"
namespace simgear namespace simgear
{ {
class ArchiveExtractorPrivate
{
public:
ArchiveExtractorPrivate(ArchiveExtractor* o) :
outer(o)
{
assert(outer);
}
virtual ~ArchiveExtractorPrivate() = default;
typedef enum {
INVALID = 0,
READING_HEADER,
READING_FILE,
READING_PADDING,
READING_PAX_GLOBAL_ATTRIBUTES,
READING_PAX_FILE_ATTRIBUTES,
PRE_END_OF_ARCHVE,
END_OF_ARCHIVE,
ERROR_STATE, ///< states above this are error conditions
BAD_ARCHIVE,
BAD_DATA,
FILTER_STOPPED
} State;
State state = INVALID;
ArchiveExtractor* outer = nullptr;
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
virtual void flush() = 0;
SGPath extractRootPath()
{
return outer->_rootPath;
}
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
{
return outer->filterPath(pathToExtract);
}
bool isSafePath(const std::string& p) const
{
if (p.empty()) {
return false;
}
// reject absolute paths
if (p.at(0) == '/') {
return false;
}
// reject paths containing '..'
size_t doubleDot = p.find("..");
if (doubleDot != std::string::npos) {
return false;
}
// on POSIX could use realpath to sanity check
return true;
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
const int ZLIB_DECODE_GZIP_HEADER = 16;
/* tar Header Block, from POSIX 1003.1-1990. */
typedef struct typedef struct
{ {
@@ -151,320 +82,413 @@ typedef struct
#define FIFOTYPE '6' /* FIFO special */ #define FIFOTYPE '6' /* FIFO special */
#define CONTTYPE '7' /* reserved */ #define CONTTYPE '7' /* reserved */
const char PAX_GLOBAL_HEADER = 'g'; const char PAX_GLOBAL_HEADER = 'g';
const char PAX_FILE_ATTRIBUTES = 'x'; const char PAX_FILE_ATTRIBUTES = 'x';
class TarExtractorPrivate : public ArchiveExtractorPrivate ///////////////////////////////////////////////////////////////////////////////////////////////////
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
const int ZLIB_DECODE_GZIP_HEADER = 16;
/* tar Header Block, from POSIX 1003.1-1990. */
class TarExtractorPrivate : public ArchiveExtractorPrivate
{
public:
union {
UstarHeaderBlock header;
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
};
size_t bytesRemaining;
std::unique_ptr<SGFile> currentFile;
size_t currentFileSize;
uint8_t* headerPtr;
bool skipCurrentEntry = false;
std::string paxAttributes;
std::string paxPathName;
TarExtractorPrivate(ArchiveExtractor* o) : ArchiveExtractorPrivate(o)
{
setState(TarExtractorPrivate::READING_HEADER);
}
~TarExtractorPrivate() = default;
void readPaddingIfRequired()
{
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
if (pad) {
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
setState(READING_PADDING);
} else {
setState(READING_HEADER);
}
}
void checkEndOfState()
{
if (bytesRemaining > 0) {
return;
}
if (state == READING_FILE) {
if (currentFile) {
currentFile->close();
currentFile.reset();
}
readPaddingIfRequired();
} else if (state == READING_HEADER) {
processHeader();
} else if (state == PRE_END_OF_ARCHVE) {
if (headerIsAllZeros()) {
setState(END_OF_ARCHIVE);
} else {
// what does the spec say here?
}
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
parsePAXAttributes(true);
readPaddingIfRequired();
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
parsePAXAttributes(false);
readPaddingIfRequired();
} else if (state == READING_PADDING) {
setState(READING_HEADER);
}
}
void setState(State newState)
{
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) {
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
headerPtr = headerBytes;
}
state = newState;
}
void extractBytes(const uint8_t* bytes, size_t count) override
{
// uncompressed, just pass through directly
processBytes((const char*)bytes, count);
}
void flush() override
{
// no-op for tar files, we process everything greedily
}
void processHeader()
{
if (headerIsAllZeros()) {
if (state == PRE_END_OF_ARCHVE) {
setState(END_OF_ARCHIVE);
} else {
setState(PRE_END_OF_ARCHVE);
}
return;
}
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) {
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong");
state = BAD_ARCHIVE;
return;
}
skipCurrentEntry = false;
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
if (!paxPathName.empty()) {
tarPath = paxPathName;
paxPathName.clear(); // clear for next file
}
if (!isSafePath(tarPath)) {
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
skipCurrentEntry = true;
}
auto result = filterPath(tarPath);
if (result == ArchiveExtractor::Stop) {
setState(FILTER_STOPPED);
return;
} else if (result == ArchiveExtractor::Skipped) {
skipCurrentEntry = true;
}
SGPath p = extractRootPath() / tarPath;
if (header.typeflag == DIRTYPE) {
if (!skipCurrentEntry) {
Dir dir(p);
dir.create(0755);
}
setState(READING_HEADER);
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
if (!skipCurrentEntry) {
currentFile.reset(new SGBinaryFile(p));
currentFile->open(SG_IO_OUT);
}
setState(READING_FILE);
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
setState(READING_PAX_GLOBAL_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
setState(READING_PAX_FILE_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
skipCurrentEntry = true;
setState(READING_HEADER);
} else {
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
state = BAD_ARCHIVE;
}
}
void processBytes(const char* bytes, size_t count)
{
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
return;
}
size_t curBytes = std::min(bytesRemaining, count);
if (state == READING_FILE) {
if (currentFile) {
currentFile->write(bytes, curBytes);
}
bytesRemaining -= curBytes;
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
memcpy(headerPtr, bytes, curBytes);
bytesRemaining -= curBytes;
headerPtr += curBytes;
} else if (state == READING_PADDING) {
bytesRemaining -= curBytes;
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
bytesRemaining -= curBytes;
paxAttributes.append(bytes, curBytes);
}
checkEndOfState();
if (count > curBytes) {
// recurse with unprocessed bytes
processBytes(bytes + curBytes, count - curBytes);
}
}
bool headerIsAllZeros() const
{
char* headerAsChar = (char*)&header;
for (size_t i = 0; i < offsetof(UstarHeaderBlock, magic); ++i) {
if (*headerAsChar++ != 0) {
return false;
}
}
return true;
}
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
void parsePAXAttributes(bool areGlobal)
{
auto lineStart = 0;
for (;;) {
auto firstSpace = paxAttributes.find(' ', lineStart);
auto firstEq = paxAttributes.find('=', lineStart);
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
break;
}
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
uint32_t dataBytes = lengthBytes - (firstEq + 1);
std::string name = paxAttributes.substr(firstSpace + 1, firstEq - (firstSpace + 1));
// dataBytes - 1 here to trim off the trailing newline
std::string data = paxAttributes.substr(firstEq + 1, dataBytes - 1);
processPAXAttribute(areGlobal, name, data);
lineStart += lengthBytes;
}
}
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
{
if (!isGlobalAttr && (attrName == "path")) {
// data is UTF-8 encoded path name
paxPathName = data;
}
}
};
///////////////////////////////////////////////////////////////////////////////
class GZTarExtractor : public TarExtractorPrivate
{ {
public: public:
GZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
union {
UstarHeaderBlock header;
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
};
size_t bytesRemaining;
std::unique_ptr<SGFile> currentFile;
size_t currentFileSize;
z_stream zlibStream;
uint8_t* zlibOutput;
bool haveInitedZLib = false;
bool uncompressedData = false; // set if reading a plain .tar (not tar.gz)
uint8_t* headerPtr;
bool skipCurrentEntry = false;
std::string paxAttributes;
std::string paxPathName;
TarExtractorPrivate(ArchiveExtractor* o) :
ArchiveExtractorPrivate(o)
{ {
memset(&zlibStream, 0, sizeof(z_stream)); memset(&zlibStream, 0, sizeof(z_stream));
zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE); zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
zlibStream.zalloc = Z_NULL; zlibStream.zalloc = Z_NULL;
zlibStream.zfree = Z_NULL; zlibStream.zfree = Z_NULL;
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE; zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
zlibStream.next_out = zlibOutput; zlibStream.next_out = zlibOutput;
} }
~TarExtractorPrivate() ~GZTarExtractor()
{ {
if (haveInitedZLib) {
inflateEnd(&zlibStream);
}
free(zlibOutput); free(zlibOutput);
} }
void readPaddingIfRequired() void extractBytes(const uint8_t* bytes, size_t count) override
{ {
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE; zlibStream.next_in = (uint8_t*)bytes;
if (pad) { zlibStream.avail_in = count;
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
setState(READING_PADDING);
} else {
setState(READING_HEADER);
}
}
void checkEndOfState()
{
if (bytesRemaining > 0) {
return;
}
if (state == READING_FILE) { if (!haveInitedZLib) {
if (currentFile) { // now we have data, see if we're dealing with GZ-compressed data or not
currentFile->close(); if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
currentFile.reset(); // GZIP identification bytes
} if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
readPaddingIfRequired(); SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
} else if (state == READING_HEADER) { state = TarExtractorPrivate::BAD_DATA;
processHeader(); return;
} else if (state == PRE_END_OF_ARCHVE) { }
if (headerIsAllZeros()) {
setState(END_OF_ARCHIVE);
} else { } else {
// what does the spec say here? // set error state
state = TarExtractorPrivate::BAD_DATA;
return;
} }
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
parsePAXAttributes(true);
readPaddingIfRequired();
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
parsePAXAttributes(false);
readPaddingIfRequired();
} else if (state == READING_PADDING) {
setState(READING_HEADER);
}
}
void setState(State newState) haveInitedZLib = true;
{ setState(TarExtractorPrivate::READING_HEADER);
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) { } // of init on first-bytes case
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
headerPtr = headerBytes;
}
state = newState; size_t writtenSize;
} // loop, running zlib() inflate and sending output bytes to
// our request body handler. Keep calling inflate until no bytes are
// written, and ZLIB has consumed all available input
do {
zlibStream.next_out = zlibOutput;
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
int result = inflate(&zlibStream, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
// nothing to do
void extractBytes(const uint8_t* bytes, size_t count) override } else if (result == Z_BUF_ERROR) {
{ // transient error, fall through
zlibStream.next_in = (uint8_t*) bytes;
zlibStream.avail_in = count;
if (!haveInitedZLib) {
// now we have data, see if we're dealing with GZ-compressed data or not
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
// GZIP identification bytes
if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
state = TarExtractorPrivate::BAD_DATA;
return;
}
} else {
UstarHeaderBlock* header = (UstarHeaderBlock*)bytes;
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
SG_LOG(SG_IO, SG_WARN, "didn't find tar magic in header");
state = TarExtractorPrivate::BAD_DATA;
return;
}
uncompressedData = true;
}
haveInitedZLib = true;
setState(TarExtractorPrivate::READING_HEADER);
} // of init on first-bytes case
if (uncompressedData) {
processBytes((const char*) bytes, count);
} else {
size_t writtenSize;
// loop, running zlib() inflate and sending output bytes to
// our request body handler. Keep calling inflate until no bytes are
// written, and ZLIB has consumed all available input
do {
zlibStream.next_out = zlibOutput;
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
int result = inflate(&zlibStream, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
// nothing to do
}
else if (result == Z_BUF_ERROR) {
// transient error, fall through
}
else {
// _error = result;
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
state = TarExtractorPrivate::BAD_DATA;
return;
}
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
if (writtenSize > 0) {
processBytes((const char*) zlibOutput, writtenSize);
}
if (result == Z_STREAM_END) {
break;
}
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
} // of Zlib-compressed data
}
void flush() override
{
// no-op for tar files, we process everything greedily
}
void processHeader()
{
if (headerIsAllZeros()) {
if (state == PRE_END_OF_ARCHVE) {
setState(END_OF_ARCHIVE);
} else { } else {
setState(PRE_END_OF_ARCHVE); // _error = result;
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
state = TarExtractorPrivate::BAD_DATA;
return;
} }
return;
}
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) { writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong"); if (writtenSize > 0) {
state = BAD_ARCHIVE; processBytes((const char*)zlibOutput, writtenSize);
return;
}
skipCurrentEntry = false;
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
if (!paxPathName.empty()) {
tarPath = paxPathName;
paxPathName.clear(); // clear for next file
}
if (!isSafePath(tarPath)) {
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
skipCurrentEntry = true;
}
auto result = filterPath(tarPath);
if (result == ArchiveExtractor::Stop) {
setState(FILTER_STOPPED);
return;
} else if (result == ArchiveExtractor::Skipped) {
skipCurrentEntry = true;
}
SGPath p = extractRootPath() / tarPath;
if (header.typeflag == DIRTYPE) {
if (!skipCurrentEntry) {
Dir dir(p);
dir.create(0755);
} }
setState(READING_HEADER);
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
if (!skipCurrentEntry) {
currentFile.reset(new SGBinaryFile(p));
currentFile->open(SG_IO_OUT);
}
setState(READING_FILE);
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
setState(READING_PAX_GLOBAL_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
setState(READING_PAX_FILE_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
skipCurrentEntry = true;
setState(READING_HEADER);
} else {
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
state = BAD_ARCHIVE;
}
}
void processBytes(const char* bytes, size_t count) if (result == Z_STREAM_END) {
{
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
return;
}
size_t curBytes = std::min(bytesRemaining, count);
if (state == READING_FILE) {
if (currentFile) {
currentFile->write(bytes, curBytes);
}
bytesRemaining -= curBytes;
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
memcpy(headerPtr, bytes, curBytes);
bytesRemaining -= curBytes;
headerPtr += curBytes;
} else if (state == READING_PADDING) {
bytesRemaining -= curBytes;
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
bytesRemaining -= curBytes;
paxAttributes.append(bytes, curBytes);
}
checkEndOfState();
if (count > curBytes) {
// recurse with unprocessed bytes
processBytes(bytes + curBytes, count - curBytes);
}
}
bool headerIsAllZeros() const
{
char* headerAsChar = (char*) &header;
for (size_t i=0; i < offsetof(UstarHeaderBlock, magic); ++i) {
if (*headerAsChar++ != 0) {
return false;
}
}
return true;
}
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
void parsePAXAttributes(bool areGlobal)
{
auto lineStart = 0;
for (;;) {
auto firstSpace = paxAttributes.find(' ', lineStart);
auto firstEq = paxAttributes.find('=', lineStart);
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
break; break;
} }
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
uint32_t dataBytes = lengthBytes - (firstEq + 1);
std::string name = paxAttributes.substr(firstSpace+1, firstEq - (firstSpace + 1));
// dataBytes - 1 here to trim off the trailing newline
std::string data = paxAttributes.substr(firstEq+1, dataBytes - 1);
processPAXAttribute(areGlobal, name, data);
lineStart += lengthBytes;
}
}
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
{
if (!isGlobalAttr && (attrName == "path")) {
// data is UTF-8 encoded path name
paxPathName = data;
}
} }
private:
z_stream zlibStream;
uint8_t* zlibOutput;
bool haveInitedZLib = false;
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#if 1 || defined(HAVE_XZ)
#include <lzma.h>
class XZTarExtractor : public TarExtractorPrivate
{
public:
XZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
{
_xzStream = LZMA_STREAM_INIT;
_outputBuffer = (uint8_t*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
auto ret = lzma_stream_decoder(&_xzStream, UINT64_MAX, LZMA_TELL_ANY_CHECK);
if (ret != LZMA_OK) {
setState(BAD_ARCHIVE);
return;
}
}
~XZTarExtractor()
{
free(_outputBuffer);
}
void extractBytes(const uint8_t* bytes, size_t count) override
{
lzma_action action = LZMA_RUN;
_xzStream.next_in = bytes;
_xzStream.avail_in = count;
size_t writtenSize;
do {
_xzStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
_xzStream.next_out = _outputBuffer;
const auto ret = lzma_code(&_xzStream, action);
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _xzStream.avail_out;
if (writtenSize > 0) {
processBytes((const char*)_outputBuffer, writtenSize);
}
if (ret == LZMA_GET_CHECK) {
//
} else if (ret == LZMA_STREAM_END) {
setState(END_OF_ARCHIVE);
break;
} else if (ret != LZMA_OK) {
setState(BAD_ARCHIVE);
break;
}
} while ((_xzStream.avail_in > 0) || (writtenSize > 0));
}
void flush() override
{
const auto ret = lzma_code(&_xzStream, LZMA_FINISH);
if (ret != LZMA_STREAM_END) {
setState(BAD_ARCHIVE);
}
}
private:
lzma_stream _xzStream;
uint8_t* _outputBuffer = nullptr;
};
#endif
///////////////////////////////////////////////////////////////////////////////
extern "C" { extern "C" {
void fill_memory_filefunc(zlib_filefunc_def*); void fill_memory_filefunc(zlib_filefunc_def*);
} }
@@ -509,32 +533,34 @@ public:
const size_t BUFFER_SIZE = 1024 * 1024; const size_t BUFFER_SIZE = 1024 * 1024;
void* buf = malloc(BUFFER_SIZE); void* buf = malloc(BUFFER_SIZE);
try { int result = unzGoToFirstFile(zip);
int result = unzGoToFirstFile(zip); if (result != UNZ_OK) {
if (result != UNZ_OK) { SG_LOG(SG_IO, SG_ALERT, outer->rootPath() << "failed to go to first file in archive:" << result);
throw sg_exception("failed to go to first file in archive");
}
while (true) {
extractCurrentFile(zip, (char*)buf, BUFFER_SIZE);
if (state == FILTER_STOPPED) {
break;
}
result = unzGoToNextFile(zip);
if (result == UNZ_END_OF_LIST_OF_FILE) {
break;
}
else if (result != UNZ_OK) {
throw sg_io_exception("failed to go to next file in the archive");
}
}
state = END_OF_ARCHIVE;
}
catch (sg_exception&) {
state = BAD_ARCHIVE; state = BAD_ARCHIVE;
free(buf);
unzClose(zip);
return;
} }
while (true) {
extractCurrentFile(zip, (char*)buf, BUFFER_SIZE);
if (state == FILTER_STOPPED) {
state = END_OF_ARCHIVE;
break;
}
result = unzGoToNextFile(zip);
if (result == UNZ_END_OF_LIST_OF_FILE) {
state = END_OF_ARCHIVE;
break;
} else if (result != UNZ_OK) {
SG_LOG(SG_IO, SG_ALERT, outer->rootPath() << "failed to go to next file in archive:" << result);
state = BAD_ARCHIVE;
break;
}
}
free(buf); free(buf);
unzClose(zip); unzClose(zip);
} }
@@ -592,7 +618,7 @@ public:
outFile.open(path, std::ios::binary | std::ios::trunc | std::ios::out); outFile.open(path, std::ios::binary | std::ios::trunc | std::ios::out);
if (outFile.fail()) { if (outFile.fail()) {
throw sg_io_exception("failed to open output file for writing", path); throw sg_io_exception("failed to open output file for writing:" + strutils::error_string(errno), path);
} }
while (!eof) { while (!eof) {
@@ -633,17 +659,19 @@ void ArchiveExtractor::extractBytes(const uint8_t* bytes, size_t count)
if (r == TarData) { if (r == TarData) {
d.reset(new TarExtractorPrivate(this)); d.reset(new TarExtractorPrivate(this));
} } else if (r == GZData) {
else if (r == ZipData) { d.reset(new GZTarExtractor(this));
d.reset(new ZipExtractorPrivate(this)); } else if (r == XZData) {
} d.reset(new XZTarExtractor(this));
else { } else if (r == ZipData) {
SG_LOG(SG_IO, SG_WARN, "Invalid archive type"); d.reset(new ZipExtractorPrivate(this));
} else {
SG_LOG(SG_IO, SG_WARN, "Invalid archive type");
_invalidDataType = true; _invalidDataType = true;
return; return;
} }
// if hit here, we created the extractor. Feed the prefbuffer // if hit here, we created the extractor. Feed the prefbuffer
// bytes through it // bytes through it
d->extractBytes((uint8_t*) _prebuffer.data(), _prebuffer.size()); d->extractBytes((uint8_t*) _prebuffer.data(), _prebuffer.size());
_prebuffer.clear(); _prebuffer.clear();
@@ -695,9 +723,18 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::determineType(const uint8_t*
return ZipData; return ZipData;
} }
auto r = isTarData(bytes, count); if (count < 6) {
if ((r == TarData) || (r == InsufficientData)) return InsufficientData;
return r; }
const uint8_t XZ_HEADER[6] = {0xFD, '7', 'z', 'X', 'Z', 0x00};
if (memcmp(bytes, XZ_HEADER, 6) == 0) {
return XZData;
}
auto r = isTarData(bytes, count);
if ((r == TarData) || (r == InsufficientData) || (r == GZData))
return r;
return Invalid; return Invalid;
} }
@@ -710,6 +747,8 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
} }
UstarHeaderBlock* header = 0; UstarHeaderBlock* header = 0;
DetermineResult result = InsufficientData;
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) { if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
// GZIP identification bytes // GZIP identification bytes
z_stream z; z_stream z;
@@ -727,11 +766,11 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
return Invalid; return Invalid;
} }
int result = inflate(&z, Z_SYNC_FLUSH); int zResult = inflate(&z, Z_SYNC_FLUSH);
if ((result == Z_OK) || (result == Z_STREAM_END)) { if ((zResult == Z_OK) || (zResult == Z_STREAM_END)) {
// all good // all good
} else { } else {
SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << result); SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << zResult);
inflateEnd(&z); inflateEnd(&z);
return Invalid; // not tar data return Invalid; // not tar data
} }
@@ -744,6 +783,7 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
header = reinterpret_cast<UstarHeaderBlock*>(zlibOutput); header = reinterpret_cast<UstarHeaderBlock*>(zlibOutput);
inflateEnd(&z); inflateEnd(&z);
result = GZData;
} else { } else {
// uncompressed tar // uncompressed tar
if (count < TAR_HEADER_BLOCK_SIZE) { if (count < TAR_HEADER_BLOCK_SIZE) {
@@ -751,13 +791,14 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
} }
header = (UstarHeaderBlock*) bytes; header = (UstarHeaderBlock*) bytes;
result = TarData;
} }
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) { if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
return Invalid; return Invalid;
} }
return TarData; return result;
} }
void ArchiveExtractor::extractLocalFile(const SGPath& archiveFile) void ArchiveExtractor::extractLocalFile(const SGPath& archiveFile)
+14 -8
View File
@@ -36,15 +36,16 @@ public:
ArchiveExtractor(const SGPath& rootPath); ArchiveExtractor(const SGPath& rootPath);
virtual ~ArchiveExtractor(); virtual ~ArchiveExtractor();
enum DetermineResult enum DetermineResult {
{ Invalid,
Invalid, InsufficientData,
InsufficientData, TarData,
TarData, ZipData,
ZipData GZData, // Gzipped-tar
}; XZData // XZ (aka LZMA) tar
};
static DetermineResult determineType(const uint8_t* bytes, size_t count); static DetermineResult determineType(const uint8_t* bytes, size_t count);
/** /**
* @brief API to extract a local zip or tar.gz * @brief API to extract a local zip or tar.gz
@@ -70,6 +71,11 @@ public:
Stop Stop
}; };
SGPath rootPath() const
{
return _rootPath;
}
protected: protected:
+7
View File
@@ -29,6 +29,13 @@ public:
/// Default constructor, initializes the instance to lat = lon = elev = 0 /// Default constructor, initializes the instance to lat = lon = elev = 0
SGGeod(void); SGGeod(void);
/**
return an SGGeod for which isValid() returns false.
This is necessaerby becuase for historical reasons, ther defaulrt constructor above initialsies to zero,zero,zero
which *is*
*/
static SGGeod invalid();
/// Factory from angular values in radians and elevation is 0 /// Factory from angular values in radians and elevation is 0
static SGGeod fromRad(double lon, double lat); static SGGeod fromRad(double lon, double lat);
/// Factory from angular values in degrees and elevation is 0 /// Factory from angular values in degrees and elevation is 0
+12
View File
@@ -537,6 +537,13 @@ SGGeodesy::advanceRadM(const SGGeoc& geoc, double course, double distance,
} }
} }
SGGeoc SGGeodesy::advanceDegM(const SGGeoc &geoc, double course,
double distance) {
SGGeoc result;
advanceRadM(geoc, course * SG_DEGREES_TO_RADIANS, distance, result);
return result;
}
double double
SGGeodesy::courseRad(const SGGeoc& from, const SGGeoc& to) SGGeodesy::courseRad(const SGGeoc& from, const SGGeoc& to)
{ {
@@ -656,3 +663,8 @@ SGGeodesy::radialIntersection(const SGGeod& a, double aRadial,
result = SGGeod::fromGeoc(r); result = SGGeod::fromGeoc(r);
return true; return true;
} }
SGGeod SGGeod::invalid()
{
return SGGeod::fromDeg(-999.9, -999.0);
}
+3
View File
@@ -65,6 +65,9 @@ public:
// Geocentric course/distance computation // Geocentric course/distance computation
static void advanceRadM(const SGGeoc& geoc, double course, double distance, static void advanceRadM(const SGGeoc& geoc, double course, double distance,
SGGeoc& result); SGGeoc& result);
static SGGeoc advanceDegM(const SGGeoc &geoc, double course, double distance);
static double courseRad(const SGGeoc& from, const SGGeoc& to); static double courseRad(const SGGeoc& from, const SGGeoc& to);
static double distanceRad(const SGGeoc& from, const SGGeoc& to); static double distanceRad(const SGGeoc& from, const SGGeoc& to);
static double distanceM(const SGGeoc& from, const SGGeoc& to); static double distanceM(const SGGeoc& from, const SGGeoc& to);
+16
View File
@@ -49,6 +49,11 @@ ResourceManager* ResourceManager::instance()
return static_manager; return static_manager;
} }
bool ResourceManager::haveInstance()
{
return static_manager != nullptr;
}
ResourceManager::~ResourceManager() ResourceManager::~ResourceManager()
{ {
assert(this == static_manager); assert(this == static_manager);
@@ -56,6 +61,15 @@ ResourceManager::~ResourceManager()
std::for_each(_providers.begin(), _providers.end(), std::for_each(_providers.begin(), _providers.end(),
[](ResourceProvider* p) { delete p; }); [](ResourceProvider* p) { delete p; });
} }
void ResourceManager::reset()
{
if (static_manager) {
delete static_manager;
static_manager = nullptr;
}
}
/** /**
* trivial provider using a fixed base path * trivial provider using a fixed base path
*/ */
@@ -107,6 +121,8 @@ void ResourceManager::removeProvider(ResourceProvider* aProvider)
SG_LOG(SG_GENERAL, SG_DEV_ALERT, "unknown provider doing remove"); SG_LOG(SG_GENERAL, SG_DEV_ALERT, "unknown provider doing remove");
return; return;
} }
_providers.erase(it);
} }
SGPath ResourceManager::findPath(const std::string& aResource, SGPath aContext) SGPath ResourceManager::findPath(const std::string& aResource, SGPath aContext)
+6 -2
View File
@@ -45,8 +45,12 @@ public:
PRIORITY_HIGH = 1000 PRIORITY_HIGH = 1000
} Priority; } Priority;
static ResourceManager* instance(); static ResourceManager* instance();
static bool haveInstance();
static void reset();
/** /**
* add a simple fixed resource location, to resolve against * add a simple fixed resource location, to resolve against
*/ */
+20 -3
View File
@@ -44,6 +44,7 @@
#if defined(SG_WINDOWS) #if defined(SG_WINDOWS)
# include <direct.h> # include <direct.h>
# include <sys/utime.h> # include <sys/utime.h>
# include <Shlwapi.h>
#endif #endif
#include "sg_path.hxx" #include "sg_path.hxx"
@@ -194,7 +195,8 @@ SGPath::SGPath(PermissionChecker validator)
_permission_checker(validator), _permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false), _rwCached(false),
_cacheEnabled(true) _cacheEnabled(true),
_existsCached(false)
{ {
} }
@@ -205,7 +207,8 @@ SGPath::SGPath( const std::string& p, PermissionChecker validator )
_permission_checker(validator), _permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false), _rwCached(false),
_cacheEnabled(true) _cacheEnabled(true),
_existsCached(false)
{ {
fix(); fix();
} }
@@ -230,7 +233,8 @@ SGPath::SGPath( const SGPath& p,
_permission_checker(validator), _permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false), _rwCached(false),
_cacheEnabled(p._cacheEnabled) _cacheEnabled(p._cacheEnabled),
_existsCached(false)
{ {
append(r); append(r);
fix(); fix();
@@ -510,6 +514,19 @@ void SGPath::checkAccess() const
bool SGPath::exists() const bool SGPath::exists() const
{ {
#if defined(SG_WINDOWS)
// optimisation: _wstat is slow, eg for TerraSync
if (!_cached && !_existsCached) {
std::wstring w(wstr());
if ((path.length() > 1) && (path.back() == '/')) {
w.pop_back();
}
_existsCached = true;
_exists = PathFileExistsW(w.c_str());
return _exists;
}
#endif
validate(); validate();
return _exists; return _exists;
} }
+1
View File
@@ -356,6 +356,7 @@ private:
mutable bool _exists : 1; mutable bool _exists : 1;
mutable bool _isDir : 1; mutable bool _isDir : 1;
mutable bool _isFile : 1; mutable bool _isFile : 1;
mutable bool _existsCached : 1; ///< only used on Windows
mutable time_t _modTime; mutable time_t _modTime;
mutable size_t _size; mutable size_t _size;
}; };
+32
View File
@@ -267,6 +267,38 @@ namespace simgear {
return do_strip( s, BOTHSTRIP ); return do_strip( s, BOTHSTRIP );
} }
string makeStringSafeForPropertyName(const std::string& str)
{
// This function replaces all characters in 'str' that are not
// alphanumeric or '-'.
// TODO: make the function multibyte safe.
string res = str;
int index = 0;
for (char& c : res) {
if (!std::isalpha(c) && !std::isdigit(c) && c != '-') {
switch (c) {
case ' ':
case '\t':
case '\n':
case '\r':
case '_':
case '.':
case '/':
case '\\':
res[index] = '-';
break;
default:
res[index] = '_';
SG_LOG(SG_GENERAL, SG_WARN, "makeStringSafeForPropertyName: Modified '" << str << "' to '" << res << "'");
}
}
index++;
}
return res;
}
void void
stripTrailingNewlines_inplace(string& s) stripTrailingNewlines_inplace(string& s)
{ {
+3 -1
View File
@@ -75,7 +75,9 @@ namespace simgear {
std::string rstrip( const std::string& s ); std::string rstrip( const std::string& s );
std::string strip( const std::string& s ); std::string strip( const std::string& s );
/** std::string makeStringSafeForPropertyName(const std::string& str);
/**
* Return a new string with any trailing \\r and \\n characters removed. * Return a new string with any trailing \\r and \\n characters removed.
* Typically useful to clean a CR-terminated line obtained from * Typically useful to clean a CR-terminated line obtained from
* std::getline() which, upon reading CRLF (\\r\\n), discards the Line * std::getline() which, upon reading CRLF (\\r\\n), discards the Line
+7 -1
View File
@@ -737,6 +737,11 @@ void testDecodeHex()
SG_VERIFY(decoded == data1); SG_VERIFY(decoded == data1);
} }
void test_makeStringSafeForPropertyName()
{
SG_CHECK_EQUAL(strutils::makeStringSafeForPropertyName(" ABC/01234\t:\\\"_*$"), "-ABC-01234-_-_-__");
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
test_strip(); test_strip();
@@ -761,6 +766,7 @@ int main(int argc, char* argv[])
test_formatGeod(); test_formatGeod();
test_iequals(); test_iequals();
testDecodeHex(); testDecodeHex();
test_makeStringSafeForPropertyName();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
+3 -3
View File
@@ -36,7 +36,7 @@ void naRuntimeError(naContext c, const char* fmt, ...)
void naRethrowError(naContext subc) void naRethrowError(naContext subc)
{ {
strncpy(subc->callParent->error, subc->error, sizeof(subc->error)); strncpy(subc->callParent->error, subc->error, sizeof(subc->callParent->error));
subc->callParent->dieArg = subc->dieArg; subc->callParent->dieArg = subc->dieArg;
longjmp(subc->callParent->jumpHandle, 1); longjmp(subc->callParent->jumpHandle, 1);
} }
@@ -248,7 +248,7 @@ void naFreeContext(naContext c)
// than I have right now. So instead I'm clearing the stack tops here, so // than I have right now. So instead I'm clearing the stack tops here, so
// a freed context looks the same as a new one returned by initContext. // a freed context looks the same as a new one returned by initContext.
c->fTop = c->opTop = c->markTop = 0; c->fTop = c->opTop = c->markTop = c->ntemps = 0;
c->nextFree = globals->freeContexts; c->nextFree = globals->freeContexts;
globals->freeContexts = c; globals->freeContexts = c;
@@ -503,7 +503,7 @@ static void setMember(naContext ctx, naRef obj, naRef fld, naRef value)
return; return;
} }
if(!IS_HASH(obj)) ERR(ctx, "non-objects have no members"); if(!IS_HASH(obj)) naRuntimeError(ctx, "non-object does not have member: %s", naStr_data(fld));
naHash_set(obj, fld, value); naHash_set(obj, fld, value);
ctx->opTop -= 2; ctx->opTop -= 2;
} }
+1
View File
@@ -21,6 +21,7 @@
#include "NasalString.hxx" #include "NasalString.hxx"
#include <cassert> #include <cassert>
#include <stdexcept> // for std::runtime_error
namespace nasal namespace nasal
{ {
@@ -63,7 +63,7 @@ namespace nasal
class NasalMainLoopRecipient : public simgear::Emesary::IReceiver { class NasalMainLoopRecipient : public simgear::Emesary::IReceiver {
public: public:
NasalMainLoopRecipient() : receiveCount(0) { NasalMainLoopRecipient() : receiveCount(0), Active(false), CanWait(false) {
simgear::Emesary::GlobalTransmitter::instance()->Register(*this); simgear::Emesary::GlobalTransmitter::instance()->Register(*this);
} }
virtual ~NasalMainLoopRecipient() { virtual ~NasalMainLoopRecipient() {
+1 -1
View File
@@ -7,7 +7,7 @@
#include "iolib.h" #include "iolib.h"
static void ghostDestroy(void* g); static void ghostDestroy(void* g);
naGhostType naIOGhostType = { ghostDestroy, "iofile" }; naGhostType naIOGhostType = { ghostDestroy, "iofile", NULL, NULL };
static struct naIOGhost* ioghost(naRef r) static struct naIOGhost* ioghost(naRef r)
{ {
+1 -1
View File
@@ -472,7 +472,7 @@ static naRef f_sprintf(naContext c, naRef me, int argc, naRef* args)
} else { } else {
arg = naNumValue(arg); arg = naNumValue(arg);
if(naIsNil(arg)) if(naIsNil(arg))
fout = dosprintf(fstr, "nil"); fout = dosprintf("nil");
else if(t=='d' || t=='i' || t=='c') else if(t=='d' || t=='i' || t=='c')
fout = dosprintf(fstr, (int)naNumValue(arg).num); fout = dosprintf(fstr, (int)naNumValue(arg).num);
else if(t=='o' || t=='u' || t=='x' || t=='X') else if(t=='o' || t=='u' || t=='x' || t=='X')
+8 -1
View File
@@ -100,7 +100,14 @@ naRef naNewHash(struct Context* c)
naRef naNewCode(struct Context* c) naRef naNewCode(struct Context* c)
{ {
return naNew(c, T_CODE); naRef r = naNew(c, T_CODE);
// naNew can return a previously used naCode. naCodeGen will init
// all these members but a GC can occur inside naCodeGen, so we see
// partially initalized state here. To avoid this, clear out the values
// which mark() cares about.
PTR(r).code->srcFile = naNil();
PTR(r).code->nConstants = 0;
return r;
} }
naRef naNewCCode(struct Context* c, naCFunction fptr) naRef naNewCCode(struct Context* c, naCFunction fptr)
+2 -2
View File
@@ -9,10 +9,10 @@
#include "code.h" #include "code.h"
static void lockDestroy(void* lock) { naFreeLock(lock); } static void lockDestroy(void* lock) { naFreeLock(lock); }
static naGhostType LockType = { lockDestroy }; static naGhostType LockType = { lockDestroy, NULL, NULL, NULL };
static void semDestroy(void* sem) { naFreeSem(sem); } static void semDestroy(void* sem) { naFreeSem(sem); }
static naGhostType SemType = { semDestroy }; static naGhostType SemType = { semDestroy, NULL, NULL, NULL };
typedef struct { typedef struct {
naContext ctx; naContext ctx;
+77 -14
View File
@@ -595,13 +595,17 @@ void Catalog::setUserEnabled(bool b)
m_userEnabled = b; m_userEnabled = b;
SGPath disableMarkerFile = installRoot() / "_disabled_"; SGPath disableMarkerFile = installRoot() / "_disabled_";
if (m_userEnabled) {
sg_ofstream of(disableMarkerFile); if (m_userEnabled == false) {
of << "1\n"; // touch the file sg_ofstream of(disableMarkerFile, std::ios::trunc | std::ios::out);
of << "1" << std::flush; // touch the file
of.close();
} else { } else {
bool ok = disableMarkerFile.remove(); if (disableMarkerFile.exists()) {
if (!ok) { const bool ok = disableMarkerFile.remove();
SG_LOG(SG_GENERAL, SG_ALERT, "Failed to remove catalog-disable marker file:" << disableMarkerFile); if (!ok) {
SG_LOG(SG_IO, SG_WARN, "Failed to remove catalog-disable marker file:" << disableMarkerFile);
}
} }
} }
@@ -615,6 +619,8 @@ void Catalog::setUserEnabled(bool b)
void Catalog::processAlternate(SGPropertyNode_ptr alt) void Catalog::processAlternate(SGPropertyNode_ptr alt)
{ {
m_refreshRequest.reset();
std::string altId; std::string altId;
const auto idPtr = alt->getStringValue("id"); const auto idPtr = alt->getStringValue("id");
if (idPtr) { if (idPtr) {
@@ -639,24 +645,81 @@ void Catalog::processAlternate(SGPropertyNode_ptr alt)
return; return;
} }
// we have an alternate ID, and it's differnt from our ID, so let's // we have an alternate ID, and it's different from our ID, so let's
// define a new catalog // define a new catalog
if (!altId.empty()) { if (!altId.empty()) {
SG_LOG(SG_GENERAL, SG_INFO, "Adding new catalog:" << altId << " as version alternate for " << id()); // don't auto-re-add Catalogs the user has explicilty rmeoved, that would
// new catalog being added // suck
createFromUrl(root(), altUrl); const auto removedByUser = root()->explicitlyRemovedCatalogs();
auto it = std::find(removedByUser.begin(), removedByUser.end(), altId);
// and we can go idle now if (it != removedByUser.end()) {
changeStatus(Delegate::FAIL_VERSION); changeStatus(Delegate::FAIL_VERSION);
return; return;
}
SG_LOG(SG_GENERAL, SG_WARN,
"Adding new catalog:" << altId << " as version alternate for "
<< id());
// new catalog being added
auto newCat = createFromUrl(root(), altUrl);
bool didRun = false;
newCat->m_migratedFrom = this;
auto migratePackagesCb = [didRun](Catalog *c) mutable {
// removing callbacks is awkward, so use this
// flag to only run once. (and hence, we need to be mutable)
if (didRun)
return;
if (c->status() == Delegate::STATUS_REFRESHED) {
didRun = true;
string_list existing;
for (const auto &pack : c->migratedFrom()->installedPackages()) {
existing.push_back(pack->id());
}
const int count = c->markPackagesForInstallation(existing);
SG_LOG(
SG_GENERAL, SG_INFO,
"Marked " << count
<< " packages from previous catalog for installation");
}
};
newCat->addStatusCallback(migratePackagesCb);
// and we can go idle now
changeStatus(Delegate::FAIL_VERSION);
return;
} }
SG_LOG(SG_GENERAL, SG_INFO, "Migrating catalog " << id() << " to new URL:" << altUrl); SG_LOG(SG_GENERAL, SG_INFO, "Migrating catalog " << id() << " to new URL:" << altUrl);
setUrl(altUrl); setUrl(altUrl);
Downloader* dl = new Downloader(this, altUrl); m_refreshRequest = new Downloader(this, altUrl);
root()->makeHTTPRequest(dl); root()->makeHTTPRequest(m_refreshRequest);
} }
int Catalog::markPackagesForInstallation(const string_list &packageIds) {
int result = 0;
for (const auto &id : packageIds) {
auto ourPkg = getPackageById(id);
if (!ourPkg)
continue;
auto existing = ourPkg->existingInstall();
if (!existing) {
ourPkg->markForInstall();
++result;
}
} // of outer package ID candidates iteration
return result;
}
CatalogRef Catalog::migratedFrom() const { return m_migratedFrom; }
} // of namespace pkg } // of namespace pkg
} // of namespace simgear } // of namespace simgear
+30 -2
View File
@@ -23,6 +23,7 @@
#include <map> #include <map>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx> #include <simgear/props/props.hxx>
#include <simgear/structure/SGReferenced.hxx> #include <simgear/structure/SGReferenced.hxx>
@@ -93,7 +94,7 @@ public:
/** /**
* retrieve all the packages in the catalog which are installed * retrieve all the packages in the catalog which are installed
* and have a pendig update * and have a pending update
*/ */
PackageList packagesNeedingUpdate() const; PackageList packagesNeedingUpdate() const;
@@ -151,7 +152,32 @@ public:
bool isUserEnabled() const; bool isUserEnabled() const;
void setUserEnabled(bool b); void setUserEnabled(bool b);
private:
/**
* Given a list of package IDs, mark all which exist in this package,
* for installation. ANy packahe IDs not present in this catalog,
* will be ignored.
*
* @result The number for packages newly marked for installation.
*/
int markPackagesForInstallation(const string_list &packageIds);
/**
* When a catalog is added due to migration, this will contain the
* Catalog which triggered the add. Usually this will be a catalog
* corresponding to an earlier version.
*
* Note it's only valid at the time, the migration actually took place;
* when the new catalog is loaded from disk, this value will return
* null.
*
* This is intended to allow Uis to show a 'catalog was migrated'
* feedback, when they see a catalog refresh, which has a non-null
* value of this method.
*/
CatalogRef migratedFrom() const;
private:
Catalog(Root* aRoot); Catalog(Root* aRoot);
class Downloader; class Downloader;
@@ -197,6 +223,8 @@ private:
PackageWeakMap m_variantDict; PackageWeakMap m_variantDict;
function_list<Callback> m_statusCallbacks; function_list<Callback> m_statusCallbacks;
CatalogRef m_migratedFrom;
}; };
} // of namespace pkg } // of namespace pkg
+144 -15
View File
@@ -166,7 +166,8 @@ int parseTest()
{ {
SGPath rootPath = simgear::Dir::current().path(); SGPath rootPath = simgear::Dir::current().path();
rootPath.append("testRoot"); rootPath.append("testRoot");
pkg::Root* root = new pkg::Root(rootPath, "8.1.12");
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
root->setLocale("de"); root->setLocale("de");
pkg::CatalogRef cat = pkg::Catalog::createFromPath(root, SGPath(SRC_DIR "/catalogTest1")); pkg::CatalogRef cat = pkg::Catalog::createFromPath(root, SGPath(SRC_DIR "/catalogTest1"));
@@ -344,7 +345,6 @@ int parseTest()
SG_CHECK_EQUAL(urls.size(), 3); SG_CHECK_EQUAL(urls.size(), 3);
SG_CHECK_EQUAL(urls.at(1), "http://localhost:2000/mirrorB/b737.tar.gz"); SG_CHECK_EQUAL(urls.at(1), "http://localhost:2000/mirrorB/b737.tar.gz");
delete root;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@@ -827,7 +827,10 @@ void testVersionMigrateToId(HTTP::Client* cl)
it = std::find(enabledCats.begin(), enabledCats.end(), altCat); it = std::find(enabledCats.begin(), enabledCats.end(), altCat);
SG_VERIFY(it != enabledCats.end()); SG_VERIFY(it != enabledCats.end());
SG_CHECK_EQUAL(altCat->packagesNeedingUpdate().size(),
1); // should be the 737
// install a parallel package from the new catalog // install a parallel package from the new catalog
pkg::PackageRef p2 = root->getPackageById("org.flightgear.test.catalog-alt.b737-NG"); pkg::PackageRef p2 = root->getPackageById("org.flightgear.test.catalog-alt.b737-NG");
SG_CHECK_EQUAL(p2->id(), "b737-NG"); SG_CHECK_EQUAL(p2->id(), "b737-NG");
@@ -840,8 +843,8 @@ void testVersionMigrateToId(HTTP::Client* cl)
pkg::PackageRef p3 = root->getPackageById("b737-NG"); pkg::PackageRef p3 = root->getPackageById("b737-NG");
SG_CHECK_EQUAL(p2, p3); SG_CHECK_EQUAL(p2, p3);
} }
// test that re-init-ing doesn't mirgate again // test that re-init-ing doesn't migrate again
{ {
pkg::RootRef root(new pkg::Root(rootPath, "7.5")); pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
root->setHTTPClient(cl); root->setHTTPClient(cl);
@@ -950,7 +953,7 @@ int parseInvalidTest()
{ {
SGPath rootPath = simgear::Dir::current().path(); SGPath rootPath = simgear::Dir::current().path();
rootPath.append("testRoot"); rootPath.append("testRoot");
pkg::Root* root = new pkg::Root(rootPath, "8.1.12"); pkg::RootRef root(new pkg::Root(rootPath, "8.1.12"));
pkg::CatalogRef cat = pkg::Catalog::createFromPath(root, SGPath(SRC_DIR "/catalogTestInvalid")); pkg::CatalogRef cat = pkg::Catalog::createFromPath(root, SGPath(SRC_DIR "/catalogTestInvalid"));
SG_VERIFY(cat.valid()); SG_VERIFY(cat.valid());
@@ -1184,6 +1187,128 @@ void testMirrorsFailure(HTTP::Client* cl)
} }
void testMigrateInstalled(HTTP::Client *cl) {
SGPath rootPath(simgear::Dir::current().path());
rootPath.append("pkg_migrate_installed");
simgear::Dir pd(rootPath);
pd.removeChildren();
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
root->setHTTPClient(cl);
pkg::CatalogRef oldCatalog, newCatalog;
{
oldCatalog = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
waitForUpdateComplete(cl, root);
pkg::PackageRef p1 =
root->getPackageById("org.flightgear.test.catalog1.b747-400");
p1->install();
auto p2 = root->getPackageById("org.flightgear.test.catalog1.c172p");
p2->install();
auto p3 = root->getPackageById("org.flightgear.test.catalog1.b737-NG");
p3->install();
waitForUpdateComplete(cl, root);
}
{
newCatalog = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest2/catalog.xml");
waitForUpdateComplete(cl, root);
string_list existing;
for (const auto &pack : oldCatalog->installedPackages()) {
existing.push_back(pack->id());
}
SG_CHECK_EQUAL(4, existing.size());
int result = newCatalog->markPackagesForInstallation(existing);
SG_CHECK_EQUAL(2, result);
SG_CHECK_EQUAL(2, newCatalog->packagesNeedingUpdate().size());
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
auto ins = p1->existingInstall();
SG_CHECK_EQUAL(0, ins->revsion());
}
{
root->scheduleAllUpdates();
waitForUpdateComplete(cl, root);
SG_CHECK_EQUAL(0, newCatalog->packagesNeedingUpdate().size());
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
auto ins = p1->existingInstall();
SG_CHECK_EQUAL(ins->revsion(), p1->revision());
}
}
void testDontMigrateRemoved(HTTP::Client *cl) {
global_catalogVersion = 2; // version which has migration info
SGPath rootPath(simgear::Dir::current().path());
rootPath.append("cat_dont_migrate_id");
simgear::Dir pd(rootPath);
pd.removeChildren();
// install and mnaully remove the alt catalog
{
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
root->setHTTPClient(cl);
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest1/catalog-alt.xml");
waitForUpdateComplete(cl, root);
root->removeCatalogById("org.flightgear.test.catalog-alt");
}
// install the migration catalog
{
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
root->setHTTPClient(cl);
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
waitForUpdateComplete(cl, root);
SG_VERIFY(c->isEnabled());
}
// change version to an alternate one
{
pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
auto removed = root->explicitlyRemovedCatalogs();
auto j = std::find(removed.begin(), removed.end(),
"org.flightgear.test.catalog-alt");
SG_VERIFY(j != removed.end());
root->setHTTPClient(cl);
// this would tirgger migration, but we blocked it
root->refresh(true);
waitForUpdateComplete(cl, root);
pkg::CatalogRef cat = root->getCatalogById("org.flightgear.test.catalog1");
SG_VERIFY(!cat->isEnabled());
SG_CHECK_EQUAL(cat->status(), pkg::Delegate::FAIL_VERSION);
SG_CHECK_EQUAL(cat->id(), "org.flightgear.test.catalog1");
SG_CHECK_EQUAL(cat->url(),
"http://localhost:2000/catalogTest1/catalog.xml");
auto enabledCats = root->catalogs();
auto it = std::find(enabledCats.begin(), enabledCats.end(), cat);
SG_VERIFY(it == enabledCats.end());
// check the new catalog
auto altCat = root->getCatalogById("org.flightgear.test.catalog-alt");
SG_VERIFY(altCat.get() == nullptr);
}
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
sglog().setLogLevels( SG_ALL, SG_WARN ); sglog().setLogLevels( SG_ALL, SG_WARN );
@@ -1208,27 +1333,31 @@ int main(int argc, char* argv[])
testRefreshCatalog(&cl); testRefreshCatalog(&cl);
testInstallTarPackage(&cl); testInstallTarPackage(&cl);
testInstallArchiveType(&cl); testInstallArchiveType(&cl);
testDisableDueToVersion(&cl); testDisableDueToVersion(&cl);
testOfflineMode(&cl); testOfflineMode(&cl);
testVersionMigrate(&cl); testVersionMigrate(&cl);
updateInvalidToValid(&cl); updateInvalidToValid(&cl);
updateValidToInvalid(&cl); updateValidToInvalid(&cl);
updateInvalidToInvalid(&cl); updateInvalidToInvalid(&cl);
removeInvalidCatalog(&cl); removeInvalidCatalog(&cl);
testVersionMigrateToId(&cl); testVersionMigrateToId(&cl);
testInstallBadPackage(&cl); testInstallBadPackage(&cl);
testMirrorsFailure(&cl); testMirrorsFailure(&cl);
testMigrateInstalled(&cl);
testDontMigrateRemoved(&cl);
cerr << "Successfully passed all tests!" << endl; cerr << "Successfully passed all tests!" << endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
+38 -2
View File
@@ -34,7 +34,7 @@ namespace simgear {
namespace pkg { namespace pkg {
Package::Package(const SGPropertyNode* aProps, CatalogRef aCatalog) : Package::Package(const SGPropertyNode* aProps, CatalogRef aCatalog) :
m_catalog(aCatalog) m_catalog(aCatalog.get())
{ {
initWithProps(aProps); initWithProps(aProps);
} }
@@ -198,6 +198,11 @@ InstallRef Package::install()
{ {
InstallRef ins = existingInstall(); InstallRef ins = existingInstall();
if (ins) { if (ins) {
// if there's updates, treat this as a 'start update' request
if (ins->hasUpdate()) {
m_catalog->root()->scheduleToUpdate(ins);
}
return ins; return ins;
} }
@@ -210,13 +215,39 @@ InstallRef Package::install()
return ins; return ins;
} }
InstallRef Package::markForInstall() {
InstallRef ins = existingInstall();
if (ins) {
return ins;
}
const auto pd = pathOnDisk();
Dir dir(pd);
if (!dir.create(0700)) {
SG_LOG(SG_IO, SG_ALERT,
"Package::markForInstall: couldn't create directory at:" << pd);
return {};
}
ins = new Install{this, pd};
_install_cb(this, ins); // not sure if we should trigger the callback for this
// repeat for dependencies to be kind
for (auto dep : dependencies()) {
dep->markForInstall();
}
return ins;
}
InstallRef Package::existingInstall(const InstallCallback& cb) const InstallRef Package::existingInstall(const InstallCallback& cb) const
{ {
InstallRef install; InstallRef install;
try { try {
install = m_catalog->root()->existingInstallForPackage(const_cast<Package*>(this)); install = m_catalog->root()->existingInstallForPackage(const_cast<Package*>(this));
} catch (std::exception& ) { } catch (std::exception& ) {
return InstallRef(); return {};
} }
if( cb ) if( cb )
@@ -235,6 +266,11 @@ std::string Package::id() const
return m_id; return m_id;
} }
CatalogRef Package::catalog() const
{
return {m_catalog};
}
std::string Package::qualifiedId() const std::string Package::qualifiedId() const
{ {
return m_catalog->id() + "." + id(); return m_catalog->id() + "." + id();
+9 -3
View File
@@ -61,6 +61,13 @@ public:
InstallRef InstallRef
existingInstall(const InstallCallback& cb = InstallCallback()) const; existingInstall(const InstallCallback& cb = InstallCallback()) const;
/**
* Mark this package for installation, but don't actually start the
* download process. This creates the on-disk placeholder, so
* the package will appear an eededing to be updated.
*/
InstallRef markForInstall();
bool isInstalled() const; bool isInstalled() const;
/** /**
@@ -130,8 +137,7 @@ public:
size_t fileSizeBytes() const; size_t fileSizeBytes() const;
CatalogRef catalog() const CatalogRef catalog() const;
{ return m_catalog; }
bool matches(const SGPropertyNode* aFilter) const; bool matches(const SGPropertyNode* aFilter) const;
@@ -236,7 +242,7 @@ private:
SGPropertyNode_ptr m_props; SGPropertyNode_ptr m_props;
std::string m_id; std::string m_id;
string_set m_tags; string_set m_tags;
CatalogRef m_catalog; Catalog* m_catalog = nullptr; // non-owning ref, explicitly
string_list m_variants; string_list m_variants;
mutable function_list<InstallCallback> _install_cb; mutable function_list<InstallCallback> _install_cb;
+67 -12
View File
@@ -133,7 +133,9 @@ public:
void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status) void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
{ {
for (auto d : delegates) { // take a copy of delegates since firing this can modify the data
const auto currentDelegates = delegates;
for (auto d : currentDelegates) {
d->catalogRefreshed(catalog, status); d->catalogRefreshed(catalog, status);
} }
} }
@@ -283,6 +285,32 @@ public:
fireDataForThumbnail(url, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size()); fireDataForThumbnail(url, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size());
} }
void writeRemovedCatalogsFile() const {
SGPath p = path / "RemovedCatalogs";
sg_ofstream stream(p, std::ios::out | std::ios::trunc | std::ios::binary);
for (const auto &cid : manuallyRemovedCatalogs) {
stream << cid << "\n";
}
stream.close();
}
void loadRemovedCatalogsFile() {
manuallyRemovedCatalogs.clear();
SGPath p = path / "RemovedCatalogs";
if (!p.exists())
return;
sg_ifstream stream(p, std::ios::in);
while (!stream.eof()) {
std::string line;
std::getline(stream, line);
const auto trimmed = strutils::strip(line);
if (!trimmed.empty()) {
manuallyRemovedCatalogs.push_back(trimmed);
}
} // of lines iteration
}
DelegateVec delegates; DelegateVec delegates;
SGPath path; SGPath path;
@@ -312,6 +340,9 @@ public:
typedef std::map<PackageRef, InstallRef> InstallCache; typedef std::map<PackageRef, InstallRef> InstallCache;
InstallCache m_installs; InstallCache m_installs;
/// persistent list of catalogs the user has manually removed
string_list manuallyRemovedCatalogs;
}; };
@@ -400,6 +431,8 @@ Root::Root(const SGPath& aPath, const std::string& aVersion) :
thumbsCacheDir.create(0755); thumbsCacheDir.create(0755);
} }
d->loadRemovedCatalogsFile();
for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) { for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
// note this will set the catalog status, which will insert into // note this will set the catalog status, which will insert into
// disabled catalogs automatically if necesary // disabled catalogs automatically if necesary
@@ -621,6 +654,13 @@ void Root::scheduleToUpdate(InstallRef aInstall)
} }
} }
void Root::scheduleAllUpdates() {
auto toBeUpdated = packagesNeedingUpdate(); // make a copy
for (const auto &u : toBeUpdated) {
scheduleToUpdate(u->existingInstall());
}
}
bool Root::isInstallQueued(InstallRef aInstall) const bool Root::isInstallQueued(InstallRef aInstall) const
{ {
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall); auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
@@ -684,20 +724,17 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
auto catIt = d->catalogs.find(aCat->id()); auto catIt = d->catalogs.find(aCat->id());
d->fireRefreshStatus(aCat, aReason); d->fireRefreshStatus(aCat, aReason);
if (aReason == Delegate::STATUS_IN_PROGRESS) { if (aCat->isUserEnabled() &&
d->refreshing.insert(aCat); (aReason == Delegate::STATUS_REFRESHED) &&
} else { (catIt == d->catalogs.end()))
d->refreshing.erase(aCat); {
}
if ((aReason == Delegate::STATUS_REFRESHED) && (catIt == d->catalogs.end())) {
assert(!aCat->id().empty()); assert(!aCat->id().empty());
d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat)); d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
// catalog might have been previously disabled, let's remove in that case // catalog might have been previously disabled, let's remove in that case
auto j = std::find(d->disabledCatalogs.begin(), auto j = std::find(d->disabledCatalogs.begin(),
d->disabledCatalogs.end(), d->disabledCatalogs.end(),
aCat); aCat);
if (j != d->disabledCatalogs.end()) { if (j != d->disabledCatalogs.end()) {
SG_LOG(SG_GENERAL, SG_INFO, "re-enabling disabled catalog:" << aCat->id()); SG_LOG(SG_GENERAL, SG_INFO, "re-enabling disabled catalog:" << aCat->id());
d->disabledCatalogs.erase(j); d->disabledCatalogs.erase(j);
@@ -705,7 +742,7 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
} }
if (!aCat->isEnabled()) { if (!aCat->isEnabled()) {
// catalog has errors, disable it // catalog has errors or was disabled by user, disable it
auto j = std::find(d->disabledCatalogs.begin(), auto j = std::find(d->disabledCatalogs.begin(),
d->disabledCatalogs.end(), d->disabledCatalogs.end(),
aCat); aCat);
@@ -720,6 +757,17 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
} }
} // of catalog is disabled } // of catalog is disabled
// remove from refreshing /after/ checking for enable / disabled, since for
// new catalogs, the reference in d->refreshing might be our /only/
// reference to the catalog. Once the refresh is done (either failed or
// succeeded) the Catalog will be in either d->catalogs or
// d->disabledCatalogs
if (aReason == Delegate::STATUS_IN_PROGRESS) {
d->refreshing.insert(aCat);
} else {
d->refreshing.erase(aCat);
}
if (d->refreshing.empty()) { if (d->refreshing.empty()) {
d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED); d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
d->firePackagesChanged(); d->firePackagesChanged();
@@ -780,6 +828,9 @@ bool Root::removeCatalogById(const std::string& aId)
<< "failed to remove directory"); << "failed to remove directory");
} }
d->manuallyRemovedCatalogs.push_back(aId);
d->writeRemovedCatalogsFile();
// notify that a catalog is being removed // notify that a catalog is being removed
d->firePackagesChanged(); d->firePackagesChanged();
@@ -851,6 +902,10 @@ void Root::unregisterInstall(InstallRef ins)
d->fireFinishUninstall(ins->package()); d->fireFinishUninstall(ins->package());
} }
string_list Root::explicitlyRemovedCatalogs() const {
return d->manuallyRemovedCatalogs;
}
} // of namespace pkg } // of namespace pkg
} // of namespace simgear } // of namespace simgear
+16 -1
View File
@@ -155,7 +155,22 @@ public:
void requestThumbnailData(const std::string& aUrl); void requestThumbnailData(const std::string& aUrl);
bool isInstallQueued(InstallRef aInstall) const; bool isInstallQueued(InstallRef aInstall) const;
private:
/**
* Mark all 'to be updated' packages for update now
*/
void scheduleAllUpdates();
/**
* @brief list of catalog IDs, the user has explicitly removed via
* removeCatalogById(). This is important to allow the user to opt-out
* of migrated packages.
*
* This information is stored in a helper file, in the root directory
*/
string_list explicitlyRemovedCatalogs() const;
private:
friend class Install; friend class Install;
friend class Catalog; friend class Catalog;
friend class Package; friend class Package;
+86
View File
@@ -0,0 +1,86 @@
<?xml version="1.0"?>
<PropertyList>
<id>org.flightgear.test.catalog2</id>
<description>Second test catalog</description>
<url>http://localhost:2000/catalogTest2/catalog.xml</url>
<catalog-version>4</catalog-version>
<version>8.1.*</version>
<version>8.0.0</version>
<version>8.2.0</version>
<package>
<id>alpha</id>
<name>Alpha package</name>
<revision type="int">8</revision>
<file-size-bytes type="int">593</file-size-bytes>
<md5>a469c4b837f0521db48616cfe65ac1ea</md5>
<url>http://localhost:2000/catalogTest1/alpha.zip</url>
<dir>alpha</dir>
</package>
<package>
<id>b737-NG</id>
<name>Boeing 737 NG</name>
<dir>b737NG</dir>
<description>A popular twin-engined narrow body jet</description>
<revision type="int">111</revision>
<file-size-bytes type="int">860</file-size-bytes>
<tag>boeing</tag>
<tag>jet</tag>
<tag>ifr</tag>
<!-- not within a localized element -->
<de>
<description>German description of B737NG XYZ</description>
</de>
<fr>
<description>French description of B737NG</description>
</fr>
<rating>
<FDM type="int">5</FDM>
<systems type="int">5</systems>
<model type="int">4</model>
<cockpit type="int">4</cockpit>
</rating>
<md5>a94ca5704f305b90767f40617d194ed6</md5>
<url>http://localhost:2000/mirrorA/b737.tar.gz</url>
<url>http://localhost:2000/mirrorB/b737.tar.gz</url>
<url>http://localhost:2000/mirrorC/b737.tar.gz</url>
</package>
<package>
<id>b747-400</id>
<name>Boeing 747-400</name>
<dir>b744</dir>
<description>A popular four-engined wide-body jet</description>
<revision type="int">111</revision>
<file-size-bytes type="int">860</file-size-bytes>
<tag>boeing</tag>
<tag>jet</tag>
<tag>ifr</tag>
<rating>
<FDM type="int">5</FDM>
<systems type="int">5</systems>
<model type="int">4</model>
<cockpit type="int">4</cockpit>
</rating>
<md5>4d3f7417d74f811aa20ccc4f35673d20</md5>
<!-- this URL will sometimes fail, on purpose -->
<url>http://localhost:2000/catalogTest1/b747.tar.gz</url>
</package>
</PropertyList>
+6
View File
@@ -59,6 +59,12 @@ void AtomicChangeListener::fireChangeListeners()
listeners.clear(); listeners.clear();
} }
void AtomicChangeListener::clearPendingChanges()
{
auto& listeners = ListenerListSingleton::instance()->listeners;
listeners.clear();
}
void AtomicChangeListener::valueChangedImplementation() void AtomicChangeListener::valueChangedImplementation()
{ {
if (!_dirty) { if (!_dirty) {
+10
View File
@@ -52,7 +52,17 @@ public:
bool isDirty() { return _dirty; } bool isDirty() { return _dirty; }
bool isValid() { return _valid; } bool isValid() { return _valid; }
virtual void unregister_property(SGPropertyNode* node) override; virtual void unregister_property(SGPropertyNode* node) override;
static void fireChangeListeners(); static void fireChangeListeners();
/**
* @brief Ensure we've deleted any pending changes.
*
* This is important in shutdown and reset, to avoid holding
* property listeners around after the property tree is destroyed
*/
static void clearPendingChanges();
private: private:
virtual void valueChangedImplementation() override; virtual void valueChangedImplementation() override;
virtual void valuesChanged(); virtual void valuesChanged();
+144 -41
View File
@@ -19,6 +19,10 @@
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <iterator> #include <iterator>
#include <exception> // can't use sg_exception becuase of PROPS_STANDALONE
#include <mutex>
#include <thread>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -48,6 +52,22 @@ using std::stringstream;
using namespace simgear; using namespace simgear;
struct SGPropertyNodeListeners
{
/* Protect _num_iterators and _items. We use a recursive mutex to allow
nested access to work as normal. */
std::recursive_mutex _rmutex;
/* This keeps a count of the current number of nested invocations of
forEachListener(). If non-zero, other code higher up the stack is iterating
_items[] so for example code must not erase items in the vector. */
int _num_iterators = 0;
std::vector<SGPropertyChangeListener *> _items;
};
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Local classes. // Local classes.
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@@ -546,23 +566,15 @@ find_node (SGPropertyNode * current,
} }
} }
#else #else
template<typename Range> template <typename Range>
SGPropertyNode* SGPropertyNode *find_node(SGPropertyNode *current, const Range &path,
find_node (SGPropertyNode * current, bool create, int last_index = -1) {
const Range& path,
bool create,
int last_index = -1)
{
using namespace boost; using namespace boost;
typedef split_iterator<typename range_result_iterator<Range>::type> auto itr = make_split_iterator(path, first_finder("/", is_equal()));
PathSplitIterator;
PathSplitIterator itr
= make_split_iterator(path, first_finder("/", is_equal()));
if (*path.begin() == '/') if (*path.begin() == '/')
return find_node_aux(current->getRootNode(), itr, create, last_index); return find_node_aux(current->getRootNode(), itr, create, last_index);
else else
return find_node_aux(current, itr, create, last_index); return find_node_aux(current, itr, create, last_index);
} }
#endif #endif
@@ -968,7 +980,7 @@ SGPropertyNode::~SGPropertyNode ()
if (_listeners) { if (_listeners) {
vector<SGPropertyChangeListener*>::iterator it; vector<SGPropertyChangeListener*>::iterator it;
for (it = _listeners->begin(); it != _listeners->end(); ++it) for (it = _listeners->_items.begin(); it != _listeners->_items.end(); ++it)
(*it)->unregister_property(this); (*it)->unregister_property(this);
delete _listeners; delete _listeners;
} }
@@ -2392,8 +2404,22 @@ SGPropertyNode::addChangeListener (SGPropertyChangeListener * listener,
bool initial) bool initial)
{ {
if (_listeners == 0) if (_listeners == 0)
_listeners = new vector<SGPropertyChangeListener*>; _listeners = new SGPropertyNodeListeners;
_listeners->push_back(listener);
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
/* If there's a nullptr entry (a listener that was unregistered), we
overwrite it. This ensures that listeners that routinely unregister+register
themselves don't make _listeners->_items grow unnecessarily. Otherwise simply
append. */
auto it = std::find(_listeners->_items.begin(), _listeners->_items.end(),
(SGPropertyChangeListener*) nullptr
);
if (it == _listeners->_items.end()) {
_listeners->_items.push_back(listener);
}
else {
*it = listener;
}
listener->register_property(this); listener->register_property(this);
if (initial) if (initial)
listener->valueChanged(this); listener->valueChanged(this);
@@ -2404,15 +2430,35 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
{ {
if (_listeners == 0) if (_listeners == 0)
return; return;
/* We use a std::unique_lock rather than a std::lock_guard because we may
need to unlock early. */
std::unique_lock<std::recursive_mutex> lock(_listeners->_rmutex);
vector<SGPropertyChangeListener*>::iterator it = vector<SGPropertyChangeListener*>::iterator it =
find(_listeners->begin(), _listeners->end(), listener); find(_listeners->_items.begin(), _listeners->_items.end(), listener);
if (it != _listeners->end()) { if (it != _listeners->_items.end()) {
_listeners->erase(it); assert(_listeners->_num_iterators >= 0);
listener->unregister_property(this); if (_listeners->_num_iterators) {
if (_listeners->empty()) { /* _listeners._items is currently being iterated further up the stack in
vector<SGPropertyChangeListener*>* tmp = _listeners; this thread by one or more nested invocations of forEachListener(), so
_listeners = 0; we must be careful to not break these iterators. So we must not delete
delete tmp; _listeners. And we must not erase this entry from the vector because that
could cause the next listener to be missed out.
So we simply set this entry to nullptr (nullptr items are skipped by
forEachListener()). When all invocations of forEachListener() have
finished iterating and it is safe to modify things again, it will clean
up any nullptr entries in _listeners->_items. */
*it = nullptr;
listener->unregister_property(this);
}
else {
_listeners->_items.erase(it);
listener->unregister_property(this);
if (_listeners->_items.empty()) {
lock.unlock();
delete _listeners;
_listeners = 0;
}
} }
} }
} }
@@ -2461,15 +2507,72 @@ SGPropertyNode::fireChildrenRemovedRecursive()
} }
} }
/* Calls <callback> for each item in _listeners. We are careful to skip nullptr
entries in _listeners->items[], which can be created if listeners are removed
while we are iterating. */
static void forEachListener(
SGPropertyNode* node,
SGPropertyNodeListeners*& _listeners,
std::function<void (SGPropertyChangeListener*)> callback
)
{
if (!_listeners) return;
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
assert(_listeners->_num_iterators >= 0);
_listeners->_num_iterators += 1;
/* We need to use an index here when iterating _listeners->_items, not an
iterator. This is because a listener may add new listeners, causing the
vector to be reallocated, which would invalidate any iterator. */
for (size_t i = 0; i < _listeners->_items.size(); ++i) {
auto listener = _listeners->_items[i];
if (listener) {
try {
callback(listener);
}
catch (std::exception& e) {
SG_LOG(SG_GENERAL, SG_ALERT, "Ignoring exception from property callback: " << e.what());
}
}
}
_listeners->_num_iterators -= 1;
assert(_listeners->_num_iterators >= 0);
if (_listeners->_num_iterators == 0) {
/* Remove any items that have been set to nullptr. */
_listeners->_items.erase(
std::remove(_listeners->_items.begin(), _listeners->_items.end(), (SGPropertyChangeListener*) nullptr),
_listeners->_items.end()
);
if (_listeners->_items.empty()) {
delete _listeners;
_listeners = nullptr;
}
}
}
int SGPropertyNode::nListeners() const
{
if (!_listeners) return 0;
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
int n = 0;
for (auto listener: _listeners->_items) {
if (listener) n += 1;
}
return n;
}
void void
SGPropertyNode::fireValueChanged (SGPropertyNode * node) SGPropertyNode::fireValueChanged (SGPropertyNode * node)
{ {
if (_listeners != 0) { forEachListener(
for (unsigned int i = 0; i < _listeners->size(); i++) { node,
if ((*_listeners)[i]) _listeners,
(*_listeners)[i]->valueChanged(node); [&](SGPropertyChangeListener* listener) { listener->valueChanged(node);}
} );
}
if (_parent != 0) if (_parent != 0)
_parent->fireValueChanged(node); _parent->fireValueChanged(node);
} }
@@ -2478,11 +2581,11 @@ void
SGPropertyNode::fireChildAdded (SGPropertyNode * parent, SGPropertyNode::fireChildAdded (SGPropertyNode * parent,
SGPropertyNode * child) SGPropertyNode * child)
{ {
if (_listeners != 0) { forEachListener(
for (unsigned int i = 0; i < _listeners->size(); i++) { parent,
(*_listeners)[i]->childAdded(parent, child); _listeners,
} [&](SGPropertyChangeListener* listener) { listener->childAdded(parent, child);}
} );
if (_parent != 0) if (_parent != 0)
_parent->fireChildAdded(parent, child); _parent->fireChildAdded(parent, child);
} }
@@ -2491,11 +2594,11 @@ void
SGPropertyNode::fireChildRemoved (SGPropertyNode * parent, SGPropertyNode::fireChildRemoved (SGPropertyNode * parent,
SGPropertyNode * child) SGPropertyNode * child)
{ {
if (_listeners != 0) { forEachListener(
for (unsigned int i = 0; i < _listeners->size(); i++) { parent,
(*_listeners)[i]->childRemoved(parent, child); _listeners,
} [&](SGPropertyChangeListener* listener) { listener->childRemoved(parent, child);}
} );
if (_parent != 0) if (_parent != 0)
_parent->fireChildRemoved(parent, child); _parent->fireChildRemoved(parent, child);
} }
+11 -7
View File
@@ -759,6 +759,9 @@ private:
}; };
struct SGPropertyNodeListeners;
/** /**
* A node in a property tree. * A node in a property tree.
*/ */
@@ -1713,7 +1716,7 @@ public:
/** /**
* Get the number of listeners. * Get the number of listeners.
*/ */
int nListeners () const { return _listeners ? (int)_listeners->size() : 0; } int nListeners () const;
/** /**
@@ -1847,7 +1850,7 @@ private:
char * string_val; char * string_val;
} _local_val; } _local_val;
std::vector<SGPropertyChangeListener *> * _listeners; SGPropertyNodeListeners* _listeners;
// Pass name as a pair of iterators // Pass name as a pair of iterators
template<typename Itr> template<typename Itr>
@@ -2194,11 +2197,12 @@ public:
_property->addChangeListener(this,initial); _property->addChangeListener(this,initial);
} }
SGPropertyChangeCallback(const SGPropertyChangeCallback<T>& other) : SGPropertyChangeCallback(const SGPropertyChangeCallback<T>& other)
_obj(other._obj), _callback(other._callback), _property(other._property) : SGPropertyChangeListener(other),
{ _obj(other._obj), _callback(other._callback), _property(other._property)
_property->addChangeListener(this,false); {
} _property->addChangeListener(this,false);
}
virtual ~SGPropertyChangeCallback() virtual ~SGPropertyChangeCallback()
{ {
+44
View File
@@ -854,4 +854,48 @@ copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
return true; return true;
} }
namespace {
// for normal recursion we want to call the predicate *before* creating children
bool _inner_copyPropertiesIf(const SGPropertyNode *in, SGPropertyNode *out,
PropertyPredicate predicate)
{
bool retval = copyPropertyValue(in, out);
if (!retval) {
return false;
}
out->setAttributes( in->getAttributes() );
int nChildren = in->nChildren();
for (int i = 0; i < nChildren; i++) {
const SGPropertyNode* in_child = in->getChild(i);
// skip this child
if (!predicate(in_child)) {
continue;
}
SGPropertyNode* out_child = out->getChild(in_child->getNameString(),
in_child->getIndex(),
true);
bool ok = copyPropertiesIf(in_child, out_child, predicate);
if (!ok) {
return false;
}
} // of children iteration
return true;
}
} // of anonymous namespace
bool copyPropertiesIf(const SGPropertyNode *in, SGPropertyNode *out,
PropertyPredicate predicate)
{
// allow the *entire* copy to be a no-op
bool doCopy = predicate(in);
if (!doCopy)
return true; // doesn't count as failure
return _inner_copyPropertiesIf(in, out, predicate);
}
// end of props_io.cxx // end of props_io.cxx
+11
View File
@@ -17,6 +17,7 @@
#include <string> #include <string>
#include <iosfwd> #include <iosfwd>
#include <functional>
/** /**
* Read properties from an XML input stream. * Read properties from an XML input stream.
@@ -67,6 +68,16 @@ bool copyProperties (const SGPropertyNode *in, SGPropertyNode *out);
bool copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out, bool copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
SGPropertyNode::Attribute attr); SGPropertyNode::Attribute attr);
using PropertyPredicate = std::function<bool (const SGPropertyNode* in)>;
/**
* Copy properties, if the predicate returns true for the in node.
* If a parent node returns false, descendats will <em>not</em> be
* checked
*/
bool copyPropertiesIf(const SGPropertyNode *in, SGPropertyNode *out,
PropertyPredicate predicate);
#endif // __PROPS_IO_HXX #endif // __PROPS_IO_HXX
// end of props_io.hxx // end of props_io.hxx
+14 -2
View File
@@ -451,13 +451,17 @@ void defineSamplePropertyTree(SGPropertyNode_ptr root)
class TestListener : public SGPropertyChangeListener class TestListener : public SGPropertyChangeListener
{ {
public: public:
TestListener(SGPropertyNode* root, bool recursive = false) : TestListener(SGPropertyNode* root, bool recursive = false, bool self_unregister = false) :
_root(root) {} _root(root), _self_unregister(self_unregister) {}
virtual void valueChanged(SGPropertyNode* node) override virtual void valueChanged(SGPropertyNode* node) override
{ {
std::cout << "saw value changed for:" << node->getPath() << std::endl; std::cout << "saw value changed for:" << node->getPath() << std::endl;
valueChangeCount[node]++; valueChangeCount[node]++;
if (_self_unregister) {
std::cout << "valueChanged(): calling removeChangeListener() to self-remove\n";
node->removeChangeListener(this);
}
} }
int checkValueChangeCount(const std::string& path) const int checkValueChangeCount(const std::string& path) const
@@ -533,6 +537,7 @@ public:
} }
private: private:
SGPropertyNode* _root; SGPropertyNode* _root;
bool _self_unregister;
std::map<SGPropertyNode*, unsigned int> valueChangeCount; std::map<SGPropertyNode*, unsigned int> valueChangeCount;
std::vector<ParentChange> adds; std::vector<ParentChange> adds;
@@ -942,6 +947,13 @@ void testDeleterListener()
SG_VERIFY(ensureNListeners(tree, 0)); SG_VERIFY(ensureNListeners(tree, 0));
} }
// Self-unregister. prior to 2019-07-08 this would segv.
{
std::shared_ptr<TestListener> l1( new TestListener(tree.get(), true /* recursive */, true /*self_unregister*/));
tree->setFloatValue("position/body/sub/self-unregister", 0.1);
tree->getNode("position/body/sub/self-unregister")->addChangeListener(l1.get());
tree->setFloatValue("position/body/sub/self-unregister", 0.2);
}
} }
int main (int ac, char ** av) int main (int ac, char ** av)
+6 -5
View File
@@ -76,7 +76,7 @@
#include <simgear/structure/SGExpression.hxx> #include <simgear/structure/SGExpression.hxx>
#include <simgear/props/props_io.hxx> #include <simgear/props/props_io.hxx>
#include <simgear/props/vectorPropTemplates.hxx> #include <simgear/props/vectorPropTemplates.hxx>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <simgear/io/iostreams/sgstream.hxx> #include <simgear/io/iostreams/sgstream.hxx>
namespace simgear namespace simgear
@@ -928,7 +928,8 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
// FIXME orig: const string& shaderName = shaderKey.first; // FIXME orig: const string& shaderName = shaderKey.first;
string shaderName = shaderKey.first; string shaderName = shaderKey.first;
Shader::Type stype = (Shader::Type)shaderKey.second; Shader::Type stype = (Shader::Type)shaderKey.second;
if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false) && const bool compositorEnabled = getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false);
if (compositorEnabled &&
shaderName.substr(0, shaderName.find("/")) == "Shaders") { shaderName.substr(0, shaderName.find("/")) == "Shaders") {
shaderName = "Compositor/" + shaderName; shaderName = "Compositor/" + shaderName;
} }
@@ -936,7 +937,9 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
if (fileName.empty()) if (fileName.empty())
{ {
SG_LOG(SG_INPUT, SG_ALERT, "Could not locate shader" << shaderName); SG_LOG(SG_INPUT, SG_ALERT, "Could not locate shader" << shaderName);
if (!compositorEnabled) {
reportError("Missing shader", shaderName);
}
throw BuilderException(string("couldn't find shader ") + throw BuilderException(string("couldn't find shader ") +
shaderName); shaderName);
@@ -1481,10 +1484,8 @@ void mergeSchemesFallbacks(Effect *effect, const SGReaderWriterOptions *options)
// Walk the techniques property tree, building techniques and // Walk the techniques property tree, building techniques and
// passes. // passes.
static std::mutex realizeTechniques_lock;
bool Effect::realizeTechniques(const SGReaderWriterOptions* options) bool Effect::realizeTechniques(const SGReaderWriterOptions* options)
{ {
std::lock_guard<std::mutex> g(realizeTechniques_lock);
if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false)) if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false))
mergeSchemesFallbacks(this, options); mergeSchemesFallbacks(this, options);
+13 -5
View File
@@ -273,11 +273,19 @@ bool setAttrs(const TexTuple& attrs, Texture* tex,
options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS_NORMALIZED); options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS_NORMALIZED);
else else
options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS); options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS);
#if OSG_VERSION_LESS_THAN(3,4,2)
result = osgDB::readImageFile(imageName, options); try {
#else #if OSG_VERSION_LESS_THAN(3,4,2)
result = osgDB::readRefImageFile(imageName, options); result = osgDB::readImageFile(imageName, options);
#endif #else
result = osgDB::readRefImageFile(imageName, options);
#endif
} catch (std::bad_alloc& ba) {
SG_LOG(SG_GL, SG_ALERT, "Bad allocation loading:" << imageName);
// todo: report low memory warning
return false;
}
options->setLoadOriginHint(origLOH); options->setLoadOriginHint(origLOH);
osg::ref_ptr<osg::Image> image; osg::ref_ptr<osg::Image> image;
if (result.success()) if (result.success())
+6 -2
View File
@@ -97,11 +97,13 @@ SGMaterial::SGMaterial( const SGReaderWriterOptions* options,
const SGPropertyNode *props, const SGPropertyNode *props,
SGPropertyNode *prop_root, SGPropertyNode *prop_root,
AreaList *a, AreaList *a,
SGSharedPtr<const SGCondition> c) SGSharedPtr<const SGCondition> c,
const std::string& n)
{ {
init(); init();
areas = a; areas = a;
condition = c; condition = c;
region = n;
read_properties( options, props, prop_root ); read_properties( options, props, prop_root );
buildEffectProperties(options); buildEffectProperties(options);
} }
@@ -110,12 +112,14 @@ SGMaterial::SGMaterial( const osgDB::Options* options,
const SGPropertyNode *props, const SGPropertyNode *props,
SGPropertyNode *prop_root, SGPropertyNode *prop_root,
AreaList *a, AreaList *a,
SGSharedPtr<const SGCondition> c) SGSharedPtr<const SGCondition> c,
const std::string& n)
{ {
osg::ref_ptr<SGReaderWriterOptions> opt; osg::ref_ptr<SGReaderWriterOptions> opt;
opt = SGReaderWriterOptions::copyOrCreate(options); opt = SGReaderWriterOptions::copyOrCreate(options);
areas = a; areas = a;
condition = c; condition = c;
region = n;
init(); init();
read_properties(opt.get(), props, prop_root); read_properties(opt.get(), props, prop_root);
buildEffectProperties(opt.get()); buildEffectProperties(opt.get());
+12 -2
View File
@@ -97,14 +97,16 @@ public:
const SGPropertyNode *props, const SGPropertyNode *props,
SGPropertyNode *prop_root, SGPropertyNode *prop_root,
AreaList *a, AreaList *a,
SGSharedPtr<const SGCondition> c); SGSharedPtr<const SGCondition> c,
const std::string& n);
SGMaterial(const simgear::SGReaderWriterOptions*, SGMaterial(const simgear::SGReaderWriterOptions*,
const SGPropertyNode *props, const SGPropertyNode *props,
SGPropertyNode *prop_root, SGPropertyNode *prop_root,
AreaList *a, AreaList *a,
SGSharedPtr<const SGCondition> c); SGSharedPtr<const SGCondition> c,
const std::string& n);
/** /**
* Destructor. * Destructor.
@@ -123,6 +125,11 @@ public:
simgear::Effect* get_one_effect(int texIndex); simgear::Effect* get_one_effect(int texIndex);
simgear::Effect* get_effect(); simgear::Effect* get_effect();
/**
* Get the region Name.
*/
const std::string get_region_name() const { return region; }
/** /**
* Get the Effect Name. * Get the Effect Name.
*/ */
@@ -487,6 +494,9 @@ private:
SGVec4f ambient, diffuse, specular, emission; SGVec4f ambient, diffuse, specular, emission;
double shininess; double shininess;
// region of this material
std::string region;
// effect for this material // effect for this material
std::string effect; std::string effect;
+2 -1
View File
@@ -128,12 +128,13 @@ bool SGMaterialLib::load( const SGPath &fg_root, const SGPath& mpath,
// Now build all the materials for this set of areas and conditions // Now build all the materials for this set of areas and conditions
const std::string region = node->getStringValue("name");
const simgear::PropertyList materials = node->getChildren("material"); const simgear::PropertyList materials = node->getChildren("material");
simgear::PropertyList::const_iterator materials_iter = materials.begin(); simgear::PropertyList::const_iterator materials_iter = materials.begin();
for (; materials_iter != materials.end(); materials_iter++) { for (; materials_iter != materials.end(); materials_iter++) {
const SGPropertyNode *node = materials_iter->get(); const SGPropertyNode *node = materials_iter->get();
SGSharedPtr<SGMaterial> m = SGSharedPtr<SGMaterial> m =
new SGMaterial(options.get(), node, prop_root, arealist, condition); new SGMaterial(options.get(), node, prop_root, arealist, condition, region);
std::vector<SGPropertyNode_ptr>names = node->getChildren("name"); std::vector<SGPropertyNode_ptr>names = node->getChildren("name");
for ( unsigned int j = 0; j < names.size(); j++ ) { for ( unsigned int j = 0; j < names.size(); j++ ) {
+34 -32
View File
@@ -28,7 +28,7 @@
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <map> #include <map>
#include <mutex>
#include <osg/AlphaFunc> #include <osg/AlphaFunc>
#include <osg/Group> #include <osg/Group>
@@ -103,37 +103,39 @@ SGMatModel::get_model_count( SGPropertyNode *prop_root )
inline void inline void
SGMatModel::load_models( SGPropertyNode *prop_root ) SGMatModel::load_models( SGPropertyNode *prop_root )
{ {
// Load model only on demand std::lock_guard<std::mutex> g(_loadMutex);
if (!_models_loaded) {
for (unsigned int i = 0; i < _paths.size(); i++) { // Load model only on demand
osg::Node *entity = SGModelLib::loadModel(_paths[i], prop_root); if (!_models_loaded) {
if (entity != 0) { for (unsigned int i = 0; i < _paths.size(); i++) {
// FIXME: this stuff can be handled osg::Node* entity = SGModelLib::loadModel(_paths[i], prop_root);
// in the XML wrapper as well (at least, if (entity != 0) {
// the billboarding should be handled // FIXME: this stuff can be handled
// there). // in the XML wrapper as well (at least,
// the billboarding should be handled
if (_heading_type == HEADING_BILLBOARD) { // there).
// if the model is a billboard, it is likely :
// 1. a branch with only leaves, if (_heading_type == HEADING_BILLBOARD) {
// 2. a tree or a non rectangular shape faked by transparency // if the model is a billboard, it is likely :
// We add alpha clamp then // 1. a branch with only leaves,
osg::StateSet* stateSet = entity->getOrCreateStateSet(); // 2. a tree or a non rectangular shape faked by transparency
osg::AlphaFunc* alphaFunc = // We add alpha clamp then
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01f); osg::StateSet* stateSet = entity->getOrCreateStateSet();
stateSet->setAttributeAndModes(alphaFunc, osg::AlphaFunc* alphaFunc =
osg::StateAttribute::OVERRIDE); new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01f);
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateSet->setAttributeAndModes(alphaFunc,
} osg::StateAttribute::OVERRIDE);
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
_models.push_back(entity); }
} else { _models.push_back(entity);
SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
// Ensure the vector contains something, otherwise get_random_model below fails } else {
_models.push_back(new osg::Node()); SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
} // Ensure the vector contains something, otherwise get_random_model below fails
} _models.push_back(new osg::Node());
}
}
} }
_models_loaded = true; _models_loaded = true;
} }
+2
View File
@@ -149,6 +149,8 @@ private:
double _spacing_m; double _spacing_m;
double _range_m; double _range_m;
HeadingType _heading_type; HeadingType _heading_type;
std::mutex _loadMutex;
}; };
+15 -11
View File
@@ -82,13 +82,13 @@ void _writeColor(GLenum pixelFormat, T* data, float scale, osg::Vec4 value)
switch(pixelFormat) switch(pixelFormat)
{ {
case(GL_DEPTH_COMPONENT): //intentionally fall through and execute the code for GL_LUMINANCE case(GL_DEPTH_COMPONENT): //intentionally fall through and execute the code for GL_LUMINANCE
case(GL_LUMINANCE): { *data++ = value.r()*scale; break; } case(GL_LUMINANCE): { *data = value.r()*scale; break; }
case(GL_ALPHA): { *data++ = value.a()*scale; break; } case(GL_ALPHA): { *data = value.a()*scale; break; }
case(GL_LUMINANCE_ALPHA): { *data++ = value.r()*scale; *data++ = value.a()*scale; break; } case(GL_LUMINANCE_ALPHA): { *data++ = value.r()*scale; *data = value.a()*scale; break; }
case(GL_RGB): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; break; } case(GL_RGB): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data = value.b()*scale; break; }
case(GL_RGBA): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; *data++ = value.a()*scale; break; } case(GL_RGBA): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; *data = value.a()*scale; break; }
case(GL_BGR): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; break; } case(GL_BGR): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data = value.r()*scale; break; }
case(GL_BGRA): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; *data++ = value.a()*scale; break; } case(GL_BGRA): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; *data = value.a()*scale; break; }
} }
} }
@@ -265,7 +265,13 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
{ {
bool computeMipmap = false; bool computeMipmap = false;
unsigned int nbComponents = osg::Image::computeNumComponents( image->getPixelFormat() ); unsigned int nbComponents = osg::Image::computeNumComponents( image->getPixelFormat() );
if ( std::get<0>(attrs) != AUTOMATIC && int s = image->s();
int t = image->t();
if ( (s & (s - 1)) || (t & (t - 1)) ) // power of two test
{
SG_LOG(SG_IO, SG_DEV_ALERT, "mipmapping: texture size not a power-of-two: " + image->getFileName());
}
else if ( std::get<0>(attrs) != AUTOMATIC &&
( std::get<1>(attrs) != AUTOMATIC || nbComponents < 2 ) && ( std::get<1>(attrs) != AUTOMATIC || nbComponents < 2 ) &&
( std::get<2>(attrs) != AUTOMATIC || nbComponents < 3 ) && ( std::get<2>(attrs) != AUTOMATIC || nbComponents < 3 ) &&
( std::get<3>(attrs) != AUTOMATIC || nbComponents < 4 ) ) ( std::get<3>(attrs) != AUTOMATIC || nbComponents < 4 ) )
@@ -283,9 +289,7 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
if ( computeMipmap ) if ( computeMipmap )
{ {
osg::ref_ptr<osg::Image> mipmaps = new osg::Image(); osg::ref_ptr<osg::Image> mipmaps = new osg::Image();
int s = image->s(), int r = image->r();
t = image->t(),
r = image->r();
int nb = osg::Image::computeNumberOfMipmapLevels(s, t, r); int nb = osg::Image::computeNumberOfMipmapLevels(s, t, r);
osg::Image::MipmapDataType mipmapOffsets; osg::Image::MipmapDataType mipmapOffsets;
unsigned int offset = 0; unsigned int offset = 0;
+11 -8
View File
@@ -113,9 +113,6 @@ protected:
} }
}; };
// disabling old-syle texture compression in favour of the
// texture-cache
#if 0
class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor { class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
public: public:
virtual void apply(int, StateSet::RefAttributePair& refAttr) virtual void apply(int, StateSet::RefAttributePair& refAttr)
@@ -146,7 +143,6 @@ public:
} }
} }
}; };
#endif
class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor { class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
public: public:
@@ -363,6 +359,8 @@ ModelRegistry::readImage(const string& fileName,
if (res.validImage()) { if (res.validImage()) {
osg::ref_ptr<osg::Image> srcImage = res.getImage(); osg::ref_ptr<osg::Image> srcImage = res.getImage();
int width = srcImage->s(); int width = srcImage->s();
//int packing = srcImage->getPacking();
//printf("packing %d format %x pixel size %d InternalTextureFormat %x\n", packing, srcImage->getPixelFormat(), srcImage->getPixelSizeInBits(), srcImage->getInternalTextureFormat() );
bool transparent = srcImage->isImageTranslucent(); bool transparent = srcImage->isImageTranslucent();
bool isNormalMap = false; bool isNormalMap = false;
bool isEffect = false; bool isEffect = false;
@@ -371,6 +369,11 @@ ModelRegistry::readImage(const string& fileName,
*/ */
bool can_compress = (transparent && compress_transparent) || (!transparent && compress_solid); bool can_compress = (transparent && compress_transparent) || (!transparent && compress_solid);
if (srcImage->getPixelSizeInBits() <= 16) {
SG_LOG(SG_IO, SG_INFO, "Ignoring " + absFileName + " for inclusion into the texture cache because pixel density too low at " << srcImage->getPixelSizeInBits() << " bits per pixek");
can_compress = false;
}
int height = srcImage->t(); int height = srcImage->t();
// use the new file origin to determine any special processing // use the new file origin to determine any special processing
@@ -390,6 +393,10 @@ ModelRegistry::readImage(const string& fileName,
isEffect = true; isEffect = true;
// can_compress = false; // can_compress = false;
} }
else if (sgoptC && !transparent && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_CANVAS) {
SG_LOG(SG_IO, SG_INFO, "From Canvas " + absFileName + " will generate mipmap only");
can_compress = false;
}
if (can_compress) if (can_compress)
{ {
std::string pot_message; std::string pot_message;
@@ -644,12 +651,8 @@ osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
SGTexDataVarianceVisitor dataVarianceVisitor; SGTexDataVarianceVisitor dataVarianceVisitor;
node->accept(dataVarianceVisitor); node->accept(dataVarianceVisitor);
// disabling old-syle texture compression in favour of the
// texture-cache
#if 0
SGTexCompressionVisitor texComp; SGTexCompressionVisitor texComp;
node->accept(texComp); node->accept(texComp);
#endif
return node; return node;
} }
+23 -9
View File
@@ -136,11 +136,19 @@ osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter& ea)
virtual void update(double dt, int keyModState) virtual void update(double dt, int keyModState)
{ {
if (!_condition || _condition->test()) { SG_UNUSED(keyModState);
SG_UNUSED(keyModState); if (_condition && !_condition->test()) {
if (!_repeatable) return;
return; }
if (!_repeatable)
return;
const bool zeroInterval = (_repeatInterval <= 0.0);
if (zeroInterval) {
// fire once per frame
fireBindingList(_bindingsDown);
} else {
_repeatTime += dt; _repeatTime += dt;
while (_repeatInterval < _repeatTime) { while (_repeatInterval < _repeatTime) {
_repeatTime -= _repeatInterval; _repeatTime -= _repeatInterval;
@@ -678,12 +686,18 @@ public:
if (_hasDragged) { if (_hasDragged) {
return; return;
} }
_repeatTime += dt; const bool zeroInterval = (_repeatInterval <= 0.0);
while (_repeatInterval < _repeatTime) { if (zeroInterval) {
_repeatTime -= _repeatInterval; // fire once per frame
fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction); fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
} // of repeat iteration } else {
_repeatTime += dt;
while (_repeatInterval < _repeatTime) {
_repeatTime -= _repeatInterval;
fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
} // of repeat iteration
}
} }
bool hover( const osg::Vec2d& windowPos, const Info& ) override bool hover( const osg::Vec2d& windowPos, const Info& ) override
+5 -4
View File
@@ -490,7 +490,8 @@ sgLoad3DModel_internal(const SGPath& path,
} }
} }
if (GlobalParticleCallback::getEnabled()){//dbOptions->getPluginStringData("SimGear::PARTICLESYSTEM") != "OFF") { auto particlesManager = ParticlesGlobalManager::instance();
if (particlesManager->isEnabled()) { //dbOptions->getPluginStringData("SimGear::PARTICLESYSTEM") != "OFF") {
std::vector<SGPropertyNode_ptr> particle_nodes; std::vector<SGPropertyNode_ptr> particle_nodes;
particle_nodes = props->getChildren("particlesystem"); particle_nodes = props->getChildren("particlesystem");
for (unsigned i = 0; i < particle_nodes.size(); ++i) { for (unsigned i = 0; i < particle_nodes.size(); ++i) {
@@ -503,9 +504,9 @@ sgLoad3DModel_internal(const SGPath& path,
options2->setDatabasePath(texturepath.utf8Str()); options2->setDatabasePath(texturepath.utf8Str());
} }
group->addChild(Particles::appendParticles(particle_nodes[i], group->addChild(particlesManager->appendParticles(particle_nodes[i],
prop_root, prop_root,
options2.get())); options2.get()));
} }
} }
+1 -1
View File
@@ -80,7 +80,7 @@ void SGText::UpdateCallback::operator()(osg::Node * node, osg::NodeVisitor *nv )
// be lazy and set the text only if the property has changed. // be lazy and set the text only if the property has changed.
// update() computes the glyph representation which looks // update() computes the glyph representation which looks
// more expensive than a the above string compare. // more expensive than a the above string compare.
text->setText( buf ); text->setText( buf, osgText::String::ENCODING_UTF8 );
text->update(); text->update();
} }
traverse( node, nv ); traverse( node, nv );
+1 -9
View File
@@ -1135,16 +1135,8 @@ void SpinAnimCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
double intPart; double intPart;
double rot = modf(rotation, &intPart); double rot = modf(rotation, &intPart);
double angle = rot * 2.0 * osg::PI; double angle = rot * 2.0 * osg::PI;
const SGVec3d& sgcenter = transform->getCenter(); transform->setAngleRad(angle);
const SGVec3d& sgaxis = transform->getAxis();
Matrixd mat = Matrixd::translate(-sgcenter[0], -sgcenter[1], -sgcenter[2])
* Matrixd::rotate(angle, sgaxis[0], sgaxis[1], sgaxis[2])
* Matrixd::translate(sgcenter[0], sgcenter[1], sgcenter[2])
* *cv->getModelViewMatrix();
ref_ptr<RefMatrix> refmat = new RefMatrix(mat);
cv->pushModelViewMatrix(refmat.get(), transform->getReferenceFrame());
traverse(transform, nv); traverse(transform, nv);
cv->popModelViewMatrix();
} else { } else {
traverse(transform, nv); traverse(transform, nv);
} }
+482 -125
View File
@@ -17,9 +17,12 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
// //
#ifdef HAVE_CONFIG_H #include <simgear_config.h>
# include <simgear_config.h>
#endif #include "particles.hxx"
#include <mutex>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx> #include <simgear/props/props.hxx>
@@ -33,62 +36,306 @@
#include <osgParticle/MultiSegmentPlacer> #include <osgParticle/MultiSegmentPlacer>
#include <osgParticle/SectorPlacer> #include <osgParticle/SectorPlacer>
#include <osgParticle/ConstantRateCounter> #include <osgParticle/ConstantRateCounter>
#include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/ParticleSystem> #include <osgParticle/ParticleSystem>
#include <osgParticle/FluidProgram> #include <osgParticle/FluidProgram>
#include <osgUtil/CullVisitor>
#include <osg/Geode> #include <osg/Geode>
#include <osg/Group> #include <osg/Group>
#include <osg/MatrixTransform> #include <osg/MatrixTransform>
#include <osg/Node> #include <osg/Node>
#include "particles.hxx"
#include <simgear/scene/model/animation.hxx>
using ParticleSystemRef = osg::ref_ptr<osgParticle::ParticleSystem>;
namespace simgear namespace simgear
{ {
void GlobalParticleCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
enabled = !enabledNode || enabledNode->getBoolValue();
if (!enabled)
return;
SGQuatd q
= SGQuatd::fromLonLatDeg(modelRoot->getFloatValue("/position/longitude-deg",0),
modelRoot->getFloatValue("/position/latitude-deg",0));
osg::Matrix om(toOsg(q));
osg::Vec3 v(0,0,9.81);
gravity = om.preMult(v);
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
const osg::Vec3& zUpWind = Particles::getWindVector();
osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
wind = om.preMult(w);
// SG_LOG(SG_PARTICLES, SG_ALERT, class ParticlesGlobalManager::ParticlesGlobalManagerPrivate : public osg::NodeCallback
// "wind vector:" << w[0] << "," <<w[1] << "," << w[2]); {
public:
ParticlesGlobalManagerPrivate();
void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
// only call this with the lock held!
osg::Group* internalGetCommonRoot();
void updateParticleSystemsFromCullCallback(int currentFrameNumber, osg::NodeVisitor* nv);
void addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame);
void registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps);
void registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame);
std::mutex _lock;
bool _frozen = false;
double _simulationDt = 0.0;
osg::ref_ptr<osg::Group> _commonRoot;
osg::ref_ptr<osg::Geode> _commonGeode;
osg::Vec3 _wind;
bool _enabled = true;
osg::Vec3 _gravity;
// osg::Vec3 _localWind;
SGGeod _currentPosition;
SGConstPropertyNode_ptr _enabledNode;
using ParticleSystemWeakRef = osg::observer_ptr<osgParticle::ParticleSystem>;
using ParticleSystemsWeakRefVec = std::vector<ParticleSystemWeakRef>;
using ParticleSystemsStrongRefVec = std::vector<ParticleSystemRef>;
ParticleSystemsWeakRefVec _systems;
osg::ref_ptr<ParticlesGlobalManager::UpdaterCallback> _cullCallback;
using GroupRefVec = std::vector<osg::ref_ptr<osg::Group>>;
GroupRefVec _newWorldParticles;
};
/**
@brief this class replaces the need to use osgParticle::ParticleSystemUpdater, which has some
thread-safety and ownership complications in our us case
*/
class ParticlesGlobalManager::UpdaterCallback : public osg::NodeCallback
{
public:
UpdaterCallback(ParticlesGlobalManagerPrivate* p) : _manager(p)
{
}
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
{
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
if (cv && nv->getFrameStamp()) {
if (_frameNumber < nv->getFrameStamp()->getFrameNumber()) {
_frameNumber = nv->getFrameStamp()->getFrameNumber();
_manager->updateParticleSystemsFromCullCallback(_frameNumber, nv);
}
}
// note, callback is responsible for scenegraph traversal so
// they must call traverse(node,nv) to ensure that the
// scene graph subtree (and associated callbacks) are traversed.
traverse(node, nv);
}
unsigned int _frameNumber = 0;
ParticlesGlobalManagerPrivate* _manager;
};
/**
single-shot node callback, used to register a particle system with the global manager. Once run, removes
itself. We used this to avoid updating a particle system until the load is complete and merged into the
main scene.
*/
class ParticlesGlobalManager::RegistrationCallback : public osg::NodeCallback
{
public:
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
{
auto d = ParticlesGlobalManager::instance()->d;
d->addParticleSystem(_system, _frame);
node->removeUpdateCallback(this); // suicide
}
ParticleSystemRef _system;
osg::ref_ptr<osg::Group> _frame;
};
ParticlesGlobalManager::ParticlesGlobalManagerPrivate::ParticlesGlobalManagerPrivate() : _commonGeode(new osg::Geode),
_cullCallback(new UpdaterCallback(this))
{
// callbacks are registered in initFromMainThread : depending on timing,
// this constructor might be called from an osgDB thread
} }
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame)
//static members
osg::Vec3 GlobalParticleCallback::gravity;
osg::Vec3 GlobalParticleCallback::wind;
bool GlobalParticleCallback::enabled = true;
SGConstPropertyNode_ptr GlobalParticleCallback::enabledNode = 0;
osg::ref_ptr<osg::Group> Particles::commonRoot;
osg::ref_ptr<osgParticle::ParticleSystemUpdater> Particles::psu = new osgParticle::ParticleSystemUpdater;
osg::ref_ptr<osg::Geode> Particles::commonGeode = new osg::Geode;
osg::Vec3 Particles::_wind;
bool Particles::_frozen = false;
Particles::Particles() :
useGravity(false),
useWind(false)
{ {
std::lock_guard<std::mutex> g(_lock);
_systems.push_back(ps);
// we're inside an update callback here, so better not modify the
// scene structure; defer it to later
if (frame.get()) {
_newWorldParticles.push_back(frame);
}
}
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps)
{
auto cb = new RegistrationCallback;
cb->_system = ps;
node->addUpdateCallback(cb);
}
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame)
{
auto cb = new RegistrationCallback;
cb->_system = ps;
cb->_frame = frame;
node->addUpdateCallback(cb);
}
// this is called from the main thread during scenery init
// after this, we beign updarting particle systems
void ParticlesGlobalManager::initFromMainThread()
{
std::lock_guard<std::mutex> g(d->_lock);
d->internalGetCommonRoot();
d->_commonRoot->addUpdateCallback(d.get());
d->_commonRoot->setCullingActive(false);
d->_commonRoot->addCullCallback(d->_cullCallback);
}
void ParticlesGlobalManager::update(double dt, const SGGeod& pos)
{
std::lock_guard<std::mutex> g(d->_lock);
d->_simulationDt = dt;
d->_currentPosition = pos;
for (auto f : d->_newWorldParticles) {
d->internalGetCommonRoot()->addChild(f);
}
d->_newWorldParticles.clear();
}
// this is called from the main thread, since it's an update callback
// lock any state used by updateParticleSystemsFromCullCallback, which
// runs during culling, potentialy on a different thread
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
std::lock_guard<std::mutex> g(_lock);
_enabled = !_enabledNode || _enabledNode->getBoolValue();
if (!_enabled)
return;
const auto q = SGQuatd::fromLonLat(_currentPosition);
osg::Matrix om(toOsg(q));
osg::Vec3 v(0, 0, 9.81);
_gravity = om.preMult(v);
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
// const osg::Vec3& zUpWind = _wind;
// osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
// _localWind = om.preMult(w);
// while we have the lock, remove all expired systems
auto firstInvalid = std::remove_if(_systems.begin(), _systems.end(), [](const ParticleSystemWeakRef& weak) {
return !weak.valid();
});
_systems.erase(firstInvalid, _systems.end());
}
// only call this with the lock held!
osg::Group* ParticlesGlobalManager::ParticlesGlobalManagerPrivate::internalGetCommonRoot()
{
if (!_commonRoot.valid()) {
_commonRoot = new osg::Group;
_commonRoot->setName("common particle system root");
_commonGeode->setName("common particle system geode");
_commonRoot->addChild(_commonGeode);
_commonRoot->setNodeMask(~simgear::MODELLIGHT_BIT);
}
return _commonRoot.get();
}
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::updateParticleSystemsFromCullCallback(int frameNumber, osg::NodeVisitor* nv)
{
ParticleSystemsStrongRefVec activeSystems;
double dt = 0.0;
// begin locked section
{
std::lock_guard<std::mutex> g(_lock);
activeSystems.reserve(_systems.size());
for (const auto& psref : _systems) {
ParticleSystemRef owningRef;
if (!psref.lock(owningRef)) {
// pointed to system is gone, skip it
// we will clean these up in the update callback, don't
// worry about that here
continue;
}
// add to the list we will update now
activeSystems.push_back(owningRef);
}
dt = _simulationDt;
} // of locked section
// from here on, don't access class data; copy it all to local variables
// before this line. this is important so we're not holding _lock during
// culling, which might block osgDB threads for example.
if (dt <= 0.0) {
return;
}
for (const auto& ps : activeSystems) {
// code inside here is copied from osgParticle::ParticleSystemUpdate
osgParticle::ParticleSystem::ScopedWriteLock lock(*(ps->getReadWriteMutex()));
// We need to allow at least 2 frames difference, because the particle system's lastFrameNumber
// is updated in the draw thread which may not have completed yet.
if (!ps->isFrozen() &&
(!ps->getFreezeOnCull() || ((frameNumber - ps->getLastFrameNumber()) <= 2))) {
ps->update(dt, *nv);
}
// end of code copied from osgParticle::ParticleSystemUpdate
}
}
static std::mutex static_managerLock;
static std::unique_ptr<ParticlesGlobalManager> static_instance;
ParticlesGlobalManager* ParticlesGlobalManager::instance()
{
std::lock_guard<std::mutex> g(static_managerLock);
if (!static_instance) {
static_instance.reset(new ParticlesGlobalManager);
}
return static_instance.get();
}
void ParticlesGlobalManager::clear()
{
std::lock_guard<std::mutex> g(static_managerLock);
static_instance.reset();
}
ParticlesGlobalManager::ParticlesGlobalManager() : d(new ParticlesGlobalManagerPrivate)
{
}
bool ParticlesGlobalManager::isEnabled() const
{
std::lock_guard<std::mutex> g(d->_lock);
return d->_enabled;
}
bool ParticlesGlobalManager::isFrozen() const
{
std::lock_guard<std::mutex> g(d->_lock);
return d->_frozen;
}
osg::Vec3 ParticlesGlobalManager::getWindVector() const
{
std::lock_guard<std::mutex> g(d->_lock);
return d->_wind;
} }
template <typename Object> template <typename Object>
class PointerGuard{ class PointerGuard{
public: public:
PointerGuard() : _ptr(0) {}
Object* get() { return _ptr; } Object* get() { return _ptr; }
Object* operator () () Object* operator () ()
{ {
@@ -97,24 +344,9 @@ public:
return _ptr; return _ptr;
} }
private: private:
Object* _ptr; Object* _ptr = nullptr;
}; };
osg::Group* Particles::getCommonRoot()
{
if(!commonRoot.valid())
{
SG_LOG(SG_PARTICLES, SG_DEBUG, "Particle common root called.");
commonRoot = new osg::Group;
commonRoot.get()->setName("common particle system root");
commonGeode.get()->setName("common particle system geode");
commonRoot.get()->addChild(commonGeode.get());
commonRoot.get()->addChild(psu.get());
commonRoot->setNodeMask( ~simgear::MODELLIGHT_BIT );
}
return commonRoot.get();
}
void transformParticles(osgParticle::ParticleSystem* particleSys, void transformParticles(osgParticle::ParticleSystem* particleSys,
const osg::Matrix& mat) const osg::Matrix& mat)
{ {
@@ -129,10 +361,182 @@ void transformParticles(osgParticle::ParticleSystem* particleSys,
} }
} }
osg::Group * Particles::appendParticles(const SGPropertyNode* configNode, void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
SGPropertyNode* modelRoot, {
const osgDB::Options* auto globalManager = ParticlesGlobalManager::instance();
options)
//SG_LOG(SG_PARTICLES, SG_ALERT, "callback!\n");
particleSys->setFrozen(globalManager->isFrozen());
using namespace osg;
if (shooterValue)
shooter->setInitialSpeedRange(shooterValue->getValue(),
(shooterValue->getValue() + shooterExtraRange));
if (counterValue)
counter->setRateRange(counterValue->getValue(),
counterValue->getValue() + counterExtraRange);
else if (counterCond)
counter->setRateRange(counterStaticValue,
counterStaticValue + counterStaticExtraRange);
if (!globalManager->isEnabled() || (counterCond && !counterCond->test()))
counter->setRateRange(0, 0);
bool colorchange = false;
for (int i = 0; i < 8; ++i) {
if (colorComponents[i]) {
staticColorComponents[i] = colorComponents[i]->getValue();
colorchange = true;
}
}
if (colorchange)
particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
if (startSizeValue)
startSize = startSizeValue->getValue();
if (endSizeValue)
endSize = endSizeValue->getValue();
if (startSizeValue || endSizeValue)
particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
if (lifeValue)
particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
if (particleFrame.valid()) {
MatrixList mlist = node->getWorldMatrices();
if (!mlist.empty()) {
const Matrix& particleMat = particleFrame->getMatrix();
Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
Vec3d displace = emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
particleMat(3, 2));
if (displace * displace > 10000.0 * 10000.0) {
// Make new frame for particle system, coincident with
// the emitter frame, but oriented with local Z.
SGGeod geod = SGGeod::fromCart(toSG(emitOrigin));
Matrix newParticleMat = makeZUpFrame(geod);
Matrix changeParticleFrame = particleMat * Matrix::inverse(newParticleMat);
particleFrame->setMatrix(newParticleMat);
transformParticles(particleSys.get(), changeParticleFrame);
}
}
}
if (program.valid() && useWind)
program->setWind(globalManager->getWindVector());
}
void Particles::setupShooterSpeedData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot)
{
shooterValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if (!shooterValue) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: shooter property error!\n");
}
shooterExtraRange = configNode->getFloatValue("extrarange", 0);
}
void Particles::setupCounterData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot)
{
counterValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if (!counterValue) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "counter property error!\n");
}
counterExtraRange = configNode->getFloatValue("extrarange", 0);
}
void Particles::Particles::setupCounterCondition(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot)
{
counterCond = sgReadCondition(modelRoot, configNode);
}
void Particles::setupCounterCondition(float aCounterStaticValue,
float aCounterStaticExtraRange)
{
counterStaticValue = aCounterStaticValue;
counterStaticExtraRange = aCounterStaticExtraRange;
}
void Particles::setupStartSizeData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot)
{
startSizeValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if (!startSizeValue) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: startSizeValue error!\n");
}
}
void Particles::setupEndSizeData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot)
{
endSizeValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if (!endSizeValue) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: startSizeValue error!\n");
}
}
void Particles::setupLifeData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot)
{
lifeValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if (!lifeValue) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: lifeValue error!\n");
}
}
void Particles::setupColorComponent(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot, int color,
int component)
{
SGSharedPtr<SGExpressiond> colorValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(),
SGLimitsd::max());
if (!colorValue) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: color property error!\n");
}
colorComponents[(color * 4) + component] = colorValue;
//number of color components = 4
}
void Particles::setupStaticColorComponent(float r1, float g1, float b1, float a1,
float r2, float g2, float b2, float a2)
{
staticColorComponents[0] = r1;
staticColorComponents[1] = g1;
staticColorComponents[2] = b1;
staticColorComponents[3] = a1;
staticColorComponents[4] = r2;
staticColorComponents[5] = g2;
staticColorComponents[6] = b2;
staticColorComponents[7] = a2;
}
void ParticlesGlobalManager::setWindVector(const osg::Vec3& wind)
{
std::lock_guard<std::mutex> g(d->_lock);
d->_wind = wind;
}
void ParticlesGlobalManager::setWindFrom(const double from_deg, const double speed_kt)
{
double map_rad = -from_deg * SG_DEGREES_TO_RADIANS;
double speed_mps = speed_kt * SG_KT_TO_MPS;
std::lock_guard<std::mutex> g(d->_lock);
d->_wind[0] = cos(map_rad) * speed_mps;
d->_wind[1] = sin(map_rad) * speed_mps;
d->_wind[2] = 0.0;
}
osg::Group* ParticlesGlobalManager::getCommonRoot()
{
std::lock_guard<std::mutex> g(d->_lock);
return d->internalGetCommonRoot();
}
osg::ref_ptr<osg::Group> ParticlesGlobalManager::appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options)
{ {
SG_LOG(SG_PARTICLES, SG_DEBUG, SG_LOG(SG_PARTICLES, SG_DEBUG,
"Setting up a particle system." << std::boolalpha "Setting up a particle system." << std::boolalpha
@@ -152,7 +556,7 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
<< "\n Wind: " << configNode->getChild("program")->getBoolValue("wind", true) << "\n Wind: " << configNode->getChild("program")->getBoolValue("wind", true)
<< std::noboolalpha); << std::noboolalpha);
osgParticle::ParticleSystem *particleSys; osg::ref_ptr<osgParticle::ParticleSystem> particleSys;
//create a generic particle system //create a generic particle system
std::string type = configNode->getStringValue("type", "normal"); std::string type = configNode->getStringValue("type", "normal");
@@ -160,11 +564,10 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
particleSys = new osgParticle::ParticleSystem; particleSys = new osgParticle::ParticleSystem;
else else
particleSys = new osgParticle::ConnectedParticleSystem; particleSys = new osgParticle::ConnectedParticleSystem;
//may not be used depending on the configuration //may not be used depending on the configuration
PointerGuard<Particles> callback; PointerGuard<Particles> callback;
getPSU()->addParticleSystem(particleSys);
getPSU()->setUpdateCallback(new GlobalParticleCallback(modelRoot));
//contains counter, placer and shooter by default //contains counter, placer and shooter by default
osgParticle::ModularEmitter* emitter = new osgParticle::ModularEmitter; osgParticle::ModularEmitter* emitter = new osgParticle::ModularEmitter;
@@ -172,7 +575,7 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
// Set up the alignment node ("stolen" from animation.cxx) // Set up the alignment node ("stolen" from animation.cxx)
// XXX Order of rotations is probably not correct. // XXX Order of rotations is probably not correct.
osg::MatrixTransform *align = new osg::MatrixTransform; osg::ref_ptr<osg::MatrixTransform> align = new osg::MatrixTransform;
osg::Matrix res_matrix; osg::Matrix res_matrix;
res_matrix.makeRotate( res_matrix.makeRotate(
configNode->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS, configNode->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
@@ -187,12 +590,8 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
configNode->getFloatValue("offsets/y-m", 0.0), configNode->getFloatValue("offsets/y-m", 0.0),
configNode->getFloatValue("offsets/z-m", 0.0)); configNode->getFloatValue("offsets/z-m", 0.0));
align->setMatrix(res_matrix * tmat); align->setMatrix(res_matrix * tmat);
align->setName("particle align"); align->setName("particle align");
//if (dynamic_cast<CustomModularEmitter*>(emitter)==0) SG_LOG(SG_PARTICLES, SG_ALERT, "observer error\n");
//align->addObserver(dynamic_cast<CustomModularEmitter*>(emitter));
align->addChild(emitter); align->addChild(emitter);
//this name can be used in the XML animation as if it was a submodel //this name can be used in the XML animation as if it was a submodel
@@ -209,7 +608,6 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
osg::Geode* g = new osg::Geode; osg::Geode* g = new osg::Geode;
g->addDrawable(particleSys); g->addDrawable(particleSys);
callback()->particleFrame->addChild(g); callback()->particleFrame->addChild(g);
getCommonRoot()->addChild(callback()->particleFrame.get());
} }
std::string textureFile; std::string textureFile;
if (configNode->hasValue("texture")) { if (configNode->hasValue("texture")) {
@@ -505,66 +903,25 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
emitter->setUpdateCallback(callback.get()); emitter->setUpdateCallback(callback.get());
} }
if (attach == "local") {
d->registerNewLocalParticleSystem(align, particleSys);
} else {
d->registerNewWorldParticleSystem(align, particleSys, callback()->particleFrame);
}
return align; return align;
} }
void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv) void ParticlesGlobalManager::setSwitchNode(const SGPropertyNode* n)
{ {
//SG_LOG(SG_PARTICLES, SG_ALERT, "callback!\n"); std::lock_guard<std::mutex> g(d->_lock);
this->particleSys->setFrozen(_frozen); d->_enabledNode = n;
using namespace osg;
if (shooterValue)
shooter->setInitialSpeedRange(shooterValue->getValue(),
(shooterValue->getValue()
+ shooterExtraRange));
if (counterValue)
counter->setRateRange(counterValue->getValue(),
counterValue->getValue() + counterExtraRange);
else if (counterCond)
counter->setRateRange(counterStaticValue,
counterStaticValue + counterStaticExtraRange);
if (!GlobalParticleCallback::getEnabled() || (counterCond && !counterCond->test()))
counter->setRateRange(0, 0);
bool colorchange=false;
for (int i = 0; i < 8; ++i) {
if (colorComponents[i]) {
staticColorComponents[i] = colorComponents[i]->getValue();
colorchange=true;
}
}
if (colorchange)
particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4( Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
if (startSizeValue)
startSize = startSizeValue->getValue();
if (endSizeValue)
endSize = endSizeValue->getValue();
if (startSizeValue || endSizeValue)
particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
if (lifeValue)
particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
if (particleFrame.valid()) {
MatrixList mlist = node->getWorldMatrices();
if (!mlist.empty()) {
const Matrix& particleMat = particleFrame->getMatrix();
Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
Vec3d displace
= emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
particleMat(3, 2));
if (displace * displace > 10000.0 * 10000.0) {
// Make new frame for particle system, coincident with
// the emitter frame, but oriented with local Z.
SGGeod geod = SGGeod::fromCart(toSG(emitOrigin));
Matrix newParticleMat = makeZUpFrame(geod);
Matrix changeParticleFrame
= particleMat * Matrix::inverse(newParticleMat);
particleFrame->setMatrix(newParticleMat);
transformParticles(particleSys.get(), changeParticleFrame);
}
}
}
if (program.valid() && useWind)
program->setWind(_wind);
} }
void ParticlesGlobalManager::setFrozen(bool b)
{
std::lock_guard<std::mutex> g(d->_lock);
d->_frozen = b;
}
} // namespace simgear } // namespace simgear
+64 -186
View File
@@ -1,5 +1,5 @@
// particles.hxx - classes to manage particles // particles.hxx - classes to manage particles
// Copyright (C) 2008 Tiago Gusmão // Copyright (C) 2008 Tiago Gusm�o
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as // modify it under the terms of the GNU General Public License as
@@ -41,7 +41,6 @@ class NodeVisitor;
namespace osgParticle namespace osgParticle
{ {
class ParticleSystem; class ParticleSystem;
class ParticleSystemUpdater;
} }
#include <simgear/scene/util/SGNodeMasks.hxx> #include <simgear/scene/util/SGNodeMasks.hxx>
@@ -53,133 +52,37 @@ class ParticleSystemUpdater;
#include <simgear/math/SGMatrix.hxx> #include <simgear/math/SGMatrix.hxx>
// Has anyone done anything *really* stupid, like making min and max macros?
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#include "animation.hxx"
namespace simgear namespace simgear
{ {
class GlobalParticleCallback : public osg::NodeCallback class ParticlesGlobalManager;
{
public:
GlobalParticleCallback(const SGPropertyNode* modelRoot)
{
this->modelRoot=modelRoot;
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
static const osg::Vec3 &getGravityVector()
{
return gravity;
}
static const osg::Vec3 &getWindVector()
{
return wind;
}
static void setSwitch(const SGPropertyNode* n)
{
enabledNode = n;
}
static bool getEnabled()
{
return enabled;
}
private:
static osg::Vec3 gravity;
static osg::Vec3 wind;
SGConstPropertyNode_ptr modelRoot;
static SGConstPropertyNode_ptr enabledNode;
static bool enabled;
};
class Particles : public osg::NodeCallback class Particles : public osg::NodeCallback
{ {
public: public:
Particles(); Particles() = default;
static osg::Group * appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options); void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
void setupShooterSpeedData(const SGPropertyNode* configNode, void setupShooterSpeedData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) SGPropertyNode* modelRoot);
{
shooterValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if(!shooterValue){
SG_LOG(SG_GENERAL, SG_ALERT, "shooter property error!\n");
}
shooterExtraRange = configNode->getFloatValue("extrarange",0);
}
void setupCounterData(const SGPropertyNode* configNode, void setupCounterData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) SGPropertyNode* modelRoot);
{
counterValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if(!counterValue){
SG_LOG(SG_GENERAL, SG_ALERT, "counter property error!\n");
}
counterExtraRange = configNode->getFloatValue("extrarange",0);
}
void setupCounterCondition(const SGPropertyNode* configNode, void setupCounterCondition(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) SGPropertyNode* modelRoot);
{
counterCond = sgReadCondition(modelRoot, configNode);
}
void setupCounterCondition(float counterStaticValue, void setupCounterCondition(float counterStaticValue,
float counterStaticExtraRange) float counterStaticExtraRange);
{
this->counterStaticValue = counterStaticValue;
this->counterStaticExtraRange = counterStaticExtraRange;
}
void setupStartSizeData(const SGPropertyNode* configNode, void setupStartSizeData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) SGPropertyNode* modelRoot);
{
startSizeValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if(!startSizeValue)
{
SG_LOG(SG_GENERAL, SG_ALERT, "startSizeValue error!\n");
}
}
void setupEndSizeData(const SGPropertyNode* configNode, void setupEndSizeData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) SGPropertyNode* modelRoot);
{
endSizeValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if(!endSizeValue){
SG_LOG(SG_GENERAL, SG_ALERT, "startSizeValue error!\n");
}
}
void setupLifeData(const SGPropertyNode* configNode, void setupLifeData(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) SGPropertyNode* modelRoot);
{
lifeValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(), SGLimitsd::max());
if(!lifeValue){
SG_LOG(SG_GENERAL, SG_ALERT, "lifeValue error!\n");
}
}
void setupStaticSizeData(float startSize, float endSize) void setupStaticSizeData(float startSize, float endSize)
{ {
@@ -212,60 +115,14 @@ public:
//component: r=0, g=1, ... ; color: 0=start color, 1=end color //component: r=0, g=1, ... ; color: 0=start color, 1=end color
void setupColorComponent(const SGPropertyNode* configNode, void setupColorComponent(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot, int color, SGPropertyNode* modelRoot, int color,
int component) int component);
{
SGExpressiond *colorValue = read_value(configNode, modelRoot, "-m",
-SGLimitsd::max(),
SGLimitsd::max());
if(!colorValue){
SG_LOG(SG_GENERAL, SG_ALERT, "color property error!\n");
}
colorComponents[(color*4)+component] = colorValue;
//number of color components = 4
}
void setupStaticColorComponent(float r1,float g1, float b1, float a1, void setupStaticColorComponent(float r1, float g1, float b1, float a1,
float r2, float g2, float b2, float a2) float r2, float g2, float b2, float a2);
{
staticColorComponents[0] = r1;
staticColorComponents[1] = g1;
staticColorComponents[2] = b1;
staticColorComponents[3] = a1;
staticColorComponents[4] = r2;
staticColorComponents[5] = g2;
staticColorComponents[6] = b2;
staticColorComponents[7] = a2;
}
static osg::Group * getCommonRoot();
static osg::Geode * getCommonGeode()
{
return commonGeode.get();
}
static osgParticle::ParticleSystemUpdater * getPSU()
{
return psu.get();
}
static void setFrozen(bool e) { _frozen = e; }
/**
* Set and get the wind vector for particles in the
* atmosphere. This vector is in the Z-up Y-north frame, and the
* magnitude is the velocity in meters per second.
*/
static void setWindVector(const osg::Vec3& wind) { _wind = wind; }
static void setWindFrom(const double from_deg, const double speed_kt) {
double map_rad = -from_deg * SG_DEGREES_TO_RADIANS;
double speed_mps = speed_kt * SG_KT_TO_MPS;
_wind[0] = cos(map_rad) * speed_mps;
_wind[1] = sin(map_rad) * speed_mps;
_wind[2] = 0.0;
}
static const osg::Vec3& getWindVector() { return _wind; }
protected: protected:
friend class ParticlesGlobalManager;
float shooterExtraRange; float shooterExtraRange;
float counterExtraRange; float counterExtraRange;
SGSharedPtr<SGExpressiond> shooterValue; SGSharedPtr<SGExpressiond> shooterValue;
@@ -285,39 +142,60 @@ protected:
osg::ref_ptr<osgParticle::ParticleSystem> particleSys; osg::ref_ptr<osgParticle::ParticleSystem> particleSys;
osg::ref_ptr<osgParticle::FluidProgram> program; osg::ref_ptr<osgParticle::FluidProgram> program;
osg::ref_ptr<osg::MatrixTransform> particleFrame; osg::ref_ptr<osg::MatrixTransform> particleFrame;
bool useGravity; bool useGravity = false;
bool useWind; bool useWind = false;
static bool _frozen;
static osg::ref_ptr<osgParticle::ParticleSystemUpdater> psu;
static osg::ref_ptr<osg::Group> commonRoot;
static osg::ref_ptr<osg::Geode> commonGeode;
static osg::Vec3 _wind;
}; };
}
#endif
class ParticlesGlobalManager
/*
class CustomModularEmitter : public osgParticle::ModularEmitter, public osg::Observer
{ {
public: public:
~ParticlesGlobalManager() = default;
virtual void objectDeleted (void *) static ParticlesGlobalManager* instance();
{ static void clear();
SG_LOG(SG_GENERAL, SG_ALERT, "object deleted!\n");
SGParticles::getCommonRoot()->removeChild(this->getParticleSystem()->getParent(0)); bool isEnabled() const;
SGParticles::getPSU()->removeParticleSystem(this->getParticleSystem());
}
osg::ref_ptr<osg::Group> appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options);
// ~CustomModularEmitter() osg::Group* getCommonRoot();
// { osg::Geode* getCommonGeode();
// SG_LOG(SG_GENERAL, SG_ALERT, "object deleted!\n");
// void initFromMainThread();
// SGParticles::getCommonRoot()->removeChild(this->getParticleSystem()->getParent(0));
// SGParticles::getPSU()->removeParticleSystem(this->getParticleSystem()); void setFrozen(bool e);
// } bool isFrozen() const;
/**
@brief update function: call from the main thread , outside of OSG calls.
*/
void update(double dt, const SGGeod& pos);
void setSwitchNode(const SGPropertyNode* n);
/**
* Set and get the wind vector for particles in the
* atmosphere. This vector is in the Z-up Y-north frame, and the
* magnitude is the velocity in meters per second.
*/
void setWindVector(const osg::Vec3& wind);
void setWindFrom(const double from_deg, const double speed_kt);
osg::Vec3 getWindVector() const;
private:
ParticlesGlobalManager();
class ParticlesGlobalManagerPrivate;
class UpdaterCallback;
class RegistrationCallback;
// because Private inherits NodeCallback, we need to own it
// via an osg::ref_ptr
osg::ref_ptr<ParticlesGlobalManagerPrivate> d;
}; };
*/
} // namespace simgear
#endif
+1 -1
View File
@@ -294,7 +294,7 @@ ReaderWriterSPT::createTree(const BucketBox& bucketBox, const LocalOptions& opti
osg::ref_ptr<osg::Node> osg::ref_ptr<osg::Node>
ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const LocalOptions& options) const ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const LocalOptions& options) const
{ {
osg::PagedLOD* pagedLOD = new osg::PagedLOD; osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD;
pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER); pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER);
SGSpheref sphere = bucketBox.getBoundingSphere(); SGSpheref sphere = bucketBox.getBoundingSphere();
+33 -15
View File
@@ -212,7 +212,8 @@ struct ReaderWriterSTG::_ModelBin {
STGObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD())); STGObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
quadtree.buildQuadTree(_objectStaticList.begin(), _objectStaticList.end()); quadtree.buildQuadTree(_objectStaticList.begin(), _objectStaticList.end());
osg::ref_ptr<osg::Group> group = quadtree.getRoot(); osg::ref_ptr<osg::Group> group = quadtree.getRoot();
group->setName("STG-group-A"); string group_name = string("STG-group-A ").append(_bucket.gen_index_str());
group->setName(group_name);
group->setDataVariance(osg::Object::STATIC); group->setDataVariance(osg::Object::STATIC);
simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center()); simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center());
@@ -221,29 +222,41 @@ struct ReaderWriterSTG::_ModelBin {
if (signBuilder.getSignsGroup()) if (signBuilder.getSignsGroup())
group->addChild(signBuilder.getSignsGroup()); group->addChild(signBuilder.getSignsGroup());
if (_buildingList.size() > 0) { if (!_buildingList.empty()) {
SGMaterialLibPtr matlib = _options->getMaterialLib(); SGMaterialLibPtr matlib = _options->getMaterialLib();
bool useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON"); bool useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
if (!matlib) { if (!matlib) {
SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get materials definition for buildings"); SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get materials definition for buildings");
} else { } else {
for (std::list<_BuildingList>::iterator i = _buildingList.begin(); i != _buildingList.end(); ++i) { for (const auto& b : _buildingList) {
// Build buildings for each list of buildings // Build buildings for each list of buildings
SGGeod geodPos = SGGeod::fromDegM(i->_lon, i->_lat, 0.0); SGGeod geodPos = SGGeod::fromDegM(b._lon, b._lat, 0.0);
SGMaterial* mat = matlib->find(i->_material_name, geodPos); SGSharedPtr<SGMaterial> mat = matlib->find(b._material_name, geodPos);
SGPath path = SGPath(i->_filename);
// trying to avoid crash on null material, see:
// https://sentry.io/organizations/flightgear/issues/1867075869
if (!mat) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Building specifies unknown material: " << b._material_name);
continue;
}
const auto path = SGPath(b._filename);
SGBuildingBin* buildingBin = new SGBuildingBin(path, mat, useVBOs); SGBuildingBin* buildingBin = new SGBuildingBin(path, mat, useVBOs);
SGBuildingBinList buildingBinList; SGBuildingBinList bbList;
buildingBinList.push_back(buildingBin); bbList.push_back(buildingBin);
osg::MatrixTransform* matrixTransform; osg::MatrixTransform* matrixTransform;
matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev))); matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(b._lon, b._lat, b._elev)));
matrixTransform->setName("rotateBuildings"); matrixTransform->setName("rotateBuildings");
matrixTransform->setDataVariance(osg::Object::STATIC); matrixTransform->setDataVariance(osg::Object::STATIC);
matrixTransform->addChild(createRandomBuildings(buildingBinList, osg::Matrix::identity(), _options)); matrixTransform->addChild(createRandomBuildings(bbList, osg::Matrix::identity(), _options));
group->addChild(matrixTransform); group->addChild(matrixTransform);
std::for_each(bbList.begin(), bbList.end(), [](SGBuildingBin* bb) {
delete bb;
});
} }
} }
} }
@@ -574,10 +587,12 @@ struct ReaderWriterSTG::_ModelBin {
{ {
osg::ref_ptr<SGReaderWriterOptions> options; osg::ref_ptr<SGReaderWriterOptions> options;
options = SGReaderWriterOptions::copyOrCreate(opt); options = SGReaderWriterOptions::copyOrCreate(opt);
float pagedLODExpiry = atoi(options->getPluginStringData("SimGear::PAGED_LOD_EXPIRY").c_str());
osg::ref_ptr<osg::Group> terrainGroup = new osg::Group; osg::ref_ptr<osg::Group> terrainGroup = new osg::Group;
terrainGroup->setDataVariance(osg::Object::STATIC); terrainGroup->setDataVariance(osg::Object::STATIC);
terrainGroup->setName("terrain"); std::string terrain_name = string("terrain ").append(bucket.gen_index_str());
terrainGroup->setName(terrain_name);
if (_foundBase) { if (_foundBase) {
for (auto stgObject : _objectList) { for (auto stgObject : _objectList) {
@@ -625,11 +640,13 @@ struct ReaderWriterSTG::_ModelBin {
} else { } else {
osg::PagedLOD* pagedLOD = new osg::PagedLOD; osg::PagedLOD* pagedLOD = new osg::PagedLOD;
pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER); pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
pagedLOD->setName("pagedObjectLOD"); std::string name = string("pagedObjectLOD ").append(bucket.gen_index_str());
pagedLOD->setName(name);
// This should be visible in any case. // This should be visible in any case.
// If this is replaced by some lower level of detail, the parent LOD node handles this. // If this is replaced by some lower level of detail, the parent LOD node handles this.
pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max()); pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max());
pagedLOD->setMinimumExpiryTime(0, pagedLODExpiry);
// we just need to know about the read file callback that itself holds the data // we just need to know about the read file callback that itself holds the data
osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback; osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
@@ -646,10 +663,11 @@ struct ReaderWriterSTG::_ModelBin {
// Objects may end up displayed up to 2x the object range. // Objects may end up displayed up to 2x the object range.
pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough); pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough);
pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), pagedLODExpiry);
pagedLOD->setRadius(SG_TILE_RADIUS); pagedLOD->setRadius(SG_TILE_RADIUS);
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() ); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() );
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough)); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Range: " << (2.0 * _object_range_rough));
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Radius: " << SG_TILE_RADIUS); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Radius: " << SG_TILE_RADIUS);
return pagedLOD; return pagedLOD;
} }
} }
+63 -48
View File
@@ -441,6 +441,10 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
if (hash_pos != std::string::npos) if (hash_pos != std::string::npos)
line.resize(hash_pos); line.resize(hash_pos);
if (line.empty()) {
continue; // skip blank lines
}
// and process further // and process further
std::stringstream in(line); std::stringstream in(line);
@@ -462,9 +466,17 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
// F is the number of floors (integer) // F is the number of floors (integer)
// WT is the texture index to use (integer) for walls. Buildings with the same WT value will have the same wall texture assigned. There are 6 small, 6 medium and 4 large textures. // WT is the texture index to use (integer) for walls. Buildings with the same WT value will have the same wall texture assigned. There are 6 small, 6 medium and 4 large textures.
// RT is the texture index to use (integer) for roofs. Buildings with the same RT value will have the same roof texture assigned. There are 6 small, 6 medium and 4 large textures. // RT is the texture index to use (integer) for roofs. Buildings with the same RT value will have the same roof texture assigned. There are 6 small, 6 medium and 4 large textures.
float x, y, z, r, w, d, h, p; float x = 0.0f, y = 0.0f, z = 0.0f, r = 0.0f, w = 0.0f, d = 0.0f, h = 0.0f, p = 0.0f;
int b, s, o, f, wt, rt; int b = 0, s = 0, o = 0, f = 0, wt = 0, rt = 0;
in >> x >> y >> z >> r >> b >> w >> d >> h >> p >> s >> o >> f >> wt >> rt; in >> x >> y >> z >> r >> b;
if (in.bad() || in.fail()) {
SG_LOG(SG_TERRAIN, SG_WARN, "Error parsing build entry in: " << absoluteFileName << " line: \"" << line << "\"");
continue;
}
// these might fail, so check them after we look at failbit
in >> w >> d >> h >> p >> s >> o >> f >> wt >> rt;
//SG_LOG(SG_TERRAIN, SG_ALERT, "Building entry " << x << " " << y << " " << z << " " << b ); //SG_LOG(SG_TERRAIN, SG_ALERT, "Building entry " << x << " " << y << " " << z << " " << b );
SGVec3f loc = SGVec3f(x,y,z); SGVec3f loc = SGVec3f(x,y,z);
@@ -489,14 +501,20 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
}; };
// Set up the building set based on the material definitions // Set up the building set based on the material definitions
SGBuildingBin::SGBuildingBin(const SGMaterial *mat, bool useVBOs) { SGBuildingBin::SGBuildingBin(const SGMaterial* mat, bool useVBOs) : _material(const_cast<SGMaterial*>(mat))
material_name = new std::string(mat->get_names()[0]); {
SG_LOG(SG_TERRAIN, SG_DEBUG, "Building material " << material_name); const auto& materialNames = mat->get_names();
material = mat; if (materialNames.empty()) {
texture = new std::string(mat->get_building_texture()); SG_LOG(SG_TERRAIN, SG_DEV_ALERT, "SGBuildingBin: material has zero names defined");
lightMap = new std::string(mat->get_building_lightmap()); } else {
buildingRange = mat->get_building_range(); _materialName = materialNames.front();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Building texture " << texture); SG_LOG(SG_TERRAIN, SG_DEBUG, "Building material " << _materialName);
}
_textureName = mat->get_building_texture();
_lightMapName = mat->get_building_lightmap();
buildingRange = mat->get_building_range();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Building texture " << _textureName);
} }
SGBuildingBin::~SGBuildingBin() { SGBuildingBin::~SGBuildingBin() {
@@ -644,15 +662,15 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
if (buildingtype == SGBuildingBin::SMALL) { if (buildingtype == SGBuildingBin::SMALL) {
// Small building // Small building
// Maximum number of floors is 3, and maximum width/depth is 192m. // Maximum number of floors is 3, and maximum width/depth is 192m.
width = material->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_small_max_width() - material->get_building_small_min_width()); width = _material->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_small_max_width() - _material->get_building_small_min_width());
depth = material->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_small_max_depth() - material->get_building_small_min_depth()); depth = _material->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_small_max_depth() - _material->get_building_small_min_depth());
floors = SGMisc<double>::round(material->get_building_small_min_floors() + mt_rand(&seed) * (material->get_building_small_max_floors() - material->get_building_small_min_floors())); floors = SGMisc<double>::round(_material->get_building_small_min_floors() + mt_rand(&seed) * (_material->get_building_small_max_floors() - _material->get_building_small_min_floors()));
height = floors * (2.8 + mt_rand(&seed)); height = floors * (2.8 + mt_rand(&seed));
// Small buildings are never deeper than they are wide. // Small buildings are never deeper than they are wide.
if (depth > width) { depth = width; } if (depth > width) { depth = width; }
pitch_height = (mt_rand(&seed) < material->get_building_small_pitch()) ? 3.0 : 0.0; pitch_height = (mt_rand(&seed) < _material->get_building_small_pitch()) ? 3.0 : 0.0;
if (pitch_height == 0.0) { if (pitch_height == 0.0) {
roof_shape = 0; roof_shape = 0;
@@ -663,18 +681,18 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
} }
} else if (buildingtype == SGBuildingBin::MEDIUM) { } else if (buildingtype == SGBuildingBin::MEDIUM) {
// MEDIUM BUILDING // MEDIUM BUILDING
width = material->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_medium_max_width() - material->get_building_medium_min_width()); width = _material->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_medium_max_width() - _material->get_building_medium_min_width());
depth = material->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_medium_max_depth() - material->get_building_medium_min_depth()); depth = _material->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_medium_max_depth() - _material->get_building_medium_min_depth());
floors = SGMisc<double>::round(material->get_building_medium_min_floors() + mt_rand(&seed) * (material->get_building_medium_max_floors() - material->get_building_medium_min_floors())); floors = SGMisc<double>::round(_material->get_building_medium_min_floors() + mt_rand(&seed) * (_material->get_building_medium_max_floors() - _material->get_building_medium_min_floors()));
height = floors * (2.8 + mt_rand(&seed)); height = floors * (2.8 + mt_rand(&seed));
while ((height > width) && (floors > material->get_building_medium_min_floors())) { while ((height > width) && (floors > _material->get_building_medium_min_floors())) {
// Ensure that medium buildings aren't taller than they are wide // Ensure that medium buildings aren't taller than they are wide
floors--; floors--;
height = floors * (2.8 + mt_rand(&seed)); height = floors * (2.8 + mt_rand(&seed));
} }
pitch_height = (mt_rand(&seed) < material->get_building_medium_pitch()) ? 3.0 : 0.0; pitch_height = (mt_rand(&seed) < _material->get_building_medium_pitch()) ? 3.0 : 0.0;
if (pitch_height == 0.0) { if (pitch_height == 0.0) {
roof_shape = 0; roof_shape = 0;
@@ -685,11 +703,11 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
} }
} else { } else {
// LARGE BUILDING // LARGE BUILDING
width = material->get_building_large_min_width() + mt_rand(&seed) * (material->get_building_large_max_width() - material->get_building_large_min_width()); width = _material->get_building_large_min_width() + mt_rand(&seed) * (_material->get_building_large_max_width() - _material->get_building_large_min_width());
depth = material->get_building_large_min_depth() + mt_rand(&seed) * (material->get_building_large_max_depth() - material->get_building_large_min_depth()); depth = _material->get_building_large_min_depth() + mt_rand(&seed) * (_material->get_building_large_max_depth() - _material->get_building_large_min_depth());
floors = SGMisc<double>::round(material->get_building_large_min_floors() + mt_rand(&seed) * (material->get_building_large_max_floors() - material->get_building_large_min_floors())); floors = SGMisc<double>::round(_material->get_building_large_min_floors() + mt_rand(&seed) * (_material->get_building_large_max_floors() - _material->get_building_large_min_floors()));
height = floors * (2.8 + mt_rand(&seed)); height = floors * (2.8 + mt_rand(&seed));
pitch_height = (mt_rand(&seed) < material->get_building_large_pitch()) ? 3.0 : 0.0; pitch_height = (mt_rand(&seed) < _material->get_building_large_pitch()) ? 3.0 : 0.0;
if (pitch_height == 0.0) { if (pitch_height == 0.0) {
roof_shape = 0; roof_shape = 0;
@@ -718,36 +736,35 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
} }
SGBuildingBin::BuildingType SGBuildingBin::getBuildingType(float roll) { SGBuildingBin::BuildingType SGBuildingBin::getBuildingType(float roll) {
if (roll < _material->get_building_small_fraction()) {
return SGBuildingBin::SMALL;
}
if (roll < material->get_building_small_fraction()) { if (roll < (_material->get_building_small_fraction() + _material->get_building_medium_fraction())) {
return SGBuildingBin::SMALL; return SGBuildingBin::MEDIUM;
} }
if (roll < (material->get_building_small_fraction() + material->get_building_medium_fraction())) {
return SGBuildingBin::MEDIUM;
}
return SGBuildingBin::LARGE; return SGBuildingBin::LARGE;
} }
float SGBuildingBin::getBuildingMaxRadius(BuildingType type) { float SGBuildingBin::getBuildingMaxRadius(BuildingType type) {
if (type == SGBuildingBin::SMALL) return material->get_building_small_max_width(); if (type == SGBuildingBin::SMALL) return _material->get_building_small_max_width();
if (type == SGBuildingBin::MEDIUM) return material->get_building_medium_max_width(); if (type == SGBuildingBin::MEDIUM) return _material->get_building_medium_max_width();
if (type == SGBuildingBin::LARGE) return material->get_building_large_max_width(); if (type == SGBuildingBin::LARGE) return _material->get_building_large_max_width();
return 0; return 0;
} }
float SGBuildingBin::getBuildingMaxDepth(BuildingType type) { float SGBuildingBin::getBuildingMaxDepth(BuildingType type) {
if (type == SGBuildingBin::SMALL) return material->get_building_small_max_depth(); if (type == SGBuildingBin::SMALL) return _material->get_building_small_max_depth();
if (type == SGBuildingBin::MEDIUM) return material->get_building_medium_max_depth(); if (type == SGBuildingBin::MEDIUM) return _material->get_building_medium_max_depth();
if (type == SGBuildingBin::LARGE) return material->get_building_large_max_depth(); if (type == SGBuildingBin::LARGE) return _material->get_building_large_max_depth();
return 0; return 0;
} }
ref_ptr<Group> SGBuildingBin::createBuildingsGroup(Matrix transInv, const SGReaderWriterOptions* options) ref_ptr<Group> SGBuildingBin::createBuildingsGroup(Matrix transInv, const SGReaderWriterOptions* options)
{ {
ref_ptr<Effect> effect; ref_ptr<Effect> effect;
EffectMap::iterator iter = buildingEffectMap.find(*texture); auto iter = buildingEffectMap.find(_textureName);
if ((iter == buildingEffectMap.end())|| if ((iter == buildingEffectMap.end())||
(!iter->second.lock(effect))) (!iter->second.lock(effect)))
@@ -756,16 +773,14 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
makeChild(effectProp, "inherits-from")->setStringValue("Effects/building"); makeChild(effectProp, "inherits-from")->setStringValue("Effects/building");
SGPropertyNode* params = makeChild(effectProp, "parameters"); SGPropertyNode* params = makeChild(effectProp, "parameters");
// Main texture - n=0 // Main texture - n=0
params->getChild("texture", 0, true)->getChild("image", 0, true) params->getChild("texture", 0, true)->getChild("image", 0, true)->setStringValue(_textureName);
->setStringValue(*texture);
// Light map - n=3 // Light map - n=3
params->getChild("texture", 3, true)->getChild("image", 0, true) params->getChild("texture", 3, true)->getChild("image", 0, true)->setStringValue(_lightMapName);
->setStringValue(*lightMap);
effect = makeEffect(effectProp, true, options); effect = makeEffect(effectProp, true, options);
if (iter == buildingEffectMap.end()) if (iter == buildingEffectMap.end())
buildingEffectMap.insert(EffectMap::value_type(*texture, effect)); buildingEffectMap.insert(EffectMap::value_type(_textureName, effect));
else else
iter->second = effect; // update existing, but empty observer iter->second = effect; // update existing, but empty observer
} }

Some files were not shown because too many files have changed in this diff Show More