Compare commits

..

231 Commits

Author SHA1 Message Date
Torsten Dreyer
2a73e6c0d5 new version: 2017.3.1 2017-09-17 12:14:00 +02:00
Richard Harrison
16b5dd5e78 Remove message "Could not find at least one of the following objects for axis animation" when not relevant.
By default you can either do

1. axis -> object-name ...
2. axis -> x-m ...
3. omit axis. This will now try to find an 'object-name' with -axis appended. If this can't be found then no message will be output; and the default behaviour of just using (0,0,0) will appyl.
2017-08-31 00:30:18 +02:00
James Turner
1aba20d642 Merge /u/gallaert/simgear/ branch next into next
https://sourceforge.net/p/flightgear/simgear/merge-requests/38/
2017-08-18 22:25:48 +00:00
Florent Rougon
27786d709d EmbeddedResourceManager: better API with respect to rehashing
Add a rehash() method to EmbeddedResourceManager::Impl to update
'poolSearchList'. Introduce a 'dirty' flag so that
EmbeddedResourceManager::Impl::rehash() is automatically called whenever
needed. It is not necessary anymore to call
EmbeddedResourceManager::selectLocale() after adding resources: changing
the EmbeddedResourceManager's locale or adding resources are both
operations that set the dirty flag. Whenever someone tries to fetch a
resource for the selected locale and the dirty flag is set, a rehash is
triggered before the actual fetching so as to ensure it is correct.
2017-08-16 23:49:19 +02:00
gallaert
e0e6a29150 CMake fails to detect OpenRTI include files. 2017-08-16 21:15:20 +01:00
Torsten Dreyer
dfd6076e19 Fix a warning 2017-08-12 22:34:52 +02:00
Alessandro Menti
e589ef8627 Remove the .spec file
Remove the .spec file as it is unused and we leave the packaging to
upstream (as it has been done with FlightGear).
2017-08-06 15:08:11 +02:00
Stuart Buchanan
e8c1baa396 Clear Uniform cache on reset. 2017-08-01 21:23:35 +01:00
James Turner
69ba2617d2 Fix thumbnail loading on Windows 2017-07-28 12:05:35 +01:00
James Turner
34c215ad83 Persistent thumbnail cache for aircraft packages
This should reduce the load on Ibiblio when browsing aircraft, the
package system caches thumbnails for seven days. Also we detect for
certain failure conditions and retry, to deal with Ibiblio’s rate-
limiting mechanism.
2017-07-28 11:38:59 +01:00
James Turner
2a3bb62001 Add efficient hasTag check to Package.
Avoids copying the tags set each time to check for presence / absence
of a particular tag.
2017-07-27 23:26:06 +01:00
James Turner
fe46ae09ef Fix MSVC build.
MSVC really needs these methods to be out of line, to avoid generating code for the shared pointers
in each translation unit which includes the header.
2017-07-19 13:00:33 +01:00
Ganael Laplanche
46f67fce7a Remove dependency to TR1.
TR1 will be deprecated in Boost 1.65, see:
https://github.com/boostorg/admin/issues/123
2017-07-17 14:49:51 +02:00
Torsten Dreyer
629e68428f Fix SimGear test target for change in commandmgr api 2017-07-07 08:56:13 +02:00
Richard Harrison
100439aadd Model relative property tree root binding.
This fixes the animation bindings to use the defined property tree root - to support multiplayer (or other) model that can bind to the correct part of the property tree.

Requires a corresponding fix in fg to allow the command methods to take an optional root parameter.

What this means is that when inside someone else's multiplayer model (e.g. backseat, or co-pilot), the multipalyer (AI) model will correctly modify properties inside the correct part of the property tree inside (/ai), rather than modifying the properties inside the same part of the tree as the non-ai model.

This means that a properly setup model will operate within it's own space in the property tree; and permit more generic multiplayer code to be written.

This is probably responsible for some of the pollution of the /sim property tree with MP aircraft properties.
2017-07-05 12:05:01 +02:00
Stuart Buchanan
f6a348ba94 Enable display lists for random trees. 2017-07-04 21:45:56 +01:00
James Turner
a54b3ffcee Extend Package delegate callbacks.
This makes it easier to report the status in the launcher, at least.
2017-06-28 22:27:40 +01:00
James Turner
47842c9ea7 Work-around older Curl on macOS 10.8n and earlier 2017-06-28 10:18:35 +01:00
Richard Harrison
834e25521b Axis based animation use ObjectName-axis as default if nothing specified. 2017-06-25 09:25:06 +02:00
James Turner
f78597f010 Merge /u/reavertm/simgear/ branch next into next
https://sourceforge.net/p/flightgear/simgear/merge-requests/35/
2017-06-21 10:52:24 +00:00
James Turner
c06e433e74 Templated lookup of subsystem-group members. 2017-06-15 18:30:27 +01:00
Maciej Mrozowski
9971d517fd Link shared SimGearScene with libgdal when enabled 2017-06-15 04:58:03 +02:00
Florent Rougon
f8548029a2 EmbeddedResourceManager.hxx: refine the previous MSVC workaround
The previous fix was successful, but a priori doesn't need to be applied
to *all* compilers that define _WIN32. Apply it only when _MSC_VER is
defined, and only to the template instantiations that MSVC doesn't
recognize (i.e., those that use a parameter from the variadic template).
2017-06-11 17:30:58 +02:00
Florent Rougon
3e337e3e97 Fix build errors related to template instantiations on Windows
The MS compiler doesn't seem to recognize our templates here:

g:\jenkins\workspace\simgear-win\source\simgear\embedded_resources\EmbeddedResourceManager.hxx(169): error C3190: 'std::shared_ptr<const simgear::AbstractEmbeddedResource> simgear::EmbeddedResourceManager::getResource(const std::string &,const std::string &) const' with the provided template arguments is not the explicit instantiation of any member function of 'simgear::EmbeddedResourceManager' (compiling source file G:\Jenkins\workspace\SimGear-Win\source\simgear\embedded_resources\EmbeddedResourceManager.cxx) [G:\Jenkins\workspace\SimGear-Win\build32\simgear\SimGearCore.vcxproj]
g:\jenkins\workspace\simgear-win\source\simgear\embedded_resources\EmbeddedResourceManager.hxx(169): error C2945: explicit instantiation does not refer to a template-class specialization (compiling source file G:\Jenkins\workspace\SimGear-Win\source\simgear\embedded_resources\EmbeddedResourceManager.cxx) [G:\Jenkins\workspace\SimGear-Win\build32\simgear\SimGearCore.vcxproj]

-> try without the explicit instantiations on Windows.
2017-06-11 10:29:43 +02:00
Florent Rougon
e59f8eda74 Embedded resources system: SimGear part
Add the EmbeddedResourceManager class as well as
AbstractEmbeddedResource and two derived concrete classes:
RawEmbeddedResource and ZlibEmbeddedResource.

The purpose of this is to provide a way for FlightGear to use data from
files without relying on FG_ROOT to be set. The whole system (SimGear
and FlightGear parts) was described in detail at [1]. I'll probably
include a copy in $FG_ROOT/Docs too for fear of the link becoming dead
one day.

Basically, classes derived from AbstractEmbeddedResource provide access
to some data---the source of which is a priori of static storage
class---and handle the conversion from whatever format it is stored in
to allow convenient use of said data. At the very least, they allow
obtaining ready-to-use data as an std::string, as well as reading it
incrementally via an std::streambuf or an std::istream interface.
ZlibEmbeddedResource instances also provide access to the compressed
size of the data (i.e., as stored in static memory) as well as its
uncompressed size, without requiring any prior decompression.

EmbeddedResourceManager is a class which FlightGear will normally
instantiate exactly once---it has createInstance() and instance() static
methods for this. It maintains a map between resource paths and
instances of concrete classes derived from AbstractEmbeddedResource. It
also provides convenience methods allowing to access a resource data in
one step (not requiring to manually fetch the
AbstractEmbeddedResource-derived object corresponding to the given
resource path before calling the appropriate method of this object).

From the EmbeddedResourceManager's point of view, resource paths (keys
of the map) are just plain std::string instances in the current
implementation. However, unless there is a good reason not to, I think
it's a good idea to only use values obtained with SGPath::utf8Str()[2].
This is precisely what fgrcc, the resource compiler in the FlightGear
repository, does; so, unless you register resources manually, your
resource paths will automatically comply with this suggestion.

[1] https://sourceforge.net/p/flightgear/mailman/message/35870025/
[2] This allows later addition of methods listing all resources under a
    given virtual path, as well as optimized resource lookup using a
    tree-like data structure instead of an std::unordered_map (not
    justified now IMO).
2017-06-10 23:38:59 +02:00
Florent Rougon
31e70b205c CharArrayStream_test: fix failing test (logic error in the test code) 2017-06-10 20:59:15 +02:00
Florent Rougon
5d02f1db5f CharArrayStream_test.cxx: attempt to fix build errors on Windows 2017-06-10 16:58:16 +02:00
Florent Rougon
b01bd93a20 Add CharArrayStreambuf and related IOStreams classes for working with char arrays
Add the following classes in the 'simgear' namespace:
  - CharArrayStreambuf    subclass of std::streambuf      stream buffer
  - ROCharArrayStreambuf  subclass of CharArrayStreambuf  stream buffer
  - CharArrayIStream      subclass of std::istream        input stream
  - CharArrayOStream      subclass of std::ostream        output stream
  - CharArrayIOStream     subclass of std::iostream       input/output stream

CharArrayStreambuf is a stream buffer class allowing to read from, and
write to char arrays (std::strstream has been deprecated since C++98).
Contrary to std::strstream, this class does no dynamic allocation: it is
very simple, strictly staying for both reads and writes within the
limits of the buffer specified in the constructor. Contrary to
std::stringstream, CharArrayStreambuf allows one to work on an array of
char (that could be static data or on the stack) without having to make
a whole copy of it.

CharArrayStreambuf supports reading and writing (including efficient
implementations of xsgetn() and xsputn()), seeking (with independent
read and write stream pointers, as for std::stringstream) and putting
back chars up to the beginning of the char array. The internal buffer
for both reads and writes is defined to be the whole buffer specified in
the constructor call. As a consequence, flushing the stream buffer with
pubsync() is useless: data is always written directly to the buffer
passed to the constructor, never to an intermediate buffer.

Of course, this buffer must remain available as long as the stream
buffer object is used.

ROCharArrayStreambuf is a read-only subclass of CharArrayStreambuf.
CharArrayIStream, CharArrayOStream and CharArrayIOStream are very simple
convenience stream classes using either CharArrayStreambuf or
ROCharArrayStreambuf as their stream buffer class.
2017-06-10 09:45:15 +02:00
Alessandro Menti
05bbba5074 Remove the last traces of enhanced lighting support
Remove some traces of the enhanced lighting support which were left
there from a refactoring several years ago.

See discussion at <https://sourceforge.net/p/flightgear/mailman/message/35875305/>
2017-06-04 16:51:27 +02:00
Automatic Release Builder
679d4e1dcd new version: 2017.3.0 2017-05-18 15:15:48 +02:00
Automatic Release Builder
aaa6231f89 new version: 2017.2.1 2017-05-18 15:15:48 +02:00
Erik Hofman
446c8cd70c Fix a bug when using properties instead of a fixed position 2017-05-17 08:28:01 +02:00
Richard Harrison
11ff1d256c Fix mesh visibility on pick animations.
(caused by incorrectly overloaded methods)
2017-05-11 19:17:24 +02:00
Richard Harrison
4acd047982 Fix animation axis text for new calling conventions.
NOTE: the transient model data as initialized contains nullptrs for mostly everything - except the required data for this test.
2017-05-11 13:02:40 +02:00
Erik Hofman
ee4b6621e7 Fit the property definied positions to the axis definition by negating them 2017-05-11 10:29:08 +02:00
Richard Harrison
77517b15fd Allow animations to take an object as the centre and axis.
Applicable to / works with rotate, translate and knob. Once the axis object-name has been used it will be hidden, however it still can be used for object-name animations. The hiding of the axis object is a useful guide as to what is left to be wired up to animations.

This allows the follow to work (on a combined ASI / Mach instrument).

 <animation>
    <type>rotate</type>
    <object-name>asi-needle</object-name>
...
    <axis>
      <object-name>airspeed-asi-axis</object-name>
    </axis>
  </animation>
  <animation>
    <type>rotate</type>
    <object-name>asi-mach-scale</object-name>
...
    <axis>
      <object-name>airspeed-asi-axis</object-name>
    </axis>
  </animation>
2017-05-11 06:32:27 +02:00
Florent Rougon
19a86a6f0d Change license from GPL2+ to LGPL2.0+ for the SimGear files I wrote
Change as per the FlightGear Policy Document:

  http://www.flightgear.org/flightgear-policy-document/
2017-05-10 17:03:50 +02:00
Stuart Buchanan
a7dee1164d materials.xml support for osm2city Buildings etc. 2017-05-09 14:30:57 +01:00
Erik Hofman
7512bc0fb2 Make position properties work for relative positions too 2017-05-09 10:57:31 +02:00
Florent Rougon
d6b57d0937 Fix MSVC warning: redefinition of default template parameter
The default values for template parameters don't have to be repeated in
the .cxx file, apparently.
2017-05-03 19:11:30 +02:00
Florent Rougon
1e99c4b2ec Add function template simgear::strutils::readNonNegativeInt()
This function is similar to simgear::strutils::to_int(), except it is:

  - generic: the return type, selected with the first template
    parameter, can be an arbitrary integral type. This type also defines
    the set of accepted input strings ("values").

  - stricter regarding the input: it must be non-empty and contain only
    valid digits for the specified base (second template parameter). If
    the input doesn't conform to these constraints or is too large to
    fit into the specified type, an exception with a suitable error
    message is raised.

  - faster (12 to 17 times as fast as simgear::strutils::to_int() on my
    system, depending on compilation flags): this is probably a
    consequence of not using std::stringstream to do the conversion.

The function template is only instantiated for <int, 10> and
<unsigned int, 10> in order to be sure not to waste memory (see comments
in strutils.cxx). If you need it for other combinations of type and
base, just enable them by adjusting the corresponding '#if 0 / #endif'
pairs in strutils.cxx and strutils_test.cxx.
2017-05-03 16:33:53 +02:00
Torsten Dreyer
04ca3ad8f4 Fix bug "dns timeout, no terrasync servers found"
The existance of a dns entry with a protocol defined caused the
naptr callback being terminated early and the request never completed.
This patch also adds a unit test for this particular case.
test_dns now also accepts up to two command line parameters:
first: dns DN to query for NAPTR records (default: terrasync.flightgear.org)
second: service to query (default: empty)
2017-05-03 10:41:11 +02:00
Florent Rougon
2b8915e35f zlibstream.cxx: explicitly mark as virtual the inherited virtual methods
Since the virtuality of a method is inherited by derived classes, this
makes the virtual status of such methods clearly visible in the
declarations of derived classes.
2017-04-30 16:19:50 +02:00
Florent Rougon
2a0801da6b zlibstream*: use C++11 scoped enumerations instead of C-style enums
These enumerations are those declared with 'enum class' instead of just
'enum' (cf. §7.2 of the C++11 standard).

+ add missing include (<zlib.h>)
2017-04-30 15:45:52 +02:00
Florent Rougon
2935d78490 Fix build failure in strutils_test.cxx
The Windows and Mac builders on jenkins complain that '\U12345678' is
an invalid universal character; let's try with '\U000000E9' instead,
which should be LATIN SMALL LETTER E WITH ACUTE.

Also replace '\uab42' with '\u00e0' to remove a warning on the Windows
builder, due to the fact that '\uab42' cannot be represented in the
current code page (1252). This doesn't mean '\uab42' was incorrect,
though; I'm only changing this to make the warning disappear.
2017-04-28 23:16:15 +02:00
Florent Rougon
2404924bcb Add function strutils::escape()
This function complements the existing strutils::unescape(). It
backslash-escapes a string for C/C++ string literal syntax.

For every std::string s, the following holds:

  unescape(escape(s)) == s

(not the other way around, because there are many ways to escape a given
character in a string literal)
2017-04-28 22:24:21 +02:00
Florent Rougon
e4c4db5cf9 strutils::unescape(): minor change
Use static_cast<char> for clarity when adding an int to an std::string
with std::string::operator+=().
2017-04-28 22:14:52 +02:00
Florent Rougon
e1c655c570 strutils::unescape(): fix handling of hexadecimal escape sequences
Hexadecimal escape sequences have no length limit and terminate at the
first character that is not a valid hexadecimal digit.
2017-04-28 22:14:52 +02:00
Florent Rougon
2788da9c51 strutils::unescape(): fix off-by-one error
An octal escape sequence in a string literal can't have more than 3
octal digits after the backslash. The previous code was using up to 4
digits per octal escape sequence.
2017-04-28 22:14:35 +02:00
Erik Hofman
4860a70443 Fix (hopefully) sim4_t class initialization problems 2017-04-27 14:54:21 +02:00
Florent Rougon
22d1433ab0 argparse.cxx: allow arguments consisting of a single '-'
Such an argument is /a priori/ perfectly valid, and not an option.
Typical use in a command line:

  -o -

equivalent to:

  -o-

The former way wasn't accepted before this commit; now it is (also
'--output -', equivalent to '--output=-').

If the last option is followed by an argument consisting of a single
'-', this argument marks the end of options and is the first non-option
argument (contrary to '--' which, given its special status, would mark
the end of options but would *not* count as a non-option argument).

This commit also adds an std::string length check in
ArgumentParser::parseArgs() that was missing before using operator[]()
(important difference between operator[]() for std::string and std::map
or std::unordered_map!).
2017-04-21 22:30:45 +02:00
Florent Rougon
ef9eedf35a Fix misleading indentation in sg_path.cxx (g++ warning) 2017-04-15 09:33:27 +02:00
Florent Rougon
a962c90b30 Change SGPath::pathListSep into a const char array of size 2
Previously, SGPath::pathListSep was a char in static memory, that could
be followed by anything (often '\0', as it seems... but not always).
This is (was) dangerous, because it is then tempting to take its address
and pass it to functions expecting a char * corresponding to a
null-terminated string (C-style).

SGPath::pathListSep is now a static array of two const chars: the path
list separator followed by a '\0'. This implies that
&SGPath::pathListSep can now be reliably interpreted as a C-style string
of length 1 (not counting the null terminator), containing only the path
list separator.
2017-04-15 09:29:59 +02:00
Florent Rougon
36275f5cce Add classes to ease writing command line parsers with GNU-style options handling
See simgear/misc/argparse.hxx for API and documentation
(simgear/misc/argparse_test.cxx also has examples, although argparse.hxx
features a very simple one at the top).

These classes were also presented in
<https://sourceforge.net/p/flightgear/mailman/message/35785019/>.
2017-04-13 11:04:01 +02:00
James Turner
60d1c87cef New string helper function: property path matching
Match property path strings against template strings containing wild
card characters.
2017-04-13 07:29:05 +01:00
Stuart Buchanan
9223f30f08 Improve warning of object in wrong bucket.
Reduce level to DEV_WARN from DEV_ALERT as it's
largely benign.
2017-04-07 21:20:42 +01:00
James Turner
fe87e7f60d Define SG_DEPRECATED, remove unused DEPRECATED.
Use this to mark two Package APIs as deprecated.
2017-04-05 22:09:52 +09:00
James Turner
f3e83cf020 Package APIs to support multiple primary aircraft.
This (with some catalog and front-end changes) will allow packages to
supply multiple primary aircraft.
2017-04-04 07:04:43 +02:00
Richard Harrison
9eee41d74a Add logging support for hexdumps 2017-04-02 17:56:31 +02:00
Florent Rougon
991d76b69e Fix build failure due to missing <osg/Version> include
Failure observed on Debian jessie with openscenegraph 3.2.1-6.
2017-04-01 15:58:07 +02:00
Richard Harrison
6f0c7da6ad OSG 3.5.x support 2017-03-30 03:58:27 +02:00
James Turner
c170f576b6 Ensure <simgear_config.h> is always included.
This is going to become important soon - prep for enabling SIMGEAR_SHARED on Windows.
2017-03-28 09:36:53 +01:00
Erik Hofman
1446f559cc Add -fPIC to make FlightGear's shared library linking for fgtestlib happy 2017-03-26 10:17:10 +02:00
James Turner
4467e68db1 Make test macros public. 2017-03-24 16:52:02 +00:00
James Turner
cc1118b330 And make Windows happier again. 2017-03-19 12:16:10 +00:00
James Turner
a1126bd42c Make the LogStreamPrivate a d-ptr. 2017-03-19 10:28:57 +00:00
James Turner
370523d5bf Remove sgGMTime, no longer used.
(And the implementation was wrong on Windows)
2017-03-17 23:25:25 +00:00
James Turner
96b4d2c03d Make debugClassToString a public static.
This helps with implementing log-callbacks in other places, which
the unit-testing framework does.
2017-03-17 23:22:59 +00:00
James Turner
c4898502bf Use std::unique_ptr to avoid leaking logging
This makes calling shutdownLogging unnecessary on exit.
2017-03-17 23:22:59 +00:00
Florent Rougon
f7a511d1b3 Fix includes in zlibstream.hxx
Forward declarations of std::streambuf and std::istream are not enough,
since this file declares classes derived from them.
2017-03-14 20:24:32 +01:00
Florent Rougon
b66c51a6f8 Fix handling of SG_LOG()'s second argument
The popup/no popup logic in SG_LOG() could be wrong before this commit,
because of missing parentheses around uses of the second macro argument.
For instance, this:

  SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "Message");

could cause a popup window to be displayed even though neither SG_WARN
nor SG_ALERT should do that in the current state of the logging system.

Thanks to Szymon Acedański for finding this.
2017-03-09 10:25:40 +01:00
Torsten Dreyer
a4cf38925b move TS dns lookup into the worker thread 2017-03-07 11:32:39 +01:00
Torsten Dreyer
41059a24a7 Fix indention, no functional change 2017-03-07 09:22:05 +01:00
Erik Hofman
ee4f5a5190 Disable SIMD for now as a test. 2017-02-28 12:42:48 +01:00
James Turner
141e98564c Generic string -> bool parser. 2017-02-27 23:11:05 +00:00
James Turner
72341a6de4 Adjust some messages to be developer-mode.
Still deciding how far to go with this, comments welcome.
2017-02-27 00:13:39 +00:00
Bertrand Coconnier
55ee59ac99 Flags ENABLE_GDAL and ENABLE_OPENMP are now carried over to FlightGear. 2017-02-26 16:39:48 +01:00
Peter Sadrozinski
61525c555e Disable GDAL, and compiling new terrain engine by default 2017-02-26 09:46:29 -05:00
Peter Sadrozinski
dad30b0cc2 alternative terrain engine - SGMesh utilizing pagedLOD 2017-02-25 19:17:55 -05:00
James Turner
9c9e4e86e7 Packages allows thumbnails per variant.
Change API for thumbnail access, so each variant can have a unique thumbnail alongside other data. Map existing catalog XML format to
this system.
2017-02-25 20:52:35 +00:00
James Turner
b88aa46e1c Merge /u/accek/simgear/ branch prop-strings into next
https://sourceforge.net/p/flightgear/simgear/merge-requests/32/
2017-02-25 16:36:49 +00:00
Bertrand Coconnier
10fa8a471a SGPath::set is called by SGPath::operator=(const char*p) so it needs to be unconditionnally compiled. 2017-02-25 16:02:36 +01:00
Szymon Acedański
4e875be0dd Added SGStringValueMethods property implementation
This is to be used in places where SGRawValueMethods returning .c_str()
from local strings were used, causing use-after-free later.
2017-02-25 15:02:37 +01:00
Bertrand Coconnier
23cc940743 gcc fails to catch std::ios_base::failure due to an inconsistent C++11 ABI between headers and libraries. See bug#66145 for more details (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145). 2017-02-25 14:38:38 +01:00
Florent Rougon
4b010cc416 Minor change to simgear/io/iostreams/zlibstream_test.cxx 2017-02-25 11:30:16 +01:00
Florent Rougon
46f39d5fbd zlibstream classes: add constructors with sink semantic based on std::unique_ptr
Add an alternate constructor to each of the following classes:
ZlibAbstractIStreambuf, ZlibCompressorIStreambuf,
ZlibDecompressorIStreambuf, ZlibCompressorIStream and
ZlibDecompressorIStream. These new constructors are passed the source
std::istream wrapped inside an std::unique_ptr instead of by reference,
and store the unique_ptr object as an instance member. This ensures that
the source std::istream object is available as long as the
ZlibDecompressorIStreambuf, etc. instance is alive (which is necessary
for its getInputData() method) without any additional work for callers,
and that it is automatically destroyed afterwards.

This is particularly useful when writing functions that create and
return an object 'zobj' whose type is a subclass of
ZlibAbstractIStreambuf, when the source std::istream is only of interest
for its role of feeding data to 'zobj'. For instance:

std::unique_ptr<simgear::ZlibCompressorIStream>
myZlibCompressorIStreamProducer(std::string str)
{
  std::unique_ptr<std::istringstream> iss(new std::istringstream(str));

  return std::unique_ptr<simgear::ZlibCompressorIStream>(
    new simgear::ZlibCompressorIStream(std::move(iss))); // new ctor here
}

Callers of such a function get access to a new ZlibCompressorIStream
instance fed by an std::istringstream object ('iss'), but they don't
even have to know this detail, nor to take any measure to ensure that
'iss' lives at least as long as the ZlibCompressorIStream object. The
std::unique_ptr<std::istream> pointing to 'iss' and stored as a member
of the ZlibCompressorIStream object by its constructor automatically
takes care of this lifetime problem.
2017-02-24 22:51:21 +01:00
Florent Rougon
03515151f0 Add new test trying to read max amount with ZlibDecompressorIStreambuf::xsgetn()
New automated test for ZlibDecompressorIStreambuf::xsgetn(). xsgetn() is
called by sgetn() from the base class std::streambuf. In our case,
xsgetn() is actually defined in the base class ZlibAbstractIStreambuf
(subclass of std::streambuf), therefore this new test also applies to
ZlibCompressorIStreambuf and the two other related classes,
ZlibCompressorIStream and ZlibDecompressorIStream.

This test asks [x]sgetn() the largest possible amount of chars every
time it is called, i.e., the largest value that can be represented by
std::streamsize. This exercises the code in interesting ways due to the
various types involved (zlib's uInt, std::size_t and std::streamsize,
which have various sizes depending on the platform).
2017-02-24 22:51:21 +01:00
Florent Rougon
8cd723d91b Fix build errors in zlibstream*.cxx, re-enable their compilation
Compilation of these files was disabled in commit
e21ad4b5c1.

Fix build errors and warnings:

  - Ambiguous template parameters for std::min();

  - No appropriate default constructor available for
    std::basic_istream<char,std::char_traits<char>> (the std::istream
    subclasses didn't explicitly call the std::istream constructor,
    which requires an argument). This is presumably exactly the reason
    why sg_gzifstream is declared like this:

      class sg_gzifstream : private gzifstream_base, public std::istream

    where gzifstream_base is an empty shell for a stream buffer object:

      struct gzifstream_base
      {
          gzifstream_base() {}

          gzfilebuf gzbuf;
      };

    This ensures that the stream buffer object (gzbuf) is initialized
    before std::istream's constructor is called. I solved this problem
    in a different way, hopefully easier to understand, and requiring
    neither an additional class nor multiple inheritance: first, we
    initialize the std::istream base with a nullptr as the
    std::streambuf * argument (this is valid C++11), then in the
    constructor bodies for ZlibCompressorIStream and
    ZlibDecompressorIStream, we call std::istream::rdbuf() to attach the
    std::istream instance to the now-initialized stream buffer object.

  - Possible truncation of constant value on 32 bits systems (this was
    in zlibMaxChunkSize() which is now removed, see below).

Type-related improvements:

  - Remove zlibMaxChunkSize() and zlibMaxChunk: in C++, one can simply
    use std::numeric_limits<uInt>::max()---most of the code in
    zlibMaxChunkSize() was there only to find this value via a zlib
    function call.

  - Add helper function templates zlibChunk() and clipCast().

  - Split preparation of the putback area out of
    ZlibAbstractIStreambuf::xsgetn() to a new utility method:
    xsgetn_preparePutbackArea().

  - More rigorous type handling in zlibstream_test.cxx.

    Some precautions are necessary because the IOStreams API uses
    std::streamsize in many places (e.g., the return value of
    std::istream::gcount()), but functions such as the following
    std::string constructor:

      std::string(const char* s, std::size_t n);

    work with std::size_t instead. Since these types are different and
    opaque, this requires some care!
2017-02-24 22:51:21 +01:00
Torsten Dreyer
6f2943ed9a disable service test for now 2017-02-23 18:34:48 +01:00
Torsten Dreyer
e509fc3f5d Prepare for terrasync/https 2017-02-23 16:35:29 +01:00
James Turner
707d9e12cf Fix issues with package upgrades on Windows. 2017-02-23 13:26:02 +00:00
Automatic Release Builder
9840302931 new version: 2017.2.0 2017-02-20 18:52:12 +01:00
Automatic Release Builder
983047982f new version: 2017.1.1 2017-02-20 18:52:12 +01:00
James Turner
f977be5fe4 Concept for developer log messages. 2017-02-14 18:27:40 -08:00
Florent Rougon
e21ad4b5c1 Disable building of zlibstream.cxx for now
It is unclear to me how to correctly fix some of the remaining build
errors obtained on jenkins, therefore I am disabling it from the build
for now.
2017-02-12 23:55:03 +01:00
Florent Rougon
619055f544 Attempt at fixing build errors for simgear/io/iostreams/zlibstream.cxx
Apparently (on Jenkins), std::unordered_map doesn't like enums as keys.
Too bad, this made the code more compact...
2017-02-12 22:22:08 +01:00
Florent Rougon
c9611fc45b Add stream buffers and std::istream subclasses to deal with zlib compression
Add:
 - two stream buffer classes (ZlibCompressorIStreambuf and
   ZlibDecompressorIStreambuf), both based on the same abstract class:
   ZlibAbstractIStreambuf;
 - two std::istream subclasses (ZlibCompressorIStream and
   ZlibDecompressorIStream), each creating and using the corresponding
   stream buffer class from the previous item.

All these allow one to work with RFC 1950 and RFC 1952 compression
formats, respectively known as the zlib and gzip formats.

These classes are *input* streaming classes, which means they can
efficiently handle arbitrary amounts of data without using any disk
space nor increasing amounts of memory, and allow "client code" to pull
exactly as much data as it wants at any given time, resuming later when
it is ready to handle the next chunk.

See comments in simgear/io/iostreams/zlibstream.hxx for more details.
2017-02-12 21:18:52 +01:00
Florent Rougon
79f869a7f3 Move IOStreams-related files to simgear/io/iostreams; rename zfstream.[ch]xx to gzfstream.[ch]xx
- Rename zfstream.cxx (resp. zfstream.hxx) to gzfstream.cxx (resp.
  gzfstream.hxx)

  This is because these files only deal with the gzip format (RFC 1952),
  while zlib can actually read and write two slightly different formats:
  this one and the "ZLIB Compressed Data Format" (RFC 1950). Since I am
  going to add std::streambuf and std::istream subclasses able to deal
  with both formats (and supporting data sources that are general
  std::istream instances, not just files), this renaming will make
  things a bit clearer, I hope.

- Add new folder simgear/io/iostreams and move the following files to
  this folder:

    simgear/misc/gzcontainerfile.cxx
    simgear/misc/gzcontainerfile.hxx
    simgear/misc/gzfstream.cxx
    simgear/misc/gzfstream.hxx
    simgear/misc/sgstream.cxx
    simgear/misc/sgstream.hxx
    simgear/misc/sgstream_test.cxx

- Adapt other files accordingly (mainly #includes and CMakeLists.txt
  files).
2017-02-12 21:18:52 +01:00
Torsten Dreyer
1b8dfb2bef Accept time token for .dirindex files
print the timestamp on log-level "info"
2017-02-12 17:39:12 +01:00
Richard Harrison
143a47482b Change the log level of the "failed to load sound buffer" as it shouldn't be a popup because within my interpreation it is not within the definition of what POPUP should be used for. 2017-02-11 13:34:01 +01:00
James Turner
a28cf0f860 Additional SGPath test for remove + rename.
Trying to track down failure to update aircraft on Windows.
2017-02-09 18:48:05 +00:00
James Turner
d9f4d7373f SGFile uses wide-string APIs on Windows. 2017-02-07 16:15:14 +00:00
James Turner
c3f48c7261 Use sg_ofstream in one more place. 2017-02-07 16:05:06 +00:00
James Turner
48b7b70e23 BinObj code uses wide-strings on Windows. 2017-02-07 16:05:06 +00:00
James Turner
b93b362e2f Fix wide-string support for wav-file reader. 2017-02-07 16:05:06 +00:00
Bertrand Coconnier
2082b18e2e Export the symbol ENABLE_SIMD for FG to build with the same setting. 2017-02-07 00:02:39 +01:00
Erik Hofman
7f65e7f905 We need to copy the matrix before altering it's contents: Fix a SGMathTest failure 2017-02-03 15:07:35 +01:00
Erik Hofman
3417ca7e49 Add a linear-interpolation function 2017-02-02 11:35:11 +01:00
Richard Senior
6334c30eb6 Allow empty reason string in validation of HTTP response.
HTTP/1.0 and HTTP/1.1 allow the reason string to be empty.
Some servers produce empty reason strings on success,
e.g. "HTTP/1.1 200 ", which throws a "bad HTTP response"
exception.

From the specification:
    "Reason-Phrase  = *<TEXT, excluding CR, LF>"

From notational conventions:
    "*(element) allows any number, including zero"

References:
www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2
www.w3.org/Protocols/HTTP/1.0/spec.html
www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
2017-02-01 10:23:10 +00:00
Erik Hofman
c87dff7e8f More code cleanups ans fix a Clang problem. And disable AVX for good, I don't have time for this crap anymore: it looks like a hardware or compielr bug 2017-01-31 00:04:01 +01:00
Erik Hofman
332f76f34d Last code cleanups and more explicit constructors 2017-01-30 18:25:42 +01:00
Erik Hofman
3387f3d084 Switch to c++11 alignas instead of our own hacks 2017-01-30 16:00:29 +01:00
Erik Hofman
bd421c381c Also make AVX available for simd4x4 2017-01-30 12:53:52 +01:00
Erik Hofman
0cce949837 Fix the last bug that prevented AVX from working properly. Hopefully this fixes all other SIMD related problems too 2017-01-30 12:25:51 +01:00
James Turner
ed3ba67925 Logging system buffers startup messages.
This allows adding log callbacks easier during startup, without 
missing the initial messages, which might be very important.
2017-01-30 07:43:35 +01:00
Erik Hofman
866f85064a Fix a mistake 2017-01-30 00:18:36 +01:00
Erik Hofman
9215c530b3 Explicit declaration of more constructors to make sure the laste lane of simd4_t<T,3> and the last two lanes of simd4_t<T,2> remain zero 2017-01-29 15:26:34 +01:00
James Turner
a6437f4e96 More SGPropertyNode::getPositions tests. 2017-01-28 15:37:41 +00:00
James Turner
5bd7be6ed1 Check for file permissions via SGPath. 2017-01-28 15:37:41 +00:00
Florent Rougon
d9cc3738b9 Improve sgstream_test.cxx
- Create "testfile" in a temporary directory (and make sure the temp dir
  and the file it contains are both removed when the test program exits).

- Use test macros such as SG_CHECK_EQUAL() instead of by-hand checks and
  "return EXIT_FAILURE" statements.

- Use sg_ofstream instead of plain std::ostream. This simplifies things
  a tiny bit, because one SGPath instance is enough to create both the
  sg_ofstream instance and the sg_gzifstream instance used afterwards to
  reread the created file.

- Don't call (sg_)ofstream::close() at the end of the block the instance
  is declared in: this is entirely unnecessary, since sg_ofstream
  derives from std::ofstream, which is automatically close()d when
  destroyed (RAII behavior).
2017-01-26 16:58:20 +01:00
Erik Hofman
edec5bbc01 There is still a segmenttion fault issue with the AVX code, disable it until this is sorted out 2017-01-26 14:46:36 +01:00
Florent Rougon
6a2d86c526 Small improvements for simgear::Dir
- Add method simgear::Dir::isNull(), analogous to SGPath::isNull().

- Make sure that simgear::Dir::tempDir() returns a null simgear::Dir
  instance when creation of the directory failed (so far, this was only
  the case on systems where HAVE_MKDTEMP is defined).

- Use simgear::strutils::error_string() instead of strerror() (the
  latter is not guaranteed to be thread-safe).

- Make sure <cerrno> is #included, regardless of the platform.

- Add automated tests for isNull(), setRemoveOnDestroy() and tempDir().
2017-01-26 12:41:59 +01:00
Stuart Buchanan
6c64e9b36c Add scenery-path-suffix support to terrasync
Remove /sim/rendering/building-mesh as it is
redundant.

Add check that objects are in the correct bounding
box of the containing STG file.
2017-01-21 22:19:38 +00:00
Erik Hofman
bcecee0f76 Small optimization to my own cross product implementation 2017-01-21 10:28:24 +01:00
Richard Harrison
c5cdfa1a1d Prevent null pointer deference when load has failed.
This was triggered by the TU154B; probably related to the console error

* "Image loading failed:Warning: reading "fgdata\AI\Aircraft\tu154b\Model" not supported.
2017-01-21 00:54:30 +01:00
Erik Hofman
f9f2b4cbdb Bring the ARM NEON code in line with the SSE code 2017-01-20 15:32:28 +01:00
Erik Hofman
33feb9a416 Move SIMD flags to CMAKE_CXX_FLAGS_RELEASE and fix a typo in the process 2017-01-20 14:56:15 +01:00
Erik Hofman
9e1aaa8b56 Make a distinction between a null-pointer and the value of 0 2017-01-19 23:14:39 +01:00
Erik Hofman
ef2eb635af Fix a header path 2017-01-19 16:41:01 +01:00
Erik Hofman
2db412a923 Specialize class constructors in the hope to ged rid of wrong compiler assignment of an unaligned float array to an SSE register 2017-01-19 16:39:30 +01:00
Erik Hofman
061fea48c8 Add an ARM NEON readu simd4x4_neon.hxx and small fixes for simd_neon.hxx 2017-01-19 13:20:52 +01:00
Erik Hofman
c4ea62a899 Add a version of simd.hxx for ARM NEON 2017-01-18 15:41:12 +01:00
Erik Hofman
789c09a402 I forgot the linux variant of ALIGN 2017-01-12 14:19:09 +01:00
Erik Hofman
de9b329115 reorganize the code a bit 2017-01-12 11:43:46 +01:00
Erik Hofman
1b793a127c Be more explicit about alignment 2017-01-12 10:18:39 +01:00
James Turner
3cb3084725 Rename ‘new’ thumbnails to ‘previews’
This avoids an XML naming clash, and allows both systems to exist in
parallel peacefully. Update the tests to check both the thumbnails
and preview system in parallel with each other.
2017-01-10 18:05:40 +00:00
Erik Hofman
637f67888a Reinstate AVX support again 2017-01-07 13:55:09 +01:00
Erik Hofman
fbc0986fd8 Do not rely on SSE3, this is just a slight drawback 2017-01-06 15:10:52 +01:00
Erik Hofman
14ebe0b618 Disable AVX support until someone figures out why it is not working properly 2017-01-06 00:11:05 +01:00
James Turner
fd34cc30b8 compare_versions: limit how many parts are checked.
This can be used to only check the first one or two parts of a version,
to ensure only the major, or major+minor parts match.
2017-01-05 10:55:14 +00:00
Erik Hofman
7b0faed03a Fix an AVX error 2017-01-04 16:30:19 +01:00
Erik Hofman
8d1dc30b07 Try to fix a possible AVX core dump 2017-01-04 12:55:28 +01:00
Erik Hofman
03cff6abca Fix Never use <avxintrin.h> directly; include <immintrin.h> instead. 2017-01-01 13:26:36 +01:00
Erik Hofman
d7821324b8 Fix a wrong enclosing bracket 2017-01-01 11:40:59 +01:00
Ron Haertel
f3e066cce0 Bug fix for bug 1920
In pt_lights.cxx in SGLightFactory::getOdal, at segment "centerline lights",
the decrement loop index i is initialized with lights.getNumLights(), then is used at lights.getLight. (ERROR)
The function getNumLights is set to the size of the vector which is one more than the max index refernce.

This restores the behavior that was in place before commit 8ddb99f62f
2016-12-30 18:49:19 -05:00
James Turner
45ac758cc9 Expose position on SGPropertyNode. 2016-12-27 11:09:07 +00:00
Erik Hofman
f0e6402fff One more, and hopefully the last, MSVC fix 2016-12-23 15:31:40 +01:00
Erik Hofman
41bf142e31 Try to fix an MSVC compile error 2016-12-23 14:50:14 +01:00
Erik Hofman
2e9efa98d7 Add accelearated cross product 2016-12-22 13:43:04 +01:00
Erik Hofman
2c2a57f368 Addd support for AVX, 4 double precision operations in one go 2016-12-21 09:04:46 +01:00
Erik Hofman
4ea1326126 A better MSVC fix and code speedups 2016-12-20 09:54:19 +01:00
Erik Hofman
09b44ac68a MSVC compiler fix (hopefully) 2016-12-19 18:37:52 +01:00
Erik Hofman
a48ab434ab Convert more code to SIMD 2016-12-19 15:58:57 +01:00
Florent Rougon
d39a56d4fb Add missing include: <cctype> for unary std::islower()
Thanks to Ron H. for the report.
2016-12-18 11:02:01 +01:00
Erik Hofman
892579456d add SSE support for the C compiler (gcc and clang) 2016-12-18 09:40:38 +01:00
Erik Hofman
4dde1d365c MSVC 2013+ for 64-bit automatically sets /arch:SSE2 and doesn't recognise te option 2016-12-17 10:50:19 +01:00
James Turner
2f21b582cd Canvas image fill and source rect setters.
C++ code can set the fill and source rect directly.
2016-12-16 19:14:44 +00:00
James Turner
b29536f8b7 Set Canvas element transforms from C++
Also allow transforms to be disabled.
2016-12-16 19:14:44 +00:00
James Turner
039f9920db Direct rect support. 2016-12-16 19:14:44 +00:00
James Turner
7c254e9c04 Native SVG path-data parsing in the Canvas.
This substantially cuts down the amount of properties needed when
importing SVG elements into Canvas paths.
2016-12-16 19:14:44 +00:00
Erik Hofman
35a115bfd4 Fix a stupid mistake and move hsum_ps_sse to shVector.c 2016-12-16 16:01:59 +01:00
Erik Hofman
70dd9d35b1 Also accelerate SHVector3 using SIMD 2016-12-16 15:12:53 +01:00
Erik Hofman
31e3cf06fb Accelerate SHVector4 and SHMatrix3x3 operations using SIMD 2016-12-16 14:54:24 +01:00
Erik Hofman
175eddd1fa Add the proper SSE options to Clang too 2016-12-16 11:05:03 +01:00
Erik Hofman
fe73247b82 Really use _mm_setzero_ps9) instead 2016-12-16 11:04:49 +01:00
Erik Hofman
203db3d095 Enable SSE and SSE2 by default 2016-12-16 10:49:44 +01:00
James Turner
8c4695b991 Fix duplicate variants in packages. 2016-12-15 17:37:32 +00:00
James Turner
a2b111bb09 Bugfix: reject dubious paths in HTTP repos.
This avoids a malicious repository writing to files outside the local
storage root.
2016-12-15 17:37:28 +00:00
James Turner
2a1542d544 Helper for parsing CSS/SVG strings. 2016-12-15 17:37:25 +00:00
Erik Hofman
e5b51677c5 MacOS fix 2016-12-15 13:11:31 +01:00
Erik Hofman
f9450d136d Minor speedups in small tweaks 2016-12-15 09:57:10 +01:00
Erik Hofman
0586cb62c3 Fix the pink-sky bug, don't initilize the vector with (z,y,z,w) but use the proper (x,y,z,w) instead 2016-12-15 09:56:52 +01:00
Erik Hofman
8fee04b32b Generalize the code to be able to test integers too 2016-12-14 11:05:56 +01:00
Erik Hofman
04e16c95e2 Consolidate some code, exlicitly use intrinsics (instead of the usual math operators): this fixes a compiler bug for integers and add some SSE4.1 accelerations for integers 2016-12-14 11:05:31 +01:00
Erik Hofman
e257dbe6ed accelerate float rotation_matrix and fix a bug for double rotation_matrix 2016-12-14 11:03:32 +01:00
Erik Hofman
abf78f8e31 Make absolutely sure the unions are 16-bytes aligned, move more setters and min() and max() to the simd implementation and add horizontal SIMD add functions for magnitude2 and dot 2016-12-12 20:27:07 +01:00
Stuart Buchanan
da13bd9f04 Fix minor compiler warnings 2016-12-10 16:00:28 +00:00
Alessandro Menti
86fb1ed00f BoostTestTargets.cmake: fix a wrong include test
BoostTestTargets.cmake requires the CMake-configurable form of the Boost
test framework to be used (i.e. "#include <BoostTestTargetConfig.h>"), and
checks each test source code file for this, outputting a warning in case
this requirement is not met.

Unfortunately, a conditional in the check is badly specified (the CMake
variable syntax is not followed), so the warning is emitted even when it
should not be.

This patch fixes this bug and restores the correct behavior.

Submitted upstream at https://github.com/rpavlik/cmake-modules/pull/44
2016-12-08 11:47:18 +01:00
Erik Hofman
56fb81dc03 Hopefuly the last MSVC fix 2016-12-08 09:39:45 +01:00
Erik Hofman
343ce57468 Tupe conversion is evil, don't allow it 2016-12-08 01:02:44 +01:00
Erik Hofman
3e52e37181 remove explicit, it looks like SMVC chokes on it 2016-12-07 13:22:17 +01:00
Erik Hofman
e768553a4a Replace auto_ptr with unique_ptr 2016-12-07 11:03:49 +01:00
Erik Hofman
b74d1a8351 Add some more accelerated functions (dot, magnitude, matrix transpose), allow simd_t4 with non mathcing elements to be assigned and a bunch of fixes. Make sure the code compiles when SSE is not available. 2016-12-07 10:00:02 +01:00
James Turner
801d8c4af5 Revert recursive listeners for the moment.
Want to verify this is the cause of crashes inside the property code,
since if it's not we have a much bigger problem.

This means all listeners are recursive, with a parent-chain walk on each
setValue call, as has been the case since 'forever'.
2016-12-05 12:57:29 +00:00
Florent Rougon
3a4693803b strutils: new functions stripTrailingNewlines() and stripTrailingNewlines_inplace() 2016-12-04 22:41:12 +01:00
Florent Rougon
79f0d3356e Convert strutils_test.cxx to use the SG test macros instead of BOOST 2016-12-04 22:41:12 +01:00
Florent Rougon
6a1bf02ddb Use appropriate test macros in parse_color_test.cxx and state_machine_test.cxx
- osg::Vec4 objects can't be output to a stream -> use the _NOSTREAM
  variant of the test macro.

- ditto for the NULL... thing -> use SG_CHECK_IS_NULL().
2016-12-04 22:41:12 +01:00
Florent Rougon
6662800deb Expand the test macros framework
- Add an _NOSTREAM variant for each macro (except for SG_TEST_FAIL, that
  doesn't take any argument, and for SG_VERIFY, which doesn't attempt to
  print its argument to a stream---doesn't sound very useful). So, if a
  and b can be output to a stream (without any undesirable side effect,
  of course), use for instance SG_CHECK_EQUAL(a, b); otherwise, use
  SG_CHECK_EQUAL_NOSTREAM(a, b).

- Add test macros SG_CHECK_IS_NULL, SG_CHECK_IS_NULL_NOSTREAM,
  SG_CHECK_IS_NOT_NULL and SG_CHECK_IS_NOT_NULL_NOSTREAM.

- Add a few comments.
2016-12-04 22:41:04 +01:00
Florent Rougon
c9bb6102c0 Test macros: add parentheses around arguments 2016-12-04 21:22:43 +01:00
Florent Rougon
90479419cc Add test macros: SG_CHECK_NE, SG_CHECK_LT, SG_CHECK_LE, SG_CHECK_GT, SG_CHECK_GE 2016-12-04 21:09:39 +01:00
Florent Rougon
b4178ae888 Test macros: be more rigorous and consistent
- Because of possible operator overloading, make SG_CHECK_EQUAL(a, b)
  fail if, and only if (a) == (b) is false (testing if (a) != (b) for
  this macro is not correct in general).

- For clarity and consistency, change the messages printed when some
  tests fail: SG_VERIFY(some_test) prints 'failed: some_test' (okay),
  but SG_CHECK_EQUAL(a, b) used to print 'failed: a != b', which is
  inconsistent. Instead, print: 'failed: a == b' because this is what we
  know that failed (again, because of possible operator overloading,
  pretending we know the the logical value of (a != b) after testing
  (a == b) is not correct in general.

  Similarly, the "approximate equality tests" SG_CHECK_EQUAL_EP() and
  SG_CHECK_EQUAL_EP2() now print something like 'failed: a ~= b' when
  they fail, instead of 'failed with epsilon: a != b'.
2016-12-04 21:09:39 +01:00
Florent Rougon
ab4814c916 simgear/misc/test_macros.hxx: add missing <iostream> header + minor changes
- The header is needed for std::cerr.
- Also improve the messages when tests fail (spacing, and in some cases
  an argument was missing).
- Reorder the macros a tiny bit.
2016-12-04 21:09:39 +01:00
Florent Rougon
6b16f96c8a Test macros: use defensive parentheses and std::fabs() for SG_CHECK_EQUAL_EP*
Some of the modules where I previously removed duplicate private
definitions of these macros had these features, and since they seem wise
to me, I'm applying them to the canonical SG_CHECK_EQUAL_EP() and
SG_CHECK_EQUAL_EP2() macros defined in simgear/misc/test_macros.hxx.
2016-12-04 21:09:39 +01:00
Florent Rougon
e655d41817 Rename the COMPARE, COMPARE_EP, COMPARE_EP2 and VERIFY test macros
COMPARE     -> SG_CHECK_EQUAL
COMPARE_EP  -> SG_CHECK_EQUAL_EP
COMPARE_EP2 -> SG_CHECK_EQUAL_EP2
VERIFY      -> SG_VERIFY

Also remove duplicate private definitions of these macros in test
modules, using instead those defined in simgear/misc/test_macros.hxx.
2016-12-04 21:04:40 +01:00
James Turner
b5c1902a2d Tweaks to recursive listener behaviour 2016-12-03 14:09:56 +00:00
James Turner
d088259739 Add aliased listener test, disabled for now. 2016-12-02 17:15:11 +00:00
James Turner
d8acf44a3a Revert unintended change in listener refactoring. 2016-12-02 17:14:27 +00:00
Torsten Dreyer
4664af12fa Fix lockup on Windows when polling DNS, add test 2016-12-02 14:44:38 +01:00
James Turner
7a909d0c0b Improved Package search function.
Handle or/and groups, and search variant descriptions and names.
2016-11-30 21:32:31 +00:00
James Turner
919c25769c Quiet a canvas message. 2016-11-30 12:18:44 +00:00
James Turner
5a0908d5bb Package::indexOfvariant works on fully-qualified IDs.
Should fix issues restoring variants in the launcher.
2016-11-29 15:36:00 +00:00
Erik Hofman
1c39daec07 Fix an error 2016-11-27 10:19:17 +01:00
James Turner
835ae941ce Unit-tests for localised-variant strings. 2016-11-26 14:28:22 +00:00
James Turner
63edff078f Fix warnings in sgvec4 test.
Use C++ type-overloaded fabs call.
2016-11-26 14:20:17 +00:00
Erik Hofman
0ea9786601 Add SIMD matrix operations 2016-11-26 11:40:01 +01:00
Florent Rougon
9e0bb33d58 Fix missing include in simgear/props/props_test.cxx 2016-11-26 01:12:34 +01:00
James Turner
64531c85e3 Generalise per-variant localised string lookup.
Will be used for per-variant long-descriptions.
2016-11-25 22:44:24 +00:00
James Turner
14cdae5102 Unit-test for copy-installed children. 2016-11-25 22:19:55 +00:00
James Turner
2aaad212e8 Revert partial-update mode for TerraSync repos.
Going to implement this a different, simpler way now,
2016-11-25 22:19:51 +00:00
James Turner
bd88bf1126 Explicit handling of recursive listeners.
Use a different strategy for recursive listeners, and make them opt-in.
2016-11-25 16:07:18 +00:00
James Turner
ea9da65b7c Expanded property unit-tests. 2016-11-25 16:07:18 +00:00
Erik Hofman
e7f80cf5f3 Finish SIMD Vector code, do not change matrix operations yet since it seems to have a problem 2016-11-25 13:31:54 +01:00
Erik Hofman
16d62f93c8 Fix a clang reported error 2016-11-22 15:18:16 +01:00
Erik Hofman
e96834fcc6 Fix an error 2016-11-22 14:36:28 +01:00
Erik Hofman
22a74c63b4 First changes for SIMD matrix operations 2016-11-22 14:27:42 +01:00
James Turner
5681fcbdc5 Unit-tests for property listener. 2016-11-22 00:19:18 +00:00
James Turner
7755f8e094 Override changes for atomic listener. 2016-11-22 00:15:14 +00:00
James Turner
e266e44f63 Extended scenery/STG suffix handling 2016-11-22 00:09:51 +00:00
Erik Hofman
4dc66a385e Include SGMathFwd.hxx to add unknown math types 2016-11-21 09:22:09 +01:00
Erik Hofman
aec29a3a37 Fix clang errors 2016-11-20 23:54:15 +01:00
Erik Hofman
0d213a1990 Fix a typo 2016-11-20 16:16:28 +01:00
Erik Hofman
8162a49f6c Give vector math a (potential) boost. Next stoP; matrices 2016-11-20 16:15:40 +01:00
Torsten Dreyer
d2e2603400 initialize udns_library on first use 2016-11-20 16:12:01 +01:00
Erik Hofman
43a8277bdb Merge branch 'next' of ssh://git.code.sf.net/p/flightgear/simgear into next 2016-11-20 15:01:10 +01:00
Erik Hofman
61a8bd5cd3 _m128d ony holds two doubles instead of 4 (obviously). 2016-11-20 15:00:24 +01:00
Erik Hofman
ad6f3d2db2 Small updates 2016-11-20 14:59:43 +01:00
Torsten Dreyer
9088f41352 Use private udns context for each DNSClient 2016-11-20 12:35:43 +01:00
Erik Hofman
73f57bbbd8 Add a test utility 2016-11-19 15:57:05 +01:00
Erik Hofman
a8673356a2 Be more specific about the exact type 2016-11-19 15:56:06 +01:00
James Turner
5ecc1ab6f2 Simplify timing code.
Assumes we have timegm on Unix, and use _mkgmtime on Windows.
This means we never need to use ftime() / timeb.h
2016-11-17 21:23:56 +01:00
Automatic Release Builder
0702f85540 new version: 2017.1.0 2016-11-17 13:43:29 +01:00
254 changed files with 20245 additions and 3922 deletions

View File

@@ -9,6 +9,17 @@ if(COMMAND cmake_policy)
endif()
endif()
message(STATUS "CMAKE Build type: ${CMAKE_BUILD_TYPE}")
# Set a default build type if none was specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to 'Debug' as none was specified.")
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
"MinSizeRel" "RelWithDebInfo")
endif()
include (CheckFunctionExists)
include (CheckIncludeFile)
include (CheckLibraryExists)
@@ -109,11 +120,16 @@ endif()
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF)
option(ENABLE_GDAL "Set to ON to build SimGear with GDAL support" OFF)
option(ENABLE_TESTS "Set to OFF to disable building SimGear's test applications" ON)
option(ENABLE_SOUND "Set to OFF to disable building SimGear's sound support" ON)
option(USE_AEONWAVE "Set to ON to use AeonWave instead of OpenAL" OFF)
option(ENABLE_PKGUTIL "Set to ON to build the sg_pkgutil application (default)" ON)
option(ENABLE_DNS "Set to ON to use udns library and DNS service resolver" ON)
option(ENABLE_SIMD "Enable SSE/SSE2 support for x86 compilers" ON)
option(ENABLE_OPENMP "Enable OpenMP compiler support" OFF)
include (DetectArch)
# until the fstream fix is applied and generally available in OSG,
# keep the compatability link option as the default
@@ -233,13 +249,12 @@ else()
# declaring symbols as declspec(import)
add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC)
set(EXPAT_INCLUDE_DIRS
${PROJECT_SOURCE_DIR}/3rdparty/expat
${PROJECT_BINARY_DIR}/3rdparty/expat)
${PROJECT_SOURCE_DIR}/3rdparty/expat
${PROJECT_BINARY_DIR}/3rdparty/expat)
endif(SYSTEM_EXPAT)
check_include_file(inttypes.h HAVE_INTTYPES_H)
check_include_file(sys/time.h HAVE_SYS_TIME_H)
check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
check_include_file(unistd.h HAVE_UNISTD_H)
check_include_file(windows.h HAVE_WINDOWS_H)
@@ -249,21 +264,41 @@ if(HAVE_INTTYPES_H)
endif()
if(ENABLE_RTI)
# See if we have any rti library variant installed
message(STATUS "RTI: ENABLED")
find_package(RTI)
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
SET(ENV{PKG_CONFIG_PATH} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
pkg_check_modules(RTI hla-rti13)
endif(PKG_CONFIG_FOUND)
if(RTI_FOUND)
SET(RTI_INCLUDE_DIR "${RTI_INCLUDE_DIRS}")
message(STATUS "RTI: ENABLED")
else()
message(STATUS "RTI: DISABLED")
endif(RTI_FOUND)
else()
message(STATUS "RTI: DISABLED")
endif(ENABLE_RTI)
if(ENABLE_GDAL)
find_package(GDAL 2.0.0 REQUIRED)
if (GDAL_FOUND)
include_directories(${GDAL_INCLUDE_DIR})
endif(GDAL_FOUND)
endif(ENABLE_GDAL)
check_function_exists(gettimeofday HAVE_GETTIMEOFDAY)
check_function_exists(ftime HAVE_FTIME)
check_function_exists(timegm HAVE_TIMEGM)
check_function_exists(rint HAVE_RINT)
check_function_exists(mkdtemp HAVE_MKDTEMP)
check_function_exists(bcopy HAVE_BCOPY)
check_function_exists(mmap HAVE_MMAP)
if (NOT MSVC)
check_function_exists(timegm HAVE_TIMEGM)
if (NOT HAVE_TIMEGM)
message(FATAL_ERROR "Non-Windows platforms must support timegm()")
endif()
endif()
if(HAVE_UNISTD_H)
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_INCLUDE_PATH})
check_cxx_source_compiles(
@@ -317,8 +352,8 @@ if (NOT ${HAVE_STD_ISNAN})
endif()
if(CMAKE_COMPILER_IS_GNUCXX)
set(WARNING_FLAGS_CXX "-Wall")
set(WARNING_FLAGS_C "-Wall")
set(WARNING_FLAGS_CXX "-Wall -fPIC")
set(WARNING_FLAGS_C "-Wall -fPIC")
if (CMAKE_VERSION VERSION_LESS 3.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
@@ -328,6 +363,13 @@ if(CMAKE_COMPILER_IS_GNUCXX)
message(WARNING "GCC 4.4 will be required soon, please upgrade")
endif()
if(ENABLE_SIMD)
if (X86 OR X86_64)
set(CMAKE_C_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse")
endif()
endif()
# certain GCC versions don't provide the atomic builds, and hence
# require is to provide them in SGAtomic.cxx
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_INCLUDE_PATH})
@@ -336,10 +378,10 @@ if(CMAKE_COMPILER_IS_GNUCXX)
GCC_ATOMIC_BUILTINS_FOUND)
endif(CMAKE_COMPILER_IS_GNUCXX)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
if (CLANG)
# Boost redeclares class members
set(WARNING_FLAGS_CXX "-Wall -Wno-overloaded-virtual -Wno-redeclared-class-member")
set(WARNING_FLAGS_C "-Wall")
set(WARNING_FLAGS_CXX "-Wall -fPIC -Wno-overloaded-virtual -Wno-redeclared-class-member")
set(WARNING_FLAGS_C "-Wall -fPIC")
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
# fix Boost compilation :(
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
@@ -347,6 +389,26 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "Ap
if (CMAKE_VERSION VERSION_LESS 3.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
if(ENABLE_SIMD)
if (X86 OR X86_64)
set(CMAKE_C_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse")
endif()
endif()
endif()
if (ENABLE_OPENMP)
find_package(OpenMP)
if(OPENMP_FOUND)
message(STATUS "OpenMP: ENABLED")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
else()
message(STATUS "OpenMP: NOT FOUND")
endif()
else()
message(STATUS "OpenMP: DISABLED")
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
@@ -365,6 +427,15 @@ if(WIN32)
if(MSVC)
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)
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()
if (NOT OSG_FSTREAM_EXPORT_FIXED)
message(STATUS "For better linking performance, use OSG with patched fstream header")
@@ -423,7 +494,8 @@ set(TEST_LIBS_INTERNAL_CORE
${RT_LIBRARY}
${DL_LIBRARY}
${COCOA_LIBRARY}
${CURL_LIBRARIES})
${CURL_LIBRARIES}
${GDAL_LIBRARY})
set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE})
if(NOT SIMGEAR_HEADLESS)

View File

@@ -157,7 +157,7 @@ function(add_boost_test _name)
endforeach()
if(NOT _boostTestTargetsNagged${_name} STREQUAL "${includeType}")
if("includeType" STREQUAL "CONFIGURED")
if("${includeType}" STREQUAL "CONFIGURED")
message(STATUS
"Test '${_name}' uses the CMake-configurable form of the boost test framework - congrats! (Including File: ${includeFileLoc})")
elseif("${includeType}" STREQUAL "INCLUDED")

View File

@@ -0,0 +1,37 @@
IF(CMAKE_SYSTEM_PROCESSOR MATCHES amd64.*|x86_64.* OR CMAKE_GENERATOR MATCHES "Visual Studio.*Win64")
IF(CMAKE_C_FLAGS MATCHES -m32 OR CMAKE_CXX_FLAGS MATCHES -m32)
SET(X86 1)
ELSE(CMAKE_C_FLAGS MATCHES -m32 OR CMAKE_CXX_FLAGS MATCHES -m32)
SET(X86_64 1)
ENDIF(CMAKE_C_FLAGS MATCHES -m32 OR CMAKE_CXX_FLAGS MATCHES -m32)
ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES i686.*|i386.*|x86.* OR WIN32)
IF(CMAKE_C_FLAGS MATCHES -m64 OR CMAKE_CXX_FLAGS MATCHES -m64)
SET(X86_64 1)
ELSE(CMAKE_C_FLAGS MATCHES -m64 OR CMAKE_CXX_FLAGS MATCHES -m64)
SET(X86 1)
ENDIF(CMAKE_C_FLAGS MATCHES -m64 OR CMAKE_CXX_FLAGS MATCHES -m64)
ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES arm.* AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
SET(ARM 1)
ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES mips)
SET(MIPS 1)
ENDIF()
IF ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang")
# using Clang
SET(CLANG 1)
ELSEIF ("${CMAKE_C_COMPILER_ID}" STREQUAL "TinyCC")
# using TinyCC
SET(TINYCC 1)
ELSEIF ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
# using GCC
SET(GCC 1)
ELSEIF ("${CMAKE_C_COMPILER_ID}" STREQUAL "Intel")
# using Intel C++
SET(INTELCC 1)
ELSEIF ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
# using Visual Studio C++
SET(MSVC 1)
ELSEIF ("${CMAKE_C_COMPILER_ID}" STREQUAL "MIPSpro")
# using SGI MIPSpro
SET(MIPSPRO 1)
ENDIF()

View File

@@ -1,76 +0,0 @@
%define ver @VERSION@
%define rel 1
%define prefix /usr
Summary: Simulator Construction Gear.
Name: @PACKAGE@
Version: %ver
Release: %rel
Copyright: LGPL
Group: Libraries/Graphics
Source: %{name}-%{version}.tar.gz
#URL:
BuildRoot: /tmp/%{name}-%{version}-%{rel}-root
Packager: Fill In As You Wish
Docdir: %{prefix}/doc
%description
This package contains a tools and libraries useful for constructing
simulation and visualization applications such as FlightGear or TerraGear.
Authors:
N/A
%prep
%setup -n %{name}-%{version}
%build
# Needed for snapshot releases.
if [ ! -f configure ]; then
CFLAGS="$RPM_OPT_FLAGS" ./autogen.sh --prefix=%prefix
else
CFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=%prefix
fi
if [ "$SMP" != "" ]; then
JSMP = '"MAKE=make -k -j $SMP"'
fi
make ${JSMP};
%install
[ -d ${RPM_BUILD_ROOT} ] && rm -rf ${RPM_BUILD_ROOT}
make prefix=${RPM_BUILD_ROOT}%{prefix} install
#
# Generating file lists and store them in file-lists
# Starting with the directory listings
#
find ${RPM_BUILD_ROOT}%{prefix}/{bin,include,lib} -type d | sed "s#^${RPM_BUILD_ROOT}#\%attr (-\,root\,root) \%dir #" > file-lists
%{?ETCDR:find ${RPM_BUILD_ROOT}%{!?SYSCF:%{prefix}}/etc -type d | sed "s#^${RPM_BUILD_ROOT}#\%attr (-\,root\,root) \%dir #" >> file-lists}
#
# Then, the file listings
#
echo "%defattr (-, root, root)" >> file-lists
%{?ETCDR:find ${RPM_BUILD_ROOT}%{!?SYSCF:%{prefix}}/etc/%{name}.conf -type f | sed -e "s#^${RPM_BUILD_ROOT}#%config #g" >> file-lists}
find ${RPM_BUILD_ROOT}%{prefix} -type f | sed -e "s#^${RPM_BUILD_ROOT}##g" >> file-lists
%clean
(cd ..; rm -rf %{name}-%{version} ${RPM_BUILD_ROOT})
%files -f file-lists
%defattr (-, root, root)
%doc AUTHORS
%doc COPYING
%doc ChangeLog
%doc INSTALL
%doc NEWS
%doc README
%doc %{name}.spec.in

View File

@@ -13,4 +13,13 @@ set(SIMGEAR_SOUND @ENABLE_SOUND@)
# find_dependency(OpenAL)
#endif()
# SSE/SSE2 support
set(ENABLE_SIMD @ENABLE_SIMD@)
# Alternative terrain engine based on pagedLOD
set(ENABLE_GDAL @ENABLE_GDAL@)
set(ENABLE_OPENMP @ENABLE_OPENMP@)
include("${CMAKE_CURRENT_LIST_DIR}/SimGearTargets.cmake")

View File

@@ -5,6 +5,7 @@ foreach( mylibfolder
bucket
bvh
debug
embedded_resources
ephemeris
io
magvar
@@ -172,6 +173,11 @@ if(NOT SIMGEAR_HEADLESS)
${OPENGL_LIBRARY}
${JPEG_LIBRARY})
if(ENABLE_GDAL)
target_link_libraries(SimGearScene
${GDAL_LIBRARIES})
endif()
# only actually needed by canvas/KeyboardEvent.cxx
target_include_directories(SimGearScene PRIVATE ${PROJECT_SOURCE_DIR}/3rdparty/utf8/source)
endif()

View File

@@ -35,123 +35,124 @@ using std::endl;
void testBucketSpans()
{
COMPARE(sg_bucket_span(0.0), 0.125);
COMPARE(sg_bucket_span(-20), 0.125);
COMPARE(sg_bucket_span(-40), 0.25);
COMPARE(sg_bucket_span(89.9), 12.0);
COMPARE(sg_bucket_span(88.1), 4.0);
COMPARE(sg_bucket_span(-89.9), 12.0);
SG_CHECK_EQUAL(sg_bucket_span(0.0), 0.125);
SG_CHECK_EQUAL(sg_bucket_span(-20), 0.125);
SG_CHECK_EQUAL(sg_bucket_span(-40), 0.25);
SG_CHECK_EQUAL(sg_bucket_span(89.9), 12.0);
SG_CHECK_EQUAL(sg_bucket_span(88.1), 4.0);
SG_CHECK_EQUAL(sg_bucket_span(-89.9), 12.0);
}
void testBasic()
{
SGBucket b1(5.1, 55.05);
COMPARE(b1.get_chunk_lon(), 5);
COMPARE(b1.get_chunk_lat(), 55);
COMPARE(b1.get_x(), 0);
COMPARE(b1.get_y(), 0);
COMPARE(b1.gen_index(), 3040320);
COMPARE(b1.gen_base_path(), "e000n50/e005n55");
VERIFY(b1.isValid());
SG_CHECK_EQUAL(b1.get_chunk_lon(), 5);
SG_CHECK_EQUAL(b1.get_chunk_lat(), 55);
SG_CHECK_EQUAL(b1.get_x(), 0);
SG_CHECK_EQUAL(b1.get_y(), 0);
SG_CHECK_EQUAL(b1.gen_index(), 3040320);
SG_CHECK_EQUAL(b1.gen_base_path(), "e000n50/e005n55");
SG_VERIFY(b1.isValid());
SGBucket b2(-10.1, -43.8);
COMPARE(b2.get_chunk_lon(), -11);
COMPARE(b2.get_chunk_lat(), -44);
COMPARE(b2.get_x(), 3);
COMPARE(b2.get_y(), 1); // latitude chunks numbered bottom to top, it seems
COMPARE(b2.gen_base_path(), "w020s50/w011s44");
VERIFY(b2.isValid());
SG_CHECK_EQUAL(b2.get_chunk_lon(), -11);
SG_CHECK_EQUAL(b2.get_chunk_lat(), -44);
SG_CHECK_EQUAL(b2.get_x(), 3);
// Latitude chunks numbered bottom to top, it seems
SG_CHECK_EQUAL(b2.get_y(), 1);
SG_CHECK_EQUAL(b2.gen_base_path(), "w020s50/w011s44");
SG_VERIFY(b2.isValid());
SGBucket b3(123.48, 9.01);
COMPARE(b3.get_chunk_lon(), 123);
COMPARE(b3.get_chunk_lat(), 9);
COMPARE(b3.get_x(), 3);
COMPARE(b3.get_y(), 0);
COMPARE(b3.gen_base_path(), "e120n00/e123n09");
VERIFY(b3.isValid());
SG_CHECK_EQUAL(b3.get_chunk_lon(), 123);
SG_CHECK_EQUAL(b3.get_chunk_lat(), 9);
SG_CHECK_EQUAL(b3.get_x(), 3);
SG_CHECK_EQUAL(b3.get_y(), 0);
SG_CHECK_EQUAL(b3.gen_base_path(), "e120n00/e123n09");
SG_VERIFY(b3.isValid());
SGBucket defBuck;
VERIFY(!defBuck.isValid());
SG_VERIFY(!defBuck.isValid());
b3.make_bad();
VERIFY(!b3.isValid());
SG_VERIFY(!b3.isValid());
SGBucket atAntiMeridian(180.0, 12.3);
VERIFY(atAntiMeridian.isValid());
COMPARE(atAntiMeridian.get_chunk_lon(), -180);
COMPARE(atAntiMeridian.get_x(), 0);
SG_VERIFY(atAntiMeridian.isValid());
SG_CHECK_EQUAL(atAntiMeridian.get_chunk_lon(), -180);
SG_CHECK_EQUAL(atAntiMeridian.get_x(), 0);
SGBucket atAntiMeridian2(-180.0, -78.1);
VERIFY(atAntiMeridian2.isValid());
COMPARE(atAntiMeridian2.get_chunk_lon(), -180);
COMPARE(atAntiMeridian2.get_x(), 0);
SG_VERIFY(atAntiMeridian2.isValid());
SG_CHECK_EQUAL(atAntiMeridian2.get_chunk_lon(), -180);
SG_CHECK_EQUAL(atAntiMeridian2.get_x(), 0);
// check comparisom operator overload
SGBucket b4(5.11, 55.1);
VERIFY(b1 == b4); // should be equal
VERIFY(b1 == b1);
VERIFY(b1 != defBuck);
VERIFY(b1 != b2);
SG_VERIFY(b1 == b4); // should be equal
SG_VERIFY(b1 == b1);
SG_VERIFY(b1 != defBuck);
SG_VERIFY(b1 != b2);
// check wrapping/clipping of inputs
SGBucket wrapMeridian(-200.0, 45.0);
COMPARE(wrapMeridian.get_chunk_lon(), 160);
SG_CHECK_EQUAL(wrapMeridian.get_chunk_lon(), 160);
SGBucket clipPole(48.9, 91);
COMPARE(clipPole.get_chunk_lat(), 89);
SG_CHECK_EQUAL(clipPole.get_chunk_lat(), 89);
}
void testPolar()
{
SGBucket b1(0.0, 89.92);
SGBucket b2(10.0, 89.96);
COMPARE(b1.get_chunk_lat(), 89);
COMPARE(b1.get_chunk_lon(), 0);
COMPARE(b1.get_x(), 0);
COMPARE(b1.get_y(), 7);
SG_CHECK_EQUAL(b1.get_chunk_lat(), 89);
SG_CHECK_EQUAL(b1.get_chunk_lon(), 0);
SG_CHECK_EQUAL(b1.get_x(), 0);
SG_CHECK_EQUAL(b1.get_y(), 7);
COMPARE_EP(b1.get_highest_lat(), 90.0);
COMPARE_EP(b1.get_width_m(), 10.0);
SG_CHECK_EQUAL_EP(b1.get_highest_lat(), 90.0);
SG_CHECK_EQUAL_EP(b1.get_width_m(), 10.0);
COMPARE(b2.get_chunk_lat(), 89);
COMPARE(b2.get_chunk_lon(), 0);
COMPARE(b2.get_x(), 0);
COMPARE(b2.get_y(), 7);
SG_CHECK_EQUAL(b2.get_chunk_lat(), 89);
SG_CHECK_EQUAL(b2.get_chunk_lon(), 0);
SG_CHECK_EQUAL(b2.get_x(), 0);
SG_CHECK_EQUAL(b2.get_y(), 7);
COMPARE(b1.gen_index(), b2.gen_index());
SG_CHECK_EQUAL(b1.gen_index(), b2.gen_index());
SGGeod actualNorthPole1 = b1.get_corner(2);
SGGeod actualNorthPole2 = b1.get_corner(3);
COMPARE_EP(actualNorthPole1.getLatitudeDeg(), 90.0);
COMPARE_EP(actualNorthPole1.getLongitudeDeg(), 12.0);
COMPARE_EP(actualNorthPole2.getLatitudeDeg(), 90.0);
COMPARE_EP(actualNorthPole2.getLongitudeDeg(), 0.0);
SG_CHECK_EQUAL_EP(actualNorthPole1.getLatitudeDeg(), 90.0);
SG_CHECK_EQUAL_EP(actualNorthPole1.getLongitudeDeg(), 12.0);
SG_CHECK_EQUAL_EP(actualNorthPole2.getLatitudeDeg(), 90.0);
SG_CHECK_EQUAL_EP(actualNorthPole2.getLongitudeDeg(), 0.0);
SGBucket b3(-2, 89.88);
SGBucket b4(-7, 89.88);
COMPARE(b3.gen_index(), b4.gen_index());
SG_CHECK_EQUAL(b3.gen_index(), b4.gen_index());
// south pole
SGBucket b5(-170, -89.88);
SGBucket b6(-179, -89.88);
COMPARE(b5.get_chunk_lat(), -90);
COMPARE(b5.get_chunk_lon(), -180);
COMPARE(b5.get_x(), 0);
COMPARE(b5.get_y(), 0);
COMPARE(b5.gen_index(), b6.gen_index());
COMPARE_EP(b5.get_highest_lat(), -90.0);
COMPARE_EP(b5.get_width_m(), 10.0);
SG_CHECK_EQUAL(b5.get_chunk_lat(), -90);
SG_CHECK_EQUAL(b5.get_chunk_lon(), -180);
SG_CHECK_EQUAL(b5.get_x(), 0);
SG_CHECK_EQUAL(b5.get_y(), 0);
SG_CHECK_EQUAL(b5.gen_index(), b6.gen_index());
SG_CHECK_EQUAL_EP(b5.get_highest_lat(), -90.0);
SG_CHECK_EQUAL_EP(b5.get_width_m(), 10.0);
SGGeod actualSouthPole1 = b5.get_corner(0);
SGGeod actualSouthPole2 = b5.get_corner(1);
COMPARE_EP(actualSouthPole1.getLatitudeDeg(), -90.0);
COMPARE_EP(actualSouthPole1.getLongitudeDeg(), -180);
COMPARE_EP(actualSouthPole2.getLatitudeDeg(), -90.0);
COMPARE_EP(actualSouthPole2.getLongitudeDeg(), -168);
SG_CHECK_EQUAL_EP(actualSouthPole1.getLatitudeDeg(), -90.0);
SG_CHECK_EQUAL_EP(actualSouthPole1.getLongitudeDeg(), -180);
SG_CHECK_EQUAL_EP(actualSouthPole2.getLatitudeDeg(), -90.0);
SG_CHECK_EQUAL_EP(actualSouthPole2.getLongitudeDeg(), -168);
SGBucket b7(200, 89.88);
COMPARE(b7.get_chunk_lon(), -168);
SG_CHECK_EQUAL(b7.get_chunk_lon(), -168);
}
@@ -160,15 +161,15 @@ void testNearPolar()
{
SGBucket b1(1, 88.5);
SGBucket b2(-1, 88.8);
COMPARE(b1.get_chunk_lon(), 0);
COMPARE(b1.get_chunk_lat(), 88);
VERIFY(b1.gen_index() != b2.gen_index());
SG_CHECK_EQUAL(b1.get_chunk_lon(), 0);
SG_CHECK_EQUAL(b1.get_chunk_lat(), 88);
SG_VERIFY(b1.gen_index() != b2.gen_index());
SGBucket b3(176.1, 88.5);
COMPARE(b3.get_chunk_lon(), 176);
SG_CHECK_EQUAL(b3.get_chunk_lon(), 176);
SGBucket b4(-178, 88.5);
COMPARE(b4.get_chunk_lon(), -180);
SG_CHECK_EQUAL(b4.get_chunk_lon(), -180);
}
void testOffset()
@@ -176,74 +177,74 @@ void testOffset()
// bucket just below the 22 degree cutoff, so the next tile north
// is twice the width
SGBucket b1(-59.8, 21.9);
COMPARE(b1.get_chunk_lat(), 21);
COMPARE(b1.get_chunk_lon(), -60);
COMPARE(b1.get_x(), 1);
COMPARE(b1.get_y(), 7);
SG_CHECK_EQUAL(b1.get_chunk_lat(), 21);
SG_CHECK_EQUAL(b1.get_chunk_lon(), -60);
SG_CHECK_EQUAL(b1.get_x(), 1);
SG_CHECK_EQUAL(b1.get_y(), 7);
// offset vertically
SGBucket b2(b1.sibling(0, 1));
COMPARE(b2.get_chunk_lat(), 22);
COMPARE(b2.get_chunk_lon(), -60);
COMPARE(b2.get_x(), 0);
COMPARE(b2.get_y(), 0);
SG_CHECK_EQUAL(b2.get_chunk_lat(), 22);
SG_CHECK_EQUAL(b2.get_chunk_lon(), -60);
SG_CHECK_EQUAL(b2.get_x(), 0);
SG_CHECK_EQUAL(b2.get_y(), 0);
COMPARE(b2.gen_index(), sgBucketOffset(-59.8, 21.9, 0, 1));
SG_CHECK_EQUAL(b2.gen_index(), sgBucketOffset(-59.8, 21.9, 0, 1));
// offset vertically and horizontally. We compute horizontal (x)
// movement at the target latitude, so this should move 0.25 * -3 degrees,
// NOT 0.125 * -3 degrees.
SGBucket b3(b1.sibling(-3, 1));
COMPARE(b3.get_chunk_lat(), 22);
COMPARE(b3.get_chunk_lon(), -61);
COMPARE(b3.get_x(), 1);
COMPARE(b3.get_y(), 0);
SG_CHECK_EQUAL(b3.get_chunk_lat(), 22);
SG_CHECK_EQUAL(b3.get_chunk_lon(), -61);
SG_CHECK_EQUAL(b3.get_x(), 1);
SG_CHECK_EQUAL(b3.get_y(), 0);
COMPARE(b3.gen_index(), sgBucketOffset(-59.8, 21.9, -3, 1));
SG_CHECK_EQUAL(b3.gen_index(), sgBucketOffset(-59.8, 21.9, -3, 1));
}
void testPolarOffset()
{
SGBucket b1(-11.7, -89.6);
COMPARE(b1.get_chunk_lat(), -90);
COMPARE(b1.get_chunk_lon(), -12);
COMPARE(b1.get_x(), 0);
COMPARE(b1.get_y(), 3);
SG_CHECK_EQUAL(b1.get_chunk_lat(), -90);
SG_CHECK_EQUAL(b1.get_chunk_lon(), -12);
SG_CHECK_EQUAL(b1.get_x(), 0);
SG_CHECK_EQUAL(b1.get_y(), 3);
// offset horizontally
SGBucket b2(b1.sibling(-2, 0));
COMPARE(b2.get_chunk_lat(), -90);
COMPARE(b2.get_chunk_lon(), -36);
COMPARE(b2.get_x(), 0);
COMPARE(b2.get_y(), 3);
SG_CHECK_EQUAL(b2.get_chunk_lat(), -90);
SG_CHECK_EQUAL(b2.get_chunk_lon(), -36);
SG_CHECK_EQUAL(b2.get_x(), 0);
SG_CHECK_EQUAL(b2.get_y(), 3);
COMPARE(b2.gen_index(), sgBucketOffset(-11.7, -89.6, -2, 0));
SG_CHECK_EQUAL(b2.gen_index(), sgBucketOffset(-11.7, -89.6, -2, 0));
// offset and wrap
SGBucket b3(-170, 89.1);
SGBucket b4(b3.sibling(-1, 0));
COMPARE(b4.get_chunk_lat(), 89);
COMPARE(b4.get_chunk_lon(), 168);
COMPARE(b4.get_x(), 0);
COMPARE(b4.get_y(), 0);
SG_CHECK_EQUAL(b4.get_chunk_lat(), 89);
SG_CHECK_EQUAL(b4.get_chunk_lon(), 168);
SG_CHECK_EQUAL(b4.get_x(), 0);
SG_CHECK_EQUAL(b4.get_y(), 0);
COMPARE(b4.gen_index(), sgBucketOffset(-170, 89.1, -1, 0));
SG_CHECK_EQUAL(b4.gen_index(), sgBucketOffset(-170, 89.1, -1, 0));
SGBucket b5(177, 87.3);
SGBucket b6(b5.sibling(1, 1));
COMPARE(b6.get_chunk_lat(), 87);
COMPARE(b6.get_chunk_lon(), -180);
COMPARE(b6.get_x(), 0);
COMPARE(b6.get_y(), 3);
SG_CHECK_EQUAL(b6.get_chunk_lat(), 87);
SG_CHECK_EQUAL(b6.get_chunk_lon(), -180);
SG_CHECK_EQUAL(b6.get_x(), 0);
SG_CHECK_EQUAL(b6.get_y(), 3);
COMPARE(b6.gen_index(), sgBucketOffset(177, 87.3, 1, 1));
SG_CHECK_EQUAL(b6.gen_index(), sgBucketOffset(177, 87.3, 1, 1));
// offset vertically towards the pole
SGBucket b7(b1.sibling(0, -5));
VERIFY(!b7.isValid());
SG_VERIFY(!b7.isValid());
VERIFY(!SGBucket(0, 90).sibling(0, 1).isValid());
SG_VERIFY(!SGBucket(0, 90).sibling(0, 1).isValid());
}
// test behaviour of bucket-offset near the anti-meridian (180-meridian)
@@ -251,17 +252,17 @@ void testOffsetWrap()
{
// near the equator
SGBucket b1(-179.8, 16.8);
COMPARE(b1.get_chunk_lat(), 16);
COMPARE(b1.get_chunk_lon(), -180);
COMPARE(b1.get_x(), 1);
COMPARE(b1.get_y(), 6);
SG_CHECK_EQUAL(b1.get_chunk_lat(), 16);
SG_CHECK_EQUAL(b1.get_chunk_lon(), -180);
SG_CHECK_EQUAL(b1.get_x(), 1);
SG_CHECK_EQUAL(b1.get_y(), 6);
SGBucket b2(b1.sibling(-2, 0));
COMPARE(b2.get_chunk_lat(), 16);
COMPARE(b2.get_chunk_lon(), 179);
COMPARE(b2.get_x(), 7);
COMPARE(b2.get_y(), 6);
COMPARE(b2.gen_index(), sgBucketOffset(-179.8, 16.8, -2, 0));
SG_CHECK_EQUAL(b2.get_chunk_lat(), 16);
SG_CHECK_EQUAL(b2.get_chunk_lon(), 179);
SG_CHECK_EQUAL(b2.get_x(), 7);
SG_CHECK_EQUAL(b2.get_y(), 6);
SG_CHECK_EQUAL(b2.gen_index(), sgBucketOffset(-179.8, 16.8, -2, 0));
}

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "BVHMaterial.hxx"
namespace simgear {

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "BVHPageNode.hxx"
#include "BVHPager.hxx"

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "BVHPageRequest.hxx"
namespace simgear {

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "BVHPager.hxx"
#include <list>

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "BVHStaticNode.hxx"
namespace simgear {

View File

@@ -15,6 +15,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include <iostream>
#include <simgear/structure/SGSharedPtr.hxx>

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "Canvas.hxx"
#include "CanvasEventManager.hxx"
#include "CanvasEventVisitor.hxx"

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasEvent.hxx"
namespace simgear

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasEventManager.hxx"
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/canvas/elements/CanvasElement.hxx>

View File

@@ -17,6 +17,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasEvent.hxx"
#include "CanvasEventVisitor.hxx"
#include <simgear/canvas/elements/CanvasElement.hxx>

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasMgr.hxx"
#include "Canvas.hxx"
#include "CanvasEventManager.hxx"

View File

@@ -19,6 +19,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "Canvas.hxx"
#include "CanvasObjectPlacement.hxx"
#include <simgear/canvas/events/MouseEvent.hxx>

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasPlacement.hxx"
#include <simgear/props/props.hxx>

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasMgr.hxx"
#include "CanvasSystemAdapter.hxx"
#include "CanvasWindow.hxx"

View File

@@ -44,6 +44,11 @@
#if defined(_MSC_VER)
# pragma warning(disable:4311)
# pragma warning(disable:4312)
# define ALIGN16 __declspec(align(16))
# define ALIGN16C
#elif defined(__GNUC__)
# define ALIGN16
# define ALIGN16C __attribute__((aligned(16)))
#endif
/* Type definitions */

View File

@@ -35,21 +35,33 @@ void SHVector2_dtor(SHVector2 *v) {
}
void SHVector3_ctor(SHVector3 *v) {
#ifdef SHIVA_USE_SIMD
v->vec = _mm_setzero_ps();
#else
v->x=0.0f; v->y=0.0f; v->z=0.0f;
#endif
}
void SHVector3_dtor(SHVector3 *v) {
}
void SHVector4_ctor(SHVector4 *v) {
#ifdef SHIVA_USE_SIMD
v->vec = _mm_setzero_ps();
#else
v->x=0.0f; v->y=0.0f; v->z=0.0f; v->w=0.0f;
#endif
}
void SHVector4_dtor(SHVector4 *v) {
}
void SHRectangle_ctor(SHRectangle *r) {
#ifdef SHIVA_USE_SIMD
r->vec = _mm_setzero_ps();
#else
r->x=0.0f; r->y=0.0f; r->w=0.0f; r->h=0.0f;
#endif
}
void SHRectangle_dtor(SHRectangle *r) {
@@ -135,3 +147,24 @@ int shLineLineXsection(SHVector2 *o1, SHVector2 *v1,
xsection->y = o1->y + t1*v1->y;
return 1;
}
#ifdef SHIVA_USE_SIMD
# ifdef __SSE3__
# include <pmmintrin.h>
inline float hsum_ps_sse(__m128 v) {
__m128 shuf = _mm_movehdup_ps(v);
__m128 sums = _mm_add_ps(v, shuf);
shuf = _mm_movehl_ps(shuf, sums);
sums = _mm_add_ss(sums, shuf);
return _mm_cvtss_f32(sums);
}
# else
inline float hsum_ps_sse(__m128 v) {
__m128 shuf = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 3, 0, 1));
__m128 sums = _mm_add_ps(v, shuf);
shuf = _mm_movehl_ps(shuf, sums);
sums = _mm_add_ss(sums, shuf);
return _mm_cvtss_f32(sums);
}
# endif
#endif

View File

@@ -21,6 +21,21 @@
#ifndef __SHVECTORS_H
#define __SHVECTORS_H
#ifdef HAVE_CONFIG_H
# include <simgear/simgear_config.h>
#endif
#ifdef ENABLE_SIMD
# ifdef __SSE__
// # define SHIVA_USE_SIMD
# endif
# endif
#ifdef SHIVA_USE_SIMD
# include <xmmintrin.h>
float hsum_ps_sse(__m128 v);
#endif
#include "shDefs.h"
/* Vector structures
@@ -33,9 +48,17 @@ typedef struct
void SHVector2_ctor(SHVector2 *v);
void SHVector2_dtor(SHVector2 *v);
typedef struct
{
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 vec;
struct { SHfloat x,y,z,w; };
} ALIGN16C;
#else
SHfloat x,y,z;
#endif
} SHVector3;
void SHVector3_ctor(SHVector3 *v);
@@ -43,7 +66,14 @@ void SHVector3_dtor(SHVector3 *v);
typedef struct
{
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 vec;
struct { SHfloat x,y,z,w; };
} ALIGN16C;
#else
SHfloat x,y,z,w;
#endif
} SHVector4;
void SHVector4_ctor(SHVector4 *v);
@@ -51,7 +81,14 @@ void SHVector4_dtor(SHVector4 *v);
typedef struct
{
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 vec;
struct { SHfloat x,y,w,h; };
} ALIGN16C;
#else
SHfloat x,y,w,h;
#endif
} SHRectangle;
void SHRectangle_ctor(SHRectangle *r);
@@ -61,7 +98,14 @@ void shRectangleSet(SHRectangle *r, SHfloat x,
typedef struct
{
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 mtx[4];
SHfloat m[4][4];
} ALIGN16C;
#else
SHfloat m[3][3];
#endif
} SHMatrix3x3;
void SHMatrix3x3_ctor(SHMatrix3x3 *m);
@@ -83,12 +127,22 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
*--------------------------------------------------------- */
#define SET2(v,xs,ys) { v.x=xs; v.y=ys; }
#define SET3(v,xs,ys,zs) { v.x=xs; v.y=ys; v.z=zs; }
#define SET4(v,xs,ys,zs,ws) { v.x=xs; v.y=ys; v.z=zs; v.w=ws; }
#ifdef SHIVA_USE_SIMD
# define SET3(v,xs,ys,zs,ws) { v.vec=_mm_set_ps(0,zs,ys,xs); }
# define SET4(v,xs,ys,zs,ws) { v.vec=_mm_set_ps(ws,zs,ys,xs); }
#else
# define SET3(v,xs,ys,zs) { v.x=xs; v.y=ys; v.z=zs; }
# define SET4(v,xs,ys,zs,ws) { v.x=xs; v.y=ys; v.z=zs; v.w=ws; }
#endif
#define SET2V(v1,v2) { v1.x=v2.x; v1.y=v2.y; }
#define SET3V(v1,v2) { v1.x=v2.x; v1.y=v2.y; v1.z=v2.z; }
#define SET4V(v1,v2) { v1.x=v2.x; v1.y=v2.y; v1.z=v2.z; v1.w=v2.w; }
#ifdef SHIVA_USE_SIMD
# define SET3V(v1,v2) { v1.vec=v2.vec; }
# define SET4V(v1,v2) { v1.vec=v2.vec; }
#else
# define SET3V(v1,v2) { v1.x=v2.x; v1.y=v2.y; v1.z=v2.z; }
# define SET4V(v1,v2) { v1.x=v2.x; v1.y=v2.y; v1.z=v2.z; v1.w=v2.w; }
#endif
#define EQ2(v,xx,yy) ( v.x==xx && v.y==yy )
#define EQ3(v,xx,yy,zz) ( v.x==xx && v.y==yy && v.z==zz )
@@ -103,48 +157,89 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#define EQ4V(v1,v2) ( v1.x==v2.x && v1.y==v2.y && v1.z==v2.z && v1.w==v2.w )
#define ADD2(v,xx,yy) { v.x+=xx; v.y+=yy; }
#define ADD3(v,xx,yy,zz) { v.x+=xx; v.y+=yy; v.z+=zz; }
#define ADD4(v,xx,yy,zz,ww) { v.x+=xx; v.y+=yy; v.z+=zz; v.w+=ww; }
#ifdef SHIVA_USE_SIMD
# define ADD3(v,xx,yy,zz,ww) { v.vec=_mm_add_ps(v.vec,_mm_set_ps(0,zz,yy,xx)); }
# define ADD4(v,xx,yy,zz,ww) { v.vec=_mm_add_ps(v.vec,_mm_set_ps(ww,zz,yy,xx)); }
#else
# define ADD3(v,xx,yy,zz) { v.x+=xx; v.y+=yy; v.z+=zz; }
# define ADD4(v,xx,yy,zz,ww) { v.x+=xx; v.y+=yy; v.z+=zz; v.w+=ww; }
#endif
#define ADD2V(v1,v2) { v1.x+=v2.x; v1.y+=v2.y; }
#define ADD3V(v1,v2) { v1.x+=v2.x; v1.y+=v2.y; v1.z+=v2.z; }
#define ADD4V(v1,v2) { v1.x+=v2.x; v1.y+=v2.y; v1.z+=v2.z; v1.w+=v2.w; }
#ifdef SHIVA_USE_SIMD
# define ADD3V(v1,v2) { v1.vec=_mm_add_ps(v1.vec,v2.vec); }
# define ADD4V(v1,v2) { v1.vec=_mm_add_ps(v1.vec,v2.vec); }
#else
# define ADD3V(v1,v2) { v1.x+=v2.x; v1.y+=v2.y; v1.z+=v2.z; }
# define ADD4V(v1,v2) { v1.x+=v2.x; v1.y+=v2.y; v1.z+=v2.z; v1.w+=v2.w; }
#endif
#define SUB2(v,xx,yy) { v.x-=xx; v.y-=yy; }
#define SUB3(v,xx,yy,zz) { v.x-=xx; v.y-=yy; v.z-=zz; }
#define SUB4(v,xx,yy,zz,ww) { v.x-=xx; v.y-=yy; v.z-=zz; v.w-=v2.w; }
#ifdef SHIVA_USE_SIMD
# define SUB3(v,xx,yy,zz,ww) { v.vec=_mm_sub_ps(v.vec,_mm_set_ps(0,zz,yy,xx)); }
# define SUB4(v,xx,yy,zz,ww) { v.vec=_mm_sub_ps(v.vec,_mm_set_ps(ww,zz,yy,xx)); }
#else
# define SUB3(v,xx,yy,zz) { v.x-=xx; v.y-=yy; v.z-=zz; }
# define SUB4(v,xx,yy,zz,ww) { v.x-=xx; v.y-=yy; v.z-=zz; v.w-=v2.w; }
#endif
#define SUB2V(v1,v2) { v1.x-=v2.x; v1.y-=v2.y; }
#define SUB3V(v1,v2) { v1.x-=v2.x; v1.y-=v2.y; v1.z-=v2.z; }
#define SUB4V(v1,v2) { v1.x-=v2.x; v1.y-=v2.y; v1.z-=v2.z; v1.w-=v2.w; }
#ifdef SHIVA_USE_SIMD
# define SUB3V(v1,v2) { v1.vec=_mm_sub_ps(v1.vec,v2.vec); }
# define SUB4V(v1,v2) { v1.vec=_mm_sub_ps(v1.vec,v2.vec); }
#else
# define SUB3V(v1,v2) { v1.x-=v2.x; v1.y-=v2.y; v1.z-=v2.z; }
# define SUB4V(v1,v2) { v1.x-=v2.x; v1.y-=v2.y; v1.z-=v2.z; v1.w-=v2.w; }
#endif
#define MUL2(v,f) { v.x*=f; v.y*=f; }
#define MUL3(v,f) { v.x*=f; v.y*=f; v.z*=z; }
#define MUL4(v,f) { v.x*=f; v.y*=f; v.z*=z; v.w*=w; }
#ifdef SHIVA_USE_SIMD
# define MUL3(v,f) { v.vec=_mm_mul_ps(v.vec,_mm_set1_ps(f)); }
# define MUL4(v,f) { v.vec=_mm_mul_ps(v.vec,_mm_set1_ps(f)); }
#else
# define MUL3(v,f) { v.x*=f; v.y*=f; v.z*=z; }
# define MUL4(v,f) { v.x*=f; v.y*=f; v.z*=z; v.w*=w; }
#endif
#define DIV2(v,f) { v.x/=f; v.y/=f; }
#define DIV3(v,f) { v.x/=f; v.y/=f; v.z/=z; }
#define DIV4(v,f) { v.x/=f; v.y/=f; v.z/=z; v.w/=w; }
#ifdef SHIVA_USE_SIMD
# define DIV3(v,f) { v.vec=_mm_div_ps(v.vec,_mm_set1_ps(f)); }
# define DIV4(v,f) { v.vec=_mm_div_ps(v.vec,_mm_set1_ps(f)); }
#else
# define DIV3(v,f) { v.x/=f; v.y/=f; v.z/=z; }
# define DIV4(v,f) { v.x/=f; v.y/=f; v.z/=z; v.w/=w; }
#endif
#define ABS2(v) { v.x=SH_ABS(v.x); v.y=SH_ABS(v.y); }
#define ABS3(v) { v.x=SH_ABS(v.x); v.y=SH_ABS(v.y); v.z=SH_ABS(v.z); }
#define ABS4(v) { v.x=SH_ABS(v.x); v.y=SH_ABS(v.y); v.z=SH_ABS(v.z); v.w=SH_ABS(v.w); }
#ifdef SHIVA_USE_SIMD
# define ABS_MASK _mm_set1_ps(-0.f)
# define ABS3(v) { v.vec=_mm_andnot_ps(ABS_MASK, v.vec); }
# define ABS4(v) { v.vec=_mm_andnot_ps(ABS_MASK, v.vec); }
#else
# define ABS3(v) { v.x=SH_ABS(v.x); v.y=SH_ABS(v.y); v.z=SH_ABS(v.z); }
# define ABS4(v) { v.x=SH_ABS(v.x); v.y=SH_ABS(v.y); v.z=SH_ABS(v.z); v.w=SH_ABS(v.w); }
#endif
#define NORMSQ2(v) (v.x*v.x + v.y*v.y)
#define NORMSQ3(v) (v.x*v.x + v.y*v.y + v.z*v.z)
#define NORMSQ4(v) (v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w)
#define NORMSQ2(v) DOT2(v,v)
#define NORMSQ3(v) DOT3(v,v)
#define NORMSQ4(v) DOT4(v,v)
#define NORM2(v) SH_SQRT(NORMSQ2(v))
#define NORM3(v) SH_SQRT(NORMSQ3(v))
#define NORM4(v) SH_SQRT(NORMSQ4(v))
#define NORMALIZE2(v) { SHfloat n=NORM2(v); v.x/=n; v.y/=n; }
#define NORMALIZE3(v) { SHfloat n=NORM3(v); v.x/=n; v.y/=n; v.z/=n; }
#define NORMALIZE4(v) { SHfloat n=NORM4(v); v.x/=n; v.y/=n; v.z/=n; v.w/=w; }
#define NORMALIZE2(v) { SHfloat n=NORM2(v); DIV2(v,n); }
#define NORMALIZE3(v) { SHfloat n=NORM3(v); DIV3(v,n); }
#define NORMALIZE4(v) { SHfloat n=NORM4(v); DIV4(v,n); }
#define DOT2(v1,v2) (v1.x*v2.x + v1.y*v2.y)
#define DOT3(v1,v2) (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)
#define DOT4(v1,v2) (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w)
#ifdef SHIVA_USE_SIMD
# define DOT4(v1,v2) hsum_ps_sse(_mm_mul_ps(v1.vec,v2.vec))
# define DOT4(v1,v2) hsum_ps_sse(_mm_mul_ps(v1.vec,v2.vec))
#else
# define DOT3(v1,v2) (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)
# define DOT4(v1,v2) (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w)
#endif
#define CROSS2(v1,v2) (v1.x*v2.y - v2.x*v1.y)
@@ -152,37 +247,84 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#define ANGLE2N(v1,v2) (SH_ACOS( DOT2(v1,v2) ))
#define OFFSET2V(v, o, s) { v.x += o.x*s; v.y += o.y*s; }
#define OFFSET3V(v, o, s) { v.x += o.x*s; v.y += o.y*s; v.z += o.z*s; }
#define OFFSET4V(v, o, s) { v.x += o.x*s; v.y += o.y*s; v.z += o.z*s; v.w += o.w*s; }
#ifdef SHIVA_USE_SIMD
# define OFFSET4V(v, o, s) { v.vec=_mm_add_ps(v.vec,_mm_mul_ps(o.vec,_mm_set1_ps(s))); }
# define OFFSET4V(v, o, s) { v.vec=_mm_add_ps(v.vec,_mm_mul_ps(o.vec,_mm_set1_ps(s))); }
#else
# define OFFSET3V(v, o, s) { v.x += o.x*s; v.y += o.y*s; v.z += o.z*s; }
# define OFFSET4V(v, o, s) { v.x += o.x*s; v.y += o.y*s; v.z += o.z*s; v.w += o.w*s; }
#endif
/*-----------------------------------------------------
* Macros for matrix operations
*-----------------------------------------------------*/
#define SETMAT(mat, m00, m01, m02, m10, m11, m12, m20, m21, m22) { \
mat.m[0][0] = m00; mat.m[0][1] = m01; mat.m[0][2] = m02; \
#ifdef SHIVA_USE_SIMD
# define SETMAT(mat, m00, m01, m02, m10, m11, m12, m20, m21, m22) { \
mat.mtx[0] = _mm_set_ps(0,m02,m01,m00); \
mat.mtx[1] = _mm_set_ps(0,m12,m11,m10); \
mat.mtx[2] = _mm_set_ps(0,m22,m21,m20); \
mat.mtx[3] = _mm_setzero_ps(); }
#else
# define SETMAT(mat, m00, m01, m02, m10, m11, m12, m20, m21, m22) { \
mat.m[0][0] = m00; mat.m[0][1] = m01; mat.m[0][2] = m02; \
mat.m[1][0] = m10; mat.m[1][1] = m11; mat.m[1][2] = m12; \
mat.m[2][0] = m20; mat.m[2][1] = m21; mat.m[2][2] = m22; }
#endif
#define SETMATMAT(m1, m2) { \
#ifdef SHIVA_USE_SIMD
# define SETMATMAT(m1, m2) { \
m1.mtx[0] = m2.mtx[0]; \
m1.mtx[1] = m2.mtx[1]; \
m1.mtx[2] = m2.mtx[2]; }
#else
# define SETMATMAT(m1, m2) { \
int i,j; \
for(i=0;i<3;i++) \
for(j=0;j<3;j++) \
m1.m[i][j] = m2.m[i][j]; }
#endif
#define MULMATS(mat, s) { \
#ifdef SHIVA_USE_SIMD
# define MULMATS(mat, s) { \
mat.mtx[0] = _mm_mul_ps(mat.mtx[0],_mm_set1_ps(s)); \
mat.mtx[1] = _mm_mul_ps(mat.mtx[1],_mm_set1_ps(s)); \
mat.mtx[2] = _mm_mul_ps(mat.mtx[2],_mm_set1_ps(s)); }
#else
# define MULMATS(mat, s) { \
int i,j; \
for(i=0;i<3;i++) \
for(j=0;j<3;j++) \
mat.m[i][j] *= s; }
#endif
#define DIVMATS(mat, s) { \
#ifdef SHIVA_USE_SIMD
# define DIVMATS(mat, s) { \
mat.mtx[0] = _mm_mul_ps(mat.mtx[0],_mm_set1_ps(1/s)); \
mat.mtx[1] = _mm_mul_ps(mat.mtx[1],_mm_set1_ps(1/s)); \
mat.mtx[2] = _mm_mul_ps(mat.mtx[2],_mm_set1_ps(1/s)); }
#else
# define DIVMATS(mat, s) { \
int i,j; \
for(i=0;i<3;i++) \
for(j=0;j<3;j++) \
mat.m[i][j] /= s; }
#endif
#define MULMATMAT(m1, m2, mout) { \
#ifdef SHIVA_USE_SIMD
# define MULMATMAT(m2, m1, mout) { \
int i,j; \
for (i=0;i<4;i++) { \
__m128 a = m1.mtx[0]; \
__m128 b = _mm_set1_ps(m2.m[i][0]); \
mout.mtx[i] = a*b; \
for (j=1;j<4;j++) { \
a = m1.mtx[j]; \
b = _mm_set1_ps(m2.m[i][j]); \
mout.mtx[i] += a*b; } } }
#else
# define MULMATMAT(m1, m2, mout) { \
int i,j; \
for(i=0;i<3;i++) \
for(j=0;j<3;j++) \
@@ -190,6 +332,7 @@ int i,j; \
m1.m[i][0] * m2.m[0][j] + \
m1.m[i][1] * m2.m[1][j] + \
m1.m[i][2] * m2.m[2][j]; }
#endif
#define IDMAT(mat) SETMAT(mat, 1,0,0, 0,1,0, 0,0,1)

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasElement.hxx"
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasEventVisitor.hxx>
@@ -640,6 +642,27 @@ namespace canvas
_scissor->_coord_reference = rf;
}
//----------------------------------------------------------------------------
void Element::setRotation(unsigned int index, double r)
{
_node->getChild(NAME_TRANSFORM, index, true)->setDoubleValue("rot", r);
}
//----------------------------------------------------------------------------
void Element::setTranslation(unsigned int index, double x, double y)
{
SGPropertyNode* tf = _node->getChild(NAME_TRANSFORM, index, true);
tf->getChild("t", 0, true)->setDoubleValue(x);
tf->getChild("t", 1, true)->setDoubleValue(y);
}
//----------------------------------------------------------------------------
void Element::setTransformEnabled(unsigned int index, bool enabled)
{
SGPropertyNode* tf = _node->getChild(NAME_TRANSFORM, index, true);
tf->setBoolValue("enabled", enabled);
}
//----------------------------------------------------------------------------
osg::BoundingBox Element::getBoundingBox() const
{
@@ -701,6 +724,9 @@ namespace canvas
continue;
SGPropertyNode* tf_node = _node->getChild("tf", i, true);
if (!tf_node->getBoolValue("enabled", true)) {
continue; // skip disabled transforms
}
// Build up the matrix representation of the current transform node
osg::Matrix tf;

View File

@@ -172,6 +172,22 @@ namespace canvas
*/
void setClipFrame(ReferenceFrame rf);
/**
*
*/
void setRotation(unsigned int index, double r);
/**
*
*/
void setTranslation(unsigned int index, double x, double y);
/**
*
*
*/
void setTransformEnabled(unsigned int index, bool enabled);
/**
* Get bounding box (may not be as tight as bounding box returned by
* #getTightBoundingBox)

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasGroup.hxx"
#include "CanvasImage.hxx"
#include "CanvasMap.hxx"
@@ -373,7 +375,7 @@ namespace canvas
SG_LOG
(
SG_GENERAL,
SG_INFO,
SG_DEBUG,
"canvas::Group: Moved element " << index << " to position " << index_new
);
}

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasImage.hxx"
#include <simgear/canvas/Canvas.hxx>
@@ -487,11 +489,17 @@ namespace canvas
if( !fill.empty() // If no color is given default to white
&& !parseColor(fill, color) )
return;
_colors->front() = color;
_colors->dirty();
setFill(color);
}
//----------------------------------------------------------------------------
void Image::setFill(const osg::Vec4& color)
{
_colors->front() = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
void Image::setOutset(const std::string& outset)
{
@@ -506,6 +514,13 @@ namespace canvas
_attributes_dirty |= SRC_RECT;
}
//----------------------------------------------------------------------------
void Image::setSourceRect(const SGRect<float>& sourceRect)
{
_attributes_dirty |= SRC_RECT;
_src_rect = sourceRect;
}
//----------------------------------------------------------------------------
void Image::setSlice(const std::string& slice)
{

View File

@@ -61,6 +61,7 @@ namespace canvas
void setImage(osg::ref_ptr<osg::Image> img);
void setFill(const std::string& fill);
void setFill(const osg::Vec4& color);
/**
* @see http://www.w3.org/TR/css3-background/#border-image-outset
@@ -95,6 +96,10 @@ namespace canvas
bool handleEvent(const EventPtr& event);
/**
*
*/
void setSourceRect(const SGRect<float>& sourceRect);
protected:
enum ImageAttributes

View File

@@ -17,6 +17,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasMap.hxx"
#include "map/geo_node_pair.hxx"
#include "map/projection.hxx"

View File

@@ -16,14 +16,18 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasPath.hxx"
#include <simgear/scene/util/parse_color.hxx>
#include <simgear/misc/strutils.hxx>
#include <osg/Drawable>
#include <osg/Version>
#include <vg/openvg.h>
#include <cassert>
#include <cctype>
namespace simgear
{
@@ -57,6 +61,153 @@ namespace canvas
*/
std::vector<float> splitAndConvert(const char del[], const std::string& str);
static float parseCSSNumber(const std::string& s)
{
if (strutils::ends_with(s, "px")) {
return std::stof(s.substr(0, s.length() - 2));
} else if (s.back() == '%') {
float f = std::stof(s.substr(0, s.length() - 1));
return f / 100.0f;
}
return std::stof(s);
}
//----------------------------------------------------------------------------
static bool parseSVGPathToVGPath(const std::string& svgPath, CmdList& commands, CoordList& coords)
{
const string_list& tokens = simgear::strutils::split_on_any_of(svgPath, "\t \n\r,");
char activeSVGCommand = 0;
bool isRelative = false;
int tokensNeeded = 0;
for (auto it = tokens.begin(); it != tokens.end(); ) {
// set up the new command data
if ((it->size() == 1) && std::isalpha(it->at(0))) {
const char svgCommand = std::toupper(it->at(0));
isRelative = std::islower(it->at(0));
switch (svgCommand) {
case 'Z':
tokensNeeded = 0;
break;
case 'M':
case 'L':
case 'T':
tokensNeeded = 2;
break;
case 'H':
case 'V':
tokensNeeded = 1;
break;
case 'C':
tokensNeeded = 6;
break;
case 'S':
case 'Q':
tokensNeeded = 4;
break;
case 'A':
tokensNeeded = 7;
break;
default:
SG_LOG(SG_GENERAL, SG_WARN, "unrecognized SVG path command: "
<< *it << " at token " << std::distance(tokens.begin(), it));
return false;
}
activeSVGCommand = svgCommand;
++it; // advance to first coordinate token
}
const int numTokensRemaining = std::distance(it, tokens.end());
if (numTokensRemaining < tokensNeeded) {
SG_LOG(SG_GENERAL, SG_WARN, "insufficent SVG path tokens");
return false;
}
bool pushTokensDirectly = true;
if (activeSVGCommand == 'Z') {
commands.push_back(VG_CLOSE_PATH);
activeSVGCommand = 0;
} else if (activeSVGCommand == 'M') {
commands.push_back(VG_MOVE_TO | isRelative);
activeSVGCommand = 'L';
} else if (activeSVGCommand == 'L') {
commands.push_back(VG_LINE_TO | isRelative);
} else if (activeSVGCommand == 'H') {
commands.push_back(VG_HLINE_TO | isRelative);
} else if (activeSVGCommand == 'V') {
commands.push_back(VG_HLINE_TO | isRelative);
} else if (activeSVGCommand == 'C') {
commands.push_back(VG_CUBIC_TO | isRelative);
} else if (activeSVGCommand == 'S') {
commands.push_back(VG_SCUBIC_TO | isRelative);
} else if (activeSVGCommand == 'Q') {
commands.push_back(VG_SCUBIC_TO | isRelative);
} else if (activeSVGCommand == 'T') {
commands.push_back(VG_SCUBIC_TO | isRelative);
} else if (activeSVGCommand == 'A') {
pushTokensDirectly = false; // deal with tokens manually
coords.push_back(parseCSSNumber(*it++)); // rx
coords.push_back(parseCSSNumber(*it++)); // ry
coords.push_back(parseCSSNumber(*it++)); // x-axis rotation
const bool isLargeArc = std::stoi(*it++); // large-angle
const bool isCCW = std::stoi(*it++); // sweep-flag
int vgCmd = isLargeArc ? (isCCW ? VG_LCCWARC_TO : VG_LCWARC_TO) :
(isCCW ? VG_SCCWARC_TO : VG_SCWARC_TO);
coords.push_back(parseCSSNumber(*it++));
coords.push_back(parseCSSNumber(*it++));
commands.push_back(vgCmd | isRelative);
} else {
SG_LOG(SG_GENERAL, SG_WARN, "malformed SVG path string: expected a command at token:"
<< std::distance(tokens.begin(), it) << " :" << *it);
return false;
}
if (pushTokensDirectly) {
for (int i=0; i<tokensNeeded;++i) {
coords.push_back(parseCSSNumber(*it++));
}
}
} // of tokens iteration
return true;
}
//---------------------------------------------------------------------------
static SGVec2f parseRectCornerRadius(SGPropertyNode* node, const std::string& xDir, const std::string& yDir, bool& haveCorner)
{
haveCorner = false;
std::string propName = "border-" + yDir + "-" + xDir + "-radius";
if (!node->hasChild(propName)) {
propName = "border-" + yDir + "-radius";
if (!node->hasChild(propName)) {
propName = "border-radius";
}
}
PropertyList props = node->getChildren(propName);
if (props.size() == 1) {
double r = props.at(0)->getDoubleValue(propName);
haveCorner = true;
return SGVec2f(r, r);
}
if (props.size() >= 2 ) {
haveCorner = true;
return SGVec2f(props.at(0)->getDoubleValue(),
props.at(1)->getDoubleValue());
}
return SGVec2f(-1.0f, -1.0f);
}
//----------------------------------------------------------------------------
class Path::PathDrawable:
public osg::Drawable
{
@@ -533,7 +684,9 @@ namespace canvas
const Style& parent_style,
ElementWeakPtr parent ):
Element(canvas, node, parent_style, parent),
_path( new PathDrawable(this) )
_path( new PathDrawable(this) ),
_hasSVG(false),
_hasRect(false)
{
staticInit();
@@ -561,6 +714,23 @@ namespace canvas
_attributes_dirty &= ~(CMDS | COORDS);
}
// SVG path overrides manual cmd/coord specification
if ( _hasSVG && (_attributes_dirty & SVG))
{
CmdList cmds;
CoordList coords;
parseSVGPathToVGPath(_node->getStringValue("svg"), cmds, coords);
_path->setSegments(cmds, coords);
_attributes_dirty &= ~SVG;
}
if ( _hasRect &&(_attributes_dirty & RECT))
{
parseRectToVGPath();
_attributes_dirty &= ~RECT;
}
Element::update(dt);
}
@@ -630,16 +800,75 @@ namespace canvas
childChanged(child);
}
//----------------------------------------------------------------------------
void Path::setSVGPath(const std::string& svgPath)
{
_node->setStringValue("svg", svgPath);
_hasSVG = true;
_attributes_dirty |= SVG;
}
//----------------------------------------------------------------------------
void Path::setRect(const SGRect<float> &r)
{
_rect = r;
_hasRect = true;
_attributes_dirty |= RECT;
}
//----------------------------------------------------------------------------
void Path::setRoundRect(const SGRect<float> &r, float radiusX, float radiusY)
{
if (radiusY < 0.0) {
radiusY = radiusX;
}
setRect(r);
_node->getChild("border-radius", 0, true)->setDoubleValue(radiusX);
_node->getChild("border-radius", 1, true)->setDoubleValue(radiusY);
}
//----------------------------------------------------------------------------
void Path::childChanged(SGPropertyNode* child)
{
const std::string& name = child->getNameString();
const std::string &prName = child->getParent()->getNameString();
if (simgear::strutils::starts_with(name, "border-"))
{
_attributes_dirty |= RECT;
return;
}
if (prName == "rect") {
_hasRect = true;
if (name == "left") {
_rect.setLeft(child->getDoubleValue());
} else if (name == "top") {
_rect.setTop(child->getDoubleValue());
} else if (name == "right") {
_rect.setRight(child->getDoubleValue());
} else if (name == "bottom") {
_rect.setBottom(child->getDoubleValue());
} else if (name == "width") {
_rect.setWidth(child->getDoubleValue());
} else if (name == "height") {
_rect.setHeight(child->getDoubleValue());
}
_attributes_dirty |= RECT;
return;
}
if( child->getParent() != _node )
return;
if( child->getNameString() == "cmd" )
if( name == "cmd" )
_attributes_dirty |= CMDS;
else if( child->getNameString() == "coord" )
else if( name == "coord" )
_attributes_dirty |= COORDS;
else if ( name == "svg")
_hasSVG = true;
_attributes_dirty |= SVG;
}
//----------------------------------------------------------------------------
@@ -664,5 +893,67 @@ namespace canvas
return values;
}
//----------------------------------------------------------------------------
void operator+=(CoordList& base, const std::initializer_list<VGfloat>& other)
{
base.insert(base.end(), other.begin(), other.end());
}
void Path::parseRectToVGPath()
{
CmdList commands;
CoordList coords;
commands.reserve(4);
coords.reserve(8);
bool haveCorner = false;
SGVec2f topLeft = parseRectCornerRadius(_node, "left", "top", haveCorner);
if (haveCorner) {
commands.push_back(VG_MOVE_TO_ABS);
coords += {_rect.l(), _rect.t() + topLeft.y()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {topLeft.x(), topLeft.y(), 0.0, topLeft.x(), -topLeft.y()};
} else {
commands.push_back(VG_MOVE_TO_ABS);
coords += {_rect.l(), _rect.t()};
}
SGVec2f topRight = parseRectCornerRadius(_node, "right", "top", haveCorner);
if (haveCorner) {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.r() - topRight.x()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {topRight.x(), topRight.y(), 0.0, topRight.x(), topRight.y()};
} else {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.r()};
}
SGVec2f bottomRight = parseRectCornerRadius(_node, "right", "bottom", haveCorner);
if (haveCorner) {
commands.push_back(VG_VLINE_TO_ABS);
coords += {_rect.b() - bottomRight.y()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {bottomRight.x(), bottomRight.y(), 0.0, -bottomRight.x(), bottomRight.y()};
} else {
commands.push_back(VG_VLINE_TO_ABS);
coords += {_rect.b()};
}
SGVec2f bottomLeft = parseRectCornerRadius(_node, "left", "bottom", haveCorner);
if (haveCorner) {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.l() + bottomLeft.x()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {bottomLeft.x(), bottomLeft.y(), 0.0, -bottomLeft.x(), -bottomLeft.y()};
} else {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.l()};
}
commands.push_back(VG_CLOSE_PATH);
_path->setSegments(commands, coords);
}
} // namespace canvas
} // namespace simgear

View File

@@ -21,6 +21,7 @@
#include "CanvasElement.hxx"
#include <boost/preprocessor/iteration/iterate.hpp>
#include <simgear/math/SGRect.hxx>
namespace simgear
{
@@ -67,18 +68,30 @@ namespace canvas
/** Close the path (implicit lineTo to first point of path) */
Path& close();
void setSVGPath(const std::string& svgPath);
void setRect(const SGRect<float>& r);
void setRoundRect(const SGRect<float>& r, float radiusX, float radiusY = -1.0);
protected:
enum PathAttributes
{
CMDS = LAST_ATTRIBUTE << 1,
COORDS = CMDS << 1
COORDS = CMDS << 1,
SVG = COORDS << 1,
RECT = SVG << 1
};
class PathDrawable;
typedef osg::ref_ptr<PathDrawable> PathDrawableRef;
PathDrawableRef _path;
bool _hasSVG : 1;
bool _hasRect : 1;
SGRect<float> _rect;
void parseRectToVGPath();
virtual void childRemoved(SGPropertyNode * child);
virtual void childChanged(SGPropertyNode * child);
};

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CanvasText.hxx"
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasSystemAdapter.hxx>
@@ -496,18 +497,31 @@ namespace canvas
{
case LEFT_TO_RIGHT:
{
osg::Vec2 delta( activefont->getKerning( previous_charcode,
charcode,
_kerningType ) );
#if OSG_VERSION_LESS_THAN(3,5,2)
osg::Vec2 delta(activefont->getKerning(previous_charcode,
charcode,
_kerningType));
#else
osg::Vec2 delta(activefont->getKerning(_fontSize,
previous_charcode,
charcode,
_kerningType));
#endif
cursor.x() += delta.x() * wr;
cursor.y() += delta.y() * hr;
break;
}
case RIGHT_TO_LEFT:
{
osg::Vec2 delta( activefont->getKerning( charcode,
previous_charcode,
_kerningType ) );
#if OSG_VERSION_LESS_THAN(3,5,2)
osg::Vec2 delta(activefont->getKerning(charcode,
previous_charcode,
_kerningType));
#else
osg::Vec2 delta(activefont->getKerning(_fontSize, charcode,
previous_charcode,
_kerningType));
#endif
cursor.x() -= delta.x() * wr;
cursor.y() -= delta.y() * hr;
break;

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "CustomEvent.hxx"
namespace simgear

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "DeviceEvent.hxx"
#include <osgGA/GUIEventAdapter>

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "KeyboardEvent.hxx"
#include "utf8.h"

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "MouseEvent.hxx"
#include <osgGA/GUIEventAdapter>

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "KeyboardEvent.hxx"
#include <osgViewer/Viewer>

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "BoxLayout.hxx"
#include "SpacerItem.hxx"
#include <simgear/canvas/Canvas.hxx>

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "Layout.hxx"
#include <simgear/debug/logstream.hxx>

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "LayoutItem.hxx"
#include <simgear/canvas/Canvas.hxx>

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "NasalWidget.hxx"
#include <simgear/canvas/Canvas.hxx>

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "SpacerItem.hxx"
namespace simgear

View File

@@ -109,10 +109,13 @@
# define SG_UNIX
#endif
#if defined( __GNUC__ )
# define DEPRECATED __attribute__ ((deprecated))
#ifdef __GNUC__
#define SG_DEPRECATED(func) func __attribute__ ((deprecated))
#elif defined(_MSC_VER)
#define SG_DEPRECATED(func) __declspec(deprecated) func
#else
# define DEPRECATED
#pragma message("WARNING: You need to implement SG_DEPRECATED for this compiler")
#define SG_DEPRECATED(func) func
#endif
#if defined(__clang__)

View File

@@ -18,7 +18,8 @@
// 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 <simgear/debug/BufferedLogCallback.hxx>
#include <boost/foreach.hpp>

View File

@@ -24,7 +24,7 @@
#define SG_DEBUG_BUFFEREDLOGCALLBACK_HXX
#include <vector>
#include <memory> // for std::auto_ptr
#include <memory> // for std::unique_ptr
#include <simgear/debug/logstream.hxx>
@@ -70,10 +70,10 @@ public:
unsigned int threadsafeCopy(vector_cstring& aOutput);
private:
class BufferedLogCallbackPrivate;
std::auto_ptr<BufferedLogCallbackPrivate> d;
std::unique_ptr<BufferedLogCallbackPrivate> d;
};
} // of namespace simgear
#endif // of SG_DEBUG_BUFFEREDLOGCALLBACK_HXX
#endif // of SG_DEBUG_BUFFEREDLOGCALLBACK_HXX

View File

@@ -42,6 +42,10 @@ typedef enum {
/**
* Define the possible logging priorities (and their order).
*
* Caution - unfortunately, this enum is exposed to Nasal via the logprint()
* function as an integer parameter. Therefore, new values should only be
* appended, or the priority Nasal reports to compiled code will change.
*/
typedef enum {
SG_BULK = 1, // For frequent messages
@@ -49,8 +53,11 @@ typedef enum {
SG_INFO, // Informatory messages
SG_WARN, // 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_ABORT // Abandon ship (core)
SG_DEV_WARN, // Warning for developers, translated to other priority
SG_DEV_ALERT // Alert for developers, translated
} sgDebugPriority;

View File

@@ -36,7 +36,7 @@
#include <simgear/threads/SGQueue.hxx>
#include <simgear/threads/SGGuard.hxx>
#include <simgear/misc/sgstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#if defined (SG_WINDOWS)
@@ -46,39 +46,7 @@
#include <io.h>
#endif
const char* 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";
default: return "unknown";
}
}
//////////////////////////////////////////////////////////////////////////////
@@ -102,6 +70,40 @@ void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
m_class = c;
}
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";
default: return "unknown";
}
}
} // of namespace simgear
//////////////////////////////////////////////////////////////////////////////
@@ -177,13 +179,13 @@ public:
#endif
class LogStreamPrivate : public SGThread
class logstream::LogStreamPrivate : public SGThread
{
private:
/**
* storage of a single log entry. Note this is not used for a persistent
* store, but rather for short term buffering between the submitting
* and output threads.
* 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
{
@@ -195,19 +197,24 @@ private:
{
}
sgDebugClass debugClass;
sgDebugPriority debugPriority;
const sgDebugClass debugClass;
const sgDebugPriority debugPriority;
const char* file;
int line;
std::string message;
const int line;
const std::string message;
};
/**
* RAII object to pause the logging thread if it's running, and restart it.
* used to safely make configuration changes.
*/
class PauseThread
{
public:
PauseThread(LogStreamPrivate* parent) : m_parent(parent)
PauseThread(LogStreamPrivate* parent)
: m_parent(parent)
, m_wasRunning(m_parent->stop())
{
m_wasRunning = m_parent->stop();
}
~PauseThread()
@@ -218,17 +225,13 @@ private:
}
private:
LogStreamPrivate* m_parent;
bool m_wasRunning;
const bool m_wasRunning;
};
public:
LogStreamPrivate() :
m_logClass(SG_ALL),
m_logPriority(SG_ALERT),
#if defined (SG_WINDOWS)
m_stdout_isRedirectedAlready(false),
m_stderr_isRedirectedAlready(false),
#endif
m_isRunning(false)
m_logPriority(SG_ALERT)
{
#if defined (SG_WINDOWS)
/*
@@ -332,7 +335,7 @@ public:
~LogStreamPrivate()
{
BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
for (simgear::LogCallback* cb : m_callbacks) {
delete cb;
}
}
@@ -340,6 +343,10 @@ public:
SGMutex m_lock;
SGBlockingQueue<LogEntry> m_entries;
// log entries posted during startup
std::vector<LogEntry> m_startupEntries;
bool m_startupLogging = false;
typedef std::vector<simgear::LogCallback*> CallbackVec;
CallbackVec m_callbacks;
/// subset of callbacks which correspond to stdout / console,
@@ -348,12 +355,13 @@ public:
sgDebugClass m_logClass;
sgDebugPriority m_logPriority;
bool m_isRunning;
bool m_isRunning = false;
#if defined (SG_WINDOWS)
// track whether the console was redirected on launch (in the constructor, which is called early on)
bool m_stderr_isRedirectedAlready;
bool m_stdout_isRedirectedAlready;
bool m_stderr_isRedirectedAlready = false;
bool m_stdout_isRedirectedAlready = false;
#endif
bool m_developerMode = false;
void startLog()
{
@@ -363,6 +371,16 @@ public:
start();
}
void setStartupLoggingEnabled(bool on)
{
if (m_startupLogging == on) {
return;
}
m_startupLogging = on;
m_startupEntries.clear();
}
virtual void run()
{
while (1) {
@@ -373,8 +391,14 @@ public:
return;
}
if (m_startupLogging) {
// save to the startup list for not-yet-added callbacks to
// pull down on startup
m_startupEntries.push_back(entry);
}
// submit to each installed callback in turn
BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
for (simgear::LogCallback* cb : m_callbacks) {
(*cb)(entry.debugClass, entry.debugPriority,
entry.file, entry.line, entry.message);
}
@@ -401,6 +425,13 @@ public:
{
PauseThread pause(this);
m_callbacks.push_back(cb);
// we clear startup entries not using this, so always safe to run
// this code, container will simply be empty
for (auto entry : m_startupEntries) {
(*cb)(entry.debugClass, entry.debugPriority,
entry.file, entry.line, entry.message);
}
}
void removeCallback(simgear::LogCallback* cb)
@@ -424,6 +455,7 @@ public:
bool would_log( sgDebugClass c, sgDebugPriority p ) const
{
p = translatePriority(p);
if (p >= SG_INFO) return true;
return ((c & m_logClass) != 0 && p >= m_logPriority);
}
@@ -431,53 +463,126 @@ public:
void log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg)
{
p = translatePriority(p);
LogEntry entry(c, p, fileName, line, msg);
m_entries.push(entry);
}
sgDebugPriority translatePriority(sgDebugPriority in) const
{
if (in == SG_DEV_WARN) {
return m_developerMode ? SG_WARN : SG_DEBUG;
}
if (in == SG_DEV_ALERT) {
return m_developerMode ? SG_POPUP : SG_WARN;
}
return in;
}
};
/////////////////////////////////////////////////////////////////////////////
static logstream* global_logstream = NULL;
static LogStreamPrivate* global_privateLogstream = NULL;
static std::unique_ptr<logstream> global_logstream;
static SGMutex global_logStreamLock;
logstream::logstream()
{
global_privateLogstream = new LogStreamPrivate;
global_privateLogstream->startLog();
d.reset(new LogStreamPrivate);
d->startLog();
}
logstream::~logstream()
{
popup_msgs.clear();
global_privateLogstream->stop();
delete global_privateLogstream;
d->stop();
}
void
logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
{
global_privateLogstream->setLogLevels(c, p);
d->setLogLevels(c, p);
}
void logstream::setDeveloperMode(bool devMode)
{
d->m_developerMode = devMode;
}
void
logstream::addCallback(simgear::LogCallback* cb)
{
global_privateLogstream->addCallback(cb);
d->addCallback(cb);
}
void
logstream::removeCallback(simgear::LogCallback* cb)
{
global_privateLogstream->removeCallback(cb);
d->removeCallback(cb);
}
void
logstream::log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg)
{
global_privateLogstream->log(c, p, fileName, line, msg);
d->log(c, p, fileName, line, msg);
}
void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns)
{
unsigned int i, j;
char temp[3000], temp1[3000];
*temp = 0;
for (i = 0; i < len + ((len % columns) ? (columns - len % columns) : 0); i++)
{
if (strlen(temp) > 500) return;
/* print offset */
if (i % columns == 0)
{
sprintf(temp1, "0x%06x: ", i);
strcat(temp, temp1);
}
/* print hex data */
if (i < len)
{
sprintf(temp1, "%02x ", 0xFF & ((char*)mem)[i]);
strcat(temp, temp1);
}
else /* end of block, just aligning for ASCII dump */
{
strcat(temp, " ");
}
/* print ASCII dump */
if (i % columns == (columns - 1))
{
for (j = i - (columns - 1); j <= i; j++)
{
if (j >= len) /* end of block, not really printing */
{
strcat(temp, " ");
}
else if (((((char*)mem)[j]) & (char)0x7f) > 32) /* printable char */
{
char t2[2];
t2[0] = 0xFF & ((char*)mem)[j];
t2[1] = 0;
strcat(temp, t2);
}
else /* other char */
{
strcat(temp, ".");
}
}
log(c, p, fileName, line, temp );
*temp = 0;
}
}
}
void
@@ -507,31 +612,31 @@ logstream::has_popup()
bool
logstream::would_log( sgDebugClass c, sgDebugPriority p ) const
{
return global_privateLogstream->would_log(c,p);
return d->would_log(c,p);
}
sgDebugClass
logstream::get_log_classes() const
{
return global_privateLogstream->m_logClass;
return d->m_logClass;
}
sgDebugPriority
logstream::get_log_priority() const
{
return global_privateLogstream->m_logPriority;
return d->m_logPriority;
}
void
logstream::set_log_priority( sgDebugPriority p)
{
global_privateLogstream->setLogLevels(global_privateLogstream->m_logClass, p);
d->setLogLevels(d->m_logClass, p);
}
void
logstream::set_log_classes( sgDebugClass c)
{
global_privateLogstream->setLogLevels(c, global_privateLogstream->m_logPriority);
d->setLogLevels(c, d->m_logPriority);
}
@@ -547,49 +652,54 @@ sglog()
SGGuard<SGMutex> g(global_logStreamLock);
if( !global_logstream )
global_logstream = new logstream();
return *global_logstream;
global_logstream.reset(new logstream);
return *(global_logstream.get());
}
void
logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
{
global_privateLogstream->addCallback(new FileLogCallback(aPath, c, p));
d->addCallback(new FileLogCallback(aPath, c, p));
}
namespace simgear
void logstream::setStartupLoggingEnabled(bool enabled)
{
d->setStartupLoggingEnabled(enabled);
}
void requestConsole()
{
void logstream::requestConsole()
{
#if defined (SG_WINDOWS)
/*
* 2016-09-20(RJH) - Reworked console handling
* This is part of the reworked console handling for Win32. This is for building as a Win32 GUI Subsystem where no
* console is allocated on launch. If building as a console app then the startup will ensure that a console is created - but
* we don't need to handle that.
* The new handling is quite simple:
* 1. The constructor will ensure that these streams exists. It will attach to the
* parent command prompt if started from the command prompt, otherwise the
* stdout/stderr will be bound to the NUL device.
* 2. with --console a window will always appear regardless of where the process was
* started from. Any non redirected streams will be redirected
* 3. You cannot use --console and either redirected stream.
*
* This is called after the Private Log Stream constructor so we need to undo any console that it has attached to.
*/
const bool stderrAlreadyRedirected = d->m_stderr_isRedirectedAlready;
const bool stdoutAlreadyRedirected = d->m_stdout_isRedirectedAlready;
if (!global_privateLogstream->m_stderr_isRedirectedAlready && !global_privateLogstream->m_stdout_isRedirectedAlready) {
/*
* 2016-09-20(RJH) - Reworked console handling
* This is part of the reworked console handling for Win32. This is for building as a Win32 GUI Subsystem where no
* console is allocated on launch. If building as a console app then the startup will ensure that a console is created - but
* we don't need to handle that.
* The new handling is quite simple:
* 1. The constructor will ensure that these streams exists. It will attach to the
* parent command prompt if started from the command prompt, otherwise the
* stdout/stderr will be bound to the NUL device.
* 2. with --console a window will always appear regardless of where the process was
* started from. Any non redirected streams will be redirected
* 3. You cannot use --console and either redirected stream.
*
* This is called after the Private Log Stream constructor so we need to undo any console that it has attached to.
*/
if (!stderrAlreadyRedirected && !stdoutAlreadyRedirected) {
FreeConsole();
if (AllocConsole()) {
if (!global_privateLogstream->m_stdout_isRedirectedAlready)
if (!stdoutAlreadyRedirected)
freopen("conout$", "w", stdout);
if (!global_privateLogstream->m_stderr_isRedirectedAlready)
if (!stderrAlreadyRedirected)
freopen("conout$", "w", stderr);
//http://stackoverflow.com/a/25927081
//Clear the error state for each of the C++ standard stream objects.
//Clear the error state for each of the C++ standard stream objects.
std::wcout.clear();
std::cout.clear();
std::wcerr.clear();
@@ -602,11 +712,19 @@ void requestConsole()
}
namespace simgear
{
void requestConsole()
{
sglog().requestConsole();
}
void shutdownLogging()
{
SGGuard<SGMutex> g(global_logStreamLock);
delete global_logstream;
global_logstream = 0;
global_logstream.reset();
}
} // of namespace simgear

View File

@@ -30,7 +30,8 @@
#include <sstream>
#include <vector>
#include <memory>
// forward decls
class SGPath;
@@ -49,6 +50,8 @@ protected:
LogCallback(sgDebugClass c, sgDebugPriority p);
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
static const char* debugClassToString(sgDebugClass c);
private:
sgDebugClass m_class;
sgDebugPriority m_priority;
@@ -74,6 +77,14 @@ public:
~logstream();
static void initGlobalLogstream();
/**
* Helper force a console on platforms where it might optional, when
* we need to show a console. This basically means Windows at the
* moment - on other plaforms it's a no-op
*/
void requestConsole();
/**
* Set the global log class and priority level.
* @param c debug class
@@ -93,12 +104,25 @@ public:
sgDebugPriority get_log_priority() const;
/**
* set developer mode on/off. In developer mode, SG_DEV_WARN messags
* are treated as warnings. In normal (non-developer) mode they are
* treated as SG_DEBUG.
*/
void setDeveloperMode(bool devMode);
/**
* the core logging method
*/
void log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg);
/**
* output formatted hex dump of memory block
*/
void hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns = 16);
/**
* support for the SG_POPUP logging class
* set the content of the popup message
@@ -134,11 +158,21 @@ public:
void removeCallback(simgear::LogCallback* cb);
/**
* optionally record all entries and submit them to new log callbacks that
* are added. This allows simplified logging configuration, but still including
* early startup information in all logs.
*/
void setStartupLoggingEnabled(bool enabled);
private:
// constructor
logstream();
std::vector<std::string> popup_msgs;
class LogStreamPrivate;
std::unique_ptr<LogStreamPrivate> d;
};
logstream& sglog();
@@ -155,12 +189,14 @@ logstream& sglog();
do { if(sglog().would_log(C,P)) { \
std::ostringstream os; os << M; \
sglog().log(C, P, __FILE__, __LINE__, os.str()); \
if (P == SG_POPUP) sglog().popup(os.str()); \
if ((P) == SG_POPUP) sglog().popup(os.str()); \
} } while(0)
#ifdef FG_NDEBUG
# define SG_LOG(C,P,M) do { if(P == SG_POPUP) SG_LOGX(C,P,M) } while(0)
# define SG_LOG(C,P,M) do { if((P) == SG_POPUP) SG_LOGX(C,P,M) } while(0)
# define SG_HEXDUMP(C,P,MEM,LEN)
#else
# define SG_LOG(C,P,M) SG_LOGX(C,P,M)
# define SG_LOG_HEXDUMP(C,P,MEM,LEN) if(sglog().would_log(C,P)) sglog().hexdump(C, P, __FILE__, __LINE__, MEM, LEN)
#endif
#define SG_ORIGIN __FILE__ ":" SG_STRINGIZE(__LINE__)

View File

@@ -0,0 +1,14 @@
include (SimGearComponent)
set(HEADERS EmbeddedResource.hxx EmbeddedResourceManager.hxx)
set(SOURCES EmbeddedResource.cxx EmbeddedResourceManager.cxx)
simgear_component(embedded_resources embedded_resources
"${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(test_embedded_resources embedded_resources_test.cxx)
target_link_libraries(test_embedded_resources ${TEST_LIBS})
add_test(embedded_resources
${EXECUTABLE_OUTPUT_PATH}/test_embedded_resources)
endif(ENABLE_TESTS)

View File

@@ -0,0 +1,265 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResource.cxx --- Class for pointing to/accessing an embedded resource
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <string>
#include <iosfwd>
#include <ios> // std::streamsize
#include <ostream>
#include <memory> // std::unique_ptr
#include <utility> // std::move()
#include <algorithm> // std::min()
#include <limits> // std::numeric_limits
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/CharArrayStream.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include "EmbeddedResource.hxx"
using std::string;
using std::unique_ptr;
// Inspired by <http://stackoverflow.com/a/21174979/4756009>
template<typename Derived, typename Base>
static unique_ptr<Derived> static_unique_ptr_cast(unique_ptr<Base> p)
{
auto d = static_cast<Derived *>(p.release());
return unique_ptr<Derived>(d);
}
namespace simgear
{
// ***************************************************************************
// * AbstractEmbeddedResource class *
// ***************************************************************************
AbstractEmbeddedResource::AbstractEmbeddedResource(const char *data,
std::size_t size)
: _data(data),
_size(size)
{ }
const char *AbstractEmbeddedResource::rawPtr() const
{
return _data;
}
std::size_t AbstractEmbeddedResource::rawSize() const
{
return _size;
}
string AbstractEmbeddedResource::str() const
{
if (_size > std::numeric_limits<string::size_type>::max()) {
throw sg_range_exception(
"Resource too large to fit in an std::string (size: " +
std::to_string(_size) + " bytes)");
}
return string(_data, _size);
}
// ***************************************************************************
// * RawEmbeddedResource class *
// ***************************************************************************
RawEmbeddedResource::RawEmbeddedResource(const char *data, std::size_t size)
: AbstractEmbeddedResource(data, size)
{ }
AbstractEmbeddedResource::CompressionType
RawEmbeddedResource::compressionType() const
{
return AbstractEmbeddedResource::CompressionType::NONE;
}
string RawEmbeddedResource::compressionDescr() const
{
return string("none");
}
unique_ptr<std::streambuf> RawEmbeddedResource::streambuf() const
{
// This is a read-only variant of CharArrayStreambuf
return unique_ptr<std::streambuf>(
new ROCharArrayStreambuf(rawPtr(), rawSize()));
}
unique_ptr<std::istream> RawEmbeddedResource::istream() const
{
return unique_ptr<std::istream>(new CharArrayIStream(rawPtr(), rawSize()));
}
// ***************************************************************************
// * ZlibEmbeddedResource class *
// ***************************************************************************
ZlibEmbeddedResource::ZlibEmbeddedResource(const char *data,
std::size_t compressedSize,
std::size_t uncompressedSize)
: AbstractEmbeddedResource(data, compressedSize),
_uncompressedSize(uncompressedSize),
_inBuf(nullptr),
_inBufSize(262144), // adjusted below in the constructor body
_outBuf(nullptr),
_outBufSize(262144),
_putbackSize(0) // default for best performance
{
static_assert(262144 <= std::numeric_limits<std::size_t>::max(),
"The std::size_t type is unexpectedly small.");
// No need to use an input buffer (where compressed data chunks are put for
// zlib to read and decompress) larger than the whole compressed resource!
_inBufSize = std::min(rawSize(), _inBufSize);
}
AbstractEmbeddedResource::CompressionType
ZlibEmbeddedResource::compressionType() const
{ return AbstractEmbeddedResource::CompressionType::ZLIB; }
string ZlibEmbeddedResource::compressionDescr() const
{ return string("zlib"); }
std::size_t ZlibEmbeddedResource::uncompressedSize() const
{ return _uncompressedSize; }
char* ZlibEmbeddedResource::getInputBufferStart()
{ return _inBuf; }
void ZlibEmbeddedResource::setInputBufferStart(char* inBuf)
{ _inBuf = inBuf; }
std::size_t ZlibEmbeddedResource::getInputBufferSize()
{ return _inBufSize; }
void ZlibEmbeddedResource::setInputBufferSize(std::size_t size)
{ _inBufSize = size; }
char* ZlibEmbeddedResource::getOutputBufferStart()
{ return _outBuf; }
void ZlibEmbeddedResource::setOutputBufferStart(char* outBuf)
{ _outBuf = outBuf; }
std::size_t ZlibEmbeddedResource::getOutputBufferSize()
{ return _outBufSize; }
void ZlibEmbeddedResource::setOutputBufferSize(std::size_t size)
{ _outBufSize = size; }
std::size_t ZlibEmbeddedResource::getPutbackSize()
{ return _putbackSize; }
void ZlibEmbeddedResource::setPutbackSize(std::size_t size)
{ _putbackSize = size; }
unique_ptr<std::streambuf> ZlibEmbeddedResource::streambuf() const
{
unique_ptr<CharArrayIStream> rawReaderIStream(
new CharArrayIStream(rawPtr(), rawSize()));
return unique_ptr<std::streambuf>(
new ZlibDecompressorIStreambuf(
std::move(rawReaderIStream),
SGPath(), // rawReaderIStream isn't bound to a file
ZLibCompressionFormat::ZLIB,
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
}
unique_ptr<std::istream> ZlibEmbeddedResource::istream() const
{
unique_ptr<CharArrayIStream> rawReaderIStream(
new CharArrayIStream(rawPtr(), rawSize()));
return unique_ptr<std::istream>(
new ZlibDecompressorIStream(
std::move(rawReaderIStream),
SGPath(), // rawReaderIStream isn't bound to a file
ZLibCompressionFormat::ZLIB,
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
}
std::string ZlibEmbeddedResource::str() const
{
static constexpr std::size_t bufSize = 65536;
static_assert(bufSize <= std::numeric_limits<std::streamsize>::max(),
"Type std::streamsize is unexpectedly small");
static_assert(bufSize <= std::numeric_limits<string::size_type>::max(),
"Type std::string::size_type is unexpectedly small");
unique_ptr<char[]> buf(new char[bufSize]);
auto decompressor =
static_unique_ptr_cast<ZlibDecompressorIStream>(istream());
std::streamsize nbCharsRead;
string result;
if (_uncompressedSize > std::numeric_limits<string::size_type>::max()) {
throw sg_range_exception(
"Resource too large to fit in an std::string (uncompressed size: "
+ std::to_string(_uncompressedSize) + " bytes)");
} else {
result.reserve(static_cast<string::size_type>(_uncompressedSize));
}
do {
decompressor->read(buf.get(), bufSize);
nbCharsRead = decompressor->gcount();
if (nbCharsRead > 0) {
result.append(buf.get(), nbCharsRead);
}
} while (*decompressor);
// decompressor->fail() would *not* indicate an error, due to the semantics
// of std::istream::read().
if (decompressor->bad()) {
throw sg_io_exception("Error while extracting a compressed resource");
}
return result;
}
// ***************************************************************************
// * Stream insertion operators *
// ***************************************************************************
std::ostream& operator<<(std::ostream& os,
const RawEmbeddedResource& resource)
{ // This won't escape double quotes, backslashes, etc. in resource.str().
return os << "RawEmbeddedResource:\n"
" compressionType = \"" << resource.compressionDescr() << "\"\n"
" rawPtr = " << (void*) resource.rawPtr() << "\n"
" rawSize = " << resource.rawSize();
}
std::ostream& operator<<(std::ostream& os,
const ZlibEmbeddedResource& resource)
{ // This won't escape double quotes, backslashes, etc. in resource.str().
return os << "ZlibEmbeddedResource:\n"
" compressionType = \"" << resource.compressionDescr() << "\"\n"
" rawPtr = " << (void*) resource.rawPtr() << "\n"
" rawSize = " << resource.rawSize() << "\n"
" uncompressedSize = " << resource.uncompressedSize();
}
} // of namespace simgear

View File

@@ -0,0 +1,163 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResource.hxx --- Class for pointing to/accessing an embedded resource
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#ifndef FG_EMBEDDEDRESOURCE_HXX
#define FG_EMBEDDEDRESOURCE_HXX
#include <iosfwd>
#include <string>
#include <ostream>
#include <memory> // std::unique_ptr
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <simgear/io/iostreams/zlibstream.hxx>
namespace simgear
{
// Abstract base class for embedded resources
class AbstractEmbeddedResource
{
public:
enum class CompressionType {
NONE = 0,
ZLIB
};
// Constructor.
//
// 'data' and 'size' indicate the resource contents. There is no requirement
// of null-termination, including for text data (given how
// EmbeddedResourceManager::getString() works, including a null terminator
// for text contents is actually counter-productive). The data may be of
// arbitrary type and size: binary, text, whatever. The constructed object
// (for derived classes since this one is abstract) does *not* hold a copy
// of the data, it just keeps a pointer to it and provides methods to access
// it. The data must therefore remain available as long as the object is in
// use---this class was designed for use with data stored in static
// variables.
explicit AbstractEmbeddedResource(const char *data, std::size_t size);
AbstractEmbeddedResource(const AbstractEmbeddedResource&) = default;
AbstractEmbeddedResource(AbstractEmbeddedResource&&) = default;
AbstractEmbeddedResource& operator=(const AbstractEmbeddedResource&) = default;
AbstractEmbeddedResource& operator=(AbstractEmbeddedResource&&) = default;
virtual ~AbstractEmbeddedResource() = default;
// Return the pointer to beginning-of-resource contents---the same that was
// passed to the constructor.
const char *rawPtr() const;
// Return the resource size, as passed to the constructor. For a compressed
// resource, this is the compressed size; such resources provide an
// additional uncompressedSize() method.
std::size_t rawSize() const;
// Return an std::string object containing a copy of the resource contents.
// For a compressed resource, this is the data obtained after decompression.
virtual std::string str() const;
// Return an std::streambuf instance providing read-only access to the
// resource contents (in uncompressed form for compressed resources). This
// allows memory-friendly access to large resources by enabling incremental
// processing with transparent decompression for compressed resources.
virtual std::unique_ptr<std::streambuf> streambuf() const = 0;
// Return an std::istream instance providing read-only access to the
// resource contents (in uncompressed form for compressed resources).
//
// The same remark as for streambuf() applies. std::istream is simply a
// higher-level interface than std::streambuf, otherwise both allow the same
// kind of processing.
virtual std::unique_ptr<std::istream> istream() const = 0;
// Return the resource compression type.
virtual CompressionType compressionType() const = 0;
// Return a string description of the resource compression type. Examples:
// "none", "zlib".
virtual std::string compressionDescr() const = 0;
private:
// Pointer to the start of resource contents
const char *_data;
// Size of resource contents, in bytes
std::size_t _size;
};
// Class to describe an uncompressed resource. See AbstractEmbeddedResource.
class RawEmbeddedResource : public AbstractEmbeddedResource
{
public:
explicit RawEmbeddedResource(const char *data, std::size_t size);
AbstractEmbeddedResource::CompressionType compressionType() const override;
std::string compressionDescr() const override;
// The str() method is inherited from AbstractEmbeddedResource
std::unique_ptr<std::streambuf> streambuf() const override;
std::unique_ptr<std::istream> istream() const override;
};
// Class to describe a zlib-compressed resource.
//
// Instances of this class point to resource contents stored in the stream
// format documented in RFC 1950.
class ZlibEmbeddedResource : public AbstractEmbeddedResource
{
public:
explicit ZlibEmbeddedResource(const char *data, std::size_t compressedSize,
std::size_t uncompressedSize);
AbstractEmbeddedResource::CompressionType compressionType() const override;
std::string compressionDescr() const override;
// Return the resource uncompressed size, in bytes.
std::size_t uncompressedSize() const;
std::string str() const override;
std::unique_ptr<std::streambuf> streambuf() const override;
std::unique_ptr<std::istream> istream() const override;
// Getters and setters for parameters used in streambuf() and istream().
// Calling any of the setters affects the subsequent streambuf() and
// istream() calls.
char* getInputBufferStart();
void setInputBufferStart(char* inBuf);
std::size_t getInputBufferSize();
void setInputBufferSize(std::size_t size);
char* getOutputBufferStart();
void setOutputBufferStart(char* outBuf);
std::size_t getOutputBufferSize();
void setOutputBufferSize(std::size_t size);
std::size_t getPutbackSize();
void setPutbackSize(std::size_t size);
private:
std::size_t _uncompressedSize;
char* _inBuf;
std::size_t _inBufSize;
char* _outBuf;
std::size_t _outBufSize;
std::size_t _putbackSize;
};
// These functions are essentially intended for troubleshooting purposes.
std::ostream& operator<<(std::ostream&, const RawEmbeddedResource&);
std::ostream& operator<<(std::ostream&, const ZlibEmbeddedResource&);
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCE_HXX

View File

@@ -0,0 +1,235 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager.cxx --- Manager class for resources embedded in an
// executable
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <memory>
#include <utility> // std::move()
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <simgear/structure/exception.hxx>
#include "EmbeddedResource.hxx"
#include "EmbeddedResourceManager.hxx"
#include "EmbeddedResourceManager_private.hxx"
using std::string;
using std::shared_ptr;
using std::unique_ptr;
namespace simgear
{
static unique_ptr<EmbeddedResourceManager> staticInstance;
// ***************************************************************************
// * EmbeddedResourceManager::Impl *
// ***************************************************************************
EmbeddedResourceManager::Impl::Impl()
: dirty(true)
{ }
void
EmbeddedResourceManager::Impl::rehash()
{
// Update the list of resource pools to search when looking up a resource.
// This allows to optimize resource lookup: no need to parse, split and hash
// the same locale string every time to find the corresponding resource
// pools.
poolSearchList = listOfResourcePoolsToSearch(selectedLocale);
dirty = false;
}
string
EmbeddedResourceManager::Impl::getLocale() const
{
return selectedLocale;
}
string
EmbeddedResourceManager::Impl::selectLocale(const std::string& locale)
{
string previousLocale = std::move(selectedLocale);
selectedLocale = locale;
dirty = true;
return previousLocale;
}
// Static method
std::vector<string>
EmbeddedResourceManager::Impl::localesSearchList(const string& locale)
{
std::vector<string> result;
if (locale.empty()) {
result.push_back(string()); // only the default locale
} else {
std::size_t sepIdx = locale.find_first_of('_');
if (sepIdx == string::npos) {
// Try the given “locale” first (e.g., fr), then the default locale
result = std::vector<string>({locale, string()});
} else {
string langCode = locale.substr(0, sepIdx);
// Try the given “locale” first (e.g., fr_FR), then the language code
// (e.g., fr) and finally the default locale
result = std::vector<string>({locale, langCode, string()});
}
}
return result;
}
auto
EmbeddedResourceManager::Impl::listOfResourcePoolsToSearch(
const string& locale) const
-> std::vector< shared_ptr<ResourcePool> >
{
std::vector<string> searchedLocales = localesSearchList(locale);
std::vector< shared_ptr<ResourcePool> > result;
for (const string& loc: searchedLocales) {
auto poolPtrIt = localeToResourcePoolMap.find(loc);
// Don't store pointers to empty resource pools in 'result'. This
// optimizes resource fetching a little bit, but requires that all
// resources are added before this method is called.
if (poolPtrIt != localeToResourcePoolMap.end()) {
// Copy a shared_ptr<ResourcePool>
result.push_back(poolPtrIt->second);
}
}
return result;
}
// Static method
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::Impl::lookupResourceInPools(
const string& virtualPath,
const std::vector< shared_ptr<ResourcePool> >& aPoolSearchList)
{
// Search the provided resource pools in proper order. For instance, the one
// for 'fr_FR', then the one for 'fr' and finally the one for the default
// locale. Return the first resource found in one of these pools.
for (const shared_ptr<ResourcePool>& poolPtr: aPoolSearchList) {
auto resourcePtrIt = poolPtr->find(virtualPath);
if (resourcePtrIt != poolPtr->end()) {
// Copy a shared_ptr<const AbstractEmbeddedResource>
return resourcePtrIt->second;
}
}
return shared_ptr<const AbstractEmbeddedResource>(); // null shared_ptr object
}
void
EmbeddedResourceManager::Impl::addResource(
const string& virtualPath,
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const string& locale)
{
// Find the resource pool corresponding to the specified locale
shared_ptr<ResourcePool>& resPoolPtr = localeToResourcePoolMap[locale];
if (!resPoolPtr) {
resPoolPtr.reset(new ResourcePool());
}
auto emplaceRetval = resPoolPtr->emplace(virtualPath, std::move(resourcePtr));
if (!emplaceRetval.second) {
const string localeDescr =
(locale.empty()) ? "the default locale" : "locale '" + locale + "'";
throw sg_error(
"Virtual path already in use for " + localeDescr +
" in the EmbeddedResourceManager: '" + virtualPath + "'");
}
dirty = true;
}
// ***************************************************************************
// * EmbeddedResourceManager *
// ***************************************************************************
EmbeddedResourceManager::EmbeddedResourceManager()
: p(unique_ptr<Impl>(new Impl))
{ }
const unique_ptr<EmbeddedResourceManager>&
EmbeddedResourceManager::createInstance()
{
staticInstance.reset(new EmbeddedResourceManager);
return staticInstance;
}
const unique_ptr<EmbeddedResourceManager>&
EmbeddedResourceManager::instance()
{
return staticInstance;
}
string
EmbeddedResourceManager::getLocale() const
{
return p->getLocale();
}
string
EmbeddedResourceManager::selectLocale(const std::string& locale)
{
return p->selectLocale(locale);
}
void
EmbeddedResourceManager::addResource(
const string& virtualPath,
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const string& locale)
{
p->addResource(virtualPath, std::move(resourcePtr), locale);
}
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath) const
{
if (p->dirty) {
p->rehash(); // update p->poolSearchList
}
// Use the selected locale
return p->lookupResourceInPools(virtualPath, p->poolSearchList);
}
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath,
const string& locale) const
{
// In this overload, we don't use the cached list of pools
// (p->poolSearchList), therefore there is no need to check the 'dirty' flag
// or to rehash().
return p->lookupResourceInPools(virtualPath,
p->listOfResourcePoolsToSearch(locale));
}
} // of namespace simgear

View File

@@ -0,0 +1,204 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager.hxx --- Manager class for resources embedded in an
// executable
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#ifndef FG_EMBEDDEDRESOURCEMANAGER_HXX
#define FG_EMBEDDEDRESOURCEMANAGER_HXX
#include <string>
#include <memory> // std::unique_ptr, std::shared_ptr
#include <unordered_map>
#include <vector>
#include <utility> // std::forward()
#include <cstddef> // std::size_t
#include <simgear/structure/exception.hxx>
#include "EmbeddedResource.hxx"
namespace simgear
{
class EmbeddedResourceManager
{
public:
EmbeddedResourceManager(const EmbeddedResourceManager&) = delete;
EmbeddedResourceManager& operator=(const EmbeddedResourceManager&) = delete;
EmbeddedResourceManager(EmbeddedResourceManager&&) = delete;
EmbeddedResourceManager& operator=(EmbeddedResourceManager&&) = delete;
// The instance is created by createInstance() -> private constructor
// but it should be deleted by its owning std::unique_ptr -> public destructor
~EmbeddedResourceManager() = default;
// Static creator
static const std::unique_ptr<EmbeddedResourceManager>& createInstance();
// Singleton accessor
static const std::unique_ptr<EmbeddedResourceManager>& instance();
// Return the currently-selected “locale”[*] for resource fetching.
//
// [*] For instance: std::string("") for the default locale
// std::string("fr") for French
// std::string("fr_FR") for French from France
std::string getLocale() const;
// Select the locale for which resources will be returned in the future, for
// the getResourceOrNullPtr(), getResource(), getString(), getStreambuf()
// and getIStream() overloads that don't have a 'locale' parameter.
// May be called several times. Return the previously-selected locale.
//
// If you just want to fetch one or two resources in a particular “locale”
// (language), it is simpler to use an overload of one of the
// getResourceOrNullPtr(), getResource(), ..., getIStream() methods that has
// a 'locale' parameter.
std::string selectLocale(const std::string& locale);
// Add a resource for the specified locale to the embedded resource manager.
// This method acts as a sink for its second argument (the std::unique_ptr
// typically has to be std::move()d). If 'locale' is empty, the resource is
// added for the default locale.
void addResource(const std::string& virtualPath,
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const std::string& locale = std::string());
// Get access to a resource.
//
// Fetch the resource for the selected locale (cf. selectLocale()), with
// fallback behavior[1]. If no resource is found for the given
// 'virtualPath', return a null
// std::shared_ptr<const AbstractEmbeddedResource>.
//
// [1] This means that for instance, if the selected locale is 'es_ES', the
// resource is first looked up for the 'es_ES' “locale”; then, if not
// found, for 'es'; and finally, if still not found, for the default
// locale ''.
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
const std::string& virtualPath) const;
// Same as the previous overload, except the resource is fetched for the
// specified locale (with fallback behavior) instead of for the selected
// locale. Use an empty 'locale' parameter to fetch the resource for the
// default locale.
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
const std::string& virtualPath,
const std::string& locale) const;
// Same overloads as for getResourceOrNullPtr(), except that if the resource
// isn't found, then an sg_exception is raised. These methods never return
// a null or empty std::shared_ptr<const AbstractEmbeddedResource>.
template <typename ...Args>
std::shared_ptr<const AbstractEmbeddedResource> getResource(
const std::string& virtualPath, Args&& ...args) const
{
const auto resPtr = getResourceOrNullPtr(virtualPath,
std::forward<Args>(args)...);
if (!resPtr) {
throw sg_exception("No embedded resource found at virtual path '" +
virtualPath + "'");
}
return resPtr;
}
// Get a resource contents in the form of an std::string. Raise an
// sg_exception if no resource is found for the specified 'virtualPath'.
//
// The returned std::string is a copy of the resource contents (possibly
// transparently decompressed, cf. simgear::ZlibEmbeddedResource).
template <typename ...Args>
std::string getString(const std::string& virtualPath, Args&& ...args) const
{
return getResource(virtualPath, std::forward<Args>(args)...)->str();
}
// Get access to a resource via an std::streambuf instance. Raise an
// sg_exception if no resource is found for the specified 'virtualPath'.
//
// This allows one to incrementally process the resource contents without
// ever making a copy of it (including incremental, transparent
// decompression if the resource happens to be compressed---cf.
// simgear::ZlibEmbeddedResource).
template <typename ...Args>
std::unique_ptr<std::streambuf> getStreambuf(const std::string& virtualPath,
Args&& ...args) const
{
return getResource(virtualPath, std::forward<Args>(args)...)->streambuf();
}
// Get access to a resource via an std::istream instance. Raise an
// sg_exception if no resource is found for the specified 'virtualPath'.
//
// The same remarks made for getStreambuf() apply here too.
template <typename ...Args>
std::unique_ptr<std::istream> getIStream(const std::string& virtualPath,
Args&& ...args) const
{
return getResource(virtualPath, std::forward<Args>(args)...)->istream();
}
private:
// Constructor called from createInstance() only
explicit EmbeddedResourceManager();
class Impl;
const std::unique_ptr<Impl> p; // Pimpl idiom
};
// Explicit template instantiations
template
std::shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResource(const std::string& virtualPath) const;
template
std::string
EmbeddedResourceManager::getString(const std::string& virtualPath) const;
template
std::unique_ptr<std::streambuf>
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath) const;
template
std::unique_ptr<std::istream>
EmbeddedResourceManager::getIStream(const std::string& virtualPath) const;
// MSVC doesn't recognize these as template instantiations of what we defined
// above (this seems to be with “Visual Studio 14 2015”), therefore only
// include them with other compilers.
#ifndef _MSC_VER
template
std::shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResource(const std::string& virtualPath,
const std::string& locale) const;
template
std::string
EmbeddedResourceManager::getString(const std::string& virtualPath,
const std::string& locale) const;
template
std::unique_ptr<std::streambuf>
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath,
const std::string& locale) const;
template
std::unique_ptr<std::istream>
EmbeddedResourceManager::getIStream(const std::string& virtualPath,
const std::string& locale) const;
#endif // #ifndef _MSC_VER
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCEMANAGER_HXX

View File

@@ -0,0 +1,102 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager_private.hxx --- Private implementation class for
// SimGear's EmbeddedResourceManager
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#ifndef FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
#define FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
#include <string>
#include <memory> // std::unique_ptr, std::shared_ptr
#include <unordered_map>
#include <vector>
#include "EmbeddedResource.hxx"
namespace simgear
{
class EmbeddedResourceManager::Impl
{
public:
explicit Impl();
// Each “locale” for which addResource() has been used has an associated
// resource pool, that is a sort of directory of all resources declared in
// this locale. The resource pool for a given locale (e.g., 'fr' or 'de_DE')
// maps resource virtual paths to the corresponding resource descriptors
// (via std::shared_ptr<const AbstractEmbeddedResource> instances).
//
// Note: for optimal lookup performance, a tree would probably be better,
// since the expected use for each key here is to store a virtual
// path. But such an optimization is likely unneeded in most cases.
typedef std::unordered_map< std::string,
std::shared_ptr<const AbstractEmbeddedResource> >
ResourcePool;
// Return the list of “locales” to scan to implement fallback behavior when
// fetching a resource for the specified locale. This list will be searched
// from left to right. Examples:
//
// "" -> [""]
// "fr" -> ["fr", ""]
// "fr_FR" -> ["fr_FR", "fr", ""]
static std::vector<std::string> localesSearchList(const std::string& locale);
// Same as localesSearchList(), except it returns the resource pools instead
// of the “locale” strings, and only those pools that are not empty.
std::vector< std::shared_ptr<ResourcePool> > listOfResourcePoolsToSearch(
const std::string& locale) const;
// Look up, in each of the pools referred to by 'poolSearchList', the
// resource associated to 'virtualPath'. Return the first match.
static std::shared_ptr<const AbstractEmbeddedResource> lookupResourceInPools(
const std::string& virtualPath,
const std::vector< std::shared_ptr<ResourcePool> >& poolSearchList);
// Recompute p->poolSearchList. This method is automatically called whenever
// needed (lazily), so it doesn't need to be part of the public interface.
void rehash();
// Implement the corresponding EmbeddedResourceManager public methods
std::string getLocale() const;
std::string selectLocale(const std::string& locale);
// Ditto
void addResource(const std::string& virtualPath,
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const std::string& locale);
std::string selectedLocale;
// Each call to rehash() updates this member to contain precisely the
// (ordered) list of pools to search for a resource in the selected
// “locale”. This allows relatively cheap resource lookups, assuming the
// desired “locale” doesn't change all the time.
std::vector< std::shared_ptr<ResourcePool> > poolSearchList;
// Indicate whether 'poolSearchList' must be updated (i.e., resources have
// been added or the selected locale was changed without rehash() being
// called afterwards).
bool dirty;
// Maps each “locale name” to the corresponding resource pool.
std::unordered_map< std::string,
std::shared_ptr<ResourcePool> > localeToResourcePoolMap;
};
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX

View File

@@ -0,0 +1,412 @@
// -*- coding: utf-8 -*-
//
// embedded_resources_test.cxx --- Automated tests for the embedded resources
// system in SimGear
//
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <string>
#include <memory>
#include <ios> // std::streamsize
#include <iostream> // std::cout (used for progress info)
#include <limits> // std::numeric_limits
#include <type_traits> // std::make_unsigned()
#include <sstream>
#include <cstdlib> // EXIT_SUCCESS
#include <cstddef> // std::size_t
#include <simgear/misc/test_macros.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/CharArrayStream.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include "EmbeddedResource.hxx"
#include "EmbeddedResourceManager.hxx"
using std::cout;
using std::cerr;
using std::endl;
using std::string;
using std::unique_ptr;
using std::shared_ptr;
using simgear::AbstractEmbeddedResource;
using simgear::RawEmbeddedResource;
using simgear::ZlibEmbeddedResource;
using simgear::EmbeddedResourceManager;
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
// Safely convert a non-negative std::streamsize into an std::size_t. If
// impossible, bail out.
std::size_t streamsizeToSize_t(std::streamsize n)
{
SG_CHECK_GE(n, 0);
SG_CHECK_LE(static_cast<uStreamSize>(n),
std::numeric_limits<std::size_t>::max());
return static_cast<std::size_t>(n);
}
// This array is null-terminated, but we'll declare the resource size as
// sizeof(res1Array) - 1 so that the null char is *not* part of it. This
// way allows one to treat text and binary resources exactly the same way,
// with the conversion to std::string via a simple
// std::string(res1Array, resourceSize) not producing a bizarre std::string
// instance whose last character would be '\0' (followed in memory by the same
// '\0' used as C-style string terminator this time!).
static const char res1Array[] = "This is a simple embedded resource test.";
static const char res1frArray[] = "Ceci est un petit test de ressource "
"embarquée.";
static const char res1fr_FRArray[] = "Ceci est un petit test de ressource "
"embarquée (variante fr_FR).";
static const string lipsum = "\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque congue ornare\n\
congue. Mauris mollis est et porttitor condimentum. Vivamus laoreet blandit\n\
odio eget consectetur. Etiam quis magna eu enim luctus pretium. In et\n\
tristique nunc, non efficitur metus. Nullam efficitur tristique velit.\n\
Praesent et luctus nunc. Mauris eros eros, rutrum at molestie quis, egestas et\n\
lorem. Ut nulla turpis, eleifend sed mauris ac, faucibus molestie nulla.\n\
Quisque viverra vel turpis nec efficitur. Proin non rutrum velit. Nam sodales\n\
metus felis, eu pharetra velit posuere ut.";
// Should be enough to store the compressed lipsum (320 bytes are required
// with zlib 1.2.8, keeping some room to account for possible future format
// changes in the zlib output...). In any case, there is no risk of buffer
// overflow, because simgear::CharArrayOStream prevents this by design.
static char res2Array[350];
static const char res2frArray[] = "Un lorem ipsum un peu plus court...";
// Read data from a string and write it in compressed form to the specified
// buffer.
std::size_t writeCompressedDataToBuffer(const string& inputString,
char *outBuf,
std::size_t outBufSize)
{
simgear::CharArrayOStream res2Writer(outBuf, outBufSize);
std::istringstream iss(inputString);
simgear::ZlibCompressorIStream compressor(
iss,
SGPath(), /* no associated file */
9 /* highest compression level */);
static constexpr std::size_t bufSize = 1024;
unique_ptr<char[]> buf(new char[bufSize]);
std::size_t res2Size = 0;
do {
compressor.read(buf.get(), bufSize);
std::streamsize nBytes = compressor.gcount();
if (nBytes > 0) { // at least one char could be read
res2Writer.write(buf.get(), nBytes);
res2Size += nBytes;
}
} while (compressor && res2Writer);
SG_VERIFY(compressor.eof()); // all the compressed data has been read
// This would fail (among other causes) if the output buffer were too small
// to hold all of the compressed data.
SG_VERIFY(res2Writer);
return res2Size;
}
void initResources()
{
cout << "Creating the EmbeddedResourceManager instance and adding a few "
"resources to it" << endl;
const auto& resMgr = EmbeddedResourceManager::createInstance();
// The resource will *not* consider the null terminator to be in.
unique_ptr<const RawEmbeddedResource> res1(
new RawEmbeddedResource(res1Array, sizeof(res1Array) - 1));
resMgr->addResource("/path/to/resource1", std::move(res1));
unique_ptr<const RawEmbeddedResource> res1fr(
new RawEmbeddedResource(res1frArray, sizeof(res1frArray) - 1));
resMgr->addResource("/path/to/resource1", std::move(res1fr), "fr");
unique_ptr<const RawEmbeddedResource> res1fr_FR(
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray) - 1));
resMgr->addResource("/path/to/resource1", std::move(res1fr_FR), "fr_FR");
// Write the contents of 'lipsum' in compressed form to the 'res2Array'
// static buffer.
std::size_t res2Size = writeCompressedDataToBuffer(lipsum, res2Array,
sizeof(res2Array));
// Now we have a compressed resource to work with, plus the corresponding
// uncompressed output -> perfect for tests!
unique_ptr<const ZlibEmbeddedResource> res2(
new ZlibEmbeddedResource(res2Array, res2Size, lipsum.size()));
resMgr->addResource("/path/to/resource2", std::move(res2));
unique_ptr<const RawEmbeddedResource> res2fr(
new RawEmbeddedResource(res2frArray, sizeof(res2frArray) - 1));
resMgr->addResource("/path/to/resource2", std::move(res2fr), "fr");
// Explicitly select the default locale (typically, English). This is for
// clarity, but isn't required.
resMgr->selectLocale("");
}
// Auxiliary function for test_RawEmbeddedResource()
void auxTest_RawEmbeddedResource_streambuf()
{
cout << "Testing EmbeddedResourceManager::getStreambuf()" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
unique_ptr<std::streambuf> sbuf(resMgr->getStreambuf("/path/to/resource1"));
// Just to show an efficient algorithm. For real applications, use larger
// buffer sizes!
static constexpr std::size_t bufSize = 4;
unique_ptr<char[]> buf(new char[bufSize]); // intermediate buffer
std::streamsize nbCharsRead;
string result;
do {
nbCharsRead = sbuf->sgetn(buf.get(), bufSize);
// The conversion to std::size_t is safe because sbuf->sgetn() returned a
// non-negative value which, in this case, can't exceed bufSize.
result.append(buf.get(), streamsizeToSize_t((nbCharsRead)));
} while (nbCharsRead == bufSize);
SG_CHECK_EQUAL(result, "This is a simple embedded resource test.");
}
// Auxiliary function for test_RawEmbeddedResource()
void auxTest_RawEmbeddedResource_istream()
{
cout << "Testing EmbeddedResourceManager::getIStream()" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
unique_ptr<std::istream> iStream(resMgr->getIStream("/path/to/resource1"));
// This is convenient, but be aware that still in 2017, some buggy C++
// compilers don't allow the exception to be caught: cf.
// <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145>.
iStream->exceptions(std::ios_base::badbit);
// Just to show an efficient algorithm. For real applications, use larger
// buffer sizes!
static constexpr std::size_t bufSize = 4;
unique_ptr<char[]> buf(new char[bufSize]); // intermediate buffer
string result;
do {
iStream->read(buf.get(), bufSize);
result.append(buf.get(), iStream->gcount());
} while (*iStream); // iStream *points* to an std::istream
// 1) If set, badbit would have caused an exception to be raised (see above).
// 2) failbit doesn't necessarily indicate an error here: it is set as soon
// as the read() call can't provide the requested number of characters.
SG_VERIFY(iStream->eof() && !iStream->bad());
SG_CHECK_EQUAL(result, "This is a simple embedded resource test.");
}
void test_RawEmbeddedResource()
{
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
"a RawEmbeddedResource" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
string("This is a simple embedded resource test."));
// Get a shared_ptr to a const AbstractEmbeddedResource
const auto res1abs = resMgr->getResource("/path/to/resource1");
// Okay because we know this resource is not a compressed one
const auto res1 =
std::dynamic_pointer_cast<const RawEmbeddedResource>(res1abs);
SG_VERIFY(res1);
// Print a representation of the resource metadata
std::cout << "\n/path/to/resource1 -> " << *res1 << "\n\n";
// The following methods would work the same with res1abs
SG_CHECK_EQUAL_NOSTREAM(res1->compressionType(),
AbstractEmbeddedResource::CompressionType::NONE);
SG_CHECK_EQUAL(res1->compressionDescr(), "none");
SG_CHECK_EQUAL(res1->rawPtr(), res1Array);
SG_CHECK_EQUAL(res1->rawSize(), sizeof(res1Array) - 1); // see above
SG_CHECK_EQUAL(res1->str(),
string("This is a simple embedded resource test."));
auxTest_RawEmbeddedResource_streambuf();
auxTest_RawEmbeddedResource_istream();
// Just reload and recheck the resource, because we can :)
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
string("This is a simple embedded resource test."));
}
void test_ZlibEmbeddedResource()
{
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
"a ZlibEmbeddedResource" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
lipsum);
// Get a shared_ptr to a const AbstractEmbeddedResource
const auto res2abs = resMgr->getResource("/path/to/resource2");
// Okay because we know this resource is a Zlib-compressed one
const auto res2 =
std::dynamic_pointer_cast<const ZlibEmbeddedResource>(res2abs);
SG_VERIFY(res2);
SG_CHECK_EQUAL(res2->uncompressedSize(), lipsum.size());
// Print a representation of the resource metadata
std::cout << "\n/path/to/resource2 -> " << *res2 << "\n\n";
cout << "Resource 2 compression ratio: " <<
static_cast<float>(res2->uncompressedSize()) /
static_cast<float>(res2->rawSize()) << "\n";
// Just reload and recheck the resource
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"), lipsum);
}
void test_getMissingResources()
{
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
"fetch inexistent resources" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
SG_VERIFY(!resMgr->getResourceOrNullPtr("/inexistant/resource"));
bool gotException = false;
try {
resMgr->getResource("/inexistant/resource");
} catch (const sg_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
gotException = false;
try {
resMgr->getString("/other/inexistant/resource");
} catch (const sg_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
void test_addAlreadyExistingResource()
{
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
"add an already existing resource" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
for (const string& locale: {"", "fr", "fr_FR"}) {
// For these tests, we don't care about the resource contents -> no need
// to substract 1 from the result of sizeof() as we did above.
unique_ptr<const RawEmbeddedResource> someRes(
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray)));
bool gotException = false;
try {
resMgr->addResource("/path/to/resource1", std::move(someRes), locale);
} catch (const sg_error&) {
gotException = true;
}
SG_VERIFY(gotException);
}
}
void test_localeDependencyOfResourceFetching()
{
cout << "Testing the locale-dependency of resource fetching from "
"EmbeddedResourceManager" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
resMgr->selectLocale(""); // select the default locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"This is a simple embedded resource test.");
// Switch to the 'fr_FR' locale (French from France)
resMgr->selectLocale("fr_FR");
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"Ceci est un petit test de ressource embarquée (variante "
"fr_FR).");
// This one is for the 'fr' “locale”, obtained as fallback since there is no
// resource mapped to /path/to/resource2 for the 'fr_FR' “locale”.
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
"Un lorem ipsum un peu plus court...");
// Explicitly ask for the resource in the default locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
"This is a simple embedded resource test.");
// Switch to the 'fr' locale (French)
resMgr->selectLocale("fr");
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"Ceci est un petit test de ressource embarquée.");
// Explicitly ask for the resource in the 'fr_FR' locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
"Ceci est un petit test de ressource embarquée "
"(variante fr_FR).");
// Switch to the default locale
resMgr->selectLocale("");
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"This is a simple embedded resource test.");
// Explicitly ask for the resource in the 'fr' locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr"),
"Ceci est un petit test de ressource embarquée.");
// Explicitly ask for the resource in the 'fr_FR' locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
"Ceci est un petit test de ressource embarquée "
"(variante fr_FR).");
// Explicitly ask for the resource in the default locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
"This is a simple embedded resource test.");
}
void test_getLocaleAndSelectLocale()
{
cout << "Testing the getLocale() and selectLocale() methods of "
"EmbeddedResourceManager" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
for (const string& locale: {"", "fr", "fr_FR", "de_DE"}) {
// The important effects of setLocale() are tested in
// test_localeDependencyOfResourceFetching()
resMgr->selectLocale(locale);
SG_CHECK_EQUAL(resMgr->getLocale(), locale);
}
}
int main(int argc, char **argv)
{
// Initialize the EmbeddedResourceManager instance, add a few resources
// to it and call its selectLocale() method.
initResources();
test_RawEmbeddedResource();
test_ZlibEmbeddedResource();
test_getMissingResources();
test_addAlreadyExistingResource();
test_localeDependencyOfResourceFetching();
test_getLocaleAndSelectLocale();
return EXIT_SUCCESS;
}

View File

@@ -10,13 +10,14 @@ if(ENABLE_TESTS)
add_executable(test_metar test_metar.cxx)
if (SIMGEAR_SHARED)
target_link_libraries(test_metar SimGearScene)
target_link_libraries(test_metar SimGearScene ${GDAL_LIBRARY})
else()
target_link_libraries(test_metar
SimGearScene SimGearCore
${CMAKE_THREAD_LIBS_INIT}
${ZLIB_LIBRARY}
${RT_LIBRARY})
${RT_LIBRARY}
${GDAL_LIBRARY})
endif()
add_test(metar ${EXECUTABLE_OUTPUT_PATH}/test_metar)

View File

@@ -4,6 +4,7 @@
#endif
#include <simgear/compiler.h>
#include <simgear/misc/test_macros.hxx>
#include <iostream>
#include <cstdlib>
@@ -23,75 +24,56 @@ using std::cerr;
using std::endl;
using std::string;
#define COMPARE(a, b) \
if ((a) != (b)) { \
cerr << "failed:" << #a << " != " << #b << endl; \
cerr << "\tgot:" << a << endl; \
exit(1); \
}
#define FUZZY_COMPARE(a, b, epsilon) \
if (fabs(a - b) > epsilon) { \
cerr << "failed:" << #a << " != " << #b << endl; \
cerr << "\tgot:" << a << endl; \
cerr << "\tepsilon:" << epsilon << endl; \
}
#define VERIFY(a) \
if (!(a)) { \
cerr << "failed:" << #a << endl; \
exit(1); \
}
const double TEST_EPSILON = 1e-9;
void test_basic()
{
SGMetar m1("2011/10/20 11:25 EHAM 201125Z 27012KT 240V300 9999 VCSH FEW025CB SCT048 10/05 Q1025 TEMPO VRB03KT");
COMPARE(m1.getYear(), 2011);
COMPARE(m1.getMonth(), 10);
COMPARE(m1.getDay(), 20);
COMPARE(m1.getHour(), 11);
COMPARE(m1.getMinute(), 25);
COMPARE(m1.getReportType(), -1); // should default to NIL?
COMPARE(m1.getWindDir(), 270);
FUZZY_COMPARE(m1.getWindSpeed_kt(), 12, TEST_EPSILON);
COMPARE(m1.getWeather().size(), 1);
COMPARE(m1.getClouds().size(), 2);
SG_CHECK_EQUAL(m1.getYear(), 2011);
SG_CHECK_EQUAL(m1.getMonth(), 10);
SG_CHECK_EQUAL(m1.getDay(), 20);
SG_CHECK_EQUAL(m1.getHour(), 11);
SG_CHECK_EQUAL(m1.getMinute(), 25);
SG_CHECK_EQUAL(m1.getReportType(), -1); // should default to NIL?
FUZZY_COMPARE(m1.getTemperature_C(), 10, TEST_EPSILON);
FUZZY_COMPARE(m1.getDewpoint_C(), 5, TEST_EPSILON);
FUZZY_COMPARE(m1.getPressure_hPa(), 1025, TEST_EPSILON);
SG_CHECK_EQUAL(m1.getWindDir(), 270);
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), 12, TEST_EPSILON);
SG_CHECK_EQUAL(m1.getWeather().size(), 1);
SG_CHECK_EQUAL(m1.getClouds().size(), 2);
SG_CHECK_EQUAL_EP2(m1.getTemperature_C(), 10, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getDewpoint_C(), 5, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON);
}
void test_sensor_failure_weather()
{
SGMetar m1("2011/10/20 11:25 EHAM 201125Z 27012KT 240V300 9999 // FEW025CB SCT048 10/05 Q1025");
COMPARE(m1.getWindDir(), 270);
FUZZY_COMPARE(m1.getWindSpeed_kt(), 12, TEST_EPSILON);
SG_CHECK_EQUAL(m1.getWindDir(), 270);
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), 12, TEST_EPSILON);
COMPARE(m1.getWeather().size(), 0);
COMPARE(m1.getClouds().size(), 2);
SG_CHECK_EQUAL(m1.getWeather().size(), 0);
SG_CHECK_EQUAL(m1.getClouds().size(), 2);
FUZZY_COMPARE(m1.getTemperature_C(), 10, TEST_EPSILON);
FUZZY_COMPARE(m1.getDewpoint_C(), 5, TEST_EPSILON);
FUZZY_COMPARE(m1.getPressure_hPa(), 1025, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getTemperature_C(), 10, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getDewpoint_C(), 5, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON);
}
void test_sensor_failure_cloud()
{
SGMetar m1("2011/10/20 11:25 EHAM 201125Z 27012KT 240V300 9999 FEW025CB/// SCT048/// 10/05 Q1025");
COMPARE(m1.getWindDir(), 270);
FUZZY_COMPARE(m1.getWindSpeed_kt(), 12, TEST_EPSILON);
SG_CHECK_EQUAL(m1.getWindDir(), 270);
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), 12, TEST_EPSILON);
COMPARE(m1.getWeather().size(), 0);
COMPARE(m1.getClouds().size(), 2);
SG_CHECK_EQUAL(m1.getWeather().size(), 0);
SG_CHECK_EQUAL(m1.getClouds().size(), 2);
FUZZY_COMPARE(m1.getTemperature_C(), 10, TEST_EPSILON);
FUZZY_COMPARE(m1.getDewpoint_C(), 5, TEST_EPSILON);
FUZZY_COMPARE(m1.getPressure_hPa(), 1025, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getTemperature_C(), 10, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getDewpoint_C(), 5, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON);
}
int main(int argc, char* argv[])

View File

@@ -22,6 +22,7 @@
* $Id$
**************************************************************************/
#include <simgear_config.h>
#include <simgear/debug/logstream.hxx>
#include <math.h>

View File

@@ -22,7 +22,7 @@
* $Id$
**************************************************************************/
#include <simgear_config.h>
#include <string.h>
#include <simgear/debug/logstream.hxx>

View File

@@ -22,6 +22,7 @@
* $Id$
**************************************************************************/
#include <simgear_config.h>
#include <cmath>
#include <simgear/debug/logstream.hxx>

View File

@@ -26,7 +26,7 @@
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sgstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include "stardata.hxx"

View File

@@ -117,7 +117,7 @@ public:
// bool unconditionalAttributeOwnershipDivestiture(const RTIHandle& objectHandle, const RTIHandleSet& attributeHandles)
// {
// try {
// std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// for (RTIHandleSet::const_iterator i = attributeHandles.begin(); i != attributeHandles.end(); ++i)
// attributeHandleSet->add(*i);
// _rtiAmbassador.unconditionalAttributeOwnershipDivestiture(objectHandle, *attributeHandleSet);
@@ -136,7 +136,7 @@ public:
// bool negotiatedAttributeOwnershipDivestiture(const RTIHandle& objectHandle, const RTIHandleSet& attributeHandles, const RTIData& tag)
// {
// try {
// std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// for (RTIHandleSet::const_iterator i = attributeHandles.begin(); i != attributeHandles.end(); ++i)
// attributeHandleSet->add(*i);
// _rtiAmbassador.negotiatedAttributeOwnershipDivestiture(objectHandle, *attributeHandleSet, tag.data());
@@ -156,7 +156,7 @@ public:
// bool attributeOwnershipAcquisition(const RTIHandle& objectHandle, const RTIHandleSet& attributeHandles, const RTIData& tag)
// {
// try {
// std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// for (RTIHandleSet::const_iterator i = attributeHandles.begin(); i != attributeHandles.end(); ++i)
// attributeHandleSet->add(*i);
// _rtiAmbassador.attributeOwnershipAcquisition(objectHandle, *attributeHandleSet, tag.data());
@@ -177,7 +177,7 @@ public:
// bool attributeOwnershipAcquisitionIfAvailable(const RTIHandle& objectHandle, const RTIHandleSet& attributeHandles)
// {
// try {
// std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// for (RTIHandleSet::const_iterator i = attributeHandles.begin(); i != attributeHandles.end(); ++i)
// attributeHandleSet->add(*i);
// _rtiAmbassador.attributeOwnershipAcquisitionIfAvailable(objectHandle, *attributeHandleSet);
@@ -199,7 +199,7 @@ public:
// RTIHandleSet attributeOwnershipReleaseResponse(const RTIHandle& objectHandle, const RTIHandleSet& attributeHandles)
// {
// try {
// std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// for (RTIHandleSet::const_iterator i = attributeHandles.begin(); i != attributeHandles.end(); ++i)
// attributeHandleSet->add(*i);
// attributeHandleSet.reset(_rtiAmbassador.attributeOwnershipReleaseResponse(objectHandle, *attributeHandleSet));
@@ -223,7 +223,7 @@ public:
// bool cancelNegotiatedAttributeOwnershipDivestiture(const RTIHandle& objectHandle, const RTIHandleSet& attributeHandles)
// {
// try {
// std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// for (RTIHandleSet::const_iterator i = attributeHandles.begin(); i != attributeHandles.end(); ++i)
// attributeHandleSet->add(*i);
// _rtiAmbassador.cancelNegotiatedAttributeOwnershipDivestiture(objectHandle, *attributeHandleSet);
@@ -243,7 +243,7 @@ public:
// bool cancelAttributeOwnershipAcquisition(const RTIHandle& objectHandle, const RTIHandleSet& attributeHandles)
// {
// try {
// std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(attributeHandles.size()));
// for (RTIHandleSet::const_iterator i = attributeHandles.begin(); i != attributeHandles.end(); ++i)
// attributeHandleSet->add(*i);
// _rtiAmbassador.cancelAttributeOwnershipAcquisition(objectHandle, *attributeHandleSet);

View File

@@ -101,7 +101,7 @@ RTI13ObjectClass::publish(const HLAIndexList& indexList)
try {
unsigned numAttributes = getNumAttributes();
std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(numAttributes));
std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(numAttributes));
for (HLAIndexList::const_iterator i = indexList.begin(); i != indexList.end(); ++i) {
if (_attributeHandleVector.size() <= *i) {
SG_LOG(SG_NETWORK, SG_WARN, "RTI13ObjectClass::publish(): Invalid attribute index!");
@@ -195,7 +195,7 @@ RTI13ObjectClass::subscribe(const HLAIndexList& indexList, bool active)
try {
unsigned numAttributes = getNumAttributes();
std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(numAttributes));
std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(numAttributes));
for (HLAIndexList::const_iterator i = indexList.begin(); i != indexList.end(); ++i) {
if (_attributeHandleVector.size() <= *i) {
SG_LOG(SG_NETWORK, SG_WARN, "RTI13ObjectClass::subscribe(): Invalid attribute index!");

View File

@@ -243,7 +243,7 @@ RTI13ObjectInstance::requestObjectAttributeValueUpdate(const HLAIndexList& index
try {
unsigned numAttributes = getNumAttributes();
std::auto_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(numAttributes));
std::unique_ptr<RTI::AttributeHandleSet> attributeHandleSet(RTI::AttributeHandleSetFactory::create(numAttributes));
for (HLAIndexList::const_iterator i = indexList.begin(); i != indexList.end(); ++i) {
if (getAttributeOwned(*i)) {
SG_LOG(SG_NETWORK, SG_WARN, "RTI13ObjectInstance::requestObjectAttributeValueUpdate(): "

View File

@@ -101,7 +101,7 @@ private:
SGSharedPtr<RTI13Ambassador> _ambassador;
// cached storage for updates
std::auto_ptr<RTI::AttributeHandleValuePairSet> _attributeValuePairSet;
std::unique_ptr<RTI::AttributeHandleValuePairSet> _attributeValuePairSet;
};
}

View File

@@ -1,4 +1,4 @@
add_subdirectory(iostreams)
include (SimGearComponent)

View File

@@ -21,8 +21,9 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "DNSClient.hxx"
#include "udns.h"
#include <udns.h>
#include <time.h>
#include <simgear/debug/logstream.hxx>
@@ -33,18 +34,33 @@ namespace DNS {
class Client::ClientPrivate {
public:
ClientPrivate() {
if (dns_init(NULL, 0) < 0)
SG_LOG(SG_IO, SG_ALERT, "Can't init udns library" );
if( dns_open(NULL) < 0 )
if( instanceCounter++ == 0 )
if (dns_init(NULL, 0) < 0)
SG_LOG(SG_IO, SG_ALERT, "Can't init udns library" );
ctx = dns_new(NULL);
if (dns_init(ctx, 0) < 0)
SG_LOG(SG_IO, SG_ALERT, "Can't create udns context" );
if( dns_open(ctx) < 0 )
SG_LOG(SG_IO, SG_ALERT, "Can't open udns context" );
}
~ClientPrivate() {
dns_close(NULL);
dns_close(ctx);
dns_free(ctx);
if( --instanceCounter == 0 )
dns_close(NULL);
}
struct dns_ctx * ctx;
static size_t instanceCounter;
};
size_t Client::ClientPrivate::instanceCounter = 0;
Request::Request( const std::string & dn ) :
_dn(dn),
_type(DNS_T_ANY),
@@ -111,10 +127,10 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
r->setComplete();
}
void SRVRequest::submit()
void SRVRequest::submit( Client * client )
{
// if service is defined, pass service and protocol
if (!dns_submit_srv(NULL, getDn().c_str(), _service.empty() ? NULL : _service.c_str(), _service.empty() ? NULL : _protocol.c_str(), 0, dnscbSRV, this )) {
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 )) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return;
}
@@ -148,10 +164,10 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
r->setComplete();
}
void TXTRequest::submit()
void TXTRequest::submit( Client * client )
{
// protocol and service an already encoded in DN so pass in NULL for both
if (!dns_submit_txt(NULL, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this )) {
if (!dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this )) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return;
}
@@ -175,11 +191,11 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
r->ttl = result->dnsnaptr_ttl;
for (int i = 0; i < result->dnsnaptr_nrr; i++) {
if( !r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service )
return;
continue;
//TODO: case ignore and result flags may have more than one flag
if( !r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags )
return;
continue;
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
r->entries.push_back(naptr);
@@ -196,9 +212,9 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
r->setComplete();
}
void NAPTRRequest::submit()
void NAPTRRequest::submit( Client * client )
{
if (!dns_submit_naptr(NULL, getDn().c_str(), 0, dnscbNAPTR, this )) {
if (!dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this )) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return;
}
@@ -217,15 +233,16 @@ Client::Client() :
void Client::makeRequest(const Request_ptr& r)
{
r->submit();
r->submit(this);
}
void Client::update(int waitTimeout)
{
time_t now = time(NULL);
if( dns_timeouts( NULL, waitTimeout, now ) < 0 )
if( dns_timeouts( d->ctx, -1, now ) < 0 )
return;
dns_ioevent(NULL, now);
dns_ioevent(d->ctx, now);
}
} // of namespace DNS

View File

@@ -24,7 +24,7 @@
#ifndef SG_DNS_CLIENT_HXX
#define SG_DNS_CLIENT_HXX
#include <memory> // for std::auto_ptr
#include <memory> // for std::unique_ptr
#include <string>
#include <vector>
#include <ctime> // for time_t
@@ -39,6 +39,7 @@ namespace simgear
namespace DNS
{
class Client;
class Request : public SGReferenced
{
public:
@@ -50,7 +51,7 @@ public:
bool isTimeout() const;
void setComplete( bool b = true ) { _complete = b; }
virtual void submit() = 0;
virtual void submit( Client * client) = 0;
std::string cname;
std::string qname;
@@ -68,7 +69,7 @@ class NAPTRRequest : public Request
{
public:
NAPTRRequest( const std::string & dn );
virtual void submit();
virtual void submit( Client * client );
struct NAPTR : SGReferenced {
int order;
@@ -91,7 +92,7 @@ class SRVRequest : public Request
public:
SRVRequest( const std::string & dn );
SRVRequest( const std::string & dn, const string & service, const string & protocol );
virtual void submit();
virtual void submit( Client * client );
struct SRV : SGReferenced {
int priority;
@@ -111,7 +112,7 @@ class TXTRequest : public Request
{
public:
TXTRequest( const std::string & dn );
virtual void submit();
virtual void submit( Client * client );
typedef std::vector<string> TXT_list;
typedef std::map<std::string,std::string> TXT_Attribute_map;
@@ -131,9 +132,8 @@ public:
// void cancelRequest(const Request_ptr& r, std::string reason = std::string());
private:
class ClientPrivate;
std::auto_ptr<ClientPrivate> d;
std::unique_ptr<ClientPrivate> d;
};
} // of namespace DNS

View File

@@ -21,6 +21,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "HTTPClient.hxx"
#include "HTTPFileRequest.hxx"
@@ -168,8 +169,32 @@ void Client::update(int waitTimeout)
return;
}
int remainingActive, messagesInQueue, numFds;
int remainingActive, messagesInQueue;
#if defined(SG_MAC)
// Mac 10.8 libCurl lacks this, let's keep compat for now
fd_set curlReadFDs, curlWriteFDs, curlErrorFDs;
int maxFD;
curl_multi_fdset(d->curlMulti,
&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;
timeout.tv_usec = (t % 1000) * 1000;
::select(maxFD, &curlReadFDs, &curlWriteFDs, &curlErrorFDs, &timeout);
#else
int numFds;
curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
#endif
curl_multi_perform(d->curlMulti, &remainingActive);
CURLMsg* msg;

View File

@@ -24,7 +24,7 @@
#ifndef SG_HTTP_CLIENT_HXX
#define SG_HTTP_CLIENT_HXX
#include <memory> // for std::auto_ptr
#include <memory> // for std::unique_ptr
#include <stdint.h> // for uint_64t
#include <simgear/io/HTTPFileRequest.hxx>
@@ -125,7 +125,7 @@ private:
friend class Request;
class ClientPrivate;
std::auto_ptr<ClientPrivate> d;
std::unique_ptr<ClientPrivate> d;
};
} // of namespace HTTP

View File

@@ -16,6 +16,8 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "HTTPFileRequest.hxx"
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>

View File

@@ -23,7 +23,7 @@
#include "HTTPRequest.hxx"
#include <simgear/misc/sgstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
namespace simgear
{

View File

@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "HTTPMemoryRequest.hxx"
namespace simgear

View File

@@ -16,10 +16,10 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "HTTPRepository.hxx"
#include <simgear_config.h>
#include "HTTPRepository.hxx"
#include <iostream>
#include <cassert>
#include <algorithm>
@@ -37,7 +37,7 @@
#include <simgear/misc/sg_dir.hxx>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/io/sg_file.hxx>
#include <simgear/misc/sgstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/timing/timestamp.hxx>
@@ -57,6 +57,8 @@ namespace simgear
{
}
virtual void cancel();
size_t contentSize() const
{
return _contentSize;
@@ -102,7 +104,6 @@ public:
hashCacheDirty(false),
p(parent),
isUpdating(false),
updateEverything(false),
status(HTTPRepository::REPO_NO_ERROR),
totalDownloaded(0)
{ ; }
@@ -114,14 +115,10 @@ public:
std::string baseUrl;
SGPath basePath;
bool isUpdating;
bool updateEverything;
string_list updatePaths;
HTTPRepository::ResultCode status;
HTTPDirectory* rootDir;
size_t totalDownloaded;
void updateWaiting();
HTTP::Request_ptr updateFile(HTTPDirectory* dir, const std::string& name,
size_t sz);
HTTP::Request_ptr updateDir(HTTPDirectory* dir, const std::string& hash,
@@ -196,12 +193,10 @@ class HTTPDirectory
typedef std::vector<ChildInfo> ChildInfoList;
ChildInfoList children;
public:
HTTPDirectory(HTTPRepoPrivate* repo, const std::string& path) :
_repository(repo),
_relativePath(path),
_state(DoNotUpdate)
_relativePath(path)
{
assert(repo);
@@ -238,8 +233,6 @@ public:
fpath.append(".dirindex");
_repository->updatedFileContents(fpath, hash);
_state = Updated;
children.clear();
parseDirIndex(children);
std::sort(children.begin(), children.end());
@@ -247,7 +240,6 @@ public:
void failedToUpdate(HTTPRepository::ResultCode status)
{
_state = UpdateFailed;
if (_relativePath.empty()) {
// root dir failed
_repository->failedToGetRootIndex(status);
@@ -261,7 +253,7 @@ public:
if (_repository->installedCopyPath.isNull()) {
return;
}
string_list indexNames = indexChildren();
const_string_list_iterator nameIt = indexNames.begin();
for (; nameIt != indexNames.end(); ++nameIt) {
@@ -284,7 +276,7 @@ public:
}
SG_LOG(SG_TERRASYNC, SG_BULK, "new child, copying existing file" << cp << p);
SGBinaryFile src(cp);
SGBinaryFile dst(p);
src.open(SG_IO_IN);
@@ -306,11 +298,7 @@ public:
void updateChildrenBasedOnHash()
{
// if we got here for a dir which is still updating or excluded
// from updates, just bail out right now.
if (_state != Updated) {
return;
}
//SG_LOG(SG_TERRASYNC, SG_DEBUG, "updated children for:" << relativePath());
copyInstalledChildren();
@@ -345,9 +333,6 @@ public:
SG_LOG(SG_TERRASYNC, SG_DEBUG, "file exists hash is good:" << it->file() );
if (c->type == ChildInfo::DirectoryType) {
HTTPDirectory* childDir = childDirectory(it->file());
if (childDir->_state == NotUpdated) {
childDir->_state = Updated;
}
childDir->updateChildrenBasedOnHash();
}
}
@@ -365,95 +350,6 @@ public:
scheduleUpdates(toBeUpdated);
}
void markAsUpToDate()
{
_state = Updated;
}
void markAsUpdating()
{
assert(_state == NotUpdated);
_state = HTTPDirectory::UpdateInProgress;
}
void markAsEnabled()
{
// assert because this should only get invoked on newly created
// directory objects which are inside the sub-tree(s) to be updated
assert(_state == DoNotUpdate);
_state = NotUpdated;
}
void markSubtreeAsNeedingUpdate()
{
if (_state == Updated) {
_state = NotUpdated; // reset back to not-updated
}
ChildInfoList::iterator cit;
for (cit = children.begin(); cit != children.end(); ++cit) {
if (cit->type == ChildInfo::DirectoryType) {
HTTPDirectory* childDir = childDirectory(cit->name);
childDir->markSubtreeAsNeedingUpdate();
}
} // of child iteration
}
void markSubtreeAsEnabled()
{
if (_state == DoNotUpdate) {
markAsEnabled();
}
ChildInfoList::iterator cit;
for (cit = children.begin(); cit != children.end(); ++cit) {
if (cit->type == ChildInfo::DirectoryType) {
HTTPDirectory* childDir = childDirectory(cit->name);
childDir->markSubtreeAsEnabled();
}
} // of child iteration
}
void markAncestorChainAsEnabled()
{
if (_state == DoNotUpdate) {
markAsEnabled();
}
if (_relativePath.empty()) {
return;
}
std::string prPath = SGPath(_relativePath).dir();
if (prPath.empty()) {
_repository->rootDir->markAncestorChainAsEnabled();
} else {
HTTPDirectory* prDir = _repository->getOrCreateDirectory(prPath);
prDir->markAncestorChainAsEnabled();
}
}
void updateIfWaiting(const std::string& hash, size_t sz)
{
if (_state == NotUpdated) {
_repository->updateDir(this, hash, sz);
return;
}
if ((_state == DoNotUpdate) || (_state == UpdateInProgress)) {
return;
}
ChildInfoList::iterator cit;
for (cit = children.begin(); cit != children.end(); ++cit) {
if (cit->type == ChildInfo::DirectoryType) {
HTTPDirectory* childDir = childDirectory(cit->name);
childDir->updateIfWaiting(cit->hash, cit->sizeInBytes);
}
} // of child iteration
}
HTTPDirectory* childDirectory(const std::string& name)
{
std::string childPath = relativePath().empty() ? name : relativePath() + "/" + name;
@@ -494,11 +390,6 @@ public:
_repository->updateFile(this, *it, cit->sizeInBytes);
} else {
HTTPDirectory* childDir = childDirectory(*it);
if (childDir->_state == DoNotUpdate) {
SG_LOG(SG_TERRASYNC, SG_WARN, "scheduleUpdate, child:" << *it << " is marked do not update so skipping");
continue;
}
_repository->updateDir(childDir, cit->hash, cit->sizeInBytes);
}
}
@@ -603,6 +494,11 @@ private:
continue; // ignore path, next line
}
if( typeData == "time" && tokens.size() > 1 ) {
SG_LOG(SG_TERRASYNC, SG_INFO, ".dirindex at '" << p.str() << "' timestamp: " << tokens[1] );
continue;
}
if( tokens.size() < 3 ) {
SG_LOG(SG_TERRASYNC, SG_WARN, "malformed .dirindex file: not enough tokens in line '" << line << "' (ignoring line)" );
continue;
@@ -612,6 +508,14 @@ private:
SG_LOG(SG_TERRASYNC, SG_WARN, "malformed .dirindex file: invalid type in line '" << line << "', expected 'd' or 'f', (ignoring line)" );
continue;
}
// security: prevent writing outside the repository via ../../.. filenames
// (valid filenames never contain / - subdirectories have their own .dirindex)
if ((tokens[1] == "..") || (tokens[1].find_first_of("/\\") != std::string::npos)) {
SG_LOG(SG_TERRASYNC, SG_WARN, "malformed .dirindex file: invalid filename in line '" << line << "', (ignoring line)" );
continue;
}
children.push_back(ChildInfo(typeData == "f" ? ChildInfo::FileType : ChildInfo::DirectoryType, tokens[1], tokens[2]));
if (tokens.size() > 3) {
@@ -656,16 +560,7 @@ private:
HTTPRepoPrivate* _repository;
std::string _relativePath; // in URL and file-system space
typedef enum
{
NotUpdated,
UpdateInProgress,
Updated,
UpdateFailed,
DoNotUpdate
} State;
State _state;
};
HTTPRepository::HTTPRepository(const SGPath& base, HTTP::Client *cl) :
@@ -703,38 +598,14 @@ SGPath HTTPRepository::fsBase() const
void HTTPRepository::update()
{
_d->rootDir->markSubtreeAsNeedingUpdate();
_d->updateWaiting();
}
void HTTPRepository::setEntireRepositoryMode()
{
if (!_d->updateEverything) {
// this is a one-way decision
_d->updateEverything = true;
}
// probably overkill but not expensive so let's check everything
// we have in case someone did something funky and switched from partial
// to 'whole repo' updating.
_d->rootDir->markSubtreeAsEnabled();
}
void HTTPRepository::addSubpath(const std::string& relPath)
{
if (_d->updateEverything) {
SG_LOG(SG_TERRASYNC, SG_WARN, "called HTTPRepository::addSubpath but updating everything");
if (_d->isUpdating) {
return;
}
_d->updatePaths.push_back(relPath);
HTTPDirectory* dir = _d->getOrCreateDirectory(relPath);
dir->markSubtreeAsEnabled();
dir->markAncestorChainAsEnabled();
_d->updateWaiting();
_d->status = REPO_NO_ERROR;
_d->isUpdating = true;
_d->failures.clear();
_d->updateDir(_d->rootDir, std::string(), 0);
}
bool HTTPRepository::isDoingSync() const
@@ -794,6 +665,12 @@ HTTPRepository::failure() const
return _d->status;
}
void HTTPRepoGetRequest::cancel()
{
_directory->repository()->http->cancelRequest(this, "Reposiotry cancelled");
_directory = 0;
}
class FileGetRequest : public HTTPRepoGetRequest
{
public:
@@ -825,7 +702,6 @@ HTTPRepository::failure() const
virtual void onDone()
{
file->close();
if (responseCode() == 200) {
std::string hash = strutils::encodeHex(sha1_result(&hashContext), HASH_LENGTH);
_directory->didUpdateFile(fileName, hash, contentSize());
@@ -862,7 +738,7 @@ HTTPRepository::failure() const
std::string fileName; // if empty, we're getting the directory itself
SGPath pathInRepo;
simgear::sha1nfo hashContext;
std::auto_ptr<SGBinaryFile> file;
std::unique_ptr<SGBinaryFile> file;
};
class DirGetRequest : public HTTPRepoGetRequest
@@ -922,8 +798,8 @@ HTTPRepository::failure() const
of.write(body.data(), body.size());
of.close();
_directory->dirIndexUpdated(hash);
} else {
_directory->markAsUpToDate();
//SG_LOG(SG_TERRASYNC, SG_INFO, "updated dir index " << _directory->absolutePath());
}
_directory->repository()->totalDownloaded += contentSize();
@@ -934,7 +810,7 @@ HTTPRepository::failure() const
SGTimeStamp st;
st.stamp();
_directory->updateChildrenBasedOnHash();
SG_LOG(SG_TERRASYNC, SG_INFO, "after update of:" << _directory->absolutePath() << " child update took:" << st.elapsedMSec());
SG_LOG(SG_TERRASYNC, SG_DEBUG, "after update of:" << _directory->absolutePath() << " child update took:" << st.elapsedMSec());
} catch (sg_exception& ) {
_directory->failedToUpdate(HTTPRepository::REPO_ERROR_IO);
}
@@ -999,7 +875,6 @@ HTTPRepository::failure() const
HTTP::Request_ptr HTTPRepoPrivate::updateDir(HTTPDirectory* dir, const std::string& hash, size_t sz)
{
dir->markAsUpdating();
RepoRequestPtr r(new DirGetRequest(dir, hash));
r->setContentSize(sz);
makeRequest(r);
@@ -1166,25 +1041,6 @@ HTTPRepository::failure() const
HTTPDirectory* d = new HTTPDirectory(this, path);
directories.push_back(d);
if (updateEverything) {
d->markAsEnabled();
} else {
string_list::const_iterator s;
bool shouldUpdate = false;
for (s = updatePaths.begin(); s != updatePaths.end(); ++s) {
size_t minLen = std::min(path.size(), s->size());
if (s->compare(0, minLen, path, 0, minLen) == 0) {
shouldUpdate = true;
break;
}
} // of paths iteration
if (shouldUpdate) {
d->markAsEnabled();
}
}
return d;
}
@@ -1280,21 +1136,4 @@ HTTPRepository::failure() const
SG_LOG(SG_TERRASYNC, SG_WARN, "failed to update entry:" << relativePath << " code:" << fileStatus);
}
void HTTPRepoPrivate::updateWaiting()
{
if (!isUpdating) {
status = HTTPRepository::REPO_NO_ERROR;
isUpdating = true;
failures.clear();
}
// find to-be-updated sub-trees and kick them off
rootDir->updateIfWaiting(std::string(), 0);
// maybe there was nothing to do
if (activeRequests.empty()) {
isUpdating = false;
}
}
} // of namespace simgear

View File

@@ -57,13 +57,6 @@ public:
virtual void update();
/**
* set if we should sync the entire repository
*/
void setEntireRepositoryMode();
void addSubpath(const std::string& relPath);
virtual bool isDoingSync() const;
virtual ResultCode failure() const;
@@ -80,7 +73,7 @@ public:
private:
bool isBare() const;
std::auto_ptr<HTTPRepoPrivate> _d;
std::unique_ptr<HTTPRepoPrivate> _d;
};
} // of namespace simgear

View File

@@ -15,6 +15,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include <simgear_config.h>
#include "HTTPRequest.hxx"
#include <simgear/compiler.h>
@@ -132,15 +133,15 @@ Request::HTTPVersion decodeHTTPVersion(const std::string& v)
//------------------------------------------------------------------------------
void Request::responseStart(const std::string& r)
{
const int maxSplit = 2; // HTTP/1.1 nnn reason-string
const int maxSplit = 2; // HTTP/1.1 nnn reason-string?
string_list parts = strutils::split(r, NULL, maxSplit);
if (parts.size() != 3) {
if (parts.size() < 2) {
throw sg_io_exception("bad HTTP response:" + r);
}
_responseVersion = decodeHTTPVersion(parts[0]);
_responseStatus = strutils::to_int(parts[1]);
_responseReason = parts[2];
_responseReason = parts.size() > 2 ? parts[2] : "";
setReadyState(STATUS_RECEIVED);
}

View File

@@ -1,3 +1,4 @@
#include <simgear_config.h>
#include <cstdio>
#include <cstring>

View File

@@ -1,3 +1,4 @@
#include <simgear_config.h>
#include <cstdio>
#include <cstring>

View File

@@ -0,0 +1,35 @@
include (SimGearComponent)
set(HEADERS
sgstream.hxx
gzfstream.hxx
gzcontainerfile.hxx
CharArrayStream.hxx
zlibstream.hxx
)
set(SOURCES
sgstream.cxx
gzfstream.cxx
gzcontainerfile.cxx
CharArrayStream.cxx
zlibstream.cxx
)
simgear_component(IOStreams io/iostreams "${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(test_streams sgstream_test.cxx )
target_link_libraries(test_streams ${TEST_LIBS})
add_test(streams ${EXECUTABLE_OUTPUT_PATH}/test_streams)
add_executable(test_CharArrayStream CharArrayStream_test.cxx)
target_link_libraries(test_CharArrayStream ${TEST_LIBS})
add_test(CharArrayStream ${EXECUTABLE_OUTPUT_PATH}/test_CharArrayStream)
add_executable(test_zlibstream zlibstream_test.cxx)
target_link_libraries(test_zlibstream ${TEST_LIBS})
add_test(zlibstream ${EXECUTABLE_OUTPUT_PATH}/test_zlibstream)
endif(ENABLE_TESTS)

View File

@@ -0,0 +1,291 @@
// -*- coding: utf-8 -*-
//
// CharArrayStream.cxx --- IOStreams classes for reading from, and writing to
// char arrays
//
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <ios> // std::streamsize
#include <istream> // std::istream and std::iostream
#include <ostream> // std::ostream
#include <type_traits> // std::make_unsigned()
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <cassert>
#include "CharArrayStream.hxx"
using traits = std::char_traits<char>;
namespace simgear
{
// ***************************************************************************
// * CharArrayStreambuf class *
// ***************************************************************************
CharArrayStreambuf::CharArrayStreambuf(char* buf, std::size_t bufSize)
: _buf(buf),
_bufSize(bufSize)
{
setg(_buf, _buf, _buf + _bufSize);
setp(_buf, _buf + _bufSize);
}
char* CharArrayStreambuf::data() const
{
return _buf;
}
std::size_t CharArrayStreambuf::size() const
{
return _bufSize;
}
int CharArrayStreambuf::underflow()
{
return (gptr() == egptr()) ? traits::eof() : traits::to_int_type(*gptr());
}
int CharArrayStreambuf::overflow(int c)
{
// cf. §27.7.1, footnote 309 of the C++11 standard
if (traits::eq_int_type(c, traits::eof())) {
return traits::not_eof(c);
} else {
// This class never writes beyond the end of the array (_buf + _bufSize)
return traits::eof();
}
}
std::streamsize CharArrayStreambuf::xsgetn(char* dest, std::streamsize n)
{
assert(n >= 0);
std::ptrdiff_t avail = egptr() - gptr();
// Compute min(avail, n). The cast is safe, because in its branch, one has
// 0 <= n < avail, which is of type std::ptrdiff_t.
std::ptrdiff_t nbChars = ( (n >= avail) ?
avail : static_cast<std::ptrdiff_t>(n) );
std::copy(gptr(), gptr() + nbChars, dest);
// eback() == _buf and egptr() == _buf + _bufSize
// I don't use gbump(), because it takes an int...
setg(eback(), gptr() + nbChars, egptr());
// Cast safe because 0 <= nbChars <= n, which is of type std::streamsize
return static_cast<std::streamsize>(nbChars); // number of chars copied
}
std::streamsize CharArrayStreambuf::xsputn(const char* s, std::streamsize n)
{
assert(n >= 0);
std::ptrdiff_t availSpace = epptr() - pptr();
// Compute min(availSpace, n). The cast is safe, because in its branch, one
// has 0 <= n < availSpace, which is of type std::ptrdiff_t.
std::ptrdiff_t nbChars = ( (n >= availSpace) ?
availSpace : static_cast<std::ptrdiff_t>(n) );
std::copy(s, s + nbChars, pptr());
// epptr() == _buf + _bufSize
// I don't use pbump(), because it takes an int...
setp(pptr() + nbChars, epptr());
// Cast safe because 0 <= nbChars <= n, which is of type std::streamsize
return static_cast<std::streamsize>(nbChars); // number of chars copied
}
std::streamsize CharArrayStreambuf::showmanyc()
{
// It is certain that underflow() will return EOF if gptr() == egptr().
return -1;
}
std::streampos CharArrayStreambuf::seekoff(std::streamoff off,
std::ios_base::seekdir way,
std::ios_base::openmode which)
{
bool positionInputSeq = false;
bool positionOutputSeq = false;
char* ptr = nullptr;
// cf. §27.8.2.4 of the C++11 standard
if ((which & std::ios_base::in) == std::ios_base::in) {
positionInputSeq = true;
ptr = gptr();
}
if ((which & std::ios_base::out) == std::ios_base::out) {
positionOutputSeq = true;
ptr = pptr();
}
if ((!positionInputSeq && !positionOutputSeq) ||
(positionInputSeq && positionOutputSeq &&
way != std::ios_base::beg && way != std::ios_base::end)) {
return std::streampos(std::streamoff(-1));
}
// If we reached this point and (positionInputSeq && positionOutputSeq),
// then (way == std::ios_base::beg || way == std::ios_base::end) and
// therefore 'ptr' won't be used.
std::streamoff refOffset;
static_assert(sizeof(std::streamoff) >= sizeof(std::ptrdiff_t),
"Unexpected: sizeof(std::streamoff) < sizeof(std::ptrdiff_t)");
static_assert(sizeof(std::streamoff) >= sizeof(std::size_t),
"Unexpected: sizeof(std::streamoff) < sizeof(std::size_t)");
if (way == std::ios_base::beg) {
refOffset = 0;
} else if (way == std::ios_base::cur) {
refOffset = static_cast<std::streamoff>(ptr - _buf);
} else {
assert(way == std::ios_base::end);
refOffset = static_cast<std::streamoff>(_bufSize);
}
// Offset, relatively to _buf, where we are supposed to seek
std::streamoff totalOffset = refOffset + off;
typedef typename std::make_unsigned<std::streamoff>::type uStreamOff;
if (totalOffset < 0 || static_cast<uStreamOff>(totalOffset) > _bufSize) {
return std::streampos(std::streamoff(-1));
} else {
// Safe because 0 <= totalOffset <= _bufSize, which is an std::size_t
char* newPtr = _buf + static_cast<std::size_t>(totalOffset);
if (positionInputSeq) {
// eback() == _buf and egptr() == _buf + _bufSize
setg(eback(), newPtr, egptr());
}
if (positionOutputSeq) {
// epptr() == _buf + _bufSize
setp(newPtr, epptr());
}
// C++11's §27.8.2.4 item 12 (for stringbuf) would return refOffset. This
// makes no sense IMHO, in particular when 'way' is std::ios_base::beg or
// std::ios_base::end. Return the new offset (from the beginning of
// '_buf') instead. Note that this doesn't violate anything, because
// §27.6.3.4.2 grants full freedom as to the semantics of seekoff() to
// classes derived from basic_streambuf.
//
// My interpretation is consistent with items 13 and 14 of §27.8.2.4
// concerning seekpos(), whereas item 12 is not (if item 12 were followed
// to the letter, seekoff() would always return 0 on success when
// way == std::ios_base::beg, and therefore items 13 and 14 would be
// incompatible).
return std::streampos(totalOffset);
}
}
std::streampos CharArrayStreambuf::seekpos(std::streampos pos,
std::ios_base::openmode which)
{
return seekoff(std::streamoff(pos), std::ios_base::beg, which);
}
// ***************************************************************************
// * ROCharArrayStreambuf class *
// ***************************************************************************
ROCharArrayStreambuf::ROCharArrayStreambuf(const char* buf, std::size_t bufSize)
: CharArrayStreambuf(const_cast<char*>(buf), bufSize)
{ }
const char* ROCharArrayStreambuf::data() const
{
return const_cast<const char*>(CharArrayStreambuf::data());
}
int ROCharArrayStreambuf::overflow(int c)
{
return traits::eof(); // indicate failure
}
std::streamsize ROCharArrayStreambuf::xsputn(const char* s, std::streamsize n)
{
return 0; // number of chars written
}
// ***************************************************************************
// * CharArrayIStream class *
// ***************************************************************************
CharArrayIStream::CharArrayIStream(const char* buf, std::size_t bufSize)
: std::istream(nullptr),
_streamBuf(buf, bufSize)
{
// Associate _streamBuf to 'this' and clear the error state flags
rdbuf(&_streamBuf);
}
const char* CharArrayIStream::data() const
{
return _streamBuf.data();
}
std::size_t CharArrayIStream::size() const
{
return _streamBuf.size();
}
// ***************************************************************************
// * CharArrayOStream class *
// ***************************************************************************
CharArrayOStream::CharArrayOStream(char* buf, std::size_t bufSize)
: std::ostream(nullptr),
_streamBuf(buf, bufSize)
{
// Associate _streamBuf to 'this' and clear the error state flags
rdbuf(&_streamBuf);
}
char* CharArrayOStream::data() const
{
return _streamBuf.data();
}
std::size_t CharArrayOStream::size() const
{
return _streamBuf.size();
}
// ***************************************************************************
// * CharArrayIOStream class *
// ***************************************************************************
CharArrayIOStream::CharArrayIOStream(char* buf, std::size_t bufSize)
: std::iostream(nullptr),
_streamBuf(buf, bufSize)
{
// Associate _streamBuf to 'this' and clear the error state flags
rdbuf(&_streamBuf);
}
char* CharArrayIOStream::data() const
{
return _streamBuf.data();
}
std::size_t CharArrayIOStream::size() const
{
return _streamBuf.size();
}
} // of namespace simgear

View File

@@ -0,0 +1,169 @@
// -*- coding: utf-8 -*-
//
// CharArrayStream.hxx --- IOStreams classes for reading from, and writing to
// char arrays
//
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#ifndef SG_CHAR_ARRAY_STREAM_HXX
#define SG_CHAR_ARRAY_STREAM_HXX
#include <istream>
#include <ostream>
#include <streambuf>
#include <ios> // std::streamsize, std::streampos...
#include <cstddef> // std::size_t
// Contrary to std::stringstream and its (i/o)stringstream friends, the
// classes in this file allow one to work on an array of char (that could be
// for instance static data) without having to make a whole copy of it.
//
// There are five classes defined here in the 'simgear' namespace:
// - CharArrayStreambuf subclass of std::streambuf stream buffer
// - ROCharArrayStreambuf subclass of CharArrayStreambuf stream buffer
// - CharArrayIStream subclass of std::istream input stream
// - CharArrayOStream subclass of std::ostream output stream
// - CharArrayIOStream subclass of std::iostream input/output stream
//
// The main class is CharArrayStreambuf. ROCharArrayStreambuf is a read-only
// subclass of CharArrayStreambuf. The other three are very simple convenience
// classes, using either CharArrayStreambuf or ROCharArrayStreambuf as their
// stream buffer class. One can easily work with CharArrayStreambuf or
// ROCharArrayStreambuf only, either directly or after attaching an instance
// to an std::istream, std::ostream or std::iostream instance (using for
// example constructors like std::istream(std::streambuf* sb) or the rdbuf()
// method of stream classes).
namespace simgear
{
// Input/output stream buffer class that reads from, and writes to a fixed
// buffer in memory specified in the constructor. This buffer must remain
// alive as long as the stream buffer object is used (the CharArrayStreambuf
// class works directly on that buffer without making any copy of it).
//
// Because reads and writes are directly performed on the buffer specified in
// the constructor, this stream buffer class has no caching behavior. You may
// use pubsync() if you like, but that is completely useless by design (it
// uses the default implementation in std::streambuf, which does nothing).
//
// CharArrayStreambuf may share similarities in features with
// std::strstreambuf (deprecated since C++98). However, at least one big
// difference is that CharArrayStreambuf does no dynamic memory allocation
// whatsoever. It works on a fixed-size-fixed-location buffer passed in the
// constructor, and nothing more. It does prevent overflowing the buffer,
// since it knows perfectly well where the buffer starts and ends.
class CharArrayStreambuf: public std::streambuf
{
public:
explicit CharArrayStreambuf(char* buf, std::size_t bufSize);
// Accessors for the buffer start pointer and size (same method names as for
// std::string)
char* data() const;
std::size_t size() const;
protected:
virtual int underflow() override;
virtual int overflow(int c = std::char_traits<char>::eof()) override;
// Optional override when subclassing std::streambuf. This is the most
// efficient way of reading several characters.
virtual std::streamsize xsgetn(char* dest, std::streamsize n) override;
// Ditto for writing
virtual std::streamsize xsputn(const char* s, std::streamsize n) override;
virtual std::streamsize showmanyc() override;
virtual std::streampos seekoff(
std::streamoff off,
std::ios_base::seekdir way,
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
override;
virtual std::streampos seekpos(
std::streampos pos,
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
override;
private:
// These two define the buffer managed by the CharArrayStreambuf instance.
char* const _buf;
const std::size_t _bufSize;
};
// Read-only version of CharArrayStreambuf
class ROCharArrayStreambuf: public CharArrayStreambuf
{
public:
explicit ROCharArrayStreambuf(const char* buf, std::size_t bufSize);
// Accessor for the buffer start pointer (same method name as for
// std::string)
const char* data() const;
private:
// Override methods pertaining to write access
virtual int overflow(int c = std::char_traits<char>::eof()) override;
virtual std::streamsize xsputn(const char* s, std::streamsize n) override;
};
// Convenience class: std::istream subclass based on ROCharArrayStreambuf
class CharArrayIStream: public std::istream
{
public:
// Same parameters as for ROCharArrayStreambuf
explicit CharArrayIStream(const char* buf, std::size_t bufSize);
// Accessors for the underlying buffer start pointer and size
const char* data() const;
std::size_t size() const;
private:
ROCharArrayStreambuf _streamBuf;
};
// Convenience class: std::ostream subclass based on CharArrayStreambuf
class CharArrayOStream: public std::ostream
{
public:
// Same parameters as for CharArrayStreambuf
explicit CharArrayOStream(char* buf, std::size_t bufSize);
// Accessors for the underlying buffer start pointer and size
char* data() const;
std::size_t size() const;
private:
CharArrayStreambuf _streamBuf;
};
// Convenience class: std::iostream subclass based on CharArrayStreambuf
class CharArrayIOStream: public std::iostream
{
public:
// Same parameters as for CharArrayStreambuf
explicit CharArrayIOStream(char* buf, std::size_t bufSize);
// Accessors for the underlying buffer start pointer and size
char* data() const;
std::size_t size() const;
private:
CharArrayStreambuf _streamBuf;
};
} // of namespace simgear
#endif // of SG_CHAR_ARRAY_STREAM_HXX

View File

@@ -0,0 +1,439 @@
// -*- coding: utf-8 -*-
//
// CharArrayStream_test.cxx --- Automated tests for CharArrayStream.cxx
//
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <ios> // std::basic_ios, std::streamsize...
#include <iostream> // std::ios_base, std::cerr, etc.
#include <sstream>
#include <limits> // std::numeric_limits
#include <type_traits> // std::make_unsigned()
#include <memory> // std::unique_ptr
#include <cassert>
#include <cstdlib> // EXIT_SUCCESS
#include <cstddef> // std::size_t
#include <cstring> // std::strlen()
#include <algorithm> // std::fill_n()
#include <vector>
#include <simgear/misc/test_macros.hxx>
#include "CharArrayStream.hxx"
using std::string;
using std::cout;
using std::cerr;
using traits = std::char_traits<char>;
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
// Safely convert a non-negative std::streamsize into an std::size_t. If
// impossible, bail out.
static std::size_t streamsizeToSize_t(std::streamsize n)
{
SG_CHECK_GE(n, 0);
SG_CHECK_LE(static_cast<uStreamSize>(n),
std::numeric_limits<std::size_t>::max());
return static_cast<std::size_t>(n);
}
void test_CharArrayStreambuf_basicOperations()
{
cerr << "Testing basic operations on CharArrayStreambuf\n";
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ";
std::istringstream text_ss(text);
string canary = "YoY";
// Reserve space for our little canary
const std::size_t bufSize = text.size() + canary.size();
std::unique_ptr<char[]> buf(new char[bufSize]);
int ch;
std::streamsize n;
std::streampos pos;
// Only allow arraySBuf to read from, and write to the first text.size()
// chars of 'buf'
simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size());
// 1) Write a copy of the 'text' string at buf.get(), testing various write
// and seek methods.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Write "01" with two sputc() calls
ch = arraySBuf.sputc(text[0]);
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0' && buf[0] == '0');
ch = arraySBuf.sputc(text[1]);
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1' && buf[1] == '1');
// Write the 34 following chars of 'text' with one sputn() call
n = arraySBuf.sputn(&text[2], 34);
SG_CHECK_EQUAL(n, 34);
SG_CHECK_EQUAL(string(&buf[2], 34), "23456789abcdefghijklmnopqrstuvwxyz");
// Indirect test of seekpos(): position the write stream pointer a bit further
pos = arraySBuf.pubseekpos(43, std::ios_base::out);
SG_CHECK_EQUAL(pos, std::streampos(43));
// Write 7 more chars with sputn()
n = arraySBuf.sputn(&text[43], 7);
SG_CHECK_EQUAL(n, 7);
SG_CHECK_EQUAL(string(&buf[43], 7), "\nGHIJK ");
// Indirect test of seekoff(): seek backwards relatively to the current write
// pointer position
pos = arraySBuf.pubseekoff(-std::streamoff(std::strlen("\nABCDEF\nGHIJK ")),
std::ios_base::cur, std::ios_base::out);
// 10 + 26, i.e., after the lowercase alphabet
SG_CHECK_EQUAL(pos, std::streampos(36));
// Write "\nABCD" to buf in one sputn() call
n = arraySBuf.sputn(&text[36], 5);
// Now write "EF" in two sputc() calls
ch = arraySBuf.sputc(text[41]);
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'E' && buf[41] == 'E');
ch = arraySBuf.sputc(text[42]);
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'F' && buf[42] == 'F');
// Place a canary to check that arraySBuf doesn't write beyond the end of buf
std::copy(canary.begin(), canary.end(), &buf[text.size()]);
// Check seeking from arraySBuf's end (which is at offset text.size(), *not*
// bufSize: cf. the construction of arraySBuf!).
pos = arraySBuf.pubseekoff(-std::streamoff(std::strlen("LMNOPQ")),
std::ios_base::end, std::ios_base::out);
SG_CHECK_EQUAL(pos, std::streampos(text.size() - std::strlen("LMNOPQ")));
// Write "LMNOPQ" to buf in one sputn() call. The other characters won't be
// written, because they would go past the end of the buffer managed by
// 'arraySBuf' (i.e., the first text.size() chars of 'buf').
static const char someChars[] = "LMNOPQ+buffer overrun that will be blocked";
n = arraySBuf.sputn(someChars, sizeof(someChars));
// Check the number of chars actually written
SG_CHECK_EQUAL(n, std::strlen("LMNOPQ"));
// Check that our canary starting at buf[text.size()] is still there and
// intact
SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary);
// Check that we now have an exact copy of 'text' in the managed buffer
SG_CHECK_EQUAL(string(&buf[0], text.size()), text);
// 2) Read back the copy of 'text' in 'buf', using various read and seek
// methods.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ch = arraySBuf.sgetc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
ch = arraySBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
ch = arraySBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1');
ch = arraySBuf.snextc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
ch = arraySBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
ch = arraySBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
ch = arraySBuf.sputbackc('4');
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
ch = arraySBuf.sputbackc('u'); // doesn't match what we read from the stream
SG_VERIFY(ch == EOF);
ch = arraySBuf.sputbackc('3'); // this one does
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
static constexpr std::streamsize buf2Size = 10;
char buf2[buf2Size];
// Most efficient way (with the underlying xsgetn()) to read several chars
// at once.
n = arraySBuf.sgetn(buf2, buf2Size);
SG_CHECK_EQUAL(n, buf2Size);
SG_CHECK_EQUAL(string(buf2, static_cast<std::size_t>(buf2Size)),
"3456789abc");
ch = arraySBuf.sungetc(); // same as sputbackc(), except no value to check
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'c');
ch = arraySBuf.sungetc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
ch = arraySBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
ch = arraySBuf.sputbackc('b'); // this one does
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
n = arraySBuf.sgetn(buf2, buf2Size);
SG_CHECK_EQUAL(n, buf2Size);
SG_CHECK_EQUAL(string(buf2, static_cast<std::size_t>(buf2Size)),
"bcdefghijk");
ch = arraySBuf.sungetc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'k');
static char buf3[64];
n = arraySBuf.sgetn(buf3, sizeof(buf3));
SG_CHECK_EQUAL(n, 36);
SG_CHECK_EQUAL(string(buf3, 36), "klmnopqrstuvwxyz\nABCDEF\nGHIJK LMNOPQ");
SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF);
// Check we can independently set the read and write pointers for arraySBuf
pos = arraySBuf.pubseekpos(10, std::ios_base::in);
SG_CHECK_EQUAL(pos, std::streampos(10));
pos = arraySBuf.pubseekpos(13, std::ios_base::out);
SG_CHECK_EQUAL(pos, std::streampos(13));
// Write "DEF" where there is currently "def" in 'buf'.
for (int i = 0; i < 3; i++) {
char c = 'D' + i;
ch = arraySBuf.sputc(c);
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == c && buf[i+13] == c);
}
n = arraySBuf.sgetn(buf3, 6);
SG_CHECK_EQUAL(n, 6);
SG_CHECK_EQUAL(string(buf3, 6), "abcDEF");
// Set both stream pointers at once (read and write)
pos = arraySBuf.pubseekpos(10, std::ios_base::in | std::ios_base::out);
SG_VERIFY(pos == std::streampos(10));
// Write "ABC" to buf in one sputn() call
n = arraySBuf.sputn("ABC", 3);
SG_CHECK_EQUAL(n, 3);
SG_CHECK_EQUAL(string(&buf[10], 3), "ABC");
// Indirect test of seekoff(): seek backwards relatively to the current read
// pointer position
pos = arraySBuf.pubseekoff(-3, std::ios_base::cur, std::ios_base::in);
SG_CHECK_EQUAL(pos, std::streampos(7));
n = arraySBuf.sgetn(buf3, 12);
SG_CHECK_EQUAL(n, 12);
SG_CHECK_EQUAL(string(buf3, 12), "789ABCDEFghi");
}
void test_CharArrayStreambuf_readOrWriteLargestPossibleAmount()
{
cerr << "Testing reading and writing from/to CharArrayStreambuf with the "
"largest possible value passed as the 'n' argument for sgetn()/sputn() "
"(number of chars to read or write)\n";
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ";
string canary = "ZaZ";
// Reserve space for our little canary
const std::size_t bufSize = text.size() + canary.size();
std::unique_ptr<char[]> buf(new char[bufSize]);
std::streamsize n_s;
std::size_t n;
std::streampos pos;
// Place a canary to check that arraySBuf doesn't write beyond the end of buf
std::copy(canary.begin(), canary.end(), &buf[text.size()]);
// Only allow arraySBuf to read from, and write to the first text.size()
// chars of 'buf'
simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size());
n_s = arraySBuf.sputn(text.c_str(),
std::numeric_limits<std::streamsize>::max());
// The conversion to std::size_t is safe because arraySBuf.sputn() returns a
// non-negative value which, in this case, can't exceed the size of the
// buffer managed by 'arraySBuf', i.e. text.size().
n = streamsizeToSize_t(n_s);
SG_CHECK_EQUAL(n, arraySBuf.size());
SG_CHECK_EQUAL(n, text.size());
SG_CHECK_EQUAL(string(&buf[0], n), text);
// Check that our canary starting at &buf[text.size()] is still there and
// intact
SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary);
// The “get” stream pointer is still at the beginning of the buffer managed
// by 'arraySBuf'. Let's ask for the maximum amount of chars from it to be
// written to a new buffer, 'buf2'.
std::unique_ptr<char[]> buf2(new char[text.size()]);
n_s = arraySBuf.sgetn(&buf2[0],
std::numeric_limits<std::streamsize>::max());
// The conversion to std::size_t is safe because arraySBuf.sgetn() returns a
// non-negative value which, in this case, can't exceed the size of the
// buffer managed by 'arraySBuf', i.e. text.size().
n = streamsizeToSize_t(n_s);
SG_CHECK_EQUAL(n, arraySBuf.size());
SG_CHECK_EQUAL(string(&buf2[0], n), text);
SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF);
}
void test_CharArrayIStream_simple()
{
// This also tests ROCharArrayStreambuf, since it is used as
// CharArrayIStream's stream buffer class.
cerr << "Testing read operations from CharArrayIStream\n";
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ";
std::unique_ptr<char[]> buf(new char[text.size()]);
std::size_t n;
simgear::CharArrayIStream caStream(&text[0], text.size());
caStream.exceptions(std::ios_base::badbit); // throw if badbit is set
SG_CHECK_EQUAL(caStream.data(), &text[0]);
SG_CHECK_EQUAL(caStream.size(), text.size());
SG_VERIFY(caStream.get(buf[0])); // get pointer = 1
SG_CHECK_EQUAL(buf[0], text[0]);
caStream.putback(buf[0]); // get pointer = 0
SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1
// std::iostream::operator bool() will return false due to EOF being reached
SG_VERIFY(!caStream.read(&buf[1],
std::numeric_limits<std::streamsize>::max()));
// If badbit had been set, it would have caused an exception to be raised
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
// The conversion to std::size_t is safe because caStream.gcount() returns a
// non-negative value which, in this case, can't exceed the size of the
// buffer managed by caStream's associated stream buffer, i.e. text.size().
n = streamsizeToSize_t(caStream.gcount());
SG_CHECK_EQUAL(n, caStream.size() - 1);
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
SG_CHECK_EQUAL(caStream.get(), EOF);
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
// Test stream extraction: operator>>()
caStream.clear(); // clear the error state flags
SG_VERIFY(caStream.seekg(0)); // rewind
std::vector<string> expectedWords = {
"0123456789abcdefghijklmnopqrstuvwxyz",
"ABCDEF",
"GHIJK",
"LMNOPQ"
};
string str;
for (int i = 0; caStream >> str; i++) {
SG_CHECK_EQUAL(str, expectedWords[i]);
}
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
}
void test_CharArrayOStream_simple()
{
cerr << "Testing write operations to CharArrayOStream\n";
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ";
// One could also use an std::vector<char>, but then beware of reallocations!
std::unique_ptr<char[]> buf(new char[text.size()]);
std::fill_n(buf.get(), text.size(), '\0'); // to ensure reproducible results
simgear::CharArrayOStream caStream(&buf[0], text.size());
SG_CHECK_EQUAL(caStream.data(), &buf[0]);
SG_CHECK_EQUAL(caStream.size(), text.size());
SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1
SG_CHECK_EQUAL(buf[0], text[0]);
SG_VERIFY(caStream.seekp(8)); // put pointer = 8
// buf[8:23] = text[8:23] (meaning: buf[8] = text[8], ..., buf[22] = text[22])
SG_VERIFY(caStream.write(&text[8], 15)); // put pointer = 23
buf[1] = 'X'; // write garbage to buf[1]
buf[2] = 'Y'; // and to buf[2]
SG_VERIFY(caStream.seekp(-22, std::ios_base::cur)); // put pointer = 23-22 = 1
SG_VERIFY(caStream.write(&text[1], 7)); // buf[1:8] = text[1:8]
// The std::ios_base::beg argument is superfluous here---just for testing.
SG_VERIFY(caStream.seekp(23, std::ios_base::beg)); // put pointer = 23
// Test stream insertion: operator<<()
SG_VERIFY(caStream << text.substr(23, 10));
SG_VERIFY(caStream.write(&text[33], text.size() - 33)); // all that remains
SG_VERIFY(!caStream.put('Z')); // doesn't fit in caStream's buffer
SG_VERIFY(caStream.bad()); // put() set the stream's badbit flag
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
}
void test_CharArrayIOStream_readWriteSeekPutbackEtc()
{
cerr << "Testing read, write, seek, putback... from/to CharArrayIOStream\n";
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ";
std::unique_ptr<char[]> buf(new char[text.size()]);
std::size_t n;
char ch;
simgear::CharArrayIOStream caStream(&buf[0], text.size());
caStream.exceptions(std::ios_base::badbit); // throw if badbit is set
SG_CHECK_EQUAL(caStream.data(), &buf[0]);
SG_CHECK_EQUAL(caStream.size(), text.size());
SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1
SG_CHECK_EQUAL(buf[0], text[0]);
SG_VERIFY(caStream.get(ch)); // read it back from buf, get pointer = 1
SG_CHECK_EQUAL(ch, text[0]);
caStream.putback(buf[0]); // get pointer = 0
SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1
SG_VERIFY(caStream.seekp(5));
// buf[5:10] = text[5:10] (meaning: buf[5] = text[5], ..., buf[9] = text[9])
SG_VERIFY(caStream.write(&text[5], 5)); // put pointer = 10
buf[1] = 'X'; // write garbage to buf[1]
buf[2] = 'Y'; // and to buf[2]
SG_VERIFY(caStream.seekp(-9, std::ios_base::cur)); // put pointer = 10 - 9 = 1
SG_VERIFY(caStream.write(&text[1], 4)); // buf[1:5] = text[1:5]
SG_VERIFY(caStream.seekp(10)); // put pointer = 10
// buf[10:] = text[10:]
SG_VERIFY(caStream.write(&text[10], text.size() - 10));
std::unique_ptr<char[]> buf2(new char[caStream.size() - 10]);
SG_VERIFY(caStream.seekg(10)); // get pointer = 10
// std::iostream::operator bool() will return false due to EOF being reached
SG_VERIFY(!caStream.read(&buf2[0],
std::numeric_limits<std::streamsize>::max()));
// If badbit had been set, it would have caused an exception to be raised
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
// The conversion to std::size_t is safe because caStream.gcount() returns a
// non-negative value which, in this case, can't exceed the size of the
// buffer managed by caStream's associated stream buffer, i.e. text.size().
n = streamsizeToSize_t(caStream.gcount());
SG_CHECK_EQUAL(n, caStream.size() - 10);
SG_CHECK_EQUAL(caStream.get(), EOF);
SG_CHECK_EQUAL(string(&buf2[0], caStream.size() - 10),
string(&text[10], text.size() - 10));
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
}
int main(int argc, char** argv)
{
test_CharArrayStreambuf_basicOperations();
test_CharArrayStreambuf_readOrWriteLargestPossibleAmount();
test_CharArrayIStream_simple();
test_CharArrayOStream_simple();
test_CharArrayIOStream_readWriteSeekPutbackEtc();
return EXIT_SUCCESS;
}

View File

@@ -22,7 +22,7 @@
#define GZ_CONTAINER_FILE_HXX
#include <string>
#include <simgear/misc/sgstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
class SGPropertyNode;

View File

@@ -34,7 +34,7 @@
#include <simgear/structure/exception.hxx>
#include <zlib.h>
#include "zfstream.hxx"
#include "gzfstream.hxx"
//
// Construct a gzfilebuf object.

View File

@@ -1,5 +1,5 @@
/**
* \file zfstream.hxx
* \file gzfstream.hxx
* A C++ I/O streams interface to the zlib gz* functions.
*/
@@ -24,8 +24,8 @@
//
// $Id$
#ifndef _zfstream_hxx
#define _zfstream_hxx
#ifndef _gzfstream_hxx
#define _gzfstream_hxx
#include <simgear/compiler.h>
@@ -164,4 +164,4 @@ struct gzofstream_base
gzfilebuf gzbuf;
};
#endif // _zfstream_hxx
#endif // _gzfstream_hxx

View File

@@ -20,6 +20,7 @@
//
// $Id$
#include <simgear_config.h>
#include <simgear/compiler.h>
#include <string>
#include <ctype.h> // isspace()

View File

@@ -40,7 +40,7 @@
#include <string>
#include <zlib.h>
#include <simgear/misc/zfstream.hxx>
#include <simgear/io/iostreams/gzfstream.hxx>
class SGPath;

View File

@@ -1,52 +1,55 @@
#include <simgear_config.h>
#include <iostream>
#include <fstream>
#include <cstdlib> // for EXIT_SUCCESS
#include <cstdlib> // for EXIT_FAILURE
using std::ofstream;
using std::string;
using std::cout;
using std::endl;
#include <simgear/misc/sgstream.hxx>
#include <simgear/misc/test_macros.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sg_dir.hxx>
int main()
{
const char* fileName = "testfile";
const string fileName = "testfile";
simgear::Dir tmpDir = simgear::Dir::tempDir("FlightGear");
tmpDir.setRemoveOnDestroy();
SGPath p(tmpDir.path() / fileName);
{
ofstream f;
f.open(fileName, std::ios::binary | std::ios::trunc | std::ios::out);
sg_ofstream f(p, std::ios::binary | std::ios::trunc | std::ios::out);
f.write("first line ends with line-feed\n"
"second line ends with just a cr\r"
"third line ends with both\r\n"
"fourth line as well\r\n"
"fifth line is another CR/LF line\r\n"
"end of test\r\n", 158);
f.close();
}
}
SGPath p(fileName);
sg_gzifstream sg(p);
std::string stuff;
sg >> skipeol;
sg >> stuff;
if (stuff != "second") return EXIT_FAILURE;
SG_CHECK_EQUAL(stuff, "second");
cout << "Detection of LF works." << endl;
sg >> skipeol;
sg >> stuff;
if (stuff != "third") return EXIT_FAILURE;
SG_CHECK_EQUAL(stuff, "third");
cout << "Detection of CR works." << endl;
sg >> skipeol;
sg >> stuff;
if (stuff != "fourth") return EXIT_FAILURE;
SG_CHECK_EQUAL(stuff, "fourth");
cout << "Detection of CR/LF works." << endl;
sg >> skipeol;
sg >> skipeol;
sg >> stuff;
if (stuff != "end") return EXIT_FAILURE;
SG_CHECK_EQUAL(stuff, "end");
cout << "Detection of 2 following CR/LF lines works." << endl;
return EXIT_SUCCESS;

View File

@@ -0,0 +1,845 @@
// -*- coding: utf-8 -*-
//
// zlibstream.cxx --- IOStreams classes for working with RFC 1950 and RFC 1952
// compression formats (respectively known as the zlib and
// gzip formats)
//
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <string>
#include <ios> // std::streamsize
#include <istream>
#include <memory> // std::unique_ptr
#include <utility> // std::move()
#include <algorithm>
#include <stdexcept>
#include <unordered_map>
#include <limits> // std::numeric_limits
#include <type_traits> // std::make_unsigned(), std::underlying_type
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <cassert>
#include <zlib.h>
#include <simgear/io/iostreams/zlibstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/debug/logstream.hxx>
using std::string;
using traits = std::char_traits<char>;
// Cast an enum value to its underlying type
template <typename T>
static constexpr typename std::underlying_type<T>::type enumValue(T e) {
return static_cast<typename std::underlying_type<T>::type>(e);
}
// Private utility function
static string zlibErrorMessage(const z_stream& zstream, int errorCode)
{
string res;
std::unordered_map<int, string> errorCodeToMessageMap = {
{Z_OK, "zlib: no error (code Z_OK)"},
{Z_STREAM_END, "zlib stream end"},
{Z_NEED_DICT, "zlib: Z_NEED_DICT"},
{Z_STREAM_ERROR, "zlib stream error"},
{Z_DATA_ERROR, "zlib data error"},
{Z_MEM_ERROR, "zlib memory error"},
{Z_BUF_ERROR, "zlib buffer error"},
{Z_VERSION_ERROR, "zlib version error"}
};
if (errorCode == Z_ERRNO) {
res = simgear::strutils::error_string(errno);
} else if (zstream.msg != nullptr) {
// Caution: this only works if the zstream structure hasn't been
// deallocated!
res = "zlib: " + string(zstream.msg);
} else {
try {
res = errorCodeToMessageMap.at(errorCode);
} catch (const std::out_of_range&) {
res = string("unknown zlib error (code " + std::to_string(errorCode) +
")");
}
}
return res;
}
// Requirement: 'val' must be non-negative.
//
// Return:
// - 'val' cast as BoundType if it's lower than the largest value BoundType
// can represent;
// - this largest value otherwise.
template<class BoundType, class T>
static BoundType clipCast(T val)
{
typedef typename std::make_unsigned<T>::type uT;
typedef typename std::make_unsigned<BoundType>::type uBoundType;
assert(val >= 0); // otherwise, the comparison and cast to uT would be unsafe
// Casts to avoid the signed-compare warning; they don't affect the values,
// since both are non-negative.
if (static_cast<uT>(val) <
static_cast<uBoundType>( std::numeric_limits<BoundType>::max() )) {
return static_cast<BoundType>(val);
} else {
return std::numeric_limits<BoundType>::max();
}
}
// Requirement: 'size' must be non-negative.
//
// Return:
// - 'size' if it is lower than or equal to std::numeric_limits<uInt>::max();
// - std::numeric_limits<uInt>::max() cast as a T otherwise (this is always
// possible in a lossless way, since in this case, one has
// 0 <= std::numeric_limits<uInt>::max() < size, and 'size' is of type T).
//
// Note: uInt is the type of z_stream.avail_in and z_stream.avail_out, hence
// the function name.
template<class T>
static T zlibChunk(T size)
{
typedef typename std::make_unsigned<T>::type uT;
assert(size >= 0); // otherwise, the comparison and cast to uT would be unsafe
if (static_cast<uT>(size) <= std::numeric_limits<uInt>::max()) {
return size;
} else {
// In this case, we are sure that T can represent
// std::numeric_limits<uInt>::max(), thus the cast is safe.
return static_cast<T>(std::numeric_limits<uInt>::max());
}
}
namespace simgear
{
// ***************************************************************************
// * ZlibAbstractIStreambuf class *
// ***************************************************************************
// Common initialization. Subclasses must complete the z_stream struct
// initialization with a call to deflateInit2() or inflateInit2(), typically.
ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(std::istream& iStream,
const SGPath& path,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: _iStream(iStream),
_iStream_p(nullptr),
_path(path),
_inBuf(inBuf),
_inBufSize(inBufSize),
_outBuf(outBuf),
_outBufSize(outBufSize),
_putbackSize(putbackSize)
{
assert(_inBufSize > 0);
assert(_putbackSize >= 0); // guaranteed unless the type is changed...
assert(_putbackSize < _outBufSize);
if (_inBuf == nullptr) {
_inBuf = new char[_inBufSize];
_inBufMustBeFreed = true;
}
if (_outBuf == nullptr) {
_outBuf = new char[_outBufSize];
_outBufMustBeFreed = true;
}
_inBufEndPtr = _inBuf;
// The input buffer is empty.
_zstream.next_in = reinterpret_cast<unsigned char *>(_inBuf);
_zstream.avail_in = 0;
// ZLib's documentation says its init functions such as inflateInit2() might
// consume stream input, therefore let's fill the input buffer now. This
// way, constructors of derived classes just have to call the appropriate
// ZLib init function: the data will already be in place.
getInputData();
// Force underflow() of the stream buffer on the first read. We could use
// some other value, but I avoid nullptr in order to be sure we can always
// reliably compare the three pointers with < and >, as well as compute the
// difference between any two of them.
setg(_outBuf, _outBuf, _outBuf);
}
ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(
std::unique_ptr<std::istream> iStream_p,
const SGPath& path,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: ZlibAbstractIStreambuf(*iStream_p, path, inBuf, inBufSize,
outBuf, outBufSize, putbackSize)
{
// Take ownership of the object. This is a way to ensure that the _iStream
// reference stays valid as long as our instance is alive, and that the
// corresponding std::istream object is automatically destroyed as soon as
// our instance is itself destroyed.
_iStream_p = std::move(iStream_p);
}
ZlibAbstractIStreambuf::~ZlibAbstractIStreambuf()
{
if (_inBufMustBeFreed) {
delete[] _inBuf;
}
if (_outBufMustBeFreed) {
delete[] _outBuf;
}
}
// Fill or refill the output buffer, and update the three pointers
// corresponding to eback(), gptr() and egptr().
int ZlibAbstractIStreambuf::underflow()
{
if (_allFinished) {
return traits::eof();
}
// According to the C++11 standard: “The public members of basic_streambuf
// call this virtual function only if gptr() is null or gptr() >= egptr()”.
// Still, it seems some people do the following or similar (maybe in case
// underflow() is called “incorrectly”?). See for instance N. Josuttis, The
// C++ Standard Library (1st edition), p. 584. One sure thing is that it
// can't hurt (except performance, marginally), so let's do it.
if (gptr() < egptr()) {
return traits::to_int_type(*gptr());
}
assert(gptr() == egptr());
assert(egptr() - eback() >= 0);
std::size_t nbPutbackChars = std::min(
static_cast<std::size_t>(egptr() - eback()), // OK because egptr() >= eback()
_putbackSize);
std::copy(egptr() - nbPutbackChars, egptr(),
_outBuf + _putbackSize - nbPutbackChars);
setg(_outBuf + _putbackSize - nbPutbackChars, // start of putback area
_outBuf + _putbackSize, // start of obtained data
fillOutputBuffer()); // one-past-end of obtained data
return (gptr() == egptr()) ? traits::eof() : traits::to_int_type(*gptr());
}
// Simple utility method for fillOutputBuffer(), used to improve readability.
// Return the remaining space available in the output buffer, where zlib can
// write.
std::size_t
ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
{
std::ptrdiff_t remainingSpaceInOutBuf = (
_outBuf + _outBufSize - reinterpret_cast<char*>(nextOutPtr));
assert(remainingSpaceInOutBuf >= 0);
return static_cast<std::size_t>(remainingSpaceInOutBuf);
}
// Simple method for dealing with the Z_BUF_ERROR code that may be returned
// by zlib's deflate() and inflate() methods.
[[ noreturn ]] void ZlibAbstractIStreambuf::handleZ_BUF_ERROR() const
{
switch (operationType()) {
case OperationType::DECOMPRESSION:
{
string message = (_path.isNull()) ?
"Got Z_BUF_ERROR from zlib while decompressing a stream. The stream "
"was probably incomplete."
:
"Got Z_BUF_ERROR from zlib during decompression. The compressed stream "
"was probably incomplete.";
// When _path.isNull(), sg_location(_path) is equivalent to sg_location()
throw sg_io_exception(message, sg_location(_path));
}
case OperationType::COMPRESSION:
throw std::logic_error(
"Called ZlibAbstractIStreambuf::handleZ_BUF_ERROR() with "
"operationType() == ZlibAbstractIStreambuf::OperationType::COMPRESSION");
default:
throw std::logic_error(
"Unexpected operationType() in "
"ZlibAbstractIStreambuf::handleZ_BUF_ERROR(): " +
std::to_string(enumValue(operationType())));
}
}
// Fill or refill the output buffer. Return a pointer to the first unused char
// in _outBuf (i.e., right after the data written by this method, if any;
// otherwise: _outBuf + _putbackSize).
char* ZlibAbstractIStreambuf::fillOutputBuffer()
{
bool allInputRead = false;
std::size_t remainingSpaceInOutBuf;
int retCode;
// We have to do these unpleasant casts, because zlib uses pointers to
// unsigned char for its input and output buffers, while the IOStreams
// library in the C++ standard uses plain char pointers...
_zstream.next_out = reinterpret_cast<unsigned char*>(_outBuf + _putbackSize);
remainingSpaceInOutBuf = fOB_remainingSpace(_zstream.next_out);
while (remainingSpaceInOutBuf > 0) {
_zstream.avail_out = clipCast<uInt>(remainingSpaceInOutBuf);
if (_zstream.avail_in == 0 && !allInputRead) {
// Get data from _iStream, store it in _inBuf
allInputRead = getInputData();
}
// Make zlib process some data (compress or decompress it). This updates
// _zstream.{avail,next}_{in,out} (4 fields of the z_stream struct).
retCode = zlibProcessData();
if (retCode == Z_BUF_ERROR) {
handleZ_BUF_ERROR(); // doesn't return
} else if (retCode == Z_STREAM_END) {
assert(_zstream.avail_in == 0); // all of _inBuf must have been used
_allFinished = true;
break;
} else if (retCode < 0) { // negative codes are errors
throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
sg_location(_path));
}
remainingSpaceInOutBuf = fOB_remainingSpace(_zstream.next_out);
}
return reinterpret_cast<char*>(_zstream.next_out);
}
// This method provides input data to zlib:
// - if data is already available in _inBuf, it updates _zstream.avail_in
// accordingly (using the largest possible amount given _zstream.avail_in's
// type, i.e., uInt);
// - otherwise, it reads() as much data as possible into _inBuf from
// _iStream and updates _zstream.avail_in to tell zlib about this new
// available data (again: largest possible amount given the uInt type).
//
// This method must be called only when _zstream.avail_in == 0, which means
// zlib has read everything we fed it (and does *not* mean, by the way, that
// the input buffer starting at _inBuf is empty; this is because the buffer
// size might exceed what can be represented by an uInt).
//
// On return:
// - either EOF has been reached for _iStream, and the return value is true;
// - or _zstream.avail_in > 0, and the return value is false.
//
// Note: don't read more in the previous paragraph concerning the state on
// return. In the first case, there is *no guarantee* about
// _zstream.avail_in's value; in the second case, there is *no guarantee*
// about whether EOF has been reached for _iStream.
bool ZlibAbstractIStreambuf::getInputData()
{
bool allInputRead = false;
assert(_zstream.avail_in == 0);
std::ptrdiff_t alreadyAvailable =
_inBufEndPtr - reinterpret_cast<char*>(_zstream.next_in);
assert(alreadyAvailable >= 0);
// Data already available?
if (alreadyAvailable > 0) {
_zstream.avail_in = clipCast<uInt>(alreadyAvailable);
return allInputRead;
}
if (_inBufEndPtr == _inBuf + _inBufSize) { // buffer full, rewind
_inBufEndPtr = _inBuf;
_zstream.next_in = reinterpret_cast<unsigned char*>(_inBuf);
}
// Fill the input buffer (as much as possible)
while (_inBufEndPtr < _inBuf + _inBufSize && !_iStream.eof()) {
std::streamsize nbCharsToRead = clipCast<std::streamsize>(
_inBuf + _inBufSize - _inBufEndPtr);
_iStream.read(_inBufEndPtr, nbCharsToRead);
if (_iStream.bad()) {
string errMsg = simgear::strutils::error_string(errno);
string msgStart = (_path.isNull()) ?
"Error while reading from a stream" : "Read error";
throw sg_io_exception(msgStart + ": " + errMsg, sg_location(_path));
}
// Could be zero if at EOF
std::streamsize nbCharsRead = _iStream.gcount();
// std::streamsize is a signed integral type!
assert(0 <= nbCharsRead);
_inBufEndPtr += nbCharsRead;
}
std::ptrdiff_t availableChars =
_inBufEndPtr - reinterpret_cast<char*>(_zstream.next_in);
// assert(availableChars >= 0); <-- already done in clipCast<uInt>()
_zstream.avail_in = clipCast<uInt>(availableChars);
if (_iStream.eof()) {
allInputRead = true;
} else {
// Trying to rewind a fully read std::istringstream with seekg() can lead
// to a weird state, where the stream doesn't return any character but
// doesn't report EOF either. Make sure we are not in this situation.
assert(_zstream.avail_in > 0);
}
return allInputRead;
}
// Implementing this method is optional, but should provide better
// performance. It makes zlib write the data directly in the buffer starting
// at 'dest'. Without it, the data would go through an intermediate buffer
// (_outBuf) before being copied to its (hopefully) final destination.
std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
{
// Despite the somewhat misleading footnote 296 of §27.5.3 of the C++11
// standard, one can't assume std::size_t to be at least as large as
// std::streamsize (64 bits std::streamsize in a 32 bits Windows program).
std::streamsize remaining = n;
char* origGptr = gptr();
char* writePtr = dest; // we'll need dest later -> work with a copy
// First, let's take data present in our internal buffer (_outBuf)
while (remaining > 0) {
// Number of available chars in _outBuf
std::ptrdiff_t avail = egptr() - gptr();
if (avail == 0) { // our internal buffer is empty
break;
}
// We need an int for gbump(), at least in C++11.
int chunkSize_i = clipCast<int>(avail);
if (chunkSize_i > remaining) {
chunkSize_i = static_cast<int>(remaining);
}
assert(chunkSize_i >= 0);
std::copy(gptr(), gptr() + chunkSize_i, writePtr);
gbump(chunkSize_i);
writePtr += chunkSize_i;
// This cast is okay because 0 <= chunkSize_i <= remaining, which is an
// std::streamsize
remaining -= static_cast<std::streamsize>(chunkSize_i);
}
if (remaining == 0) {
// Everything we needed was already in _outBuf. The putback area is set up
// as it should between eback() and the current gptr(), so we are fine to
// return.
return n;
}
// Now, let's make it so that the remaining data we need is directly written
// to the destination area, without going through _outBuf.
_zstream.next_out = reinterpret_cast<unsigned char*>(writePtr);
bool allInputRead = false;
int retCode;
while (remaining > 0) {
std::streamsize chunkSize_s = zlibChunk(remaining);
// chunkSize_s > 0 and does fit in a zlib uInt: that's the whole point of
// zlibChunk.
_zstream.avail_out = static_cast<uInt>(chunkSize_s);
if (_zstream.avail_in == 0 && !allInputRead) {
allInputRead = getInputData();
}
// Make zlib process some data (compress or decompress). This updates
// _zstream.{avail,next}_{in,out} (4 fields of the z_stream struct).
retCode = zlibProcessData();
// chunkSize_s - _zstream.avail_out is the number of chars written by zlib.
// 0 <= _zstream.avail_out <= chunkSize_s, which is an std::streamsize.
remaining -= chunkSize_s - static_cast<std::streamsize>(_zstream.avail_out);
if (retCode == Z_BUF_ERROR) {
handleZ_BUF_ERROR(); // doesn't return
} else if (retCode == Z_STREAM_END) {
assert(_zstream.avail_in == 0); // all of _inBuf must have been used
_allFinished = true;
break;
} else if (retCode < 0) { // negative codes are errors
throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
sg_location(_path));
}
}
// Finally, copy chars to the putback area.
std::size_t nbPutbackChars = xsgetn_preparePutbackArea(
origGptr, dest, reinterpret_cast<char*>(_zstream.next_out));
setg(_outBuf + _putbackSize - nbPutbackChars, // start of putback area
_outBuf + _putbackSize, // the buffer for pending,
_outBuf + _putbackSize); // available data is empty
assert(remaining >= 0);
assert(n - remaining >= 0);
// Total number of chars copied.
return n - remaining;
}
// Utility method for xsgetn(): copy some chars to the putback area
std::size_t ZlibAbstractIStreambuf::xsgetn_preparePutbackArea(
char* origGptr, char* dest, char* writePtr)
{
// There are two buffers containing characters we potentially have to copy
// to the putback area: the one starting at _outBuf and the one starting at
// dest. In the following diagram, ***** represents those from _outBuf,
// which are right-aligned at origGptr[1], and the series of # represents
// those in the [dest, writePtr) address range:
//
// |_outBuf |eback() *****|origGptr |_outBuf + _outBufSize
//
// |dest #################|writePtr
//
// Together, these two memory blocks logically form a contiguous stream of
// chars we gave to the “client”: *****#################. All we have to do
// now is copy the appropriate amount of chars from this logical stream to
// the putback area, according to _putbackSize. These chars are the last
// ones in said stream (i.e., right portion of *****#################), but
// we have to copy them in order of increasing address to avoid possible
// overlapping problems in _outBuf. This is because some of the chars to
// copy may be located before _outBuf + _putbackSize (i.e., already be in
// the putback area).
//
// [1] This means that the last char represented by a star is at address
// origGptr-1.
// It seems std::ptrdiff_t is the signed counterpart of std::size_t,
// therefore this should always hold (even with equality).
static_assert(sizeof(std::size_t) >= sizeof(std::ptrdiff_t),
"Unexpected: sizeof(std::size_t) < sizeof(std::ptrdiff_t)");
assert(writePtr - dest >= 0);
std::size_t inDestBuffer = static_cast<std::size_t>(writePtr - dest);
assert(origGptr - eback() >= 0);
std::size_t nbPutbackChars = std::min(
static_cast<std::size_t>(origGptr - eback()) + inDestBuffer,
_putbackSize);
std::size_t nbPutbackCharsToGo = nbPutbackChars;
// Are there chars in _outBuf that need to be copied to the putback area?
if (nbPutbackChars > inDestBuffer) {
std::size_t chunkSize = nbPutbackChars - inDestBuffer; // yes, this number
std::copy(origGptr - chunkSize, origGptr,
_outBuf + _putbackSize - nbPutbackChars);
nbPutbackCharsToGo -= chunkSize;
}
// Finally, copy those that are not in _outBuf
std::copy(writePtr - nbPutbackCharsToGo, writePtr,
_outBuf + _putbackSize - nbPutbackCharsToGo);
return nbPutbackChars;
}
// ***************************************************************************
// * ZlibCompressorIStreambuf class *
// ***************************************************************************
ZlibCompressorIStreambuf::ZlibCompressorIStreambuf(
std::istream& iStream,
const SGPath& path,
int compressionLevel,
ZLibCompressionFormat format,
ZLibMemoryStrategy memStrategy,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: ZlibAbstractIStreambuf(iStream, path, inBuf, inBufSize, outBuf, outBufSize,
putbackSize)
{
zStreamInit(compressionLevel, format, memStrategy);
}
ZlibCompressorIStreambuf::ZlibCompressorIStreambuf(
std::unique_ptr<std::istream> iStream_p,
const SGPath& path,
int compressionLevel,
ZLibCompressionFormat format,
ZLibMemoryStrategy memStrategy,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: ZlibCompressorIStreambuf(*iStream_p, path, compressionLevel, format,
memStrategy, inBuf, inBufSize, outBuf, outBufSize,
putbackSize)
{
_iStream_p = std::move(iStream_p); // take ownership of the object
}
ZlibCompressorIStreambuf::~ZlibCompressorIStreambuf()
{
int retCode = deflateEnd(&_zstream); // deallocate the z_stream struct
if (retCode != Z_OK) {
// In C++11, we can't throw exceptions from a destructor.
SG_LOG(SG_IO, SG_ALERT, "ZlibCompressorIStreambuf: " <<
::zlibErrorMessage(_zstream, retCode));
}
}
ZlibAbstractIStreambuf::OperationType
ZlibCompressorIStreambuf::operationType() const
{
return OperationType::COMPRESSION;
}
void ZlibCompressorIStreambuf::zStreamInit(int compressionLevel,
ZLibCompressionFormat format,
ZLibMemoryStrategy memStrategy)
{
int windowBits, memLevel;
// Intentionally not listing ZLibCompressionFormat::AUTODETECT here (it is
// only for decompression!)
switch (format) {
case ZLibCompressionFormat::ZLIB:
windowBits = 15;
break;
case ZLibCompressionFormat::GZIP:
windowBits = 31;
break;
default:
throw std::logic_error("Unexpected compression format: " +
std::to_string(enumValue(format)));
}
switch (memStrategy) {
case ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED:
memLevel = 8;
break;
case ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY:
memLevel = 9;
break;
default:
throw std::logic_error("Unexpected memory strategy: " +
std::to_string(enumValue(memStrategy)));
}
_zstream.zalloc = Z_NULL; // No custom memory allocation routines
_zstream.zfree = Z_NULL; // Ditto. Therefore, the 'opaque' field won't
_zstream.opaque = Z_NULL; // be used, actually.
int retCode = deflateInit2(&_zstream, compressionLevel, Z_DEFLATED,
windowBits, memLevel, Z_DEFAULT_STRATEGY);
if (retCode != Z_OK) {
throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
sg_location(_path));
}
}
int ZlibCompressorIStreambuf::zlibProcessData()
{
// Compress as much data as possible given _zstream.avail_in and
// _zstream.avail_out. The input data starts at _zstream.next_in, the output
// at _zstream.next_out, and these four fields are all updated by this call
// to reflect exactly the amount of data zlib has consumed and produced.
return deflate(&_zstream, _zstream.avail_in ? Z_NO_FLUSH : Z_FINISH);
}
// ***************************************************************************
// * ZlibDecompressorIStreambuf class *
// ***************************************************************************
ZlibDecompressorIStreambuf::ZlibDecompressorIStreambuf(
std::istream& iStream,
const SGPath& path,
ZLibCompressionFormat format,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: ZlibAbstractIStreambuf(iStream, path, inBuf, inBufSize, outBuf, outBufSize,
putbackSize)
{
zStreamInit(format);
}
ZlibDecompressorIStreambuf::ZlibDecompressorIStreambuf(
std::unique_ptr<std::istream> iStream_p,
const SGPath& path,
ZLibCompressionFormat format,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: ZlibDecompressorIStreambuf(*iStream_p, path, format, inBuf, inBufSize,
outBuf, outBufSize, putbackSize)
{
_iStream_p = std::move(iStream_p); // take ownership of the object
}
ZlibDecompressorIStreambuf::~ZlibDecompressorIStreambuf()
{
int retCode = inflateEnd(&_zstream); // deallocate the z_stream struct
if (retCode != Z_OK) {
// In C++11, we can't throw exceptions from a destructor.
SG_LOG(SG_IO, SG_ALERT, "ZlibDecompressorIStreambuf: " <<
::zlibErrorMessage(_zstream, retCode));
}
}
ZlibAbstractIStreambuf::OperationType
ZlibDecompressorIStreambuf::operationType() const
{
return OperationType::DECOMPRESSION;
}
void ZlibDecompressorIStreambuf::zStreamInit(ZLibCompressionFormat format)
{
int windowBits;
switch (format) {
case ZLibCompressionFormat::ZLIB:
windowBits = 15;
break;
case ZLibCompressionFormat::GZIP:
windowBits = 31;
break;
case ZLibCompressionFormat::AUTODETECT:
windowBits = 47; // 47 = 32 + 15
break;
default:
throw std::logic_error("Unexpected compression format: " +
std::to_string(enumValue(format)));
}
_zstream.zalloc = Z_NULL; // No custom memory allocation routines
_zstream.zfree = Z_NULL; // Ditto. Therefore, the 'opaque' field won't
_zstream.opaque = Z_NULL; // be used, actually.
int retCode = inflateInit2(&_zstream, windowBits);
if (retCode != Z_OK) {
throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
sg_location(_path));
}
}
int ZlibDecompressorIStreambuf::zlibProcessData()
{
// Decompress as much data as possible given _zstream.avail_in and
// _zstream.avail_out. The input data starts at _zstream.next_in, the output
// at _zstream.next_out, and these four fields are all updated by this call
// to reflect exactly the amount of data zlib has consumed and produced.
return inflate(&_zstream, _zstream.avail_in ? Z_NO_FLUSH : Z_FINISH);
}
// ***************************************************************************
// * ZlibCompressorIStream class *
// ***************************************************************************
ZlibCompressorIStream::ZlibCompressorIStream(std::istream& iStream,
const SGPath& path,
int compressionLevel,
ZLibCompressionFormat format,
ZLibMemoryStrategy memStrategy,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: std::istream(nullptr),
_streamBuf(iStream, path, compressionLevel, format, memStrategy, inBuf,
inBufSize, outBuf, outBufSize, putbackSize)
{
// Associate _streamBuf to 'this' and clear the error state flags
rdbuf(&_streamBuf);
}
ZlibCompressorIStream::ZlibCompressorIStream(
std::unique_ptr<std::istream> iStream_p,
const SGPath& path,
int compressionLevel,
ZLibCompressionFormat format,
ZLibMemoryStrategy memStrategy,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: std::istream(nullptr),
_streamBuf(std::move(iStream_p), path, compressionLevel, format,
memStrategy, inBuf, inBufSize, outBuf, outBufSize, putbackSize)
{
// Associate _streamBuf to 'this' and clear the error state flags
rdbuf(&_streamBuf);
}
ZlibCompressorIStream::~ZlibCompressorIStream()
{ }
// ***************************************************************************
// * ZlibDecompressorIStream class *
// ***************************************************************************
ZlibDecompressorIStream::ZlibDecompressorIStream(std::istream& iStream,
const SGPath& path,
ZLibCompressionFormat format,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: std::istream(nullptr),
_streamBuf(iStream, path, format, inBuf, inBufSize, outBuf, outBufSize,
putbackSize)
{
// Associate _streamBuf to 'this' and clear the error state flags
rdbuf(&_streamBuf);
}
ZlibDecompressorIStream::ZlibDecompressorIStream(
std::unique_ptr<std::istream> iStream_p,
const SGPath& path,
ZLibCompressionFormat format,
char* inBuf,
std::size_t inBufSize,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: std::istream(nullptr),
_streamBuf(std::move(iStream_p), path, format, inBuf, inBufSize,
outBuf, outBufSize, putbackSize)
{
// Associate _streamBuf to 'this' and clear the error state flags
rdbuf(&_streamBuf);
}
ZlibDecompressorIStream::~ZlibDecompressorIStream()
{ }
} // of namespace simgear

View File

@@ -0,0 +1,477 @@
// -*- coding: utf-8 -*-
//
// zlibstream.hxx --- IOStreams classes for working with RFC 1950 and RFC 1952
// compression formats (respectively known as the zlib and
// gzip formats)
//
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#ifndef SG_ZLIBSTREAM_HXX
#define SG_ZLIBSTREAM_HXX
#include <ios> // std::streamsize
#include <istream>
#include <streambuf>
#include <memory> // std::unique_ptr
#include <zlib.h> // struct z_stream
#include <simgear/misc/sg_path.hxx>
// This file contains:
//
// - two stream buffer classes (ZlibCompressorIStreambuf and
// ZlibDecompressorIStreambuf), both based on the same abstract class:
// ZlibAbstractIStreambuf;
//
// - two std::istream subclasses (ZlibCompressorIStream and
// ZlibDecompressorIStream), each creating and using the corresponding
// stream buffer class from the previous item.
//
// All these allow one to work with RFC 1950 and RFC 1952 compression
// formats, respectively known as the zlib and gzip formats.
//
// These classes are *input* streaming classes, which means they can
// efficiently handle arbitrary amounts of data without using any disk
// space nor increasing amounts of memory, and allow “client code” to pull
// exactly as much data as it wants at any given time, resuming later
// when it is ready to handle the next chunk.
//
// So, for example, assuming you've created an instance of
// ZlibCompressorIStream (bound to some input stream of your choice, let's
// call it iStream), you could read 512 bytes of data from it, and you
// would get the first 512 bytes of *compressed* data corresponding to what
// iStream provided. Then you could resume at any time and ask for the next
// 512 bytes of compressed data (or any other amount), etc.
//
// Therefore, these classes are well suited, among others, to compress or
// decompress data streams while at the same time packing the result into
// discrete chunks or packets with size constraints (you can think of the
// process as making sausages :).
//
// The input being in each case an std::istream (for compressing as well as
// for decompressing), it can be tied to an arbitrary source: a file with
// sg_ifstream or std::ifstream, a memory buffer with std::istringstream or
// std::stringstream, a TCP socket with a custom std::streambuf subclass[1]
// to interface with the sockets API, etc.
//
// [1] Possibly wrapped in an std::istream.
//
// The stream buffer classes upon which ZlibCompressorIStream and
// ZlibDecompressorIStream are built have an xsgetn() implementation that
// avoids useless copies of data by asking zlib to write directly to the
// destination buffer. This xsgetn() method is used when calling read() on
// the std::istream subclasses, or sgetn() if you are using the stream
// buffer classes directly (i.e., ZlibCompressorIStreambuf and
// ZlibDecompressorIStreambuf). Other std::istream methods may instead rely
// only on the internal buffer and the underflow() method, and therefore be
// less efficient for large amounts of data. You may want to take a look at
// zlibstream_test.cxx to see various ways of using these classes.
//
// In case you use std::istream& operator>>(std::istream&, std::string&) or
// its overload friends, beware that it splits fields at spaces, and by
// default ignores spaces at the beginning of a field (cf. std::skipws,
// std::noskipws and friends). As far as I understand it, most of these
// operators are mainly intended in the IOStreams library to be used to
// read an int here, a double there, a space-delimited string afterwards,
// etc. (the exception could be the overload writing to a stream buffer,
// however it doesn't seem to be very efficient on my system with GNU
// libstdc++ [it is not using xsgetn()], so beware also of this one if you
// are handling large amounts of data). For moderately complex or large
// input handling, I'd suggest to use std::istream methods such as read(),
// gcount() and getline() (std::getline() can be useful too). Or directly
// use the stream buffer classes, in particular with sgetn().
namespace simgear
{
enum class ZLibCompressionFormat {
ZLIB = 0,
GZIP,
AUTODETECT
};
enum class ZLibMemoryStrategy {
FAVOR_MEMORY_OVER_SPEED = 0,
FAVOR_SPEED_OVER_MEMORY
};
// Abstract base class for both the compressor and decompressor stream buffers.
class ZlibAbstractIStreambuf: public std::streambuf
{
public:
/**
* @brief Constructor for ZlibAbstractIStreambuf.
* @param iStream Input stream to read from.
* @param path Optional path to the file corresponding to iStream,
* if any. Only used for error messages.
* @param inBuf Pointer to the input buffer (data read from iStream is
* written there before being compressed or decompressed).
* If nullptr, the buffer is allocated on the heap in the
* constructor and deallocated in the destructor.
* @param inBufSize Size of the input buffer, in chars.
* @param outBuf Pointer to the output buffer. Data is read by zlib
* from the input buffer, compressed or decompressed, and
* the result is directly written to the output buffer.
* If nullptr, the buffer is allocated on the heap in the
* constructor and deallocated in the destructor.
* @param outBufSize Size of the output buffer, in chars.
* @param putbackSize Size of the putback area inside the output buffer, in
* chars.
*
* It is required that putbackSize < outBufSize. It is guaranteed that,
* if at least putbackSize chars have been read without any putback (or
* unget) operation intermixed, then at least putbackSize chars can be
* put back in sequence. If you don't need this feature, use zero for the
* putbackSize value (the default) for best performance.
*/
explicit ZlibAbstractIStreambuf(std::istream& iStream,
const SGPath& path = SGPath(),
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0);
// Alternate constructor with sink semantics for the “source” std::istream.
// When used, the class takes ownership of the std::istream instance pointed
// to by the first constructor argument, and keeps it alive as long as the
// object this constructor is for is itself alive.
explicit ZlibAbstractIStreambuf(std::unique_ptr<std::istream> iStream_p,
const SGPath& path = SGPath(),
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0);
ZlibAbstractIStreambuf(const ZlibAbstractIStreambuf&) = delete;
ZlibAbstractIStreambuf& operator=(const ZlibAbstractIStreambuf&) = delete;
virtual ~ZlibAbstractIStreambuf();
protected:
enum class OperationType {
COMPRESSION = 0,
DECOMPRESSION
};
virtual OperationType operationType() const = 0;
// Either compress or decompress a chunk of data (depending on the
// particular subclass implementation). The semantics are the same as for
// zlib's inflate() and deflate() functions applied to member _zstream,
// concerning 1) the return value and 2) where, and how much to read and
// write (which thus depends on _zstream.avail_in, _zstream.next_in,
// _zstream.avail_out and _zstream.next_out).
virtual int zlibProcessData() = 0;
// The input stream, from which data is read before being processed by zlib
std::istream& _iStream;
// Pointer to the same, used when calling the constructor that takes an
// std::unique_ptr<std::istream> as its first argument; empty
// std::unique_ptr object otherwise.
std::unique_ptr<std::istream> _iStream_p;
// Corresponding path, if any (default-constructed SGPath instance otherwise)
const SGPath _path;
// Structure used to communicate with zlib
z_stream _zstream;
private:
// Callback whose role is to refill the output buffer when it's empty and
// the “client” tries to read more.
virtual int underflow() override;
// Optional override when subclassing std::streambuf. This is the most
// efficient way of reading several characters (as soon as we've emptied the
// output buffer, data is written by zlib directly to the destination
// buffer).
virtual std::streamsize xsgetn(char* dest, std::streamsize n) override;
// Utility method for xsgetn()
std::size_t xsgetn_preparePutbackArea(char* origGptr, char* dest,
char* writePtr);
// Make sure there is data to read in the input buffer, or signal EOF.
bool getInputData();
// Utility method for fillOutputBuffer()
std::size_t fOB_remainingSpace(unsigned char* nextOutPtr) const;
// Fill the output buffer (using zlib functions) as much as possible.
char* fillOutputBuffer();
// Utility method
[[ noreturn ]] void handleZ_BUF_ERROR() const;
bool _allFinished = false;
// The buffers
// ~~~~~~~~~~~
//
// The input buffer receives data obtained from _iStream, before it is
// processed by zlib. In underflow(), zlib reads from this buffer it and
// writes the resulting data(*) to the output buffer. Then we point the
// standard std::streambuf pointers (gptr() and friends) directly towards
// the data inside that output buffer. xsgetn() is even more optimized: it
// first empties the output buffer, then makes zlib write the remaining data
// directly to the destination area.
//
// (*) Compressed or decompressed, depending on the particular
// implementation of zlibProcessData() in each subclass.
char* _inBuf;
const std::size_t _inBufSize;
// _inBufEndPtr points right after the last data retrieved from _iStream and
// stored into _inBuf. When zlib has read all such data, _zstream.next_in is
// equal to _inBufEndPtr (after proper casting). Except in this particular
// situation, only _zstream.next_in <= _inBufEndPtr is guaranteed.
char* _inBufEndPtr;
// Layout of the _outBuf buffer:
//
// |_outBuf <putback area> |_outBuf + _putbackSize |_outBuf + _outBufSize
//
// The first _putbackSize chars in _outBuf are reserved for the putback area
// (right-aligned at _outBuf + _putbackSize). The actual output buffer thus
// starts at _outBuf + _putbackSize. At any given time for callers of this
// class, the number of characters that can be put back is gptr() - eback().
// It may be lower than _putbackSize if we haven't read that many characters
// yet. It may also be larger if gptr() > _outBuf + _putbackSize, i.e.,
// when the buffer for pending data is non-empty.
//
// At any given time, callers should see:
//
// _outBuf <= eback() <= _outBuf + _putbackSize <= gptr() <= egptr()
// <= _outBuf + _outBufSize
//
// (hoping this won't get out of sync with the code!)
char *_outBuf;
const std::size_t _outBufSize;
// Space reserved for characters to be put back into the stream. Must be
// strictly smaller than _outBufSize (this is checked in the constructor).
// It is guaranteed that this number of chars can be put back, except of
// course if we haven't read that many characters from the input stream yet.
// If characters are buffered in _outBuf[2], then it may be that more
// characters than _putbackSize can be put back (it is essentially a matter
// for std::streambuf of decreasing the “next pointer for the input
// sequence”, i.e., the one returned by gptr()).
//
// [2] In the [_outBuf + _putbackSize, _outBuf + _outBufSize) area.
const std::size_t _putbackSize;
// Since the constructor optionally allocates memory for the input and
// output buffers, these members allow the destructor to know which buffers
// have to be deallocated, if any.
bool _inBufMustBeFreed = false;
bool _outBufMustBeFreed = false;
};
// Stream buffer class for compressing data. Input data is obtained from an
// std::istream instance; the corresponding compressed data can be read using
// the standard std::streambuf read interface (mainly: sbumpc(), sgetc(),
// snextc(), sgetn(), sputbackc(), sungetc()). Input, uncompressed data is
// “pulled” as needed for the amount of compressed data requested by the
// “client” using the methods I just listed.
class ZlibCompressorIStreambuf: public ZlibAbstractIStreambuf
{
public:
// Same parameters as for ZlibAbstractIStreambuf, except:
//
// compressionLevel: in the [0,9] range. 0 means no compression at all.
// Levels 1 to 9 yield compressed data, with 1 giving
// the highest compression speed but worst compression
// ratio, and 9 the highest compression ratio but lowest
// compression speed.
// format either ZLibCompressionFormat::ZLIB or
// ZLibCompressionFormat::GZIP
// memStrategy either ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED or
// ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY
explicit ZlibCompressorIStreambuf(
std::istream& iStream,
const SGPath& path = SGPath(),
int compressionLevel = Z_DEFAULT_COMPRESSION,
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0);
// Alternate constructor with sink semantics for the “source” std::istream.
explicit ZlibCompressorIStreambuf(
std::unique_ptr<std::istream> _iStream_p,
const SGPath& path = SGPath(),
int compressionLevel = Z_DEFAULT_COMPRESSION,
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0);
ZlibCompressorIStreambuf(const ZlibCompressorIStreambuf&) = delete;
ZlibCompressorIStreambuf& operator=(const ZlibCompressorIStreambuf&) = delete;
virtual ~ZlibCompressorIStreambuf();
protected:
virtual OperationType operationType() const override;
// Initialize the z_stream struct used by zlib
void zStreamInit(int compressionLevel, ZLibCompressionFormat format,
ZLibMemoryStrategy memStrategy);
// Call zlib's deflate() function to compress data.
virtual int zlibProcessData() override;
};
// Stream buffer class for decompressing data. Input data is obtained from an
// std::istream instance; the corresponding decompressed data can be read
// using the standard std::streambuf read interface (mainly: sbumpc(),
// sgetc(), snextc(), sgetn(), sputbackc(), sungetc()). Input, compressed data
// is “pulled” as needed for the amount of uncompressed data requested by the
// “client” using the methods I just listed.
class ZlibDecompressorIStreambuf: public ZlibAbstractIStreambuf
{
public:
// Same parameters as for ZlibAbstractIStreambuf, except:
//
// format ZLibCompressionFormat::ZLIB,
// ZLibCompressionFormat::GZIP or
// ZLibCompressionFormat::AUTODETECT
explicit ZlibDecompressorIStreambuf(
std::istream& iStream,
const SGPath& path = SGPath(),
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0); // default optimized for speed
// Alternate constructor with sink semantics for the “source” std::istream.
explicit ZlibDecompressorIStreambuf(
std::unique_ptr<std::istream> _iStream_p,
const SGPath& path = SGPath(),
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0); // default optimized for speed
ZlibDecompressorIStreambuf(const ZlibDecompressorIStreambuf&) = delete;
ZlibDecompressorIStreambuf& operator=(const ZlibDecompressorIStreambuf&)
= delete;
virtual ~ZlibDecompressorIStreambuf();
protected:
virtual OperationType operationType() const override;
void zStreamInit(ZLibCompressionFormat format);
virtual int zlibProcessData() override;
};
// std::istream subclass for compressing data. Input data is obtained from an
// std::istream instance; the corresponding compressed data can be read using
// the standard std::istream interface (read(), readsome(), gcount(), get(),
// getline(), operator>>(), peek(), putback(), ignore(), unget()... plus
// operator overloads such as istream& operator>>(istream&, string&) as
// defined in <string>, and std::getline()). Input, uncompressed data is
// “pulled” as needed for the amount of compressed data requested by the
// “client”.
//
// To get data efficiently from an instance of this class, use its read()
// method (typically in conjunction with gcount(), inside a loop).
class ZlibCompressorIStream: public std::istream
{
public:
// Same parameters as for ZlibCompressorIStreambuf
explicit ZlibCompressorIStream(
std::istream& iStream,
const SGPath& path = SGPath(),
int compressionLevel = Z_DEFAULT_COMPRESSION,
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0); // default optimized for speed
// Alternate constructor with sink semantics for the “source” std::istream.
explicit ZlibCompressorIStream(
std::unique_ptr<std::istream> _iStream_p,
const SGPath& path = SGPath(),
int compressionLevel = Z_DEFAULT_COMPRESSION,
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0); // default optimized for speed
ZlibCompressorIStream(const ZlibCompressorIStream&) = delete;
ZlibCompressorIStream& operator=(const ZlibCompressorIStream&) = delete;
virtual ~ZlibCompressorIStream();
private:
ZlibCompressorIStreambuf _streamBuf;
};
// std::istream subclass for decompressing data. Input data is obtained from
// an std::istream instance; the corresponding decompressed data can be read
// using the standard std::istream interface (read(), readsome(), gcount(),
// get(), getline(), operator>>(), peek(), putback(), ignore(), unget()...
// plus operator overloads such as istream& operator>>(istream&, string&) as
// defined in <string>, and std::getline()). Input, compressed data is
// “pulled” as needed for the amount of uncompressed data requested by the
// “client”.
//
// To get data efficiently from an instance of this class, use its read()
// method (typically in conjunction with gcount(), inside a loop).
class ZlibDecompressorIStream: public std::istream
{
public:
// Same parameters as for ZlibDecompressorIStreambuf
explicit ZlibDecompressorIStream(
std::istream& iStream,
const SGPath& path = SGPath(),
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0); // default optimized for speed
// Alternate constructor with sink semantics for the “source” std::istream.
explicit ZlibDecompressorIStream(
std::unique_ptr<std::istream> _iStream_p,
const SGPath& path = SGPath(),
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
char* inBuf = nullptr,
std::size_t inBufSize = 262144,
char* outBuf = nullptr,
std::size_t outBufSize = 262144,
std::size_t putbackSize = 0); // default optimized for speed
ZlibDecompressorIStream(const ZlibDecompressorIStream&) = delete;
ZlibDecompressorIStream& operator=(const ZlibDecompressorIStream&) = delete;
virtual ~ZlibDecompressorIStream();
private:
ZlibDecompressorIStreambuf _streamBuf;
};
} // of namespace simgear
#endif // of SG_ZLIBSTREAM_HXX

View File

@@ -0,0 +1,732 @@
// -*- coding: utf-8 -*-
//
// zlibstream_test.cxx --- Automated tests for zlibstream.cxx / zlibstream.hxx
//
// Copyright (C) 2017 Florent Rougon
//
// 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 Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <ios> // std::basic_ios, std::streamsize...
#include <iostream> // std::ios_base, std::cerr, etc.
#include <sstream>
#include <array>
#include <random>
#include <memory> // std::unique_ptr
#include <utility> // std::move()
#include <limits> // std::numeric_limits
#include <type_traits> // std::make_unsigned()
#include <functional> // std::bind()
#include <cassert>
#include <cstdlib> // EXIT_SUCCESS
#include <cstddef> // std::size_t
#include <cstring> // strcmp()
#include <zlib.h> // Z_BEST_COMPRESSION
#include <simgear/misc/test_macros.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sg_dir.hxx>
using std::string;
using std::cout;
using std::cerr;
using traits = std::char_traits<char>;
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
// Safely convert a non-negative std::streamsize into an std::size_t. If
// impossible, bail out.
static std::size_t streamsizeToSize_t(std::streamsize n)
{
SG_CHECK_GE(n, 0);
SG_CHECK_LE(static_cast<uStreamSize>(n),
std::numeric_limits<std::size_t>::max());
return static_cast<std::size_t>(n);
}
// In many tests below, I use very small buffer sizes. Of course, this is bad
// for performance. The reason I do it this way is simply because it better
// exercises the code we want to *test* here (we are more likely to find bugs
// if the buffer(s) has/have to be refilled one or more times). Similarly, if
// you don't need the putback feature in non-test code, best performance is
// achieved with putback size = 0.
//
// I suggest reading test_IStreamConstructorWithSinkSemantics() below to see
// how to use the classes efficiently.
static std::default_random_engine randomNumbersGenerator;
// Sample string for tests
static const string lipsum = "\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque congue ornare\n\
congue. Mauris mollis est et porttitor condimentum. Vivamus laoreet blandit\n\
odio eget consectetur. Etiam quis magna eu enim luctus pretium. In et\n\
tristique nunc, non efficitur metus. Nullam efficitur tristique velit.\n\
Praesent et luctus nunc. Mauris eros eros, rutrum at molestie quis, egestas et\n\
lorem. Ut nulla turpis, eleifend sed mauris ac, faucibus molestie nulla.\n\
Quisque viverra vel turpis nec efficitur. Proin non rutrum velit. Nam sodales\n\
metus felis, eu pharetra velit posuere ut.\n\
\n\
Suspendisse pellentesque tincidunt ligula et pretium. Etiam id justo mauris.\n\
Aenean porta, sapien in suscipit tristique, metus diam malesuada dui, in porta\n\
felis mi non felis. Etiam vel aliquam leo, non vehicula magna. Proin justo\n\
massa, ultrices at porta eu, tempor in ligula. Duis enim ipsum, dictum quis\n\
ultricies et, tempus eu libero. Morbi vulputate libero ut dolor rutrum, a\n\
imperdiet nibh egestas. Phasellus finibus massa vel tempus hendrerit. Nulla\n\
lobortis est non ligula viverra, quis egestas ante hendrerit. Pellentesque\n\
finibus mollis blandit. In ac sollicitudin mauris, eget dignissim mauris.";
// Utility function: generate a random string whose length is in the
// [minLen, maxLen] range.
string randomString(string::size_type minLen, string::size_type maxLen)
{
std::uniform_int_distribution<string::size_type> sLenDist(minLen, maxLen);
std::uniform_int_distribution<int> byteDist(0, 255);
auto randomByte = std::bind(byteDist, randomNumbersGenerator);
string::size_type len = sLenDist(randomNumbersGenerator);
string str;
while (str.size() < len) {
str += traits::to_char_type(randomByte());
}
return str;
}
// Utility function: perform a compression-decompression cycle on the input
// string using the stream buffer classes:
//
// 1) Compress the input string, write the result to a temporary file using
// std::ostream& operator<<(streambuf* sb), where 'sb' points to a
// ZlibCompressorIStreambuf instance.
//
// 2) Close the temporary file to make sure all the data is flushed.
//
// 3) Create a ZlibDecompressorIStreambuf instance reading from the
// temporary file, then decompress to an std::ostringstream using
// std::ostream& operator<<(streambuf* sb), where 'sb' points to our
// ZlibDecompressorIStreambuf instance.
//
// 4) Compare the result with the input string.
//
// Of course, it is possible to do this without any temporary file, even
// without any std::stringstream or such to hold the intermediate, compressed
// data. See the other tests for actual compressor + decompressor pipelines.
void pipeCompOrDecompIStreambufIntoOStream(const string& input)
{
// Test “std::ostream << &ZlibCompressorIStreambuf”
std::istringstream input_ss(input);
simgear::ZlibCompressorIStreambuf compSBuf(input_ss, SGPath(), 9);
simgear::Dir tmpDir = simgear::Dir::tempDir("FlightGear");
tmpDir.setRemoveOnDestroy();
SGPath path = tmpDir.path() / "testFile.dat";
sg_ofstream oFile(path,
std::ios::binary | std::ios::trunc | std::ios::out);
oFile << &compSBuf;
// Make sure the compressed stream is flushed, otherwise when we read the
// file back, decompression is likely to fail with Z_BUF_ERROR.
oFile.close();
SG_CHECK_EQUAL(compSBuf.sgetc(), EOF);
// Read back and decompress the data we've written to 'path', testing
// “std::ostream << &ZlibDecompressorIStreambuf”
sg_ifstream iFile(path, std::ios::binary | std::ios::in);
simgear::ZlibDecompressorIStreambuf decompSBuf(iFile, path);
std::ostringstream roundTripResult_ss;
// This is also possible, though maybe not as good for error detection:
//
// decompSBuf >> roundTripResult_ss.rdbuf();
roundTripResult_ss << &decompSBuf;
SG_CHECK_EQUAL(decompSBuf.sgetc(), EOF);
SG_CHECK_EQUAL(roundTripResult_ss.str(), input);
}
// Perform a compression-decompression cycle on bunch of strings, using the
// stream buffer classes directly (not the std::istream subclasses).
void test_pipeCompOrDecompIStreambufIntoOStream()
{
cerr << "Compression-decompression cycles using the stream buffer classes "
"directly\n";
pipeCompOrDecompIStreambufIntoOStream(""); // empty string
pipeCompOrDecompIStreambufIntoOStream("a");
pipeCompOrDecompIStreambufIntoOStream("lorem ipsum");
for (int i=0; i < 10; i++) {
pipeCompOrDecompIStreambufIntoOStream(randomString(0, 20));
}
for (int i=0; i < 10; i++) {
pipeCompOrDecompIStreambufIntoOStream(randomString(21, 1000));
}
assert(std::numeric_limits<string::size_type>::max() >= 65535);
for (int i=0; i < 10; i++) {
pipeCompOrDecompIStreambufIntoOStream(randomString(1000, 65535));
}
}
void test_StreambufBasicOperations()
{
cerr << "Testing basic operations on ZlibDecompressorIStreambuf\n";
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ";
std::istringstream text_ss(text);
static constexpr std::size_t compInBufSize = 6;
static constexpr std::size_t compOutBufSize = 4;
static constexpr std::size_t compPutbackSize = 0;
simgear::ZlibCompressorIStreambuf compSBuf(
text_ss, SGPath(), 8, simgear::ZLibCompressionFormat::ZLIB,
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
nullptr, compInBufSize, nullptr, compOutBufSize, compPutbackSize);
std::stringstream compressedOutput_ss;
compressedOutput_ss << &compSBuf;
static constexpr std::size_t decompInBufSize = 5;
static constexpr std::size_t decompOutBufSize = 4;
static constexpr std::size_t decompPutbackSize = 2;
simgear::ZlibDecompressorIStreambuf decompSBuf(
compressedOutput_ss, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
nullptr, decompInBufSize, nullptr, decompOutBufSize, decompPutbackSize);
int ch = decompSBuf.sgetc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
ch = decompSBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
ch = decompSBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1');
ch = decompSBuf.snextc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
ch = decompSBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
ch = decompSBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
ch = decompSBuf.sputbackc('4');
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
ch = decompSBuf.sputbackc('u'); // doesn't match what we read from the stream
SG_VERIFY(ch == EOF);
ch = decompSBuf.sputbackc('3'); // this one does
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
static constexpr std::streamsize bufSize = 10;
char buf[bufSize];
// Most efficient way (with the underlying xsgetn()) to read several chars
// at once.
std::streamsize n = decompSBuf.sgetn(buf, bufSize);
SG_CHECK_EQUAL(n, bufSize);
SG_CHECK_EQUAL(string(buf, static_cast<std::size_t>(bufSize)),
"3456789abc");
ch = decompSBuf.sungetc(); // same as sputbackc(), except no value to check
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'c');
ch = decompSBuf.sungetc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
ch = decompSBuf.sbumpc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
ch = decompSBuf.sputbackc('b'); // this one does
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
n = decompSBuf.sgetn(buf, bufSize);
SG_CHECK_EQUAL(n, bufSize);
SG_CHECK_EQUAL(string(buf, static_cast<std::size_t>(bufSize)),
"bcdefghijk");
ch = decompSBuf.sungetc();
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'k');
static char buf2[64];
n = decompSBuf.sgetn(buf2, sizeof(buf2));
SG_CHECK_EQUAL(n, 36);
SG_CHECK_EQUAL(string(buf2, 36), "klmnopqrstuvwxyz\nABCDEF\nGHIJK LMNOPQ");
ch = decompSBuf.sbumpc();
SG_CHECK_EQUAL(ch, EOF);
}
// Utility function: take a string as input to compress and return the
// compressed output as a string.
string compress(const string& dataToCompress,
simgear::ZLibCompressionFormat compressionFormat,
int compressionLevel,
simgear::ZLibMemoryStrategy memStrategy,
std::size_t putbackSize,
SGPath path = SGPath())
{
// Static storage is only okay for very small sizes like these, and because
// we won't call the function from several threads at the same time. Plain
// char arrays would be fine here, but I'll use std::array to show it can be
// used here too.
static std::array<char, 7> inBuf;
static std::array<char, 7> outBuf;
std::istringstream iss(dataToCompress);
simgear::ZlibCompressorIStream compressor(
iss, path, compressionLevel, compressionFormat, memStrategy,
&inBuf.front(), inBuf.size(), &outBuf.front(), outBuf.size(),
putbackSize);
compressor.exceptions(std::ios_base::badbit); // throw if badbit is set
std::ostringstream compressedData_ss;
compressor >> compressedData_ss.rdbuf();
return compressedData_ss.str();
}
// Test simgear::ZlibDecompressorIStreambuf::[x]sgetn(), asking the largest
// possible amount of chars every time it is called (i.e., the largest value
// that can be represented by std::streamsize).
void test_ZlibDecompressorIStreambuf_readLargestPossibleAmount()
{
// Nothing special with these values
constexpr std::size_t maxDataSize = 8192;
std::istringstream input_ss(randomString(4096, maxDataSize));
simgear::ZlibCompressorIStream compIStream(
input_ss, // input stream
SGPath(), // this stream is not associated to a file
9, // compression level
simgear::ZLibCompressionFormat::ZLIB,
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
nullptr, // dynamically allocate the input buffer
230, // input buffer size
nullptr, // dynamically allocate the output buffer
120, // output buffer size
1 // putback size
);
// Decompressor stream buffer (std::streambuf subclass) that gets input data
// from our compressor 'compIStream' (std::istream subclass)
simgear::ZlibDecompressorIStreambuf decompSBuf(
compIStream, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
nullptr, 150, nullptr, 175, 2);
std::unique_ptr<char[]> buf(new char[maxDataSize]);
std::ostringstream roundTripResult_ss;
std::streamsize totalCharsToRead = input_ss.str().size();
while (totalCharsToRead > 0) {
// Ask sgetn() the largest possible amount of chars. Of course, we know we
// can't get more than maxDataSize, but this does exercise the code in
// interesting ways due to the various types involved (zlib's uInt,
// std::size_t and std::streamsize, which have various sizes depending on
// the platform).
std::streamsize nbCharsRead = decompSBuf.sgetn(
&buf[0], std::numeric_limits<std::streamsize>::max());
if (nbCharsRead == 0) {
break; // no more data
}
// The conversion to std::size_t is safe because decompSBuf.sgetn()
// returned a non-negative value which, in this case, can't exceed
// maxDataSize.
roundTripResult_ss << string(&buf[0], streamsizeToSize_t((nbCharsRead)));
}
SG_CHECK_EQUAL(decompSBuf.sgetc(), EOF);
SG_CHECK_EQUAL(roundTripResult_ss.str(), input_ss.str());
}
void test_formattedInputFromDecompressor()
{
cerr << "Testing ZlibDecompressorIStream >> std::string\n";
static char inBuf[6];
static char outBuf[15];
string compressed = compress(
lipsum, simgear::ZLibCompressionFormat::ZLIB, Z_BEST_COMPRESSION,
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED, /* putback size */ 0);
std::istringstream compressed_ss(compressed);
simgear::ZlibDecompressorIStream decompressor(
compressed_ss, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
inBuf, sizeof(inBuf), outBuf, sizeof(outBuf), /* putback size */ 1);
decompressor.exceptions(std::ios_base::badbit); // throw if badbit is set
int count = 0;
string word;
while (decompressor >> word) {
count++;
}
SG_CHECK_EQUAL(count, 175); // Number of words in 'lipsum'
// If set, badbit would have caused an exception to be raised
SG_VERIFY(!decompressor.bad());
if (!decompressor.eof()) {
assert(decompressor.fail());
cerr << "Decompressor: stream extraction (operator>>) failed before "
"reaching EOF.\n";
SG_TEST_FAIL("Did not expect operator>> to fail with an std::string "
"argument.");
}
}
void test_ZlibDecompressorIStream_readPutbackEtc()
{
cerr << "Testing many operations on ZlibDecompressorIStream (read(), "
"putback(), etc.\n";
static char compInBuf[4];
static char compOutBuf[6];
static char decompInBuf[8];
static char decompOutBuf[5];
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ";
std::istringstream text_ss(text);
simgear::ZlibCompressorIStream compressor(
text_ss, SGPath(), Z_BEST_COMPRESSION,
simgear::ZLibCompressionFormat::ZLIB,
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED,
compInBuf, sizeof(compInBuf), compOutBuf, sizeof(compOutBuf),
/* putback size */ 0);
compressor.exceptions(std::ios_base::badbit); // throw if badbit is set
// Use the compressor (subclass of std::istream) as input to the decompressor
simgear::ZlibDecompressorIStream decompressor(
compressor, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
decompInBuf, sizeof(decompInBuf), decompOutBuf, sizeof(decompOutBuf),
/* putback size */ 3);
decompressor.exceptions(std::ios_base::badbit);
{
static std::array<char, 17> buf;
decompressor.read(&buf.front(), 10);
SG_VERIFY(decompressor.good() && decompressor.gcount() == 10);
SG_CHECK_EQUAL(string(&buf.front(), 10), "0123456789");
SG_VERIFY(decompressor.putback('9'));
SG_VERIFY(decompressor.putback('8'));
SG_VERIFY(decompressor.putback('7'));
SG_VERIFY(decompressor.get(buf[10]));
SG_VERIFY(decompressor.read(&buf[11], 6));
SG_CHECK_EQUAL(string(&buf.front(), 17), "0123456789789abcd");
}
{
bool gotException = false;
try {
// 'Z' is not the last character read from the stream
decompressor.putback('Z');
} catch (std::ios_base::failure) {
gotException = true;
} catch (const std::exception& e) {
// gcc fails to catch std::ios_base::failure due to an inconsistent C++11
// ABI between headers and libraries. See bug#66145 for more details.
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145
if (!strcmp(e.what(), "basic_ios::clear"))
gotException = true;
else
throw e;
}
SG_VERIFY(gotException && decompressor.bad());
}
{
int c;
decompressor.clear();
// Check we can resume normally now that we've cleared the error state flags
c = decompressor.get();
SG_VERIFY(c != traits::eof() && traits::to_char_type(c) == 'e');
// unget() and get() in sequence
SG_VERIFY(decompressor.unget());
c = decompressor.get();
SG_VERIFY(c != traits::eof() && traits::to_char_type(c)== 'e');
// peek() looks at, but doesn't consume
c = decompressor.peek();
SG_VERIFY(c != traits::eof() && traits::to_char_type(c) == 'f');
c = decompressor.get();
SG_VERIFY(c != traits::eof() && traits::to_char_type(c)== 'f');
}
{
static std::array<char, 21> buf; // 20 chars + terminating NUL char
// std::istream::getline() will be stopped by \n after reading 20 chars
SG_VERIFY(decompressor.getline(&buf.front(), 25));
SG_VERIFY(decompressor.gcount() == 21 &&
!strcmp(&buf.front(), "ghijklmnopqrstuvwxyz"));
string str;
// std::getline() will be stopped by \n after 7 chars have been extracted,
// namely 6 chars plus the \n which is extracted and discarded.
SG_VERIFY(std::getline(decompressor, str));
SG_CHECK_EQUAL(str, "ABCDEF");
SG_VERIFY(decompressor.ignore(2));
SG_VERIFY(decompressor >> str);
SG_CHECK_EQUAL(str, "IJK");
static char buf2[5];
// Read up to sizeof(buf) chars, without waiting if not that many are
// immediately avaiable.
int nbCharsRead = decompressor.readsome(buf2, sizeof(buf2));
string rest(buf2, nbCharsRead);
do {
decompressor.read(buf2, sizeof(buf2));
// The conversion to std::size_t is safe because decompressor.read()
// returns a non-negative value which, in this case, can't exceed
// sizeof(buf2).
rest += string(buf2, streamsizeToSize_t(decompressor.gcount()));
} while (decompressor);
SG_CHECK_EQUAL(rest, " LMNOPQ");
}
// If set, badbit would have caused an exception to be raised, anyway
SG_VERIFY(decompressor.eof() && !decompressor.bad());
}
// Utility function: parametrized round-trip test with a compressor +
// decompressor pipeline.
//
// Note: this is nice conceptually, allows to keep memory use constant even in
// case an arbitrary amount of data is passed through, and exercises the
// stream buffer classes well, however this technique is more than twice
// as slow on my computer than using an intermediate buffer like this:
//
// std::stringstream compressedData_ss;
// compressor >> compressedData_ss.rdbuf();
//
// simgear::ZlibDecompressorIStream decompressor(compressedData_ss,
// SGPath(), ...
void roundTripWithIStreams(
simgear::ZLibCompressionFormat compressionFormat,
int compressionLevel,
simgear::ZLibMemoryStrategy memStrategy,
std::size_t compInBufSize,
std::size_t compOutBufSize,
std::size_t decompInBufSize,
std::size_t decompOutBufSize,
std::size_t compPutbackSize,
std::size_t decompPutbackSize,
bool useAutoFormatForDecompression = false)
{
const simgear::ZLibCompressionFormat decompFormat =
(useAutoFormatForDecompression) ?
simgear::ZLibCompressionFormat::AUTODETECT : compressionFormat;
std::istringstream lipsum_ss(lipsum);
// This tests the optional dynamic buffer allocation in ZlibAbstractIStreambuf
simgear::ZlibCompressorIStream compressor(
lipsum_ss, SGPath(), compressionLevel, compressionFormat, memStrategy,
nullptr, compInBufSize, nullptr, compOutBufSize, compPutbackSize);
compressor.exceptions(std::ios_base::badbit); // throw if badbit is set
// Use the compressor as input to the decompressor (pipeline). The
// decompressor uses read() with chunks that are as large as possible given
// the available space in its input buffer. These read() calls are served by
// ZlibCompressorIStreambuf::xsgetn(), which is efficient.
simgear::ZlibDecompressorIStream decompressor(
compressor, SGPath(), decompFormat,
nullptr, decompInBufSize, nullptr, decompOutBufSize, decompPutbackSize);
decompressor.exceptions(std::ios_base::badbit);
std::ostringstream roundTripResult;
// This, on the other hand, appears not to use xsgetn() (tested with the GNU
// libstdc++ from g++ 6.3.0, 20170124). This causes useless copying of the
// data to the decompressor output buffer, instead of writing it directly to
// roundTripResult's internal buffer. This is simple and nice in appearance,
// but if you are after performance, better use decompressor.read()
// (typically in conjunction with gcount(), inside a loop).
decompressor >> roundTripResult.rdbuf();
SG_CHECK_EQUAL(roundTripResult.str(), lipsum);
}
// Round-trip conversion with simgear::ZlibCompressorIStream and
// simgear::ZlibDecompressorIStream, using various parameter combinations.
void test_RoundTripMultiWithIStreams()
{
cerr <<
"Compression-decompression cycles using the std::istream subclasses (many\n"
"combinations of buffer sizes and compression parameters tested)\n";
{ // More variations on these later
const std::size_t compPutbackSize = 1;
const std::size_t decompPutbackSize = 1;
for (auto format: {simgear::ZLibCompressionFormat::ZLIB,
simgear::ZLibCompressionFormat::GZIP}) {
for (int compressionLevel: {1, 4, 7, 9}) {
for (auto memStrategy: {
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED,
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY}) {
for (std::size_t compInBufSize: {3, 4}) {
for (std::size_t compOutBufSize: {3, 5}) {
for (std::size_t decompInBufSize: {3, 4}) {
for (std::size_t decompOutBufSize: {3, 4,}) {
roundTripWithIStreams(
format, compressionLevel, memStrategy, compInBufSize,
compOutBufSize, decompInBufSize, decompOutBufSize,
compPutbackSize, decompPutbackSize);
}
}
}
}
}
}
}
}
{
const auto format = simgear::ZLibCompressionFormat::ZLIB;
const int compressionLevel = Z_DEFAULT_COMPRESSION;
const auto memStrategy =
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY;
for (std::size_t compInBufSize: {3, 4, 31, 256, 19475}) {
for (std::size_t compOutBufSize: {3, 5, 9, 74, 4568}) {
for (std::size_t decompInBufSize: {3, 4, 256, 24568}) {
for (std::size_t decompOutBufSize: {3, 5, 42, 4568}) {
for (std::size_t compPutbackSize: {0, 1, 2}) {
for (std::size_t decompPutbackSize: {0, 1, 2}) {
roundTripWithIStreams(
format, compressionLevel, memStrategy, compInBufSize,
compOutBufSize, decompInBufSize, decompOutBufSize,
compPutbackSize, decompPutbackSize);
}
}
}
}
}
}
}
{
const std::size_t compInBufSize = 5;
const std::size_t compOutBufSize = 107;
const std::size_t decompInBufSize = 65536;
const std::size_t decompOutBufSize = 84;
int i = 0;
for (std::size_t compPutbackSize: {25, 40, 105}) {
for (std::size_t decompPutbackSize: {30, 60, 81}) {
const simgear::ZLibCompressionFormat compFormat = (i++ % 2) ?
simgear::ZLibCompressionFormat::ZLIB :
simgear::ZLibCompressionFormat::GZIP;
roundTripWithIStreams(
compFormat, Z_BEST_COMPRESSION,
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED,
compInBufSize, compOutBufSize, decompInBufSize, decompOutBufSize,
compPutbackSize, decompPutbackSize,
/* automatic format detection for decompression */ true);
}
}
}
}
// Utility function showing how to return a (unique_ptr to a)
// ZlibCompressorIStream instance, that keeps a reference to its data source
// as long as the ZlibCompressorIStream instance is alive. Thus, calling code
// doesn't have to worry about the lifetime of said data source (here, an
// std::istringstream instance).
std::unique_ptr<simgear::ZlibCompressorIStream>
IStreamConstructorWithSinkSemantics_compressorFactory(const string& str)
{
std::unique_ptr<std::istringstream> iss(new std::istringstream(str));
// The returned compressor object retains a “reference” (of unique_ptr type)
// to the std::istringstream object pointed to by 'iss' as long as it is
// alive. When the returned compressor object (wrapped in a unique_ptr) is
// destroyed, this std::istringstream object will be automatically
// destroyed too.
//
// Note: it's an implementation detail, but this test also indirectly
// exercises the ZlibCompressorIStreambuf constructor taking an
// argument of type std::unique_ptr<std::istream>.
return std::unique_ptr<simgear::ZlibCompressorIStream>(
new simgear::ZlibCompressorIStream(std::move(iss)));
}
void test_IStreamConstructorWithSinkSemantics()
{
cerr << "Testing the unique_ptr-based ZlibCompressorIStream constructor\n";
string someString = randomString(4096, 8192); // arbitrary values
// This shows how to get a new compressor or decompressor object from a
// factory function. Of course, we could create the object directly on the
// stack without using a separate function!
std::unique_ptr<simgear::ZlibCompressorIStream> compressor =
IStreamConstructorWithSinkSemantics_compressorFactory(someString);
compressor->exceptions(std::ios_base::badbit); // throw if badbit is set
// Use the compressor as input to the decompressor (pipeline). The
// decompressor uses read() with chunks that are as large as possible given
// the available space in its input buffer. These read() calls are served by
// ZlibCompressorIStreambuf::xsgetn(), which is efficient. We won't need the
// compressor afterwards, so let's just std::move() its unique_ptr.
simgear::ZlibDecompressorIStream decompressor(std::move(compressor));
decompressor.exceptions(std::ios_base::badbit);
std::ostringstream roundTripResult;
// Of course, you may want to adjust bufSize depending on the application.
static constexpr std::size_t bufSize = 1024;
std::unique_ptr<char[]> buf(new char[bufSize]);
// Relatively efficient way of reading from the decompressor (modulo
// possible adjustments to 'bufSize', of course). The decompressed data is
// first written to 'buf', then copied to 'roundTripResult'. There is no
// other useless copy via, for instance, an intermediate std::string object,
// as would be the case if we used std::string(buf.get(), bufSize).
//
// Of course, ideally 'roundTripResult' would directly pull from
// 'decompressor' without going through 'buf', but I don't think this is
// possible with std::stringstream and friends. Such an optimized data flow
// is however straightforward to implement if you replace 'roundTripResult'
// with a custom data sink that calls decompressor.read().
do {
decompressor.read(buf.get(), bufSize);
if (decompressor.gcount() > 0) { // at least one char could be read
roundTripResult.write(buf.get(), decompressor.gcount());
}
} while (decompressor && roundTripResult);
// 1) If set, badbit would have caused an exception to be raised (see above).
// 2) failbit doesn't necessarily indicate an error here: it is set as soon
// as the read() call can't provide the requested number of characters.
SG_VERIFY(decompressor.eof() && !decompressor.bad());
// Because of std::ostringstream::write(), 'roundTripResult' might have its
// failbit or badbit set, either of which would indicate a real problem.
SG_VERIFY(roundTripResult);
SG_CHECK_EQUAL(roundTripResult.str(), someString);
}
int main(int argc, char** argv)
{
test_pipeCompOrDecompIStreambufIntoOStream();
test_StreambufBasicOperations();
test_ZlibDecompressorIStreambuf_readLargestPossibleAmount();
test_RoundTripMultiWithIStreams();
test_formattedInputFromDecompressor();
test_ZlibDecompressorIStream_readPutbackEtc();
test_IStreamConstructorWithSinkSemantics();
return EXIT_SUCCESS;
}

View File

@@ -94,6 +94,17 @@ enum sgVertexAttributeTypes {
SG_VA_FLOAT_3 = 0x00000800,
};
static gzFile gzFileFromSGPath(const SGPath& path, const char* mode)
{
#if defined(SG_WINDOWS)
std::wstring ws = path.wstr();
return gzopen_w(ws.c_str(), mode);
#else
std::string ps = path.utf8Str();
return gzopen(ps.c_str(), mode);
#endif
}
enum sgPropertyTypes {
SG_MATERIAL = 0,
SG_INDEX_TYPES = 1,
@@ -534,15 +545,16 @@ bool SGBinObject::read_bin( const SGPath& file ) {
fans_vas.clear();
fan_materials.clear();
gzFile fp;
string f = file.local8BitStr();
if ( (fp = gzopen( f.c_str(), "rb" )) == NULL ) {
string filegz = f + ".gz";
if ( (fp = gzopen( filegz.c_str(), "rb" )) == NULL ) {
gzFile fp = gzFileFromSGPath(file, "rb");
if ( fp == NULL ) {
SGPath withGZ = file;
withGZ.concat(".gz");
fp = gzFileFromSGPath(withGZ, "rb");
if (fp == nullptr) {
SG_LOG( SG_EVENT, SG_ALERT,
"ERROR: opening " << file << " or " << filegz << " for reading!");
"ERROR: opening " << file << " or " << withGZ << " for reading!");
throw sg_io_exception("Error opening for reading (and .gz)", sg_location(f));
throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file));
}
}
@@ -957,9 +969,8 @@ bool SGBinObject::write_bin_file(const SGPath& file)
SGPath file2(file);
file2.create_dir( 0755 );
gzFile fp;
std::string localPath = file.local8BitStr();
if ( (fp = gzopen( localPath.c_str(), "wb9" )) == NULL ) {
gzFile fp = gzFileFromSGPath(file, "wb9");
if ( fp == nullptr ) {
cout << "ERROR: opening " << file << " for writing!" << endl;
return false;
}

View File

@@ -20,7 +20,7 @@
//
// $Id$
#include <simgear_config.h>
#include <simgear/compiler.h>
#include <string>
@@ -69,16 +69,27 @@ SGFile::~SGFile() {
bool SGFile::open( const SGProtocolDir d ) {
set_dir( d );
std::string n = file_name.local8BitStr();
#if defined(SG_WINDOWS)
std::wstring n = file_name.wstr();
#else
std::string n = file_name.utf8Str();
#endif
if ( get_dir() == SG_IO_OUT ) {
#ifdef _WIN32
#if defined(SG_WINDOWS)
int mode = _S_IREAD | _S_IWRITE;
fp = ::_wopen(n.c_str(), O_WRONLY | O_CREAT | O_TRUNC | extraoflags, mode);
#else
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
#endif
fp = ::open( n.c_str(), O_WRONLY | O_CREAT | O_TRUNC | extraoflags, mode );
#endif
} else if ( get_dir() == SG_IO_IN ) {
#if defined(SG_WINDOWS)
fp = ::_wopen( n.c_str(), O_RDONLY | extraoflags );
#else
fp = ::open( n.c_str(), O_RDONLY | extraoflags );
#endif
} else {
SG_LOG( SG_IO, SG_ALERT,
"Error: bidirection mode not available for files." );
@@ -145,7 +156,7 @@ int SGFile::readline( char *buf, int length ) {
result = i;
}
lseek( fp, pos + result, SEEK_SET );
// just in case ...
buf[ result ] = '\0';

View File

@@ -23,6 +23,7 @@
$Id: netBuffer.cxx 1568 2002-09-02 06:05:49Z sjbaker $
*/
#include <simgear_config.h>
#include "sg_netBuffer.hxx"
#include <cassert>

View File

@@ -29,6 +29,7 @@
// to write or something...]
// Maybe assert valid handle, too?
#include <simgear_config.h>
#include "sg_netChannel.hxx"
#include <memory>

View File

@@ -20,6 +20,7 @@
//
// $Id$
#include <simgear_config.h>
#include <cstdlib>
#include <cstring>

View File

@@ -4,6 +4,8 @@
#include <map>
#include <sstream>
#include <errno.h>
#include <thread>
#include <atomic>
#include <boost/algorithm/string/case_conv.hpp>
@@ -16,6 +18,7 @@
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/misc/test_macros.hxx>
using std::cout;
using std::cerr;
@@ -23,23 +26,82 @@ using std::endl;
using namespace simgear;
#define COMPARE(a, b) \
if ((a) != (b)) { \
cerr << "failed:" << #a << " != " << #b << endl; \
cerr << "\tgot:'" << a << "'" << endl; \
exit(1); \
class Watchdog
{
public:
Watchdog() : _interval(0), _timer(0), _running(false) {}
~Watchdog() {
stop();
}
void start(unsigned int milliseconds)
{
_interval = milliseconds;
_timer = 0;
_running = true;
_thread = std::thread(&Watchdog::loop, this);
}
void stop()
{
_running = false;
_thread.join();
}
private:
unsigned int _interval;
std::atomic<unsigned int> _timer;
std::atomic<bool> _running;
std::thread _thread;
void loop()
{
while (_running)
{
_timer++;
if (_timer >= _interval)
{
_running = false;
std::cerr << "Failure: timeout." << endl;
exit(EXIT_FAILURE);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
};
int main(int argc, char* argv[])
{
sglog().setLogLevels( SG_ALL, SG_DEBUG );
DNS::Client cl;
#define EXISTING_RECORD "terrasync.flightgear.org"
const char * EXISTING_RECORD = argc > 1 ? argv[1] : "terrasync.flightgear.org";
const char * QSERVICE = argc > 2 ? argv[2] : "https+ws20";
// test existing NAPTR
// fgtest.t3r.de. 600 IN NAPTR 999 99 "U" "test" "!^.*$!http://dnstest.flightgear.org/!" .
Watchdog watchdog;
watchdog.start(100);
simgear::Socket::initSockets();
DNS::Client cl;
cout << "test update without prior pending request" << endl;
{
cout << "polling.";
for( int i = 0; i < 20; i++ ) {
SGTimeStamp::sleepForMSec(200);
cl.update(0);
cout << ".";
cout.flush();
}
cout << "done" << endl;
}
cout << "test existing NAPTR: " << EXISTING_RECORD << endl;
{
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest(EXISTING_RECORD);
DNS::Request_ptr r(naptrRequest);
@@ -50,23 +112,24 @@ int main(int argc, char* argv[])
}
if( r->isTimeout() ) {
cerr << "timeout testing existing record " EXISTING_RECORD << endl;
cerr << "timeout testing existing record " << EXISTING_RECORD << endl;
return EXIT_FAILURE;
}
if(naptrRequest->entries.empty()) {
cerr << "no results for " EXISTING_RECORD << endl;
cerr << "no results for " << EXISTING_RECORD << endl;
return EXIT_FAILURE;
}
// test for ascending preference/order
cout << "test for ascending preference/order" << endl;
int order = -1, preference = -1;
for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin(); it != naptrRequest->entries.end(); ++it ) {
cout << "NAPTR " << (*it)->order << " " << (*it)->preference << " '" << (*it)->service << "' '" << (*it)->regexp << "' '" << (*it)->replacement << "'" << endl;
// currently only support "U" which implies empty replacement
COMPARE((*it)->flags, "U" );
COMPARE(naptrRequest->entries[0]->replacement, "" );
SG_CHECK_EQUAL((*it)->flags, "U" );
SG_CHECK_EQUAL(naptrRequest->entries[0]->replacement, "" );
// currently only support ws20
COMPARE((*it)->service, "ws20" );
// currently only support ws20, disable temporarily
//SG_CHECK_EQUAL((*it)->service, "ws20" );
if( (*it)->order < order ) {
cerr << "NAPTR entries not ascending for field 'order'" << endl;
@@ -94,8 +157,31 @@ int main(int argc, char* argv[])
}
}
cout << "test existing NAPTR with explicit qservice: " << QSERVICE << endl;
{
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest(EXISTING_RECORD);
naptrRequest->qservice = QSERVICE;
DNS::Request_ptr r(naptrRequest);
cl.makeRequest(r);
while( !r->isComplete() && !r->isTimeout()) {
SGTimeStamp::sleepForMSec(200);
cl.update(0);
}
// test non-existing NAPTR
if( r->isTimeout() ) {
cerr << "timeout testing existing record " << EXISTING_RECORD << endl;
return EXIT_FAILURE;
}
if(naptrRequest->entries.empty()) {
cerr << "no results for " << EXISTING_RECORD << endl;
//return EXIT_FAILURE; // not yet a failure - probably add this for 2017.4 and create DNS entries
}
for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin(); it != naptrRequest->entries.end(); ++it ) {
cout << "NAPTR " << (*it)->order << " " << (*it)->preference << " '" << (*it)->service << "' '" << (*it)->regexp << "' '" << (*it)->replacement << "'" << endl;
}
}
cout << "test non-existing NAPTR" << endl;
{
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest("jurkxkqdiufqzpfvzqok.prozhqrlcaavbxifkkhf");
DNS::Request_ptr r(naptrRequest);
@@ -109,7 +195,7 @@ int main(int argc, char* argv[])
cerr << "timeout testing non-existing record." << endl;
return EXIT_FAILURE;
}
COMPARE(naptrRequest->entries.size(), 0 );
SG_CHECK_EQUAL(naptrRequest->entries.size(), 0 );
}
cout << "all tests passed ok" << endl;

View File

@@ -3,7 +3,7 @@
#include <iostream>
#include <map>
#include <sstream>
#include <errno.h>
#include <cerrno>
#include <boost/algorithm/string/case_conv.hpp>
@@ -17,6 +17,7 @@
#include <simgear/misc/strutils.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/test_macros.hxx>
#include <curl/multi.h>
@@ -41,18 +42,6 @@ const char* BODY3 = "Cras ut neque nulla. Duis ut velit neque, sit amet "
const unsigned int body2Size = 8 * 1024;
char body2[body2Size];
#define COMPARE(a, b) \
if ((a) != (b)) { \
cerr << "failed:" << #a << " != " << #b << endl; \
cerr << "\tgot:'" << a << "'" << endl; \
exit(1); \
}
#define VERIFY(a) \
if (!(a)) { \
cerr << "failed:" << #a << endl; \
exit(1); \
}
class TestRequest : public HTTP::Request
{
@@ -126,8 +115,9 @@ public:
d << contentStr;
push(d.str().c_str());
} else if (path == "/test_headers") {
COMPARE(requestHeaders["X-Foo"], string("Bar"));
COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
SG_CHECK_EQUAL(requestHeaders["X-Foo"], string("Bar"));
SG_CHECK_EQUAL(requestHeaders["X-AnotherHeader"],
string("A longer value"));
string contentStr(BODY1);
stringstream d;
@@ -304,7 +294,7 @@ public:
} else if (path == "/test_put") {
std::cerr << "sending PUT response" << std::endl;
COMPARE(buffer, BODY3);
SG_CHECK_EQUAL(buffer, BODY3);
stringstream d;
d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
d << "\r\n"; // final CRLF to terminate the headers
@@ -314,7 +304,7 @@ public:
std::string entityStr = "http://localhost:2000/something.txt";
COMPARE(buffer, BODY3);
SG_CHECK_EQUAL(buffer, BODY3);
stringstream d;
d << "HTTP/1.1 " << 201 << " " << reasonForCode(201) << "\r\n";
d << "Location:" << entityStr << "\r\n";
@@ -385,18 +375,18 @@ int main(int argc, char* argv[])
// test URL parsing
TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
COMPARE(tr1->scheme(), "http");
COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
COMPARE(tr1->host(), "localhost.woo.zar");
COMPARE(tr1->port(), 2000);
COMPARE(tr1->path(), "/test1");
SG_CHECK_EQUAL(tr1->scheme(), "http");
SG_CHECK_EQUAL(tr1->hostAndPort(), "localhost.woo.zar:2000");
SG_CHECK_EQUAL(tr1->host(), "localhost.woo.zar");
SG_CHECK_EQUAL(tr1->port(), 2000);
SG_CHECK_EQUAL(tr1->path(), "/test1");
TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
COMPARE(tr2->scheme(), "http");
COMPARE(tr2->hostAndPort(), "192.168.1.1");
COMPARE(tr2->host(), "192.168.1.1");
COMPARE(tr2->port(), 80);
COMPARE(tr2->path(), "/test1/dir/thing/file.png");
SG_CHECK_EQUAL(tr2->scheme(), "http");
SG_CHECK_EQUAL(tr2->hostAndPort(), "192.168.1.1");
SG_CHECK_EQUAL(tr2->host(), "192.168.1.1");
SG_CHECK_EQUAL(tr2->port(), 80);
SG_CHECK_EQUAL(tr2->path(), "/test1/dir/thing/file.png");
// basic get request
{
@@ -405,11 +395,11 @@ int main(int argc, char* argv[])
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseReason(), string("OK"));
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->responseBytesReceived(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseReason(), string("OK"));
SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY1));
SG_CHECK_EQUAL(tr->bodyData, string(BODY1));
}
{
@@ -418,11 +408,11 @@ int main(int argc, char* argv[])
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseReason(), string("OK"));
COMPARE(tr->responseLength(), strlen(BODY3));
COMPARE(tr->responseBytesReceived(), strlen(BODY3));
COMPARE(tr->bodyData, string(BODY3));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseReason(), string("OK"));
SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY3));
SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY3));
SG_CHECK_EQUAL(tr->bodyData, string(BODY3));
}
{
@@ -430,7 +420,7 @@ int main(int argc, char* argv[])
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseCode(), 200);
}
{
@@ -441,11 +431,11 @@ int main(int argc, char* argv[])
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseReason(), string("OK"));
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->responseBytesReceived(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseReason(), string("OK"));
SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY1));
SG_CHECK_EQUAL(tr->bodyData, string(BODY1));
}
// larger get request
@@ -458,9 +448,9 @@ int main(int argc, char* argv[])
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseBytesReceived(), body2Size);
COMPARE(tr->bodyData, string(body2, body2Size));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseBytesReceived(), body2Size);
SG_CHECK_EQUAL(tr->bodyData, string(body2, body2Size));
}
cerr << "testing chunked transfer encoding" << endl;
@@ -470,12 +460,12 @@ int main(int argc, char* argv[])
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseReason(), string("OK"));
COMPARE(tr->responseBytesReceived(), 30);
COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseReason(), string("OK"));
SG_CHECK_EQUAL(tr->responseBytesReceived(), 30);
SG_CHECK_EQUAL(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
// check trailers made it too
COMPARE(tr->headers["x-foobar"], string("wibble"));
SG_CHECK_EQUAL(tr->headers["x-foobar"], string("wibble"));
}
// test 404
@@ -484,9 +474,9 @@ int main(int argc, char* argv[])
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 404);
COMPARE(tr->responseReason(), string("not found"));
COMPARE(tr->responseLength(), 0);
SG_CHECK_EQUAL(tr->responseCode(), 404);
SG_CHECK_EQUAL(tr->responseReason(), string("not found"));
SG_CHECK_EQUAL(tr->responseLength(), 0);
}
cout << "done 404 test" << endl;
@@ -496,7 +486,7 @@ int main(int argc, char* argv[])
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseCode(), 200);
}
cout << "done1" << endl;
@@ -506,9 +496,9 @@ int main(int argc, char* argv[])
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr->bodyData, string(BODY1));
}
cout << "done2" << endl;
@@ -518,9 +508,9 @@ int main(int argc, char* argv[])
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr->bodyData, string(BODY1));
}
cout << "done3" << endl;
// test connectToHost failure
@@ -534,7 +524,7 @@ int main(int argc, char* argv[])
const int HOST_NOT_FOUND_CODE = CURLE_COULDNT_RESOLVE_HOST;
COMPARE(tr->responseCode(), HOST_NOT_FOUND_CODE);
SG_CHECK_EQUAL(tr->responseCode(), HOST_NOT_FOUND_CODE);
}
cout << "testing abrupt close" << endl;
@@ -546,7 +536,7 @@ int main(int argc, char* argv[])
waitForFailed(&cl, tr);
const int SERVER_NO_DATA_CODE = CURLE_GOT_NOTHING;
COMPARE(tr->responseCode(), SERVER_NO_DATA_CODE);
SG_CHECK_EQUAL(tr->responseCode(), SERVER_NO_DATA_CODE);
}
cout << "testing proxy close" << endl;
@@ -557,9 +547,9 @@ cout << "testing proxy close" << endl;
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseLength(), body2Size);
COMPARE(tr->bodyData, string(body2, body2Size));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseLength(), body2Size);
SG_CHECK_EQUAL(tr->bodyData, string(body2, body2Size));
}
{
@@ -568,9 +558,9 @@ cout << "testing proxy close" << endl;
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseBytesReceived(), body2Size);
COMPARE(tr->bodyData, string(body2, body2Size));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseBytesReceived(), body2Size);
SG_CHECK_EQUAL(tr->bodyData, string(body2, body2Size));
}
// pipelining
@@ -595,17 +585,17 @@ cout << "testing proxy close" << endl;
cl.makeRequest(tr3);
waitForComplete(&cl, tr3);
VERIFY(tr->complete);
VERIFY(tr2->complete);
COMPARE(tr->bodyData, string(BODY1));
SG_VERIFY(tr->complete);
SG_VERIFY(tr2->complete);
SG_CHECK_EQUAL(tr->bodyData, string(BODY1));
COMPARE(tr2->responseLength(), strlen(BODY3));
COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
COMPARE(tr2->bodyData, string(BODY3));
SG_CHECK_EQUAL(tr2->responseLength(), strlen(BODY3));
SG_CHECK_EQUAL(tr2->responseBytesReceived(), strlen(BODY3));
SG_CHECK_EQUAL(tr2->bodyData, string(BODY3));
COMPARE(tr3->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr3->bodyData, string(BODY1));
COMPARE(testServer.connectCount(), 1);
SG_CHECK_EQUAL(testServer.connectCount(), 1);
}
// multiple requests with an HTTP 1.0 server
@@ -626,17 +616,17 @@ cout << "testing proxy close" << endl;
cl.makeRequest(tr3);
waitForComplete(&cl, tr3);
VERIFY(tr->complete);
VERIFY(tr2->complete);
SG_VERIFY(tr->complete);
SG_VERIFY(tr2->complete);
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->responseBytesReceived(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY1));
SG_CHECK_EQUAL(tr->bodyData, string(BODY1));
COMPARE(tr2->responseLength(), strlen(BODY3));
COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
COMPARE(tr2->bodyData, string(BODY3));
COMPARE(tr3->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr2->responseLength(), strlen(BODY3));
SG_CHECK_EQUAL(tr2->responseBytesReceived(), strlen(BODY3));
SG_CHECK_EQUAL(tr2->bodyData, string(BODY3));
SG_CHECK_EQUAL(tr3->bodyData, string(BODY1));
}
// POST
@@ -648,7 +638,7 @@ cout << "testing proxy close" << endl;
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 204);
SG_CHECK_EQUAL(tr->responseCode(), 204);
}
// PUT
@@ -660,7 +650,7 @@ cout << "testing proxy close" << endl;
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 204);
SG_CHECK_EQUAL(tr->responseCode(), 204);
}
{
@@ -671,7 +661,7 @@ cout << "testing proxy close" << endl;
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 201);
SG_CHECK_EQUAL(tr->responseCode(), 201);
}
// test_zero_length_content
@@ -681,9 +671,9 @@ cout << "testing proxy close" << endl;
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->bodyData, string());
COMPARE(tr->responseBytesReceived(), 0);
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->bodyData, string());
SG_CHECK_EQUAL(tr->responseBytesReceived(), 0);
}
// test cancel
@@ -711,12 +701,12 @@ cout << "testing proxy close" << endl;
waitForComplete(&cl, tr3);
COMPARE(tr->responseCode(), -1);
COMPARE(tr2->responseReason(), "my reason 2");
SG_CHECK_EQUAL(tr->responseCode(), -1);
SG_CHECK_EQUAL(tr2->responseReason(), "my reason 2");
COMPARE(tr3->responseLength(), strlen(BODY1));
COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
COMPARE(tr3->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr3->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr3->responseBytesReceived(), strlen(BODY1));
SG_CHECK_EQUAL(tr3->bodyData, string(BODY1));
}
// test cancel
@@ -742,16 +732,16 @@ cout << "testing proxy close" << endl;
waitForComplete(&cl, tr3);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->responseBytesReceived(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY1));
SG_CHECK_EQUAL(tr->bodyData, string(BODY1));
COMPARE(tr2->responseCode(), -1);
SG_CHECK_EQUAL(tr2->responseCode(), -1);
COMPARE(tr3->responseLength(), strlen(BODY1));
COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
COMPARE(tr3->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr3->responseLength(), strlen(BODY1));
SG_CHECK_EQUAL(tr3->responseBytesReceived(), strlen(BODY1));
SG_CHECK_EQUAL(tr3->bodyData, string(BODY1));
}
{
@@ -776,12 +766,12 @@ cout << "testing proxy close" << endl;
cl.makeRequest(tr2);
waitForComplete(&cl, tr2);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->bodyData, string(BODY3));
COMPARE(tr->responseBytesReceived(), strlen(BODY3));
COMPARE(tr2->responseCode(), 200);
COMPARE(tr2->bodyData, string(BODY1));
COMPARE(tr2->responseBytesReceived(), strlen(BODY1));
SG_CHECK_EQUAL(tr->responseCode(), 200);
SG_CHECK_EQUAL(tr->bodyData, string(BODY3));
SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY3));
SG_CHECK_EQUAL(tr2->responseCode(), 200);
SG_CHECK_EQUAL(tr2->bodyData, string(BODY1));
SG_CHECK_EQUAL(tr2->responseBytesReceived(), strlen(BODY1));
}
cout << "all tests passed ok" << endl;

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