Compare commits

..

61 Commits

Author SHA1 Message Date
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
156 changed files with 8611 additions and 979 deletions

View File

@@ -120,12 +120,14 @@ 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)
@@ -269,6 +271,13 @@ 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(rint HAVE_RINT)
check_function_exists(mkdtemp HAVE_MKDTEMP)
@@ -335,8 +344,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")
@@ -363,8 +372,8 @@ endif(CMAKE_COMPILER_IS_GNUCXX)
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++")
@@ -381,6 +390,19 @@ if (CLANG)
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")
# boost goes haywire wrt static asserts
check_cxx_compiler_flag(-Wno-unused-local-typedefs HAS_NOWARN_UNUSED_TYPEDEFS)
@@ -464,7 +486,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

@@ -17,4 +17,9 @@ set(SIMGEAR_SOUND @ENABLE_SOUND@)
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

@@ -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

@@ -35,7 +35,7 @@ void SHVector2_dtor(SHVector2 *v) {
}
void SHVector3_ctor(SHVector3 *v) {
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
v->vec = _mm_setzero_ps();
#else
v->x=0.0f; v->y=0.0f; v->z=0.0f;
@@ -46,7 +46,7 @@ void SHVector3_dtor(SHVector3 *v) {
}
void SHVector4_ctor(SHVector4 *v) {
#ifdef __SSE__
#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;
@@ -57,7 +57,7 @@ void SHVector4_dtor(SHVector4 *v) {
}
void SHRectangle_ctor(SHRectangle *r) {
#ifdef __SSE__
#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;
@@ -148,7 +148,7 @@ int shLineLineXsection(SHVector2 *o1, SHVector2 *v1,
return 1;
}
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
# ifdef __SSE3__
# include <pmmintrin.h>
inline float hsum_ps_sse(__m128 v) {

View File

@@ -21,7 +21,17 @@
#ifndef __SHVECTORS_H
#define __SHVECTORS_H
#ifdef __SSE__
#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
@@ -41,7 +51,7 @@ void SHVector2_dtor(SHVector2 *v);
typedef struct
{
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 vec;
struct { SHfloat x,y,z,w; };
@@ -56,7 +66,7 @@ void SHVector3_dtor(SHVector3 *v);
typedef struct
{
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 vec;
struct { SHfloat x,y,z,w; };
@@ -71,7 +81,7 @@ void SHVector4_dtor(SHVector4 *v);
typedef struct
{
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 vec;
struct { SHfloat x,y,w,h; };
@@ -88,7 +98,7 @@ void shRectangleSet(SHRectangle *r, SHfloat x,
typedef struct
{
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
union ALIGN16 {
__m128 mtx[4];
SHfloat m[4][4];
@@ -117,7 +127,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
*--------------------------------------------------------- */
#define SET2(v,xs,ys) { v.x=xs; v.y=ys; }
#ifdef __SSE__
#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
@@ -126,7 +136,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#endif
#define SET2V(v1,v2) { v1.x=v2.x; v1.y=v2.y; }
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
# define SET3V(v1,v2) { v1.vec=v2.vec; }
# define SET4V(v1,v2) { v1.vec=v2.vec; }
#else
@@ -147,7 +157,7 @@ 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; }
#ifdef __SSE__
#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
@@ -156,7 +166,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#endif
#define ADD2V(v1,v2) { v1.x+=v2.x; v1.y+=v2.y; }
#ifdef __SSE__
#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
@@ -165,7 +175,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#endif
#define SUB2(v,xx,yy) { v.x-=xx; v.y-=yy; }
#ifdef __SSE__
#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
@@ -174,7 +184,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#endif
#define SUB2V(v1,v2) { v1.x-=v2.x; v1.y-=v2.y; }
#ifdef __SSE__
#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
@@ -183,7 +193,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#endif
#define MUL2(v,f) { v.x*=f; v.y*=f; }
#ifdef __SSE__
#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
@@ -192,7 +202,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#endif
#define DIV2(v,f) { v.x/=f; v.y/=f; }
#ifdef __SSE__
#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
@@ -201,7 +211,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#endif
#define ABS2(v) { v.x=SH_ABS(v.x); v.y=SH_ABS(v.y); }
#ifdef __SSE__
#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); }
@@ -223,7 +233,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
#define NORMALIZE4(v) { SHfloat n=NORM4(v); DIV4(v,n); }
#define DOT2(v1,v2) (v1.x*v2.x + v1.y*v2.y)
#ifdef __SSE__
#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
@@ -237,7 +247,7 @@ 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; }
#ifdef __SSE__
#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
@@ -249,7 +259,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
* Macros for matrix operations
*-----------------------------------------------------*/
#ifdef __SSE__
#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); \
@@ -262,7 +272,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
mat.m[2][0] = m20; mat.m[2][1] = m21; mat.m[2][2] = m22; }
#endif
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
# define SETMATMAT(m1, m2) { \
m1.mtx[0] = m2.mtx[0]; \
m1.mtx[1] = m2.mtx[1]; \
@@ -275,7 +285,7 @@ int i,j; \
m1.m[i][j] = m2.m[i][j]; }
#endif
#ifdef __SSE__
#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)); \
@@ -288,7 +298,7 @@ int i,j; \
mat.m[i][j] *= s; }
#endif
#ifdef __SSE__
#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)); \
@@ -301,7 +311,7 @@ int i,j; \
mat.m[i][j] /= s; }
#endif
#ifdef __SSE__
#ifdef SHIVA_USE_SIMD
# define MULMATMAT(m2, m1, mout) { \
int i,j; \
for (i=0;i<4;i++) { \

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>

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"

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>

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,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 "CanvasPath.hxx"
#include <simgear/scene/util/parse_color.hxx>
#include <simgear/misc/strutils.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 "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

@@ -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,7 +179,7 @@ public:
#endif
class LogStreamPrivate : public SGThread
class logstream::LogStreamPrivate : public SGThread
{
private:
/**
@@ -482,52 +484,105 @@ public:
/////////////////////////////////////////////////////////////////////////////
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)
{
global_privateLogstream->m_developerMode = 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, 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
@@ -557,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);
}
@@ -597,54 +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));
}
void logstream::setStartupLoggingEnabled(bool enabled)
{
global_privateLogstream->setStartupLoggingEnabled(enabled);
d->setStartupLoggingEnabled(enabled);
}
namespace simgear
void logstream::requestConsole()
{
void 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();
@@ -657,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
@@ -106,6 +117,12 @@ public:
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, int columns = 16);
/**
* support for the SG_POPUP logging class
* set the content of the popup message
@@ -152,6 +169,10 @@ private:
logstream();
std::vector<std::string> popup_msgs;
class LogStreamPrivate;
std::unique_ptr<LogStreamPrivate> d;
};
logstream& sglog();
@@ -168,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

@@ -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

@@ -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

@@ -21,6 +21,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include "DNSClient.hxx"
#include <udns.h>
#include <time.h>
@@ -190,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);

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"

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

@@ -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>

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>

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

@@ -4,12 +4,14 @@ set(HEADERS
sgstream.hxx
gzfstream.hxx
gzcontainerfile.hxx
zlibstream.hxx
)
set(SOURCES
sgstream.cxx
gzfstream.cxx
gzcontainerfile.cxx
zlibstream.cxx
)
simgear_component(IOStreams io/iostreams "${SOURCES}" "${HEADERS}")
@@ -20,4 +22,8 @@ if(ENABLE_TESTS)
target_link_libraries(test_streams ${TEST_LIBS})
add_test(streams ${EXECUTABLE_OUTPUT_PATH}/test_streams)
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

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

View File

@@ -1,3 +1,5 @@
#include <simgear_config.h>
#include <iostream>
#include <cstdlib> // for EXIT_SUCCESS

View File

@@ -6,27 +6,33 @@
//
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// This 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 program is distributed in the hope that it will be useful,
// 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 General Public License for more details.
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// 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>
@@ -41,6 +47,11 @@
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)
@@ -75,38 +86,53 @@ static string zlibErrorMessage(const z_stream& zstream, int errorCode)
return res;
}
// Return the largest value that can be represented with zlib's uInt type,
// which is the type of z_stream.avail_in and z_stream.avail_out, hence the
// function name.
static std::size_t zlibMaxChunkSize()
// 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)
{
uLong flags = ::zlibCompileFlags();
std::size_t res;
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
switch (flags & 0x3) {
case 0x3:
SG_LOG(SG_IO, SG_WARN,
"Unknown size for zlib's uInt type (code 3). Will assume 64 bits, "
"but the actual value is probably higher.");
// No 'break' here, this is intentional.
case 0x2:
res = 0xFFFFFFFFFFFFFFFF; // 2^64 - 1
break;
case 0x1:
res = 0xFFFFFFFF; // 2^32 - 1
break;
case 0x0:
res = 0xFFFF; // 2^16 - 1
break;
default:
throw std::logic_error("It should be impossible to get here.");
// 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();
}
return res;
}
static const std::size_t zlibMaxChunk = ::zlibMaxChunkSize();
// 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
{
@@ -125,13 +151,13 @@ ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(std::istream& iStream,
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...
@@ -164,6 +190,24 @@ ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(std::istream& iStream,
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) {
@@ -214,8 +258,8 @@ int ZlibAbstractIStreambuf::underflow()
std::size_t
ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
{
std::ptrdiff_t remainingSpaceInOutBuf = _outBuf + _outBufSize -
reinterpret_cast<char*>(nextOutPtr);
std::ptrdiff_t remainingSpaceInOutBuf = (
_outBuf + _outBufSize - reinterpret_cast<char*>(nextOutPtr));
assert(remainingSpaceInOutBuf >= 0);
return static_cast<std::size_t>(remainingSpaceInOutBuf);
@@ -226,7 +270,7 @@ ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
[[ noreturn ]] void ZlibAbstractIStreambuf::handleZ_BUF_ERROR() const
{
switch (operationType()) {
case OPERATION_TYPE_DECOMPRESSION:
case OperationType::DECOMPRESSION:
{
string message = (_path.isNull()) ?
"Got Z_BUF_ERROR from zlib while decompressing a stream. The stream "
@@ -237,15 +281,15 @@ ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
// When _path.isNull(), sg_location(_path) is equivalent to sg_location()
throw sg_io_exception(message, sg_location(_path));
}
case OPERATION_TYPE_COMPRESSION:
case OperationType::COMPRESSION:
throw std::logic_error(
"Called ZlibAbstractIStreambuf::handleZ_BUF_ERROR() with "
"operationType() == OPERATION_TYPE_DECOMPRESSION");
"operationType() == ZlibAbstractIStreambuf::OperationType::COMPRESSION");
default:
throw std::logic_error(
"Unexpected operationType() in "
"ZlibAbstractIStreambuf::handleZ_BUF_ERROR(): " +
std::to_string(operationType()));
std::to_string(enumValue(operationType())));
}
}
@@ -265,9 +309,7 @@ char* ZlibAbstractIStreambuf::fillOutputBuffer()
remainingSpaceInOutBuf = fOB_remainingSpace(_zstream.next_out);
while (remainingSpaceInOutBuf > 0) {
// This does fit in a zlib uInt: that's the whole point of zlibMaxChunk.
_zstream.avail_out = static_cast<uInt>(
std::min(remainingSpaceInOutBuf, zlibMaxChunk));
_zstream.avail_out = clipCast<uInt>(remainingSpaceInOutBuf);
if (_zstream.avail_in == 0 && !allInputRead) {
// Get data from _iStream, store it in _inBuf
@@ -327,9 +369,7 @@ bool ZlibAbstractIStreambuf::getInputData()
// Data already available?
if (alreadyAvailable > 0) {
_zstream.avail_in = static_cast<uInt>(
std::min(static_cast<std::size_t>(alreadyAvailable),
zlibMaxChunk));
_zstream.avail_in = clipCast<uInt>(alreadyAvailable);
return allInputRead;
}
@@ -340,9 +380,8 @@ bool ZlibAbstractIStreambuf::getInputData()
// Fill the input buffer (as much as possible)
while (_inBufEndPtr < _inBuf + _inBufSize && !_iStream.eof()) {
std::streamsize nbCharsToRead = std::min(
_inBuf + _inBufSize - _inBufEndPtr,
std::numeric_limits<std::streamsize>::max()); // max we can pass to read()
std::streamsize nbCharsToRead = clipCast<std::streamsize>(
_inBuf + _inBufSize - _inBufEndPtr);
_iStream.read(_inBufEndPtr, nbCharsToRead);
if (_iStream.bad()) {
@@ -361,10 +400,8 @@ bool ZlibAbstractIStreambuf::getInputData()
std::ptrdiff_t availableChars =
_inBufEndPtr - reinterpret_cast<char*>(_zstream.next_in);
assert(availableChars >= 0);
_zstream.avail_in = static_cast<uInt>(
std::min(static_cast<std::size_t>(availableChars),
zlibMaxChunk));
// assert(availableChars >= 0); <-- already done in clipCast<uInt>()
_zstream.avail_in = clipCast<uInt>(availableChars);
if (_iStream.eof()) {
allInputRead = true;
@@ -384,24 +421,34 @@ bool ZlibAbstractIStreambuf::getInputData()
// (_outBuf) before being copied to its (hopefully) final destination.
std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
{
std::size_t remaining = static_cast<std::size_t>(n);
std::size_t chunkSize;
std::ptrdiff_t avail;
const char* origGptr = gptr();
// 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) {
avail = egptr() - gptr(); // number of available chars in _outBuf
// Number of available chars in _outBuf
std::ptrdiff_t avail = egptr() - gptr();
if (avail == 0) { // our internal buffer is empty
break;
}
chunkSize = std::min(remaining, static_cast<std::size_t>(avail));
std::copy(gptr(), gptr() + chunkSize, writePtr);
gbump(chunkSize);
writePtr += chunkSize;
remaining -= chunkSize;
// 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) {
@@ -418,9 +465,10 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
int retCode;
while (remaining > 0) {
chunkSize = std::min(remaining, zlibMaxChunk);
// It does fit in a zlib uInt: that's the whole point of zlibMaxChunk.
_zstream.avail_out = static_cast<uInt>(chunkSize);
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();
@@ -429,8 +477,9 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
// 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 - _zstream.avail_out is the nb of chars written by zlib
remaining -= chunkSize - _zstream.avail_out;
// 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
@@ -444,12 +493,23 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
}
}
// Finally, prepare the putback area.
//
// We could use reinterpret_cast<char*>(_zstream.next_out) everywhere below,
// except it is hardly readable. This points after the latest data we wrote.
writePtr = reinterpret_cast<char*>(_zstream.next_out);
// 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,
@@ -472,38 +532,33 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
//
// [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;
// chunkSize has an unsigned type; precomputing it before the following test
// wouldn't work.
// Are there chars in _outBuf that need to be copied to the putback area?
if (nbPutbackChars > inDestBuffer) {
chunkSize = nbPutbackChars - inDestBuffer; // yes, this number
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);
setg(_outBuf + _putbackSize - nbPutbackChars, // start of putback area
_outBuf + _putbackSize, // the buffer for pending,
_outBuf + _putbackSize); // available data is empty
std::streamsize rem = static_cast<std::streamsize>(remaining);
// This is guaranteed because n is of type std::streamsize and the whole
// algorithm ensures that 0 <= remaining <= n.
assert(rem >= 0);
assert(static_cast<std::size_t>(rem) == remaining);
assert(n - rem >= 0);
// Total number of chars copied.
return n - rem;
return nbPutbackChars;
}
// ***************************************************************************
@@ -527,6 +582,24 @@ ZlibCompressorIStreambuf::ZlibCompressorIStreambuf(
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
@@ -540,7 +613,7 @@ ZlibCompressorIStreambuf::~ZlibCompressorIStreambuf()
ZlibAbstractIStreambuf::OperationType
ZlibCompressorIStreambuf::operationType() const
{
return OPERATION_TYPE_COMPRESSION;
return OperationType::COMPRESSION;
}
void ZlibCompressorIStreambuf::zStreamInit(int compressionLevel,
@@ -549,30 +622,30 @@ void ZlibCompressorIStreambuf::zStreamInit(int compressionLevel,
{
int windowBits, memLevel;
// Intentionally not listing ZLIB_COMPRESSION_FORMAT_AUTODETECT here (it is
// Intentionally not listing ZLibCompressionFormat::AUTODETECT here (it is
// only for decompression!)
switch (format) {
case ZLIB_COMPRESSION_FORMAT_ZLIB:
case ZLibCompressionFormat::ZLIB:
windowBits = 15;
break;
case ZLIB_COMPRESSION_FORMAT_GZIP:
case ZLibCompressionFormat::GZIP:
windowBits = 31;
break;
default:
throw std::logic_error("Unexpected compression format: " +
std::to_string(format));
std::to_string(enumValue(format)));
}
switch (memStrategy) {
case ZLIB_FAVOR_MEMORY_OVER_SPEED:
case ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED:
memLevel = 8;
break;
case ZLIB_FAVOR_SPEED_OVER_MEMORY:
case ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY:
memLevel = 9;
break;
default:
throw std::logic_error("Unexpected memory strategy: " +
std::to_string(memStrategy));
std::to_string(enumValue(memStrategy)));
}
_zstream.zalloc = Z_NULL; // No custom memory allocation routines
@@ -615,6 +688,21 @@ ZlibDecompressorIStreambuf::ZlibDecompressorIStreambuf(
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
@@ -628,7 +716,7 @@ ZlibDecompressorIStreambuf::~ZlibDecompressorIStreambuf()
ZlibAbstractIStreambuf::OperationType
ZlibDecompressorIStreambuf::operationType() const
{
return OPERATION_TYPE_DECOMPRESSION;
return OperationType::DECOMPRESSION;
}
void ZlibDecompressorIStreambuf::zStreamInit(ZLibCompressionFormat format)
@@ -636,18 +724,18 @@ void ZlibDecompressorIStreambuf::zStreamInit(ZLibCompressionFormat format)
int windowBits;
switch (format) {
case ZLIB_COMPRESSION_FORMAT_ZLIB:
case ZLibCompressionFormat::ZLIB:
windowBits = 15;
break;
case ZLIB_COMPRESSION_FORMAT_GZIP:
case ZLibCompressionFormat::GZIP:
windowBits = 31;
break;
case ZLIB_COMPRESSION_FORMAT_AUTODETECT:
case ZLibCompressionFormat::AUTODETECT:
windowBits = 47; // 47 = 32 + 15
break;
default:
throw std::logic_error("Unexpected compression format: " +
std::to_string(format));
std::to_string(enumValue(format)));
}
_zstream.zalloc = Z_NULL; // No custom memory allocation routines
@@ -684,10 +772,31 @@ ZlibCompressorIStream::ZlibCompressorIStream(std::istream& iStream,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: _streamBuf(iStream, path, compressionLevel, format, memStrategy, inBuf,
: std::istream(nullptr),
_streamBuf(iStream, path, compressionLevel, format, memStrategy, inBuf,
inBufSize, outBuf, outBufSize, putbackSize)
{
rdbuf(&_streamBuf); // Associate _streamBuf to 'this'
// 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()
@@ -705,10 +814,29 @@ ZlibDecompressorIStream::ZlibDecompressorIStream(std::istream& iStream,
char *outBuf,
std::size_t outBufSize,
std::size_t putbackSize)
: _streamBuf(iStream, path, format, inBuf, inBufSize, outBuf, outBufSize,
: std::istream(nullptr),
_streamBuf(iStream, path, format, inBuf, inBufSize, outBuf, outBufSize,
putbackSize)
{
rdbuf(&_streamBuf); // Associate _streamBuf to 'this'
// 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()

View File

@@ -6,25 +6,28 @@
//
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// This 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 program is distributed in the hope that it will be useful,
// 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 General Public License for more details.
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// 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 <iosfwd>
#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>
@@ -97,15 +100,15 @@
namespace simgear
{
enum ZLibCompressionFormat {
ZLIB_COMPRESSION_FORMAT_ZLIB = 0,
ZLIB_COMPRESSION_FORMAT_GZIP,
ZLIB_COMPRESSION_FORMAT_AUTODETECT
enum class ZLibCompressionFormat {
ZLIB = 0,
GZIP,
AUTODETECT
};
enum ZLibMemoryStrategy {
ZLIB_FAVOR_MEMORY_OVER_SPEED = 0,
ZLIB_FAVOR_SPEED_OVER_MEMORY
enum class ZLibMemoryStrategy {
FAVOR_MEMORY_OVER_SPEED = 0,
FAVOR_SPEED_OVER_MEMORY
};
// Abstract base class for both the compressor and decompressor stream buffers.
@@ -144,14 +147,27 @@ public:
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;
~ZlibAbstractIStreambuf();
virtual ~ZlibAbstractIStreambuf();
protected:
enum OperationType {
OPERATION_TYPE_COMPRESSION = 0,
OPERATION_TYPE_DECOMPRESSION
enum class OperationType {
COMPRESSION = 0,
DECOMPRESSION
};
virtual OperationType operationType() const = 0;
@@ -166,6 +182,11 @@ protected:
// 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
@@ -174,12 +195,15 @@ protected:
private:
// Callback whose role is to refill the output buffer when it's empty and
// the “client” tries to read more.
int underflow() override;
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).
std::streamsize xsgetn(char* dest, std::streamsize n) override;
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()
@@ -267,32 +291,46 @@ public:
// the highest compression speed but worst compression
// ratio, and 9 the highest compression ratio but lowest
// compression speed.
// format either ZLIB_COMPRESSION_FORMAT_ZLIB or
// ZLIB_COMPRESSION_FORMAT_GZIP
// memStrategy either ZLIB_FAVOR_MEMORY_OVER_SPEED or
// ZLIB_FAVOR_SPEED_OVER_MEMORY
// 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 = ZLIB_COMPRESSION_FORMAT_ZLIB,
ZLibMemoryStrategy memStrategy = ZLIB_FAVOR_SPEED_OVER_MEMORY,
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;
~ZlibCompressorIStreambuf();
virtual ~ZlibCompressorIStreambuf();
protected:
OperationType operationType() const override;
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.
int zlibProcessData() override;
virtual int zlibProcessData() override;
};
@@ -307,27 +345,39 @@ class ZlibDecompressorIStreambuf: public ZlibAbstractIStreambuf
public:
// Same parameters as for ZlibAbstractIStreambuf, except:
//
// format ZLIB_COMPRESSION_FORMAT_ZLIB,
// ZLIB_COMPRESSION_FORMAT_GZIP or
// ZLIB_COMPRESSION_FORMAT_AUTODETECT
// format ZLibCompressionFormat::ZLIB,
// ZLibCompressionFormat::GZIP or
// ZLibCompressionFormat::AUTODETECT
explicit ZlibDecompressorIStreambuf(
std::istream& iStream,
const SGPath& path = SGPath(),
ZLibCompressionFormat format = ZLIB_COMPRESSION_FORMAT_ZLIB,
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;
~ZlibDecompressorIStreambuf();
virtual ~ZlibDecompressorIStreambuf();
protected:
OperationType operationType() const override;
virtual OperationType operationType() const override;
void zStreamInit(ZLibCompressionFormat format);
int zlibProcessData() override;
virtual int zlibProcessData() override;
};
// std::istream subclass for compressing data. Input data is obtained from an
@@ -349,16 +399,30 @@ public:
std::istream& iStream,
const SGPath& path = SGPath(),
int compressionLevel = Z_DEFAULT_COMPRESSION,
ZLibCompressionFormat format = ZLIB_COMPRESSION_FORMAT_ZLIB,
ZLibMemoryStrategy memStrategy = ZLIB_FAVOR_SPEED_OVER_MEMORY,
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;
~ZlibCompressorIStream();
virtual ~ZlibCompressorIStream();
private:
ZlibCompressorIStreambuf _streamBuf;
@@ -382,15 +446,27 @@ public:
explicit ZlibDecompressorIStream(
std::istream& iStream,
const SGPath& path = SGPath(),
ZLibCompressionFormat format = ZLIB_COMPRESSION_FORMAT_ZLIB,
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;
~ZlibDecompressorIStream();
virtual ~ZlibDecompressorIStream();
private:
ZlibDecompressorIStreambuf _streamBuf;

View File

@@ -4,36 +4,39 @@
//
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// This 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 program is distributed in the hope that it will be useful,
// 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 General Public License for more details.
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// 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()
using std::string;
using std::cout;
using std::cerr;
using traits = std::char_traits<char>;
#include <zlib.h> // Z_BEST_COMPRESSION
#include <simgear/misc/test_macros.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
@@ -41,6 +44,24 @@ using traits = std::char_traits<char>;
#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
@@ -48,8 +69,8 @@ using traits = std::char_traits<char>;
// you don't need the putback feature in non-test code, best performance is
// achieved with putback size = 0.
//
// I suggest you read roundTripWithIStreams() below to see how to use the
// classes most efficiently (especially the comments!).
// I suggest reading test_IStreamConstructorWithSinkSemantics() below to see
// how to use the classes efficiently.
static std::default_random_engine randomNumbersGenerator;
@@ -177,9 +198,9 @@ void test_StreambufBasicOperations()
static constexpr std::size_t compOutBufSize = 4;
static constexpr std::size_t compPutbackSize = 0;
simgear::ZlibCompressorIStreambuf compSBuf(
text_ss, SGPath(), 8, simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
simgear::ZLIB_FAVOR_SPEED_OVER_MEMORY, nullptr, compInBufSize, nullptr,
compOutBufSize, compPutbackSize);
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;
@@ -187,7 +208,7 @@ void test_StreambufBasicOperations()
static constexpr std::size_t decompOutBufSize = 4;
static constexpr std::size_t decompPutbackSize = 2;
simgear::ZlibDecompressorIStreambuf decompSBuf(
compressedOutput_ss, SGPath(), simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
compressedOutput_ss, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
nullptr, decompInBufSize, nullptr, decompOutBufSize, decompPutbackSize);
int ch = decompSBuf.sgetc();
@@ -214,7 +235,9 @@ void test_StreambufBasicOperations()
// Most efficient way (with the underlying xsgetn()) to read several chars
// at once.
std::streamsize n = decompSBuf.sgetn(buf, bufSize);
SG_VERIFY(n == bufSize && string(buf, bufSize) == "3456789abc");
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');
@@ -226,15 +249,17 @@ void test_StreambufBasicOperations()
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
n = decompSBuf.sgetn(buf, bufSize);
SG_VERIFY(n == bufSize && string(buf, bufSize) == "bcdefghijk");
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_VERIFY(n == 36 && string(buf2, n) == "klmnopqrstuvwxyz\nABCDEF\nGHIJK "
"LMNOPQ");
SG_CHECK_EQUAL(n, 36);
SG_CHECK_EQUAL(string(buf2, 36), "klmnopqrstuvwxyz\nABCDEF\nGHIJK LMNOPQ");
ch = decompSBuf.sbumpc();
SG_CHECK_EQUAL(ch, EOF);
@@ -268,6 +293,60 @@ string compress(const string& dataToCompress,
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";
@@ -275,12 +354,12 @@ void test_formattedInputFromDecompressor()
static char inBuf[6];
static char outBuf[15];
string compressed = compress(
lipsum, simgear::ZLIB_COMPRESSION_FORMAT_ZLIB, Z_BEST_COMPRESSION,
simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED, /* putback size */ 0);
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::ZLIB_COMPRESSION_FORMAT_ZLIB,
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
@@ -319,15 +398,15 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
simgear::ZlibCompressorIStream compressor(
text_ss, SGPath(), Z_BEST_COMPRESSION,
simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED,
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::ZLIB_COMPRESSION_FORMAT_ZLIB,
compressor, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
decompInBuf, sizeof(decompInBuf), decompOutBuf, sizeof(decompOutBuf),
/* putback size */ 3);
decompressor.exceptions(std::ios_base::badbit);
@@ -354,6 +433,14 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
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());
}
@@ -404,7 +491,10 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
string rest(buf2, nbCharsRead);
do {
decompressor.read(buf2, sizeof(buf2));
rest += string(buf2, decompressor.gcount());
// 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");
@@ -417,7 +507,6 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
// 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
@@ -442,7 +531,7 @@ void roundTripWithIStreams(
{
const simgear::ZLibCompressionFormat decompFormat =
(useAutoFormatForDecompression) ?
simgear::ZLIB_COMPRESSION_FORMAT_AUTODETECT : compressionFormat;
simgear::ZLibCompressionFormat::AUTODETECT : compressionFormat;
std::istringstream lipsum_ss(lipsum);
// This tests the optional dynamic buffer allocation in ZlibAbstractIStreambuf
@@ -482,11 +571,12 @@ void test_RoundTripMultiWithIStreams()
const std::size_t compPutbackSize = 1;
const std::size_t decompPutbackSize = 1;
for (auto format: {simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
simgear::ZLIB_COMPRESSION_FORMAT_GZIP}) {
for (auto format: {simgear::ZLibCompressionFormat::ZLIB,
simgear::ZLibCompressionFormat::GZIP}) {
for (int compressionLevel: {1, 4, 7, 9}) {
for (auto memStrategy: {simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED,
simgear::ZLIB_FAVOR_SPEED_OVER_MEMORY}) {
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}) {
@@ -505,9 +595,10 @@ void test_RoundTripMultiWithIStreams()
}
{
const auto format = simgear::ZLIB_COMPRESSION_FORMAT_ZLIB;
const auto format = simgear::ZLibCompressionFormat::ZLIB;
const int compressionLevel = Z_DEFAULT_COMPRESSION;
const auto memStrategy = simgear::ZLIB_FAVOR_SPEED_OVER_MEMORY;
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}) {
@@ -537,11 +628,12 @@ void test_RoundTripMultiWithIStreams()
for (std::size_t compPutbackSize: {25, 40, 105}) {
for (std::size_t decompPutbackSize: {30, 60, 81}) {
const simgear::ZLibCompressionFormat compFormat = (i++ % 2) ?
simgear::ZLIB_COMPRESSION_FORMAT_ZLIB :
simgear::ZLIB_COMPRESSION_FORMAT_GZIP;
simgear::ZLibCompressionFormat::ZLIB :
simgear::ZLibCompressionFormat::GZIP;
roundTripWithIStreams(
compFormat, Z_BEST_COMPRESSION, simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED,
compFormat, Z_BEST_COMPRESSION,
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED,
compInBufSize, compOutBufSize, decompInBufSize, decompOutBufSize,
compPutbackSize, decompPutbackSize,
/* automatic format detection for decompression */ true);
@@ -550,13 +642,91 @@ void test_RoundTripMultiWithIStreams()
}
}
// 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

@@ -20,7 +20,7 @@
//
// $Id$
#include <simgear_config.h>
#include <simgear/compiler.h>
#include <string>

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

@@ -78,6 +78,9 @@ int main(int argc, char* argv[])
{
sglog().setLogLevels( SG_ALL, SG_DEBUG );
const char * EXISTING_RECORD = argc > 1 ? argv[1] : "terrasync.flightgear.org";
const char * QSERVICE = argc > 2 ? argv[2] : "https+ws20";
Watchdog watchdog;
watchdog.start(100);
@@ -98,8 +101,7 @@ int main(int argc, char* argv[])
cout << "done" << endl;
}
#define EXISTING_RECORD "terrasync.flightgear.org"
cout << "test existing NAPTR: " EXISTING_RECORD << endl;
cout << "test existing NAPTR: " << EXISTING_RECORD << endl;
{
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest(EXISTING_RECORD);
DNS::Request_ptr r(naptrRequest);
@@ -110,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;
}
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
SG_CHECK_EQUAL((*it)->flags, "U" );
SG_CHECK_EQUAL(naptrRequest->entries[0]->replacement, "" );
// currently only support ws20
SG_CHECK_EQUAL((*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;
@@ -154,6 +157,29 @@ 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);
}
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;
{

View File

@@ -2,6 +2,7 @@
// Test harness.
////////////////////////////////////////////////////////////////////////
#include <simgear_config.h>
#include <simgear/compiler.h>
#include <iostream>

View File

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

View File

@@ -341,18 +341,16 @@ private:
};
public:
simd4_t(void) : simd4(_mm_setzero_ps()) {}
simd4_t(float f) {}
simd4_t(void) { simd4 = _mm_setzero_ps(); }
simd4_t(float f) { simd4 = _mm_set1_ps(f); }
simd4_t(float x, float y) : simd4_t(x,y,0,0) {}
simd4_t(float x, float y, float z) : simd4_t(x,y,z,0) {}
simd4_t(float x, float y, float z, float w) {}
simd4_t(const __vec4f_t v) {}
simd4_t(const simd4_t<float,4>& v) {}
simd4_t(const simd4_t<float,3>& v) {}
simd4_t(const simd4_t<float,2>& v) {}
simd4_t(const __m128& v) {
simd4 = v;
}
simd4_t(float x, float y, float z, float w) { simd4 = _mm_set_ps(w,z,y,x); }
simd4_t(const __vec4f_t v) { simd4 = _mm_loadu_ps(v); }
simd4_t(const simd4_t<float,4>& v) { simd4 = v.v4(); }
simd4_t(const simd4_t<float,3>& v) { simd4 = v.v4(); }
simd4_t(const simd4_t<float,2>& v) { simd4 = v.v4(); }
simd4_t(const __m128& v) { simd4 = v; }
inline const __m128 (&v4(void) const) {
return simd4;
@@ -583,18 +581,18 @@ private:
};
public:
simd4_t(void) : simd4(_mm256_setzero_pd()) {}
simd4_t(double d) {}
simd4_t(void) { simd4 = _mm256_setzero_pd(); }
simd4_t(double d) { simd4 = _mm256_set1_pd(d); }
simd4_t(double x, double y) : simd4_t(x,y,0,0) {}
simd4_t(double x, double y, double z) : simd4_t(x,y,z,0) {}
simd4_t(double x, double y, double z, double w) {}
simd4_t(const __vec4d_t v) {}
simd4_t(const simd4_t<double,4>& v) {}
simd4_t(const simd4_t<double,3>& v) {}
simd4_t(const simd4_t<double,2>& v) {}
simd4_t(const __m256d& v) {
simd4 = v;
simd4_t(double x, double y, double z, double w) {
simd4 = _mm256_set_pd(w,z,y,x);
}
simd4_t(const __vec4d_t v) { simd4 = _mm256_loadu_pd(v); }
simd4_t(const simd4_t<double,4>& v) { simd4 = v.v4(); }
simd4_t(const simd4_t<double,3>& v) { simd4 = v.v4(); }
simd4_t(const simd4_t<double,2>& v) { simd4 = v.v4(); }
simd4_t(const __m256d& v) { simd4 = v; }
inline const __m256d (&v4(void) const) {
return simd4;
@@ -819,14 +817,24 @@ private:
public:
simd4_t(void) { simd4[0] = simd4[1] = _mm_setzero_pd(); }
simd4_t(double d) {}
simd4_t(double d) { simd4[0] = simd4[1] = _mm_set1_pd(d); }
simd4_t(double x, double y) : simd4_t(x,y,0,0) {}
simd4_t(double x, double y, double z) : simd4_t(x,y,z,0) {}
simd4_t(double x, double y, double z, double w) {}
simd4_t(const __vec4d_t v) {}
simd4_t(const simd4_t<double,4>& v) {}
simd4_t(const simd4_t<double,3>& v) {}
simd4_t(const simd4_t<double,2>& v) {}
simd4_t(double x, double y, double z, double w) {
simd4[0] = _mm_set_pd(y,x); simd4[1] = _mm_set_pd(w,z);
}
simd4_t(const __vec4d_t v) {
simd4[0] = _mm_loadu_pd(v); simd4[1] = _mm_loadu_pd(v+2);
}
simd4_t(const simd4_t<double,4>& v) {
simd4[0] = v.v4()[0]; simd4[1] = v.v4()[1];
}
simd4_t(const simd4_t<double,3>& v) {
simd4[0] = v.v4()[0]; simd4[1] = v.v4()[1];
}
simd4_t(const simd4_t<double,2>& v) {
simd4[0] = v.v4()[0]; simd4[1] = _mm_setzero_pd();
}
simd4_t(const __m128d v[2]) {
simd4[0] = v[0];
simd4[1] = v[1];
@@ -1101,18 +1109,16 @@ private:
};
public:
simd4_t(void) : simd4(_mm_setzero_si128()) {}
simd4_t(int i) {}
simd4_t(void) { simd4 = _mm_setzero_si128(); }
simd4_t(int i) { simd4 = _mm_set1_epi32(i); }
simd4_t(int x, int y) : simd4_t(x,y,0,0) {}
simd4_t(int x, int y, int z) : simd4_t(x,y,z,0) {}
simd4_t(int x, int y, int z, int w) {}
simd4_t(const __vec4i_t v) {}
simd4_t(const simd4_t<int,4>& v) {}
simd4_t(const simd4_t<int,3>& v) {}
simd4_t(const simd4_t<int,2>& v) {}
simd4_t(const __m128i& v) {
simd4 = v;
}
simd4_t(int x, int y, int z, int w) { simd4 = _mm_set_epi32(w,z,y,x); }
simd4_t(const __vec4i_t v) { simd4 = _mm_loadu_si128((const __m128i*)v); }
simd4_t(const simd4_t<int,4>& v) { simd4 = v.v4(); }
simd4_t(const simd4_t<int,3>& v) { simd4 = v.v4(); }
simd4_t(const simd4_t<int,2>& v) { simd4 = v.v4(); }
simd4_t(const __m128i& v) { simd4 = v; }
inline __m128i (&v4(void)) {
return simd4;

View File

@@ -7,6 +7,7 @@ set(HEADERS
ResourceManager.hxx
SimpleMarkdown.hxx
SVGpreserveAspectRatio.hxx
argparse.hxx
interpolator.hxx
make_new.hxx
sg_dir.hxx
@@ -17,6 +18,7 @@ set(HEADERS
strutils.hxx
tabbed_values.hxx
texcoord.hxx
test_macros.hxx
)
set(SOURCES
@@ -24,6 +26,7 @@ set(SOURCES
ResourceManager.cxx
SimpleMarkdown.cxx
SVGpreserveAspectRatio.cxx
argparse.cxx
interpolator.cxx
sg_dir.cxx
sg_path.cxx
@@ -45,6 +48,10 @@ simgear_component(misc misc "${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(test_argparse argparse_test.cxx)
target_link_libraries(test_argparse ${TEST_LIBS})
add_test(argparse ${EXECUTABLE_OUTPUT_PATH}/test_argparse)
add_executable(test_CSSBorder CSSBorder_test.cxx)
add_test(CSSBorder ${EXECUTABLE_OUTPUT_PATH}/test_CSSBorder)
target_link_libraries(test_CSSBorder ${TEST_LIBS})

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 "SVGpreserveAspectRatio.hxx"
#include <simgear/debug/logstream.hxx>

391
simgear/misc/argparse.cxx Normal file
View File

@@ -0,0 +1,391 @@
// -*- coding: utf-8 -*-
//
// argparse.cxx --- Simple, generic parser for command-line arguments
// 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 <vector>
#include <memory>
#include <utility> // std::pair, std::move()
#include <cstddef> // std::size_t
#include <cstring> // std::strcmp()
#include <cassert>
#include <simgear/misc/strutils.hxx>
#include <simgear/structure/exception.hxx>
#include "argparse.hxx"
using std::string;
using std::shared_ptr;
namespace simgear
{
namespace argparse
{
// ***************************************************************************
// * Base class for custom exceptions *
// ***************************************************************************
Error::Error(const string& message, const std::string& origin)
: sg_exception("Argument parser error: " + message, origin)
{ }
Error::Error(const char* message, const char* origin)
: Error(string(message), string(origin))
{ }
// ***************************************************************************
// * OptionDesc class *
// ***************************************************************************
OptionDesc::OptionDesc(
const string& optionId, std::vector<char> shortAliases,
std::vector<string> longAliases, OptionArgType argumentType)
: _id(optionId),
_shortAliases(shortAliases),
_longAliases(longAliases),
_argumentType(argumentType)
{ }
const std::string& OptionDesc::id() const
{ return _id; }
const std::vector<char>& OptionDesc::shortAliases() const
{ return _shortAliases; }
const std::vector<std::string>& OptionDesc::longAliases() const
{ return _longAliases; }
OptionArgType OptionDesc::argumentType() const
{ return _argumentType; }
// ***************************************************************************
// * OptionValue class *
// ***************************************************************************
OptionValue::OptionValue(shared_ptr<const OptionDesc> optionDesc,
const string& passedAs, const string& value,
bool hasValue)
: _optionDesc(std::move(optionDesc)),
_passedAs(passedAs),
_value(value),
_hasValue(hasValue)
{ }
shared_ptr<const OptionDesc> OptionValue::optionDesc() const
{ return _optionDesc; } // return a copy of the shared_ptr
void OptionValue::setOptionDesc(shared_ptr<const OptionDesc> descPtr)
{ _optionDesc = std::move(descPtr); }
string OptionValue::passedAs() const
{ return _passedAs; }
void OptionValue::setPassedAs(const string& passedAs)
{ _passedAs = passedAs; }
string OptionValue::value() const
{ return _value; }
void OptionValue::setValue(const string& value)
{ _value = value; }
bool OptionValue::hasValue() const
{ return _hasValue; }
void OptionValue::setHasValue(bool hasValue)
{ _hasValue = hasValue; }
const string OptionValue::id() const
{
const auto desc = optionDesc();
return (desc) ? desc->id() : string();
}
// ***************************************************************************
// * ArgumentParser class *
// ***************************************************************************
// Static utility method.
std::vector<char>
ArgumentParser::removeHyphens(const std::vector<string>& shortAliases,
std::vector<string>& longAliases)
{
std::vector<char> shortAliasesCharVec;
shortAliasesCharVec.reserve(shortAliases.size());
for (const string& opt: shortAliases) {
if (opt.size() != 2 || opt[0] != '-' || opt[1] == '-' || opt[1] > 127) {
throw Error("unexpected form for a short option: '" + opt + "' (expecting "
"a string of size 2 whose first character is a hyphen and "
"second character an ASCII char that is not a hyphen)");
}
shortAliasesCharVec.emplace_back(opt[1]); // emplace the char after hyphen
}
for (string& longOpt: longAliases) {
if (longOpt.size() < 3 ||
!simgear::strutils::starts_with(longOpt, string("--"))) {
throw Error("unexpected form for a long option: '" + longOpt + "' "
"(expecting a string of size 3 or more that starts with "
"two hyphens)");
}
longOpt.erase(0, 2); // remove the two leading hyphens
}
return shortAliasesCharVec;
}
void
ArgumentParser::addOption(const string& optionId,
OptionArgType argType,
std::vector<string> shortAliases,
std::vector<string> longAliases)
{
// Remove the leading dashes and do a sanity check for these arguments
std::vector<char> shortAliasesCharVec = removeHyphens(shortAliases,
longAliases);
const auto desc_p = std::make_shared<const OptionDesc>(
optionId, std::move(shortAliasesCharVec), std::move(longAliases), argType);
for (const char c: desc_p->shortAliases()) {
if (!_shortOptionMap.emplace(c, desc_p).second) {
throw Error(
"trying to add option '-" + string(1, c) + "', however it is already "
"in the short option map");
}
}
for (const string& longOpt: desc_p->longAliases()) {
if (!_longOptionMap.emplace(longOpt, desc_p).second) {
throw Error(
"trying to add option '--" + longOpt + "', however it is already in "
"the long option map");
}
}
}
void
ArgumentParser::addOption(const string& optionId, OptionArgType argumentType,
string shortOpt, string longOpt)
{
std::vector<string> shortOptList;
std::vector<string> longOptList;
if (!shortOpt.empty()) {
shortOptList.push_back(std::move(shortOpt));
}
if (!longOpt.empty()) {
longOptList.push_back(std::move(longOpt));
}
addOption(optionId, argumentType, std::move(shortOptList),
std::move(longOptList));
}
std::pair< std::vector<OptionValue>, std::vector<string> >
ArgumentParser::parseArgs(int argc, const char *const *argv) const
{
std::pair< std::vector<OptionValue>, std::vector<string> > res;
std::vector<OptionValue>& optsWithValues = res.first;
std::vector<string>& nonOptionArgs = res.second;
bool inOptions = true;
for (int i = 1; i < argc; i++) {
// Decode from command line encoding
const string currentArg = cmdEncToUtf8(argv[i]);
if ((inOptions) && (currentArg == "--")) {
// We found the end-of-options delimiter
inOptions = false;
continue;
}
if (inOptions) {
if (currentArg.size() >= 2 && currentArg[0] == '-') {
if (currentArg[1] == '-') {
i += readLongOption(argc, argv, currentArg, i+1, optsWithValues);
} else {
i += readShortOptions(argc, argv, currentArg, i+1, optsWithValues);
}
} else {
// The argument is neither an option, nor a cluster of short options.
inOptions = false;
nonOptionArgs.push_back(currentArg);
}
} else {
nonOptionArgs.push_back(currentArg);
}
}
return res;
}
// Static method
string ArgumentParser::cmdEncToUtf8(const string& s)
{
#if defined(SG_WINDOWS)
// Untested code path. Comments and/or testing by Windows people welcome.
return simgear::strutils::convertWindowsLocal8BitToUtf8(s);
#else
// XXX This assumes UTF-8 encoding for command line arguments on non-Windows
// platforms. Unfortunately, the current (April 2017) standard C++ API for
// encoding conversions has big problems (cf.
// <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0618r0.html>).
// Should be fixed when we have a good way to do such conversions.
return s;
#endif
}
// Return the number of arguments used by the option value, if any (i.e., how
// much the caller should shift to resume arguments processing).
int ArgumentParser::readLongOption(int argc, const char *const *argv,
const string& currentArg, int nextArgIdx,
std::vector<OptionValue>& optsWithValues)
const
{
const string s = currentArg.substr(2); // skip the two initial dashes
// UTF-8 guarantees that ASCII bytes (here, '=') cannot be part of the
// encoding of a non-ASCII character.
std::size_t optEnd = s.find('=');
string opt = s.substr(0, optEnd);
const auto mapElt = _longOptionMap.find(opt);
if (mapElt != _longOptionMap.end()) {
const shared_ptr<const OptionDesc>& optDesc = mapElt->second;
OptionValue optVal(optDesc, string("--") + opt);
switch (optDesc->argumentType()) {
case OptionArgType::NO_ARGUMENT:
optVal.setHasValue(false);
optsWithValues.push_back(std::move(optVal));
return 0;
case OptionArgType::OPTIONAL_ARGUMENT: // pass through
case OptionArgType::MANDATORY_ARGUMENT:
if (optEnd != string::npos) {
// The optional value is present in the same command line
// argument as the option name (syntax '--option=value').
optVal.setHasValue(true);
optVal.setValue(s.substr(optEnd + 1));
optsWithValues.push_back(std::move(optVal));
return 0;
} else if (nextArgIdx < argc &&
(argv[nextArgIdx][0] != '-' ||
!std::strcmp(argv[nextArgIdx], "-"))) {
// The optional value is present as a separate command line argument
// (syntax '--option value').
optVal.setHasValue(true);
optVal.setValue(cmdEncToUtf8(argv[nextArgIdx]));
optsWithValues.push_back(std::move(optVal));
return 1;
} else if (optDesc->argumentType() ==
OptionArgType::OPTIONAL_ARGUMENT) {
// No argument (value) can be found for the option
optVal.setHasValue(false);
optsWithValues.push_back(std::move(optVal));
return 0;
} else {
assert(optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
throw InvalidUserInput("option '" + optVal.passedAs() + "' requires an "
"argument, but none was provided");
}
default:
throw sg_error("This piece of code should be unreachable.");
}
} else {
throw InvalidUserInput("invalid option: '--" + opt + "'");
}
}
int ArgumentParser::readShortOptions(int argc, const char *const *argv,
const string& currentArg, int nextArgIdx,
std::vector<OptionValue>& optsWithValues)
const
{
shared_ptr<const OptionDesc> optDesc;
const string s = currentArg.substr(1); // skip the initial dash
std::size_t i = 0; // index inside s
// Read all options taking no argument in 'currentArg'; stop at the first
// taking an optional or mandatory argument.
for (/* empty */; i < s.size(); i++) {
const auto mapElt = _shortOptionMap.find(s[i]);
if (mapElt != _shortOptionMap.end()) {
optDesc = mapElt->second;
if (optDesc->argumentType() == OptionArgType::NO_ARGUMENT) {
optsWithValues.emplace_back(optDesc, string("-") + s[i], string(),
false /* no value */);
} else {
break;
}
} else {
throw InvalidUserInput(string("invalid option: '-") + s[i] + "'");
}
}
if (i == s.size()) {
// The command line argument in 'currentArg' was fully read and only
// contains options that take no argument.
return 0;
}
// We've already “eaten” all options taking no argument in 'currentArg'.
assert(optDesc->argumentType() == OptionArgType::OPTIONAL_ARGUMENT ||
optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
if (i + 1 < s.size()) {
// The option has a value at the end of 'currentArg': s.substr(i+1)
optsWithValues.emplace_back(optDesc, string("-") + s[i], s.substr(i+1),
true /* hasValue */);
return 0;
} else if (nextArgIdx < argc &&
(argv[nextArgIdx][0] != '-' ||
!std::strcmp(argv[nextArgIdx], "-"))) {
assert(i + 1 == s.size());
// The option is at the end of 'currentArg' and has a value:
// argv[nextArgIdx].
optsWithValues.emplace_back(optDesc, string("-") + s[i],
cmdEncToUtf8(argv[nextArgIdx]),
true /* hasValue */);
return 1;
} else if (optDesc->argumentType() ==
OptionArgType::OPTIONAL_ARGUMENT) {
// No argument (value) can be found for the option
optsWithValues.emplace_back(optDesc, string("-") + s[i], string(),
false /* no value */);
return 0;
} else {
assert(optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
throw InvalidUserInput(string("option '-") + s[i] + "' requires an "
"argument, but none was provided");
}
}
} // of namespace argparse
} // of namespace simgear

281
simgear/misc/argparse.hxx Normal file
View File

@@ -0,0 +1,281 @@
// -*- coding: utf-8 -*-
//
// argparse.hxx --- Simple, generic parser for command-line arguments
// 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 _SIMGEAR_ARGPARSE_HXX_
#define _SIMGEAR_ARGPARSE_HXX_
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <utility> // std::pair
#include <simgear/structure/exception.hxx>
// Usage example:
//
// using simgear::argparse::OptionArgType;
//
// simgear::argparse::ArgumentParser parser;
// parser.addOption("root option", OptionArgType::MANDATORY_ARGUMENT,
// "", "--root");
// parser.addOption("test option", OptionArgType::NO_ARGUMENT, "-t", "--test");
//
// const auto res = parser.parseArgs(argc, argv);
//
// for (const auto& opt: res.first) {
// std::cerr << "Got option '" << opt.id() << "' as '" << opt.passedAs() <<
// "'" << ((opt.hasValue()) ? " with value '" + opt.value() + "'" : "") <<
// "\n";
// }
//
// for (const auto& arg: res.second) {
// std::cerr << "Got non-option argument '" << arg << "'\n";
// }
namespace simgear
{
namespace argparse
{
// Custom exception classes
class Error : public sg_exception
{
public:
explicit Error(const std::string& message,
const std::string& origin = std::string());
explicit Error(const char* message, const char* origin = nullptr);
};
class InvalidUserInput : public Error
{
using Error::Error; // inherit all constructors
};
enum class OptionArgType {
NO_ARGUMENT = 0,
OPTIONAL_ARGUMENT,
MANDATORY_ARGUMENT
};
// All strings inside this class are encoded in UTF-8.
class OptionDesc
{
public:
explicit OptionDesc(const std::string& optionId,
std::vector<char> shortAliases,
std::vector<std::string> longAliases,
OptionArgType argumentType);
// Simple getters for the private members
const std::string& id() const;
const std::vector<char>& shortAliases() const;
const std::vector<std::string>& longAliases() const;
OptionArgType argumentType() const;
private:
// Option identifier, invisible to the end user. Used to easily refer to the
// option despite the various forms it may take (short and/or long aliases).
std::string _id;
// Each element of _shortAliases must be an ASCII character. For instance,
// 'o' for an option called '-o'.
std::vector<char> _shortAliases;
// Each element of _longAliases should be the name of a long option, with
// the two leading dashes removed. For instance, 'generate-foobar' for an
// option named '--generate-foobar'.
std::vector<std::string> _longAliases;
OptionArgType _argumentType;
};
// All strings inside this class are encoded in UTF-8.
class OptionValue
{
public:
explicit OptionValue(std::shared_ptr<const OptionDesc> optionDesc,
const std::string& passedAs,
const std::string& value = std::string(),
bool hasValue = false);
// Simple getters/accessors for the private members
std::shared_ptr<const OptionDesc> optionDesc() const;
std::string passedAs() const;
std::string value() const;
bool hasValue() const;
// The corresponding setters
void setOptionDesc(std::shared_ptr<const OptionDesc>);
void setPassedAs(const std::string&);
void setValue(const std::string&);
void setHasValue(bool);
// For convenience: get the option ID from the result of optionDesc()
const std::string id() const;
private:
// Pointer to the option descriptor.
std::shared_ptr<const OptionDesc> _optionDesc;
// Exact option passed (e.g., -f or --foobar).
std::string _passedAs;
// Value given for the option, if any (otherwise, the empty string).
std::string _value;
// Tells whether the option has been given a value. This is of course mainly
// useful for options taking an *optional* argument. The value in question
// can be the empty string, if given on a separate command line argument
// from the option.
bool _hasValue;
};
// Main class for command line processing. Every string coming out of it is
// encoded in UTF-8.
class ArgumentParser
{
public:
// Register an option, with zero or more short aliases (e.g., -a, -u, - F)
// and zero or more long aliases (e.g., --foobar, --barnum, --bleh). The
// option may take no argument, or one optional argument, or one mandatory
// argument. The 'optionId' is used to refer to the option in a clear and
// simple way, even in the presence of several short or long aliases. It is
// thus visible to the programmer using this API, but not to users of the
// command line interface being implemented.
//
// Note: this method and all its overloads take options in the form "-o" or
// "--foobar" (as std::string instances). While it would be possible
// to only require a char for each short option and to take long
// option declarations without the two leading dashes, the API chosen
// here should lead to more readable and searchable user code.
//
// shortAliases: each element should consist of two characters: an ASCII
// hyphen (-) followed by an ASCII character.
// longAliases: each element should be a string in UTF-8 encoding, starting
// with two ASCII/UTF-8 hyphens (U+002D).
//
// This API could be extended to automatically generate --help output from
// strings passed to addOption().
void addOption(const std::string& optionId,
OptionArgType argumentType,
std::vector<std::string> shortAliases,
std::vector<std::string> longAliases);
// Convenience overload that should be enough for most cases. To register
// only a short option or only a long option, simply pass the empty string
// for the corresponding parameter.
void addOption(const std::string& optionId,
OptionArgType argumentType,
std::string shortOpt = std::string(),
std::string longOpt = std::string());
// Parse arguments from an argc/argv pair of variables. 'argc' should be the
// number of elements in 'argv', the first of which is ignored for the sake
// of options and arguments extraction (since it normally holds the program
// name).
//
// Note: this “number of elements” doesn't count the usual---and completely
// unneeded here---final null pointer.
//
// Short options may be grouped in the usual way. For instance, if '-x',
// '-z' and '-f' are three short options, the first two taking no argument
// and '-f' taking one mandatory argument, then both '-xzf bar' and
// '-xzfbar' are equivalent to '-x -z -f bar' as well as to '-x -z -fbar'
// ('bar' being the value taken by option '-f').
//
// Long options are handled in the usual way too:
//
// '--foobar' for an option taking no argument
//
// '--foobar=value' for an option taking an optional or mandatory
// or '--foobar value' argument (two separate command line arguments in the
// second case)
//
// Long option names may contain spaces, though this is extremely uncommon
// and inconvenient for users. Any option argument (be it for a long or a
// short option) may contain spaces, as expected.
//
// As usual too, the special '--' argument consisting of two ASCII/UTF-8
// hyphens, can be used to cause all subsequent arguments to be treated as
// non-option arguments, regardless of whether they start with a hyphen or
// not. In the absence of this special argument, the first argument that is
// not the value of an option and is either a single hyphen, or doesn't
// start with a hyphen, marks the end of options. This and all subsequent
// arguments are read as non-option arguments.
//
// Return a pair containing:
// - the list of supplied options (with their respective values, when
// applicable);
// - the list of non-option arguments that were given after the options.
//
// Both of these lists (vectors) may be empty and preserve the order used in
// 'argv'.
std::pair< std::vector<OptionValue>, std::vector<std::string> >
parseArgs(int argc, const char *const *argv) const;
private:
// Convert from the encoding used for argv (command line arguments) to
// UTF-8.
//
// This method is currently not very satisfactory (cf. comments in the
// implementation). The Windows code path is untested; the non-Windows code
// path assumes command line arguments are encoded in UTF-8 (in other words,
// it's a no-op).
static std::string cmdEncToUtf8(const std::string& stringInCmdLineEncoding);
// Remove leading dashes and do sanity checks. 'longAliases' is modified
// in-place. 'shortAliases' is not, because we build an std::vector<char>
// from an std::vector<std::string>.
static std::vector<char> removeHyphens(
const std::vector<std::string>& shortAliases,
std::vector<std::string>& longAliases);
// Read a long option and its value, if any (in total: one or two command
// line arguments).
//
// Return the number of arguments consumed by this process after
// 'currentArg' (i.e., 0 or 1 depending on whether the last option in
// 'currentArg' has been given a value).
//
// 'currentArg' comes from argv[nextArgIdx-1], after decoding by
// cmdEncToUtf8(). Thus, argv[nextArgIdx] is the command-line argument
// coming after 'currentArg'.
int readLongOption(
int argc, const char *const *argv, const std::string& currentArg,
int nextArgIdx, std::vector<OptionValue>& optsWithValues) const;
// Read all short options in a command line argument, plus the option value
// of the last one of these, if any (even if the option value is in the next
// command line argument).
//
// See readLongOption() for the return value and meaning of parameters.
int readShortOptions(
int argc, const char *const *argv, const std::string& currentArg,
int nextArgIdx, std::vector<OptionValue>& optsWithValues) const;
// Keys are short option names without the leading dash
std::unordered_map< char,
std::shared_ptr<const OptionDesc> > _shortOptionMap;
// Keys are long option names without the two leading dashes
std::unordered_map< std::string,
std::shared_ptr<const OptionDesc> > _longOptionMap;
};
} // of namespace argparse
} // of namespace simgear
#endif // _SIMGEAR_ARGPARSE_HXX_

View File

@@ -0,0 +1,621 @@
// -*- coding: utf-8 -*-
//
// argparse_test.cxx --- Automated tests for argparse.cxx / argparse.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 <iostream> // std::cout
#include <vector>
#include <cstdlib> // EXIT_SUCCESS
#include <simgear/misc/test_macros.hxx>
#include "argparse.hxx"
using std::string;
using std::vector;
using std::cout;
using std::cerr;
using std::endl;
void test_mixOfShortAndLongOptions()
{
cout << "Testing a mix of short and long options, plus non-option arguments"
<< endl;
using namespace simgear::argparse;
ArgumentParser parser;
parser.addOption("test option", OptionArgType::NO_ARGUMENT, "-t");
parser.addOption("long opt w/o arg", OptionArgType::NO_ARGUMENT,
"", "--long-option-without-arg");
parser.addOption("other test option", OptionArgType::NO_ARGUMENT, "-O");
parser.addOption("yet another test option",
OptionArgType::OPTIONAL_ARGUMENT, "-y", "--yes-we-can");
parser.addOption("and again", OptionArgType::MANDATORY_ARGUMENT, "-a",
"--all-you-need-is-love");
parser.addOption("long option with opt arg",
OptionArgType::OPTIONAL_ARGUMENT, "", // no short alias
"--long-option-with-opt-arg");
// Using an std::vector to avoid the need to count the elements ourselves
const vector<const char*> v({
"FoobarProg", "-Oy", "-aarg for -a", "-OtOty", "arg for -y",
"-tyOther arg for -y", "--long-option-without-arg", "-a", "Arg for -a",
"--long-option-with-opt-arg", "--long-option-with-opt-arg", "value 1",
"--long-option-with-opt-arg=value 2", "-t",
"--all-you-need-is-love", "oh this is true", "-ypouet",
"--all-you-need-is-love=right, I'll shut up ;-)", "non option",
" other non option ", "--", "<-- came too late, treated as an arg"});
// v.size() corresponds to argc, &v[0] corresponds to argv.
const auto res = parser.parseArgs(v.size(), &v[0]);
const auto& opts = res.first;
const auto& otherArgs = res.second;
SG_CHECK_EQUAL(opts.size(), 19); // number of passed options
SG_CHECK_EQUAL(otherArgs.size(), 4); // number of non-option arguments
// Check all passed options and their values
SG_CHECK_EQUAL(opts[0].passedAs(), "-O");
SG_CHECK_EQUAL(opts[0].value(), "");
SG_CHECK_EQUAL(opts[0].hasValue(), false);
SG_CHECK_EQUAL(opts[0].id(), "other test option");
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->argumentType(),
OptionArgType::NO_ARGUMENT);
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->shortAliases(),
vector<char>(1, 'O'));
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->longAliases(), vector<string>());
SG_CHECK_EQUAL(opts[1].passedAs(), "-y");
SG_CHECK_EQUAL(opts[1].value(), "");
SG_CHECK_EQUAL(opts[1].hasValue(), false);
SG_CHECK_EQUAL(opts[1].id(), "yet another test option");
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->argumentType(),
OptionArgType::OPTIONAL_ARGUMENT);
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->shortAliases(),
vector<char>(1, 'y'));
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->longAliases(),
vector<string>(1, "yes-we-can"));
SG_CHECK_EQUAL(opts[2].passedAs(), "-a");
SG_CHECK_EQUAL(opts[2].value(), "arg for -a");
SG_CHECK_EQUAL(opts[2].hasValue(), true);
SG_CHECK_EQUAL(opts[2].id(), "and again");
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->argumentType(),
OptionArgType::MANDATORY_ARGUMENT);
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->shortAliases(),
vector<char>(1, 'a'));
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->longAliases(),
vector<string>(1, "all-you-need-is-love"));
SG_CHECK_EQUAL(opts[3].passedAs(), "-O");
SG_CHECK_EQUAL(opts[3].value(), "");
SG_CHECK_EQUAL(opts[3].hasValue(), false);
SG_CHECK_EQUAL(opts[3].id(), "other test option");
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->argumentType(),
OptionArgType::NO_ARGUMENT);
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->shortAliases(),
vector<char>(1, 'O'));
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->longAliases(), vector<string>());
SG_CHECK_EQUAL(opts[4].passedAs(), "-t");
SG_CHECK_EQUAL(opts[4].value(), "");
SG_CHECK_EQUAL(opts[4].hasValue(), false);
SG_CHECK_EQUAL(opts[4].id(), "test option");
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->argumentType(),
OptionArgType::NO_ARGUMENT);
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->shortAliases(),
vector<char>(1, 't'));
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->longAliases(), vector<string>());
SG_CHECK_EQUAL(opts[5].passedAs(), "-O");
SG_CHECK_EQUAL(opts[5].value(), "");
SG_CHECK_EQUAL(opts[5].hasValue(), false);
SG_CHECK_EQUAL(opts[5].id(), "other test option");
SG_CHECK_EQUAL(opts[6].passedAs(), "-t");
SG_CHECK_EQUAL(opts[6].value(), "");
SG_CHECK_EQUAL(opts[6].hasValue(), false);
SG_CHECK_EQUAL(opts[6].id(), "test option");
SG_CHECK_EQUAL(opts[7].passedAs(), "-y");
SG_CHECK_EQUAL(opts[7].value(), "arg for -y");
SG_CHECK_EQUAL(opts[7].hasValue(), true);
SG_CHECK_EQUAL(opts[7].id(), "yet another test option");
SG_CHECK_EQUAL(opts[8].passedAs(), "-t");
SG_CHECK_EQUAL(opts[8].value(), "");
SG_CHECK_EQUAL(opts[8].hasValue(), false);
SG_CHECK_EQUAL(opts[8].id(), "test option");
SG_CHECK_EQUAL(opts[9].passedAs(), "-y");
SG_CHECK_EQUAL(opts[9].value(), "Other arg for -y");
SG_CHECK_EQUAL(opts[9].hasValue(), true);
SG_CHECK_EQUAL(opts[9].id(), "yet another test option");
SG_CHECK_EQUAL(opts[10].passedAs(), "--long-option-without-arg");
SG_CHECK_EQUAL(opts[10].value(), "");
SG_CHECK_EQUAL(opts[10].hasValue(), false);
SG_CHECK_EQUAL(opts[10].id(), "long opt w/o arg");
SG_CHECK_EQUAL(opts[11].passedAs(), "-a");
SG_CHECK_EQUAL(opts[11].value(), "Arg for -a");
SG_CHECK_EQUAL(opts[11].hasValue(), true);
SG_CHECK_EQUAL(opts[11].id(), "and again");
SG_CHECK_EQUAL(opts[12].passedAs(), "--long-option-with-opt-arg");
SG_CHECK_EQUAL(opts[12].value(), "");
SG_CHECK_EQUAL(opts[12].hasValue(), false);
SG_CHECK_EQUAL(opts[12].id(), "long option with opt arg");
SG_CHECK_EQUAL(opts[13].passedAs(), "--long-option-with-opt-arg");
SG_CHECK_EQUAL(opts[13].value(), "value 1");
SG_CHECK_EQUAL(opts[13].hasValue(), true);
SG_CHECK_EQUAL(opts[13].id(), "long option with opt arg");
SG_CHECK_EQUAL(opts[14].passedAs(), "--long-option-with-opt-arg");
SG_CHECK_EQUAL(opts[14].value(), "value 2");
SG_CHECK_EQUAL(opts[14].hasValue(), true);
SG_CHECK_EQUAL(opts[14].id(), "long option with opt arg");
SG_CHECK_EQUAL(opts[15].passedAs(), "-t");
SG_CHECK_EQUAL(opts[15].value(), "");
SG_CHECK_EQUAL(opts[15].hasValue(), false);
SG_CHECK_EQUAL(opts[15].id(), "test option");
SG_CHECK_EQUAL(opts[16].passedAs(), "--all-you-need-is-love");
SG_CHECK_EQUAL(opts[16].value(), "oh this is true");
SG_CHECK_EQUAL(opts[16].hasValue(), true);
SG_CHECK_EQUAL(opts[16].id(), "and again");
SG_CHECK_EQUAL(opts[17].passedAs(), "-y");
SG_CHECK_EQUAL(opts[17].value(), "pouet");
SG_CHECK_EQUAL(opts[17].hasValue(), true);
SG_CHECK_EQUAL(opts[17].id(), "yet another test option");
SG_CHECK_EQUAL(opts[18].passedAs(), "--all-you-need-is-love");
SG_CHECK_EQUAL(opts[18].value(), "right, I'll shut up ;-)");
SG_CHECK_EQUAL(opts[18].hasValue(), true);
SG_CHECK_EQUAL(opts[18].id(), "and again");
// Check all non-option arguments that were passed to parser.parseArgs()
SG_CHECK_EQUAL_NOSTREAM(
otherArgs,
vector<string>({"non option", " other non option ", "--",
"<-- came too late, treated as an arg"}));
}
void test_whenOptionValueIsASingleHyphen()
{
cout << "Testing cases where a single hyphen is used as an option value" <<
endl;
using namespace simgear::argparse;
ArgumentParser parser;
parser.addOption("option -T", OptionArgType::NO_ARGUMENT, "-T", "--test");
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT,
"-o", "--with-opt-arg");
parser.addOption("option -m", OptionArgType::MANDATORY_ARGUMENT, "-m",
"--with-mandatory-arg");
const vector<const char*> v({
"FoobarProg", "-To", "-", "-o-", "-oT", "-o", "-T", "-o", "-",
"--with-opt-arg=-", "--with-opt-arg", "-", "--with-opt-arg",
"-m-", "--with-mandatory-arg=-", "--with-mandatory-arg", "-", "-m", "-",
"non option 1", "non option 2", "non option 3"});
const auto res = parser.parseArgs(v.size(), &v[0]);
const auto& opts = res.first;
const auto& otherArgs = res.second;
SG_CHECK_EQUAL(opts.size(), 14); // number of passed options
SG_CHECK_EQUAL(otherArgs.size(), 3); // number of non-option arguments
SG_CHECK_EQUAL(opts[0].passedAs(), "-T");
SG_CHECK_EQUAL(opts[0].value(), "");
SG_CHECK_EQUAL(opts[0].hasValue(), false);
SG_CHECK_EQUAL(opts[0].id(), "option -T");
SG_CHECK_EQUAL(opts[1].passedAs(), "-o");
SG_CHECK_EQUAL(opts[1].value(), "-");
SG_CHECK_EQUAL(opts[1].hasValue(), true);
SG_CHECK_EQUAL(opts[1].id(), "option -o");
SG_CHECK_EQUAL(opts[2].passedAs(), "-o");
SG_CHECK_EQUAL(opts[2].value(), "-");
SG_CHECK_EQUAL(opts[2].hasValue(), true);
SG_CHECK_EQUAL(opts[2].id(), "option -o");
SG_CHECK_EQUAL(opts[3].passedAs(), "-o");
SG_CHECK_EQUAL(opts[3].value(), "T");
SG_CHECK_EQUAL(opts[3].hasValue(), true);
SG_CHECK_EQUAL(opts[3].id(), "option -o");
SG_CHECK_EQUAL(opts[4].passedAs(), "-o");
SG_CHECK_EQUAL(opts[4].value(), "");
SG_CHECK_EQUAL(opts[4].hasValue(), false);
SG_CHECK_EQUAL(opts[4].id(), "option -o");
SG_CHECK_EQUAL(opts[5].passedAs(), "-T");
SG_CHECK_EQUAL(opts[5].value(), "");
SG_CHECK_EQUAL(opts[5].hasValue(), false);
SG_CHECK_EQUAL(opts[5].id(), "option -T");
SG_CHECK_EQUAL(opts[6].passedAs(), "-o");
SG_CHECK_EQUAL(opts[6].value(), "-");
SG_CHECK_EQUAL(opts[6].hasValue(), true);
SG_CHECK_EQUAL(opts[6].id(), "option -o");
SG_CHECK_EQUAL(opts[7].passedAs(), "--with-opt-arg");
SG_CHECK_EQUAL(opts[7].value(), "-");
SG_CHECK_EQUAL(opts[7].hasValue(), true);
SG_CHECK_EQUAL(opts[7].id(), "option -o");
SG_CHECK_EQUAL(opts[8].passedAs(), "--with-opt-arg");
SG_CHECK_EQUAL(opts[8].value(), "-");
SG_CHECK_EQUAL(opts[8].hasValue(), true);
SG_CHECK_EQUAL(opts[8].id(), "option -o");
SG_CHECK_EQUAL(opts[9].passedAs(), "--with-opt-arg");
SG_CHECK_EQUAL(opts[9].value(), "");
SG_CHECK_EQUAL(opts[9].hasValue(), false);
SG_CHECK_EQUAL(opts[9].id(), "option -o");
SG_CHECK_EQUAL(opts[10].passedAs(), "-m");
SG_CHECK_EQUAL(opts[10].value(), "-");
SG_CHECK_EQUAL(opts[10].hasValue(), true);
SG_CHECK_EQUAL(opts[10].id(), "option -m");
SG_CHECK_EQUAL(opts[11].passedAs(), "--with-mandatory-arg");
SG_CHECK_EQUAL(opts[11].value(), "-");
SG_CHECK_EQUAL(opts[11].hasValue(), true);
SG_CHECK_EQUAL(opts[11].id(), "option -m");
SG_CHECK_EQUAL(opts[12].passedAs(), "--with-mandatory-arg");
SG_CHECK_EQUAL(opts[12].value(), "-");
SG_CHECK_EQUAL(opts[12].hasValue(), true);
SG_CHECK_EQUAL(opts[12].id(), "option -m");
SG_CHECK_EQUAL(opts[13].passedAs(), "-m");
SG_CHECK_EQUAL(opts[13].value(), "-");
SG_CHECK_EQUAL(opts[13].hasValue(), true);
SG_CHECK_EQUAL(opts[13].id(), "option -m");
SG_CHECK_EQUAL_NOSTREAM(
otherArgs,
vector<string>({"non option 1", "non option 2", "non option 3"}));
}
void test_frontierBetweenOptionsAndNonOptions()
{
cout << "Testing around the frontier between options and non-options" << endl;
using namespace simgear::argparse;
ArgumentParser parser;
parser.addOption("option -T", OptionArgType::NO_ARGUMENT, "-T");
parser.addOption("long opt w/o arg", OptionArgType::NO_ARGUMENT,
"", "--long-option-without-arg");
parser.addOption("option -a", OptionArgType::MANDATORY_ARGUMENT, "-a",
"--this-is-option-a");
// Test 1: both options and non-options; '--' used as a normal non-option
// argument (i.e., after other non-option arguments).
const vector<const char*> v1({
"FoobarProg", "--long-option-without-arg", "-aval", "non option 1",
"non option 2", "--", "non option 3"});
// v1.size() corresponds to argc, &v1[0] corresponds to argv.
const auto res1 = parser.parseArgs(v1.size(), &v1[0]);
const auto& opts1 = res1.first;
const auto& otherArgs1 = res1.second;
SG_CHECK_EQUAL(opts1.size(), 2); // number of passed options
SG_CHECK_EQUAL(otherArgs1.size(), 4); // number of non-option arguments
SG_CHECK_EQUAL_NOSTREAM(
otherArgs1,
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
// Test 2: some options but no non-options arguments
const vector<const char*> v2({
"FoobarProg", "--long-option-without-arg", "-aval"});
const auto res2 = parser.parseArgs(v2.size(), &v2[0]);
const auto& opts2 = res2.first;
const auto& otherArgs2 = res2.second;
SG_CHECK_EQUAL(opts2.size(), 2);
SG_VERIFY(otherArgs2.empty());
SG_CHECK_EQUAL_NOSTREAM(otherArgs2, vector<string>());
// Test 3: same as test 2, but with useless end-of-options delimiter
const vector<const char*> v3({
"FoobarProg", "--long-option-without-arg", "-aval", "--"});
const auto res3 = parser.parseArgs(v3.size(), &v3[0]);
const auto& opts3 = res3.first;
const auto& otherArgs3 = res3.second;
SG_CHECK_EQUAL(opts3.size(), 2);
SG_VERIFY(otherArgs3.empty());
SG_CHECK_EQUAL_NOSTREAM(otherArgs3, vector<string>());
// Test 4: only non-option arguments
const vector<const char*> v4({
"FoobarProg", "non option 1",
"non option 2", "--", "non option 3"});
const auto res4 = parser.parseArgs(v4.size(), &v4[0]);
const auto& opts4 = res4.first;
const auto& otherArgs4 = res4.second;
SG_VERIFY(opts4.empty());
SG_CHECK_EQUAL(otherArgs4.size(), 4);
SG_CHECK_EQUAL_NOSTREAM(
otherArgs4,
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
// Test 5: only non-options arguments, but starting with --
const vector<const char*> v5({
"FoobarProg", "--", "non option 1",
"non option 2", "--", "non option 3"});
const auto res5 = parser.parseArgs(v5.size(), &v5[0]);
const auto& opts5 = res5.first;
const auto& otherArgs5 = res5.second;
SG_VERIFY(opts5.empty());
SG_CHECK_EQUAL(otherArgs5.size(), 4);
SG_CHECK_EQUAL_NOSTREAM(
otherArgs5,
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
// Test 6: use the '--' delimiter before what would otherwise be considered
// an option
const vector<const char*> v6({
"FoobarProg", "--long-option-without-arg", "-aval", "--", "-T",
"non option 1", "non option 2", "--", "non option 3"});
const auto res6 = parser.parseArgs(v6.size(), &v6[0]);
const auto& opts6 = res6.first;
const auto& otherArgs6 = res6.second;
SG_CHECK_EQUAL(opts6.size(), 2);
SG_CHECK_EQUAL(otherArgs6.size(), 5);
SG_CHECK_EQUAL_NOSTREAM(
otherArgs6,
vector<string>({"-T", "non option 1", "non option 2", "--",
"non option 3"}));
// Test 7: use the '--' delimiter before an argument that doesn't look like
// an option
const vector<const char*> v7({
"FoobarProg", "--long-option-without-arg", "-aval", "--",
"doesn't look like an option", "non option 1", "non option 2", "--",
"non option 3"});
const auto res7 = parser.parseArgs(v7.size(), &v7[0]);
const auto& opts7 = res7.first;
const auto& otherArgs7 = res7.second;
SG_CHECK_EQUAL(opts7.size(), 2);
SG_CHECK_EQUAL(otherArgs7.size(), 5);
SG_CHECK_EQUAL_NOSTREAM(
otherArgs7,
vector<string>({"doesn't look like an option", "non option 1",
"non option 2", "--", "non option 3"}));
// Test 8: the argument marking the end of options is the empty string
const vector<const char*> v8({
"FoobarProg", "--long-option-without-arg", "-aval",
"", "non option 1", "non option 2", "-", "non option 3"});
const auto res8 = parser.parseArgs(v8.size(), &v8[0]);
const auto& opts8 = res8.first;
const auto& otherArgs8 = res8.second;
SG_CHECK_EQUAL(opts8.size(), 2);
SG_CHECK_EQUAL(otherArgs8.size(), 5);
SG_CHECK_EQUAL_NOSTREAM(
otherArgs8,
vector<string>({"", "non option 1", "non option 2", "-", "non option 3"}));
// Test 9: the argument marking the end of options is a single hyphen
const vector<const char*> v9({
"FoobarProg", "--long-option-without-arg", "-aval",
"-", "non option 1", "non option 2", "-", "non option 3"});
const auto res9 = parser.parseArgs(v9.size(), &v9[0]);
const auto& opts9 = res9.first;
const auto& otherArgs9 = res9.second;
SG_CHECK_EQUAL(opts9.size(), 2);
SG_CHECK_EQUAL(otherArgs9.size(), 5);
SG_CHECK_EQUAL_NOSTREAM(
otherArgs9,
vector<string>({"-", "non option 1", "non option 2", "-", "non option 3"}));
// Test 10: no other argument than the program name in argv
const vector<const char*> v10({"FoobarProg"});
const auto res10 = parser.parseArgs(v10.size(), &v10[0]);
const auto& opts10 = res10.first;
const auto& otherArgs10 = res10.second;
SG_VERIFY(opts10.empty());
SG_VERIFY(otherArgs10.empty());
}
void test_optionsWithMultipleAliases()
{
cout << "Testing options with multiple aliases" << endl;
using namespace simgear::argparse;
ArgumentParser parser;
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT,
vector<string>({"-o", "-O", "-0"}),
vector<string>({"--o-alias-1", "--o-alias-2"}));
parser.addOption("option -a", OptionArgType::MANDATORY_ARGUMENT,
vector<string>({"-a", "-r"}),
vector<string>({"--a-alias-1", "--a-alias-2",
"--a-alias-3"}));
parser.addOption("option -N", OptionArgType::NO_ARGUMENT,
vector<string>({"-N", "-p"}),
vector<string>({"--N-alias-1", "--N-alias-2"}));
const vector<const char*> v({
"FoobarProg", "--o-alias-1", "-aarg for -a", "-pO", "arg for -O",
"--a-alias-2=value 1", "--o-alias-2", "value 2", "-Novalue 3",
"--N-alias-2", "--a-alias-3=value 4", "-0value 5", "--N-alias-1",
"non option 1", "non option 2", "non option 3"});
// v.size() corresponds to argc, &v[0] corresponds to argv.
const auto res = parser.parseArgs(v.size(), &v[0]);
const auto& opts = res.first;
const auto& otherArgs = res.second;
SG_CHECK_EQUAL(opts.size(), 12); // number of passed options
SG_CHECK_EQUAL(otherArgs.size(), 3); // number of non-option arguments
SG_CHECK_EQUAL(opts[0].passedAs(), "--o-alias-1");
SG_CHECK_EQUAL(opts[0].value(), "");
SG_CHECK_EQUAL(opts[0].hasValue(), false);
SG_CHECK_EQUAL(opts[0].id(), "option -o");
SG_CHECK_EQUAL(opts[1].passedAs(), "-a");
SG_CHECK_EQUAL(opts[1].value(), "arg for -a");
SG_CHECK_EQUAL(opts[1].hasValue(), true);
SG_CHECK_EQUAL(opts[1].id(), "option -a");
SG_CHECK_EQUAL(opts[2].passedAs(), "-p");
SG_CHECK_EQUAL(opts[2].value(), "");
SG_CHECK_EQUAL(opts[2].hasValue(), false);
SG_CHECK_EQUAL(opts[2].id(), "option -N");
SG_CHECK_EQUAL(opts[3].passedAs(), "-O");
SG_CHECK_EQUAL(opts[3].value(), "arg for -O");
SG_CHECK_EQUAL(opts[3].hasValue(), true);
SG_CHECK_EQUAL(opts[3].id(), "option -o");
SG_CHECK_EQUAL(opts[4].passedAs(), "--a-alias-2");
SG_CHECK_EQUAL(opts[4].value(), "value 1");
SG_CHECK_EQUAL(opts[4].hasValue(), true);
SG_CHECK_EQUAL(opts[4].id(), "option -a");
SG_CHECK_EQUAL(opts[5].passedAs(), "--o-alias-2");
SG_CHECK_EQUAL(opts[5].value(), "value 2");
SG_CHECK_EQUAL(opts[5].hasValue(), true);
SG_CHECK_EQUAL(opts[5].id(), "option -o");
SG_CHECK_EQUAL(opts[6].passedAs(), "-N");
SG_CHECK_EQUAL(opts[6].value(), "");
SG_CHECK_EQUAL(opts[6].hasValue(), false);
SG_CHECK_EQUAL(opts[6].id(), "option -N");
SG_CHECK_EQUAL(opts[7].passedAs(), "-o");
SG_CHECK_EQUAL(opts[7].value(), "value 3");
SG_CHECK_EQUAL(opts[7].hasValue(), true);
SG_CHECK_EQUAL(opts[7].id(), "option -o");
SG_CHECK_EQUAL(opts[8].passedAs(), "--N-alias-2");
SG_CHECK_EQUAL(opts[8].value(), "");
SG_CHECK_EQUAL(opts[8].hasValue(), false);
SG_CHECK_EQUAL(opts[8].id(), "option -N");
SG_CHECK_EQUAL(opts[9].passedAs(), "--a-alias-3");
SG_CHECK_EQUAL(opts[9].value(), "value 4");
SG_CHECK_EQUAL(opts[9].hasValue(), true);
SG_CHECK_EQUAL(opts[9].id(), "option -a");
SG_CHECK_EQUAL(opts[10].passedAs(), "-0");
SG_CHECK_EQUAL(opts[10].value(), "value 5");
SG_CHECK_EQUAL(opts[10].hasValue(), true);
SG_CHECK_EQUAL(opts[10].id(), "option -o");
SG_CHECK_EQUAL(opts[11].passedAs(), "--N-alias-1");
SG_CHECK_EQUAL(opts[11].value(), "");
SG_CHECK_EQUAL(opts[11].hasValue(), false);
SG_CHECK_EQUAL(opts[11].id(), "option -N");
SG_CHECK_EQUAL_NOSTREAM(
otherArgs,
vector<string>({"non option 1", "non option 2", "non option 3"}));
}
// Auxiliary function used by test_invalidOptionOrArgumentMissing()
void aux_invalidOptionOrMissingArgument_checkRaiseExcecption(
const simgear::argparse::ArgumentParser& parser,
const vector<const char*>& v)
{
bool gotException = false;
try {
parser.parseArgs(v.size(), &v[0]);
} catch (const simgear::argparse::Error&) {
gotException = true;
}
SG_VERIFY(gotException);
}
void test_invalidOptionOrMissingArgument()
{
cout << "Testing passing invalid options and other syntax errors" << endl;
using simgear::argparse::OptionArgType;
simgear::argparse::ArgumentParser parser;
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT, "-o");
parser.addOption("option -m", OptionArgType::MANDATORY_ARGUMENT,
"-m", "--mandatory-arg");
parser.addOption("option -n", OptionArgType::NO_ARGUMENT, "-n", "--no-arg");
const vector<vector<const char*> > listOfArgvs({
{"FoobarProg", "-ovalue", "-n", "-X",
"non option 1", "non option 2", "non option 3"},
{"FoobarProg", "-ovalue", "-nXn",
"non option 1", "non option 2", "non option 3"},
{"FoobarProg", "-ovalue", "-n", "--non-existent-option",
"non option 1", "non option 2", "non option 3"},
{"FoobarProg", "-ovalue", "-n", "--non-existent-option=value",
"non option 1", "non option 2", "non option 3"},
{"FoobarProg", "-ovalue", "-n", "-m", "--",
"non option 1", "non option 2", "non option 3"},
{"FoobarProg", "-ovalue", "-n", "-X", "-m"},
{"FoobarProg", "-ovalue", "-n", "--mandatory-arg", "--",
"non option 1", "non option 2", "non option 3"},
{"FoobarProg", "-ovalue", "-n", "--mandatory-arg"}
});
for (const auto& argv: listOfArgvs) {
aux_invalidOptionOrMissingArgument_checkRaiseExcecption(parser, argv);
}
}
int main(int argc, const char *const *argv)
{
test_mixOfShortAndLongOptions();
test_whenOptionValueIsASingleHyphen();
test_frontierBetweenOptionsAndNonOptions();
test_optionsWithMultipleAliases();
test_invalidOptionOrMissingArgument();
return EXIT_SUCCESS;
}

View File

@@ -1,3 +1,4 @@
#include <simgear_config.h>
#include <simgear/compiler.h>

View File

@@ -1,3 +1,5 @@
#include <simgear_config.h>
#include <cstdlib>
#include <simgear/misc/sg_path.hxx>

View File

@@ -53,9 +53,9 @@ static const char sgDirPathSep = '/';
static const char sgDirPathSepBad = '\\';
#ifdef _WIN32
const char SGPath::pathListSep = ';';
const char SGPath::pathListSep[] = ";"; // this is null-terminated
#else
const char SGPath::pathListSep = ':';
const char SGPath::pathListSep[] = ":"; // ditto
#endif
#ifdef _WIN32
@@ -278,7 +278,6 @@ SGPath& SGPath::operator=(const SGPath& p)
SGPath::~SGPath() {
}
#if defined(ENABLE_OLD_PATH_API)
// set path
void SGPath::set( const string& p ) {
path = p;
@@ -286,7 +285,6 @@ void SGPath::set( const string& p ) {
_cached = false;
_rwCached = false;
}
#endif
//------------------------------------------------------------------------------
void SGPath::setPermissionChecker(PermissionChecker validator)
@@ -334,7 +332,7 @@ SGPath SGPath::operator/( const std::string& p ) const
#if defined(ENABLE_OLD_PATH_API)
//add a new path component to the existing path string
void SGPath::add( const string& p ) {
append( SGPath::pathListSep+p );
append( SGPath::pathListSep[0] + p );
}
#endif
@@ -534,10 +532,11 @@ void SGPath::validate() const
//------------------------------------------------------------------------------
void SGPath::checkAccess() const
{
if( _rwCached && _cacheEnabled )
if ( _rwCached && _cacheEnabled ) {
return;
}
validate();
validate();
_rwCached = true;
}
@@ -666,7 +665,7 @@ string_list sgPathSplit( const string &search_path ) {
bool done = false;
while ( !done ) {
int index = tmp.find(SGPath::pathListSep);
int index = tmp.find(SGPath::pathListSep[0]);
if (index >= 0) {
result.push_back( tmp.substr(0, index) );
tmp = tmp.substr( index + 1 );

View File

@@ -52,8 +52,8 @@ class SGPath {
public:
// OS-dependent separator used in paths lists
static const char pathListSep;
// OS-dependent separator used in paths lists (C-style string of length 1)
static const char pathListSep[2];
struct Permissions
{

View File

@@ -20,12 +20,15 @@
//
// $Id$
#include <ctype.h>
#include <cstring>
#include <simgear_config.h>
#include <string>
#include <sstream>
#include <algorithm>
#include <string.h> // strerror_r() and strerror_s()
#include <errno.h>
#include <type_traits>
#include <cstring> // strerror_r() and strerror_s()
#include <cctype>
#include <cerrno>
#if defined(HAVE_CPP11_CODECVT)
#include <codecvt> // new in C++11
@@ -372,6 +375,170 @@ namespace simgear {
return result;
}
template<>
int digitValue<10>(char c)
{
if ('0' <= c && c <= '9') {
return static_cast<int>(c - '0');
} else {
throw sg_range_exception("invalid as a decimal digit: '" +
std::string(1, c) + "'");
}
}
template<>
int digitValue<16>(char c)
{
if ('0' <= c && c <= '9') {
return static_cast<int>(c - '0');
} else if ('a' <= c && c <= 'f') {
return 10 + static_cast<int>(c - 'a');
} else if ('A' <= c && c <= 'F') {
return 10 + static_cast<int>(c - 'A');
} else {
throw sg_range_exception("invalid as an hexadecimal digit: '" +
std::string(1, c) + "'");
}
}
template<>
std::string numerationBaseAdjective<10>()
{ return std::string("decimal"); }
template<>
std::string numerationBaseAdjective<16>()
{ return std::string("hexadecimal"); }
template<class T, int BASE, typename>
T readNonNegativeInt(const std::string& s)
{
static_assert(0 < BASE,
"template value BASE must be a positive integer");
static_assert(BASE <= std::numeric_limits<T>::max(),
"template type T too small: it cannot represent BASE");
T res(0);
T multiplier(1);
T increment;
int digit;
if (s.empty()) {
throw sg_format_exception("expected a non-empty string", s);
}
for (auto it = s.crbegin(); it != s.crend(); it++) {
if (it != s.crbegin()) {
// Check if 'multiplier *= BASE' is going to overflow. This is
// reliable because 'multiplier' and 'BASE' are positive.
if (multiplier > std::numeric_limits<T>::max() / BASE) {
// If all remaining digits are '0', it doesn't matter that
// the multiplier overflows.
if (std::all_of(it, s.crend(),
[](char c){ return (c == '0'); })) {
return res;
} else {
throw sg_range_exception(
"doesn't fit in the specified type: '" + s + "'");
}
}
multiplier *= BASE;
}
try {
digit = digitValue<BASE>(*it);
} catch (const sg_range_exception&) {
throw sg_format_exception(
"expected a string containing " +
numerationBaseAdjective<BASE>() +
" digits only, but got '" + s + "'", s);
}
// Reliable because 'multiplier' is positive
if (digit > 0 &&
multiplier > std::numeric_limits<T>::max() / digit) {
throw sg_range_exception(
"doesn't fit in the specified type: '" + s + "'");
}
increment = multiplier*digit;
if (res > std::numeric_limits<T>::max() - increment) {
throw sg_range_exception(
"doesn't fit in the specified type: '" + s + "'");
}
res += increment;
}
return res;
}
// Explicit template instantiations.
//
// In order to save some bytes for the SimGearCore library[*], we only
// instantiate a small number of variants of readNonNegativeInt() below.
// Just enable the ones you need if they are disabled.
//
// [*] The exact amount depends a lot on what you measure and in which
// circumstances. On Linux amd64 with g++, I measured a cost ranging
// from 2 KB per template in a Release build to 19 KB per template in
// a RelWithDebInfo build for the in-memory code size of the resulting
// fgfs binary (CODE column in 'top', after selecting a suitable
// unit). If I look at the fgfs binary size (statically-linked with
// SimGear), I measure from 2 KB per template (Release) to 30 KB per
// template (RelWithDebInfo). Finally, a Debug build compiled with
// '-fno-omit-frame-pointer -O0 -fno-inline' lies between the Release
// and the RelWithDebInfo builds.
#if 0
template
signed char readNonNegativeInt<signed char, 10>(const std::string& s);
template
signed char readNonNegativeInt<signed char, 16>(const std::string& s);
template
unsigned char readNonNegativeInt<unsigned char, 10>(const std::string& s);
template
unsigned char readNonNegativeInt<unsigned char, 16>(const std::string& s);
template
short readNonNegativeInt<short, 10>(const std::string& s);
template
short readNonNegativeInt<short, 16>(const std::string& s);
template
unsigned short readNonNegativeInt<unsigned short, 10>(const std::string& s);
template
unsigned short readNonNegativeInt<unsigned short, 16>(const std::string& s);
#endif
template
int readNonNegativeInt<int, 10>(const std::string& s);
template
unsigned int readNonNegativeInt<unsigned int, 10>(const std::string& s);
#if 0
template
int readNonNegativeInt<int, 16>(const std::string& s);
template
unsigned int readNonNegativeInt<unsigned int, 16>(const std::string& s);
template
long readNonNegativeInt<long, 10>(const std::string& s);
template
long readNonNegativeInt<long, 16>(const std::string& s);
template
unsigned long readNonNegativeInt<unsigned long, 10>(const std::string& s);
template
unsigned long readNonNegativeInt<unsigned long, 16>(const std::string& s);
template
long long readNonNegativeInt<long long, 10>(const std::string& s);
template
long long readNonNegativeInt<long long, 16>(const std::string& s);
template
unsigned long long readNonNegativeInt<unsigned long long, 10>(
const std::string& s);
template
unsigned long long readNonNegativeInt<unsigned long long, 16>(
const std::string& s);
#endif
int compare_versions(const string& v1, const string& v2, int maxComponents)
{
vector<string> v1parts(split(v1, "."));
@@ -630,6 +797,59 @@ std::string encodeHex(const unsigned char* rawBytes, unsigned int length)
return hex;
}
// Write an octal backslash-escaped respresentation of 'val' to 'buf'.
//
// At least 4 write positions must be available at 'buf'. The result is *not*
// null-terminated. Only the 8 least significant bits of 'val' are used;
// higher-order bits have no influence on the chars written to 'buf'.
static void writeOctalBackslashEscapedRepr(char *buf, unsigned char val)
{
buf[0] = '\\';
buf[1] = '0' + ((val >> 6) & 3); // 2 bits
buf[2] = '0' + ((val >> 3) & 7); // 3 bits
buf[3] = '0' + (val & 7); // 3 bits
}
// Backslash-escape a string for C/C++ string literal syntax.
std::string escape(const std::string& s) {
string res;
char buf[4];
for (const char c: s) {
// We don't really *need* to special-case \a, \b, \f, \n, \r, \t and \v,
// because they could be handled like the other non-ASCII or non-printable
// characters. However, doing so will make the output string both shorter
// and more readable.
if (c == '\a') {
res += "\\a";
} else if (c == '\b') {
res += "\\b";
} else if (c == '\f') {
res += "\\f";
} else if (c == '\n') {
res += "\\n";
} else if (c == '\r') {
res += "\\r";
} else if (c == '\t') {
res += "\\t";
} else if (c == '\v') {
res += "\\v";
} else if (c < 0x20 || c > 0x7e) { // non-ASCII or non-printable character
// This is fast (no memory allocation nor IOStreams needed)
writeOctalBackslashEscapedRepr(buf, static_cast<unsigned char>(c));
res.append(buf, 4);
} else if (c == '\\') {
res += "\\\\";
} else if (c == '"') {
res += "\\\"";
} else {
res += c;
}
}
return res;
}
//------------------------------------------------------------------------------
std::string unescape(const char* s)
{
@@ -665,16 +885,17 @@ std::string unescape(const char* s)
if (!*++s)
break;
int v = 0;
for (int i = 0; i < 2 && isxdigit(*s); i++, s++)
for (/* empty */; isxdigit(*s); s++) {
v = v * 16 + (isdigit(*s) ? *s - '0' : 10 + tolower(*s) - 'a');
r += v;
}
r += static_cast<char>(v);
continue;
} else if (*s >= '0' && *s <= '7') {
int v = *s++ - '0';
for (int i = 0; i < 3 && *s >= '0' && *s <= '7'; i++, s++)
for (int i = 0; i < 2 && *s >= '0' && *s <= '7'; i++, s++)
v = v * 8 + *s - '0';
r += v;
r += static_cast<char>(v);
continue;
} else {
@@ -739,6 +960,94 @@ std::string error_string(int errnum)
#endif // !defined(_GNU_SOURCE)
}
bool to_bool(const std::string& s)
{
if (!strcasecmp(s.c_str(), "yes")) return true;
if (!strcasecmp(s.c_str(), "no")) return false;
if (!strcasecmp(s.c_str(), "true")) return true;
if (!strcasecmp(s.c_str(), "false")) return false;
if (s == "1") return true;
if (s == "0") return false;
SG_LOG(SG_GENERAL, SG_WARN, "Unable to parse string as boolean:" << s);
return false;
}
enum PropMatchState
{
MATCH_LITERAL = 0,
MATCH_WILD_INDEX,
MATCH_WILD_NAME
};
bool matchPropPathToTemplate(const std::string& path, const std::string& templatePath)
{
if (path.empty()) {
return false;
}
const char* pathPtr = path.c_str();
const char* tPtr = templatePath.c_str();
PropMatchState state = MATCH_LITERAL;
while (true) {
bool advanceInTemplate = true;
const char p = *pathPtr;
if (p == 0) {
// ran out of chars in the path. If we are matching a trailing
// wildcard, this is a match, otherwise it's a fail
if (state == MATCH_WILD_NAME) {
// check this is the last * in the template string
if (*(tPtr + 1) == 0) {
return true;
}
}
return false;
}
switch (state) {
case MATCH_LITERAL:
if (*tPtr != p) {
// literal mismatch
return false;
}
++pathPtr;
break;
case MATCH_WILD_NAME:
if ((p == '-') || isalpha(p)) {
advanceInTemplate = false;
++pathPtr;
} else {
// something else, we will advance in the template
}
break;
case MATCH_WILD_INDEX:
if (isdigit(p)) {
advanceInTemplate = false;
++pathPtr;
} else {
// something else, we will advance in the template
}
break;
} // of state switch
if (advanceInTemplate) {
const char nextTemplate = *(++tPtr);
if (nextTemplate == 0) {
// end of template, successful match
return true;
} else if (nextTemplate == '*') {
state = (*(tPtr - 1) == '[') ? MATCH_WILD_INDEX : MATCH_WILD_NAME;
} else {
state = MATCH_LITERAL;
}
}
}
// unreachable
}
} // end namespace strutils
} // end namespace simgear

View File

@@ -31,6 +31,7 @@
#include <string>
#include <vector>
#include <type_traits>
#include <cstdlib>
typedef std::vector < std::string > string_list;
@@ -168,7 +169,52 @@ namespace simgear {
* convert a string representing a decimal number, to an int
*/
int to_int(const std::string& s, int base = 10);
/** Convert a char to the integer it represents in the specified BASE.
*
* Contrary to std::isdigit() and std::isxdigit(), only the standard ASCII
* digits for BASE are accepted (with both uppercase and lowercase 'a'-'f'
* letters for base 16). Throw sg_range_exception if the char is not a
* valid digit for this base.
*
* See template specializations in strutils.cxx.
*/
template<int BASE>
int digitValue(char c);
/** Return:
* - std::string("decimal") if BASE is 10;
* - std::string("hexadecimal") if BASE is 16.
*
* Template specializations in strutils.cxx.
*/
template<int BASE>
std::string numerationBaseAdjective();
/** Convert a string representing an integer to an integral type.
*
* The input string must be non-empty and contain only digits of the
* specified BASE (template parameter). Throw:
* - sg_format_exception if the input string doesn't respect these
* constraints;
* - sg_range_exception if the value can't be represented by type T
* (i.e., if it is too large).
*
* Explicit template instantiations are added as needed in strutils.cxx.
* Have a look there and enable the ones you need!
*/
template<
class T,
int BASE = 10,
typename = typename std::enable_if<std::is_integral<T>::value, T>::type >
T readNonNegativeInt(const std::string& s);
/**
* Convert a string representing a boolean, to a bool.
* Accepted values include YES, true, 0, 1, false, no, True,
*/
bool to_bool(const std::string& s);
/**
* Like strcmp(), but for dotted versions strings NN.NN.NN
* any number of terms are supported.
@@ -223,26 +269,43 @@ namespace simgear {
* malformed
*/
void decodeBase64(const std::string& a, std::vector<unsigned char>& output);
/**
* convert bytes to hexadecimal equivalent
*/
std::string encodeHex(const std::string& bytes);
std::string encodeHex(const unsigned char* rawBytes, unsigned int length);
/**
* Backslash-escape a string for C/C++ string literal syntax.
*
* @param s Input string.
* @return a copy of the input string with proper escaping, so that if the
* result is part of a C or C++ file and enclosed in double
* quotes, it can be used to represent a string literal that is
* equal to the input string.
*
* @note For every std::string s: unescape(escape(s)) == s
* @see unescape()
*/
std::string escape(const std::string& s);
/**
* Unescape string.
*
* @param str String possibly containing escaped characters.
* @return string with escaped characters replaced by single character
* values.
*
* @note For every std::string s: unescape(escape(s)) == s
* @see escape()
*/
std::string unescape(const char* str);
inline std::string unescape(const std::string& str)
{ return unescape(str.c_str()); }
/**
* Check a printf-style format string for dangerous (buffer-overflowing,
* memory re-writing) format tokens. If a problematic token is
@@ -258,6 +321,20 @@ namespace simgear {
*/
std::string error_string(int errnum);
/**
* Match a property path, obtained from prop->getPath(), against a
* template string. Templates are allowed to contain widlcards denoted by
* an asterix in certain places - at the end of names, or inside indices.
* Note that paths returned by getPath() always include an index on every
* path component, so template strings should be structured accordingly.
*
* Examples:
* /foo[*]/bar* will match /foo/barber, /foo[2]/bargain
* /views[0]/view[*]/f* will match /views[0]/view[99]/foo,
* /views[0]/view[4]/fig, /views[0]/view[1000]/flight
*/
bool matchPropPathToTemplate(const std::string& path, const std::string& templatePath);
} // end namespace strutils
} // end namespace simgear

View File

@@ -1,15 +1,27 @@
// -*- coding: utf-8 -*-
//
// Unit tests for functions inside the strutils package
#include <errno.h>
#include <stdlib.h> // _set_errno() on Windows
#include <string>
#include <vector>
#include <utility> // std::move()
#include <fstream> // std::ifstream
#include <sstream> // std::ostringstream
#include <ios> // std::dec, std::hex
#include <limits> // std::numeric_limits
#include <typeinfo> // typeid()
#include <cstdint> // uint16_t, uintmax_t, etc.
#include <cstdlib> // _set_errno() on Windows
#include <cerrno>
#include <cassert>
#include <simgear/misc/test_macros.hxx>
#include <simgear/compiler.h>
#include <simgear/misc/strutils.hxx>
#include <simgear/structure/exception.hxx>
using std::string;
using std::vector;
namespace strutils = simgear::strutils;
@@ -85,6 +97,290 @@ void test_to_int()
SG_CHECK_EQUAL(strutils::to_int("-10000"), -10000);
}
// Auxiliary function for test_readNonNegativeInt()
void aux_readNonNegativeInt_setUpOStringStream(std::ostringstream& oss, int base)
{
switch (base) {
case 10:
oss << std::dec;
break;
case 16:
oss << std::hex;
break;
default:
SG_TEST_FAIL("unsupported value for 'base': " + std::to_string(base));
}
}
// Auxiliary function for test_readNonNegativeInt(): round-trip conversion for
// the given number of values below and up to std::numeric_limits<T>::max().
template<typename T, int BASE>
void aux_readNonNegativeInt_testValuesCloseToMax(T nbValues)
{
std::ostringstream oss;
assert(0 <= nbValues && nbValues <= std::numeric_limits<T>::max());
aux_readNonNegativeInt_setUpOStringStream(oss, BASE);
for (T i = std::numeric_limits<T>::max() - nbValues;
i < std::numeric_limits<T>::max(); i++) {
T valueToTest = i + 1;
T roundTripResult;
bool gotException = false;
oss.str("");
// The cast is only useful when T is a char type
oss << static_cast<uintmax_t>(valueToTest);
try {
roundTripResult = strutils::readNonNegativeInt<T, BASE>(oss.str());
} catch (const sg_range_exception&) {
gotException = true;
}
SG_VERIFY(!gotException);
SG_CHECK_EQUAL(roundTripResult, valueToTest);
}
}
// Auxiliary class for test_readNonNegativeInt(): test that we do get an
// exception when trying to convert the smallest, positive out-of-range value
// for type T.
template<typename T, int BASE>
class ReadNonNegativeInt_JustOutOfRangeTester {
public:
ReadNonNegativeInt_JustOutOfRangeTester()
{ }
// Run the test
void run()
{
std::ostringstream oss;
aux_readNonNegativeInt_setUpOStringStream(oss, BASE);
oss << 1 + static_cast<uintmax_t>(std::numeric_limits<T>::max());
bool gotException = false;
try {
strutils::readNonNegativeInt<T, BASE>(oss.str());
} catch (const sg_range_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
};
class ReadNonNegativeInt_DummyTester {
public:
ReadNonNegativeInt_DummyTester()
{ }
void run()
{ }
};
// We use this helper class to automatically determine for which types
// ReadNonNegativeInt_JustOutOfRangeTester::run() can be run.
template<typename T, int BASE>
class AuxReadNonNegativeInt_JustOutOfRange_Helper
{
typedef typename std::make_unsigned<T>::type uT;
// Define TestRunner to be either
//
// ReadNonNegativeInt_JustOutOfRangeTester<T, BASE>
//
// or
//
// ReadNonNegativeInt_DummyTester
//
// depending on whether 1 + std::numeric_limits<T>::max() can be
// represented by uintmax_t.
typedef typename std::conditional<
static_cast<uT>(std::numeric_limits<T>::max()) <
std::numeric_limits<uintmax_t>::max(),
ReadNonNegativeInt_JustOutOfRangeTester<T, BASE>,
ReadNonNegativeInt_DummyTester >::type TestRunner;
public:
AuxReadNonNegativeInt_JustOutOfRange_Helper()
{ };
void test()
{
TestRunner().run();
}
};
void test_readNonNegativeInt()
{
// In order to save some bytes for the SimGearCore library[*], we only
// instantiated a small number of variants of readNonNegativeInt() in
// strutils.cxx. This is why many tests are disabled with '#if 0' below. Of
// course, more variants can be enabled when they are needed.
//
// [*] See measures in strutils.cxx before the template instantiations.
#if 0
SG_CHECK_EQUAL((strutils::readNonNegativeInt<short>("0")), 0);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<short>("23")), 23);
#endif
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int>("0")), 0);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int>("00000000")), 0);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int>("12345")), 12345);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int, 10>("12345")), 12345);
#if 0
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int, 16>("ff")), 0xff);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int, 16>("a5E9")), 0xa5e9);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<unsigned long, 16>("0cda")),
0x0cda);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<uint16_t, 10>("65535")), 0xffff);
SG_CHECK_EQUAL(
(strutils::readNonNegativeInt<uint16_t, 10>("00000000000000000000065535")),
0xffff);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<uint16_t, 16>("ffff")), 0xffff);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int16_t, 10>("32767")), 0x7fff);
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int16_t, 16>("7fff")), 0x7fff);
#endif
// Nothing special about the values :)
SG_CHECK_EQUAL_NOSTREAM((typeid(strutils::readNonNegativeInt<int, 10>("72"))),
typeid(int(12)));
#if 0
SG_CHECK_EQUAL_NOSTREAM((typeid(strutils::readNonNegativeInt<long, 10>("72"))),
typeid(12L));
SG_CHECK_EQUAL_NOSTREAM(
(typeid(strutils::readNonNegativeInt<long long, 10>("72"))),
typeid(12LL));
#endif
{
bool gotException = false;
try {
strutils::readNonNegativeInt<int>(""); // empty string: illegal
} catch (const sg_format_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
{
bool gotException = false;
try {
strutils::readNonNegativeInt<int>("-1"); // non-digit character: illegal
} catch (const sg_format_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
{
bool gotException = false;
try {
strutils::readNonNegativeInt<int>("+1"); // non-digit character: illegal
} catch (const sg_format_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
{
bool gotException = false;
try {
strutils::readNonNegativeInt<int>("858efe"); // trailing garbage: illegal
} catch (const sg_format_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
#if 0
{
bool gotException = false;
try {
strutils::readNonNegativeInt<int, 16>("858g5k"); // ditto for base 16
} catch (const sg_format_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
#endif
{
bool gotException = false;
try {
strutils::readNonNegativeInt<int>(" 858"); // leading whitespace/garbage:
} catch (const sg_format_exception&) { // illegal too
gotException = true;
}
SG_VERIFY(gotException);
}
// Try to read a value that is 1 unit too large for the type. Check that it
// raises an sg_range_exception in each case.
#if 0
AuxReadNonNegativeInt_JustOutOfRange_Helper<signed char, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<signed char, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned char, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned char, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<short, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<short, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned short, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned short, 16>().test();
#endif
AuxReadNonNegativeInt_JustOutOfRange_Helper<int, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned int, 10>().test();
#if 0
AuxReadNonNegativeInt_JustOutOfRange_Helper<int, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned int, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<long, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<long, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<long long, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<long long, 16>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long long, 10>().test();
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long long, 16>().test();
#endif
// Round trip tests with large values, including the largest value that can
// be represented by the type, in each case.
//
// Can be casted as any of the following types
constexpr int nbValues = 5000;
#if 0
aux_readNonNegativeInt_testValuesCloseToMax<signed char, 10>(127);
aux_readNonNegativeInt_testValuesCloseToMax<signed char, 16>(127);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned char, 10>(127);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned char, 16>(127);
aux_readNonNegativeInt_testValuesCloseToMax<short, 10>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<short, 16>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned short, 10>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned short, 16>(nbValues);
#endif
aux_readNonNegativeInt_testValuesCloseToMax<int, 10>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned int, 10>(nbValues);
#if 0
aux_readNonNegativeInt_testValuesCloseToMax<int, 16>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned int, 16>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<long, 10>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<long, 16>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long, 10>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long, 16>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<long long, 10>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<long long, 16>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long long, 10>(nbValues);
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long long, 16>(nbValues);
#endif
}
void test_split()
{
string_list l = strutils::split("zero one two three four five");
@@ -139,9 +435,72 @@ void test_split()
}
}
void test_escape()
{
SG_CHECK_EQUAL(strutils::escape(""), "");
SG_CHECK_EQUAL(strutils::escape("\\"), "\\\\");
SG_CHECK_EQUAL(strutils::escape("\""), "\\\"");
SG_CHECK_EQUAL(strutils::escape("\\n"), "\\\\n");
SG_CHECK_EQUAL(strutils::escape("n\\"), "n\\\\");
SG_CHECK_EQUAL(strutils::escape(" ab\nc \\def\t\r \\ ghi\\"),
" ab\\nc \\\\def\\t\\r \\\\ ghi\\\\");
// U+0152 is LATIN CAPITAL LIGATURE OE. The last word is Egg translated in
// French and encoded in UTF-8 ('Œuf' if you can read UTF-8).
SG_CHECK_EQUAL(strutils::escape("Un \"Bel\" '\u0152uf'"),
"Un \\\"Bel\\\" '\\305\\222uf'");
SG_CHECK_EQUAL(strutils::escape("\a\b\f\n\r\t\v"),
"\\a\\b\\f\\n\\r\\t\\v");
// Test with non-printable characters
//
// - 'prefix' is an std::string that *contains* a NUL character.
// - \012 is \n (LINE FEED).
// - \037 (\x1F) is the last non-printable ASCII character before \040 (\x20),
// which is the space.
// - \176 (\x7E) is '~', the last printable ASCII character.
// - \377 is \xFF. Higher char values (> 255) are not faithfully encoded by
// strutils::escape(): only the lowest 8 bits are used; higher-order bits
// are ignored (for people who use chars with more than 8 bits...).
const string prefix = string("abc") + '\000';
SG_CHECK_EQUAL(strutils::escape(prefix +
"\003def\012\037\040\176\177\376\377"),
"abc\\000\\003def\\n\\037 ~\\177\\376\\377");
SG_CHECK_EQUAL(strutils::escape(" \n\tAOa"), " \\n\\tAOa");
}
void test_unescape()
{
SG_CHECK_EQUAL(strutils::unescape("\\ \\n\\t\\x41\\117a"), " \n\tAOa");
// Two chars: '\033' (ESC) followed by '2'
SG_CHECK_EQUAL(strutils::unescape("\\0332"), "\0332");
// Hex escapes have no length limit and terminate at the first character
// that is not a valid hexadecimal digit.
SG_CHECK_EQUAL(strutils::unescape("\\x00020|"), " |");
SG_CHECK_EQUAL(strutils::unescape("\\xA"), "\n");
SG_CHECK_EQUAL(strutils::unescape("\\xA-"), "\n-");
}
void aux_escapeAndUnescapeRoundTripTest(const string& testString)
{
SG_CHECK_EQUAL(strutils::unescape(strutils::escape(testString)), testString);
}
void test_escapeAndUnescapeRoundTrips()
{
// "\0332" contains two chars: '\033' (ESC) followed by '2'.
// Ditto for "\0402": it's a space ('\040') followed by a '2'.
vector<string> stringsToTest(
{"", "\\", "\n", "\\\\", "\"\'\?\t\rAG\v\a \b\f\\", "\x23\xf8",
"\0332", "\0402", "\u00e0", "\U000000E9"});
const string withBinary = (string("abc") + '\000' +
"\003def\012\037\040\176\177\376\377");
stringsToTest.push_back(std::move(withBinary));
for (const string& s: stringsToTest) {
aux_escapeAndUnescapeRoundTripTest(s);
}
}
void test_compare_versions()
@@ -175,6 +534,32 @@ void test_md5_hex()
SG_CHECK_EQUAL(strutils::md5("test"), "098f6bcd4621d373cade4e832627b4f6");
}
void test_propPathMatch()
{
const char* testTemplate1 = "/sim[*]/views[*]/render";
SG_VERIFY(strutils::matchPropPathToTemplate("/sim[0]/views[50]/render-buildings[0]", testTemplate1));
SG_VERIFY(strutils::matchPropPathToTemplate("/sim[1]/views[0]/rendering-enabled", testTemplate1));
SG_VERIFY(!strutils::matchPropPathToTemplate("/sim[0]/views[50]/something-else", testTemplate1));
SG_VERIFY(!strutils::matchPropPathToTemplate("/sim[0]/gui[0]/wibble", testTemplate1));
// test explicit index matching
const char* testTemplate2 = "/view[5]/*";
SG_VERIFY(!strutils::matchPropPathToTemplate("/view[2]/render-buildings[0]", testTemplate2));
SG_VERIFY(!strutils::matchPropPathToTemplate("/sim[1]/foo", testTemplate2));
SG_VERIFY(!strutils::matchPropPathToTemplate("/view[50]/foo", testTemplate2));
SG_VERIFY(!strutils::matchPropPathToTemplate("/view[55]/foo", testTemplate2));
SG_VERIFY(strutils::matchPropPathToTemplate("/view[5]/foo", testTemplate2));
SG_VERIFY(strutils::matchPropPathToTemplate("/view[5]/child[3]/bar", testTemplate2));
const char* testTemplate3 = "/*[*]/fdm*[*]/aero*";
SG_VERIFY(strutils::matchPropPathToTemplate("/position[2]/fdm-jsb[0]/aerodynamic", testTemplate3));
SG_VERIFY(!strutils::matchPropPathToTemplate("/position[2]/foo[0]/aerodynamic", testTemplate3));
}
void test_error_string()
{
#if defined(_WIN32)
@@ -205,11 +590,15 @@ int main(int argc, char* argv[])
test_ends_with();
test_simplify();
test_to_int();
test_readNonNegativeInt();
test_split();
test_escape();
test_unescape();
test_escapeAndUnescapeRoundTrips();
test_compare_versions();
test_md5_hex();
test_error_string();
test_propPathMatch();
return EXIT_SUCCESS;
}

View File

@@ -15,6 +15,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include <simgear/package/Catalog.hxx>
#include <boost/foreach.hpp>

View File

@@ -167,16 +167,14 @@ int parseTest()
SG_CHECK_EQUAL(index->url, "http://foo.bar.com/thumb-panel.png");
SG_VERIFY(index->type == pkg::Package::Preview::Type::PANEL);
// old-style thumbnails
string_list oldThumbUrls = p2->thumbnailUrls();
SG_CHECK_EQUAL(oldThumbUrls.size(), 1);
SG_CHECK_EQUAL(oldThumbUrls.at(0), "http://foo.bar.com/thumb-exterior.png");
string_list oldThumbPaths = p2->thumbnails();
SG_CHECK_EQUAL(oldThumbPaths.size(), 1);
SG_CHECK_EQUAL(oldThumbPaths.at(0), "exterior.png");
// thumbnails
const pkg::Package::Thumbnail& thumb = p2->thumbnailForVariant(0);
SG_CHECK_EQUAL(thumb.url, "http://foo.bar.com/thumb-exterior.png");
SG_CHECK_EQUAL(thumb.path, "exterior.png");
// test variants
SG_CHECK_EQUAL(p2->parentIdForVariant(0), std::string());
try {
p2->indexOfVariant("fofofo");
SG_TEST_FAIL("lookup of non-existant variant did not throw");
@@ -190,6 +188,8 @@ int parseTest()
unsigned int skisVariant = p2->indexOfVariant("c172p-skis");
SG_VERIFY(skisVariant > 0);
SG_CHECK_EQUAL(p2->parentIdForVariant(skisVariantFull), "c172p");
SG_CHECK_EQUAL(skisVariant, skisVariantFull);
SG_CHECK_EQUAL(p2->getLocalisedProp("description", skisVariant),
@@ -199,6 +199,7 @@ int parseTest()
unsigned int floatsVariant = p2->indexOfVariant("c172p-floats");
SG_VERIFY(floatsVariant > 0);
SG_CHECK_EQUAL(p2->parentIdForVariant(floatsVariant), "c172p");
SG_CHECK_EQUAL(p2->getLocalisedProp("description", floatsVariant),
"A plane with floats");
@@ -215,6 +216,22 @@ int parseTest()
SG_CHECK_EQUAL(index->url, "http://foo.bar.com/thumb-exterior-skis.png");
SG_VERIFY(index->type == pkg::Package::Preview::Type::EXTERIOR);
const pkg::Package::Thumbnail& thumb2 = p2->thumbnailForVariant(floatsVariant);
SG_CHECK_EQUAL(thumb2.url, "http://foo.bar.com/thumb-floats.png");
SG_CHECK_EQUAL(thumb2.path, "thumb-floats.png");
// test multiple primary
unsigned int rVariant = p2->indexOfVariant("c172r");
SG_VERIFY(rVariant > 0);
SG_CHECK_EQUAL(p2->parentIdForVariant(rVariant), std::string());
unsigned int rFloatVariant = p2->indexOfVariant("c172r-floats");
SG_VERIFY(rFloatVariant > 0);
SG_CHECK_EQUAL(p2->parentIdForVariant(rFloatVariant), std::string("c172r"));
string_list primaries = {"c172p", "c172r"};
SG_VERIFY(p2->primaryVariants() == primaries);
// test filtering / searching too
string_set tags(p2->tags());

View File

@@ -15,6 +15,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include <simgear/package/Install.hxx>
#include <boost/foreach.hpp>
@@ -152,6 +153,11 @@ protected:
return;
}
// disable caching on the owner's path, otherwise the upcoming
// delete & rename confuse everything
m_owner->m_path.set_cached(false);
m_extractPath.set_cached(false);
if (m_owner->path().exists()) {
Dir destDir(m_owner->path());
destDir.remove(true /* recursive */);
@@ -450,33 +456,6 @@ void Install::cancelDownload()
m_package->catalog()->root()->cancelDownload(this);
}
struct PathAppender
{
PathAppender(const SGPath& p) : m_path(p) {}
SGPath operator()(const std::string& s) const
{
SGPath p(m_path);
p.append(s);
return p;
}
SGPath m_path;
};
PathList Install::thumbnailPaths() const
{
const string_list& thumbs(m_package->thumbnails());
PathList result;
if (thumbs.empty())
return result;
std::transform(thumbs.begin(), thumbs.end(),
std::back_inserter(result),
PathAppender(m_path));
return result;
}
SGPath Install::primarySetPath() const
{
SGPath setPath(m_path);

View File

@@ -98,13 +98,6 @@ public:
*/
void cancelDownload();
/**
* return the thumbnails associated with this install, but as locations
* on the file system, not URLs. It is assumed the order of thumbnails
* is consistent with the URLs returned from Package::thumbnailUrls()
*/
PathList thumbnailPaths() const;
/**
* Set the handler to be called when the installation successfully
* completes.

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include <simgear/package/Package.hxx>
#include <cassert>
@@ -274,28 +276,12 @@ SGPropertyNode* Package::properties() const
string_list Package::thumbnailUrls() const
{
string_list r;
if (!m_props) {
return r;
string_list urls;
const Thumbnail& thumb(thumbnailForVariant(0));
if (!thumb.url.empty()) {
urls.push_back(thumb.url);
}
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("thumbnail")) {
r.push_back(dl->getStringValue());
}
return r;
}
string_list Package::thumbnails() const
{
string_list r;
if (!m_props) {
return r;
}
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("thumbnail-path")) {
r.push_back(dl->getStringValue());
}
return r;
return urls;
}
string_list Package::downloadUrls() const
@@ -305,7 +291,7 @@ string_list Package::downloadUrls() const
return r;
}
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("url")) {
for (auto dl : m_props->getChildren("url")) {
r.push_back(dl->getStringValue());
}
return r;
@@ -426,7 +412,49 @@ SGPropertyNode_ptr Package::propsForVariant(const unsigned int vIndex, const cha
return m_props;
}
throw sg_exception("Unknow variant in package " + id());
throw sg_exception("Unknown variant in package " + id());
}
std::string Package::parentIdForVariant(unsigned int variantIndex) const
{
const std::string parentId = propsForVariant(variantIndex)->getStringValue("variant-of");
if ((variantIndex == 0) || (parentId == "_package_")) {
return std::string();
}
if (parentId.empty()) {
// this is a variant without a variant-of, so assume its parent is
// the first primary
return m_variants.front();
}
assert(indexOfVariant(parentId) >= 0);
return parentId;
}
string_list Package::primaryVariants() const
{
string_list result;
for (unsigned int v = 0; v < m_variants.size(); ++v) {
const auto pr = parentIdForVariant(v);
if (pr.empty()) {
result.push_back(m_variants.at(v));
}
}
assert(!result.empty());
assert(result.front() == id());
return result;
}
Package::Thumbnail Package::thumbnailForVariant(unsigned int vIndex) const
{
SGPropertyNode_ptr var = propsForVariant(vIndex);
// allow for variants without distinct thumbnails
if (!var->hasChild("thumbnail") || !var->hasChild("thumbnail-path")) {
var = m_props;
}
return {var->getStringValue("thumbnail"), var->getStringValue("thumbnail-path")};
}
Package::PreviewVec Package::previewsForVariant(unsigned int vIndex) const

View File

@@ -32,7 +32,7 @@ typedef std::set<std::string> string_set;
namespace simgear
{
namespace pkg
{
@@ -40,13 +40,13 @@ namespace pkg
class Install;
class Catalog;
class Package;
typedef SGSharedPtr<Package> PackageRef;
typedef SGSharedPtr<Catalog> CatalogRef;
typedef SGSharedPtr<Install> InstallRef;
typedef std::vector<PackageRef> PackageList;
class Package : public SGReferenced
{
public:
@@ -62,15 +62,25 @@ public:
existingInstall(const InstallCallback& cb = InstallCallback()) const;
bool isInstalled() const;
/**
* package ID
*/
std::string id() const;
/**
* Variant IDs. Note the primary ID will always be included as
* variants()[0], to simplify enumerating all variants
* Variant IDs
*/
string_list variants() const;
/**
* All variants without a parent, i.e top-level variants in this package.
* Often this is a single-element list matching id() above, but when
* packages contain multiple primary aircraft, this will have multiple
* elements.
*/
string_list primaryVariants() const;
/**
* Fully-qualified ID, including our catalog'd ID
*/
@@ -89,8 +99,10 @@ public:
/**
* human-readable name - note this is probably not localised,
* although this is not ruled out for the future.
*
* Deprecated - please use nameForVariant
*/
std::string name() const;
SG_DEPRECATED(std::string name() const);
/**
* Human readable name of a variant
@@ -101,28 +113,31 @@ public:
/**
* syntactic sugar to get the localised description
*
* Deprecated - please use getLocalisedProp to get the variant-specific
* description.
*/
std::string description() const;
SG_DEPRECATED(std::string description() const);
/**
* access the raw property data in the package
*/
SGPropertyNode* properties() const;
/**
* hex-encoded MD5 sum of the download files
*/
std::string md5() const;
std::string getLocalisedProp(const std::string& aName, const unsigned int vIndex = 0) const;
unsigned int revision() const;
size_t fileSizeBytes() const;
CatalogRef catalog() const
{ return m_catalog; }
bool matches(const SGPropertyNode* aFilter) const;
string_set tags() const;
@@ -131,13 +146,15 @@ public:
* download URLs for the package
*/
string_list downloadUrls() const;
string_list thumbnailUrls() const;
/**
* thumbnail file paths within the package on disk
*/
string_list thumbnails() const;
struct Thumbnail
{
std::string url;
std::string path;
};
Thumbnail thumbnailForVariant(unsigned int vIndex) const;
/**
* information about a preview image
@@ -167,7 +184,7 @@ public:
* retrieve all the thumbnails for a variant
*/
PreviewVec previewsForVariant(unsigned int vIndex) const;
/**
* Packages we depend upon.
* If the dependency list cannot be satisifed for some reason,
@@ -180,14 +197,23 @@ public:
* same as the primary ID, depending on the aircraft author
*/
std::string dirName() const;
/**
* Return the parent variant of a variant. This will be the emtpy string if
* the variant is primary (top-level), otherwise the local (non-qualified)
* ID. This allows establishing a heirarchy of variants within the package.
* Note at present most code assumes a maxiumum two-level deep heirarchy
* (parents and children)
*/
std::string parentIdForVariant(unsigned int variantIndex) const;
private:
SGPath pathOnDisk() const;
friend class Catalog;
friend class Root;
Package(const SGPropertyNode* aProps, CatalogRef aCatalog);
void initWithProps(const SGPropertyNode* aProps);
void updateFromProps(const SGPropertyNode* aProps);
@@ -203,7 +229,7 @@ private:
string_set m_tags;
CatalogRef m_catalog;
string_list m_variants;
mutable function_list<InstallCallback> _install_cb;
};
@@ -215,4 +241,3 @@ private:
} // of namespace simgear
#endif // of SG_PACKAGE_PACKAGE_HXX

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include <simgear/package/Root.hxx>
#include <boost/foreach.hpp>
@@ -477,7 +479,7 @@ void Root::installProgress(InstallRef aInstall, unsigned int aBytes, unsigned in
void Root::startNext(InstallRef aCurrent)
{
if (d->updateDeque.front() != aCurrent) {
if (d->updateDeque.empty() || (d->updateDeque.front() != aCurrent)) {
SG_LOG(SG_GENERAL, SG_ALERT, "current install of package not head of the deque");
} else {
d->updateDeque.pop_front();

View File

@@ -89,6 +89,9 @@
<path>thumb-panel.png</path>
<url>http://foo.bar.com/thumb-panel.png</url>
</preview>
<thumbnail>http://foo.bar.com/thumb-floats.png</thumbnail>
<thumbnail-path>thumb-floats.png</thumbnail-path>
</variant>
<variant>
@@ -96,6 +99,8 @@
<name>C172 with skis</name>
<description>A plane with skis</description>
<variant-of>c172p</variant-of>
<preview>
<type>exterior</type>
<path>thumb-exterior-skis.png</path>
@@ -109,10 +114,33 @@
</preview>
</variant>
<variant>
<id>c172r</id>
<name>C172R</name>
<description>Equally good version of the C172</description>
<variant-of>_package_</variant-of>
<preview>
<type>panel</type>
<path>thumb-panel.png</path>
<url>http://foo.bar.com/thumb-panel.png</url>
</preview>
</variant>
<variant>
<id>c172r-floats</id>
<name>C172R-floats</name>
<description>Equally good version of the C172 with floats</description>
<variant-of>c172r</variant-of>
<preview>
<type>panel</type>
<path>thumb-panel.png</path>
<url>http://foo.bar.com/thumb-panel.png</url>
</preview>
</variant>
<md5>ec0e2ffdf98d6a5c05c77445e5447ff5</md5>
<url>http://localhost:2000/catalogTest1/c172p.zip</url>
<!-- legacy thumbnails also supported -->
<thumbnail>http://foo.bar.com/thumb-exterior.png</thumbnail>
<thumbnail-path>exterior.png</thumbnail-path>
</package>

View File

@@ -15,6 +15,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear_config.h>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Package.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 "PropertyBasedElement.hxx"
#include <boost/algorithm/string/predicate.hpp>

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 "PropertyBasedMgr.hxx"
#include <boost/foreach.hpp>

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 "PropertyInterpolationMgr.hxx"
#include "PropertyInterpolator.hxx"
#include "props.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 "PropertyInterpolator.hxx"
#include "props.hxx"

View File

@@ -602,6 +602,45 @@ private:
};
/**
* A string value managed through an object and access methods.
*
* This class stores the std::string returned by the getter, so that
* the value returned by c_str() is still valid.
*
* A read-only value will not have a setter; a write-only value will
* not have a getter.
*/
template <class C>
class SGStringValueMethods : public SGRawValue<const char*>
{
public:
typedef std::string (C::*getter_t)() const;
typedef void (C::*setter_t)(const std::string&);
SGStringValueMethods (C &obj, getter_t getter = 0, setter_t setter = 0)
: _obj(obj), _getter(getter), _setter(setter) {}
virtual ~SGStringValueMethods () {}
virtual const char* getValue () const {
SGStringValueMethods* pthis = const_cast<SGStringValueMethods*>(this);
if (_getter) { pthis->_value = (_obj.*_getter)();
return pthis->_value.c_str(); }
else { return SGRawValue<const char*>::DefaultValue(); }
}
virtual bool setValue (const char* value) {
if (_setter) { (_obj.*_setter)(value ? value : ""); return true; }
else return false;
}
virtual SGRaw* clone () const {
return new SGStringValueMethods(_obj, _getter, _setter);
}
private:
C &_obj;
getter_t _getter;
setter_t _setter;
std::string _value;
};
/**
* An indexed value managed through an object and access methods.
*

View File

@@ -15,3 +15,6 @@ foreach( mylibfolder
endforeach( mylibfolder )
if(ENABLE_GDAL)
add_subdirectory(dem)
endif(ENABLE_GDAL)

View File

@@ -0,0 +1,24 @@
include (SimGearComponent)
set(HEADERS
ReaderWriterPGT.hxx
SGDem.hxx
SGDemLevel.hxx
SGDemRoot.hxx
SGDemSession.hxx
SGDemTile.hxx
SGMesh.hxx
)
set(SOURCES
ReaderWriterPGT.cxx
SGDem.cxx
SGDemLevel.cxx
SGDemRoot.cxx
SGDemSession.cxx
SGDemTile.cxx
SGDemTile_gdal.cxx
SGMesh.cxx
)
simgear_scene_component(dem scene/dem "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,418 @@
// ReaderWriterPGT.cxx -- Provide a paged database for flightgear scenery.
//
// Copyright (C) 2010 - 2013 Mathias Froehlich
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include "ReaderWriterPGT.hxx"
#include <cassert>
#include <osg/PagedLOD>
#include <osg/MatrixTransform>
#include <osg/Texture2D>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
#include <simgear/scene/dem/SGDem.hxx>
#include <simgear/scene/dem/SGMesh.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/tgdb/BucketBox.hxx>
#include <simgear/scene/model/ModelRegistry.hxx>
namespace simgear {
// Cull away tiles that we watch from downside
struct ReaderWriterPGT::CullCallback : public osg::NodeCallback {
virtual ~CullCallback()
{ }
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
const osg::BoundingSphere& nodeBound = node->getBound();
// If the bounding sphere of the node is empty, there is nothing to do
if (!nodeBound.valid())
return;
// Culling away tiles that we look at from the downside.
// This is done by computing the maximum distance we can
// see something from the current eyepoint. If the sphere
// that is defined by this radius does no intersects the
// nodes sphere, then this tile is culled away.
// Computing this radius happens by two rectangular triangles:
// Let r be the view point. rmin is the minimum radius we find
// a ground surface we need to look above. rmax is the
// maximum object radius we expect any object.
//
// d1 d2
// x----x----x
// r\ rmin /rmax
// \ | /
// \ | /
// \|/
//
// The distance from the eyepoint to the point
// where the line of sight is perpandicular to
// the radius vector with minimal height is
// d1 = sqrt(r^2 - rmin^2).
// The distance from the point where the line of sight
// is perpandicular to the radius vector with minimal height
// to the highest possible object on earth with radius rmax is
// d2 = sqrt(rmax^2 - rmin^2).
// So the maximum distance we can see something on the earth
// from a viewpoint r is
// d = d1 + d2
// This is the equatorial earth radius minus 450m,
// little lower than Dead Sea.
float rmin = 6378137 - 450;
float rmin2 = rmin*rmin;
// This is the equatorial earth radius plus 9000m,
// little higher than Mount Everest.
float rmax = 6378137 + 9000;
float rmax2 = rmax*rmax;
// Check if we are looking from below any ground
osg::Vec3 viewPoint = nv->getViewPoint();
// blow the viewpoint up to a spherical earth with equatorial radius:
osg::Vec3 sphericViewPoint = viewPoint;
sphericViewPoint[2] *= 1.0033641;
float r2 = sphericViewPoint.length2();
if (r2 <= rmin2)
return;
// Due to this line of sight computation, the visible tiles
// are limited to be within a sphere with radius d1 + d2.
float d1 = sqrtf(r2 - rmin2);
float d2 = sqrtf(rmax2 - rmin2);
// Note that we again base the sphere around elliptic view point,
// but use the radius from the spherical computation.
if (!nodeBound.intersects(osg::BoundingSphere(viewPoint, d1 + d2)))
return;
traverse(node, nv);
}
};
struct ReaderWriterPGT::LocalOptions {
LocalOptions(const osgDB::Options* options) :
_options(options)
{
osg::ref_ptr<SGReaderWriterOptions> sgOptions;
sgOptions = SGReaderWriterOptions::copyOrCreate(options);
std::string pageLevelsString;
std::string meshResolutionString;
std::string meshTexturing;
if (_options) {
pageLevelsString = _options->getPluginStringData("SimGear::SPT_PAGE_LEVELS");
meshResolutionString = _options->getPluginStringData("SimGear::SPT_MESH_RESOLUTION");
meshTexturing = _options->getPluginStringData("SimGear::SPT_LOD_TEXTURING");
}
// Get the default if nothing given from outside
// ignore option - pagelevels come from mesh
_pageLevels.push_back(1);
_pageLevels.push_back(2);
// dem level 2 - 2, 4, 12 degrees
_pageLevels.push_back(3);
_pageLevels.push_back(4);
_pageLevels.push_back(5);
// dem level 1 - 1/8, 1/4, 1/2 and 1 degree
_pageLevels.push_back(6);
_pageLevels.push_back(7);
_pageLevels.push_back(8);
_pageLevels.push_back(9);
_dem = sgOptions->getDem();
// Get the default if nothing given from outside
if (meshResolutionString.empty()) {
_meshResolution = 1;
} else {
// If configured from outside
std::stringstream ss(meshResolutionString);
while (ss.good()) {
ss >> _meshResolution;
}
}
if ( meshTexturing.empty() ) {
_textureMethod = SGMesh::TEXTURE_BLUEMARBLE;
} else if ( meshTexturing == "bluemarble" ) {
_textureMethod = SGMesh::TEXTURE_BLUEMARBLE;
} else if ( meshTexturing == "raster" ) {
_textureMethod = SGMesh::TEXTURE_RASTER;
} else if ( meshTexturing == "debug" ) {
_textureMethod = SGMesh::TEXTURE_DEBUG;
}
}
bool isPageLevel(unsigned level) const
{
return std::find(_pageLevels.begin(), _pageLevels.end(), level) != _pageLevels.end();
}
std::string getLodPathForBucketBox(const BucketBox& bucketBox) const
{
std::stringstream ss;
ss << "LOD/";
for (std::vector<unsigned>::const_iterator i = _pageLevels.begin(); i != _pageLevels.end(); ++i) {
if (bucketBox.getStartLevel() <= *i)
break;
ss << bucketBox.getParentBox(*i) << "/";
}
ss << bucketBox;
return ss.str();
}
float getRangeMultiplier() const
{
float rangeMultiplier = 2;
if (!_options)
return rangeMultiplier;
std::stringstream ss(_options->getPluginStringData("SimGear::SPT_RANGE_MULTIPLIER"));
ss >> rangeMultiplier;
return rangeMultiplier;
}
const osgDB::Options* _options;
std::vector<unsigned> _pageLevels;
SGDemPtr _dem;
unsigned _meshResolution;
SGMesh::TextureMethod _textureMethod;
};
ReaderWriterPGT::ReaderWriterPGT()
{
supportsExtension("pgt", "SimGear realtime paged terrain meta database.");
}
ReaderWriterPGT::~ReaderWriterPGT()
{
}
const char*
ReaderWriterPGT::className() const
{
return "simgear::ReaderWriterPGT";
}
osgDB::ReaderWriter::ReadResult
ReaderWriterPGT::readObject(const std::string& fileName, const osgDB::Options* options) const
{
// We get called with different extensions. To make sure search continues,
// we need to return FILE_NOT_HANDLED in this case.
if (osgDB::getLowerCaseFileExtension(fileName) != "pgt")
return ReadResult(ReadResult::FILE_NOT_HANDLED);
if (fileName != "state.pgt")
return ReadResult(ReadResult::FILE_NOT_FOUND);
osg::StateSet* stateSet = new osg::StateSet;
stateSet->setAttributeAndModes(new osg::CullFace);
std::string imageFileName = options->getPluginStringData("SimGear::FG_WORLD_TEXTURE");
if (imageFileName.empty()) {
imageFileName = options->getPluginStringData("SimGear::FG_ROOT");
imageFileName = osgDB::concatPaths(imageFileName, "Textures");
imageFileName = osgDB::concatPaths(imageFileName, "Globe");
imageFileName = osgDB::concatPaths(imageFileName, "world.topo.bathy.200407.3x4096x2048.png");
}
if (osg::Image* image = osgDB::readImageFile(imageFileName, options)) {
osg::Texture2D* texture = new osg::Texture2D;
texture->setImage(image);
texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP);
stateSet->setTextureAttributeAndModes(0, texture);
}
return stateSet;
}
osgDB::ReaderWriter::ReadResult
ReaderWriterPGT::readNode(const std::string& fileName, const osgDB::Options* options) const
{
LocalOptions localOptions(options);
SG_LOG(SG_IO, SG_WARN, "ReaderWriterPGT::readNode - reading:" << fileName );
// The file name without path and without the pgt extension
std::string strippedFileName = osgDB::getStrippedName(fileName);
if (strippedFileName == "earth")
return ReadResult(createTree(BucketBox(-180, -90, 360, 180), localOptions, true));
std::stringstream ss(strippedFileName);
BucketBox bucketBox;
ss >> bucketBox;
if (ss.fail()) {
SG_LOG(SG_IO, SG_WARN, "error reading:" << strippedFileName );
return ReadResult::FILE_NOT_FOUND;
}
BucketBox bucketBoxList[2];
unsigned bucketBoxListSize = bucketBox.periodicSplit(bucketBoxList);
if (bucketBoxListSize == 0)
return ReadResult::FILE_NOT_FOUND;
if (bucketBoxListSize == 1)
return ReadResult(createTree(bucketBoxList[0], localOptions, true));
assert(bucketBoxListSize == 2);
osg::ref_ptr<osg::Group> group = new osg::Group;
group->addChild(createTree(bucketBoxList[0], localOptions, true));
group->addChild(createTree(bucketBoxList[1], localOptions, true));
return ReadResult(group);
}
osg::ref_ptr<osg::Node>
ReaderWriterPGT::createTree(const BucketBox& bucketBox, const LocalOptions& options, bool topLevel) const
{
if (bucketBox.getIsBucketSize()) {
std::string fileName;
fileName = bucketBox.getBucket().gen_index_str() + std::string(".stg");
return createTileMesh(bucketBox, options._options);
} else if (!topLevel && options.isPageLevel(bucketBox.getStartLevel())) {
return createPagedLOD(bucketBox, options);
} else {
BucketBox bucketBoxList[100];
unsigned numTiles = bucketBox.getSubDivision(bucketBoxList, 100);
if (numTiles == 0)
return 0;
if (numTiles == 1)
return createTree(bucketBoxList[0], options, false);
osg::ref_ptr<osg::Group> group = new osg::Group;
for (unsigned i = 0; i < numTiles; ++i) {
osg::ref_ptr<osg::Node> node = createTree(bucketBoxList[i], options, false);
if (!node.valid())
continue;
group->addChild(node.get());
}
if (!group->getNumChildren())
return 0;
return group;
}
}
osg::ref_ptr<osg::Node>
ReaderWriterPGT::createPagedLOD(const BucketBox& bucketBox, const LocalOptions& options) const
{
osg::PagedLOD* pagedLOD = new osg::PagedLOD;
pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER);
SGSpheref sphere = bucketBox.getBoundingSphere();
pagedLOD->setCenter(toOsg(sphere.getCenter()));
pagedLOD->setRadius(sphere.getRadius());
pagedLOD->setCullCallback(new CullCallback);
osg::ref_ptr<osgDB::Options> localOptions;
localOptions = static_cast<osgDB::Options*>(options._options->clone(osg::CopyOp()));
// FIXME:
// The particle systems have nodes with culling disabled.
// PagedLOD nodes with childnodes like this will never expire.
// So, for now switch them off.
localOptions->setPluginStringData("SimGear::PARTICLESYSTEM", "OFF");
pagedLOD->setDatabaseOptions(localOptions.get());
// The break point for the low level of detail to the high level of detail
float rangeMultiplier = options.getRangeMultiplier();
float range = rangeMultiplier*sphere.getRadius();
// Look for a low level of detail tile
std::string lodPath = options.getLodPathForBucketBox(bucketBox);
const char* extensions[] = { ".btg.gz", ".flt" };
for (unsigned i = 0; i < sizeof(extensions)/sizeof(extensions[0]); ++i) {
std::string fileName = osgDB::findDataFile(lodPath + extensions[i], options._options);
if (fileName.empty())
continue;
osg::ref_ptr<osg::Node> node = osgDB::readRefNodeFile(fileName, options._options);
if (!node.valid())
continue;
pagedLOD->addChild(node.get(), range, std::numeric_limits<float>::max());
break;
}
// Add the static sea level textured shell if there is nothing found
if (pagedLOD->getNumChildren() == 0) {
osg::ref_ptr<osg::Node> node = createTileMesh(bucketBox, options._options);
if (node.valid())
pagedLOD->addChild(node.get(), range, std::numeric_limits<float>::max());
}
// Add the paged file name that creates the subtrees on demand
std::stringstream ss;
ss << bucketBox << ".pgt";
pagedLOD->setFileName(pagedLOD->getNumChildren(), ss.str());
pagedLOD->setRange(pagedLOD->getNumChildren(), 0.0, range);
return pagedLOD;
}
osg::ref_ptr<osg::Node>
ReaderWriterPGT::createTileMesh(const BucketBox& bucketBox, const LocalOptions& options) const
{
if (options._options->getPluginStringData("SimGear::FG_EARTH") != "ON")
return 0;
SGSpheref sphere = bucketBox.getBoundingSphere();
osg::Matrixd transform;
transform.makeTranslate(toOsg(-sphere.getCenter()));
// TODO : return geode, not geometry - so we texture in SGMesh
// osg::Geometry* geometry = bucketBox.getTileTriangleMesh( options._dem, options._meshResolution, options._textureMethod );
osg::Geode* geode = bucketBox.getTileTriangleMesh( options._dem, options._meshResolution, options._textureMethod, options._options );
if ( geode ) {
transform.makeTranslate(toOsg(sphere.getCenter()));
osg::MatrixTransform* matrixTransform = new osg::MatrixTransform(transform);
matrixTransform->setDataVariance(osg::Object::STATIC);
matrixTransform->addChild(geode);
return matrixTransform;
} else {
return 0;
}
}
osg::ref_ptr<osg::StateSet>
ReaderWriterPGT::getLowLODStateSet(const LocalOptions& options) const
{
osg::ref_ptr<osgDB::Options> localOptions;
localOptions = static_cast<osgDB::Options*>(options._options->clone(osg::CopyOp()));
localOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL);
osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile("state.pgt", localOptions.get());
if (!dynamic_cast<osg::StateSet*>(object.get()))
return 0;
return static_cast<osg::StateSet*>(object.get());
}
} // namespace simgear
// simgear::ModelRegistryCallbackProxy<simgear::LoadOnlyCallback> g_pgtCallbackProxy("pgt");

View File

@@ -0,0 +1,53 @@
// ReaderWriterSPT.cxx -- Provide a paged database for flightgear scenery.
//
// Copyright (C) 2010 - 2013 Mathias Froehlich
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef _READERWRITERPGT_HXX
#define _READERWRITERPGT_HXX
#include <osgDB/ReaderWriter>
namespace simgear {
class BucketBox;
class ReaderWriterPGT : public osgDB::ReaderWriter {
public:
ReaderWriterPGT();
virtual ~ReaderWriterPGT();
virtual const char* className() const;
virtual osgDB::ReaderWriter::ReadResult readObject(const std::string& fileName, const osgDB::Options* options) const;
virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& fileName, const osgDB::Options* options) const;
protected:
struct LocalOptions;
osg::ref_ptr<osg::Node> createTree(const BucketBox& bucketBox, const LocalOptions& options, bool topLevel) const;
osg::ref_ptr<osg::Node> createPagedLOD(const BucketBox& bucketBox, const LocalOptions& options) const;
osg::ref_ptr<osg::Node> createTileMesh(const BucketBox& bucketBox, const LocalOptions& options) const;
osg::ref_ptr<osg::StateSet> getLowLODStateSet(const LocalOptions& options) const;
private:
struct CullCallback;
};
} // namespace simgear
#endif

332
simgear/scene/dem/SGDem.cxx Normal file
View File

@@ -0,0 +1,332 @@
// SGDem.cxx -- read, write DEM heiarchy
//
// Written by Peter Sadrozinski, started August 2016.
//
// Copyright (C) 2001 - 2003 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <fstream>
#include <limits>
#include <boost/foreach.hpp>
#include <cpl_conv.h> // for CPLMalloc()
#include "ogr_spatialref.h"
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/stdint.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/scene/dem/SGDem.hxx>
#include <simgear/scene/dem/SGDemSession.hxx>
using namespace simgear;
#define DEM_DEBUG (0)
// where to add these...
int SGDem::floorWithEpsilon(double x)
{
return static_cast<int>(floor(x + SG_EPSILON));
}
unsigned SGDem::normalizeLongitude(unsigned offset)
{
return offset - (360*8)*(offset/(360*8));
}
unsigned SGDem::longitudeDegToOffset(double lon)
{
unsigned offset = (unsigned)( floorWithEpsilon( 8.0 * (lon + 180.0) ) );
return normalizeLongitude(offset);
}
double SGDem::offsetToLongitudeDeg(unsigned offset)
{
return offset*0.125 - 180;
}
unsigned SGDem::latitudeDegToOffset(double lat)
{
if (lat < -90)
return 0;
unsigned offset = (unsigned)( floorWithEpsilon( 8.0 * (lat + 90.0) ) );
if (8*180 < offset)
return 8*180;
return offset;
}
double SGDem::offsetToLatitudeDeg(unsigned offset)
{
return offset*0.125 - 90;
}
int SGDem::addRoot( const SGPath& root )
{
GDALAllRegister();
SGDemRoot demRoot( root );
// collect subdir for each dem level - format is level_X
if ( root.isDir() ) {
Dir d(root);
if (d.exists() ) {
PathList levelPaths = d.children(Dir::TYPE_DIR);
SG_LOG( SG_TERRAIN, SG_INFO, levelPaths.size() << " Directories in " << d.path() );
for(const SGPath& p : levelPaths) {
std::string prefix;
int level;
std::istringstream iss( p.file() );
getline(iss, prefix, '_');
if ( ((iss.rdstate() & std::ifstream::failbit ) == 0 ) &&
(prefix == "level" ) ) {
iss >> level;
// read the deminfo.txt file
SGPath infoFile = p / "deminfo.txt";
SGPath extentsFile = p / "demextents.bin";
if ( infoFile.exists() ) {
std::fstream demInfo(infoFile.c_str(), std::ios_base::in);
int w, h, x, y, o;
std::string ext;
unsigned long extents[45][180];
demInfo >> w >> h >> x >> y >> o >> ext;
// search level for extents
memset( (unsigned char*)extents, 0, sizeof(extents) );
if ( extentsFile.exists() ) {
std::fstream demExtents(extentsFile.c_str(), std::ios_base::in | std::ios_base::binary );
for ( unsigned int i=0; i<45; i++ ) {
for ( unsigned int j=0; j<180; j++ ) {
demExtents >> extents[i][j];
}
}
}
SG_LOG( SG_TERRAIN, SG_INFO, " found DEM level " << level << " in directory " << p << " width : " << w << " height : " << h << " xres : " << x << " yres : " << y << " overlap : " << o << " extension: " << ext );
demRoot.addLevel( SGDemLevel( level, p, w, h, x, y, o, &extents[0][0], ext, true ) );
} else {
SG_LOG( SG_TERRAIN, SG_INFO, " found DEM level " << level << " in directory " << p << " without info file " );
}
} else {
SG_LOG( SG_TERRAIN, SG_INFO, " invalid dem level dir " << p << " got prefix " << prefix );
}
}
}
}
if ( demRoot.numLevels() ) {
demRoots.push_back( demRoot );
}
return demRoot.numLevels();
}
int SGDem::createRoot( const SGPath& root )
{
// collect subdir for each dem level - format is level_X
// create the directory
SGPath newDir = root / "dummy";
newDir.create_dir();
demRoots.push_back( SGDemRoot(root) );
return 0;
}
unsigned SGDem::roundDown( unsigned offset, unsigned roundTo )
{
if ( roundTo == 0 ) {
return offset;
} else {
return (offset / roundTo) * roundTo;
}
}
unsigned SGDem::roundUp( unsigned offset, unsigned roundTo )
{
if ( roundTo == 0 ) {
return offset;
} else {
return ((offset+roundTo-1) / roundTo) * roundTo;
}
}
SGDemSession SGDem::openSession( unsigned wo, unsigned so, unsigned eo, unsigned no, int level, bool cache )
{
SGDemSession s;
// Create the session
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::OpenSession - level " << level );
if ( level >= 0 ) {
SGDemRoot* demRoot = findDem( wo, so, eo, no, level );
if ( demRoot ) {
// traverse the demRoots, to see if any have this level
unsigned w = demRoot->getWidth( level );
unsigned h = demRoot->getHeight( level );
unsigned x = demRoot->getResX( level );
unsigned y = demRoot->getResY( level );
unsigned o = demRoot->getOverlap( level );
s = SGDemSession(wo, so, eo, no, level, w, h, demRoot);
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::OpenSession - from offsets " << wo << ", " << so << " to offsets " << eo << ", " << no );
// calc min offsets based on tile widths and height for this level
unsigned min_lon = roundDown( wo, w );
unsigned min_lat = roundDown( so, h );
unsigned max_lon = roundUp( eo, w );
unsigned max_lat = roundUp( no, h );
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::OpenSession - from pre level rounding offsets " << min_lon << ", " << min_lat << " to offsets " << max_lon << ", " << max_lat );
for (unsigned lon = min_lon; lon < max_lon; lon += w) {
for (unsigned lat = min_lat; lat < max_lat; lat += h) {
s.addTile( demRoot->getOrCreateTile( lon, lat, w, h, x, y, o, level, cache ) );
}
}
} else {
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::OpenSession - could not find DEM for " << wo << ", " << so << " - " << eo << ", " << no << " level " << level );
}
} else {
SG_LOG( SG_TERRAIN, SG_ALERT, "SGDem::OpenSession - invalid level " << level );
}
return s;
}
SGDemSession SGDem::openSession( const SGGeod& min, const SGGeod& max, const SGPath& input )
{
#define FP_ROUNDOFF_OUTSIDE (0.1)
// create a new demRoot for creation
SGDemRoot demRoot(input);
// open all tiles between min and max
int min_lon = (int)(floor(min.getLongitudeDeg()-FP_ROUNDOFF_OUTSIDE));
int min_lat = (int)(floor(min.getLatitudeDeg()-FP_ROUNDOFF_OUTSIDE));
int max_lon = (int)(ceil(max.getLongitudeDeg()+FP_ROUNDOFF_OUTSIDE));
int max_lat = (int)(ceil(max.getLatitudeDeg()+FP_ROUNDOFF_OUTSIDE));
// Create the session
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::OpenSession - create sesion obj - req from " <<
min.getLongitudeDeg() << ", " << min.getLatitudeDeg() << " to " <<
max.getLongitudeDeg() << ", " << max.getLatitudeDeg() << " - getting " <<
min_lon << ", " << min_lat << " to " << max_lon << ", " << max_lat );
SGDemSession s(min_lon, min_lat, max_lon, max_lat, &demRoot);
// todo - read a tile from the input dir
int w = 1;
int h = 1;
int x = 1201;
int y = 1201;
int o = 32;
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::OpenSession - Traverse tiles");
for (int lon = min_lon; lon < max_lon; lon += w) {
for (int lat = min_lat; lat < max_lat; lat += h) {
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::OpenSession - Create tile " <<
lon << ", " << lat << " from dir " << input );
unsigned wo = (lon+180)*8;
unsigned so = (lat+ 90)*8;
SGDemTile* pTile = new SGDemTile( input, wo, so, w, h, x, y, o, false );
s.addTile( pTile );
}
}
return s;
}
SGDemRoot* SGDem::findDem( unsigned wo, unsigned so, unsigned eo, unsigned no, int lvl )
{
SGDemRoot* dr = NULL;
for ( unsigned int i=0; i<demRoots.size(); i++ )
{
if ( demRoots[i].isValid( lvl, wo, so, eo, no ) ) {
dr = &demRoots[i];
break;
} else {
SG_LOG( SG_TERRAIN, SG_ALERT, "SGDem::findDem - dem " << i << " is not vald." );
}
}
return dr;
}
#if 0 // todo - move to session
unsigned short SGDem::getAlt( const SGDemSession& s, const SGGeod& loc ) const
{
int alt = 0;
// which level index are we querying?
int lvlIndex = s.getLvlIndex();
if ( lvlIndex >= 0 ) {
// be careful with coordinates that lie on session boundaries -
// on min, ok.
// on max - make sure we select the tile in the session...
int lvlWidth = levels[lvlIndex].getWidth();
int lvlHeight = levels[lvlIndex].getHeight();
int intLon, intLat;
// we need to find the correct tile.
// shift by 180, 90 to use 0 based ints
intLon = (int)(round(loc.getLongitudeDeg())) + 180;
intLon = (intLon / lvlWidth) * lvlWidth;
intLon = intLon - 180;
if ( intLon == s.getMaxLon() ) {
intLon -= levels[lvlIndex].getWidth();
}
intLat = (int)(round(loc.getLatitudeDeg())) + 90;
intLat = (intLat / lvlHeight) * lvlHeight;
intLat = intLat - 90;
if ( intLat == s.getMaxLat() ) {
intLat -= levels[lvlIndex].getHeight();
}
unsigned long key = (intLon + 180) << 16 | (intLat + 90);
SGDemCache::const_iterator it = caches[lvlIndex].find(key);
if ( it != caches[lvlIndex].end() ) {
SGDemTileRef tile = it->second;
alt = tile->getAlt( loc );
} else {
// fprintf( stderr, " Could NOT find tile %d,%d in cache %d key is %08lx\n", intLon, intLat, lvlIndex, key );
alt = 0;
}
}
return alt;
}
#endif

View File

@@ -0,0 +1,82 @@
// SGDem.hxx -- read, write DEM heiarchy
//
// Written by Peter Sadrozinski, started August 2016.
//
// Copyright (C) 2001 - 2003 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef __SG_DEM_HXX__
#define __SG_DEM_HXX__
#include <map>
#include <simgear/compiler.h>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/dem/SGDemRoot.hxx>
class SGDem : public SGReferenced
{
public:
SGDem() {};
~SGDem() {};
int addRoot( const SGPath& root );
int createRoot( const SGPath& root );
SGDemRoot* getRoot( unsigned int i ) {
if ( i < demRoots.size() ) {
return &demRoots[i];
} else {
return NULL;
}
}
unsigned int getNumRoots( void ) {
return demRoots.size();
}
// todo : move to session
// unsigned short getAlt( const SGDemSession& s, const SGGeod& loc ) const;
// find a Dem to satisfy a session - must have the level, and extents
SGDemRoot* findDem( unsigned wo, unsigned so, unsigned eo, unsigned no, int lvl );
// open a session from a dem level - tiles will be read and reference counted until closed
//SGDemSession openSession( const SGGeod& min, const SGGeod& max, int level, bool cache );
SGDemSession openSession( unsigned wo, unsigned so, unsigned eo, unsigned no, int level, bool cache );
// open a session from an bare directory
SGDemSession openSession( const SGGeod& min, const SGGeod& max, const SGPath& input );
// static helpers
static int floorWithEpsilon( double x );
static unsigned normalizeLongitude( unsigned offset );
static unsigned longitudeDegToOffset( double lon );
static double offsetToLongitudeDeg( unsigned offset );
static unsigned latitudeDegToOffset( double lat );
static double offsetToLatitudeDeg( unsigned offset );
static unsigned roundDown( unsigned offset, unsigned roundTo );
static unsigned roundUp( unsigned offset, unsigned roundTo );
private:
std::vector<SGDemRoot> demRoots;
};
typedef SGSharedPtr<SGDem> SGDemPtr;
#endif /* __SG_DEM_HXX__ */

View File

@@ -0,0 +1,72 @@
#include <iomanip>
#include <fstream>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/scene/dem/SGDemLevel.hxx>
bool SGDemLevel::isValid( unsigned int wo, unsigned int so, unsigned int eo, unsigned int no ) const
{
bool valid = true;
return valid;
}
void SGDemLevel::addExtent( unsigned int wo, unsigned int so, unsigned int eo, unsigned int no )
{
// add bitmap to level extent
unsigned int minLon = (wo+0)/8;
unsigned int maxLon = (eo+0)/8;
unsigned int minLat = (so+0)/8;
unsigned int maxLat = (no+0)/8;
fprintf(stderr, "addExtent %u, %u - %u, %u - minLon %u, minLat %u, maxLon %u, maxLat %u\n", wo, so, eo, no, minLon, minLat, maxLon, maxLat );
for ( unsigned int lat=minLat; lat<=maxLat; lat++ ) {
// convert lon range to mask range
unsigned char minMask = minLon/8;
unsigned char maxMask = maxLon/8;
fprintf(stderr, "addExtent minMask %u, maxMask %d\n", minMask, maxMask );
for ( unsigned int msk=minMask; msk<=maxMask; msk++ ) {
if ( msk == minMask ) {
// find beginning of mask
unsigned char bitPos = minLon % 8;
unsigned char bitMask = 0;
for ( unsigned char i=0; i<=bitPos; i++ ) {
bitMask = (bitMask >> 1) | 0x80;
}
fprintf(stderr, "addExtent minMask - lat %u, lonmsk %u, bits %u\n", lat, msk, bitMask );
extent[lat][msk] |= bitMask;
} else if ( msk == maxMask ) {
// find end of mask
unsigned char bitPos = maxLon % 8;
unsigned char bitMask = 0;
for ( unsigned int i=0; i<=bitPos; i++ ) {
bitMask = (bitMask >> 1) | 0x80;
}
fprintf(stderr, "addExtent maxMask - lat %u, lonmsk %u, bits %x\n", lat, msk, bitMask );
extent[lat][msk] |= bitMask;
} else {
fprintf(stderr, "addExtent middlemask - lat %u, lonmsk %u, bits %x\n", lat, msk, 0xFF );
extent[lat][msk] = 0xFF;
}
}
}
}
void SGDemLevel::close( void )
{
// save extent to file
fprintf( stderr, "closing level\n" );
SGPath extentFile = path / "demextent.txt";
std::fstream demExtent(extentFile.c_str(), std::ios_base::out | std::ios_base::binary );
for ( unsigned char lat = 0; lat < 180; lat++ ) {
for ( unsigned char lon = 0; lon < 360/8; lon++ ) {
demExtent << extent[lat][lon];
}
}
fprintf( stderr, "closing level complete\n" );
}

View File

@@ -0,0 +1,90 @@
#ifndef __SG_DEM_LEVEL_HXX__
#define __SG_DEM_LEVEL_HXX__
#include <cstring>
#include <cstdio>
#include <simgear/misc/sg_dir.hxx>
class SGDemLevel
{
public:
SGDemLevel( int l, const SGPath& p, int w, int h, int x, int y, int o, const unsigned long* e, std::string ext, bool r )
{
level = l;
path = p;
width = w;
height = h;
xres = x;
yres = y;
overlap = o;
extension = ext;
ready = r;
std::fprintf( stderr, "copying extent fle size %lu\n", sizeof(extent) );
std::memcpy( (void *)&extent[0][0], (const void *)e, sizeof(extent) );
};
SGDemLevel( int l, const SGPath& p, int w, int h, int x, int y, int o, std::string ext, bool r )
{
level = l;
path = p;
width = w;
height = h;
xres = x;
yres = y;
overlap = o;
extension = ext;
ready = r;
std::fprintf( stderr, "setting extent fle size %lu\n", sizeof(extent) );
memset( (unsigned char*)extent, 0, sizeof(extent) );
};
int getLevel( void ) const {
return level;
}
const SGPath& getLevelDir( void ) const {
return path;
}
bool isReady( void ) const {
return ready;
}
bool isValid( unsigned int wo, unsigned int so, unsigned int eo, unsigned int no ) const;
void addExtent( unsigned int wo, unsigned int so, unsigned int eo, unsigned int no );
void close( void );
int getWidth( void ) const {
return width;
}
int getHeight( void ) const {
return height;
}
int getResX( void ) const {
return xres;
}
int getResY( void ) const {
return yres;
}
int getOverlap( void ) const {
return overlap;
}
private:
SGPath path;
std::string extension;
bool ready;
int level;
int width;
int height;
int xres;
int yres;
int overlap;
// bitmap of tiles (1x1 degree)
unsigned char extent[180][360/8];
};
#endif /* __SG_DEM_LEVEL_HXX__ */

View File

@@ -0,0 +1,118 @@
#include <iomanip>
#include <fstream>
#include <sstream>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/scene/dem/SGDemRoot.hxx>
#include <simgear/debug/logstream.hxx>
bool SGDemRoot::isValid( int lvl, unsigned wo, unsigned so, unsigned eo, unsigned no ) const
{
bool valid = false;
// check if we have requested level
if ( (unsigned int)lvl < levels.size() ) {
// check if we fit in this levels extents
valid = levels[lvl].isValid( wo, so, eo, no );
} else {
SG_LOG( SG_TERRAIN, SG_INFO, "SGDemRoot::isValid - lvl " << lvl << " is greater than #levels in root " << levels.size() );
}
return valid;
}
int SGDemRoot::createLevel( int w, int h, int x, int y, int overlap, const std::string& ext )
{
int lvlidx = -1;
std::stringstream ss;
ss << "level_" << std::setw(2) << std::setfill('0') << levels.size()+1;
// see if dir already exists
SGPath lvlPath = demRoot / ss.str();
if ( lvlPath.isDir() ) {
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::createLevel: found existing level directory at " << lvlPath.c_str() );
} else {
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::createLevel: creating level directory at " << lvlPath.c_str() );
// create the dir - needs the filename to do this !?!?
SGPath infoFile = lvlPath / "deminfo.txt";
int res = infoFile.create_dir();
if ( res == 0 ) {
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::createLevel: successfully created directory at " << infoFile.realpath().c_str() << " error " << res );
lvlidx = levels.size();
std::fstream demInfo(infoFile.c_str(), std::ios_base::out);
demInfo << w << " " << h << " " << x << " " << y << " " << overlap << " " << ext << std::endl;
levels.push_back( SGDemLevel( lvlidx, lvlPath, w, h, x, y, overlap, ext, false ) );
caches.push_back( SGDemCache() );
} else {
SG_LOG( SG_TERRAIN, SG_INFO, "SGDem::createLevel: can't create level directory at " << lvlPath.c_str() << " error " << res );
}
}
return lvlidx;
}
void SGDemRoot::closeLevel( int lvl )
{
if ( (unsigned int)lvl < levels.size() ) {
levels[lvl].close();
}
}
SGDemTileRef SGDemRoot::createTile( int lvl, int lon, int lat, int overlap, SGDemSession& s )
{
SGDemTileRef rTile = NULL;
bool bWritten = false;
if ( lvl >= 0 ) {
int w = levels[lvl].getWidth();
int h = levels[lvl].getHeight();
int x = levels[lvl].getResX();
int y = levels[lvl].getResY();
unsigned wo = (unsigned)(lon+180)*8;
unsigned so = (unsigned)(lat+ 90)*8;
rTile = new SGDemTile( levels[lvl].getLevelDir(),
wo, so,
w, h, x, y, overlap, s, bWritten );
if ( bWritten ) {
fprintf( stderr, "CreateTile - add extent (lat%d+h%d+90)*8-1 is %u\n", lat, h, (lat+h+90)*8-1 );
levels[lvl].addExtent( (lon+180)*8, (lat+90)*8, (lon+w+180)*8-1, (lat+h+90)*8-1 );
}
}
return rTile;
}
void SGDemRoot::flushCaches( int lvl )
{
// traverse the map - delete any tiles with ref count == 1 ( us )
//fprintf( stderr, "flush caches in lvl %d - num tiles is %lu\n", lvl, caches[lvl].size() );
SGDemCache::iterator it = caches[lvl].begin();
while ( it != caches[lvl].end() ) {
if ( it->second.getNumRefs() == 1 ) {
caches[lvl].erase(it++);
} else {
//fprintf( stderr, "can't flush tile - numrefs is %u\n", it->second.getNumRefs() );
++it;
}
}
}
SGDemTileRef SGDemRoot::getTile( int lvlIndex, unsigned long key )
{
SGDemTileRef tile = NULL;
SGDemCache::const_iterator it = caches[lvlIndex].find(key);
if ( it != caches[lvlIndex].end() ) {
tile = it->second;
}
return tile;
}

View File

@@ -0,0 +1,102 @@
#ifndef __SG_DEM_ROOT_HXX__
#define __SG_DEM_ROOT_HXX__
#include <simgear/scene/dem/SGDemLevel.hxx>
#include <simgear/scene/dem/SGDemTile.hxx>
class SGDemRoot
{
public:
SGDemRoot( SGPath p ) {
demRoot = p;
}
void addLevel( const SGDemLevel& level ) {
levels.push_back( level );
caches.push_back( SGDemCache() );
}
// create a new level
int createLevel( int w, int h, int x, int y, int overlap, const std::string& ext );
SGDemTileRef createTile( int lvl, int lon, int lat, int overlap, SGDemSession& s );
void closeLevel( int lvl );
unsigned int numLevels( void ) const {
return levels.size();
}
SGDemTileRef getTile( int lvlIndex, unsigned long key );
void flushCaches( int lvl );
bool isValid( int lvl, unsigned wo, unsigned so, unsigned eo, unsigned no ) const;
unsigned int getWidth( unsigned int level ) {
if ( level < levels.size() ) {
return levels[level].getWidth() * 8;
} else {
return 0;
}
}
unsigned int getHeight( unsigned int level ) {
if ( level < levels.size() ) {
return levels[level].getHeight() * 8;
} else {
return 0;
}
}
unsigned int getResX( unsigned int level ) {
if ( level < levels.size() ) {
return levels[level].getResX();
} else {
return 0;
}
}
unsigned int getResY( unsigned int level ) {
if ( level < levels.size() ) {
return levels[level].getResY();
} else {
return 0;
}
}
unsigned int getOverlap( unsigned int level ) {
if ( level < levels.size() ) {
return levels[level].getOverlap();
} else {
return 0;
}
}
SGDemTile* getOrCreateTile( unsigned wo, unsigned so,
unsigned w, unsigned h, unsigned x, unsigned y,
unsigned o, int level, bool cache )
{
unsigned long key = wo << 16 | so;
// is this tile already loaded?
SGDemCache::const_iterator it = caches[level].find(key);
if ( it != caches[level].end() ) {
// yes - add the reference to this session
//printf( "********************** adding ref to tile at %d,%d with key %lx\n", lon, lat, key );
return it->second;
} else {
// no load the tile, now
SGDemTile* pTile = new SGDemTile( levels[level].getLevelDir(),
wo, so, w, h,
x, y, o, cache );
caches[level][key] = pTile;
return pTile;
}
}
private:
SGPath demRoot;
std::vector<SGDemLevel> levels;
std::vector<SGDemCache> caches;
};
#endif /* __SG_DEM_ROOT_HXX__ */

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